source: trunk/macosx/Torrent.m @ 8098

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

the Mac build works with the new speed limit libT code - still a bit quirky

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