source: trunk/macosx/Torrent.m @ 9046

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

whoops

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