source: trunk/macosx/Torrent.m @ 3947

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

show remaining time for seeding torrents towards ratio

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