source: trunk/macosx/Torrent.m @ 3210

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

get rid of a warning and add an item to news

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