source: trunk/macosx/Torrent.m @ 7988

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

move the percent to the stop ratio into libtransmission

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