source: trunk/macosx/Torrent.m @ 4054

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

patch from BiHi? to always display the second value of remaining times with double digits; rephrase "Resume Selected Without Wait" to "Resume Selected Right Away"

  • Property svn:keywords set to Date Rev Author Id
File size: 53.9 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 4054 2007-12-03 19:59:11Z 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: [self folder] ? 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- (BOOL) folder
609{
610    return fInfo->isMultifile;
611}
612
613- (uint64_t) size
614{
615    return fInfo->totalSize;
616}
617
618- (uint64_t) sizeLeft
619{
620    return fStat->leftUntilDone;
621}
622
623- (NSString *) trackerAddress
624{
625    return [NSString stringWithFormat: @"http://%s:%d", fStat->tracker->address, fStat->tracker->port];
626}
627
628- (NSString *) trackerAddressAnnounce
629{
630    return [NSString stringWithUTF8String: fStat->tracker->announce];
631}
632
633- (NSArray *) allTrackers
634{
635    NSMutableArray * trackers = [NSMutableArray arrayWithCapacity: fInfo->trackerTiers], * subTrackers;
636   
637    int i, j;
638    for (i = 0; i < fInfo->trackerTiers; i++)
639    {
640        subTrackers = [NSMutableArray arrayWithCapacity: fInfo->trackerList[i].count];
641        for (j = 0; j < fInfo->trackerList[i].count; j++)
642            [subTrackers addObject: [NSString stringWithFormat: @"http://%s:%d",
643                fInfo->trackerList[i].list[j].address, fInfo->trackerList[i].list[j].port]];
644       
645        [trackers addObject: subTrackers];
646    }
647   
648    return trackers;
649}
650
651- (NSString *) comment
652{
653    return [NSString stringWithUTF8String: fInfo->comment];
654}
655
656- (NSString *) creator
657{
658    return [NSString stringWithUTF8String: fInfo->creator];
659}
660
661- (NSDate *) dateCreated
662{
663    int date = fInfo->dateCreated;
664    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
665}
666
667- (int) pieceSize
668{
669    return fInfo->pieceSize;
670}
671
672- (int) pieceCount
673{
674    return fInfo->pieceCount;
675}
676
677- (NSString *) hashString
678{
679    return [NSString stringWithUTF8String: fInfo->hashString];
680}
681
682- (BOOL) privateTorrent
683{
684    return fInfo->isPrivate;
685}
686
687- (NSString *) torrentLocation
688{
689    return [NSString stringWithUTF8String: fInfo->torrent];
690}
691
692- (NSString *) publicTorrentLocation
693{
694    return fPublicTorrentLocation;
695}
696
697- (NSString *) dataLocation
698{
699    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
700}
701
702- (BOOL) publicTorrent
703{
704    return fPublicTorrent;
705}
706
707- (float) progress
708{
709    return fStat->percentComplete;
710    //return (float)[self haveTotal] / [self size];
711}
712
713- (float) progressDone
714{
715    return fStat->percentDone;
716}
717
718- (float) progressLeft
719{
720    return (float)[self sizeLeft] / [self size];
721}
722
723- (int) eta
724{
725    return fStat->eta;
726}
727
728- (int) etaRatio
729{
730    if (![self isSeeding])
731        return -1;
732   
733    float uploadRate = [self uploadRate];
734    if (uploadRate < 0.1)
735        return -1;
736   
737    float stopRatio = [self actualStopRatio], ratio = [self ratio];
738    if (stopRatio == INVALID || ratio >= stopRatio)
739        return -1;
740   
741    return (float)MAX([self downloadedTotal], [self haveVerified]) * (stopRatio - ratio) / uploadRate / 1024.0;
742}
743
744- (NSString * ) etaString: (int) eta
745{
746    if (eta < 0)
747        return @"";
748   
749    if (eta < 60)
750        return [NSString stringWithFormat: NSLocalizedString(@"%d sec", "Torrent -> remaining time"), eta];
751    else if (eta < 3600) //60 * 60
752        return [NSString stringWithFormat: NSLocalizedString(@"%d min %02d sec", "Torrent -> remaining time"),
753                                                eta / 60, eta % 60];
754    else if (eta < 86400) //24 * 60 * 60
755        return [NSString stringWithFormat: NSLocalizedString(@"%d hr %02d min", "Torrent -> remaining time"),
756                                                eta / 3600, (eta / 60) % 60];
757    else
758    {
759        int days = eta / 86400, hours = (eta / 3600) % 24;
760        if (days > 1)
761            return [NSString stringWithFormat: NSLocalizedString(@"%d days %02d hr", "Torrent -> remaining time"), days, hours];
762        else
763            return [NSString stringWithFormat: NSLocalizedString(@"1 day %02d hr", "Torrent -> remaining time"), hours];
764    }
765}
766
767- (float) notAvailableDesired
768{
769    return (float)(fStat->desiredSize - fStat->desiredAvailable) / [self size];
770}
771
772- (BOOL) isActive
773{
774    return fStat->status != TR_STATUS_STOPPED;
775}
776
777- (BOOL) isSeeding
778{
779    return fStat->status == TR_STATUS_SEED || fStat->status == TR_STATUS_DONE;
780}
781
782- (BOOL) isChecking
783{
784    return fStat->status == TR_STATUS_CHECK || fStat->status == TR_STATUS_CHECK_WAIT;
785}
786
787- (BOOL) allDownloaded
788{
789    return [self progressDone] >= 1.0;
790}
791
792- (BOOL) isComplete
793{
794    return [self progress] >= 1.0;
795}
796
797- (BOOL) isError
798{
799    return fStat->error != 0;
800}
801
802- (NSString *) errorMessage
803{
804    if (![self isError])
805        return @"";
806   
807    NSString * error;
808    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
809        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
810        error = NSLocalizedString(@"(unreadable error)", "Torrent -> error string unreadable");
811   
812    return error;
813}
814
815- (NSArray *) peers
816{
817    int totalPeers, i;
818    tr_peer_stat * peers = tr_torrentPeers(fHandle, &totalPeers);
819   
820    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
821    NSMutableDictionary * dic;
822   
823    tr_peer_stat * peer;
824    for (i = 0; i < totalPeers; i++)
825    {
826        peer = &peers[i];
827       
828        dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
829            [NSNumber numberWithInt: peer->from], @"From",
830            [NSString stringWithCString: (char *)peer->addr encoding: NSUTF8StringEncoding], @"IP",
831            [NSNumber numberWithInt: peer->port], @"Port",
832            [NSNumber numberWithFloat: peer->progress], @"Progress",
833            [NSNumber numberWithBool: peer->isEncrypted], @"Encryption",
834            [NSString stringWithCString: (char *)peer->client encoding: NSUTF8StringEncoding], @"Client",
835            [NSNumber numberWithInt: peer->status], @"Status", nil];
836       
837        if (peer->isDownloading)
838            [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
839        if (peer->isUploading)
840            [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
841       
842        [peerDics addObject: dic];
843    }
844   
845    tr_torrentPeersFree(peers, totalPeers);
846   
847    return peerDics;
848}
849
850- (NSString *) progressString
851{
852    NSString * string;
853   
854    if (![self allDownloaded])
855    {
856        if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
857        {
858            uint64_t have = [self haveTotal];
859            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ selected (%.2f%%)", "Torrent -> progress string"),
860                            [NSString stringForFileSize: have], [NSString stringForFileSize: have + [self sizeLeft]],
861                            100.0 * [self progressDone]];
862        }
863        else
864            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ (%.2f%%)", "Torrent -> progress string"),
865                            [NSString stringForFileSize: [self haveTotal]],
866                            [NSString stringForFileSize: [self size]], 100.0 * [self progress]];
867    }
868    else if (![self isComplete])
869    {
870        if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
871            string = [NSString stringWithFormat: NSLocalizedString(@"%@ selected, uploaded %@ (Ratio: %@)",
872                "Torrent -> progress string"), [NSString stringForFileSize: [self haveTotal]],
873                [NSString stringForFileSize: [self uploadedTotal]], [NSString stringForRatio: [self ratio]]];
874        else
875            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ (%.2f%%), uploaded %@ (Ratio: %@)",
876                "Torrent -> progress string"), [NSString stringForFileSize: [self haveTotal]],
877                [NSString stringForFileSize: [self size]], 100.0 * [self progress],
878                [NSString stringForFileSize: [self uploadedTotal]], [NSString stringForRatio: [self ratio]]];
879    }
880    else
881        string = [NSString stringWithFormat: NSLocalizedString(@"%@, uploaded %@ (Ratio: %@)", "Torrent -> progress string"),
882                [NSString stringForFileSize: [self size]], [NSString stringForFileSize: [self uploadedTotal]],
883                [NSString stringForRatio: [self ratio]]];
884   
885    //add time when downloading
886    if (fStat->status == TR_STATUS_DOWNLOAD || ([self isSeeding] && fRatioSetting != NSOffState))
887    {
888        int eta = fStat->status == TR_STATUS_DOWNLOAD ? [self eta] : [self etaRatio];
889        string = eta >= 0 ? [string stringByAppendingFormat: NSLocalizedString(@" - %@ remaining", "Torrent -> progress string"),
890                                [self etaString: eta]]
891            : [string stringByAppendingString: NSLocalizedString(@" - remaining time unknown", "Torrent -> progress string")];
892    }
893   
894    return string;
895}
896
897- (NSString *) statusString
898{
899    NSString * string;
900   
901    if ([self isError])
902    {
903        NSString * errorString = [self errorMessage];
904        if (!errorString || [errorString isEqualToString: @""])
905            string = NSLocalizedString(@"Error", "Torrent -> status string");
906        else
907            string = [NSLocalizedString(@"Error: ", "Torrent -> status string") stringByAppendingString: errorString];
908    }
909    else
910    {
911        switch (fStat->status)
912        {
913            case TR_STATUS_STOPPED:
914                if (fWaitToStart)
915                {
916                    string = ![self allDownloaded]
917                            ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
918                            : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
919                }
920                else if (fFinishedSeeding)
921                    string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
922                else
923                    string = NSLocalizedString(@"Paused", "Torrent -> status string");
924                break;
925
926            case TR_STATUS_CHECK_WAIT:
927                string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
928                break;
929
930            case TR_STATUS_CHECK:
931                string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
932                                        "Torrent -> status string"), 100.0 * fStat->recheckProgress];
933                break;
934
935            case TR_STATUS_DOWNLOAD:
936                if ([self totalPeersConnected] != 1)
937                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
938                                                    "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
939                else
940                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
941                                                    "Torrent -> status string"), [self peersSendingToUs]];
942                break;
943
944            case TR_STATUS_SEED:
945            case TR_STATUS_DONE:
946                if ([self totalPeersConnected] != 1)
947                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
948                                                    [self peersGettingFromUs], [self totalPeersConnected]];
949                else
950                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
951                                                    [self peersGettingFromUs]];
952                break;
953           
954            default:
955                string = @"";
956        }
957       
958        if (fStalled)
959            string = [NSLocalizedString(@"Stalled, ", "Torrent -> status string") stringByAppendingString: string];
960    }
961   
962    //append even if error
963    if ([self isActive] && ![self isChecking])
964    {
965        if (fStat->status == TR_STATUS_DOWNLOAD)
966            string = [string stringByAppendingFormat: NSLocalizedString(@" - DL: %@, UL: %@", "Torrent -> status string"),
967                    [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
968        else
969            string = [string stringByAppendingFormat: NSLocalizedString(@" - UL: %@", "Torrent -> status string"),
970                        [NSString stringForSpeed: [self uploadRate]]];
971    }
972   
973    return string;
974}
975
976- (NSString *) shortStatusString
977{
978    NSString * string;
979   
980    switch (fStat->status)
981    {
982        case TR_STATUS_STOPPED:
983            if (fWaitToStart)
984            {
985                string = ![self allDownloaded]
986                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
987                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
988            }
989            else if (fFinishedSeeding)
990                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
991            else
992                string = NSLocalizedString(@"Paused", "Torrent -> status string");
993            break;
994
995        case TR_STATUS_CHECK_WAIT:
996            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
997            break;
998
999        case TR_STATUS_CHECK:
1000            string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1001                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
1002            break;
1003       
1004        case TR_STATUS_DOWNLOAD:
1005            string = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, UL: %@", "Torrent -> status string"),
1006                            [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
1007            break;
1008       
1009        case TR_STATUS_SEED:
1010        case TR_STATUS_DONE:
1011            string = [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, UL: %@", "Torrent -> status string"),
1012                            [NSString stringForRatio: [self ratio]], [NSString stringForSpeed: [self uploadRate]]];
1013            break;
1014       
1015        default:
1016            string = @"";
1017    }
1018   
1019    return string;
1020}
1021
1022- (NSString *) remainingTimeString
1023{
1024    switch (fStat->status)
1025    {
1026        case TR_STATUS_DOWNLOAD:
1027            return [self eta] >= 0 ? [self etaString: [self eta]] : NSLocalizedString(@"Unknown", "Torrent -> remaining time");
1028       
1029        case TR_STATUS_SEED:
1030        case TR_STATUS_DONE:
1031            return [NSLocalizedString(@"Ratio: ", "Torrent -> status string") stringByAppendingString:
1032                                                                            [NSString stringForRatio: [self ratio]]];
1033       
1034        default:
1035            return [self shortStatusString];
1036    }
1037}
1038
1039- (NSString *) stateString
1040{
1041    switch (fStat->status)
1042    {
1043        case TR_STATUS_STOPPED:
1044            return NSLocalizedString(@"Paused", "Torrent -> status string");
1045
1046        case TR_STATUS_CHECK:
1047            return [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1048                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
1049       
1050        case TR_STATUS_CHECK_WAIT:
1051            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1052
1053        case TR_STATUS_DOWNLOAD:
1054            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1055
1056        case TR_STATUS_SEED:
1057        case TR_STATUS_DONE:
1058            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1059       
1060        default:
1061            return NSLocalizedString(@"N/A", "Torrent -> status string");
1062    }
1063}
1064
1065- (int) seeders
1066{
1067    return fStat->seeders;
1068}
1069
1070- (int) leechers
1071{
1072    return fStat->leechers;
1073}
1074
1075- (int) completedFromTracker
1076{
1077    return fStat->completedFromTracker;
1078}
1079
1080- (int) totalPeersConnected
1081{
1082    return fStat->peersConnected;
1083}
1084
1085- (int) totalPeersTracker
1086{
1087    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1088}
1089
1090- (int) totalPeersIncoming
1091{
1092    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1093}
1094
1095- (int) totalPeersCache
1096{
1097    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1098}
1099
1100- (int) totalPeersPex
1101{
1102    return fStat->peersFrom[TR_PEER_FROM_PEX];
1103}
1104
1105- (int) totalPeersKnown
1106{
1107    return fStat->peersKnown;
1108}
1109
1110- (int) peersSendingToUs
1111{
1112    return fStat->peersSendingToUs;
1113}
1114
1115- (int) peersGettingFromUs
1116{
1117    return fStat->peersGettingFromUs;
1118}
1119
1120- (float) downloadRate
1121{
1122    return fStat->rateDownload;
1123}
1124
1125- (float) uploadRate
1126{
1127    return fStat->rateUpload;
1128}
1129
1130- (float) totalRate
1131{
1132    return [self downloadRate] + [self uploadRate];
1133}
1134
1135- (uint64_t) haveVerified
1136{
1137    return fStat->haveValid;
1138}
1139
1140- (uint64_t) haveTotal
1141{
1142    return [self haveVerified] + fStat->haveUnchecked;
1143}
1144
1145- (uint64_t) downloadedTotal
1146{
1147    return fStat->downloadedEver;
1148}
1149
1150- (uint64_t) uploadedTotal
1151{
1152    return fStat->uploadedEver;
1153}
1154
1155- (uint64_t) failedHash
1156{
1157    return fStat->corruptEver;
1158}
1159
1160- (float) swarmSpeed
1161{
1162    return fStat->swarmspeed;
1163}
1164
1165- (BOOL) pex
1166{
1167        return tr_torrentIsPexEnabled(fHandle);
1168}
1169
1170- (void) setPex: (BOOL) enable
1171{
1172        tr_torrentDisablePex(fHandle, !enable);
1173}
1174
1175- (int) orderValue
1176{
1177    return fOrderValue;
1178}
1179
1180- (void) setOrderValue: (int) orderValue
1181{
1182    fOrderValue = orderValue;
1183}
1184
1185- (NSArray *) fileList
1186{
1187    return fFileList;
1188}
1189
1190- (int) fileCount
1191{
1192    return fInfo->fileCount;
1193}
1194
1195- (void) updateFileStat
1196{
1197    if (fileStat)
1198        tr_torrentFilesFree(fileStat, [self fileCount]);
1199   
1200    int count;
1201    fileStat = tr_torrentFiles(fHandle, &count);
1202}
1203
1204- (float) fileProgress: (int) index
1205{
1206    if (!fileStat)
1207        [self updateFileStat];
1208       
1209    return fileStat[index].progress;
1210}
1211
1212- (BOOL) canChangeDownloadCheckForFile: (int) index
1213{
1214    if (!fileStat)
1215        [self updateFileStat];
1216   
1217    return [self fileCount] > 1 && fileStat[index].progress < 1.0;
1218}
1219
1220- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1221{
1222    if ([self fileCount] <= 1 || [self isComplete])
1223        return NO;
1224   
1225    if (!fileStat)
1226        [self updateFileStat];
1227   
1228    int index;
1229    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1230        if (fileStat[index].progress < 1.0)
1231            return YES;
1232    return NO;
1233}
1234
1235- (int) checkForFiles: (NSIndexSet *) indexSet
1236{
1237    BOOL onState = NO, offState = NO;
1238    int index;
1239    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1240    {
1241        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1242            onState = YES;
1243        else
1244            offState = YES;
1245       
1246        if (onState && offState)
1247            return NSMixedState;
1248    }
1249    return onState ? NSOnState : NSOffState;
1250}
1251
1252- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
1253{
1254    int count = [indexSet count], i = 0, index;
1255    int * files = malloc(count * sizeof(int));
1256    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1257    {
1258        files[i] = index;
1259        i++;
1260    }
1261   
1262    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1263    free(files);
1264   
1265    [self update];
1266}
1267
1268- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1269{
1270    int count = [indexSet count], i = 0, index;
1271    int * files = malloc(count * sizeof(int));
1272    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1273    {
1274        files[i] = index;
1275        i++;
1276    }
1277   
1278    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1279    free(files);
1280}
1281
1282- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1283{
1284    int index;
1285    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1286        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1287            return YES;
1288    return NO;
1289}
1290
1291- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1292{
1293    BOOL low = NO, normal = NO, high = NO;
1294    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1295   
1296    int index, priority;
1297    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1298    {
1299        if (![self canChangeDownloadCheckForFile: index])
1300            continue;
1301       
1302        priority = tr_torrentGetFilePriority(fHandle, index);
1303        if (priority == TR_PRI_LOW)
1304        {
1305            if (low)
1306                continue;
1307            low = YES;
1308        }
1309        else if (priority == TR_PRI_HIGH)
1310        {
1311            if (high)
1312                continue;
1313            high = YES;
1314        }
1315        else
1316        {
1317            if (normal)
1318                continue;
1319            normal = YES;
1320        }
1321       
1322        [priorities addObject: [NSNumber numberWithInt: priority]];
1323        if (low && normal && high)
1324            break;
1325    }
1326    return priorities;
1327}
1328
1329- (NSMenu *) fileMenu
1330{
1331    if (!fFileMenu)
1332    {
1333        fFileMenu = [[NSMenu alloc] initWithTitle: [@"TorrentMenu:" stringByAppendingString: [self name]]];
1334        [fFileMenu setAutoenablesItems: NO];
1335    }
1336    return fFileMenu;
1337}
1338
1339- (NSDate *) dateAdded
1340{
1341    return fDateAdded;
1342}
1343
1344- (NSDate *) dateCompleted
1345{
1346    return fDateCompleted;
1347}
1348
1349- (NSDate *) dateActivity
1350{
1351    uint64_t date = fStat->activityDate;
1352    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1353}
1354
1355- (NSDate *) dateActivityOrAdd
1356{
1357    NSDate * date = [self dateActivity];
1358    return date ? date : [self dateAdded];
1359}
1360
1361- (int) stalledMinutes
1362{
1363    uint64_t start;
1364    if ((start = fStat->startDate) == 0)
1365        return -1;
1366   
1367    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1368            * activity = [self dateActivity];
1369   
1370    NSDate * laterDate = (!activity || [started compare: activity] == NSOrderedDescending) ? started : activity;
1371    return -1 * [laterDate timeIntervalSinceNow] / 60;
1372}
1373
1374- (BOOL) isStalled
1375{
1376    return fStalled;
1377}
1378
1379- (NSNumber *) stateSortKey
1380{
1381    if (![self isActive])
1382        return [NSNumber numberWithInt: 0];
1383    else if ([self isSeeding])
1384        return [NSNumber numberWithInt: 1];
1385    else
1386        return [NSNumber numberWithInt: 2];
1387}
1388
1389- (int) torrentID
1390{
1391    return fID;
1392}
1393
1394- (const tr_info *) torrentInfo
1395{
1396    return fInfo;
1397}
1398
1399- (const tr_stat *) torrentStat
1400{
1401    return fStat;
1402}
1403
1404@end
1405
1406@implementation Torrent (Private)
1407
1408//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1409- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle *) lib
1410        publicTorrent: (NSNumber *) publicTorrent
1411        downloadFolder: (NSString *) downloadFolder
1412        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1413        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
1414        dateActivity: (NSDate *) dateActivity
1415        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
1416        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;
1417{
1418    if (!(self = [super init]))
1419        return nil;
1420   
1421    static_lastid++;
1422    fID = static_lastid;
1423   
1424    fLib = lib;
1425    fDefaults = [NSUserDefaults standardUserDefaults];
1426
1427    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1428    if (fPublicTorrent)
1429        fPublicTorrentLocation = [path retain];
1430   
1431    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1432    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1433   
1434    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1435                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1436    if (fUseIncompleteFolder)
1437    {
1438        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1439        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1440    }
1441   
1442    NSString * currentDownloadFolder;
1443    tr_info info;
1444    int error;
1445    if (hashString)
1446    {
1447        if (tr_torrentParseHash(fLib, [hashString UTF8String], NULL, &info) == TR_OK)
1448        {
1449            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1450                                        ? fIncompleteFolder : fDownloadFolder;
1451            fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], [currentDownloadFolder UTF8String], YES, &error);
1452        }
1453        tr_metainfoFree(&info);
1454    }
1455    if (!fHandle && path)
1456    {
1457        if (tr_torrentParse(fLib, [path UTF8String], NULL, &info) == TR_OK)
1458        {
1459            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1460                                        ? fIncompleteFolder : fDownloadFolder;
1461            fHandle = tr_torrentInit(fLib, [path UTF8String], [currentDownloadFolder UTF8String], YES, &error);
1462        }
1463        tr_metainfoFree(&info);
1464    }
1465    if (!fHandle)
1466    {
1467        [self release];
1468        return nil;
1469    }
1470   
1471    tr_torrentSetStatusCallback(fHandle, completenessChangeCallback, self);
1472   
1473    fInfo = tr_torrentInfo(fHandle);
1474
1475    fDateAdded = dateAdded ? [dateAdded retain] : [[NSDate alloc] init];
1476        if (dateCompleted)
1477                fDateCompleted = [dateCompleted retain];
1478    if (dateActivity)
1479                fDateActivity = [dateActivity retain];
1480       
1481    fRatioSetting = ratioSetting ? [ratioSetting intValue] : NSMixedState;
1482    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1483    fFinishedSeeding = NO;
1484   
1485    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1486   
1487    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1488    fError = NO;
1489   
1490    [self createFileList];
1491   
1492    [self update];
1493   
1494    //mark incomplete files to be ignored by Time Machine
1495    if ([NSApp isOnLeopardOrBetter])
1496    {
1497        NSURL *url = [NSURL fileURLWithPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1498        fNeedSetTimeMachine = CSBackupSetItemExcluded((CFURLRef)url, ![self allDownloaded], false) != noErr;
1499    }
1500    else
1501        fNeedSetTimeMachine = NO;
1502   
1503    return self;
1504}
1505
1506- (void) createFileList
1507{
1508    int count = [self fileCount], i;
1509    tr_file * file;
1510    NSMutableArray * pathComponents;
1511    NSString * path;
1512   
1513    NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count];
1514   
1515    for (i = 0; i < count; i++)
1516    {
1517        file = &fInfo->files[i];
1518       
1519        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1520        if ([self folder])
1521        {
1522            path = [pathComponents objectAtIndex: 0];
1523            [pathComponents removeObjectAtIndex: 0];
1524        }
1525        else
1526            path = @"";
1527       
1528        [self insertPath: pathComponents forSiblings: fileList previousPath: path fileSize: file->length index: i];
1529        [pathComponents release];
1530    }
1531   
1532    fFileList = [[NSArray alloc] initWithArray: fileList];
1533    [fileList release];
1534}
1535
1536- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings previousPath: (NSString *) previousPath
1537            fileSize: (uint64_t) size index: (int) index
1538{
1539    NSString * name = [components objectAtIndex: 0];
1540    BOOL isFolder = [components count] > 1;
1541   
1542    NSMutableDictionary * dict = nil;
1543    if (isFolder)
1544    {
1545        NSEnumerator * enumerator = [siblings objectEnumerator];
1546        while ((dict = [enumerator nextObject]))
1547            if ([[dict objectForKey: @"Name"] isEqualToString: name] && [[dict objectForKey: @"IsFolder"] boolValue])
1548                break;
1549    }
1550   
1551    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1552   
1553    //create new folder or item if it doesn't already exist
1554    if (!dict)
1555    {
1556        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1557                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1558        [siblings addObject: dict];
1559       
1560        if (isFolder)
1561        {
1562            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1563            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1564        }
1565        else
1566        {
1567            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1568            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1569           
1570            NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
1571            [icon setFlipped: YES];
1572            [dict setObject: icon forKey: @"Icon"];
1573        }
1574    }
1575    else
1576        [[dict objectForKey: @"Indexes"] addIndex: index];
1577   
1578    if (isFolder)
1579    {
1580        [components removeObjectAtIndex: 0];
1581        [self insertPath: components forSiblings: [dict objectForKey: @"Children"] previousPath: currentPath fileSize: size
1582                index: index];
1583    }
1584}
1585
1586- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1587{
1588    return fUseIncompleteFolder &&
1589        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1590}
1591
1592- (void) updateDownloadFolder
1593{
1594    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1595    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1596}
1597
1598//status has been retained
1599- (void) completenessChange: (NSNumber *) status
1600{
1601    [self update];
1602   
1603    BOOL canMove;
1604    switch ([status intValue])
1605    {
1606        case TR_CP_DONE:
1607        case TR_CP_COMPLETE:
1608            canMove = YES;
1609       
1610            //move file from incomplete folder to download folder
1611            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1612                && (canMove = [self alertForMoveFolderAvailable]))
1613            {
1614                [self quickPause];
1615               
1616                if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1617                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
1618                    [self updateDownloadFolder];
1619                else
1620                    canMove = NO;
1621               
1622                [self endQuickPause];
1623            }
1624           
1625            if (!canMove)
1626            {
1627                fUseIncompleteFolder = NO;
1628               
1629                [fDownloadFolder release];
1630                fDownloadFolder = fIncompleteFolder;
1631                fIncompleteFolder = nil;
1632            }
1633           
1634            [fDateCompleted release];
1635            fDateCompleted = [[NSDate alloc] init];
1636           
1637            //allow to be backed up by Time Machine
1638            if ([NSApp isOnLeopardOrBetter])
1639            {
1640                NSURL *url = [NSURL fileURLWithPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1641                fNeedSetTimeMachine = CSBackupSetItemExcluded((CFURLRef)url, false, false) != noErr;
1642            }
1643           
1644            fStat = tr_torrentStat(fHandle);
1645            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1646            break;
1647       
1648        case TR_CP_INCOMPLETE:
1649            //do not allow to be backed up by Time Machine
1650            if ([NSApp isOnLeopardOrBetter])
1651            {
1652                NSURL *url = [NSURL fileURLWithPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1653                fNeedSetTimeMachine = CSBackupSetItemExcluded((CFURLRef)url, true, false) != noErr;
1654            }
1655           
1656            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1657            break;
1658    }
1659    [status release];
1660}
1661
1662- (void) quickPause
1663{
1664    if (fQuickPauseDict)
1665        return;
1666
1667    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1668                    [NSNumber numberWithInt: [self speedMode: YES]], @"UploadSpeedMode",
1669                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1670                    [NSNumber numberWithInt: [self speedMode: NO]], @"DownloadSpeedMode",
1671                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1672   
1673    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: YES];
1674    [self setSpeedLimit: 0 upload: YES];
1675    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: NO];
1676    [self setSpeedLimit: 0 upload: NO];
1677}
1678
1679- (void) endQuickPause
1680{
1681    if (!fQuickPauseDict)
1682        return;
1683   
1684    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"UploadSpeedMode"] intValue] upload: YES];
1685    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1686    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"DownloadSpeedMode"] intValue] upload: NO];
1687    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1688   
1689    [fQuickPauseDict release];
1690    fQuickPauseDict = nil;
1691}
1692
1693- (void) trashFile: (NSString *) path
1694{
1695    //attempt to move to trash
1696    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1697            source: [path stringByDeletingLastPathComponent] destination: @""
1698            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1699    {
1700        //if cannot trash, just delete it (will work if it is on a remote volume)
1701        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1702            NSLog(@"Could not trash %@", path);
1703    }
1704}
1705
1706@end
Note: See TracBrowser for help on using the repository browser.