source: trunk/macosx/Torrent.m @ 5452

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

(trunk) Fix visual glitch with the tracker inspector tab when there is no scrape address

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