source: trunk/macosx/Torrent.m @ 3483

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

remove "Stopping" status, and update all string files accordingly

  • Property svn:keywords set to Date Rev Author Id
File size: 51.2 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 3483 2007-10-20 21:17:24Z 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: @"UpdateInfoSettings" 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: @"UpdateInfoSettings" 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_ACTIVE;
733}
734
735- (BOOL) isSeeding
736{
737    return fStat->status == TR_STATUS_SEED || fStat->status == TR_STATUS_DONE;
738}
739
740- (BOOL) isPaused
741{
742    return fStat->status == TR_STATUS_STOPPED;
743}
744
745- (BOOL) isChecking
746{
747    return fStat->status == TR_STATUS_CHECK || fStat->status == TR_STATUS_CHECK_WAIT;
748}
749
750- (BOOL) allDownloaded
751{
752    return [self progressDone] >= 1.0;
753}
754
755- (BOOL) isComplete
756{
757    return [self progress] >= 1.0;
758}
759
760- (BOOL) isError
761{
762    return fStat->error != 0;
763}
764
765- (NSString *) errorMessage
766{
767    if (![self isError])
768        return @"";
769   
770    NSString * error;
771    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
772        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
773        error = NSLocalizedString(@"(unreadable error)", "Torrent -> error string unreadable");
774   
775    return error;
776}
777
778- (NSArray *) peers
779{
780    int totalPeers, i;
781    tr_peer_stat * peers = tr_torrentPeers(fHandle, &totalPeers);
782   
783    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
784    NSMutableDictionary * dic;
785   
786    tr_peer_stat * peer;
787    for (i = 0; i < totalPeers; i++)
788    {
789        peer = &peers[i];
790       
791        dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
792            [NSNumber numberWithInt: peer->from], @"From",
793            [NSString stringWithCString: (char *)peer->addr encoding: NSUTF8StringEncoding], @"IP",
794            [NSNumber numberWithInt: peer->port], @"Port",
795            [NSNumber numberWithFloat: peer->progress], @"Progress",
796            [NSNumber numberWithBool: peer->isEncrypted], @"Encryption",
797            [NSString stringWithCString: (char *)peer->client encoding: NSUTF8StringEncoding], @"Client", nil];
798       
799        if (peer->isDownloading)
800            [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
801        if (peer->isUploading)
802            [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
803       
804        [peerDics addObject: dic];
805    }
806   
807    tr_torrentPeersFree(peers, totalPeers);
808   
809    return peerDics;
810}
811
812- (NSString *) progressString
813{
814    NSString * string;
815   
816    if (![self allDownloaded])
817    {
818        if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
819        {
820            uint64_t have = [self haveTotal];
821            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ selected (%.2f%%)", "Torrent -> progress string"),
822                            [NSString stringForFileSize: have], [NSString stringForFileSize: have + [self sizeLeft]],
823                            100.0 * [self progressDone]];
824        }
825        else
826            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ (%.2f%%)", "Torrent -> progress string"),
827                            [NSString stringForFileSize: [self haveTotal]],
828                            [NSString stringForFileSize: [self size]], 100.0 * [self progress]];
829    }
830    else if (![self isComplete])
831    {
832        if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
833            string = [NSString stringWithFormat: NSLocalizedString(@"%@ selected, uploaded %@ (Ratio: %@)",
834                "Torrent -> progress string"), [NSString stringForFileSize: [self haveTotal]],
835                [NSString stringForFileSize: [self uploadedTotal]], [NSString stringForRatio: [self ratio]]];
836        else
837            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ (%.2f%%), uploaded %@ (Ratio: %@)",
838                "Torrent -> progress string"), [NSString stringForFileSize: [self haveTotal]],
839                [NSString stringForFileSize: [self size]], 100.0 * [self progress],
840                [NSString stringForFileSize: [self uploadedTotal]], [NSString stringForRatio: [self ratio]]];
841    }
842    else
843        string = [NSString stringWithFormat: NSLocalizedString(@"%@, uploaded %@ (Ratio: %@)", "Torrent -> progress string"),
844                [NSString stringForFileSize: [self size]], [NSString stringForFileSize: [self uploadedTotal]],
845                [NSString stringForRatio: [self ratio]]];
846   
847    //add time when downloading
848    if (fStat->status == TR_STATUS_DOWNLOAD)
849    {
850        string = [self eta] >= 0
851            ? [string stringByAppendingFormat: NSLocalizedString(@" - %@ remaining", "Torrent -> progress string"), [self etaString]]
852            : [string stringByAppendingString: NSLocalizedString(@" - remaining time unknown", "Torrent -> progress string")];
853    }
854   
855    return string;
856}
857
858- (NSString *) statusString
859{
860    NSString * string;
861   
862    if ([self isError])
863    {
864        NSString * errorString = [self errorMessage];
865        if (!errorString || [errorString isEqualToString: @""])
866            string = NSLocalizedString(@"Error", "Torrent -> status string");
867        else
868            string = [NSLocalizedString(@"Error: ", "Torrent -> status string") stringByAppendingString: errorString];
869    }
870    else
871    {
872        switch (fStat->status)
873        {
874            case TR_STATUS_STOPPED:
875                if (fWaitToStart)
876                {
877                    string = ![self allDownloaded]
878                            ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
879                            : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
880                }
881                else if (fFinishedSeeding)
882                    string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
883                else
884                    string = NSLocalizedString(@"Paused", "Torrent -> status string");
885                break;
886
887            case TR_STATUS_CHECK_WAIT:
888                string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
889                break;
890
891            case TR_STATUS_CHECK:
892                string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
893                                        "Torrent -> status string"), 100.0 * fStat->recheckProgress];
894                break;
895
896            case TR_STATUS_DOWNLOAD:
897                if ([self totalPeersConnected] != 1)
898                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
899                                                    "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
900                else
901                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
902                                                    "Torrent -> status string"), [self peersSendingToUs]];
903                break;
904
905            case TR_STATUS_SEED:
906            case TR_STATUS_DONE:
907                if ([self totalPeersConnected] != 1)
908                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
909                                                    [self peersGettingFromUs], [self totalPeersConnected]];
910                else
911                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
912                                                    [self peersGettingFromUs]];
913                break;
914           
915            default:
916                string = @"";
917        }
918       
919        if (fStalled)
920            string = [NSLocalizedString(@"Stalled, ", "Torrent -> status string") stringByAppendingString: string];
921    }
922   
923    //append even if error
924    if ([self isActive] && ![self isChecking])
925    {
926        if (fStat->status == TR_STATUS_DOWNLOAD)
927            string = [string stringByAppendingFormat: @" - DL: %@, UL: %@",
928                    [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
929        else
930            string = [string stringByAppendingFormat: @" - UL: %@", [NSString stringForSpeed: [self uploadRate]]];
931    }
932   
933    return string;
934}
935
936- (NSString *) shortStatusString
937{
938    NSString * string;
939   
940    switch (fStat->status)
941    {
942        case TR_STATUS_STOPPED:
943            if (fWaitToStart)
944            {
945                string = ![self allDownloaded]
946                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
947                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
948            }
949            else if (fFinishedSeeding)
950                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
951            else
952                string = NSLocalizedString(@"Paused", "Torrent -> status string");
953            break;
954
955        case TR_STATUS_CHECK_WAIT:
956            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
957            break;
958
959        case TR_STATUS_CHECK:
960            string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
961                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
962            break;
963       
964        case TR_STATUS_DOWNLOAD:
965            string = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, UL: %@", "Torrent -> status string"),
966                            [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
967            break;
968       
969        case TR_STATUS_SEED:
970        case TR_STATUS_DONE:
971            string = [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, UL: %@", "Torrent -> status string"),
972                            [NSString stringForRatio: [self ratio]], [NSString stringForSpeed: [self uploadRate]]];
973            break;
974       
975        default:
976            string = @"";
977    }
978   
979    return string;
980}
981
982- (NSString *) remainingTimeString
983{
984    switch (fStat->status)
985    {
986        case TR_STATUS_DOWNLOAD:
987            return [self eta] >= 0 ? [self etaString] : NSLocalizedString(@"Unknown", "Torrent -> remaining time");
988       
989        case TR_STATUS_SEED:
990        case TR_STATUS_DONE:
991            return [NSLocalizedString(@"Ratio: ", "Torrent -> status string") stringByAppendingString:
992                                                                            [NSString stringForRatio: [self ratio]]];
993       
994        default:
995            return [self shortStatusString];
996    }
997}
998
999- (NSString *) stateString
1000{
1001    switch (fStat->status)
1002    {
1003        case TR_STATUS_STOPPED:
1004            return NSLocalizedString(@"Paused", "Torrent -> status string");
1005
1006        case TR_STATUS_CHECK:
1007            return [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1008                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
1009       
1010        case TR_STATUS_CHECK_WAIT:
1011            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1012
1013        case TR_STATUS_DOWNLOAD:
1014            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1015
1016        case TR_STATUS_SEED:
1017        case TR_STATUS_DONE:
1018            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1019       
1020        default:
1021            return NSLocalizedString(@"N/A", "Torrent -> status string");
1022    }
1023}
1024
1025- (int) seeders
1026{
1027    return fStat->seeders;
1028}
1029
1030- (int) leechers
1031{
1032    return fStat->leechers;
1033}
1034
1035- (int) completedFromTracker
1036{
1037    return fStat->completedFromTracker;
1038}
1039
1040- (int) totalPeersConnected
1041{
1042    return fStat->peersConnected;
1043}
1044
1045- (int) totalPeersTracker
1046{
1047    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1048}
1049
1050- (int) totalPeersIncoming
1051{
1052    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1053}
1054
1055- (int) totalPeersCache
1056{
1057    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1058}
1059
1060- (int) totalPeersPex
1061{
1062    return fStat->peersFrom[TR_PEER_FROM_PEX];
1063}
1064
1065- (int) totalPeersKnown
1066{
1067    return fStat->peersKnown;
1068}
1069
1070- (int) peersSendingToUs
1071{
1072    return fStat->peersSendingToUs;
1073}
1074
1075- (int) peersGettingFromUs
1076{
1077    return fStat->peersGettingFromUs;
1078}
1079
1080- (float) downloadRate
1081{
1082    return fStat->rateDownload;
1083}
1084
1085- (float) uploadRate
1086{
1087    return fStat->rateUpload;
1088}
1089
1090- (uint64_t) haveVerified
1091{
1092    return fStat->haveValid;
1093}
1094
1095- (uint64_t) haveTotal
1096{
1097    return [self haveVerified] + fStat->haveUnchecked;
1098}
1099
1100- (uint64_t) downloadedTotal
1101{
1102    return fStat->downloadedEver;
1103}
1104
1105- (uint64_t) uploadedTotal
1106{
1107    return fStat->uploadedEver;
1108}
1109
1110- (uint64_t) failedHash
1111{
1112    return fStat->corruptEver;
1113}
1114
1115- (float) swarmSpeed
1116{
1117    return fStat->swarmspeed;
1118}
1119
1120- (BOOL) pex
1121{
1122        return tr_torrentIsPexEnabled(fHandle);
1123}
1124
1125- (void) setPex: (BOOL) enable
1126{
1127        tr_torrentDisablePex(fHandle, !enable);
1128}
1129
1130- (NSNumber *) orderValue
1131{
1132    return fOrderValue;
1133}
1134
1135- (void) setOrderValue: (int) orderValue
1136{
1137    [fOrderValue release];
1138    fOrderValue = [[NSNumber alloc] initWithInt: orderValue];
1139}
1140
1141- (NSArray *) fileList
1142{
1143    return fFileList;
1144}
1145
1146- (int) fileCount
1147{
1148    return fInfo->fileCount;
1149}
1150
1151- (void) updateFileStat
1152{
1153    if (fileStat)
1154        tr_torrentFilesFree(fileStat, [self fileCount]);
1155   
1156    int count;
1157    fileStat = tr_torrentFiles(fHandle, &count);
1158}
1159
1160- (float) fileProgress: (int) index
1161{
1162    if (!fileStat)
1163        [self updateFileStat];
1164       
1165    return fileStat[index].progress;
1166}
1167
1168- (BOOL) canChangeDownloadCheckForFile: (int) index
1169{
1170    if (!fileStat)
1171        [self updateFileStat];
1172   
1173    return [self fileCount] > 1 && fileStat[index].progress < 1.0;
1174}
1175
1176- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1177{
1178    if ([self fileCount] <= 1 || [self isComplete])
1179        return NO;
1180   
1181    if (!fileStat)
1182        [self updateFileStat];
1183   
1184    int index;
1185    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1186        if (fileStat[index].progress < 1.0)
1187            return YES;
1188    return NO;
1189}
1190
1191- (int) checkForFiles: (NSIndexSet *) indexSet
1192{
1193    BOOL onState = NO, offState = NO;
1194    int index;
1195    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1196    {
1197        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1198            onState = YES;
1199        else
1200            offState = YES;
1201       
1202        if (onState && offState)
1203            return NSMixedState;
1204    }
1205    return onState ? NSOnState : NSOffState;
1206}
1207
1208- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
1209{
1210    int count = [indexSet count], i = 0, index;
1211    int * files = malloc(count * sizeof(int));
1212    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1213    {
1214        files[i] = index;
1215        i++;
1216    }
1217   
1218    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1219    free(files);
1220   
1221    [self update];
1222}
1223
1224- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1225{
1226    int count = [indexSet count], i = 0, index;
1227    int * files = malloc(count * sizeof(int));
1228    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1229    {
1230        files[i] = index;
1231        i++;
1232    }
1233   
1234    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1235    free(files);
1236}
1237
1238- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1239{
1240    int index;
1241    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1242        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1243            return YES;
1244    return NO;
1245}
1246
1247- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1248{
1249    BOOL low = NO, normal = NO, high = NO;
1250    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1251   
1252    int index, priority;
1253    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1254    {
1255        if (![self canChangeDownloadCheckForFile: index])
1256            continue;
1257       
1258        priority = tr_torrentGetFilePriority(fHandle, index);
1259        if (priority == TR_PRI_LOW)
1260        {
1261            if (low)
1262                continue;
1263            low = YES;
1264        }
1265        else if (priority == TR_PRI_HIGH)
1266        {
1267            if (high)
1268                continue;
1269            high = YES;
1270        }
1271        else
1272        {
1273            if (normal)
1274                continue;
1275            normal = YES;
1276        }
1277       
1278        [priorities addObject: [NSNumber numberWithInt: priority]];
1279        if (low && normal && high)
1280            break;
1281    }
1282    return priorities;
1283}
1284
1285- (NSMenu *) fileMenu
1286{
1287    if (!fFileMenu)
1288    {
1289        fFileMenu = [[NSMenu alloc] initWithTitle: [@"TorrentMenu:" stringByAppendingString: [self name]]];
1290        [fFileMenu setAutoenablesItems: NO];
1291    }
1292    return fFileMenu;
1293}
1294
1295- (NSDate *) dateAdded
1296{
1297    return fDateAdded;
1298}
1299
1300- (NSDate *) dateCompleted
1301{
1302    return fDateCompleted;
1303}
1304
1305- (NSDate *) dateActivity
1306{
1307    uint64_t date = fStat->activityDate;
1308    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1309}
1310
1311- (int) stalledMinutes
1312{
1313    uint64_t start;
1314    if ((start = fStat->startDate) == 0)
1315        return -1;
1316   
1317    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1318            * activity = [self dateActivity];
1319   
1320    NSDate * laterDate = (!activity || [started compare: activity] == NSOrderedDescending) ? started : activity;
1321    return -1 * [laterDate timeIntervalSinceNow] / 60;
1322}
1323
1324- (BOOL) isStalled
1325{
1326    return fStalled;
1327}
1328
1329- (NSNumber *) stateSortKey
1330{
1331    if (![self isActive])
1332        return [NSNumber numberWithInt: 0];
1333    else if ([self isSeeding])
1334        return [NSNumber numberWithInt: 1];
1335    else
1336        return [NSNumber numberWithInt: 2];
1337}
1338
1339- (NSNumber *) progressSortKey
1340{
1341    return [NSNumber numberWithFloat: [self progress]];
1342}
1343
1344- (NSNumber *) ratioSortKey
1345{
1346    return [NSNumber numberWithFloat: [self ratio]];
1347}
1348
1349- (NSNumber *) ratioProgressSortKey
1350{
1351    return [NSNumber numberWithFloat: [self progressStopRatio]];
1352}
1353
1354- (int) torrentID
1355{
1356    return fID;
1357}
1358
1359- (const tr_info *) torrentInfo
1360{
1361    return fInfo;
1362}
1363
1364- (const tr_stat *) torrentStat
1365{
1366    return fStat;
1367}
1368
1369@end
1370
1371@implementation Torrent (Private)
1372
1373//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1374- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle *) lib
1375        publicTorrent: (NSNumber *) publicTorrent
1376        downloadFolder: (NSString *) downloadFolder
1377        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1378        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
1379        dateActivity: (NSDate *) dateActivity
1380        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
1381        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;
1382{
1383    if (!(self = [super init]))
1384        return nil;
1385   
1386    static_lastid++;
1387    fID = static_lastid;
1388   
1389    fLib = lib;
1390    fDefaults = [NSUserDefaults standardUserDefaults];
1391
1392    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1393    if (fPublicTorrent)
1394        fPublicTorrentLocation = [path retain];
1395   
1396    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1397    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1398   
1399    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1400                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1401    if (fUseIncompleteFolder)
1402    {
1403        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1404        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1405    }
1406   
1407    NSString * currentDownloadFolder;
1408    tr_info info;
1409    int error;
1410    if (hashString)
1411    {
1412        if (tr_torrentParseHash(fLib, [hashString UTF8String], NULL, &info) == TR_OK)
1413        {
1414            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1415                                        ? fIncompleteFolder : fDownloadFolder;
1416            fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], [currentDownloadFolder UTF8String], YES, &error);
1417        }
1418        tr_metainfoFree(&info);
1419    }
1420    if (!fHandle && path)
1421    {
1422        if (tr_torrentParse(fLib, [path UTF8String], NULL, &info) == TR_OK)
1423        {
1424            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1425                                        ? fIncompleteFolder : fDownloadFolder;
1426            fHandle = tr_torrentInit(fLib, [path UTF8String], [currentDownloadFolder UTF8String], YES, &error);
1427        }
1428        tr_metainfoFree(&info);
1429    }
1430    if (!fHandle)
1431    {
1432        [self release];
1433        return nil;
1434    }
1435   
1436    tr_torrentSetStatusCallback(fHandle, completenessChangeCallback, self);
1437   
1438    fInfo = tr_torrentInfo(fHandle);
1439
1440    fDateAdded = dateAdded ? [dateAdded retain] : [[NSDate alloc] init];
1441        if (dateCompleted)
1442                fDateCompleted = [dateCompleted retain];
1443    if (dateActivity)
1444                fDateActivity = [dateActivity retain];
1445       
1446    fRatioSetting = ratioSetting ? [ratioSetting intValue] : NSMixedState;
1447    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1448    fFinishedSeeding = NO;
1449   
1450    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1451   
1452    [self setOrderValue: orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1];
1453    fError = NO;
1454   
1455    [self createFileList];
1456   
1457    [self update];
1458    return self;
1459}
1460
1461- (void) createFileList
1462{
1463    int count = [self fileCount], i;
1464    tr_file * file;
1465    NSMutableArray * pathComponents;
1466    NSString * path;
1467   
1468    NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count];
1469   
1470    for (i = 0; i < count; i++)
1471    {
1472        file = &fInfo->files[i];
1473       
1474        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1475        if (fInfo->isMultifile)
1476        {
1477            path = [pathComponents objectAtIndex: 0];
1478            [pathComponents removeObjectAtIndex: 0];
1479        }
1480        else
1481            path = @"";
1482       
1483        [self insertPath: pathComponents forSiblings: fileList previousPath: path fileSize: file->length index: i];
1484        [pathComponents release];
1485    }
1486   
1487    fFileList = [[NSArray alloc] initWithArray: fileList];
1488    [fileList release];
1489}
1490
1491- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings previousPath: (NSString *) previousPath
1492            fileSize: (uint64_t) size index: (int) index
1493{
1494    NSString * name = [components objectAtIndex: 0];
1495    BOOL isFolder = [components count] > 1;
1496   
1497    NSMutableDictionary * dict = nil;
1498    if (isFolder)
1499    {
1500        NSEnumerator * enumerator = [siblings objectEnumerator];
1501        while ((dict = [enumerator nextObject]))
1502            if ([[dict objectForKey: @"Name"] isEqualToString: name] && [[dict objectForKey: @"IsFolder"] boolValue])
1503                break;
1504    }
1505   
1506    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1507   
1508    //create new folder or item if it doesn't already exist
1509    if (!dict)
1510    {
1511        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1512                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1513        [siblings addObject: dict];
1514       
1515        if (isFolder)
1516        {
1517            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1518            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1519        }
1520        else
1521        {
1522            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1523            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1524           
1525            NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
1526            [icon setFlipped: YES];
1527            [dict setObject: icon forKey: @"Icon"];
1528        }
1529    }
1530    else
1531        [[dict objectForKey: @"Indexes"] addIndex: index];
1532   
1533    if (isFolder)
1534    {
1535        [components removeObjectAtIndex: 0];
1536        [self insertPath: components forSiblings: [dict objectForKey: @"Children"] previousPath: currentPath fileSize: size
1537                index: index];
1538    }
1539}
1540
1541- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1542{
1543    return fUseIncompleteFolder &&
1544        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1545}
1546
1547- (void) updateDownloadFolder
1548{
1549    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1550    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1551}
1552
1553//status has been retained
1554- (void) completenessChange: (NSNumber *) status
1555{
1556    [self update];
1557   
1558    BOOL canMove;
1559    switch ([status intValue])
1560    {
1561        case TR_CP_DONE:
1562        case TR_CP_COMPLETE:
1563            canMove = YES;
1564       
1565            //move file from incomplete folder to download folder
1566            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1567                && (canMove = [self alertForMoveFolderAvailable]))
1568            {
1569                [self quickPause];
1570               
1571                if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1572                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
1573                    [self updateDownloadFolder];
1574                else
1575                    canMove = NO;
1576               
1577                [self endQuickPause];
1578            }
1579           
1580            if (!canMove)
1581            {
1582                fUseIncompleteFolder = NO;
1583               
1584                [fDownloadFolder release];
1585                fDownloadFolder = fIncompleteFolder;
1586                fIncompleteFolder = nil;
1587            }
1588           
1589            [fDateCompleted release];
1590            fDateCompleted = [[NSDate alloc] init];
1591           
1592            fStat = tr_torrentStat(fHandle);
1593            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1594            break;
1595       
1596        case TR_CP_INCOMPLETE:
1597            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1598            break;
1599    }
1600    [status release];
1601}
1602
1603- (void) quickPause
1604{
1605    if (fQuickPauseDict)
1606        return;
1607
1608    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1609                    [NSNumber numberWithInt: [self speedMode: YES]], @"UploadSpeedMode",
1610                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1611                    [NSNumber numberWithInt: [self speedMode: NO]], @"DownloadSpeedMode",
1612                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1613   
1614    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: YES];
1615    [self setSpeedLimit: 0 upload: YES];
1616    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: NO];
1617    [self setSpeedLimit: 0 upload: NO];
1618}
1619
1620- (void) endQuickPause
1621{
1622    if (!fQuickPauseDict)
1623        return;
1624   
1625    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"UploadSpeedMode"] intValue] upload: YES];
1626    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1627    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"DownloadSpeedMode"] intValue] upload: NO];
1628    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1629   
1630    [fQuickPauseDict release];
1631    fQuickPauseDict = nil;
1632}
1633
1634- (void) trashFile: (NSString *) path
1635{
1636    //attempt to move to trash
1637    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1638            source: [path stringByDeletingLastPathComponent] destination: @""
1639            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1640    {
1641        //if cannot trash, just delete it (will work if it is on a remote volume)
1642        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1643            NSLog(@"Could not trash %@", path);
1644    }
1645}
1646
1647@end
Note: See TracBrowser for help on using the repository browser.