source: trunk/macosx/Torrent.m @ 4183

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

preliminary commit of group code

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