1/******************************************************************************
2 * $Id: InfoPeersViewController.m 13434 2012-08-13 00:52:04Z livings124 $
3 *
4 * Copyright (c) 2010-2012 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#import "WebSeedTableView.h"
31
32#import "transmission.h" // required by utils.h
33#import "utils.h"
34
35@interface InfoPeersViewController (Private)
36
37- (void) setupInfo;
38
39- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate;
40- (NSArray *) peerSortDescriptors;
41
42@end
43
44@implementation InfoPeersViewController
45
46- (id) init
47{
48    if ((self = [super initWithNibName: @"InfoPeersView" bundle: nil]))
49    {
50        [self setTitle: NSLocalizedString(@"Peers", "Inspector view -> title")];
51    }
52    
53    return self;
54}
55
56- (void) awakeFromNib
57{
58    const CGFloat height = [[NSUserDefaults standardUserDefaults] floatForKey: @"InspectorContentHeightPeers"];
59    if (height != 0.0)
60    {
61        NSRect viewRect = [[self view] frame];
62        viewRect.size.height = height;
63        [[self view] setFrame: viewRect];
64    }
65    
66    //set table header text
67    [[[fPeerTable tableColumnWithIdentifier: @"IP"] headerCell] setStringValue: NSLocalizedString(@"IP Address",
68                                                                        "inspector -> peer table -> header")];
69    [[[fPeerTable tableColumnWithIdentifier: @"Client"] headerCell] setStringValue: NSLocalizedString(@"Client",
70                                                                        "inspector -> peer table -> header")];
71    [[[fPeerTable tableColumnWithIdentifier: @"DL From"] headerCell] setStringValue: NSLocalizedString(@"DL",
72                                                                        "inspector -> peer table -> header")];
73    [[[fPeerTable tableColumnWithIdentifier: @"UL To"] headerCell] setStringValue: NSLocalizedString(@"UL",
74                                                                        "inspector -> peer table -> header")];
75    
76    [[[fWebSeedTable tableColumnWithIdentifier: @"Address"] headerCell] setStringValue: NSLocalizedString(@"Web Seeds",
77                                                                        "inspector -> web seed table -> header")];
78    [[[fWebSeedTable tableColumnWithIdentifier: @"DL From"] headerCell] setStringValue: NSLocalizedString(@"DL",
79                                                                        "inspector -> web seed table -> header")];
80    
81    //set table header tool tips
82    [[fPeerTable tableColumnWithIdentifier: @"Encryption"] setHeaderToolTip: NSLocalizedString(@"Encrypted Connection",
83                                                                        "inspector -> peer table -> header tool tip")];
84    [[fPeerTable tableColumnWithIdentifier: @"Progress"] setHeaderToolTip: NSLocalizedString(@"Available",
85                                                                        "inspector -> peer table -> header tool tip")];
86    [[fPeerTable tableColumnWithIdentifier: @"DL From"] setHeaderToolTip: NSLocalizedString(@"Downloading From Peer",
87                                                                        "inspector -> peer table -> header tool tip")];
88    [[fPeerTable tableColumnWithIdentifier: @"UL To"] setHeaderToolTip: NSLocalizedString(@"Uploading To Peer",
89                                                                        "inspector -> peer table -> header tool tip")];
90    
91    [[fWebSeedTable tableColumnWithIdentifier: @"DL From"] setHeaderToolTip: NSLocalizedString(@"Downloading From Web Seed",
92                                                                        "inspector -> web seed table -> header tool tip")];
93    
94    //prepare for animating peer table and web seed table
95    NSRect webSeedTableFrame = [[fWebSeedTable enclosingScrollView] frame];
96    fWebSeedTableHeight = webSeedTableFrame.size.height;
97    fSpaceBetweenWebSeedAndPeer = webSeedTableFrame.origin.y - NSMaxY([[fPeerTable enclosingScrollView] frame]);
98    
99    [self setWebSeedTableHidden: YES animate: NO];
100}
101
102- (void) dealloc
103{
104    [fTorrents release];
105    
106    [fPeers release];
107    [fWebSeeds release];
108    
109    [fWebSeedTableAnimation release];
110    
111    [super dealloc];
112}
113
114#warning subclass?
115- (void) setInfoForTorrents: (NSArray *) torrents
116{
117    //don't check if it's the same in case the metadata changed
118    [fTorrents release];
119    fTorrents = [torrents retain];
120    
121    fSet = NO;
122}
123
124- (void) updateInfo
125{
126    if (!fSet)
127        [self setupInfo];
128    
129    if ([fTorrents count] == 0)
130        return;
131    
132    if (!fPeers)
133        fPeers = [[NSMutableArray alloc] init];
134    else
135        [fPeers removeAllObjects];
136    
137    if (!fWebSeeds)
138        fWebSeeds = [[NSMutableArray alloc] init];
139    else
140        [fWebSeeds removeAllObjects];
141    
142    NSUInteger connected = 0, tracker = 0, incoming = 0, cache = 0, lpd = 0, pex = 0, dht = 0, ltep = 0,
143                toUs = 0, fromUs = 0;
144    BOOL anyActive = false;
145    for (Torrent * torrent in fTorrents)
146    {
147        if ([torrent webSeedCount] > 0)
148            [fWebSeeds addObjectsFromArray: [torrent webSeeds]];
149        
150        if ([torrent isActive])
151        {
152            anyActive = YES;
153            [fPeers addObjectsFromArray: [torrent peers]];
154            
155            const NSUInteger connectedThis = [torrent totalPeersConnected];
156            if (connectedThis > 0)
157            {
158                connected += [torrent totalPeersConnected];
159                tracker += [torrent totalPeersTracker];
160                incoming += [torrent totalPeersIncoming];
161                cache += [torrent totalPeersCache];
162                lpd += [torrent totalPeersLocal];
163                pex += [torrent totalPeersPex];
164                dht += [torrent totalPeersDHT];
165                ltep += [torrent totalPeersLTEP];
166                
167                toUs += [torrent peersSendingToUs];
168                fromUs += [torrent peersGettingFromUs];
169            }
170        }
171    }
172    
173    [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
174    [fPeerTable reloadData];
175    
176    [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
177    [fWebSeedTable reloadData];
178    [fWebSeedTable setWebSeeds: fWebSeeds];
179    
180    if (anyActive)
181    {
182        NSString * connectedText = [NSString stringWithFormat: NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"),
183                                    connected];
184        
185        if (connected > 0)
186        {
187            NSMutableArray * upDownComponents = [NSMutableArray arrayWithCapacity: 2];
188            if (toUs > 0)
189                [upDownComponents addObject: [NSString stringWithFormat:
190                                        NSLocalizedString(@"DL from %d", "Inspector -> Peers tab -> peers"), toUs]];
191            if (fromUs > 0)
192                [upDownComponents addObject: [NSString stringWithFormat:
193                                        NSLocalizedString(@"UL to %d", "Inspector -> Peers tab -> peers"), fromUs]];
194            if ([upDownComponents count] > 0)
195                connectedText = [connectedText stringByAppendingFormat: @": %@", [upDownComponents componentsJoinedByString: @", "]];
196            
197            NSMutableArray * fromComponents = [NSMutableArray arrayWithCapacity: 7];
198            if (tracker > 0)
199                [fromComponents addObject: [NSString stringWithFormat:
200                                        NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), tracker]];
201            if (incoming > 0)
202                [fromComponents addObject: [NSString stringWithFormat:
203                                        NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), incoming]];
204            if (cache > 0)
205                [fromComponents addObject: [NSString stringWithFormat:
206                                        NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), cache]];
207            if (lpd > 0)
208                [fromComponents addObject: [NSString stringWithFormat:
209                                        NSLocalizedString(@"%d local discovery", "Inspector -> Peers tab -> peers"), lpd]];
210            if (pex > 0)
211                [fromComponents addObject: [NSString stringWithFormat:
212                                        NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), pex]];
213            if (dht > 0)
214                [fromComponents addObject: [NSString stringWithFormat:
215                                        NSLocalizedString(@"%d DHT", "Inspector -> Peers tab -> peers"), dht]];
216            if (ltep > 0)
217                [fromComponents addObject: [NSString stringWithFormat:
218                                        NSLocalizedString(@"%d LTEP", "Inspector -> Peers tab -> peers"), ltep]];
219            
220            connectedText = [connectedText stringByAppendingFormat: @"\n%@", [fromComponents componentsJoinedByString: @", "]];
221        }
222        
223        [fConnectedPeersField setStringValue: connectedText];
224    }
225    else
226    {
227        NSString * notActiveString;
228        if ([fTorrents count] == 1)
229            notActiveString = NSLocalizedString(@"Transfer Not Active", "Inspector -> Peers tab -> peers");
230        else
231            notActiveString = NSLocalizedString(@"Transfers Not Active", "Inspector -> Peers tab -> peers");
232        
233        [fConnectedPeersField setStringValue: notActiveString];
234    }
235}
236
237- (void) saveViewSize
238{
239    [[NSUserDefaults standardUserDefaults] setFloat: NSHeight([[self view] frame]) forKey: @"InspectorContentHeightPeers"];
240}
241
242- (void) clearView
243{
244    //if in the middle of animating, just stop and resize immediately
245    if (fWebSeedTableAnimation)
246        [self setWebSeedTableHidden: !fWebSeeds animate: NO];
247    
248    [fPeers release];
249    fPeers = nil;
250    [fWebSeeds release];
251    fWebSeeds = nil;
252}
253
254- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
255{
256    if (tableView == fWebSeedTable)
257        return fWebSeeds ? [fWebSeeds count] : 0;
258    else
259        return fPeers ? [fPeers count] : 0;
260}
261
262- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
263{
264    if (tableView == fWebSeedTable)
265    {
266        NSString * ident = [column identifier];
267        NSDictionary * webSeed = [fWebSeeds objectAtIndex: row];
268        
269        if ([ident isEqualToString: @"DL From"])
270        {
271            NSNumber * rate;
272            return (rate = [webSeed objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate doubleValue]] : @"";
273        }
274        else
275            return [webSeed objectForKey: @"Address"];
276    }
277    else
278    {
279        NSString * ident = [column identifier];
280        NSDictionary * peer = [fPeers objectAtIndex: row];
281        
282        if ([ident isEqualToString: @"Encryption"])
283            return [[peer objectForKey: @"Encryption"] boolValue] ? [NSImage imageNamed: @"Lock"] : nil;
284        else if ([ident isEqualToString: @"Client"])
285            return [peer objectForKey: @"Client"];
286        else if  ([ident isEqualToString: @"Progress"])
287            return [peer objectForKey: @"Progress"];
288        else if ([ident isEqualToString: @"UL To"])
289        {
290            NSNumber * rate;
291            return (rate = [peer objectForKey: @"UL To Rate"]) ? [NSString stringForSpeedAbbrev: [rate doubleValue]] : @"";
292        }
293        else if ([ident isEqualToString: @"DL From"])
294        {
295            NSNumber * rate;
296            return (rate = [peer objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate doubleValue]] : @"";
297        }
298        else
299            return [peer objectForKey: @"IP"];
300    }
301}
302
303- (void) tableView: (NSTableView *) tableView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
304    row: (NSInteger) row
305{
306    if (tableView == fPeerTable)
307    {
308        NSString * ident = [tableColumn identifier];
309        
310        if  ([ident isEqualToString: @"Progress"])
311        {
312            NSDictionary * peer = [fPeers objectAtIndex: row];
313            [(PeerProgressIndicatorCell *)cell setSeed: [[peer objectForKey: @"Seed"] boolValue]];
314        }
315    }
316}
317
318- (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColumn *) tableColumn
319{
320    if (tableView == fWebSeedTable)
321    {
322        if (fWebSeeds)
323        {
324            [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
325            [tableView reloadData];
326        }
327    }
328    else
329    {
330        if (fPeers)
331        {
332            [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
333            [tableView reloadData];
334        }
335    }
336}
337
338- (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (NSInteger) row
339{
340    return tableView != fPeerTable;
341}
342
343- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
344                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
345{
346    if (tableView == fPeerTable)
347    {
348        const BOOL multiple = [fTorrents count] > 1;
349        
350        NSDictionary * peer = [fPeers objectAtIndex: row];
351        NSMutableArray * components = [NSMutableArray arrayWithCapacity: multiple ? 6 : 5];
352        
353        if (multiple)
354            [components addObject: [peer objectForKey: @"Name"]];
355        
356        const CGFloat progress = [[peer objectForKey: @"Progress"] floatValue];
357        NSString * progressString = [NSString stringWithFormat: NSLocalizedString(@"Progress: %@",
358                                        "Inspector -> Peers tab -> table row tooltip"),
359                                        [NSString percentString: progress longDecimals: NO]];
360        if (progress < 1.0 && [[peer objectForKey: @"Seed"] boolValue])
361            progressString = [progressString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"Partial Seed",
362                                "Inspector -> Peers tab -> table row tooltip")];
363        [components addObject: progressString];
364        
365        NSString * protocolString = [[peer objectForKey: @"uTP"] boolValue] ? @"\u00b5TP" : @"TCP";
366        if ([[peer objectForKey: @"Encryption"] boolValue])
367            protocolString = [protocolString stringByAppendingFormat: @" (%@)",
368                                NSLocalizedString(@"encrypted", "Inspector -> Peers tab -> table row tooltip")];
369        [components addObject: [NSString stringWithFormat:
370                                NSLocalizedString(@"Protocol: %@", "Inspector -> Peers tab -> table row tooltip"),
371                                protocolString]];
372        
373        NSString * portString;
374        NSInteger port;
375        if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
376            portString = [NSString stringWithFormat: @"%ld", port];
377        else
378            portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
379        [components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
380            "Inspector -> Peers tab -> table row tooltip"), portString]];
381        
382        const NSInteger peerFrom = [[peer objectForKey: @"From"] integerValue];
383        switch (peerFrom)
384        {
385            case TR_PEER_FROM_TRACKER:
386                [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
387                break;
388            case TR_PEER_FROM_INCOMING:
389                [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
390                break;
391            case TR_PEER_FROM_RESUME:
392                [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
393                break;
394            case TR_PEER_FROM_LPD:
395                [components addObject: NSLocalizedString(@"From: local peer discovery", "Inspector -> Peers tab -> table row tooltip")];
396                break;
397            case TR_PEER_FROM_PEX:
398                [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
399                break;
400            case TR_PEER_FROM_DHT:
401                [components addObject: NSLocalizedString(@"From: distributed hash table", "Inspector -> Peers tab -> table row tooltip")];
402                break;
403            case TR_PEER_FROM_LTEP:
404                [components addObject: NSLocalizedString(@"From: libtorrent extension protocol handshake",
405                                        "Inspector -> Peers tab -> table row tooltip")];
406                break;
407            default:
408                NSAssert1(NO, @"Peer from unknown source: %ld", peerFrom);
409        }
410        
411        //determing status strings from flags
412        NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
413        NSString * flags = [peer objectForKey: @"Flags"];
414        
415        if ([flags rangeOfString: @"D"].location != NSNotFound)
416            [statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
417                "Inspector -> peer -> status")];
418        if ([flags rangeOfString: @"d"].location != NSNotFound)
419            [statusArray addObject: NSLocalizedString(@"You want to download, but peer does not want to send (interested and choked)",
420                "Inspector -> peer -> status")];
421        if ([flags rangeOfString: @"U"].location != NSNotFound)
422            [statusArray addObject: NSLocalizedString(@"Currently uploading (interested and not choked)",
423                "Inspector -> peer -> status")];
424        if ([flags rangeOfString: @"u"].location != NSNotFound)
425            [statusArray addObject: NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)",
426                "Inspector -> peer -> status")];
427        if ([flags rangeOfString: @"K"].location != NSNotFound)
428            [statusArray addObject: NSLocalizedString(@"Peer is unchoking you, but you are not interested",
429                "Inspector -> peer -> status")];
430        if ([flags rangeOfString: @"?"].location != NSNotFound)
431            [statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
432                "Inspector -> peer -> status")];
433        
434        if ([statusArray count] > 0)
435        {
436            NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
437            [components addObject: [@"\n" stringByAppendingString: statusStrings]];
438        }
439        
440        return [components componentsJoinedByString: @"\n"];
441    }
442    else
443    {
444        if ([fTorrents count] > 1)
445            return [[fWebSeeds objectAtIndex: row] objectForKey: @"Name"];
446    }
447    
448    return nil;
449}
450
451- (void) animationDidEnd: (NSAnimation *) animation
452{
453    if (animation == fWebSeedTableAnimation)
454    {
455        [fWebSeedTableAnimation release];
456        fWebSeedTableAnimation = nil;
457    }
458}
459
460- (void) stopWebSeedAnimation
461{
462    if (fWebSeedTableAnimation)
463    {
464        [fWebSeedTableAnimation stopAnimation]; // jumps to end frame
465        [fWebSeedTableAnimation release];
466        fWebSeedTableAnimation = nil;
467    }
468}
469
470@end
471
472@implementation InfoPeersViewController (Private)
473
474- (void) setupInfo
475{
476    __block BOOL hasWebSeeds = NO;
477    
478    if ([fTorrents count] == 0)
479    {
480        [fPeers release];
481        fPeers = nil;
482        [fPeerTable reloadData];
483        
484        [fConnectedPeersField setStringValue: @""];
485    }
486    else
487    {
488        [fTorrents enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(Torrent * torrent, NSUInteger idx, BOOL *stop) {
489            if ([torrent webSeedCount] > 0)
490            {
491                hasWebSeeds = YES;
492                *stop = YES;
493            }
494        }];
495    }
496    
497    if (!hasWebSeeds)
498    {
499        [fWebSeeds release];
500        fWebSeeds = nil;
501        [fWebSeedTable reloadData];
502    }
503    else
504        [fWebSeedTable deselectAll: self];
505    [self setWebSeedTableHidden: !hasWebSeeds animate: YES];
506    
507    fSet = YES;
508}
509
510- (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate
511{
512    if (animate && (![[self view] window] || ![[[self view] window] isVisible]))
513        animate = NO;
514    
515    if (fWebSeedTableAnimation)
516    {
517        [fWebSeedTableAnimation stopAnimation];
518        [fWebSeedTableAnimation release];
519        fWebSeedTableAnimation = nil;
520    }
521    
522    NSRect webSeedFrame = [[fWebSeedTable enclosingScrollView] frame];
523    NSRect peerFrame = [[fPeerTable enclosingScrollView] frame];
524    
525    if (hide)
526    {
527        CGFloat webSeedFrameMaxY = NSMaxY(webSeedFrame);
528        webSeedFrame.size.height = 0;
529        webSeedFrame.origin.y = webSeedFrameMaxY;
530        
531        peerFrame.size.height = webSeedFrameMaxY - peerFrame.origin.y;
532    }
533    else
534    {
535        webSeedFrame.origin.y -= fWebSeedTableHeight - webSeedFrame.size.height;
536        webSeedFrame.size.height = fWebSeedTableHeight;
537        
538        peerFrame.size.height = (webSeedFrame.origin.y - fSpaceBetweenWebSeedAndPeer) - peerFrame.origin.y;
539    }
540    
541    [[fWebSeedTable enclosingScrollView] setHidden: NO]; //this is needed for some reason
542    
543    //actually resize tables
544    if (animate)
545    {
546        NSDictionary * webSeedDict = [NSDictionary dictionaryWithObjectsAndKeys:
547                                    [fWebSeedTable enclosingScrollView], NSViewAnimationTargetKey,
548                                    [NSValue valueWithRect: [[fWebSeedTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
549                                    [NSValue valueWithRect: webSeedFrame], NSViewAnimationEndFrameKey, nil],
550                    * peerDict = [NSDictionary dictionaryWithObjectsAndKeys:
551                                    [fPeerTable enclosingScrollView], NSViewAnimationTargetKey,
552                                    [NSValue valueWithRect: [[fPeerTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
553                                    [NSValue valueWithRect: peerFrame], NSViewAnimationEndFrameKey, nil];
554        
555        fWebSeedTableAnimation = [[NSViewAnimation alloc] initWithViewAnimations:
556                                        [NSArray arrayWithObjects: webSeedDict, peerDict, nil]];
557        [fWebSeedTableAnimation setDuration: 0.125];
558        [fWebSeedTableAnimation setAnimationBlockingMode: NSAnimationNonblocking];
559        [fWebSeedTableAnimation setDelegate: self];
560        
561        [fWebSeedTableAnimation startAnimation];
562    }
563    else
564    {
565        [[fWebSeedTable enclosingScrollView] setFrame: webSeedFrame];
566        [[fPeerTable enclosingScrollView] setFrame: peerFrame];
567    }
568}
569
570- (NSArray *) peerSortDescriptors
571{
572    NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
573    
574    NSArray * oldDescriptors = [fPeerTable sortDescriptors];
575    BOOL useSecond = YES, asc = YES;
576    if ([oldDescriptors count] > 0)
577    {
578        NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
579        [descriptors addObject: descriptor];
580        
581        if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
582            asc = [descriptor ascending];
583    }
584    
585    //sort by IP after primary sort
586    if (useSecond)
587    {
588        NSSortDescriptor * secondDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"IP" ascending: asc selector: @selector(compareNumeric:)];
589        [descriptors addObject: secondDescriptor];
590    }
591    
592    return descriptors;
593}
594
595@end
596