source: branches/scrape/macosx/Torrent.m @ 1136

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

don't force more announces if the torrent is complete, and determining if there are many peers can now acknowledge a change for having many to not having many

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