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

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

(1.5x) really fix mac build

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