source: trunk/macosx/Torrent.m @ 11409

Last change on this file since 11409 was 11409, checked in by livings124, 11 years ago

a couple of float -> double changes

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