source: branches/0.9x/macosx/Torrent.m @ 3675

Last change on this file since 3675 was 3675, checked in by livings124, 15 years ago

0.9x: attempt to fix Time Machine setting

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