source: trunk/macosx/InfoPeersViewController.m @ 10369

Last change on this file since 10369 was 10369, checked in by livings124, 12 years ago

add an optional clearView method to the InfoViewController? protocol

File size: 22.9 KB
Line 
1/******************************************************************************
2 * $Id$
3 *
4 * Copyright (c) 2010 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "InfoPeersViewController.h"
26#import "NSApplicationAdditions.h"
27#import "NSStringAdditions.h"
28#import "PeerProgressIndicatorCell.h"
29#import "Torrent.h"
30
31@interface InfoPeersViewController (Private)
32
33- (void) setupInfo;
34
35- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate;
36- (NSArray *) peerSortDescriptors;
37
38@end
39
40@implementation InfoPeersViewController
41
42- (id) init
43{
44    self = [super initWithNibName: @"InfoPeersView" bundle: nil];
45    return self;
46}
47
48- (void) awakeFromNib
49{
50    //set table header text
51    [[[fPeerTable tableColumnWithIdentifier: @"IP"] headerCell] setStringValue: NSLocalizedString(@"IP Address",
52                                                                        "inspector -> peer table -> header")];
53    [[[fPeerTable tableColumnWithIdentifier: @"Client"] headerCell] setStringValue: NSLocalizedString(@"Client",
54                                                                        "inspector -> peer table -> header")];
55    [[[fPeerTable tableColumnWithIdentifier: @"DL From"] headerCell] setStringValue: NSLocalizedString(@"DL",
56                                                                        "inspector -> peer table -> header")];
57    [[[fPeerTable tableColumnWithIdentifier: @"UL To"] headerCell] setStringValue: NSLocalizedString(@"UL",
58                                                                        "inspector -> peer table -> header")];
59   
60    [[[fWebSeedTable tableColumnWithIdentifier: @"Address"] headerCell] setStringValue: NSLocalizedString(@"Web Seeds",
61                                                                        "inspector -> web seed table -> header")];
62    [[[fWebSeedTable tableColumnWithIdentifier: @"DL From"] headerCell] setStringValue: NSLocalizedString(@"DL",
63                                                                        "inspector -> web seed table -> header")];
64   
65    //set table header tool tips
66    [[fPeerTable tableColumnWithIdentifier: @"Encryption"] setHeaderToolTip: NSLocalizedString(@"Encrypted Connection",
67                                                                        "inspector -> peer table -> header tool tip")];
68    [[fPeerTable tableColumnWithIdentifier: @"Progress"] setHeaderToolTip: NSLocalizedString(@"Available",
69                                                                        "inspector -> peer table -> header tool tip")];
70    [[fPeerTable tableColumnWithIdentifier: @"DL From"] setHeaderToolTip: NSLocalizedString(@"Downloading From Peer",
71                                                                        "inspector -> peer table -> header tool tip")];
72    [[fPeerTable tableColumnWithIdentifier: @"UL To"] setHeaderToolTip: NSLocalizedString(@"Uploading To Peer",
73                                                                        "inspector -> peer table -> header tool tip")];
74   
75    [[fWebSeedTable tableColumnWithIdentifier: @"DL From"] setHeaderToolTip: NSLocalizedString(@"Downloading From Web Seed",
76                                                                        "inspector -> web seed table -> header tool tip")];
77   
78    //prepare for animating peer table and web seed table
79    NSRect webSeedTableFrame = [[fWebSeedTable enclosingScrollView] frame];
80    fWebSeedTableHeight = webSeedTableFrame.size.height;
81    fSpaceBetweenWebSeedAndPeer = webSeedTableFrame.origin.y - NSMaxY([[fPeerTable enclosingScrollView] frame]);
82   
83    [self setWebSeedTableHidden: YES animate: NO];
84}
85
86- (void) dealloc
87{
88    [fTorrents release];
89   
90    [fPeers release];
91    [fWebSeeds release];
92   
93    [fWebSeedTableAnimation release];
94   
95    [super dealloc];
96}
97
98#warning subclass?
99- (void) setInfoForTorrents: (NSArray *) torrents
100{
101    if (fTorrents && [fTorrents isEqualToArray: torrents])
102        return;
103   
104    [fTorrents release];
105    fTorrents = [torrents retain];
106   
107    fSet = NO;
108}
109
110- (void) updateInfo
111{
112    if (!fSet)
113        [self setupInfo];
114   
115    if ([fTorrents count] == 0)
116        return;
117   
118    if (!fPeers)
119        fPeers = [[NSMutableArray alloc] init];
120    else
121        [fPeers removeAllObjects];
122   
123    if (!fWebSeeds)
124        fWebSeeds = [[NSMutableArray alloc] init];
125    else
126        [fWebSeeds removeAllObjects];
127   
128    NSUInteger known = 0, connected = 0, tracker = 0, incoming = 0, cache = 0, pex = 0, dht = 0, ltep = 0,
129                toUs = 0, fromUs = 0;
130    BOOL anyActive = false;
131    for (Torrent * torrent in fTorrents)
132    {
133        if ([torrent webSeedCount] > 0)
134            [fWebSeeds addObjectsFromArray: [torrent webSeeds]];
135       
136        known += [torrent totalPeersKnown];
137       
138        if ([torrent isActive])
139        {
140            anyActive = YES;
141            [fPeers addObjectsFromArray: [torrent peers]];
142           
143            const NSUInteger connectedThis = [torrent totalPeersConnected];
144            if (connectedThis > 0)
145            {
146                connected += [torrent totalPeersConnected];
147                tracker += [torrent totalPeersTracker];
148                incoming += [torrent totalPeersIncoming];
149                cache += [torrent totalPeersCache];
150                pex += [torrent totalPeersPex];
151                dht += [torrent totalPeersDHT];
152                ltep += [torrent totalPeersLTEP];
153               
154                toUs += [torrent peersSendingToUs];
155                fromUs += [torrent peersGettingFromUs];
156            }
157        }
158    }
159   
160    [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
161    [fPeerTable reloadData];
162   
163    [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
164    [fWebSeedTable reloadData];
165   
166    NSString * knownString = [NSString stringWithFormat: NSLocalizedString(@"%d known", "Inspector -> Peers tab -> peers"), known];
167    if (anyActive)
168    {
169        NSString * connectedText = [NSString stringWithFormat: NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"),
170                                    connected];
171       
172        if (connected > 0)
173        {
174            NSMutableArray * fromComponents = [NSMutableArray arrayWithCapacity: 6];
175            if (tracker > 0)
176                [fromComponents addObject: [NSString stringWithFormat:
177                                        NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), tracker]];
178            if (incoming > 0)
179                [fromComponents addObject: [NSString stringWithFormat:
180                                        NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), incoming]];
181            if (cache > 0)
182                [fromComponents addObject: [NSString stringWithFormat:
183                                        NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), cache]];
184            if (pex > 0)
185                [fromComponents addObject: [NSString stringWithFormat:
186                                        NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), pex]];
187            if (dht > 0)
188                [fromComponents addObject: [NSString stringWithFormat:
189                                        NSLocalizedString(@"%d DHT", "Inspector -> Peers tab -> peers"), dht]];
190            if (ltep > 0)
191                [fromComponents addObject: [NSString stringWithFormat:
192                                        NSLocalizedString(@"%d LTEP", "Inspector -> Peers tab -> peers"), ltep]];
193           
194            NSMutableArray * upDownComponents = [NSMutableArray arrayWithCapacity: 3];
195            if (toUs > 0)
196                [upDownComponents addObject: [NSString stringWithFormat:
197                                        NSLocalizedString(@"DL from %d", "Inspector -> Peers tab -> peers"), toUs]];
198            if (fromUs > 0)
199                [upDownComponents addObject: [NSString stringWithFormat:
200                                        NSLocalizedString(@"UL to %d", "Inspector -> Peers tab -> peers"), fromUs]];
201            [upDownComponents addObject: knownString];
202           
203            connectedText = [connectedText stringByAppendingFormat: @": %@\n%@", [fromComponents componentsJoinedByString: @", "],
204                                [upDownComponents componentsJoinedByString: @", "]];
205        }
206        else
207            connectedText = [connectedText stringByAppendingFormat: @"\n%@", knownString];
208       
209        [fConnectedPeersField setStringValue: connectedText];
210    }
211    else
212    {
213        NSString * activeString;
214        if ([fTorrents count] == 1)
215            activeString = NSLocalizedString(@"Transfer Not Active", "Inspector -> Peers tab -> peers");
216        else
217            activeString = NSLocalizedString(@"Transfers Not Active", "Inspector -> Peers tab -> peers");
218       
219        NSString * connectedText = [activeString stringByAppendingFormat: @"\n%@", knownString];
220        [fConnectedPeersField setStringValue: connectedText];
221    }
222}
223
224- (void) clearView
225{
226    //if in the middle of animating, just stop and resize immediately
227    if (fWebSeedTableAnimation)
228        [self setWebSeedTableHidden: !fWebSeeds animate: NO];
229   
230    [fPeers release];
231    fPeers = nil;
232    [fWebSeeds release];
233    fWebSeeds = nil;
234}
235
236- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
237{
238    if (tableView == fWebSeedTable)
239        return fWebSeeds ? [fWebSeeds count] : 0;
240    else
241        return fPeers ? [fPeers count] : 0;
242}
243
244- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
245{
246    if (tableView == fWebSeedTable)
247    {
248        NSString * ident = [column identifier];
249        NSDictionary * webSeed = [fWebSeeds objectAtIndex: row];
250       
251        if ([ident isEqualToString: @"DL From"])
252        {
253            NSNumber * rate;
254            return (rate = [webSeed objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
255        }
256        else
257            return [webSeed objectForKey: @"Address"];
258    }
259    else
260    {
261        NSString * ident = [column identifier];
262        NSDictionary * peer = [fPeers objectAtIndex: row];
263       
264        if ([ident isEqualToString: @"Encryption"])
265            return [[peer objectForKey: @"Encryption"] boolValue] ? [NSImage imageNamed: @"Lock.png"] : nil;
266        else if ([ident isEqualToString: @"Client"])
267            return [peer objectForKey: @"Client"];
268        else if  ([ident isEqualToString: @"Progress"])
269            return [peer objectForKey: @"Progress"];
270        else if ([ident isEqualToString: @"UL To"])
271        {
272            NSNumber * rate;
273            return (rate = [peer objectForKey: @"UL To Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
274        }
275        else if ([ident isEqualToString: @"DL From"])
276        {
277            NSNumber * rate;
278            return (rate = [peer objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate floatValue]] : @"";
279        }
280        else
281            return [peer objectForKey: @"IP"];
282    }
283}
284
285- (void) tableView: (NSTableView *) tableView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
286    row: (NSInteger) row
287{
288    if (tableView == fPeerTable)
289    {
290        NSString * ident = [tableColumn identifier];
291       
292        if  ([ident isEqualToString: @"Progress"])
293        {
294            NSDictionary * peer = [fPeers objectAtIndex: row];
295            [(PeerProgressIndicatorCell *)cell setSeed: [[peer objectForKey: @"Seed"] boolValue]];
296        }
297    }
298}
299
300- (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColumn *) tableColumn
301{
302    if (tableView == fWebSeedTable)
303    {
304        if (fWebSeeds)
305        {
306            [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
307            [tableView reloadData];
308        }
309    }
310    else
311    {
312        if (fPeers)
313        {
314            [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
315            [tableView reloadData];
316        }
317    }
318}
319
320- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (NSInteger) row
321{
322    return NO;
323}
324
325- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
326                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
327{
328    if (tableView == fPeerTable)
329    {
330        const BOOL multiple = [fTorrents count] > 1;
331       
332        NSDictionary * peer = [fPeers objectAtIndex: row];
333        NSMutableArray * components = [NSMutableArray arrayWithCapacity: multiple ? 6 : 5];
334       
335        if (multiple)
336            [components addObject: [peer objectForKey: @"Name"]];
337       
338        const CGFloat progress = [[peer objectForKey: @"Progress"] floatValue];
339        NSString * progressString = [NSString localizedStringWithFormat: NSLocalizedString(@"Progress: %.1f%%",
340                                        "Inspector -> Peers tab -> table row tooltip"), progress * 100.0];
341        if (progress < 1.0 && [[peer objectForKey: @"Seed"] boolValue])
342            progressString = [progressString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"Partial Seed",
343                                "Inspector -> Peers tab -> table row tooltip")];
344        [components addObject: progressString];
345       
346        if ([[peer objectForKey: @"Encryption"] boolValue])
347            [components addObject: NSLocalizedString(@"Encrypted Connection", "Inspector -> Peers tab -> table row tooltip")];
348       
349        NSString * portString;
350        NSInteger port;
351        if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
352            portString = [NSString stringWithFormat: @"%d", port];
353        else
354            portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
355        [components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
356            "Inspector -> Peers tab -> table row tooltip"), portString]];
357       
358        const NSInteger peerFrom = [[peer objectForKey: @"From"] integerValue];
359        switch (peerFrom)
360        {
361            case TR_PEER_FROM_TRACKER:
362                [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
363                break;
364            case TR_PEER_FROM_INCOMING:
365                [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
366                break;
367            case TR_PEER_FROM_RESUME:
368                [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
369                break;
370            case TR_PEER_FROM_PEX:
371                [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
372                break;
373            case TR_PEER_FROM_DHT:
374                [components addObject: NSLocalizedString(@"From: distributed hash table", "Inspector -> Peers tab -> table row tooltip")];
375                break;
376            case TR_PEER_FROM_LTEP:
377                [components addObject: NSLocalizedString(@"From: libtorrent extension protocol handshake",
378                                        "Inspector -> Peers tab -> table row tooltip")];
379                break;
380            default:
381                NSAssert1(NO, @"Peer from unknown source: %d", peerFrom);
382        }
383       
384        //determing status strings from flags
385        NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
386        NSString * flags = [peer objectForKey: @"Flags"];
387       
388        if ([flags rangeOfString: @"D"].location != NSNotFound)
389            [statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
390                "Inspector -> peer -> status")];
391        if ([flags rangeOfString: @"d"].location != NSNotFound)
392            [statusArray addObject: NSLocalizedString(@"You want to download, but peer does not want to send (interested and choked)",
393                "Inspector -> peer -> status")];
394        if ([flags rangeOfString: @"U"].location != NSNotFound)
395            [statusArray addObject: NSLocalizedString(@"Currently uploading (interested and not choked)",
396                "Inspector -> peer -> status")];
397        if ([flags rangeOfString: @"u"].location != NSNotFound)
398            [statusArray addObject: NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)",
399                "Inspector -> peer -> status")];
400        if ([flags rangeOfString: @"K"].location != NSNotFound)
401            [statusArray addObject: NSLocalizedString(@"Peer is unchoking you, but you are not interested",
402                "Inspector -> peer -> status")];
403        if ([flags rangeOfString: @"?"].location != NSNotFound)
404            [statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
405                "Inspector -> peer -> status")];
406       
407        if ([statusArray count] > 0)
408        {
409            NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
410            [components addObject: [@"\n" stringByAppendingString: statusStrings]];
411        }
412       
413        return [components componentsJoinedByString: @"\n"];
414    }
415    else
416    {
417        if ([fTorrents count] > 1)
418            return [[fWebSeeds objectAtIndex: row] objectForKey: @"Name"];
419    }
420   
421    return nil;
422}
423
424- (void) animationDidEnd: (NSAnimation *) animation
425{
426    if (animation == fWebSeedTableAnimation)
427    {
428        [fWebSeedTableAnimation release];
429        fWebSeedTableAnimation = nil;
430    }
431}
432
433- (void) stopWebSeedAnimation
434{
435    if (fWebSeedTableAnimation)
436    {
437        [fWebSeedTableAnimation stopAnimation]; // jumps to end frame
438        [fWebSeedTableAnimation release];
439        fWebSeedTableAnimation = nil;
440    }
441}
442
443@end
444
445@implementation InfoPeersViewController (Private)
446
447- (void) setupInfo
448{
449    BOOL hasWebSeeds = NO;
450   
451    if ([fTorrents count] == 0)
452    {
453        [fPeers release];
454        fPeers = nil;
455        [fPeerTable reloadData];
456       
457        [fConnectedPeersField setStringValue: @""];
458    }
459    else
460    {
461        for (Torrent * torrent in fTorrents)
462            if ([torrent webSeedCount] > 0)
463            {
464                hasWebSeeds = YES;
465                break;
466            }
467    }
468   
469    if (!hasWebSeeds)
470    {
471        [fWebSeeds release];
472        fWebSeeds = nil;
473        [fWebSeedTable reloadData];
474    }
475    [self setWebSeedTableHidden: !hasWebSeeds animate: YES];
476   
477    fSet = YES;
478}
479
480- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate
481{
482    if (animate && (![[self view] window] || ![[[self view] window] isVisible]))
483        animate = NO;
484   
485    if (fWebSeedTableAnimation)
486    {
487        [fWebSeedTableAnimation stopAnimation];
488        [fWebSeedTableAnimation release];
489        fWebSeedTableAnimation = nil;
490    }
491   
492    NSRect webSeedFrame = [[fWebSeedTable enclosingScrollView] frame];
493    NSRect peerFrame = [[fPeerTable enclosingScrollView] frame];
494   
495    if (hide)
496    {
497        CGFloat webSeedFrameMaxY = NSMaxY(webSeedFrame);
498        webSeedFrame.size.height = 0;
499        webSeedFrame.origin.y = webSeedFrameMaxY;
500       
501        peerFrame.size.height = webSeedFrameMaxY - peerFrame.origin.y;
502    }
503    else
504    {
505        webSeedFrame.origin.y -= fWebSeedTableHeight - webSeedFrame.size.height;
506        webSeedFrame.size.height = fWebSeedTableHeight;
507       
508        peerFrame.size.height = (webSeedFrame.origin.y - fSpaceBetweenWebSeedAndPeer) - peerFrame.origin.y;
509    }
510   
511    [[fWebSeedTable enclosingScrollView] setHidden: NO]; //this is needed for some reason
512   
513    //actually resize tables
514    if (animate)
515    {
516        NSDictionary * webSeedDict = [NSDictionary dictionaryWithObjectsAndKeys:
517                                    [fWebSeedTable enclosingScrollView], NSViewAnimationTargetKey,
518                                    [NSValue valueWithRect: [[fWebSeedTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
519                                    [NSValue valueWithRect: webSeedFrame], NSViewAnimationEndFrameKey, nil],
520                    * peerDict = [NSDictionary dictionaryWithObjectsAndKeys:
521                                    [fPeerTable enclosingScrollView], NSViewAnimationTargetKey,
522                                    [NSValue valueWithRect: [[fPeerTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
523                                    [NSValue valueWithRect: peerFrame], NSViewAnimationEndFrameKey, nil];
524       
525        fWebSeedTableAnimation = [[NSViewAnimation alloc] initWithViewAnimations:
526                                        [NSArray arrayWithObjects: webSeedDict, peerDict, nil]];
527        [fWebSeedTableAnimation setDuration: 0.125];
528        [fWebSeedTableAnimation setAnimationBlockingMode: NSAnimationNonblocking];
529        [fWebSeedTableAnimation setDelegate: self];
530       
531        [fWebSeedTableAnimation startAnimation];
532    }
533    else
534    {
535        [[fWebSeedTable enclosingScrollView] setFrame: webSeedFrame];
536        [[fPeerTable enclosingScrollView] setFrame: peerFrame];
537    }
538}
539
540- (NSArray *) peerSortDescriptors
541{
542    NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
543   
544    NSArray * oldDescriptors = [fPeerTable sortDescriptors];
545    BOOL useSecond = YES, asc = YES;
546    if ([oldDescriptors count] > 0)
547    {
548        NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
549        [descriptors addObject: descriptor];
550       
551        if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
552            asc = [descriptor ascending];
553    }
554   
555    //sort by IP after primary sort
556    if (useSecond)
557    {
558        #warning when 10.6-only, replace with sortDescriptorWithKey:ascending:selector:
559        NSSortDescriptor * secondDescriptor = [[NSSortDescriptor alloc] initWithKey: @"IP" ascending: asc
560                                                                        selector: @selector(compareNumeric:)];
561        [descriptors addObject: secondDescriptor];
562        [secondDescriptor release];
563    }
564   
565    return descriptors;
566}
567
568@end
Note: See TracBrowser for help on using the repository browser.