source: trunk/macosx/Torrent.m @ 9473

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

for now, manually update error messages on the Mac client to use the proper wording for the "move file" menu item

  • Property svn:keywords set to Date Rev Author Id
File size: 52.4 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 9473 2009-11-02 01:33:15Z 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; ++newCount, ++oldIndex)
620    {
621        if (![trackers member: [NSString stringWithUTF8String: fInfo->trackers[oldIndex].announce]])
622            trackerStructs[newCount] = fInfo->trackers[oldIndex];
623        else
624            --newCount;
625    }
626   
627    const tr_announce_list_err result = tr_torrentSetAnnounceList(fHandle, trackerStructs, newCount);
628    NSAssert1(result == TR_ANNOUNCE_LIST_OK, @"Removing tracker addresses resulted in error: %d", result);
629   
630    tr_free(trackerStructs);
631}
632
633- (NSString *) comment
634{
635    return [NSString stringWithUTF8String: fInfo->comment];
636}
637
638- (NSString *) creator
639{
640    return [NSString stringWithUTF8String: fInfo->creator];
641}
642
643- (NSDate *) dateCreated
644{
645    NSInteger date = fInfo->dateCreated;
646    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
647}
648
649- (NSInteger) pieceSize
650{
651    return fInfo->pieceSize;
652}
653
654- (NSInteger) pieceCount
655{
656    return fInfo->pieceCount;
657}
658
659- (NSString *) hashString
660{
661    return fHashString;
662}
663
664- (BOOL) privateTorrent
665{
666    return fInfo->isPrivate;
667}
668
669- (NSString *) torrentLocation
670{
671    return [NSString stringWithUTF8String: fInfo->torrent];
672}
673
674- (NSString *) dataLocation
675{
676    if ([self isFolder])
677    {
678        NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: [self name]];
679       
680        if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
681            return nil;
682       
683        return dataLocation;
684    }
685    else
686    {
687        char * location = tr_torrentFindFile(fHandle, 0);
688        if (location == NULL)
689            return nil;
690       
691        NSString * dataLocation = [NSString stringWithUTF8String: location];
692        free(location);
693       
694        return dataLocation;
695    }
696}
697
698- (NSString *) fileLocation: (FileListNode *) node
699{
700    if ([node isFolder])
701    {
702        NSString * basePath = [[node path] stringByAppendingPathComponent: [node name]];
703        NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: basePath];
704       
705        if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
706            return nil;
707       
708        return dataLocation;
709    }
710    else
711    {
712        char * location = tr_torrentFindFile(fHandle, [[node indexes] firstIndex]);
713        if (location == NULL)
714            return nil;
715       
716        NSString * dataLocation = [NSString stringWithUTF8String: location];
717        free(location);
718       
719        return dataLocation;
720    }
721}
722
723- (CGFloat) progress
724{
725    return fStat->percentComplete;
726}
727
728- (CGFloat) progressDone
729{
730    return fStat->percentDone;
731}
732
733- (CGFloat) checkingProgress
734{
735    return fStat->recheckProgress;
736}
737
738- (NSInteger) eta
739{
740    return fStat->eta;
741}
742
743- (CGFloat) availableDesired
744{
745    return (CGFloat)fStat->desiredAvailable / [self sizeLeft];
746}
747
748- (BOOL) isActive
749{
750    return fStat->activity != TR_STATUS_STOPPED;
751}
752
753- (BOOL) isSeeding
754{
755    return fStat->activity == TR_STATUS_SEED;
756}
757
758- (BOOL) isChecking
759{
760    return fStat->activity == TR_STATUS_CHECK || fStat->activity == TR_STATUS_CHECK_WAIT;
761}
762
763- (BOOL) isCheckingWaiting
764{
765    return fStat->activity == TR_STATUS_CHECK_WAIT;
766}
767
768- (BOOL) allDownloaded
769{
770    return [self sizeLeft] == 0;
771}
772
773- (BOOL) isComplete
774{
775    return [self progress] >= 1.0;
776}
777
778- (BOOL) isError
779{
780    return fStat->error == TR_STAT_LOCAL_ERROR;
781}
782
783- (BOOL) isAnyErrorOrWarning
784{
785    return fStat->error != TR_STAT_OK;
786}
787
788- (NSString *) errorMessage
789{
790    if (![self isAnyErrorOrWarning])
791        return @"";
792   
793    NSString * error;
794    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
795        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
796        error = [NSString stringWithFormat: @"(%@)", NSLocalizedString(@"unreadable error", "Torrent -> error string unreadable")];
797   
798    //libtransmission uses "Set Location", Mac client uses "Move data file to..." - very hacky!
799    error = [error stringByReplacingOccurrencesOfString: @"Set Location" withString: [@"Move Data File To" stringByAppendingEllipsis]];
800   
801    return error;
802}
803
804- (NSArray *) peers
805{
806    int totalPeers;
807    tr_peer_stat * peers = tr_torrentPeers(fHandle, &totalPeers);
808   
809    NSMutableArray * peerDicts = [NSMutableArray arrayWithCapacity: totalPeers];
810   
811    for (int i = 0; i < totalPeers; i++)
812    {
813        tr_peer_stat * peer = &peers[i];
814        NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 10];
815       
816        [dict setObject: [NSNumber numberWithInt: peer->from] forKey: @"From"];
817        [dict setObject: [NSString stringWithUTF8String: peer->addr] forKey: @"IP"];
818        [dict setObject: [NSNumber numberWithInt: peer->port] forKey: @"Port"];
819        [dict setObject: [NSNumber numberWithFloat: peer->progress] forKey: @"Progress"];
820        [dict setObject: [NSNumber numberWithBool: peer->isSeed] forKey: @"Seed"];
821        [dict setObject: [NSNumber numberWithBool: peer->isEncrypted] forKey: @"Encryption"];
822        [dict setObject: [NSString stringWithUTF8String: peer->client] forKey: @"Client"];
823        [dict setObject: [NSString stringWithUTF8String: peer->flagStr] forKey: @"Flags"];
824       
825        if (peer->isUploadingTo)
826            [dict setObject: [NSNumber numberWithFloat: peer->rateToPeer] forKey: @"UL To Rate"];
827        if (peer->isDownloadingFrom)
828            [dict setObject: [NSNumber numberWithFloat: peer->rateToClient] forKey: @"DL From Rate"];
829       
830        [peerDicts addObject: dict];
831    }
832   
833    tr_torrentPeersFree(peers, totalPeers);
834   
835    return peerDicts;
836}
837
838- (NSUInteger) webSeedCount
839{
840    return fInfo->webseedCount;
841}
842
843- (NSArray *) webSeeds
844{
845    const NSInteger webSeedCount = fInfo->webseedCount;
846    NSMutableArray * webSeeds = [NSMutableArray arrayWithCapacity: webSeedCount];
847   
848    float * dlSpeeds = tr_torrentWebSpeeds(fHandle);
849   
850    for (NSInteger i = 0; i < webSeedCount; i++)
851    {
852        NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 2];
853       
854        [dict setObject: [NSString stringWithUTF8String: fInfo->webseeds[i]] forKey: @"Address"];
855       
856        if (dlSpeeds[i] != -1.0)
857            [dict setObject: [NSNumber numberWithFloat: dlSpeeds[i]] forKey: @"DL From Rate"];
858       
859        [webSeeds addObject: dict];
860    }
861   
862    tr_free(dlSpeeds);
863   
864    return webSeeds;
865}
866
867- (NSString *) progressString
868{
869    NSString * string;
870   
871    if (![self allDownloaded])
872    {
873        CGFloat progress;
874        if ([self isFolder] && [fDefaults boolForKey: @"DisplayStatusProgressSelected"])
875        {
876            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ selected", "Torrent -> progress string"),
877                        [NSString stringForFileSize: [self haveTotal]], [NSString stringForFileSize: [self totalSizeSelected]]];
878            progress = 100.0 * [self progressDone];
879        }
880        else
881        {
882            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "Torrent -> progress string"),
883                        [NSString stringForFileSize: [self haveTotal]], [NSString stringForFileSize: [self size]]];
884            progress = 100.0 * [self progress];
885        }
886       
887        string = [NSString localizedStringWithFormat: @"%@ (%.2f%%)", string, progress];
888    }
889    else
890    {
891        NSString * downloadString;
892        if (![self isComplete]) //only multifile possible
893        {
894            if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
895                downloadString = [NSString stringWithFormat: NSLocalizedString(@"%@ selected", "Torrent -> progress string"),
896                                    [NSString stringForFileSize: [self haveTotal]]];
897            else
898            {
899                downloadString = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "Torrent -> progress string"),
900                                    [NSString stringForFileSize: [self haveTotal]], [NSString stringForFileSize: [self size]]];
901               
902                downloadString = [NSString localizedStringWithFormat: @"%@ (%.2f%%)", downloadString, 100.0 * [self progress]];
903            }
904        }
905        else
906            downloadString = [NSString stringForFileSize: [self size]];
907       
908        NSString * uploadString = [NSString stringWithFormat: NSLocalizedString(@"uploaded %@ (Ratio: %@)",
909                                    "Torrent -> progress string"), [NSString stringForFileSize: [self uploadedTotal]],
910                                    [NSString stringForRatio: [self ratio]]];
911       
912        string = [downloadString stringByAppendingFormat: @", %@", uploadString];
913    }
914   
915    //add time when downloading
916    if (fStat->activity == TR_STATUS_DOWNLOAD || ([self isSeeding] && [self seedRatioSet]))
917        string = [string stringByAppendingFormat: @" - %@", [self etaString]];
918   
919    return string;
920}
921
922- (NSString *) statusString
923{
924    NSString * string;
925   
926    if ([self isAnyErrorOrWarning])
927    {
928        switch (fStat->error)
929        {
930            case TR_STAT_LOCAL_ERROR: string = NSLocalizedString(@"Error", "Torrent -> status string"); break;
931            case TR_STAT_TRACKER_ERROR: string = NSLocalizedString(@"Tracker returned an error", "Torrent -> status string"); break;
932            case TR_STAT_TRACKER_WARNING: string = NSLocalizedString(@"Tracker returned a warning", "Torrent -> status string"); break;
933            default: NSAssert(NO, @"unknown error state");
934        }
935       
936        NSString * errorString = [self errorMessage];
937        if (errorString && ![errorString isEqualToString: @""])
938            string = [string stringByAppendingFormat: @": %@", errorString];
939    }
940    else
941    {
942        switch (fStat->activity)
943        {
944            case TR_STATUS_STOPPED:
945                if (fWaitToStart)
946                {
947                    string = ![self allDownloaded]
948                            ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
949                            : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
950                }
951                else if (fFinishedSeeding)
952                    string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
953                else
954                    string = NSLocalizedString(@"Paused", "Torrent -> status string");
955                break;
956
957            case TR_STATUS_CHECK_WAIT:
958                string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
959                break;
960
961            case TR_STATUS_CHECK:
962                string = [NSString localizedStringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
963                                        "Torrent -> status string"), 100.0 * [self checkingProgress]];
964                break;
965
966            case TR_STATUS_DOWNLOAD:
967                if ([self totalPeersConnected] != 1)
968                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
969                                                    "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
970                else
971                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
972                                                    "Torrent -> status string"), [self peersSendingToUs]];
973               
974                NSInteger webSeedCount = fStat->webseedsSendingToUs;
975                if (webSeedCount > 0)
976                {
977                    NSString * webSeedString;
978                    if (webSeedCount == 1)
979                        webSeedString = NSLocalizedString(@"web seed", "Torrent -> status string");
980                    else
981                        webSeedString = [NSString stringWithFormat: NSLocalizedString(@"%d web seeds", "Torrent -> status string"),
982                                                                    webSeedCount];
983                   
984                    string = [string stringByAppendingFormat: @" + %@", webSeedString];
985                }
986               
987                break;
988
989            case TR_STATUS_SEED:
990                if ([self totalPeersConnected] != 1)
991                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
992                                                    [self peersGettingFromUs], [self totalPeersConnected]];
993                else
994                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
995                                                    [self peersGettingFromUs]];
996        }
997       
998        if (fStalled)
999            string = [NSLocalizedString(@"Stalled", "Torrent -> status string") stringByAppendingFormat: @", %@", string];
1000    }
1001   
1002    //append even if error
1003    if ([self isActive] && ![self isChecking])
1004    {
1005        if (fStat->activity == TR_STATUS_DOWNLOAD)
1006            string = [string stringByAppendingFormat: @" - %@: %@, %@: %@",
1007                        NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1008                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1009        else
1010            string = [string stringByAppendingFormat: @" - %@: %@",
1011                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1012    }
1013   
1014    return string;
1015}
1016
1017- (NSString *) shortStatusString
1018{
1019    NSString * string;
1020   
1021    switch (fStat->activity)
1022    {
1023        case TR_STATUS_STOPPED:
1024            if (fWaitToStart)
1025            {
1026                string = ![self allDownloaded]
1027                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
1028                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1029            }
1030            else if (fFinishedSeeding)
1031                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1032            else
1033                string = NSLocalizedString(@"Paused", "Torrent -> status string");
1034            break;
1035
1036        case TR_STATUS_CHECK_WAIT:
1037            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1038            break;
1039
1040        case TR_STATUS_CHECK:
1041            string = [NSString localizedStringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1042                                    "Torrent -> status string"), 100.0 * [self checkingProgress]];
1043            break;
1044       
1045        case TR_STATUS_DOWNLOAD:
1046            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1047                            NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1048                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1049            break;
1050       
1051        case TR_STATUS_SEED:
1052            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1053                            NSLocalizedString(@"Ratio", "Torrent -> status string"), [NSString stringForRatio: [self ratio]],
1054                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1055    }
1056   
1057    return string;
1058}
1059
1060- (NSString *) remainingTimeString
1061{
1062    if (fStat->activity == TR_STATUS_DOWNLOAD || ([self isSeeding] && [self seedRatioSet]))
1063        return [self etaString];
1064    else
1065        return [self shortStatusString];
1066}
1067
1068- (NSString *) stateString
1069{
1070    switch (fStat->activity)
1071    {
1072        case TR_STATUS_STOPPED:
1073            return NSLocalizedString(@"Paused", "Torrent -> status string");
1074
1075        case TR_STATUS_CHECK:
1076            return [NSString localizedStringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1077                                    "Torrent -> status string"), 100.0 * [self checkingProgress]];
1078       
1079        case TR_STATUS_CHECK_WAIT:
1080            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1081
1082        case TR_STATUS_DOWNLOAD:
1083            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1084
1085        case TR_STATUS_SEED:
1086            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1087    }
1088}
1089
1090- (NSInteger) totalPeersConnected
1091{
1092    return fStat->peersConnected;
1093}
1094
1095- (NSInteger) totalPeersTracker
1096{
1097    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1098}
1099
1100- (NSInteger) totalPeersIncoming
1101{
1102    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1103}
1104
1105- (NSInteger) totalPeersCache
1106{
1107    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1108}
1109
1110- (NSInteger) totalPeersPex
1111{
1112    return fStat->peersFrom[TR_PEER_FROM_PEX];
1113}
1114
1115- (NSInteger) totalPeersDHT
1116{
1117    return fStat->peersFrom[TR_PEER_FROM_DHT];
1118}
1119
1120- (NSInteger) totalPeersKnown
1121{
1122    return fStat->peersKnown;
1123}
1124
1125- (NSInteger) peersSendingToUs
1126{
1127    return fStat->peersSendingToUs;
1128}
1129
1130- (NSInteger) peersGettingFromUs
1131{
1132    return fStat->peersGettingFromUs;
1133}
1134
1135- (CGFloat) downloadRate
1136{
1137    return fStat->pieceDownloadSpeed;
1138}
1139
1140- (CGFloat) uploadRate
1141{
1142    return fStat->pieceUploadSpeed;
1143}
1144
1145- (CGFloat) totalRate
1146{
1147    return [self downloadRate] + [self uploadRate];
1148}
1149
1150- (uint64_t) haveVerified
1151{
1152    return fStat->haveValid;
1153}
1154
1155- (uint64_t) haveTotal
1156{
1157    return [self haveVerified] + fStat->haveUnchecked;
1158}
1159
1160- (uint64_t) totalSizeSelected
1161{
1162    return fStat->sizeWhenDone;
1163}
1164
1165- (uint64_t) downloadedTotal
1166{
1167    return fStat->downloadedEver;
1168}
1169
1170- (uint64_t) uploadedTotal
1171{
1172    return fStat->uploadedEver;
1173}
1174
1175- (uint64_t) failedHash
1176{
1177    return fStat->corruptEver;
1178}
1179
1180- (CGFloat) swarmSpeed
1181{
1182    return fStat->swarmSpeed;
1183}
1184
1185- (NSInteger) groupValue
1186{
1187    return fGroupValue;
1188}
1189
1190- (void) setGroupValue: (NSInteger) goupValue
1191{
1192    fGroupValue = goupValue;
1193}
1194
1195- (NSInteger) groupOrderValue
1196{
1197    return [[GroupsController groups] rowValueForIndex: fGroupValue];
1198}
1199
1200- (void) checkGroupValueForRemoval: (NSNotification *) notification
1201{
1202    if (fGroupValue != -1 && [[[notification userInfo] objectForKey: @"Index"] intValue] == fGroupValue)
1203        fGroupValue = -1;
1204}
1205
1206- (NSArray *) fileList
1207{
1208    return fFileList;
1209}
1210
1211- (NSInteger) fileCount
1212{
1213    return fInfo->fileCount;
1214}
1215
1216- (void) updateFileStat
1217{
1218    if (fFileStat)
1219        tr_torrentFilesFree(fFileStat, [self fileCount]);
1220   
1221    fFileStat = tr_torrentFiles(fHandle, NULL);
1222}
1223
1224- (CGFloat) fileProgress: (FileListNode *) node
1225{
1226    if ([self isComplete])
1227        return 1.0;
1228   
1229    if (!fFileStat)
1230        [self updateFileStat];
1231   
1232    NSIndexSet * indexSet = [node indexes];
1233   
1234    if ([indexSet count] == 1)
1235        return fFileStat[[indexSet firstIndex]].progress;
1236   
1237    uint64_t have = 0;
1238    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1239        have += fFileStat[index].bytesCompleted;
1240   
1241    NSAssert([node size], @"directory in torrent file has size 0");
1242    return (CGFloat)have / [node size];
1243}
1244
1245- (NSArray *) flatFileList
1246{
1247    return fFlatFileList;
1248}
1249
1250- (BOOL) canChangeDownloadCheckForFile: (NSInteger) index
1251{
1252    if (!fFileStat)
1253        [self updateFileStat];
1254   
1255    return [self fileCount] > 1 && fFileStat[index].progress < 1.0;
1256}
1257
1258- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1259{
1260    if ([self fileCount] <= 1 || [self isComplete])
1261        return NO;
1262   
1263    if (!fFileStat)
1264        [self updateFileStat];
1265   
1266    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1267        if (fFileStat[index].progress < 1.0)
1268            return YES;
1269    return NO;
1270}
1271
1272- (NSInteger) checkForFiles: (NSIndexSet *) indexSet
1273{
1274    BOOL onState = NO, offState = NO;
1275    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1276    {
1277        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1278            onState = YES;
1279        else
1280            offState = YES;
1281       
1282        if (onState && offState)
1283            return NSMixedState;
1284    }
1285    return onState ? NSOnState : NSOffState;
1286}
1287
1288- (void) setFileCheckState: (NSInteger) state forIndexes: (NSIndexSet *) indexSet
1289{
1290    NSUInteger count = [indexSet count];
1291    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1292    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1293        files[i] = index;
1294   
1295    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1296    free(files);
1297   
1298    [self update];
1299    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
1300}
1301
1302- (void) setFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1303{
1304    const NSUInteger count = [indexSet count];
1305    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1306    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1307        files[i] = index;
1308   
1309    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1310    free(files);
1311}
1312
1313- (BOOL) hasFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1314{
1315    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1316        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1317            return YES;
1318    return NO;
1319}
1320
1321- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1322{
1323    BOOL low = NO, normal = NO, high = NO;
1324    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1325   
1326    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1327    {
1328        if (![self canChangeDownloadCheckForFile: index])
1329            continue;
1330       
1331        const tr_priority_t priority = tr_torrentGetFilePriority(fHandle, index);
1332        if (priority == TR_PRI_LOW)
1333        {
1334            if (low)
1335                continue;
1336            low = YES;
1337        }
1338        else if (priority == TR_PRI_HIGH)
1339        {
1340            if (high)
1341                continue;
1342            high = YES;
1343        }
1344        else
1345        {
1346            if (normal)
1347                continue;
1348            normal = YES;
1349        }
1350       
1351        [priorities addObject: [NSNumber numberWithInteger: priority]];
1352        if (low && normal && high)
1353            break;
1354    }
1355    return priorities;
1356}
1357
1358- (NSDate *) dateAdded
1359{
1360    const time_t date = fStat->addedDate;
1361    return [NSDate dateWithTimeIntervalSince1970: date];
1362}
1363
1364- (NSDate *) dateCompleted
1365{
1366    const time_t date = fStat->doneDate;
1367    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1368}
1369
1370- (NSDate *) dateActivity
1371{
1372    const time_t date = fStat->activityDate;
1373    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1374}
1375
1376- (NSDate *) dateActivityOrAdd
1377{
1378    NSDate * date = [self dateActivity];
1379    return date ? date : [self dateAdded];
1380}
1381
1382- (NSInteger) stalledMinutes
1383{
1384    time_t start = fStat->startDate;
1385    if (start == 0)
1386        return -1;
1387   
1388    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start],
1389            * activity = [self dateActivity];
1390   
1391    NSDate * laterDate = activity ? [started laterDate: activity] : started;
1392    return -1 * [laterDate timeIntervalSinceNow] / 60;
1393}
1394
1395- (BOOL) isStalled
1396{
1397    return fStalled;
1398}
1399
1400- (void) updateTimeMachineExclude
1401{
1402    NSString * currentLocation = ![self allDownloaded] ? [self dataLocation] : nil;
1403   
1404    //return if the locations are the same
1405    if (fTimeMachineExclude && currentLocation && [fTimeMachineExclude isEqualToString: currentLocation])
1406        return;
1407   
1408    //remove old location...
1409    if (fTimeMachineExclude)
1410    {
1411        [self setTimeMachineExclude: NO forPath: fTimeMachineExclude];
1412        [fTimeMachineExclude release];
1413        fTimeMachineExclude = nil;
1414    }
1415   
1416    //...set new location
1417    if (currentLocation)
1418    {
1419        [self setTimeMachineExclude: YES forPath: currentLocation];
1420        fTimeMachineExclude = [currentLocation retain];
1421    }
1422}
1423
1424- (NSInteger) stateSortKey
1425{
1426    if (![self isActive]) //paused
1427    {
1428        if (fWaitToStart)
1429            return 1;
1430        else
1431            return 0;
1432    }
1433    else if ([self isSeeding]) //seeding
1434        return 10;
1435    else //downloading
1436        return 20;
1437}
1438
1439- (NSString *) trackerSortKey
1440{
1441    int count;
1442    tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
1443   
1444    NSString * best = nil;
1445   
1446    for (int i=0; i < count; ++i)
1447    {
1448        NSString * tracker = [NSString stringWithUTF8String: stats[i].host];
1449        if (!best || [tracker localizedCaseInsensitiveCompare: best] == NSOrderedAscending)
1450            best = tracker;
1451    }
1452   
1453    tr_torrentTrackersFree(stats, count);
1454    return best;
1455}
1456
1457- (tr_torrent *) torrentStruct
1458{
1459    return fHandle;
1460}
1461
1462- (NSURL *) previewItemURL
1463{
1464    NSString * location = [self dataLocation];
1465    return location ? [NSURL fileURLWithPath: location] : nil;
1466}
1467
1468@end
1469
1470@implementation Torrent (Private)
1471
1472- (id) initWithPath: (NSString *) path hash: (NSString *) hashString torrentStruct: (tr_torrent *) torrentStruct lib: (tr_session *) lib
1473        waitToStart: (NSNumber *) waitToStart
1474        groupValue: (NSNumber *) groupValue
1475        legacyDownloadFolder: (NSString *) downloadFolder legacyIncompleteFolder: (NSString *) incompleteFolder
1476{
1477    if (!(self = [super init]))
1478        return nil;
1479   
1480    fDefaults = [NSUserDefaults standardUserDefaults];
1481   
1482    if (torrentStruct)
1483    {
1484        fHandle = torrentStruct;
1485        fInfo = tr_torrentInfo(fHandle);
1486    }
1487    else
1488    {
1489        //set libtransmission settings for initialization
1490        tr_ctor * ctor = tr_ctorNew(lib);
1491        tr_ctorSetPaused(ctor, TR_FORCE, YES);
1492       
1493        int result = TR_PARSE_ERR;
1494        if (path)
1495            result = tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1496       
1497        //backup - shouldn't be needed after upgrade to 1.70
1498        if (result != TR_PARSE_OK && hashString)
1499            result = tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1500       
1501        if (result == TR_PARSE_OK)
1502        {
1503            tr_info info;
1504            result = tr_torrentParse(ctor, &info);
1505           
1506            if (result == TR_PARSE_OK)
1507            {
1508                if (downloadFolder)
1509                    tr_ctorSetDownloadDir(ctor, TR_FORCE, [downloadFolder UTF8String]);
1510                if (incompleteFolder)
1511                    tr_ctorSetIncompleteDir(ctor, [incompleteFolder UTF8String]);
1512               
1513                fHandle = tr_torrentNew(ctor, NULL);
1514            }
1515            if (result != TR_PARSE_ERR)
1516                tr_metainfoFree(&info);
1517        }
1518       
1519        tr_ctorFree(ctor);
1520       
1521        if (!fHandle)
1522        {
1523            [self release];
1524            return nil;
1525        }
1526       
1527        fInfo = tr_torrentInfo(fHandle);
1528    }
1529   
1530    tr_torrentSetCompletenessCallback(fHandle, completenessChangeCallback, self);
1531    tr_torrentSetRatioLimitHitCallback(fHandle, ratioLimitHitCallback, self);
1532   
1533    fNameString = [[NSString alloc] initWithUTF8String: fInfo->name];
1534    fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
1535       
1536    fFinishedSeeding = NO;
1537   
1538    fWaitToStart = waitToStart && [waitToStart boolValue];
1539    fResumeOnWake = NO;
1540       
1541    [self createFileList];
1542       
1543    fGroupValue = groupValue ? [groupValue intValue] : [[GroupsController groups] groupIndexForTorrent: self];
1544   
1545    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1546        name: @"GroupValueRemoved" object: nil];
1547   
1548    fTimeMachineExclude = nil;
1549    [self update];
1550   
1551    return self;
1552}
1553
1554- (void) createFileList
1555{
1556    if ([self isFolder])
1557    {
1558        NSInteger count = [self fileCount];
1559        NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count],
1560                    * flatFileList = [[NSMutableArray alloc] initWithCapacity: count];
1561       
1562        for (NSInteger i = 0; i < count; i++)
1563        {
1564            tr_file * file = &fInfo->files[i];
1565           
1566            NSMutableArray * pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1567            NSString * path = [pathComponents objectAtIndex: 0];
1568            NSString * name = [pathComponents objectAtIndex: 1];
1569            [pathComponents removeObjectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, 2)]];
1570           
1571            if ([pathComponents count] > 0)
1572            {
1573                //determine if folder node already exists
1574                FileListNode * node;
1575                for (node in fileList)
1576                    if ([node isFolder] && [[node name] isEqualToString: name])
1577                        break;
1578               
1579                if (!node)
1580                {
1581                    node = [[FileListNode alloc] initWithFolderName: name path: path];
1582                    [fileList addObject: node];
1583                    [node release];
1584                }
1585               
1586                [node insertIndex: i withSize: file->length];
1587                [self insertPath: pathComponents forParent: node fileSize: file->length index: i flatList: flatFileList];
1588            }
1589            else
1590            {
1591                FileListNode * node = [[FileListNode alloc] initWithFileName: name path: path size: file->length index: i];
1592                [fileList addObject: node];
1593                [flatFileList addObject: node];
1594                [node release];
1595            }
1596           
1597            [pathComponents release];
1598        }
1599       
1600        fFileList = [[NSArray alloc] initWithArray: fileList];
1601        [fileList release];
1602       
1603        fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1604        [flatFileList release];
1605    }
1606    else
1607    {
1608        FileListNode * node = [[FileListNode alloc] initWithFileName: [self name] path: @"" size: [self size] index: 0];
1609        fFileList = [[NSArray arrayWithObject: node] retain];
1610        fFlatFileList = [fFileList retain];
1611        [node release];
1612    }
1613}
1614
1615- (void) insertPath: (NSMutableArray *) components forParent: (FileListNode *) parent fileSize: (uint64_t) size
1616    index: (NSInteger) index flatList: (NSMutableArray *) flatFileList
1617{
1618    NSString * name = [components objectAtIndex: 0];
1619    const BOOL isFolder = [components count] > 1;
1620   
1621    FileListNode * node = nil;
1622    if (isFolder)
1623    {
1624        for (node in [parent children])
1625            if ([[node name] isEqualToString: name] && [node isFolder])
1626                break;
1627    }
1628   
1629    //create new folder or file if it doesn't already exist
1630    if (!node)
1631    {
1632        NSString * path = [[parent path] stringByAppendingPathComponent: [parent name]];
1633        if (isFolder)
1634            node = [[FileListNode alloc] initWithFolderName: name path: path];
1635        else
1636        {
1637            node = [[FileListNode alloc] initWithFileName: name path: path size: size index: index];
1638            [flatFileList addObject: node];
1639        }
1640       
1641        [parent insertChild: node];
1642        [node release];
1643    }
1644   
1645    if (isFolder)
1646    {
1647        [node insertIndex: index withSize: size];
1648       
1649        [components removeObjectAtIndex: 0];
1650        [self insertPath: components forParent: node fileSize: size index: index flatList: flatFileList];
1651    }
1652}
1653
1654//status has been retained
1655- (void) completenessChange: (NSNumber *) status
1656{
1657    fStat = tr_torrentStat(fHandle); //don't call update yet to avoid auto-stop
1658   
1659    switch ([status intValue])
1660    {
1661        case TR_SEED:
1662        case TR_PARTIAL_SEED:
1663            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1664            break;
1665       
1666        case TR_LEECH:
1667            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1668            break;
1669    }
1670    [status release];
1671   
1672    [self update];
1673    [self updateTimeMachineExclude];
1674}
1675
1676- (void) ratioLimitHit
1677{
1678    fStat = tr_torrentStat(fHandle);
1679   
1680    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
1681   
1682    fFinishedSeeding = YES;
1683}
1684
1685- (NSString *) etaString
1686{
1687    const NSInteger eta = [self eta];
1688    switch (eta)
1689    {
1690        case TR_ETA_NOT_AVAIL:
1691        case TR_ETA_UNKNOWN:
1692            return NSLocalizedString(@"remaining time unknown", "Torrent -> eta string");
1693        default:
1694            return [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "Torrent -> eta string"),
1695                        [NSString timeString: eta showSeconds: YES maxFields: 2]];
1696    }
1697}
1698
1699- (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path
1700{
1701    CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, true);
1702}
1703
1704@end
Note: See TracBrowser for help on using the repository browser.