source: trunk/macosx/Torrent.m @ 3667

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

trunk: Leopard: Time Machine will ignore incomplete files

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