source: trunk/macosx/Torrent.m @ 8439

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

update Mac code for DHT (still doesn't compile)

  • Property svn:keywords set to Date Rev Author Id
File size: 62.6 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 8439 2009-05-19 22:51:37Z 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) totalPeersDHT
1290{
1291    return fStat->peersFrom[TR_PEER_FROM_DHT];
1292}
1293
1294- (NSInteger) totalPeersKnown
1295{
1296    return fStat->peersKnown;
1297}
1298
1299- (NSInteger) peersSendingToUs
1300{
1301    return fStat->peersSendingToUs;
1302}
1303
1304- (NSInteger) peersGettingFromUs
1305{
1306    return fStat->peersGettingFromUs;
1307}
1308
1309- (CGFloat) downloadRate
1310{
1311    return fStat->pieceDownloadSpeed;
1312}
1313
1314- (CGFloat) uploadRate
1315{
1316    return fStat->pieceUploadSpeed;
1317}
1318
1319- (CGFloat) totalRate
1320{
1321    return [self downloadRate] + [self uploadRate];
1322}
1323
1324- (uint64_t) haveVerified
1325{
1326    return fStat->haveValid;
1327}
1328
1329- (uint64_t) haveTotal
1330{
1331    return [self haveVerified] + fStat->haveUnchecked;
1332}
1333
1334- (uint64_t) totalSizeSelected
1335{
1336    return fStat->sizeWhenDone;
1337}
1338
1339- (uint64_t) downloadedTotal
1340{
1341    return fStat->downloadedEver;
1342}
1343
1344- (uint64_t) uploadedTotal
1345{
1346    return fStat->uploadedEver;
1347}
1348
1349- (uint64_t) failedHash
1350{
1351    return fStat->corruptEver;
1352}
1353
1354- (CGFloat) swarmSpeed
1355{
1356    return fStat->swarmSpeed;
1357}
1358
1359- (NSInteger) groupValue
1360{
1361    return fGroupValue;
1362}
1363
1364- (void) setGroupValue: (NSInteger) goupValue
1365{
1366    fGroupValue = goupValue;
1367}
1368
1369- (NSInteger) groupOrderValue
1370{
1371    return [[GroupsController groups] rowValueForIndex: fGroupValue];
1372}
1373
1374- (void) checkGroupValueForRemoval: (NSNotification *) notification
1375{
1376    if (fGroupValue != -1 && [[[notification userInfo] objectForKey: @"Index"] intValue] == fGroupValue)
1377        fGroupValue = -1;
1378}
1379
1380- (NSArray *) fileList
1381{
1382    return fFileList;
1383}
1384
1385- (NSInteger) fileCount
1386{
1387    return fInfo->fileCount;
1388}
1389
1390- (void) updateFileStat
1391{
1392    if (fFileStat)
1393        tr_torrentFilesFree(fFileStat, [self fileCount]);
1394   
1395    fFileStat = tr_torrentFiles(fHandle, NULL);
1396}
1397
1398- (CGFloat) fileProgress: (FileListNode *) node
1399{
1400    if ([self isComplete])
1401        return 1.0;
1402   
1403    if (!fFileStat)
1404        [self updateFileStat];
1405   
1406    NSIndexSet * indexSet = [node indexes];
1407   
1408    if ([indexSet count] == 1)
1409        return fFileStat[[indexSet firstIndex]].progress;
1410   
1411    uint64_t have = 0;
1412    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1413        have += fFileStat[index].bytesCompleted;
1414   
1415    NSAssert([node size], @"directory in torrent file has size 0");
1416    return (CGFloat)have / [node size];
1417}
1418
1419- (NSArray *) flatFileList
1420{
1421    return fFlatFileList;
1422}
1423
1424- (BOOL) canChangeDownloadCheckForFile: (NSInteger) index
1425{
1426    if (!fFileStat)
1427        [self updateFileStat];
1428   
1429    return [self fileCount] > 1 && fFileStat[index].progress < 1.0;
1430}
1431
1432- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1433{
1434    if ([self fileCount] <= 1 || [self isComplete])
1435        return NO;
1436   
1437    if (!fFileStat)
1438        [self updateFileStat];
1439   
1440    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1441        if (fFileStat[index].progress < 1.0)
1442            return YES;
1443    return NO;
1444}
1445
1446- (NSInteger) checkForFiles: (NSIndexSet *) indexSet
1447{
1448    BOOL onState = NO, offState = NO;
1449    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1450    {
1451        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1452            onState = YES;
1453        else
1454            offState = YES;
1455       
1456        if (onState && offState)
1457            return NSMixedState;
1458    }
1459    return onState ? NSOnState : NSOffState;
1460}
1461
1462- (void) setFileCheckState: (NSInteger) state forIndexes: (NSIndexSet *) indexSet
1463{
1464    NSUInteger count = [indexSet count];
1465    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1466    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1467        files[i] = index;
1468   
1469    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1470    free(files);
1471   
1472    [self update];
1473    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
1474}
1475
1476- (void) setFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1477{
1478    const NSUInteger count = [indexSet count];
1479    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1480    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1481        files[i] = index;
1482   
1483    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1484    free(files);
1485}
1486
1487- (BOOL) hasFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1488{
1489    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1490        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1491            return YES;
1492    return NO;
1493}
1494
1495- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1496{
1497    BOOL low = NO, normal = NO, high = NO;
1498    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1499   
1500    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1501    {
1502        if (![self canChangeDownloadCheckForFile: index])
1503            continue;
1504       
1505        const tr_priority_t priority = tr_torrentGetFilePriority(fHandle, index);
1506        if (priority == TR_PRI_LOW)
1507        {
1508            if (low)
1509                continue;
1510            low = YES;
1511        }
1512        else if (priority == TR_PRI_HIGH)
1513        {
1514            if (high)
1515                continue;
1516            high = YES;
1517        }
1518        else
1519        {
1520            if (normal)
1521                continue;
1522            normal = YES;
1523        }
1524       
1525        [priorities addObject: [NSNumber numberWithInteger: priority]];
1526        if (low && normal && high)
1527            break;
1528    }
1529    return priorities;
1530}
1531
1532- (NSDate *) dateAdded
1533{
1534    const time_t date = fStat->addedDate;
1535    return [NSDate dateWithTimeIntervalSince1970: date];
1536}
1537
1538- (NSDate *) dateCompleted
1539{
1540    const time_t date = fStat->doneDate;
1541    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1542}
1543
1544- (NSDate *) dateActivity
1545{
1546    const time_t date = fStat->activityDate;
1547    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1548}
1549
1550- (NSDate *) dateActivityOrAdd
1551{
1552    NSDate * date = [self dateActivity];
1553    return date ? date : [self dateAdded];
1554}
1555
1556- (NSInteger) stalledMinutes
1557{
1558    time_t start = fStat->startDate;
1559    if (start == 0)
1560        return -1;
1561   
1562    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start],
1563            * activity = [self dateActivity];
1564   
1565    NSDate * laterDate = activity ? [started laterDate: activity] : started;
1566    return -1 * [laterDate timeIntervalSinceNow] / 60;
1567}
1568
1569- (BOOL) isStalled
1570{
1571    return fStalled;
1572}
1573
1574- (NSInteger) stateSortKey
1575{
1576    if (![self isActive]) //paused
1577        return 0;
1578    else if ([self isSeeding]) //seeding
1579        return 1;
1580    else //downloading
1581        return 2;
1582}
1583
1584- (tr_torrent *) torrentStruct
1585{
1586    return fHandle;
1587}
1588
1589@end
1590
1591@implementation Torrent (Private)
1592
1593//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1594- (id) initWithHash: (NSString *) hashString path: (NSString *) path torrentStruct: (tr_torrent *) torrentStruct lib: (tr_session *) lib
1595        publicTorrent: (NSNumber *) publicTorrent
1596        downloadFolder: (NSString *) downloadFolder
1597        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1598        waitToStart: (NSNumber *) waitToStart
1599        groupValue: (NSNumber *) groupValue addedTrackers: (NSNumber *) addedTrackers
1600{
1601    if (!(self = [super init]))
1602        return nil;
1603   
1604    fDefaults = [NSUserDefaults standardUserDefaults];
1605
1606    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1607    if (fPublicTorrent)
1608        fPublicTorrentLocation = [path retain];
1609   
1610    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1611    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1612   
1613    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1614                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1615    if (fUseIncompleteFolder)
1616    {
1617        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1618        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1619    }
1620   
1621    if (torrentStruct)
1622    {
1623        fHandle = torrentStruct;
1624        fInfo = tr_torrentInfo(fHandle);
1625       
1626        NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: fInfo->name]]
1627                                                ? fIncompleteFolder : fDownloadFolder;
1628        tr_torrentSetDownloadDir(fHandle, [currentDownloadFolder UTF8String]);
1629    }
1630    else
1631    {
1632        //set libtransmission settings for initialization
1633        tr_ctor * ctor = tr_ctorNew(lib);
1634        tr_ctorSetPaused(ctor, TR_FORCE, YES);
1635        tr_ctorSetPeerLimit(ctor, TR_FALLBACK, [fDefaults integerForKey: @"PeersTorrent"]);
1636       
1637        tr_info info;
1638        if (hashString)
1639        {
1640            tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1641            if (tr_torrentParse(ctor, &info) == TR_OK)
1642            {
1643                NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1644                                                    ? fIncompleteFolder : fDownloadFolder;
1645                tr_ctorSetDownloadDir(ctor, TR_FORCE, [currentDownloadFolder UTF8String]);
1646               
1647                fHandle = tr_torrentNew(ctor, NULL);
1648            }
1649            tr_metainfoFree(&info);
1650        }
1651        if (!fHandle && path)
1652        {
1653            tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1654            if (tr_torrentParse(ctor, &info) == TR_OK)
1655            {
1656                NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1657                                                    ? fIncompleteFolder : fDownloadFolder;
1658                tr_ctorSetDownloadDir(ctor, TR_FORCE, [currentDownloadFolder UTF8String]);
1659               
1660                fHandle = tr_torrentNew(ctor, NULL);
1661            }
1662            tr_metainfoFree(&info);
1663        }
1664       
1665        tr_ctorFree(ctor);
1666       
1667        if (!fHandle)
1668        {
1669            [self release];
1670            return nil;
1671        }
1672       
1673        fInfo = tr_torrentInfo(fHandle);
1674    }
1675   
1676    tr_torrentSetCompletenessCallback(fHandle, completenessChangeCallback, self);
1677    tr_torrentSetRatioLimitHitCallback(fHandle, ratioLimitHitCallback, self);
1678   
1679    fNameString = [[NSString alloc] initWithUTF8String: fInfo->name];
1680    fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
1681       
1682    fFinishedSeeding = NO;
1683   
1684    fWaitToStart = waitToStart && [waitToStart boolValue];
1685    fResumeOnWake = NO;
1686       
1687    [self createFileList];
1688       
1689    fGroupValue = groupValue ? [groupValue intValue] : [[GroupsController groups] groupIndexForTorrent: self];
1690   
1691    fAddedTrackers = addedTrackers ? [addedTrackers boolValue] : NO;   
1692   
1693    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1694        name: @"GroupValueRemoved" object: nil];
1695   
1696    [self update];
1697   
1698    //mark incomplete files to be ignored by Time Machine
1699    [self setTimeMachineExclude: ![self allDownloaded] forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1700   
1701    return self;
1702}
1703
1704- (void) createFileList
1705{
1706    if ([self isFolder])
1707    {
1708        NSInteger count = [self fileCount];
1709        NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count],
1710                    * flatFileList = [[NSMutableArray alloc] initWithCapacity: count];
1711       
1712        for (NSInteger i = 0; i < count; i++)
1713        {
1714            tr_file * file = &fInfo->files[i];
1715           
1716            NSMutableArray * pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1717            NSString * path = [pathComponents objectAtIndex: 0];
1718            NSString * name = [pathComponents objectAtIndex: 1];
1719            [pathComponents removeObjectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, 2)]];
1720           
1721            if ([pathComponents count] > 0)
1722            {
1723                //determine if folder node already exists
1724                FileListNode * node;
1725                for (node in fileList)
1726                    if ([[node name] isEqualToString: name] && [node isFolder])
1727                        break;
1728               
1729                if (!node)
1730                {
1731                    node = [[FileListNode alloc] initWithFolderName: name path: path];
1732                    [fileList addObject: node];
1733                    [node release];
1734                }
1735               
1736                [node insertIndex: i withSize: file->length];
1737                [self insertPath: pathComponents forParent: node fileSize: file->length index: i flatList: flatFileList];
1738            }
1739            else
1740            {
1741                FileListNode * node = [[FileListNode alloc] initWithFileName: name path: path size: file->length index: i];
1742                [fileList addObject: node];
1743                [flatFileList addObject: node];
1744                [node release];
1745            }
1746           
1747            [pathComponents release];
1748        }
1749       
1750        fFileList = [[NSArray alloc] initWithArray: fileList];
1751        [fileList release];
1752       
1753        fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1754        [flatFileList release];
1755    }
1756    else
1757    {
1758        FileListNode * node = [[FileListNode alloc] initWithFileName: [self name] path: @"" size: [self size] index: 0];
1759        fFileList = [[NSArray arrayWithObject: node] retain];
1760        fFlatFileList = [fFileList copy];
1761        [node release];
1762    }
1763}
1764
1765- (void) insertPath: (NSMutableArray *) components forParent: (FileListNode *) parent fileSize: (uint64_t) size
1766    index: (NSInteger) index flatList: (NSMutableArray *) flatFileList
1767{
1768    NSString * name = [components objectAtIndex: 0];
1769    BOOL isFolder = [components count] > 1;
1770   
1771    FileListNode * node = nil;
1772    if (isFolder)
1773    {
1774        for (node in [parent children])
1775            if ([[node name] isEqualToString: name] && [node isFolder])
1776                break;
1777    }
1778   
1779    //create new folder or file if it doesn't already exist
1780    if (!node)
1781    {
1782        if (isFolder)
1783            node = [[FileListNode alloc] initWithFolderName: name path: [parent fullPath]];
1784        else
1785        {
1786            node = [[FileListNode alloc] initWithFileName: name path: [parent fullPath] size: size index: index];
1787            [flatFileList addObject: node];
1788        }
1789       
1790        [parent insertChild: node];
1791        [node release];
1792    }
1793   
1794    if (isFolder)
1795    {
1796        [node insertIndex: index withSize: size];
1797       
1798        [components removeObjectAtIndex: 0];
1799        [self insertPath: components forParent: node fileSize: size index: index flatList: flatFileList];
1800    }
1801}
1802
1803- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1804{
1805    return fUseIncompleteFolder &&
1806        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1807}
1808
1809- (void) updateDownloadFolder
1810{
1811    //remove old Time Machine location
1812    [self setTimeMachineExclude: NO forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1813   
1814    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1815    tr_torrentSetDownloadDir(fHandle, [folder UTF8String]);
1816   
1817    [self setTimeMachineExclude: ![self allDownloaded] forPath: [folder stringByAppendingPathComponent: [self name]]];
1818}
1819
1820//status has been retained
1821- (void) completenessChange: (NSNumber *) status
1822{
1823    fStat = tr_torrentStat(fHandle); //don't call update yet to avoid auto-stop
1824   
1825    BOOL canMove;
1826    switch ([status intValue])
1827    {
1828        case TR_SEED:
1829        case TR_PARTIAL_SEED:
1830            canMove = YES;
1831           
1832            //move file from incomplete folder to download folder
1833            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1834                && (canMove = [self alertForMoveFolderAvailable]))
1835            {
1836                [self quickPause];
1837               
1838                if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1839                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
1840                    [self updateDownloadFolder];
1841                else
1842                    canMove = NO;
1843               
1844                [self endQuickPause];
1845            }
1846           
1847            if (!canMove)
1848            {
1849                fUseIncompleteFolder = NO;
1850               
1851                [fDownloadFolder release];
1852                fDownloadFolder = fIncompleteFolder;
1853                fIncompleteFolder = nil;
1854            }
1855           
1856            //allow to be backed up by Time Machine
1857            [self setTimeMachineExclude: NO forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1858           
1859            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1860            break;
1861       
1862        case TR_LEECH:
1863            //do not allow to be backed up by Time Machine
1864            [self setTimeMachineExclude: YES forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1865           
1866            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1867            break;
1868    }
1869    [status release];
1870   
1871    [self update];
1872}
1873
1874- (void) ratioLimitHit
1875{
1876    fStat = tr_torrentStat(fHandle);
1877   
1878    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
1879   
1880    fFinishedSeeding = YES;
1881}
1882
1883- (void) quickPause
1884{
1885    if (fQuickPauseDict)
1886        return;
1887
1888    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1889                    [NSNumber numberWithInt: [self usesSpeedLimit: YES]], @"UploadUsesSpeedLimit",
1890                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1891                    [NSNumber numberWithInt: [self usesSpeedLimit: NO]], @"DownloadUsesSpeedLimit",
1892                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1893   
1894    [self setUseSpeedLimit: YES upload: YES];
1895    [self setSpeedLimit: 0 upload: YES];
1896    [self setUseSpeedLimit: YES upload: NO];
1897    [self setSpeedLimit: 0 upload: NO];
1898}
1899
1900- (void) endQuickPause
1901{
1902    if (!fQuickPauseDict)
1903        return;
1904   
1905    [self setUseSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadUsesSpeedLimit"] intValue] upload: YES];
1906    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1907    [self setUseSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadUsesSpeedLimit"] intValue] upload: NO];
1908    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1909   
1910    [fQuickPauseDict release];
1911    fQuickPauseDict = nil;
1912}
1913
1914- (NSString *) etaString
1915{
1916    const NSInteger eta = [self eta];
1917    switch (eta)
1918    {
1919        case TR_ETA_NOT_AVAIL:
1920        case TR_ETA_UNKNOWN:
1921            return NSLocalizedString(@"remaining time unknown", "Torrent -> eta string");
1922        default:
1923            return [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "Torrent -> eta string"),
1924                        [NSString timeString: eta showSeconds: YES maxFields: 2]];
1925    }
1926}
1927
1928- (void) updateAllTrackers: (NSMutableArray *) trackers
1929{
1930    //get count
1931    NSInteger count = 0;
1932    for (id object in trackers)
1933        if (![object isKindOfClass: [NSNumber class]])
1934            count++;
1935   
1936    //recreate the tracker structure
1937    tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, count);
1938    NSInteger tier = 0, i = 0;
1939    for (id object in trackers)
1940    {
1941        if (![object isKindOfClass: [NSNumber class]])
1942        {
1943            trackerStructs[i].tier = tier;
1944            trackerStructs[i].announce = (char *)[object UTF8String];
1945            i++;
1946        }
1947        else
1948            tier++;
1949    }
1950   
1951    tr_torrentSetAnnounceList(fHandle, trackerStructs, count);
1952    tr_free(trackerStructs);
1953}
1954
1955+ (void) trashFile: (NSString *) path
1956{
1957    //attempt to move to trash
1958    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1959        source: [path stringByDeletingLastPathComponent] destination: @""
1960        files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1961    {
1962        //if cannot trash, just delete it (will work if it's on a remote volume)
1963        NSError * error;
1964        if (![[NSFileManager defaultManager] removeItemAtPath: path error: &error])
1965            NSLog(@"Could not trash %@: %@", path, [error localizedDescription]);
1966    }
1967}
1968
1969- (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path
1970{
1971    CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, true);
1972}
1973
1974@end
Note: See TracBrowser for help on using the repository browser.