source: trunk/macosx/Torrent.m @ 1328

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

when a download finishes, stop and make wait if the seeding queue is full

  • Property svn:keywords set to Date Rev Author Id
File size: 41.3 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 1328 2007-01-09 03:46:21Z 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
38        stopRatioCustom: (NSNumber *) ratioCustom
39        shouldStopAtRatio: (NSNumber *) shouldStopAtRatio
40        ratioLimit: (NSNumber *) ratioLimit
41        limitSpeedCustom: (NSNumber *) limitCustom
42        checkUpload: (NSNumber *) checkUpload uploadLimit: (NSNumber *) uploadLimit
43        checkDownload: (NSNumber *) checkDownload downloadLimit: (NSNumber *) downloadLimit
44        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;
45
46- (NSImage *) advancedBar;
47
48- (void) trashFile: (NSString *) path;
49
50@end
51
52@implementation Torrent
53
54// Used to optimize drawing. They contain packed RGBA pixels for every color needed.
55#define BE OSSwapBigToHostConstInt32
56
57static uint32_t kRed   = BE(0xFF6450FF), //255, 100, 80
58                kBlue = BE(0x50A0FFFF), //80, 160, 255
59                kBlue2 = BE(0x1E46B4FF), //30, 70, 180
60                kGray  = BE(0x969696FF), //150, 150, 150
61                kGreen1 = BE(0x99FFCCFF), //153, 255, 204
62                kGreen2 = BE(0x66FF99FF), //102, 255, 153
63                kGreen3 = BE(0x00FF66FF), //0, 255, 102
64                kWhite = BE(0xFFFFFFFF); //255, 255, 255
65
66- (id) initWithPath: (NSString *) path forceDeleteTorrent: (BOOL) delete lib: (tr_handle_t *) lib
67{
68    self = [self initWithHash: nil path: path lib: lib
69            publicTorrent: delete ? [NSNumber numberWithBool: NO] : nil
70            date: nil
71            stopRatioCustom: nil shouldStopAtRatio: nil ratioLimit: nil
72            limitSpeedCustom: nil
73            checkUpload: nil uploadLimit: nil
74            checkDownload: nil downloadLimit: nil
75            waitToStart: nil orderValue: nil];
76   
77    if (self)
78    {
79        fUseIncompleteFolder = [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
80        fIncompleteFolder = [[fDefaults stringForKey: @"IncompleteDownloadFolder"] copy];
81       
82        if (!fPublicTorrent)
83            [self trashFile: path];
84    }
85    return self;
86}
87
88- (id) initWithHistory: (NSDictionary *) history lib: (tr_handle_t *) lib
89{
90    self = [self initWithHash: [history objectForKey: @"TorrentHash"]
91                path: [history objectForKey: @"TorrentPath"] lib: lib
92                publicTorrent: [history objectForKey: @"PublicCopy"]
93                date: [history objectForKey: @"Date"]
94                stopRatioCustom: [history objectForKey: @"StopRatioCustom"]
95                shouldStopAtRatio: [history objectForKey: @"ShouldStopAtRatio"]
96                ratioLimit: [history objectForKey: @"RatioLimit"]
97                limitSpeedCustom: [history objectForKey: @"LimitSpeedCustom"]
98                checkUpload: [history objectForKey: @"CheckUpload"]
99                uploadLimit: [history objectForKey: @"UploadLimit"]
100                checkDownload: [history objectForKey: @"CheckDownload"]
101                downloadLimit: [history objectForKey: @"DownloadLimit"]
102                waitToStart: [history objectForKey: @"WaitToStart"]
103                orderValue: [history objectForKey: @"OrderValue"]];
104   
105    if (self)
106    {
107        //download folders
108        NSString * downloadFolder;
109        if (!(downloadFolder = [history objectForKey: @"DownloadFolder"]))
110            downloadFolder = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
111       
112        NSNumber * useIncompleteFolder;
113        if ((useIncompleteFolder = [history objectForKey: @"UseIncompleteFolder"]))
114        {
115            if ((fUseIncompleteFolder = [useIncompleteFolder boolValue]))
116            {
117                NSString * incompleteFolder;
118                if (incompleteFolder = [history objectForKey: @"IncompleteFolder"])
119                    fIncompleteFolder = [incompleteFolder copy];
120                else
121                    fIncompleteFolder = [[[fDefaults stringForKey: @"IncompleteDownloadFolder"]
122                                            stringByExpandingTildeInPath] copy];
123            }
124        }
125        else
126            fUseIncompleteFolder = NO;
127       
128        [self setDownloadFolder: downloadFolder];
129
130        NSString * paused;
131        if (!(paused = [history objectForKey: @"Paused"]) || [paused isEqualToString: @"NO"])
132        {
133            fStat = tr_torrentStat(fHandle);
134            [self startTransfer];
135        }
136    }
137    return self;
138}
139
140- (NSDictionary *) history
141{
142    NSMutableDictionary * history = [NSMutableDictionary dictionaryWithObjectsAndKeys:
143                    [NSNumber numberWithBool: fPublicTorrent], @"PublicCopy",
144                    [self hashString], @"TorrentHash",
145                    fDownloadFolder, @"DownloadFolder",
146                    [NSNumber numberWithBool: fUseIncompleteFolder], @"UseIncompleteFolder",
147                    [self isActive] ? @"NO" : @"YES", @"Paused",
148                    [self date], @"Date",
149                    [NSNumber numberWithBool: fRatioCustom], @"StopRatioCustom",
150                    [NSNumber numberWithBool: fShouldStopAtRatio], @"ShouldStopAtRatio",
151                    [NSNumber numberWithFloat: fRatioLimit], @"RatioLimit",
152                    [NSNumber numberWithBool: fLimitCustom], @"LimitSpeedCustom",
153                    [NSNumber numberWithBool: fCheckUpload], @"CheckUpload",
154                    [NSNumber numberWithInt: fUploadLimit], @"UploadLimit",
155                    [NSNumber numberWithBool: fCheckDownload], @"CheckDownload",
156                    [NSNumber numberWithInt: fDownloadLimit], @"DownloadLimit",
157                    [NSNumber numberWithBool: fWaitToStart], @"WaitToStart",
158                    [self orderValue], @"OrderValue", nil];
159   
160    if (fUseIncompleteFolder)
161        [history setObject: fIncompleteFolder forKey: @"IncompleteFolder"];
162
163    if (fPublicTorrent)
164        [history setObject: [self publicTorrentLocation] forKey: @"TorrentPath"];
165   
166    return history;
167}
168
169- (void) dealloc
170{
171    if (fHandle)
172    {
173        tr_torrentClose(fLib, fHandle);
174       
175        if (fDownloadFolder)
176            [fDownloadFolder release];
177        if (fIncompleteFolder)
178            [fIncompleteFolder release];
179       
180        if (fPublicTorrentLocation)
181            [fPublicTorrentLocation release];
182       
183        tr_torrentRemoveSaved(fHandle);
184       
185        [fDate release];
186       
187        if (fAnnounceDate)
188            [fAnnounceDate release];
189       
190        [fIcon release];
191        [fIconFlipped release];
192        [fIconSmall release];
193       
194        [fProgressString release];
195        [fStatusString release];
196        [fShortStatusString release];
197        [fRemainingTimeString release];
198       
199        [fBitmap release];
200        free(fPieces);
201    }
202    [super dealloc];
203}
204
205- (void) setDownloadFolder: (NSString *) path
206{
207    if (path)
208        fDownloadFolder = [path copy];
209   
210    if (!fUseIncompleteFolder || [[NSFileManager defaultManager] fileExistsAtPath:
211                                    [path stringByAppendingPathComponent: [self name]]])
212        tr_torrentSetFolder(fHandle, [path UTF8String]);
213    else
214        tr_torrentSetFolder(fHandle, [fIncompleteFolder UTF8String]);
215}
216
217- (NSString *) downloadFolder
218{
219    return [NSString stringWithUTF8String: tr_torrentGetFolder(fHandle)];
220}
221
222- (void) getAvailability: (int8_t *) tab size: (int) size
223{
224    tr_torrentAvailability(fHandle, tab, size);
225}
226
227- (void) getAmountFinished: (float *) tab size: (int) size
228{
229    tr_torrentAmountFinished(fHandle, tab, size);
230}
231
232- (void) update
233{
234    fStat = tr_torrentStat(fHandle);
235   
236    //notification when downloading finished
237    if ([self justFinished])
238    {
239        BOOL canMove = YES;
240       
241        //move file from incomplete folder to download folder
242        if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
243            && (canMove = [self alertForMoveVolumeAvailable]))
244        {
245            tr_torrentStop(fHandle);
246            if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
247                                    toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
248                tr_torrentSetFolder(fHandle, [fDownloadFolder UTF8String]);
249            tr_torrentStart(fHandle);
250        }
251       
252        if (!canMove)
253        {
254            fUseIncompleteFolder = NO;
255           
256            [fDownloadFolder release];
257            fDownloadFolder = fIncompleteFolder;
258            fIncompleteFolder = nil;
259        }
260       
261        fStat = tr_torrentStat(fHandle);
262        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
263    }
264   
265    //check to stop for ratio
266    if ([self isSeeding] && ((fRatioCustom && fShouldStopAtRatio && [self ratio] >= fRatioLimit)
267            || (!fRatioCustom && [fDefaults boolForKey: @"RatioCheck"]
268                && [self ratio] >= [fDefaults floatForKey: @"RatioLimit"])))
269    {
270        [self stopTransfer];
271        fStat = tr_torrentStat(fHandle);
272       
273        fFinishedSeeding = YES;
274       
275        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
276    }
277
278    [fProgressString setString: @""];
279    if ([self progress] < 1.0)
280        [fProgressString appendFormat: NSLocalizedString(@"%@ of %@ (%.2f%%)", "Torrent -> progress string"),
281                            [NSString stringForFileSize: [self downloadedValid]],
282                            [NSString stringForFileSize: [self size]], 100.0 * [self progress]];
283    else
284        [fProgressString appendFormat: NSLocalizedString(@"%@, uploaded %@ (Ratio: %@)", "Torrent -> progress string"),
285                [NSString stringForFileSize: [self size]], [NSString stringForFileSize: [self uploadedTotal]],
286                [NSString stringForRatio: [self ratio]]];
287
288    switch (fStat->status)
289    {
290        NSString * tempString;
291   
292        case TR_STATUS_PAUSE:
293            if (fWaitToStart)
294            {
295                tempString = [self progress] < 1.0
296                        ? [NSLocalizedString(@"Waiting to start", "Torrent -> status string") stringByAppendingEllipsis]
297                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
298            }
299            else if (fFinishedSeeding)
300                tempString = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
301            else
302                tempString = NSLocalizedString(@"Paused", "Torrent -> status string");
303           
304            [fStatusString setString: tempString];
305            [fShortStatusString setString: tempString];
306           
307            break;
308
309        case TR_STATUS_CHECK:
310            tempString = [NSLocalizedString(@"Checking existing files", "Torrent -> status string") stringByAppendingEllipsis];
311           
312            [fStatusString setString: tempString];
313            [fShortStatusString setString: tempString];
314            [fRemainingTimeString setString: tempString];
315           
316            break;
317
318        case TR_STATUS_DOWNLOAD:
319            [fStatusString setString: @""];
320            if ([self totalPeers] > 1)
321                [fStatusString appendFormat: NSLocalizedString(@"Downloading from %d of %d peers",
322                                                "Torrent -> status string"), [self peersUploading], [self totalPeers]];
323            else
324                [fStatusString appendFormat: NSLocalizedString(@"Downloading from %d of %d peer",
325                                                "Torrent -> status string"), [self peersUploading], [self totalPeers]];
326           
327            [fRemainingTimeString setString: @""];
328            int eta = [self eta];
329            if (eta < 0)
330            {
331                [fRemainingTimeString setString: NSLocalizedString(@"Unknown", "Torrent -> remaining time")];
332                [fProgressString appendString: NSLocalizedString(@" - remaining time unknown", "Torrent -> progress string")];
333            }
334            else
335            {
336                if (eta < 60)
337                    [fRemainingTimeString appendFormat: NSLocalizedString(@"%d sec", "Torrent -> remaining time"), eta];
338                else if (eta < 3600) //60 * 60
339                    [fRemainingTimeString appendFormat: NSLocalizedString(@"%d min %02d sec", "Torrent -> remaining time"),
340                                                            eta / 60, eta % 60];
341                else if (eta < 86400) //24 * 60 * 60
342                    [fRemainingTimeString appendFormat: NSLocalizedString(@"%d hr %02d min", "Torrent -> remaining time"),
343                                                            eta / 3600, (eta / 60) % 60];
344                else
345                {
346                    if (eta / 86400 > 1)
347                        [fRemainingTimeString appendFormat: NSLocalizedString(@"%d days %d hr", "Torrent -> remaining time"),
348                                                                eta / 86400, (eta / 3600) % 24];
349                    else
350                        [fRemainingTimeString appendFormat: NSLocalizedString(@"%d day %d hr", "Torrent -> remaining time"),
351                                                                eta / 86400, (eta / 3600) % 24];
352                }
353               
354                [fProgressString appendFormat: NSLocalizedString(@" - %@ remaining", "Torrent -> progress string"),
355                                                                    fRemainingTimeString];
356            }
357           
358            break;
359
360        case TR_STATUS_SEED:
361            [fStatusString setString: @""];
362            if ([self totalPeers] > 1)
363                [fStatusString appendFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
364                                                [self peersDownloading], [self totalPeers]];
365            else
366                [fStatusString appendFormat: NSLocalizedString(@"Seeding to %d of %d peer", "Torrent -> status string"),
367                                                [self peersDownloading], [self totalPeers]];
368           
369            break;
370
371        case TR_STATUS_STOPPING:
372            tempString = [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
373       
374            [fStatusString setString: tempString];
375            [fShortStatusString setString: tempString];
376           
377            break;
378    }
379   
380    if (fStat->error & TR_ETRACKER)
381    {
382        [fStatusString setString: [NSLocalizedString(@"Error: ", "Torrent -> status string") stringByAppendingString:
383                                    [self errorMessage]]];
384    }
385   
386    BOOL wasError = fError;
387    if ((fError = fStat->cannotConnect))
388    {
389        if (!wasError && [self isActive])
390            [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
391    }
392
393    if ([self isActive] && fStat->status != TR_STATUS_CHECK )
394    {
395        NSString * stringToAppend = @"";
396        if ([self progress] < 1.0)
397        {
398            stringToAppend = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, ", "Torrent -> status string"),
399                                [NSString stringForSpeed: [self downloadRate]]];
400            [fShortStatusString setString: @""];
401        }
402        else
403        {
404            NSString * ratioString = [NSString stringForRatio: [self ratio]];
405       
406            [fShortStatusString setString: [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, ",
407                                            "Torrent -> status string"), ratioString]];
408            [fRemainingTimeString setString: [NSLocalizedString(@"Ratio: ", "Torrent -> status string")
409                                                stringByAppendingString: ratioString]];
410        }
411       
412        stringToAppend = [stringToAppend stringByAppendingString: [NSLocalizedString(@"UL: ", "Torrent -> status string")
413                                            stringByAppendingString: [NSString stringForSpeed: [self uploadRate]]]];
414
415        [fStatusString appendFormat: @" - %@", stringToAppend];
416        [fShortStatusString appendString: stringToAppend];
417    }
418}
419
420- (NSDictionary *) infoForCurrentView
421{
422    NSMutableDictionary * info = [NSMutableDictionary dictionaryWithObjectsAndKeys:
423                                    [self name], @"Name",
424                                    [NSNumber numberWithBool: [self isSeeding]], @"Seeding",
425                                    [NSNumber numberWithFloat: [self progress]], @"Progress",
426                                    [NSNumber numberWithBool: [self isActive]], @"Active",
427                                    [NSNumber numberWithBool: [self isError]], @"Error", nil];
428   
429    if (![fDefaults boolForKey: @"SmallView"])
430    {
431        [info setObject: fIconFlipped forKey: @"Icon"];
432        [info setObject: [self progressString] forKey: @"ProgressString"];
433        [info setObject: [self statusString] forKey: @"StatusString"];
434    }
435    else
436    {
437        [info setObject: fIconSmall forKey: @"Icon"];
438        [info setObject: [self remainingTimeString] forKey: @"RemainingTimeString"];
439        [info setObject: [self shortStatusString] forKey: @"ShortStatusString"];
440    }
441   
442    if ([fDefaults boolForKey: @"UseAdvancedBar"])
443        [info setObject: [self advancedBar] forKey: @"AdvancedBar"];
444   
445    return info;
446}
447
448- (void) startTransfer
449{
450    fWaitToStart = YES;
451    fFinishedSeeding = NO;
452   
453    if (![self isActive] && [self alertForVolumeAvailable] && [self alertForRemainingDiskSpace])
454    {
455        tr_torrentStart(fHandle);
456        [self update];
457    }
458}
459
460- (void) stopTransfer
461{
462    fError = NO;
463    fWaitToStart = NO;
464   
465    if ([self isActive])
466    {
467        tr_torrentStop(fHandle);
468        [self update];
469
470        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
471    }
472}
473
474- (void) stopTransferForQuit
475{
476    if ([self isActive])
477        tr_torrentStop(fHandle);
478}
479
480- (void) sleep
481{
482    if ((fResumeOnWake = [self isActive]))
483        tr_torrentStop(fHandle);
484}
485
486- (void) wakeUp
487{
488    if (fResumeOnWake)
489        tr_torrentStart(fHandle);
490}
491
492- (void) announce
493{
494    if (![self isActive])
495        return;
496   
497    tr_manualUpdate(fHandle);
498   
499    if (fAnnounceDate)
500        [fAnnounceDate release];
501    fAnnounceDate = [[NSDate date] retain];
502}
503
504- (NSDate *) announceDate
505{
506    return fAnnounceDate;
507}
508
509- (float) ratio
510{
511    return fStat->ratio;
512}
513
514- (BOOL) customRatioSetting
515{
516        return fRatioCustom;
517}
518
519- (void) setCustomRatioSetting: (BOOL) setting
520{
521    fRatioCustom = setting;
522}
523
524- (BOOL) shouldStopAtRatio
525{
526        return fShouldStopAtRatio;
527}
528
529- (void) setShouldStopAtRatio: (BOOL) setting
530{
531    fShouldStopAtRatio = setting;
532}
533
534- (float) ratioLimit
535{
536    return fRatioLimit;
537}
538
539- (void) setRatioLimit: (float) limit
540{
541    if (limit >= 0)
542        fRatioLimit = limit;
543}
544
545- (BOOL) checkUpload
546{
547    return fCheckUpload;
548}
549
550- (void) setLimitUpload: (BOOL) limit
551{
552    fCheckUpload = limit;
553    [self updateSpeedSetting];
554}
555
556- (int) uploadLimit
557{
558    return fUploadLimit;
559}
560
561- (void) setUploadLimit: (int) limit
562{
563    fUploadLimit = limit;
564    [self updateSpeedSetting];
565}
566
567- (BOOL) checkDownload
568{
569    return fCheckDownload;
570}
571
572- (void) setLimitDownload: (BOOL) limit
573{
574    fCheckDownload = limit;
575    [self updateSpeedSetting];
576}
577
578- (int) downloadLimit
579{
580    return fDownloadLimit;
581}
582
583- (void) setDownloadLimit: (int) limit
584{
585    fDownloadLimit = limit;
586    [self updateSpeedSetting];
587}
588
589- (BOOL) customLimitSetting
590{
591    return fLimitCustom;
592}
593
594- (void) setCustomLimitSetting: (BOOL) setting
595{
596    fLimitCustom = setting;
597    [self updateSpeedSetting];
598}
599
600- (void) updateSpeedSetting
601{
602    tr_setUseCustomLimit(fHandle, fLimitCustom);
603    tr_setUploadLimit(fHandle, fCheckUpload ? fUploadLimit : -1);
604    tr_setDownloadLimit(fHandle, fCheckDownload ? fDownloadLimit : -1);
605}
606
607- (void) setWaitToStart: (BOOL) wait
608{
609    fWaitToStart = wait;
610}
611
612- (BOOL) waitingToStart
613{
614    return fWaitToStart;
615}
616
617- (void) revealData
618{
619    [[NSWorkspace sharedWorkspace] selectFile: [self dataLocation] inFileViewerRootedAtPath: nil];
620}
621
622- (void) revealPublicTorrent
623{
624    if (fPublicTorrent)
625        [[NSWorkspace sharedWorkspace] selectFile: fPublicTorrentLocation inFileViewerRootedAtPath: nil];
626}
627
628- (void) trashData
629{
630    [self trashFile: [self dataLocation]];
631}
632
633- (void) trashTorrent
634{
635    if (fPublicTorrent)
636        [self trashFile: [self publicTorrentLocation]];
637}
638
639- (BOOL) alertForRemainingDiskSpace
640{
641    if ([self progress] >= 1.0)
642        return YES;
643   
644    NSString * volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: [self downloadFolder]]
645                                                                                                objectAtIndex: 0];
646    NSDictionary * fsAttributes = [[NSFileManager defaultManager] fileSystemAttributesAtPath: [self downloadFolder]];
647    uint64_t remainingSpace = [[fsAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue],
648            torrentRemaining = [self size] - (uint64_t)[self downloadedValid];
649   
650    /*NSLog(@"Volume: %@", volumeName);
651    NSLog(@"Remaining disk space: %qu (%@)", remainingSpace, [NSString stringForFileSize: remainingSpace]);
652    NSLog(@"Torrent remaining size: %qu (%@)", torrentRemaining, [NSString stringForFileSize: torrentRemaining]);*/
653   
654    if (volumeName && remainingSpace <= torrentRemaining)
655    {
656        NSAlert * alert = [[NSAlert alloc] init];
657        [alert setMessageText: [NSString stringWithFormat:
658                                NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
659                                    "Torrent file disk space alert -> title"), [self name]]];
660        [alert setInformativeText: [NSString stringWithFormat:
661                        NSLocalizedString(@"The transfer will be paused. Clear up space on \"%@\" to continue.",
662                                            "Torrent file disk space alert -> message"), volumeName]];
663        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file disk space alert -> button")];
664        [alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent file disk space alert -> button")];
665       
666        BOOL ret = [alert runModal] != NSAlertFirstButtonReturn;
667       
668        [alert release];
669       
670        return ret;
671    }
672    return YES;
673}
674
675- (BOOL) alertForVolumeAvailable
676{
677    NSArray * pathComponents = [[self downloadFolder] pathComponents];
678    if ([pathComponents count] < 3)
679        return YES;
680   
681    NSString * volume = [[[pathComponents objectAtIndex: 0] stringByAppendingPathComponent:
682                    [pathComponents objectAtIndex: 1]] stringByAppendingPathComponent: [pathComponents objectAtIndex: 2]];
683   
684    /*NSLog(@"%@", [self downloadFolder]);
685    NSLog(@"Volume: %@", volume);*/
686   
687    if (![[NSFileManager defaultManager] fileExistsAtPath: volume])
688    {
689        NSString * volumeName = [pathComponents objectAtIndex: 2];
690       
691        NSAlert * alert = [[NSAlert alloc] init];
692        [alert setMessageText: [NSString stringWithFormat:
693                                NSLocalizedString(@"The volume for downloading \"%@\" cannot be found.",
694                                    "Volume cannot be found alert -> title"), [self name]]];
695        [alert setInformativeText: [NSString stringWithFormat:
696                        NSLocalizedString(@"The transfer will be paused. Mount the volume \"%@\" to continue.",
697                                            "Volume cannot be found alert -> message"), volumeName]];
698        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Volume cannot be found alert -> button")];
699        [alert addButtonWithTitle: [NSLocalizedString(@"Choose New Directory",
700                                    "Volume cannot be found alert -> directory button") stringByAppendingEllipsis]];
701       
702        if ([alert runModal] != NSAlertFirstButtonReturn)
703        {
704            NSOpenPanel * panel = [NSOpenPanel openPanel];
705           
706            [panel setPrompt: @"Select"];
707            [panel setAllowsMultipleSelection: NO];
708            [panel setCanChooseFiles: NO];
709            [panel setCanChooseDirectories: YES];
710
711            [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
712                                "Open torrent -> select destination folder"), [self name]]];
713           
714            [[NSNotificationCenter defaultCenter] postNotificationName: @"MakeWindowKey" object: nil];
715            [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: [NSApp keyWindow] modalDelegate: self
716                    didEndSelector: @selector(destinationChoiceClosed:returnCode:contextInfo:) contextInfo: nil];
717        }
718       
719        [alert release];
720       
721        return NO;
722    }
723   
724    return YES;
725}
726
727- (void) destinationChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) context
728{
729    NSString * folder = [[openPanel filenames] objectAtIndex: 0];
730    if (code == NSOKButton)
731    {
732        if (fUseIncompleteFolder)
733        {
734            [fIncompleteFolder release];
735            fIncompleteFolder = [folder retain];
736            [self setDownloadFolder: nil];
737        }
738        else
739        {
740            [fDownloadFolder release];
741            fDownloadFolder = folder;
742            [self setDownloadFolder: fDownloadFolder];
743        }
744       
745        [self startTransfer];
746        [self update];
747       
748        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateInfoSettings" object: nil];
749    }
750}
751
752- (BOOL) alertForMoveVolumeAvailable
753{
754    NSArray * pathComponents = [fDownloadFolder pathComponents];
755    if ([pathComponents count] < 3)
756        return YES;
757   
758    NSString * volume = [[[pathComponents objectAtIndex: 0] stringByAppendingPathComponent:
759                    [pathComponents objectAtIndex: 1]] stringByAppendingPathComponent: [pathComponents objectAtIndex: 2]];
760   
761    /*NSLog(@"%@", [self downloadFolder]);
762    NSLog(@"Volume: %@", volume);*/
763   
764    if (![[NSFileManager defaultManager] fileExistsAtPath: volume])
765    {
766        NSAlert * alert = [[NSAlert alloc] init];
767        [alert setMessageText: [NSString stringWithFormat:
768                                NSLocalizedString(@"The volume for moving the completed \"%@\" cannot be found.",
769                                    "Move volume cannot be found alert -> title"), [self name]]];
770        [alert setInformativeText: NSLocalizedString(@"The file will remain in its current location",
771                                    "Move volume cannot be found alert -> message")];
772        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move volume cannot be found alert -> button")];
773       
774        [alert runModal];
775        [alert release];
776       
777        return NO;
778    }
779   
780    return YES;
781}
782
783- (NSImage *) icon
784{
785    return fIcon;
786}
787
788- (NSImage *) iconFlipped
789{
790    return fIconFlipped;
791}
792
793- (NSImage *) iconSmall
794{
795    return fIconSmall;
796}
797
798- (NSString *) name
799{
800    return [NSString stringWithUTF8String: fInfo->name];
801}
802
803- (uint64_t) size
804{
805    return fInfo->totalSize;
806}
807
808- (NSString *) trackerAddress
809{
810    return [NSString stringWithFormat: @"http://%s:%d", fStat->trackerAddress, fStat->trackerPort];
811}
812
813- (NSString *) trackerAddressAnnounce
814{
815    return [NSString stringWithUTF8String: fStat->trackerAnnounce];
816}
817
818- (NSString *) comment
819{
820    return [NSString stringWithUTF8String: fInfo->comment];
821}
822
823- (NSString *) creator
824{
825    return [NSString stringWithUTF8String: fInfo->creator];
826}
827
828- (NSDate *) dateCreated
829{
830    int date = fInfo->dateCreated;
831    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
832}
833
834- (int) pieceSize
835{
836    return fInfo->pieceSize;
837}
838
839- (int) pieceCount
840{
841    return fInfo->pieceCount;
842}
843
844- (NSString *) hashString
845{
846    return [NSString stringWithUTF8String: fInfo->hashString];
847}
848
849- (BOOL) privateTorrent
850{
851    return fInfo->privateTorrent;
852}
853
854- (NSString *) torrentLocation
855{
856    return [NSString stringWithUTF8String: fInfo->torrent];
857}
858
859- (NSString *) publicTorrentLocation
860{
861    return fPublicTorrentLocation;
862}
863
864- (NSString *) dataLocation
865{
866    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
867}
868
869- (BOOL) publicTorrent
870{
871    return fPublicTorrent;
872}
873
874- (NSString *) stateString
875{
876    switch( fStat->status )
877    {
878        case TR_STATUS_PAUSE:
879            return NSLocalizedString(@"Paused", "Torrent -> status string");
880            break;
881
882        case TR_STATUS_CHECK:
883            return [NSLocalizedString(@"Checking existing files", "Torrent -> status string") stringByAppendingEllipsis];
884            break;
885
886        case TR_STATUS_DOWNLOAD:
887            return NSLocalizedString(@"Downloading", "Torrent -> status string");
888            break;
889
890        case TR_STATUS_SEED:
891            return NSLocalizedString(@"Seeding", "Torrent -> status string");
892            break;
893
894        case TR_STATUS_STOPPING:
895            return [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
896            break;
897       
898        default:
899            return NSLocalizedString(@"N/A", "Torrent -> status string");
900    }
901}
902
903- (float) progress
904{
905    return fStat->progress;
906}
907
908- (int) eta
909{
910    return fStat->eta;
911}
912
913- (BOOL) isActive
914{
915    return fStat->status & TR_STATUS_ACTIVE;
916}
917
918- (BOOL) isSeeding
919{
920    return fStat->status == TR_STATUS_SEED;
921}
922
923- (BOOL) isPaused
924{
925    return fStat->status == TR_STATUS_PAUSE;
926}
927
928- (BOOL) isError
929{
930    return fStat->error & TR_ETRACKER;
931}
932
933- (NSString *) errorMessage
934{
935    [NSString stringWithUTF8String: fStat->trackerError];
936}
937
938- (BOOL) justFinished
939{
940    return tr_getFinished(fHandle);
941}
942
943- (NSArray *) peers
944{
945    int totalPeers, i;
946    tr_peer_stat_t * peers = tr_torrentPeers(fHandle, & totalPeers);
947   
948    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
949    NSMutableDictionary * dic;
950   
951    tr_peer_stat_t * peer;
952    NSString * client;
953    for (i = 0; i < totalPeers; i++)
954    {
955        peer = &peers[i];
956       
957        dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
958            [NSNumber numberWithBool: peer->isConnected], @"Connected",
959            [NSNumber numberWithBool: peer->isIncoming], @"Incoming",
960            [NSString stringWithCString: (char *) peer->addr encoding: NSUTF8StringEncoding], @"IP",
961            [NSString stringWithCString: (char *) peer->client encoding: NSUTF8StringEncoding], @"Client",
962            [NSNumber numberWithFloat: peer->progress], @"Progress",
963            [NSNumber numberWithBool: peer->isDownloading], @"UL To",
964            [NSNumber numberWithBool: peer->isUploading], @"DL From",
965            [NSNumber numberWithInt: peer->port], @"Port", nil];
966       
967        if (peer->isDownloading)
968            [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
969        if (peer->isUploading)
970            [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
971       
972        [peerDics addObject: dic];
973    }
974   
975    tr_torrentPeersFree(peers, totalPeers);
976   
977    return peerDics;
978}
979
980- (NSString *) progressString
981{
982    return fProgressString;
983}
984
985- (NSString *) statusString
986{
987    return fStatusString;
988}
989
990- (NSString *) shortStatusString
991{
992    return fShortStatusString;
993}
994
995- (NSString *) remainingTimeString
996{
997    return fRemainingTimeString;
998}
999
1000- (int) seeders
1001{
1002    return fStat->seeders;
1003}
1004
1005- (int) leechers
1006{
1007    return fStat->leechers;
1008}
1009
1010- (int) completedFromTracker
1011{
1012    return fStat->completedFromTracker;
1013}
1014
1015- (int) totalPeers
1016{
1017    return fStat->peersTotal;
1018}
1019
1020- (int) totalPeersIncoming
1021{
1022    return fStat->peersIncoming;
1023}
1024
1025- (int) totalPeersOutgoing
1026{
1027    return [self totalPeers] - [self totalPeersIncoming];
1028}
1029
1030//peers uploading to you
1031- (int) peersUploading
1032{
1033    return fStat->peersUploading;
1034}
1035
1036//peers downloading from you
1037- (int) peersDownloading
1038{
1039    return fStat->peersDownloading;
1040}
1041
1042- (float) downloadRate
1043{
1044    return fStat->rateDownload;
1045}
1046
1047- (float) uploadRate
1048{
1049    return fStat->rateUpload;
1050}
1051
1052- (float) downloadedValid
1053{
1054    return [self progress] * [self size];
1055}
1056
1057- (uint64_t) downloadedTotal
1058{
1059    return fStat->downloaded;
1060}
1061
1062- (uint64_t) uploadedTotal
1063{
1064    return fStat->uploaded;
1065}
1066
1067- (float) swarmSpeed
1068{
1069    return fStat->swarmspeed;
1070}
1071
1072- (NSNumber *) orderValue
1073{
1074    return [NSNumber numberWithInt: fOrderValue];
1075}
1076
1077- (void) setOrderValue: (int) orderValue
1078{
1079    fOrderValue = orderValue;
1080}
1081
1082- (NSArray *) fileList
1083{
1084    int count = fInfo->fileCount, i;
1085    tr_file_t file;
1086    NSMutableArray * files = [NSMutableArray arrayWithCapacity: count];
1087   
1088    for (i = 0; i < count; i++)
1089    {
1090        file = fInfo->files[i];
1091        [files addObject: [NSDictionary dictionaryWithObjectsAndKeys:
1092            [[self downloadFolder] stringByAppendingPathComponent: [NSString stringWithUTF8String: file.name]], @"Name",
1093            [NSNumber numberWithUnsignedLongLong: file.length], @"Size", nil]];
1094    }
1095   
1096    return files;
1097}
1098
1099- (NSDate *) date
1100{
1101    return fDate;
1102}
1103
1104- (NSNumber *) stateSortKey
1105{
1106    if (![self isActive])
1107        return [NSNumber numberWithInt: 0];
1108    else if ([self isSeeding])
1109        return [NSNumber numberWithInt: 1];
1110    else
1111        return [NSNumber numberWithInt: 2];
1112}
1113
1114- (NSNumber *) progressSortKey
1115{
1116    //if finished downloading sort by ratio instead of progress
1117    float progress = [self progress];
1118    return [NSNumber numberWithFloat: progress < 1.0 ? progress : 100.0 + [self ratio]];
1119}
1120
1121@end
1122
1123
1124@implementation Torrent (Private)
1125
1126//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1127- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
1128        publicTorrent: (NSNumber *) publicTorrent
1129        date: (NSDate *) date
1130        stopRatioCustom: (NSNumber *) ratioCustom
1131        shouldStopAtRatio: (NSNumber *) shouldStopAtRatio
1132        ratioLimit: (NSNumber *) ratioLimit
1133        limitSpeedCustom: (NSNumber *) limitCustom
1134        checkUpload: (NSNumber *) checkUpload uploadLimit: (NSNumber *) uploadLimit
1135        checkDownload: (NSNumber *) checkDownload downloadLimit: (NSNumber *) downloadLimit
1136        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue
1137{
1138    if (!(self = [super init]))
1139        return nil;
1140
1141    fLib = lib;
1142    fDefaults = [NSUserDefaults standardUserDefaults];
1143
1144    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1145    if (fPublicTorrent)
1146        fPublicTorrentLocation = [path retain];
1147
1148    int error;
1149    if (hashString)
1150        fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], TR_FSAVEPRIVATE, & error);
1151   
1152    if (!fHandle && path)
1153        fHandle = tr_torrentInit(fLib, [path UTF8String], TR_FSAVEPRIVATE, & error);
1154
1155    if (!fHandle)
1156    {
1157        [self release];
1158        return nil;
1159    }
1160   
1161    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
1162    [nc addObserver: self selector: @selector(updateSpeedSetting:)
1163                name: @"UpdateSpeedSetting" object: nil];
1164   
1165    fInfo = tr_torrentInfo( fHandle );
1166
1167    fDate = date ? [date retain] : [[NSDate alloc] init];
1168   
1169    fRatioCustom = ratioCustom ? [ratioCustom boolValue] : NO;
1170    fShouldStopAtRatio = shouldStopAtRatio ? [shouldStopAtRatio boolValue] : [fDefaults boolForKey: @"RatioCheck"];
1171    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1172    fFinishedSeeding = NO;
1173   
1174    fLimitCustom = limitCustom ? [limitCustom boolValue] : NO;
1175    fCheckUpload = checkUpload ? [checkUpload boolValue] : NO;
1176    fUploadLimit = uploadLimit ? [uploadLimit intValue] : [fDefaults integerForKey: @"UploadLimit"];
1177    fCheckDownload = checkDownload ? [checkDownload boolValue] : NO;
1178    fDownloadLimit = downloadLimit ? [downloadLimit intValue] : [fDefaults integerForKey: @"DownloadLimit"];
1179    [self updateSpeedSetting];
1180   
1181    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1182    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1183    fError = NO;
1184   
1185    NSString * fileType = fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr') : [[self name] pathExtension];
1186    fIcon = [[NSWorkspace sharedWorkspace] iconForFileType: fileType];
1187    [fIcon retain];
1188   
1189    fIconFlipped = [fIcon copy];
1190    [fIconFlipped setFlipped: YES];
1191   
1192    fIconSmall = [fIconFlipped copy];
1193    [fIconSmall setScalesWhenResized: YES];
1194    [fIconSmall setSize: NSMakeSize(16.0, 16.0)];
1195
1196    fProgressString = [[NSMutableString alloc] initWithCapacity: 50];
1197    fStatusString = [[NSMutableString alloc] initWithCapacity: 75];
1198    fShortStatusString = [[NSMutableString alloc] initWithCapacity: 30];
1199    fRemainingTimeString = [[NSMutableString alloc] initWithCapacity: 30];
1200   
1201    //set up advanced bar
1202    fBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
1203        pixelsWide: MAX_PIECES pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
1204        isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
1205   
1206    fPieces = malloc(MAX_PIECES);
1207    int i;
1208    for (i = 0; i < MAX_PIECES; i++)
1209        fPieces[i] = BLANK_PIECE;
1210
1211    [self update];
1212    return self;
1213}
1214
1215- (NSImage *) advancedBar
1216{
1217    int h, w;
1218    uint32_t * p;
1219    uint8_t * bitmapData = [fBitmap bitmapData];
1220    int bytesPerRow = [fBitmap bytesPerRow];
1221
1222    int8_t * pieces = malloc(MAX_PIECES);
1223    [self getAvailability: pieces size: MAX_PIECES];
1224   
1225    //lines 2 to 14: blue, green, or gray depending on whether we have the piece or not
1226    int have = 0, avail = 0;
1227    uint32_t color;
1228    BOOL change;
1229    for (w = 0; w < MAX_PIECES; w++)
1230    {
1231        change = NO;
1232        if (pieces[w] < 0)
1233        {
1234            if (fPieces[w] != -1)
1235            {
1236                color = kBlue;
1237                fPieces[w] = -1;
1238                change = YES;
1239            }
1240            have++;
1241        }
1242        else if (pieces[w] == 0)
1243        {
1244            if (fPieces[w] != 0)
1245            {
1246                color = kGray;
1247                fPieces[w] = 0;
1248                change = YES;
1249            }
1250        }
1251        else
1252        {
1253            if (pieces[w] == 1)
1254            {
1255                if (fPieces[w] != 1)
1256                {
1257                    color = kGreen1;
1258                    fPieces[w] = 1;
1259                    change = YES;
1260                }
1261            }
1262            else if (pieces[w] == 2)
1263            {
1264                if (fPieces[w] != 2)
1265                {
1266                    color = kGreen2;
1267                    fPieces[w] = 2;
1268                    change = YES;
1269                }
1270            }
1271            else
1272            {
1273                if (fPieces[w] != 3)
1274                {
1275                    color = kGreen3;
1276                    fPieces[w] = 3;
1277                    change = YES;
1278                }
1279            }
1280            avail++;
1281        }
1282       
1283        if (change)
1284        {
1285            //point to pixel (w, 2) and draw "vertically"
1286            p = (uint32_t *)(bitmapData + 2 * bytesPerRow) + w;
1287            for (h = 2; h < BAR_HEIGHT; h++)
1288            {
1289                p[0] = color;
1290                p = (uint32_t *)((uint8_t *)p + bytesPerRow);
1291            }
1292        }
1293    }
1294   
1295    //first two lines: dark blue to show progression, green to show available
1296    p = (uint32_t *) bitmapData;
1297    for (w = 0; w < have; w++)
1298    {
1299        p[w] = kBlue2;
1300        p[w + bytesPerRow / 4] = kBlue2;
1301    }
1302    for (; w < avail + have; w++)
1303    {
1304        p[w] = kGreen3;
1305        p[w + bytesPerRow / 4] = kGreen3;
1306    }
1307    for (; w < MAX_PIECES; w++)
1308    {
1309        p[w] = kWhite;
1310        p[w + bytesPerRow / 4] = kWhite;
1311    }
1312   
1313    free(pieces);
1314   
1315    //actually draw image
1316    NSImage * bar = [[NSImage alloc] initWithSize: [fBitmap size]];
1317    [bar addRepresentation: fBitmap];
1318    [bar setScalesWhenResized: YES];
1319   
1320    return [bar autorelease];
1321}
1322
1323- (void) trashFile: (NSString *) path
1324{
1325    //attempt to move to trash
1326    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1327            source: [path stringByDeletingLastPathComponent] destination: @""
1328            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1329    {
1330        //if cannot trash, just delete it (will work if it is on a remote volume)
1331        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1332            NSLog(@"Could not trash %@", path);
1333    }
1334}
1335
1336@end
Note: See TracBrowser for help on using the repository browser.