source: trunk/macosx/Torrent.m @ 5457

Last change on this file since 5457 was 5457, checked in by charles, 14 years ago

remove `TR_STATUS_DONE' from libtransmission's public API. It's useful as an internal state but not for code calling libtransmission.

  • Property svn:keywords set to Date Rev Author Id
File size: 58.8 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 5457 2008-03-31 17:59:16Z charles $
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;
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                if ([self totalPeersConnected] != 1)
1062                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
1063                                                    [self peersGettingFromUs], [self totalPeersConnected]];
1064                else
1065                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
1066                                                    [self peersGettingFromUs]];
1067                break;
1068           
1069            default:
1070                string = @"";
1071        }
1072       
1073        if (fStalled)
1074            string = [NSLocalizedString(@"Stalled, ", "Torrent -> status string") stringByAppendingString: string];
1075    }
1076   
1077    //append even if error
1078    if ([self isActive] && ![self isChecking])
1079    {
1080        if (fStat->status == TR_STATUS_DOWNLOAD)
1081            string = [string stringByAppendingFormat: @" - %@: %@, %@: %@",
1082                        NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1083                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1084        else
1085            string = [string stringByAppendingFormat: @" - %@: %@",
1086                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1087    }
1088   
1089    return string;
1090}
1091
1092- (NSString *) shortStatusString
1093{
1094    NSString * string;
1095   
1096    switch (fStat->status)
1097    {
1098        case TR_STATUS_STOPPED:
1099            if (fWaitToStart)
1100            {
1101                string = ![self allDownloaded]
1102                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
1103                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1104            }
1105            else if (fFinishedSeeding)
1106                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1107            else
1108                string = NSLocalizedString(@"Paused", "Torrent -> status string");
1109            break;
1110
1111        case TR_STATUS_CHECK_WAIT:
1112            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1113            break;
1114
1115        case TR_STATUS_CHECK:
1116            string = [NSString localizedStringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1117                                    "Torrent -> status string"), 100.0 * [self checkingProgress]];
1118            break;
1119       
1120        case TR_STATUS_DOWNLOAD:
1121            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1122                            NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1123                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1124            break;
1125       
1126        case TR_STATUS_SEED:
1127            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1128                            NSLocalizedString(@"Ratio", "Torrent -> status string"), [NSString stringForRatio: [self ratio]],
1129                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1130            break;
1131       
1132        default:
1133            string = @"";
1134    }
1135   
1136    return string;
1137}
1138
1139- (NSString *) remainingTimeString
1140{
1141    if (![self isActive] || ([self isSeeding]
1142        && !(fRatioSetting == NSOnState || (fRatioSetting == NSMixedState && [fDefaults boolForKey: @"RatioCheck"]))))
1143        return [self shortStatusString];
1144   
1145    int eta = [self isSeeding] ? [self etaRatio] : [self eta];
1146    return eta >= 0 ? [NSString timeString: eta showSeconds: YES maxDigits: 2]
1147                    : NSLocalizedString(@"Unknown", "Torrent -> remaining time");
1148}
1149
1150- (NSString *) stateString
1151{
1152    switch (fStat->status)
1153    {
1154        case TR_STATUS_STOPPED:
1155            return NSLocalizedString(@"Paused", "Torrent -> status string");
1156
1157        case TR_STATUS_CHECK:
1158            return [NSString localizedStringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1159                                    "Torrent -> status string"), 100.0 * [self checkingProgress]];
1160       
1161        case TR_STATUS_CHECK_WAIT:
1162            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1163
1164        case TR_STATUS_DOWNLOAD:
1165            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1166
1167        case TR_STATUS_SEED:
1168            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1169       
1170        default:
1171            return NSLocalizedString(@"N/A", "Torrent -> status string");
1172    }
1173}
1174
1175- (int) seeders
1176{
1177    return fStat->seeders;
1178}
1179
1180- (int) leechers
1181{
1182    return fStat->leechers;
1183}
1184
1185- (int) completedFromTracker
1186{
1187    return fStat->completedFromTracker;
1188}
1189
1190- (int) totalPeersConnected
1191{
1192    return fStat->peersConnected;
1193}
1194
1195- (int) totalPeersTracker
1196{
1197    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1198}
1199
1200- (int) totalPeersIncoming
1201{
1202    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1203}
1204
1205- (int) totalPeersCache
1206{
1207    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1208}
1209
1210- (int) totalPeersPex
1211{
1212    return fStat->peersFrom[TR_PEER_FROM_PEX];
1213}
1214
1215- (int) totalPeersKnown
1216{
1217    return fStat->peersKnown;
1218}
1219
1220- (int) peersSendingToUs
1221{
1222    return fStat->peersSendingToUs;
1223}
1224
1225- (int) peersGettingFromUs
1226{
1227    return fStat->peersGettingFromUs;
1228}
1229
1230- (float) downloadRate
1231{
1232    return fStat->rateDownload;
1233}
1234
1235- (float) uploadRate
1236{
1237    return fStat->rateUpload;
1238}
1239
1240- (float) totalRate
1241{
1242    return [self downloadRate] + [self uploadRate];
1243}
1244
1245- (uint64_t) haveVerified
1246{
1247    return fStat->haveValid;
1248}
1249
1250- (uint64_t) haveTotal
1251{
1252    return [self haveVerified] + fStat->haveUnchecked;
1253}
1254
1255- (uint64_t) totalSizeSelected
1256{
1257    return [self haveTotal] + [self sizeLeft];
1258}
1259
1260- (uint64_t) downloadedTotal
1261{
1262    return fStat->downloadedEver;
1263}
1264
1265- (uint64_t) uploadedTotal
1266{
1267    return fStat->uploadedEver;
1268}
1269
1270- (uint64_t) failedHash
1271{
1272    return fStat->corruptEver;
1273}
1274
1275- (float) swarmSpeed
1276{
1277    return fStat->swarmspeed;
1278}
1279
1280- (int) orderValue
1281{
1282    return fOrderValue;
1283}
1284
1285- (void) setOrderValue: (int) orderValue
1286{
1287    fOrderValue = orderValue;
1288}
1289
1290- (int) groupValue
1291{
1292    return fGroupValue;
1293}
1294
1295- (void) setGroupValue: (int) goupValue
1296{
1297    fGroupValue = goupValue;
1298}
1299
1300- (int) groupOrderValue
1301{
1302    return [[GroupsController groups] rowValueForIndex: fGroupValue];
1303}
1304
1305- (void) checkGroupValueForRemoval: (NSNotification *) notification
1306{
1307    if (fGroupValue != -1 && [[[notification userInfo] objectForKey: @"Indexes"] containsIndex: fGroupValue])
1308        fGroupValue = -1;
1309}
1310
1311- (NSArray *) fileList
1312{
1313    return fFileList;
1314}
1315
1316- (int) fileCount
1317{
1318    return fInfo->fileCount;
1319}
1320
1321- (void) updateFileStat
1322{
1323    if (fFileStat)
1324        tr_torrentFilesFree(fFileStat, [self fileCount]);
1325   
1326    fFileStat = tr_torrentFiles(fHandle, NULL);
1327}
1328
1329- (float) fileProgress: (int) index
1330{
1331    if (!fFileStat)
1332        [self updateFileStat];
1333       
1334    return fFileStat[index].progress;
1335}
1336
1337- (BOOL) canChangeDownloadCheckForFile: (int) index
1338{
1339    if (!fFileStat)
1340        [self updateFileStat];
1341   
1342    return [self fileCount] > 1 && fFileStat[index].progress < 1.0;
1343}
1344
1345- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1346{
1347    if ([self fileCount] <= 1 || [self isComplete])
1348        return NO;
1349   
1350    if (!fFileStat)
1351        [self updateFileStat];
1352   
1353    int index;
1354    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1355        if (fFileStat[index].progress < 1.0)
1356            return YES;
1357    return NO;
1358}
1359
1360- (int) checkForFiles: (NSIndexSet *) indexSet
1361{
1362    BOOL onState = NO, offState = NO;
1363    int index;
1364    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1365    {
1366        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1367            onState = YES;
1368        else
1369            offState = YES;
1370       
1371        if (onState && offState)
1372            return NSMixedState;
1373    }
1374    return onState ? NSOnState : NSOffState;
1375}
1376
1377- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
1378{
1379    NSUInteger count = [indexSet count], i = 0, index;
1380    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1381    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1382    {
1383        files[i] = index;
1384        i++;
1385    }
1386   
1387    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1388    free(files);
1389   
1390    [self update];
1391    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
1392}
1393
1394- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1395{
1396    NSUInteger count = [indexSet count], i = 0, index;
1397    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1398    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1399    {
1400        files[i] = index;
1401        i++;
1402    }
1403   
1404    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1405    free(files);
1406}
1407
1408- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1409{
1410    int index;
1411    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1412        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1413            return YES;
1414    return NO;
1415}
1416
1417- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1418{
1419    BOOL low = NO, normal = NO, high = NO;
1420    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1421   
1422    int index, priority;
1423    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1424    {
1425        if (![self canChangeDownloadCheckForFile: index])
1426            continue;
1427       
1428        priority = tr_torrentGetFilePriority(fHandle, index);
1429        if (priority == TR_PRI_LOW)
1430        {
1431            if (low)
1432                continue;
1433            low = YES;
1434        }
1435        else if (priority == TR_PRI_HIGH)
1436        {
1437            if (high)
1438                continue;
1439            high = YES;
1440        }
1441        else
1442        {
1443            if (normal)
1444                continue;
1445            normal = YES;
1446        }
1447       
1448        [priorities addObject: [NSNumber numberWithInt: priority]];
1449        if (low && normal && high)
1450            break;
1451    }
1452    return priorities;
1453}
1454
1455- (NSDate *) dateAdded
1456{
1457    return fDateAdded;
1458}
1459
1460- (NSDate *) dateCompleted
1461{
1462    return fDateCompleted;
1463}
1464
1465- (NSDate *) dateActivity
1466{
1467    uint64_t date = fStat->activityDate;
1468    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1469}
1470
1471- (NSDate *) dateActivityOrAdd
1472{
1473    NSDate * date = [self dateActivity];
1474    return date ? date : [self dateAdded];
1475}
1476
1477- (int) stalledMinutes
1478{
1479    uint64_t start;
1480    if ((start = fStat->startDate) == 0)
1481        return -1;
1482   
1483    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1484            * activity = [self dateActivity];
1485   
1486    NSDate * laterDate = activity ? [started laterDate: activity] : started;
1487    return -1 * [laterDate timeIntervalSinceNow] / 60;
1488}
1489
1490- (BOOL) isStalled
1491{
1492    return fStalled;
1493}
1494
1495- (NSNumber *) stateSortKey
1496{
1497    if (![self isActive])
1498        return [NSNumber numberWithInt: 0];
1499    else if ([self isSeeding])
1500        return [NSNumber numberWithInt: 1];
1501    else
1502        return [NSNumber numberWithInt: 2];
1503}
1504
1505- (int) torrentID
1506{
1507    return fID;
1508}
1509
1510- (const tr_info *) torrentInfo
1511{
1512    return fInfo;
1513}
1514
1515- (const tr_stat *) torrentStat
1516{
1517    return fStat;
1518}
1519
1520@end
1521
1522@implementation Torrent (Private)
1523
1524//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1525- (id) initWithHash: (NSString *) hashString path: (NSString *) path data: (NSData *) data lib: (tr_handle *) lib
1526        publicTorrent: (NSNumber *) publicTorrent
1527        downloadFolder: (NSString *) downloadFolder
1528        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1529        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
1530        dateActivity: (NSDate *) dateActivity
1531        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
1532        waitToStart: (NSNumber *) waitToStart
1533        orderValue: (NSNumber *) orderValue groupValue: (NSNumber *) groupValue;
1534{
1535    if (!(self = [super init]))
1536        return nil;
1537   
1538    static_lastid++;
1539    fID = static_lastid;
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_ctorSetMaxConnectedPeers(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_ctorSetDestination(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_ctorSetDestination(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_ctorSetDestination(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   
1632    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(lib) - 1;
1633    fGroupValue = groupValue ? [groupValue intValue] : -1;
1634   
1635    [self createFileList];
1636   
1637    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1638        name: @"GroupValueRemoved" object: nil];
1639   
1640    [self update];
1641   
1642    //mark incomplete files to be ignored by Time Machine
1643    [self setTimeMachineExclude: ![self allDownloaded] forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1644   
1645    return self;
1646}
1647
1648- (void) createFileList
1649{
1650    int count = [self fileCount], i;
1651    NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count];
1652   
1653    for (i = 0; i < count; i++)
1654    {
1655        tr_file * file = &fInfo->files[i];
1656       
1657        NSMutableArray * pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1658        NSString * path;
1659        if ([self folder])
1660        {
1661            path = [pathComponents objectAtIndex: 0];
1662            [pathComponents removeObjectAtIndex: 0];
1663        }
1664        else
1665            path = @"";
1666       
1667        [self insertPath: pathComponents forSiblings: fileList previousPath: path fileSize: file->length index: i];
1668        [pathComponents release];
1669    }
1670   
1671    fFileList = [[NSArray alloc] initWithArray: fileList];
1672    [fileList release];
1673}
1674
1675- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings previousPath: (NSString *) previousPath
1676            fileSize: (uint64_t) size index: (int) index
1677{
1678    NSString * name = [components objectAtIndex: 0];
1679    BOOL isFolder = [components count] > 1;
1680   
1681    NSMutableDictionary * dict = nil;
1682    if (isFolder)
1683    {
1684        NSEnumerator * enumerator = [siblings objectEnumerator];
1685        while ((dict = [enumerator nextObject]))
1686            if ([[dict objectForKey: @"Name"] isEqualToString: name] && [[dict objectForKey: @"IsFolder"] boolValue])
1687                break;
1688    }
1689   
1690    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1691   
1692    //create new folder or item if it doesn't already exist
1693    if (!dict)
1694    {
1695        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1696                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1697        [siblings addObject: dict];
1698       
1699        if (isFolder)
1700        {
1701            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1702            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1703        }
1704        else
1705        {
1706            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1707            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1708           
1709            NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
1710            [icon setFlipped: YES];
1711            [dict setObject: icon forKey: @"Icon"];
1712        }
1713    }
1714    else
1715        [[dict objectForKey: @"Indexes"] addIndex: index];
1716   
1717    if (isFolder)
1718    {
1719        [components removeObjectAtIndex: 0];
1720        [self insertPath: components forSiblings: [dict objectForKey: @"Children"] previousPath: currentPath fileSize: size
1721                index: index];
1722    }
1723}
1724
1725- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1726{
1727    return fUseIncompleteFolder &&
1728        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1729}
1730
1731- (void) updateDownloadFolder
1732{
1733    //remove old Time Machine location
1734    [self setTimeMachineExclude: NO forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1735   
1736    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1737    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1738   
1739    [self setTimeMachineExclude: ![self allDownloaded] forPath: [folder stringByAppendingPathComponent: [self name]]];
1740}
1741
1742//status has been retained
1743- (void) completenessChange: (NSNumber *) status
1744{
1745    [self update];
1746   
1747    BOOL canMove;
1748    switch ([status intValue])
1749    {
1750        case TR_CP_DONE:
1751        case TR_CP_COMPLETE:
1752            canMove = YES;
1753           
1754            //move file from incomplete folder to download folder
1755            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1756                && (canMove = [self alertForMoveFolderAvailable]))
1757            {
1758                [self quickPause];
1759               
1760                if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1761                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
1762                    [self updateDownloadFolder];
1763                else
1764                    canMove = NO;
1765               
1766                [self endQuickPause];
1767            }
1768           
1769            if (!canMove)
1770            {
1771                fUseIncompleteFolder = NO;
1772               
1773                [fDownloadFolder release];
1774                fDownloadFolder = fIncompleteFolder;
1775                fIncompleteFolder = nil;
1776            }
1777           
1778            [fDateCompleted release];
1779            fDateCompleted = [[NSDate alloc] init];
1780           
1781            //allow to be backed up by Time Machine
1782            [self setTimeMachineExclude: NO forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1783           
1784            fStat = tr_torrentStat(fHandle);
1785            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1786            break;
1787       
1788        case TR_CP_INCOMPLETE:
1789            //do not allow to be backed up by Time Machine
1790            [self setTimeMachineExclude: YES forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1791           
1792            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1793            break;
1794    }
1795    [status release];
1796}
1797
1798- (void) quickPause
1799{
1800    if (fQuickPauseDict)
1801        return;
1802
1803    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1804                    [NSNumber numberWithInt: [self speedMode: YES]], @"UploadSpeedMode",
1805                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1806                    [NSNumber numberWithInt: [self speedMode: NO]], @"DownloadSpeedMode",
1807                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1808   
1809    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: YES];
1810    [self setSpeedLimit: 0 upload: YES];
1811    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: NO];
1812    [self setSpeedLimit: 0 upload: NO];
1813}
1814
1815- (void) endQuickPause
1816{
1817    if (!fQuickPauseDict)
1818        return;
1819   
1820    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"UploadSpeedMode"] intValue] upload: YES];
1821    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1822    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"DownloadSpeedMode"] intValue] upload: NO];
1823    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1824   
1825    [fQuickPauseDict release];
1826    fQuickPauseDict = nil;
1827}
1828
1829- (void) trashFile: (NSString *) path
1830{
1831    //attempt to move to trash
1832    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1833            source: [path stringByDeletingLastPathComponent] destination: @""
1834            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1835    {
1836        //if cannot trash, just delete it (will work if it is on a remote volume)
1837        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1838            NSLog(@"Could not trash %@", path);
1839    }
1840}
1841
1842- (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path
1843{
1844    if ([NSApp isOnLeopardOrBetter])
1845        CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, true);
1846}
1847
1848@end
Note: See TracBrowser for help on using the repository browser.