source: trunk/macosx/Torrent.m @ 1119

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

add support in libT and the mac interface for displaying comment, creator, and date created

  • Property svn:keywords set to Date Rev Author Id
File size: 37.4 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 1119 2006-11-23 01:38:18Z 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                                    [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: fInfo->dateCreated] : 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) totalPeers
887{
888    return fStat->peersTotal;
889}
890
891- (int) totalPeersIncoming
892{
893    return fStat->peersIncoming;
894}
895
896- (int) totalPeersOutgoing
897{
898    return [self totalPeers] - [self totalPeersIncoming];
899}
900
901//peers uploading to you
902- (int) peersUploading
903{
904    return fStat->peersUploading;
905}
906
907//peers downloading from you
908- (int) peersDownloading
909{
910    return fStat->peersDownloading;
911}
912
913- (float) downloadRate
914{
915    return fStat->rateDownload;
916}
917
918- (float) uploadRate
919{
920    return fStat->rateUpload;
921}
922
923- (float) downloadedValid
924{
925    return [self progress] * [self size];
926}
927
928- (uint64_t) downloadedTotal
929{
930    return fStat->downloaded;
931}
932
933- (uint64_t) uploadedTotal
934{
935    return fStat->uploaded;
936}
937
938- (float) swarmSpeed
939{
940    return fStat->swarmspeed;
941}
942
943- (NSNumber *) orderValue
944{
945    return [NSNumber numberWithInt: fOrderValue];
946}
947
948- (void) setOrderValue: (int) orderValue
949{
950    fOrderValue = orderValue;
951}
952
953- (NSArray *) fileList
954{
955    int count = fInfo->fileCount, i;
956    tr_file_t file;
957    NSMutableArray * files = [NSMutableArray arrayWithCapacity: count];
958   
959    for (i = 0; i < count; i++)
960    {
961        file = fInfo->files[i];
962        [files addObject: [NSDictionary dictionaryWithObjectsAndKeys:
963            [[self downloadFolder] stringByAppendingPathComponent: [NSString stringWithUTF8String: file.name]], @"Name",
964            [NSNumber numberWithUnsignedLongLong: file.length], @"Size", nil]];
965    }
966   
967    return files;
968}
969
970- (NSDate *) date
971{
972    return fDate;
973}
974
975- (NSNumber *) stateSortKey
976{
977    if (![self isActive])
978        return [NSNumber numberWithInt: 0];
979    else if ([self isSeeding])
980        return [NSNumber numberWithInt: 1];
981    else
982        return [NSNumber numberWithInt: 2];
983}
984
985- (NSNumber *) progressSortKey
986{
987    //if finished downloading sort by ratio instead of progress
988    float progress = [self progress];
989    return [NSNumber numberWithFloat: progress < 1.0 ? progress : 2.0 + [self ratio]];
990}
991
992@end
993
994
995@implementation Torrent (Private)
996
997//if a hash is given, attempt to load that; otherwise, attempt to open file at path
998- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
999        publicTorrent: (NSNumber *) publicTorrent
1000        date: (NSDate *) date stopRatioSetting: (NSNumber *) stopRatioSetting
1001        ratioLimit: (NSNumber *) ratioLimit waitToStart: (NSNumber *) waitToStart
1002        orderValue: (NSNumber *) orderValue
1003{
1004    if (!(self = [super init]))
1005        return nil;
1006
1007    fLib = lib;
1008    fDefaults = [NSUserDefaults standardUserDefaults];
1009
1010    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1011    if (fPublicTorrent)
1012        fPublicTorrentLocation = [path retain];
1013
1014    int error;
1015    if (hashString)
1016        fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], TR_FSAVEPRIVATE, & error);
1017   
1018    if (!fHandle && path)
1019        fHandle = tr_torrentInit(fLib, [path UTF8String], TR_FSAVEPRIVATE, & error);
1020
1021    if (!fHandle)
1022    {
1023        [self release];
1024        return nil;
1025    }
1026   
1027    fInfo = tr_torrentInfo( fHandle );
1028
1029    fDate = date ? [date retain] : [[NSDate alloc] init];
1030   
1031    fStopRatioSetting = stopRatioSetting ? [stopRatioSetting intValue] : -1;
1032    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1033    fFinishedSeeding = NO;
1034   
1035    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1036    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1037    fError = NO;
1038   
1039    NSString * fileType = fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr') : [[self name] pathExtension];
1040    fIcon = [[NSWorkspace sharedWorkspace] iconForFileType: fileType];
1041    [fIcon retain];
1042   
1043    fIconFlipped = [fIcon copy];
1044    [fIconFlipped setFlipped: YES];
1045   
1046    fIconSmall = [fIconFlipped copy];
1047    [fIconSmall setScalesWhenResized: YES];
1048    [fIconSmall setSize: NSMakeSize(16.0, 16.0)];
1049
1050    fProgressString = [[NSMutableString alloc] initWithCapacity: 50];
1051    fStatusString = [[NSMutableString alloc] initWithCapacity: 75];
1052    fShortStatusString = [[NSMutableString alloc] initWithCapacity: 30];
1053    fRemainingTimeString = [[NSMutableString alloc] initWithCapacity: 30];
1054   
1055    //set up advanced bar
1056    fBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
1057        pixelsWide: MAX_PIECES pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
1058        isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
1059   
1060    fPieces = malloc(MAX_PIECES);
1061    int i;
1062    for (i = 0; i < MAX_PIECES; i++)
1063        fPieces[i] = BLANK_PIECE;
1064
1065    [self update];
1066    return self;
1067}
1068
1069- (NSImage *) advancedBar
1070{
1071    int h, w;
1072    uint32_t * p;
1073    uint8_t * bitmapData = [fBitmap bitmapData];
1074    int bytesPerRow = [fBitmap bytesPerRow];
1075
1076    int8_t * pieces = malloc(MAX_PIECES);
1077    [self getAvailability: pieces size: MAX_PIECES];
1078   
1079    //lines 2 to 14: blue, green, or gray depending on whether we have the piece or not
1080    int have = 0, avail = 0;
1081    uint32_t color;
1082    BOOL change;
1083    for (w = 0; w < MAX_PIECES; w++)
1084    {
1085        change = NO;
1086        if (pieces[w] < 0)
1087        {
1088            if (fPieces[w] != -1)
1089            {
1090                color = kBlue;
1091                fPieces[w] = -1;
1092                change = YES;
1093            }
1094            have++;
1095        }
1096        else if (pieces[w] == 0)
1097        {
1098            if (fPieces[w] != 0)
1099            {
1100                color = kGray;
1101                fPieces[w] = 0;
1102                change = YES;
1103            }
1104        }
1105        else
1106        {
1107            if (pieces[w] == 1)
1108            {
1109                if (fPieces[w] != 1)
1110                {
1111                    color = kGreen1;
1112                    fPieces[w] = 1;
1113                    change = YES;
1114                }
1115            }
1116            else if (pieces[w] == 2)
1117            {
1118                if (fPieces[w] != 2)
1119                {
1120                    color = kGreen2;
1121                    fPieces[w] = 2;
1122                    change = YES;
1123                }
1124            }
1125            else
1126            {
1127                if (fPieces[w] != 3)
1128                {
1129                    color = kGreen3;
1130                    fPieces[w] = 3;
1131                    change = YES;
1132                }
1133            }
1134            avail++;
1135        }
1136       
1137        if (change)
1138        {
1139            //point to pixel (w, 2) and draw "vertically"
1140            p = (uint32_t *)(bitmapData + 2 * bytesPerRow) + w;
1141            for (h = 2; h < BAR_HEIGHT; h++)
1142            {
1143                p[0] = color;
1144                p = (uint32_t *)((uint8_t *)p + bytesPerRow);
1145            }
1146        }
1147    }
1148   
1149    //first two lines: dark blue to show progression, green to show available
1150    p = (uint32_t *) bitmapData;
1151    for (w = 0; w < have; w++)
1152    {
1153        p[w] = kBlue2;
1154        p[w + bytesPerRow / 4] = kBlue2;
1155    }
1156    for (; w < avail + have; w++)
1157    {
1158        p[w] = kGreen3;
1159        p[w + bytesPerRow / 4] = kGreen3;
1160    }
1161    for (; w < MAX_PIECES; w++)
1162    {
1163        p[w] = kWhite;
1164        p[w + bytesPerRow / 4] = kWhite;
1165    }
1166   
1167    free(pieces);
1168   
1169    //actually draw image
1170    NSImage * bar = [[NSImage alloc] initWithSize: [fBitmap size]];
1171    [bar addRepresentation: fBitmap];
1172    [bar setScalesWhenResized: YES];
1173   
1174    return [bar autorelease];
1175}
1176
1177- (void) trashFile: (NSString *) path
1178{
1179    //attempt to move to trash
1180    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1181            source: [path stringByDeletingLastPathComponent] destination: @""
1182            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1183    {
1184        //if cannot trash, just delete it (will work if it is on a remote volume)
1185        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1186            NSLog(@"Could not trash %@", path);
1187    }
1188}
1189
1190@end
Note: See TracBrowser for help on using the repository browser.