source: trunk/macosx/Torrent.m @ 3207

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

clean up some of the new display string code

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