source: trunk/macosx/Torrent.m @ 9204

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

dead code removal

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