source: trunk/macosx/Torrent.m @ 1618

Last change on this file since 1618 was 1618, checked in by livings124, 16 years ago

Add preference option to disable PEX in Mac frontend.

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