1 /* 2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Copyright 2011-2016, 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 } 122 123 status_t Init() 124 { 125 status_t error = fEntries.Init(); 126 if (error != B_OK) 127 return error; 128 129 return B_OK; 130 } 131 132 LocatableFile* GetFile(const BString& directoryPath, 133 const BString& relativePath) 134 { 135 if (directoryPath.Length() == 0 || relativePath[0] == '/') 136 return GetFile(relativePath); 137 return GetFile(BString(directoryPath) << '/' << relativePath); 138 } 139 140 LocatableFile* GetFile(const BString& path) 141 { 142 BString directoryPath; 143 BString name; 144 _SplitPath(path, directoryPath, name); 145 LocatableFile* file = _GetFile(directoryPath, name); 146 if (file == NULL) 147 return NULL; 148 149 // try to auto-locate the file 150 if (LocatableDirectory* directory = file->Parent()) { 151 if (directory->State() == LOCATABLE_ENTRY_UNLOCATED) { 152 // parent not yet located -- try locate with the entry's path 153 BString path; 154 file->GetPath(path); 155 _LocateEntry(file, path, true, true); 156 } else { 157 // parent already located -- locate the entry in the parent 158 BString locatedDirectoryPath; 159 if (directory->GetLocatedPath(locatedDirectoryPath)) 160 _LocateEntryInParentDir(file, locatedDirectoryPath, true); 161 } 162 } 163 164 return file; 165 } 166 167 void EntryLocated(const BString& path, const BString& locatedPath) 168 { 169 BString directory; 170 BString name; 171 _SplitPath(path, directory, name); 172 173 LocatableEntry* entry = _LookupEntry(EntryPath(directory, name)); 174 if (entry == NULL) 175 return; 176 177 _LocateEntry(entry, locatedPath, false, true); 178 } 179 180 private: 181 virtual bool Lock() 182 { 183 return fManager->Lock(); 184 } 185 186 virtual void Unlock() 187 { 188 fManager->Unlock(); 189 } 190 191 virtual void LocatableEntryUnused(LocatableEntry* entry) 192 { 193 AutoLocker<FileManager> lock(fManager); 194 if (fEntries.Lookup(EntryPath(entry)) == entry) 195 fEntries.Remove(entry); 196 else { 197 DeadEntryList::Iterator iterator = fDeadEntries.GetIterator(); 198 while (iterator.HasNext()) { 199 if (iterator.Next() == entry) { 200 fDeadEntries.Remove(entry); 201 break; 202 } 203 } 204 } 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 // if already known return the file 295 LocatableEntry* entry = _LookupEntry(EntryPath(directoryPath, name)); 296 if (entry != NULL) { 297 LocatableFile* file = dynamic_cast<LocatableFile*>(entry); 298 if (file == NULL) 299 return NULL; 300 301 if (file->AcquireReference() == 0) { 302 fEntries.Remove(file); 303 fDeadEntries.Insert(file); 304 } else 305 return file; 306 } 307 308 // no such file yet -- create it 309 BString normalizedDirPath; 310 _NormalizePath(directoryPath, normalizedDirPath); 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 return file; 326 } 327 328 LocatableDirectory* _GetDirectory(const BString& path) 329 { 330 BString directoryPath; 331 BString fileName; 332 _SplitNormalizedPath(path, directoryPath, fileName); 333 334 // if already know return the directory 335 LocatableEntry* entry 336 = _LookupEntry(EntryPath(directoryPath, fileName)); 337 if (entry != NULL) { 338 LocatableDirectory* directory 339 = dynamic_cast<LocatableDirectory*>(entry); 340 if (directory == NULL) 341 return NULL; 342 directory->AcquireReference(); 343 return directory; 344 } 345 346 // get the parent directory 347 LocatableDirectory* parentDirectory = NULL; 348 if (directoryPath.Length() > 0) { 349 parentDirectory = _GetDirectory(directoryPath); 350 if (parentDirectory == NULL) 351 return NULL; 352 } 353 354 // create a new directory 355 LocatableDirectory* directory = new(std::nothrow) LocatableDirectory( 356 this, parentDirectory, path); 357 if (directory == NULL) { 358 parentDirectory->ReleaseReference(); 359 return NULL; 360 } 361 362 // auto-locate, if possible 363 if (fIsLocal) { 364 BString dirPath; 365 directory->GetPath(dirPath); 366 directory->SetLocatedPath(dirPath, false); 367 } else if (parentDirectory != NULL 368 && parentDirectory->State() != LOCATABLE_ENTRY_UNLOCATED) { 369 BString locatedDirectoryPath; 370 if (parentDirectory->GetLocatedPath(locatedDirectoryPath)) 371 _LocateEntryInParentDir(directory, locatedDirectoryPath, true); 372 } 373 374 if (parentDirectory != NULL) 375 parentDirectory->AddEntry(directory); 376 377 fEntries.Insert(directory); 378 return directory; 379 } 380 381 LocatableEntry* _LookupEntry(const EntryPath& entryPath) 382 { 383 LocatableEntry* entry = fEntries.Lookup(entryPath); 384 if (entry == NULL) 385 return NULL; 386 387 // if already unreferenced, remove it 388 if (entry->CountReferences() == 0) { 389 fEntries.Remove(entry); 390 return NULL; 391 } 392 393 return entry; 394 } 395 396 void _NormalizePath(const BString& path, BString& _normalizedPath) 397 { 398 BString normalizedPath; 399 char* buffer = normalizedPath.LockBuffer(path.Length()); 400 int32 outIndex = 0; 401 const char* remaining = path.String(); 402 403 while (*remaining != '\0') { 404 // collapse repeated slashes 405 if (*remaining == '/') { 406 buffer[outIndex++] = '/'; 407 remaining++; 408 while (*remaining == '/') 409 remaining++; 410 } 411 412 if (*remaining == '\0') { 413 // remove trailing slash (unless it's "/" only) 414 if (outIndex > 1) 415 outIndex--; 416 break; 417 } 418 419 // skip "." components 420 if (*remaining == '.') { 421 if (remaining[1] == '\0') 422 break; 423 424 if (remaining[1] == '/') { 425 remaining += 2; 426 while (*remaining == '/') 427 remaining++; 428 continue; 429 } 430 } 431 432 // copy path component 433 while (*remaining != '\0' && *remaining != '/') 434 buffer[outIndex++] = *(remaining++); 435 } 436 437 // If the path didn't change, use the original path (BString's copy on 438 // write mechanism) rather than the new string. 439 if (outIndex == path.Length()) { 440 _normalizedPath = path; 441 } else { 442 normalizedPath.UnlockBuffer(outIndex); 443 _normalizedPath = normalizedPath; 444 } 445 } 446 447 void _SplitPath(const BString& path, BString& _directory, BString& _name) 448 { 449 BString normalized; 450 _NormalizePath(path, normalized); 451 _SplitNormalizedPath(normalized, _directory, _name); 452 } 453 454 void _SplitNormalizedPath(const BString& path, BString& _directory, 455 BString& _name) 456 { 457 // handle single component (including root dir) cases 458 int32 lastSlash = path.FindLast('/'); 459 if (lastSlash < 0 || path.Length() == 1) { 460 _directory = (const char*)NULL; 461 _name = path; 462 return; 463 } 464 465 // handle root dir + one component and multi component cases 466 if (lastSlash == 0) 467 _directory = "/"; 468 else 469 _directory.SetTo(path, lastSlash); 470 _name = path.String() + (lastSlash + 1); 471 } 472 473 private: 474 FileManager* fManager; 475 LocatableEntryTable fEntries; 476 DeadEntryList fDeadEntries; 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 StringUtils::HashValue(key); 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 delete fSourceFiles; 559 } 560 561 562 status_t 563 FileManager::Init(bool targetIsLocal) 564 { 565 status_t error = fLock.InitCheck(); 566 if (error != B_OK) 567 return error; 568 569 // create target domain 570 fTargetDomain = new(std::nothrow) Domain(this, targetIsLocal); 571 if (fTargetDomain == NULL) 572 return B_NO_MEMORY; 573 574 error = fTargetDomain->Init(); 575 if (error != B_OK) 576 return error; 577 578 // create source domain 579 fSourceDomain = new(std::nothrow) Domain(this, false); 580 if (fSourceDomain == NULL) 581 return B_NO_MEMORY; 582 583 error = fSourceDomain->Init(); 584 if (error != B_OK) 585 return error; 586 587 // create source file table 588 fSourceFiles = new(std::nothrow) SourceFileTable; 589 if (fSourceFiles == NULL) 590 return B_NO_MEMORY; 591 592 error = fSourceFiles->Init(); 593 if (error != B_OK) 594 return error; 595 596 return B_OK; 597 } 598 599 600 LocatableFile* 601 FileManager::GetTargetFile(const BString& directory, 602 const BString& relativePath) 603 { 604 AutoLocker<FileManager> locker(this); 605 return fTargetDomain->GetFile(directory, relativePath); 606 } 607 608 609 LocatableFile* 610 FileManager::GetTargetFile(const BString& path) 611 { 612 AutoLocker<FileManager> locker(this); 613 return fTargetDomain->GetFile(path); 614 } 615 616 617 void 618 FileManager::TargetEntryLocated(const BString& path, 619 const BString& locatedPath) 620 { 621 AutoLocker<FileManager> locker(this); 622 fTargetDomain->EntryLocated(path, locatedPath); 623 } 624 625 626 LocatableFile* 627 FileManager::GetSourceFile(const BString& directory, 628 const BString& relativePath) 629 { 630 AutoLocker<FileManager> locker(this); 631 LocatableFile* file = fSourceDomain->GetFile(directory, relativePath); 632 633 return file; 634 } 635 636 637 LocatableFile* 638 FileManager::GetSourceFile(const BString& path) 639 { 640 AutoLocker<FileManager> locker(this); 641 LocatableFile* file = fSourceDomain->GetFile(path); 642 643 return file; 644 } 645 646 647 status_t 648 FileManager::SourceEntryLocated(const BString& path, 649 const BString& locatedPath) 650 { 651 AutoLocker<FileManager> locker(this); 652 653 // check if we already have this path mapped. If so, 654 // first clear the mapping, as the user may be attempting 655 // to correct an existing entry. 656 SourceFileEntry* entry = _LookupSourceFile(path); 657 if (entry != NULL) 658 _SourceFileUnused(entry); 659 660 fSourceDomain->EntryLocated(path, locatedPath); 661 662 try { 663 fSourceLocationMappings[path] = locatedPath; 664 } catch (...) { 665 return B_NO_MEMORY; 666 } 667 668 return B_OK; 669 } 670 671 672 status_t 673 FileManager::LoadSourceFile(LocatableFile* file, SourceFile*& _sourceFile) 674 { 675 AutoLocker<FileManager> locker(this); 676 677 // get the path 678 BString path; 679 BString originalPath; 680 file->GetPath(originalPath); 681 if (!file->GetLocatedPath(path)) { 682 // see if this is a file we have a lazy mapping for. 683 if (!_LocateFileIfMapped(originalPath, file) 684 || !file->GetLocatedPath(path)) { 685 return B_ENTRY_NOT_FOUND; 686 } 687 } 688 689 // we might already know the source file 690 SourceFileEntry* entry = _LookupSourceFile(originalPath); 691 if (entry != NULL) { 692 entry->file->AcquireReference(); 693 _sourceFile = entry->file; 694 return B_OK; 695 } 696 697 // create the hash table entry 698 entry = new(std::nothrow) SourceFileEntry(this, originalPath); 699 if (entry == NULL) 700 return B_NO_MEMORY; 701 702 // load the file 703 SourceFile* sourceFile = new(std::nothrow) SourceFile(entry); 704 if (sourceFile == NULL) { 705 delete entry; 706 return B_NO_MEMORY; 707 } 708 ObjectDeleter<SourceFile> sourceFileDeleter(sourceFile); 709 710 entry->file = sourceFile; 711 712 status_t error = sourceFile->Init(path); 713 if (error != B_OK) 714 return error; 715 716 fSourceFiles->Insert(entry); 717 718 _sourceFile = sourceFileDeleter.Detach(); 719 return B_OK; 720 } 721 722 723 status_t 724 FileManager::LoadLocationMappings(TeamFileManagerSettings* settings) 725 { 726 AutoLocker<FileManager> locker(this); 727 for (int32 i = 0; i < settings->CountSourceMappings(); i++) { 728 BString sourcePath; 729 BString locatedPath; 730 731 if (settings->GetSourceMappingAt(i, sourcePath, locatedPath) != B_OK) 732 return B_NO_MEMORY; 733 734 try { 735 fSourceLocationMappings[sourcePath] = locatedPath; 736 } catch (...) { 737 return B_NO_MEMORY; 738 } 739 } 740 741 return B_OK; 742 } 743 744 745 status_t 746 FileManager::SaveLocationMappings(TeamFileManagerSettings* settings) 747 { 748 AutoLocker<FileManager> locker(this); 749 750 for (LocatedFileMap::const_iterator it = fSourceLocationMappings.begin(); 751 it != fSourceLocationMappings.end(); ++it) { 752 status_t error = settings->AddSourceMapping(it->first, it->second); 753 if (error != B_OK) 754 return error; 755 } 756 757 return B_OK; 758 } 759 760 761 FileManager::SourceFileEntry* 762 FileManager::_LookupSourceFile(const BString& path) 763 { 764 SourceFileEntry* entry = fSourceFiles->Lookup(path); 765 if (entry == NULL) 766 return NULL; 767 768 // the entry might be unused already -- in that case remove it 769 if (entry->file->CountReferences() == 0) { 770 fSourceFiles->Remove(entry); 771 return NULL; 772 } 773 774 return entry; 775 } 776 777 778 void 779 FileManager::_SourceFileUnused(SourceFileEntry* entry) 780 { 781 AutoLocker<FileManager> locker(this); 782 783 SourceFileEntry* otherEntry = fSourceFiles->Lookup(entry->path); 784 if (otherEntry == entry) 785 fSourceFiles->Remove(entry); 786 } 787 788 789 bool 790 FileManager::_LocateFileIfMapped(const BString& sourcePath, 791 LocatableFile* file) 792 { 793 // called with lock held 794 795 LocatedFileMap::const_iterator it = fSourceLocationMappings.find( 796 sourcePath); 797 if (it != fSourceLocationMappings.end() 798 && file->State() != LOCATABLE_ENTRY_LOCATED_EXPLICITLY 799 && file->State() != LOCATABLE_ENTRY_LOCATED_IMPLICITLY) { 800 fSourceDomain->EntryLocated(it->first, it->second); 801 return true; 802 } 803 804 return false; 805 } 806