source: trunk/macosx/Torrent.m @ 9402

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

one less warning

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