source: trunk/macosx/Torrent.m @ 13872

Last change on this file since 13872 was 13872, checked in by livings124, 8 years ago

remove two dictionary subscripts

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