source: trunk/macosx/Torrent.m @ 7440

Last change on this file since 7440 was 7440, checked in by livings124, 12 years ago

more thorough check of files in a folder when attempting to trash it

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