#include "vxnotebookconfigmgr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vxnodeconfig.h" #include "vxnotebookconfigmgrfactory.h" using namespace vnotex; using namespace vnotex::vx_node_config; const QString VXNotebookConfigMgr::c_nodeConfigName = "vx.json"; bool VXNotebookConfigMgr::s_initialized = false; QVector VXNotebookConfigMgr::s_externalNodeExcludePatterns; VXNotebookConfigMgr::VXNotebookConfigMgr(const QSharedPointer &p_backend, QObject *p_parent) : BundleNotebookConfigMgr(p_backend, p_parent) { if (!s_initialized) { s_initialized = true; const auto &patterns = ConfigMgr::getInst().getCoreConfig().getExternalNodeExcludePatterns(); s_externalNodeExcludePatterns.reserve(patterns.size()); for (const auto &pat : patterns) { if (!pat.isEmpty()) { s_externalNodeExcludePatterns.push_back(QRegExp(pat, Qt::CaseInsensitive, QRegExp::Wildcard)); } } } } QString VXNotebookConfigMgr::getName() const { return VXNotebookConfigMgrFactory::c_name; } QString VXNotebookConfigMgr::getDisplayName() const { return VXNotebookConfigMgrFactory::c_displayName; } QString VXNotebookConfigMgr::getDescription() const { return VXNotebookConfigMgrFactory::c_description; } void VXNotebookConfigMgr::createEmptySkeleton(const NotebookParameters &p_paras) { BundleNotebookConfigMgr::createEmptySkeleton(p_paras); createEmptyRootNode(); } void VXNotebookConfigMgr::createEmptyRootNode() { auto currentTime = QDateTime::currentDateTimeUtc(); NodeConfig node(getCodeVersion(), Node::InvalidId, Node::InvalidId, currentTime, currentTime); writeNodeConfig(c_nodeConfigName, node); } QSharedPointer VXNotebookConfigMgr::loadRootNode() { auto nodeConfig = readNodeConfig(""); QSharedPointer root = nodeConfigToNode(*nodeConfig, "", nullptr); root->setUse(Node::Use::Root); root->setExists(true); Q_ASSERT(root->isLoaded()); if (static_cast(getNotebook())->getConfigVersion() < 3) { removeLegacyRecycleBinNode(root); } return root; } void VXNotebookConfigMgr::removeLegacyRecycleBinNode(const QSharedPointer &p_root) { // Do not support recycle bin node as it complicates everything. auto node = p_root->findChild(QStringLiteral("vx_recycle_bin"), FileUtils::isPlatformNameCaseSensitive()); if (node) { removeNode(node, true, true); } } void VXNotebookConfigMgr::markNodeReadOnly(Node *p_node) const { if (p_node->isReadOnly()) { return; } p_node->setReadOnly(true); for (const auto &child : p_node->getChildrenRef()) { markNodeReadOnly(child.data()); } } QSharedPointer VXNotebookConfigMgr::readNodeConfig(const QString &p_path) const { auto backend = getBackend(); if (!backend->exists(p_path)) { Exception::throwOne(Exception::Type::InvalidArgument, QString("node path (%1) does not exist").arg(p_path)); } if (backend->isFile(p_path)) { Exception::throwOne(Exception::Type::InvalidArgument, QString("node (%1) is a file node without config").arg(p_path)); } else { auto configPath = PathUtils::concatenateFilePath(p_path, c_nodeConfigName); auto data = backend->readFile(configPath); auto nodeConfig = QSharedPointer::create(); nodeConfig->fromJson(QJsonDocument::fromJson(data).object()); return nodeConfig; } return nullptr; } QString VXNotebookConfigMgr::getNodeConfigFilePath(const Node *p_node) const { Q_ASSERT(p_node->isContainer()); return PathUtils::concatenateFilePath(p_node->fetchPath(), c_nodeConfigName); } void VXNotebookConfigMgr::writeNodeConfig(const QString &p_path, const NodeConfig &p_config) const { getBackend()->writeFile(p_path, p_config.toJson()); } void VXNotebookConfigMgr::writeNodeConfig(const Node *p_node) { auto config = nodeToNodeConfig(p_node); writeNodeConfig(getNodeConfigFilePath(p_node), *config); } QSharedPointer VXNotebookConfigMgr::nodeConfigToNode(const NodeConfig &p_config, const QString &p_name, Node *p_parent) { auto node = QSharedPointer::create(p_name, getNotebook(), p_parent); loadFolderNode(node.data(), p_config); return node; } void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_config) { QVector> children; children.reserve(p_config.m_files.size() + p_config.m_folders.size()); const auto basePath = p_node->fetchPath(); bool needUpdateConfig = false; for (const auto &folder : p_config.m_folders) { if (folder.m_name.isEmpty()) { // Skip empty name node. qWarning() << "skipped loading node with empty name under" << p_node->fetchPath(); continue; } auto folderNode = QSharedPointer::create(folder.m_name, getNotebook(), p_node); inheritNodeFlags(p_node, folderNode.data()); folderNode->setExists(getBackend()->existsDir(PathUtils::concatenateFilePath(basePath, folder.m_name))); children.push_back(folderNode); } for (const auto &file : p_config.m_files) { if (file.m_name.isEmpty()) { // Skip empty name node. qWarning() << "skipped loading node with empty name under" << p_node->fetchPath(); continue; } // For compability only. needUpdateConfig = needUpdateConfig || file.m_signature == Node::InvalidId; auto fileNode = QSharedPointer::create(file.m_name, file.toNodeParameters(), getNotebook(), p_node); inheritNodeFlags(p_node, fileNode.data()); fileNode->setExists(getBackend()->existsFile(PathUtils::concatenateFilePath(basePath, file.m_name))); children.push_back(fileNode); } p_node->loadCompleteInfo(p_config.toNodeParameters(), children); needUpdateConfig = needUpdateConfig || p_config.m_signature == Node::InvalidId; if (needUpdateConfig) { writeNodeConfig(p_node); } } QSharedPointer VXNotebookConfigMgr::newNode(Node *p_parent, Node::Flags p_flags, const QString &p_name, const QString &p_content) { Q_ASSERT(p_parent && p_parent->isContainer() && !p_name.isEmpty()); QSharedPointer node; if (p_flags & Node::Flag::Content) { Q_ASSERT(!(p_flags & Node::Flag::Container)); node = newFileNode(p_parent, p_name, p_content, true, NodeParameters()); } else { node = newFolderNode(p_parent, p_name, true, NodeParameters()); } return node; } QSharedPointer VXNotebookConfigMgr::addAsNode(Node *p_parent, Node::Flags p_flags, const QString &p_name, const NodeParameters &p_paras) { Q_ASSERT(p_parent && p_parent->isContainer()); // TODO: reuse the config if available. QSharedPointer node; if (p_flags & Node::Flag::Content) { Q_ASSERT(!(p_flags & Node::Flag::Container)); node = newFileNode(p_parent, p_name, "", false, p_paras); } else { node = newFolderNode(p_parent, p_name, false, p_paras); } return node; } QSharedPointer VXNotebookConfigMgr::copyAsNode(Node *p_parent, Node::Flags p_flags, const QString &p_path) { Q_ASSERT(p_parent && p_parent->isContainer()); QSharedPointer node; if (p_flags & Node::Flag::Content) { Q_ASSERT(!(p_flags & Node::Flag::Container)); node = copyFileAsChildOf(p_path, p_parent); } else { node = copyFolderAsChildOf(p_path, p_parent); } return node; } QSharedPointer VXNotebookConfigMgr::newFileNode(Node *p_parent, const QString &p_name, const QString &p_content, bool p_create, const NodeParameters &p_paras) { ensureNodeInDatabase(p_parent); auto notebook = getNotebook(); // Create file node. auto node = QSharedPointer::create(p_name, p_paras, notebook, p_parent); // Write empty file. if (p_create) { if (getBackend()->childExistsCaseInsensitive(p_parent->fetchPath(), p_name)) { // File already exists. Exception. Exception::throwOne(Exception::Type::FileExistsOnCreate, QString("file (%1) already exists when creating new node").arg(node->fetchPath())); return nullptr; } getBackend()->writeFile(node->fetchPath(), p_content); node->setExists(true); } else { node->setExists(getBackend()->existsFile(node->fetchPath())); } addChildNode(p_parent, node); writeNodeConfig(p_parent); addNodeToDatabase(node.data()); return node; } QSharedPointer VXNotebookConfigMgr::newFolderNode(Node *p_parent, const QString &p_name, bool p_create, const NodeParameters &p_paras) { ensureNodeInDatabase(p_parent); auto notebook = getNotebook(); // Create folder node. auto node = QSharedPointer::create(p_name, notebook, p_parent); node->loadCompleteInfo(p_paras, QVector>()); // Make folder. if (p_create) { if (getBackend()->childExistsCaseInsensitive(p_parent->fetchPath(), p_name)) { // Dir already exists. Exception. Exception::throwOne(Exception::Type::DirExistsOnCreate, QString("dir (%1) already exists when creating new node").arg(node->fetchPath())); return nullptr; } getBackend()->makePath(node->fetchPath()); node->setExists(true); } else { node->setExists(getBackend()->existsDir(node->fetchPath())); } writeNodeConfig(node.data()); addChildNode(p_parent, node); writeNodeConfig(p_parent); addNodeToDatabase(node.data()); return node; } QSharedPointer VXNotebookConfigMgr::nodeToNodeConfig(const Node *p_node) const { Q_ASSERT(p_node->isContainer()); Q_ASSERT(p_node->isLoaded()); auto config = QSharedPointer::create(getCodeVersion(), p_node->getId(), p_node->getSignature(), p_node->getCreatedTimeUtc(), p_node->getModifiedTimeUtc()); for (const auto &child : p_node->getChildrenRef()) { if (child->hasContent()) { NodeFileConfig fileConfig; fileConfig.m_name = child->getName(); fileConfig.m_id = child->getId(); fileConfig.m_signature = child->getSignature(); fileConfig.m_createdTimeUtc = child->getCreatedTimeUtc(); fileConfig.m_modifiedTimeUtc = child->getModifiedTimeUtc(); fileConfig.m_attachmentFolder = child->getAttachmentFolder(); fileConfig.m_tags = child->getTags(); config->m_files.push_back(fileConfig); } else { Q_ASSERT(child->isContainer()); NodeFolderConfig folderConfig; folderConfig.m_name = child->getName(); config->m_folders.push_back(folderConfig); } } return config; } void VXNotebookConfigMgr::loadNode(Node *p_node) { if (p_node->isLoaded() || !p_node->exists()) { return; } auto config = readNodeConfig(p_node->fetchPath()); Q_ASSERT(p_node->isContainer()); loadFolderNode(p_node, *config); } void VXNotebookConfigMgr::saveNode(const Node *p_node) { if (p_node->isContainer()) { writeNodeConfig(p_node); } else { Q_ASSERT(!p_node->isRoot()); writeNodeConfig(p_node->getParent()); } } void VXNotebookConfigMgr::renameNode(Node *p_node, const QString &p_name) { Q_ASSERT(!p_node->isRoot()); if (p_node->isContainer()) { getBackend()->renameDir(p_node->fetchPath(), p_name); } else { getBackend()->renameFile(p_node->fetchPath(), p_name); } p_node->setName(p_name); writeNodeConfig(p_node->getParent()); ensureNodeInDatabase(p_node); updateNodeInDatabase(p_node); } // Do not touch DB here since it will be called at different scenarios. void VXNotebookConfigMgr::addChildNode(Node *p_parent, const QSharedPointer &p_child) const { if (p_child->isContainer()) { int idx = 0; const auto &children = p_parent->getChildrenRef(); for (; idx < children.size(); ++idx) { if (!children[idx]->isContainer()) { break; } } p_parent->insertChild(idx, p_child); } else { p_parent->addChild(p_child); } inheritNodeFlags(p_parent, p_child.data()); } QSharedPointer VXNotebookConfigMgr::loadNodeByPath(const QSharedPointer &p_root, const QString &p_relativePath) { auto p = PathUtils::cleanPath(p_relativePath); auto paths = p.split('/', QString::SkipEmptyParts); auto node = p_root; for (auto &pa : paths) { // Find child @pa in @node. if (!node->isLoaded()) { loadNode(node.data()); } auto child = node->findChild(pa, FileUtils::isPlatformNameCaseSensitive()); if (!child) { return nullptr; } node = child; } return node; } // @p_src may belong to different notebook or different kind of configmgr. // TODO: we could constrain @p_src within the same configrmgr? QSharedPointer VXNotebookConfigMgr::copyNodeAsChildOf(const QSharedPointer &p_src, Node *p_dest, bool p_move) { return copyNodeAsChildOf(p_src, p_dest, p_move, true); } QSharedPointer VXNotebookConfigMgr::copyNodeAsChildOf(const QSharedPointer &p_src, Node *p_dest, bool p_move, bool p_updateDatabase) { Q_ASSERT(p_dest->isContainer()); if (!p_src->exists()) { if (p_move) { // It is OK to always update the database. p_src->getNotebook()->removeNode(p_src); } return nullptr; } QSharedPointer node; if (p_src->isContainer()) { node = copyFolderNodeAsChildOf(p_src, p_dest, p_move, p_updateDatabase); } else { node = copyFileNodeAsChildOf(p_src, p_dest, p_move, p_updateDatabase); } return node; } QSharedPointer VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPointer &p_src, Node *p_dest, bool p_move, bool p_updateDatabase) { QString destFilePath; QString attachmentFolder; copyFilesOfFileNode(p_src, p_dest->fetchPath(), destFilePath, attachmentFolder); // Create a file node. auto notebook = getNotebook(); const bool sameNotebook = p_src->getNotebook() == notebook; if (p_updateDatabase) { ensureNodeInDatabase(p_dest); if (sameNotebook) { ensureNodeInDatabase(p_src.data()); } } NodeParameters paras; if (p_move && sameNotebook) { paras.m_id = p_src->getId(); paras.m_signature = p_src->getSignature(); } paras.m_createdTimeUtc = p_src->getCreatedTimeUtc(); paras.m_modifiedTimeUtc = p_src->getModifiedTimeUtc(); paras.m_tags = p_src->getTags(); paras.m_attachmentFolder = attachmentFolder; auto destNode = QSharedPointer::create(PathUtils::fileName(destFilePath), paras, notebook, p_dest); destNode->setExists(true); addChildNode(p_dest, destNode); writeNodeConfig(p_dest); if (p_updateDatabase) { if (p_move && sameNotebook) { updateNodeInDatabase(destNode.data()); } else { addNodeToDatabase(destNode.data()); } Q_ASSERT(nodeExistsInDatabase(destNode.data())); } if (p_move) { if (sameNotebook) { // The same notebook. Do not directly call removeNode() since we already update the record // in database directly. removeNode(p_src, false, false, false); } else { p_src->getNotebook()->removeNode(p_src); } } return destNode; } QSharedPointer VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedPointer &p_src, Node *p_dest, bool p_move, bool p_updateDatabase) { auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchPath(), p_src->getName()); destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath); // Make folder. getBackend()->makePath(destFolderPath); // Create a folder node. auto notebook = getNotebook(); const bool sameNotebook = p_src->getNotebook() == notebook; if (p_updateDatabase) { ensureNodeInDatabase(p_dest); if (sameNotebook) { ensureNodeInDatabase(p_src.data()); } } auto destNode = QSharedPointer::create(PathUtils::fileName(destFolderPath), notebook, p_dest); { NodeParameters paras; if (p_move && sameNotebook) { paras.m_id = p_src->getId(); paras.m_signature = p_src->getSignature(); } paras.m_createdTimeUtc = p_src->getCreatedTimeUtc(); paras.m_modifiedTimeUtc = p_src->getModifiedTimeUtc(); destNode->loadCompleteInfo(paras, QVector>()); } destNode->setExists(true); writeNodeConfig(destNode.data()); addChildNode(p_dest, destNode); writeNodeConfig(p_dest); if (p_updateDatabase) { if (p_move && sameNotebook) { p_updateDatabase = false; updateNodeInDatabase(destNode.data()); } else { addNodeToDatabase(destNode.data()); } } // Copy children node. auto children = p_src->getChildren(); for (const auto &childNode : children) { copyNodeAsChildOf(childNode, destNode.data(), p_move, p_updateDatabase); } if (p_move) { if (sameNotebook) { removeNode(p_src, false, false, false); } else { p_src->getNotebook()->removeNode(p_src); } } return destNode; } void VXNotebookConfigMgr::removeNode(const QSharedPointer &p_node, bool p_force, bool p_configOnly) { removeNode(p_node, p_force, p_configOnly, true); } void VXNotebookConfigMgr::removeNode(const QSharedPointer &p_node, bool p_force, bool p_configOnly, bool p_updateDatabase) { if (!p_configOnly && p_node->exists()) { // Remove all children. auto children = p_node->getChildren(); for (const auto &childNode : children) { // With DELETE CASCADE, we could just touch the DB at parent level. removeNode(childNode, p_force, p_configOnly, false); } try { removeFilesOfNode(p_node.data(), p_force); } catch (Exception &p_e) { qWarning() << "failed to remove files of node" << p_node->fetchPath() << p_e.what(); } } if (p_updateDatabase) { // Remove it from data base before modifying the parent. removeNodeFromDatabase(p_node.data()); } if (auto parentNode = p_node->getParent()) { parentNode->removeChild(p_node); writeNodeConfig(parentNode); } } void VXNotebookConfigMgr::removeFilesOfNode(Node *p_node, bool p_force) { Q_ASSERT(p_node->getNotebook() == getNotebook()); if (!p_node->isContainer()) { // Delete attachment. if (!p_node->getAttachmentFolder().isEmpty()) { getBackend()->removeDir(p_node->fetchAttachmentFolderPath()); } // Delete media files fetched from content. ContentMediaUtils::removeMediaFiles(p_node); // Delete node file itself. auto filePath = p_node->fetchPath(); getBackend()->removeFile(filePath); } else { Q_ASSERT(p_node->getChildrenCount() == 0); // Delete node config file and the dir if it is empty. auto configFilePath = getNodeConfigFilePath(p_node); getBackend()->removeFile(configFilePath); auto folderPath = p_node->fetchPath(); if (p_force) { getBackend()->removeDir(folderPath); } else { getBackend()->removeEmptyDir(folderPath); bool deleted = getBackend()->removeDirIfEmpty(folderPath); if (!deleted) { qWarning() << "folder is not deleted since it is not empty" << folderPath; } } } } void VXNotebookConfigMgr::removeNodeToFolder(const QSharedPointer &p_node, const QString &p_destFolder) { if (p_node->isContainer()) { removeFolderNodeToFolder(p_node, p_destFolder); } else { removeFileNodeToFolder(p_node, p_destFolder); } } void VXNotebookConfigMgr::removeFolderNodeToFolder(const QSharedPointer &p_node, const QString &p_destFolder) { auto destFolderPath = PathUtils::concatenateFilePath(p_destFolder, p_node->getName()); destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath); // Make folder. getBackend()->makePath(destFolderPath); // Children. auto children = p_node->getChildren(); for (const auto &child : children) { removeNodeToFolder(child, destFolderPath); } removeNode(p_node, false, false); } void VXNotebookConfigMgr::removeFileNodeToFolder(const QSharedPointer &p_node, const QString &p_destFolder) { // Use a wrapper folder. auto destFolderPath = PathUtils::concatenateFilePath(p_destFolder, p_node->getName()); destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath); // Make folder. getBackend()->makePath(destFolderPath); QString destFilePath; QString attachmentFolder; copyFilesOfFileNode(p_node, destFolderPath, destFilePath, attachmentFolder); removeNode(p_node, false, false); } void VXNotebookConfigMgr::copyFilesOfFileNode(const QSharedPointer &p_node, const QString &p_destFolder, QString &p_destFilePath, QString &p_attachmentFolder) { // Copy source file itself. auto nodeFilePath = p_node->fetchAbsolutePath(); p_destFilePath = PathUtils::concatenateFilePath(p_destFolder, PathUtils::fileName(nodeFilePath)); p_destFilePath = getBackend()->renameIfExistsCaseInsensitive(p_destFilePath); getBackend()->copyFile(nodeFilePath, p_destFilePath); // Copy media files fetched from content. ContentMediaUtils::copyMediaFiles(p_node.data(), getBackend().data(), p_destFilePath); // Copy attachment folder. Rename attachment folder if conflicts. p_attachmentFolder = p_node->getAttachmentFolder(); if (!p_attachmentFolder.isEmpty()) { auto destAttachmentFolderPath = fetchNodeAttachmentFolder(p_destFilePath, p_attachmentFolder); ContentMediaUtils::copyAttachment(p_node.data(), getBackend().data(), p_destFilePath, destAttachmentFolderPath); } } QString VXNotebookConfigMgr::fetchNodeImageFolderPath(Node *p_node) { auto pa = PathUtils::concatenateFilePath(PathUtils::parentDirPath(p_node->fetchAbsolutePath()), getNotebook()->getImageFolder()); // Do not make the folder when it is a folder node request. if (p_node->hasContent()) { getBackend()->makePath(pa); } return pa; } QString VXNotebookConfigMgr::fetchNodeAttachmentFolderPath(Node *p_node) { auto notebookFolder = PathUtils::concatenateFilePath(PathUtils::parentDirPath(p_node->fetchAbsolutePath()), getNotebook()->getAttachmentFolder()); if (p_node->hasContent()) { auto nodeFolder = p_node->getAttachmentFolder(); if (nodeFolder.isEmpty()) { auto folderPath = fetchNodeAttachmentFolder(p_node->fetchAbsolutePath(), nodeFolder); p_node->setAttachmentFolder(nodeFolder); saveNode(p_node); getBackend()->makePath(folderPath); return folderPath; } else { return PathUtils::concatenateFilePath(notebookFolder, nodeFolder); } } else { // Do not make the folder when it is a folder node request. return notebookFolder; } } QString VXNotebookConfigMgr::fetchNodeAttachmentFolder(const QString &p_nodePath, QString &p_folderName) { auto notebookFolder = PathUtils::concatenateFilePath(PathUtils::parentDirPath(p_nodePath), getNotebook()->getAttachmentFolder()); if (p_folderName.isEmpty()) { p_folderName = FileUtils::generateUniqueFileName(notebookFolder, QString(), QString()); } else if (FileUtils::childExistsCaseInsensitive(notebookFolder, p_folderName)) { p_folderName = FileUtils::generateFileNameWithSequence(notebookFolder, p_folderName, QString()); } return PathUtils::concatenateFilePath(notebookFolder, p_folderName); } bool VXNotebookConfigMgr::isBuiltInFile(const Node *p_node, const QString &p_name) const { static const QString backupFileExtension = ConfigMgr::getInst().getEditorConfig().getBackupFileExtension().toLower(); const auto name = p_name.toLower(); if (name == c_nodeConfigName || name == QStringLiteral("_vnote.json") || name.endsWith(backupFileExtension)) { return true; } return BundleNotebookConfigMgr::isBuiltInFile(p_node, p_name); } bool VXNotebookConfigMgr::isBuiltInFolder(const Node *p_node, const QString &p_name) const { const auto name = p_name.toLower(); const auto &nb = getNotebook(); if (name == nb->getImageFolder().toLower() || name == nb->getAttachmentFolder().toLower() || name == QStringLiteral("_v_images") || name == QStringLiteral("_v_attachments")) { return true; } return BundleNotebookConfigMgr::isBuiltInFolder(p_node, p_name); } QSharedPointer VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_srcPath, Node *p_dest) { // Skip copy if it already locates in dest folder. auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchAbsolutePath(), PathUtils::fileName(p_srcPath)); if (!PathUtils::areSamePaths(p_srcPath, destFilePath)) { // Copy source file itself. destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath); getBackend()->copyFile(p_srcPath, destFilePath); // Copy media files fetched from content. ContentMediaUtils::copyMediaFiles(p_srcPath, getBackend().data(), destFilePath); } ensureNodeInDatabase(p_dest); const auto name = PathUtils::fileName(destFilePath); auto destNode = p_dest->findChild(name, true); if (destNode) { // Already have the node. ensureNodeInDatabase(destNode.data()); return destNode; } // Create a file node. destNode = QSharedPointer::create(name, NodeParameters(), getNotebook(), p_dest); destNode->setExists(true); addChildNode(p_dest, destNode); writeNodeConfig(p_dest); addNodeToDatabase(destNode.data()); return destNode; } QSharedPointer VXNotebookConfigMgr::copyFolderAsChildOf(const QString &p_srcPath, Node *p_dest) { // Skip copy if it already locates in dest folder. auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchAbsolutePath(), PathUtils::fileName(p_srcPath)); if (!PathUtils::areSamePaths(p_srcPath, destFolderPath)) { destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath); // Copy folder. getBackend()->copyDir(p_srcPath, destFolderPath); } ensureNodeInDatabase(p_dest); const auto name = PathUtils::fileName(destFolderPath); auto destNode = p_dest->findChild(name, true); if (destNode) { // Already have the node. ensureNodeInDatabase(destNode.data()); return destNode; } // Create a folder node. auto notebook = getNotebook(); destNode = QSharedPointer::create(name, notebook, p_dest); destNode->loadCompleteInfo(NodeParameters(), QVector>()); destNode->setExists(true); writeNodeConfig(destNode.data()); addChildNode(p_dest, destNode); writeNodeConfig(p_dest); addNodeToDatabase(destNode.data()); return destNode; } void VXNotebookConfigMgr::inheritNodeFlags(const Node *p_node, Node *p_child) const { if (p_node->isReadOnly()) { markNodeReadOnly(p_child); } } QVector> VXNotebookConfigMgr::fetchExternalChildren(Node *p_node) const { Q_ASSERT(p_node->isContainer()); QVector> externalNodes; auto dir = p_node->toDir(); // Folders. { const auto folders = dir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot); for (const auto &folder : folders) { if (isBuiltInFolder(p_node, folder)) { continue; } if (isExcludedFromExternalNode(folder)) { continue; } if (p_node->containsContainerChild(folder)) { continue; } externalNodes.push_back(QSharedPointer::create(p_node, folder, ExternalNode::Type::Folder)); } } // Files. { const auto files = dir.entryList(QDir::Files); for (const auto &file : files) { if (isBuiltInFile(p_node, file)) { continue; } if (isExcludedFromExternalNode(file)) { continue; } if (p_node->containsContentChild(file)) { continue; } externalNodes.push_back(QSharedPointer::create(p_node, file, ExternalNode::Type::File)); } } return externalNodes; } bool VXNotebookConfigMgr::isExcludedFromExternalNode(const QString &p_name) const { for (const auto ®Exp : s_externalNodeExcludePatterns) { if (regExp.exactMatch(p_name)) { return true; } } return false; } bool VXNotebookConfigMgr::checkNodeExists(Node *p_node) { bool exists = getBackend()->exists(p_node->fetchPath()); p_node->setExists(exists); return exists; } QStringList VXNotebookConfigMgr::scanAndImportExternalFiles(Node *p_node) { QStringList files; if (!p_node->isContainer()) { return files; } // External nodes. auto dir = p_node->toDir(); auto externalNodes = fetchExternalChildren(p_node); for (const auto &node : externalNodes) { Node::Flags flags = Node::Flag::Content; if (node->isFolder()) { if (isLikelyImageFolder(dir.filePath(node->getName()))) { qWarning() << "skip importing folder containing only images" << node->getName(); continue; } flags = Node::Flag::Container; } addAsNode(p_node, flags, node->getName(), NodeParameters()); files << dir.filePath(node->getName()); } // Children folders (including newly-added external nodes). for (const auto &child : p_node->getChildrenRef()) { if (child->isContainer()) { files << scanAndImportExternalFiles(child.data()); } } return files; } bool VXNotebookConfigMgr::isLikelyImageFolder(const QString &p_dirPath) { QDir dir(p_dirPath); const auto folders = dir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot); if (!folders.isEmpty()) { return false; } const auto files = dir.entryList(QDir::Files); if (files.isEmpty()) { return false; } for (const auto &file : files) { if (!FileUtils::isImage(dir.filePath(file))) { return false; } } return true; } NotebookDatabaseAccess *VXNotebookConfigMgr::getDatabaseAccess() const { return static_cast(getNotebook())->getDatabaseAccess(); } void VXNotebookConfigMgr::updateNodeInDatabase(Node *p_node) { Q_ASSERT(sameNotebook(p_node)); getDatabaseAccess()->updateNode(p_node); } void VXNotebookConfigMgr::ensureNodeInDatabase(Node *p_node) { if (!p_node) { return; } Q_ASSERT(sameNotebook(p_node)); auto db = getDatabaseAccess(); if (db->existsNode(p_node)) { return; } ensureNodeInDatabase(p_node->getParent()); db->addNode(p_node, false); db->clearObsoleteNodes(); } void VXNotebookConfigMgr::addNodeToDatabase(Node *p_node) { Q_ASSERT(sameNotebook(p_node)); auto db = getDatabaseAccess(); db->addNode(p_node, false); db->clearObsoleteNodes(); } bool VXNotebookConfigMgr::nodeExistsInDatabase(const Node *p_node) { Q_ASSERT(sameNotebook(p_node)); return getDatabaseAccess()->existsNode(p_node); } void VXNotebookConfigMgr::removeNodeFromDatabase(const Node *p_node) { Q_ASSERT(sameNotebook(p_node)); getDatabaseAccess()->removeNode(p_node); } bool VXNotebookConfigMgr::sameNotebook(const Node *p_node) const { return p_node ? p_node->getNotebook() == getNotebook() : true; }