source: trunk/macosx/Torrent.m @ 3240

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

rearrange some status string code

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