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

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

Time Machine ignoring is going to have to wait until 1.00, since setting the project to use 10.5 libraries will just break other stuff

  • Property svn:keywords set to Date Rev Author Id
File size: 51.1 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 3677 2007-11-01 14:19: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    return self;
1455}
1456
1457- (void) createFileList
1458{
1459    int count = [self fileCount], i;
1460    tr_file * file;
1461    NSMutableArray * pathComponents;
1462    NSString * path;
1463   
1464    NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count];
1465   
1466    for (i = 0; i < count; i++)
1467    {
1468        file = &fInfo->files[i];
1469       
1470        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1471        if (fInfo->isMultifile)
1472        {
1473            path = [pathComponents objectAtIndex: 0];
1474            [pathComponents removeObjectAtIndex: 0];
1475        }
1476        else
1477            path = @"";
1478       
1479        [self insertPath: pathComponents forSiblings: fileList previousPath: path fileSize: file->length index: i];
1480        [pathComponents release];
1481    }
1482   
1483    fFileList = [[NSArray alloc] initWithArray: fileList];
1484    [fileList release];
1485}
1486
1487- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings previousPath: (NSString *) previousPath
1488            fileSize: (uint64_t) size index: (int) index
1489{
1490    NSString * name = [components objectAtIndex: 0];
1491    BOOL isFolder = [components count] > 1;
1492   
1493    NSMutableDictionary * dict = nil;
1494    if (isFolder)
1495    {
1496        NSEnumerator * enumerator = [siblings objectEnumerator];
1497        while ((dict = [enumerator nextObject]))
1498            if ([[dict objectForKey: @"Name"] isEqualToString: name] && [[dict objectForKey: @"IsFolder"] boolValue])
1499                break;
1500    }
1501   
1502    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1503   
1504    //create new folder or item if it doesn't already exist
1505    if (!dict)
1506    {
1507        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1508                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1509        [siblings addObject: dict];
1510       
1511        if (isFolder)
1512        {
1513            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1514            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1515        }
1516        else
1517        {
1518            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1519            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1520           
1521            NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
1522            [icon setFlipped: YES];
1523            [dict setObject: icon forKey: @"Icon"];
1524        }
1525    }
1526    else
1527        [[dict objectForKey: @"Indexes"] addIndex: index];
1528   
1529    if (isFolder)
1530    {
1531        [components removeObjectAtIndex: 0];
1532        [self insertPath: components forSiblings: [dict objectForKey: @"Children"] previousPath: currentPath fileSize: size
1533                index: index];
1534    }
1535}
1536
1537- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1538{
1539    return fUseIncompleteFolder &&
1540        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1541}
1542
1543- (void) updateDownloadFolder
1544{
1545    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1546    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1547}
1548
1549//status has been retained
1550- (void) completenessChange: (NSNumber *) status
1551{
1552    [self update];
1553   
1554    BOOL canMove;
1555    switch ([status intValue])
1556    {
1557        case TR_CP_DONE:
1558        case TR_CP_COMPLETE:
1559            canMove = YES;
1560       
1561            //move file from incomplete folder to download folder
1562            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1563                && (canMove = [self alertForMoveFolderAvailable]))
1564            {
1565                [self quickPause];
1566               
1567                if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1568                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
1569                    [self updateDownloadFolder];
1570                else
1571                    canMove = NO;
1572               
1573                [self endQuickPause];
1574            }
1575           
1576            if (!canMove)
1577            {
1578                fUseIncompleteFolder = NO;
1579               
1580                [fDownloadFolder release];
1581                fDownloadFolder = fIncompleteFolder;
1582                fIncompleteFolder = nil;
1583            }
1584           
1585            [fDateCompleted release];
1586            fDateCompleted = [[NSDate alloc] init];
1587           
1588            fStat = tr_torrentStat(fHandle);
1589            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1590            break;
1591       
1592        case TR_CP_INCOMPLETE:
1593            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1594            break;
1595    }
1596    [status release];
1597}
1598
1599- (void) quickPause
1600{
1601    if (fQuickPauseDict)
1602        return;
1603
1604    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1605                    [NSNumber numberWithInt: [self speedMode: YES]], @"UploadSpeedMode",
1606                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1607                    [NSNumber numberWithInt: [self speedMode: NO]], @"DownloadSpeedMode",
1608                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1609   
1610    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: YES];
1611    [self setSpeedLimit: 0 upload: YES];
1612    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: NO];
1613    [self setSpeedLimit: 0 upload: NO];
1614}
1615
1616- (void) endQuickPause
1617{
1618    if (!fQuickPauseDict)
1619        return;
1620   
1621    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"UploadSpeedMode"] intValue] upload: YES];
1622    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1623    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"DownloadSpeedMode"] intValue] upload: NO];
1624    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1625   
1626    [fQuickPauseDict release];
1627    fQuickPauseDict = nil;
1628}
1629
1630- (void) trashFile: (NSString *) path
1631{
1632    //attempt to move to trash
1633    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1634            source: [path stringByDeletingLastPathComponent] destination: @""
1635            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1636    {
1637        //if cannot trash, just delete it (will work if it is on a remote volume)
1638        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1639            NSLog(@"Could not trash %@", path);
1640    }
1641}
1642
1643@end
Note: See TracBrowser for help on using the repository browser.