source: trunk/macosx/Torrent.m @ 2230

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

make sure all history items are actually saved, and don't start all transfers at app launch

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