source: trunk/macosx/Torrent.m @ 9348

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

update the time machine exclude list when the file is moved through rpc

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