source: trunk/macosx/Torrent.m @ 3946

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

sort by speed, then last activity

  • Property svn:keywords set to Date Rev Author Id
File size: 53.5 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 3946 2007-11-23 16:03:49Z 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- (float) totalRate
1113{
1114    return [self downloadRate] + [self uploadRate];
1115}
1116
1117- (uint64_t) haveVerified
1118{
1119    return fStat->haveValid;
1120}
1121
1122- (uint64_t) haveTotal
1123{
1124    return [self haveVerified] + fStat->haveUnchecked;
1125}
1126
1127- (uint64_t) downloadedTotal
1128{
1129    return fStat->downloadedEver;
1130}
1131
1132- (uint64_t) uploadedTotal
1133{
1134    return fStat->uploadedEver;
1135}
1136
1137- (uint64_t) failedHash
1138{
1139    return fStat->corruptEver;
1140}
1141
1142- (float) swarmSpeed
1143{
1144    return fStat->swarmspeed;
1145}
1146
1147- (BOOL) pex
1148{
1149        return tr_torrentIsPexEnabled(fHandle);
1150}
1151
1152- (void) setPex: (BOOL) enable
1153{
1154        tr_torrentDisablePex(fHandle, !enable);
1155}
1156
1157- (int) orderValue
1158{
1159    return fOrderValue;
1160}
1161
1162- (void) setOrderValue: (int) orderValue
1163{
1164    fOrderValue = orderValue;
1165}
1166
1167- (NSArray *) fileList
1168{
1169    return fFileList;
1170}
1171
1172- (int) fileCount
1173{
1174    return fInfo->fileCount;
1175}
1176
1177- (void) updateFileStat
1178{
1179    if (fileStat)
1180        tr_torrentFilesFree(fileStat, [self fileCount]);
1181   
1182    int count;
1183    fileStat = tr_torrentFiles(fHandle, &count);
1184}
1185
1186- (float) fileProgress: (int) index
1187{
1188    if (!fileStat)
1189        [self updateFileStat];
1190       
1191    return fileStat[index].progress;
1192}
1193
1194- (BOOL) canChangeDownloadCheckForFile: (int) index
1195{
1196    if (!fileStat)
1197        [self updateFileStat];
1198   
1199    return [self fileCount] > 1 && fileStat[index].progress < 1.0;
1200}
1201
1202- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1203{
1204    if ([self fileCount] <= 1 || [self isComplete])
1205        return NO;
1206   
1207    if (!fileStat)
1208        [self updateFileStat];
1209   
1210    int index;
1211    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1212        if (fileStat[index].progress < 1.0)
1213            return YES;
1214    return NO;
1215}
1216
1217- (int) checkForFiles: (NSIndexSet *) indexSet
1218{
1219    BOOL onState = NO, offState = NO;
1220    int index;
1221    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1222    {
1223        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1224            onState = YES;
1225        else
1226            offState = YES;
1227       
1228        if (onState && offState)
1229            return NSMixedState;
1230    }
1231    return onState ? NSOnState : NSOffState;
1232}
1233
1234- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
1235{
1236    int count = [indexSet count], i = 0, index;
1237    int * files = malloc(count * sizeof(int));
1238    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1239    {
1240        files[i] = index;
1241        i++;
1242    }
1243   
1244    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1245    free(files);
1246   
1247    [self update];
1248}
1249
1250- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1251{
1252    int count = [indexSet count], i = 0, index;
1253    int * files = malloc(count * sizeof(int));
1254    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1255    {
1256        files[i] = index;
1257        i++;
1258    }
1259   
1260    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1261    free(files);
1262}
1263
1264- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1265{
1266    int index;
1267    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1268        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1269            return YES;
1270    return NO;
1271}
1272
1273- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1274{
1275    BOOL low = NO, normal = NO, high = NO;
1276    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1277   
1278    int index, priority;
1279    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1280    {
1281        if (![self canChangeDownloadCheckForFile: index])
1282            continue;
1283       
1284        priority = tr_torrentGetFilePriority(fHandle, index);
1285        if (priority == TR_PRI_LOW)
1286        {
1287            if (low)
1288                continue;
1289            low = YES;
1290        }
1291        else if (priority == TR_PRI_HIGH)
1292        {
1293            if (high)
1294                continue;
1295            high = YES;
1296        }
1297        else
1298        {
1299            if (normal)
1300                continue;
1301            normal = YES;
1302        }
1303       
1304        [priorities addObject: [NSNumber numberWithInt: priority]];
1305        if (low && normal && high)
1306            break;
1307    }
1308    return priorities;
1309}
1310
1311- (NSMenu *) fileMenu
1312{
1313    if (!fFileMenu)
1314    {
1315        fFileMenu = [[NSMenu alloc] initWithTitle: [@"TorrentMenu:" stringByAppendingString: [self name]]];
1316        [fFileMenu setAutoenablesItems: NO];
1317    }
1318    return fFileMenu;
1319}
1320
1321- (NSDate *) dateAdded
1322{
1323    return fDateAdded;
1324}
1325
1326- (NSDate *) dateCompleted
1327{
1328    return fDateCompleted;
1329}
1330
1331- (NSDate *) dateActivity
1332{
1333    uint64_t date = fStat->activityDate;
1334    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1335}
1336
1337- (NSDate *) dateActivityOrAdd
1338{
1339    NSDate * date = [self dateActivity];
1340    return date ? date : [self dateAdded];
1341}
1342
1343- (int) stalledMinutes
1344{
1345    uint64_t start;
1346    if ((start = fStat->startDate) == 0)
1347        return -1;
1348   
1349    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1350            * activity = [self dateActivity];
1351   
1352    NSDate * laterDate = (!activity || [started compare: activity] == NSOrderedDescending) ? started : activity;
1353    return -1 * [laterDate timeIntervalSinceNow] / 60;
1354}
1355
1356- (BOOL) isStalled
1357{
1358    return fStalled;
1359}
1360
1361- (NSNumber *) stateSortKey
1362{
1363    if (![self isActive])
1364        return [NSNumber numberWithInt: 0];
1365    else if ([self isSeeding])
1366        return [NSNumber numberWithInt: 1];
1367    else
1368        return [NSNumber numberWithInt: 2];
1369}
1370
1371- (int) torrentID
1372{
1373    return fID;
1374}
1375
1376- (const tr_info *) torrentInfo
1377{
1378    return fInfo;
1379}
1380
1381- (const tr_stat *) torrentStat
1382{
1383    return fStat;
1384}
1385
1386@end
1387
1388@implementation Torrent (Private)
1389
1390//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1391- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle *) lib
1392        publicTorrent: (NSNumber *) publicTorrent
1393        downloadFolder: (NSString *) downloadFolder
1394        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1395        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
1396        dateActivity: (NSDate *) dateActivity
1397        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
1398        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;
1399{
1400    if (!(self = [super init]))
1401        return nil;
1402   
1403    static_lastid++;
1404    fID = static_lastid;
1405   
1406    fLib = lib;
1407    fDefaults = [NSUserDefaults standardUserDefaults];
1408
1409    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1410    if (fPublicTorrent)
1411        fPublicTorrentLocation = [path retain];
1412   
1413    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1414    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1415   
1416    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1417                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1418    if (fUseIncompleteFolder)
1419    {
1420        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1421        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1422    }
1423   
1424    NSString * currentDownloadFolder;
1425    tr_info info;
1426    int error;
1427    if (hashString)
1428    {
1429        if (tr_torrentParseHash(fLib, [hashString UTF8String], NULL, &info) == TR_OK)
1430        {
1431            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1432                                        ? fIncompleteFolder : fDownloadFolder;
1433            fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], [currentDownloadFolder UTF8String], YES, &error);
1434        }
1435        tr_metainfoFree(&info);
1436    }
1437    if (!fHandle && path)
1438    {
1439        if (tr_torrentParse(fLib, [path UTF8String], NULL, &info) == TR_OK)
1440        {
1441            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1442                                        ? fIncompleteFolder : fDownloadFolder;
1443            fHandle = tr_torrentInit(fLib, [path UTF8String], [currentDownloadFolder UTF8String], YES, &error);
1444        }
1445        tr_metainfoFree(&info);
1446    }
1447    if (!fHandle)
1448    {
1449        [self release];
1450        return nil;
1451    }
1452   
1453    tr_torrentSetStatusCallback(fHandle, completenessChangeCallback, self);
1454   
1455    fInfo = tr_torrentInfo(fHandle);
1456
1457    fDateAdded = dateAdded ? [dateAdded retain] : [[NSDate alloc] init];
1458        if (dateCompleted)
1459                fDateCompleted = [dateCompleted retain];
1460    if (dateActivity)
1461                fDateActivity = [dateActivity retain];
1462       
1463    fRatioSetting = ratioSetting ? [ratioSetting intValue] : NSMixedState;
1464    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1465    fFinishedSeeding = NO;
1466   
1467    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1468   
1469    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1470    fError = NO;
1471   
1472    [self createFileList];
1473   
1474    [self update];
1475   
1476    //mark incomplete files to be ignored by Time Machine
1477    if ([NSApp isOnLeopardOrBetter])
1478    {
1479        NSURL *url = [NSURL fileURLWithPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1480        fNeedSetTimeMachine = CSBackupSetItemExcluded((CFURLRef)url, ![self allDownloaded], false) != noErr;
1481    }
1482    else
1483        fNeedSetTimeMachine = NO;
1484   
1485    return self;
1486}
1487
1488- (void) createFileList
1489{
1490    int count = [self fileCount], i;
1491    tr_file * file;
1492    NSMutableArray * pathComponents;
1493    NSString * path;
1494   
1495    NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count];
1496   
1497    for (i = 0; i < count; i++)
1498    {
1499        file = &fInfo->files[i];
1500       
1501        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1502        if (fInfo->isMultifile)
1503        {
1504            path = [pathComponents objectAtIndex: 0];
1505            [pathComponents removeObjectAtIndex: 0];
1506        }
1507        else
1508            path = @"";
1509       
1510        [self insertPath: pathComponents forSiblings: fileList previousPath: path fileSize: file->length index: i];
1511        [pathComponents release];
1512    }
1513   
1514    fFileList = [[NSArray alloc] initWithArray: fileList];
1515    [fileList release];
1516}
1517
1518- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings previousPath: (NSString *) previousPath
1519            fileSize: (uint64_t) size index: (int) index
1520{
1521    NSString * name = [components objectAtIndex: 0];
1522    BOOL isFolder = [components count] > 1;
1523   
1524    NSMutableDictionary * dict = nil;
1525    if (isFolder)
1526    {
1527        NSEnumerator * enumerator = [siblings objectEnumerator];
1528        while ((dict = [enumerator nextObject]))
1529            if ([[dict objectForKey: @"Name"] isEqualToString: name] && [[dict objectForKey: @"IsFolder"] boolValue])
1530                break;
1531    }
1532   
1533    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1534   
1535    //create new folder or item if it doesn't already exist
1536    if (!dict)
1537    {
1538        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1539                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1540        [siblings addObject: dict];
1541       
1542        if (isFolder)
1543        {
1544            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1545            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1546        }
1547        else
1548        {
1549            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1550            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1551           
1552            NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
1553            [icon setFlipped: YES];
1554            [dict setObject: icon forKey: @"Icon"];
1555        }
1556    }
1557    else
1558        [[dict objectForKey: @"Indexes"] addIndex: index];
1559   
1560    if (isFolder)
1561    {
1562        [components removeObjectAtIndex: 0];
1563        [self insertPath: components forSiblings: [dict objectForKey: @"Children"] previousPath: currentPath fileSize: size
1564                index: index];
1565    }
1566}
1567
1568- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1569{
1570    return fUseIncompleteFolder &&
1571        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1572}
1573
1574- (void) updateDownloadFolder
1575{
1576    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1577    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1578}
1579
1580//status has been retained
1581- (void) completenessChange: (NSNumber *) status
1582{
1583    [self update];
1584   
1585    BOOL canMove;
1586    switch ([status intValue])
1587    {
1588        case TR_CP_DONE:
1589        case TR_CP_COMPLETE:
1590            canMove = YES;
1591       
1592            //move file from incomplete folder to download folder
1593            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1594                && (canMove = [self alertForMoveFolderAvailable]))
1595            {
1596                [self quickPause];
1597               
1598                if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1599                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
1600                    [self updateDownloadFolder];
1601                else
1602                    canMove = NO;
1603               
1604                [self endQuickPause];
1605            }
1606           
1607            if (!canMove)
1608            {
1609                fUseIncompleteFolder = NO;
1610               
1611                [fDownloadFolder release];
1612                fDownloadFolder = fIncompleteFolder;
1613                fIncompleteFolder = nil;
1614            }
1615           
1616            [fDateCompleted release];
1617            fDateCompleted = [[NSDate alloc] init];
1618           
1619            //allow to be backed up by Time Machine
1620            if ([NSApp isOnLeopardOrBetter])
1621            {
1622                NSURL *url = [NSURL fileURLWithPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1623                fNeedSetTimeMachine = CSBackupSetItemExcluded((CFURLRef)url, false, false) != noErr;
1624            }
1625           
1626            fStat = tr_torrentStat(fHandle);
1627            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1628            break;
1629       
1630        case TR_CP_INCOMPLETE:
1631            //do not allow to be backed up by Time Machine
1632            if ([NSApp isOnLeopardOrBetter])
1633            {
1634                NSURL *url = [NSURL fileURLWithPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1635                fNeedSetTimeMachine = CSBackupSetItemExcluded((CFURLRef)url, true, false) != noErr;
1636            }
1637           
1638            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1639            break;
1640    }
1641    [status release];
1642}
1643
1644- (void) quickPause
1645{
1646    if (fQuickPauseDict)
1647        return;
1648
1649    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1650                    [NSNumber numberWithInt: [self speedMode: YES]], @"UploadSpeedMode",
1651                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1652                    [NSNumber numberWithInt: [self speedMode: NO]], @"DownloadSpeedMode",
1653                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1654   
1655    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: YES];
1656    [self setSpeedLimit: 0 upload: YES];
1657    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: NO];
1658    [self setSpeedLimit: 0 upload: NO];
1659}
1660
1661- (void) endQuickPause
1662{
1663    if (!fQuickPauseDict)
1664        return;
1665   
1666    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"UploadSpeedMode"] intValue] upload: YES];
1667    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1668    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"DownloadSpeedMode"] intValue] upload: NO];
1669    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1670   
1671    [fQuickPauseDict release];
1672    fQuickPauseDict = nil;
1673}
1674
1675- (void) trashFile: (NSString *) path
1676{
1677    //attempt to move to trash
1678    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1679            source: [path stringByDeletingLastPathComponent] destination: @""
1680            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1681    {
1682        //if cannot trash, just delete it (will work if it is on a remote volume)
1683        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1684            NSLog(@"Could not trash %@", path);
1685    }
1686}
1687
1688@end
Note: See TracBrowser for help on using the repository browser.