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