source: trunk/macosx/Torrent.m

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

Get rid of some more deprecation warnings (OS X)

  • Property svn:keywords set to Date Rev Author Id
File size: 65.3 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 14667 2016-01-08 10:05:19Z mikedld $
3 *
4 * Copyright (c) 2006-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 "Torrent.h"
26#import "GroupsController.h"
27#import "FileListNode.h"
28#import "NSApplicationAdditions.h"
29#import "NSStringAdditions.h"
30#import "TrackerNode.h"
31
32#import "log.h"
33#import "transmission.h" // required by utils.h
34#import "error.h"
35#import "utils.h" // tr_new()
36
37#define ETA_IDLE_DISPLAY_SEC (2*60)
38
39@interface Torrent (Private)
40
41- (id) initWithPath: (NSString *) path hash: (NSString *) hashString torrentStruct: (tr_torrent *) torrentStruct
42        magnetAddress: (NSString *) magnetAddress lib: (tr_session *) lib
43        groupValue: (NSNumber *) groupValue
44        removeWhenFinishSeeding: (NSNumber *) removeWhenFinishSeeding
45        downloadFolder: (NSString *) downloadFolder
46        legacyIncompleteFolder: (NSString *) incompleteFolder;
47
48- (void) createFileList;
49- (void) insertPathForComponents: (NSArray *) components withComponentIndex: (NSUInteger) componentIndex forParent: (FileListNode *) parent fileSize: (uint64_t) size
50    index: (NSInteger) index flatList: (NSMutableArray *) flatFileList;
51- (void) sortFileList: (NSMutableArray *) fileNodes;
52
53- (void) startQueue;
54- (void) completenessChange: (tr_completeness) status wasRunning: (BOOL) wasRunning;
55- (void) ratioLimitHit;
56- (void) idleLimitHit;
57- (void) metadataRetrieved;
58- (void)renameFinished: (BOOL) success nodes: (NSArray *) nodes completionHandler: (void (^)(BOOL)) completionHandler oldPath: (NSString *) oldPath newName: (NSString *) newName;
59
60- (BOOL) shouldShowEta;
61- (NSString *) etaString;
62
63- (void) setTimeMachineExclude: (BOOL) exclude;
64
65@end
66
67void startQueueCallback(tr_torrent * torrent, void * torrentData)
68{
69    dispatch_async(dispatch_get_main_queue(), ^{
70        [(Torrent *)torrentData startQueue];
71    });
72}
73
74void completenessChangeCallback(tr_torrent * torrent, tr_completeness status, bool wasRunning, void * torrentData)
75{
76    dispatch_async(dispatch_get_main_queue(), ^{
77        [(Torrent *)torrentData completenessChange: status wasRunning: wasRunning];
78    });
79}
80
81void ratioLimitHitCallback(tr_torrent * torrent, void * torrentData)
82{
83    dispatch_async(dispatch_get_main_queue(), ^{
84        [(Torrent *)torrentData ratioLimitHit];
85    });
86}
87
88void idleLimitHitCallback(tr_torrent * torrent, void * torrentData)
89{
90    dispatch_async(dispatch_get_main_queue(), ^{
91        [(Torrent *)torrentData idleLimitHit];
92    });
93}
94
95void metadataCallback(tr_torrent * torrent, void * torrentData)
96{
97    dispatch_async(dispatch_get_main_queue(), ^{
98        [(Torrent *)torrentData metadataRetrieved];
99    });
100}
101
102void renameCallback(tr_torrent * torrent, const char * oldPathCharString, const char * newNameCharString, int error, void * contextInfo)
103{
104    @autoreleasepool {
105        NSString * oldPath = [NSString stringWithUTF8String: oldPathCharString];
106        NSString * newName = [NSString stringWithUTF8String: newNameCharString];
107       
108        dispatch_async(dispatch_get_main_queue(), ^{
109            NSDictionary * contextDict = [(NSDictionary *)contextInfo autorelease];
110            Torrent * torrentObject = [contextDict objectForKey: @"Torrent"];
111            [torrentObject renameFinished: error == 0 nodes: [contextDict objectForKey: @"Nodes"] completionHandler: [contextDict objectForKey: @"CompletionHandler"] oldPath: oldPath newName: newName];
112        });
113    }
114}
115
116bool trashDataFile(const char * filename, tr_error ** error)
117{
118    if (filename == NULL)
119        return false;
120
121    @autoreleasepool
122    {
123        NSError * localError;
124        if (![Torrent trashFile: [NSString stringWithUTF8String: filename] error: &localError])
125        {
126            tr_error_set_literal(error, [localError code], [[localError description] UTF8String]);
127            return false;
128        }
129    }
130
131    return true;
132}
133
134@implementation Torrent
135
136#warning remove ivars in header when 64-bit only (or it compiles in 32-bit mode)
137@synthesize removeWhenFinishSeeding = fRemoveWhenFinishSeeding;
138
139- (id) initWithPath: (NSString *) path location: (NSString *) location deleteTorrentFile: (BOOL) torrentDelete
140        lib: (tr_session *) lib
141{
142    self = [self initWithPath: path hash: nil torrentStruct: NULL magnetAddress: nil lib: lib
143            groupValue: nil
144            removeWhenFinishSeeding: nil
145            downloadFolder: location
146            legacyIncompleteFolder: nil];
147   
148    if (self)
149    {
150        if (torrentDelete && ![[self torrentLocation] isEqualToString: path])
151            [Torrent trashFile: path error: nil];
152    }
153    return self;
154}
155
156- (id) initWithTorrentStruct: (tr_torrent *) torrentStruct location: (NSString *) location lib: (tr_session *) lib
157{
158    self = [self initWithPath: nil hash: nil torrentStruct: torrentStruct magnetAddress: nil lib: lib
159            groupValue: nil
160            removeWhenFinishSeeding: nil
161            downloadFolder: location
162            legacyIncompleteFolder: nil];
163   
164    return self;
165}
166
167- (id) initWithMagnetAddress: (NSString *) address location: (NSString *) location lib: (tr_session *) lib
168{
169    self = [self initWithPath: nil hash: nil torrentStruct: nil magnetAddress: address
170            lib: lib groupValue: nil
171            removeWhenFinishSeeding: nil
172            downloadFolder: location legacyIncompleteFolder: nil];
173   
174    return self;
175}
176
177- (id) initWithHistory: (NSDictionary *) history lib: (tr_session *) lib forcePause: (BOOL) pause
178{
179    self = [self initWithPath: [history objectForKey: @"InternalTorrentPath"]
180                hash: [history objectForKey: @"TorrentHash"]
181                torrentStruct: NULL
182                magnetAddress: nil
183                lib: lib
184                groupValue: [history objectForKey: @"GroupValue"]
185                removeWhenFinishSeeding: [history objectForKey: @"RemoveWhenFinishSeeding"]
186                downloadFolder: [history objectForKey: @"DownloadFolder"] //upgrading from versions < 1.80
187                legacyIncompleteFolder: [[history objectForKey: @"UseIncompleteFolder"] boolValue] //upgrading from versions < 1.80
188                                        ? [history objectForKey: @"IncompleteFolder"] : nil];
189   
190    if (self)
191    {
192        //start transfer
193        NSNumber * active;
194        if (!pause && (active = [history objectForKey: @"Active"]) && [active boolValue])
195        {
196            fStat = tr_torrentStat(fHandle);
197            [self startTransferNoQueue];
198        }
199       
200        //upgrading from versions < 1.30: get old added, activity, and done dates
201        NSDate * date;
202        if ((date = [history objectForKey: @"Date"]))
203            tr_torrentSetAddedDate(fHandle, [date timeIntervalSince1970]);
204        if ((date = [history objectForKey: @"DateActivity"]))
205            tr_torrentSetActivityDate(fHandle, [date timeIntervalSince1970]);
206        if ((date = [history objectForKey: @"DateCompleted"]))
207            tr_torrentSetDoneDate(fHandle, [date timeIntervalSince1970]);
208       
209        //upgrading from versions < 1.60: get old stop ratio settings
210        NSNumber * ratioSetting;
211        if ((ratioSetting = [history objectForKey: @"RatioSetting"]))
212        {
213            switch ([ratioSetting intValue])
214            {
215                case NSOnState: [self setRatioSetting: TR_RATIOLIMIT_SINGLE]; break;
216                case NSOffState: [self setRatioSetting: TR_RATIOLIMIT_UNLIMITED]; break;
217                case NSMixedState: [self setRatioSetting: TR_RATIOLIMIT_GLOBAL]; break;
218            }
219        }
220        NSNumber * ratioLimit;
221        if ((ratioLimit = [history objectForKey: @"RatioLimit"]))
222            [self setRatioLimit: [ratioLimit floatValue]];
223    }
224    return self;
225}
226
227- (NSDictionary *) history
228{
229    return [NSDictionary dictionaryWithObjectsAndKeys:
230            [self torrentLocation], @"InternalTorrentPath",
231            [self hashString], @"TorrentHash",
232            [NSNumber numberWithBool: [self isActive]], @"Active",
233            [NSNumber numberWithBool: [self waitingToStart]], @"WaitToStart",
234            [NSNumber numberWithInt: fGroupValue], @"GroupValue",
235            [NSNumber numberWithBool: fRemoveWhenFinishSeeding], @"RemoveWhenFinishSeeding", nil];
236}
237
238- (void) dealloc
239{
240    [[NSNotificationCenter defaultCenter] removeObserver: self];
241   
242    if (fFileStat)
243        tr_torrentFilesFree(fFileStat, [self fileCount]);
244   
245    [fPreviousFinishedIndexes release];
246    [fPreviousFinishedIndexesDate release];
247   
248    [fHashString release];
249   
250    [fIcon release];
251   
252    [fFileList release];
253    [fFlatFileList release];
254   
255    [super dealloc];
256}
257
258- (NSString *) description
259{
260    return [@"Torrent: " stringByAppendingString: [self name]];
261}
262
263- (id) copyWithZone: (NSZone *) zone
264{
265    return [self retain];
266}
267
268- (void) closeRemoveTorrent: (BOOL) trashFiles
269{
270    //allow the file to be indexed by Time Machine
271    [self setTimeMachineExclude: NO];
272   
273    tr_torrentRemove(fHandle, trashFiles, trashDataFile);
274}
275
276- (void) changeDownloadFolderBeforeUsing: (NSString *) folder determinationType: (TorrentDeterminationType) determinationType
277{
278    //if data existed in original download location, unexclude it before changing the location
279    [self setTimeMachineExclude: NO];
280   
281    tr_torrentSetDownloadDir(fHandle, [folder UTF8String]);
282       
283    fDownloadFolderDetermination = determinationType;
284}
285
286- (NSString *) currentDirectory
287{
288    return [NSString stringWithUTF8String: tr_torrentGetCurrentDir(fHandle)];
289}
290
291- (void) getAvailability: (int8_t *) tab size: (NSInteger) size
292{
293    tr_torrentAvailability(fHandle, tab, size);
294}
295
296- (void) getAmountFinished: (float *) tab size: (NSInteger) size
297{
298    tr_torrentAmountFinished(fHandle, tab, size);
299}
300
301- (NSIndexSet *) previousFinishedPieces
302{
303    //if the torrent hasn't been seen in a bit, and therefore hasn't been refreshed, return nil
304    if (fPreviousFinishedIndexesDate && [fPreviousFinishedIndexesDate timeIntervalSinceNow] > -2.0)
305        return fPreviousFinishedIndexes;
306    else
307        return nil;
308}
309
310- (void) setPreviousFinishedPieces: (NSIndexSet *) indexes
311{
312    [fPreviousFinishedIndexes release];
313    fPreviousFinishedIndexes = [indexes retain];
314   
315    [fPreviousFinishedIndexesDate release];
316    fPreviousFinishedIndexesDate = indexes != nil ? [[NSDate alloc] init] : nil;
317}
318
319- (void) update
320{
321    //get previous stalled value before update
322    const BOOL wasStalled = fStat != NULL && [self isStalled];
323   
324    fStat = tr_torrentStat(fHandle);
325   
326    //make sure the "active" filter is updated when stalled-ness changes
327    if (wasStalled != [self isStalled])
328        //posting asynchronously with coalescing to prevent stack overflow on lots of torrents changing state at the same time
329        [[NSNotificationQueue defaultQueue] enqueueNotification: [NSNotification notificationWithName: @"UpdateQueue" object: self]
330                                                   postingStyle: NSPostASAP
331                                                   coalesceMask: NSNotificationCoalescingOnName
332                                                       forModes: nil];
333
334    //when the torrent is first loaded, update the time machine exclusion
335    if (!fTimeMachineExcludeInitialized)
336        [self updateTimeMachineExclude];
337}
338
339- (void) startTransferIgnoringQueue: (BOOL) ignoreQueue
340{
341    if ([self alertForRemainingDiskSpace])
342    {
343        ignoreQueue ? tr_torrentStartNow(fHandle) : tr_torrentStart(fHandle);
344        [self update];
345       
346        //capture, specifically, stop-seeding settings changing to unlimited
347        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
348    }
349}
350
351- (void) startTransferNoQueue
352{
353    [self startTransferIgnoringQueue: YES];
354}
355
356- (void) startTransfer
357{
358    [self startTransferIgnoringQueue: NO];
359}
360
361- (void) stopTransfer
362{
363    tr_torrentStop(fHandle);
364    [self update];
365}
366
367- (void) sleep
368{
369    if ((fResumeOnWake = [self isActive]))
370        tr_torrentStop(fHandle);
371}
372
373- (void) wakeUp
374{
375    if (fResumeOnWake)
376    {
377        tr_logAddNamedInfo( fInfo->name, "restarting because of wakeUp" );
378        tr_torrentStart(fHandle);
379    }
380}
381
382- (NSInteger) queuePosition
383{
384    return fStat->queuePosition;
385}
386
387- (void) setQueuePosition: (NSUInteger) index
388{
389    tr_torrentSetQueuePosition(fHandle, index);
390}
391
392- (void) manualAnnounce
393{
394    tr_torrentManualUpdate(fHandle);
395}
396
397- (BOOL) canManualAnnounce
398{
399    return tr_torrentCanManualUpdate(fHandle);
400}
401
402- (void) resetCache
403{
404    tr_torrentVerify(fHandle, NULL, NULL);
405    [self update];
406}
407
408- (BOOL) isMagnet
409{
410    return !tr_torrentHasMetadata(fHandle);
411}
412
413- (NSString *) magnetLink
414{
415    return [NSString stringWithUTF8String: tr_torrentGetMagnetLink(fHandle)];
416}
417
418- (CGFloat) ratio
419{
420    return fStat->ratio;
421}
422
423- (tr_ratiolimit) ratioSetting
424{
425    return tr_torrentGetRatioMode(fHandle);
426}
427
428- (void) setRatioSetting: (tr_ratiolimit) setting
429{
430    tr_torrentSetRatioMode(fHandle, setting);
431}
432
433- (CGFloat) ratioLimit
434{
435    return tr_torrentGetRatioLimit(fHandle);
436}
437
438- (void) setRatioLimit: (CGFloat) limit
439{
440    NSParameterAssert(limit >= 0);
441   
442    tr_torrentSetRatioLimit(fHandle, limit);
443}
444
445- (CGFloat) progressStopRatio
446{
447    return fStat->seedRatioPercentDone;
448}
449
450- (tr_idlelimit) idleSetting
451{
452    return tr_torrentGetIdleMode(fHandle);
453}
454
455- (void) setIdleSetting: (tr_idlelimit) setting
456{
457    tr_torrentSetIdleMode(fHandle, setting);
458}
459
460- (NSUInteger) idleLimitMinutes
461{
462    return tr_torrentGetIdleLimit(fHandle);
463}
464
465- (void) setIdleLimitMinutes: (NSUInteger) limit
466{
467    NSParameterAssert(limit > 0);
468   
469    tr_torrentSetIdleLimit(fHandle, limit);
470}
471
472- (BOOL) usesSpeedLimit: (BOOL) upload
473{
474    return tr_torrentUsesSpeedLimit(fHandle, upload ? TR_UP : TR_DOWN);
475}
476
477- (void) setUseSpeedLimit: (BOOL) use upload: (BOOL) upload
478{
479    tr_torrentUseSpeedLimit(fHandle, upload ? TR_UP : TR_DOWN, use);
480}
481
482- (NSInteger) speedLimit: (BOOL) upload
483{
484    return tr_torrentGetSpeedLimit_KBps(fHandle, upload ? TR_UP : TR_DOWN);
485}
486
487- (void) setSpeedLimit: (NSInteger) limit upload: (BOOL) upload
488{
489    tr_torrentSetSpeedLimit_KBps(fHandle, upload ? TR_UP : TR_DOWN, limit);
490}
491
492- (BOOL) usesGlobalSpeedLimit
493{
494    return tr_torrentUsesSessionLimits(fHandle);
495}
496
497- (void) setUseGlobalSpeedLimit: (BOOL) use
498{
499    tr_torrentUseSessionLimits(fHandle, use);
500}
501
502- (void) setMaxPeerConnect: (uint16_t) count
503{
504    NSParameterAssert(count > 0);
505   
506    tr_torrentSetPeerLimit(fHandle, count);
507}
508
509- (uint16_t) maxPeerConnect
510{
511    return tr_torrentGetPeerLimit(fHandle);
512}
513- (BOOL) waitingToStart
514{
515    return fStat->activity == TR_STATUS_DOWNLOAD_WAIT || fStat->activity == TR_STATUS_SEED_WAIT;
516}
517
518- (tr_priority_t) priority
519{
520    return tr_torrentGetPriority(fHandle);
521}
522
523- (void) setPriority: (tr_priority_t) priority
524{
525    return tr_torrentSetPriority(fHandle, priority);
526}
527
528+ (BOOL) trashFile: (NSString *) path error: (NSError **) error
529{
530    //attempt to move to trash
531    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
532                                                      source: [path stringByDeletingLastPathComponent] destination: @""
533                                                       files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
534    {
535        //if cannot trash, just delete it (will work if it's on a remote volume)
536        NSError * localError;
537        if (![[NSFileManager defaultManager] removeItemAtPath: path error: &localError])
538        {
539            NSLog(@"old Could not trash %@: %@", path, [localError localizedDescription]);
540            if (error != nil)
541                *error = localError;
542            return NO;
543        }
544        else
545        {
546            NSLog(@"old removed %@", path);
547        }
548    }
549
550    return YES;
551}
552
553- (void) moveTorrentDataFileTo: (NSString *) folder
554{
555    NSString * oldFolder = [self currentDirectory];
556    if ([oldFolder isEqualToString: folder])
557        return;
558   
559    //check if moving inside itself
560    NSArray * oldComponents = [oldFolder pathComponents],
561            * newComponents = [folder pathComponents];
562    const NSInteger oldCount = [oldComponents count];
563   
564    if (oldCount < [newComponents count] && [[newComponents objectAtIndex: oldCount] isEqualToString: [self name]]
565        && [folder hasPrefix: oldFolder])
566    {
567        NSAlert * alert = [[NSAlert alloc] init];
568        [alert setMessageText: NSLocalizedString(@"A folder cannot be moved to inside itself.",
569                                                    "Move inside itself alert -> title")];
570        [alert setInformativeText: [NSString stringWithFormat:
571                        NSLocalizedString(@"The move operation of \"%@\" cannot be done.",
572                                            "Move inside itself alert -> message"), [self name]]];
573        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move inside itself alert -> button")];
574       
575        [alert runModal];
576        [alert release];
577       
578        return;
579    }
580   
581    volatile int status;
582    tr_torrentSetLocation(fHandle, [folder UTF8String], YES, NULL, &status);
583   
584    while (status == TR_LOC_MOVING) //block while moving (for now)
585        [NSThread sleepForTimeInterval: 0.05];
586   
587    if (status == TR_LOC_DONE)
588        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateStats" object: nil];
589    else
590    {
591        NSAlert * alert = [[NSAlert alloc] init];
592        [alert setMessageText: NSLocalizedString(@"There was an error moving the data file.", "Move error alert -> title")];
593        [alert setInformativeText: [NSString stringWithFormat:
594                NSLocalizedString(@"The move operation of \"%@\" cannot be done.", "Move error alert -> message"), [self name]]];
595        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move error alert -> button")];
596       
597        [alert runModal];
598        [alert release];
599    }
600   
601    [self updateTimeMachineExclude];
602}
603
604- (void) copyTorrentFileTo: (NSString *) path
605{
606    [[NSFileManager defaultManager] copyItemAtPath: [self torrentLocation] toPath: path error: NULL];
607}
608
609- (BOOL) alertForRemainingDiskSpace
610{
611    if ([self allDownloaded] || ![fDefaults boolForKey: @"WarningRemainingSpace"])
612        return YES;
613   
614    NSString * downloadFolder = [self currentDirectory];
615    NSDictionary * systemAttributes;
616    if ((systemAttributes = [[NSFileManager defaultManager] attributesOfFileSystemForPath: downloadFolder error: NULL]))
617    {
618        const uint64_t remainingSpace = [[systemAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue];
619       
620        //if the remaining space is greater than the size left, then there is enough space regardless of preallocation
621        if (remainingSpace < [self sizeLeft] && remainingSpace < tr_torrentGetBytesLeftToAllocate(fHandle))
622        {
623            NSString * volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: downloadFolder] objectAtIndex: 0];
624           
625            NSAlert * alert = [[NSAlert alloc] init];
626            [alert setMessageText: [NSString stringWithFormat:
627                                    NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
628                                        "Torrent disk space alert -> title"), [self name]]];
629            [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"The transfer will be paused."
630                                        " Clear up space on %@ or deselect files in the torrent inspector to continue.",
631                                        "Torrent disk space alert -> message"), volumeName]];
632            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent disk space alert -> button")];
633            [alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent disk space alert -> button")];
634           
635            [alert setShowsSuppressionButton: YES];
636            [[alert suppressionButton] setTitle: NSLocalizedString(@"Do not check disk space again",
637                                                    "Torrent disk space alert -> button")];
638
639            const NSInteger result = [alert runModal];
640            if ([[alert suppressionButton] state] == NSOnState)
641                [fDefaults setBool: NO forKey: @"WarningRemainingSpace"];
642            [alert release];
643           
644            return result != NSAlertFirstButtonReturn;
645        }
646    }
647    return YES;
648}
649
650- (NSImage *) icon
651{
652    if ([self isMagnet])
653        return [NSImage imageNamed: @"Magnet"];
654   
655    if (!fIcon)
656        fIcon = [self isFolder] ? [[NSImage imageNamed: NSImageNameFolder] retain]
657                                : [[[NSWorkspace sharedWorkspace] iconForFileType: [[self name] pathExtension]] retain];
658    return fIcon;
659}
660
661- (NSString *) name
662{
663    return fInfo->name != NULL ? [NSString stringWithUTF8String: fInfo->name] : fHashString;
664}
665
666- (BOOL) isFolder
667{
668    return fInfo->isFolder;
669}
670
671- (uint64_t) size
672{
673    return fInfo->totalSize;
674}
675
676- (uint64_t) sizeLeft
677{
678    return fStat->leftUntilDone;
679}
680
681- (NSMutableArray *) allTrackerStats
682{
683    int count;
684    tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
685   
686    NSMutableArray * trackers = [NSMutableArray arrayWithCapacity: (count > 0 ? count + (stats[count-1].tier + 1) : 0)];
687   
688    int prevTier = -1;
689    for (int i=0; i < count; ++i)
690    {
691        if (stats[i].tier != prevTier)
692        {
693            [trackers addObject: @{ @"Tier" : @(stats[i].tier + 1), @"Name" : [self name] }];
694            prevTier = stats[i].tier;
695        }
696       
697        TrackerNode * tracker = [[TrackerNode alloc] initWithTrackerStat: &stats[i] torrent: self];
698        [trackers addObject: tracker];
699        [tracker release];
700    }
701   
702    tr_torrentTrackersFree(stats, count);
703    return trackers;
704}
705
706- (NSArray *) allTrackersFlat
707{
708    NSMutableArray * allTrackers = [NSMutableArray arrayWithCapacity: fInfo->trackerCount];
709   
710    for (NSInteger i=0; i < fInfo->trackerCount; i++)
711        [allTrackers addObject: [NSString stringWithUTF8String: fInfo->trackers[i].announce]];
712   
713    return allTrackers;
714}
715
716- (BOOL) addTrackerToNewTier: (NSString *) tracker
717{
718    tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
719   
720    if ([tracker rangeOfString: @"://"].location == NSNotFound)
721        tracker = [@"http://" stringByAppendingString: tracker];
722   
723    //recreate the tracker structure
724    const int oldTrackerCount = fInfo->trackerCount;
725    tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, oldTrackerCount+1);
726    for (NSUInteger i=0; i < oldTrackerCount; ++i)
727        trackerStructs[i] = fInfo->trackers[i];
728   
729    trackerStructs[oldTrackerCount].announce = (char *)[tracker UTF8String];
730    trackerStructs[oldTrackerCount].tier = trackerStructs[oldTrackerCount-1].tier + 1;
731    trackerStructs[oldTrackerCount].id = oldTrackerCount;
732   
733    const BOOL success = tr_torrentSetAnnounceList(fHandle, trackerStructs, oldTrackerCount+1);
734    tr_free(trackerStructs);
735   
736    return success;
737}
738
739- (void) removeTrackers: (NSSet *) trackers
740{
741    //recreate the tracker structure
742    tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, fInfo->trackerCount);
743   
744    NSUInteger newCount = 0;
745    for (NSUInteger i = 0; i < fInfo->trackerCount; i++)
746    {
747        if (![trackers containsObject: [NSString stringWithUTF8String: fInfo->trackers[i].announce]])
748            trackerStructs[newCount++] = fInfo->trackers[i];
749    }
750   
751    const BOOL success = tr_torrentSetAnnounceList(fHandle, trackerStructs, newCount);
752    NSAssert(success, @"Removing tracker addresses failed");
753   
754    tr_free(trackerStructs);
755}
756
757- (NSString *) comment
758{
759    return fInfo->comment ? [NSString stringWithUTF8String: fInfo->comment] : @"";
760}
761
762- (NSString *) creator
763{
764    return fInfo->creator ? [NSString stringWithUTF8String: fInfo->creator] : @"";
765}
766
767- (NSDate *) dateCreated
768{
769    NSInteger date = fInfo->dateCreated;
770    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
771}
772
773- (NSInteger) pieceSize
774{
775    return fInfo->pieceSize;
776}
777
778- (NSInteger) pieceCount
779{
780    return fInfo->pieceCount;
781}
782
783- (NSString *) hashString
784{
785    return fHashString;
786}
787
788- (BOOL) privateTorrent
789{
790    return fInfo->isPrivate;
791}
792
793- (NSString *) torrentLocation
794{
795    return fInfo->torrent ? [NSString stringWithUTF8String: fInfo->torrent] : @"";
796}
797
798- (NSString *) dataLocation
799{
800    if ([self isMagnet])
801        return nil;
802   
803    if ([self isFolder])
804    {
805        NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: [self name]];
806       
807        if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
808            return nil;
809       
810        return dataLocation;
811    }
812    else
813    {
814        char * location = tr_torrentFindFile(fHandle, 0);
815        if (location == NULL)
816            return nil;
817       
818        NSString * dataLocation = [NSString stringWithUTF8String: location];
819        free(location);
820       
821        return dataLocation;
822    }
823}
824
825- (NSString *) fileLocation: (FileListNode *) node
826{
827    if ([node isFolder])
828    {
829        NSString * basePath = [[node path] stringByAppendingPathComponent: [node name]];
830        NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: basePath];
831       
832        if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
833            return nil;
834       
835        return dataLocation;
836    }
837    else
838    {
839        char * location = tr_torrentFindFile(fHandle, [[node indexes] firstIndex]);
840        if (location == NULL)
841            return nil;
842       
843        NSString * dataLocation = [NSString stringWithUTF8String: location];
844        free(location);
845       
846        return dataLocation;
847    }
848}
849
850- (void) renameTorrent: (NSString *) newName completionHandler: (void (^)(BOOL didRename)) completionHandler
851{
852    NSParameterAssert(newName != nil);
853    NSParameterAssert(![newName isEqualToString: @""]);
854   
855    NSDictionary * contextInfo = [@{ @"Torrent" : self, @"CompletionHandler" : [[completionHandler copy] autorelease] } retain];
856   
857    tr_torrentRenamePath(fHandle, fInfo->name, [newName UTF8String], renameCallback, contextInfo);
858}
859
860- (void) renameFileNode: (FileListNode *) node withName: (NSString *) newName completionHandler: (void (^)(BOOL didRename)) completionHandler
861{
862    NSParameterAssert([node torrent] == self);
863    NSParameterAssert(newName != nil);
864    NSParameterAssert(![newName isEqualToString: @""]);
865   
866    NSDictionary * contextInfo = [@{ @"Torrent" : self, @"Nodes" : @[ node ], @"CompletionHandler" : [[completionHandler copy] autorelease] } retain];
867   
868    NSString * oldPath = [[node path] stringByAppendingPathComponent: [node name]];
869    tr_torrentRenamePath(fHandle, [oldPath UTF8String], [newName UTF8String], renameCallback, contextInfo);
870}
871
872- (CGFloat) progress
873{
874    return fStat->percentComplete;
875}
876
877- (CGFloat) progressDone
878{
879    return fStat->percentDone;
880}
881
882- (CGFloat) progressLeft
883{
884    if ([self size] == 0) //magnet links
885        return 0.0;
886   
887    return (CGFloat)[self sizeLeft] / [self size];
888}
889
890- (CGFloat) checkingProgress
891{
892    return fStat->recheckProgress;
893}
894
895- (CGFloat) availableDesired
896{
897    return (CGFloat)fStat->desiredAvailable / [self sizeLeft];
898}
899
900- (BOOL) isActive
901{
902    return fStat->activity != TR_STATUS_STOPPED && fStat->activity != TR_STATUS_DOWNLOAD_WAIT && fStat->activity != TR_STATUS_SEED_WAIT;
903}
904
905- (BOOL) isSeeding
906{
907    return fStat->activity == TR_STATUS_SEED;
908}
909
910- (BOOL) isChecking
911{
912    return fStat->activity == TR_STATUS_CHECK || fStat->activity == TR_STATUS_CHECK_WAIT;
913}
914
915- (BOOL) isCheckingWaiting
916{
917    return fStat->activity == TR_STATUS_CHECK_WAIT;
918}
919
920- (BOOL) allDownloaded
921{
922    return [self sizeLeft] == 0 && ![self isMagnet];
923}
924
925- (BOOL) isComplete
926{
927    return [self progress] >= 1.0;
928}
929
930- (BOOL) isFinishedSeeding
931{
932    return fStat->finished;
933}
934
935- (BOOL) isError
936{
937    return fStat->error == TR_STAT_LOCAL_ERROR;
938}
939
940- (BOOL) isAnyErrorOrWarning
941{
942    return fStat->error != TR_STAT_OK;
943}
944
945- (NSString *) errorMessage
946{
947    if (![self isAnyErrorOrWarning])
948        return @"";
949   
950    NSString * error;
951    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
952        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
953        error = [NSString stringWithFormat: @"(%@)", NSLocalizedString(@"unreadable error", "Torrent -> error string unreadable")];
954   
955    //libtransmission uses "Set Location", Mac client uses "Move data file to..." - very hacky!
956    error = [error stringByReplacingOccurrencesOfString: @"Set Location" withString: [@"Move Data File To" stringByAppendingEllipsis]];
957   
958    return error;
959}
960
961- (NSArray *) peers
962{
963    int totalPeers;
964    tr_peer_stat * peers = tr_torrentPeers(fHandle, &totalPeers);
965   
966    NSMutableArray * peerDicts = [NSMutableArray arrayWithCapacity: totalPeers];
967   
968    for (int i = 0; i < totalPeers; i++)
969    {
970        tr_peer_stat * peer = &peers[i];
971        NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 12];
972       
973        [dict setObject: [self name] forKey: @"Name"];
974        [dict setObject: [NSNumber numberWithInt: peer->from] forKey: @"From"];
975        [dict setObject: [NSString stringWithUTF8String: peer->addr] forKey: @"IP"];
976        [dict setObject: [NSNumber numberWithInt: peer->port] forKey: @"Port"];
977        [dict setObject: [NSNumber numberWithFloat: peer->progress] forKey: @"Progress"];
978        [dict setObject: [NSNumber numberWithBool: peer->isSeed] forKey: @"Seed"];
979        [dict setObject: [NSNumber numberWithBool: peer->isEncrypted] forKey: @"Encryption"];
980        [dict setObject: [NSNumber numberWithBool: peer->isUTP] forKey: @"uTP"];
981        [dict setObject: [NSString stringWithUTF8String: peer->client] forKey: @"Client"];
982        [dict setObject: [NSString stringWithUTF8String: peer->flagStr] forKey: @"Flags"];
983       
984        if (peer->isUploadingTo)
985            [dict setObject: [NSNumber numberWithDouble: peer->rateToPeer_KBps] forKey: @"UL To Rate"];
986        if (peer->isDownloadingFrom)
987            [dict setObject: [NSNumber numberWithDouble: peer->rateToClient_KBps] forKey: @"DL From Rate"];
988       
989        [peerDicts addObject: dict];
990    }
991   
992    tr_torrentPeersFree(peers, totalPeers);
993   
994    return peerDicts;
995}
996
997- (NSUInteger) webSeedCount
998{
999    return fInfo->webseedCount;
1000}
1001
1002- (NSArray *) webSeeds
1003{
1004    NSMutableArray * webSeeds = [NSMutableArray arrayWithCapacity: fInfo->webseedCount];
1005   
1006    double * dlSpeeds = tr_torrentWebSpeeds_KBps(fHandle);
1007   
1008    for (NSInteger i = 0; i < fInfo->webseedCount; i++)
1009    {
1010        NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 3];
1011       
1012        [dict setObject: [self name] forKey: @"Name"];
1013        [dict setObject: [NSString stringWithUTF8String: fInfo->webseeds[i]] forKey: @"Address"];
1014       
1015        if (dlSpeeds[i] != -1.0)
1016            [dict setObject: [NSNumber numberWithDouble: dlSpeeds[i]] forKey: @"DL From Rate"];
1017       
1018        [webSeeds addObject: dict];
1019    }
1020   
1021    tr_free(dlSpeeds);
1022   
1023    return webSeeds;
1024}
1025
1026- (NSString *) progressString
1027{
1028    if ([self isMagnet])
1029    {
1030        NSString * progressString = fStat->metadataPercentComplete > 0.0
1031                    ? [NSString stringWithFormat: NSLocalizedString(@"%@ of torrent metadata retrieved",
1032                        "Torrent -> progress string"), [NSString percentString: fStat->metadataPercentComplete longDecimals: YES]]
1033                    : NSLocalizedString(@"torrent metadata needed", "Torrent -> progress string");
1034       
1035        return [NSString stringWithFormat: @"%@ - %@", NSLocalizedString(@"Magnetized transfer", "Torrent -> progress string"),
1036                                            progressString];
1037    }
1038   
1039    NSString * string;
1040   
1041    if (![self allDownloaded])
1042    {
1043        CGFloat progress;
1044        if ([self isFolder] && [fDefaults boolForKey: @"DisplayStatusProgressSelected"])
1045        {
1046            string = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self totalSizeSelected]];
1047            progress = [self progressDone];
1048        }
1049        else
1050        {
1051            string = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self size]];
1052            progress = [self progress];
1053        }
1054       
1055        string = [string stringByAppendingFormat: @" (%@)", [NSString percentString: progress longDecimals: YES]];
1056    }
1057    else
1058    {
1059        NSString * downloadString;
1060        if (![self isComplete]) //only multifile possible
1061        {
1062            if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
1063                downloadString = [NSString stringWithFormat: NSLocalizedString(@"%@ selected", "Torrent -> progress string"),
1064                                    [NSString stringForFileSize: [self haveTotal]]];
1065            else
1066            {
1067                downloadString = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self size]];
1068                downloadString = [downloadString stringByAppendingFormat: @" (%@)",
1069                                    [NSString percentString: [self progress] longDecimals: YES]];
1070            }
1071        }
1072        else
1073            downloadString = [NSString stringForFileSize: [self size]];
1074       
1075        NSString * uploadString = [NSString stringWithFormat: NSLocalizedString(@"uploaded %@ (Ratio: %@)",
1076                                    "Torrent -> progress string"), [NSString stringForFileSize: [self uploadedTotal]],
1077                                    [NSString stringForRatio: [self ratio]]];
1078       
1079        string = [downloadString stringByAppendingFormat: @", %@", uploadString];
1080    }
1081   
1082    //add time when downloading or seed limit set
1083    if ([self shouldShowEta])
1084        string = [string stringByAppendingFormat: @" - %@", [self etaString]];
1085   
1086    return string;
1087}
1088
1089- (NSString *) statusString
1090{
1091    NSString * string;
1092   
1093    if ([self isAnyErrorOrWarning])
1094    {
1095        switch (fStat->error)
1096        {
1097            case TR_STAT_LOCAL_ERROR: string = NSLocalizedString(@"Error", "Torrent -> status string"); break;
1098            case TR_STAT_TRACKER_ERROR: string = NSLocalizedString(@"Tracker returned error", "Torrent -> status string"); break;
1099            case TR_STAT_TRACKER_WARNING: string = NSLocalizedString(@"Tracker returned warning", "Torrent -> status string"); break;
1100            default: NSAssert(NO, @"unknown error state");
1101        }
1102       
1103        NSString * errorString = [self errorMessage];
1104        if (errorString && ![errorString isEqualToString: @""])
1105            string = [string stringByAppendingFormat: @": %@", errorString];
1106    }
1107    else
1108    {
1109        switch (fStat->activity)
1110        {
1111            case TR_STATUS_STOPPED:
1112                if ([self isFinishedSeeding])
1113                    string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1114                else
1115                    string = NSLocalizedString(@"Paused", "Torrent -> status string");
1116                break;
1117           
1118            case TR_STATUS_DOWNLOAD_WAIT:
1119                string = [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis];
1120                break;
1121               
1122            case TR_STATUS_SEED_WAIT:
1123                string = [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1124                break;
1125           
1126            case TR_STATUS_CHECK_WAIT:
1127                string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1128                break;
1129
1130            case TR_STATUS_CHECK:
1131                string = [NSString stringWithFormat: @"%@ (%@)",
1132                            NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
1133                            [NSString percentString: [self checkingProgress] longDecimals: YES]];
1134                break;
1135
1136            case TR_STATUS_DOWNLOAD:
1137                if ([self totalPeersConnected] != 1)
1138                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
1139                                                    "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
1140                else
1141                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
1142                                                    "Torrent -> status string"), [self peersSendingToUs]];
1143               
1144                const NSInteger webSeedCount = fStat->webseedsSendingToUs;
1145                if (webSeedCount > 0)
1146                {
1147                    NSString * webSeedString;
1148                    if (webSeedCount == 1)
1149                        webSeedString = NSLocalizedString(@"web seed", "Torrent -> status string");
1150                    else
1151                        webSeedString = [NSString stringWithFormat: NSLocalizedString(@"%d web seeds", "Torrent -> status string"),
1152                                                                    webSeedCount];
1153                   
1154                    string = [string stringByAppendingFormat: @" + %@", webSeedString];
1155                }
1156               
1157                break;
1158
1159            case TR_STATUS_SEED:
1160                if ([self totalPeersConnected] != 1)
1161                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
1162                                                    [self peersGettingFromUs], [self totalPeersConnected]];
1163                else
1164                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
1165                                                    [self peersGettingFromUs]];
1166        }
1167       
1168        if ([self isStalled])
1169            string = [NSLocalizedString(@"Stalled", "Torrent -> status string") stringByAppendingFormat: @", %@", string];
1170    }
1171   
1172    //append even if error
1173    if ([self isActive] && ![self isChecking])
1174    {
1175        if (fStat->activity == TR_STATUS_DOWNLOAD)
1176            string = [string stringByAppendingFormat: @" - %@: %@, %@: %@",
1177                        NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1178                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1179        else
1180            string = [string stringByAppendingFormat: @" - %@: %@",
1181                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1182    }
1183   
1184    return string;
1185}
1186
1187- (NSString *) shortStatusString
1188{
1189    NSString * string;
1190   
1191    switch (fStat->activity)
1192    {
1193        case TR_STATUS_STOPPED:
1194            if ([self isFinishedSeeding])
1195                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1196            else
1197                string = NSLocalizedString(@"Paused", "Torrent -> status string");
1198            break;
1199       
1200        case TR_STATUS_DOWNLOAD_WAIT:
1201            string = [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis];
1202            break;
1203           
1204        case TR_STATUS_SEED_WAIT:
1205            string = [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1206            break;
1207
1208        case TR_STATUS_CHECK_WAIT:
1209            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1210            break;
1211
1212        case TR_STATUS_CHECK:
1213            string = [NSString stringWithFormat: @"%@ (%@)",
1214                        NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
1215                        [NSString percentString: [self checkingProgress] longDecimals: YES]];
1216            break;
1217       
1218        case TR_STATUS_DOWNLOAD:
1219            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1220                            NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1221                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1222            break;
1223       
1224        case TR_STATUS_SEED:
1225            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1226                            NSLocalizedString(@"Ratio", "Torrent -> status string"), [NSString stringForRatio: [self ratio]],
1227                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1228    }
1229   
1230    return string;
1231}
1232
1233- (NSString *) remainingTimeString
1234{
1235    if ([self shouldShowEta])
1236        return [self etaString];
1237    else
1238        return [self shortStatusString];
1239}
1240
1241- (NSString *) stateString
1242{
1243    switch (fStat->activity)
1244    {
1245        case TR_STATUS_STOPPED:
1246        case TR_STATUS_DOWNLOAD_WAIT:
1247        case TR_STATUS_SEED_WAIT:
1248        {
1249            NSString * string = NSLocalizedString(@"Paused", "Torrent -> status string");
1250           
1251            NSString * extra = nil;
1252            if ([self waitingToStart])
1253            {
1254                extra = fStat->activity == TR_STATUS_DOWNLOAD_WAIT
1255                        ? NSLocalizedString(@"Waiting to download", "Torrent -> status string")
1256                        : NSLocalizedString(@"Waiting to seed", "Torrent -> status string");
1257            }
1258            else if ([self isFinishedSeeding])
1259                extra = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1260            else;
1261       
1262            return extra ? [string stringByAppendingFormat: @" (%@)", extra] : string;
1263        }
1264       
1265        case TR_STATUS_CHECK_WAIT:
1266            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1267
1268        case TR_STATUS_CHECK:
1269            return [NSString stringWithFormat: @"%@ (%@)",
1270                    NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
1271                    [NSString percentString: [self checkingProgress] longDecimals: YES]];
1272
1273        case TR_STATUS_DOWNLOAD:
1274            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1275
1276        case TR_STATUS_SEED:
1277            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1278    }
1279}
1280
1281- (NSInteger) totalPeersConnected
1282{
1283    return fStat->peersConnected;
1284}
1285
1286- (NSInteger) totalPeersTracker
1287{
1288    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1289}
1290
1291- (NSInteger) totalPeersIncoming
1292{
1293    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1294}
1295
1296- (NSInteger) totalPeersCache
1297{
1298    return fStat->peersFrom[TR_PEER_FROM_RESUME];
1299}
1300
1301- (NSInteger) totalPeersPex
1302{
1303    return fStat->peersFrom[TR_PEER_FROM_PEX];
1304}
1305
1306- (NSInteger) totalPeersDHT
1307{
1308    return fStat->peersFrom[TR_PEER_FROM_DHT];
1309}
1310
1311- (NSInteger) totalPeersLocal
1312{
1313    return fStat->peersFrom[TR_PEER_FROM_LPD];
1314}
1315
1316- (NSInteger) totalPeersLTEP
1317{
1318    return fStat->peersFrom[TR_PEER_FROM_LTEP];
1319}
1320
1321- (NSInteger) peersSendingToUs
1322{
1323    return fStat->peersSendingToUs;
1324}
1325
1326- (NSInteger) peersGettingFromUs
1327{
1328    return fStat->peersGettingFromUs;
1329}
1330
1331- (CGFloat) downloadRate
1332{
1333    return fStat->pieceDownloadSpeed_KBps;
1334}
1335
1336- (CGFloat) uploadRate
1337{
1338    return fStat->pieceUploadSpeed_KBps;
1339}
1340
1341- (CGFloat) totalRate
1342{
1343    return [self downloadRate] + [self uploadRate];
1344}
1345
1346- (uint64_t) haveVerified
1347{
1348    return fStat->haveValid;
1349}
1350
1351- (uint64_t) haveTotal
1352{
1353    return [self haveVerified] + fStat->haveUnchecked;
1354}
1355
1356- (uint64_t) totalSizeSelected
1357{
1358    return fStat->sizeWhenDone;
1359}
1360
1361- (uint64_t) downloadedTotal
1362{
1363    return fStat->downloadedEver;
1364}
1365
1366- (uint64_t) uploadedTotal
1367{
1368    return fStat->uploadedEver;
1369}
1370
1371- (uint64_t) failedHash
1372{
1373    return fStat->corruptEver;
1374}
1375
1376- (NSInteger) groupValue
1377{
1378    return fGroupValue;
1379}
1380
1381- (void) setGroupValue: (NSInteger) groupValue determinationType: (TorrentDeterminationType) determinationType;
1382{
1383    if (groupValue != fGroupValue)
1384    {
1385        fGroupValue = groupValue;
1386        [[NSNotificationCenter defaultCenter] postNotificationName: kTorrentDidChangeGroupNotification object: self];
1387        }
1388    fGroupValueDetermination = determinationType;
1389}
1390
1391- (NSInteger) groupOrderValue
1392{
1393    return [[GroupsController groups] rowValueForIndex: fGroupValue];
1394}
1395
1396- (void) checkGroupValueForRemoval: (NSNotification *) notification
1397{
1398    if (fGroupValue != -1 && [[[notification userInfo] objectForKey: @"Index"] integerValue] == fGroupValue)
1399        fGroupValue = -1;
1400}
1401
1402- (NSArray *) fileList
1403{
1404    return fFileList;
1405}
1406
1407- (NSArray *) flatFileList
1408{
1409    return fFlatFileList;
1410}
1411
1412- (NSInteger) fileCount
1413{
1414    return fInfo->fileCount;
1415}
1416
1417- (void) updateFileStat
1418{
1419    if (fFileStat)
1420        tr_torrentFilesFree(fFileStat, [self fileCount]);
1421   
1422    fFileStat = tr_torrentFiles(fHandle, NULL);
1423}
1424
1425- (CGFloat) fileProgress: (FileListNode *) node
1426{
1427    if ([self fileCount] == 1 || [self isComplete])
1428        return [self progress];
1429   
1430    if (!fFileStat)
1431        [self updateFileStat];
1432   
1433    // #5501
1434    if ([node size] == 0) {
1435        return 1.0;
1436    }
1437   
1438    NSIndexSet * indexSet = [node indexes];
1439   
1440    if ([indexSet count] == 1)
1441        return fFileStat[[indexSet firstIndex]].progress;
1442   
1443    uint64_t have = 0;
1444    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1445        have += fFileStat[index].bytesCompleted;
1446   
1447    return (CGFloat)have / [node size];
1448}
1449
1450- (BOOL) canChangeDownloadCheckForFile: (NSUInteger) index
1451{
1452    NSAssert2(index < [self fileCount], @"Index %ld is greater than file count %ld", index, [self fileCount]);
1453   
1454    return [self canChangeDownloadCheckForFiles: [NSIndexSet indexSetWithIndex: index]];
1455}
1456
1457- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1458{
1459    if ([self fileCount] == 1 || [self isComplete])
1460        return NO;
1461   
1462    if (!fFileStat)
1463        [self updateFileStat];
1464   
1465    __block BOOL canChange = NO;
1466    [indexSet enumerateIndexesWithOptions: NSEnumerationConcurrent usingBlock: ^(NSUInteger index, BOOL *stop) {
1467        if (fFileStat[index].progress < 1.0)
1468        {
1469            canChange = YES;
1470            *stop = YES;
1471        }
1472    }];
1473    return canChange;
1474}
1475
1476- (NSInteger) checkForFiles: (NSIndexSet *) indexSet
1477{
1478    BOOL onState = NO, offState = NO;
1479    for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1480    {
1481        if (!fInfo->files[index].dnd || ![self canChangeDownloadCheckForFile: index])
1482            onState = YES;
1483        else
1484            offState = YES;
1485       
1486        if (onState && offState)
1487            return NSMixedState;
1488    }
1489    return onState ? NSOnState : NSOffState;
1490}
1491
1492- (void) setFileCheckState: (NSInteger) state forIndexes: (NSIndexSet *) indexSet
1493{
1494    NSUInteger count = [indexSet count];
1495    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1496    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1497        files[i] = index;
1498   
1499    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1500    free(files);
1501   
1502    [self update];
1503    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
1504}
1505
1506- (void) setFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1507{
1508    const NSUInteger count = [indexSet count];
1509    tr_file_index_t * files = tr_malloc(count * sizeof(tr_file_index_t));
1510    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1511        files[i] = index;
1512   
1513    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1514    tr_free(files);
1515}
1516
1517- (BOOL) hasFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1518{
1519    for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1520        if (priority == fInfo->files[index].priority && [self canChangeDownloadCheckForFile: index])
1521            return YES;
1522    return NO;
1523}
1524
1525- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1526{
1527    BOOL low = NO, normal = NO, high = NO;
1528    NSMutableSet * priorities = [NSMutableSet setWithCapacity: MIN([indexSet count], 3)];
1529   
1530    for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1531    {
1532        if (![self canChangeDownloadCheckForFile: index])
1533            continue;
1534       
1535        const tr_priority_t priority = fInfo->files[index].priority;
1536        switch (priority)
1537        {
1538            case TR_PRI_LOW:
1539                if (low)
1540                    continue;
1541                low = YES;
1542                break;
1543            case TR_PRI_NORMAL:
1544                if (normal)
1545                    continue;
1546                normal = YES;
1547                break;
1548            case TR_PRI_HIGH:
1549                if (high)
1550                    continue;
1551                high = YES;
1552                break;
1553            default:
1554                NSAssert2(NO, @"Unknown priority %d for file index %ld", priority, index);
1555        }
1556       
1557        [priorities addObject: [NSNumber numberWithInteger: priority]];
1558        if (low && normal && high)
1559            break;
1560    }
1561    return priorities;
1562}
1563
1564- (NSDate *) dateAdded
1565{
1566    const time_t date = fStat->addedDate;
1567    return [NSDate dateWithTimeIntervalSince1970: date];
1568}
1569
1570- (NSDate *) dateCompleted
1571{
1572    const time_t date = fStat->doneDate;
1573    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1574}
1575
1576- (NSDate *) dateActivity
1577{
1578    const time_t date = fStat->activityDate;
1579    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1580}
1581
1582- (NSDate *) dateActivityOrAdd
1583{
1584    NSDate * date = [self dateActivity];
1585    return date ? date : [self dateAdded];
1586}
1587
1588- (NSInteger) secondsDownloading
1589{
1590    return fStat->secondsDownloading;
1591}
1592
1593- (NSInteger) secondsSeeding
1594{
1595    return fStat->secondsSeeding;
1596}
1597
1598- (NSInteger) stalledMinutes
1599{
1600    if (fStat->idleSecs == -1)
1601        return -1;
1602   
1603    return fStat->idleSecs / 60;
1604}
1605
1606- (BOOL) isStalled
1607{
1608    return fStat->isStalled;
1609}
1610
1611- (void) updateTimeMachineExclude
1612{
1613    [self setTimeMachineExclude: ![self allDownloaded]];
1614}
1615
1616- (NSInteger) stateSortKey
1617{
1618    if (![self isActive]) //paused
1619    {
1620        if ([self waitingToStart])
1621            return 1;
1622        else
1623            return 0;
1624    }
1625    else if ([self isSeeding]) //seeding
1626        return 10;
1627    else //downloading
1628        return 20;
1629}
1630
1631- (NSString *) trackerSortKey
1632{
1633    int count;
1634    tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
1635   
1636    NSString * best = nil;
1637   
1638    for (int i=0; i < count; ++i)
1639    {
1640        NSString * tracker = [NSString stringWithUTF8String: stats[i].host];
1641        if (!best || [tracker localizedCaseInsensitiveCompare: best] == NSOrderedAscending)
1642            best = tracker;
1643    }
1644   
1645    tr_torrentTrackersFree(stats, count);
1646    return best;
1647}
1648
1649- (tr_torrent *) torrentStruct
1650{
1651    return fHandle;
1652}
1653
1654- (NSURL *) previewItemURL
1655{
1656    NSString * location = [self dataLocation];
1657    return location ? [NSURL fileURLWithPath: location] : nil;
1658}
1659
1660@end
1661
1662@implementation Torrent (Private)
1663
1664- (id) initWithPath: (NSString *) path hash: (NSString *) hashString torrentStruct: (tr_torrent *) torrentStruct
1665        magnetAddress: (NSString *) magnetAddress lib: (tr_session *) lib
1666        groupValue: (NSNumber *) groupValue
1667        removeWhenFinishSeeding: (NSNumber *) removeWhenFinishSeeding
1668        downloadFolder: (NSString *) downloadFolder
1669        legacyIncompleteFolder: (NSString *) incompleteFolder
1670{
1671    if (!(self = [super init]))
1672        return nil;
1673   
1674    fDefaults = [NSUserDefaults standardUserDefaults];
1675   
1676    if (torrentStruct)
1677        fHandle = torrentStruct;
1678    else
1679    {
1680        //set libtransmission settings for initialization
1681        tr_ctor * ctor = tr_ctorNew(lib);
1682       
1683        tr_ctorSetPaused(ctor, TR_FORCE, YES);
1684        if (downloadFolder)
1685            tr_ctorSetDownloadDir(ctor, TR_FORCE, [downloadFolder UTF8String]);
1686        if (incompleteFolder)
1687            tr_ctorSetIncompleteDir(ctor, [incompleteFolder UTF8String]);
1688       
1689        tr_parse_result result = TR_PARSE_ERR;
1690        if (path)
1691            result = tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1692       
1693        if (result != TR_PARSE_OK && magnetAddress)
1694            result = tr_ctorSetMetainfoFromMagnetLink(ctor, [magnetAddress UTF8String]);
1695       
1696        //backup - shouldn't be needed after upgrade to 1.70
1697        if (result != TR_PARSE_OK && hashString)
1698            result = tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1699       
1700        if (result == TR_PARSE_OK)
1701            fHandle = tr_torrentNew(ctor, NULL, NULL);
1702       
1703        tr_ctorFree(ctor);
1704       
1705        if (!fHandle)
1706        {
1707            [self release];
1708            return nil;
1709        }
1710    }
1711
1712    fInfo = tr_torrentInfo(fHandle);
1713   
1714    tr_torrentSetQueueStartCallback(fHandle, startQueueCallback, self);
1715    tr_torrentSetCompletenessCallback(fHandle, completenessChangeCallback, self);
1716    tr_torrentSetRatioLimitHitCallback(fHandle, ratioLimitHitCallback, self);
1717    tr_torrentSetIdleLimitHitCallback(fHandle, idleLimitHitCallback, self);
1718    tr_torrentSetMetadataCallback(fHandle, metadataCallback, self);
1719   
1720    fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
1721   
1722    fResumeOnWake = NO;
1723   
1724    //don't do after this point - it messes with auto-group functionality
1725    if (![self isMagnet])
1726        [self createFileList];
1727       
1728    fDownloadFolderDetermination = TorrentDeterminationAutomatic;
1729   
1730    if (groupValue)
1731    {
1732        fGroupValueDetermination = TorrentDeterminationUserSpecified;
1733        fGroupValue = [groupValue intValue];
1734    }
1735    else
1736    {
1737        fGroupValueDetermination = TorrentDeterminationAutomatic;
1738        fGroupValue = [[GroupsController groups] groupIndexForTorrent: self];
1739    }
1740   
1741    fRemoveWhenFinishSeeding = removeWhenFinishSeeding ? [removeWhenFinishSeeding boolValue] : [fDefaults boolForKey: @"RemoveWhenFinishSeeding"];
1742   
1743    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1744        name: @"GroupValueRemoved" object: nil];
1745   
1746    fTimeMachineExcludeInitialized = NO;
1747    [self update];
1748   
1749    return self;
1750}
1751
1752- (void) createFileList
1753{
1754    NSAssert(![self isMagnet], @"Cannot create a file list until the torrent is demagnetized");
1755   
1756    if ([self isFolder])
1757    {
1758        const NSInteger count = [self fileCount];
1759        NSMutableArray * flatFileList = [NSMutableArray arrayWithCapacity: count];
1760       
1761        FileListNode * tempNode = nil;
1762       
1763        for (NSInteger i = 0; i < count; i++)
1764        {
1765            tr_file * file = &fInfo->files[i];
1766           
1767            NSString * fullPath = [NSString stringWithUTF8String: file->name];
1768            NSArray * pathComponents = [fullPath pathComponents];
1769           
1770            if (!tempNode)
1771                tempNode = [[FileListNode alloc] initWithFolderName:[pathComponents objectAtIndex: 0] path:@"" torrent:self];
1772           
1773            [self insertPathForComponents: pathComponents withComponentIndex: 1 forParent: tempNode fileSize: file->length index: i flatList: flatFileList];
1774        }
1775       
1776        [self sortFileList: [tempNode children]];
1777        [self sortFileList: flatFileList];
1778       
1779        fFileList = [[NSArray alloc] initWithArray: [tempNode children]];
1780        fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1781        [tempNode release];
1782    }
1783    else
1784    {
1785        FileListNode * node = [[FileListNode alloc] initWithFileName: [self name] path: @"" size: [self size] index: 0 torrent: self];
1786        fFileList = [[NSArray arrayWithObject: node] retain];
1787        fFlatFileList = [fFileList retain];
1788        [node release];
1789    }
1790}
1791
1792- (void) insertPathForComponents: (NSArray *) components withComponentIndex: (NSUInteger) componentIndex forParent: (FileListNode *) parent fileSize: (uint64_t) size
1793    index: (NSInteger) index flatList: (NSMutableArray *) flatFileList
1794{
1795    NSParameterAssert([components count] > 0);
1796    NSParameterAssert(componentIndex < [components count]);
1797   
1798    NSString * name = [components objectAtIndex: componentIndex];
1799    const BOOL isFolder = componentIndex < ([components count]-1);
1800   
1801    //determine if folder node already exists
1802    __block FileListNode * node = nil;
1803    if (isFolder)
1804    {
1805        [[parent children] enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * searchNode, NSUInteger idx, BOOL * stop) {
1806            if ([[searchNode name] isEqualToString: name] && [searchNode isFolder])
1807            {
1808                node = searchNode;
1809                *stop = YES;
1810            }
1811        }];
1812    }
1813   
1814    //create new folder or file if it doesn't already exist
1815    if (!node)
1816    {
1817        NSString * path = [[parent path] stringByAppendingPathComponent: [parent name]];
1818        if (isFolder)
1819            node = [[[FileListNode alloc] initWithFolderName: name path: path torrent: self] autorelease];
1820        else
1821        {
1822            node = [[[FileListNode alloc] initWithFileName: name path: path size: size index: index torrent: self] autorelease];
1823            [flatFileList addObject: node];
1824        }
1825       
1826        [parent insertChild: node];
1827    }
1828   
1829    if (isFolder)
1830    {
1831        [node insertIndex: index withSize: size];
1832       
1833        [self insertPathForComponents: components withComponentIndex: (componentIndex+1) forParent: node fileSize: size index: index flatList: flatFileList];
1834    }
1835}
1836
1837- (void) sortFileList: (NSMutableArray *) fileNodes
1838{
1839    NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey: @"name" ascending: YES selector: @selector(localizedStandardCompare:)];
1840    [fileNodes sortUsingDescriptors: [NSArray arrayWithObject: descriptor]];
1841   
1842    [fileNodes enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * node, NSUInteger idx, BOOL * stop) {
1843        if ([node isFolder])
1844            [self sortFileList: [node children]];
1845    }];
1846}
1847
1848- (void) startQueue
1849{
1850    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
1851}
1852
1853//status has been retained
1854- (void) completenessChange: (tr_completeness) status wasRunning: (BOOL) wasRunning
1855{
1856    fStat = tr_torrentStat(fHandle); //don't call update yet to avoid auto-stop
1857   
1858    switch (status)
1859    {
1860        case TR_SEED:
1861        case TR_PARTIAL_SEED:
1862        {
1863            NSDictionary * statusInfo = @{ @"Status" : @(status), @"WasRunning" : @(wasRunning) };
1864            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self userInfo: statusInfo];
1865           
1866            //quarantine the finished data
1867            NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: [self name]];
1868            FSRef ref;
1869            if (FSPathMakeRef((const UInt8 *)[dataLocation UTF8String], &ref, NULL) == noErr)
1870            {
1871                NSDictionary * quarantineProperties = [NSDictionary dictionaryWithObject: (NSString *)kLSQuarantineTypeOtherDownload forKey: (NSString *)kLSQuarantineTypeKey];
1872                if (LSSetItemAttribute(&ref, kLSRolesAll, kLSItemQuarantineProperties, quarantineProperties) != noErr)
1873                    NSLog(@"Failed to quarantine: %@", dataLocation);
1874            }
1875            else
1876                NSLog(@"Could not find file to quarantine: %@", dataLocation);
1877           
1878            break;
1879        }
1880        case TR_LEECH:
1881            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1882            break;
1883    }
1884   
1885    [self update];
1886    [self updateTimeMachineExclude];
1887}
1888
1889- (void) ratioLimitHit
1890{
1891    fStat = tr_torrentStat(fHandle);
1892   
1893    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedSeeding" object: self];
1894}
1895
1896- (void) idleLimitHit
1897{
1898    fStat = tr_torrentStat(fHandle);
1899   
1900    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedSeeding" object: self];
1901}
1902
1903- (void) metadataRetrieved
1904{
1905    fStat = tr_torrentStat(fHandle);
1906
1907    [self createFileList];
1908   
1909    /* If the torrent is in no group, or the group was automatically determined based on criteria evaluated
1910     * before we had metadata for this torrent, redetermine the group
1911     */
1912    if ((fGroupValueDetermination == TorrentDeterminationAutomatic) || ([self groupValue] == -1))
1913        [self setGroupValue: [[GroupsController groups] groupIndexForTorrent: self] determinationType: TorrentDeterminationAutomatic];
1914   
1915    //change the location if the group calls for it and it's either not already set or was set automatically before
1916    if (((fDownloadFolderDetermination == TorrentDeterminationAutomatic) || !tr_torrentGetCurrentDir(fHandle)) &&
1917        [[GroupsController groups] usesCustomDownloadLocationForIndex: [self groupValue]])
1918    {
1919        NSString *location = [[GroupsController groups] customDownloadLocationForIndex: [self groupValue]];
1920        [self changeDownloadFolderBeforeUsing: location determinationType:TorrentDeterminationAutomatic];
1921    }
1922   
1923    [[NSNotificationCenter defaultCenter] postNotificationName: @"ResetInspector" object: self userInfo: @{ @"Torrent" : self }];
1924}
1925
1926- (void)renameFinished: (BOOL) success nodes: (NSArray *) nodes completionHandler: (void (^)(BOOL)) completionHandler oldPath: (NSString *) oldPath newName: (NSString *) newName
1927{
1928    NSParameterAssert(completionHandler != nil);
1929    NSParameterAssert(oldPath != nil);
1930    NSParameterAssert(newName != nil);
1931   
1932    NSString * path = [oldPath stringByDeletingLastPathComponent];
1933   
1934    if (success)
1935    {
1936        NSString * oldName = [oldPath lastPathComponent];
1937        void (^__block updateNodeAndChildrenForRename)(FileListNode *) = ^(FileListNode * node) {
1938            [node updateFromOldName: oldName toNewName: newName inPath: path];
1939           
1940            if ([node isFolder]) {
1941                [[node children] enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * childNode, NSUInteger idx, BOOL * stop) {
1942                    updateNodeAndChildrenForRename(childNode);
1943                }];
1944            }
1945        };
1946       
1947        if (!nodes)
1948            nodes = fFlatFileList;
1949        [nodes enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * node, NSUInteger idx, BOOL *stop) {
1950            updateNodeAndChildrenForRename(node);
1951        }];
1952       
1953        //resort lists
1954        NSMutableArray * fileList = [fFileList mutableCopy];
1955        [fFileList release];
1956        [self sortFileList: fileList];
1957        fFileList = fileList;
1958       
1959        NSMutableArray * flatFileList = [fFlatFileList mutableCopy];
1960        [fFlatFileList release];
1961        [self sortFileList: flatFileList];
1962        fFlatFileList = flatFileList;
1963    }
1964    else
1965        NSLog(@"Error renaming %@ to %@", oldPath, [path stringByAppendingPathComponent: newName]);
1966   
1967    completionHandler(success);
1968}
1969
1970- (BOOL) shouldShowEta
1971{
1972    if (fStat->activity == TR_STATUS_DOWNLOAD)
1973        return YES;
1974    else if ([self isSeeding])
1975    {
1976        //ratio: show if it's set at all
1977        if (tr_torrentGetSeedRatio(fHandle, NULL))
1978            return YES;
1979       
1980        //idle: show only if remaining time is less than cap
1981        if (fStat->etaIdle != TR_ETA_NOT_AVAIL && fStat->etaIdle < ETA_IDLE_DISPLAY_SEC)
1982            return YES;
1983    }
1984   
1985    return NO;
1986}
1987
1988- (NSString *) etaString
1989{
1990    NSInteger eta;
1991    BOOL fromIdle;
1992    //don't check for both, since if there's a regular ETA, the torrent isn't idle so it's meaningless
1993    if (fStat->eta != TR_ETA_NOT_AVAIL && fStat->eta != TR_ETA_UNKNOWN)
1994    {
1995        eta = fStat->eta;
1996        fromIdle = NO;
1997    }
1998    else if (fStat->etaIdle != TR_ETA_NOT_AVAIL && fStat->etaIdle < ETA_IDLE_DISPLAY_SEC)
1999    {
2000        eta = fStat->etaIdle;
2001        fromIdle = YES;
2002    }
2003    else
2004        return NSLocalizedString(@"remaining time unknown", "Torrent -> eta string");
2005   
2006    NSString * idleString;
2007   
2008    if ([NSApp isOnYosemiteOrBetter]) {
2009        static NSDateComponentsFormatter *formatter;
2010        static dispatch_once_t onceToken;
2011        dispatch_once(&onceToken, ^{
2012            formatter = [NSDateComponentsFormatter new];
2013            formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleShort;
2014            formatter.maximumUnitCount = 2;
2015            formatter.collapsesLargestUnit = YES;
2016            formatter.includesTimeRemainingPhrase = YES;
2017        });
2018       
2019        idleString = [formatter stringFromTimeInterval: eta];
2020    }
2021    else {
2022        idleString = [NSString timeString: eta includesTimeRemainingPhrase: YES showSeconds: YES maxFields: 2];
2023    }
2024   
2025    if (fromIdle) {
2026        idleString = [idleString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"inactive", "Torrent -> eta string")];
2027    }
2028   
2029    return idleString;
2030}
2031
2032- (void) setTimeMachineExclude: (BOOL) exclude
2033{
2034    NSString * path;
2035    if ((path = [self dataLocation]))
2036    {
2037        CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, false);
2038        fTimeMachineExcludeInitialized = YES;
2039    }
2040}
2041
2042@end
Note: See TracBrowser for help on using the repository browser.