source: trunk/macosx/Torrent.m @ 9491

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

tiny changes I have sitting around

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