source: trunk/macosx/Torrent.m @ 14017

Last change on this file since 14017 was 14017, checked in by livings124, 9 years ago

perform the rename callback in the main thread

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