source: trunk/macosx/Torrent.m @ 10438

Last change on this file since 10438 was 10438, checked in by livings124, 12 years ago

when the ratio is set to unlimited when resuming, reload the inspector

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