ReconItem.m (21654B)
1 #import "ReconItem.h" 2 #import "Bridge.h" 3 4 #import <Carbon/Carbon.h> 5 6 @implementation ReconItem 7 8 - init { 9 [super init]; 10 selected = NO; // NB only used/updated during sorts. Not a 11 // reliable indicator of whether item is selected 12 fileSize = -1.; 13 bytesTransferred = -1.; 14 return self; 15 } 16 17 - (void)dealloc 18 { 19 [path release]; 20 [fullPath release]; 21 // [direction release]; // assuming retained by cache, so not retained 22 // [directionSortString release]; // no retain/release necessary because is constant 23 [super dealloc]; 24 } 25 26 - (ReconItem *)parent 27 { 28 return parent; 29 } 30 31 - (void)setParent:(ReconItem *)p 32 { 33 parent = p; 34 } 35 36 - (void)willChange 37 { 38 // propagate up parent chain 39 [parent willChange]; 40 } 41 42 - (NSArray *)children 43 { 44 return nil; 45 } 46 47 - (BOOL)selected 48 { 49 return selected; 50 } 51 52 - (void)setSelected:(BOOL)x 53 { 54 selected = x; 55 } 56 57 - (NSString *)path 58 { 59 return path; 60 } 61 62 - (void)setPath:(NSString *)aPath 63 { 64 [path autorelease]; 65 path = [aPath retain]; 66 67 // invalidate 68 [fullPath autorelease]; 69 fullPath = nil; 70 } 71 72 - (NSString *)fullPath 73 { 74 if (!fullPath) { 75 NSString *parentPath = [parent fullPath]; 76 [self setFullPath:(([parentPath length] > 0) ? [parentPath stringByAppendingFormat:@"/%@", path] : path)]; 77 } 78 79 return fullPath; 80 } 81 82 - (void)setFullPath:(NSString *)p 83 { 84 [fullPath autorelease]; 85 fullPath = [p retain]; 86 } 87 88 - (NSString *)left 89 { 90 return nil; 91 } 92 93 - (NSString *)right 94 { 95 return nil; 96 } 97 98 static NSMutableDictionary *_ChangeIconsByType = nil; 99 100 - (NSImage *)changeIconFor:(NSString *)type other:(NSString *)other 101 { 102 if (![type length]) { 103 if ([other isEqual:@"Created"]) { 104 type = @"Absent"; 105 } else if ([other length]) { 106 type = @"Unmodified"; 107 } else 108 return nil; 109 } 110 111 NSImage *result = [_ChangeIconsByType objectForKey:type]; 112 if (!result) { 113 NSString *imageName = [NSString stringWithFormat:@"Change_%@.png", type]; 114 result = [NSImage imageNamed:imageName]; 115 if (!_ChangeIconsByType) _ChangeIconsByType = [[NSMutableDictionary alloc] init]; 116 [_ChangeIconsByType setObject:result forKey:type]; 117 } 118 return result; 119 } 120 121 - (NSImage *)leftIcon 122 { 123 return [self changeIconFor:[self left] other:[self right]]; 124 } 125 126 - (NSImage *)rightIcon 127 { 128 return [self changeIconFor:[self right] other:[self left]]; 129 } 130 131 132 - (double)computeFileSize 133 { 134 return 0.; 135 } 136 137 - (double)bytesTransferred 138 { 139 return 0.; 140 } 141 142 - (long)fileCount 143 { 144 return 1; 145 } 146 147 - (double)fileSize 148 { 149 if (fileSize == -1.) fileSize = [self computeFileSize]; 150 return fileSize; 151 } 152 153 - (NSString *)formatFileSize:(double)size 154 { 155 if (size == 0) return @"--"; 156 if (size < 1024) return @"< 1KB"; // return [NSString stringWithFormat:@"%i bytes", size]; 157 size /= 1024; 158 if (size < 1024) return [NSString stringWithFormat:@"%i KB", (int)size]; 159 size /= 1024; 160 if (size < 1024) return [NSString stringWithFormat:@"%1.1f MB", size]; 161 size = size / 1024; 162 return [NSString stringWithFormat:@"%1.1f GB", size]; 163 } 164 165 - (NSString *)fileSizeString 166 { 167 return [self formatFileSize:[self fileSize]]; 168 } 169 170 - (NSString *)bytesTransferredString 171 { 172 return [self formatFileSize:[self bytesTransferred]]; 173 } 174 175 - (NSNumber *)percentTransferred 176 { 177 double size = [self computeFileSize]; 178 return (size > 0) ? [NSNumber numberWithDouble:([self bytesTransferred] / (size) * 100.0)] 179 : nil; 180 } 181 182 static NSMutableDictionary *_iconsByExtension = nil; 183 184 - (NSImage *)iconForExtension:(NSString *)extension 185 { 186 NSImage *icon = [_iconsByExtension objectForKey:extension]; 187 if (!_iconsByExtension) _iconsByExtension = [[NSMutableDictionary alloc] init]; 188 if (!icon) { 189 icon = [[NSWorkspace sharedWorkspace] iconForFileType:extension]; 190 [icon setSize:NSMakeSize(16.0, 16.0)]; 191 [_iconsByExtension setObject:icon forKey:extension]; 192 } 193 return icon; 194 } 195 196 - (NSImage *)fileIcon 197 { 198 return [self iconForExtension:NSFileTypeForHFSTypeCode(kOpenFolderIcon)]; 199 } 200 201 - (NSString *)dirString 202 { 203 return @"<-?->"; 204 } 205 206 - (NSImage *)direction 207 { 208 if (direction) return direction; 209 NSString * dirString = [self dirString]; 210 211 BOOL changedFromDefault = [self changedFromDefault]; 212 213 if ([dirString isEqual:@"<-?->"]) { 214 if (changedFromDefault | resolved) { 215 direction = [NSImage imageNamed: @"table-skip.tif"]; 216 directionSortString = @"3"; 217 } 218 else { 219 direction = [NSImage imageNamed: @"table-conflict.tif"]; 220 directionSortString = @"2"; 221 } 222 } 223 224 else if ([dirString isEqual:@"---->"]) { 225 if (changedFromDefault) { 226 direction = [NSImage imageNamed: @"table-right-blue.tif"]; 227 directionSortString = @"6"; 228 } 229 else { 230 direction = [NSImage imageNamed: @"table-right-green.tif"]; 231 directionSortString = @"8"; 232 } 233 } 234 235 else if ([dirString isEqual:@"<----"]) { 236 if (changedFromDefault) { 237 direction = [NSImage imageNamed: @"table-left-blue.tif"]; 238 directionSortString = @"5"; 239 } 240 else { 241 direction = [NSImage imageNamed: @"table-left-green.tif"]; 242 directionSortString = @"7"; 243 } 244 } 245 246 else if ([dirString isEqual:@"<-M->"]) { 247 direction = [NSImage imageNamed: @"table-merge.tif"]; 248 directionSortString = @"4"; 249 } 250 251 else if ([dirString isEqual:@"<--->"]) { 252 direction = [NSImage imageNamed: @"table-mixed.tif"]; 253 directionSortString = @"9"; 254 } 255 256 else { 257 direction = [NSImage imageNamed: @"table-error.tif"]; 258 directionSortString = @"1"; 259 } 260 261 [direction retain]; 262 return direction; 263 } 264 265 - (void)setDirection:(char *)d 266 { 267 [direction autorelease]; 268 direction = nil; 269 } 270 271 - (void)doAction:(unichar)action 272 { 273 switch (action) { 274 case '>': 275 [self setDirection:"unisonRiSetRight"]; 276 break; 277 case '<': 278 [self setDirection:"unisonRiSetLeft"]; 279 break; 280 case '/': 281 [self setDirection:"unisonRiSetConflict"]; 282 resolved = YES; 283 break; 284 case '-': 285 [self setDirection:"unisonRiForceOlder"]; 286 break; 287 case '+': 288 [self setDirection:"unisonRiForceNewer"]; 289 break; 290 case 'm': 291 [self setDirection:"unisonRiSetMerge"]; 292 break; 293 case 'd': 294 [self showDiffs]; 295 break; 296 case 'R': 297 [self revertDirection]; 298 break; 299 default: 300 NSLog(@"ReconItem.doAction : unknown action"); 301 break; 302 } 303 } 304 305 - (void)doIgnore:(unichar)action 306 { 307 switch (action) { 308 case 'I': 309 ocamlCall("xS", "unisonIgnorePath", [self fullPath]); 310 break; 311 case 'E': 312 ocamlCall("xS", "unisonIgnoreExt", [self path]); 313 break; 314 case 'N': 315 ocamlCall("xS", "unisonIgnoreName", [self path]); 316 break; 317 default: 318 NSLog(@"ReconItem.doIgnore : unknown ignore"); 319 break; 320 } 321 } 322 323 /* Sorting functions. These have names equal to 324 column identifiers + "SortKey", and return NSStrings that 325 can be automatically sorted with their compare method */ 326 327 - (NSString *) leftSortKey 328 { 329 return [self replicaSortKey:[self left]]; 330 } 331 332 - (NSString *) rightSortKey 333 { 334 return [self replicaSortKey:[self right]]; 335 } 336 337 - (NSString *) replicaSortKey:(NSString *)sortString 338 { 339 /* sort order for left and right replicas */ 340 341 if ([sortString isEqualToString:@"Created"]) return @"1"; 342 else if ([sortString isEqualToString:@"Deleted"]) return @"2"; 343 else if ([sortString isEqualToString:@"Modified"]) return @"3"; 344 else if ([sortString isEqualToString:@""]) return @"4"; 345 else return @"5"; 346 } 347 348 - (NSString *) directionSortKey 349 { 350 /* Since the direction indicators are unsortable images, use 351 directionSortString instead */ 352 353 if ([directionSortString isEqual:@""]) 354 [self direction]; 355 return directionSortString; 356 } 357 358 - (NSString *) progressSortKey 359 { 360 /* Percentages, "done" and "" are sorted OK without help, 361 but "start " should be sorted after "" and before "0%" */ 362 363 NSString * progressString = [self progress]; 364 if ([progressString isEqualToString:@"start "]) progressString = @" "; 365 return progressString; 366 } 367 368 - (NSString *) pathSortKey 369 { 370 /* default alphanumeric sort is fine for paths */ 371 return [self path]; 372 } 373 374 - (NSString *)progress 375 { 376 return nil; 377 } 378 379 - (BOOL)transferInProgress 380 { 381 double soFar = [self bytesTransferred]; 382 return (soFar > 0) && (soFar < [self fileSize]); 383 } 384 385 - (void)resetProgress 386 { 387 } 388 389 - (NSString *)progressString 390 { 391 NSString *progress = [self progress]; 392 if ([progress length] == 0. || [progress hasSuffix:@"%"]) 393 progress = [self transferInProgress] ? [self bytesTransferredString] : @""; 394 else if ([progress isEqual:@"done"]) progress = @""; 395 return progress; 396 } 397 398 - (NSString *)details 399 { 400 return nil; 401 } 402 403 - (NSString *)updateDetails 404 { 405 return [self details]; 406 } 407 408 - (BOOL)isConflict 409 { 410 return NO; 411 } 412 413 - (BOOL)changedFromDefault 414 { 415 return NO; 416 } 417 418 - (void)revertDirection 419 { 420 [self willChange]; 421 [direction release]; 422 direction = nil; 423 resolved = NO; 424 } 425 426 - (BOOL)canDiff 427 { 428 return NO; 429 } 430 431 - (void)showDiffs 432 { 433 } 434 435 - (ReconItem *)collapseParentsWithSingleChildren:(BOOL)isRoot 436 { 437 return self; 438 } 439 440 - (NSString *)description 441 { 442 return [NSString stringWithFormat:@"ReconItem: %@ %@", fullPath, path]; 443 } 444 445 - (NSString *)debugDescription 446 { 447 return [self description]; 448 } 449 @end 450 451 452 // --- Leaf items -- actually corresponding to ReconItems in OCaml 453 @implementation LeafReconItem 454 455 - initWithRiAndIndex:(OCamlValue *)v index:(long)i 456 { 457 [super init]; 458 ri = [v retain]; 459 index = i; 460 resolved = NO; 461 directionSortString = @""; 462 return self; 463 } 464 465 -(void)dealloc 466 { 467 [ri release]; 468 [left release]; 469 [right release]; 470 [progress release]; 471 [details release]; 472 473 [super dealloc]; 474 } 475 476 - (NSString *)path 477 { 478 if (!path) path = [(NSString *)ocamlCall("S@", "unisonRiToPath", ri) retain]; 479 return path; 480 } 481 482 - (NSString *)left 483 { 484 if (!left) left = [(NSString *)ocamlCall("S@", "unisonRiToLeft", ri) retain]; 485 return left; 486 } 487 488 - (NSString *)right 489 { 490 if (!right) right = [(NSString *)ocamlCall("S@", "unisonRiToRight", ri) retain]; 491 return right; 492 } 493 494 - (double)computeFileSize 495 { 496 return [(NSNumber *)ocamlCall("N@", "unisonRiToFileSize", ri) doubleValue]; 497 } 498 499 - (double)bytesTransferred 500 { 501 if (bytesTransferred == -1.) { 502 // need to force to fileSize if done, otherwise may not match up to 100% 503 bytesTransferred = ([[self progress] isEqual:@"done"]) ? [self fileSize] 504 : [(NSNumber*)ocamlCall("N@", "unisonRiToBytesTransferred", ri) doubleValue]; 505 } 506 return bytesTransferred; 507 } 508 509 - (NSImage *)fileIcon 510 { 511 NSString *extension = [[self path] pathExtension]; 512 513 if ([@"" isEqual:extension]) { 514 NSString *type = (NSString *)ocamlCall("S@", "unisonRiToFileType", ri); 515 extension = [type isEqual:@"dir"] 516 ? NSFileTypeForHFSTypeCode(kGenericFolderIcon) 517 : NSFileTypeForHFSTypeCode(kGenericDocumentIcon); 518 } 519 return [self iconForExtension:extension]; 520 } 521 522 - (NSString *)dirString 523 { 524 return (NSString *)ocamlCall("S@", "unisonRiToDirection", ri); 525 } 526 527 - (void)setDirection:(char *)d 528 { 529 [self willChange]; 530 [super setDirection:d]; 531 ocamlCall("x@", d, ri); 532 } 533 534 - (NSString *)progress 535 { 536 if (!progress) { 537 progress = [(NSString *)ocamlCall("S@", "unisonRiToProgress", ri) retain]; 538 if ([progress isEqual:@"FAILED"]) [self updateDetails]; 539 } 540 return progress; 541 } 542 543 - (void)resetProgress 544 { 545 // Get rid of the memoized progress because we expect it to change 546 [self willChange]; 547 bytesTransferred = -1.; 548 [progress release]; 549 550 // Force update now so we get the result while the OCaml thread is available 551 // [self progress]; 552 // [self bytesTransferred]; 553 progress = nil; 554 } 555 556 - (NSString *)details 557 { 558 if (details) return details; 559 return [self updateDetails]; 560 } 561 562 - (NSString *)updateDetails 563 { 564 [details autorelease]; 565 details = [(NSString *)ocamlCall("S@", "unisonRiToDetails", ri) retain]; 566 return details; 567 } 568 569 - (BOOL)isConflict 570 { 571 return ((long)ocamlCall("i@", "unisonRiIsConflict", ri) ? YES : NO); 572 } 573 574 - (BOOL)changedFromDefault 575 { 576 return ((long)ocamlCall("i@", "changedFromDefault", ri) ? YES : NO); 577 } 578 579 - (void)revertDirection 580 { 581 ocamlCall("x@", "unisonRiRevert", ri); 582 [super revertDirection]; 583 } 584 585 - (BOOL)canDiff 586 { 587 return ((long)ocamlCall("i@", "canDiff", ri) ? YES : NO); 588 } 589 590 - (void)showDiffs 591 { 592 ocamlCall("x@i", "runShowDiffs", ri, index); 593 } 594 595 @end 596 597 @interface NSImage (TintedImage) 598 - (NSImage *)tintedImageWithColor:(NSColor *) tint operation:(NSCompositingOperation) op; 599 @end 600 601 @implementation NSImage (TintedImage) 602 603 - (NSImage *)tintedImageWithColor:(NSColor *) tint operation:(NSCompositingOperation) op 604 { 605 NSSize size = [self size]; 606 NSRect imageBounds = NSMakeRect(0, 0, size.width, size.height); 607 NSImage *newImage = [[NSImage alloc] initWithSize:size]; 608 609 [newImage lockFocus]; 610 [self compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver]; 611 [tint set]; 612 NSRectFillUsingOperation(imageBounds, op); 613 [newImage unlockFocus]; 614 615 return [newImage autorelease]; 616 } 617 618 @end 619 620 // ---- Parent nodes in grouped items 621 @implementation ParentReconItem 622 623 - init 624 { 625 [super init]; 626 _children = [[NSMutableArray alloc] init]; 627 return self; 628 } 629 630 - initWithPath:(NSString *)aPath 631 { 632 [self init]; 633 path = [aPath retain]; 634 return self; 635 } 636 637 - (void)dealloc 638 { 639 [_children release]; 640 [super dealloc]; 641 } 642 643 - (NSArray *)children; 644 { 645 return _children; 646 } 647 648 - (ReconItem *)findChildWithPath:(NSArray *)pathArray level:(NSUInteger)level{ 649 ReconItem *item; 650 NSString *element = [pathArray count] ? [pathArray objectAtIndex:level] : @""; 651 652 for (item in _children) { 653 if ([item isKindOfClass:[ParentReconItem class]] && [[item path] isEqual:element]) { 654 return item; 655 } 656 } 657 return nil; 658 } 659 660 - (void)addChild:(ReconItem *)item pathArray:(NSArray *)pathArray level:(NSUInteger)level 661 { 662 // NSLog(@"Adding child: %@", pathArray); 663 NSString *element = [pathArray count] ? [pathArray objectAtIndex:level] : @""; 664 665 // if we're at the leaf of the path, then add the item 666 if (((0 == [pathArray count]) && (0 == level)) || (level == [pathArray count]-1)) { 667 [item setParent:self]; 668 [item setPath:element]; 669 [_children addObject:item]; 670 return; 671 } 672 673 // find / add matching parent node 674 ReconItem *parentItem = [self findChildWithPath:pathArray level:level]; 675 if (parentItem == nil || ![parentItem isKindOfClass:[ParentReconItem class]]) { 676 parentItem = [[ParentReconItem alloc] initWithPath:element]; 677 [parentItem setParent:self]; 678 [_children addObject:parentItem]; 679 [parentItem release]; 680 } 681 682 [(ParentReconItem *)parentItem addChild:item pathArray:pathArray level:level+1]; 683 } 684 685 - (void)addChild:(ReconItem *)item nested:(BOOL)nested 686 { 687 [item setPath:nil]; // invalidate/reset 688 689 if (nested) { 690 [self addChild:item pathArray:[[item path] pathComponents] level:0]; 691 } else { 692 [item setParent:self]; 693 [_children addObject:item]; 694 } 695 } 696 697 - (void)sortUsingDescriptors:(NSArray *)sortDescriptors 698 { 699 // sort our children 700 [_children sortUsingDescriptors:sortDescriptors]; 701 702 // then have them sort theirs 703 NSUInteger i = [_children count]; 704 while (i--) { 705 id child = [_children objectAtIndex:i]; 706 if ([child isKindOfClass:[ParentReconItem class]]) [child sortUsingDescriptors:sortDescriptors]; 707 } 708 } 709 710 - (ReconItem *)collapseParentsWithSingleChildren:(BOOL)isRoot 711 { 712 // replace ourselves? 713 if (!isRoot && [_children count] == 1) { 714 ReconItem *child = [_children lastObject]; 715 [child setPath:[path stringByAppendingFormat:@"/%@", [child path]]]; 716 return [child collapseParentsWithSingleChildren:NO]; 717 } 718 719 // recurse 720 NSUInteger i = [_children count]; 721 while (i--) { 722 ReconItem *child = [_children objectAtIndex:i]; 723 ReconItem *replacement = [child collapseParentsWithSingleChildren:NO]; 724 if (child != replacement) { 725 [_children replaceObjectAtIndex:i withObject:replacement]; 726 [replacement setParent:self]; 727 } 728 } 729 return self; 730 } 731 732 - (void)willChange 733 { 734 // invalidate child-based state 735 // Assuming caches / constant, so not retained / released 736 // [direction autorelease]; 737 // [directionSortString autorelease]; 738 direction = nil; 739 directionSortString = nil; 740 bytesTransferred = -1.; 741 // fileSize = -1; 742 // resolved = NO; 743 744 // propagate up parent chain 745 [parent willChange]; 746 } 747 748 // Propagation methods 749 - (void)doAction:(unichar)action 750 { 751 NSUInteger i = [_children count]; 752 while (i--) { 753 ReconItem *child = [_children objectAtIndex:i]; 754 [child doAction:action]; 755 } 756 } 757 758 - (void)doIgnore:(unichar)action 759 { 760 // handle Path ignores at this level, name and extension at the child nodes 761 if (action == 'I') { 762 [super doIgnore:'I']; 763 } else { 764 NSUInteger i = [_children count]; 765 while (i--) { 766 ReconItem *child = [_children objectAtIndex:i]; 767 [child doIgnore:action]; 768 } 769 } 770 } 771 772 // Rollup methods 773 - (long)fileCount 774 { 775 if (fileCount == 0) { 776 NSUInteger i = [_children count]; 777 while (i--) { 778 ReconItem *child = [_children objectAtIndex:i]; 779 fileCount += [child fileCount]; 780 } 781 } 782 return fileCount; 783 } 784 785 - (double)computeFileSize 786 { 787 double size = 0; 788 NSUInteger i = [_children count]; 789 while (i--) { 790 ReconItem *child = [_children objectAtIndex:i]; 791 size += [child fileSize]; 792 } 793 return size; 794 } 795 796 - (double)bytesTransferred 797 { 798 if (bytesTransferred == -1.) { 799 bytesTransferred = 0.; 800 NSUInteger i = [_children count]; 801 while (i--) { 802 ReconItem *child = [_children objectAtIndex:i]; 803 bytesTransferred += [child bytesTransferred]; 804 } 805 } 806 return bytesTransferred; 807 } 808 809 810 811 - (NSString *)dirString 812 { 813 NSString *rollup = nil; 814 NSUInteger i = [_children count]; 815 while (i--) { 816 ReconItem *child = [_children objectAtIndex:i]; 817 NSString *dirString = [child dirString]; 818 if (!rollup || [dirString isEqual:rollup]) { 819 rollup = dirString; 820 } else { 821 // conflict 822 if ([dirString isEqual:@"---->"] || [dirString isEqual:@"<----"] || [dirString isEqual:@"<--->"]) { 823 if ([rollup isEqual:@"---->"] || [rollup isEqual:@"<----"] || [rollup isEqual:@"<--->"]) { 824 rollup = @"<--->"; 825 } 826 } else { 827 rollup = @"<-?->"; 828 } 829 } 830 } 831 // NSLog(@"dirString for %@: %@", path, rollup); 832 return rollup; 833 } 834 835 - (BOOL)hasConflictedChildren 836 { 837 NSString *dirString = [self dirString]; 838 BOOL result = [dirString isEqual:@"<--->"] || [dirString isEqual:@"<-?->"]; 839 // NSLog(@"hasConflictedChildren (%@): %@: %i", [self path], dirString, result); 840 return result; 841 } 842 843 static NSMutableDictionary *_parentImages = nil; 844 static NSColor *_veryLightGreyColor = nil; 845 - (NSImage *)direction 846 { 847 if (!_parentImages) { 848 _parentImages = [[NSMutableDictionary alloc] init]; 849 _veryLightGreyColor = [[NSColor colorWithCalibratedRed:0.7 green:0.7 blue:0.7 alpha:1.0] retain]; 850 } 851 NSImage *baseImage = [super direction]; 852 NSImage *parentImage = [_parentImages objectForKey:baseImage]; 853 if (!parentImage) { 854 // make parent images a grey version of the leaf images 855 parentImage = [baseImage tintedImageWithColor:_veryLightGreyColor operation:NSCompositeSourceIn]; 856 [_parentImages setObject:parentImage forKey:baseImage]; 857 } 858 return parentImage; 859 } 860 861 @end