source: trunk/macosx/Torrent.m @ 3250

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

remove "connected dot" from peer inspector, and append speed string properly to torrents with errors

  • Property svn:keywords set to Date Rev Author Id
File size: 53.0 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 3250 2007-10-01 11:34:01Z 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 numberWithInt: peer->from], @"From",
807            [NSString stringWithCString: (char *) peer->addr encoding: NSUTF8StringEncoding], @"IP",
808            [NSNumber numberWithInt: peer->port], @"Port",
809            [NSNumber numberWithFloat: peer->progress], @"Progress",
810            [NSNumber numberWithBool: peer->isEncrypted], @"Encryption",
811            [NSString stringWithCString: (char *)peer->client encoding: NSUTF8StringEncoding], @"Client", nil];
812       
813        if (peer->isDownloading)
814            [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
815        if (peer->isUploading)
816            [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
817       
818        [peerDics addObject: dic];
819    }
820   
821    tr_torrentPeersFree(peers, totalPeers);
822   
823    return peerDics;
824}
825
826- (NSString *) progressString
827{
828    NSString * string;
829   
830    if (![self allDownloaded])
831    {
832        if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
833        {
834            uint64_t have = [self haveTotal];
835            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ selected (%.2f%%)", "Torrent -> progress string"),
836                            [NSString stringForFileSize: have], [NSString stringForFileSize: have + fStat->leftUntilDone],
837                            100.0 * [self progressDone]];
838        }
839        else
840            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ (%.2f%%)", "Torrent -> progress string"),
841                            [NSString stringForFileSize: [self haveTotal]],
842                            [NSString stringForFileSize: [self size]], 100.0 * [self progress]];
843    }
844    else if (![self isComplete])
845    {
846        if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
847            string = [NSString stringWithFormat: NSLocalizedString(@"%@ selected, uploaded %@ (Ratio: %@)",
848                "Torrent -> progress string"), [NSString stringForFileSize: [self haveTotal]],
849                [NSString stringForFileSize: [self uploadedTotal]], [NSString stringForRatio: [self ratio]]];
850        else
851            string = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@ (%.2f%%), uploaded %@ (Ratio: %@)",
852                "Torrent -> progress string"), [NSString stringForFileSize: [self haveTotal]],
853                [NSString stringForFileSize: [self size]], 100.0 * [self progress],
854                [NSString stringForFileSize: [self uploadedTotal]], [NSString stringForRatio: [self ratio]]];
855    }
856    else
857        string = [NSString stringWithFormat: NSLocalizedString(@"%@, uploaded %@ (Ratio: %@)", "Torrent -> progress string"),
858                [NSString stringForFileSize: [self size]], [NSString stringForFileSize: [self uploadedTotal]],
859                [NSString stringForRatio: [self ratio]]];
860   
861    //add time when downloading
862    if (fStat->status == TR_STATUS_DOWNLOAD)
863    {
864        string = [self eta] >= 0
865            ? [string stringByAppendingFormat: NSLocalizedString(@" - %@ remaining", "Torrent -> progress string"), [self etaString]]
866            : [string stringByAppendingString: NSLocalizedString(@" - remaining time unknown", "Torrent -> progress string")];
867    }
868   
869    return string;
870}
871
872- (NSString *) statusString
873{
874    NSString * string;
875   
876    if ([self isError])
877    {
878        NSString * errorString = [self errorMessage];
879        if (!errorString || [errorString isEqualToString: @""])
880            string = NSLocalizedString(@"Error", "Torrent -> status string");
881        else
882            string = [NSLocalizedString(@"Error: ", "Torrent -> status string") stringByAppendingString: errorString];
883    }
884    else
885    {
886        switch (fStat->status)
887        {
888            case TR_STATUS_STOPPED:
889                if (fWaitToStart)
890                {
891                    string = ![self allDownloaded]
892                            ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
893                            : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
894                }
895                else if (fFinishedSeeding)
896                    string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
897                else
898                    string = NSLocalizedString(@"Paused", "Torrent -> status string");
899                break;
900           
901            case TR_STATUS_STOPPING:
902                string = [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
903                break;
904
905            case TR_STATUS_CHECK_WAIT:
906                string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
907                break;
908
909            case TR_STATUS_CHECK:
910                string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
911                                        "Torrent -> status string"), 100.0 * fStat->recheckProgress];
912                break;
913
914            case TR_STATUS_DOWNLOAD:
915                if ([self totalPeersConnected] != 1)
916                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
917                                                    "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
918                else
919                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
920                                                    "Torrent -> status string"), [self peersSendingToUs]];
921                break;
922
923            case TR_STATUS_SEED:
924            case TR_STATUS_DONE:
925                if ([self totalPeersConnected] != 1)
926                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
927                                                    [self peersGettingFromUs], [self totalPeersConnected]];
928                else
929                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
930                                                    [self peersGettingFromUs]];
931                break;
932           
933            default:
934                string = @"";
935        }
936       
937        if (fStalled)
938            string = [NSLocalizedString(@"Stalled, ", "Torrent -> status string") stringByAppendingString: string];
939    }
940   
941    //append even if error
942    if ([self isActive] && ![self isChecking])
943    {
944        if (fStat->status == TR_STATUS_DOWNLOAD)
945            string = [string stringByAppendingFormat: @" - DL: %@, UL: %@",
946                    [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
947        else
948            string = [string stringByAppendingFormat: @" - UL: %@", [NSString stringForSpeed: [self uploadRate]]];
949    }
950   
951    return string;
952}
953
954- (NSString *) shortStatusString
955{
956    NSString * string;
957   
958    switch (fStat->status)
959    {
960        case TR_STATUS_STOPPED:
961            if (fWaitToStart)
962            {
963                string = ![self allDownloaded]
964                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
965                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
966            }
967            else if (fFinishedSeeding)
968                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
969            else
970                string = NSLocalizedString(@"Paused", "Torrent -> status string");
971            break;
972       
973        case TR_STATUS_STOPPING:
974            string = [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
975            break;
976
977        case TR_STATUS_CHECK_WAIT:
978            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
979            break;
980
981        case TR_STATUS_CHECK:
982            string = [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
983                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
984            break;
985       
986        case TR_STATUS_DOWNLOAD:
987            string = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, UL: %@", "Torrent -> status string"),
988                            [NSString stringForSpeed: [self downloadRate]], [NSString stringForSpeed: [self uploadRate]]];
989            break;
990       
991        case TR_STATUS_SEED:
992        case TR_STATUS_DONE:
993            string = [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, UL: %@", "Torrent -> status string"),
994                            [NSString stringForRatio: [self ratio]], [NSString stringForSpeed: [self uploadRate]]];
995            break;
996       
997        default:
998            string = @"";
999    }
1000   
1001    return string;
1002}
1003
1004- (NSString *) remainingTimeString
1005{
1006    switch (fStat->status)
1007    {
1008        case TR_STATUS_DOWNLOAD:
1009            return [self eta] >= 0 ? [self etaString] : NSLocalizedString(@"Unknown", "Torrent -> remaining time");
1010            break;
1011       
1012        case TR_STATUS_SEED:
1013        case TR_STATUS_DONE:
1014            return [NSLocalizedString(@"Ratio: ", "Torrent -> status string") stringByAppendingString:
1015                                                                            [NSString stringForRatio: [self ratio]]];
1016            break;
1017       
1018        default:
1019            return [self shortStatusString];
1020    }
1021}
1022
1023- (NSString *) stateString
1024{
1025    switch (fStat->status)
1026    {
1027        case TR_STATUS_STOPPED:
1028            return NSLocalizedString(@"Paused", "Torrent -> status string");
1029            break;
1030       
1031        case TR_STATUS_STOPPING:
1032            return [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
1033            break;
1034
1035        case TR_STATUS_CHECK:
1036            return [NSString stringWithFormat: NSLocalizedString(@"Checking existing data (%.2f%%)",
1037                                    "Torrent -> status string"), 100.0 * fStat->recheckProgress];
1038            break;
1039       
1040        case TR_STATUS_CHECK_WAIT:
1041            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1042            break;
1043
1044        case TR_STATUS_DOWNLOAD:
1045            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1046            break;
1047
1048        case TR_STATUS_SEED:
1049        case TR_STATUS_DONE:
1050            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1051            break;
1052       
1053        default:
1054            return NSLocalizedString(@"N/A", "Torrent -> status string");
1055    }
1056}
1057
1058- (int) seeders
1059{
1060    return fStat->seeders;
1061}
1062
1063- (int) leechers
1064{
1065    return fStat->leechers;
1066}
1067
1068- (int) completedFromTracker
1069{
1070    return fStat->completedFromTracker;
1071}
1072
1073- (int) totalPeersConnected
1074{
1075    return fStat->peersConnected;
1076}
1077
1078- (int) totalPeersTracker
1079{
1080    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1081}
1082
1083- (int) totalPeersIncoming
1084{
1085    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1086}
1087
1088- (int) totalPeersCache
1089{
1090    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1091}
1092
1093- (int) totalPeersPex
1094{
1095    return fStat->peersFrom[TR_PEER_FROM_PEX];
1096}
1097
1098- (int) peersSendingToUs
1099{
1100    return fStat->peersSendingToUs;
1101}
1102
1103- (int) peersGettingFromUs
1104{
1105    return fStat->peersGettingFromUs;
1106}
1107
1108- (float) downloadRate
1109{
1110    return fStat->rateDownload;
1111}
1112
1113- (float) uploadRate
1114{
1115    return fStat->rateUpload;
1116}
1117
1118- (uint64_t) haveVerified
1119{
1120    return fStat->haveValid;
1121}
1122
1123- (uint64_t) haveTotal
1124{
1125    return [self haveVerified] + fStat->haveUnchecked;
1126}
1127
1128- (uint64_t) downloadedTotal
1129{
1130    return fStat->downloadedEver;
1131}
1132
1133- (uint64_t) uploadedTotal
1134{
1135    return fStat->uploadedEver;
1136}
1137
1138- (uint64_t) failedHash
1139{
1140    return fStat->corruptEver;
1141}
1142
1143- (float) swarmSpeed
1144{
1145    return fStat->swarmspeed;
1146}
1147
1148- (BOOL) pex
1149{
1150        return fPex;
1151}
1152
1153- (void) setPex: (BOOL) setting
1154{
1155        if (![self privateTorrent])
1156        {
1157                fPex = setting;
1158                tr_torrentDisablePex(fHandle, !setting);
1159        }
1160}
1161
1162- (NSNumber *) orderValue
1163{
1164    return [NSNumber numberWithInt: fOrderValue];
1165}
1166
1167- (void) setOrderValue: (int) orderValue
1168{
1169    fOrderValue = orderValue;
1170}
1171
1172- (NSArray *) fileList
1173{
1174    return fFileList;
1175}
1176
1177- (int) fileCount
1178{
1179    return fInfo->fileCount;
1180}
1181
1182- (void) updateFileStat
1183{
1184    if (fileStat)
1185        tr_torrentFilesFree(fileStat, [self fileCount]);
1186   
1187    int count;
1188    fileStat = tr_torrentFiles(fHandle, &count);
1189}
1190
1191- (float) fileProgress: (int) index
1192{
1193    if (!fileStat)
1194        [self updateFileStat];
1195       
1196    return fileStat[index].progress;
1197}
1198
1199- (BOOL) canChangeDownloadCheckForFile: (int) index
1200{
1201    if (!fileStat)
1202        [self updateFileStat];
1203   
1204    return [self fileCount] > 1 && fileStat[index].completionStatus != TR_CP_COMPLETE;
1205}
1206
1207- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1208{
1209    if ([self fileCount] <= 1 || [self isComplete])
1210        return NO;
1211   
1212    if (!fileStat)
1213        [self updateFileStat];
1214   
1215    int index;
1216    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1217        if (fileStat[index].completionStatus != TR_CP_COMPLETE)
1218            return YES;
1219    return NO;
1220}
1221
1222- (int) checkForFiles: (NSIndexSet *) indexSet
1223{
1224    BOOL onState = NO, offState = NO;
1225    int index;
1226    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1227    {
1228        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFile: index])
1229            onState = YES;
1230        else
1231            offState = YES;
1232       
1233        if (onState && offState)
1234            return NSMixedState;
1235    }
1236    return onState ? NSOnState : NSOffState;
1237}
1238
1239- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
1240{
1241    int count = [indexSet count], i = 0, index;
1242    int * files = malloc(count * sizeof(int));
1243    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1244    {
1245        files[i] = index;
1246        i++;
1247    }
1248   
1249    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1250    free(files);
1251   
1252    [self update];
1253}
1254
1255- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1256{
1257    int count = [indexSet count], i = 0, index;
1258    int * files = malloc(count * sizeof(int));
1259    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1260    {
1261        files[i] = index;
1262        i++;
1263    }
1264
1265    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1266    free(files);
1267}
1268
1269- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1270{
1271    int index;
1272    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1273        if (priority == tr_torrentGetFilePriority(fHandle, index) && [self canChangeDownloadCheckForFile: index])
1274            return YES;
1275    return NO;
1276}
1277
1278- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1279{
1280    BOOL low = NO, normal = NO, high = NO;
1281    NSMutableSet * priorities = [NSMutableSet setWithCapacity: 3];
1282   
1283    int index, priority;
1284    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1285    {
1286        if (![self canChangeDownloadCheckForFile: index])
1287            continue;
1288       
1289        priority = tr_torrentGetFilePriority(fHandle, index);
1290        if (priority == TR_PRI_LOW)
1291        {
1292            if (low)
1293                continue;
1294            low = YES;
1295        }
1296        else if (priority == TR_PRI_HIGH)
1297        {
1298            if (high)
1299                continue;
1300            high = YES;
1301        }
1302        else
1303        {
1304            if (normal)
1305                continue;
1306            normal = YES;
1307        }
1308       
1309        [priorities addObject: [NSNumber numberWithInt: priority]];
1310        if (low && normal && high)
1311            break;
1312    }
1313    return priorities;
1314}
1315
1316- (NSMenu *) fileMenu
1317{
1318    if (!fFileMenu)
1319    {
1320        fFileMenu = [[NSMenu alloc] initWithTitle: [@"TorrentMenu:" stringByAppendingString: [self name]]];
1321        [fFileMenu setAutoenablesItems: NO];
1322    }
1323    return fFileMenu;
1324}
1325
1326- (NSDate *) dateAdded
1327{
1328    return fDateAdded;
1329}
1330
1331- (NSDate *) dateCompleted
1332{
1333    return fDateCompleted;
1334}
1335
1336- (NSDate *) dateActivity
1337{
1338    uint64_t date = fStat->activityDate;
1339    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1340}
1341
1342- (int) stalledMinutes
1343{
1344    uint64_t start;
1345    if ((start = fStat->startDate) == 0)
1346        return -1;
1347   
1348    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1349            * activity = [self dateActivity];
1350   
1351    NSDate * laterDate = (!activity || [started compare: activity] == NSOrderedDescending) ? started : activity;
1352    return -1 * [laterDate timeIntervalSinceNow] / 60;
1353}
1354
1355- (BOOL) isStalled
1356{
1357    return fStalled;
1358}
1359
1360- (NSNumber *) stateSortKey
1361{
1362    if (![self isActive])
1363        return [NSNumber numberWithInt: 0];
1364    else if ([self isSeeding])
1365        return [NSNumber numberWithInt: 1];
1366    else
1367        return [NSNumber numberWithInt: 2];
1368}
1369
1370- (NSNumber *) progressSortKey
1371{
1372    return [NSNumber numberWithFloat: [self progress]];
1373}
1374
1375- (NSNumber *) ratioSortKey
1376{
1377    return [NSNumber numberWithFloat: [self ratio]];
1378}
1379
1380- (NSNumber *) ratioProgressSortKey
1381{
1382    return [NSNumber numberWithFloat: [self progressStopRatio]];
1383}
1384
1385- (int) torrentID
1386{
1387    return fID;
1388}
1389
1390- (const tr_info *) torrentInfo
1391{
1392    return fInfo;
1393}
1394
1395- (const tr_stat *) torrentStat
1396{
1397    return fStat;
1398}
1399
1400@end
1401
1402@implementation Torrent (Private)
1403
1404//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1405- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle *) lib
1406        publicTorrent: (NSNumber *) publicTorrent
1407        downloadFolder: (NSString *) downloadFolder
1408        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1409        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
1410        dateActivity: (NSDate *) dateActivity
1411        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
1412                pex: (NSNumber *) pex
1413        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;
1414{
1415    if (!(self = [super init]))
1416        return nil;
1417   
1418    static_lastid++;
1419    fID = static_lastid;
1420   
1421    fLib = lib;
1422    fDefaults = [NSUserDefaults standardUserDefaults];
1423
1424    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1425    if (fPublicTorrent)
1426        fPublicTorrentLocation = [path retain];
1427   
1428    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1429    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1430   
1431    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1432                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1433    if (fUseIncompleteFolder)
1434    {
1435        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1436        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1437    }
1438   
1439    NSString * currentDownloadFolder;
1440    tr_info info;
1441    int error;
1442    if (hashString)
1443    {
1444        if (tr_torrentParseHash(fLib, [hashString UTF8String], NULL, &info) == TR_OK)
1445        {
1446            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1447                                        ? fIncompleteFolder : fDownloadFolder;
1448            fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], [currentDownloadFolder UTF8String],
1449                                            TR_FLAG_SAVE | TR_FLAG_PAUSED, &error);
1450        }
1451        tr_metainfoFree(&info);
1452    }
1453    if (!fHandle && path)
1454    {
1455        if (tr_torrentParse(fLib, [path UTF8String], NULL, &info) == TR_OK)
1456        {
1457            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1458                                        ? fIncompleteFolder : fDownloadFolder;
1459            fHandle = tr_torrentInit(fLib, [path UTF8String], [currentDownloadFolder UTF8String],
1460                                        TR_FLAG_SAVE | TR_FLAG_PAUSED, &error);
1461        }
1462        tr_metainfoFree(&info);
1463    }
1464    if (!fHandle)
1465    {
1466        [self release];
1467        return nil;
1468    }
1469   
1470    tr_torrentSetStatusCallback(fHandle, completenessChangeCallback, self);
1471   
1472    fInfo = tr_torrentInfo(fHandle);
1473
1474    fDateAdded = dateAdded ? [dateAdded retain] : [[NSDate alloc] init];
1475        if (dateCompleted)
1476                fDateCompleted = [dateCompleted retain];
1477    if (dateActivity)
1478                fDateActivity = [dateActivity retain];
1479       
1480    fRatioSetting = ratioSetting ? [ratioSetting intValue] : NSMixedState;
1481    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1482    fFinishedSeeding = NO;
1483       
1484        if ([self privateTorrent])
1485                fPex = NO;
1486        else
1487                fPex = pex ? [pex boolValue] : YES;
1488        tr_torrentDisablePex(fHandle, !fPex);
1489   
1490    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1491    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1492    fError = NO;
1493   
1494    [self createFileList];
1495   
1496    [self update];
1497    return self;
1498}
1499
1500- (void) createFileList
1501{
1502    int count = [self fileCount], i;
1503    tr_file * file;
1504    NSMutableArray * pathComponents;
1505    NSString * path;
1506   
1507    NSMutableArray * fileList = [[NSMutableArray alloc] initWithCapacity: count];
1508   
1509    for (i = 0; i < count; i++)
1510    {
1511        file = &fInfo->files[i];
1512       
1513        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1514        if (fInfo->multifile)
1515        {
1516            path = [pathComponents objectAtIndex: 0];
1517            [pathComponents removeObjectAtIndex: 0];
1518        }
1519        else
1520            path = @"";
1521       
1522        [self insertPath: pathComponents forSiblings: fileList withParent: nil previousPath: path fileSize: file->length index: i];
1523        [pathComponents autorelease];
1524    }
1525   
1526    fFileList = [[NSArray alloc] initWithArray: fileList];
1527    [fileList release];
1528}
1529
1530- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings withParent: (NSMutableDictionary *) parent
1531            previousPath: (NSString *) previousPath fileSize: (uint64_t) size index: (int) index
1532{
1533    NSString * name = [components objectAtIndex: 0];
1534    BOOL isFolder = [components count] > 1;
1535   
1536    NSMutableDictionary * dict = nil;
1537    if (isFolder)
1538    {
1539        NSEnumerator * enumerator = [siblings objectEnumerator];
1540        while ((dict = [enumerator nextObject]))
1541            if ([[dict objectForKey: @"IsFolder"] boolValue] && [[dict objectForKey: @"Name"] isEqualToString: name])
1542                break;
1543    }
1544   
1545    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1546   
1547    //create new folder or item if it doesn't already exist
1548    if (!dict)
1549    {
1550        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1551                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1552        [siblings addObject: dict];
1553       
1554        if (isFolder)
1555        {
1556            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1557            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1558        }
1559        else
1560        {
1561            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1562            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1563           
1564            NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]];
1565            [icon setFlipped: YES];
1566            [dict setObject: icon forKey: @"Icon"];
1567        }
1568       
1569        if (parent)
1570            [dict setObject: parent forKey: @"Parent"];
1571    }
1572    else
1573        [[dict objectForKey: @"Indexes"] addIndex: index];
1574   
1575    if (isFolder)
1576    {
1577        [components removeObjectAtIndex: 0];
1578        [self insertPath: components forSiblings: [dict objectForKey: @"Children"] withParent: dict
1579                previousPath: currentPath fileSize: size index: index];
1580    }
1581}
1582
1583- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1584{
1585    return fUseIncompleteFolder &&
1586        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1587}
1588
1589- (void) updateDownloadFolder
1590{
1591    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1592    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1593}
1594
1595//status has been retained
1596- (void) completenessChange: (NSNumber *) status
1597{
1598    [self update];
1599   
1600    BOOL canMove;
1601    switch ([status intValue])
1602    {
1603        case TR_CP_DONE:
1604        case TR_CP_COMPLETE:
1605            canMove = YES;
1606       
1607            //move file from incomplete folder to download folder
1608            if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
1609                && (canMove = [self alertForMoveFolderAvailable]))
1610            {
1611                [self quickPause];
1612               
1613                if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
1614                                        toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
1615                    [self updateDownloadFolder];
1616                else
1617                    canMove = NO;
1618               
1619                [self endQuickPause];
1620            }
1621           
1622            if (!canMove)
1623            {
1624                fUseIncompleteFolder = NO;
1625               
1626                [fDownloadFolder release];
1627                fDownloadFolder = fIncompleteFolder;
1628                fIncompleteFolder = nil;
1629            }
1630           
1631            [fDateCompleted release];
1632            fDateCompleted = [[NSDate alloc] init];
1633           
1634            fStat = tr_torrentStat(fHandle);
1635            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
1636            break;
1637       
1638        case TR_CP_INCOMPLETE:
1639            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1640            break;
1641    }
1642    [status release];
1643}
1644
1645- (void) quickPause
1646{
1647    if (fQuickPauseDict)
1648        return;
1649
1650    fQuickPauseDict = [[NSDictionary alloc] initWithObjectsAndKeys:
1651                    [NSNumber numberWithInt: [self speedMode: YES]], @"UploadSpeedMode",
1652                    [NSNumber numberWithInt: [self speedLimit: YES]], @"UploadSpeedLimit",
1653                    [NSNumber numberWithInt: [self speedMode: NO]], @"DownloadSpeedMode",
1654                    [NSNumber numberWithInt: [self speedLimit: NO]], @"DownloadSpeedLimit", nil];
1655   
1656    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: YES];
1657    [self setSpeedLimit: 0 upload: YES];
1658    [self setSpeedMode: TR_SPEEDLIMIT_SINGLE upload: NO];
1659    [self setSpeedLimit: 0 upload: NO];
1660}
1661
1662- (void) endQuickPause
1663{
1664    if (!fQuickPauseDict)
1665        return;
1666   
1667    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"UploadSpeedMode"] intValue] upload: YES];
1668    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"UploadSpeedLimit"] intValue] upload: YES];
1669    [self setSpeedMode: [[fQuickPauseDict objectForKey: @"DownloadSpeedMode"] intValue] upload: NO];
1670    [self setSpeedLimit: [[fQuickPauseDict objectForKey: @"DownloadSpeedLimit"] intValue] upload: NO];
1671   
1672    [fQuickPauseDict release];
1673    fQuickPauseDict = nil;
1674}
1675
1676- (void) trashFile: (NSString *) path
1677{
1678    //attempt to move to trash
1679    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1680            source: [path stringByDeletingLastPathComponent] destination: @""
1681            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1682    {
1683        //if cannot trash, just delete it (will work if it is on a remote volume)
1684        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1685            NSLog(@"Could not trash %@", path);
1686    }
1687}
1688
1689@end
Note: See TracBrowser for help on using the repository browser.