source: trunk/macosx/Torrent.m @ 4287

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

remove ctor from torrent creation and parse functions

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