transmission: update from 2.13 to 2.22
[tomato.git] / release / src / router / transmission / macosx / InfoPeersViewController.m
blob9a23d8a2a8c795d00ad9179045ecff4199ed76c8
1 /******************************************************************************
2  * $Id: InfoPeersViewController.m 11617 2011-01-01 20:42:14Z livings124 $
3  *
4  * Copyright (c) 2010-2011 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 known = 0, 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         known += [torrent totalPeersKnown];
150         
151         if ([torrent isActive])
152         {
153             anyActive = YES;
154             [fPeers addObjectsFromArray: [torrent peers]];
155             
156             const NSUInteger connectedThis = [torrent totalPeersConnected];
157             if (connectedThis > 0)
158             {
159                 connected += [torrent totalPeersConnected];
160                 tracker += [torrent totalPeersTracker];
161                 incoming += [torrent totalPeersIncoming];
162                 cache += [torrent totalPeersCache];
163                 lpd += [torrent totalPeersLocal];
164                 pex += [torrent totalPeersPex];
165                 dht += [torrent totalPeersDHT];
166                 ltep += [torrent totalPeersLTEP];
167                 
168                 toUs += [torrent peersSendingToUs];
169                 fromUs += [torrent peersGettingFromUs];
170             }
171         }
172     }
173     
174     [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
175     [fPeerTable reloadData];
176     
177     [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
178     [fWebSeedTable reloadData];
179     
180     NSString * knownString = [NSString stringWithFormat: NSLocalizedString(@"%d known", "Inspector -> Peers tab -> peers"), known];
181     if (anyActive)
182     {
183         NSString * connectedText = [NSString stringWithFormat: NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"),
184                                     connected];
185         
186         if (connected > 0)
187         {
188             NSMutableArray * fromComponents = [NSMutableArray arrayWithCapacity: 7];
189             if (tracker > 0)
190                 [fromComponents addObject: [NSString stringWithFormat:
191                                         NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), tracker]];
192             if (incoming > 0)
193                 [fromComponents addObject: [NSString stringWithFormat:
194                                         NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), incoming]];
195             if (cache > 0)
196                 [fromComponents addObject: [NSString stringWithFormat:
197                                         NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), cache]];
198             if (lpd > 0)
199                 [fromComponents addObject: [NSString stringWithFormat:
200                                         NSLocalizedString(@"%d local discovery", "Inspector -> Peers tab -> peers"), lpd]];
201             if (pex > 0)
202                 [fromComponents addObject: [NSString stringWithFormat:
203                                         NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), pex]];
204             if (dht > 0)
205                 [fromComponents addObject: [NSString stringWithFormat:
206                                         NSLocalizedString(@"%d DHT", "Inspector -> Peers tab -> peers"), dht]];
207             if (ltep > 0)
208                 [fromComponents addObject: [NSString stringWithFormat:
209                                         NSLocalizedString(@"%d LTEP", "Inspector -> Peers tab -> peers"), ltep]];
210             
211             NSMutableArray * upDownComponents = [NSMutableArray arrayWithCapacity: 3];
212             if (toUs > 0)
213                 [upDownComponents addObject: [NSString stringWithFormat:
214                                         NSLocalizedString(@"DL from %d", "Inspector -> Peers tab -> peers"), toUs]];
215             if (fromUs > 0)
216                 [upDownComponents addObject: [NSString stringWithFormat:
217                                         NSLocalizedString(@"UL to %d", "Inspector -> Peers tab -> peers"), fromUs]];
218             [upDownComponents addObject: knownString];
219             
220             connectedText = [connectedText stringByAppendingFormat: @": %@\n%@", [fromComponents componentsJoinedByString: @", "],
221                                 [upDownComponents componentsJoinedByString: @", "]];
222         }
223         else
224             connectedText = [connectedText stringByAppendingFormat: @"\n%@", knownString];
225         
226         [fConnectedPeersField setStringValue: connectedText];
227     }
228     else
229     {
230         NSString * activeString;
231         if ([fTorrents count] == 1)
232             activeString = NSLocalizedString(@"Transfer Not Active", "Inspector -> Peers tab -> peers");
233         else
234             activeString = NSLocalizedString(@"Transfers Not Active", "Inspector -> Peers tab -> peers");
235         
236         NSString * connectedText = [activeString stringByAppendingFormat: @"\n%@", knownString];
237         [fConnectedPeersField setStringValue: connectedText];
238     }
241 - (void) saveViewSize
243     [[NSUserDefaults standardUserDefaults] setFloat: NSHeight([[self view] frame]) forKey: @"InspectorContentHeightPeers"];
246 - (void) clearView
248     //if in the middle of animating, just stop and resize immediately
249     if (fWebSeedTableAnimation)
250         [self setWebSeedTableHidden: !fWebSeeds animate: NO];
251     
252     [fPeers release];
253     fPeers = nil;
254     [fWebSeeds release];
255     fWebSeeds = nil;
258 - (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
260     if (tableView == fWebSeedTable)
261         return fWebSeeds ? [fWebSeeds count] : 0;
262     else
263         return fPeers ? [fPeers count] : 0;
266 - (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
268     if (tableView == fWebSeedTable)
269     {
270         NSString * ident = [column identifier];
271         NSDictionary * webSeed = [fWebSeeds objectAtIndex: row];
272         
273         if ([ident isEqualToString: @"DL From"])
274         {
275             NSNumber * rate;
276             return (rate = [webSeed objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate doubleValue]] : @"";
277         }
278         else
279             return [webSeed objectForKey: @"Address"];
280     }
281     else
282     {
283         NSString * ident = [column identifier];
284         NSDictionary * peer = [fPeers objectAtIndex: row];
285         
286         if ([ident isEqualToString: @"Encryption"])
287             return [[peer objectForKey: @"Encryption"] boolValue] ? [NSImage imageNamed: @"Lock.png"] : nil;
288         else if ([ident isEqualToString: @"Client"])
289             return [peer objectForKey: @"Client"];
290         else if  ([ident isEqualToString: @"Progress"])
291             return [peer objectForKey: @"Progress"];
292         else if ([ident isEqualToString: @"UL To"])
293         {
294             NSNumber * rate;
295             return (rate = [peer objectForKey: @"UL To Rate"]) ? [NSString stringForSpeedAbbrev: [rate doubleValue]] : @"";
296         }
297         else if ([ident isEqualToString: @"DL From"])
298         {
299             NSNumber * rate;
300             return (rate = [peer objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate doubleValue]] : @"";
301         }
302         else
303             return [peer objectForKey: @"IP"];
304     }
307 - (void) tableView: (NSTableView *) tableView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
308     row: (NSInteger) row
310     if (tableView == fPeerTable)
311     {
312         NSString * ident = [tableColumn identifier];
313         
314         if  ([ident isEqualToString: @"Progress"])
315         {
316             NSDictionary * peer = [fPeers objectAtIndex: row];
317             [(PeerProgressIndicatorCell *)cell setSeed: [[peer objectForKey: @"Seed"] boolValue]];
318         }
319     }
322 - (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColumn *) tableColumn
324     if (tableView == fWebSeedTable)
325     {
326         if (fWebSeeds)
327         {
328             [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
329             [tableView reloadData];
330         }
331     }
332     else
333     {
334         if (fPeers)
335         {
336             [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
337             [tableView reloadData];
338         }
339     }
342 - (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (NSInteger) row
344     return NO;
347 - (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
348                 tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
350     if (tableView == fPeerTable)
351     {
352         const BOOL multiple = [fTorrents count] > 1;
353         
354         NSDictionary * peer = [fPeers objectAtIndex: row];
355         NSMutableArray * components = [NSMutableArray arrayWithCapacity: multiple ? 6 : 5];
356         
357         if (multiple)
358             [components addObject: [peer objectForKey: @"Name"]];
359         
360         const CGFloat progress = [[peer objectForKey: @"Progress"] floatValue];
361         NSString * progressString = [NSString stringWithFormat: NSLocalizedString(@"Progress: %@",
362                                         "Inspector -> Peers tab -> table row tooltip"),
363                                         [NSString percentString: progress longDecimals: NO]];
364         if (progress < 1.0 && [[peer objectForKey: @"Seed"] boolValue])
365             progressString = [progressString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"Partial Seed",
366                                 "Inspector -> Peers tab -> table row tooltip")];
367         [components addObject: progressString];
368         
369         if ([[peer objectForKey: @"Encryption"] boolValue])
370             [components addObject: NSLocalizedString(@"Encrypted Connection", "Inspector -> Peers tab -> table row tooltip")];
371         
372         NSString * portString;
373         NSInteger port;
374         if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
375             portString = [NSString stringWithFormat: @"%d", port];
376         else
377             portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
378         [components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
379             "Inspector -> Peers tab -> table row tooltip"), portString]];
380         
381         const NSInteger peerFrom = [[peer objectForKey: @"From"] integerValue];
382         switch (peerFrom)
383         {
384             case TR_PEER_FROM_TRACKER:
385                 [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
386                 break;
387             case TR_PEER_FROM_INCOMING:
388                 [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
389                 break;
390             case TR_PEER_FROM_RESUME:
391                 [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
392                 break;
393             case TR_PEER_FROM_LPD:
394                 [components addObject: NSLocalizedString(@"From: local peer discovery", "Inspector -> Peers tab -> table row tooltip")];
395                 break;
396             case TR_PEER_FROM_PEX:
397                 [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
398                 break;
399             case TR_PEER_FROM_DHT:
400                 [components addObject: NSLocalizedString(@"From: distributed hash table", "Inspector -> Peers tab -> table row tooltip")];
401                 break;
402             case TR_PEER_FROM_LTEP:
403                 [components addObject: NSLocalizedString(@"From: libtorrent extension protocol handshake",
404                                         "Inspector -> Peers tab -> table row tooltip")];
405                 break;
406             default:
407                 NSAssert1(NO, @"Peer from unknown source: %d", peerFrom);
408         }
409         
410         //determing status strings from flags
411         NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
412         NSString * flags = [peer objectForKey: @"Flags"];
413         
414         if ([flags rangeOfString: @"D"].location != NSNotFound)
415             [statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
416                 "Inspector -> peer -> status")];
417         if ([flags rangeOfString: @"d"].location != NSNotFound)
418             [statusArray addObject: NSLocalizedString(@"You want to download, but peer does not want to send (interested and choked)",
419                 "Inspector -> peer -> status")];
420         if ([flags rangeOfString: @"U"].location != NSNotFound)
421             [statusArray addObject: NSLocalizedString(@"Currently uploading (interested and not choked)",
422                 "Inspector -> peer -> status")];
423         if ([flags rangeOfString: @"u"].location != NSNotFound)
424             [statusArray addObject: NSLocalizedString(@"Peer wants you to upload, but you do not want to (interested and choked)",
425                 "Inspector -> peer -> status")];
426         if ([flags rangeOfString: @"K"].location != NSNotFound)
427             [statusArray addObject: NSLocalizedString(@"Peer is unchoking you, but you are not interested",
428                 "Inspector -> peer -> status")];
429         if ([flags rangeOfString: @"?"].location != NSNotFound)
430             [statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
431                 "Inspector -> peer -> status")];
432         
433         if ([statusArray count] > 0)
434         {
435             NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
436             [components addObject: [@"\n" stringByAppendingString: statusStrings]];
437         }
438         
439         return [components componentsJoinedByString: @"\n"];
440     }
441     else
442     {
443         if ([fTorrents count] > 1)
444             return [[fWebSeeds objectAtIndex: row] objectForKey: @"Name"];
445     }
446     
447     return nil;
450 - (void) animationDidEnd: (NSAnimation *) animation
452     if (animation == fWebSeedTableAnimation)
453     {
454         [fWebSeedTableAnimation release];
455         fWebSeedTableAnimation = nil;
456     }
459 - (void) stopWebSeedAnimation
461     if (fWebSeedTableAnimation)
462     {
463         [fWebSeedTableAnimation stopAnimation]; // jumps to end frame
464         [fWebSeedTableAnimation release];
465         fWebSeedTableAnimation = nil;
466     }
469 @end
471 @implementation InfoPeersViewController (Private)
473 - (void) setupInfo
475     BOOL hasWebSeeds = NO;
476     
477     if ([fTorrents count] == 0)
478     {
479         [fPeers release];
480         fPeers = nil;
481         [fPeerTable reloadData];
482         
483         [fConnectedPeersField setStringValue: @""];
484     }
485     else
486     {
487         for (Torrent * torrent in fTorrents)
488             if ([torrent webSeedCount] > 0)
489             {
490                 hasWebSeeds = YES;
491                 break;
492             }
493     }
494     
495     if (!hasWebSeeds)
496     {
497         [fWebSeeds release];
498         fWebSeeds = nil;
499         [fWebSeedTable reloadData];
500     }
501     [self setWebSeedTableHidden: !hasWebSeeds animate: YES];
502     
503     fSet = YES;
506 - (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate
508     if (animate && (![[self view] window] || ![[[self view] window] isVisible]))
509         animate = NO;
510     
511     if (fWebSeedTableAnimation)
512     {
513         [fWebSeedTableAnimation stopAnimation];
514         [fWebSeedTableAnimation release];
515         fWebSeedTableAnimation = nil;
516     }
517     
518     NSRect webSeedFrame = [[fWebSeedTable enclosingScrollView] frame];
519     NSRect peerFrame = [[fPeerTable enclosingScrollView] frame];
520     
521     if (hide)
522     {
523         CGFloat webSeedFrameMaxY = NSMaxY(webSeedFrame);
524         webSeedFrame.size.height = 0;
525         webSeedFrame.origin.y = webSeedFrameMaxY;
526         
527         peerFrame.size.height = webSeedFrameMaxY - peerFrame.origin.y;
528     }
529     else
530     {
531         webSeedFrame.origin.y -= fWebSeedTableHeight - webSeedFrame.size.height;
532         webSeedFrame.size.height = fWebSeedTableHeight;
533         
534         peerFrame.size.height = (webSeedFrame.origin.y - fSpaceBetweenWebSeedAndPeer) - peerFrame.origin.y;
535     }
536     
537     [[fWebSeedTable enclosingScrollView] setHidden: NO]; //this is needed for some reason
538     
539     //actually resize tables
540     if (animate)
541     {
542         NSDictionary * webSeedDict = [NSDictionary dictionaryWithObjectsAndKeys:
543                                     [fWebSeedTable enclosingScrollView], NSViewAnimationTargetKey,
544                                     [NSValue valueWithRect: [[fWebSeedTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
545                                     [NSValue valueWithRect: webSeedFrame], NSViewAnimationEndFrameKey, nil],
546                     * peerDict = [NSDictionary dictionaryWithObjectsAndKeys:
547                                     [fPeerTable enclosingScrollView], NSViewAnimationTargetKey,
548                                     [NSValue valueWithRect: [[fPeerTable enclosingScrollView] frame]], NSViewAnimationStartFrameKey,
549                                     [NSValue valueWithRect: peerFrame], NSViewAnimationEndFrameKey, nil];
550         
551         fWebSeedTableAnimation = [[NSViewAnimation alloc] initWithViewAnimations:
552                                         [NSArray arrayWithObjects: webSeedDict, peerDict, nil]];
553         [fWebSeedTableAnimation setDuration: 0.125];
554         [fWebSeedTableAnimation setAnimationBlockingMode: NSAnimationNonblocking];
555         [fWebSeedTableAnimation setDelegate: self];
556         
557         [fWebSeedTableAnimation startAnimation];
558     }
559     else
560     {
561         [[fWebSeedTable enclosingScrollView] setFrame: webSeedFrame];
562         [[fPeerTable enclosingScrollView] setFrame: peerFrame];
563     }
566 - (NSArray *) peerSortDescriptors
568     NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
569     
570     NSArray * oldDescriptors = [fPeerTable sortDescriptors];
571     BOOL useSecond = YES, asc = YES;
572     if ([oldDescriptors count] > 0)
573     {
574         NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
575         [descriptors addObject: descriptor];
576         
577         if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
578             asc = [descriptor ascending];
579     }
580     
581     //sort by IP after primary sort
582     if (useSecond)
583     {
584         #warning when 10.6-only, replace with sortDescriptorWithKey:ascending:selector:
585         NSSortDescriptor * secondDescriptor = [[NSSortDescriptor alloc] initWithKey: @"IP" ascending: asc
586                                                                         selector: @selector(compareNumeric:)];
587         [descriptors addObject: secondDescriptor];
588         [secondDescriptor release];
589     }
590     
591     return descriptors;
594 @end