source: trunk/macosx/Torrent.m @ 5910

Last change on this file since 5910 was 5910, checked in by livings124, 14 years ago

streamline FileListNode?.m, moving asserts to more appropriate locations

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