source: trunk/macosx/Torrent.m @ 9191

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

actually return the array of flat trackers from the corresponding method

  • Property svn:keywords set to Date Rev Author Id
File size: 60.6 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 9191 2009-09-27 15:49:33Z 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/*- (NSString *) trackerAddressAnnounce
676{
677#warning fix
678    return @"fix me!";
679    //return fStat->announceURL ? [NSString stringWithUTF8String: fStat->announceURL] : nil;
680}
681
682- (NSDate *) lastAnnounceTime
683{
684    NSInteger date = fStat->lastAnnounceTime;
685    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
686}
687
688- (NSInteger) nextAnnounceTime
689{
690    NSInteger date = fStat->nextAnnounceTime;
691    NSTimeInterval difference;
692    switch (date)
693    {
694        case 0:
695            return STAT_TIME_NONE;
696        case 1:
697            return STAT_TIME_NOW;
698        default:
699            difference = [[NSDate dateWithTimeIntervalSince1970: date] timeIntervalSinceNow];
700            return difference > 0 ? (NSInteger)difference : STAT_TIME_NONE;
701    }
702}
703
704- (NSString *) announceResponse
705{
706    return [NSString stringWithUTF8String: fStat->announceResponse];
707}
708
709- (NSString *) trackerAddressScrape
710{
711#warning fix
712    return @"fix me!";
713    //return fStat->scrapeURL ? [NSString stringWithUTF8String: fStat->scrapeURL] : nil;
714}
715
716- (NSDate *) lastScrapeTime
717{
718    NSInteger date = fStat->lastScrapeTime;
719    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
720}
721
722- (NSInteger) nextScrapeTime
723{
724    NSInteger date = fStat->nextScrapeTime;
725    NSTimeInterval difference;
726    switch (date)
727    {
728        case 0:
729            return STAT_TIME_NONE;
730        case 1:
731            return STAT_TIME_NOW;
732        default:
733            difference = [[NSDate dateWithTimeIntervalSince1970: date] timeIntervalSinceNow];
734            return difference > 0 ? (NSInteger)difference : STAT_TIME_NONE;
735    }
736}
737
738- (NSString *) scrapeResponse
739{
740    return [NSString stringWithUTF8String: fStat->scrapeResponse];
741}*/
742
743- (NSArray *) allTrackerStats
744{
745    int count;
746    tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
747   
748    NSMutableArray * trackers = [NSMutableArray arrayWithCapacity: count + stats[count-1].tier];
749   
750    int prevTier = -1;
751    for (int i=0; i < count; ++i)
752    {
753        if (stats[i].tier != prevTier)
754        {
755            [trackers addObject: [NSNumber numberWithInteger: stats[i].tier]];
756            prevTier = stats[i].tier;
757        }
758       
759        TrackerNode * tracker = [[TrackerNode alloc] initWithTrackerStat: &stats[i]];
760        [trackers addObject: tracker];
761        [tracker release];
762    }
763   
764    tr_torrentTrackersFree(stats, count);
765    return trackers;
766}
767
768- (NSArray *) allTrackersFlat
769{
770    NSMutableArray * allTrackers = [NSMutableArray arrayWithCapacity: fInfo->trackerCount];
771   
772    for (NSInteger i=0; i < fInfo->trackerCount; i++)
773        [allTrackers addObject: [NSString stringWithUTF8String: fInfo->trackers[i].announce]];
774   
775    return allTrackers;
776}
777
778/*- (BOOL) updateAllTrackersForAdd: (NSMutableArray *) trackers
779{
780    NSString * tracker = [trackers lastObject];
781    tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
782   
783    if ([tracker rangeOfString: @"://"].location == NSNotFound)
784    {
785        tracker = [@"http://" stringByAppendingString: tracker];
786        [trackers replaceObjectAtIndex: [trackers count]-1 withObject: tracker];
787    }
788   
789    if (!tr_httpIsValidURL([tracker UTF8String]))
790        return NO;
791   
792    [self updateAllTrackers: trackers];
793   
794    return YES;
795}
796
797- (void) updateAllTrackersForRemove: (NSMutableArray *) trackers
798{
799    [self updateAllTrackers: trackers];
800}*/
801
802- (NSString *) comment
803{
804    return [NSString stringWithUTF8String: fInfo->comment];
805}
806
807- (NSString *) creator
808{
809    return [NSString stringWithUTF8String: fInfo->creator];
810}
811
812- (NSDate *) dateCreated
813{
814    NSInteger date = fInfo->dateCreated;
815    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
816}
817
818- (NSInteger) pieceSize
819{
820    return fInfo->pieceSize;
821}
822
823- (NSInteger) pieceCount
824{
825    return fInfo->pieceCount;
826}
827
828- (NSString *) hashString
829{
830    return fHashString;
831}
832
833- (BOOL) privateTorrent
834{
835    return fInfo->isPrivate;
836}
837
838- (NSString *) torrentLocation
839{
840    return [NSString stringWithUTF8String: fInfo->torrent];
841}
842
843- (NSString *) dataLocation
844{
845    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
846}
847
848- (CGFloat) progress
849{
850    return fStat->percentComplete;
851}
852
853- (CGFloat) progressDone
854{
855    return fStat->percentDone;
856}
857
858- (CGFloat) checkingProgress
859{
860    return fStat->recheckProgress;
861}
862
863- (NSInteger) eta
864{
865    return fStat->eta;
866}
867
868- (CGFloat) availableDesired
869{
870    return (CGFloat)fStat->desiredAvailable / [self sizeLeft];
871}
872
873- (BOOL) isActive
874{
875    return fStat->activity != TR_STATUS_STOPPED;
876}
877
878- (BOOL) isSeeding
879{
880    return fStat->activity == TR_STATUS_SEED;
881}
882
883- (BOOL) isChecking
884{
885    return fStat->activity == TR_STATUS_CHECK || fStat->activity == TR_STATUS_CHECK_WAIT;
886}
887
888- (BOOL) isCheckingWaiting
889{
890    return fStat->activity == TR_STATUS_CHECK_WAIT;
891}
892
893- (BOOL) allDownloaded
894{
895    return [self sizeLeft] == 0;
896}
897
898- (BOOL) isComplete
899{
900    return [self progress] >= 1.0;
901}
902
903- (BOOL) isError
904{
905    return fStat->error == TR_STAT_LOCAL_ERROR || fStat->error == TR_STAT_TRACKER_ERROR;
906}
907
908- (BOOL) isErrorOrWarning
909{
910    return fStat->error != TR_STAT_OK;
911}
912
913- (NSString *) errorMessage
914{
915    if (![self isErrorOrWarning])
916        return @"";
917   
918    NSString * error;
919    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
920        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
921        error = [NSString stringWithFormat: @"(%@)", NSLocalizedString(@"unreadable error", "Torrent -> error string unreadable")];
922   
923    return error;
924}
925
926- (NSArray *) peers
927{
928    int totalPeers;
929    tr_peer_stat * peers = tr_torrentPeers(fHandle, &totalPeers);
930   
931    NSMutableArray * peerDicts = [NSMutableArray arrayWithCapacity: totalPeers];
932   
933    for (int i = 0; i < totalPeers; i++)
934    {
935        tr_peer_stat * peer = &peers[i];
936        NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 10];
937       
938        [dict setObject: [NSNumber numberWithInt: peer->from] forKey: @"From"];
939        [dict setObject: [NSString stringWithUTF8String: peer->addr] forKey: @"IP"];
940        [dict setObject: [NSNumber numberWithInt: peer->port] forKey: @"Port"];
941        [dict setObject: [NSNumber numberWithFloat: peer->progress] forKey: @"Progress"];
942        [dict setObject: [NSNumber numberWithBool: peer->isSeed] forKey: @"Seed"];
943        [dict setObject: [NSNumber numberWithBool: peer->isEncrypted] forKey: @"Encryption"];
944        [dict setObject: [NSString stringWithUTF8String: peer->client] forKey: @"Client"];
945        [dict setObject: [NSString stringWithUTF8String: peer->flagStr] forKey: @"Flags"];
946       
947        if (peer->isUploadingTo)
948            [dict setObject: [NSNumber numberWithFloat: peer->rateToPeer] forKey: @"UL To Rate"];
949        if (peer->isDownloadingFrom)
950            [dict setObject: [NSNumber numberWithFloat: peer->rateToClient] forKey: @"DL From Rate"];
951       
952        [peerDicts addObject: dict];
953    }
954   
955    tr_torrentPeersFree(peers, totalPeers);
956   
957    return peerDicts;
958}
959
960- (NSUInteger) webSeedCount
961{
962    return fInfo->webseedCount;
963}
964
965- (NSArray *) webSeeds
966{
967    const NSInteger webSeedCount = fInfo->webseedCount;
968    NSMutableArray * webSeeds = [NSMutableArray arrayWithCapacity: webSeedCount];
969   
970    float * dlSpeeds = tr_torrentWebSpeeds(fHandle);
971   
972    for (NSInteger i = 0; i < webSeedCount; i++)
973    {
974        NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 2];
975       
976        [dict setObject: [NSString stringWithUTF8String: fInfo->webseeds[i]] forKey: @"Address"];
977       
978        if (dlSpeeds[i] != -1.0)
979            [dict setObject: [NSNumber numberWithFloat: dlSpeeds[i]] forKey: @"DL From Rate"];
980       
981        [webSeeds addObject: dict];
982    }
983   
984    tr_free(dlSpeeds);
985   
986    return webSeeds;
987}
988
989- (NSString *) progressString
990{
991    NSString * string;
992   
993    if (![self allDownloaded])
994    {
995        CGFloat progress;
996        if ([self isFolder] && [fDefaults boolForKey: @"DisplayStatusProgressSelected"])
997        {
998            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ selected", "Torrent -> progress string"),
999                        [NSString stringForFileSize: [self haveTotal]], [NSString stringForFileSize: [self totalSizeSelected]]];
1000            progress = 100.0 * [self progressDone];
1001        }
1002        else
1003        {
1004            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "Torrent -> progress string"),
1005                        [NSString stringForFileSize: [self haveTotal]], [NSString stringForFileSize: [self size]]];
1006            progress = 100.0 * [self progress];
1007        }
1008       
1009        string = [NSString localizedStringWithFormat: @"%@ (%.2f%%)", string, progress];
1010    }
1011    else
1012    {
1013        NSString * downloadString;
1014        if (![self isComplete]) //only multifile possible
1015        {
1016            if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
1017                downloadString = [NSString stringWithFormat: NSLocalizedString(@"%@ selected", "Torrent -> progress string"),
1018                                    [NSString stringForFileSize: [self haveTotal]]];
1019            else
1020            {
1021                downloadString = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "Torrent -> progress string"),
1022                                    [NSString stringForFileSize: [self haveTotal]], [NSString stringForFileSize: [self size]]];
1023               
1024                downloadString = [NSString localizedStringWithFormat: @"%@ (%.2f%%)", downloadString, 100.0 * [self progress]];
1025            }
1026        }
1027        else
1028            downloadString = [NSString stringForFileSize: [self size]];
1029       
1030        NSString * uploadString = [NSString stringWithFormat: NSLocalizedString(@"uploaded %@ (Ratio: %@)",
1031                                    "Torrent -> progress string"), [NSString stringForFileSize: [self uploadedTotal]],
1032                                    [NSString stringForRatio: [self ratio]]];
1033       
1034        string = [downloadString stringByAppendingFormat: @", %@", uploadString];
1035    }
1036   
1037    //add time when downloading
1038    if (fStat->activity == TR_STATUS_DOWNLOAD || ([self isSeeding] && [self seedRatioSet]))
1039        string = [string stringByAppendingFormat: @" - %@", [self etaString]];
1040   
1041    return string;
1042}
1043
1044- (NSString *) statusString
1045{
1046    NSString * string;
1047   
1048    if ([self isErrorOrWarning])
1049    {
1050        switch (fStat->error)
1051        {
1052            case TR_STAT_LOCAL_ERROR: string = NSLocalizedString(@"Error", "Torrent -> status string"); break;
1053            case TR_STAT_TRACKER_ERROR: string = NSLocalizedString(@"Tracker returned an error", "Torrent -> status string"); break;
1054            case TR_STAT_TRACKER_WARNING: string = NSLocalizedString(@"Tracker returned a warning", "Torrent -> status string"); break;
1055            default: NSAssert(NO, @"unknown error state");
1056        }
1057       
1058        NSString * errorString = [self errorMessage];
1059        if (errorString && ![errorString isEqualToString: @""])
1060            string = [string stringByAppendingFormat: @": %@", errorString];
1061    }
1062    else
1063    {
1064        switch (fStat->activity)
1065        {
1066            case TR_STATUS_STOPPED:
1067                if (fWaitToStart)
1068                {
1069                    string = ![self allDownloaded]
1070                            ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
1071                            : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1072                }
1073                else if (fFinishedSeeding)
1074                    string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1075                else
1076                    string = NSLocalizedString(@"Paused", "Torrent -> status string");
1077                break;
1078
1079            case TR_STATUS_CHECK_WAIT:
1080                string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1081                break;
1082
1083            case TR_STATUS_CHECK:
1084                string = [NSString localizedStringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1085                                        "Torrent -> status string"), 100.0 * [self checkingProgress]];
1086                break;
1087
1088            case TR_STATUS_DOWNLOAD:
1089                if ([self totalPeersConnected] != 1)
1090                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
1091                                                    "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
1092                else
1093                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
1094                                                    "Torrent -> status string"), [self peersSendingToUs]];
1095               
1096                NSInteger webSeedCount = fStat->webseedsSendingToUs;
1097                if (webSeedCount > 0)
1098                {
1099                    NSString * webSeedString;
1100                    if (webSeedCount == 1)
1101                        webSeedString = NSLocalizedString(@"web seed", "Torrent -> status string");
1102                    else
1103                        webSeedString = [NSString stringWithFormat: NSLocalizedString(@"%d web seeds", "Torrent -> status string"),
1104                                                                    webSeedCount];
1105                   
1106                    string = [string stringByAppendingFormat: @" + %@", webSeedString];
1107                }
1108               
1109                break;
1110
1111            case TR_STATUS_SEED:
1112                if ([self totalPeersConnected] != 1)
1113                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
1114                                                    [self peersGettingFromUs], [self totalPeersConnected]];
1115                else
1116                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
1117                                                    [self peersGettingFromUs]];
1118        }
1119       
1120        if (fStalled)
1121            string = [NSLocalizedString(@"Stalled", "Torrent -> status string") stringByAppendingFormat: @", %@", string];
1122    }
1123   
1124    //append even if error
1125    if ([self isActive] && ![self isChecking])
1126    {
1127        if (fStat->activity == TR_STATUS_DOWNLOAD)
1128            string = [string stringByAppendingFormat: @" - %@: %@, %@: %@",
1129                        NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1130                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1131        else
1132            string = [string stringByAppendingFormat: @" - %@: %@",
1133                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1134    }
1135   
1136    return string;
1137}
1138
1139- (NSString *) shortStatusString
1140{
1141    NSString * string;
1142   
1143    switch (fStat->activity)
1144    {
1145        case TR_STATUS_STOPPED:
1146            if (fWaitToStart)
1147            {
1148                string = ![self allDownloaded]
1149                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
1150                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1151            }
1152            else if (fFinishedSeeding)
1153                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1154            else
1155                string = NSLocalizedString(@"Paused", "Torrent -> status string");
1156            break;
1157
1158        case TR_STATUS_CHECK_WAIT:
1159            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1160            break;
1161
1162        case TR_STATUS_CHECK:
1163            string = [NSString localizedStringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1164                                    "Torrent -> status string"), 100.0 * [self checkingProgress]];
1165            break;
1166       
1167        case TR_STATUS_DOWNLOAD:
1168            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1169                            NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1170                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1171            break;
1172       
1173        case TR_STATUS_SEED:
1174            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1175                            NSLocalizedString(@"Ratio", "Torrent -> status string"), [NSString stringForRatio: [self ratio]],
1176                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1177    }
1178   
1179    return string;
1180}
1181
1182- (NSString *) remainingTimeString
1183{
1184    if (fStat->activity == TR_STATUS_DOWNLOAD || ([self isSeeding] && [self seedRatioSet]))
1185        return [self etaString];
1186    else
1187        return [self shortStatusString];
1188}
1189
1190- (NSString *) stateString
1191{
1192    switch (fStat->activity)
1193    {
1194        case TR_STATUS_STOPPED:
1195            return NSLocalizedString(@"Paused", "Torrent -> status string");
1196
1197        case TR_STATUS_CHECK:
1198            return [NSString localizedStringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1199                                    "Torrent -> status string"), 100.0 * [self checkingProgress]];
1200       
1201        case TR_STATUS_CHECK_WAIT:
1202            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1203
1204        case TR_STATUS_DOWNLOAD:
1205            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1206
1207        case TR_STATUS_SEED:
1208            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1209    }
1210}
1211
1212- (NSInteger) totalPeersConnected
1213{
1214    return fStat->peersConnected;
1215}
1216
1217- (NSInteger) totalPeersTracker
1218{
1219    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1220}
1221
1222- (NSInteger) totalPeersIncoming
1223{
1224    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1225}
1226
1227- (NSInteger) totalPeersCache
1228{
1229    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1230}
1231
1232- (NSInteger) totalPeersPex
1233{
1234    return fStat->peersFrom[TR_PEER_FROM_PEX];
1235}
1236
1237- (NSInteger) totalPeersDHT
1238{
1239    return fStat->peersFrom[TR_PEER_FROM_DHT];
1240}
1241
1242- (NSInteger) totalPeersKnown
1243{
1244    return fStat->peersKnown;
1245}
1246
1247- (NSInteger) peersSendingToUs
1248{
1249    return fStat->peersSendingToUs;
1250}
1251
1252- (NSInteger) peersGettingFromUs
1253{
1254    return fStat->peersGettingFromUs;
1255}
1256
1257- (CGFloat) downloadRate
1258{
1259    return fStat->pieceDownloadSpeed;
1260}
1261
1262- (CGFloat) uploadRate
1263{
1264    return fStat->pieceUploadSpeed;
1265}
1266
1267- (CGFloat) totalRate
1268{
1269    return [self downloadRate] + [self uploadRate];
1270}
1271
1272- (uint64_t) haveVerified
1273{
1274    return fStat->haveValid;
1275}
1276
1277- (uint64_t) haveTotal
1278{
1279    return [self haveVerified] + fStat->haveUnchecked;
1280}
1281
1282- (uint64_t) totalSizeSelected
1283{
1284    return fStat->sizeWhenDone;
1285}
1286
1287- (uint64_t) downloadedTotal
1288{
1289    return fStat->downloadedEver;
1290}
1291
1292- (uint64_t) uploadedTotal
1293{
1294    return fStat->uploadedEver;
1295}
1296
1297- (uint64_t) failedHash
1298{
1299    return fStat->corruptEver;
1300}
1301
1302- (CGFloat) swarmSpeed
1303{
1304    return fStat->swarmSpeed;
1305}
1306
1307- (NSInteger) groupValue
1308{
1309    return fGroupValue;
1310}
1311
1312- (void) setGroupValue: (NSInteger) goupValue
1313{
1314    fGroupValue = goupValue;
1315}
1316
1317- (NSInteger) groupOrderValue
1318{
1319    return [[GroupsController groups] rowValueForIndex: fGroupValue];
1320}
1321
1322- (void) checkGroupValueForRemoval: (NSNotification *) notification
1323{
1324    if (fGroupValue != -1 && [[[notification userInfo] objectForKey: @"Index"] intValue] == fGroupValue)
1325        fGroupValue = -1;
1326}
1327
1328- (NSArray *) fileList
1329{
1330    return fFileList;
1331}
1332
1333- (NSInteger) fileCount
1334{
1335    return fInfo->fileCount;
1336}
1337
1338- (void) updateFileStat
1339{
1340    if (fFileStat)
1341        tr_torrentFilesFree(fFileStat, [self fileCount]);
1342   
1343    fFileStat = tr_torrentFiles(fHandle, NULL);
1344}
1345
1346- (CGFloat) fileProgress: (FileListNode *) node
1347{
1348    if ([self isComplete])
1349        return 1.0;
1350   
1351    if (!fFileStat)
1352        [self updateFileStat];
1353   
1354    NSIndexSet * indexSet = [node indexes];
1355   
1356    if ([indexSet count] == 1)
1357        return fFileStat[[indexSet firstIndex]].progress;
1358   
1359    uint64_t have = 0;
1360    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1361        have += fFileStat[index].bytesCompleted;
1362   
1363    NSAssert([node size], @"directory in torrent file has size 0");
1364    return (CGFloat)have / [node size];
1365}
1366
1367- (NSArray *) flatFileList
1368{
1369    return fFlatFileList;
1370}
1371
1372- (BOOL) canChangeDownloadCheckForFile: (NSInteger) index
1373{
1374    if (!fFileStat)
1375        [self updateFileStat];
1376   
1377    return [self fileCount] > 1 && fFileStat[index].progress < 1.0;
1378}
1379
1380- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1381{
1382    if ([self fileCount] <= 1 || [self isComplete])
1383        return NO;
1384   
1385    if (!fFileStat)
1386        [self updateFileStat];
1387   
1388    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1389        if (fFileStat[index].progress < 1.0)
1390            return YES;
1391    return NO;
1392}
1393
1394- (NSInteger) checkForFiles: (NSIndexSet *) indexSet
1395{
1396    BOOL onState = NO, offState = NO;
1397    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1398    {
1399        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1400            onState = YES;
1401        else
1402            offState = YES;
1403       
1404        if (onState && offState)
1405            return NSMixedState;
1406    }
1407    return onState ? NSOnState : NSOffState;
1408}
1409
1410- (void) setFileCheckState: (NSInteger) state forIndexes: (NSIndexSet *) indexSet
1411{
1412    NSUInteger count = [indexSet count];
1413    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1414    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1415        files[i] = index;
1416   
1417    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1418    free(files);
1419   
1420    [self update];
1421    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
1422}
1423
1424- (void) setFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1425{
1426    const NSUInteger count = [indexSet count];
1427    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1428    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1429        files[i] = index;
1430   
1431    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1432    free(files);
1433}
1434
1435- (BOOL) hasFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1436{
1437    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1438        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1439            return YES;
1440    return NO;
1441}
1442
1443- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1444{
1445    BOOL low = NO, normal = NO, high = NO;
1446    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1447   
1448    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1449    {
1450        if (![self canChangeDownloadCheckForFile: index])
1451            continue;
1452       
1453        const tr_priority_t priority = tr_torrentGetFilePriority(fHandle, index);
1454        if (priority == TR_PRI_LOW)
1455        {
1456            if (low)
1457                continue;
1458            low = YES;
1459        }
1460        else if (priority == TR_PRI_HIGH)
1461        {
1462            if (high)
1463                continue;
1464            high = YES;
1465        }
1466        else
1467        {
1468            if (normal)
1469                continue;
1470            normal = YES;
1471        }
1472       
1473        [priorities addObject: [NSNumber numberWithInteger: priority]];
1474        if (low && normal && high)
1475            break;
1476    }
1477    return priorities;
1478}
1479
1480- (NSDate *) dateAdded
1481{
1482    const time_t date = fStat->addedDate;
1483    return [NSDate dateWithTimeIntervalSince1970: date];
1484}
1485
1486- (NSDate *) dateCompleted
1487{
1488    const time_t date = fStat->doneDate;
1489    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1490}
1491
1492- (NSDate *) dateActivity
1493{
1494    const time_t date = fStat->activityDate;
1495    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1496}
1497
1498- (NSDate *) dateActivityOrAdd
1499{
1500    NSDate * date = [self dateActivity];
1501    return date ? date : [self dateAdded];
1502}
1503
1504- (NSInteger) stalledMinutes
1505{
1506    time_t start = fStat->startDate;
1507    if (start == 0)
1508        return -1;
1509   
1510    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start],
1511            * activity = [self dateActivity];
1512   
1513    NSDate * laterDate = activity ? [started laterDate: activity] : started;
1514    return -1 * [laterDate timeIntervalSinceNow] / 60;
1515}
1516
1517- (BOOL) isStalled
1518{
1519    return fStalled;
1520}
1521
1522- (NSInteger) stateSortKey
1523{
1524    if (![self isActive]) //paused
1525        return 0;
1526    else if ([self isSeeding]) //seeding
1527        return 1;
1528    else //downloading
1529        return 2;
1530}
1531
1532- (tr_torrent *) torrentStruct
1533{
1534    return fHandle;
1535}
1536
1537- (NSURL *) previewItemURL
1538{
1539    return [NSURL fileURLWithPath: [self dataLocation]];
1540}
1541
1542@end
1543
1544@implementation Torrent (Private)
1545
1546- (id) initWithPath: (NSString *) path hash: (NSString *) hashString torrentStruct: (tr_torrent *) torrentStruct lib: (tr_session *) lib
1547        downloadFolder: (NSString *) downloadFolder
1548        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1549        waitToStart: (NSNumber *) waitToStart
1550        groupValue: (NSNumber *) groupValue
1551{
1552    if (!(self = [super init]))
1553        return nil;
1554   
1555    fDefaults = [NSUserDefaults standardUserDefaults];
1556   
1557    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1558    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1559   
1560    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1561                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1562    if (fUseIncompleteFolder)
1563    {
1564        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1565        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1566    }
1567   
1568    if (torrentStruct)
1569    {
1570        fHandle = torrentStruct;
1571        fInfo = tr_torrentInfo(fHandle);
1572       
1573        NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: fInfo->name]]
1574                                                ? fIncompleteFolder : fDownloadFolder;
1575        tr_torrentSetDownloadDir(fHandle, [currentDownloadFolder UTF8String]);
1576    }
1577    else
1578    {
1579        //set libtransmission settings for initialization
1580        tr_ctor * ctor = tr_ctorNew(lib);
1581        tr_ctorSetPaused(ctor, TR_FORCE, YES);
1582        tr_ctorSetPeerLimit(ctor, TR_FALLBACK, [fDefaults integerForKey: @"PeersTorrent"]);
1583       
1584        int result = TR_PARSE_ERR;
1585        if (path)
1586            result = tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1587       
1588        //backup - shouldn't be needed after upgrade to 1.70
1589        if (result != TR_PARSE_OK && hashString)
1590            result = tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1591       
1592        if (result == TR_PARSE_OK)
1593        {
1594            tr_info info;
1595            result = tr_torrentParse(ctor, &info);
1596           
1597            if (result == TR_PARSE_OK)
1598            {
1599                NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1600                                                    ? fIncompleteFolder : fDownloadFolder;
1601                tr_ctorSetDownloadDir(ctor, TR_FORCE, [currentDownloadFolder UTF8String]);
1602               
1603                fHandle = tr_torrentNew(ctor, NULL);
1604            }
1605            if (result != TR_PARSE_ERR)
1606                tr_metainfoFree(&info);
1607        }
1608       
1609        tr_ctorFree(ctor);
1610       
1611        if (!fHandle)
1612        {
1613            [self release];
1614            return nil;
1615        }
1616       
1617        fInfo = tr_torrentInfo(fHandle);
1618    }
1619   
1620    tr_torrentSetCompletenessCallback(fHandle, completenessChangeCallback, self);
1621    tr_torrentSetRatioLimitHitCallback(fHandle, ratioLimitHitCallback, self);
1622   
1623    fNameString = [[NSString alloc] initWithUTF8String: fInfo->name];
1624    fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
1625       
1626    fFinishedSeeding = NO;
1627   
1628    fWaitToStart = waitToStart && [waitToStart boolValue];
1629    fResumeOnWake = NO;
1630       
1631    [self createFileList];
1632       
1633    fGroupValue = groupValue ? [groupValue intValue] : [[GroupsController groups] groupIndexForTorrent: self];
1634   
1635    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1636        name: @"GroupValueRemoved" object: nil];
1637   
1638    [self update];
1639   
1640    //mark incomplete files to be ignored by Time Machine
1641    [self setTimeMachineExclude: ![self allDownloaded] forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1642   
1643    return self;
1644}
1645
1646- (void) createFileList
1647{
1648    if ([self isFolder])
1649    {
1650        NSInteger count = [self fileCount];
1651        NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count],
1652                    * flatFileList = [[NSMutableArray alloc] initWithCapacity: count];
1653       
1654        for (NSInteger i = 0; i < count; i++)
1655        {
1656            tr_file * file = &fInfo->files[i];
1657           
1658            NSMutableArray * pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1659            NSString * path = [pathComponents objectAtIndex: 0];
1660            NSString * name = [pathComponents objectAtIndex: 1];
1661            [pathComponents removeObjectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, 2)]];
1662           
1663            if ([pathComponents count] > 0)
1664            {
1665                //determine if folder node already exists
1666                FileListNode * node;
1667                for (node in fileList)
1668                    if ([[node name] isEqualToString: name] && [node isFolder])
1669                        break;
1670               
1671                if (!node)
1672                {
1673                    node = [[FileListNode alloc] initWithFolderName: name path: path];
1674                    [fileList addObject: node];
1675                    [node release];
1676                }
1677               
1678                [node insertIndex: i withSize: file->length];
1679                [self insertPath: pathComponents forParent: node fileSize: file->length index: i flatList: flatFileList];
1680            }
1681            else
1682            {
1683                FileListNode * node = [[FileListNode alloc] initWithFileName: name path: path size: file->length index: i];
1684                [fileList addObject: node];
1685                [flatFileList addObject: node];
1686                [node release];
1687            }
1688           
1689            [pathComponents release];
1690        }
1691       
1692        fFileList = [[NSArray alloc] initWithArray: fileList];
1693        [fileList release];
1694       
1695        fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1696        [flatFileList release];
1697    }
1698    else
1699    {
1700        FileListNode * node = [[FileListNode alloc] initWithFileName: [self name] path: @"" size: [self size] index: 0];
1701        fFileList = [[NSArray arrayWithObject: node] retain];
1702        fFlatFileList = [fFileList copy];
1703        [node release];
1704    }
1705}
1706
1707- (void) insertPath: (NSMutableArray *) components forParent: (FileListNode *) parent fileSize: (uint64_t) size
1708    index: (NSInteger) index flatList: (NSMutableArray *) flatFileList
1709{
1710    NSString * name = [components objectAtIndex: 0];
1711    BOOL isFolder = [components count] > 1;
1712   
1713    FileListNode * node = nil;
1714    if (isFolder)
1715    {
1716        for (node in [parent children])
1717            if ([[node name] isEqualToString: name] && [node isFolder])
1718                break;
1719    }
1720   
1721    //create new folder or file if it doesn't already exist
1722    if (!node)
1723    {
1724        if (isFolder)
1725            node = [[FileListNode alloc] initWithFolderName: name path: [parent fullPath]];
1726        else
1727        {
1728            node = [[FileListNode alloc] initWithFileName: name path: [parent fullPath] size: size index: index];
1729            [flatFileList addObject: node];
1730        }
1731       
1732        [parent insertChild: node];
1733        [node release];
1734    }
1735   
1736    if (isFolder)
1737    {
1738        [node insertIndex: index withSize: size];
1739       
1740        [components removeObjectAtIndex: 0];
1741        [self insertPath: components forParent: node fileSize: size index: index flatList: flatFileList];
1742    }
1743}
1744
1745- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1746{
1747    return fUseIncompleteFolder &&
1748        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1749}
1750
1751- (void) updateDownloadFolder
1752{
1753    //remove old Time Machine location
1754    [self setTimeMachineExclude: NO forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1755   
1756    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1757    tr_torrentSetDownloadDir(fHandle, [folder UTF8String]);
1758   
1759    [self setTimeMachineExclude: ![self allDownloaded] forPath: [folder stringByAppendingPathComponent: [self name]]];
1760}
1761
1762//status has been retained
1763- (void) completenessChange: (NSNumber *) status
1764{
1765    fStat = tr_torrentStat(fHandle); //don't call update yet to avoid auto-stop
1766   
1767    BOOL canMove;
1768    switch ([status intValue])
1769    {
1770        case TR_SEED:
1771        case TR_PARTIAL_SEED:
1772            canMove = YES;
1773           
1774            //move file from incomplete folder to download folder
1775            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1776                && (canMove = [self alertForMoveFolderAvailable]))
1777            {
1778                [self quickPause];
1779               
1780                if ([[NSFileManager defaultManager] moveItemAtPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1781                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] error: NULL])
1782                    [self updateDownloadFolder];
1783                else
1784                    canMove = NO;
1785               
1786                [self endQuickPause];
1787            }
1788           
1789            if (!canMove)
1790            {
1791                fUseIncompleteFolder = NO;
1792               
1793                [fDownloadFolder release];
1794                fDownloadFolder = fIncompleteFolder;
1795                fIncompleteFolder = nil;
1796            }
1797           
1798            //allow to be backed up by Time Machine
1799            [self setTimeMachineExclude: NO forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1800           
1801            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1802            break;
1803       
1804        case TR_LEECH:
1805            //do not allow to be backed up by Time Machine
1806            [self setTimeMachineExclude: YES forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1807           
1808            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1809            break;
1810    }
1811    [status release];
1812   
1813    [self update];
1814}
1815
1816- (void) ratioLimitHit
1817{
1818    fStat = tr_torrentStat(fHandle);
1819   
1820    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
1821   
1822    fFinishedSeeding = YES;
1823}
1824
1825- (void) quickPause
1826{
1827    if (fQuickPauseDict)
1828        return;
1829
1830    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1831                    [NSNumber numberWithInt: [self usesSpeedLimit: YES]], @"UploadUsesSpeedLimit",
1832                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1833                    [NSNumber numberWithInt: [self usesSpeedLimit: NO]], @"DownloadUsesSpeedLimit",
1834                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1835   
1836    [self setUseSpeedLimit: YES upload: YES];
1837    [self setSpeedLimit: 0 upload: YES];
1838    [self setUseSpeedLimit: YES upload: NO];
1839    [self setSpeedLimit: 0 upload: NO];
1840}
1841
1842- (void) endQuickPause
1843{
1844    if (!fQuickPauseDict)
1845        return;
1846   
1847    [self setUseSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadUsesSpeedLimit"] intValue] upload: YES];
1848    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1849    [self setUseSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadUsesSpeedLimit"] intValue] upload: NO];
1850    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1851   
1852    [fQuickPauseDict release];
1853    fQuickPauseDict = nil;
1854}
1855
1856- (NSString *) etaString
1857{
1858    const NSInteger eta = [self eta];
1859    switch (eta)
1860    {
1861        case TR_ETA_NOT_AVAIL:
1862        case TR_ETA_UNKNOWN:
1863            return NSLocalizedString(@"remaining time unknown", "Torrent -> eta string");
1864        default:
1865            return [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "Torrent -> eta string"),
1866                        [NSString timeString: eta showSeconds: YES maxFields: 2]];
1867    }
1868}
1869
1870- (void) updateAllTrackers: (NSMutableArray *) trackers
1871{
1872    //get count
1873    NSInteger count = 0;
1874    for (id object in trackers)
1875        if (![object isKindOfClass: [NSNumber class]])
1876            count++;
1877   
1878    //recreate the tracker structure
1879    tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, count);
1880    NSInteger tier = 0, i = 0;
1881    for (id object in trackers)
1882    {
1883        if (![object isKindOfClass: [NSNumber class]])
1884        {
1885            trackerStructs[i].tier = tier;
1886            trackerStructs[i].announce = (char *)[object UTF8String];
1887            i++;
1888        }
1889        else
1890            tier++;
1891    }
1892   
1893    tr_torrentSetAnnounceList(fHandle, trackerStructs, count);
1894    tr_free(trackerStructs);
1895}
1896
1897- (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path
1898{
1899    CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, true);
1900}
1901
1902@end
Note: See TracBrowser for help on using the repository browser.