source: trunk/macosx/Torrent.m @ 1256

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

display actual upload/download rate in inspector for individual peers

  • Property svn:keywords set to Date Rev Author Id
File size: 38.0 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 1256 2006-12-18 06:25:40Z 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        date: (NSDate *) date stopRatioSetting: (NSNumber *) stopRatioSetting
38        ratioLimit: (NSNumber *) ratioLimit waitToStart: (NSNumber *) waitToStart
39        orderValue: (NSNumber *) orderValue;
40
41- (NSImage *) advancedBar;
42
43- (void) trashFile: (NSString *) path;
44
45@end
46
47@implementation Torrent
48
49// Used to optimize drawing. They contain packed RGBA pixels for every color needed.
50#define BE OSSwapBigToHostConstInt32
51
52static uint32_t kRed   = BE(0xFF6450FF), //255, 100, 80
53                kBlue = BE(0x50A0FFFF), //80, 160, 255
54                kBlue2 = BE(0x1E46B4FF), //30, 70, 180
55                kGray  = BE(0x969696FF), //150, 150, 150
56                kGreen1 = BE(0x99FFCCFF), //153, 255, 204
57                kGreen2 = BE(0x66FF99FF), //102, 255, 153
58                kGreen3 = BE(0x00FF66FF), //0, 255, 102
59                kWhite = BE(0xFFFFFFFF); //255, 255, 255
60
61- (id) initWithPath: (NSString *) path forceDeleteTorrent: (BOOL) delete lib: (tr_handle_t *) lib
62{
63    self = [self initWithHash: nil path: path lib: lib
64            publicTorrent: delete ? [NSNumber numberWithBool: NO] : nil
65            date: nil stopRatioSetting: nil ratioLimit: nil waitToStart: nil orderValue: nil];
66   
67    if (self)
68    {
69        fUseIncompleteFolder = [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
70        fIncompleteFolder = [[fDefaults stringForKey: @"IncompleteDownloadFolder"] copy];
71       
72        if (!fPublicTorrent)
73            [self trashFile: path];
74    }
75    return self;
76}
77
78- (id) initWithHistory: (NSDictionary *) history lib: (tr_handle_t *) lib
79{
80    self = [self initWithHash: [history objectForKey: @"TorrentHash"]
81                path: [history objectForKey: @"TorrentPath"] lib: lib
82                publicTorrent: [history objectForKey: @"PublicCopy"]
83                date: [history objectForKey: @"Date"]
84                stopRatioSetting: [history objectForKey: @"StopRatioSetting"]
85                ratioLimit: [history objectForKey: @"RatioLimit"]
86                waitToStart: [history objectForKey: @"WaitToStart"]
87                orderValue: [history objectForKey: @"OrderValue"]];
88   
89    if (self)
90    {
91        //download folders
92        NSString * downloadFolder;
93        if (!(downloadFolder = [history objectForKey: @"DownloadFolder"]))
94            downloadFolder = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
95       
96        NSNumber * useIncompleteFolder;
97        if ((useIncompleteFolder = [history objectForKey: @"UseIncompleteFolder"]))
98        {
99            if ((fUseIncompleteFolder = [useIncompleteFolder boolValue]))
100            {
101                NSString * incompleteFolder;
102                if (incompleteFolder = [history objectForKey: @"IncompleteFolder"])
103                    fIncompleteFolder = [incompleteFolder copy];
104                else
105                    fIncompleteFolder = [[[fDefaults stringForKey: @"IncompleteDownloadFolder"]
106                                            stringByExpandingTildeInPath] copy];
107            }
108        }
109        else
110            fUseIncompleteFolder = NO;
111       
112        [self setDownloadFolder: downloadFolder];
113
114        NSString * paused;
115        if (!(paused = [history objectForKey: @"Paused"]) || [paused isEqualToString: @"NO"])
116        {
117            fStat = tr_torrentStat(fHandle);
118            [self startTransfer];
119        }
120    }
121    return self;
122}
123
124- (NSDictionary *) history
125{
126    NSMutableDictionary * history = [NSMutableDictionary dictionaryWithObjectsAndKeys:
127                    [NSNumber numberWithBool: fPublicTorrent], @"PublicCopy",
128                    [self hashString], @"TorrentHash",
129                    fDownloadFolder, @"DownloadFolder",
130                    [NSNumber numberWithBool: fUseIncompleteFolder], @"UseIncompleteFolder",
131                    [self isActive] ? @"NO" : @"YES", @"Paused",
132                    [self date], @"Date",
133                    [NSNumber numberWithInt: fStopRatioSetting], @"StopRatioSetting",
134                    [NSNumber numberWithFloat: fRatioLimit], @"RatioLimit",
135                    [NSNumber numberWithBool: fWaitToStart], @"WaitToStart",
136                    [self orderValue], @"OrderValue", nil];
137   
138    if (fUseIncompleteFolder)
139        [history setObject: fIncompleteFolder forKey: @"IncompleteFolder"];
140
141    if (fPublicTorrent)
142        [history setObject: [self publicTorrentLocation] forKey: @"TorrentPath"];
143   
144    return history;
145}
146
147- (void) dealloc
148{
149    if (fHandle)
150    {
151        tr_torrentClose(fLib, fHandle);
152       
153        if (fDownloadFolder)
154            [fDownloadFolder release];
155        if (fIncompleteFolder)
156            [fIncompleteFolder release];
157       
158        if (fPublicTorrentLocation)
159            [fPublicTorrentLocation release];
160       
161        [fDate release];
162       
163        [fIcon release];
164        [fIconFlipped release];
165        [fIconSmall release];
166       
167        [fProgressString release];
168        [fStatusString release];
169        [fShortStatusString release];
170        [fRemainingTimeString release];
171       
172        [fBitmap release];
173        free(fPieces);
174    }
175    [super dealloc];
176}
177
178- (void) setDownloadFolder: (NSString *) path
179{
180    if (path)
181        fDownloadFolder = [path copy];
182   
183    if (!fUseIncompleteFolder || [[NSFileManager defaultManager] fileExistsAtPath:
184                                    [path stringByAppendingPathComponent: [self name]]])
185        tr_torrentSetFolder(fHandle, [path UTF8String]);
186    else
187        tr_torrentSetFolder(fHandle, [fIncompleteFolder UTF8String]);
188}
189
190- (NSString *) downloadFolder
191{
192    return [NSString stringWithUTF8String: tr_torrentGetFolder(fHandle)];
193}
194
195- (void) getAvailability: (int8_t *) tab size: (int) size
196{
197    tr_torrentAvailability(fHandle, tab, size);
198}
199
200- (void) getAmountFinished: (float *) tab size: (int) size
201{
202    tr_torrentAmountFinished(fHandle, tab, size);
203}
204
205- (void) update
206{
207    fStat = tr_torrentStat(fHandle);
208   
209    //notification when downloading finished
210    if ([self justFinished])
211    {
212        BOOL canMove = YES;
213       
214        //move file from incomplete folder to download folder
215        if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
216            && (canMove = [self alertForMoveVolumeAvailable]))
217        {
218            tr_torrentStop(fHandle);
219            if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
220                                    toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
221                tr_torrentSetFolder(fHandle, [fDownloadFolder UTF8String]);
222            tr_torrentStart(fHandle);
223        }
224       
225        if (!canMove)
226        {
227            fUseIncompleteFolder = NO;
228           
229            [fDownloadFolder release];
230            fDownloadFolder = fIncompleteFolder;
231            fIncompleteFolder = nil;
232        }
233       
234        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
235    }
236   
237    //check to stop for ratio
238    if ([self isSeeding] && ((fStopRatioSetting == RATIO_CHECK && [self ratio] >= fRatioLimit)
239            || (fStopRatioSetting == RATIO_GLOBAL && [fDefaults boolForKey: @"RatioCheck"]
240            && [self ratio] >= [fDefaults floatForKey: @"RatioLimit"])))
241    {
242        [self stopTransfer];
243        [self setStopRatioSetting: RATIO_NO_CHECK];
244        fFinishedSeeding = YES;
245       
246        fStat = tr_torrentStat(fHandle);
247       
248        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
249    }
250
251    [fProgressString setString: @""];
252    if ([self progress] < 1.0)
253        [fProgressString appendFormat: NSLocalizedString(@"%@ of %@ (%.2f%%)", "Torrent -> progress string"),
254                            [NSString stringForFileSize: [self downloadedValid]],
255                            [NSString stringForFileSize: [self size]], 100.0 * [self progress]];
256    else
257        [fProgressString appendFormat: NSLocalizedString(@"%@, uploaded %@ (Ratio: %@)", "Torrent -> progress string"),
258                [NSString stringForFileSize: [self size]], [NSString stringForFileSize: [self uploadedTotal]],
259                [NSString stringForRatioWithDownload: [self downloadedTotal] upload: [self uploadedTotal]]];
260
261    switch (fStat->status)
262    {
263        NSString * tempString;
264   
265        case TR_STATUS_PAUSE:
266            if (fFinishedSeeding)
267                tempString = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
268            else if (fWaitToStart)
269                tempString = [NSLocalizedString(@"Waiting to start", "Torrent -> status string") stringByAppendingEllipsis];
270            else
271                tempString = NSLocalizedString(@"Paused", "Torrent -> status string");
272           
273            [fStatusString setString: tempString];
274            [fShortStatusString setString: tempString];
275           
276            break;
277
278        case TR_STATUS_CHECK:
279            tempString = [NSLocalizedString(@"Checking existing files", "Torrent -> status string") stringByAppendingEllipsis];
280           
281            [fStatusString setString: tempString];
282            [fShortStatusString setString: tempString];
283            [fRemainingTimeString setString: tempString];
284           
285            break;
286
287        case TR_STATUS_DOWNLOAD:
288            [fStatusString setString: @""];
289            if ([self totalPeers] > 1)
290                [fStatusString appendFormat: NSLocalizedString(@"Downloading from %d of %d peers",
291                                                "Torrent -> status string"), [self peersUploading], [self totalPeers]];
292            else
293                [fStatusString appendFormat: NSLocalizedString(@"Downloading from %d of %d peer",
294                                                "Torrent -> status string"), [self peersUploading], [self totalPeers]];
295           
296            [fRemainingTimeString setString: @""];
297            int eta = [self eta];
298            if (eta < 0)
299            {
300                [fRemainingTimeString setString: NSLocalizedString(@"Unknown", "Torrent -> remaining time")];
301                [fProgressString appendString: NSLocalizedString(@" - remaining time unknown", "Torrent -> progress string")];
302            }
303            else
304            {
305                if (eta < 60)
306                    [fRemainingTimeString appendFormat: NSLocalizedString(@"%d sec", "Torrent -> remaining time"), eta];
307                else if (eta < 3600) //60 * 60
308                    [fRemainingTimeString appendFormat: NSLocalizedString(@"%d min %02d sec", "Torrent -> remaining time"),
309                                                            eta / 60, eta % 60];
310                else if (eta < 86400) //24 * 60 * 60
311                    [fRemainingTimeString appendFormat: NSLocalizedString(@"%d hr %02d min", "Torrent -> remaining time"),
312                                                            eta / 3600, (eta / 60) % 60];
313                else
314                {
315                    if (eta / 86400 > 1)
316                        [fRemainingTimeString appendFormat: NSLocalizedString(@"%d days %d hr", "Torrent -> remaining time"),
317                                                                eta / 86400, (eta / 3600) % 24];
318                    else
319                        [fRemainingTimeString appendFormat: NSLocalizedString(@"%d day %d hr", "Torrent -> remaining time"),
320                                                                eta / 86400, (eta / 3600) % 24];
321                }
322               
323                [fProgressString appendFormat: NSLocalizedString(@" - %@ remaining", "Torrent -> progress string"),
324                                                                    fRemainingTimeString];
325            }
326           
327            break;
328
329        case TR_STATUS_SEED:
330            [fStatusString setString: @""];
331            if ([self totalPeers] > 1)
332                [fStatusString appendFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
333                                                [self peersDownloading], [self totalPeers]];
334            else
335                [fStatusString appendFormat: NSLocalizedString(@"Seeding to %d of %d peer", "Torrent -> status string"),
336                                                [self peersDownloading], [self totalPeers]];
337           
338            break;
339
340        case TR_STATUS_STOPPING:
341            tempString = [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
342       
343            [fStatusString setString: tempString];
344            [fShortStatusString setString: tempString];
345           
346            break;
347    }
348   
349    if (fStat->error & TR_ETRACKER)
350    {
351        [fStatusString setString: [NSLocalizedString(@"Error: ", "Torrent -> status string") stringByAppendingString:
352                                    [self errorMessage]]];
353    }
354   
355    BOOL wasError = fError;
356    if ((fError = fStat->cannotConnect))
357    {
358        if (!wasError && [self isActive] && ![self isSeeding])
359            [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
360    }
361
362    if ([self isActive] && fStat->status != TR_STATUS_CHECK )
363    {
364        NSString * stringToAppend = @"";
365        if ([self progress] < 1.0)
366        {
367            stringToAppend = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, ", "Torrent -> status string"),
368                                [NSString stringForSpeed: [self downloadRate]]];
369            [fShortStatusString setString: @""];
370        }
371        else
372        {
373            NSString * ratioString = [NSString stringForRatioWithDownload: [self downloadedTotal]
374                                                upload: [self uploadedTotal]];
375       
376            [fShortStatusString setString: [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, ",
377                                            "Torrent -> status string"), ratioString]];
378            [fRemainingTimeString setString: [NSLocalizedString(@"Ratio: ", "Torrent -> status string")
379                                                stringByAppendingString: ratioString]];
380        }
381       
382        stringToAppend = [stringToAppend stringByAppendingString: [NSLocalizedString(@"UL: ", "Torrent -> status string")
383                                            stringByAppendingString: [NSString stringForSpeed: [self uploadRate]]]];
384
385        [fStatusString appendFormat: @" - %@", stringToAppend];
386        [fShortStatusString appendString: stringToAppend];
387    }
388}
389
390- (NSDictionary *) infoForCurrentView
391{
392    NSMutableDictionary * info = [NSMutableDictionary dictionaryWithObjectsAndKeys:
393                                    [self name], @"Name",
394                                    [NSNumber numberWithBool: [self isSeeding]], @"Seeding",
395                                    [NSNumber numberWithFloat: [self progress]], @"Progress",
396                                    [NSNumber numberWithBool: [self isActive]], @"Active",
397                                    [NSNumber numberWithBool: [self isError]], @"Error", nil];
398   
399    if (![fDefaults boolForKey: @"SmallView"])
400    {
401        [info setObject: fIconFlipped forKey: @"Icon"];
402        [info setObject: [self progressString] forKey: @"ProgressString"];
403        [info setObject: [self statusString] forKey: @"StatusString"];
404    }
405    else
406    {
407        [info setObject: fIconSmall forKey: @"Icon"];
408        [info setObject: [self remainingTimeString] forKey: @"RemainingTimeString"];
409        [info setObject: [self shortStatusString] forKey: @"ShortStatusString"];
410    }
411   
412    if ([fDefaults boolForKey: @"UseAdvancedBar"])
413        [info setObject: [self advancedBar] forKey: @"AdvancedBar"];
414   
415    return info;
416}
417
418- (void) startTransfer
419{
420    fWaitToStart = NO;
421    fFinishedSeeding = NO;
422   
423    if (![self isActive] && [self alertForVolumeAvailable] && [self alertForRemainingDiskSpace])
424        tr_torrentStart(fHandle);
425}
426
427- (void) stopTransfer
428{
429    fError = NO;
430   
431    if ([self isActive])
432    {
433        BOOL wasSeeding = [self isSeeding];
434   
435        tr_torrentStop(fHandle);
436
437        if (!wasSeeding)
438            [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
439    }
440}
441
442- (void) stopTransferForQuit
443{
444    if ([self isActive])
445        tr_torrentStop(fHandle);
446}
447
448- (void) removeForever
449{
450    tr_torrentRemoveSaved(fHandle);
451}
452
453- (void) sleep
454{
455    if ((fResumeOnWake = [self isActive]))
456        tr_torrentStop(fHandle);
457}
458
459- (void) wakeUp
460{
461    if (fResumeOnWake)
462        tr_torrentStart(fHandle);
463}
464
465- (float) ratio
466{
467    float downloaded = [self downloadedTotal];
468    return downloaded > 0 ? (float)[self uploadedTotal] / downloaded : -1;
469}
470
471- (int) stopRatioSetting
472{
473        return fStopRatioSetting;
474}
475
476- (void) setStopRatioSetting: (int) setting
477{
478    fStopRatioSetting = setting;
479}
480
481- (float) ratioLimit
482{
483    return fRatioLimit;
484}
485
486- (void) setRatioLimit: (float) limit
487{
488    if (limit >= 0)
489        fRatioLimit = limit;
490}
491
492- (void) setWaitToStart: (BOOL) wait
493{
494    fWaitToStart = wait;
495}
496
497- (BOOL) waitingToStart
498{
499    return fWaitToStart;
500}
501
502- (void) revealData
503{
504    [[NSWorkspace sharedWorkspace] selectFile: [self dataLocation] inFileViewerRootedAtPath: nil];
505}
506
507- (void) revealPublicTorrent
508{
509    if (fPublicTorrent)
510        [[NSWorkspace sharedWorkspace] selectFile: fPublicTorrentLocation inFileViewerRootedAtPath: nil];
511}
512
513- (void) trashData
514{
515    [self trashFile: [self dataLocation]];
516}
517
518- (void) trashTorrent
519{
520    if (fPublicTorrent)
521        [self trashFile: [self publicTorrentLocation]];
522}
523
524- (BOOL) alertForRemainingDiskSpace
525{
526    if ([self progress] >= 1.0)
527        return YES;
528   
529    NSString * volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: [self downloadFolder]]
530                                                                                                objectAtIndex: 0];
531    NSDictionary * fsAttributes = [[NSFileManager defaultManager] fileSystemAttributesAtPath: [self downloadFolder]];
532    uint64_t remainingSpace = [[fsAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue],
533            torrentRemaining = [self size] - (uint64_t)[self downloadedValid];
534   
535    /*NSLog(@"Volume: %@", volumeName);
536    NSLog(@"Remaining disk space: %qu (%@)", remainingSpace, [NSString stringForFileSize: remainingSpace]);
537    NSLog(@"Torrent remaining size: %qu (%@)", torrentRemaining, [NSString stringForFileSize: torrentRemaining]);*/
538   
539    if (volumeName && remainingSpace <= torrentRemaining)
540    {
541        NSAlert * alert = [[NSAlert alloc] init];
542        [alert setMessageText: [NSString stringWithFormat:
543                                NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
544                                    "Torrent file disk space alert -> title"), [self name]]];
545        [alert setInformativeText: [NSString stringWithFormat:
546                        NSLocalizedString(@"The transfer will be paused. Clear up space on \"%@\" to continue.",
547                                            "Torrent file disk space alert -> message"), volumeName]];
548        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file disk space alert -> button")];
549        [alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent file disk space alert -> button")];
550       
551        if ([alert runModal] == NSAlertFirstButtonReturn)
552        {
553            [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
554            return NO;
555        }
556        else
557            return YES;
558       
559        [alert release];
560    }
561    return YES;
562}
563
564- (BOOL) alertForVolumeAvailable
565{
566    NSArray * pathComponents = [[self downloadFolder] pathComponents];
567    if ([pathComponents count] < 3)
568        return YES;
569   
570    NSString * volume = [[[pathComponents objectAtIndex: 0] stringByAppendingPathComponent:
571                    [pathComponents objectAtIndex: 1]] stringByAppendingPathComponent: [pathComponents objectAtIndex: 2]];
572   
573    /*NSLog(@"%@", [self downloadFolder]);
574    NSLog(@"Volume: %@", volume);*/
575   
576    if (![[NSFileManager defaultManager] fileExistsAtPath: volume])
577    {
578        NSString * volumeName = [pathComponents objectAtIndex: 2];
579       
580        NSAlert * alert = [[NSAlert alloc] init];
581        [alert setMessageText: [NSString stringWithFormat:
582                                NSLocalizedString(@"The volume for downloading \"%@\" cannot be found.",
583                                    "Volume cannot be found alert -> title"), [self name]]];
584        [alert setInformativeText: [NSString stringWithFormat:
585                        NSLocalizedString(@"The transfer will be paused. Mount the volume \"%@\" to continue.",
586                                            "Volume cannot be found alert -> message"), volumeName]];
587        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Volume cannot be found alert -> button")];
588        [alert addButtonWithTitle: [NSLocalizedString(@"Choose New Directory",
589                                    "Volume cannot be found alert -> directory button") stringByAppendingEllipsis]];
590       
591        if ([alert runModal] == NSAlertFirstButtonReturn)
592            [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
593        else
594        {
595            NSOpenPanel * panel = [NSOpenPanel openPanel];
596           
597            [panel setPrompt: @"Select"];
598            [panel setAllowsMultipleSelection: NO];
599            [panel setCanChooseFiles: NO];
600            [panel setCanChooseDirectories: YES];
601
602            [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
603                                "Open torrent -> select destination folder"), [self name]]];
604           
605            [[NSNotificationCenter defaultCenter] postNotificationName: @"MakeWindowKey" object: nil];
606            [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: [NSApp keyWindow] modalDelegate: self
607                    didEndSelector: @selector(destinationChoiceClosed:returnCode:contextInfo:) contextInfo: nil];
608        }
609       
610        [alert release];
611       
612        return NO;
613    }
614   
615    return YES;
616}
617
618- (void) destinationChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) context
619{
620    NSString * folder = [[openPanel filenames] objectAtIndex: 0];
621    if (code == NSOKButton)
622    {
623        if (fUseIncompleteFolder)
624        {
625            [fIncompleteFolder release];
626            fIncompleteFolder = [folder retain];
627            [self setDownloadFolder: nil];
628        }
629        else
630        {
631            [fDownloadFolder release];
632            fDownloadFolder = folder;
633            [self setDownloadFolder: fDownloadFolder];
634        }
635       
636        [self startTransfer];
637        [self update];
638       
639        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateInfoSettings" object: nil];
640    }
641    else
642        [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
643}
644
645- (BOOL) alertForMoveVolumeAvailable
646{
647    NSArray * pathComponents = [fDownloadFolder pathComponents];
648    if ([pathComponents count] < 3)
649        return YES;
650   
651    NSString * volume = [[[pathComponents objectAtIndex: 0] stringByAppendingPathComponent:
652                    [pathComponents objectAtIndex: 1]] stringByAppendingPathComponent: [pathComponents objectAtIndex: 2]];
653   
654    /*NSLog(@"%@", [self downloadFolder]);
655    NSLog(@"Volume: %@", volume);*/
656   
657    if (![[NSFileManager defaultManager] fileExistsAtPath: volume])
658    {
659        NSAlert * alert = [[NSAlert alloc] init];
660        [alert setMessageText: [NSString stringWithFormat:
661                                NSLocalizedString(@"The volume for moving the completed \"%@\" cannot be found.",
662                                    "Move volume cannot be found alert -> title"), [self name]]];
663        [alert setInformativeText: NSLocalizedString(@"The file will remain in its current location",
664                                    "Move volume cannot be found alert -> message")];
665        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move volume cannot be found alert -> button")];
666       
667        [alert runModal];
668        [alert release];
669       
670        return NO;
671    }
672   
673    return YES;
674}
675
676- (NSImage *) icon
677{
678    return fIcon;
679}
680
681- (NSImage *) iconFlipped
682{
683    return fIconFlipped;
684}
685
686- (NSImage *) iconSmall
687{
688    return fIconSmall;
689}
690
691- (NSString *) name
692{
693    return [NSString stringWithUTF8String: fInfo->name];
694}
695
696- (uint64_t) size
697{
698    return fInfo->totalSize;
699}
700
701- (NSString *) tracker
702{
703    return [NSString stringWithFormat: @"http://%s:%d", fStat->trackerAddress, fStat->trackerPort];
704}
705
706- (NSString *) announce
707{
708    return [NSString stringWithUTF8String: fStat->trackerAnnounce];
709}
710
711- (NSString *) comment
712{
713    return [NSString stringWithUTF8String: fInfo->comment];
714}
715
716- (NSString *) creator
717{
718    return [NSString stringWithUTF8String: fInfo->creator];
719}
720
721- (NSDate *) dateCreated
722{
723    int date = fInfo->dateCreated;
724    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
725}
726
727- (int) pieceSize
728{
729    return fInfo->pieceSize;
730}
731
732- (int) pieceCount
733{
734    return fInfo->pieceCount;
735}
736
737- (NSString *) hashString
738{
739    return [NSString stringWithUTF8String: fInfo->hashString];
740}
741
742- (BOOL) privateTorrent
743{
744    return fInfo->privateTorrent;
745}
746
747- (NSString *) torrentLocation
748{
749    return [NSString stringWithUTF8String: fInfo->torrent];
750}
751
752- (NSString *) publicTorrentLocation
753{
754    return fPublicTorrentLocation;
755}
756
757- (NSString *) dataLocation
758{
759    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
760}
761
762- (BOOL) publicTorrent
763{
764    return fPublicTorrent;
765}
766
767- (NSString *) stateString
768{
769    switch( fStat->status )
770    {
771        case TR_STATUS_PAUSE:
772            return NSLocalizedString(@"Paused", "Torrent -> status string");
773            break;
774
775        case TR_STATUS_CHECK:
776            return [NSLocalizedString(@"Checking existing files", "Torrent -> status string") stringByAppendingEllipsis];
777            break;
778
779        case TR_STATUS_DOWNLOAD:
780            return NSLocalizedString(@"Downloading", "Torrent -> status string");
781            break;
782
783        case TR_STATUS_SEED:
784            return NSLocalizedString(@"Seeding", "Torrent -> status string");
785            break;
786
787        case TR_STATUS_STOPPING:
788            return [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
789            break;
790       
791        default:
792            return NSLocalizedString(@"N/A", "Torrent -> status string");
793    }
794}
795
796- (float) progress
797{
798    return fStat->progress;
799}
800
801- (int) eta
802{
803    return fStat->eta;
804}
805
806- (BOOL) isActive
807{
808    return fStat->status & TR_STATUS_ACTIVE;
809}
810
811- (BOOL) isSeeding
812{
813    return fStat->status == TR_STATUS_SEED;
814}
815
816- (BOOL) isPaused
817{
818    return fStat->status == TR_STATUS_PAUSE;
819}
820
821- (BOOL) isError
822{
823    return fStat->error & TR_ETRACKER;
824}
825
826- (NSString *) errorMessage
827{
828    [NSString stringWithUTF8String: fStat->trackerError];
829}
830
831- (BOOL) justFinished
832{
833    return tr_getFinished(fHandle);
834}
835
836- (NSArray *) peers
837{
838    int totalPeers, i;
839    tr_peer_stat_t * peers = tr_torrentPeers(fHandle, & totalPeers);
840   
841    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
842    NSMutableDictionary * dic;
843   
844    tr_peer_stat_t * peer;
845    NSString * client;
846    for (i = 0; i < totalPeers; i++)
847    {
848        peer = &peers[i];
849       
850        dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
851            [NSNumber numberWithBool: peer->isConnected], @"Connected",
852            [NSNumber numberWithBool: peer->isIncoming], @"Incoming",
853            [NSString stringWithCString: (char *) peer->addr encoding: NSUTF8StringEncoding], @"IP",
854            [NSString stringWithCString: (char *) peer->client encoding: NSUTF8StringEncoding], @"Client",
855            [NSNumber numberWithFloat: peer->progress], @"Progress",
856            [NSNumber numberWithBool: peer->isDownloading], @"UL To",
857            [NSNumber numberWithBool: peer->isUploading], @"DL From",
858            [NSNumber numberWithInt: peer->port], @"Port", nil];
859       
860        if (peer->isDownloading)
861            [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
862        if (peer->isUploading)
863            [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
864       
865        [peerDics addObject: dic];
866    }
867   
868    tr_torrentPeersFree(peers, totalPeers);
869   
870    return peerDics;
871}
872
873- (NSString *) progressString
874{
875    return fProgressString;
876}
877
878- (NSString *) statusString
879{
880    return fStatusString;
881}
882
883- (NSString *) shortStatusString
884{
885    return fShortStatusString;
886}
887
888- (NSString *) remainingTimeString
889{
890    return fRemainingTimeString;
891}
892
893- (int) seeders
894{
895    return fStat->seeders;
896}
897
898- (int) leechers
899{
900    return fStat->leechers;
901}
902
903- (int) completedFromTracker
904{
905    return fStat->completedFromTracker;
906}
907
908- (int) totalPeers
909{
910    return fStat->peersTotal;
911}
912
913- (int) totalPeersIncoming
914{
915    return fStat->peersIncoming;
916}
917
918- (int) totalPeersOutgoing
919{
920    return [self totalPeers] - [self totalPeersIncoming];
921}
922
923//peers uploading to you
924- (int) peersUploading
925{
926    return fStat->peersUploading;
927}
928
929//peers downloading from you
930- (int) peersDownloading
931{
932    return fStat->peersDownloading;
933}
934
935- (float) downloadRate
936{
937    return fStat->rateDownload;
938}
939
940- (float) uploadRate
941{
942    return fStat->rateUpload;
943}
944
945- (float) downloadedValid
946{
947    return [self progress] * [self size];
948}
949
950- (uint64_t) downloadedTotal
951{
952    return fStat->downloaded;
953}
954
955- (uint64_t) uploadedTotal
956{
957    return fStat->uploaded;
958}
959
960- (float) swarmSpeed
961{
962    return fStat->swarmspeed;
963}
964
965- (NSNumber *) orderValue
966{
967    return [NSNumber numberWithInt: fOrderValue];
968}
969
970- (void) setOrderValue: (int) orderValue
971{
972    fOrderValue = orderValue;
973}
974
975- (NSArray *) fileList
976{
977    int count = fInfo->fileCount, i;
978    tr_file_t file;
979    NSMutableArray * files = [NSMutableArray arrayWithCapacity: count];
980   
981    for (i = 0; i < count; i++)
982    {
983        file = fInfo->files[i];
984        [files addObject: [NSDictionary dictionaryWithObjectsAndKeys:
985            [[self downloadFolder] stringByAppendingPathComponent: [NSString stringWithUTF8String: file.name]], @"Name",
986            [NSNumber numberWithUnsignedLongLong: file.length], @"Size", nil]];
987    }
988   
989    return files;
990}
991
992- (NSDate *) date
993{
994    return fDate;
995}
996
997- (NSNumber *) stateSortKey
998{
999    if (![self isActive])
1000        return [NSNumber numberWithInt: 0];
1001    else if ([self isSeeding])
1002        return [NSNumber numberWithInt: 1];
1003    else
1004        return [NSNumber numberWithInt: 2];
1005}
1006
1007- (NSNumber *) progressSortKey
1008{
1009    //if finished downloading sort by ratio instead of progress
1010    float progress = [self progress];
1011    return [NSNumber numberWithFloat: progress < 1.0 ? progress : 2.0 + [self ratio]];
1012}
1013
1014@end
1015
1016
1017@implementation Torrent (Private)
1018
1019//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1020- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
1021        publicTorrent: (NSNumber *) publicTorrent
1022        date: (NSDate *) date stopRatioSetting: (NSNumber *) stopRatioSetting
1023        ratioLimit: (NSNumber *) ratioLimit waitToStart: (NSNumber *) waitToStart
1024        orderValue: (NSNumber *) orderValue
1025{
1026    if (!(self = [super init]))
1027        return nil;
1028
1029    fLib = lib;
1030    fDefaults = [NSUserDefaults standardUserDefaults];
1031
1032    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1033    if (fPublicTorrent)
1034        fPublicTorrentLocation = [path retain];
1035
1036    int error;
1037    if (hashString)
1038        fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], TR_FSAVEPRIVATE, & error);
1039   
1040    if (!fHandle && path)
1041        fHandle = tr_torrentInit(fLib, [path UTF8String], TR_FSAVEPRIVATE, & error);
1042
1043    if (!fHandle)
1044    {
1045        [self release];
1046        return nil;
1047    }
1048   
1049    fInfo = tr_torrentInfo( fHandle );
1050
1051    fDate = date ? [date retain] : [[NSDate alloc] init];
1052   
1053    fStopRatioSetting = stopRatioSetting ? [stopRatioSetting intValue] : -1;
1054    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1055    fFinishedSeeding = NO;
1056   
1057    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1058    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1059    fError = NO;
1060   
1061    NSString * fileType = fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr') : [[self name] pathExtension];
1062    fIcon = [[NSWorkspace sharedWorkspace] iconForFileType: fileType];
1063    [fIcon retain];
1064   
1065    fIconFlipped = [fIcon copy];
1066    [fIconFlipped setFlipped: YES];
1067   
1068    fIconSmall = [fIconFlipped copy];
1069    [fIconSmall setScalesWhenResized: YES];
1070    [fIconSmall setSize: NSMakeSize(16.0, 16.0)];
1071
1072    fProgressString = [[NSMutableString alloc] initWithCapacity: 50];
1073    fStatusString = [[NSMutableString alloc] initWithCapacity: 75];
1074    fShortStatusString = [[NSMutableString alloc] initWithCapacity: 30];
1075    fRemainingTimeString = [[NSMutableString alloc] initWithCapacity: 30];
1076   
1077    //set up advanced bar
1078    fBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
1079        pixelsWide: MAX_PIECES pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
1080        isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
1081   
1082    fPieces = malloc(MAX_PIECES);
1083    int i;
1084    for (i = 0; i < MAX_PIECES; i++)
1085        fPieces[i] = BLANK_PIECE;
1086
1087    [self update];
1088    return self;
1089}
1090
1091- (NSImage *) advancedBar
1092{
1093    int h, w;
1094    uint32_t * p;
1095    uint8_t * bitmapData = [fBitmap bitmapData];
1096    int bytesPerRow = [fBitmap bytesPerRow];
1097
1098    int8_t * pieces = malloc(MAX_PIECES);
1099    [self getAvailability: pieces size: MAX_PIECES];
1100   
1101    //lines 2 to 14: blue, green, or gray depending on whether we have the piece or not
1102    int have = 0, avail = 0;
1103    uint32_t color;
1104    BOOL change;
1105    for (w = 0; w < MAX_PIECES; w++)
1106    {
1107        change = NO;
1108        if (pieces[w] < 0)
1109        {
1110            if (fPieces[w] != -1)
1111            {
1112                color = kBlue;
1113                fPieces[w] = -1;
1114                change = YES;
1115            }
1116            have++;
1117        }
1118        else if (pieces[w] == 0)
1119        {
1120            if (fPieces[w] != 0)
1121            {
1122                color = kGray;
1123                fPieces[w] = 0;
1124                change = YES;
1125            }
1126        }
1127        else
1128        {
1129            if (pieces[w] == 1)
1130            {
1131                if (fPieces[w] != 1)
1132                {
1133                    color = kGreen1;
1134                    fPieces[w] = 1;
1135                    change = YES;
1136                }
1137            }
1138            else if (pieces[w] == 2)
1139            {
1140                if (fPieces[w] != 2)
1141                {
1142                    color = kGreen2;
1143                    fPieces[w] = 2;
1144                    change = YES;
1145                }
1146            }
1147            else
1148            {
1149                if (fPieces[w] != 3)
1150                {
1151                    color = kGreen3;
1152                    fPieces[w] = 3;
1153                    change = YES;
1154                }
1155            }
1156            avail++;
1157        }
1158       
1159        if (change)
1160        {
1161            //point to pixel (w, 2) and draw "vertically"
1162            p = (uint32_t *)(bitmapData + 2 * bytesPerRow) + w;
1163            for (h = 2; h < BAR_HEIGHT; h++)
1164            {
1165                p[0] = color;
1166                p = (uint32_t *)((uint8_t *)p + bytesPerRow);
1167            }
1168        }
1169    }
1170   
1171    //first two lines: dark blue to show progression, green to show available
1172    p = (uint32_t *) bitmapData;
1173    for (w = 0; w < have; w++)
1174    {
1175        p[w] = kBlue2;
1176        p[w + bytesPerRow / 4] = kBlue2;
1177    }
1178    for (; w < avail + have; w++)
1179    {
1180        p[w] = kGreen3;
1181        p[w + bytesPerRow / 4] = kGreen3;
1182    }
1183    for (; w < MAX_PIECES; w++)
1184    {
1185        p[w] = kWhite;
1186        p[w + bytesPerRow / 4] = kWhite;
1187    }
1188   
1189    free(pieces);
1190   
1191    //actually draw image
1192    NSImage * bar = [[NSImage alloc] initWithSize: [fBitmap size]];
1193    [bar addRepresentation: fBitmap];
1194    [bar setScalesWhenResized: YES];
1195   
1196    return [bar autorelease];
1197}
1198
1199- (void) trashFile: (NSString *) path
1200{
1201    //attempt to move to trash
1202    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1203            source: [path stringByDeletingLastPathComponent] destination: @""
1204            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1205    {
1206        //if cannot trash, just delete it (will work if it is on a remote volume)
1207        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1208            NSLog(@"Could not trash %@", path);
1209    }
1210}
1211
1212@end
Note: See TracBrowser for help on using the repository browser.