source: trunk/macosx/Torrent.m @ 2810

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

allow folders of files to be enabled/disabled from the action button

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