source: trunk/macosx/Torrent.m @ 1765

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

Disable the stop ratio once it is reached (while seeding).

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