source: trunk/macosx/Torrent.m @ 6325

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

#1042: show "in progress" in the inspector when announcing/scraping

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