source: trunk/macosx/Torrent.m @ 7483

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

remove Mac's custom code to show a warning when a folder contains extra data when deleting (in preparation of implementing [7473])

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