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