source: trunk/macosx/Torrent.m @ 2196

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

my bad

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