source: trunk/macosx/Torrent.m @ 14290

Last change on this file since 14290 was 14290, checked in by jordan, 8 years ago

(trunk) replace the tr_metainfo_builder.isSingleFile and tr_info.isMultifile fields an 'isFolder' bool in both structs.

This makes the variable naming more uniform. It also clarifies the information we're really trying to convey -- previously, isSingleFile was false whenever the torrent held a directory tree, even if there was only a single file in the tree.

Sync the Mac OS X client's use to match the libtransmission variable names.

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