source: trunk/macosx/Torrent.m @ 9342

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

use libtransmission for moving, incomplete directory, and directory controlling in general (still rough around the edges)

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