source: trunk/macosx/Torrent.m @ 9487

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

the new way of determining the unwanted width of the progress bar wasn't cutting it, so use the old way

  • Property svn:keywords set to Date Rev Author Id
File size: 52.4 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 9487 2009-11-07 02:22:57Z 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: NSLocalizedString(@"Checking existing data (%.2f%%)",
966                                        "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 (!fFileStat)
1233        [self updateFileStat];
1234   
1235    NSIndexSet * indexSet = [node indexes];
1236   
1237    if ([indexSet count] == 1)
1238        return fFileStat[[indexSet firstIndex]].progress;
1239   
1240    uint64_t have = 0;
1241    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1242        have += fFileStat[index].bytesCompleted;
1243   
1244    NSAssert([node size], @"directory in torrent file has size 0");
1245    return (CGFloat)have / [node size];
1246}
1247
1248- (NSArray *) flatFileList
1249{
1250    return fFlatFileList;
1251}
1252
1253- (BOOL) canChangeDownloadCheckForFile: (NSInteger) index
1254{
1255    if (!fFileStat)
1256        [self updateFileStat];
1257   
1258    return [self fileCount] > 1 && fFileStat[index].progress < 1.0;
1259}
1260
1261- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1262{
1263    if ([self fileCount] <= 1 || [self isComplete])
1264        return NO;
1265   
1266    if (!fFileStat)
1267        [self updateFileStat];
1268   
1269    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1270        if (fFileStat[index].progress < 1.0)
1271            return YES;
1272    return NO;
1273}
1274
1275- (NSInteger) checkForFiles: (NSIndexSet *) indexSet
1276{
1277    BOOL onState = NO, offState = NO;
1278    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1279    {
1280        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1281            onState = YES;
1282        else
1283            offState = YES;
1284       
1285        if (onState && offState)
1286            return NSMixedState;
1287    }
1288    return onState ? NSOnState : NSOffState;
1289}
1290
1291- (void) setFileCheckState: (NSInteger) state forIndexes: (NSIndexSet *) indexSet
1292{
1293    NSUInteger count = [indexSet count];
1294    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1295    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1296        files[i] = index;
1297   
1298    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1299    free(files);
1300   
1301    [self update];
1302    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
1303}
1304
1305- (void) setFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1306{
1307    const NSUInteger count = [indexSet count];
1308    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1309    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1310        files[i] = index;
1311   
1312    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1313    free(files);
1314}
1315
1316- (BOOL) hasFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1317{
1318    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1319        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1320            return YES;
1321    return NO;
1322}
1323
1324- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1325{
1326    BOOL low = NO, normal = NO, high = NO;
1327    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1328   
1329    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1330    {
1331        if (![self canChangeDownloadCheckForFile: index])
1332            continue;
1333       
1334        const tr_priority_t priority = tr_torrentGetFilePriority(fHandle, index);
1335        if (priority == TR_PRI_LOW)
1336        {
1337            if (low)
1338                continue;
1339            low = YES;
1340        }
1341        else if (priority == TR_PRI_HIGH)
1342        {
1343            if (high)
1344                continue;
1345            high = YES;
1346        }
1347        else
1348        {
1349            if (normal)
1350                continue;
1351            normal = YES;
1352        }
1353       
1354        [priorities addObject: [NSNumber numberWithInteger: priority]];
1355        if (low && normal && high)
1356            break;
1357    }
1358    return priorities;
1359}
1360
1361- (NSDate *) dateAdded
1362{
1363    const time_t date = fStat->addedDate;
1364    return [NSDate dateWithTimeIntervalSince1970: date];
1365}
1366
1367- (NSDate *) dateCompleted
1368{
1369    const time_t date = fStat->doneDate;
1370    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1371}
1372
1373- (NSDate *) dateActivity
1374{
1375    const time_t date = fStat->activityDate;
1376    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1377}
1378
1379- (NSDate *) dateActivityOrAdd
1380{
1381    NSDate * date = [self dateActivity];
1382    return date ? date : [self dateAdded];
1383}
1384
1385- (NSInteger) stalledMinutes
1386{
1387    time_t start = fStat->startDate;
1388    if (start == 0)
1389        return -1;
1390   
1391    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start],
1392            * activity = [self dateActivity];
1393   
1394    NSDate * laterDate = activity ? [started laterDate: activity] : started;
1395    return -1 * [laterDate timeIntervalSinceNow] / 60;
1396}
1397
1398- (BOOL) isStalled
1399{
1400    return fStalled;
1401}
1402
1403- (void) updateTimeMachineExclude
1404{
1405    NSString * currentLocation = ![self allDownloaded] ? [self dataLocation] : nil;
1406   
1407    //return if the locations are the same
1408    if (fTimeMachineExclude && currentLocation && [fTimeMachineExclude isEqualToString: currentLocation])
1409        return;
1410   
1411    //remove old location...
1412    if (fTimeMachineExclude)
1413    {
1414        [self setTimeMachineExclude: NO forPath: fTimeMachineExclude];
1415        [fTimeMachineExclude release];
1416        fTimeMachineExclude = nil;
1417    }
1418   
1419    //...set new location
1420    if (currentLocation)
1421    {
1422        [self setTimeMachineExclude: YES forPath: currentLocation];
1423        fTimeMachineExclude = [currentLocation retain];
1424    }
1425}
1426
1427- (NSInteger) stateSortKey
1428{
1429    if (![self isActive]) //paused
1430    {
1431        if (fWaitToStart)
1432            return 1;
1433        else
1434            return 0;
1435    }
1436    else if ([self isSeeding]) //seeding
1437        return 10;
1438    else //downloading
1439        return 20;
1440}
1441
1442- (NSString *) trackerSortKey
1443{
1444    int count;
1445    tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
1446   
1447    NSString * best = nil;
1448   
1449    for (int i=0; i < count; ++i)
1450    {
1451        NSString * tracker = [NSString stringWithUTF8String: stats[i].host];
1452        if (!best || [tracker localizedCaseInsensitiveCompare: best] == NSOrderedAscending)
1453            best = tracker;
1454    }
1455   
1456    tr_torrentTrackersFree(stats, count);
1457    return best;
1458}
1459
1460- (tr_torrent *) torrentStruct
1461{
1462    return fHandle;
1463}
1464
1465- (NSURL *) previewItemURL
1466{
1467    NSString * location = [self dataLocation];
1468    return location ? [NSURL fileURLWithPath: location] : nil;
1469}
1470
1471@end
1472
1473@implementation Torrent (Private)
1474
1475- (id) initWithPath: (NSString *) path hash: (NSString *) hashString torrentStruct: (tr_torrent *) torrentStruct lib: (tr_session *) lib
1476        waitToStart: (NSNumber *) waitToStart
1477        groupValue: (NSNumber *) groupValue
1478        legacyDownloadFolder: (NSString *) downloadFolder legacyIncompleteFolder: (NSString *) incompleteFolder
1479{
1480    if (!(self = [super init]))
1481        return nil;
1482   
1483    fDefaults = [NSUserDefaults standardUserDefaults];
1484   
1485    if (torrentStruct)
1486    {
1487        fHandle = torrentStruct;
1488        fInfo = tr_torrentInfo(fHandle);
1489    }
1490    else
1491    {
1492        //set libtransmission settings for initialization
1493        tr_ctor * ctor = tr_ctorNew(lib);
1494        tr_ctorSetPaused(ctor, TR_FORCE, YES);
1495       
1496        int result = TR_PARSE_ERR;
1497        if (path)
1498            result = tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1499       
1500        //backup - shouldn't be needed after upgrade to 1.70
1501        if (result != TR_PARSE_OK && hashString)
1502            result = tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1503       
1504        if (result == TR_PARSE_OK)
1505        {
1506            tr_info info;
1507            result = tr_torrentParse(ctor, &info);
1508           
1509            if (result == TR_PARSE_OK)
1510            {
1511                if (downloadFolder)
1512                    tr_ctorSetDownloadDir(ctor, TR_FORCE, [downloadFolder UTF8String]);
1513                if (incompleteFolder)
1514                    tr_ctorSetIncompleteDir(ctor, [incompleteFolder UTF8String]);
1515               
1516                fHandle = tr_torrentNew(ctor, NULL);
1517            }
1518            if (result != TR_PARSE_ERR)
1519                tr_metainfoFree(&info);
1520        }
1521       
1522        tr_ctorFree(ctor);
1523       
1524        if (!fHandle)
1525        {
1526            [self release];
1527            return nil;
1528        }
1529       
1530        fInfo = tr_torrentInfo(fHandle);
1531    }
1532   
1533    tr_torrentSetCompletenessCallback(fHandle, completenessChangeCallback, self);
1534    tr_torrentSetRatioLimitHitCallback(fHandle, ratioLimitHitCallback, self);
1535   
1536    fNameString = [[NSString alloc] initWithUTF8String: fInfo->name];
1537    fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
1538       
1539    fFinishedSeeding = NO;
1540   
1541    fWaitToStart = waitToStart && [waitToStart boolValue];
1542    fResumeOnWake = NO;
1543       
1544    [self createFileList];
1545       
1546    fGroupValue = groupValue ? [groupValue intValue] : [[GroupsController groups] groupIndexForTorrent: self];
1547   
1548    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1549        name: @"GroupValueRemoved" object: nil];
1550   
1551    fTimeMachineExclude = nil;
1552    [self update];
1553   
1554    return self;
1555}
1556
1557- (void) createFileList
1558{
1559    if ([self isFolder])
1560    {
1561        NSInteger count = [self fileCount];
1562        NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count],
1563                    * flatFileList = [[NSMutableArray alloc] initWithCapacity: count];
1564       
1565        for (NSInteger i = 0; i < count; i++)
1566        {
1567            tr_file * file = &fInfo->files[i];
1568           
1569            NSMutableArray * pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1570            NSString * path = [pathComponents objectAtIndex: 0];
1571            NSString * name = [pathComponents objectAtIndex: 1];
1572            [pathComponents removeObjectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, 2)]];
1573           
1574            if ([pathComponents count] > 0)
1575            {
1576                //determine if folder node already exists
1577                FileListNode * node;
1578                for (node in fileList)
1579                    if ([node isFolder] && [[node name] isEqualToString: name])
1580                        break;
1581               
1582                if (!node)
1583                {
1584                    node = [[FileListNode alloc] initWithFolderName: name path: path];
1585                    [fileList addObject: node];
1586                    [node release];
1587                }
1588               
1589                [node insertIndex: i withSize: file->length];
1590                [self insertPath: pathComponents forParent: node fileSize: file->length index: i flatList: flatFileList];
1591            }
1592            else
1593            {
1594                FileListNode * node = [[FileListNode alloc] initWithFileName: name path: path size: file->length index: i];
1595                [fileList addObject: node];
1596                [flatFileList addObject: node];
1597                [node release];
1598            }
1599           
1600            [pathComponents release];
1601        }
1602       
1603        fFileList = [[NSArray alloc] initWithArray: fileList];
1604        [fileList release];
1605       
1606        fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1607        [flatFileList release];
1608    }
1609    else
1610    {
1611        FileListNode * node = [[FileListNode alloc] initWithFileName: [self name] path: @"" size: [self size] index: 0];
1612        fFileList = [[NSArray arrayWithObject: node] retain];
1613        fFlatFileList = [fFileList retain];
1614        [node release];
1615    }
1616}
1617
1618- (void) insertPath: (NSMutableArray *) components forParent: (FileListNode *) parent fileSize: (uint64_t) size
1619    index: (NSInteger) index flatList: (NSMutableArray *) flatFileList
1620{
1621    NSString * name = [components objectAtIndex: 0];
1622    const BOOL isFolder = [components count] > 1;
1623   
1624    FileListNode * node = nil;
1625    if (isFolder)
1626    {
1627        for (node in [parent children])
1628            if ([[node name] isEqualToString: name] && [node isFolder])
1629                break;
1630    }
1631   
1632    //create new folder or file if it doesn't already exist
1633    if (!node)
1634    {
1635        NSString * path = [[parent path] stringByAppendingPathComponent: [parent name]];
1636        if (isFolder)
1637            node = [[FileListNode alloc] initWithFolderName: name path: path];
1638        else
1639        {
1640            node = [[FileListNode alloc] initWithFileName: name path: path size: size index: index];
1641            [flatFileList addObject: node];
1642        }
1643       
1644        [parent insertChild: node];
1645        [node release];
1646    }
1647   
1648    if (isFolder)
1649    {
1650        [node insertIndex: index withSize: size];
1651       
1652        [components removeObjectAtIndex: 0];
1653        [self insertPath: components forParent: node fileSize: size index: index flatList: flatFileList];
1654    }
1655}
1656
1657//status has been retained
1658- (void) completenessChange: (NSNumber *) status
1659{
1660    fStat = tr_torrentStat(fHandle); //don't call update yet to avoid auto-stop
1661   
1662    switch ([status intValue])
1663    {
1664        case TR_SEED:
1665        case TR_PARTIAL_SEED:
1666            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1667            break;
1668       
1669        case TR_LEECH:
1670            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1671            break;
1672    }
1673    [status release];
1674   
1675    [self update];
1676    [self updateTimeMachineExclude];
1677}
1678
1679- (void) ratioLimitHit
1680{
1681    fStat = tr_torrentStat(fHandle);
1682   
1683    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
1684   
1685    fFinishedSeeding = YES;
1686}
1687
1688- (NSString *) etaString
1689{
1690    const NSInteger eta = [self eta];
1691    switch (eta)
1692    {
1693        case TR_ETA_NOT_AVAIL:
1694        case TR_ETA_UNKNOWN:
1695            return NSLocalizedString(@"remaining time unknown", "Torrent -> eta string");
1696        default:
1697            return [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "Torrent -> eta string"),
1698                        [NSString timeString: eta showSeconds: YES maxFields: 2]];
1699    }
1700}
1701
1702- (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path
1703{
1704    CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, true);
1705}
1706
1707@end
Note: See TracBrowser for help on using the repository browser.