source: trunk/macosx/Torrent.m @ 2794

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

store only a single icon in the Torrent class instead of 2

  • Property svn:keywords set to Date Rev Author Id
File size: 51.8 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 2794 2007-08-16 04:12:43Z 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   
827    return fIcon;
828}
829
830- (NSString *) name
831{
832    return [NSString stringWithUTF8String: fInfo->name];
833}
834
835- (uint64_t) size
836{
837    return fInfo->totalSize;
838}
839
840- (NSString *) trackerAddress
841{
842    return [NSString stringWithFormat: @"http://%s:%d", fStat->tracker->address, fStat->tracker->port];
843}
844
845- (NSString *) trackerAddressAnnounce
846{
847    return [NSString stringWithUTF8String: fStat->tracker->announce];
848}
849
850- (NSString *) comment
851{
852    return [NSString stringWithUTF8String: fInfo->comment];
853}
854
855- (NSString *) creator
856{
857    return [NSString stringWithUTF8String: fInfo->creator];
858}
859
860- (NSDate *) dateCreated
861{
862    int date = fInfo->dateCreated;
863    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
864}
865
866- (int) pieceSize
867{
868    return fInfo->pieceSize;
869}
870
871- (int) pieceCount
872{
873    return fInfo->pieceCount;
874}
875
876- (NSString *) hashString
877{
878    return [NSString stringWithUTF8String: fInfo->hashString];
879}
880
881- (BOOL) privateTorrent
882{
883    return TR_FLAG_PRIVATE & fInfo->flags;
884}
885
886- (NSString *) torrentLocation
887{
888    return [NSString stringWithUTF8String: fInfo->torrent];
889}
890
891- (NSString *) publicTorrentLocation
892{
893    return fPublicTorrentLocation;
894}
895
896- (NSString *) dataLocation
897{
898    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
899}
900
901- (BOOL) publicTorrent
902{
903    return fPublicTorrent;
904}
905
906- (NSString *) stateString
907{
908    switch( fStat->status )
909    {
910        case TR_STATUS_STOPPED:
911            return NSLocalizedString(@"Paused", "Torrent -> status string");
912            break;
913
914        case TR_STATUS_CHECK:
915            return [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
916                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
917            break;
918       
919        case TR_STATUS_CHECK_WAIT:
920            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
921            break;
922
923        case TR_STATUS_DOWNLOAD:
924            return NSLocalizedString(@"Downloading", "Torrent -> status string");
925            break;
926
927        case TR_STATUS_SEED:
928        case TR_STATUS_DONE:
929            return NSLocalizedString(@"Seeding", "Torrent -> status string");
930            break;
931
932        case TR_STATUS_STOPPING:
933            return [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
934            break;
935       
936        default:
937            return NSLocalizedString(@"N/A", "Torrent -> status string");
938    }
939}
940
941- (float) progress
942{
943    return fStat->percentComplete;
944}
945
946- (float) progressDone
947{
948    return fStat->percentDone;
949}
950
951- (float) progressLeft
952{
953    return (float)fStat->left/[self size];
954}
955
956- (int) eta
957{
958    return fStat->eta;
959}
960
961- (BOOL) isActive
962{
963    return fStat->status & TR_STATUS_ACTIVE;
964}
965
966- (BOOL) isSeeding
967{
968    return fStat->status == TR_STATUS_SEED || fStat->status == TR_STATUS_DONE;
969}
970
971- (BOOL) isPaused
972{
973    return fStat->status == TR_STATUS_STOPPED;
974}
975
976- (BOOL) isChecking
977{
978    return fStat->status == TR_STATUS_CHECK || fStat->status == TR_STATUS_CHECK_WAIT;
979}
980
981- (BOOL) allDownloaded
982{
983    return fStat->cpStatus != TR_CP_INCOMPLETE;
984}
985
986- (BOOL) isComplete
987{
988    return fStat->cpStatus == TR_CP_COMPLETE;
989}
990
991- (BOOL) isError
992{
993    return fStat->error != 0;
994}
995
996- (NSString *) errorMessage
997{
998    if (![self isError])
999        return @"";
1000   
1001    NSString * error;
1002    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
1003        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
1004        error = NSLocalizedString(@"(unreadable error)", "Torrent -> error string unreadable");
1005   
1006    return error;
1007}
1008
1009- (NSArray *) peers
1010{
1011    int totalPeers, i;
1012    tr_peer_stat_t * peers = tr_torrentPeers(fHandle, &totalPeers);
1013   
1014    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
1015    NSMutableDictionary * dic;
1016   
1017    tr_peer_stat_t * peer;
1018    for (i = 0; i < totalPeers; i++)
1019    {
1020        peer = &peers[i];
1021       
1022        dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1023            [NSNumber numberWithBool: peer->isConnected], @"Connected",
1024            [NSNumber numberWithInt: peer->from], @"From",
1025            [NSString stringWithCString: (char *) peer->addr encoding: NSUTF8StringEncoding], @"IP",
1026            [NSNumber numberWithInt: peer->port], @"Port", nil];
1027       
1028        if (peer->isConnected)
1029        {
1030            [dic setObject: [NSNumber numberWithFloat: peer->progress] forKey: @"Progress"];
1031           
1032            if (peer->isDownloading)
1033                [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
1034            if (peer->isUploading)
1035                [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
1036           
1037            [dic setObject: [NSString stringWithCString: (char *) peer->client encoding: NSUTF8StringEncoding] forKey: @"Client"];
1038        }
1039        else
1040            [dic setObject: @"" forKey: @"Client"]; //needed to be set here for client sort
1041       
1042        [peerDics addObject: dic];
1043    }
1044   
1045    tr_torrentPeersFree(peers, totalPeers);
1046   
1047    return peerDics;
1048}
1049
1050- (NSString *) progressString
1051{
1052    return fProgressString;
1053}
1054
1055- (NSString *) statusString
1056{
1057    return fStatusString;
1058}
1059
1060- (NSString *) shortStatusString
1061{
1062    return fShortStatusString;
1063}
1064
1065- (NSString *) remainingTimeString
1066{
1067    return fRemainingTimeString;
1068}
1069
1070- (int) seeders
1071{
1072    return fStat->seeders;
1073}
1074
1075- (int) leechers
1076{
1077    return fStat->leechers;
1078}
1079
1080- (int) completedFromTracker
1081{
1082    return fStat->completedFromTracker;
1083}
1084
1085- (int) totalPeersConnected
1086{
1087    return fStat->peersConnected;
1088}
1089
1090- (int) totalPeersTracker
1091{
1092    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1093}
1094
1095- (int) totalPeersIncoming
1096{
1097    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1098}
1099
1100- (int) totalPeersCache
1101{
1102    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1103}
1104
1105- (int) totalPeersPex
1106{
1107    return fStat->peersFrom[TR_PEER_FROM_PEX];
1108}
1109
1110- (int) peersSendingToUs
1111{
1112    return fStat->peersSendingToUs;
1113}
1114
1115- (int) peersGettingFromUs
1116{
1117    return fStat->peersGettingFromUs;
1118}
1119
1120- (float) downloadRate
1121{
1122    return fStat->rateDownload;
1123}
1124
1125- (float) uploadRate
1126{
1127    return fStat->rateUpload;
1128}
1129
1130- (uint64_t) downloadedValid
1131{
1132    return fStat->downloadedValid;
1133}
1134
1135- (uint64_t) downloadedTotal
1136{
1137    return fStat->downloaded;
1138}
1139
1140- (uint64_t) uploadedTotal
1141{
1142    return fStat->uploaded;
1143}
1144
1145- (float) swarmSpeed
1146{
1147    return fStat->swarmspeed;
1148}
1149
1150- (BOOL) pex
1151{
1152        return fPex;
1153}
1154
1155- (void) setPex: (BOOL) setting
1156{
1157        if (![self privateTorrent])
1158        {
1159                fPex = setting;
1160                tr_torrentDisablePex(fHandle, !setting);
1161        }
1162}
1163
1164- (NSNumber *) orderValue
1165{
1166    return [NSNumber numberWithInt: fOrderValue];
1167}
1168
1169- (void) setOrderValue: (int) orderValue
1170{
1171    fOrderValue = orderValue;
1172}
1173
1174- (NSArray *) fileList
1175{
1176    return fFileList;
1177}
1178
1179- (int) fileCount
1180{
1181    return fInfo->fileCount;
1182}
1183
1184- (void) updateFileStat
1185{
1186    if (fileStat)
1187        tr_torrentFilesFree(fileStat, [self fileCount]);
1188   
1189    int count;
1190    fileStat = tr_torrentFiles(fHandle, &count);
1191}
1192
1193- (float) fileProgress: (int) index
1194{
1195    if (!fileStat)
1196        [self updateFileStat];
1197       
1198    return fileStat[index].progress;
1199}
1200
1201- (BOOL) canChangeDownloadCheckForFile: (int) index
1202{
1203    if (!fileStat)
1204        [self updateFileStat];
1205   
1206    return [self fileCount] > 1 && fileStat[index].completionStatus != TR_CP_COMPLETE;
1207}
1208
1209- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1210{
1211    if ([self fileCount] <= 1 || [self isComplete])
1212        return NO;
1213   
1214    if (!fileStat)
1215        [self updateFileStat];
1216   
1217    int index;
1218    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1219        if (fileStat[index].completionStatus != TR_CP_COMPLETE)
1220            return YES;
1221    return NO;
1222}
1223
1224- (int) checkForFiles: (NSIndexSet *) indexSet
1225{
1226    BOOL onState = NO, offState = NO;
1227    int index;
1228    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1229    {
1230        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1231            onState = YES;
1232        else
1233            offState = YES;
1234       
1235        if (onState && offState)
1236            return NSMixedState;
1237    }
1238    return onState ? NSOnState : NSOffState;
1239}
1240
1241- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
1242{
1243    int count = [indexSet count], i = 0, index;
1244    int * files = malloc(count * sizeof(int));
1245    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1246    {
1247        files[i] = index;
1248        i++;
1249    }
1250   
1251    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1252    free(files);
1253   
1254    [self update];
1255}
1256
1257- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1258{
1259    int count = [indexSet count], i = 0, index;
1260    int * files = malloc(count * sizeof(int));
1261    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1262    {
1263        files[i] = index;
1264        i++;
1265    }
1266
1267    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1268    free(files);
1269}
1270
1271- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1272{
1273    int index;
1274    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1275        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1276            return YES;
1277    return NO;
1278}
1279
1280- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1281{
1282    BOOL low = NO, normal = NO, high = NO;
1283    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1284   
1285    int index, priority;
1286    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1287    {
1288        if (![self canChangeDownloadCheckForFile: index])
1289            continue;
1290       
1291        priority = tr_torrentGetFilePriority(fHandle, index);
1292        if (priority == TR_PRI_LOW)
1293        {
1294            if (low)
1295                continue;
1296            low = YES;
1297        }
1298        else if (priority == TR_PRI_HIGH)
1299        {
1300            if (high)
1301                continue;
1302            high = YES;
1303        }
1304        else
1305        {
1306            if (normal)
1307                continue;
1308            normal = YES;
1309        }
1310       
1311        [priorities addObject: [NSNumber numberWithInt: priority]];
1312        if (low && normal && high)
1313            break;
1314    }
1315    return priorities;
1316}
1317
1318- (NSMenu *) torrentMenu
1319{
1320    if (!fTorrentMenu)
1321    {
1322        fTorrentMenu = [[NSMenu alloc] initWithTitle: [@"TorrentMenu:" stringByAppendingString: [self name]]];
1323        [fTorrentMenu setAutoenablesItems: NO];
1324    }
1325    return fTorrentMenu;
1326}
1327
1328- (NSDate *) dateAdded
1329{
1330    return fDateAdded;
1331}
1332
1333- (NSDate *) dateCompleted
1334{
1335    return fDateCompleted;
1336}
1337
1338- (NSDate *) dateActivity
1339{
1340    uint64_t date = fStat->activityDate;
1341    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1342}
1343
1344- (int) stalledMinutes
1345{
1346    uint64_t start;
1347    if ((start = fStat->startDate) == 0)
1348        return -1;
1349   
1350    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1351            * activity = [self dateActivity];
1352   
1353    NSDate * laterDate = (!activity || [started compare: activity] == NSOrderedDescending) ? started : activity;
1354    return -1 * [laterDate timeIntervalSinceNow] / 60;
1355}
1356
1357- (BOOL) isStalled
1358{
1359    return fStalled;
1360}
1361
1362- (NSNumber *) stateSortKey
1363{
1364    if (![self isActive])
1365        return [NSNumber numberWithInt: 0];
1366    else if ([self isSeeding])
1367        return [NSNumber numberWithInt: 1];
1368    else
1369        return [NSNumber numberWithInt: 2];
1370}
1371
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.