source: trunk/macosx/Torrent.m @ 5049

Last change on this file since 5049 was 5049, checked in by livings124, 14 years ago

in minimal mode, show eta for seeding

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