source: trunk/macosx/Torrent.m @ 4189

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

sort groups by their order in the table

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