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