source: trunk/macosx/Torrent.m @ 9349

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

block while moving a file (for now)

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