1 /******************************************************************************
2 * $Id: Torrent.m 13219 2012-02-11 05:13:46Z livings124 $
4 * Copyright (c) 2006-2012 Transmission authors and contributors
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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 *****************************************************************************/
26 #import "GroupsController.h"
27 #import "FileListNode.h"
28 #import "NSStringAdditions.h"
29 #import "TrackerNode.h"
31 #import "transmission.h" // required by utils.h
32 #import "utils.h" // tr_new()
34 #define ETA_IDLE_DISPLAY_SEC (2*60)
36 @interface Torrent (Private)
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;
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;
51 - (void) completenessChange: (NSDictionary *) statusInfo;
52 - (void) ratioLimitHit;
53 - (void) idleLimitHit;
54 - (void) metadataRetrieved;
56 - (BOOL) shouldShowEta;
57 - (NSString *) etaString;
59 - (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path;
63 void startQueueCallback(tr_torrent * torrent, void * torrentData)
65 [(Torrent *)torrentData performSelectorOnMainThread: @selector(startQueue) withObject: nil waitUntilDone: NO];
68 void completenessChangeCallback(tr_torrent * torrent, tr_completeness status, bool wasRunning, void * torrentData)
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];
78 void ratioLimitHitCallback(tr_torrent * torrent, void * torrentData)
80 [(Torrent *)torrentData performSelectorOnMainThread: @selector(ratioLimitHit) withObject: nil waitUntilDone: NO];
83 void idleLimitHitCallback(tr_torrent * torrent, void * torrentData)
85 [(Torrent *)torrentData performSelectorOnMainThread: @selector(idleLimitHit) withObject: nil waitUntilDone: NO];
88 void metadataCallback(tr_torrent * torrent, void * torrentData)
90 [(Torrent *)torrentData performSelectorOnMainThread: @selector(metadataRetrieved) withObject: nil waitUntilDone: NO];
93 int trashDataFile(const char * filename)
98 [Torrent trashFile: [NSString stringWithUTF8String: filename]];
103 @implementation Torrent
105 - (id) initWithPath: (NSString *) path location: (NSString *) location deleteTorrentFile: (BOOL) torrentDelete
106 lib: (tr_session *) lib
108 self = [self initWithPath: path hash: nil torrentStruct: NULL magnetAddress: nil lib: lib
110 timeMachineExcludeLocation: nil
111 downloadFolder: location
112 legacyIncompleteFolder: nil];
116 if (torrentDelete && ![[self torrentLocation] isEqualToString: path])
117 [Torrent trashFile: path];
122 - (id) initWithTorrentStruct: (tr_torrent *) torrentStruct location: (NSString *) location lib: (tr_session *) lib
124 self = [self initWithPath: nil hash: nil torrentStruct: torrentStruct magnetAddress: nil lib: lib
126 timeMachineExcludeLocation: nil
127 downloadFolder: location
128 legacyIncompleteFolder: nil];
133 - (id) initWithMagnetAddress: (NSString *) address location: (NSString *) location lib: (tr_session *) lib
135 self = [self initWithPath: nil hash: nil torrentStruct: nil magnetAddress: address
136 lib: lib groupValue: nil
137 timeMachineExcludeLocation: nil
138 downloadFolder: location legacyIncompleteFolder: nil];
143 - (id) initWithHistory: (NSDictionary *) history lib: (tr_session *) lib forcePause: (BOOL) pause
145 self = [self initWithPath: [history objectForKey: @"InternalTorrentPath"]
146 hash: [history objectForKey: @"TorrentHash"]
150 groupValue: [history objectForKey: @"GroupValue"]
151 timeMachineExcludeLocation: [history objectForKey: @"TimeMachineExcludeLocation"]
152 downloadFolder: [history objectForKey: @"DownloadFolder"] //upgrading from versions < 1.80
153 legacyIncompleteFolder: [[history objectForKey: @"UseIncompleteFolder"] boolValue] //upgrading from versions < 1.80
154 ? [history objectForKey: @"IncompleteFolder"] : nil];
160 if (!pause && (active = [history objectForKey: @"Active"]) && [active boolValue])
162 fStat = tr_torrentStat(fHandle);
163 [self startTransferNoQueue];
166 //upgrading from versions < 1.30: get old added, activity, and done dates
168 if ((date = [history objectForKey: @"Date"]))
169 tr_torrentSetAddedDate(fHandle, [date timeIntervalSince1970]);
170 if ((date = [history objectForKey: @"DateActivity"]))
171 tr_torrentSetActivityDate(fHandle, [date timeIntervalSince1970]);
172 if ((date = [history objectForKey: @"DateCompleted"]))
173 tr_torrentSetDoneDate(fHandle, [date timeIntervalSince1970]);
175 //upgrading from versions < 1.60: get old stop ratio settings
176 NSNumber * ratioSetting;
177 if ((ratioSetting = [history objectForKey: @"RatioSetting"]))
179 switch ([ratioSetting intValue])
181 case NSOnState: [self setRatioSetting: TR_RATIOLIMIT_SINGLE]; break;
182 case NSOffState: [self setRatioSetting: TR_RATIOLIMIT_UNLIMITED]; break;
183 case NSMixedState: [self setRatioSetting: TR_RATIOLIMIT_GLOBAL]; break;
186 NSNumber * ratioLimit;
187 if ((ratioLimit = [history objectForKey: @"RatioLimit"]))
188 [self setRatioLimit: [ratioLimit floatValue]];
193 - (NSDictionary *) history
195 NSMutableDictionary * history = [NSMutableDictionary dictionaryWithObjectsAndKeys:
196 [self torrentLocation], @"InternalTorrentPath",
197 [self hashString], @"TorrentHash",
198 [NSNumber numberWithBool: [self isActive]], @"Active",
199 [NSNumber numberWithBool: [self waitingToStart]], @"WaitToStart",
200 [NSNumber numberWithInt: fGroupValue], @"GroupValue", nil];
202 if (fTimeMachineExclude)
203 [history setObject: fTimeMachineExclude forKey: @"TimeMachineExcludeLocation"];
210 [[NSNotificationCenter defaultCenter] removeObserver: self];
213 tr_torrentFilesFree(fFileStat, [self fileCount]);
215 [fPreviousFinishedIndexes release];
216 [fPreviousFinishedIndexesDate release];
218 [fHashString release];
223 [fFlatFileList release];
225 [fTimeMachineExclude release];
230 - (NSString *) description
232 return [@"Torrent: " stringByAppendingString: [self name]];
235 - (id) copyWithZone: (NSZone *) zone
237 return [self retain];
240 - (void) closeRemoveTorrent: (BOOL) trashFiles
242 //allow the file to be indexed by Time Machine
243 if (fTimeMachineExclude)
245 [self setTimeMachineExclude: NO forPath: fTimeMachineExclude];
246 [fTimeMachineExclude release];
247 fTimeMachineExclude = nil;
250 tr_torrentRemove(fHandle, trashFiles, trashDataFile);
253 - (void) changeDownloadFolderBeforeUsing: (NSString *) folder
255 tr_torrentSetDownloadDir(fHandle, [folder UTF8String]);
256 [self updateTimeMachineExclude];
259 - (NSString *) currentDirectory
261 return [NSString stringWithUTF8String: tr_torrentGetCurrentDir(fHandle)];
264 - (void) getAvailability: (int8_t *) tab size: (NSInteger) size
266 tr_torrentAvailability(fHandle, tab, size);
269 - (void) getAmountFinished: (float *) tab size: (NSInteger) size
271 tr_torrentAmountFinished(fHandle, tab, size);
274 - (NSIndexSet *) previousFinishedPieces
276 //if the torrent hasn't been seen in a bit, and therefore hasn't been refreshed, return nil
277 if (fPreviousFinishedIndexesDate && [fPreviousFinishedIndexesDate timeIntervalSinceNow] > -2.0)
278 return fPreviousFinishedIndexes;
283 - (void) setPreviousFinishedPieces: (NSIndexSet *) indexes
285 [fPreviousFinishedIndexes release];
286 fPreviousFinishedIndexes = [indexes retain];
288 [fPreviousFinishedIndexesDate release];
289 fPreviousFinishedIndexesDate = indexes != nil ? [[NSDate alloc] init] : nil;
294 //get previous stalled value before update
295 const BOOL wasStalled = fStat != NULL && [self isStalled];
297 fStat = tr_torrentStat(fHandle);
299 //make sure the "active" filter is updated when stalled-ness changes
300 if (wasStalled != [self isStalled])
301 [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
303 //when the data first appears, update time machine exclusion
304 if (!fTimeMachineExclude)
305 [self updateTimeMachineExclude];
308 - (void) startTransferIgnoringQueue: (BOOL) ignoreQueue
310 if ([self alertForRemainingDiskSpace])
312 ignoreQueue ? tr_torrentStartNow(fHandle) : tr_torrentStart(fHandle);
315 //capture, specifically, stop-seeding settings changing to unlimited
316 [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
320 - (void) startTransferNoQueue
322 [self startTransferIgnoringQueue: YES];
325 - (void) startTransfer
327 [self startTransferIgnoringQueue: NO];
330 - (void) stopTransfer
332 tr_torrentStop(fHandle);
338 if ((fResumeOnWake = [self isActive]))
339 tr_torrentStop(fHandle);
346 tr_ninf( fInfo->name, "restarting because of wakeUp" );
347 tr_torrentStart(fHandle);
351 - (NSInteger) queuePosition
353 return fStat->queuePosition;
356 - (void) setQueuePosition: (NSUInteger) index
358 tr_torrentSetQueuePosition(fHandle, index);
361 - (void) manualAnnounce
363 tr_torrentManualUpdate(fHandle);
366 - (BOOL) canManualAnnounce
368 return tr_torrentCanManualUpdate(fHandle);
373 tr_torrentVerify(fHandle);
379 return !tr_torrentHasMetadata(fHandle);
382 - (NSString *) magnetLink
384 return [NSString stringWithUTF8String: tr_torrentGetMagnetLink(fHandle)];
392 - (tr_ratiolimit) ratioSetting
394 return tr_torrentGetRatioMode(fHandle);
397 - (void) setRatioSetting: (tr_ratiolimit) setting
399 tr_torrentSetRatioMode(fHandle, setting);
402 - (CGFloat) ratioLimit
404 return tr_torrentGetRatioLimit(fHandle);
407 - (void) setRatioLimit: (CGFloat) limit
409 NSAssert(limit >= 0, @"Ratio cannot be negative");
410 tr_torrentSetRatioLimit(fHandle, limit);
413 - (CGFloat) progressStopRatio
415 return fStat->seedRatioPercentDone;
418 - (tr_idlelimit) idleSetting
420 return tr_torrentGetIdleMode(fHandle);
423 - (void) setIdleSetting: (tr_idlelimit) setting
425 tr_torrentSetIdleMode(fHandle, setting);
428 - (NSUInteger) idleLimitMinutes
430 return tr_torrentGetIdleLimit(fHandle);
433 - (void) setIdleLimitMinutes: (NSUInteger) limit
435 NSAssert(limit > 0, @"Idle limit must be greater than zero");
436 tr_torrentSetIdleLimit(fHandle, limit);
439 - (BOOL) usesSpeedLimit: (BOOL) upload
441 return tr_torrentUsesSpeedLimit(fHandle, upload ? TR_UP : TR_DOWN);
444 - (void) setUseSpeedLimit: (BOOL) use upload: (BOOL) upload
446 tr_torrentUseSpeedLimit(fHandle, upload ? TR_UP : TR_DOWN, use);
449 - (NSInteger) speedLimit: (BOOL) upload
451 return tr_torrentGetSpeedLimit_KBps(fHandle, upload ? TR_UP : TR_DOWN);
454 - (void) setSpeedLimit: (NSInteger) limit upload: (BOOL) upload
456 tr_torrentSetSpeedLimit_KBps(fHandle, upload ? TR_UP : TR_DOWN, limit);
459 - (BOOL) usesGlobalSpeedLimit
461 return tr_torrentUsesSessionLimits(fHandle);
464 - (void) setUseGlobalSpeedLimit: (BOOL) use
466 tr_torrentUseSessionLimits(fHandle, use);
469 - (void) setMaxPeerConnect: (uint16_t) count
471 NSAssert(count > 0, @"max peer count must be greater than 0");
473 tr_torrentSetPeerLimit(fHandle, count);
476 - (uint16_t) maxPeerConnect
478 return tr_torrentGetPeerLimit(fHandle);
480 - (BOOL) waitingToStart
482 return fStat->activity == TR_STATUS_DOWNLOAD_WAIT || fStat->activity == TR_STATUS_SEED_WAIT;
485 - (tr_priority_t) priority
487 return tr_torrentGetPriority(fHandle);
490 - (void) setPriority: (tr_priority_t) priority
492 return tr_torrentSetPriority(fHandle, priority);
495 #warning when 10.6-only use recycleURLs:completionHandler:
496 + (void) trashFile: (NSString *) path
498 //attempt to move to trash
499 if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
500 source: [path stringByDeletingLastPathComponent] destination: @""
501 files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
503 //if cannot trash, just delete it (will work if it's on a remote volume)
505 if (![[NSFileManager defaultManager] removeItemAtPath: path error: &error])
506 NSLog(@"Could not trash %@: %@", path, [error localizedDescription]);
510 - (void) moveTorrentDataFileTo: (NSString *) folder
512 NSString * oldFolder = [self currentDirectory];
513 if ([oldFolder isEqualToString: folder])
516 //check if moving inside itself
517 NSArray * oldComponents = [oldFolder pathComponents],
518 * newComponents = [folder pathComponents];
519 const NSInteger oldCount = [oldComponents count];
521 if (oldCount < [newComponents count] && [[newComponents objectAtIndex: oldCount] isEqualToString: [self name]]
522 && [folder hasPrefix: oldFolder])
524 NSAlert * alert = [[NSAlert alloc] init];
525 [alert setMessageText: NSLocalizedString(@"A folder cannot be moved to inside itself.",
526 "Move inside itself alert -> title")];
527 [alert setInformativeText: [NSString stringWithFormat:
528 NSLocalizedString(@"The move operation of \"%@\" cannot be done.",
529 "Move inside itself alert -> message"), [self name]]];
530 [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move inside itself alert -> button")];
539 tr_torrentSetLocation(fHandle, [folder UTF8String], YES, NULL, &status);
541 while (status == TR_LOC_MOVING) //block while moving (for now)
542 [NSThread sleepForTimeInterval: 0.05];
544 if (status == TR_LOC_DONE)
545 [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateStats" object: nil];
548 NSAlert * alert = [[NSAlert alloc] init];
549 [alert setMessageText: NSLocalizedString(@"There was an error moving the data file.", "Move error alert -> title")];
550 [alert setInformativeText: [NSString stringWithFormat:
551 NSLocalizedString(@"The move operation of \"%@\" cannot be done.", "Move error alert -> message"), [self name]]];
552 [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move error alert -> button")];
558 [self updateTimeMachineExclude];
561 - (void) copyTorrentFileTo: (NSString *) path
563 [[NSFileManager defaultManager] copyItemAtPath: [self torrentLocation] toPath: path error: NULL];
566 - (BOOL) alertForRemainingDiskSpace
568 if ([self allDownloaded] || ![fDefaults boolForKey: @"WarningRemainingSpace"])
571 NSString * downloadFolder = [self currentDirectory];
572 NSDictionary * systemAttributes;
573 if ((systemAttributes = [[NSFileManager defaultManager] attributesOfFileSystemForPath: downloadFolder error: NULL]))
575 const uint64_t remainingSpace = [[systemAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue];
577 //if the remaining space is greater than the size left, then there is enough space regardless of preallocation
578 if (remainingSpace < [self sizeLeft] && remainingSpace < tr_torrentGetBytesLeftToAllocate(fHandle))
580 NSString * volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: downloadFolder] objectAtIndex: 0];
582 NSAlert * alert = [[NSAlert alloc] init];
583 [alert setMessageText: [NSString stringWithFormat:
584 NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
585 "Torrent disk space alert -> title"), [self name]]];
586 [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"The transfer will be paused."
587 " Clear up space on %@ or deselect files in the torrent inspector to continue.",
588 "Torrent disk space alert -> message"), volumeName]];
589 [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent disk space alert -> button")];
590 [alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent disk space alert -> button")];
592 [alert setShowsSuppressionButton: YES];
593 [[alert suppressionButton] setTitle: NSLocalizedString(@"Do not check disk space again",
594 "Torrent disk space alert -> button")];
596 const NSInteger result = [alert runModal];
597 if ([[alert suppressionButton] state] == NSOnState)
598 [fDefaults setBool: NO forKey: @"WarningRemainingSpace"];
601 return result != NSAlertFirstButtonReturn;
610 return [NSImage imageNamed: @"Magnet.png"];
612 #warning replace kGenericFolderIcon stuff with NSImageNameFolder on 10.6
614 fIcon = [[[NSWorkspace sharedWorkspace] iconForFileType: [self isFolder] ? NSFileTypeForHFSTypeCode(kGenericFolderIcon)
615 : [[self name] pathExtension]] retain];
621 return fInfo->name != NULL ? [NSString stringWithUTF8String: fInfo->name] : fHashString;
626 return fInfo->isMultifile;
631 return fInfo->totalSize;
634 - (uint64_t) sizeLeft
636 return fStat->leftUntilDone;
639 - (NSMutableArray *) allTrackerStats
642 tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
644 NSMutableArray * trackers = [NSMutableArray arrayWithCapacity: (count > 0 ? count + (stats[count-1].tier + 1) : 0)];
647 for (int i=0; i < count; ++i)
649 if (stats[i].tier != prevTier)
651 [trackers addObject: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger: stats[i].tier + 1], @"Tier",
652 [self name], @"Name", nil]];
653 prevTier = stats[i].tier;
656 TrackerNode * tracker = [[TrackerNode alloc] initWithTrackerStat: &stats[i] torrent: self];
657 [trackers addObject: tracker];
661 tr_torrentTrackersFree(stats, count);
665 - (NSArray *) allTrackersFlat
667 NSMutableArray * allTrackers = [NSMutableArray arrayWithCapacity: fInfo->trackerCount];
669 for (NSInteger i=0; i < fInfo->trackerCount; i++)
670 [allTrackers addObject: [NSString stringWithUTF8String: fInfo->trackers[i].announce]];
675 - (BOOL) addTrackerToNewTier: (NSString *) tracker
677 tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
679 if ([tracker rangeOfString: @"://"].location == NSNotFound)
680 tracker = [@"http://" stringByAppendingString: tracker];
682 //recreate the tracker structure
683 const int oldTrackerCount = fInfo->trackerCount;
684 tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, oldTrackerCount+1);
685 for (NSUInteger i=0; i < oldTrackerCount; ++i)
686 trackerStructs[i] = fInfo->trackers[i];
688 trackerStructs[oldTrackerCount].announce = (char *)[tracker UTF8String];
689 trackerStructs[oldTrackerCount].tier = trackerStructs[oldTrackerCount-1].tier + 1;
690 trackerStructs[oldTrackerCount].id = oldTrackerCount;
692 const BOOL success = tr_torrentSetAnnounceList(fHandle, trackerStructs, oldTrackerCount+1);
693 tr_free(trackerStructs);
698 - (void) removeTrackers: (NSSet *) trackers
700 //recreate the tracker structure
701 tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, fInfo->trackerCount);
703 NSUInteger newCount = 0;
704 for (NSUInteger i = 0; i < fInfo->trackerCount; i++)
706 if (![trackers containsObject: [NSString stringWithUTF8String: fInfo->trackers[i].announce]])
707 trackerStructs[newCount++] = fInfo->trackers[i];
710 const BOOL success = tr_torrentSetAnnounceList(fHandle, trackerStructs, newCount);
711 NSAssert(success, @"Removing tracker addresses failed");
713 tr_free(trackerStructs);
716 - (NSString *) comment
718 return fInfo->comment ? [NSString stringWithUTF8String: fInfo->comment] : @"";
721 - (NSString *) creator
723 return fInfo->creator ? [NSString stringWithUTF8String: fInfo->creator] : @"";
726 - (NSDate *) dateCreated
728 NSInteger date = fInfo->dateCreated;
729 return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
732 - (NSInteger) pieceSize
734 return fInfo->pieceSize;
737 - (NSInteger) pieceCount
739 return fInfo->pieceCount;
742 - (NSString *) hashString
747 - (BOOL) privateTorrent
749 return fInfo->isPrivate;
752 - (NSString *) torrentLocation
754 return fInfo->torrent ? [NSString stringWithUTF8String: fInfo->torrent] : @"";
757 - (NSString *) dataLocation
764 NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: [self name]];
766 if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
773 char * location = tr_torrentFindFile(fHandle, 0);
774 if (location == NULL)
777 NSString * dataLocation = [NSString stringWithUTF8String: location];
784 - (NSString *) fileLocation: (FileListNode *) node
788 NSString * basePath = [[node path] stringByAppendingPathComponent: [node name]];
789 NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: basePath];
791 if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
798 char * location = tr_torrentFindFile(fHandle, [[node indexes] firstIndex]);
799 if (location == NULL)
802 NSString * dataLocation = [NSString stringWithUTF8String: location];
811 return fStat->percentComplete;
814 - (CGFloat) progressDone
816 return fStat->percentDone;
819 - (CGFloat) progressLeft
821 if ([self size] == 0) //magnet links
824 return (CGFloat)[self sizeLeft] / [self size];
827 - (CGFloat) checkingProgress
829 return fStat->recheckProgress;
832 - (CGFloat) availableDesired
834 return (CGFloat)fStat->desiredAvailable / [self sizeLeft];
839 return fStat->activity != TR_STATUS_STOPPED && fStat->activity != TR_STATUS_DOWNLOAD_WAIT && fStat->activity != TR_STATUS_SEED_WAIT;
844 return fStat->activity == TR_STATUS_SEED;
849 return fStat->activity == TR_STATUS_CHECK || fStat->activity == TR_STATUS_CHECK_WAIT;
852 - (BOOL) isCheckingWaiting
854 return fStat->activity == TR_STATUS_CHECK_WAIT;
857 - (BOOL) allDownloaded
859 return [self sizeLeft] == 0 && ![self isMagnet];
864 return [self progress] >= 1.0;
867 - (BOOL) isFinishedSeeding
869 return fStat->finished;
874 return fStat->error == TR_STAT_LOCAL_ERROR;
877 - (BOOL) isAnyErrorOrWarning
879 return fStat->error != TR_STAT_OK;
882 - (NSString *) errorMessage
884 if (![self isAnyErrorOrWarning])
888 if (!(error = [NSString stringWithUTF8String: fStat->errorString])
889 && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
890 error = [NSString stringWithFormat: @"(%@)", NSLocalizedString(@"unreadable error", "Torrent -> error string unreadable")];
892 //libtransmission uses "Set Location", Mac client uses "Move data file to..." - very hacky!
893 error = [error stringByReplacingOccurrencesOfString: @"Set Location" withString: [@"Move Data File To" stringByAppendingEllipsis]];
901 tr_peer_stat * peers = tr_torrentPeers(fHandle, &totalPeers);
903 NSMutableArray * peerDicts = [NSMutableArray arrayWithCapacity: totalPeers];
905 for (int i = 0; i < totalPeers; i++)
907 tr_peer_stat * peer = &peers[i];
908 NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 12];
910 [dict setObject: [self name] forKey: @"Name"];
911 [dict setObject: [NSNumber numberWithInt: peer->from] forKey: @"From"];
912 [dict setObject: [NSString stringWithUTF8String: peer->addr] forKey: @"IP"];
913 [dict setObject: [NSNumber numberWithInt: peer->port] forKey: @"Port"];
914 [dict setObject: [NSNumber numberWithFloat: peer->progress] forKey: @"Progress"];
915 [dict setObject: [NSNumber numberWithBool: peer->isSeed] forKey: @"Seed"];
916 [dict setObject: [NSNumber numberWithBool: peer->isEncrypted] forKey: @"Encryption"];
917 [dict setObject: [NSNumber numberWithBool: peer->isUTP] forKey: @"uTP"];
918 [dict setObject: [NSString stringWithUTF8String: peer->client] forKey: @"Client"];
919 [dict setObject: [NSString stringWithUTF8String: peer->flagStr] forKey: @"Flags"];
921 if (peer->isUploadingTo)
922 [dict setObject: [NSNumber numberWithDouble: peer->rateToPeer_KBps] forKey: @"UL To Rate"];
923 if (peer->isDownloadingFrom)
924 [dict setObject: [NSNumber numberWithDouble: peer->rateToClient_KBps] forKey: @"DL From Rate"];
926 [peerDicts addObject: dict];
929 tr_torrentPeersFree(peers, totalPeers);
934 - (NSUInteger) webSeedCount
936 return fInfo->webseedCount;
939 - (NSArray *) webSeeds
941 NSMutableArray * webSeeds = [NSMutableArray arrayWithCapacity: fInfo->webseedCount];
943 double * dlSpeeds = tr_torrentWebSpeeds_KBps(fHandle);
945 for (NSInteger i = 0; i < fInfo->webseedCount; i++)
947 NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 3];
949 [dict setObject: [self name] forKey: @"Name"];
950 [dict setObject: [NSString stringWithUTF8String: fInfo->webseeds[i]] forKey: @"Address"];
952 if (dlSpeeds[i] != -1.0)
953 [dict setObject: [NSNumber numberWithDouble: dlSpeeds[i]] forKey: @"DL From Rate"];
955 [webSeeds addObject: dict];
963 - (NSString *) progressString
967 NSString * progressString = fStat->metadataPercentComplete > 0.0
968 ? [NSString stringWithFormat: NSLocalizedString(@"%@ of torrent metadata retrieved",
969 "Torrent -> progress string"), [NSString percentString: fStat->metadataPercentComplete longDecimals: YES]]
970 : NSLocalizedString(@"torrent metadata needed", "Torrent -> progress string");
972 return [NSString stringWithFormat: @"%@ - %@", NSLocalizedString(@"Magnetized transfer", "Torrent -> progress string"),
978 if (![self allDownloaded])
981 if ([self isFolder] && [fDefaults boolForKey: @"DisplayStatusProgressSelected"])
983 string = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self totalSizeSelected]];
984 progress = [self progressDone];
988 string = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self size]];
989 progress = [self progress];
992 string = [string stringByAppendingFormat: @" (%@)", [NSString percentString: progress longDecimals: YES]];
996 NSString * downloadString;
997 if (![self isComplete]) //only multifile possible
999 if ([fDefaults boolForKey: @"DisplayStatusProgressSelected"])
1000 downloadString = [NSString stringWithFormat: NSLocalizedString(@"%@ selected", "Torrent -> progress string"),
1001 [NSString stringForFileSize: [self haveTotal]]];
1004 downloadString = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self size]];
1005 downloadString = [downloadString stringByAppendingFormat: @" (%@)",
1006 [NSString percentString: [self progress] longDecimals: YES]];
1010 downloadString = [NSString stringForFileSize: [self size]];
1012 NSString * uploadString = [NSString stringWithFormat: NSLocalizedString(@"uploaded %@ (Ratio: %@)",
1013 "Torrent -> progress string"), [NSString stringForFileSize: [self uploadedTotal]],
1014 [NSString stringForRatio: [self ratio]]];
1016 string = [downloadString stringByAppendingFormat: @", %@", uploadString];
1019 //add time when downloading or seed limit set
1020 if ([self shouldShowEta])
1021 string = [string stringByAppendingFormat: @" - %@", [self etaString]];
1026 - (NSString *) statusString
1030 if ([self isAnyErrorOrWarning])
1032 switch (fStat->error)
1034 case TR_STAT_LOCAL_ERROR: string = NSLocalizedString(@"Error", "Torrent -> status string"); break;
1035 case TR_STAT_TRACKER_ERROR: string = NSLocalizedString(@"Tracker returned error", "Torrent -> status string"); break;
1036 case TR_STAT_TRACKER_WARNING: string = NSLocalizedString(@"Tracker returned warning", "Torrent -> status string"); break;
1037 default: NSAssert(NO, @"unknown error state");
1040 NSString * errorString = [self errorMessage];
1041 if (errorString && ![errorString isEqualToString: @""])
1042 string = [string stringByAppendingFormat: @": %@", errorString];
1046 switch (fStat->activity)
1048 case TR_STATUS_STOPPED:
1049 if ([self isFinishedSeeding])
1050 string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1052 string = NSLocalizedString(@"Paused", "Torrent -> status string");
1055 case TR_STATUS_DOWNLOAD_WAIT:
1056 string = [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis];
1059 case TR_STATUS_SEED_WAIT:
1060 string = [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1063 case TR_STATUS_CHECK_WAIT:
1064 string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1067 case TR_STATUS_CHECK:
1068 string = [NSString stringWithFormat: @"%@ (%@)",
1069 NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
1070 [NSString percentString: [self checkingProgress] longDecimals: YES]];
1073 case TR_STATUS_DOWNLOAD:
1074 if ([self totalPeersConnected] != 1)
1075 string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of %d peers",
1076 "Torrent -> status string"), [self peersSendingToUs], [self totalPeersConnected]];
1078 string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
1079 "Torrent -> status string"), [self peersSendingToUs]];
1081 const NSInteger webSeedCount = fStat->webseedsSendingToUs;
1082 if (webSeedCount > 0)
1084 NSString * webSeedString;
1085 if (webSeedCount == 1)
1086 webSeedString = NSLocalizedString(@"web seed", "Torrent -> status string");
1088 webSeedString = [NSString stringWithFormat: NSLocalizedString(@"%d web seeds", "Torrent -> status string"),
1091 string = [string stringByAppendingFormat: @" + %@", webSeedString];
1096 case TR_STATUS_SEED:
1097 if ([self totalPeersConnected] != 1)
1098 string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
1099 [self peersGettingFromUs], [self totalPeersConnected]];
1101 string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
1102 [self peersGettingFromUs]];
1105 if ([self isStalled])
1106 string = [NSLocalizedString(@"Stalled", "Torrent -> status string") stringByAppendingFormat: @", %@", string];
1109 //append even if error
1110 if ([self isActive] && ![self isChecking])
1112 if (fStat->activity == TR_STATUS_DOWNLOAD)
1113 string = [string stringByAppendingFormat: @" - %@: %@, %@: %@",
1114 NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1115 NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1117 string = [string stringByAppendingFormat: @" - %@: %@",
1118 NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1124 - (NSString *) shortStatusString
1128 switch (fStat->activity)
1130 case TR_STATUS_STOPPED:
1131 if ([self isFinishedSeeding])
1132 string = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1134 string = NSLocalizedString(@"Paused", "Torrent -> status string");
1137 case TR_STATUS_DOWNLOAD_WAIT:
1138 string = [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis];
1141 case TR_STATUS_SEED_WAIT:
1142 string = [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
1145 case TR_STATUS_CHECK_WAIT:
1146 string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1149 case TR_STATUS_CHECK:
1150 string = [NSString stringWithFormat: @"%@ (%@)",
1151 NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
1152 [NSString percentString: [self checkingProgress] longDecimals: YES]];
1155 case TR_STATUS_DOWNLOAD:
1156 string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1157 NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
1158 NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1161 case TR_STATUS_SEED:
1162 string = [NSString stringWithFormat: @"%@: %@, %@: %@",
1163 NSLocalizedString(@"Ratio", "Torrent -> status string"), [NSString stringForRatio: [self ratio]],
1164 NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
1170 - (NSString *) remainingTimeString
1172 if ([self shouldShowEta])
1173 return [self etaString];
1175 return [self shortStatusString];
1178 - (NSString *) stateString
1180 switch (fStat->activity)
1182 case TR_STATUS_STOPPED:
1183 case TR_STATUS_DOWNLOAD_WAIT:
1184 case TR_STATUS_SEED_WAIT:
1186 NSString * string = NSLocalizedString(@"Paused", "Torrent -> status string");
1188 NSString * extra = nil;
1189 if ([self waitingToStart])
1191 extra = fStat->activity == TR_STATUS_DOWNLOAD_WAIT
1192 ? NSLocalizedString(@"Waiting to download", "Torrent -> status string")
1193 : NSLocalizedString(@"Waiting to seed", "Torrent -> status string");
1195 else if ([self isFinishedSeeding])
1196 extra = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
1199 return extra ? [string stringByAppendingFormat: @" (%@)", extra] : string;
1202 case TR_STATUS_CHECK_WAIT:
1203 return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
1205 case TR_STATUS_CHECK:
1206 return [NSString stringWithFormat: @"%@ (%@)",
1207 NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
1208 [NSString percentString: [self checkingProgress] longDecimals: YES]];
1210 case TR_STATUS_DOWNLOAD:
1211 return NSLocalizedString(@"Downloading", "Torrent -> status string");
1213 case TR_STATUS_SEED:
1214 return NSLocalizedString(@"Seeding", "Torrent -> status string");
1218 - (NSInteger) totalPeersConnected
1220 return fStat->peersConnected;
1223 - (NSInteger) totalPeersTracker
1225 return fStat->peersFrom[TR_PEER_FROM_TRACKER];
1228 - (NSInteger) totalPeersIncoming
1230 return fStat->peersFrom[TR_PEER_FROM_INCOMING];
1233 - (NSInteger) totalPeersCache
1235 return fStat->peersFrom[TR_PEER_FROM_RESUME];
1238 - (NSInteger) totalPeersPex
1240 return fStat->peersFrom[TR_PEER_FROM_PEX];
1243 - (NSInteger) totalPeersDHT
1245 return fStat->peersFrom[TR_PEER_FROM_DHT];
1248 - (NSInteger) totalPeersLocal
1250 return fStat->peersFrom[TR_PEER_FROM_LPD];
1253 - (NSInteger) totalPeersLTEP
1255 return fStat->peersFrom[TR_PEER_FROM_LTEP];
1258 - (NSInteger) peersSendingToUs
1260 return fStat->peersSendingToUs;
1263 - (NSInteger) peersGettingFromUs
1265 return fStat->peersGettingFromUs;
1268 - (CGFloat) downloadRate
1270 return fStat->pieceDownloadSpeed_KBps;
1273 - (CGFloat) uploadRate
1275 return fStat->pieceUploadSpeed_KBps;
1278 - (CGFloat) totalRate
1280 return [self downloadRate] + [self uploadRate];
1283 - (uint64_t) haveVerified
1285 return fStat->haveValid;
1288 - (uint64_t) haveTotal
1290 return [self haveVerified] + fStat->haveUnchecked;
1293 - (uint64_t) totalSizeSelected
1295 return fStat->sizeWhenDone;
1298 - (uint64_t) downloadedTotal
1300 return fStat->downloadedEver;
1303 - (uint64_t) uploadedTotal
1305 return fStat->uploadedEver;
1308 - (uint64_t) failedHash
1310 return fStat->corruptEver;
1313 - (NSInteger) groupValue
1318 - (void) setGroupValue: (NSInteger) goupValue
1320 fGroupValue = goupValue;
1323 - (NSInteger) groupOrderValue
1325 return [[GroupsController groups] rowValueForIndex: fGroupValue];
1328 - (void) checkGroupValueForRemoval: (NSNotification *) notification
1330 if (fGroupValue != -1 && [[[notification userInfo] objectForKey: @"Index"] integerValue] == fGroupValue)
1334 - (NSArray *) fileList
1339 - (NSArray *) flatFileList
1341 return fFlatFileList;
1344 - (NSInteger) fileCount
1346 return fInfo->fileCount;
1349 - (void) updateFileStat
1352 tr_torrentFilesFree(fFileStat, [self fileCount]);
1354 fFileStat = tr_torrentFiles(fHandle, NULL);
1357 - (CGFloat) fileProgress: (FileListNode *) node
1359 if ([self fileCount] == 1 || [self isComplete])
1360 return [self progress];
1363 [self updateFileStat];
1365 NSIndexSet * indexSet = [node indexes];
1367 if ([indexSet count] == 1)
1368 return fFileStat[[indexSet firstIndex]].progress;
1371 for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1372 have += fFileStat[index].bytesCompleted;
1374 NSAssert([node size], @"directory in torrent file has size 0");
1375 return (CGFloat)have / [node size];
1378 - (BOOL) canChangeDownloadCheckForFile: (NSUInteger) index
1380 NSAssert2(index < [self fileCount], @"Index %d is greater than file count %d", index, [self fileCount]);
1382 return [self canChangeDownloadCheckForFiles: [NSIndexSet indexSetWithIndex: index]];
1385 - (BOOL) canChangeDownloadCheckForFiles: (NSIndexSet *) indexSet
1387 if ([self fileCount] == 1 || [self isComplete])
1391 [self updateFileStat];
1393 __block BOOL canChange = NO;
1394 [indexSet enumerateIndexesWithOptions: NSEnumerationConcurrent usingBlock: ^(NSUInteger index, BOOL *stop) {
1395 if (fFileStat[index].progress < 1.0)
1404 - (NSInteger) checkForFiles: (NSIndexSet *) indexSet
1406 BOOL onState = NO, offState = NO;
1407 for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1409 if (!fInfo->files[index].dnd || ![self canChangeDownloadCheckForFile: index])
1414 if (onState && offState)
1415 return NSMixedState;
1417 return onState ? NSOnState : NSOffState;
1420 - (void) setFileCheckState: (NSInteger) state forIndexes: (NSIndexSet *) indexSet
1422 NSUInteger count = [indexSet count];
1423 tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
1424 for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1427 tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
1431 [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
1434 - (void) setFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1436 const NSUInteger count = [indexSet count];
1437 tr_file_index_t * files = tr_malloc(count * sizeof(tr_file_index_t));
1438 for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
1441 tr_torrentSetFilePriorities(fHandle, files, count, priority);
1445 - (BOOL) hasFilePriority: (tr_priority_t) priority forIndexes: (NSIndexSet *) indexSet
1447 for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1448 if (priority == fInfo->files[index].priority && [self canChangeDownloadCheckForFile: index])
1453 - (NSSet *) filePrioritiesForIndexes: (NSIndexSet *) indexSet
1455 BOOL low = NO, normal = NO, high = NO;
1456 NSMutableSet * priorities = [NSMutableSet setWithCapacity: MIN([indexSet count], 3)];
1458 for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
1460 if (![self canChangeDownloadCheckForFile: index])
1463 const tr_priority_t priority = fInfo->files[index].priority;
1482 NSAssert2(NO, @"Unknown priority %d for file index %d", priority, index);
1485 [priorities addObject: [NSNumber numberWithInteger: priority]];
1486 if (low && normal && high)
1492 - (NSDate *) dateAdded
1494 const time_t date = fStat->addedDate;
1495 return [NSDate dateWithTimeIntervalSince1970: date];
1498 - (NSDate *) dateCompleted
1500 const time_t date = fStat->doneDate;
1501 return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1504 - (NSDate *) dateActivity
1506 const time_t date = fStat->activityDate;
1507 return date != 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
1510 - (NSDate *) dateActivityOrAdd
1512 NSDate * date = [self dateActivity];
1513 return date ? date : [self dateAdded];
1516 - (NSInteger) secondsDownloading
1518 return fStat->secondsDownloading;
1521 - (NSInteger) secondsSeeding
1523 return fStat->secondsSeeding;
1526 - (NSInteger) stalledMinutes
1528 if (fStat->idleSecs == -1)
1531 return fStat->idleSecs / 60;
1536 return fStat->isStalled;
1539 - (void) updateTimeMachineExclude
1541 NSString * currentLocation = ![self allDownloaded] ? [self dataLocation] : nil;
1543 //return if the locations are the same
1544 if (fTimeMachineExclude && currentLocation && [fTimeMachineExclude isEqualToString: currentLocation])
1547 //remove old location...
1548 if (fTimeMachineExclude)
1550 [self setTimeMachineExclude: NO forPath: fTimeMachineExclude];
1551 [fTimeMachineExclude release];
1552 fTimeMachineExclude = nil;
1555 //...set new location
1556 if (currentLocation)
1558 [self setTimeMachineExclude: YES forPath: currentLocation];
1559 fTimeMachineExclude = [currentLocation retain];
1563 - (NSInteger) stateSortKey
1565 if (![self isActive]) //paused
1567 if ([self waitingToStart])
1572 else if ([self isSeeding]) //seeding
1578 - (NSString *) trackerSortKey
1581 tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
1583 NSString * best = nil;
1585 for (int i=0; i < count; ++i)
1587 NSString * tracker = [NSString stringWithUTF8String: stats[i].host];
1588 if (!best || [tracker localizedCaseInsensitiveCompare: best] == NSOrderedAscending)
1592 tr_torrentTrackersFree(stats, count);
1596 - (tr_torrent *) torrentStruct
1601 - (NSURL *) previewItemURL
1603 NSString * location = [self dataLocation];
1604 return location ? [NSURL fileURLWithPath: location] : nil;
1609 @implementation Torrent (Private)
1611 - (id) initWithPath: (NSString *) path hash: (NSString *) hashString torrentStruct: (tr_torrent *) torrentStruct
1612 magnetAddress: (NSString *) magnetAddress lib: (tr_session *) lib
1613 groupValue: (NSNumber *) groupValue
1614 timeMachineExcludeLocation: (NSString *) timeMachineExclude
1615 downloadFolder: (NSString *) downloadFolder
1616 legacyIncompleteFolder: (NSString *) incompleteFolder
1618 if (!(self = [super init]))
1621 fDefaults = [NSUserDefaults standardUserDefaults];
1624 fHandle = torrentStruct;
1627 //set libtransmission settings for initialization
1628 tr_ctor * ctor = tr_ctorNew(lib);
1630 tr_ctorSetPaused(ctor, TR_FORCE, YES);
1632 tr_ctorSetDownloadDir(ctor, TR_FORCE, [downloadFolder UTF8String]);
1633 if (incompleteFolder)
1634 tr_ctorSetIncompleteDir(ctor, [incompleteFolder UTF8String]);
1636 tr_parse_result result = TR_PARSE_ERR;
1638 result = tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
1640 if (result != TR_PARSE_OK && magnetAddress)
1641 result = tr_ctorSetMetainfoFromMagnetLink(ctor, [magnetAddress UTF8String]);
1643 //backup - shouldn't be needed after upgrade to 1.70
1644 if (result != TR_PARSE_OK && hashString)
1645 result = tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
1647 if (result == TR_PARSE_OK)
1648 fHandle = tr_torrentNew(ctor, NULL);
1659 fInfo = tr_torrentInfo(fHandle);
1661 tr_torrentSetQueueStartCallback(fHandle, startQueueCallback, self);
1662 tr_torrentSetCompletenessCallback(fHandle, completenessChangeCallback, self);
1663 tr_torrentSetRatioLimitHitCallback(fHandle, ratioLimitHitCallback, self);
1664 tr_torrentSetIdleLimitHitCallback(fHandle, idleLimitHitCallback, self);
1665 tr_torrentSetMetadataCallback(fHandle, metadataCallback, self);
1667 fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
1671 //don't do after this point - it messes with auto-group functionality
1672 if (![self isMagnet])
1673 [self createFileList];
1675 fGroupValue = groupValue ? [groupValue intValue] : [[GroupsController groups] groupIndexForTorrent: self];
1677 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
1678 name: @"GroupValueRemoved" object: nil];
1680 fTimeMachineExclude = [timeMachineExclude retain];
1686 - (void) createFileList
1688 NSAssert(![self isMagnet], @"Cannot create a file list until the torrent is demagnetized");
1690 if ([self isFolder])
1692 const NSInteger count = [self fileCount];
1693 NSMutableArray * fileList = [NSMutableArray arrayWithCapacity: count],
1694 * flatFileList = [NSMutableArray arrayWithCapacity: count];
1696 for (NSInteger i = 0; i < count; i++)
1698 tr_file * file = &fInfo->files[i];
1700 NSString * fullPath = [NSString stringWithUTF8String: file->name];
1701 NSArray * pathComponents = [fullPath pathComponents];
1702 NSAssert1([pathComponents count] >= 2, @"Not enough components in path %@", fullPath);
1704 NSString * path = [pathComponents objectAtIndex: 0];
1705 NSString * name = [pathComponents objectAtIndex: 1];
1707 if ([pathComponents count] > 2)
1709 //determine if folder node already exists
1710 __block FileListNode * node = nil;
1711 [fileList enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * searchNode, NSUInteger idx, BOOL * stop) {
1712 if ([[searchNode name] isEqualToString: name] && [searchNode isFolder])
1721 node = [[FileListNode alloc] initWithFolderName: name path: path torrent: self];
1722 [fileList addObject: node];
1726 NSMutableArray * trimmedComponents = [NSMutableArray arrayWithArray: [pathComponents subarrayWithRange: NSMakeRange(2, [pathComponents count]-2)]];
1728 [node insertIndex: i withSize: file->length];
1729 [self insertPath: trimmedComponents forParent: node fileSize: file->length index: i flatList: flatFileList];
1733 FileListNode * node = [[FileListNode alloc] initWithFileName: name path: path size: file->length index: i torrent: self];
1734 [fileList addObject: node];
1735 [flatFileList addObject: node];
1740 [self sortFileList: fileList];
1741 [self sortFileList: flatFileList];
1743 fFileList = [[NSArray alloc] initWithArray: fileList];
1744 fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
1748 FileListNode * node = [[FileListNode alloc] initWithFileName: [self name] path: @"" size: [self size] index: 0 torrent: self];
1749 fFileList = [[NSArray arrayWithObject: node] retain];
1750 fFlatFileList = [fFileList retain];
1755 - (void) insertPath: (NSMutableArray *) components forParent: (FileListNode *) parent fileSize: (uint64_t) size
1756 index: (NSInteger) index flatList: (NSMutableArray *) flatFileList
1758 NSString * name = [components objectAtIndex: 0];
1759 const BOOL isFolder = [components count] > 1;
1761 //determine if folder node already exists
1762 __block FileListNode * node = nil;
1765 [[parent children] enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * searchNode, NSUInteger idx, BOOL * stop) {
1766 if ([[searchNode name] isEqualToString: name] && [searchNode isFolder])
1774 //create new folder or file if it doesn't already exist
1777 NSString * path = [[parent path] stringByAppendingPathComponent: [parent name]];
1779 node = [[FileListNode alloc] initWithFolderName: name path: path torrent: self];
1782 node = [[FileListNode alloc] initWithFileName: name path: path size: size index: index torrent: self];
1783 [flatFileList addObject: node];
1786 [parent insertChild: node];
1792 [node insertIndex: index withSize: size];
1794 [components removeObjectAtIndex: 0];
1795 [self insertPath: components forParent: node fileSize: size index: index flatList: flatFileList];
1799 - (void) sortFileList: (NSMutableArray *) fileNodes
1801 NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey: @"name" ascending: YES selector: @selector(localizedStandardCompare:)];
1802 [fileNodes sortUsingDescriptors: [NSArray arrayWithObject: descriptor]];
1804 [fileNodes enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * node, NSUInteger idx, BOOL * stop) {
1805 if ([node isFolder])
1806 [self sortFileList: [node children]];
1812 [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
1815 //status has been retained
1816 - (void) completenessChange: (NSDictionary *) statusInfo
1818 fStat = tr_torrentStat(fHandle); //don't call update yet to avoid auto-stop
1820 switch ([[statusInfo objectForKey: @"Status"] intValue])
1823 case TR_PARTIAL_SEED:
1824 //simpler to create a new dictionary than to use statusInfo - avoids retention chicanery
1825 [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self
1826 userInfo: [NSDictionary dictionaryWithObject: [statusInfo objectForKey: @"WasRunning"] forKey: @"WasRunning"]];
1828 //quarantine the finished data
1829 NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: [self name]];
1831 if (FSPathMakeRef((const UInt8 *)[dataLocation UTF8String], &ref, NULL) == noErr)
1833 NSDictionary * quarantineProperties = [NSDictionary dictionaryWithObject: (NSString *)kLSQuarantineTypeOtherDownload forKey: (NSString *)kLSQuarantineTypeKey];
1834 if (LSSetItemAttribute(&ref, kLSRolesAll, kLSItemQuarantineProperties, quarantineProperties) != noErr)
1835 NSLog(@"Failed to quarantine: %@", dataLocation);
1838 NSLog(@"Could not find file to quarantine: %@", dataLocation);
1843 [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
1846 [statusInfo release];
1849 [self updateTimeMachineExclude];
1852 - (void) ratioLimitHit
1854 fStat = tr_torrentStat(fHandle);
1856 [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedSeeding" object: self];
1859 - (void) idleLimitHit
1861 fStat = tr_torrentStat(fHandle);
1863 [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedSeeding" object: self];
1866 - (void) metadataRetrieved
1868 fStat = tr_torrentStat(fHandle);
1870 [self createFileList];
1872 [[NSNotificationCenter defaultCenter] postNotificationName: @"ResetInspector" object: self];
1875 - (BOOL) shouldShowEta
1877 if (fStat->activity == TR_STATUS_DOWNLOAD)
1879 else if ([self isSeeding])
1881 //ratio: show if it's set at all
1882 if (tr_torrentGetSeedRatio(fHandle, NULL))
1885 //idle: show only if remaining time is less than cap
1886 if (fStat->etaIdle != TR_ETA_NOT_AVAIL && fStat->etaIdle < ETA_IDLE_DISPLAY_SEC)
1893 - (NSString *) etaString
1897 //don't check for both, since if there's a regular ETA, the torrent isn't idle so it's meaningless
1898 if (fStat->eta != TR_ETA_NOT_AVAIL && fStat->eta != TR_ETA_UNKNOWN)
1903 else if (fStat->etaIdle != TR_ETA_NOT_AVAIL && fStat->etaIdle < ETA_IDLE_DISPLAY_SEC)
1905 eta = fStat->etaIdle;
1909 return NSLocalizedString(@"remaining time unknown", "Torrent -> eta string");
1911 NSString * idleString = [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "Torrent -> eta string"),
1912 [NSString timeString: eta showSeconds: YES maxFields: 2]];
1914 idleString = [idleString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"inactive", "Torrent -> eta string")];
1919 - (void) setTimeMachineExclude: (BOOL) exclude forPath: (NSString *) path
1921 CSBackupSetItemExcluded((CFURLRef)[NSURL fileURLWithPath: path], exclude, true);