source: trunk/macosx/Torrent.m @ 5282

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

speed up check for enough remaining space by first determining if there is enough space to download what's needed ignoring preallocation

  • Property svn:keywords set to Date Rev Author Id
File size: 58.4 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 5282 2008-03-18 03:20:27Z 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) allDownloaded
899{
900    return [self progressDone] >= 1.0;
901}
902
903- (BOOL) isComplete
904{
905    return [self progress] >= 1.0;
906}
907
908- (BOOL) isError
909{
910    return fStat->error != 0;
911}
912
913- (NSString *) errorMessage
914{
915    if (![self isError])
916        return @"";
917   
918    NSString * error;
919    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
920        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
921        error = NSLocalizedString(@"(unreadable error)", "Torrent -> error string unreadable");
922   
923    return error;
924}
925
926- (NSArray *) peers
927{
928    int totalPeers, i;
929    tr_peer_stat * peers = tr_torrentPeers(fHandle, &totalPeers);
930   
931    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
932    NSMutableDictionary * dic;
933   
934    tr_peer_stat * peer;
935    for (i = 0; i < totalPeers; i++)
936    {
937        peer = &peers[i];
938       
939        dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
940            [NSNumber numberWithInt: peer->from], @"From",
941            [NSString stringWithUTF8String: peer->addr], @"IP",
942            [NSNumber numberWithInt: peer->port], @"Port",
943            [NSNumber numberWithFloat: peer->progress], @"Progress",
944            [NSNumber numberWithBool: peer->isEncrypted], @"Encryption",
945            [NSString stringWithUTF8String: peer->client], @"Client",
946            [NSString stringWithUTF8String: peer->flagStr], @"Flags", nil];
947       
948        if (peer->isUploadingTo)
949            [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
950        if (peer->isDownloadingFrom)
951            [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
952       
953        [peerDics addObject: dic];
954    }
955   
956    tr_torrentPeersFree(peers, totalPeers);
957   
958    return peerDics;
959}
960
961- (NSString *) progressString
962{
963    NSString * string;
964   
965    if (![self allDownloaded])
966    {
967        if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
968        {
969            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ selected (%.2f%%)", "Torrent -> progress string"),
970                            [NSString stringForFileSize: [self haveTotal]], [NSString stringForFileSize: [self totalSizeSelected]],
971                            100.0 * [self progressDone]];
972        }
973        else
974            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ (%.2f%%)", "Torrent -> progress string"),
975                            [NSString stringForFileSize: [self haveTotal]],
976                            [NSString stringForFileSize: [self size]], 100.0 * [self progress]];
977    }
978    else if (![self isComplete])
979    {
980        if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
981            string = [NSString stringWithFormat: NSLocalizedString(@"%@ selected, uploaded %@ (Ratio: %@)",
982                "Torrent -> progress string"), [NSString stringForFileSize: [self haveTotal]],
983                [NSString stringForFileSize: [self uploadedTotal]], [NSString stringForRatio: [self ratio]]];
984        else
985            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ (%.2f%%), uploaded %@ (Ratio: %@)",
986                "Torrent -> progress string"), [NSString stringForFileSize: [self haveTotal]],
987                [NSString stringForFileSize: [self size]], 100.0 * [self progress],
988                [NSString stringForFileSize: [self uploadedTotal]], [NSString stringForRatio: [self ratio]]];
989    }
990    else
991        string = [NSString stringWithFormat: NSLocalizedString(@"%@, uploaded %@ (Ratio: %@)", "Torrent -> progress string"),
992                [NSString stringForFileSize: [self size]], [NSString stringForFileSize: [self uploadedTotal]],
993                [NSString stringForRatio: [self ratio]]];
994   
995    //add time when downloading
996    if (fStat->status == TR_STATUS_DOWNLOAD || ([self isSeeding]
997        && (fRatioSetting == NSOnState || (fRatioSetting == NSMixedState && [fDefaults boolForKey: @"RatioCheck"]))))
998    {
999        int eta = fStat->status == TR_STATUS_DOWNLOAD ? [self eta] : [self etaRatio];
1000        string = eta >= 0 ? [string stringByAppendingFormat: NSLocalizedString(@" - %@ remaining", "Torrent -> progress string"),
1001                                [NSString timeString: eta showSeconds: YES maxDigits: 2]]
1002            : [string stringByAppendingString: NSLocalizedString(@" - remaining time unknown", "Torrent -> progress string")];
1003    }
1004   
1005    return string;
1006}
1007
1008- (NSString *) statusString
1009{
1010    NSString * string;
1011   
1012    if ([self isError])
1013    {
1014        string = NSLocalizedString(@"Error", "Torrent -> status string");
1015        NSString * errorString = [self errorMessage];
1016        if (errorString && ![errorString isEqualToString: @""])
1017            string = [NSString stringWithFormat: @"%@: %@", string, errorString];
1018    }
1019    else
1020    {
1021        switch (fStat->status)
1022        {
1023            case TR_STATUS_STOPPED:
1024                if (fWaitToStart)
1025                {
1026                    string = ![self allDownloaded]
1027                            ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
1028                            : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1029                }
1030                else if (fFinishedSeeding)
1031                    string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1032                else
1033                    string = NSLocalizedString(@"Paused", "Torrent -> status string");
1034                break;
1035
1036            case TR_STATUS_CHECK_WAIT:
1037                string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1038                break;
1039
1040            case TR_STATUS_CHECK:
1041                string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1042                                        "Torrent -> status string"), 100.0 * [self checkingProgress]];
1043                break;
1044
1045            case TR_STATUS_DOWNLOAD:
1046                if ([self totalPeersConnected] != 1)
1047                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
1048                                                    "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
1049                else
1050                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
1051                                                    "Torrent -> status string"), [self peersSendingToUs]];
1052                break;
1053
1054            case TR_STATUS_SEED:
1055            case TR_STATUS_DONE:
1056                if ([self totalPeersConnected] != 1)
1057                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
1058                                                    [self peersGettingFromUs], [self totalPeersConnected]];
1059                else
1060                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
1061                                                    [self peersGettingFromUs]];
1062                break;
1063           
1064            default:
1065                string = @"";
1066        }
1067       
1068        if (fStalled)
1069            string = [NSLocalizedString(@"Stalled, ", "Torrent -> status string") stringByAppendingString: string];
1070    }
1071   
1072    //append even if error
1073    if ([self isActive] && ![self isChecking])
1074    {
1075        if (fStat->status == TR_STATUS_DOWNLOAD)
1076            string = [string stringByAppendingFormat: NSLocalizedString(@" - DL: %@, UL: %@", "Torrent -> status string"),
1077                    [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
1078        else
1079            string = [string stringByAppendingFormat: NSLocalizedString(@" - UL: %@", "Torrent -> status string"),
1080                        [NSString stringForSpeed: [self uploadRate]]];
1081    }
1082   
1083    return string;
1084}
1085
1086- (NSString *) shortStatusString
1087{
1088    NSString * string;
1089   
1090    switch (fStat->status)
1091    {
1092        case TR_STATUS_STOPPED:
1093            if (fWaitToStart)
1094            {
1095                string = ![self allDownloaded]
1096                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
1097                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1098            }
1099            else if (fFinishedSeeding)
1100                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1101            else
1102                string = NSLocalizedString(@"Paused", "Torrent -> status string");
1103            break;
1104
1105        case TR_STATUS_CHECK_WAIT:
1106            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1107            break;
1108
1109        case TR_STATUS_CHECK:
1110            string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1111                                    "Torrent -> status string"), 100.0 * [self checkingProgress]];
1112            break;
1113       
1114        case TR_STATUS_DOWNLOAD:
1115            string = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, UL: %@", "Torrent -> status string"),
1116                            [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
1117            break;
1118       
1119        case TR_STATUS_SEED:
1120        case TR_STATUS_DONE:
1121            string = [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, UL: %@", "Torrent -> status string"),
1122                            [NSString stringForRatio: [self ratio]], [NSString stringForSpeed: [self uploadRate]]];
1123            break;
1124       
1125        default:
1126            string = @"";
1127    }
1128   
1129    return string;
1130}
1131
1132- (NSString *) remainingTimeString
1133{
1134    if (![self isActive] || ([self isSeeding]
1135        && !(fRatioSetting == NSOnState || (fRatioSetting == NSMixedState && [fDefaults boolForKey: @"RatioCheck"]))))
1136        return [self shortStatusString];
1137   
1138    int eta = [self isSeeding] ? [self etaRatio] : [self eta];
1139    return eta >= 0 ? [NSString timeString: eta showSeconds: YES maxDigits: 2]
1140                    : NSLocalizedString(@"Unknown", "Torrent -> remaining time");
1141}
1142
1143- (NSString *) stateString
1144{
1145    switch (fStat->status)
1146    {
1147        case TR_STATUS_STOPPED:
1148            return NSLocalizedString(@"Paused", "Torrent -> status string");
1149
1150        case TR_STATUS_CHECK:
1151            return [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1152                                    "Torrent -> status string"), 100.0 * [self checkingProgress]];
1153       
1154        case TR_STATUS_CHECK_WAIT:
1155            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1156
1157        case TR_STATUS_DOWNLOAD:
1158            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1159
1160        case TR_STATUS_SEED:
1161        case TR_STATUS_DONE:
1162            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1163       
1164        default:
1165            return NSLocalizedString(@"N/A", "Torrent -> status string");
1166    }
1167}
1168
1169- (int) seeders
1170{
1171    return fStat->seeders;
1172}
1173
1174- (int) leechers
1175{
1176    return fStat->leechers;
1177}
1178
1179- (int) completedFromTracker
1180{
1181    return fStat->completedFromTracker;
1182}
1183
1184- (int) totalPeersConnected
1185{
1186    return fStat->peersConnected;
1187}
1188
1189- (int) totalPeersTracker
1190{
1191    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1192}
1193
1194- (int) totalPeersIncoming
1195{
1196    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1197}
1198
1199- (int) totalPeersCache
1200{
1201    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1202}
1203
1204- (int) totalPeersPex
1205{
1206    return fStat->peersFrom[TR_PEER_FROM_PEX];
1207}
1208
1209- (int) totalPeersKnown
1210{
1211    return fStat->peersKnown;
1212}
1213
1214- (int) peersSendingToUs
1215{
1216    return fStat->peersSendingToUs;
1217}
1218
1219- (int) peersGettingFromUs
1220{
1221    return fStat->peersGettingFromUs;
1222}
1223
1224- (float) downloadRate
1225{
1226    return fStat->rateDownload;
1227}
1228
1229- (float) uploadRate
1230{
1231    return fStat->rateUpload;
1232}
1233
1234- (float) totalRate
1235{
1236    return [self downloadRate] + [self uploadRate];
1237}
1238
1239- (uint64_t) haveVerified
1240{
1241    return fStat->haveValid;
1242}
1243
1244- (uint64_t) haveTotal
1245{
1246    return [self haveVerified] + fStat->haveUnchecked;
1247}
1248
1249- (uint64_t) totalSizeSelected
1250{
1251    return [self haveTotal] + [self sizeLeft];
1252}
1253
1254- (uint64_t) downloadedTotal
1255{
1256    return fStat->downloadedEver;
1257}
1258
1259- (uint64_t) uploadedTotal
1260{
1261    return fStat->uploadedEver;
1262}
1263
1264- (uint64_t) failedHash
1265{
1266    return fStat->corruptEver;
1267}
1268
1269- (float) swarmSpeed
1270{
1271    return fStat->swarmspeed;
1272}
1273
1274- (int) orderValue
1275{
1276    return fOrderValue;
1277}
1278
1279- (void) setOrderValue: (int) orderValue
1280{
1281    fOrderValue = orderValue;
1282}
1283
1284- (int) groupValue
1285{
1286    return fGroupValue;
1287}
1288
1289- (void) setGroupValue: (int) goupValue
1290{
1291    fGroupValue = goupValue;
1292}
1293
1294- (int) groupOrderValue
1295{
1296    return [[GroupsWindowController groups] orderValueForIndex: fGroupValue];
1297}
1298
1299- (void) checkGroupValueForRemoval: (NSNotification *) notification
1300{
1301    if (fGroupValue != -1 && [[[notification userInfo] objectForKey: @"Indexes"] containsIndex: fGroupValue])
1302        fGroupValue = -1;
1303}
1304
1305- (NSArray *) fileList
1306{
1307    return fFileList;
1308}
1309
1310- (int) fileCount
1311{
1312    return fInfo->fileCount;
1313}
1314
1315- (void) updateFileStat
1316{
1317    if (fFileStat)
1318        tr_torrentFilesFree(fFileStat, [self fileCount]);
1319   
1320    int count;
1321    fFileStat = tr_torrentFiles(fHandle, &count);
1322}
1323
1324- (float) fileProgress: (int) index
1325{
1326    if (!fFileStat)
1327        [self updateFileStat];
1328       
1329    return fFileStat[index].progress;
1330}
1331
1332- (BOOL) canChangeDownloadCheckForFile: (int) index
1333{
1334    if (!fFileStat)
1335        [self updateFileStat];
1336   
1337    return [self fileCount] > 1 && fFileStat[index].progress < 1.0;
1338}
1339
1340- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1341{
1342    if ([self fileCount] <= 1 || [self isComplete])
1343        return NO;
1344   
1345    if (!fFileStat)
1346        [self updateFileStat];
1347   
1348    int index;
1349    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1350        if (fFileStat[index].progress < 1.0)
1351            return YES;
1352    return NO;
1353}
1354
1355- (int) checkForFiles: (NSIndexSet *) indexSet
1356{
1357    BOOL onState = NO, offState = NO;
1358    int index;
1359    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1360    {
1361        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1362            onState = YES;
1363        else
1364            offState = YES;
1365       
1366        if (onState && offState)
1367            return NSMixedState;
1368    }
1369    return onState ? NSOnState : NSOffState;
1370}
1371
1372- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
1373{
1374    int count = [indexSet count], i = 0, index;
1375    int * files = malloc(count * sizeof(int));
1376    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1377    {
1378        files[i] = index;
1379        i++;
1380    }
1381   
1382    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1383    free(files);
1384   
1385    [self update];
1386    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
1387}
1388
1389- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1390{
1391    int count = [indexSet count], i = 0, index;
1392    int * files = malloc(count * sizeof(int));
1393    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1394    {
1395        files[i] = index;
1396        i++;
1397    }
1398   
1399    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1400    free(files);
1401}
1402
1403- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1404{
1405    int index;
1406    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1407        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1408            return YES;
1409    return NO;
1410}
1411
1412- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1413{
1414    BOOL low = NO, normal = NO, high = NO;
1415    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1416   
1417    int index, priority;
1418    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1419    {
1420        if (![self canChangeDownloadCheckForFile: index])
1421            continue;
1422       
1423        priority = tr_torrentGetFilePriority(fHandle, index);
1424        if (priority == TR_PRI_LOW)
1425        {
1426            if (low)
1427                continue;
1428            low = YES;
1429        }
1430        else if (priority == TR_PRI_HIGH)
1431        {
1432            if (high)
1433                continue;
1434            high = YES;
1435        }
1436        else
1437        {
1438            if (normal)
1439                continue;
1440            normal = YES;
1441        }
1442       
1443        [priorities addObject: [NSNumber numberWithInt: priority]];
1444        if (low && normal && high)
1445            break;
1446    }
1447    return priorities;
1448}
1449
1450- (NSDate *) dateAdded
1451{
1452    return fDateAdded;
1453}
1454
1455- (NSDate *) dateCompleted
1456{
1457    return fDateCompleted;
1458}
1459
1460- (NSDate *) dateActivity
1461{
1462    uint64_t date = fStat->activityDate;
1463    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1464}
1465
1466- (NSDate *) dateActivityOrAdd
1467{
1468    NSDate * date = [self dateActivity];
1469    return date ? date : [self dateAdded];
1470}
1471
1472- (int) stalledMinutes
1473{
1474    uint64_t start;
1475    if ((start = fStat->startDate) == 0)
1476        return -1;
1477   
1478    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1479            * activity = [self dateActivity];
1480   
1481    NSDate * laterDate = activity ? [started laterDate: activity] : started;
1482    return -1 * [laterDate timeIntervalSinceNow] / 60;
1483}
1484
1485- (BOOL) isStalled
1486{
1487    return fStalled;
1488}
1489
1490- (NSNumber *) stateSortKey
1491{
1492    if (![self isActive])
1493        return [NSNumber numberWithInt: 0];
1494    else if ([self isSeeding])
1495        return [NSNumber numberWithInt: 1];
1496    else
1497        return [NSNumber numberWithInt: 2];
1498}
1499
1500- (int) torrentID
1501{
1502    return fID;
1503}
1504
1505- (const tr_info *) torrentInfo
1506{
1507    return fInfo;
1508}
1509
1510- (const tr_stat *) torrentStat
1511{
1512    return fStat;
1513}
1514
1515@end
1516
1517@implementation Torrent (Private)
1518
1519//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1520- (id) initWithHash: (NSString *) hashString path: (NSString *) path data: (NSData *) data lib: (tr_handle *) lib
1521        publicTorrent: (NSNumber *) publicTorrent
1522        downloadFolder: (NSString *) downloadFolder
1523        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1524        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
1525        dateActivity: (NSDate *) dateActivity
1526        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
1527        waitToStart: (NSNumber *) waitToStart
1528        orderValue: (NSNumber *) orderValue groupValue: (NSNumber *) groupValue;
1529{
1530    if (!(self = [super init]))
1531        return nil;
1532   
1533    static_lastid++;
1534    fID = static_lastid;
1535   
1536    fLib = lib;
1537    fDefaults = [NSUserDefaults standardUserDefaults];
1538
1539    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1540    if (fPublicTorrent)
1541        fPublicTorrentLocation = [path retain];
1542   
1543    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1544    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1545   
1546    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1547                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1548    if (fUseIncompleteFolder)
1549    {
1550        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1551        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1552    }
1553   
1554    //set libtransmission settings for initialization
1555    tr_ctor * ctor = tr_ctorNew(fLib);
1556    tr_ctorSetPaused(ctor, TR_FORCE, YES);
1557    tr_ctorSetMaxConnectedPeers(ctor, TR_FALLBACK, [fDefaults integerForKey: @"PeersTorrent"]);
1558   
1559    tr_info info;
1560    int error;
1561    if (hashString)
1562    {
1563        tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1564        if (tr_torrentParse(fLib, ctor, &info) == TR_OK)
1565        {
1566            NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1567                                                ? fIncompleteFolder : fDownloadFolder;
1568            tr_ctorSetDestination(ctor, TR_FORCE, [currentDownloadFolder UTF8String]);
1569           
1570            fHandle = tr_torrentNew(fLib, ctor, &error);
1571        }
1572        tr_metainfoFree(&info);
1573    }
1574    if (!fHandle && path)
1575    {
1576        tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1577        if (tr_torrentParse(fLib, ctor, &info) == TR_OK)
1578        {
1579            NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1580                                                ? fIncompleteFolder : fDownloadFolder;
1581            tr_ctorSetDestination(ctor, TR_FORCE, [currentDownloadFolder UTF8String]);
1582           
1583            fHandle = tr_torrentNew(fLib, ctor, &error);
1584        }
1585        tr_metainfoFree(&info);
1586    }
1587    if (!fHandle && data)
1588    {
1589        tr_ctorSetMetainfo(ctor, [data bytes], [data length]);
1590        if (tr_torrentParse(fLib, ctor, &info) == TR_OK)
1591        {
1592            NSString * currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1593                                                ? fIncompleteFolder : fDownloadFolder;
1594            tr_ctorSetDestination(ctor, TR_FORCE, [currentDownloadFolder UTF8String]);
1595           
1596            fHandle = tr_torrentNew(fLib, ctor, &error);
1597        }
1598        tr_metainfoFree(&info);
1599    }
1600   
1601    tr_ctorFree(ctor);
1602   
1603    if (!fHandle)
1604    {
1605        [self release];
1606        return nil;
1607    }
1608   
1609    tr_torrentSetStatusCallback(fHandle, completenessChangeCallback, self);
1610   
1611    fInfo = tr_torrentInfo(fHandle);
1612   
1613    fNameString = [[NSString alloc] initWithUTF8String: fInfo->name];
1614    fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
1615   
1616    fDateAdded = dateAdded ? [dateAdded retain] : [[NSDate alloc] init];
1617        if (dateCompleted)
1618                fDateCompleted = [dateCompleted retain];
1619    if (dateActivity)
1620                fDateActivity = [dateActivity retain];
1621       
1622    fRatioSetting = ratioSetting ? [ratioSetting intValue] : NSMixedState;
1623    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1624    fFinishedSeeding = NO;
1625   
1626    fWaitToStart = waitToStart && [waitToStart boolValue];
1627   
1628    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1629    fGroupValue = groupValue ? [groupValue intValue] : -1;
1630   
1631    [self createFileList];
1632   
1633    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1634        name: @"GroupValueRemoved" object: nil];
1635   
1636    [self update];
1637   
1638    //mark incomplete files to be ignored by Time Machine
1639    [self setTimeMachineExclude: ![self allDownloaded] forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1640   
1641    return self;
1642}
1643
1644- (void) createFileList
1645{
1646    int count = [self fileCount], i;
1647    tr_file * file;
1648    NSMutableArray * pathComponents;
1649    NSString * path;
1650   
1651    NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count];
1652   
1653    for (i = 0; i < count; i++)
1654    {
1655        file = &fInfo->files[i];
1656       
1657        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1658        if ([self folder])
1659        {
1660            path = [pathComponents objectAtIndex: 0];
1661            [pathComponents removeObjectAtIndex: 0];
1662        }
1663        else
1664            path = @"";
1665       
1666        [self insertPath: pathComponents forSiblings: fileList previousPath: path fileSize: file->length index: i];
1667        [pathComponents release];
1668    }
1669   
1670    fFileList = [[NSArray alloc] initWithArray: fileList];
1671    [fileList release];
1672}
1673
1674- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings previousPath: (NSString *) previousPath
1675            fileSize: (uint64_t) size index: (int) index
1676{
1677    NSString * name = [components objectAtIndex: 0];
1678    BOOL isFolder = [components count] > 1;
1679   
1680    NSMutableDictionary * dict = nil;
1681    if (isFolder)
1682    {
1683        NSEnumerator * enumerator = [siblings objectEnumerator];
1684        while ((dict = [enumerator nextObject]))
1685            if ([[dict objectForKey: @"Name"] isEqualToString: name] && [[dict objectForKey: @"IsFolder"] boolValue])
1686                break;
1687    }
1688   
1689    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1690   
1691    //create new folder or item if it doesn't already exist
1692    if (!dict)
1693    {
1694        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1695                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1696        [siblings addObject: dict];
1697       
1698        if (isFolder)
1699        {
1700            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1701            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1702        }
1703        else
1704        {
1705            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1706            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1707           
1708            NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
1709            [icon setFlipped: YES];
1710            [dict setObject: icon forKey: @"Icon"];
1711        }
1712    }
1713    else
1714        [[dict objectForKey: @"Indexes"] addIndex: index];
1715   
1716    if (isFolder)
1717    {
1718        [components removeObjectAtIndex: 0];
1719        [self insertPath: components forSiblings: [dict objectForKey: @"Children"] previousPath: currentPath fileSize: size
1720                index: index];
1721    }
1722}
1723
1724- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1725{
1726    return fUseIncompleteFolder &&
1727        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1728}
1729
1730- (void) updateDownloadFolder
1731{
1732    //remove old Time Machine location
1733    [self setTimeMachineExclude: NO forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1734   
1735    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1736    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1737   
1738    [self setTimeMachineExclude: ![self allDownloaded] forPath: [folder stringByAppendingPathComponent: [self name]]];
1739}
1740
1741//status has been retained
1742- (void) completenessChange: (NSNumber *) status
1743{
1744    [self update];
1745   
1746    BOOL canMove;
1747    switch ([status intValue])
1748    {
1749        case TR_CP_DONE:
1750        case TR_CP_COMPLETE:
1751            canMove = YES;
1752           
1753            //move file from incomplete folder to download folder
1754            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1755                && (canMove = [self alertForMoveFolderAvailable]))
1756            {
1757                [self quickPause];
1758               
1759                if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1760                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
1761                    [self updateDownloadFolder];
1762                else
1763                    canMove = NO;
1764               
1765                [self endQuickPause];
1766            }
1767           
1768            if (!canMove)
1769            {
1770                fUseIncompleteFolder = NO;
1771               
1772                [fDownloadFolder release];
1773                fDownloadFolder = fIncompleteFolder;
1774                fIncompleteFolder = nil;
1775            }
1776           
1777            [fDateCompleted release];
1778            fDateCompleted = [[NSDate alloc] init];
1779           
1780            //allow to be backed up by Time Machine
1781            [self setTimeMachineExclude: NO forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1782           
1783            fStat = tr_torrentStat(fHandle);
1784            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1785            break;
1786       
1787        case TR_CP_INCOMPLETE:
1788            //do not allow to be backed up by Time Machine
1789            [self setTimeMachineExclude: YES forPath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]];
1790           
1791            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1792            break;
1793    }
1794    [status release];
1795}
1796
1797- (void) quickPause
1798{
1799    if (fQuickPauseDict)
1800        return;
1801
1802    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1803                    [NSNumber numberWithInt: [self speedMode: YES]], @"UploadSpeedMode",
1804                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1805                    [NSNumber numberWithInt: [self speedMode: NO]], @"DownloadSpeedMode",
1806                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1807   
1808    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: YES];
1809    [self setSpeedLimit: 0 upload: YES];
1810    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: NO];
1811    [self setSpeedLimit: 0 upload: NO];
1812}
1813
1814- (void) endQuickPause
1815{
1816    if (!fQuickPauseDict)
1817        return;
1818   
1819    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"UploadSpeedMode"] intValue] upload: YES];
1820    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1821    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"DownloadSpeedMode"] intValue] upload: NO];
1822    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1823   
1824    [fQuickPauseDict release];
1825    fQuickPauseDict = nil;
1826}
1827
1828- (void) trashFile: (NSString *) path
1829{
1830    //attempt to move to trash
1831    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1832            source: [path stringByDeletingLastPathComponent] destination: @""
1833            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1834    {
1835        //if cannot trash, just delete it (will work if it is on a remote volume)
1836        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1837            NSLog(@"Could not trash %@", path);
1838    }
1839}
1840
1841- (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path
1842{
1843    if ([NSApp isOnLeopardOrBetter])
1844        CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, true);
1845}
1846
1847@end
Note: See TracBrowser for help on using the repository browser.