source: trunk/macosx/Torrent.m @ 14341

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

Yosemite: use NSDateComponentsFormatter instead of custom time string code

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