source: trunk/macosx/Torrent.m @ 12772

Last change on this file since 12772 was 12772, checked in by livings124, 10 years ago

add an RPC session callback for when queue order changes

  • Property svn:keywords set to Date Rev Author Id
File size: 59.4 KB
Line 
1/******************************************************************************
2 * $Id: Torrent.m 12772 2011-08-27 23:54:10Z livings124 $
3 *
4 * Copyright (c) 2006-2011 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 "GroupsController.h"
27#import "FileListNode.h"
28#import "NSStringAdditions.h"
29#import "TrackerNode.h"
30
31#import "transmission.h" // required by utils.h
32#import "utils.h" // tr_new()
33
34#define ETA_IDLE_DISPLAY_SEC (2*60)
35
36@interface Torrent (Private)
37
38- (id) initWithPath: (NSString *) path hash: (NSString *) hashString torrentStruct: (tr_torrent *) torrentStruct
39        magnetAddress: (NSString *) magnetAddress lib: (tr_session *) lib
40        groupValue: (NSNumber *) groupValue
41        timeMachineExcludeLocation: (NSString *) timeMachineExclude
42        downloadFolder: (NSString *) downloadFolder
43        legacyIncompleteFolder: (NSString *) incompleteFolder;
44
45- (void) createFileList;
46- (void) insertPath: (NSMutableArray *) components forParent: (FileListNode *) parent fileSize: (uint64_t) size
47    index: (NSInteger) index flatList: (NSMutableArray *) flatFileList;
48- (void) sortFileList: (NSMutableArray *) fileNodes;
49
50- (void) startQueue;
51- (void) completenessChange: (NSDictionary *) statusInfo;
52- (void) ratioLimitHit;
53- (void) idleLimitHit;
54- (void) metadataRetrieved;
55
56- (BOOL) shouldShowEta;
57- (NSString *) etaString;
58
59- (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path;
60
61@end
62
63void startQueueCallback(tr_torrent * torrent, void * torrentData)
64{
65    [(Torrent *)torrentData performSelectorOnMainThread: @selector(startQueue) withObject: nil waitUntilDone: NO];
66}
67
68void completenessChangeCallback(tr_torrent * torrent, tr_completeness status, bool wasRunning, void * torrentData)
69{
70    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
71   
72    NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt: status], @"Status",
73                            [NSNumber numberWithBool: wasRunning], @"WasRunning", nil];
74    [(Torrent *)torrentData performSelectorOnMainThread: @selector(completenessChange:) withObject: dict waitUntilDone: NO];
75   
76    [pool drain];
77}
78
79void ratioLimitHitCallback(tr_torrent * torrent, void * torrentData)
80{
81    [(Torrent *)torrentData performSelectorOnMainThread: @selector(ratioLimitHit) withObject: nil waitUntilDone: NO];
82}
83
84void idleLimitHitCallback(tr_torrent * torrent, void * torrentData)
85{
86    [(Torrent *)torrentData performSelectorOnMainThread: @selector(idleLimitHit) withObject: nil waitUntilDone: NO];
87}
88
89void metadataCallback(tr_torrent * torrent, void * torrentData)
90{
91    [(Torrent *)torrentData performSelectorOnMainThread: @selector(metadataRetrieved) withObject: nil waitUntilDone: NO];
92}
93
94int trashDataFile(const char * filename)
95{
96    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
97   
98    if (filename != NULL)
99        [Torrent trashFile: [NSString stringWithUTF8String: filename]];
100   
101    [pool drain];
102    return 0;
103}
104
105@implementation Torrent
106
107- (id) initWithPath: (NSString *) path location: (NSString *) location deleteTorrentFile: (BOOL) torrentDelete
108        lib: (tr_session *) lib
109{
110    self = [self initWithPath: path hash: nil torrentStruct: NULL magnetAddress: nil lib: lib
111            groupValue: nil
112            timeMachineExcludeLocation: nil
113            downloadFolder: location
114            legacyIncompleteFolder: nil];
115   
116    if (self)
117    {
118        if (torrentDelete && ![[self torrentLocation] isEqualToString: path])
119            [Torrent trashFile: path];
120    }
121    return self;
122}
123
124- (id) initWithTorrentStruct: (tr_torrent *) torrentStruct location: (NSString *) location lib: (tr_session *) lib
125{
126    self = [self initWithPath: nil hash: nil torrentStruct: torrentStruct magnetAddress: nil lib: lib
127            groupValue: nil
128            timeMachineExcludeLocation: nil
129            downloadFolder: location
130            legacyIncompleteFolder: nil];
131   
132    return self;
133}
134
135- (id) initWithMagnetAddress: (NSString *) address location: (NSString *) location lib: (tr_session *) lib
136{
137    self = [self initWithPath: nil hash: nil torrentStruct: nil magnetAddress: address
138            lib: lib groupValue: nil
139            timeMachineExcludeLocation: nil
140            downloadFolder: location legacyIncompleteFolder: nil];
141   
142    return self;
143}
144
145- (id) initWithHistory: (NSDictionary *) history lib: (tr_session *) lib forcePause: (BOOL) pause
146{
147    self = [self initWithPath: [history objectForKey: @"InternalTorrentPath"]
148                hash: [history objectForKey: @"TorrentHash"]
149                torrentStruct: NULL
150                magnetAddress: nil
151                lib: lib
152                groupValue: [history objectForKey: @"GroupValue"]
153                timeMachineExcludeLocation: [history objectForKey: @"TimeMachineExcludeLocation"]
154                downloadFolder: [history objectForKey: @"DownloadFolder"] //upgrading from versions < 1.80
155                legacyIncompleteFolder: [[history objectForKey: @"UseIncompleteFolder"] boolValue] //upgrading from versions < 1.80
156                                        ? [history objectForKey: @"IncompleteFolder"] : nil];
157   
158    if (self)
159    {
160        //start transfer
161        NSNumber * active;
162        if (!pause && (active = [history objectForKey: @"Active"]) && [active boolValue])
163        {
164            fStat = tr_torrentStat(fHandle);
165            [self startTransferNoQueue];
166        }
167       
168        //upgrading from versions < 1.30: get old added, activity, and done dates
169        NSDate * date;
170        if ((date = [history objectForKey: @"Date"]))
171            tr_torrentSetAddedDate(fHandle, [date timeIntervalSince1970]);
172        if ((date = [history objectForKey: @"DateActivity"]))
173            tr_torrentSetActivityDate(fHandle, [date timeIntervalSince1970]);
174        if ((date = [history objectForKey: @"DateCompleted"]))
175            tr_torrentSetDoneDate(fHandle, [date timeIntervalSince1970]);
176       
177        //upgrading from versions < 1.60: get old stop ratio settings
178        NSNumber * ratioSetting;
179        if ((ratioSetting = [history objectForKey: @"RatioSetting"]))
180        {
181            switch ([ratioSetting intValue])
182            {
183                case NSOnState: [self setRatioSetting: TR_RATIOLIMIT_SINGLE]; break;
184                case NSOffState: [self setRatioSetting: TR_RATIOLIMIT_UNLIMITED]; break;
185                case NSMixedState: [self setRatioSetting: TR_RATIOLIMIT_GLOBAL]; break;
186            }
187        }
188        NSNumber * ratioLimit;
189        if ((ratioLimit = [history objectForKey: @"RatioLimit"]))
190            [self setRatioLimit: [ratioLimit floatValue]];
191    }
192    return self;
193}
194
195- (NSDictionary *) history
196{
197    NSMutableDictionary * history = [NSMutableDictionary dictionaryWithObjectsAndKeys:
198                                        [self torrentLocation], @"InternalTorrentPath",
199                                        [self hashString], @"TorrentHash",
200                                        [NSNumber numberWithBool: [self isActive]], @"Active",
201                                        [NSNumber numberWithBool: [self waitingToStart]], @"WaitToStart",
202                                        [NSNumber numberWithInt: fGroupValue], @"GroupValue", nil];
203   
204    if (fTimeMachineExclude)
205        [history setObject: fTimeMachineExclude forKey: @"TimeMachineExcludeLocation"];
206   
207    return history;
208}
209
210- (void) dealloc
211{
212    [[NSNotificationCenter defaultCenter] removeObserver: self];
213   
214    if (fFileStat)
215        tr_torrentFilesFree(fFileStat, [self fileCount]);
216   
217    [fPreviousFinishedIndexes release];
218    [fPreviousFinishedIndexesDate release];
219   
220    [fHashString release];
221   
222    [fIcon release];
223   
224    [fFileList release];
225    [fFlatFileList release];
226   
227    [fTimeMachineExclude release];
228   
229    [super dealloc];
230}
231
232- (NSString *) description
233{
234    return [@"Torrent: " stringByAppendingString: [self name]];
235}
236
237- (id) copyWithZone: (NSZone *) zone
238{
239    return [self retain];
240}
241
242- (void) closeRemoveTorrent: (BOOL) trashFiles
243{
244    //allow the file to be indexed by Time Machine
245    if (fTimeMachineExclude)
246    {
247        [self setTimeMachineExclude: NO forPath: fTimeMachineExclude];
248        [fTimeMachineExclude release];
249        fTimeMachineExclude = nil;
250    }
251   
252    tr_torrentRemove(fHandle, trashFiles, trashDataFile);
253}
254
255- (void) changeDownloadFolderBeforeUsing: (NSString *) folder
256{
257     tr_torrentSetDownloadDir(fHandle, [folder UTF8String]);
258     [self updateTimeMachineExclude];
259}
260
261- (NSString *) currentDirectory
262{
263    return [NSString stringWithUTF8String: tr_torrentGetCurrentDir(fHandle)];
264}
265
266- (void) getAvailability: (int8_t *) tab size: (NSInteger) size
267{
268    tr_torrentAvailability(fHandle, tab, size);
269}
270
271- (void) getAmountFinished: (float *) tab size: (NSInteger) size
272{
273    tr_torrentAmountFinished(fHandle, tab, size);
274}
275
276- (NSIndexSet *) previousFinishedPieces
277{
278    //if the torrent hasn't been seen in a bit, and therefore hasn't been refreshed, return nil
279    if (fPreviousFinishedIndexesDate && [fPreviousFinishedIndexesDate timeIntervalSinceNow] > -2.0)
280        return fPreviousFinishedIndexes;
281    else
282        return nil;
283}
284
285-(void) setPreviousFinishedPieces: (NSIndexSet *) indexes
286{
287    [fPreviousFinishedIndexes release];
288    fPreviousFinishedIndexes = [indexes retain];
289   
290    [fPreviousFinishedIndexesDate release];
291    fPreviousFinishedIndexesDate = indexes != nil ? [[NSDate alloc] init] : nil;
292}
293
294- (void) update
295{
296    fStat = tr_torrentStat(fHandle);
297   
298    //when the data first appears, update time machine exclusion
299    if (!fTimeMachineExclude)
300        [self updateTimeMachineExclude];
301}
302
303- (void) startTransferNoQueue
304{
305    if ([self alertForRemainingDiskSpace])
306    {
307        tr_torrentStartNow(fHandle);
308        [self update];
309       
310        //capture, specifically, stop-seeding settings changing to unlimited
311        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
312    }
313}
314
315#warning merge
316- (void) startTransfer
317{
318    if ([self alertForRemainingDiskSpace])
319    {
320        tr_torrentStart(fHandle);
321        [self update];
322       
323        //capture, specifically, stop-seeding settings changing to unlimited
324        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
325    }
326}
327
328- (void) stopTransfer
329{
330    tr_torrentStop(fHandle);
331    [self update];
332}
333
334- (void) sleep
335{
336    if ((fResumeOnWake = [self isActive]))
337        tr_torrentStop(fHandle);
338}
339
340- (void) wakeUp
341{
342    if (fResumeOnWake)
343    {
344        tr_ninf( fInfo->name, "restarting because of wakeUp" );
345        tr_torrentStart(fHandle);
346    }
347}
348
349- (NSInteger) queuePosition
350{
351    return fStat->queuePosition;
352}
353
354- (void) setQueuePosition: (NSUInteger) index
355{
356    tr_torrentSetQueuePosition(fHandle, index);
357}
358
359- (void) manualAnnounce
360{
361    tr_torrentManualUpdate(fHandle);
362}
363
364- (BOOL) canManualAnnounce
365{
366    return tr_torrentCanManualUpdate(fHandle);
367}
368
369- (void) resetCache
370{
371    tr_torrentVerify(fHandle);
372    [self update];
373}
374
375- (BOOL) isMagnet
376{
377    return !tr_torrentHasMetadata(fHandle);
378}
379
380- (NSString *) magnetLink
381{
382    return [NSString stringWithUTF8String: tr_torrentGetMagnetLink(fHandle)];
383}
384
385- (CGFloat) ratio
386{
387    return fStat->ratio;
388}
389
390- (tr_ratiolimit) ratioSetting
391{
392    return tr_torrentGetRatioMode(fHandle);
393}
394
395- (void) setRatioSetting: (tr_ratiolimit) setting
396{
397    tr_torrentSetRatioMode(fHandle, setting);
398}
399
400- (CGFloat) ratioLimit
401{
402    return tr_torrentGetRatioLimit(fHandle);
403}
404
405- (void) setRatioLimit: (CGFloat) limit
406{
407    NSAssert(limit >= 0, @"Ratio cannot be negative");
408    tr_torrentSetRatioLimit(fHandle, limit);
409}
410
411- (CGFloat) progressStopRatio
412{
413    return fStat->seedRatioPercentDone;
414}
415
416- (tr_idlelimit) idleSetting
417{
418    return tr_torrentGetIdleMode(fHandle);
419}
420
421- (void) setIdleSetting: (tr_idlelimit) setting
422{
423    tr_torrentSetIdleMode(fHandle, setting);
424}
425
426- (NSUInteger) idleLimitMinutes
427{
428    return tr_torrentGetIdleLimit(fHandle);
429}
430
431- (void) setIdleLimitMinutes: (NSUInteger) limit
432{
433    NSAssert(limit > 0, @"Idle limit must be greater than zero");
434    tr_torrentSetIdleLimit(fHandle, limit);
435}
436
437- (BOOL) usesSpeedLimit: (BOOL) upload
438{
439    return tr_torrentUsesSpeedLimit(fHandle, upload ? TR_UP : TR_DOWN);
440}
441
442- (void) setUseSpeedLimit: (BOOL) use upload: (BOOL) upload
443{
444    tr_torrentUseSpeedLimit(fHandle, upload ? TR_UP : TR_DOWN, use);
445}
446
447- (NSInteger) speedLimit: (BOOL) upload
448{
449    return tr_torrentGetSpeedLimit_KBps(fHandle, upload ? TR_UP : TR_DOWN);
450}
451
452- (void) setSpeedLimit: (NSInteger) limit upload: (BOOL) upload
453{
454    tr_torrentSetSpeedLimit_KBps(fHandle, upload ? TR_UP : TR_DOWN, limit);
455}
456
457- (BOOL) usesGlobalSpeedLimit
458{
459    return tr_torrentUsesSessionLimits(fHandle);
460}
461
462- (void) setUseGlobalSpeedLimit: (BOOL) use
463{
464    tr_torrentUseSessionLimits(fHandle, use);
465}
466
467- (void) setMaxPeerConnect: (uint16_t) count
468{
469    NSAssert(count > 0, @"max peer count must be greater than 0");
470   
471    tr_torrentSetPeerLimit(fHandle, count);
472}
473
474- (uint16_t) maxPeerConnect
475{
476    return tr_torrentGetPeerLimit(fHandle);
477}
478- (BOOL) waitingToStart
479{
480    return fStat->activity == TR_STATUS_DOWNLOAD_WAIT || fStat->activity == TR_STATUS_SEED_WAIT;
481}
482
483- (tr_priority_t) priority
484{
485    return tr_torrentGetPriority(fHandle);
486}
487
488- (void) setPriority: (tr_priority_t) priority
489{
490    return tr_torrentSetPriority(fHandle, priority);
491}
492
493#warning when 10.6-only use recycleURLs:completionHandler:
494+ (void) trashFile: (NSString *) path
495{
496    //attempt to move to trash
497    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
498        source: [path stringByDeletingLastPathComponent] destination: @""
499        files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
500    {
501        //if cannot trash, just delete it (will work if it's on a remote volume)
502        NSError * error;
503        if (![[NSFileManager defaultManager] removeItemAtPath: path error: &error])
504            NSLog(@"Could not trash %@: %@", path, [error localizedDescription]);
505    }
506}
507
508- (void) moveTorrentDataFileTo: (NSString *) folder
509{
510    NSString * oldFolder = [self currentDirectory];
511    if ([oldFolder isEqualToString: folder])
512        return;
513   
514    //check if moving inside itself
515    NSArray * oldComponents = [oldFolder pathComponents],
516            * newComponents = [folder pathComponents];
517    const NSInteger oldCount = [oldComponents count];
518   
519    if (oldCount < [newComponents count] && [[newComponents objectAtIndex: oldCount] isEqualToString: [self name]]
520        && [folder hasPrefix: oldFolder])
521    {
522        NSAlert * alert = [[NSAlert alloc] init];
523        [alert setMessageText: NSLocalizedString(@"A folder cannot be moved to inside itself.",
524                                                    "Move inside itself alert -> title")];
525        [alert setInformativeText: [NSString stringWithFormat:
526                        NSLocalizedString(@"The move operation of \"%@\" cannot be done.",
527                                            "Move inside itself alert -> message"), [self name]]];
528        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move inside itself alert -> button")];
529       
530        [alert runModal];
531        [alert release];
532       
533        return;
534    }
535   
536    volatile int status;
537    tr_torrentSetLocation(fHandle, [folder UTF8String], YES, NULL, &status);
538   
539    while (status == TR_LOC_MOVING) //block while moving (for now)
540        [NSThread sleepForTimeInterval: 0.05];
541   
542    if (status == TR_LOC_DONE)
543        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateStats" object: nil];
544    else
545    {
546        NSAlert * alert = [[NSAlert alloc] init];
547        [alert setMessageText: NSLocalizedString(@"There was an error moving the data file.", "Move error alert -> title")];
548        [alert setInformativeText: [NSString stringWithFormat:
549                NSLocalizedString(@"The move operation of \"%@\" cannot be done.", "Move error alert -> message"), [self name]]];
550        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move error alert -> button")];
551       
552        [alert runModal];
553        [alert release];
554    }
555   
556    [self updateTimeMachineExclude];
557}
558
559- (void) copyTorrentFileTo: (NSString *) path
560{
561    [[NSFileManager defaultManager] copyItemAtPath: [self torrentLocation] toPath: path error: NULL];
562}
563
564- (BOOL) alertForRemainingDiskSpace
565{
566    if ([self allDownloaded] || ![fDefaults boolForKey: @"WarningRemainingSpace"])
567        return YES;
568   
569    NSString * downloadFolder = [self currentDirectory];
570    NSDictionary * systemAttributes;
571    if ((systemAttributes = [[NSFileManager defaultManager] attributesOfFileSystemForPath: downloadFolder error: NULL]))
572    {
573        const uint64_t remainingSpace = [[systemAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue];
574       
575        //if the remaining space is greater than the size left, then there is enough space regardless of preallocation
576        if (remainingSpace < [self sizeLeft] && remainingSpace < tr_torrentGetBytesLeftToAllocate(fHandle))
577        {
578            NSString * volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: downloadFolder] objectAtIndex: 0];
579           
580            NSAlert * alert = [[NSAlert alloc] init];
581            [alert setMessageText: [NSString stringWithFormat:
582                                    NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
583                                        "Torrent disk space alert -> title"), [self name]]];
584            [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"The transfer will be paused."
585                                        " Clear up space on %@ or deselect files in the torrent inspector to continue.",
586                                        "Torrent disk space alert -> message"), volumeName]];
587            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent disk space alert -> button")];
588            [alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent disk space alert -> button")];
589           
590            [alert setShowsSuppressionButton: YES];
591            [[alert suppressionButton] setTitle: NSLocalizedString(@"Do not check disk space again",
592                                                    "Torrent disk space alert -> button")];
593
594            const NSInteger result = [alert runModal];
595            if ([[alert suppressionButton] state] == NSOnState)
596                [fDefaults setBool: NO forKey: @"WarningRemainingSpace"];
597            [alert release];
598           
599            return result != NSAlertFirstButtonReturn;
600        }
601    }
602    return YES;
603}
604
605- (NSImage *) icon
606{
607    if ([self isMagnet])
608        return [NSImage imageNamed: @"Magnet.png"];
609   
610    #warning replace 'fldr' stuff with NSImageNameFolder on 10.6
611    if (!fIcon)
612        fIcon = [[[NSWorkspace sharedWorkspace] iconForFileType: [self isFolder] ? NSFileTypeForHFSTypeCode('fldr')
613                                                                                : [[self name] pathExtension]] retain];
614    return fIcon;
615}
616
617- (NSString *) name
618{
619    return fInfo->name != NULL ? [NSString stringWithUTF8String: fInfo->name] : fHashString;
620}
621
622- (BOOL) isFolder
623{
624    return fInfo->isMultifile;
625}
626
627- (uint64_t) size
628{
629    return fInfo->totalSize;
630}
631
632- (uint64_t) sizeLeft
633{
634    return fStat->leftUntilDone;
635}
636
637- (NSMutableArray *) allTrackerStats
638{
639    int count;
640    tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
641   
642    NSMutableArray * trackers = [NSMutableArray arrayWithCapacity: (count > 0 ? count + (stats[count-1].tier + 1) : 0)];
643   
644    int prevTier = -1;
645    for (int i=0; i < count; ++i)
646    {
647        if (stats[i].tier != prevTier)
648        {
649            [trackers addObject: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger: stats[i].tier + 1], @"Tier",
650                                    [self name], @"Name", nil]];
651            prevTier = stats[i].tier;
652        }
653       
654        TrackerNode * tracker = [[TrackerNode alloc] initWithTrackerStat: &stats[i] torrent: self];
655        [trackers addObject: tracker];
656        [tracker release];
657    }
658   
659    tr_torrentTrackersFree(stats, count);
660    return trackers;
661}
662
663- (NSArray *) allTrackersFlat
664{
665    NSMutableArray * allTrackers = [NSMutableArray arrayWithCapacity: fInfo->trackerCount];
666   
667    for (NSInteger i=0; i < fInfo->trackerCount; i++)
668        [allTrackers addObject: [NSString stringWithUTF8String: fInfo->trackers[i].announce]];
669   
670    return allTrackers;
671}
672
673- (BOOL) addTrackerToNewTier: (NSString *) tracker
674{
675    tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
676   
677    if ([tracker rangeOfString: @"://"].location == NSNotFound)
678        tracker = [@"http://" stringByAppendingString: tracker];
679   
680    //recreate the tracker structure
681    const int oldTrackerCount = fInfo->trackerCount;
682    tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, oldTrackerCount+1);
683    for (NSUInteger i=0; i < oldTrackerCount; ++i)
684        trackerStructs[i] = fInfo->trackers[i];
685   
686    trackerStructs[oldTrackerCount].announce = (char *)[tracker UTF8String];
687    trackerStructs[oldTrackerCount].tier = trackerStructs[oldTrackerCount-1].tier + 1;
688    trackerStructs[oldTrackerCount].id = oldTrackerCount;
689   
690    const BOOL success = tr_torrentSetAnnounceList(fHandle, trackerStructs, oldTrackerCount+1);
691    tr_free(trackerStructs);
692   
693    return success;
694}
695
696- (void) removeTrackers: (NSSet *) trackers
697{
698    //recreate the tracker structure
699    tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, fInfo->trackerCount);
700   
701    NSUInteger newCount = 0;
702    for (NSUInteger i = 0; i < fInfo->trackerCount; i++)
703    {
704        if (![trackers containsObject: [NSString stringWithUTF8String: fInfo->trackers[i].announce]])
705            trackerStructs[newCount++] = fInfo->trackers[i];
706    }
707   
708    const BOOL success = tr_torrentSetAnnounceList(fHandle, trackerStructs, newCount);
709    NSAssert(success, @"Removing tracker addresses failed");
710   
711    tr_free(trackerStructs);
712}
713
714- (NSString *) comment
715{
716    return fInfo->comment ? [NSString stringWithUTF8String: fInfo->comment] : @"";
717}
718
719- (NSString *) creator
720{
721    return fInfo->creator ? [NSString stringWithUTF8String: fInfo->creator] : @"";
722}
723
724- (NSDate *) dateCreated
725{
726    NSInteger date = fInfo->dateCreated;
727    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
728}
729
730- (NSInteger) pieceSize
731{
732    return fInfo->pieceSize;
733}
734
735- (NSInteger) pieceCount
736{
737    return fInfo->pieceCount;
738}
739
740- (NSString *) hashString
741{
742    return fHashString;
743}
744
745- (BOOL) privateTorrent
746{
747    return fInfo->isPrivate;
748}
749
750- (NSString *) torrentLocation
751{
752    return fInfo->torrent ? [NSString stringWithUTF8String: fInfo->torrent] : @"";
753}
754
755- (NSString *) dataLocation
756{
757    if ([self isMagnet])
758        return nil;
759   
760    if ([self isFolder])
761    {
762        NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: [self name]];
763       
764        if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
765            return nil;
766       
767        return dataLocation;
768    }
769    else
770    {
771        char * location = tr_torrentFindFile(fHandle, 0);
772        if (location == NULL)
773            return nil;
774       
775        NSString * dataLocation = [NSString stringWithUTF8String: location];
776        free(location);
777       
778        return dataLocation;
779    }
780}
781
782- (NSString *) fileLocation: (FileListNode *) node
783{
784    if ([node isFolder])
785    {
786        NSString * basePath = [[node path] stringByAppendingPathComponent: [node name]];
787        NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: basePath];
788       
789        if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
790            return nil;
791       
792        return dataLocation;
793    }
794    else
795    {
796        char * location = tr_torrentFindFile(fHandle, [[node indexes] firstIndex]);
797        if (location == NULL)
798            return nil;
799       
800        NSString * dataLocation = [NSString stringWithUTF8String: location];
801        free(location);
802       
803        return dataLocation;
804    }
805}
806
807- (CGFloat) progress
808{
809    return fStat->percentComplete;
810}
811
812- (CGFloat) progressDone
813{
814    return fStat->percentDone;
815}
816
817- (CGFloat) progressLeft
818{
819    if ([self size] == 0) //magnet links
820        return 0.0;
821   
822    return (CGFloat)[self sizeLeft] / [self size];
823}
824
825- (CGFloat) checkingProgress
826{
827    return fStat->recheckProgress;
828}
829
830- (CGFloat) availableDesired
831{
832    return (CGFloat)fStat->desiredAvailable / [self sizeLeft];
833}
834
835- (BOOL) isActive
836{
837    return fStat->activity != TR_STATUS_STOPPED && fStat->activity != TR_STATUS_DOWNLOAD_WAIT && fStat->activity != TR_STATUS_SEED_WAIT;
838}
839
840- (BOOL) isSeeding
841{
842    return fStat->activity == TR_STATUS_SEED;
843}
844
845- (BOOL) isChecking
846{
847    return fStat->activity == TR_STATUS_CHECK || fStat->activity == TR_STATUS_CHECK_WAIT;
848}
849
850- (BOOL) isCheckingWaiting
851{
852    return fStat->activity == TR_STATUS_CHECK_WAIT;
853}
854
855- (BOOL) allDownloaded
856{
857    return [self sizeLeft] == 0 && ![self isMagnet];
858}
859
860- (BOOL) isComplete
861{
862    return [self progress] >= 1.0;
863}
864
865- (BOOL) isFinishedSeeding
866{
867    return fStat->finished;
868}
869
870- (BOOL) isError
871{
872    return fStat->error == TR_STAT_LOCAL_ERROR;
873}
874
875- (BOOL) isAnyErrorOrWarning
876{
877    return fStat->error != TR_STAT_OK;
878}
879
880- (NSString *) errorMessage
881{
882    if (![self isAnyErrorOrWarning])
883        return @"";
884   
885    NSString * error;
886    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
887        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
888        error = [NSString stringWithFormat: @"(%@)", NSLocalizedString(@"unreadable error", "Torrent -> error string unreadable")];
889   
890    //libtransmission uses "Set Location", Mac client uses "Move data file to..." - very hacky!
891    error = [error stringByReplacingOccurrencesOfString: @"Set Location" withString: [@"Move Data File To" stringByAppendingEllipsis]];
892   
893    return error;
894}
895
896- (NSArray *) peers
897{
898    int totalPeers;
899    tr_peer_stat * peers = tr_torrentPeers(fHandle, &totalPeers);
900   
901    NSMutableArray * peerDicts = [NSMutableArray arrayWithCapacity: totalPeers];
902   
903    for (int i = 0; i < totalPeers; i++)
904    {
905        tr_peer_stat * peer = &peers[i];
906        NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 12];
907       
908        [dict setObject: [self name] forKey: @"Name"];
909        [dict setObject: [NSNumber numberWithInt: peer->from] forKey: @"From"];
910        [dict setObject: [NSString stringWithUTF8String: peer->addr] forKey: @"IP"];
911        [dict setObject: [NSNumber numberWithInt: peer->port] forKey: @"Port"];
912        [dict setObject: [NSNumber numberWithFloat: peer->progress] forKey: @"Progress"];
913        [dict setObject: [NSNumber numberWithBool: peer->isSeed] forKey: @"Seed"];
914        [dict setObject: [NSNumber numberWithBool: peer->isEncrypted] forKey: @"Encryption"];
915        [dict setObject: [NSNumber numberWithBool: peer->isUTP] forKey: @"uTP"];
916        [dict setObject: [NSString stringWithUTF8String: peer->client] forKey: @"Client"];
917        [dict setObject: [NSString stringWithUTF8String: peer->flagStr] forKey: @"Flags"];
918       
919        if (peer->isUploadingTo)
920            [dict setObject: [NSNumber numberWithDouble: peer->rateToPeer_KBps] forKey: @"UL To Rate"];
921        if (peer->isDownloadingFrom)
922            [dict setObject: [NSNumber numberWithDouble: peer->rateToClient_KBps] forKey: @"DL From Rate"];
923       
924        [peerDicts addObject: dict];
925    }
926   
927    tr_torrentPeersFree(peers, totalPeers);
928   
929    return peerDicts;
930}
931
932- (NSUInteger) webSeedCount
933{
934    return fInfo->webseedCount;
935}
936
937- (NSArray *) webSeeds
938{
939    NSMutableArray * webSeeds = [NSMutableArray arrayWithCapacity: fInfo->webseedCount];
940   
941    double * dlSpeeds = tr_torrentWebSpeeds_KBps(fHandle);
942   
943    for (NSInteger i = 0; i < fInfo->webseedCount; i++)
944    {
945        NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 3];
946       
947        [dict setObject: [self name] forKey: @"Name"];
948        [dict setObject: [NSString stringWithUTF8String: fInfo->webseeds[i]] forKey: @"Address"];
949       
950        if (dlSpeeds[i] != -1.0)
951            [dict setObject: [NSNumber numberWithDouble: dlSpeeds[i]] forKey: @"DL From Rate"];
952       
953        [webSeeds addObject: dict];
954    }
955   
956    tr_free(dlSpeeds);
957   
958    return webSeeds;
959}
960
961- (NSString *) progressString
962{
963    if ([self isMagnet])
964    {
965        NSString * progressString = fStat->metadataPercentComplete > 0.0
966                    ? [NSString stringWithFormat: NSLocalizedString(@"%@ of torrent metadata retrieved",
967                        "Torrent -> progress string"), [NSString percentString: fStat->metadataPercentComplete longDecimals: YES]]
968                    : NSLocalizedString(@"torrent metadata needed", "Torrent -> progress string");
969       
970        return [NSString stringWithFormat: @"%@ - %@", NSLocalizedString(@"Magnetized transfer", "Torrent -> progress string"),
971                                            progressString];
972    }
973   
974    NSString * string;
975   
976    if (![self allDownloaded])
977    {
978        CGFloat progress;
979        if ([self isFolder] && [fDefaults boolForKey: @"DisplayStatusProgressSelected"])
980        {
981            string = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self totalSizeSelected]];
982            progress = [self progressDone];
983        }
984        else
985        {
986            string = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self size]];
987            progress = [self progress];
988        }
989       
990        string = [string stringByAppendingFormat: @" (%@)", [NSString percentString: progress longDecimals: YES]];
991    }
992    else
993    {
994        NSString * downloadString;
995        if (![self isComplete]) //only multifile possible
996        {
997            if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
998                downloadString = [NSString stringWithFormat: NSLocalizedString(@"%@ selected", "Torrent -> progress string"),
999                                    [NSString stringForFileSize: [self haveTotal]]];
1000            else
1001            {
1002                downloadString = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self size]];
1003                downloadString = [downloadString stringByAppendingFormat: @" (%@)",
1004                                    [NSString percentString: [self progress] longDecimals: YES]];
1005            }
1006        }
1007        else
1008            downloadString = [NSString stringForFileSize: [self size]];
1009       
1010        NSString * uploadString = [NSString stringWithFormat: NSLocalizedString(@"uploaded %@ (Ratio: %@)",
1011                                    "Torrent -> progress string"), [NSString stringForFileSize: [self uploadedTotal]],
1012                                    [NSString stringForRatio: [self ratio]]];
1013       
1014        string = [downloadString stringByAppendingFormat: @", %@", uploadString];
1015    }
1016   
1017    //add time when downloading or seed limit set
1018    if ([self shouldShowEta])
1019        string = [string stringByAppendingFormat: @" - %@", [self etaString]];
1020   
1021    return string;
1022}
1023
1024- (NSString *) statusString
1025{
1026    NSString * string;
1027   
1028    if ([self isAnyErrorOrWarning])
1029    {
1030        switch (fStat->error)
1031        {
1032            case TR_STAT_LOCAL_ERROR: string = NSLocalizedString(@"Error", "Torrent -> status string"); break;
1033            case TR_STAT_TRACKER_ERROR: string = NSLocalizedString(@"Tracker returned error", "Torrent -> status string"); break;
1034            case TR_STAT_TRACKER_WARNING: string = NSLocalizedString(@"Tracker returned warning", "Torrent -> status string"); break;
1035            default: NSAssert(NO, @"unknown error state");
1036        }
1037       
1038        NSString * errorString = [self errorMessage];
1039        if (errorString && ![errorString isEqualToString: @""])
1040            string = [string stringByAppendingFormat: @": %@", errorString];
1041    }
1042    else
1043    {
1044        switch (fStat->activity)
1045        {
1046            case TR_STATUS_STOPPED:
1047                if ([self isFinishedSeeding])
1048                    string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1049                else
1050                    string = NSLocalizedString(@"Paused", "Torrent -> status string");
1051                break;
1052           
1053            case TR_STATUS_DOWNLOAD_WAIT:
1054                string = [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis];
1055                break;
1056               
1057            case TR_STATUS_SEED_WAIT:
1058                string = [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1059                break;
1060           
1061            case TR_STATUS_CHECK_WAIT:
1062                string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1063                break;
1064
1065            case TR_STATUS_CHECK:
1066                string = [NSString stringWithFormat: @"%@ (%@)",
1067                            NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
1068                            [NSString percentString: [self checkingProgress] longDecimals: YES]];
1069                break;
1070
1071            case TR_STATUS_DOWNLOAD:
1072                if ([self totalPeersConnected] != 1)
1073                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
1074                                                    "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
1075                else
1076                    string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
1077                                                    "Torrent -> status string"), [self peersSendingToUs]];
1078               
1079                const NSInteger webSeedCount = fStat->webseedsSendingToUs;
1080                if (webSeedCount > 0)
1081                {
1082                    NSString * webSeedString;
1083                    if (webSeedCount == 1)
1084                        webSeedString = NSLocalizedString(@"web seed", "Torrent -> status string");
1085                    else
1086                        webSeedString = [NSString stringWithFormat: NSLocalizedString(@"%d web seeds", "Torrent -> status string"),
1087                                                                    webSeedCount];
1088                   
1089                    string = [string stringByAppendingFormat: @" + %@", webSeedString];
1090                }
1091               
1092                break;
1093
1094            case TR_STATUS_SEED:
1095                if ([self totalPeersConnected] != 1)
1096                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
1097                                                    [self peersGettingFromUs], [self totalPeersConnected]];
1098                else
1099                    string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
1100                                                    [self peersGettingFromUs]];
1101        }
1102       
1103        if ([self isStalled])
1104            string = [NSLocalizedString(@"Stalled", "Torrent -> status string") stringByAppendingFormat: @", %@", string];
1105    }
1106   
1107    //append even if error
1108    if ([self isActive] && ![self isChecking])
1109    {
1110        if (fStat->activity == TR_STATUS_DOWNLOAD)
1111            string = [string stringByAppendingFormat: @" - %@: %@, %@: %@",
1112                        NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1113                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1114        else
1115            string = [string stringByAppendingFormat: @" - %@: %@",
1116                        NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1117    }
1118   
1119    return string;
1120}
1121
1122- (NSString *) shortStatusString
1123{
1124    NSString * string;
1125   
1126    switch (fStat->activity)
1127    {
1128        case TR_STATUS_STOPPED:
1129            if ([self isFinishedSeeding])
1130                string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1131            else
1132                string = NSLocalizedString(@"Paused", "Torrent -> status string");
1133            break;
1134       
1135        case TR_STATUS_DOWNLOAD_WAIT:
1136            string = [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis];
1137            break;
1138           
1139        case TR_STATUS_SEED_WAIT:
1140            string = [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1141            break;
1142
1143        case TR_STATUS_CHECK_WAIT:
1144            string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1145            break;
1146
1147        case TR_STATUS_CHECK:
1148            string = [NSString stringWithFormat: @"%@ (%@)",
1149                        NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
1150                        [NSString percentString: [self checkingProgress] longDecimals: YES]];
1151            break;
1152       
1153        case TR_STATUS_DOWNLOAD:
1154            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1155                            NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1156                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1157            break;
1158       
1159        case TR_STATUS_SEED:
1160            string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1161                            NSLocalizedString(@"Ratio", "Torrent -> status string"), [NSString stringForRatio: [self ratio]],
1162                            NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1163    }
1164   
1165    return string;
1166}
1167
1168- (NSString *) remainingTimeString
1169{
1170    if ([self shouldShowEta])
1171        return [self etaString];
1172    else
1173        return [self shortStatusString];
1174}
1175
1176- (NSString *) stateString
1177{
1178    switch (fStat->activity)
1179    {
1180        case TR_STATUS_STOPPED:
1181        case TR_STATUS_DOWNLOAD_WAIT:
1182        case TR_STATUS_SEED_WAIT:
1183        {
1184            NSString * string = NSLocalizedString(@"Paused", "Torrent -> status string");
1185           
1186            NSString * extra = nil;
1187            if ([self waitingToStart])
1188            {
1189                extra = fStat->activity == TR_STATUS_DOWNLOAD_WAIT
1190                        ? NSLocalizedString(@"Waiting to download", "Torrent -> status string")
1191                        : NSLocalizedString(@"Waiting to seed", "Torrent -> status string");
1192            }
1193            else if ([self isFinishedSeeding])
1194                extra = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1195            else;
1196       
1197            return extra ? [string stringByAppendingFormat: @" (%@)", extra] : string;
1198        }
1199       
1200        case TR_STATUS_CHECK_WAIT:
1201            return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1202
1203        case TR_STATUS_CHECK:
1204            return [NSString stringWithFormat: @"%@ (%@)",
1205                    NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
1206                    [NSString percentString: [self checkingProgress] longDecimals: YES]];
1207
1208        case TR_STATUS_DOWNLOAD:
1209            return NSLocalizedString(@"Downloading", "Torrent -> status string");
1210
1211        case TR_STATUS_SEED:
1212            return NSLocalizedString(@"Seeding", "Torrent -> status string");
1213    }
1214}
1215
1216- (NSInteger) totalPeersConnected
1217{
1218    return fStat->peersConnected;
1219}
1220
1221- (NSInteger) totalPeersTracker
1222{
1223    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1224}
1225
1226- (NSInteger) totalPeersIncoming
1227{
1228    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1229}
1230
1231- (NSInteger) totalPeersCache
1232{
1233    return fStat->peersFrom[TR_PEER_FROM_RESUME];
1234}
1235
1236- (NSInteger) totalPeersPex
1237{
1238    return fStat->peersFrom[TR_PEER_FROM_PEX];
1239}
1240
1241- (NSInteger) totalPeersDHT
1242{
1243    return fStat->peersFrom[TR_PEER_FROM_DHT];
1244}
1245
1246- (NSInteger) totalPeersLocal
1247{
1248    return fStat->peersFrom[TR_PEER_FROM_LPD];
1249}
1250
1251- (NSInteger) totalPeersLTEP
1252{
1253    return fStat->peersFrom[TR_PEER_FROM_LTEP];
1254}
1255
1256- (NSInteger) peersSendingToUs
1257{
1258    return fStat->peersSendingToUs;
1259}
1260
1261- (NSInteger) peersGettingFromUs
1262{
1263    return fStat->peersGettingFromUs;
1264}
1265
1266- (CGFloat) downloadRate
1267{
1268    return fStat->pieceDownloadSpeed_KBps;
1269}
1270
1271- (CGFloat) uploadRate
1272{
1273    return fStat->pieceUploadSpeed_KBps;
1274}
1275
1276- (CGFloat) totalRate
1277{
1278    return [self downloadRate] + [self uploadRate];
1279}
1280
1281- (uint64_t) haveVerified
1282{
1283    return fStat->haveValid;
1284}
1285
1286- (uint64_t) haveTotal
1287{
1288    return [self haveVerified] + fStat->haveUnchecked;
1289}
1290
1291- (uint64_t) totalSizeSelected
1292{
1293    return fStat->sizeWhenDone;
1294}
1295
1296- (uint64_t) downloadedTotal
1297{
1298    return fStat->downloadedEver;
1299}
1300
1301- (uint64_t) uploadedTotal
1302{
1303    return fStat->uploadedEver;
1304}
1305
1306- (uint64_t) failedHash
1307{
1308    return fStat->corruptEver;
1309}
1310
1311- (NSInteger) groupValue
1312{
1313    return fGroupValue;
1314}
1315
1316- (void) setGroupValue: (NSInteger) goupValue
1317{
1318    fGroupValue = goupValue;
1319}
1320
1321- (NSInteger) groupOrderValue
1322{
1323    return [[GroupsController groups] rowValueForIndex: fGroupValue];
1324}
1325
1326- (void) checkGroupValueForRemoval: (NSNotification *) notification
1327{
1328    if (fGroupValue != -1 && [[[notification userInfo] objectForKey: @"Index"] integerValue] == fGroupValue)
1329        fGroupValue = -1;
1330}
1331
1332- (NSArray *) fileList
1333{
1334    return fFileList;
1335}
1336
1337- (NSArray *) flatFileList
1338{
1339    return fFlatFileList;
1340}
1341
1342- (NSInteger) fileCount
1343{
1344    return fInfo->fileCount;
1345}
1346
1347- (void) updateFileStat
1348{
1349    if (fFileStat)
1350        tr_torrentFilesFree(fFileStat, [self fileCount]);
1351   
1352    fFileStat = tr_torrentFiles(fHandle, NULL);
1353}
1354
1355- (CGFloat) fileProgress: (FileListNode *) node
1356{
1357    if ([self fileCount] == 1 || [self isComplete])
1358        return [self progress];
1359   
1360    if (!fFileStat)
1361        [self updateFileStat];
1362   
1363    NSIndexSet * indexSet = [node indexes];
1364   
1365    if ([indexSet count] == 1)
1366        return fFileStat[[indexSet firstIndex]].progress;
1367   
1368    uint64_t have = 0;
1369    for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1370        have += fFileStat[index].bytesCompleted;
1371   
1372    NSAssert([node size], @"directory in torrent file has size 0");
1373    return (CGFloat)have / [node size];
1374}
1375
1376- (BOOL) canChangeDownloadCheckForFile: (NSUInteger) index
1377{
1378    NSAssert2(index < [self fileCount], @"Index %d is greater than file count %d", index, [self fileCount]);
1379   
1380    if ([self fileCount] == 1 || [self isComplete])
1381        return NO;
1382   
1383    if (!fFileStat)
1384        [self updateFileStat];
1385   
1386    return fFileStat[index].progress < 1.0;
1387}
1388
1389- (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1390{
1391    if ([self fileCount] == 1 || [self isComplete])
1392        return NO;
1393   
1394    if (!fFileStat)
1395        [self updateFileStat];
1396   
1397    for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1398        if (fFileStat[index].progress < 1.0)
1399            return YES;
1400    return NO;
1401}
1402
1403- (NSInteger) checkForFiles: (NSIndexSet *) indexSet
1404{
1405    BOOL onState = NO, offState = NO;
1406    for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1407    {
1408        if (!fInfo->files[index].dnd || ![self canChangeDownloadCheckForFile: index])
1409            onState = YES;
1410        else
1411            offState = YES;
1412       
1413        if (onState && offState)
1414            return NSMixedState;
1415    }
1416    return onState ? NSOnState : NSOffState;
1417}
1418
1419- (void) setFileCheckState: (NSInteger) state forIndexes: (NSIndexSet *) indexSet
1420{
1421    NSUInteger count = [indexSet count];
1422    tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1423    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1424        files[i] = index;
1425   
1426    tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1427    free(files);
1428   
1429    [self update];
1430    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
1431}
1432
1433- (void) setFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1434{
1435    const NSUInteger count = [indexSet count];
1436    tr_file_index_t * files = tr_malloc(count * sizeof(tr_file_index_t));
1437    for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1438        files[i] = index;
1439   
1440    tr_torrentSetFilePriorities(fHandle, files, count, priority);
1441    tr_free(files);
1442}
1443
1444- (BOOL) hasFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1445{
1446    for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1447        if (priority == fInfo->files[index].priority && [self canChangeDownloadCheckForFile: index])
1448            return YES;
1449    return NO;
1450}
1451
1452- (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1453{
1454    BOOL low = NO, normal = NO, high = NO;
1455    NSMutableSet * priorities = [NSMutableSet setWithCapacity: MIN([indexSet count], 3)];
1456   
1457    for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1458    {
1459        if (![self canChangeDownloadCheckForFile: index])
1460            continue;
1461       
1462        const tr_priority_t priority = fInfo->files[index].priority;
1463        switch (priority)
1464        {
1465            case TR_PRI_LOW:
1466                if (low)
1467                    continue;
1468                low = YES;
1469                break;
1470            case TR_PRI_NORMAL:
1471                if (normal)
1472                    continue;
1473                normal = YES;
1474                break;
1475            case TR_PRI_HIGH:
1476                if (high)
1477                    continue;
1478                high = YES;
1479                break;
1480            default:
1481                NSAssert2(NO, @"Unknown priority %d for file index %d", priority, index);
1482        }
1483       
1484        [priorities addObject: [NSNumber numberWithInteger: priority]];
1485        if (low && normal && high)
1486            break;
1487    }
1488    return priorities;
1489}
1490
1491- (NSDate *) dateAdded
1492{
1493    const time_t date = fStat->addedDate;
1494    return [NSDate dateWithTimeIntervalSince1970: date];
1495}
1496
1497- (NSDate *) dateCompleted
1498{
1499    const time_t date = fStat->doneDate;
1500    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1501}
1502
1503- (NSDate *) dateActivity
1504{
1505    const time_t date = fStat->activityDate;
1506    return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1507}
1508
1509- (NSDate *) dateActivityOrAdd
1510{
1511    NSDate * date = [self dateActivity];
1512    return date ? date : [self dateAdded];
1513}
1514
1515- (NSInteger) secondsDownloading
1516{
1517    return fStat->secondsDownloading;
1518}
1519
1520- (NSInteger) secondsSeeding
1521{
1522    return fStat->secondsSeeding;
1523}
1524
1525- (NSInteger) stalledMinutes
1526{
1527    if (fStat->idleSecs == -1)
1528        return -1;
1529   
1530    return fStat->idleSecs / 60;
1531}
1532
1533- (BOOL) isStalled
1534{
1535    return fStat->isStalled;
1536}
1537
1538- (void) updateTimeMachineExclude
1539{
1540    NSString * currentLocation = ![self allDownloaded] ? [self dataLocation] : nil;
1541   
1542    //return if the locations are the same
1543    if (fTimeMachineExclude && currentLocation && [fTimeMachineExclude isEqualToString: currentLocation])
1544        return;
1545   
1546    //remove old location...
1547    if (fTimeMachineExclude)
1548    {
1549        [self setTimeMachineExclude: NO forPath: fTimeMachineExclude];
1550        [fTimeMachineExclude release];
1551        fTimeMachineExclude = nil;
1552    }
1553   
1554    //...set new location
1555    if (currentLocation)
1556    {
1557        [self setTimeMachineExclude: YES forPath: currentLocation];
1558        fTimeMachineExclude = [currentLocation retain];
1559    }
1560}
1561
1562- (NSInteger) stateSortKey
1563{
1564    if (![self isActive]) //paused
1565    {
1566        if ([self waitingToStart])
1567            return 1;
1568        else
1569            return 0;
1570    }
1571    else if ([self isSeeding]) //seeding
1572        return 10;
1573    else //downloading
1574        return 20;
1575}
1576
1577- (NSString *) trackerSortKey
1578{
1579    int count;
1580    tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
1581   
1582    NSString * best = nil;
1583   
1584    for (int i=0; i < count; ++i)
1585    {
1586        NSString * tracker = [NSString stringWithUTF8String: stats[i].host];
1587        if (!best || [tracker localizedCaseInsensitiveCompare: best] == NSOrderedAscending)
1588            best = tracker;
1589    }
1590   
1591    tr_torrentTrackersFree(stats, count);
1592    return best;
1593}
1594
1595- (tr_torrent *) torrentStruct
1596{
1597    return fHandle;
1598}
1599
1600- (NSURL *) previewItemURL
1601{
1602    NSString * location = [self dataLocation];
1603    return location ? [NSURL fileURLWithPath: location] : nil;
1604}
1605
1606@end
1607
1608@implementation Torrent (Private)
1609
1610- (id) initWithPath: (NSString *) path hash: (NSString *) hashString torrentStruct: (tr_torrent *) torrentStruct
1611        magnetAddress: (NSString *) magnetAddress lib: (tr_session *) lib
1612        groupValue: (NSNumber *) groupValue
1613        timeMachineExcludeLocation: (NSString *) timeMachineExclude
1614        downloadFolder: (NSString *) downloadFolder
1615        legacyIncompleteFolder: (NSString *) incompleteFolder
1616{
1617    if (!(self = [super init]))
1618        return nil;
1619   
1620    fDefaults = [NSUserDefaults standardUserDefaults];
1621   
1622    if (torrentStruct)
1623        fHandle = torrentStruct;
1624    else
1625    {
1626        //set libtransmission settings for initialization
1627        tr_ctor * ctor = tr_ctorNew(lib);
1628       
1629        tr_ctorSetPaused(ctor, TR_FORCE, YES);
1630        if (downloadFolder)
1631            tr_ctorSetDownloadDir(ctor, TR_FORCE, [downloadFolder UTF8String]);
1632        if (incompleteFolder)
1633            tr_ctorSetIncompleteDir(ctor, [incompleteFolder UTF8String]);
1634       
1635        tr_parse_result result = TR_PARSE_ERR;
1636        if (path)
1637            result = tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1638       
1639        if (result != TR_PARSE_OK && magnetAddress)
1640            result = tr_ctorSetMetainfoFromMagnetLink(ctor, [magnetAddress UTF8String]);
1641       
1642        //backup - shouldn't be needed after upgrade to 1.70
1643        if (result != TR_PARSE_OK && hashString)
1644            result = tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1645       
1646        if (result == TR_PARSE_OK)
1647            fHandle = tr_torrentNew(ctor, NULL);
1648       
1649        tr_ctorFree(ctor);
1650       
1651        if (!fHandle)
1652        {
1653            [self release];
1654            return nil;
1655        }
1656    }
1657
1658    fInfo = tr_torrentInfo(fHandle);
1659   
1660    tr_torrentSetQueueStartCallback(fHandle, startQueueCallback, self);
1661    tr_torrentSetCompletenessCallback(fHandle, completenessChangeCallback, self);
1662    tr_torrentSetRatioLimitHitCallback(fHandle, ratioLimitHitCallback, self);
1663    tr_torrentSetIdleLimitHitCallback(fHandle, idleLimitHitCallback, self);
1664    tr_torrentSetMetadataCallback(fHandle, metadataCallback, self);
1665   
1666    fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
1667   
1668    fResumeOnWake = NO;
1669   
1670    //don't do after this point - it messes with auto-group functionality
1671    if (![self isMagnet])
1672        [self createFileList];
1673       
1674    fGroupValue = groupValue ? [groupValue intValue] : [[GroupsController groups] groupIndexForTorrent: self];
1675   
1676    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1677        name: @"GroupValueRemoved" object: nil];
1678   
1679    fTimeMachineExclude = [timeMachineExclude retain];
1680    [self update];
1681   
1682    return self;
1683}
1684
1685- (void) createFileList
1686{
1687    NSAssert(![self isMagnet], @"Cannot create a file list until the torrent is demagnetized");
1688   
1689    if ([self isFolder])
1690    {
1691        const NSInteger count = [self fileCount];
1692        NSMutableArray * fileList = [NSMutableArray arrayWithCapacity: count],
1693                    * flatFileList = [NSMutableArray arrayWithCapacity: count];
1694       
1695        for (NSInteger i = 0; i < count; i++)
1696        {
1697            tr_file * file = &fInfo->files[i];
1698           
1699            NSString * fullPath = [NSString stringWithUTF8String: file->name];
1700            NSArray * pathComponents = [fullPath pathComponents];
1701            NSAssert1([pathComponents count] >= 2, @"Not enough components in path %@", fullPath);
1702           
1703            NSString * path = [pathComponents objectAtIndex: 0];
1704            NSString * name = [pathComponents objectAtIndex: 1];
1705           
1706            if ([pathComponents count] > 2)
1707            {
1708                //determine if folder node already exists
1709                FileListNode * node;
1710                for (node in fileList)
1711                    if ([[node name] isEqualToString: name] && [node isFolder])
1712                        break;
1713               
1714                if (!node)
1715                {
1716                    node = [[FileListNode alloc] initWithFolderName: name path: path torrent: self];
1717                    [fileList addObject: node];
1718                    [node release];
1719                }
1720               
1721                NSMutableArray * trimmedComponents = [NSMutableArray arrayWithArray: [pathComponents subarrayWithRange:
1722                                                        NSMakeRange(2, [pathComponents count]-2)]];
1723               
1724                [node insertIndex: i withSize: file->length];
1725                [self insertPath: trimmedComponents forParent: node fileSize: file->length index: i flatList: flatFileList];
1726            }
1727            else
1728            {
1729                FileListNode * node = [[FileListNode alloc] initWithFileName: name path: path size: file->length index: i torrent: self];
1730                [fileList addObject: node];
1731                [flatFileList addObject: node];
1732                [node release];
1733            }
1734        }
1735       
1736        [self sortFileList: fileList];
1737        [self sortFileList: flatFileList];
1738       
1739        fFileList = [[NSArray alloc] initWithArray: fileList];
1740        fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1741    }
1742    else
1743    {
1744        FileListNode * node = [[FileListNode alloc] initWithFileName: [self name] path: @"" size: [self size] index: 0 torrent: self];
1745        fFileList = [[NSArray arrayWithObject: node] retain];
1746        fFlatFileList = [fFileList retain];
1747        [node release];
1748    }
1749}
1750
1751- (void) insertPath: (NSMutableArray *) components forParent: (FileListNode *) parent fileSize: (uint64_t) size
1752    index: (NSInteger) index flatList: (NSMutableArray *) flatFileList
1753{
1754    NSString * name = [components objectAtIndex: 0];
1755    const BOOL isFolder = [components count] > 1;
1756   
1757    FileListNode * node = nil;
1758    if (isFolder)
1759    {
1760        for (node in [parent children])
1761            if ([[node name] isEqualToString: name] && [node isFolder])
1762                break;
1763    }
1764   
1765    //create new folder or file if it doesn't already exist
1766    if (!node)
1767    {
1768        NSString * path = [[parent path] stringByAppendingPathComponent: [parent name]];
1769        if (isFolder)
1770            node = [[FileListNode alloc] initWithFolderName: name path: path torrent: self];
1771        else
1772        {
1773            node = [[FileListNode alloc] initWithFileName: name path: path size: size index: index torrent: self];
1774            [flatFileList addObject: node];
1775        }
1776       
1777        [parent insertChild: node];
1778        [node release];
1779    }
1780   
1781    if (isFolder)
1782    {
1783        [node insertIndex: index withSize: size];
1784       
1785        [components removeObjectAtIndex: 0];
1786        [self insertPath: components forParent: node fileSize: size index: index flatList: flatFileList];
1787    }
1788}
1789
1790- (void) sortFileList: (NSMutableArray *) fileNodes
1791{
1792    NSSortDescriptor * descriptor = [[[NSSortDescriptor alloc] initWithKey: @"name" ascending: YES
1793                                            selector: @selector(compareFinder:)] autorelease];
1794    [fileNodes sortUsingDescriptors: [NSArray arrayWithObject: descriptor]];
1795   
1796    for (FileListNode * node in fileNodes)
1797        if ([node isFolder])
1798            [self sortFileList: [node children]];
1799}
1800
1801- (void) startQueue
1802{
1803    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
1804}
1805
1806//status has been retained
1807- (void) completenessChange: (NSDictionary *) statusInfo
1808{
1809    fStat = tr_torrentStat(fHandle); //don't call update yet to avoid auto-stop
1810   
1811    switch ([[statusInfo objectForKey: @"Status"] intValue])
1812    {
1813        case TR_SEED:
1814        case TR_PARTIAL_SEED:
1815            //simpler to create a new dictionary than to use statusInfo - avoids retention chicanery
1816            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self
1817                userInfo: [NSDictionary dictionaryWithObject: [statusInfo objectForKey: @"WasRunning"] forKey: @"WasRunning"]];
1818            break;
1819       
1820        case TR_LEECH:
1821            [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1822            break;
1823    }
1824    [statusInfo release];
1825   
1826    [self update];
1827    [self updateTimeMachineExclude];
1828}
1829
1830- (void) ratioLimitHit
1831{
1832    fStat = tr_torrentStat(fHandle);
1833   
1834    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedSeeding" object: self];
1835}
1836
1837- (void) idleLimitHit
1838{
1839    fStat = tr_torrentStat(fHandle);
1840   
1841    [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedSeeding" object: self];
1842}
1843
1844- (void) metadataRetrieved
1845{
1846    fStat = tr_torrentStat(fHandle);
1847   
1848    [self createFileList];
1849   
1850    [[NSNotificationCenter defaultCenter] postNotificationName: @"ResetInspector" object: self];
1851}
1852
1853- (BOOL) shouldShowEta
1854{
1855    if (fStat->activity == TR_STATUS_DOWNLOAD)
1856        return YES;
1857    else if ([self isSeeding])
1858    {
1859        //ratio: show if it's set at all
1860        if (tr_torrentGetSeedRatio(fHandle, NULL))
1861            return YES;
1862       
1863        //idle: show only if remaining time is less than cap
1864        if (fStat->etaIdle != TR_ETA_NOT_AVAIL && fStat->etaIdle < ETA_IDLE_DISPLAY_SEC)
1865            return YES;
1866    }
1867   
1868    return NO;
1869}
1870
1871- (NSString *) etaString
1872{
1873    NSInteger eta;
1874    BOOL fromIdle;
1875    //don't check for both, since if there's a regular ETA, the torrent isn't idle so it's meaningless
1876    if (fStat->eta != TR_ETA_NOT_AVAIL && fStat->eta != TR_ETA_UNKNOWN)
1877    {
1878        eta = fStat->eta;
1879        fromIdle = NO;
1880    }
1881    else if (fStat->etaIdle != TR_ETA_NOT_AVAIL && fStat->etaIdle < ETA_IDLE_DISPLAY_SEC)
1882    {
1883        eta = fStat->etaIdle;
1884        fromIdle = YES;
1885    }
1886    else
1887        return NSLocalizedString(@"remaining time unknown", "Torrent -> eta string");
1888   
1889    NSString * idleString = [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "Torrent -> eta string"),
1890                                [NSString timeString: eta showSeconds: YES maxFields: 2]];
1891    if (fromIdle)
1892        idleString = [idleString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"inactive", "Torrent -> eta string")];
1893   
1894    return idleString;
1895}
1896
1897- (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path
1898{
1899    CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, true);
1900}
1901
1902@end
Note: See TracBrowser for help on using the repository browser.