source: trunk/macosx/Torrent.m @ 3088

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

trivial Torrent code cleanup

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