source: trunk/macosx/Torrent.m @ 7894

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

assorted trivial cleanup

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