source: branches/1.5x/macosx/Torrent.m @ 8481

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

(1.5x) save the torrent location for when a user upgrades to a newer version

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