source: trunk/macosx/InfoPeersViewController.m

Last change on this file was 14706, checked in by mikedld, 6 years ago

Fix constraints issues on Yosemite when hiding web seed view (OS X)

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