source: trunk/macosx/Torrent.m @ 8067

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

r8061 whoops

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