#include "BsProjectLibrary.h" #include "BsEditorApplication.h" #include "CmPath.h" #include "CmFileSystem.h" #include "CmException.h" #include "CmResources.h" #include "CmResourceManifest.h" #include "CmImporter.h" #include "BsResourceMeta.h" #include "CmResources.h" #include "CmImporter.h" #include "CmImportOptions.h" #include "CmFileSerializer.h" #include "CmFolderMonitor.h" #include "CmDebug.h" using namespace CamelotFramework; using namespace BansheeEngine; namespace BansheeEditor { const WString ProjectLibrary::INTERNAL_RESOURCES_DIR = L"Internal/Resources"; ProjectLibrary::ProjectLibrary() :mRootEntry(nullptr) { mMonitor = cm_new(); FolderChange folderChanges = (FolderChange)((UINT32)FolderChange::FileName | (UINT32)FolderChange::DirName | (UINT32)FolderChange::Creation | (UINT32)FolderChange::LastWrite); mMonitor->startMonitor(EditorApplication::instance().getResourcesFolderPath(), true, folderChanges); mMonitor->onAdded.connect(boost::bind(&ProjectLibrary::onMonitorFileModified, this, _1)); mMonitor->onRemoved.connect(boost::bind(&ProjectLibrary::onMonitorFileModified, this, _1)); mMonitor->onModified.connect(boost::bind(&ProjectLibrary::onMonitorFileModified, this, _1)); } ProjectLibrary::~ProjectLibrary() { mMonitor->stopMonitorAll(); cm_delete(mMonitor); if(mRootEntry != nullptr) deleteDirectoryInternal(mRootEntry); } void ProjectLibrary::update() { mMonitor->update(); } void ProjectLibrary::checkForModifications(const CM::WString& fullPath) { if(!PathUtil::includes(fullPath, EditorApplication::instance().getResourcesFolderPath())) return; // Folder not part of our resources path, so no modifications if(mRootEntry == nullptr) { mRootEntry = cm_new(); mRootEntry->path = toPath(EditorApplication::instance().getResourcesFolderPath()); } WPath pathToSearch = toPath(fullPath); LibraryEntry* entry = findEntry(pathToSearch); if(entry == nullptr) // File could be new, try to find parent directory entry { WPath parentDirPath = PathUtil::parentPath(pathToSearch); entry = findEntry(parentDirPath); // Cannot find parent directory. Create the needed hierarchy. DirectoryEntry* entryParent = nullptr; DirectoryEntry* newHierarchyParent = nullptr; if(entry == nullptr) createInternalParentHierarchy(pathToSearch, &newHierarchyParent, &entryParent); else entryParent = static_cast(entry); if(FileSystem::isFile(pathToSearch)) { addResourceInternal(entryParent, pathToSearch); } else if(FileSystem::isDirectory(pathToSearch)) { addDirectoryInternal(entryParent, pathToSearch); if(newHierarchyParent == nullptr) checkForModifications(toWString(pathToSearch)); } if(newHierarchyParent != nullptr) checkForModifications(toWString(newHierarchyParent->path)); } else if(entry->type == LibraryEntryType::File) { if(FileSystem::isFile(entry->path)) { reimportResourceInternal(static_cast(entry)); } else { deleteResourceInternal(static_cast(entry)); } } else if(entry->type == LibraryEntryType::Directory) // Check folder and all subfolders for modifications { Stack::type todo; todo.push(static_cast(entry)); Vector::type childFiles; Vector::type childDirectories; Vector::type existingEntries; Vector::type toDelete; while(!todo.empty()) { DirectoryEntry* currentDir = todo.top(); todo.pop(); existingEntries.clear(); existingEntries.resize(currentDir->mChildren.size()); for(UINT32 i = 0; i < (UINT32)currentDir->mChildren.size(); i++) existingEntries[i] = false; childFiles.clear(); childDirectories.clear(); FileSystem::getChildren(currentDir->path, childFiles, childDirectories); for(auto& filePath : childFiles) { if(isMeta(filePath)) { WPath sourceFilePath = filePath; PathUtil::replaceExtension(sourceFilePath, L""); if(FileSystem::isFile(sourceFilePath)) { LOGWRN("Found a .meta file without a corresponding resource. Deleting."); FileSystem::remove(filePath); } } else { ResourceEntry* existingEntry = nullptr; UINT32 idx = 0; for(auto& child : currentDir->mChildren) { if(child->type == LibraryEntryType::File && child->path == filePath) { existingEntries[idx] = true; existingEntry = static_cast(child); break; } idx++; } if(existingEntry != nullptr) { reimportResourceInternal(existingEntry); } else { addResourceInternal(currentDir, filePath); } } } for(auto& dirPath : childDirectories) { DirectoryEntry* existingEntry = nullptr; UINT32 idx = 0; for(auto& child : currentDir->mChildren) { if(child->type == LibraryEntryType::Directory && child->path == dirPath) { existingEntries[idx] = true; existingEntry = static_cast(child); break; } idx++; } if(existingEntry == nullptr) addDirectoryInternal(currentDir, dirPath); } { UINT32 idx = 0; toDelete.clear(); for(auto& child : currentDir->mChildren) { if(existingEntries[idx]) continue; toDelete.push_back(child); idx++; } for(auto& child : toDelete) { if(child->type == LibraryEntryType::Directory) deleteDirectoryInternal(static_cast(child)); else if(child->type == LibraryEntryType::File) deleteResourceInternal(static_cast(child)); } } for(auto& child : currentDir->mChildren) { if(child->type == LibraryEntryType::Directory) todo.push(static_cast(child)); } } } } ProjectLibrary::ResourceEntry* ProjectLibrary::addResourceInternal(DirectoryEntry* parent, const CM::WPath& filePath) { ResourceEntry* newResource = cm_new(); newResource->type = LibraryEntryType::File; newResource->path = filePath; newResource->parent = parent; parent->mChildren.push_back(newResource); reimportResourceInternal(newResource); if(!onEntryAdded.empty()) onEntryAdded(filePath); return newResource; } ProjectLibrary::DirectoryEntry* ProjectLibrary::addDirectoryInternal(DirectoryEntry* parent, const CM::WPath& dirPath) { DirectoryEntry* newEntry = cm_new(); newEntry->type = LibraryEntryType::Directory; newEntry->path = dirPath; newEntry->parent = parent; parent->mChildren.push_back(newEntry); if(!onEntryAdded.empty()) onEntryAdded(dirPath); return newEntry; } void ProjectLibrary::deleteResourceInternal(ResourceEntry* resource) { ResourceManifestPtr manifest = gResources().getResourceManifest(); if(resource->meta != nullptr) { if(manifest->uuidExists(resource->meta->getUUID())) { const WPath& path = manifest->uuidToFilePath(resource->meta->getUUID()); if(FileSystem::isFile(path)) FileSystem::remove(path); manifest->unregisterResource(resource->meta->getUUID()); } } DirectoryEntry* parent = resource->parent; auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(), [&] (const LibraryEntry* entry) { return entry == resource; }); parent->mChildren.erase(findIter); if(!onEntryRemoved.empty()) onEntryRemoved(resource->path); cm_delete(resource); } void ProjectLibrary::deleteDirectoryInternal(DirectoryEntry* directory) { if(directory == mRootEntry) mRootEntry = nullptr; for(auto& child : directory->mChildren) { if(child->type == LibraryEntryType::Directory) deleteDirectoryInternal(static_cast(child)); else deleteResourceInternal(static_cast(child)); } DirectoryEntry* parent = directory->parent; auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(), [&] (const LibraryEntry* entry) { return entry == directory; }); parent->mChildren.erase(findIter); if(!onEntryRemoved.empty()) onEntryRemoved(directory->path); cm_delete(directory); } void ProjectLibrary::reimportResourceInternal(ResourceEntry* resource) { WString ext = toWString(PathUtil::getExtension(resource->path)); WPath metaPath = resource->path; PathUtil::replaceExtension(metaPath, ext + L".meta"); ext = ext.substr(1, ext.size() - 1); // Remove the . if(!Importer::instance().supportsFileType(ext)) return; if(resource->meta == nullptr) { FileSerializer fs; if(FileSystem::isFile(metaPath)) { std::shared_ptr loadedMeta = fs.decode(toWString(metaPath)); if(loadedMeta != nullptr && loadedMeta->isDerivedFrom(ResourceMeta::getRTTIStatic())) { ResourceMetaPtr resourceMeta = std::static_pointer_cast(loadedMeta); resource->meta = resourceMeta; } } } if(!isUpToDate(resource)) { ImportOptionsPtr importOptions = nullptr; if(resource->meta != nullptr) importOptions = resource->meta->getImportOptions(); else importOptions = Importer::instance().createImportOptions(toWString(resource->path)); HResource importedResource = Importer::instance().import(toWString(resource->path), importOptions); if(resource->meta == nullptr) { resource->meta = ResourceMeta::create(importedResource.getUUID(), importOptions); FileSerializer fs; fs.encode(resource->meta.get(), toWString(metaPath)); } WPath internalResourcesPath = toPath(EditorApplication::instance().getActiveProjectPath()) / toPath(INTERNAL_RESOURCES_DIR); if(!FileSystem::isDirectory(internalResourcesPath)) FileSystem::createDir(internalResourcesPath); internalResourcesPath /= toPath(toWString(importedResource.getUUID()) + L".asset"); gResources().save(importedResource, toWString(internalResourcesPath), true); ResourceManifestPtr manifest = gResources().getResourceManifest(); manifest->registerResource(importedResource.getUUID(), internalResourcesPath); resource->lastUpdateTime = std::time(nullptr); } } bool ProjectLibrary::isUpToDate(ResourceEntry* resource) const { if(resource->meta == nullptr) return false; ResourceManifestPtr manifest = gResources().getResourceManifest(); if(!manifest->uuidExists(resource->meta->getUUID())) return false; const WPath& path = manifest->uuidToFilePath(resource->meta->getUUID()); if(!FileSystem::isFile(path)) return false; std::time_t lastModifiedTime = FileSystem::getLastModifiedTime(path); return lastModifiedTime <= resource->lastUpdateTime; } ProjectLibrary::LibraryEntry* ProjectLibrary::findEntry(const CM::WPath& fullPath) const { auto pathIter = fullPath.begin(); auto rootIter = mRootEntry->path.begin(); while(*pathIter == *rootIter) { ++pathIter; ++rootIter; } if(pathIter == fullPath.begin()) // Not a single entry matches Resources path return nullptr; --pathIter; Stack::type todo; todo.push(mRootEntry); while(!todo.empty()) { LibraryEntry* current = todo.top(); todo.pop(); if(*pathIter == *(--(current->path.end()))) { if(current->type == LibraryEntryType::Directory) { DirectoryEntry* dirEntry = static_cast(current); for(auto& child : dirEntry->mChildren) todo.push(child); } if(pathIter == fullPath.end()) return current; ++pathIter; } } return nullptr; } void ProjectLibrary::moveEntry(const CM::WPath& oldPath, const CM::WPath& newPath) { if(FileSystem::isFile(oldPath) || FileSystem::isDirectory(oldPath)) FileSystem::move(oldPath, newPath); WPath oldMetaPath = getMetaPath(oldPath); WPath newMetaPath = getMetaPath(newPath); LibraryEntry* oldEntry = findEntry(oldPath); if(oldEntry != nullptr) // Moving from the Resources folder { // Moved outside of Resources, delete entry & meta file if(!PathUtil::includes(toWString(newPath), EditorApplication::instance().getResourcesFolderPath())) { if(oldEntry->type == LibraryEntryType::File) { deleteResourceInternal(static_cast(oldEntry)); if(FileSystem::isFile(oldMetaPath)) FileSystem::remove(oldMetaPath); } else if(oldEntry->type == LibraryEntryType::Directory) deleteDirectoryInternal(static_cast(oldEntry)); } else // Just moving internally { if(FileSystem::isFile(oldMetaPath)) FileSystem::move(oldMetaPath, newMetaPath); DirectoryEntry* parent = oldEntry->parent; auto findIter = std::find(parent->mChildren.begin(), parent->mChildren.end(), oldEntry); if(findIter != parent->mChildren.end()) parent->mChildren.erase(findIter); WPath parentPath = PathUtil::parentPath(newPath); DirectoryEntry* newEntryParent = nullptr; LibraryEntry* newEntryParentLib = findEntry(parentPath); if(newEntryParentLib != nullptr) { assert(newEntryParentLib->type == LibraryEntryType::Directory); newEntryParent = static_cast(newEntryParentLib); } DirectoryEntry* newHierarchyParent = nullptr; if(newEntryParent == nullptr) // New path parent doesn't exist, so we need to create the hierarchy createInternalParentHierarchy(newPath, &newHierarchyParent, &newEntryParent); newEntryParent->mChildren.push_back(oldEntry); oldEntry->parent = newEntryParent; oldEntry->path = newPath; if(!onEntryRemoved.empty()) onEntryRemoved(oldPath); if(!onEntryAdded.empty()) onEntryAdded(newPath); if(newHierarchyParent != nullptr) checkForModifications(toWString(newHierarchyParent->path)); } } else // Moving from outside of the Resources folder (likely adding a new resource) { checkForModifications(toWString(newPath)); } } void ProjectLibrary::deleteEntry(const CM::WPath& path) { if(FileSystem::isFile(path)) FileSystem::remove(path); LibraryEntry* entry = findEntry(path); if(entry != nullptr) { if(entry->type == LibraryEntryType::File) { deleteResourceInternal(static_cast(entry)); WPath metaPath = getMetaPath(path); if(FileSystem::isFile(metaPath)) FileSystem::remove(metaPath); } else if(entry->type == LibraryEntryType::Directory) deleteDirectoryInternal(static_cast(entry)); } } void ProjectLibrary::createInternalParentHierarchy(const CM::WPath& fullPath, DirectoryEntry** newHierarchyRoot, DirectoryEntry** newHierarchyLeaf) { WPath parentPath = fullPath; DirectoryEntry* newEntryParent = nullptr; Stack::type parentPaths; do { WPath newParentPath = PathUtil::parentPath(parentPath); if(newParentPath == parentPath) break; LibraryEntry* newEntryParentLib = findEntry(newParentPath); if(newEntryParentLib != nullptr) { assert(newEntryParentLib->type == LibraryEntryType::Directory); newEntryParent = static_cast(newEntryParentLib); break; } parentPaths.push(newParentPath); parentPath = newParentPath; } while (true); assert(newEntryParent != nullptr); // Must exist if(newHierarchyRoot != nullptr) *newHierarchyRoot = newEntryParent; while(!parentPaths.empty()) { WPath curPath = parentPaths.top(); parentPaths.pop(); newEntryParent = addDirectoryInternal(newEntryParent, curPath); } if(newHierarchyLeaf != nullptr) *newHierarchyLeaf = newEntryParent; } WPath ProjectLibrary::getMetaPath(const CM::WPath& path) const { WString ext = toWString(PathUtil::getExtension(path)); WPath metaPath = path; PathUtil::replaceExtension(metaPath, ext + L".meta"); return metaPath; } bool ProjectLibrary::isMeta(const WPath& fullPath) const { return PathUtil::getExtension(fullPath) == toPath(L".meta"); } void ProjectLibrary::onMonitorFileModified(const WString& path) { checkForModifications(path); } }