source: trunk/macosx/Torrent.m @ 2327

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

If Transmission is opened by double-clicking a torrent file, open that torrent before setting up auto-import

  • Property svn:keywords set to Date Rev Author Id
File size: 56.5 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 2327 2007-07-11 01:40:49Z livings124 $
3 *
4 * Copyright (c) 2006-2007 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
33static int static_lastid = 0;
34
35@interface Torrent (Private)
36
37- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
38        publicTorrent: (NSNumber *) publicTorrent
39        downloadFolder: (NSString *) downloadFolder
40        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
41        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
42        dateActivity: (NSDate *) dateActivity
43        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
44        limitSpeedCustom: (NSNumber *) limitCustom
45        checkUpload: (NSNumber *) checkUpload uploadLimit: (NSNumber *) uploadLimit
46        checkDownload: (NSNumber *) checkDownload downloadLimit: (NSNumber *) downloadLimit
47                pex: (NSNumber *) pex
48        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;
49
50- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name;
51- (void) updateDownloadFolder;
52
53- (void) createFileList;
54- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings
55            withParent: (NSMutableDictionary *) parent previousPath: (NSString *) previousPath
56            flatList: (NSMutableArray *) flatList fileSize: (uint64_t) size index: (int) index;
57- (NSImage *) advancedBar;
58- (void) trashFile: (NSString *) path;
59
60@end
61
62@implementation Torrent
63
64// Used to optimize drawing. They contain packed RGBA pixels for every color needed.
65#define BE OSSwapBigToHostConstInt32
66
67static uint32_t kRed   = BE(0xFF6450FF), //255, 100, 80
68                kBlue = BE(0x50A0FFFF), //80, 160, 255
69                kBlue2 = BE(0x1E46B4FF), //30, 70, 180
70                kGray  = BE(0x969696FF), //150, 150, 150
71                kGreen1 = BE(0x99FFCCFF), //153, 255, 204
72                kGreen2 = BE(0x66FF99FF), //102, 255, 153
73                kGreen3 = BE(0x00FF66FF), //0, 255, 102
74                kWhite = BE(0xFFFFFFFF); //255, 255, 255
75
76- (id) initWithPath: (NSString *) path location: (NSString *) location forceDeleteTorrent: (BOOL) delete lib: (tr_handle_t *) lib
77{
78    self = [self initWithHash: nil path: path lib: lib
79            publicTorrent: delete ? [NSNumber numberWithBool: NO] : nil
80            downloadFolder: location
81            useIncompleteFolder: nil incompleteFolder: nil
82            dateAdded: nil dateCompleted: nil
83            dateActivity: nil
84            ratioSetting: nil ratioLimit: nil
85            limitSpeedCustom: nil
86            checkUpload: nil uploadLimit: nil
87            checkDownload: nil downloadLimit: nil
88                        pex: nil
89            waitToStart: nil orderValue: nil];
90   
91    if (self)
92    {
93        if (!fPublicTorrent)
94            [self trashFile: path];
95    }
96    return self;
97}
98
99- (id) initWithHistory: (NSDictionary *) history lib: (tr_handle_t *) lib
100{
101    self = [self initWithHash: [history objectForKey: @"TorrentHash"]
102                path: [history objectForKey: @"TorrentPath"] lib: lib
103                publicTorrent: [history objectForKey: @"PublicCopy"]
104                downloadFolder: [history objectForKey: @"DownloadFolder"]
105                useIncompleteFolder: [history objectForKey: @"UseIncompleteFolder"]
106                incompleteFolder: [history objectForKey: @"IncompleteFolder"]
107                dateAdded: [history objectForKey: @"Date"]
108                                dateCompleted: [history objectForKey: @"DateCompleted"]
109                dateActivity: [history objectForKey: @"DateActivity"]
110                ratioSetting: [history objectForKey: @"RatioSetting"]
111                ratioLimit: [history objectForKey: @"RatioLimit"]
112                limitSpeedCustom: [history objectForKey: @"LimitSpeedCustom"]
113                checkUpload: [history objectForKey: @"CheckUpload"]
114                uploadLimit: [history objectForKey: @"UploadLimit"]
115                checkDownload: [history objectForKey: @"CheckDownload"]
116                downloadLimit: [history objectForKey: @"DownloadLimit"]
117                                pex: [history objectForKey: @"Pex"]
118                waitToStart: [history objectForKey: @"WaitToStart"]
119                orderValue: [history objectForKey: @"OrderValue"]];
120   
121    if (self)
122    {
123        //start transfer
124        NSNumber * active;
125        if ((active = [history objectForKey: @"Active"]) && [active boolValue])
126        {
127            fStat = tr_torrentStat(fHandle);
128            [self startTransfer];
129        }
130    }
131    return self;
132}
133
134- (NSDictionary *) history
135{
136    NSMutableDictionary * history = [NSMutableDictionary dictionaryWithObjectsAndKeys:
137                    [NSNumber numberWithBool: fPublicTorrent], @"PublicCopy",
138                    [self hashString], @"TorrentHash",
139                    fDownloadFolder, @"DownloadFolder",
140                    [NSNumber numberWithBool: fUseIncompleteFolder], @"UseIncompleteFolder",
141                    [NSNumber numberWithBool: [self isActive]], @"Active",
142                    fDateAdded, @"Date",
143                    [NSNumber numberWithInt: fRatioSetting], @"RatioSetting",
144                    [NSNumber numberWithFloat: fRatioLimit], @"RatioLimit",
145                    [NSNumber numberWithInt: fCheckUpload], @"CheckUpload",
146                    [NSNumber numberWithInt: fUploadLimit], @"UploadLimit",
147                    [NSNumber numberWithInt: fCheckDownload], @"CheckDownload",
148                    [NSNumber numberWithInt: fDownloadLimit], @"DownloadLimit",
149                    [NSNumber numberWithBool: fWaitToStart], @"WaitToStart",
150                    [self orderValue], @"OrderValue",
151                    nil];
152   
153    if (fIncompleteFolder)
154        [history setObject: fIncompleteFolder forKey: @"IncompleteFolder"];
155
156    if (fPublicTorrent)
157        [history setObject: [self publicTorrentLocation] forKey: @"TorrentPath"];
158       
159        if (![self privateTorrent])
160                [history setObject: [NSNumber numberWithBool: fPex] forKey: @"Pex"];
161       
162        if (fDateCompleted)
163                [history setObject: fDateCompleted forKey: @"DateCompleted"];
164   
165    NSDate * dateActivity = [self dateActivity];
166    if (dateActivity)
167                [history setObject: dateActivity forKey: @"DateActivity"];
168       
169    return history;
170}
171
172- (void) dealloc
173{
174    [fDownloadFolder release];
175    [fIncompleteFolder release];
176   
177    [fPublicTorrentLocation release];
178   
179    [fDateAdded release];
180    [fDateCompleted release];
181    [fDateActivity release];
182   
183    [fAnnounceDate release];
184   
185    [fIcon release];
186    [fIconFlipped release];
187    [fIconSmall release];
188   
189    [fProgressString release];
190    [fStatusString release];
191    [fShortStatusString release];
192    [fRemainingTimeString release];
193   
194    [fFileList release];
195    [fFlatFileList release];
196   
197    [fBitmap release];
198   
199    if (fPieces)
200        free(fPieces);
201   
202    [super dealloc];
203}
204
205- (void) closeTorrent
206{
207    tr_torrentClose(fHandle);
208}
209
210- (void) closeRemoveTorrent
211{
212    tr_torrentRemoveSaved(fHandle);
213    [self closeTorrent];
214}
215
216- (void) changeIncompleteDownloadFolder: (NSString *) folder
217{
218    fUseIncompleteFolder = folder != nil;
219    if (fIncompleteFolder)
220    {
221        [fIncompleteFolder release];
222        fIncompleteFolder = nil;
223    }
224   
225    if (folder)
226        fIncompleteFolder = [folder retain];
227   
228    [self updateDownloadFolder];
229}
230
231- (void) changeDownloadFolder: (NSString *) folder
232{
233    if (fDownloadFolder)
234        [fDownloadFolder release];
235    fDownloadFolder = [folder retain];
236   
237    [self updateDownloadFolder];
238}
239
240- (NSString *) downloadFolder
241{
242    return [NSString stringWithUTF8String: tr_torrentGetFolder(fHandle)];
243}
244
245- (void) getAvailability: (int8_t *) tab size: (int) size
246{
247    tr_torrentAvailability(fHandle, tab, size);
248}
249
250- (void) getAmountFinished: (float *) tab size: (int) size
251{
252    tr_torrentAmountFinished(fHandle, tab, size);
253}
254
255- (void) update
256{
257    fStat = tr_torrentStat(fHandle);
258   
259    //notification when downloading finished
260    if (tr_getComplete(fHandle) || tr_getDone(fHandle))
261    {
262        BOOL canMove = YES;
263       
264        //move file from incomplete folder to download folder
265        if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
266            && (canMove = [self alertForMoveFolderAvailable]))
267        {
268            //pause without actually stopping
269            tr_setUseCustomUpload(fHandle, 1);
270            tr_setUploadLimit(fHandle, 0);
271           
272            tr_setUseCustomDownload(fHandle, 1);
273            tr_setDownloadLimit(fHandle, 0);
274           
275            if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
276                                    toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
277                [self updateDownloadFolder];
278           
279            [self updateSpeedSetting];
280        }
281       
282        if (!canMove)
283        {
284            fUseIncompleteFolder = NO;
285           
286            [fDownloadFolder release];
287            fDownloadFolder = fIncompleteFolder;
288            fIncompleteFolder = nil;
289        }
290               
291                if (fDateCompleted)
292                        [fDateCompleted release];
293                fDateCompleted = [[NSDate alloc] init];
294       
295        fStat = tr_torrentStat(fHandle);
296        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
297    }
298    else if (tr_getIncomplete(fHandle))
299        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
300    else;
301   
302    //check to stop for ratio
303    float stopRatio;
304    if ([self isSeeding] && (stopRatio = [self actualStopRatio]) != INVALID && [self ratio] >= stopRatio)
305    {
306        [self stopTransfer];
307        fStat = tr_torrentStat(fHandle);
308       
309        fFinishedSeeding = YES;
310       
311        [self setRatioSetting: NSOffState];
312        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
313    }
314       
315        NSMutableString * progressString = [NSMutableString stringWithString: @""],
316                                        * remainingTimeString = [NSMutableString stringWithString: @""],
317                                        * statusString = [NSMutableString string],
318                                        * shortStatusString = [NSMutableString string];
319
320    if (![self allDownloaded])
321        [progressString appendFormat: NSLocalizedString(@"%@ of %@ (%.2f%%)", "Torrent -> progress string"),
322                            [NSString stringForFileSize: [self downloadedValid]],
323                            [NSString stringForFileSize: [self size]], 100.0 * [self progress]];
324    else if ([self progress] < 1.0)
325        [progressString appendFormat: NSLocalizedString(@"%@ of %@ (%.2f%%), uploaded %@ (Ratio: %@)",
326                "Torrent -> progress string"),
327                [NSString stringForFileSize: [self downloadedValid]], [NSString stringForFileSize: [self size]],
328                100.0 * [self progress], [NSString stringForFileSize: [self uploadedTotal]],
329                [NSString stringForRatio: [self ratio]]];
330    else
331        [progressString appendFormat: NSLocalizedString(@"%@, uploaded %@ (Ratio: %@)", "Torrent -> progress string"),
332                [NSString stringForFileSize: [self size]], [NSString stringForFileSize: [self uploadedTotal]],
333                [NSString stringForRatio: [self ratio]]];
334
335    BOOL wasChecking = fChecking;
336    fChecking = NO;
337    switch (fStat->status)
338    {
339        NSString * tempString;
340       
341        case TR_STATUS_STOPPED:
342            if (fWaitToStart)
343            {
344                tempString = ![self allDownloaded]
345                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
346                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
347            }
348            else if (fFinishedSeeding)
349                tempString = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
350            else
351                tempString = NSLocalizedString(@"Paused", "Torrent -> status string");
352           
353            [statusString setString: tempString];
354            [shortStatusString setString: tempString];
355           
356            break;
357
358        case TR_STATUS_CHECK_WAIT:
359            tempString = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string")
360                            stringByAppendingEllipsis];
361           
362            [statusString setString: tempString];
363            [shortStatusString setString: tempString];
364            [remainingTimeString setString: tempString];
365           
366            fChecking = YES;
367           
368            break;
369
370        case TR_STATUS_CHECK:
371            tempString = [NSLocalizedString(@"Checking existing data", "Torrent -> status string") stringByAppendingEllipsis];
372           
373            [statusString setString: tempString];
374            [shortStatusString setString: tempString];
375            [remainingTimeString setString: tempString];
376           
377            fChecking = YES;
378           
379            break;
380
381        case TR_STATUS_DOWNLOAD:
382            [statusString setString: @""];
383            if ([self totalPeers] != 1)
384                [statusString appendFormat: NSLocalizedString(@"Downloading from %d of %d peers",
385                                                "Torrent -> status string"), [self peersUploading], [self totalPeers]];
386            else
387                [statusString appendFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
388                                                "Torrent -> status string"), [self peersUploading]];
389           
390            int eta = [self eta];
391            if (eta < 0)
392            {
393                [remainingTimeString setString: NSLocalizedString(@"Unknown", "Torrent -> remaining time")];
394                [progressString appendString: NSLocalizedString(@" - remaining time unknown", "Torrent -> progress string")];
395            }
396            else
397            {
398                if (eta < 60)
399                    [remainingTimeString appendFormat: NSLocalizedString(@"%d sec", "Torrent -> remaining time"), eta];
400                else if (eta < 3600) //60 * 60
401                    [remainingTimeString appendFormat: NSLocalizedString(@"%d min %d sec", "Torrent -> remaining time"),
402                                                            eta / 60, eta % 60];
403                else if (eta < 86400) //24 * 60 * 60
404                    [remainingTimeString appendFormat: NSLocalizedString(@"%d hr %d min", "Torrent -> remaining time"),
405                                                            eta / 3600, (eta / 60) % 60];
406                else
407                {
408                                        int days = eta / 86400;
409                    if (days > 1)
410                        [remainingTimeString appendFormat: NSLocalizedString(@"%d days %d hr", "Torrent -> remaining time"),
411                                                                days, (eta / 3600) % 24];
412                    else
413                        [remainingTimeString appendFormat: NSLocalizedString(@"1 day %d hr", "Torrent -> remaining time"),
414                                                                (eta / 3600) % 24];
415                }
416               
417                [progressString appendFormat: NSLocalizedString(@" - %@ remaining", "Torrent -> progress string"),
418                                                                    remainingTimeString];
419            }
420           
421            break;
422
423        case TR_STATUS_SEED:
424        case TR_STATUS_DONE:
425            [statusString setString: @""];
426            if ([self totalPeers] != 1)
427                [statusString appendFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
428                                                [self peersDownloading], [self totalPeers]];
429            else
430                [statusString appendFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
431                                                [self peersDownloading]];
432           
433            break;
434
435        case TR_STATUS_STOPPING:
436            tempString = [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
437       
438            [statusString setString: tempString];
439            [shortStatusString setString: tempString];
440           
441            break;
442    }
443   
444    //check for error
445    BOOL wasError = fError;
446    fError = [self isError];
447   
448    //check if stalled
449    BOOL wasStalled = fStalled;
450    fStalled = [self isActive] && [fDefaults boolForKey: @"CheckStalled"]
451                && [fDefaults integerForKey: @"StalledMinutes"] < [self stalledMinutes];
452   
453    //create strings for error or stalled
454    if (fError)
455        [statusString setString: [NSLocalizedString(@"Error: ", "Torrent -> status string")
456                                    stringByAppendingString: [self errorMessage]]];
457    else if (fStalled)
458        [statusString setString: [NSLocalizedString(@"Stalled, ", "Torrent -> status string")
459                                    stringByAppendingString: statusString]];
460    else;
461   
462    //update queue for checking (from downloading to seeding), stalled, or error
463    if ((wasChecking && !fChecking) || (!wasStalled && fStalled) || (!wasError && fError && [self isActive]))
464        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
465
466    if ([self isActive] && ![self isChecking])
467    {
468        NSString * stringToAppend = @"";
469        if (![self allDownloaded])
470        {
471            stringToAppend = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, ", "Torrent -> status string"),
472                                [NSString stringForSpeed: [self downloadRate]]];
473            [shortStatusString setString: @""];
474        }
475        else
476        {
477            NSString * ratioString = [NSString stringForRatio: [self ratio]];
478       
479            [shortStatusString setString: [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, ",
480                                            "Torrent -> status string"), ratioString]];
481            [remainingTimeString setString: [NSLocalizedString(@"Ratio: ", "Torrent -> status string")
482                                                stringByAppendingString: ratioString]];
483        }
484       
485        stringToAppend = [stringToAppend stringByAppendingString: [NSLocalizedString(@"UL: ", "Torrent -> status string")
486                                            stringByAppendingString: [NSString stringForSpeed: [self uploadRate]]]];
487
488        [statusString appendFormat: @" - %@", stringToAppend];
489        [shortStatusString appendString: stringToAppend];
490    }
491       
492        [fProgressString setString: progressString];
493        [fStatusString setString: statusString];
494        [fShortStatusString setString: shortStatusString];
495        [fRemainingTimeString setString: remainingTimeString];
496}
497
498- (NSDictionary *) infoForCurrentView
499{
500    NSMutableDictionary * info = [NSMutableDictionary dictionaryWithObjectsAndKeys:
501                                    [self name], @"Name",
502                                    [NSNumber numberWithFloat: [self progress]], @"Progress",
503                                    [NSNumber numberWithFloat: (float)fStat->left/[self size]], @"Left",
504                                    [NSNumber numberWithBool: [self isActive]], @"Active",
505                                    [NSNumber numberWithBool: [self isSeeding]], @"Seeding",
506                                    [NSNumber numberWithBool: [self isChecking]], @"Checking",
507                                    [NSNumber numberWithBool: fWaitToStart], @"Waiting",
508                                    [NSNumber numberWithBool: [self isError]], @"Error", nil];
509   
510    if ([self isSeeding])
511        [info setObject: [NSNumber numberWithFloat: [self progressStopRatio]] forKey: @"ProgressStopRatio"];
512   
513    if (![fDefaults boolForKey: @"SmallView"])
514    {
515        [info setObject: [self iconFlipped] forKey: @"Icon"];
516        [info setObject: [self progressString] forKey: @"ProgressString"];
517        [info setObject: [self statusString] forKey: @"StatusString"];
518    }
519    else
520    {
521        [info setObject: [self iconSmall] forKey: @"Icon"];
522        [info setObject: [self remainingTimeString] forKey: @"RemainingTimeString"];
523        [info setObject: [self shortStatusString] forKey: @"ShortStatusString"];
524    }
525   
526    if ([fDefaults boolForKey: @"UseAdvancedBar"])
527        [info setObject: [self advancedBar] forKey: @"AdvancedBar"];
528   
529    return info;
530}
531
532- (void) startTransfer
533{
534    fWaitToStart = NO;
535    fFinishedSeeding = NO;
536   
537    if (![self isActive] && [self alertForFolderAvailable] && [self alertForRemainingDiskSpace])
538    {
539        tr_torrentStart(fHandle);
540        [self update];
541    }
542}
543
544- (void) stopTransfer
545{
546    fError = NO;
547    fWaitToStart = NO;
548   
549    if ([self isActive])
550    {
551        tr_torrentStop(fHandle);
552        [self update];
553
554        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
555    }
556}
557
558- (void) sleep
559{
560    if ((fResumeOnWake = [self isActive]))
561        tr_torrentStop(fHandle);
562}
563
564- (void) wakeUp
565{
566    if (fResumeOnWake)
567        tr_torrentStart(fHandle);
568}
569
570- (void) announce
571{
572    if (![self isActive])
573        return;
574   
575    tr_manualUpdate(fHandle);
576   
577    if (fAnnounceDate)
578        [fAnnounceDate release];
579    fAnnounceDate = [[NSDate date] retain];
580}
581
582- (NSDate *) announceDate
583{
584    return fAnnounceDate;
585}
586
587- (void) resetCache
588{
589    tr_torrentRecheck(fHandle);
590    [self update];
591}
592
593- (float) ratio
594{
595    return fStat->ratio;
596}
597
598- (int) ratioSetting
599{
600    return fRatioSetting;
601}
602
603- (void) setRatioSetting: (int) setting
604{
605    fRatioSetting = setting;
606}
607
608- (float) ratioLimit
609{
610    return fRatioLimit;
611}
612
613- (void) setRatioLimit: (float) limit
614{
615    if (limit >= 0)
616        fRatioLimit = limit;
617}
618
619- (float) actualStopRatio
620{
621    if (fRatioSetting == NSOnState)
622        return fRatioLimit;
623    else if (fRatioSetting == NSMixedState && [fDefaults boolForKey: @"RatioCheck"])
624        return [fDefaults floatForKey: @"RatioLimit"];
625    else
626        return INVALID;
627}
628
629- (float) progressStopRatio
630{
631    float stopRatio, ratio;
632    if ((stopRatio = [self actualStopRatio]) == INVALID || (ratio = [self ratio]) >= stopRatio)
633        return 1.0;
634    else if (ratio > 0 && stopRatio > 0)
635        return ratio / stopRatio;
636    else
637        return 0;
638}
639
640- (int) checkUpload
641{
642    return fCheckUpload;
643}
644
645- (void) setCheckUpload: (int) setting
646{
647    fCheckUpload = setting;
648    [self updateSpeedSetting];
649}
650
651- (int) uploadLimit
652{
653    return fUploadLimit;
654}
655
656- (void) setUploadLimit: (int) limit
657{
658    fUploadLimit = limit;
659    [self updateSpeedSetting];
660}
661
662- (int) checkDownload
663{
664    return fCheckDownload;
665}
666
667- (void) setCheckDownload: (int) setting
668{
669    fCheckDownload = setting;
670    [self updateSpeedSetting];
671}
672
673- (int) downloadLimit
674{
675    return fDownloadLimit;
676}
677
678- (void) setDownloadLimit: (int) limit
679{
680    fDownloadLimit = limit;
681    [self updateSpeedSetting];
682}
683
684- (void) updateSpeedSetting
685{
686    tr_setUseCustomUpload(fHandle, fCheckUpload != NSMixedState);
687    tr_setUploadLimit(fHandle, fCheckUpload == NSOnState ? fUploadLimit : -1);
688   
689    tr_setUseCustomDownload(fHandle, fCheckDownload != NSMixedState);
690    tr_setDownloadLimit(fHandle, fCheckDownload == NSOnState ? fDownloadLimit : -1);
691}
692
693- (void) setWaitToStart: (BOOL) wait
694{
695    fWaitToStart = wait;
696}
697
698- (BOOL) waitingToStart
699{
700    return fWaitToStart;
701}
702
703- (void) revealData
704{
705    [[NSWorkspace sharedWorkspace] selectFile: [self dataLocation] inFileViewerRootedAtPath: nil];
706}
707
708- (void) revealPublicTorrent
709{
710    if (fPublicTorrent)
711        [[NSWorkspace sharedWorkspace] selectFile: fPublicTorrentLocation inFileViewerRootedAtPath: nil];
712}
713
714- (void) trashData
715{
716    [self trashFile: [self dataLocation]];
717}
718
719- (void) trashTorrent
720{
721    if (fPublicTorrent)
722        [self trashFile: [self publicTorrentLocation]];
723}
724
725- (void) moveTorrentDataFileTo: (NSString *) folder
726{
727    NSString * oldFolder = [self downloadFolder];
728    if (![oldFolder isEqualToString: folder] || ![fDownloadFolder isEqualToString: folder])
729    {
730        //check if moving inside itself
731        NSArray * oldComponents = [oldFolder pathComponents],
732                * newComponents = [folder pathComponents];
733        int count;
734       
735        if ((count = [oldComponents count]) < [newComponents count]
736                && [[newComponents objectAtIndex: count] isEqualToString: [self name]]
737                && [oldComponents isEqualToArray:
738                        [newComponents objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, count)]]])
739        {
740            NSAlert * alert = [[NSAlert alloc] init];
741            [alert setMessageText: NSLocalizedString(@"A folder cannot be moved to inside itself.",
742                                                        "Move inside itself alert -> title")];
743            [alert setInformativeText: [NSString stringWithFormat:
744                            NSLocalizedString(@"The move operation of \"%@\" cannot be done.",
745                                                "Move inside itself alert -> message"), [self name]]];
746            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move inside itself alert -> button")];
747           
748            [alert runModal];
749            [alert release];
750           
751            return;
752        }
753       
754        //pause without actually stopping
755        tr_setDownloadLimit(fHandle, 0);
756        tr_setUploadLimit(fHandle, 0);
757       
758        if ([[NSFileManager defaultManager] movePath: [oldFolder stringByAppendingPathComponent: [self name]]
759                            toPath: [folder stringByAppendingPathComponent: [self name]] handler: nil])
760        {
761            //get rid of both incomplete folder and old download folder, even if move failed
762            fUseIncompleteFolder = NO;
763            if (fIncompleteFolder)
764            {
765                [fIncompleteFolder release];
766                fIncompleteFolder = nil;
767            }
768            [self changeDownloadFolder: folder];
769           
770            [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateInfoSettings" object: nil];
771           
772            [self updateSpeedSetting];
773        }
774        else
775        {
776            [self updateSpeedSetting]; //restart before showing the alert
777       
778            NSAlert * alert = [[NSAlert alloc] init];
779            [alert setMessageText: NSLocalizedString(@"There was an error moving the data file.", "Move error alert -> title")];
780            [alert setInformativeText: [NSString stringWithFormat:
781                            NSLocalizedString(@"The move operation of \"%@\" cannot be done.",
782                                                "Move error alert -> message"), [self name]]];
783            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move error alert -> button")];
784           
785            [alert runModal];
786            [alert release];
787        }
788    }
789}
790
791- (void) copyTorrentFileTo: (NSString *) path
792{
793    [[NSFileManager defaultManager] copyPath: [self torrentLocation] toPath: path handler: nil];
794}
795
796- (BOOL) alertForRemainingDiskSpace
797{
798    if ([self allDownloaded] || ![fDefaults boolForKey: @"WarningRemainingSpace"])
799        return YES;
800   
801    NSString * volumeName;
802    if ((volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: [self downloadFolder]] objectAtIndex: 0]))
803    {
804        NSDictionary * fsAttributes = [[NSFileManager defaultManager] fileSystemAttributesAtPath: [self downloadFolder]];
805        uint64_t remainingSpace = [[fsAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue];
806       
807        if (remainingSpace <= fStat->left)
808        {
809            NSAlert * alert = [[NSAlert alloc] init];
810            [alert setMessageText: [NSString stringWithFormat:
811                                    NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
812                                        "Torrent file disk space alert -> title"), [self name]]];
813            [alert setInformativeText: [NSString stringWithFormat:
814                            NSLocalizedString(@"The transfer will be paused. Clear up space on \"%@\" to continue.",
815                                                "Torrent file disk space alert -> message"), volumeName]];
816            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file disk space alert -> button")];
817            [alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent file disk space alert -> button")];
818            [alert addButtonWithTitle: NSLocalizedString(@"Always Download", "Torrent file disk space alert -> button")];
819           
820            int result = [alert runModal];
821            [alert release];
822           
823            if (result == NSAlertThirdButtonReturn)
824                [fDefaults setBool: NO forKey: @"WarningRemainingSpace"];
825           
826            return result != NSAlertFirstButtonReturn;
827        }
828    }
829    return YES;
830}
831
832- (BOOL) alertForFolderAvailable
833{
834    if (access(tr_torrentGetFolder(fHandle), 0))
835    {
836        NSAlert * alert = [[NSAlert alloc] init];
837        [alert setMessageText: [NSString stringWithFormat:
838                                NSLocalizedString(@"The folder for downloading \"%@\" cannot be found.",
839                                    "Folder cannot be found alert -> title"), [self name]]];
840        [alert setInformativeText: [NSString stringWithFormat:
841                        NSLocalizedString(@"\"%@\" cannot be found. The transfer will be paused.",
842                                            "Folder cannot be found alert -> message"), [self downloadFolder]]];
843        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Folder cannot be found alert -> button")];
844        [alert addButtonWithTitle: [NSLocalizedString(@"Choose New Location",
845                                    "Folder cannot be found alert -> location button") stringByAppendingEllipsis]];
846       
847        if ([alert runModal] != NSAlertFirstButtonReturn)
848        {
849            NSOpenPanel * panel = [NSOpenPanel openPanel];
850           
851            [panel setPrompt: NSLocalizedString(@"Select", "Folder cannot be found alert -> prompt")];
852            [panel setAllowsMultipleSelection: NO];
853            [panel setCanChooseFiles: NO];
854            [panel setCanChooseDirectories: YES];
855            [panel setCanCreateDirectories: YES];
856
857            [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
858                                "Folder cannot be found alert -> select destination folder"), [self name]]];
859           
860            [[NSNotificationCenter defaultCenter] postNotificationName: @"MakeWindowKey" object: nil];
861            [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: [NSApp keyWindow] modalDelegate: self
862                    didEndSelector: @selector(destinationChoiceClosed:returnCode:contextInfo:) contextInfo: nil];
863        }
864       
865        [alert release];
866       
867        return NO;
868    }
869   
870    return YES;
871}
872
873- (void) destinationChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) context
874{
875    if (code != NSOKButton)
876        return;
877   
878    NSString * folder = [[openPanel filenames] objectAtIndex: 0];
879    if (fUseIncompleteFolder)
880        [self changeDownloadFolder: folder];
881    else
882        [self changeDownloadFolder: folder];
883   
884    [self startTransfer];
885    [self update];
886   
887    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateInfoSettings" object: nil];
888}
889
890- (BOOL) alertForMoveFolderAvailable
891{
892    if (access([fDownloadFolder UTF8String], 0))
893    {
894        NSAlert * alert = [[NSAlert alloc] init];
895        [alert setMessageText: [NSString stringWithFormat:
896                                NSLocalizedString(@"The folder for moving the completed \"%@\" cannot be found.",
897                                    "Move folder cannot be found alert -> title"), [self name]]];
898        [alert setInformativeText: [NSString stringWithFormat:
899                                NSLocalizedString(@"\"%@\" cannot be found. The file will remain in its current location.",
900                                    "Move folder cannot be found alert -> message"), fDownloadFolder]];
901        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move folder cannot be found alert -> button")];
902       
903        [alert runModal];
904        [alert release];
905       
906        return NO;
907    }
908   
909    return YES;
910}
911
912- (NSImage *) icon
913{
914    return fIcon;
915}
916
917- (NSImage *) iconFlipped
918{
919    if (!fIconFlipped)
920    {
921        fIconFlipped = [fIcon copy];
922        [fIconFlipped setFlipped: YES];
923    }
924    return fIconFlipped;
925}
926
927- (NSImage *) iconSmall
928{
929    if (!fIconSmall)
930    {
931        fIconSmall = [fIcon copy];
932        [fIconSmall setFlipped: YES];
933        [fIconSmall setScalesWhenResized: YES];
934        [fIconSmall setSize: NSMakeSize(16.0, 16.0)];
935    }
936    return fIconSmall;
937}
938
939- (NSString *) name
940{
941    return [NSString stringWithUTF8String: fInfo->name];
942}
943
944- (uint64_t) size
945{
946    return fInfo->totalSize;
947}
948
949- (NSString *) trackerAddress
950{
951    return [NSString stringWithFormat: @"http://%s:%d", fStat->tracker->address, fStat->tracker->port];
952}
953
954- (NSString *) trackerAddressAnnounce
955{
956    return [NSString stringWithUTF8String: fStat->tracker->announce];
957}
958
959- (NSString *) comment
960{
961    return [NSString stringWithUTF8String: fInfo->comment];
962}
963
964- (NSString *) creator
965{
966    return [NSString stringWithUTF8String: fInfo->creator];
967}
968
969- (NSDate *) dateCreated
970{
971    int date = fInfo->dateCreated;
972    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
973}
974
975- (int) pieceSize
976{
977    return fInfo->pieceSize;
978}
979
980- (int) pieceCount
981{
982    return fInfo->pieceCount;
983}
984
985- (NSString *) hashString
986{
987    return [NSString stringWithUTF8String: fInfo->hashString];
988}
989
990- (BOOL) privateTorrent
991{
992    return TR_FLAG_PRIVATE & fInfo->flags;
993}
994
995- (NSString *) torrentLocation
996{
997    return [NSString stringWithUTF8String: fInfo->torrent];
998}
999
1000- (NSString *) publicTorrentLocation
1001{
1002    return fPublicTorrentLocation;
1003}
1004
1005- (NSString *) dataLocation
1006{
1007    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
1008}
1009
1010- (BOOL) publicTorrent
1011{
1012    return fPublicTorrent;
1013}
1014
1015- (NSString *) stateString
1016{
1017    switch( fStat->status )
1018    {
1019        case TR_STATUS_STOPPED:
1020            return NSLocalizedString(@"Paused", "Torrent -> status string");
1021            break;
1022
1023        case TR_STATUS_CHECK:
1024            return [NSLocalizedString(@"Checking existing data", "Torrent -> status string") stringByAppendingEllipsis];
1025            break;
1026
1027        case TR_STATUS_DOWNLOAD:
1028            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1029            break;
1030
1031        case TR_STATUS_SEED:
1032        case TR_STATUS_DONE:
1033            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1034            break;
1035
1036        case TR_STATUS_STOPPING:
1037            return [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
1038            break;
1039       
1040        default:
1041            return NSLocalizedString(@"N/A", "Torrent -> status string");
1042    }
1043}
1044
1045- (float) progress
1046{
1047    return fStat->percentComplete;
1048}
1049
1050- (float) progressDone
1051{
1052    return fStat->percentDone;
1053}
1054
1055- (int) eta
1056{
1057    return fStat->eta;
1058}
1059
1060- (BOOL) isActive
1061{
1062    return fStat->status & TR_STATUS_ACTIVE;
1063}
1064
1065- (BOOL) isSeeding
1066{
1067    return fStat->status == TR_STATUS_SEED || fStat->status == TR_STATUS_DONE;
1068}
1069
1070- (BOOL) isPaused
1071{
1072    return fStat->status == TR_STATUS_STOPPED;
1073}
1074
1075- (BOOL) isChecking
1076{
1077    return fStat->status == TR_STATUS_CHECK || fStat->status == TR_STATUS_CHECK_WAIT;
1078}
1079
1080- (BOOL) allDownloaded
1081{
1082    return fStat->cpStatus != TR_CP_INCOMPLETE;
1083}
1084
1085- (BOOL) isError
1086{
1087    return fStat->error != 0;
1088}
1089
1090- (NSString *) errorMessage
1091{
1092    if (![self isError])
1093        return @"";
1094   
1095    NSString * error;
1096    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
1097        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
1098        error = NSLocalizedString(@"(unreadable error)", "Torrent -> error string unreadable");
1099   
1100    return error;
1101}
1102
1103- (NSArray *) peers
1104{
1105    int totalPeers, i;
1106    tr_peer_stat_t * peers = tr_torrentPeers(fHandle, & totalPeers);
1107   
1108    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
1109    NSMutableDictionary * dic;
1110   
1111    tr_peer_stat_t * peer;
1112    for (i = 0; i < totalPeers; i++)
1113    {
1114        peer = &peers[i];
1115       
1116        dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1117            [NSNumber numberWithBool: peer->isConnected], @"Connected",
1118            [NSNumber numberWithInt: peer->from], @"From",
1119            [NSString stringWithCString: (char *) peer->addr encoding: NSUTF8StringEncoding], @"IP",
1120            [NSNumber numberWithInt: peer->port], @"Port", nil];
1121       
1122        if (peer->isConnected)
1123        {
1124            [dic setObject: [NSNumber numberWithFloat: peer->progress] forKey: @"Progress"];
1125           
1126            if (peer->isDownloading)
1127                [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
1128            if (peer->isUploading)
1129                [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
1130           
1131            [dic setObject: [NSString stringWithCString: (char *) peer->client encoding: NSUTF8StringEncoding] forKey: @"Client"];
1132        }
1133        else
1134            [dic setObject: @"" forKey: @"Client"];
1135       
1136        [peerDics addObject: dic];
1137    }
1138   
1139    tr_torrentPeersFree(peers, totalPeers);
1140   
1141    return peerDics;
1142}
1143
1144- (NSString *) progressString
1145{
1146    return fProgressString;
1147}
1148
1149- (NSString *) statusString
1150{
1151    return fStatusString;
1152}
1153
1154- (NSString *) shortStatusString
1155{
1156    return fShortStatusString;
1157}
1158
1159- (NSString *) remainingTimeString
1160{
1161    return fRemainingTimeString;
1162}
1163
1164- (int) seeders
1165{
1166    return fStat->seeders;
1167}
1168
1169- (int) leechers
1170{
1171    return fStat->leechers;
1172}
1173
1174- (int) completedFromTracker
1175{
1176    return fStat->completedFromTracker;
1177}
1178
1179- (int) totalPeers
1180{
1181    return fStat->peersTotal;
1182}
1183
1184- (int) totalPeersTracker
1185{
1186    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1187}
1188
1189- (int) totalPeersIncoming
1190{
1191    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1192}
1193
1194- (int) totalPeersCache
1195{
1196    return fStat->peersFrom[TR_PEER_FROM_CACHE];
1197}
1198
1199- (int) totalPeersPex
1200{
1201    return fStat->peersFrom[TR_PEER_FROM_PEX];
1202}
1203
1204//peers uploading to you
1205- (int) peersUploading
1206{
1207    return fStat->peersUploading;
1208}
1209
1210//peers downloading from you
1211- (int) peersDownloading
1212{
1213    return fStat->peersDownloading;
1214}
1215
1216- (float) downloadRate
1217{
1218    return fStat->rateDownload;
1219}
1220
1221- (float) uploadRate
1222{
1223    return fStat->rateUpload;
1224}
1225
1226- (uint64_t) downloadedValid
1227{
1228    return fStat->downloadedValid;
1229}
1230
1231- (uint64_t) downloadedTotal
1232{
1233    return fStat->downloaded;
1234}
1235
1236- (uint64_t) uploadedTotal
1237{
1238    return fStat->uploaded;
1239}
1240
1241- (float) swarmSpeed
1242{
1243    return fStat->swarmspeed;
1244}
1245
1246- (BOOL) pex
1247{
1248        return fPex;
1249}
1250
1251- (void) setPex: (BOOL) setting
1252{
1253        if (![self privateTorrent])
1254        {
1255                fPex = setting;
1256                tr_torrentDisablePex(fHandle, !setting);
1257        }
1258}
1259
1260- (NSNumber *) orderValue
1261{
1262    return [NSNumber numberWithInt: fOrderValue];
1263}
1264
1265- (void) setOrderValue: (int) orderValue
1266{
1267    fOrderValue = orderValue;
1268}
1269
1270- (NSArray *) fileList
1271{
1272    return fFileList;
1273}
1274
1275- (int) fileCount
1276{
1277    return fInfo->fileCount;
1278}
1279
1280- (float) fileProgress: (int) index
1281{
1282    return tr_torrentFileCompletion(fHandle, index);
1283}
1284
1285- (int) checkForFiles: (NSIndexSet *) indexSet
1286{
1287    BOOL onState = NO, offState = NO;
1288    int index;
1289    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1290    {
1291        if (tr_torrentGetFileDL(fHandle, index) || ![self canChangeDownloadCheckForFiles: [NSIndexSet indexSetWithIndex: index]])
1292            onState = YES;
1293        else
1294            offState = YES;
1295       
1296        if (onState == offState)
1297            return NSMixedState;
1298    }
1299    return onState ? NSOnState : NSOffState;
1300}
1301
1302- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1303{
1304    if ([self fileCount] <= 1)
1305        return NO;
1306   
1307    int index;
1308    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1309        if ([self fileProgress: index] < 1.0)
1310            return YES;
1311    return NO;
1312}
1313
1314- (void) setFileCheckState: (int) state forIndexes: (NSIndexSet *) indexSet
1315{
1316    #warning multiples
1317    int index;
1318    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1319        tr_torrentSetFileDL(fHandle, index, state != NSOffState);
1320   
1321    [self update];
1322    if ([self isPaused])
1323        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self]; //for paused torrents
1324}
1325
1326- (void) setFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1327{
1328    #warning multiples
1329    int index;
1330    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1331        tr_torrentSetFilePriority(fHandle, index, priority);
1332}
1333
1334- (BOOL) hasFilePriority: (int) priority forIndexes: (NSIndexSet *) indexSet
1335{
1336    int index;
1337    for (index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1338        if (priority == tr_torrentGetFilePriority(fHandle, index)
1339                && [self canChangeDownloadCheckForFiles: [NSIndexSet indexSetWithIndex: index]])
1340            return YES;
1341    return NO;
1342}
1343
1344- (NSDate *) dateAdded
1345{
1346    return fDateAdded;
1347}
1348
1349- (NSDate *) dateCompleted
1350{
1351    return fDateCompleted;
1352}
1353
1354- (NSDate *) dateActivity
1355{
1356    uint64_t date = fStat->activityDate;
1357    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date / 1000] : fDateActivity;
1358}
1359
1360- (int) stalledMinutes
1361{
1362    uint64_t start;
1363    if ((start = fStat->startDate) == 0)
1364        return -1;
1365   
1366    NSDate * started = [NSDate dateWithTimeIntervalSince1970: start / 1000],
1367            * activity = [self dateActivity];
1368    if (!activity || [started compare: activity] == NSOrderedDescending)
1369        return -1 * [started timeIntervalSinceNow] / 60;
1370    else
1371        return -1 * [activity timeIntervalSinceNow] / 60;
1372}
1373
1374- (BOOL) isStalled
1375{
1376    return fStalled;
1377}
1378
1379- (NSNumber *) stateSortKey
1380{
1381    if (![self isActive])
1382        return [NSNumber numberWithInt: 0];
1383    else if ([self isSeeding])
1384        return [NSNumber numberWithInt: 1];
1385    else
1386        return [NSNumber numberWithInt: 2];
1387}
1388
1389- (NSNumber *) progressSortKey
1390{
1391    float progress;
1392    if ((progress = [self progress]) >= 1.0)
1393       progress += [self progressStopRatio];
1394   
1395    return [NSNumber numberWithFloat: progress];
1396}
1397
1398- (NSNumber *) ratioSortKey
1399{
1400    return [NSNumber numberWithFloat: [self ratio]];
1401}
1402
1403- (int) torrentID
1404{
1405    return fID;
1406}
1407
1408- (const tr_info_t *) torrentInfo
1409{
1410    return fInfo;
1411}
1412
1413- (tr_stat_t *) torrentStat
1414{
1415    return fStat;
1416}
1417
1418@end
1419
1420@implementation Torrent (Private)
1421
1422//if a hash is given, attempt to load that; otherwise, attempt to open file at path
1423- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
1424        publicTorrent: (NSNumber *) publicTorrent
1425        downloadFolder: (NSString *) downloadFolder
1426        useIncompleteFolder: (NSNumber *) useIncompleteFolder incompleteFolder: (NSString *) incompleteFolder
1427        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
1428        dateActivity: (NSDate *) dateActivity
1429        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
1430        limitSpeedCustom: (NSNumber *) limitCustom
1431        checkUpload: (NSNumber *) checkUpload uploadLimit: (NSNumber *) uploadLimit
1432        checkDownload: (NSNumber *) checkDownload downloadLimit: (NSNumber *) downloadLimit
1433                pex: (NSNumber *) pex
1434        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;
1435{
1436    if (!(self = [super init]))
1437        return nil;
1438   
1439    static_lastid++;
1440    fID = static_lastid;
1441   
1442    fLib = lib;
1443    fDefaults = [NSUserDefaults standardUserDefaults];
1444
1445    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
1446    if (fPublicTorrent)
1447        fPublicTorrentLocation = [path retain];
1448   
1449    fDownloadFolder = downloadFolder ? downloadFolder : [fDefaults stringForKey: @"DownloadFolder"];
1450    fDownloadFolder = [[fDownloadFolder stringByExpandingTildeInPath] retain];
1451   
1452    fUseIncompleteFolder = useIncompleteFolder ? [useIncompleteFolder boolValue]
1453                                : [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
1454    if (fUseIncompleteFolder)
1455    {
1456        fIncompleteFolder = incompleteFolder ? incompleteFolder : [fDefaults stringForKey: @"IncompleteDownloadFolder"];
1457        fIncompleteFolder = [[fIncompleteFolder stringByExpandingTildeInPath] retain];
1458    }
1459   
1460    NSString * currentDownloadFolder;
1461    tr_info_t info;
1462    int error;
1463    if (hashString)
1464    {
1465        if (tr_torrentParseHash(fLib, [hashString UTF8String], NULL, &info) == TR_OK)
1466        {
1467            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1468                                        ? fIncompleteFolder : fDownloadFolder;
1469            fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], [currentDownloadFolder UTF8String],
1470                                            TR_FLAG_SAVE | TR_FLAG_PAUSED, &error);
1471        }
1472        tr_metainfoFree(&info);
1473    }
1474    if (!fHandle && path)
1475    {
1476        if (tr_torrentParse(fLib, [path UTF8String], NULL, &info) == TR_OK)
1477        {
1478            currentDownloadFolder = [self shouldUseIncompleteFolderForName: [NSString stringWithUTF8String: info.name]]
1479                                        ? fIncompleteFolder : fDownloadFolder;
1480            fHandle = tr_torrentInit(fLib, [path UTF8String], [currentDownloadFolder UTF8String],
1481                                        TR_FLAG_SAVE | TR_FLAG_PAUSED, &error);
1482        }
1483        tr_metainfoFree(&info);
1484    }
1485    if (!fHandle)
1486    {
1487        [self release];
1488        return nil;
1489    }
1490   
1491    fInfo = tr_torrentInfo(fHandle);
1492   
1493    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
1494    [nc addObserver: self selector: @selector(updateSpeedSetting:)
1495                name: @"UpdateSpeedSetting" object: nil];
1496
1497    fDateAdded = dateAdded ? [dateAdded retain] : [[NSDate alloc] init];
1498        if (dateCompleted)
1499                fDateCompleted = [dateCompleted retain];
1500    if (dateActivity)
1501                fDateActivity = [dateActivity retain];
1502       
1503    fRatioSetting = ratioSetting ? [ratioSetting intValue] : NSMixedState;
1504    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
1505    fFinishedSeeding = NO;
1506   
1507    fCheckUpload = checkUpload ? [checkUpload intValue] : NSMixedState;
1508    fUploadLimit = uploadLimit ? [uploadLimit intValue] : [fDefaults integerForKey: @"UploadLimit"];
1509    fCheckDownload = checkDownload ? [checkDownload intValue] : NSMixedState;
1510    fDownloadLimit = downloadLimit ? [downloadLimit intValue] : [fDefaults integerForKey: @"DownloadLimit"];
1511    [self updateSpeedSetting];
1512       
1513        if ([self privateTorrent])
1514                fPex = NO;
1515        else
1516                fPex = pex ? [pex boolValue] : YES;
1517        tr_torrentDisablePex(fHandle, !fPex);
1518   
1519    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
1520    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
1521    fError = NO;
1522   
1523    fIcon = [[[NSWorkspace sharedWorkspace] iconForFileType: fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr')
1524                                                : [[self name] pathExtension]] retain];
1525
1526    fProgressString = [[NSMutableString alloc] initWithCapacity: 50];
1527    fStatusString = [[NSMutableString alloc] initWithCapacity: 75];
1528    fShortStatusString = [[NSMutableString alloc] initWithCapacity: 30];
1529    fRemainingTimeString = [[NSMutableString alloc] initWithCapacity: 30];
1530   
1531    [self createFileList];
1532   
1533    //set up advanced bar
1534    fBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
1535        pixelsWide: MAX_PIECES pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
1536        isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
1537   
1538    fPieces = malloc(MAX_PIECES);
1539    int i;
1540    for (i = 0; i < MAX_PIECES; i++)
1541        fPieces[i] = BLANK_PIECE;
1542   
1543    [self update];
1544    return self;
1545}
1546
1547- (void) createFileList
1548{
1549    int count = [self fileCount], i;
1550    tr_file_t * file;
1551    NSMutableArray * pathComponents;
1552    NSString * path;
1553   
1554    NSMutableArray * fileList = [[NSMutableArray alloc] init],
1555                    * flatFileList = [[NSMutableArray alloc] initWithCapacity: count];
1556   
1557    for (i = 0; i < count; i++)
1558    {
1559        file = &fInfo->files[i];
1560       
1561        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
1562        if (fInfo->multifile)
1563        {
1564            path = [pathComponents objectAtIndex: 0];
1565            [pathComponents removeObjectAtIndex: 0];
1566        }
1567        else
1568            path = @"";
1569       
1570        [self insertPath: pathComponents forSiblings: fileList withParent: nil previousPath: path
1571                flatList: flatFileList fileSize: file->length index: i];
1572        [pathComponents autorelease];
1573    }
1574   
1575    fFileList = [[NSArray alloc] initWithArray: fileList];
1576    [fileList release];
1577    fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1578    [flatFileList release];
1579}
1580
1581- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings
1582        withParent: (NSMutableDictionary *) parent previousPath: (NSString *) previousPath
1583        flatList: (NSMutableArray *) flatList fileSize: (uint64_t) size index: (int) index
1584{
1585    NSString * name = [components objectAtIndex: 0];
1586    BOOL isFolder = [components count] > 1;
1587   
1588    NSMutableDictionary * dict = nil;
1589    if (isFolder)
1590    {
1591        NSEnumerator * enumerator = [siblings objectEnumerator];
1592        while ((dict = [enumerator nextObject]))
1593            if ([[dict objectForKey: @"IsFolder"] boolValue] && [[dict objectForKey: @"Name"] isEqualToString: name])
1594                break;
1595    }
1596   
1597    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
1598   
1599    //create new folder or item if it doesn't already exist
1600    if (!dict)
1601    {
1602        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
1603                [NSNumber numberWithBool: isFolder], @"IsFolder", currentPath, @"Path", nil];
1604        [siblings addObject: dict];
1605       
1606        if (isFolder)
1607        {
1608            [dict setObject: [NSMutableArray array] forKey: @"Children"];
1609            [dict setObject: [NSMutableIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1610        }
1611        else
1612        {
1613            [dict setObject: [NSIndexSet indexSetWithIndex: index] forKey: @"Indexes"];
1614            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
1615            [dict setObject: [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]] forKey: @"Icon"];
1616           
1617            [flatList addObject: dict];
1618        }
1619       
1620        if (parent)
1621            [dict setObject: parent forKey: @"Parent"];
1622    }
1623    else
1624        [[dict objectForKey: @"Indexes"] addIndex: index];
1625   
1626    if (isFolder)
1627    {
1628        [components removeObjectAtIndex: 0];
1629        [self insertPath: components forSiblings: [dict objectForKey: @"Children"]
1630            withParent: dict previousPath: currentPath flatList: flatList fileSize: size index: index];
1631    }
1632}
1633
1634- (BOOL) shouldUseIncompleteFolderForName: (NSString *) name
1635{
1636    return fUseIncompleteFolder &&
1637        ![[NSFileManager defaultManager] fileExistsAtPath: [fDownloadFolder stringByAppendingPathComponent: name]];
1638}
1639
1640- (void) updateDownloadFolder
1641{
1642    NSString * folder = [self shouldUseIncompleteFolderForName: [self name]] ? fIncompleteFolder : fDownloadFolder;
1643    tr_torrentSetFolder(fHandle, [folder UTF8String]);
1644}
1645
1646#warning move?
1647- (NSImage *) advancedBar
1648{
1649    uint32_t * p;
1650    uint8_t * bitmapData = [fBitmap bitmapData];
1651    int bytesPerRow = [fBitmap bytesPerRow];
1652   
1653    int pieceCount = [self pieceCount];
1654    int8_t * piecesAvailablity = malloc(pieceCount);
1655    [self getAvailability: piecesAvailablity size: pieceCount];
1656   
1657    //lines 2 to 14: blue, green, or gray depending on piece availability
1658    int i, h, index = 0;
1659    float increment = (float)pieceCount / (float)MAX_PIECES, indexValue = 0;
1660    uint32_t color;
1661    BOOL change;
1662    for (i = 0; i < MAX_PIECES; i++)
1663    {
1664        change = NO;
1665        if (piecesAvailablity[index] < 0)
1666        {
1667            if (fPieces[i] != -1)
1668            {
1669                color = kBlue;
1670                fPieces[i] = -1;
1671                change = YES;
1672            }
1673        }
1674        else if (piecesAvailablity[index] == 0)
1675        {
1676            if (fPieces[i] != 0)
1677            {
1678                color = kGray;
1679                fPieces[i] = 0;
1680                change = YES;
1681            }
1682        }
1683        else if (piecesAvailablity[index] <= 4)
1684        {
1685            if (fPieces[i] != 1)
1686            {
1687                color = kGreen1;
1688                fPieces[i] = 1;
1689                change = YES;
1690            }
1691        }
1692        else if (piecesAvailablity[index] <= 8)
1693        {
1694            if (fPieces[i] != 2)
1695            {
1696                color = kGreen2;
1697                fPieces[i] = 2;
1698                change = YES;
1699            }
1700        }
1701        else
1702        {
1703            if (fPieces[i] != 3)
1704            {
1705                color = kGreen3;
1706                fPieces[i] = 3;
1707                change = YES;
1708            }
1709        }
1710       
1711        if (change)
1712        {
1713            //point to pixel (i, 2) and draw "vertically"
1714            p = (uint32_t *)(bitmapData + 2 * bytesPerRow) + i;
1715            for (h = 2; h < BAR_HEIGHT; h++)
1716            {
1717                p[0] = color;
1718                p = (uint32_t *)((uint8_t *)p + bytesPerRow);
1719            }
1720        }
1721       
1722        indexValue += increment;
1723        index = (int)indexValue;
1724    }
1725   
1726    //determine percentage finished and available
1727    int have = rintf((float)MAX_PIECES * [self progress]), avail;
1728    if ([self progress] >= 1.0 || ![self isActive] || [self totalPeers] <= 0)
1729        avail = 0;
1730    else
1731    {
1732        float * piecesFinished = malloc(pieceCount * sizeof(float));
1733        [self getAmountFinished: piecesFinished size: pieceCount];
1734       
1735        float available = 0;
1736        for (i = 0; i < pieceCount; i++)
1737            if (piecesAvailablity[i] > 0)
1738                available += 1.0 - piecesFinished[i];
1739       
1740        avail = rintf((float)MAX_PIECES * available / (float)pieceCount);
1741        if (have + avail > MAX_PIECES) //case if both end in .5 and all pieces are available
1742            avail--;
1743       
1744        free(piecesFinished);
1745    }
1746   
1747    free(piecesAvailablity);
1748   
1749    //first two lines: dark blue to show progression, green to show available
1750    p = (uint32_t *)bitmapData;
1751    for (i = 0; i < have; i++)
1752    {
1753        p[i] = kBlue2;
1754        p[i + bytesPerRow / 4] = kBlue2;
1755    }
1756    for (; i < avail + have; i++)
1757    {
1758        p[i] = kGreen3;
1759        p[i + bytesPerRow / 4] = kGreen3;
1760    }
1761    for (; i < MAX_PIECES; i++)
1762    {
1763        p[i] = kWhite;
1764        p[i + bytesPerRow / 4] = kWhite;
1765    }
1766   
1767    //actually draw image
1768    NSImage * bar = [[NSImage alloc] initWithSize: [fBitmap size]];
1769    [bar addRepresentation: fBitmap];
1770    [bar setScalesWhenResized: YES];
1771   
1772    return [bar autorelease];
1773}
1774
1775- (void) trashFile: (NSString *) path
1776{
1777    //attempt to move to trash
1778    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
1779            source: [path stringByDeletingLastPathComponent] destination: @""
1780            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
1781    {
1782        //if cannot trash, just delete it (will work if it is on a remote volume)
1783        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
1784            NSLog(@"Could not trash %@", path);
1785    }
1786}
1787
1788@end
Note: See TracBrowser for help on using the repository browser.