source: trunk/macosx/Torrent.m @ 9350

Last change on this file since 9350 was 9350, checked in by livings124, 13 years ago

more efficient time machine location check

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