source: trunk/macosx/Torrent.m @ 11001

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

whoops

  • Property svn:keywords set to Date Rev Author Id
File size: 56.6 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 11001 2010-07-11 21:06:28Z 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    if (fStat->idleSecs == -1)
1483        return -1;
1484   
1485    return fStat->idleSecs / 60;
1486}
1487
1488- (BOOL) isStalled
1489{
1490    return fStalled;
1491}
1492
1493- (void) updateTimeMachineExclude
1494{
1495    NSString * currentLocation = ![self allDownloaded] ? [self dataLocation] : nil;
1496   
1497    //return if the locations are the same
1498    if (fTimeMachineExclude && currentLocation && [fTimeMachineExclude isEqualToString: currentLocation])
1499        return;
1500   
1501    //remove old location...
1502    if (fTimeMachineExclude)
1503    {
1504        [self setTimeMachineExclude: NO forPath: fTimeMachineExclude];
1505        [fTimeMachineExclude release];
1506        fTimeMachineExclude = nil;
1507    }
1508   
1509    //...set new location
1510    if (currentLocation)
1511    {
1512        [self setTimeMachineExclude: YES forPath: currentLocation];
1513        fTimeMachineExclude = [currentLocation retain];
1514    }
1515}
1516
1517- (NSInteger) stateSortKey
1518{
1519    if (![self isActive]) //paused
1520    {
1521        if (fWaitToStart)
1522            return 1;
1523        else
1524            return 0;
1525    }
1526    else if ([self isSeeding]) //seeding
1527        return 10;
1528    else //downloading
1529        return 20;
1530}
1531
1532- (NSString *) trackerSortKey
1533{
1534    int count;
1535    tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
1536   
1537    NSString * best = nil;
1538   
1539    for (int i=0; i < count; ++i)
1540    {
1541        NSString * tracker = [NSString stringWithUTF8String: stats[i].host];
1542        if (!best || [tracker localizedCaseInsensitiveCompare: best] == NSOrderedAscending)
1543            best = tracker;
1544    }
1545   
1546    tr_torrentTrackersFree(stats, count);
1547    return best;
1548}
1549
1550- (tr_torrent *) torrentStruct
1551{
1552    return fHandle;
1553}
1554
1555- (NSURL *) previewItemURL
1556{
1557    NSString * location = [self dataLocation];
1558    return location ? [NSURL fileURLWithPath: location] : nil;
1559}
1560
1561@end
1562
1563@implementation Torrent (Private)
1564
1565- (id) initWithPath: (NSString *) path hash: (NSString *) hashString torrentStruct: (tr_torrent *) torrentStruct
1566        magnetAddress: (NSString *) magnetAddress lib: (tr_session *) lib
1567        waitToStart: (NSNumber *) waitToStart
1568        groupValue: (NSNumber *) groupValue
1569        downloadFolder: (NSString *) downloadFolder legacyIncompleteFolder: (NSString *) incompleteFolder
1570{
1571    if (!(self = [super init]))
1572        return nil;
1573   
1574    fDefaults = [NSUserDefaults standardUserDefaults];
1575   
1576    if (torrentStruct)
1577        fHandle = torrentStruct;
1578    else
1579    {
1580        //set libtransmission settings for initialization
1581        tr_ctor * ctor = tr_ctorNew(lib);
1582       
1583        tr_ctorSetPaused(ctor, TR_FORCE, YES);
1584        if (downloadFolder)
1585            tr_ctorSetDownloadDir(ctor, TR_FORCE, [downloadFolder UTF8String]);
1586        if (incompleteFolder)
1587            tr_ctorSetIncompleteDir(ctor, [incompleteFolder UTF8String]);
1588       
1589        tr_parse_result result = TR_PARSE_ERR;
1590        if (path)
1591            result = tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1592       
1593        if (result != TR_PARSE_OK && magnetAddress)
1594            result = tr_ctorSetMetainfoFromMagnetLink(ctor, [magnetAddress UTF8String]);
1595       
1596        //backup - shouldn't be needed after upgrade to 1.70
1597        if (result != TR_PARSE_OK && hashString)
1598            result = tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1599       
1600        if (result == TR_PARSE_OK)
1601            fHandle = tr_torrentNew(ctor, NULL);
1602       
1603        tr_ctorFree(ctor);
1604       
1605        if (!fHandle)
1606        {
1607            [self release];
1608            return nil;
1609        }
1610    }
1611
1612    fInfo = tr_torrentInfo(fHandle);
1613   
1614    tr_torrentSetCompletenessCallback(fHandle, completenessChangeCallback, self);
1615    tr_torrentSetRatioLimitHitCallback(fHandle, ratioLimitHitCallback, self);
1616    tr_torrentSetMetadataCallback(fHandle, metadataCallback, self);
1617   
1618    fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
1619   
1620    fWaitToStart = waitToStart && [waitToStart boolValue];
1621    fResumeOnWake = NO;
1622   
1623    //don't do after this point - it messes with auto-group functionality
1624    if (![self isMagnet])
1625        [self createFileList];
1626       
1627    fGroupValue = groupValue ? [groupValue intValue] : [[GroupsController groups] groupIndexForTorrent: self];
1628   
1629    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1630        name: @"GroupValueRemoved" object: nil];
1631   
1632    fTimeMachineExclude = nil;
1633    [self update];
1634   
1635    return self;
1636}
1637
1638- (void) createFileList
1639{
1640    NSAssert(![self isMagnet], @"Cannot create a file list until the torrent is demagnetized");
1641   
1642    if ([self isFolder])
1643    {
1644        NSInteger count = [self fileCount];
1645        NSMutableArray * fileList = [NSMutableArray arrayWithCapacity: count],
1646                    * flatFileList = [NSMutableArray arrayWithCapacity: count];
1647       
1648        for (NSInteger i = 0; i < count; i++)
1649        {
1650            tr_file * file = &fInfo->files[i];
1651           
1652            NSString * fullPath = [NSString stringWithUTF8String: file->name];
1653            NSArray * pathComponents = [fullPath pathComponents];
1654            NSAssert1([pathComponents count] >= 2, @"Not enough components in path %@", fullPath);
1655           
1656            NSString * path = [pathComponents objectAtIndex: 0];
1657            NSString * name = [pathComponents objectAtIndex: 1];
1658           
1659            if ([pathComponents count] > 2)
1660            {
1661                //determine if folder node already exists
1662                FileListNode * node;
1663                for (node in fileList)
1664                    if ([[node name] isEqualToString: name] && [node isFolder])
1665                        break;
1666               
1667                if (!node)
1668                {
1669                    node = [[FileListNode alloc] initWithFolderName: name path: path];
1670                    [fileList addObject: node];
1671                    [node release];
1672                }
1673               
1674                NSMutableArray * trimmedComponents = [NSMutableArray arrayWithArray: [pathComponents subarrayWithRange:
1675                                                        NSMakeRange(2, [pathComponents count]-2)]];
1676               
1677                [node insertIndex: i withSize: file->length];
1678                [self insertPath: trimmedComponents forParent: node fileSize: file->length index: i flatList: flatFileList];
1679            }
1680            else
1681            {
1682                FileListNode * node = [[FileListNode alloc] initWithFileName: name path: path size: file->length index: i];
1683                [fileList addObject: node];
1684                [flatFileList addObject: node];
1685                [node release];
1686            }
1687        }
1688       
1689        fFileList = [[NSArray alloc] initWithArray: fileList];
1690        fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1691    }
1692    else
1693    {
1694        FileListNode * node = [[FileListNode alloc] initWithFileName: [self name] path: @"" size: [self size] index: 0];
1695        fFileList = [[NSArray arrayWithObject: node] retain];
1696        fFlatFileList = [fFileList retain];
1697        [node release];
1698    }
1699}
1700
1701- (void) insertPath: (NSMutableArray *) components forParent: (FileListNode *) parent fileSize: (uint64_t) size
1702    index: (NSInteger) index flatList: (NSMutableArray *) flatFileList
1703{
1704    NSString * name = [components objectAtIndex: 0];
1705    const BOOL isFolder = [components count] > 1;
1706   
1707    FileListNode * node = nil;
1708    if (isFolder)
1709    {
1710        for (node in [parent children])
1711            if ([[node name] isEqualToString: name] && [node isFolder])
1712                break;
1713    }
1714   
1715    //create new folder or file if it doesn't already exist
1716    if (!node)
1717    {
1718        NSString * path = [[parent path] stringByAppendingPathComponent: [parent name]];
1719        if (isFolder)
1720            node = [[FileListNode alloc] initWithFolderName: name path: path];
1721        else
1722        {
1723            node = [[FileListNode alloc] initWithFileName: name path: path size: size index: index];
1724            [flatFileList addObject: node];
1725        }
1726       
1727        [parent insertChild: node];
1728        [node release];
1729    }
1730   
1731    if (isFolder)
1732    {
1733        [node insertIndex: index withSize: size];
1734       
1735        [components removeObjectAtIndex: 0];
1736        [self insertPath: components forParent: node fileSize: size index: index flatList: flatFileList];
1737    }
1738}
1739
1740//status has been retained
1741- (void) completenessChange: (NSDictionary *) statusInfo
1742{
1743    fStat = tr_torrentStat(fHandle); //don't call update yet to avoid auto-stop
1744   
1745    switch ([[statusInfo objectForKey: @"Status"] intValue])
1746    {
1747        case TR_SEED:
1748        case TR_PARTIAL_SEED:
1749            //simpler to create a new dictionary than to use statusInfo - avoids retention chicanery
1750            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self
1751                userInfo: [NSDictionary dictionaryWithObject: [statusInfo objectForKey: @"WasRunning"] forKey: @"WasRunning"]];
1752            break;
1753       
1754        case TR_LEECH:
1755            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1756            break;
1757    }
1758    [statusInfo release];
1759   
1760    [self update];
1761    [self updateTimeMachineExclude];
1762}
1763
1764- (void) ratioLimitHit
1765{
1766    fStat = tr_torrentStat(fHandle);
1767   
1768    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
1769}
1770
1771- (void) metadataRetrieved
1772{
1773    fStat = tr_torrentStat(fHandle);
1774   
1775    [self createFileList];
1776   
1777    [[NSNotificationCenter defaultCenter] postNotificationName: @"ResetInspector" object: self];
1778}
1779
1780- (NSString *) etaString
1781{
1782    const NSInteger eta = [self eta];
1783    switch (eta)
1784    {
1785        case TR_ETA_NOT_AVAIL:
1786        case TR_ETA_UNKNOWN:
1787            return NSLocalizedString(@"remaining time unknown", "Torrent -> eta string");
1788        default:
1789            return [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "Torrent -> eta string"),
1790                        [NSString timeString: eta showSeconds: YES maxFields: 2]];
1791    }
1792}
1793
1794- (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path
1795{
1796    CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, true);
1797}
1798
1799@end
Note: See TracBrowser for help on using the repository browser.