source: trunk/macosx/Torrent.m @ 8386

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

#2045 hold down option key on launch to start with all torrents paused

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