source: trunk/macosx/Torrent.m @ 6463

Last change on this file since 6463 was 6463, checked in by livings124, 13 years ago

the web ui in 1.4 won't be beta

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