source: trunk/macosx/Torrent.m @ 3975

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

attempt to make per-torrent action menu a little faster

  • Property svn:keywords set to Date Rev Author Id
File size: 53.9 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 3975 2007-11-26 17:33:31Z 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 %d sec", "Torrent -> remaining time"),
753                                                eta / 60, eta % 60];
754    else if (eta < 86400) //24 * 60 * 60
755        return [NSString stringWithFormat: NSLocalizedString(@"%d hr %d 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 %d hr", "Torrent -> remaining time"), days, hours];
762        else
763            return [NSString stringWithFormat: NSLocalizedString(@"1 day %d 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.