source: trunk/macosx/Torrent.m @ 3087

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

use "init" instead of "initWithString: @"""

  • Property svn:keywords set to Date Rev Author Id
File size: 51.9 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 3087 2007-09-16 16:13:51Z livings124 $
3 *
4 * Copyright (c) 2006-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "Torrent.h"
26#import "NSStringAdditions.h"
27
28static int static_lastid = 0;
29
30@interface Torrent (Private)
31
32- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_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    [fFileMenu 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 alloc] init],
283                                        * remainingTimeString = [[NSMutableString alloc] init],
284                                        * statusString = [[NSMutableString alloc] init],
285                                        * shortStatusString = [[NSMutableString alloc] init];
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    {
423        NSString * errorString = [self errorMessage];
424        if (!errorString || [errorString isEqualToString: @""])
425            [statusString setString: NSLocalizedString(@"Error", "Torrent -> status string")];
426        else
427            [statusString setString: [NSLocalizedString(@"Error: ", "Torrent -> status string")
428                                    stringByAppendingString: errorString]];
429    }
430    else if (fStalled)
431        [statusString insertString: NSLocalizedString(@"Stalled, ", "Torrent -> status string") atIndex: 0];
432    else;
433   
434    //update queue for checking (from downloading to seeding), stalled, or error
435    if ((wasChecking && !fChecking) || (!wasStalled && fStalled) || (!wasError && fError && [self isActive]))
436        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
437
438    if ([self isActive] && ![self isChecking])
439    {
440        NSString * stringToAppend = @"";
441        if (![self allDownloaded])
442        {
443            stringToAppend = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, ", "Torrent -> status string"),
444                                [NSString stringForSpeed: [self downloadRate]]];
445            [shortStatusString setString: @""];
446        }
447        else
448        {
449            NSString * ratioString = [NSString stringForRatio: [self ratio]];
450       
451            [shortStatusString setString: [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, ",
452                                            "Torrent -> status string"), ratioString]];
453            [remainingTimeString setString: [NSLocalizedString(@"Ratio: ", "Torrent -> status string")
454                                                stringByAppendingString: ratioString]];
455        }
456       
457        stringToAppend = [stringToAppend stringByAppendingString: [NSLocalizedString(@"UL: ", "Torrent -> status string")
458                                            stringByAppendingString: [NSString stringForSpeed: [self uploadRate]]]];
459
460        [statusString appendFormat: @" - %@", stringToAppend];
461        [shortStatusString appendString: stringToAppend];
462    }
463       
464    //retain the strings
465    [fProgressString release];
466    fProgressString = progressString;
467   
468    [fStatusString release];
469    fStatusString = statusString;
470   
471    [fShortStatusString release];
472    fShortStatusString = shortStatusString;
473   
474    [fRemainingTimeString release];
475    fRemainingTimeString = remainingTimeString;
476}
477
478- (void) startTransfer
479{
480    fWaitToStart = NO;
481    fFinishedSeeding = NO;
482   
483    if (![self isActive] && [self alertForFolderAvailable] && [self alertForRemainingDiskSpace])
484    {
485        tr_torrentStart(fHandle);
486        [self update];
487    }
488}
489
490- (void) stopTransfer
491{
492    fError = NO;
493    fWaitToStart = NO;
494   
495    if ([self isActive])
496    {
497        tr_torrentStop(fHandle);
498        [self update];
499       
500        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
501    }
502}
503
504- (void) sleep
505{
506    if ((fResumeOnWake = [self isActive]))
507        tr_torrentStop(fHandle);
508}
509
510- (void) wakeUp
511{
512    if (fResumeOnWake)
513        tr_torrentStart(fHandle);
514}
515
516- (void) manualAnnounce
517{
518    tr_manualUpdate(fHandle);
519}
520
521- (BOOL) canManualAnnounce
522{
523    return tr_torrentCanManualUpdate(fHandle);
524}
525
526- (void) resetCache
527{
528    tr_torrentRecheck(fHandle);
529    [self update];
530}
531
532- (float) ratio
533{
534    return fStat->ratio;
535}
536
537- (int) ratioSetting
538{
539    return fRatioSetting;
540}
541
542- (void) setRatioSetting: (int) setting
543{
544    fRatioSetting = setting;
545}
546
547- (float) ratioLimit
548{
549    return fRatioLimit;
550}
551
552- (void) setRatioLimit: (float) limit
553{
554    if (limit >= 0)
555        fRatioLimit = limit;
556}
557
558- (float) actualStopRatio
559{
560    if (fRatioSetting == NSOnState)
561        return fRatioLimit;
562    else if (fRatioSetting == NSMixedState && [fDefaults boolForKey: @"RatioCheck"])
563        return [fDefaults floatForKey: @"RatioLimit"];
564    else
565        return INVALID;
566}
567
568- (float) progressStopRatio
569{
570    float stopRatio, ratio;
571    if ((stopRatio = [self actualStopRatio]) == INVALID || (ratio = [self ratio]) >= stopRatio)
572        return 1.0;
573    else if (ratio > 0 && stopRatio > 0)
574        return ratio / stopRatio;
575    else
576        return 0;
577}
578
579#warning use enum value
580- (int) speedMode: (BOOL) upload
581{
582    return tr_torrentGetSpeedMode(fHandle, upload ? TR_UP : TR_DOWN);
583}
584
585- (void) setSpeedMode: (int) mode upload: (BOOL) upload
586{
587    tr_torrentSetSpeedMode(fHandle, upload ? TR_UP : TR_DOWN, mode);
588}
589
590- (int) speedLimit: (BOOL) upload
591{
592    return tr_torrentGetSpeedLimit(fHandle, upload ? TR_UP : TR_DOWN);
593}
594
595- (void) setSpeedLimit: (int) limit upload: (BOOL) upload
596{
597    tr_torrentSetSpeedLimit(fHandle, upload ? TR_UP : TR_DOWN, limit);
598}
599
600- (void) setWaitToStart: (BOOL) wait
601{
602    fWaitToStart = wait;
603}
604
605- (BOOL) waitingToStart
606{
607    return fWaitToStart;
608}
609
610- (void) revealData
611{
612    [[NSWorkspace sharedWorkspace] selectFile: [self dataLocation] inFileViewerRootedAtPath: nil];
613}
614
615- (void) revealPublicTorrent
616{
617    if (fPublicTorrent)
618        [[NSWorkspace sharedWorkspace] selectFile: fPublicTorrentLocation inFileViewerRootedAtPath: nil];
619}
620
621- (void) trashData
622{
623    [self trashFile: [self dataLocation]];
624}
625
626- (void) trashTorrent
627{
628    if (fPublicTorrent)
629        [self trashFile: [self publicTorrentLocation]];
630}
631
632- (void) moveTorrentDataFileTo: (NSString *) folder
633{
634    NSString * oldFolder = [self downloadFolder];
635    if (![oldFolder isEqualToString: folder] || ![fDownloadFolder isEqualToString: folder])
636    {
637        //check if moving inside itself
638        NSArray * oldComponents = [oldFolder pathComponents],
639                * newComponents = [folder pathComponents];
640        int count;
641       
642        if ((count = [oldComponents count]) < [newComponents count]
643                && [[newComponents objectAtIndex: count] isEqualToString: [self name]]
644                && [oldComponents isEqualToArray:
645                        [newComponents objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, count)]]])
646        {
647            NSAlert * alert = [[NSAlert alloc] init];
648            [alert setMessageText: NSLocalizedString(@"A folder cannot be moved to inside itself.",
649                                                        "Move inside itself alert -> title")];
650            [alert setInformativeText: [NSString stringWithFormat:
651                            NSLocalizedString(@"The move operation of \"%@\" cannot be done.",
652                                                "Move inside itself alert -> message"), [self name]]];
653            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move inside itself alert -> button")];
654           
655            [alert runModal];
656            [alert release];
657           
658            return;
659        }
660       
661        [self quickPause];
662       
663        if ([[NSFileManager defaultManager] movePath: [oldFolder stringByAppendingPathComponent: [self name]]
664                            toPath: [folder stringByAppendingPathComponent: [self name]] handler: nil])
665        {
666            //get rid of both incomplete folder and old download folder, even if move failed
667            fUseIncompleteFolder = NO;
668            if (fIncompleteFolder)
669            {
670                [fIncompleteFolder release];
671                fIncompleteFolder = nil;
672            }
673            [self changeDownloadFolder: folder];
674           
675            [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateInfoSettings" object: nil];
676           
677            [self endQuickPause];
678        }
679        else
680        {
681            [self endQuickPause];
682       
683            NSAlert * alert = [[NSAlert alloc] init];
684            [alert setMessageText: NSLocalizedString(@"There was an error moving the data file.", "Move error alert -> title")];
685            [alert setInformativeText: [NSString stringWithFormat:
686                            NSLocalizedString(@"The move operation of \"%@\" cannot be done.",
687                                                "Move error alert -> message"), [self name]]];
688            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move error alert -> button")];
689           
690            [alert runModal];
691            [alert release];
692        }
693    }
694}
695
696- (void) copyTorrentFileTo: (NSString *) path
697{
698    [[NSFileManager defaultManager] copyPath: [self torrentLocation] toPath: path handler: nil];
699}
700
701- (BOOL) alertForRemainingDiskSpace
702{
703    if ([self allDownloaded] || ![fDefaults boolForKey: @"WarningRemainingSpace"])
704        return YES;
705   
706    NSString * volumeName;
707    if ((volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: [self downloadFolder]] objectAtIndex: 0]))
708    {
709        NSDictionary * systemAttributes = [[NSFileManager defaultManager] fileSystemAttributesAtPath: [self downloadFolder]];
710        uint64_t remainingSpace = [[systemAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue];
711       
712        uint64_t existingSize = 0;
713        NSDirectoryEnumerator * enumerator;
714        if ((enumerator = [[NSFileManager defaultManager] enumeratorAtPath:
715                    [[self downloadFolder] stringByAppendingPathComponent: [self name]]]))
716        {
717            NSDictionary * fileAttributes;
718            while ([enumerator nextObject])
719            {
720                fileAttributes = [enumerator fileAttributes];
721                if (![[fileAttributes objectForKey: NSFileType] isEqualTo: NSFileTypeDirectory])
722                    existingSize += [[fileAttributes objectForKey: NSFileSize] unsignedLongLongValue];
723            }
724        }
725       
726        #warning factor in checked files
727        if (remainingSpace + existingSize <= [self size])
728        {
729            NSAlert * alert = [[NSAlert alloc] init];
730            [alert setMessageText: [NSString stringWithFormat:
731                                    NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
732                                        "Torrent file disk space alert -> title"), [self name]]];
733            [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"The transfer will be paused."
734                                        " Clear up space on %@ or deselect files in the torrent inspector to continue.",
735                                        "Torrent file disk space alert -> message"), volumeName]];
736            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file disk space alert -> button")];
737            [alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent file disk space alert -> button")];
738            [alert addButtonWithTitle: NSLocalizedString(@"Always Download", "Torrent file disk space alert -> button")];
739           
740            int result = [alert runModal];
741            [alert release];
742           
743            if (result == NSAlertThirdButtonReturn)
744                [fDefaults setBool: NO forKey: @"WarningRemainingSpace"];
745           
746            return result != NSAlertFirstButtonReturn;
747        }
748    }
749    return YES;
750}
751
752- (BOOL) alertForFolderAvailable
753{
754    if (access(tr_torrentGetFolder(fHandle), 0))
755    {
756        NSAlert * alert = [[NSAlert alloc] init];
757        [alert setMessageText: [NSString stringWithFormat:
758                                NSLocalizedString(@"The folder for downloading \"%@\" cannot be used.",
759                                    "Folder cannot be used alert -> title"), [self name]]];
760        [alert setInformativeText: [NSString stringWithFormat:
761                        NSLocalizedString(@"\"%@\" cannot be used. The transfer will be paused.",
762                                            "Folder cannot be used alert -> message"), [self downloadFolder]]];
763        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Folder cannot be used alert -> button")];
764        [alert addButtonWithTitle: [NSLocalizedString(@"Choose New Location",
765                                    "Folder cannot be used alert -> location button") stringByAppendingEllipsis]];
766       
767        if ([alert runModal] != NSAlertFirstButtonReturn)
768        {
769            NSOpenPanel * panel = [NSOpenPanel openPanel];
770           
771            [panel setPrompt: NSLocalizedString(@"Select", "Folder cannot be used alert -> prompt")];
772            [panel setAllowsMultipleSelection: NO];
773            [panel setCanChooseFiles: NO];
774            [panel setCanChooseDirectories: YES];
775            [panel setCanCreateDirectories: YES];
776
777            [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
778                                "Folder cannot be used alert -> select destination folder"), [self name]]];
779           
780            [[NSNotificationCenter defaultCenter] postNotificationName: @"MakeWindowKey" object: nil];
781            [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: [NSApp keyWindow] modalDelegate: self
782                    didEndSelector: @selector(destinationChoiceClosed:returnCode:contextInfo:) contextInfo: nil];
783        }
784       
785        [alert release];
786       
787        return NO;
788    }
789   
790    return YES;
791}
792
793- (void) destinationChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) context
794{
795    if (code != NSOKButton)
796        return;
797   
798    NSString * folder = [[openPanel filenames] objectAtIndex: 0];
799    if (fUseIncompleteFolder)
800        [self changeDownloadFolder: folder];
801    else
802        [self changeDownloadFolder: folder];
803   
804    [self startTransfer];
805    [self update];
806   
807    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateInfoSettings" object: nil];
808}
809
810- (BOOL) alertForMoveFolderAvailable
811{
812    if (access([fDownloadFolder UTF8String], 0))
813    {
814        NSAlert * alert = [[NSAlert alloc] init];
815        [alert setMessageText: [NSString stringWithFormat:
816                                NSLocalizedString(@"The folder for moving the completed \"%@\" cannot be used.",
817                                    "Move folder cannot be used alert -> title"), [self name]]];
818        [alert setInformativeText: [NSString stringWithFormat:
819                                NSLocalizedString(@"\"%@\" cannot be used. The file will remain in its current location.",
820                                    "Move folder cannot be used alert -> message"), fDownloadFolder]];
821        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move folder cannot be used alert -> button")];
822       
823        [alert runModal];
824        [alert release];
825       
826        return NO;
827    }
828   
829    return YES;
830}
831
832- (NSImage *) icon
833{
834    if (!fIcon)
835    {
836        fIcon = [[[NSWorkspace sharedWorkspace] iconForFileType: fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr')
837                                                : [[self name] pathExtension]] retain];
838        [fIcon setFlipped: YES];
839    }
840    return fIcon;
841}
842
843- (NSString *) name
844{
845    return [NSString stringWithUTF8String: fInfo->name];
846}
847
848- (uint64_t) size
849{
850    return fInfo->totalSize;
851}
852
853- (NSString *) trackerAddress
854{
855    return [NSString stringWithFormat: @"http://%s:%d", fStat->tracker->address, fStat->tracker->port];
856}
857
858- (NSString *) trackerAddressAnnounce
859{
860    return [NSString stringWithUTF8String: fStat->tracker->announce];
861}
862
863- (NSString *) comment
864{
865    return [NSString stringWithUTF8String: fInfo->comment];
866}
867
868- (NSString *) creator
869{
870    return [NSString stringWithUTF8String: fInfo->creator];
871}
872
873- (NSDate *) dateCreated
874{
875    int date = fInfo->dateCreated;
876    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
877}
878
879- (int) pieceSize
880{
881    return fInfo->pieceSize;
882}
883
884- (int) pieceCount
885{
886    return fInfo->pieceCount;
887}
888
889- (NSString *) hashString
890{
891    return [NSString stringWithUTF8String: fInfo->hashString];
892}
893
894- (BOOL) privateTorrent
895{
896    return TR_FLAG_PRIVATE & fInfo->flags;
897}
898
899- (NSString *) torrentLocation
900{
901    return [NSString stringWithUTF8String: fInfo->torrent];
902}
903
904- (NSString *) publicTorrentLocation
905{
906    return fPublicTorrentLocation;
907}
908
909- (NSString *) dataLocation
910{
911    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
912}
913
914- (BOOL) publicTorrent
915{
916    return fPublicTorrent;
917}
918
919- (NSString *) stateString
920{
921    switch( fStat->status )
922    {
923        case TR_STATUS_STOPPED:
924            return NSLocalizedString(@"Paused", "Torrent -> status string");
925            break;
926
927        case TR_STATUS_CHECK:
928            return [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
929                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
930            break;
931       
932        case TR_STATUS_CHECK_WAIT:
933            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
934            break;
935
936        case TR_STATUS_DOWNLOAD:
937            return NSLocalizedString(@"Downloading", "Torrent -> status string");
938            break;
939
940        case TR_STATUS_SEED:
941        case TR_STATUS_DONE:
942            return NSLocalizedString(@"Seeding", "Torrent -> status string");
943            break;
944
945        case TR_STATUS_STOPPING:
946            return [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
947            break;
948       
949        default:
950            return NSLocalizedString(@"N/A", "Torrent -> status string");
951    }
952}
953
954- (float) progress
955{
956    return fStat->percentComplete;
957}
958
959- (float) progressDone
960{
961    return fStat->percentDone;
962}
963
964- (float) progressLeft
965{
966    return (float)fStat->left/[self size];
967}
968
969- (int) eta
970{
971    return fStat->eta;
972}
973
974- (BOOL) isActive
975{
976    return fStat->status & TR_STATUS_ACTIVE;
977}
978
979- (BOOL) isSeeding
980{
981    return fStat->status == TR_STATUS_SEED || fStat->status == TR_STATUS_DONE;
982}
983
984- (BOOL) isPaused
985{
986    return fStat->status == TR_STATUS_STOPPED;
987}
988
989- (BOOL) isChecking
990{
991    return fStat->status == TR_STATUS_CHECK || fStat->status == TR_STATUS_CHECK_WAIT;
992}
993
994- (BOOL) allDownloaded
995{
996    return fStat->cpStatus != TR_CP_INCOMPLETE;
997}
998
999- (BOOL) isComplete
1000{
1001    return fStat->cpStatus == TR_CP_COMPLETE;
1002}
1003
1004- (BOOL) isError
1005{
1006    return fStat->error != 0;
1007}
1008
1009- (NSString *) errorMessage
1010{
1011    if (![self isError])
1012        return @"";
1013   
1014    NSString * error;
1015    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
1016        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
1017        error = NSLocalizedString(@"(unreadable error)", "Torrent -> error string unreadable");
1018   
1019    return error;
1020}
1021
1022- (NSArray *) peers
1023{
1024    int totalPeers, i;
1025    tr_peer_stat_t * peers = tr_torrentPeers(fHandle, &totalPeers);
1026   
1027    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
1028    NSMutableDictionary * dic;
1029   
1030    tr_peer_stat_t * peer;
1031    for (i = 0; i < totalPeers; i++)
1032    {
1033        peer = &peers[i];
1034       
1035        dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1036            [NSNumber numberWithBool: peer->isConnected], @"Connected",
1037            [NSNumber numberWithInt: peer->from], @"From",
1038            [NSString stringWithCString: (char *) peer->addr encoding: NSUTF8StringEncoding], @"IP",
1039            [NSNumber numberWithInt: peer->port], @"Port", nil];
1040       
1041        if (peer->isConnected)
1042        {
1043            [dic setObject: [NSNumber numberWithFloat: peer->progress] forKey: @"Progress"];
1044           
1045            if (peer->isDownloading)
1046                [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
1047            if (peer->isUploading)
1048                [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
1049           
1050            [dic setObject: [NSNumber numberWithBool: /*peer->isEncrypted*/NO] forKey: @"Encryption"];
1051           
1052            [dic setObject: [NSString stringWithCString: (char *) peer->client encoding: NSUTF8StringEncoding] forKey: @"Client"];
1053        }
1054        else
1055            [dic setObject: @"" forKey: @"Client"]; //needed to be set here for client sort
1056       
1057        [peerDics addObject: dic];
1058    }
1059   
1060    tr_torrentPeersFree(peers, totalPeers);
1061   
1062    return peerDics;
1063}
1064
1065- (NSString *) progressString
1066{
1067    return fProgressString;
1068}
1069
1070- (NSString *) statusString
1071{
1072    return fStatusString;
1073}
1074
1075- (NSString *) shortStatusString
1076{
1077    return fShortStatusString;
1078}
1079
1080- (NSString *) remainingTimeString
1081{
1082    return fRemainingTimeString;
1083}
1084
1085- (int) seeders
1086{
1087    return fStat->seeders;
1088}
1089
1090- (int) leechers
1091{
1092    return fStat->leechers;
1093}
1094
1095- (int) completedFromTracker
1096{
1097    return fStat->completedFromTracker;
1098}
1099
1100- (int) totalPeersConnected
1101{
1102    return fStat->peersConnected;
1103}
1104
1105- (int) totalPeersTracker
1106{
1107    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1108}
1109
1110- (int) totalPeersIncoming
1111{
1112    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1113}
1114
1115- (int) totalPeersCache
1116{
1117    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1118}
1119
1120- (int) totalPeersPex
1121{
1122    return fStat->peersFrom[TR_PEER_FROM_PEX];
1123}
1124
1125- (int) peersSendingToUs
1126{
1127    return fStat->peersSendingToUs;
1128}
1129
1130- (int) peersGettingFromUs
1131{
1132    return fStat->peersGettingFromUs;
1133}
1134
1135- (float) downloadRate
1136{
1137    return fStat->rateDownload;
1138}
1139
1140- (float) uploadRate
1141{
1142    return fStat->rateUpload;
1143}
1144
1145- (uint64_t) downloadedValid
1146{
1147    return fStat->downloadedValid;
1148}
1149
1150- (uint64_t) downloadedTotal
1151{
1152    return fStat->downloaded;
1153}
1154
1155- (uint64_t) uploadedTotal
1156{
1157    return fStat->uploaded;
1158}
1159
1160- (float) swarmSpeed
1161{
1162    return fStat->swarmspeed;
1163}
1164
1165- (BOOL) pex
1166{
1167        return fPex;
1168}
1169
1170- (void) setPex: (BOOL) setting
1171{
1172        if (![self privateTorrent])
1173        {
1174                fPex = setting;
1175                tr_torrentDisablePex(fHandle, !setting);
1176        }
1177}
1178
1179- (NSNumber *) orderValue
1180{
1181    return [NSNumber numberWithInt: fOrderValue];
1182}
1183
1184- (void) setOrderValue: (int) orderValue
1185{
1186    fOrderValue = orderValue;
1187}
1188
1189- (NSArray *) fileList
1190{
1191    return fFileList;
1192}
1193
1194- (int) fileCount
1195{
1196    return fInfo->fileCount;
1197}
1198
1199- (void) updateFileStat
1200{
1201    if (fileStat)
1202        tr_torrentFilesFree(fileStat, [self fileCount]);
1203   
1204    int count;
1205    fileStat = tr_torrentFiles(fHandle, &count);
1206}
1207
1208- (float) fileProgress: (int) index
1209{
1210    if (!fileStat)
1211        [self updateFileStat];
1212       
1213    return fileStat[index].progress;
1214}
1215
1216- (BOOL) canChangeDownloadCheckForFile: (int) index
1217{
1218    if (!fileStat)
1219        [self updateFileStat];
1220   
1221    return [self fileCount] > 1 && fileStat[index].completionStatus != TR_CP_COMPLETE;
1222}
1223
1224- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1225{
1226    if ([self fileCount] <= 1 || [self isComplete])
1227        return NO;
1228   
1229    if (!fileStat)
1230        [self updateFileStat];
1231   
1232    int index;
1233    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1234        if (fileStat[index].completionStatus != TR_CP_COMPLETE)
1235            return YES;
1236    return NO;
1237}
1238
1239- (int) checkForFiles: (NSIndexSet *) indexSet
1240{
1241    BOOL onState = NO, offState = NO;
1242    int index;
1243    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1244    {
1245        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1246            onState = YES;
1247        else
1248            offState = YES;
1249       
1250        if (onState && offState)
1251            return NSMixedState;
1252    }
1253    return onState ? NSOnState : NSOffState;
1254}
1255
1256- (void) setFileCheckState: (int) state 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_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1267    free(files);
1268   
1269    [self update];
1270}
1271
1272- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1273{
1274    int count = [indexSet count], i = 0, index;
1275    int * files = malloc(count * sizeof(int));
1276    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1277    {
1278        files[i] = index;
1279        i++;
1280    }
1281
1282    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1283    free(files);
1284}
1285
1286- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1287{
1288    int index;
1289    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1290        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1291            return YES;
1292    return NO;
1293}
1294
1295- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1296{
1297    BOOL low = NO, normal = NO, high = NO;
1298    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1299   
1300    int index, priority;
1301    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1302    {
1303        if (![self canChangeDownloadCheckForFile: index])
1304            continue;
1305       
1306        priority = tr_torrentGetFilePriority(fHandle, index);
1307        if (priority == TR_PRI_LOW)
1308        {
1309            if (low)
1310                continue;
1311            low = YES;
1312        }
1313        else if (priority == TR_PRI_HIGH)
1314        {
1315            if (high)
1316                continue;
1317            high = YES;
1318        }
1319        else
1320        {
1321            if (normal)
1322                continue;
1323            normal = YES;
1324        }
1325       
1326        [priorities addObject: [NSNumber numberWithInt: priority]];
1327        if (low && normal && high)
1328            break;
1329    }
1330    return priorities;
1331}
1332
1333- (NSMenu *) fileMenu
1334{
1335    if (!fFileMenu)
1336    {
1337        fFileMenu = [[NSMenu alloc] initWithTitle: [@"TorrentMenu:" stringByAppendingString: [self name]]];
1338        [fFileMenu setAutoenablesItems: NO];
1339    }
1340    return fFileMenu;
1341}
1342
1343- (NSDate *) dateAdded
1344{
1345    return fDateAdded;
1346}
1347
1348- (NSDate *) dateCompleted
1349{
1350    return fDateCompleted;
1351}
1352
1353- (NSDate *) dateActivity
1354{
1355    uint64_t date = fStat->activityDate;
1356    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1357}
1358
1359- (int) stalledMinutes
1360{
1361    uint64_t start;
1362    if ((start = fStat->startDate) == 0)
1363        return -1;
1364   
1365    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1366            * activity = [self dateActivity];
1367   
1368    NSDate * laterDate = (!activity || [started compare: activity] == NSOrderedDescending) ? started : activity;
1369    return -1 * [laterDate timeIntervalSinceNow] / 60;
1370}
1371
1372- (BOOL) isStalled
1373{
1374    return fStalled;
1375}
1376
1377- (NSNumber *) stateSortKey
1378{
1379    if (![self isActive])
1380        return [NSNumber numberWithInt: 0];
1381    else if ([self isSeeding])
1382        return [NSNumber numberWithInt: 1];
1383    else
1384        return [NSNumber numberWithInt: 2];
1385}
1386
1387#warning is progress different when seeding?
1388- (NSNumber *) progressSortKey
1389{
1390    float progress;
1391    if ((progress = [self progress]) >= 1.0)
1392       progress += [self progressStopRatio];
1393   
1394    return [NSNumber numberWithFloat: progress];
1395}
1396
1397- (NSNumber *) ratioSortKey
1398{
1399    return [NSNumber numberWithFloat: [self ratio]];
1400}
1401
1402- (int) torrentID
1403{
1404    return fID;
1405}
1406
1407- (const tr_info_t *) torrentInfo
1408{
1409    return fInfo;
1410}
1411
1412- (const tr_stat_t *) torrentStat
1413{
1414    return fStat;
1415}
1416
1417@end
1418
1419@implementation Torrent (Private)
1420
1421//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1422- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
1423        publicTorrent: (NSNumber *) publicTorrent
1424        downloadFolder: (NSString *) downloadFolder
1425        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1426        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
1427        dateActivity: (NSDate *) dateActivity
1428        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
1429                pex: (NSNumber *) pex
1430        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;
1431{
1432    if (!(self = [super init]))
1433        return nil;
1434   
1435    static_lastid++;
1436    fID = static_lastid;
1437   
1438    fLib = lib;
1439    fDefaults = [NSUserDefaults standardUserDefaults];
1440
1441    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1442    if (fPublicTorrent)
1443        fPublicTorrentLocation = [path retain];
1444   
1445    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1446    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1447   
1448    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1449                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1450    if (fUseIncompleteFolder)
1451    {
1452        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1453        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1454    }
1455   
1456    NSString * currentDownloadFolder;
1457    tr_info_t info;
1458    int error;
1459    if (hashString)
1460    {
1461        if (tr_torrentParseHash(fLib, [hashString UTF8String], NULL, &info) == TR_OK)
1462        {
1463            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1464                                        ? fIncompleteFolder : fDownloadFolder;
1465            fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], [currentDownloadFolder UTF8String],
1466                                            TR_FLAG_SAVE | TR_FLAG_PAUSED, &error);
1467        }
1468        tr_metainfoFree(&info);
1469    }
1470    if (!fHandle && path)
1471    {
1472        if (tr_torrentParse(fLib, [path UTF8String], NULL, &info) == TR_OK)
1473        {
1474            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1475                                        ? fIncompleteFolder : fDownloadFolder;
1476            fHandle = tr_torrentInit(fLib, [path UTF8String], [currentDownloadFolder UTF8String],
1477                                        TR_FLAG_SAVE | TR_FLAG_PAUSED, &error);
1478        }
1479        tr_metainfoFree(&info);
1480    }
1481    if (!fHandle)
1482    {
1483        [self release];
1484        return nil;
1485    }
1486   
1487    fInfo = tr_torrentInfo(fHandle);
1488
1489    fDateAdded = dateAdded ? [dateAdded retain] : [[NSDate alloc] init];
1490        if (dateCompleted)
1491                fDateCompleted = [dateCompleted retain];
1492    if (dateActivity)
1493                fDateActivity = [dateActivity retain];
1494       
1495    fRatioSetting = ratioSetting ? [ratioSetting intValue] : NSMixedState;
1496    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1497    fFinishedSeeding = NO;
1498       
1499        if ([self privateTorrent])
1500                fPex = NO;
1501        else
1502                fPex = pex ? [pex boolValue] : YES;
1503        tr_torrentDisablePex(fHandle, !fPex);
1504   
1505    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1506    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1507    fError = NO;
1508   
1509    [self createFileList];
1510   
1511    [self update];
1512    return self;
1513}
1514
1515- (void) createFileList
1516{
1517    int count = [self fileCount], i;
1518    tr_file_t * file;
1519    NSMutableArray * pathComponents;
1520    NSString * path;
1521   
1522    NSMutableArray * fileList = [[NSMutableArray alloc] init],
1523                    * flatFileList = [[NSMutableArray alloc] initWithCapacity: count];
1524   
1525    for (i = 0; i < count; i++)
1526    {
1527        file = &fInfo->files[i];
1528       
1529        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1530        if (fInfo->multifile)
1531        {
1532            path = [pathComponents objectAtIndex: 0];
1533            [pathComponents removeObjectAtIndex: 0];
1534        }
1535        else
1536            path = @"";
1537       
1538        [self insertPath: pathComponents forSiblings: fileList withParent: nil previousPath: path
1539                flatList: flatFileList fileSize: file->length index: i];
1540        [pathComponents autorelease];
1541    }
1542   
1543    fFileList = [[NSArray alloc] initWithArray: fileList];
1544    [fileList release];
1545    fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1546    [flatFileList release];
1547}
1548
1549- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings
1550        withParent: (NSMutableDictionary *) parent previousPath: (NSString *) previousPath
1551        flatList: (NSMutableArray *) flatList fileSize: (uint64_t) size index: (int) index
1552{
1553    NSString * name = [components objectAtIndex: 0];
1554    BOOL isFolder = [components count] > 1;
1555   
1556    NSMutableDictionary * dict = nil;
1557    if (isFolder)
1558    {
1559        NSEnumerator * enumerator = [siblings objectEnumerator];
1560        while ((dict = [enumerator nextObject]))
1561            if ([[dict objectForKey: @"IsFolder"] boolValue] && [[dict objectForKey: @"Name"] isEqualToString: name])
1562                break;
1563    }
1564   
1565    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1566   
1567    //create new folder or item if it doesn't already exist
1568    if (!dict)
1569    {
1570        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1571                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1572        [siblings addObject: dict];
1573       
1574        if (isFolder)
1575        {
1576            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1577            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1578        }
1579        else
1580        {
1581            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1582            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1583           
1584            NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
1585            [icon setFlipped: YES];
1586            [dict setObject: icon forKey: @"Icon"];
1587           
1588            [flatList addObject: dict];
1589        }
1590       
1591        if (parent)
1592            [dict setObject: parent forKey: @"Parent"];
1593    }
1594    else
1595        [[dict objectForKey: @"Indexes"] addIndex: index];
1596   
1597    if (isFolder)
1598    {
1599        [components removeObjectAtIndex: 0];
1600        [self insertPath: components forSiblings: [dict objectForKey: @"Children"]
1601            withParent: dict previousPath: currentPath flatList: flatList fileSize: size index: index];
1602    }
1603}
1604
1605- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1606{
1607    return fUseIncompleteFolder &&
1608        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1609}
1610
1611- (void) updateDownloadFolder
1612{
1613    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1614    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1615}
1616
1617- (void) quickPause
1618{
1619    if (fQuickPauseDict)
1620        return;
1621
1622    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1623                    [NSNumber numberWithInt: [self speedMode: YES]], @"UploadSpeedMode",
1624                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1625                    [NSNumber numberWithInt: [self speedMode: NO]], @"DownloadSpeedMode",
1626                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1627   
1628    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: YES];
1629    [self setSpeedLimit: 0 upload: YES];
1630    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: NO];
1631    [self setSpeedLimit: 0 upload: NO];
1632}
1633
1634- (void) endQuickPause
1635{
1636    if (!fQuickPauseDict)
1637        return;
1638   
1639    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"UploadSpeedMode"] intValue] upload: YES];
1640    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1641    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"DownloadSpeedMode"] intValue] upload: NO];
1642    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1643   
1644    [fQuickPauseDict release];
1645    fQuickPauseDict = nil;
1646}
1647
1648- (void) trashFile: (NSString *) path
1649{
1650    //attempt to move to trash
1651    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1652            source: [path stringByDeletingLastPathComponent] destination: @""
1653            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1654    {
1655        //if cannot trash, just delete it (will work if it is on a remote volume)
1656        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1657            NSLog(@"Could not trash %@", path);
1658    }
1659}
1660
1661@end
Note: See TracBrowser for help on using the repository browser.