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