source: trunk/macosx/Torrent.m @ 1149

Last change on this file since 1149 was 1149, checked in by joshe, 15 years ago

Merge scrape branch:

Automatically scrape trackers as needed.
If tracker supplies a trackerid then use it (untested).
Use tracker's min interval, clamped to the same range as interval.
Show total completed downloads in the MacOS X frontend.

  • Property svn:keywords set to Date Rev Author Id
File size: 37.5 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 1149 2006-12-02 01:46:54Z joshe $
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                                    [NSString stringWithUTF8String: fStat->trackerError]]];
353        if (!fError && [self isActive])
354        {
355            fError = YES;
356            if (![self isSeeding])
357                [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
358        }
359    }
360    else
361    {
362        if (fError)
363            fError = NO;
364    }
365
366    if ([self isActive] && fStat->status != TR_STATUS_CHECK )
367    {
368        NSString * stringToAppend = @"";
369        if ([self progress] < 1.0)
370        {
371            stringToAppend = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, ", "Torrent -> status string"),
372                                [NSString stringForSpeed: [self downloadRate]]];
373            [fShortStatusString setString: @""];
374        }
375        else
376        {
377            NSString * ratioString = [NSString stringForRatioWithDownload: [self downloadedTotal]
378                                                upload: [self uploadedTotal]];
379       
380            [fShortStatusString setString: [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, ",
381                                            "Torrent -> status string"), ratioString]];
382            [fRemainingTimeString setString: [NSLocalizedString(@"Ratio: ", "Torrent -> status string")
383                                                stringByAppendingString: ratioString]];
384        }
385       
386        stringToAppend = [stringToAppend stringByAppendingString: [NSLocalizedString(@"UL: ", "Torrent -> status string")
387                                            stringByAppendingString: [NSString stringForSpeed: [self uploadRate]]]];
388
389        [fStatusString appendFormat: @" - %@", stringToAppend];
390        [fShortStatusString appendString: stringToAppend];
391    }
392}
393
394- (NSDictionary *) infoForCurrentView
395{
396    NSMutableDictionary * info = [NSMutableDictionary dictionaryWithObjectsAndKeys:
397                                    [self name], @"Name",
398                                    [NSNumber numberWithBool: [self isSeeding]], @"Seeding",
399                                    [NSNumber numberWithFloat: [self progress]], @"Progress",
400                                    [NSNumber numberWithBool: [self isActive]], @"Active",
401                                    [NSNumber numberWithBool: [self isError]], @"Error", nil];
402   
403    if (![fDefaults boolForKey: @"SmallView"])
404    {
405        [info setObject: fIconFlipped forKey: @"Icon"];
406        [info setObject: [self progressString] forKey: @"ProgressString"];
407        [info setObject: [self statusString] forKey: @"StatusString"];
408    }
409    else
410    {
411        [info setObject: fIconSmall forKey: @"Icon"];
412        [info setObject: [self remainingTimeString] forKey: @"RemainingTimeString"];
413        [info setObject: [self shortStatusString] forKey: @"ShortStatusString"];
414    }
415   
416    if ([fDefaults boolForKey: @"UseAdvancedBar"])
417        [info setObject: [self advancedBar] forKey: @"AdvancedBar"];
418   
419    return info;
420}
421
422- (void) startTransfer
423{
424    fWaitToStart = NO;
425    fFinishedSeeding = NO;
426   
427    if (![self isActive] && [self alertForVolumeAvailable] && [self alertForRemainingDiskSpace])
428        tr_torrentStart(fHandle);
429}
430
431- (void) stopTransfer
432{
433    fError = NO;
434   
435    if ([self isActive])
436    {
437        BOOL wasSeeding = [self isSeeding];
438   
439        tr_torrentStop(fHandle);
440
441        if (!wasSeeding)
442            [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
443    }
444}
445
446- (void) stopTransferForQuit
447{
448    if ([self isActive])
449        tr_torrentStop(fHandle);
450}
451
452- (void) removeForever
453{
454    tr_torrentRemoveSaved(fHandle);
455}
456
457- (void) sleep
458{
459    if ((fResumeOnWake = [self isActive]))
460        tr_torrentStop(fHandle);
461}
462
463- (void) wakeUp
464{
465    if (fResumeOnWake)
466        tr_torrentStart(fHandle);
467}
468
469- (float) ratio
470{
471    float downloaded = [self downloadedTotal];
472    return downloaded > 0 ? (float)[self uploadedTotal] / downloaded : -1;
473}
474
475- (int) stopRatioSetting
476{
477        return fStopRatioSetting;
478}
479
480- (void) setStopRatioSetting: (int) setting
481{
482    fStopRatioSetting = setting;
483}
484
485- (float) ratioLimit
486{
487    return fRatioLimit;
488}
489
490- (void) setRatioLimit: (float) limit
491{
492    if (limit >= 0)
493        fRatioLimit = limit;
494}
495
496- (void) setWaitToStart: (BOOL) wait
497{
498    fWaitToStart = wait;
499}
500
501- (BOOL) waitingToStart
502{
503    return fWaitToStart;
504}
505
506- (void) revealData
507{
508    [[NSWorkspace sharedWorkspace] selectFile: [self dataLocation] inFileViewerRootedAtPath: nil];
509}
510
511- (void) revealPublicTorrent
512{
513    if (fPublicTorrent)
514        [[NSWorkspace sharedWorkspace] selectFile: fPublicTorrentLocation inFileViewerRootedAtPath: nil];
515}
516
517- (void) trashData
518{
519    [self trashFile: [self dataLocation]];
520}
521
522- (void) trashTorrent
523{
524    if (fPublicTorrent)
525        [self trashFile: [self publicTorrentLocation]];
526}
527
528- (BOOL) alertForRemainingDiskSpace
529{
530    if ([self progress] >= 1.0)
531        return YES;
532   
533    NSString * volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: [self downloadFolder]]
534                                                                                                objectAtIndex: 0];
535    NSDictionary * fsAttributes = [[NSFileManager defaultManager] fileSystemAttributesAtPath: [self downloadFolder]];
536    uint64_t remainingSpace = [[fsAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue],
537            torrentRemaining = [self size] - (uint64_t)[self downloadedValid];
538   
539    /*NSLog(@"Volume: %@", volumeName);
540    NSLog(@"Remaining disk space: %qu (%@)", remainingSpace, [NSString stringForFileSize: remainingSpace]);
541    NSLog(@"Torrent remaining size: %qu (%@)", torrentRemaining, [NSString stringForFileSize: torrentRemaining]);*/
542   
543    if (volumeName && remainingSpace <= torrentRemaining)
544    {
545        NSAlert * alert = [[NSAlert alloc] init];
546        [alert setMessageText: [NSString stringWithFormat:
547                                NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
548                                    "Torrent file disk space alert -> title"), [self name]]];
549        [alert setInformativeText: [NSString stringWithFormat:
550                        NSLocalizedString(@"The transfer will be paused. Clear up space on \"%@\" to continue.",
551                                            "Torrent file disk space alert -> message"), volumeName]];
552        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file disk space alert -> button")];
553        [alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent file disk space alert -> button")];
554       
555        if ([alert runModal] == NSAlertFirstButtonReturn)
556        {
557            [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
558            return NO;
559        }
560        else
561            return YES;
562       
563        [alert release];
564    }
565    return YES;
566}
567
568- (BOOL) alertForVolumeAvailable
569{
570    NSArray * pathComponents = [[self downloadFolder] pathComponents];
571    if ([pathComponents count] < 3)
572        return YES;
573   
574    NSString * volume = [[[pathComponents objectAtIndex: 0] stringByAppendingPathComponent:
575                    [pathComponents objectAtIndex: 1]] stringByAppendingPathComponent: [pathComponents objectAtIndex: 2]];
576   
577    /*NSLog(@"%@", [self downloadFolder]);
578    NSLog(@"Volume: %@", volume);*/
579   
580    if (![[NSFileManager defaultManager] fileExistsAtPath: volume])
581    {
582        NSString * volumeName = [pathComponents objectAtIndex: 2];
583       
584        NSAlert * alert = [[NSAlert alloc] init];
585        [alert setMessageText: [NSString stringWithFormat:
586                                NSLocalizedString(@"The volume for downloading \"%@\" cannot be found.",
587                                    "Volume cannot be found alert -> title"), [self name]]];
588        [alert setInformativeText: [NSString stringWithFormat:
589                        NSLocalizedString(@"The transfer will be paused. Mount the volume \"%@\" to continue.",
590                                            "Volume cannot be found alert -> message"), volumeName]];
591        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Volume cannot be found alert -> button")];
592        [alert addButtonWithTitle: [NSLocalizedString(@"Choose New Directory",
593                                    "Volume cannot be found alert -> directory button") stringByAppendingEllipsis]];
594       
595        if ([alert runModal] == NSAlertFirstButtonReturn)
596            [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
597        else
598        {
599            NSOpenPanel * panel = [NSOpenPanel openPanel];
600           
601            [panel setPrompt: @"Select"];
602            [panel setAllowsMultipleSelection: NO];
603            [panel setCanChooseFiles: NO];
604            [panel setCanChooseDirectories: YES];
605
606            [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
607                                "Open torrent -> select destination folder"), [self name]]];
608           
609            [[NSNotificationCenter defaultCenter] postNotificationName: @"MakeWindowKey" object: nil];
610            [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: [NSApp keyWindow] modalDelegate: self
611                    didEndSelector: @selector(destinationChoiceClosed:returnCode:contextInfo:) contextInfo: nil];
612        }
613       
614        [alert release];
615       
616        return NO;
617    }
618   
619    return YES;
620}
621
622- (void) destinationChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) context
623{
624    NSString * folder = [[openPanel filenames] objectAtIndex: 0];
625    if (code == NSOKButton)
626    {
627        if (fUseIncompleteFolder)
628        {
629            [fIncompleteFolder release];
630            fIncompleteFolder = [folder retain];
631            [self setDownloadFolder: nil];
632        }
633        else
634        {
635            [fDownloadFolder release];
636            fDownloadFolder = folder;
637            [self setDownloadFolder: fDownloadFolder];
638        }
639       
640        [self startTransfer];
641        [self update];
642       
643        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateInfoSettings" object: nil];
644    }
645    else
646        [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
647}
648
649- (BOOL) alertForMoveVolumeAvailable
650{
651    NSArray * pathComponents = [fDownloadFolder pathComponents];
652    if ([pathComponents count] < 3)
653        return YES;
654   
655    NSString * volume = [[[pathComponents objectAtIndex: 0] stringByAppendingPathComponent:
656                    [pathComponents objectAtIndex: 1]] stringByAppendingPathComponent: [pathComponents objectAtIndex: 2]];
657   
658    /*NSLog(@"%@", [self downloadFolder]);
659    NSLog(@"Volume: %@", volume);*/
660   
661    if (![[NSFileManager defaultManager] fileExistsAtPath: volume])
662    {
663        NSAlert * alert = [[NSAlert alloc] init];
664        [alert setMessageText: [NSString stringWithFormat:
665                                NSLocalizedString(@"The volume for moving the completed \"%@\" cannot be found.",
666                                    "Move volume cannot be found alert -> title"), [self name]]];
667        [alert setInformativeText: NSLocalizedString(@"The file will remain in its current location",
668                                    "Move volume cannot be found alert -> message")];
669        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move volume cannot be found alert -> button")];
670       
671        [alert runModal];
672        [alert release];
673       
674        return NO;
675    }
676   
677    return YES;
678}
679
680- (NSImage *) icon
681{
682    return fIcon;
683}
684
685- (NSImage *) iconFlipped
686{
687    return fIconFlipped;
688}
689
690- (NSImage *) iconSmall
691{
692    return fIconSmall;
693}
694
695- (NSString *) name
696{
697    return [NSString stringWithUTF8String: fInfo->name];
698}
699
700- (uint64_t) size
701{
702    return fInfo->totalSize;
703}
704
705- (NSString *) tracker
706{
707    return [NSString stringWithFormat: @"%s:%d", fInfo->trackerAddress, fInfo->trackerPort];
708}
709
710- (NSString *) announce
711{
712    return [NSString stringWithUTF8String: fInfo->trackerAnnounce];
713}
714
715- (NSString *) comment
716{
717    return [NSString stringWithUTF8String: fInfo->comment];
718}
719
720- (NSString *) creator
721{
722    return [NSString stringWithUTF8String: fInfo->creator];
723}
724
725- (NSDate *) dateCreated
726{
727    int date = fInfo->dateCreated;
728    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
729}
730
731- (int) pieceSize
732{
733    return fInfo->pieceSize;
734}
735
736- (int) pieceCount
737{
738    return fInfo->pieceCount;
739}
740
741- (NSString *) hashString
742{
743    return [NSString stringWithUTF8String: fInfo->hashString];
744}
745
746- (NSString *) torrentLocation
747{
748    return [NSString stringWithUTF8String: fInfo->torrent];
749}
750
751- (NSString *) publicTorrentLocation
752{
753    return fPublicTorrentLocation;
754}
755
756- (NSString *) dataLocation
757{
758    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
759}
760
761- (BOOL) publicTorrent
762{
763    return fPublicTorrent;
764}
765
766- (NSString *) stateString
767{
768    switch( fStat->status )
769    {
770        case TR_STATUS_PAUSE:
771            return NSLocalizedString(@"Paused", "Torrent -> status string");
772            break;
773
774        case TR_STATUS_CHECK:
775            return [NSLocalizedString(@"Checking existing files", "Torrent -> status string") stringByAppendingEllipsis];
776            break;
777
778        case TR_STATUS_DOWNLOAD:
779            return NSLocalizedString(@"Downloading", "Torrent -> status string");
780            break;
781
782        case TR_STATUS_SEED:
783            return NSLocalizedString(@"Seeding", "Torrent -> status string");
784            break;
785
786        case TR_STATUS_STOPPING:
787            return [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
788            break;
789       
790        default:
791            return NSLocalizedString(@"N/A", "Torrent -> status string");
792    }
793}
794
795- (float) progress
796{
797    return fStat->progress;
798}
799
800- (int) eta
801{
802    return fStat->eta;
803}
804
805- (BOOL) isActive
806{
807    return fStat->status & TR_STATUS_ACTIVE;
808}
809
810- (BOOL) isSeeding
811{
812    return fStat->status == TR_STATUS_SEED;
813}
814
815- (BOOL) isPaused
816{
817    return fStat->status == TR_STATUS_PAUSE;
818}
819
820- (BOOL) isError
821{
822    return fStat->error & TR_ETRACKER;
823}
824
825- (BOOL) justFinished
826{
827    return tr_getFinished(fHandle);
828}
829
830- (NSArray *) peers
831{
832    int totalPeers, i;
833    tr_peer_stat_t * peers = tr_torrentPeers(fHandle, & totalPeers);
834   
835    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
836    tr_peer_stat_t peer;
837    NSString * client;
838    for (i = 0; i < totalPeers; i++)
839    {
840        peer = peers[i];
841        [peerDics addObject: [NSDictionary dictionaryWithObjectsAndKeys:
842            [NSNumber numberWithBool: peer.isConnected], @"Connected",
843            [NSNumber numberWithBool: peer.isIncoming], @"Incoming",
844            [NSString stringWithCString: (char *) peer.addr encoding: NSUTF8StringEncoding], @"IP",
845            [NSString stringWithCString: (char *) peer.client encoding: NSUTF8StringEncoding], @"Client",
846            [NSNumber numberWithFloat: peer.progress], @"Progress",
847            [NSNumber numberWithBool: peer.isDownloading], @"UL To",
848            [NSNumber numberWithBool: peer.isUploading], @"DL From", nil]];
849    }
850   
851    tr_torrentPeersFree(peers, totalPeers);
852   
853    return peerDics;
854}
855
856- (NSString *) progressString
857{
858    return fProgressString;
859}
860
861- (NSString *) statusString
862{
863    return fStatusString;
864}
865
866- (NSString *) shortStatusString
867{
868    return fShortStatusString;
869}
870
871- (NSString *) remainingTimeString
872{
873    return fRemainingTimeString;
874}
875
876- (int) seeders
877{
878    return fStat->seeders;
879}
880
881- (int) leechers
882{
883    return fStat->leechers;
884}
885
886- (int) completedFromTracker
887{
888    return fStat->completedFromTracker;
889}
890
891- (int) totalPeers
892{
893    return fStat->peersTotal;
894}
895
896- (int) totalPeersIncoming
897{
898    return fStat->peersIncoming;
899}
900
901- (int) totalPeersOutgoing
902{
903    return [self totalPeers] - [self totalPeersIncoming];
904}
905
906//peers uploading to you
907- (int) peersUploading
908{
909    return fStat->peersUploading;
910}
911
912//peers downloading from you
913- (int) peersDownloading
914{
915    return fStat->peersDownloading;
916}
917
918- (float) downloadRate
919{
920    return fStat->rateDownload;
921}
922
923- (float) uploadRate
924{
925    return fStat->rateUpload;
926}
927
928- (float) downloadedValid
929{
930    return [self progress] * [self size];
931}
932
933- (uint64_t) downloadedTotal
934{
935    return fStat->downloaded;
936}
937
938- (uint64_t) uploadedTotal
939{
940    return fStat->uploaded;
941}
942
943- (float) swarmSpeed
944{
945    return fStat->swarmspeed;
946}
947
948- (NSNumber *) orderValue
949{
950    return [NSNumber numberWithInt: fOrderValue];
951}
952
953- (void) setOrderValue: (int) orderValue
954{
955    fOrderValue = orderValue;
956}
957
958- (NSArray *) fileList
959{
960    int count = fInfo->fileCount, i;
961    tr_file_t file;
962    NSMutableArray * files = [NSMutableArray arrayWithCapacity: count];
963   
964    for (i = 0; i < count; i++)
965    {
966        file = fInfo->files[i];
967        [files addObject: [NSDictionary dictionaryWithObjectsAndKeys:
968            [[self downloadFolder] stringByAppendingPathComponent: [NSString stringWithUTF8String: file.name]], @"Name",
969            [NSNumber numberWithUnsignedLongLong: file.length], @"Size", nil]];
970    }
971   
972    return files;
973}
974
975- (NSDate *) date
976{
977    return fDate;
978}
979
980- (NSNumber *) stateSortKey
981{
982    if (![self isActive])
983        return [NSNumber numberWithInt: 0];
984    else if ([self isSeeding])
985        return [NSNumber numberWithInt: 1];
986    else
987        return [NSNumber numberWithInt: 2];
988}
989
990- (NSNumber *) progressSortKey
991{
992    //if finished downloading sort by ratio instead of progress
993    float progress = [self progress];
994    return [NSNumber numberWithFloat: progress < 1.0 ? progress : 2.0 + [self ratio]];
995}
996
997@end
998
999
1000@implementation Torrent (Private)
1001
1002//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1003- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
1004        publicTorrent: (NSNumber *) publicTorrent
1005        date: (NSDate *) date stopRatioSetting: (NSNumber *) stopRatioSetting
1006        ratioLimit: (NSNumber *) ratioLimit waitToStart: (NSNumber *) waitToStart
1007        orderValue: (NSNumber *) orderValue
1008{
1009    if (!(self = [super init]))
1010        return nil;
1011
1012    fLib = lib;
1013    fDefaults = [NSUserDefaults standardUserDefaults];
1014
1015    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1016    if (fPublicTorrent)
1017        fPublicTorrentLocation = [path retain];
1018
1019    int error;
1020    if (hashString)
1021        fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], TR_FSAVEPRIVATE, & error);
1022   
1023    if (!fHandle && path)
1024        fHandle = tr_torrentInit(fLib, [path UTF8String], TR_FSAVEPRIVATE, & error);
1025
1026    if (!fHandle)
1027    {
1028        [self release];
1029        return nil;
1030    }
1031   
1032    fInfo = tr_torrentInfo( fHandle );
1033
1034    fDate = date ? [date retain] : [[NSDate alloc] init];
1035   
1036    fStopRatioSetting = stopRatioSetting ? [stopRatioSetting intValue] : -1;
1037    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1038    fFinishedSeeding = NO;
1039   
1040    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1041    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1042    fError = NO;
1043   
1044    NSString * fileType = fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr') : [[self name] pathExtension];
1045    fIcon = [[NSWorkspace sharedWorkspace] iconForFileType: fileType];
1046    [fIcon retain];
1047   
1048    fIconFlipped = [fIcon copy];
1049    [fIconFlipped setFlipped: YES];
1050   
1051    fIconSmall = [fIconFlipped copy];
1052    [fIconSmall setScalesWhenResized: YES];
1053    [fIconSmall setSize: NSMakeSize(16.0, 16.0)];
1054
1055    fProgressString = [[NSMutableString alloc] initWithCapacity: 50];
1056    fStatusString = [[NSMutableString alloc] initWithCapacity: 75];
1057    fShortStatusString = [[NSMutableString alloc] initWithCapacity: 30];
1058    fRemainingTimeString = [[NSMutableString alloc] initWithCapacity: 30];
1059   
1060    //set up advanced bar
1061    fBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
1062        pixelsWide: MAX_PIECES pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
1063        isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
1064   
1065    fPieces = malloc(MAX_PIECES);
1066    int i;
1067    for (i = 0; i < MAX_PIECES; i++)
1068        fPieces[i] = BLANK_PIECE;
1069
1070    [self update];
1071    return self;
1072}
1073
1074- (NSImage *) advancedBar
1075{
1076    int h, w;
1077    uint32_t * p;
1078    uint8_t * bitmapData = [fBitmap bitmapData];
1079    int bytesPerRow = [fBitmap bytesPerRow];
1080
1081    int8_t * pieces = malloc(MAX_PIECES);
1082    [self getAvailability: pieces size: MAX_PIECES];
1083   
1084    //lines 2 to 14: blue, green, or gray depending on whether we have the piece or not
1085    int have = 0, avail = 0;
1086    uint32_t color;
1087    BOOL change;
1088    for (w = 0; w < MAX_PIECES; w++)
1089    {
1090        change = NO;
1091        if (pieces[w] < 0)
1092        {
1093            if (fPieces[w] != -1)
1094            {
1095                color = kBlue;
1096                fPieces[w] = -1;
1097                change = YES;
1098            }
1099            have++;
1100        }
1101        else if (pieces[w] == 0)
1102        {
1103            if (fPieces[w] != 0)
1104            {
1105                color = kGray;
1106                fPieces[w] = 0;
1107                change = YES;
1108            }
1109        }
1110        else
1111        {
1112            if (pieces[w] == 1)
1113            {
1114                if (fPieces[w] != 1)
1115                {
1116                    color = kGreen1;
1117                    fPieces[w] = 1;
1118                    change = YES;
1119                }
1120            }
1121            else if (pieces[w] == 2)
1122            {
1123                if (fPieces[w] != 2)
1124                {
1125                    color = kGreen2;
1126                    fPieces[w] = 2;
1127                    change = YES;
1128                }
1129            }
1130            else
1131            {
1132                if (fPieces[w] != 3)
1133                {
1134                    color = kGreen3;
1135                    fPieces[w] = 3;
1136                    change = YES;
1137                }
1138            }
1139            avail++;
1140        }
1141       
1142        if (change)
1143        {
1144            //point to pixel (w, 2) and draw "vertically"
1145            p = (uint32_t *)(bitmapData + 2 * bytesPerRow) + w;
1146            for (h = 2; h < BAR_HEIGHT; h++)
1147            {
1148                p[0] = color;
1149                p = (uint32_t *)((uint8_t *)p + bytesPerRow);
1150            }
1151        }
1152    }
1153   
1154    //first two lines: dark blue to show progression, green to show available
1155    p = (uint32_t *) bitmapData;
1156    for (w = 0; w < have; w++)
1157    {
1158        p[w] = kBlue2;
1159        p[w + bytesPerRow / 4] = kBlue2;
1160    }
1161    for (; w < avail + have; w++)
1162    {
1163        p[w] = kGreen3;
1164        p[w + bytesPerRow / 4] = kGreen3;
1165    }
1166    for (; w < MAX_PIECES; w++)
1167    {
1168        p[w] = kWhite;
1169        p[w + bytesPerRow / 4] = kWhite;
1170    }
1171   
1172    free(pieces);
1173   
1174    //actually draw image
1175    NSImage * bar = [[NSImage alloc] initWithSize: [fBitmap size]];
1176    [bar addRepresentation: fBitmap];
1177    [bar setScalesWhenResized: YES];
1178   
1179    return [bar autorelease];
1180}
1181
1182- (void) trashFile: (NSString *) path
1183{
1184    //attempt to move to trash
1185    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1186            source: [path stringByDeletingLastPathComponent] destination: @""
1187            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1188    {
1189        //if cannot trash, just delete it (will work if it is on a remote volume)
1190        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1191            NSLog(@"Could not trash %@", path);
1192    }
1193}
1194
1195@end
Note: See TracBrowser for help on using the repository browser.