source: trunk/macosx/Torrent.m @ 5309

Last change on this file since 5309 was 5309, checked in by livings124, 14 years ago

#799 treat "waiting to check..." as paused

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