source: trunk/macosx/Torrent.m @ 13251

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

reverse r13248-r13250, since ARC doesn't support 32-bit.

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