source: trunk/macosx/Torrent.m @ 3535

Last change on this file since 3535 was 3535, checked in by livings124, 15 years ago

display all trackers in the announce list in the inspector's tooltip

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