1 /* 2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Copyright 2011-2017, Rene Gollent, rene@gollent.com. 4 * Distributed under the terms of the MIT License. 5 */ 6 7 #include "FileManager.h" 8 9 #include <new> 10 11 #include <AutoDeleter.h> 12 #include <AutoLocker.h> 13 14 #include "LocatableDirectory.h" 15 #include "LocatableFile.h" 16 #include "SourceFile.h" 17 #include "StringUtils.h" 18 #include "TeamFileManagerSettings.h" 19 20 21 // #pragma mark - EntryPath 22 23 24 struct FileManager::EntryPath { 25 const char* directory; 26 const char* name; 27 28 EntryPath(const char* directory, const char* name) 29 : 30 directory(directory), 31 name(name) 32 { 33 } 34 35 EntryPath(const BString& directory, const BString& name) 36 : 37 directory(directory.Length() > 0 ? directory.String() : NULL), 38 name(name.String()) 39 { 40 } 41 42 EntryPath(const LocatableEntry* entry) 43 : 44 directory(NULL), 45 name(entry->Name()) 46 { 47 LocatableDirectory* parent = entry->Parent(); 48 if (parent != NULL && strlen(parent->Path()) > 0) 49 directory = parent->Path(); 50 } 51 52 EntryPath(const EntryPath& other) 53 : 54 directory(other.directory), 55 name(other.name) 56 { 57 } 58 59 size_t HashValue() const 60 { 61 return StringUtils::HashValue(directory) 62 ^ StringUtils::HashValue(name); 63 } 64 65 bool operator==(const EntryPath& other) const 66 { 67 if (directory != other.directory 68 && (directory == NULL || other.directory == NULL 69 || strcmp(directory, other.directory) != 0)) { 70 return false; 71 } 72 73 return strcmp(name, other.name) == 0; 74 } 75 }; 76 77 78 // #pragma mark - EntryHashDefinition 79 80 81 struct FileManager::EntryHashDefinition { 82 typedef EntryPath KeyType; 83 typedef LocatableEntry ValueType; 84 85 size_t HashKey(const EntryPath& key) const 86 { 87 return key.HashValue(); 88 } 89 90 size_t Hash(const LocatableEntry* value) const 91 { 92 return HashKey(EntryPath(value)); 93 } 94 95 bool Compare(const EntryPath& key, const LocatableEntry* value) const 96 { 97 return EntryPath(value) == key; 98 } 99 100 LocatableEntry*& GetLink(LocatableEntry* value) const 101 { 102 return value->fNext; 103 } 104 }; 105 106 107 // #pragma mark - Domain 108 109 110 class FileManager::Domain : private LocatableEntryOwner { 111 public: 112 Domain(FileManager* manager, bool isLocal) 113 : 114 fManager(manager), 115 fIsLocal(isLocal) 116 { 117 } 118 119 ~Domain() 120 { 121 LocatableEntry* entry = fEntries.Clear(true); 122 while (entry != NULL) { 123 LocatableEntry* next = entry->fNext; 124 entry->ReleaseReference(); 125 entry = next; 126 } 127 } 128 129 status_t Init() 130 { 131 status_t error = fEntries.Init(); 132 if (error != B_OK) 133 return error; 134 135 return B_OK; 136 } 137 138 LocatableFile* GetFile(const BString& directoryPath, 139 const BString& relativePath) 140 { 141 if (directoryPath.Length() == 0 || relativePath[0] == '/') 142 return GetFile(relativePath); 143 return GetFile(BString(directoryPath) << '/' << relativePath); 144 } 145 146 LocatableFile* GetFile(const BString& path) 147 { 148 BString directoryPath; 149 BString name; 150 _SplitPath(path, directoryPath, name); 151 LocatableFile* file = _GetFile(directoryPath, name); 152 if (file == NULL) 153 return NULL; 154 155 // try to auto-locate the file 156 if (LocatableDirectory* directory = file->Parent()) { 157 if (directory->State() == LOCATABLE_ENTRY_UNLOCATED) { 158 // parent not yet located -- try locate with the entry's path 159 BString path; 160 file->GetPath(path); 161 _LocateEntry(file, path, true, true); 162 } else { 163 // parent already located -- locate the entry in the parent 164 BString locatedDirectoryPath; 165 if (directory->GetLocatedPath(locatedDirectoryPath)) 166 _LocateEntryInParentDir(file, locatedDirectoryPath, true); 167 } 168 } 169 170 return file; 171 } 172 173 void EntryLocated(const BString& path, const BString& locatedPath) 174 { 175 BString directory; 176 BString name; 177 _SplitPath(path, directory, name); 178 179 LocatableEntry* entry = _LookupEntry(EntryPath(directory, name)); 180 if (entry == NULL) 181 return; 182 183 _LocateEntry(entry, locatedPath, false, true); 184 } 185 186 private: 187 virtual bool Lock() 188 { 189 return fManager->Lock(); 190 } 191 192 virtual void Unlock() 193 { 194 fManager->Unlock(); 195 } 196 197 virtual void LocatableEntryUnused(LocatableEntry* entry) 198 { 199 AutoLocker<FileManager> lock(fManager); 200 if (fEntries.Lookup(EntryPath(entry)) == entry) 201 fEntries.Remove(entry); 202 203 LocatableDirectory* parent = entry->Parent(); 204 if (parent != NULL) 205 parent->RemoveEntry(entry); 206 } 207 208 bool _LocateDirectory(LocatableDirectory* directory, 209 const BString& locatedPath, bool implicit) 210 { 211 if (directory == NULL 212 || directory->State() != LOCATABLE_ENTRY_UNLOCATED) { 213 return false; 214 } 215 216 if (!_LocateEntry(directory, locatedPath, implicit, true)) 217 return false; 218 219 _LocateEntries(directory, locatedPath, implicit); 220 221 return true; 222 } 223 224 bool _LocateEntry(LocatableEntry* entry, const BString& locatedPath, 225 bool implicit, bool locateAncestors) 226 { 227 if (implicit && entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY) 228 return false; 229 230 struct stat st; 231 if (stat(locatedPath, &st) != 0) 232 return false; 233 234 if (S_ISDIR(st.st_mode)) { 235 LocatableDirectory* directory 236 = dynamic_cast<LocatableDirectory*>(entry); 237 if (directory == NULL) 238 return false; 239 entry->SetLocatedPath(locatedPath, implicit); 240 } else if (S_ISREG(st.st_mode)) { 241 LocatableFile* file = dynamic_cast<LocatableFile*>(entry); 242 if (file == NULL) 243 return false; 244 entry->SetLocatedPath(locatedPath, implicit); 245 } 246 247 // locate the ancestor directories, if requested 248 if (locateAncestors) { 249 BString locatedDirectory; 250 BString locatedName; 251 _SplitPath(locatedPath, locatedDirectory, locatedName); 252 if (locatedName == entry->Name()) 253 _LocateDirectory(entry->Parent(), locatedDirectory, implicit); 254 } 255 256 return true; 257 } 258 259 bool _LocateEntryInParentDir(LocatableEntry* entry, 260 const BString& locatedDirectoryPath, bool implicit) 261 { 262 // construct the located entry path 263 BString locatedEntryPath(locatedDirectoryPath); 264 int32 pathLength = locatedEntryPath.Length(); 265 if (pathLength >= 1 && locatedEntryPath[pathLength - 1] != '/') 266 locatedEntryPath << '/'; 267 locatedEntryPath << entry->Name(); 268 269 return _LocateEntry(entry, locatedEntryPath, implicit, false); 270 } 271 272 void _LocateEntries(LocatableDirectory* directory, 273 const BString& locatedPath, bool implicit) 274 { 275 for (LocatableEntryList::ConstIterator it 276 = directory->Entries().GetIterator(); 277 LocatableEntry* entry = it.Next();) { 278 if (entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY) 279 continue; 280 281 if (_LocateEntryInParentDir(entry, locatedPath, implicit)) { 282 // recurse for directories 283 if (LocatableDirectory* subDir 284 = dynamic_cast<LocatableDirectory*>(entry)) { 285 BString locatedEntryPath; 286 if (subDir->GetLocatedPath(locatedEntryPath)) 287 _LocateEntries(subDir, locatedEntryPath, implicit); 288 } 289 } 290 } 291 } 292 293 LocatableFile* _GetFile(const BString& directoryPath, const BString& name) 294 { 295 BString normalizedDirPath; 296 _NormalizePath(directoryPath, normalizedDirPath); 297 298 // if already known return the file 299 LocatableEntry* entry = _LookupEntry(EntryPath(normalizedDirPath, name)); 300 if (entry != NULL) { 301 LocatableFile* file = dynamic_cast<LocatableFile*>(entry); 302 if (file == NULL) 303 return NULL; 304 305 if (file->AcquireReference() == 0) 306 fEntries.Remove(file); 307 else 308 return file; 309 } 310 311 // no such file yet -- create it 312 LocatableDirectory* directory = _GetDirectory(normalizedDirPath); 313 if (directory == NULL) 314 return NULL; 315 316 LocatableFile* file = new(std::nothrow) LocatableFile(this, directory, 317 name); 318 if (file == NULL) { 319 directory->ReleaseReference(); 320 return NULL; 321 } 322 323 directory->AddEntry(file); 324 325 fEntries.Insert(file); 326 327 return file; 328 } 329 330 LocatableDirectory* _GetDirectory(const BString& path) 331 { 332 BString directoryPath; 333 BString fileName; 334 _SplitNormalizedPath(path, directoryPath, fileName); 335 336 // if already know return the directory 337 LocatableEntry* entry 338 = _LookupEntry(EntryPath(directoryPath, fileName)); 339 if (entry != NULL) { 340 LocatableDirectory* directory 341 = dynamic_cast<LocatableDirectory*>(entry); 342 if (directory == NULL) 343 return NULL; 344 directory->AcquireReference(); 345 return directory; 346 } 347 348 // get the parent directory 349 LocatableDirectory* parentDirectory = NULL; 350 if (directoryPath.Length() > 0) { 351 parentDirectory = _GetDirectory(directoryPath); 352 if (parentDirectory == NULL) 353 return NULL; 354 } 355 356 // create a new directory 357 LocatableDirectory* directory = new(std::nothrow) LocatableDirectory( 358 this, parentDirectory, path); 359 if (directory == NULL) { 360 parentDirectory->ReleaseReference(); 361 return NULL; 362 } 363 364 // auto-locate, if possible 365 if (fIsLocal) { 366 BString dirPath; 367 directory->GetPath(dirPath); 368 directory->SetLocatedPath(dirPath, false); 369 } else if (parentDirectory != NULL 370 && parentDirectory->State() != LOCATABLE_ENTRY_UNLOCATED) { 371 BString locatedDirectoryPath; 372 if (parentDirectory->GetLocatedPath(locatedDirectoryPath)) 373 _LocateEntryInParentDir(directory, locatedDirectoryPath, true); 374 } 375 376 if (parentDirectory != NULL) 377 parentDirectory->AddEntry(directory); 378 379 fEntries.Insert(directory); 380 return directory; 381 } 382 383 LocatableEntry* _LookupEntry(const EntryPath& entryPath) 384 { 385 LocatableEntry* entry = fEntries.Lookup(entryPath); 386 if (entry == NULL) 387 return NULL; 388 389 // if already unreferenced, remove it 390 if (entry->CountReferences() == 0) { 391 fEntries.Remove(entry); 392 return NULL; 393 } 394 395 return entry; 396 } 397 398 void _NormalizePath(const BString& path, BString& _normalizedPath) 399 { 400 BString normalizedPath; 401 char* buffer = normalizedPath.LockBuffer(path.Length()); 402 int32 outIndex = 0; 403 const char* remaining = path.String(); 404 405 while (*remaining != '\0') { 406 // collapse repeated slashes 407 if (*remaining == '/') { 408 buffer[outIndex++] = '/'; 409 remaining++; 410 while (*remaining == '/') 411 remaining++; 412 } 413 414 if (*remaining == '\0') { 415 // remove trailing slash (unless it's "/" only) 416 if (outIndex > 1) 417 outIndex--; 418 break; 419 } 420 421 // skip "." components 422 if (*remaining == '.') { 423 if (remaining[1] == '\0') 424 break; 425 426 if (remaining[1] == '/') { 427 remaining += 2; 428 while (*remaining == '/') 429 remaining++; 430 continue; 431 } 432 } 433 434 // copy path component 435 while (*remaining != '\0' && *remaining != '/') 436 buffer[outIndex++] = *(remaining++); 437 } 438 439 // If the path didn't change, use the original path (BString's copy on 440 // write mechanism) rather than the new string. 441 if (outIndex == path.Length()) { 442 _normalizedPath = path; 443 } else { 444 normalizedPath.UnlockBuffer(outIndex); 445 _normalizedPath = normalizedPath; 446 } 447 } 448 449 void _SplitPath(const BString& path, BString& _directory, BString& _name) 450 { 451 BString normalized; 452 _NormalizePath(path, normalized); 453 _SplitNormalizedPath(normalized, _directory, _name); 454 } 455 456 void _SplitNormalizedPath(const BString& path, BString& _directory, 457 BString& _name) 458 { 459 // handle single component (including root dir) cases 460 int32 lastSlash = path.FindLast('/'); 461 if (lastSlash < 0 || path.Length() == 1) { 462 _directory = (const char*)NULL; 463 _name = path; 464 return; 465 } 466 467 // handle root dir + one component and multi component cases 468 if (lastSlash == 0) 469 _directory = "/"; 470 else 471 _directory.SetTo(path, lastSlash); 472 _name = path.String() + (lastSlash + 1); 473 } 474 475 private: 476 FileManager* fManager; 477 LocatableEntryTable fEntries; 478 bool fIsLocal; 479 }; 480 481 482 // #pragma mark - SourceFileEntry 483 484 485 struct FileManager::SourceFileEntry : public SourceFileOwner { 486 487 FileManager* manager; 488 BString path; 489 SourceFile* file; 490 SourceFileEntry* next; 491 492 SourceFileEntry(FileManager* manager, const BString& path) 493 : 494 manager(manager), 495 path(path), 496 file(NULL) 497 { 498 } 499 500 virtual void SourceFileUnused(SourceFile* sourceFile) 501 { 502 manager->_SourceFileUnused(this); 503 } 504 505 virtual void SourceFileDeleted(SourceFile* sourceFile) 506 { 507 // We have already been removed from the table, so commit suicide. 508 delete this; 509 } 510 }; 511 512 513 // #pragma mark - SourceFileHashDefinition 514 515 516 struct FileManager::SourceFileHashDefinition { 517 typedef BString KeyType; 518 typedef SourceFileEntry ValueType; 519 520 size_t HashKey(const BString& key) const 521 { 522 return StringUtils::HashValue(key); 523 } 524 525 size_t Hash(const SourceFileEntry* value) const 526 { 527 return HashKey(value->path); 528 } 529 530 bool Compare(const BString& key, const SourceFileEntry* value) const 531 { 532 return value->path == key; 533 } 534 535 SourceFileEntry*& GetLink(SourceFileEntry* value) const 536 { 537 return value->next; 538 } 539 }; 540 541 542 // #pragma mark - FileManager 543 544 545 FileManager::FileManager() 546 : 547 fLock("file manager"), 548 fTargetDomain(NULL), 549 fSourceDomain(NULL), 550 fSourceFiles(NULL) 551 { 552 } 553 554 555 FileManager::~FileManager() 556 { 557 delete fTargetDomain; 558 delete fSourceDomain; 559 560 SourceFileEntry* entry = fSourceFiles->Clear(); 561 while (entry != NULL) { 562 SourceFileEntry* next = entry->next; 563 delete entry; 564 entry = next; 565 } 566 delete fSourceFiles; 567 } 568 569 570 status_t 571 FileManager::Init(bool targetIsLocal) 572 { 573 status_t error = fLock.InitCheck(); 574 if (error != B_OK) 575 return error; 576 577 // create target domain 578 fTargetDomain = new(std::nothrow) Domain(this, targetIsLocal); 579 if (fTargetDomain == NULL) 580 return B_NO_MEMORY; 581 582 error = fTargetDomain->Init(); 583 if (error != B_OK) 584 return error; 585 586 // create source domain 587 fSourceDomain = new(std::nothrow) Domain(this, false); 588 if (fSourceDomain == NULL) 589 return B_NO_MEMORY; 590 591 error = fSourceDomain->Init(); 592 if (error != B_OK) 593 return error; 594 595 // create source file table 596 fSourceFiles = new(std::nothrow) SourceFileTable; 597 if (fSourceFiles == NULL) 598 return B_NO_MEMORY; 599 600 error = fSourceFiles->Init(); 601 if (error != B_OK) 602 return error; 603 604 return B_OK; 605 } 606 607 608 LocatableFile* 609 FileManager::GetTargetFile(const BString& directory, 610 const BString& relativePath) 611 { 612 AutoLocker<FileManager> locker(this); 613 return fTargetDomain->GetFile(directory, relativePath); 614 } 615 616 617 LocatableFile* 618 FileManager::GetTargetFile(const BString& path) 619 { 620 AutoLocker<FileManager> locker(this); 621 return fTargetDomain->GetFile(path); 622 } 623 624 625 void 626 FileManager::TargetEntryLocated(const BString& path, 627 const BString& locatedPath) 628 { 629 AutoLocker<FileManager> locker(this); 630 fTargetDomain->EntryLocated(path, locatedPath); 631 } 632 633 634 LocatableFile* 635 FileManager::GetSourceFile(const BString& directory, 636 const BString& relativePath) 637 { 638 AutoLocker<FileManager> locker(this); 639 LocatableFile* file = fSourceDomain->GetFile(directory, relativePath); 640 641 return file; 642 } 643 644 645 LocatableFile* 646 FileManager::GetSourceFile(const BString& path) 647 { 648 AutoLocker<FileManager> locker(this); 649 LocatableFile* file = fSourceDomain->GetFile(path); 650 651 return file; 652 } 653 654 655 status_t 656 FileManager::SourceEntryLocated(const BString& path, 657 const BString& locatedPath) 658 { 659 AutoLocker<FileManager> locker(this); 660 661 // check if we already have this path mapped. If so, 662 // first clear the mapping, as the user may be attempting 663 // to correct an existing entry. 664 SourceFileEntry* entry = _LookupSourceFile(path); 665 if (entry != NULL) 666 _SourceFileUnused(entry); 667 668 fSourceDomain->EntryLocated(path, locatedPath); 669 670 try { 671 fSourceLocationMappings[path] = locatedPath; 672 } catch (...) { 673 return B_NO_MEMORY; 674 } 675 676 return B_OK; 677 } 678 679 680 status_t 681 FileManager::LoadSourceFile(LocatableFile* file, SourceFile*& _sourceFile) 682 { 683 AutoLocker<FileManager> locker(this); 684 685 // get the path 686 BString path; 687 BString originalPath; 688 file->GetPath(originalPath); 689 if (!file->GetLocatedPath(path)) { 690 // see if this is a file we have a lazy mapping for. 691 if (!_LocateFileIfMapped(originalPath, file) 692 || !file->GetLocatedPath(path)) { 693 return B_ENTRY_NOT_FOUND; 694 } 695 } 696 697 // we might already know the source file 698 SourceFileEntry* entry = _LookupSourceFile(originalPath); 699 if (entry != NULL) { 700 entry->file->AcquireReference(); 701 _sourceFile = entry->file; 702 return B_OK; 703 } 704 705 // create the hash table entry 706 entry = new(std::nothrow) SourceFileEntry(this, originalPath); 707 if (entry == NULL) 708 return B_NO_MEMORY; 709 710 // load the file 711 SourceFile* sourceFile = new(std::nothrow) SourceFile(entry); 712 if (sourceFile == NULL) { 713 delete entry; 714 return B_NO_MEMORY; 715 } 716 ObjectDeleter<SourceFile> sourceFileDeleter(sourceFile); 717 718 entry->file = sourceFile; 719 720 status_t error = sourceFile->Init(path); 721 if (error != B_OK) 722 return error; 723 724 fSourceFiles->Insert(entry); 725 726 _sourceFile = sourceFileDeleter.Detach(); 727 return B_OK; 728 } 729 730 731 status_t 732 FileManager::LoadLocationMappings(TeamFileManagerSettings* settings) 733 { 734 AutoLocker<FileManager> locker(this); 735 for (int32 i = 0; i < settings->CountSourceMappings(); i++) { 736 BString sourcePath; 737 BString locatedPath; 738 739 if (settings->GetSourceMappingAt(i, sourcePath, locatedPath) != B_OK) 740 return B_NO_MEMORY; 741 742 try { 743 fSourceLocationMappings[sourcePath] = locatedPath; 744 } catch (...) { 745 return B_NO_MEMORY; 746 } 747 } 748 749 return B_OK; 750 } 751 752 753 status_t 754 FileManager::SaveLocationMappings(TeamFileManagerSettings* settings) 755 { 756 AutoLocker<FileManager> locker(this); 757 758 for (LocatedFileMap::const_iterator it = fSourceLocationMappings.begin(); 759 it != fSourceLocationMappings.end(); ++it) { 760 status_t error = settings->AddSourceMapping(it->first, it->second); 761 if (error != B_OK) 762 return error; 763 } 764 765 return B_OK; 766 } 767 768 769 FileManager::SourceFileEntry* 770 FileManager::_LookupSourceFile(const BString& path) 771 { 772 SourceFileEntry* entry = fSourceFiles->Lookup(path); 773 if (entry == NULL) 774 return NULL; 775 776 // the entry might be unused already -- in that case remove it 777 if (entry->file->CountReferences() == 0) { 778 fSourceFiles->Remove(entry); 779 return NULL; 780 } 781 782 return entry; 783 } 784 785 786 void 787 FileManager::_SourceFileUnused(SourceFileEntry* entry) 788 { 789 AutoLocker<FileManager> locker(this); 790 791 SourceFileEntry* otherEntry = fSourceFiles->Lookup(entry->path); 792 if (otherEntry == entry) 793 fSourceFiles->Remove(entry); 794 } 795 796 797 bool 798 FileManager::_LocateFileIfMapped(const BString& sourcePath, 799 LocatableFile* file) 800 { 801 // called with lock held 802 803 LocatedFileMap::const_iterator it = fSourceLocationMappings.find( 804 sourcePath); 805 if (it != fSourceLocationMappings.end() 806 && file->State() != LOCATABLE_ENTRY_LOCATED_EXPLICITLY 807 && file->State() != LOCATABLE_ENTRY_LOCATED_IMPLICITLY) { 808 fSourceDomain->EntryLocated(it->first, it->second); 809 return true; 810 } 811 812 return false; 813 } 814