source: trunk/macosx/Torrent.m @ 3944

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

get rid of unneeded sort descriptors; hold queue order as an integer instead of an object

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