source: trunk/macosx/Torrent.m @ 12774

Last change on this file since 12774 was 12774, checked in by livings124, 10 years ago

remember selected transfers when updating the queue order over rpd

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