source: trunk/macosx/Torrent.m @ 11039

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

fix inactivity typo

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