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

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

0.9x: Leopard: Time Machine will ignore incomplete files

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