source: trunk/macosx/Torrent.m @ 10482

Last change on this file since 10482 was 10482, checked in by charles, 12 years ago

"this time for sure!"

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