/* * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2011-2017, Rene Gollent, rene@gollent.com. * Distributed under the terms of the MIT License. */ #include "FileManager.h" #include #include #include #include "LocatableDirectory.h" #include "LocatableFile.h" #include "SourceFile.h" #include "StringUtils.h" #include "TeamFileManagerSettings.h" // #pragma mark - EntryPath struct FileManager::EntryPath { const char* directory; const char* name; EntryPath(const char* directory, const char* name) : directory(directory), name(name) { } EntryPath(const BString& directory, const BString& name) : directory(directory.Length() > 0 ? directory.String() : NULL), name(name.String()) { } EntryPath(const LocatableEntry* entry) : directory(NULL), name(entry->Name()) { LocatableDirectory* parent = entry->Parent(); if (parent != NULL && strlen(parent->Path()) > 0) directory = parent->Path(); } EntryPath(const EntryPath& other) : directory(other.directory), name(other.name) { } size_t HashValue() const { return StringUtils::HashValue(directory) ^ StringUtils::HashValue(name); } bool operator==(const EntryPath& other) const { if (directory != other.directory && (directory == NULL || other.directory == NULL || strcmp(directory, other.directory) != 0)) { return false; } return strcmp(name, other.name) == 0; } }; // #pragma mark - EntryHashDefinition struct FileManager::EntryHashDefinition { typedef EntryPath KeyType; typedef LocatableEntry ValueType; size_t HashKey(const EntryPath& key) const { return key.HashValue(); } size_t Hash(const LocatableEntry* value) const { return HashKey(EntryPath(value)); } bool Compare(const EntryPath& key, const LocatableEntry* value) const { return EntryPath(value) == key; } LocatableEntry*& GetLink(LocatableEntry* value) const { return value->fNext; } }; // #pragma mark - Domain class FileManager::Domain : private LocatableEntryOwner { public: Domain(FileManager* manager, bool isLocal) : fManager(manager), fIsLocal(isLocal) { } ~Domain() { LocatableEntry* entry = fEntries.Clear(true); while (entry != NULL) { LocatableEntry* next = entry->fNext; entry->ReleaseReference(); entry = next; } } status_t Init() { status_t error = fEntries.Init(); if (error != B_OK) return error; return B_OK; } LocatableFile* GetFile(const BString& directoryPath, const BString& relativePath) { if (directoryPath.Length() == 0 || relativePath[0] == '/') return GetFile(relativePath); return GetFile(BString(directoryPath) << '/' << relativePath); } LocatableFile* GetFile(const BString& path) { BString directoryPath; BString name; _SplitPath(path, directoryPath, name); LocatableFile* file = _GetFile(directoryPath, name); if (file == NULL) return NULL; // try to auto-locate the file if (LocatableDirectory* directory = file->Parent()) { if (directory->State() == LOCATABLE_ENTRY_UNLOCATED) { // parent not yet located -- try locate with the entry's path BString path; file->GetPath(path); _LocateEntry(file, path, true, true); } else { // parent already located -- locate the entry in the parent BString locatedDirectoryPath; if (directory->GetLocatedPath(locatedDirectoryPath)) _LocateEntryInParentDir(file, locatedDirectoryPath, true); } } return file; } void EntryLocated(const BString& path, const BString& locatedPath) { BString directory; BString name; _SplitPath(path, directory, name); LocatableEntry* entry = _LookupEntry(EntryPath(directory, name)); if (entry == NULL) return; _LocateEntry(entry, locatedPath, false, true); } private: virtual bool Lock() { return fManager->Lock(); } virtual void Unlock() { fManager->Unlock(); } virtual void LocatableEntryUnused(LocatableEntry* entry) { AutoLocker lock(fManager); if (fEntries.Lookup(EntryPath(entry)) == entry) fEntries.Remove(entry); LocatableDirectory* parent = entry->Parent(); if (parent != NULL) parent->RemoveEntry(entry); } bool _LocateDirectory(LocatableDirectory* directory, const BString& locatedPath, bool implicit) { if (directory == NULL || directory->State() != LOCATABLE_ENTRY_UNLOCATED) { return false; } if (!_LocateEntry(directory, locatedPath, implicit, true)) return false; _LocateEntries(directory, locatedPath, implicit); return true; } bool _LocateEntry(LocatableEntry* entry, const BString& locatedPath, bool implicit, bool locateAncestors) { if (implicit && entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY) return false; struct stat st; if (stat(locatedPath, &st) != 0) return false; if (S_ISDIR(st.st_mode)) { LocatableDirectory* directory = dynamic_cast(entry); if (directory == NULL) return false; entry->SetLocatedPath(locatedPath, implicit); } else if (S_ISREG(st.st_mode)) { LocatableFile* file = dynamic_cast(entry); if (file == NULL) return false; entry->SetLocatedPath(locatedPath, implicit); } // locate the ancestor directories, if requested if (locateAncestors) { BString locatedDirectory; BString locatedName; _SplitPath(locatedPath, locatedDirectory, locatedName); if (locatedName == entry->Name()) _LocateDirectory(entry->Parent(), locatedDirectory, implicit); } return true; } bool _LocateEntryInParentDir(LocatableEntry* entry, const BString& locatedDirectoryPath, bool implicit) { // construct the located entry path BString locatedEntryPath(locatedDirectoryPath); int32 pathLength = locatedEntryPath.Length(); if (pathLength >= 1 && locatedEntryPath[pathLength - 1] != '/') locatedEntryPath << '/'; locatedEntryPath << entry->Name(); return _LocateEntry(entry, locatedEntryPath, implicit, false); } void _LocateEntries(LocatableDirectory* directory, const BString& locatedPath, bool implicit) { for (LocatableEntryList::ConstIterator it = directory->Entries().GetIterator(); LocatableEntry* entry = it.Next();) { if (entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY) continue; if (_LocateEntryInParentDir(entry, locatedPath, implicit)) { // recurse for directories if (LocatableDirectory* subDir = dynamic_cast(entry)) { BString locatedEntryPath; if (subDir->GetLocatedPath(locatedEntryPath)) _LocateEntries(subDir, locatedEntryPath, implicit); } } } } LocatableFile* _GetFile(const BString& directoryPath, const BString& name) { BString normalizedDirPath; _NormalizePath(directoryPath, normalizedDirPath); // if already known return the file LocatableEntry* entry = _LookupEntry(EntryPath(normalizedDirPath, name)); if (entry != NULL) { LocatableFile* file = dynamic_cast(entry); if (file == NULL) return NULL; if (file->AcquireReference() == 0) fEntries.Remove(file); else return file; } // no such file yet -- create it LocatableDirectory* directory = _GetDirectory(normalizedDirPath); if (directory == NULL) return NULL; LocatableFile* file = new(std::nothrow) LocatableFile(this, directory, name); if (file == NULL) { directory->ReleaseReference(); return NULL; } directory->AddEntry(file); fEntries.Insert(file); return file; } LocatableDirectory* _GetDirectory(const BString& path) { BString directoryPath; BString fileName; _SplitNormalizedPath(path, directoryPath, fileName); // if already know return the directory LocatableEntry* entry = _LookupEntry(EntryPath(directoryPath, fileName)); if (entry != NULL) { LocatableDirectory* directory = dynamic_cast(entry); if (directory == NULL) return NULL; directory->AcquireReference(); return directory; } // get the parent directory LocatableDirectory* parentDirectory = NULL; if (directoryPath.Length() > 0) { parentDirectory = _GetDirectory(directoryPath); if (parentDirectory == NULL) return NULL; } // create a new directory LocatableDirectory* directory = new(std::nothrow) LocatableDirectory( this, parentDirectory, path); if (directory == NULL) { parentDirectory->ReleaseReference(); return NULL; } // auto-locate, if possible if (fIsLocal) { BString dirPath; directory->GetPath(dirPath); directory->SetLocatedPath(dirPath, false); } else if (parentDirectory != NULL && parentDirectory->State() != LOCATABLE_ENTRY_UNLOCATED) { BString locatedDirectoryPath; if (parentDirectory->GetLocatedPath(locatedDirectoryPath)) _LocateEntryInParentDir(directory, locatedDirectoryPath, true); } if (parentDirectory != NULL) parentDirectory->AddEntry(directory); fEntries.Insert(directory); return directory; } LocatableEntry* _LookupEntry(const EntryPath& entryPath) { LocatableEntry* entry = fEntries.Lookup(entryPath); if (entry == NULL) return NULL; // if already unreferenced, remove it if (entry->CountReferences() == 0) { fEntries.Remove(entry); return NULL; } return entry; } void _NormalizePath(const BString& path, BString& _normalizedPath) { BString normalizedPath; char* buffer = normalizedPath.LockBuffer(path.Length()); int32 outIndex = 0; const char* remaining = path.String(); while (*remaining != '\0') { // collapse repeated slashes if (*remaining == '/') { buffer[outIndex++] = '/'; remaining++; while (*remaining == '/') remaining++; } if (*remaining == '\0') { // remove trailing slash (unless it's "/" only) if (outIndex > 1) outIndex--; break; } // skip "." components if (*remaining == '.') { if (remaining[1] == '\0') break; if (remaining[1] == '/') { remaining += 2; while (*remaining == '/') remaining++; continue; } } // copy path component while (*remaining != '\0' && *remaining != '/') buffer[outIndex++] = *(remaining++); } // If the path didn't change, use the original path (BString's copy on // write mechanism) rather than the new string. if (outIndex == path.Length()) { _normalizedPath = path; } else { normalizedPath.UnlockBuffer(outIndex); _normalizedPath = normalizedPath; } } void _SplitPath(const BString& path, BString& _directory, BString& _name) { BString normalized; _NormalizePath(path, normalized); _SplitNormalizedPath(normalized, _directory, _name); } void _SplitNormalizedPath(const BString& path, BString& _directory, BString& _name) { // handle single component (including root dir) cases int32 lastSlash = path.FindLast('/'); if (lastSlash < 0 || path.Length() == 1) { _directory = (const char*)NULL; _name = path; return; } // handle root dir + one component and multi component cases if (lastSlash == 0) _directory = "/"; else _directory.SetTo(path, lastSlash); _name = path.String() + (lastSlash + 1); } private: FileManager* fManager; LocatableEntryTable fEntries; bool fIsLocal; }; // #pragma mark - SourceFileEntry struct FileManager::SourceFileEntry : public SourceFileOwner { FileManager* manager; BString path; SourceFile* file; SourceFileEntry* next; SourceFileEntry(FileManager* manager, const BString& path) : manager(manager), path(path), file(NULL) { } virtual void SourceFileUnused(SourceFile* sourceFile) { manager->_SourceFileUnused(this); } virtual void SourceFileDeleted(SourceFile* sourceFile) { // We have already been removed from the table, so commit suicide. delete this; } }; // #pragma mark - SourceFileHashDefinition struct FileManager::SourceFileHashDefinition { typedef BString KeyType; typedef SourceFileEntry ValueType; size_t HashKey(const BString& key) const { return StringUtils::HashValue(key); } size_t Hash(const SourceFileEntry* value) const { return HashKey(value->path); } bool Compare(const BString& key, const SourceFileEntry* value) const { return value->path == key; } SourceFileEntry*& GetLink(SourceFileEntry* value) const { return value->next; } }; // #pragma mark - FileManager FileManager::FileManager() : fLock("file manager"), fTargetDomain(NULL), fSourceDomain(NULL), fSourceFiles(NULL) { } FileManager::~FileManager() { delete fTargetDomain; delete fSourceDomain; SourceFileEntry* entry = fSourceFiles->Clear(); while (entry != NULL) { SourceFileEntry* next = entry->next; delete entry; entry = next; } delete fSourceFiles; } status_t FileManager::Init(bool targetIsLocal) { status_t error = fLock.InitCheck(); if (error != B_OK) return error; // create target domain fTargetDomain = new(std::nothrow) Domain(this, targetIsLocal); if (fTargetDomain == NULL) return B_NO_MEMORY; error = fTargetDomain->Init(); if (error != B_OK) return error; // create source domain fSourceDomain = new(std::nothrow) Domain(this, false); if (fSourceDomain == NULL) return B_NO_MEMORY; error = fSourceDomain->Init(); if (error != B_OK) return error; // create source file table fSourceFiles = new(std::nothrow) SourceFileTable; if (fSourceFiles == NULL) return B_NO_MEMORY; error = fSourceFiles->Init(); if (error != B_OK) return error; return B_OK; } LocatableFile* FileManager::GetTargetFile(const BString& directory, const BString& relativePath) { AutoLocker locker(this); return fTargetDomain->GetFile(directory, relativePath); } LocatableFile* FileManager::GetTargetFile(const BString& path) { AutoLocker locker(this); return fTargetDomain->GetFile(path); } void FileManager::TargetEntryLocated(const BString& path, const BString& locatedPath) { AutoLocker locker(this); fTargetDomain->EntryLocated(path, locatedPath); } LocatableFile* FileManager::GetSourceFile(const BString& directory, const BString& relativePath) { AutoLocker locker(this); LocatableFile* file = fSourceDomain->GetFile(directory, relativePath); return file; } LocatableFile* FileManager::GetSourceFile(const BString& path) { AutoLocker locker(this); LocatableFile* file = fSourceDomain->GetFile(path); return file; } status_t FileManager::SourceEntryLocated(const BString& path, const BString& locatedPath) { AutoLocker locker(this); // check if we already have this path mapped. If so, // first clear the mapping, as the user may be attempting // to correct an existing entry. SourceFileEntry* entry = _LookupSourceFile(path); if (entry != NULL) _SourceFileUnused(entry); fSourceDomain->EntryLocated(path, locatedPath); try { fSourceLocationMappings[path] = locatedPath; } catch (...) { return B_NO_MEMORY; } return B_OK; } status_t FileManager::LoadSourceFile(LocatableFile* file, SourceFile*& _sourceFile) { AutoLocker locker(this); // get the path BString path; BString originalPath; file->GetPath(originalPath); if (!file->GetLocatedPath(path)) { // see if this is a file we have a lazy mapping for. if (!_LocateFileIfMapped(originalPath, file) || !file->GetLocatedPath(path)) { return B_ENTRY_NOT_FOUND; } } // we might already know the source file SourceFileEntry* entry = _LookupSourceFile(originalPath); if (entry != NULL) { entry->file->AcquireReference(); _sourceFile = entry->file; return B_OK; } // create the hash table entry entry = new(std::nothrow) SourceFileEntry(this, originalPath); if (entry == NULL) return B_NO_MEMORY; // load the file SourceFile* sourceFile = new(std::nothrow) SourceFile(entry); if (sourceFile == NULL) { delete entry; return B_NO_MEMORY; } ObjectDeleter sourceFileDeleter(sourceFile); entry->file = sourceFile; status_t error = sourceFile->Init(path); if (error != B_OK) return error; fSourceFiles->Insert(entry); _sourceFile = sourceFileDeleter.Detach(); return B_OK; } status_t FileManager::LoadLocationMappings(TeamFileManagerSettings* settings) { AutoLocker locker(this); for (int32 i = 0; i < settings->CountSourceMappings(); i++) { BString sourcePath; BString locatedPath; if (settings->GetSourceMappingAt(i, sourcePath, locatedPath) != B_OK) return B_NO_MEMORY; try { fSourceLocationMappings[sourcePath] = locatedPath; } catch (...) { return B_NO_MEMORY; } } return B_OK; } status_t FileManager::SaveLocationMappings(TeamFileManagerSettings* settings) { AutoLocker locker(this); for (LocatedFileMap::const_iterator it = fSourceLocationMappings.begin(); it != fSourceLocationMappings.end(); ++it) { status_t error = settings->AddSourceMapping(it->first, it->second); if (error != B_OK) return error; } return B_OK; } FileManager::SourceFileEntry* FileManager::_LookupSourceFile(const BString& path) { SourceFileEntry* entry = fSourceFiles->Lookup(path); if (entry == NULL) return NULL; // the entry might be unused already -- in that case remove it if (entry->file->CountReferences() == 0) { fSourceFiles->Remove(entry); return NULL; } return entry; } void FileManager::_SourceFileUnused(SourceFileEntry* entry) { AutoLocker locker(this); SourceFileEntry* otherEntry = fSourceFiles->Lookup(entry->path); if (otherEntry == entry) fSourceFiles->Remove(entry); } bool FileManager::_LocateFileIfMapped(const BString& sourcePath, LocatableFile* file) { // called with lock held LocatedFileMap::const_iterator it = fSourceLocationMappings.find( sourcePath); if (it != fSourceLocationMappings.end() && file->State() != LOCATABLE_ENTRY_LOCATED_EXPLICITLY && file->State() != LOCATABLE_ENTRY_LOCATED_IMPLICITLY) { fSourceDomain->EntryLocated(it->first, it->second); return true; } return false; }