source: trunk/macosx/Torrent.m @ 10437

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

#1869 Move the finished state to libtransmission. This setting is now remembered between launches. This also causes torrents that hit the seed ratio to not have this setting changed to unlimited until start.

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