source: trunk/macosx/Torrent.m @ 3934

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

when sorting by last activity, if there was no activity use date

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