source: trunk/macosx/Torrent.m @ 5657

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

the last change to libT allows the use of the stat field's sizeWhenDone when displaying total size selected

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