source: trunk/macosx/Torrent.m @ 9403

Last change on this file since 9403 was 9403, checked in by livings124, 13 years ago

since the Mac UI is the only one to use it, move percentRatio calculation into the Mac code

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