source: trunk/macosx/Torrent.m @ 1100

Last change on this file since 1100 was 1100, checked in by titer, 15 years ago

When checking free space on the hard drive, use the destination folder (the data location may not exist yet and fileSystemAttributesAtPath gives you incorrect values then)

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