source: trunk/macosx/Torrent.m @ 9324

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

remove the header from the file table

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