1 /******************************************************************************
2 * $Id: InfoPeersViewController.m 11617 2011-01-01 20:42:14Z livings124 $
4 * Copyright (c) 2010-2011 Transmission authors and contributors
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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"
31 #import "transmission.h" // required by utils.h
34 @interface InfoPeersViewController (Private)
38 - (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate;
39 - (NSArray *) peerSortDescriptors;
43 @implementation InfoPeersViewController
47 if ((self = [super initWithNibName: @"InfoPeersView" bundle: nil]))
49 [self setTitle: NSLocalizedString(@"Peers", "Inspector view -> title")];
57 const CGFloat height = [[NSUserDefaults standardUserDefaults] floatForKey: @"InspectorContentHeightPeers"];
60 NSRect viewRect = [[self view] frame];
61 viewRect.size.height = height;
62 [[self view] setFrame: viewRect];
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")];
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")];
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")];
90 [[fWebSeedTable tableColumnWithIdentifier: @"DL From"] setHeaderToolTip: NSLocalizedString(@"Downloading From Web Seed",
91 "inspector -> web seed table -> header tool tip")];
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]);
98 [self setWebSeedTableHidden: YES animate: NO];
108 [fWebSeedTableAnimation release];
114 - (void) setInfoForTorrents: (NSArray *) torrents
116 //don't check if it's the same in case the metadata changed
118 fTorrents = [torrents retain];
128 if ([fTorrents count] == 0)
132 fPeers = [[NSMutableArray alloc] init];
134 [fPeers removeAllObjects];
137 fWebSeeds = [[NSMutableArray alloc] init];
139 [fWebSeeds removeAllObjects];
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)
146 if ([torrent webSeedCount] > 0)
147 [fWebSeeds addObjectsFromArray: [torrent webSeeds]];
149 known += [torrent totalPeersKnown];
151 if ([torrent isActive])
154 [fPeers addObjectsFromArray: [torrent peers]];
156 const NSUInteger connectedThis = [torrent totalPeersConnected];
157 if (connectedThis > 0)
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];
168 toUs += [torrent peersSendingToUs];
169 fromUs += [torrent peersGettingFromUs];
174 [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
175 [fPeerTable reloadData];
177 [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
178 [fWebSeedTable reloadData];
180 NSString * knownString = [NSString stringWithFormat: NSLocalizedString(@"%d known", "Inspector -> Peers tab -> peers"), known];
183 NSString * connectedText = [NSString stringWithFormat: NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"),
188 NSMutableArray * fromComponents = [NSMutableArray arrayWithCapacity: 7];
190 [fromComponents addObject: [NSString stringWithFormat:
191 NSLocalizedString(@"%d tracker", "Inspector -> Peers tab -> peers"), tracker]];
193 [fromComponents addObject: [NSString stringWithFormat:
194 NSLocalizedString(@"%d incoming", "Inspector -> Peers tab -> peers"), incoming]];
196 [fromComponents addObject: [NSString stringWithFormat:
197 NSLocalizedString(@"%d cache", "Inspector -> Peers tab -> peers"), cache]];
199 [fromComponents addObject: [NSString stringWithFormat:
200 NSLocalizedString(@"%d local discovery", "Inspector -> Peers tab -> peers"), lpd]];
202 [fromComponents addObject: [NSString stringWithFormat:
203 NSLocalizedString(@"%d PEX", "Inspector -> Peers tab -> peers"), pex]];
205 [fromComponents addObject: [NSString stringWithFormat:
206 NSLocalizedString(@"%d DHT", "Inspector -> Peers tab -> peers"), dht]];
208 [fromComponents addObject: [NSString stringWithFormat:
209 NSLocalizedString(@"%d LTEP", "Inspector -> Peers tab -> peers"), ltep]];
211 NSMutableArray * upDownComponents = [NSMutableArray arrayWithCapacity: 3];
213 [upDownComponents addObject: [NSString stringWithFormat:
214 NSLocalizedString(@"DL from %d", "Inspector -> Peers tab -> peers"), toUs]];
216 [upDownComponents addObject: [NSString stringWithFormat:
217 NSLocalizedString(@"UL to %d", "Inspector -> Peers tab -> peers"), fromUs]];
218 [upDownComponents addObject: knownString];
220 connectedText = [connectedText stringByAppendingFormat: @": %@\n%@", [fromComponents componentsJoinedByString: @", "],
221 [upDownComponents componentsJoinedByString: @", "]];
224 connectedText = [connectedText stringByAppendingFormat: @"\n%@", knownString];
226 [fConnectedPeersField setStringValue: connectedText];
230 NSString * activeString;
231 if ([fTorrents count] == 1)
232 activeString = NSLocalizedString(@"Transfer Not Active", "Inspector -> Peers tab -> peers");
234 activeString = NSLocalizedString(@"Transfers Not Active", "Inspector -> Peers tab -> peers");
236 NSString * connectedText = [activeString stringByAppendingFormat: @"\n%@", knownString];
237 [fConnectedPeersField setStringValue: connectedText];
241 - (void) saveViewSize
243 [[NSUserDefaults standardUserDefaults] setFloat: NSHeight([[self view] frame]) forKey: @"InspectorContentHeightPeers"];
248 //if in the middle of animating, just stop and resize immediately
249 if (fWebSeedTableAnimation)
250 [self setWebSeedTableHidden: !fWebSeeds animate: NO];
258 - (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
260 if (tableView == fWebSeedTable)
261 return fWebSeeds ? [fWebSeeds count] : 0;
263 return fPeers ? [fPeers count] : 0;
266 - (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
268 if (tableView == fWebSeedTable)
270 NSString * ident = [column identifier];
271 NSDictionary * webSeed = [fWebSeeds objectAtIndex: row];
273 if ([ident isEqualToString: @"DL From"])
276 return (rate = [webSeed objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate doubleValue]] : @"";
279 return [webSeed objectForKey: @"Address"];
283 NSString * ident = [column identifier];
284 NSDictionary * peer = [fPeers objectAtIndex: row];
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"])
295 return (rate = [peer objectForKey: @"UL To Rate"]) ? [NSString stringForSpeedAbbrev: [rate doubleValue]] : @"";
297 else if ([ident isEqualToString: @"DL From"])
300 return (rate = [peer objectForKey: @"DL From Rate"]) ? [NSString stringForSpeedAbbrev: [rate doubleValue]] : @"";
303 return [peer objectForKey: @"IP"];
307 - (void) tableView: (NSTableView *) tableView willDisplayCell: (id) cell forTableColumn: (NSTableColumn *) tableColumn
310 if (tableView == fPeerTable)
312 NSString * ident = [tableColumn identifier];
314 if ([ident isEqualToString: @"Progress"])
316 NSDictionary * peer = [fPeers objectAtIndex: row];
317 [(PeerProgressIndicatorCell *)cell setSeed: [[peer objectForKey: @"Seed"] boolValue]];
322 - (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColumn *) tableColumn
324 if (tableView == fWebSeedTable)
328 [fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
329 [tableView reloadData];
336 [fPeers sortUsingDescriptors: [self peerSortDescriptors]];
337 [tableView reloadData];
342 - (BOOL) tableView: (NSTableView *) tableView shouldSelectRow: (NSInteger) row
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)
352 const BOOL multiple = [fTorrents count] > 1;
354 NSDictionary * peer = [fPeers objectAtIndex: row];
355 NSMutableArray * components = [NSMutableArray arrayWithCapacity: multiple ? 6 : 5];
358 [components addObject: [peer objectForKey: @"Name"]];
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];
369 if ([[peer objectForKey: @"Encryption"] boolValue])
370 [components addObject: NSLocalizedString(@"Encrypted Connection", "Inspector -> Peers tab -> table row tooltip")];
372 NSString * portString;
374 if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
375 portString = [NSString stringWithFormat: @"%d", port];
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]];
381 const NSInteger peerFrom = [[peer objectForKey: @"From"] integerValue];
384 case TR_PEER_FROM_TRACKER:
385 [components addObject: NSLocalizedString(@"From: tracker", "Inspector -> Peers tab -> table row tooltip")];
387 case TR_PEER_FROM_INCOMING:
388 [components addObject: NSLocalizedString(@"From: incoming connection", "Inspector -> Peers tab -> table row tooltip")];
390 case TR_PEER_FROM_RESUME:
391 [components addObject: NSLocalizedString(@"From: cache", "Inspector -> Peers tab -> table row tooltip")];
393 case TR_PEER_FROM_LPD:
394 [components addObject: NSLocalizedString(@"From: local peer discovery", "Inspector -> Peers tab -> table row tooltip")];
396 case TR_PEER_FROM_PEX:
397 [components addObject: NSLocalizedString(@"From: peer exchange", "Inspector -> Peers tab -> table row tooltip")];
399 case TR_PEER_FROM_DHT:
400 [components addObject: NSLocalizedString(@"From: distributed hash table", "Inspector -> Peers tab -> table row tooltip")];
402 case TR_PEER_FROM_LTEP:
403 [components addObject: NSLocalizedString(@"From: libtorrent extension protocol handshake",
404 "Inspector -> Peers tab -> table row tooltip")];
407 NSAssert1(NO, @"Peer from unknown source: %d", peerFrom);
410 //determing status strings from flags
411 NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
412 NSString * flags = [peer objectForKey: @"Flags"];
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")];
433 if ([statusArray count] > 0)
435 NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
436 [components addObject: [@"\n" stringByAppendingString: statusStrings]];
439 return [components componentsJoinedByString: @"\n"];
443 if ([fTorrents count] > 1)
444 return [[fWebSeeds objectAtIndex: row] objectForKey: @"Name"];
450 - (void) animationDidEnd: (NSAnimation *) animation
452 if (animation == fWebSeedTableAnimation)
454 [fWebSeedTableAnimation release];
455 fWebSeedTableAnimation = nil;
459 - (void) stopWebSeedAnimation
461 if (fWebSeedTableAnimation)
463 [fWebSeedTableAnimation stopAnimation]; // jumps to end frame
464 [fWebSeedTableAnimation release];
465 fWebSeedTableAnimation = nil;
471 @implementation InfoPeersViewController (Private)
475 BOOL hasWebSeeds = NO;
477 if ([fTorrents count] == 0)
481 [fPeerTable reloadData];
483 [fConnectedPeersField setStringValue: @""];
487 for (Torrent * torrent in fTorrents)
488 if ([torrent webSeedCount] > 0)
499 [fWebSeedTable reloadData];
501 [self setWebSeedTableHidden: !hasWebSeeds animate: YES];
506 - (void) setWebSeedTableHidden: (BOOL) hide animate: (BOOL) animate
508 if (animate && (![[self view] window] || ![[[self view] window] isVisible]))
511 if (fWebSeedTableAnimation)
513 [fWebSeedTableAnimation stopAnimation];
514 [fWebSeedTableAnimation release];
515 fWebSeedTableAnimation = nil;
518 NSRect webSeedFrame = [[fWebSeedTable enclosingScrollView] frame];
519 NSRect peerFrame = [[fPeerTable enclosingScrollView] frame];
523 CGFloat webSeedFrameMaxY = NSMaxY(webSeedFrame);
524 webSeedFrame.size.height = 0;
525 webSeedFrame.origin.y = webSeedFrameMaxY;
527 peerFrame.size.height = webSeedFrameMaxY - peerFrame.origin.y;
531 webSeedFrame.origin.y -= fWebSeedTableHeight - webSeedFrame.size.height;
532 webSeedFrame.size.height = fWebSeedTableHeight;
534 peerFrame.size.height = (webSeedFrame.origin.y - fSpaceBetweenWebSeedAndPeer) - peerFrame.origin.y;
537 [[fWebSeedTable enclosingScrollView] setHidden: NO]; //this is needed for some reason
539 //actually resize tables
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];
551 fWebSeedTableAnimation = [[NSViewAnimation alloc] initWithViewAnimations:
552 [NSArray arrayWithObjects: webSeedDict, peerDict, nil]];
553 [fWebSeedTableAnimation setDuration: 0.125];
554 [fWebSeedTableAnimation setAnimationBlockingMode: NSAnimationNonblocking];
555 [fWebSeedTableAnimation setDelegate: self];
557 [fWebSeedTableAnimation startAnimation];
561 [[fWebSeedTable enclosingScrollView] setFrame: webSeedFrame];
562 [[fPeerTable enclosingScrollView] setFrame: peerFrame];
566 - (NSArray *) peerSortDescriptors
568 NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
570 NSArray * oldDescriptors = [fPeerTable sortDescriptors];
571 BOOL useSecond = YES, asc = YES;
572 if ([oldDescriptors count] > 0)
574 NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
575 [descriptors addObject: descriptor];
577 if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
578 asc = [descriptor ascending];
581 //sort by IP after primary sort
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];