source: trunk/macosx/Torrent.m @ 12664

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

fix starting transfers when adding

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