source: trunk/macosx/Torrent.m @ 835

Last change on this file since 835 was 835, checked in by livings124, 17 years ago

Display an error image on the torrent icon when there is an error with the torrent.

  • Property svn:keywords set to Date Rev Author Id
File size: 20.5 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 835 2006-08-29 00:09:49Z livings124 $
3 *
4 * Copyright (c) 2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "Torrent.h"
26#import "StringAdditions.h"
27
28@interface Torrent (Private)
29
30- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
31        privateTorrent: (NSNumber *) privateTorrent publicTorrent: (NSNumber *) publicTorrent
32        date: (NSDate *) date stopRatioSetting: (NSNumber *) stopRatioSetting
33        ratioLimit: (NSNumber *) ratioLimit waitToStart: (NSNumber *) waitToStart
34        orderValue: (NSNumber *) orderValue;
35
36- (void) trashFile: (NSString *) path;
37
38@end
39
40@implementation Torrent
41
42- (id) initWithPath: (NSString *) path lib: (tr_handle_t *) lib
43{
44    self = [self initWithHash: nil path: path lib: lib privateTorrent: nil publicTorrent: nil
45            date: nil stopRatioSetting: nil ratioLimit: nil waitToStart: nil orderValue: nil];
46   
47    if (self)
48    {
49        if (!fPublicTorrent)
50            [self trashFile: path];
51    }
52    return self;
53}
54
55- (id) initWithHistory: (NSDictionary *) history lib: (tr_handle_t *) lib
56{
57    self = [self initWithHash: [history objectForKey: @"TorrentHash"]
58                path: [history objectForKey: @"TorrentPath"] lib: lib
59                privateTorrent: [history objectForKey: @"PrivateCopy"]
60                publicTorrent: [history objectForKey: @"PublicCopy"]
61                date: [history objectForKey: @"Date"]
62                stopRatioSetting: [history objectForKey: @"StopRatioSetting"]
63                ratioLimit: [history objectForKey: @"RatioLimit"]
64                waitToStart: [history objectForKey: @"WaitToStart"]
65                orderValue: [history objectForKey: @"OrderValue"]];
66   
67    if (self)
68    {
69        NSString * downloadFolder;
70        if (!(downloadFolder = [history objectForKey: @"DownloadFolder"]))
71            downloadFolder = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
72        [self setDownloadFolder: downloadFolder];
73
74        NSString * paused;
75        if (!(paused = [history objectForKey: @"Paused"]) || [paused isEqualToString: @"NO"])
76            tr_torrentStart(fHandle);
77    }
78    return self;
79}
80
81- (NSDictionary *) history
82{
83    NSMutableDictionary * history = [NSMutableDictionary dictionaryWithObjectsAndKeys:
84                    [NSNumber numberWithBool: fPrivateTorrent], @"PrivateCopy",
85                    [NSNumber numberWithBool: fPublicTorrent], @"PublicCopy",
86                    [self downloadFolder], @"DownloadFolder",
87                    [self isActive] ? @"NO" : @"YES", @"Paused",
88                    [self date], @"Date",
89                    [NSNumber numberWithInt: fStopRatioSetting], @"StopRatioSetting",
90                    [NSNumber numberWithFloat: fRatioLimit], @"RatioLimit",
91                    [NSNumber numberWithBool: fWaitToStart], @"WaitToStart",
92                    [self orderValue], @"OrderValue", nil];
93           
94    if (fPrivateTorrent)
95        [history setObject: [self hashString] forKey: @"TorrentHash"];
96
97    if (fPublicTorrent)
98        [history setObject: [self publicTorrentLocation] forKey: @"TorrentPath"];
99   
100    return history;
101}
102
103- (void) dealloc
104{
105    if (fHandle)
106    {
107        tr_torrentClose(fLib, fHandle);
108       
109        if (fPublicTorrentLocation)
110            [fPublicTorrentLocation release];
111       
112        [fDate release];
113       
114        [fIcon release];
115        [fIconFlipped release];
116        [fIconSmall release];
117       
118        [fProgressString release];
119        [fStatusString release];
120        [fShortStatusString release];
121        [fRemainingTimeString release];
122    }
123    [super dealloc];
124}
125
126- (void) setDownloadFolder: (NSString *) path
127{
128    tr_torrentSetFolder(fHandle, [path UTF8String]);
129}
130
131- (NSString *) downloadFolder
132{
133    return [NSString stringWithUTF8String: tr_torrentGetFolder(fHandle)];
134}
135
136- (void) getAvailability: (int8_t *) tab size: (int) size
137{
138    tr_torrentAvailability(fHandle, tab, size);
139}
140
141- (void) update
142{
143    fStat = tr_torrentStat(fHandle);
144   
145    //notification when downloading finished
146    if ([self justFinished])
147        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
148   
149    //check to stop for ratio
150    if ([self isSeeding] && ((fStopRatioSetting == RATIO_CHECK && [self ratio] >= fRatioLimit)
151            || (fStopRatioSetting == RATIO_GLOBAL && [fDefaults boolForKey: @"RatioCheck"]
152            && [self ratio] >= [fDefaults floatForKey: @"RatioLimit"])))
153    {
154        [self stopTransfer];
155        [self setStopRatioSetting: RATIO_NO_CHECK];
156        fFinishedSeeding = YES;
157       
158        fStat = tr_torrentStat(fHandle);
159       
160        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
161    }
162
163    [fProgressString setString: @""];
164    if ([self progress] < 1.0)
165        [fProgressString appendFormat: @"%@ of %@ (%.2f%%)", [NSString stringForFileSize:
166                [self downloadedValid]], [NSString stringForFileSize: [self size]], 100.0 * [self progress]];
167    else
168        [fProgressString appendFormat: @"%@, uploaded %@ (Ratio: %@)", [NSString stringForFileSize:
169                [self size]], [NSString stringForFileSize: [self uploadedTotal]],
170                [NSString stringForRatioWithDownload: [self downloadedTotal] upload: [self uploadedTotal]]];
171
172    switch (fStat->status)
173    {
174        NSString * tempString;
175   
176        case TR_STATUS_PAUSE:
177            if (fFinishedSeeding)
178                tempString = @"Seeding complete";
179            else if (fWaitToStart && [[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
180                tempString = [@"Waiting to start" stringByAppendingEllipsis];
181            else
182                tempString = @"Paused";
183           
184            [fStatusString setString: tempString];
185            [fShortStatusString setString: tempString];
186           
187            break;
188
189        case TR_STATUS_CHECK:
190            tempString = [@"Checking existing files" stringByAppendingEllipsis];
191           
192            [fStatusString setString: tempString];
193            [fShortStatusString setString: tempString];
194            [fRemainingTimeString setString: tempString];
195           
196            break;
197
198        case TR_STATUS_DOWNLOAD:
199            [fStatusString setString: @""];
200            [fStatusString appendFormat:
201                @"Downloading from %d of %d peer%s", [self peersUploading], [self totalPeers],
202                [self totalPeers] == 1 ? "" : "s"];
203           
204            [fRemainingTimeString setString: @""];
205            int eta = [self eta];
206            if (eta < 0)
207            {
208                [fRemainingTimeString setString: @"Unknown"];
209                [fProgressString appendString: @" - remaining time unknown"];
210            }
211            else
212            {
213                if (eta < 60)
214                    [fRemainingTimeString appendFormat: @"%d sec", eta];
215                else if (eta < 3600) //60 * 60
216                    [fRemainingTimeString appendFormat: @"%d min %02d sec", eta / 60, eta % 60];
217                else if (eta < 86400) //24 * 60 * 60
218                    [fRemainingTimeString appendFormat: @"%d hr %02d min", eta / 3600, (eta / 60) % 60];
219                else
220                    [fRemainingTimeString appendFormat: @"%d day%s %d hr",
221                                                eta / 86400, eta / 86400 == 1 ? "" : "s", (eta / 3600) % 24];
222               
223                [fProgressString appendFormat: @" - %@ remaining", fRemainingTimeString];
224            }
225           
226            break;
227
228        case TR_STATUS_SEED:
229            [fStatusString setString: @""];
230            [fStatusString appendFormat:
231                @"Seeding to %d of %d peer%s",
232                [self peersDownloading], [self totalPeers], [self totalPeers] == 1 ? "" : "s"];
233           
234            break;
235
236        case TR_STATUS_STOPPING:
237            tempString = [@"Stopping" stringByAppendingEllipsis];
238       
239            [fStatusString setString: tempString];
240            [fShortStatusString setString: tempString];
241           
242            break;
243    }
244   
245    if( fStat->error & TR_ETRACKER )
246        [fStatusString setString: [@"Error: " stringByAppendingString:
247                        [NSString stringWithUTF8String: fStat->trackerError]]];
248
249    if ([self isActive])
250    {
251        NSString * stringToAppend = @"";
252        if ([self progress] < 1.0)
253        {
254            stringToAppend = [NSString stringWithFormat: @"DL: %@, ", [NSString stringForSpeed: [self downloadRate]]];
255            [fShortStatusString setString: @""];
256        }
257        else
258        {
259            NSString * ratioString = [NSString stringForRatioWithDownload: [self downloadedTotal]
260                                                upload: [self uploadedTotal]];
261       
262            [fShortStatusString setString: [NSString stringWithFormat: @"Ratio: %@, ", ratioString]];
263            [fRemainingTimeString setString: [@"Ratio: " stringByAppendingString: ratioString]];
264        }
265       
266        stringToAppend = [stringToAppend stringByAppendingString: [@"UL: " stringByAppendingString:
267                                                [NSString stringForSpeed: [self uploadRate]]]];
268
269        [fStatusString appendFormat: @" - %@", stringToAppend];
270        [fShortStatusString appendString: stringToAppend];
271    }
272}
273
274- (void) startTransfer
275{
276    if (![self isActive])
277    {
278        tr_torrentStart(fHandle);
279
280        fFinishedSeeding = NO;
281        fWaitToStart = NO;
282       
283        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentSettingChange" object: self];
284    }
285}
286
287- (void) stopTransfer
288{
289    if ([self isActive])
290    {
291        BOOL wasSeeding = [self isSeeding];
292   
293        tr_torrentStop(fHandle);
294
295        if (!wasSeeding)
296            [[NSNotificationCenter defaultCenter] postNotificationName: @"StoppedDownloading" object: self];
297    }
298}
299
300- (void) stopTransferForQuit
301{
302    if ([self isActive])
303        tr_torrentStop(fHandle);
304}
305
306- (void) removeForever
307{
308    if (fPrivateTorrent)
309        tr_torrentRemoveSaved(fHandle);
310}
311
312- (void) sleep
313{
314    if ((fResumeOnWake = [self isActive]))
315        tr_torrentStop(fHandle);
316}
317
318- (void) wakeUp
319{
320    if (fResumeOnWake)
321        tr_torrentStart(fHandle);
322}
323
324- (float) ratio
325{
326    float downloaded = [self downloadedTotal];
327    return downloaded > 0 ? (float)[self uploadedTotal] / downloaded : -1;
328}
329
330- (int) stopRatioSetting
331{
332        return fStopRatioSetting;
333}
334
335- (void) setStopRatioSetting: (int) setting
336{
337    fStopRatioSetting = setting;
338}
339
340- (float) ratioLimit
341{
342    return fRatioLimit;
343}
344
345- (void) setRatioLimit: (float) limit
346{
347    if (limit >= 0)
348        fRatioLimit = limit;
349}
350
351- (void) setWaitToStart: (BOOL) wait
352{
353    fWaitToStart = wait;
354}
355
356- (BOOL) waitingToStart
357{
358    return fWaitToStart;
359}
360
361- (void) revealData
362{
363    [[NSWorkspace sharedWorkspace] selectFile: [self dataLocation] inFileViewerRootedAtPath: nil];
364}
365
366- (void) trashData
367{
368    [self trashFile: [self dataLocation]];
369}
370
371- (void) trashTorrent
372{
373    if (fPublicTorrent)
374        [self trashFile: [self publicTorrentLocation]];
375}
376
377- (NSImage *) icon
378{
379    return fIcon;
380}
381
382- (NSImage *) iconFlipped
383{
384    return fIconFlipped;
385}
386
387- (NSImage *) iconSmall
388{
389    return fIconSmall;
390}
391
392- (NSString *) name
393{
394    return [NSString stringWithUTF8String: fInfo->name];
395}
396
397- (uint64_t) size
398{
399    return fInfo->totalSize;
400}
401
402- (NSString *) tracker
403{
404    return [NSString stringWithFormat: @"%s:%d", fInfo->trackerAddress, fInfo->trackerPort];
405}
406
407- (NSString *) announce
408{
409    return [NSString stringWithUTF8String: fInfo->trackerAnnounce];
410}
411
412- (int) pieceSize
413{
414    return fInfo->pieceSize;
415}
416
417- (int) pieceCount
418{
419    return fInfo->pieceCount;
420}
421
422- (NSString *) hashString
423{
424    return [NSString stringWithUTF8String: fInfo->hashString];
425}
426
427- (NSString *) torrentLocation
428{
429    return [NSString stringWithUTF8String: fInfo->torrent];
430}
431
432- (NSString *) publicTorrentLocation
433{
434    return fPublicTorrentLocation;
435}
436
437- (NSString *) torrentLocationString
438{
439    return fPrivateTorrent ? @"Transmission Support Folder"
440            : [fPublicTorrentLocation stringByAbbreviatingWithTildeInPath];
441}
442
443- (NSString *) dataLocation
444{
445    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
446}
447
448- (BOOL) publicTorrent
449{
450    return fPublicTorrent;
451}
452
453- (BOOL) privateTorrent
454{
455    return fPrivateTorrent;
456}
457
458- (NSString *) stateString
459{
460    switch( fStat->status )
461    {
462        case TR_STATUS_PAUSE:
463            return @"Paused";
464            break;
465
466        case TR_STATUS_CHECK:
467            return [@"Checking existing files" stringByAppendingEllipsis];
468            break;
469
470        case TR_STATUS_DOWNLOAD:
471            return @"Downloading";
472            break;
473
474        case TR_STATUS_SEED:
475            return @"Seeding";
476            break;
477
478        case TR_STATUS_STOPPING:
479            return [@"Stopping" stringByAppendingEllipsis];
480            break;
481       
482        default:
483            return @"N/A";
484    }
485}
486
487- (float) progress
488{
489    return fStat->progress;
490}
491
492- (int) eta
493{
494    return fStat->eta;
495}
496
497- (BOOL) isActive
498{
499    return fStat->status & TR_STATUS_ACTIVE;
500}
501
502- (BOOL) isSeeding
503{
504    return fStat->status == TR_STATUS_SEED;
505}
506
507- (BOOL) isPaused
508{
509    return fStat->status == TR_STATUS_PAUSE;
510}
511
512- (BOOL) isError
513{
514    return fStat->error & TR_ETRACKER;
515}
516
517- (BOOL) justFinished
518{
519    return tr_getFinished(fHandle);
520}
521
522- (NSArray *) peers
523{
524    int totalPeers, i;
525    tr_peer_stat_t * peers = tr_torrentPeers(fHandle, & totalPeers);
526   
527    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
528    tr_peer_stat_t peer;
529    NSString * client;
530    for (i = 0; i < totalPeers; i++)
531    {
532        peer = peers[i];
533       
534        [peerDics addObject: [NSDictionary dictionaryWithObjectsAndKeys:
535            [NSNumber numberWithBool: peer.isConnected], @"Connected",
536            [NSString stringWithCString: (char *) peer.addr encoding: NSUTF8StringEncoding], @"IP",
537            [NSString stringWithCString: (char *) peer.client encoding: NSUTF8StringEncoding], @"Client",
538            [NSNumber numberWithBool: peer.isDownloading], @"UL To",
539            [NSNumber numberWithBool: peer.isUploading], @"DL From", nil]];
540    }
541   
542    tr_torrentPeersFree(peers, totalPeers);
543   
544    return peerDics;
545}
546
547- (NSString *) progressString
548{
549    return fProgressString;
550}
551
552- (NSString *) statusString
553{
554    return fStatusString;
555}
556
557- (NSString *) shortStatusString
558{
559    return fShortStatusString;
560}
561
562- (NSString *) remainingTimeString
563{
564    return fRemainingTimeString;
565}
566
567- (int) seeders
568{
569    return fStat->seeders;
570}
571
572- (int) leechers
573{
574    return fStat->leechers;
575}
576
577- (int) totalPeers
578{
579    return fStat->peersTotal;
580}
581
582//peers uploading to you
583- (int) peersUploading
584{
585    return fStat->peersUploading;
586}
587
588//peers downloading from you
589- (int) peersDownloading
590{
591    return fStat->peersDownloading;
592}
593
594- (float) downloadRate
595{
596    return fStat->rateDownload;
597}
598
599- (float) uploadRate
600{
601    return fStat->rateUpload;
602}
603
604- (float) downloadedValid
605{
606    return [self progress] * [self size];
607}
608
609- (uint64_t) downloadedTotal
610{
611    return fStat->downloaded;
612}
613
614- (uint64_t) uploadedTotal
615{
616    return fStat->uploaded;
617}
618
619- (float) swarmSpeed
620{
621    return fStat->swarmspeed;
622}
623
624- (NSNumber *) orderValue
625{
626    return [NSNumber numberWithInt: fOrderValue];
627}
628
629- (void) setOrderValue: (int) orderValue
630{
631    fOrderValue = orderValue;
632}
633
634- (NSArray *) fileList
635{
636    int count = fInfo->fileCount, i;
637    tr_file_t file;
638    NSMutableArray * files = [NSMutableArray arrayWithCapacity: count];
639   
640    for (i = 0; i < count; i++)
641    {
642        file = fInfo->files[i];
643        [files addObject: [NSDictionary dictionaryWithObjectsAndKeys: [[self downloadFolder]
644            stringByAppendingPathComponent: [NSString stringWithUTF8String: file.name]], @"Name",
645            [NSNumber numberWithUnsignedLongLong: file.length], @"Size", nil]];
646    }
647   
648    return files;
649}
650
651- (NSDate *) date
652{
653    return fDate;
654}
655
656- (NSNumber *) stateSortKey
657{
658    if (![self isActive])
659        return [NSNumber numberWithInt: 0];
660    else if ([self isSeeding])
661        return [NSNumber numberWithInt: 1];
662    else
663        return [NSNumber numberWithInt: 2];
664}
665
666- (NSNumber *) progressSortKey
667{
668    //if finished downloading sort by ratio instead of progress
669    float progress = [self progress];
670    return [NSNumber numberWithFloat: progress < 1.0 ? progress : 2.0 + [self ratio]];
671}
672
673@end
674
675
676@implementation Torrent (Private)
677
678//if a hash is given, attempt to load that; otherwise, attempt to open file at path
679- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
680        privateTorrent: (NSNumber *) privateTorrent publicTorrent: (NSNumber *) publicTorrent
681        date: (NSDate *) date stopRatioSetting: (NSNumber *) stopRatioSetting
682        ratioLimit: (NSNumber *) ratioLimit waitToStart: (NSNumber *) waitToStart
683        orderValue: (NSNumber *) orderValue
684{
685    if (!(self = [super init]))
686        return nil;
687
688    fLib = lib;
689    fDefaults = [NSUserDefaults standardUserDefaults];
690
691    fPrivateTorrent = privateTorrent ? [privateTorrent boolValue] : [fDefaults boolForKey: @"SavePrivateTorrent"];
692    fPublicTorrent = !fPrivateTorrent || (publicTorrent ? [publicTorrent boolValue]
693                                            : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
694
695    int error;
696    if (fPrivateTorrent && hashString)
697        fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], TR_FSAVEPRIVATE, & error);
698   
699    if (!fHandle && path)
700        fHandle = tr_torrentInit(fLib, [path UTF8String], fPrivateTorrent ? TR_FSAVEPRIVATE : 0, & error);
701
702    if (!fHandle)
703    {
704        [self release];
705        return nil;
706    }
707   
708    fInfo = tr_torrentInfo( fHandle );
709
710    if (fPublicTorrent)
711        fPublicTorrentLocation = [path retain];
712
713    fDate = date ? [date retain] : [[NSDate alloc] init];
714   
715    fStopRatioSetting = stopRatioSetting ? [stopRatioSetting intValue] : -1;
716    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
717    fFinishedSeeding = NO;
718   
719    fWaitToStart = waitToStart ? [waitToStart boolValue]
720                    : ![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Manual"];
721    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
722   
723    NSString * fileType = fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr') : [[self name] pathExtension];
724    fIcon = [[NSWorkspace sharedWorkspace] iconForFileType: fileType];
725    [fIcon retain];
726   
727    fIconFlipped = [fIcon copy];
728    [fIconFlipped setFlipped: YES];
729   
730    fIconSmall = [fIconFlipped copy];
731    [fIconSmall setScalesWhenResized: YES];
732    [fIconSmall setSize: NSMakeSize(16.0, 16.0)];
733
734    fProgressString = [[NSMutableString alloc] initWithCapacity: 50];
735    fStatusString = [[NSMutableString alloc] initWithCapacity: 75];
736    fShortStatusString = [[NSMutableString alloc] initWithCapacity: 30];
737    fRemainingTimeString = [[NSMutableString alloc] initWithCapacity: 30];
738
739    [self update];
740    return self;
741}
742
743- (void) trashFile: (NSString *) path
744{
745    //attempt to move to trash
746    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
747            source: [path stringByDeletingLastPathComponent] destination: @""
748            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
749    {
750        //if cannot trash, just delete it (will work if it is on a remote volume)
751        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
752            NSLog([@"Could not trash " stringByAppendingString: path]);
753    }
754}
755
756@end
Note: See TracBrowser for help on using the repository browser.