source: trunk/macosx/Torrent.m @ 6466

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

warnings--

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