source: trunk/macosx/Torrent.m @ 1866

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

show an error when moving the file fails

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