source: trunk/macosx/Torrent.m @ 8470

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

save internal torrent path to history, and prefer that over the hash when loading on startup

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