source: trunk/macosx/Torrent.m @ 13418

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

Tweak how we first load in the torrent file list. This time submit the right file.

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