source: branches/multitracker/macosx/Torrent.m @ 1234

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

libT can now better report if it can connect to a tracker, and as a result the mac's queue system will now start the next for any error (not just those sent by the tracker)

  • Property svn:keywords set to Date Rev Author Id
File size: 37.6 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 1234 2006-12-16 01:58:07Z 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])
359        {
360            fError = YES;
361            if (![self isSeeding])
362                [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
363        }
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: @"http://%s:%d", fStat->trackerAddress, fStat->trackerPort];
708}
709
710- (NSString *) announce
711{
712    return [NSString stringWithUTF8String: fStat->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- (BOOL) privateTorrent
747{
748    return fInfo->privateTorrent;
749}
750
751- (NSString *) torrentLocation
752{
753    return [NSString stringWithUTF8String: fInfo->torrent];
754}
755
756- (NSString *) publicTorrentLocation
757{
758    return fPublicTorrentLocation;
759}
760
761- (NSString *) dataLocation
762{
763    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
764}
765
766- (BOOL) publicTorrent
767{
768    return fPublicTorrent;
769}
770
771- (NSString *) stateString
772{
773    switch( fStat->status )
774    {
775        case TR_STATUS_PAUSE:
776            return NSLocalizedString(@"Paused", "Torrent -> status string");
777            break;
778
779        case TR_STATUS_CHECK:
780            return [NSLocalizedString(@"Checking existing files", "Torrent -> status string") stringByAppendingEllipsis];
781            break;
782
783        case TR_STATUS_DOWNLOAD:
784            return NSLocalizedString(@"Downloading", "Torrent -> status string");
785            break;
786
787        case TR_STATUS_SEED:
788            return NSLocalizedString(@"Seeding", "Torrent -> status string");
789            break;
790
791        case TR_STATUS_STOPPING:
792            return [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
793            break;
794       
795        default:
796            return NSLocalizedString(@"N/A", "Torrent -> status string");
797    }
798}
799
800- (float) progress
801{
802    return fStat->progress;
803}
804
805- (int) eta
806{
807    return fStat->eta;
808}
809
810- (BOOL) isActive
811{
812    return fStat->status & TR_STATUS_ACTIVE;
813}
814
815- (BOOL) isSeeding
816{
817    return fStat->status == TR_STATUS_SEED;
818}
819
820- (BOOL) isPaused
821{
822    return fStat->status == TR_STATUS_PAUSE;
823}
824
825- (BOOL) isError
826{
827    return fStat->error & TR_ETRACKER;
828}
829
830- (NSString *) errorMessage
831{
832    [NSString stringWithUTF8String: fStat->trackerError];
833}
834
835- (BOOL) justFinished
836{
837    return tr_getFinished(fHandle);
838}
839
840- (NSArray *) peers
841{
842    int totalPeers, i;
843    tr_peer_stat_t * peers = tr_torrentPeers(fHandle, & totalPeers);
844   
845    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
846    tr_peer_stat_t peer;
847    NSString * client;
848    for (i = 0; i < totalPeers; i++)
849    {
850        peer = peers[i];
851        [peerDics addObject: [NSDictionary dictionaryWithObjectsAndKeys:
852            [NSNumber numberWithBool: peer.isConnected], @"Connected",
853            [NSNumber numberWithBool: peer.isIncoming], @"Incoming",
854            [NSString stringWithCString: (char *) peer.addr encoding: NSUTF8StringEncoding], @"IP",
855            [NSString stringWithCString: (char *) peer.client encoding: NSUTF8StringEncoding], @"Client",
856            [NSNumber numberWithFloat: peer.progress], @"Progress",
857            [NSNumber numberWithBool: peer.isDownloading], @"UL To",
858            [NSNumber numberWithBool: peer.isUploading], @"DL From", nil]];
859    }
860   
861    tr_torrentPeersFree(peers, totalPeers);
862   
863    return peerDics;
864}
865
866- (NSString *) progressString
867{
868    return fProgressString;
869}
870
871- (NSString *) statusString
872{
873    return fStatusString;
874}
875
876- (NSString *) shortStatusString
877{
878    return fShortStatusString;
879}
880
881- (NSString *) remainingTimeString
882{
883    return fRemainingTimeString;
884}
885
886- (int) seeders
887{
888    return fStat->seeders;
889}
890
891- (int) leechers
892{
893    return fStat->leechers;
894}
895
896- (int) completedFromTracker
897{
898    return fStat->completedFromTracker;
899}
900
901- (int) totalPeers
902{
903    return fStat->peersTotal;
904}
905
906- (int) totalPeersIncoming
907{
908    return fStat->peersIncoming;
909}
910
911- (int) totalPeersOutgoing
912{
913    return [self totalPeers] - [self totalPeersIncoming];
914}
915
916//peers uploading to you
917- (int) peersUploading
918{
919    return fStat->peersUploading;
920}
921
922//peers downloading from you
923- (int) peersDownloading
924{
925    return fStat->peersDownloading;
926}
927
928- (float) downloadRate
929{
930    return fStat->rateDownload;
931}
932
933- (float) uploadRate
934{
935    return fStat->rateUpload;
936}
937
938- (float) downloadedValid
939{
940    return [self progress] * [self size];
941}
942
943- (uint64_t) downloadedTotal
944{
945    return fStat->downloaded;
946}
947
948- (uint64_t) uploadedTotal
949{
950    return fStat->uploaded;
951}
952
953- (float) swarmSpeed
954{
955    return fStat->swarmspeed;
956}
957
958- (NSNumber *) orderValue
959{
960    return [NSNumber numberWithInt: fOrderValue];
961}
962
963- (void) setOrderValue: (int) orderValue
964{
965    fOrderValue = orderValue;
966}
967
968- (NSArray *) fileList
969{
970    int count = fInfo->fileCount, i;
971    tr_file_t file;
972    NSMutableArray * files = [NSMutableArray arrayWithCapacity: count];
973   
974    for (i = 0; i < count; i++)
975    {
976        file = fInfo->files[i];
977        [files addObject: [NSDictionary dictionaryWithObjectsAndKeys:
978            [[self downloadFolder] stringByAppendingPathComponent: [NSString stringWithUTF8String: file.name]], @"Name",
979            [NSNumber numberWithUnsignedLongLong: file.length], @"Size", nil]];
980    }
981   
982    return files;
983}
984
985- (NSDate *) date
986{
987    return fDate;
988}
989
990- (NSNumber *) stateSortKey
991{
992    if (![self isActive])
993        return [NSNumber numberWithInt: 0];
994    else if ([self isSeeding])
995        return [NSNumber numberWithInt: 1];
996    else
997        return [NSNumber numberWithInt: 2];
998}
999
1000- (NSNumber *) progressSortKey
1001{
1002    //if finished downloading sort by ratio instead of progress
1003    float progress = [self progress];
1004    return [NSNumber numberWithFloat: progress < 1.0 ? progress : 2.0 + [self ratio]];
1005}
1006
1007@end
1008
1009
1010@implementation Torrent (Private)
1011
1012//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1013- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
1014        publicTorrent: (NSNumber *) publicTorrent
1015        date: (NSDate *) date stopRatioSetting: (NSNumber *) stopRatioSetting
1016        ratioLimit: (NSNumber *) ratioLimit waitToStart: (NSNumber *) waitToStart
1017        orderValue: (NSNumber *) orderValue
1018{
1019    if (!(self = [super init]))
1020        return nil;
1021
1022    fLib = lib;
1023    fDefaults = [NSUserDefaults standardUserDefaults];
1024
1025    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1026    if (fPublicTorrent)
1027        fPublicTorrentLocation = [path retain];
1028
1029    int error;
1030    if (hashString)
1031        fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], TR_FSAVEPRIVATE, & error);
1032   
1033    if (!fHandle && path)
1034        fHandle = tr_torrentInit(fLib, [path UTF8String], TR_FSAVEPRIVATE, & error);
1035
1036    if (!fHandle)
1037    {
1038        [self release];
1039        return nil;
1040    }
1041   
1042    fInfo = tr_torrentInfo( fHandle );
1043
1044    fDate = date ? [date retain] : [[NSDate alloc] init];
1045   
1046    fStopRatioSetting = stopRatioSetting ? [stopRatioSetting intValue] : -1;
1047    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1048    fFinishedSeeding = NO;
1049   
1050    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1051    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1052    fError = NO;
1053   
1054    NSString * fileType = fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr') : [[self name] pathExtension];
1055    fIcon = [[NSWorkspace sharedWorkspace] iconForFileType: fileType];
1056    [fIcon retain];
1057   
1058    fIconFlipped = [fIcon copy];
1059    [fIconFlipped setFlipped: YES];
1060   
1061    fIconSmall = [fIconFlipped copy];
1062    [fIconSmall setScalesWhenResized: YES];
1063    [fIconSmall setSize: NSMakeSize(16.0, 16.0)];
1064
1065    fProgressString = [[NSMutableString alloc] initWithCapacity: 50];
1066    fStatusString = [[NSMutableString alloc] initWithCapacity: 75];
1067    fShortStatusString = [[NSMutableString alloc] initWithCapacity: 30];
1068    fRemainingTimeString = [[NSMutableString alloc] initWithCapacity: 30];
1069   
1070    //set up advanced bar
1071    fBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
1072        pixelsWide: MAX_PIECES pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
1073        isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
1074   
1075    fPieces = malloc(MAX_PIECES);
1076    int i;
1077    for (i = 0; i < MAX_PIECES; i++)
1078        fPieces[i] = BLANK_PIECE;
1079
1080    [self update];
1081    return self;
1082}
1083
1084- (NSImage *) advancedBar
1085{
1086    int h, w;
1087    uint32_t * p;
1088    uint8_t * bitmapData = [fBitmap bitmapData];
1089    int bytesPerRow = [fBitmap bytesPerRow];
1090
1091    int8_t * pieces = malloc(MAX_PIECES);
1092    [self getAvailability: pieces size: MAX_PIECES];
1093   
1094    //lines 2 to 14: blue, green, or gray depending on whether we have the piece or not
1095    int have = 0, avail = 0;
1096    uint32_t color;
1097    BOOL change;
1098    for (w = 0; w < MAX_PIECES; w++)
1099    {
1100        change = NO;
1101        if (pieces[w] < 0)
1102        {
1103            if (fPieces[w] != -1)
1104            {
1105                color = kBlue;
1106                fPieces[w] = -1;
1107                change = YES;
1108            }
1109            have++;
1110        }
1111        else if (pieces[w] == 0)
1112        {
1113            if (fPieces[w] != 0)
1114            {
1115                color = kGray;
1116                fPieces[w] = 0;
1117                change = YES;
1118            }
1119        }
1120        else
1121        {
1122            if (pieces[w] == 1)
1123            {
1124                if (fPieces[w] != 1)
1125                {
1126                    color = kGreen1;
1127                    fPieces[w] = 1;
1128                    change = YES;
1129                }
1130            }
1131            else if (pieces[w] == 2)
1132            {
1133                if (fPieces[w] != 2)
1134                {
1135                    color = kGreen2;
1136                    fPieces[w] = 2;
1137                    change = YES;
1138                }
1139            }
1140            else
1141            {
1142                if (fPieces[w] != 3)
1143                {
1144                    color = kGreen3;
1145                    fPieces[w] = 3;
1146                    change = YES;
1147                }
1148            }
1149            avail++;
1150        }
1151       
1152        if (change)
1153        {
1154            //point to pixel (w, 2) and draw "vertically"
1155            p = (uint32_t *)(bitmapData + 2 * bytesPerRow) + w;
1156            for (h = 2; h < BAR_HEIGHT; h++)
1157            {
1158                p[0] = color;
1159                p = (uint32_t *)((uint8_t *)p + bytesPerRow);
1160            }
1161        }
1162    }
1163   
1164    //first two lines: dark blue to show progression, green to show available
1165    p = (uint32_t *) bitmapData;
1166    for (w = 0; w < have; w++)
1167    {
1168        p[w] = kBlue2;
1169        p[w + bytesPerRow / 4] = kBlue2;
1170    }
1171    for (; w < avail + have; w++)
1172    {
1173        p[w] = kGreen3;
1174        p[w + bytesPerRow / 4] = kGreen3;
1175    }
1176    for (; w < MAX_PIECES; w++)
1177    {
1178        p[w] = kWhite;
1179        p[w + bytesPerRow / 4] = kWhite;
1180    }
1181   
1182    free(pieces);
1183   
1184    //actually draw image
1185    NSImage * bar = [[NSImage alloc] initWithSize: [fBitmap size]];
1186    [bar addRepresentation: fBitmap];
1187    [bar setScalesWhenResized: YES];
1188   
1189    return [bar autorelease];
1190}
1191
1192- (void) trashFile: (NSString *) path
1193{
1194    //attempt to move to trash
1195    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1196            source: [path stringByDeletingLastPathComponent] destination: @""
1197            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1198    {
1199        //if cannot trash, just delete it (will work if it is on a remote volume)
1200        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1201            NSLog(@"Could not trash %@", path);
1202    }
1203}
1204
1205@end
Note: See TracBrowser for help on using the repository browser.