source: trunk/macosx/Torrent.m @ 4297

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

show remaining time for seeding torrents set to global stop seeding setting (with that setting on) reported by m1b

  • Property svn:keywords set to Date Rev Author Id
File size: 55.6 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 4297 2007-12-22 22:02:38Z 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]
903        && (fRatioSetting == NSOnState || (fRatioSetting == NSMixedState && [fDefaults boolForKey: @"RatioCheck"]))))
904    {
905        int eta = fStat->status == TR_STATUS_DOWNLOAD ? [self eta] : [self etaRatio];
906        string = eta >= 0 ? [string stringByAppendingFormat: NSLocalizedString(@" - %@ remaining", "Torrent -> progress string"),
907                                [self etaString: eta]]
908            : [string stringByAppendingString: NSLocalizedString(@" - remaining time unknown", "Torrent -> progress string")];
909    }
910   
911    return string;
912}
913
914- (NSString *) statusString
915{
916    NSString * string;
917   
918    if ([self isError])
919    {
920        NSString * errorString = [self errorMessage];
921        if (!errorString || [errorString isEqualToString: @""])
922            string = NSLocalizedString(@"Error", "Torrent -> status string");
923        else
924            string = [NSLocalizedString(@"Error: ", "Torrent -> status string") stringByAppendingString: errorString];
925    }
926    else
927    {
928        switch (fStat->status)
929        {
930            case TR_STATUS_STOPPED:
931                if (fWaitToStart)
932                {
933                    string = ![self allDownloaded]
934                            ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
935                            : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
936                }
937                else if (fFinishedSeeding)
938                    string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
939                else
940                    string = NSLocalizedString(@"Paused", "Torrent -> status string");
941                break;
942
943            case TR_STATUS_CHECK_WAIT:
944                string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
945                break;
946
947            case TR_STATUS_CHECK:
948                string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
949                                        "Torrent -> status string"), 100.0 * fStat->recheckProgress];
950                break;
951
952            case TR_STATUS_DOWNLOAD:
953                if ([self totalPeersConnected] != 1)
954                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
955                                                    "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
956                else
957                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
958                                                    "Torrent -> status string"), [self peersSendingToUs]];
959                break;
960
961            case TR_STATUS_SEED:
962            case TR_STATUS_DONE:
963                if ([self totalPeersConnected] != 1)
964                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
965                                                    [self peersGettingFromUs], [self totalPeersConnected]];
966                else
967                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
968                                                    [self peersGettingFromUs]];
969                break;
970           
971            default:
972                string = @"";
973        }
974       
975        if (fStalled)
976            string = [NSLocalizedString(@"Stalled, ", "Torrent -> status string") stringByAppendingString: string];
977    }
978   
979    //append even if error
980    if ([self isActive] && ![self isChecking])
981    {
982        if (fStat->status == TR_STATUS_DOWNLOAD)
983            string = [string stringByAppendingFormat: NSLocalizedString(@" - DL: %@, UL: %@", "Torrent -> status string"),
984                    [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
985        else
986            string = [string stringByAppendingFormat: NSLocalizedString(@" - UL: %@", "Torrent -> status string"),
987                        [NSString stringForSpeed: [self uploadRate]]];
988    }
989   
990    return string;
991}
992
993- (NSString *) shortStatusString
994{
995    NSString * string;
996   
997    switch (fStat->status)
998    {
999        case TR_STATUS_STOPPED:
1000            if (fWaitToStart)
1001            {
1002                string = ![self allDownloaded]
1003                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
1004                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1005            }
1006            else if (fFinishedSeeding)
1007                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1008            else
1009                string = NSLocalizedString(@"Paused", "Torrent -> status string");
1010            break;
1011
1012        case TR_STATUS_CHECK_WAIT:
1013            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1014            break;
1015
1016        case TR_STATUS_CHECK:
1017            string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1018                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
1019            break;
1020       
1021        case TR_STATUS_DOWNLOAD:
1022            string = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, UL: %@", "Torrent -> status string"),
1023                            [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
1024            break;
1025       
1026        case TR_STATUS_SEED:
1027        case TR_STATUS_DONE:
1028            string = [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, UL: %@", "Torrent -> status string"),
1029                            [NSString stringForRatio: [self ratio]], [NSString stringForSpeed: [self uploadRate]]];
1030            break;
1031       
1032        default:
1033            string = @"";
1034    }
1035   
1036    return string;
1037}
1038
1039- (NSString *) remainingTimeString
1040{
1041    switch (fStat->status)
1042    {
1043        case TR_STATUS_DOWNLOAD:
1044            return [self eta] >= 0 ? [self etaString: [self eta]] : NSLocalizedString(@"Unknown", "Torrent -> remaining time");
1045       
1046        case TR_STATUS_SEED:
1047        case TR_STATUS_DONE:
1048            return [NSLocalizedString(@"Ratio: ", "Torrent -> status string") stringByAppendingString:
1049                                                                            [NSString stringForRatio: [self ratio]]];
1050       
1051        default:
1052            return [self shortStatusString];
1053    }
1054}
1055
1056- (NSString *) stateString
1057{
1058    switch (fStat->status)
1059    {
1060        case TR_STATUS_STOPPED:
1061            return NSLocalizedString(@"Paused", "Torrent -> status string");
1062
1063        case TR_STATUS_CHECK:
1064            return [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1065                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
1066       
1067        case TR_STATUS_CHECK_WAIT:
1068            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1069
1070        case TR_STATUS_DOWNLOAD:
1071            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1072
1073        case TR_STATUS_SEED:
1074        case TR_STATUS_DONE:
1075            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1076       
1077        default:
1078            return NSLocalizedString(@"N/A", "Torrent -> status string");
1079    }
1080}
1081
1082- (int) seeders
1083{
1084    return fStat->seeders;
1085}
1086
1087- (int) leechers
1088{
1089    return fStat->leechers;
1090}
1091
1092- (int) completedFromTracker
1093{
1094    return fStat->completedFromTracker;
1095}
1096
1097- (int) totalPeersConnected
1098{
1099    return fStat->peersConnected;
1100}
1101
1102- (int) totalPeersTracker
1103{
1104    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1105}
1106
1107- (int) totalPeersIncoming
1108{
1109    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1110}
1111
1112- (int) totalPeersCache
1113{
1114    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1115}
1116
1117- (int) totalPeersPex
1118{
1119    return fStat->peersFrom[TR_PEER_FROM_PEX];
1120}
1121
1122- (int) totalPeersKnown
1123{
1124    return fStat->peersKnown;
1125}
1126
1127- (int) peersSendingToUs
1128{
1129    return fStat->peersSendingToUs;
1130}
1131
1132- (int) peersGettingFromUs
1133{
1134    return fStat->peersGettingFromUs;
1135}
1136
1137- (float) downloadRate
1138{
1139    return fStat->rateDownload;
1140}
1141
1142- (float) uploadRate
1143{
1144    return fStat->rateUpload;
1145}
1146
1147- (float) totalRate
1148{
1149    return [self downloadRate] + [self uploadRate];
1150}
1151
1152- (uint64_t) haveVerified
1153{
1154    return fStat->haveValid;
1155}
1156
1157- (uint64_t) haveTotal
1158{
1159    return [self haveVerified] + fStat->haveUnchecked;
1160}
1161
1162- (uint64_t) downloadedTotal
1163{
1164    return fStat->downloadedEver;
1165}
1166
1167- (uint64_t) uploadedTotal
1168{
1169    return fStat->uploadedEver;
1170}
1171
1172- (uint64_t) failedHash
1173{
1174    return fStat->corruptEver;
1175}
1176
1177- (float) swarmSpeed
1178{
1179    return fStat->swarmspeed;
1180}
1181
1182- (BOOL) pex
1183{
1184        return tr_torrentIsPexEnabled(fHandle);
1185}
1186
1187- (void) setPex: (BOOL) enable
1188{
1189        tr_torrentDisablePex(fHandle, !enable);
1190}
1191
1192- (int) orderValue
1193{
1194    return fOrderValue;
1195}
1196
1197- (void) setOrderValue: (int) orderValue
1198{
1199    fOrderValue = orderValue;
1200}
1201
1202- (int) groupValue
1203{
1204    return fGroupValue;
1205}
1206
1207- (void) setGroupValue: (int) goupValue
1208{
1209    fGroupValue = goupValue;
1210}
1211
1212- (int) groupOrderValue
1213{
1214    return [[GroupsWindowController groupsController] orderValueForIndex: fGroupValue];
1215}
1216
1217- (void) checkGroupValueForRemoval: (NSNotification *) notification
1218{
1219    if (fGroupValue != -1 && [[[notification userInfo] objectForKey: @"Indexes"] containsIndex: fGroupValue])
1220        fGroupValue = -1;
1221}
1222
1223- (NSArray *) fileList
1224{
1225    return fFileList;
1226}
1227
1228- (int) fileCount
1229{
1230    return fInfo->fileCount;
1231}
1232
1233- (void) updateFileStat
1234{
1235    if (fileStat)
1236        tr_torrentFilesFree(fileStat, [self fileCount]);
1237   
1238    int count;
1239    fileStat = tr_torrentFiles(fHandle, &count);
1240}
1241
1242- (float) fileProgress: (int) index
1243{
1244    if (!fileStat)
1245        [self updateFileStat];
1246       
1247    return fileStat[index].progress;
1248}
1249
1250- (BOOL) canChangeDownloadCheckForFile: (int) index
1251{
1252    if (!fileStat)
1253        [self updateFileStat];
1254   
1255    return [self fileCount] > 1 && fileStat[index].progress < 1.0;
1256}
1257
1258- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1259{
1260    if ([self fileCount] <= 1 || [self isComplete])
1261        return NO;
1262   
1263    if (!fileStat)
1264        [self updateFileStat];
1265   
1266    int index;
1267    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1268        if (fileStat[index].progress < 1.0)
1269            return YES;
1270    return NO;
1271}
1272
1273- (int) checkForFiles: (NSIndexSet *) indexSet
1274{
1275    BOOL onState = NO, offState = NO;
1276    int index;
1277    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1278    {
1279        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1280            onState = YES;
1281        else
1282            offState = YES;
1283       
1284        if (onState && offState)
1285            return NSMixedState;
1286    }
1287    return onState ? NSOnState : NSOffState;
1288}
1289
1290- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
1291{
1292    int count = [indexSet count], i = 0, index;
1293    int * files = malloc(count * sizeof(int));
1294    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1295    {
1296        files[i] = index;
1297        i++;
1298    }
1299   
1300    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1301    free(files);
1302   
1303    [self update];
1304}
1305
1306- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1307{
1308    int count = [indexSet count], i = 0, index;
1309    int * files = malloc(count * sizeof(int));
1310    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1311    {
1312        files[i] = index;
1313        i++;
1314    }
1315   
1316    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1317    free(files);
1318}
1319
1320- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1321{
1322    int index;
1323    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1324        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1325            return YES;
1326    return NO;
1327}
1328
1329- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1330{
1331    BOOL low = NO, normal = NO, high = NO;
1332    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1333   
1334    int index, priority;
1335    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1336    {
1337        if (![self canChangeDownloadCheckForFile: index])
1338            continue;
1339       
1340        priority = tr_torrentGetFilePriority(fHandle, index);
1341        if (priority == TR_PRI_LOW)
1342        {
1343            if (low)
1344                continue;
1345            low = YES;
1346        }
1347        else if (priority == TR_PRI_HIGH)
1348        {
1349            if (high)
1350                continue;
1351            high = YES;
1352        }
1353        else
1354        {
1355            if (normal)
1356                continue;
1357            normal = YES;
1358        }
1359       
1360        [priorities addObject: [NSNumber numberWithInt: priority]];
1361        if (low && normal && high)
1362            break;
1363    }
1364    return priorities;
1365}
1366
1367- (NSMenu *) fileMenu
1368{
1369    if (!fFileMenu)
1370    {
1371        fFileMenu = [[NSMenu alloc] initWithTitle: [@"TorrentMenu:" stringByAppendingString: [self name]]];
1372        [fFileMenu setAutoenablesItems: NO];
1373    }
1374    return fFileMenu;
1375}
1376
1377- (NSDate *) dateAdded
1378{
1379    return fDateAdded;
1380}
1381
1382- (NSDate *) dateCompleted
1383{
1384    return fDateCompleted;
1385}
1386
1387- (NSDate *) dateActivity
1388{
1389    uint64_t date = fStat->activityDate;
1390    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1391}
1392
1393- (NSDate *) dateActivityOrAdd
1394{
1395    NSDate * date = [self dateActivity];
1396    return date ? date : [self dateAdded];
1397}
1398
1399- (int) stalledMinutes
1400{
1401    uint64_t start;
1402    if ((start = fStat->startDate) == 0)
1403        return -1;
1404   
1405    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1406            * activity = [self dateActivity];
1407   
1408    NSDate * laterDate = (!activity || [started compare: activity] == NSOrderedDescending) ? started : activity;
1409    return -1 * [laterDate timeIntervalSinceNow] / 60;
1410}
1411
1412- (BOOL) isStalled
1413{
1414    return fStalled;
1415}
1416
1417- (NSNumber *) stateSortKey
1418{
1419    if (![self isActive])
1420        return [NSNumber numberWithInt: 0];
1421    else if ([self isSeeding])
1422        return [NSNumber numberWithInt: 1];
1423    else
1424        return [NSNumber numberWithInt: 2];
1425}
1426
1427- (int) torrentID
1428{
1429    return fID;
1430}
1431
1432- (const tr_info *) torrentInfo
1433{
1434    return fInfo;
1435}
1436
1437- (const tr_stat *) torrentStat
1438{
1439    return fStat;
1440}
1441
1442@end
1443
1444@implementation Torrent (Private)
1445
1446//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1447- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle *) lib
1448        publicTorrent: (NSNumber *) publicTorrent
1449        downloadFolder: (NSString *) downloadFolder
1450        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1451        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
1452        dateActivity: (NSDate *) dateActivity
1453        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
1454        waitToStart: (NSNumber *) waitToStart
1455        orderValue: (NSNumber *) orderValue groupValue: (NSNumber *) groupValue;
1456{
1457    if (!(self = [super init]))
1458        return nil;
1459   
1460    static_lastid++;
1461    fID = static_lastid;
1462   
1463    fLib = lib;
1464    fDefaults = [NSUserDefaults standardUserDefaults];
1465
1466    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1467    if (fPublicTorrent)
1468        fPublicTorrentLocation = [path retain];
1469   
1470    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1471    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1472   
1473    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1474                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1475    if (fUseIncompleteFolder)
1476    {
1477        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1478        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1479    }
1480   
1481    //set libtransmission settings for initialization
1482    tr_ctor * ctor = tr_ctorNew(fLib);
1483    tr_ctorSetPaused(ctor, TR_FORCE, YES);
1484    tr_ctorSetMaxConnectedPeers(ctor, TR_FALLBACK, [fDefaults integerForKey: @"PeersTorrent"]);
1485   
1486    tr_info info;
1487    int error;
1488    if (hashString)
1489    {
1490        tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1491        if (tr_torrentParse(fLib, ctor, &info) == TR_OK)
1492        {
1493            NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1494                                                ? fIncompleteFolder : fDownloadFolder;
1495            tr_ctorSetDestination(ctor, TR_FORCE, [currentDownloadFolder UTF8String]);
1496           
1497            fHandle = tr_torrentNew(fLib, ctor, &error);
1498        }
1499        tr_metainfoFree(&info);
1500    }
1501    if (!fHandle && path)
1502    {
1503        tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1504        if (tr_torrentParse(fLib, ctor, &info) == TR_OK)
1505        {
1506            NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1507                                                ? fIncompleteFolder : fDownloadFolder;
1508            tr_ctorSetDestination(ctor, TR_FORCE, [currentDownloadFolder UTF8String]);
1509           
1510            fHandle = tr_torrentNew(fLib, ctor, &error);
1511        }
1512        tr_metainfoFree(&info);
1513    }
1514    tr_ctorFree(ctor);
1515   
1516    if (!fHandle)
1517    {
1518        [self release];
1519        return nil;
1520    }
1521   
1522    tr_torrentSetStatusCallback(fHandle, completenessChangeCallback, self);
1523   
1524    fInfo = tr_torrentInfo(fHandle);
1525
1526    fDateAdded = dateAdded ? [dateAdded retain] : [[NSDate alloc] init];
1527        if (dateCompleted)
1528                fDateCompleted = [dateCompleted retain];
1529    if (dateActivity)
1530                fDateActivity = [dateActivity retain];
1531       
1532    fRatioSetting = ratioSetting ? [ratioSetting intValue] : NSMixedState;
1533    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1534    fFinishedSeeding = NO;
1535   
1536    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1537   
1538    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1539    fGroupValue = groupValue ? [groupValue intValue] : -1;;
1540   
1541    fError = NO;
1542   
1543    [self createFileList];
1544   
1545    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1546        name: @"GroupValueRemoved" object: nil];
1547   
1548    [self update];
1549   
1550    //mark incomplete files to be ignored by Time Machine
1551    if ([NSApp isOnLeopardOrBetter])
1552    {
1553        NSURL *url = [NSURL fileURLWithPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1554        fNeedSetTimeMachine = CSBackupSetItemExcluded((CFURLRef)url, ![self allDownloaded], false) != noErr;
1555    }
1556    else
1557        fNeedSetTimeMachine = NO;
1558   
1559    return self;
1560}
1561
1562- (void) createFileList
1563{
1564    int count = [self fileCount], i;
1565    tr_file * file;
1566    NSMutableArray * pathComponents;
1567    NSString * path;
1568   
1569    NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count];
1570   
1571    for (i = 0; i < count; i++)
1572    {
1573        file = &fInfo->files[i];
1574       
1575        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1576        if ([self folder])
1577        {
1578            path = [pathComponents objectAtIndex: 0];
1579            [pathComponents removeObjectAtIndex: 0];
1580        }
1581        else
1582            path = @"";
1583       
1584        [self insertPath: pathComponents forSiblings: fileList previousPath: path fileSize: file->length index: i];
1585        [pathComponents release];
1586    }
1587   
1588    fFileList = [[NSArray alloc] initWithArray: fileList];
1589    [fileList release];
1590}
1591
1592- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings previousPath: (NSString *) previousPath
1593            fileSize: (uint64_t) size index: (int) index
1594{
1595    NSString * name = [components objectAtIndex: 0];
1596    BOOL isFolder = [components count] > 1;
1597   
1598    NSMutableDictionary * dict = nil;
1599    if (isFolder)
1600    {
1601        NSEnumerator * enumerator = [siblings objectEnumerator];
1602        while ((dict = [enumerator nextObject]))
1603            if ([[dict objectForKey: @"Name"] isEqualToString: name] && [[dict objectForKey: @"IsFolder"] boolValue])
1604                break;
1605    }
1606   
1607    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1608   
1609    //create new folder or item if it doesn't already exist
1610    if (!dict)
1611    {
1612        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1613                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1614        [siblings addObject: dict];
1615       
1616        if (isFolder)
1617        {
1618            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1619            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1620        }
1621        else
1622        {
1623            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1624            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1625           
1626            NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
1627            [icon setFlipped: YES];
1628            [dict setObject: icon forKey: @"Icon"];
1629        }
1630    }
1631    else
1632        [[dict objectForKey: @"Indexes"] addIndex: index];
1633   
1634    if (isFolder)
1635    {
1636        [components removeObjectAtIndex: 0];
1637        [self insertPath: components forSiblings: [dict objectForKey: @"Children"] previousPath: currentPath fileSize: size
1638                index: index];
1639    }
1640}
1641
1642- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1643{
1644    return fUseIncompleteFolder &&
1645        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1646}
1647
1648- (void) updateDownloadFolder
1649{
1650    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1651    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1652}
1653
1654//status has been retained
1655- (void) completenessChange: (NSNumber *) status
1656{
1657    [self update];
1658   
1659    BOOL canMove;
1660    switch ([status intValue])
1661    {
1662        case TR_CP_DONE:
1663        case TR_CP_COMPLETE:
1664            canMove = YES;
1665       
1666            //move file from incomplete folder to download folder
1667            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1668                && (canMove = [self alertForMoveFolderAvailable]))
1669            {
1670                [self quickPause];
1671               
1672                if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1673                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
1674                    [self updateDownloadFolder];
1675                else
1676                    canMove = NO;
1677               
1678                [self endQuickPause];
1679            }
1680           
1681            if (!canMove)
1682            {
1683                fUseIncompleteFolder = NO;
1684               
1685                [fDownloadFolder release];
1686                fDownloadFolder = fIncompleteFolder;
1687                fIncompleteFolder = nil;
1688            }
1689           
1690            [fDateCompleted release];
1691            fDateCompleted = [[NSDate alloc] init];
1692           
1693            //allow to be backed up by Time Machine
1694            if ([NSApp isOnLeopardOrBetter])
1695            {
1696                NSURL *url = [NSURL fileURLWithPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1697                fNeedSetTimeMachine = CSBackupSetItemExcluded((CFURLRef)url, false, false) != noErr;
1698            }
1699           
1700            fStat = tr_torrentStat(fHandle);
1701            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1702            break;
1703       
1704        case TR_CP_INCOMPLETE:
1705            //do not allow to be backed up by Time Machine
1706            if ([NSApp isOnLeopardOrBetter])
1707            {
1708                NSURL *url = [NSURL fileURLWithPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1709                fNeedSetTimeMachine = CSBackupSetItemExcluded((CFURLRef)url, true, false) != noErr;
1710            }
1711           
1712            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1713            break;
1714    }
1715    [status release];
1716}
1717
1718- (void) quickPause
1719{
1720    if (fQuickPauseDict)
1721        return;
1722
1723    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1724                    [NSNumber numberWithInt: [self speedMode: YES]], @"UploadSpeedMode",
1725                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1726                    [NSNumber numberWithInt: [self speedMode: NO]], @"DownloadSpeedMode",
1727                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1728   
1729    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: YES];
1730    [self setSpeedLimit: 0 upload: YES];
1731    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: NO];
1732    [self setSpeedLimit: 0 upload: NO];
1733}
1734
1735- (void) endQuickPause
1736{
1737    if (!fQuickPauseDict)
1738        return;
1739   
1740    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"UploadSpeedMode"] intValue] upload: YES];
1741    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1742    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"DownloadSpeedMode"] intValue] upload: NO];
1743    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1744   
1745    [fQuickPauseDict release];
1746    fQuickPauseDict = nil;
1747}
1748
1749- (void) trashFile: (NSString *) path
1750{
1751    //attempt to move to trash
1752    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1753            source: [path stringByDeletingLastPathComponent] destination: @""
1754            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1755    {
1756        //if cannot trash, just delete it (will work if it is on a remote volume)
1757        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1758            NSLog(@"Could not trash %@", path);
1759    }
1760}
1761
1762@end
Note: See TracBrowser for help on using the repository browser.