source: trunk/macosx/Torrent.m @ 10712

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

whoops

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