source: trunk/macosx/Torrent.m @ 10999

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

Have libtransmission keep track of idle seconds. Use this value for determining the stalled minutes in the Mac code.

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