source: trunk/macosx/Torrent.m @ 5149

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

show amount selected in open window

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