source: trunk/macosx/Torrent.m @ 4090

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

miscellaneous adjustments

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