source: trunk/macosx/Torrent.m @ 6076

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

update the peer inspector tab to show web seeders

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