#include "vdirectory.h" #include #include #include #include #include "vconfigmanager.h" #include "vnotefile.h" #include "utils/vutils.h" extern VConfigManager *g_config; VDirectory::VDirectory(VNotebook *p_notebook, VDirectory *p_parent, const QString &p_name, QDateTime p_createdTimeUtc) : QObject(p_parent), m_notebook(p_notebook), m_name(p_name), m_opened(false), m_expanded(false), m_createdTimeUtc(p_createdTimeUtc) { } bool VDirectory::open() { if (m_opened) { return true; } V_ASSERT(m_subDirs.isEmpty() && m_files.isEmpty()); QString path = fetchPath(); QJsonObject configJson = VConfigManager::readDirectoryConfig(path); if (configJson.isEmpty()) { qWarning() << "invalid directory configuration in path" << path; return false; } // created_time m_createdTimeUtc = QDateTime::fromString(configJson[DirConfig::c_createdTime].toString(), Qt::ISODate); // [sub_directories] section QJsonArray dirJson = configJson[DirConfig::c_subDirectories].toArray(); for (int i = 0; i < dirJson.size(); ++i) { QJsonObject dirItem = dirJson[i].toObject(); VDirectory *dir = new VDirectory(m_notebook, this, dirItem[DirConfig::c_name].toString()); m_subDirs.append(dir); } // [files] section QJsonArray fileJson = configJson[DirConfig::c_files].toArray(); for (int i = 0; i < fileJson.size(); ++i) { QJsonObject fileItem = fileJson[i].toObject(); VNoteFile *file = VNoteFile::fromJson(this, fileItem, FileType::Note, true); m_files.append(file); } m_opened = true; return true; } void VDirectory::close() { if (!m_opened) { return; } for (int i = 0; i < m_subDirs.size(); ++i) { VDirectory *dir = m_subDirs[i]; dir->close(); delete dir; } m_subDirs.clear(); for (int i = 0; i < m_files.size(); ++i) { VNoteFile *file = m_files[i]; file->close(); delete file; } m_files.clear(); m_opened = false; } QString VDirectory::fetchBasePath() const { return VUtils::basePathFromPath(fetchPath()); } QString VDirectory::fetchPath(const VDirectory *p_dir) const { if (!p_dir) { return ""; } VDirectory *parentDir = (VDirectory *)p_dir->parent(); if (parentDir) { // Not the root directory return QDir(parentDir->fetchPath()).filePath(p_dir->getName()); } else { return p_dir->getNotebook()->getPath(); } } QString VDirectory::fetchRelativePath(const VDirectory *p_dir) const { if (!p_dir) { return ""; } VDirectory *parentDir = (VDirectory *)p_dir->parent(); if (parentDir) { // Not the root directory return QDir(fetchRelativePath(parentDir)).filePath(p_dir->getName()); } else { return ""; } } QJsonObject VDirectory::toConfigJson() const { QJsonObject dirJson; dirJson[DirConfig::c_version] = "1"; dirJson[DirConfig::c_createdTime] = m_createdTimeUtc.toString(Qt::ISODate); QJsonArray subDirs; for (int i = 0; i < m_subDirs.size(); ++i) { QJsonObject item; item[DirConfig::c_name] = m_subDirs[i]->getName(); subDirs.append(item); } dirJson[DirConfig::c_subDirectories] = subDirs; QJsonArray files; for (int i = 0; i < m_files.size(); ++i) { files.append(m_files[i]->toConfigJson()); } dirJson[DirConfig::c_files] = files; return dirJson; } bool VDirectory::readConfig() { return true; } bool VDirectory::writeToConfig() const { QJsonObject json = toConfigJson(); if (!getParentDirectory()) { // Root directory. addNotebookConfig(json); } qDebug() << "folder" << m_name << "write to config" << json; return writeToConfig(json); } bool VDirectory::updateFileConfig(const VNoteFile *p_file) { Q_ASSERT(m_opened); Q_UNUSED(p_file); return writeToConfig(); } bool VDirectory::writeToConfig(const QJsonObject &p_json) const { return VConfigManager::writeDirectoryConfig(fetchPath(), p_json); } void VDirectory::addNotebookConfig(QJsonObject &p_json) const { V_ASSERT(!getParentDirectory()); QJsonObject nbJson = m_notebook->toConfigJsonNotebook(); // Merge it to p_json. for (auto it = nbJson.begin(); it != nbJson.end(); ++it) { V_ASSERT(!p_json.contains(it.key())); p_json[it.key()] = it.value(); } } VDirectory *VDirectory::createSubDirectory(const QString &p_name, QString *p_errMsg) { Q_ASSERT(!p_name.isEmpty()); if (!open()) { VUtils::addErrMsg(p_errMsg, tr("Fail to open folder %1.").arg(m_name)); return NULL; } QDir dir(fetchPath()); if (dir.exists(p_name)) { VUtils::addErrMsg(p_errMsg, tr("%1 already exists in directory %2.") .arg(p_name) .arg(fetchPath())); return NULL; } if (!dir.mkdir(p_name)) { VUtils::addErrMsg(p_errMsg, tr("Fail to create folder in %1.") .arg(m_name)); return NULL; } VDirectory *ret = new VDirectory(m_notebook, this, p_name, QDateTime::currentDateTimeUtc()); if (!ret->writeToConfig()) { VUtils::addErrMsg(p_errMsg, tr("Fail to write configuration of folder %1.") .arg(p_name)); dir.rmdir(p_name); delete ret; return NULL; } m_subDirs.append(ret); if (!writeToConfig()) { VUtils::addErrMsg(p_errMsg, tr("Fail to write configuration of folder %1.") .arg(p_name)); QDir subdir(dir.filePath(p_name)); subdir.removeRecursively(); delete ret; m_subDirs.removeLast(); return NULL; } return ret; } VDirectory *VDirectory::findSubDirectory(const QString &p_name, bool p_caseSensitive) { if (!open()) { return NULL; } QString name = p_caseSensitive ? p_name : p_name.toLower(); for (int i = 0; i < m_subDirs.size(); ++i) { if (name == (p_caseSensitive ? m_subDirs[i]->getName() : m_subDirs[i]->getName().toLower())) { return m_subDirs[i]; } } return NULL; } VNoteFile *VDirectory::findFile(const QString &p_name, bool p_caseSensitive) { if (!open()) { return NULL; } QString name = p_caseSensitive ? p_name : p_name.toLower(); for (int i = 0; i < m_files.size(); ++i) { if (name == (p_caseSensitive ? m_files[i]->getName() : m_files[i]->getName().toLower())) { return m_files[i]; } } return NULL; } bool VDirectory::containsFile(const VFile *p_file) const { if (!p_file) { return false; } QObject *paDir = p_file->parent(); while (paDir) { if (paDir == this) { return true; } else { paDir = paDir->parent(); } } return false; } VNoteFile *VDirectory::createFile(const QString &p_name, bool p_front) { Q_ASSERT(!p_name.isEmpty()); if (!open()) { return NULL; } QString path = fetchPath(); QFile file(QDir(path).filePath(p_name)); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "fail to create file" << p_name; return NULL; } file.close(); QDateTime dateTime = QDateTime::currentDateTimeUtc(); VNoteFile *ret = new VNoteFile(this, p_name, FileType::Note, true, dateTime, dateTime); int idx = -1; if (p_front) { m_files.prepend(ret); idx = 0; } else { m_files.append(ret); idx = m_files.size() - 1; } if (!writeToConfig()) { file.remove(); delete ret; m_files.remove(idx); return NULL; } qDebug() << "note" << p_name << "created in folder" << m_name; return ret; } bool VDirectory::addFile(VNoteFile *p_file, int p_index) { if (!open()) { return false; } if (p_index == -1) { m_files.append(p_file); } else { m_files.insert(p_index, p_file); } if (!writeToConfig()) { if (p_index == -1) { m_files.removeLast(); } else { m_files.remove(p_index); } return false; } p_file->setParent(this); // Add tags from this file to the notebook. const QStringList &tags = p_file->getTags(); for (auto const & tag : tags) { m_notebook->addTag(tag); } qDebug() << "note" << p_file->getName() << "added to folder" << m_name; return true; } VNoteFile *VDirectory::addFile(const QString &p_name, int p_index) { if (!open() || p_name.isEmpty()) { return NULL; } QDateTime dateTime = QDateTime::currentDateTimeUtc(); VNoteFile *file = new VNoteFile(this, p_name, FileType::Note, true, dateTime, dateTime); if (!file) { return NULL; } if (!addFile(file, p_index)) { delete file; return NULL; } return file; } bool VDirectory::addSubDirectory(VDirectory *p_dir, int p_index) { if (!open()) { return false; } if (p_index == -1) { m_subDirs.append(p_dir); } else { m_subDirs.insert(p_index, p_dir); } if (!writeToConfig()) { if (p_index == -1) { m_subDirs.removeLast(); } else { m_subDirs.remove(p_index); } return false; } p_dir->setParent(this); p_dir->m_notebook = m_notebook; m_notebook->addTags(p_dir); qDebug() << "folder" << p_dir->getName() << "added to folder" << m_name; return true; } VDirectory *VDirectory::addSubDirectory(const QString &p_name, int p_index) { if (!open() || p_name.isEmpty()) { return NULL; } VDirectory *dir = new VDirectory(m_notebook, this, p_name, QDateTime::currentDateTimeUtc()); if (!dir) { return NULL; } if (!addSubDirectory(dir, p_index)) { delete dir; return NULL; } return dir; } bool VDirectory::deleteDirectory(bool p_skipRecycleBin, QString *p_errMsg) { Q_ASSERT(!m_opened); Q_ASSERT(parent()); // Delete the entire directory. bool ret = true; QString dirPath = fetchPath(); if (!VUtils::deleteDirectory(m_notebook, dirPath, p_skipRecycleBin)) { VUtils::addErrMsg(p_errMsg, tr("Fail to delete the directory %1.").arg(dirPath)); ret = false; } return ret; } bool VDirectory::deleteDirectory(VDirectory *p_dir, bool p_skipRecycleBin, QString *p_errMsg) { p_dir->close(); bool ret = true; QString name = p_dir->getName(); QString path = p_dir->fetchPath(); if (!p_dir->deleteDirectory(p_skipRecycleBin, p_errMsg)) { ret = false; } VDirectory *paDir = p_dir->getParentDirectory(); Q_ASSERT(paDir); if (!paDir->removeSubDirectory(p_dir)) { VUtils::addErrMsg(p_errMsg, tr("Fail to remove the folder from the folder configuration.")); ret = false; } delete p_dir; return ret; } bool VDirectory::removeSubDirectory(VDirectory *p_dir) { V_ASSERT(m_opened); V_ASSERT(p_dir); int index = m_subDirs.indexOf(p_dir); V_ASSERT(index != -1); m_subDirs.remove(index); if (!writeToConfig()) { return false; } return true; } bool VDirectory::removeFile(VNoteFile *p_file) { V_ASSERT(m_opened); V_ASSERT(p_file); int index = m_files.indexOf(p_file); V_ASSERT(index != -1); m_files.remove(index); if (!writeToConfig()) { return false; } return true; } bool VDirectory::rename(const QString &p_name) { if (m_name == p_name) { return true; } QString oldName = m_name; VDirectory *parentDir = getParentDirectory(); V_ASSERT(parentDir); // Rename it in disk. QDir dir(parentDir->fetchPath()); if (!dir.rename(m_name, p_name)) { qWarning() << "fail to rename folder" << m_name << "to" << p_name << "in disk"; return false; } m_name = p_name; // Update parent's config file if (!parentDir->writeToConfig()) { m_name = oldName; dir.rename(p_name, m_name); return false; } qDebug() << "folder renamed from" << oldName << "to" << m_name; return true; } bool VDirectory::copyDirectory(VDirectory *p_destDir, const QString &p_destName, VDirectory *p_dir, bool p_isCut, VDirectory **p_targetDir, QString *p_errMsg) { bool ret = true; *p_targetDir = NULL; QString srcPath = QDir::cleanPath(p_dir->fetchPath()); QString destPath = QDir::cleanPath(QDir(p_destDir->fetchPath()).filePath(p_destName)); if (VUtils::equalPath(srcPath, destPath)) { *p_targetDir = p_dir; return false; } if (!p_destDir->isOpened()) { VUtils::addErrMsg(p_errMsg, tr("Fail to open target folder.")); return false; } QString opStr = p_isCut ? tr("cut") : tr("copy"); VDirectory *paDir = p_dir->getParentDirectory(); Q_ASSERT(paDir->isOpened()); // Copy the directory. if (!VUtils::copyDirectory(srcPath, destPath, p_isCut)) { VUtils::addErrMsg(p_errMsg, tr("Fail to %1 the folder.").arg(opStr)); qWarning() << "fail to" << opStr << "the folder directory" << srcPath << "to" << destPath; return false; } // Add directory to VDirectory. VDirectory *destDir = NULL; if (p_isCut) { paDir->removeSubDirectory(p_dir); p_dir->setName(p_destName); // Add the directory to new dir's config if (p_destDir->addSubDirectory(p_dir, -1)) { destDir = p_dir; } else { destDir = NULL; } } else { destDir = p_destDir->addSubDirectory(p_destName, -1); } if (!destDir) { VUtils::addErrMsg(p_errMsg, tr("Fail to add the folder to target folder's configuration.")); return false; } *p_targetDir = destDir; return ret; } void VDirectory::setExpanded(bool p_expanded) { if (p_expanded) { V_ASSERT(m_opened); } m_expanded = p_expanded; } VNoteFile *VDirectory::tryLoadFile(QStringList &p_filePath) { if (p_filePath.isEmpty()) { return NULL; } bool opened = isOpened(); if (!open()) { return NULL; } VNoteFile *file = NULL; #if defined(Q_OS_WIN) bool caseSensitive = false; #else bool caseSensitive = true; #endif if (p_filePath.size() == 1) { // File. file = findFile(p_filePath.at(0), caseSensitive); } else { // Directory. VDirectory *dir = findSubDirectory(p_filePath.at(0), caseSensitive); if (dir) { p_filePath.removeFirst(); file = dir->tryLoadFile(p_filePath); } } if (!file && !opened) { close(); } return file; } VDirectory *VDirectory::tryLoadDirectory(QStringList &p_filePath) { if (p_filePath.isEmpty()) { return NULL; } bool opened = isOpened(); if (!open()) { return NULL; } #if defined(Q_OS_WIN) bool caseSensitive = false; #else bool caseSensitive = true; #endif VDirectory *dir = findSubDirectory(p_filePath.at(0), caseSensitive); if (dir) { if (p_filePath.size() > 1) { p_filePath.removeFirst(); dir = dir->tryLoadDirectory(p_filePath); } } if (!dir && !opened) { close(); } return dir; } bool VDirectory::sortFiles(const QVector &p_sortedIdx) { V_ASSERT(m_opened); V_ASSERT(p_sortedIdx.size() == m_files.size()); auto ori = m_files; for (int i = 0; i < p_sortedIdx.size(); ++i) { m_files[i] = ori[p_sortedIdx[i]]; } bool ret = true; if (!writeToConfig()) { qWarning() << "fail to reorder files in config" << p_sortedIdx; m_files = ori; ret = false; } return ret; } bool VDirectory::sortSubDirectories(const QVector &p_sortedIdx) { V_ASSERT(m_opened); V_ASSERT(p_sortedIdx.size() == m_subDirs.size()); auto ori = m_subDirs; for (int i = 0; i < p_sortedIdx.size(); ++i) { m_subDirs[i] = ori[p_sortedIdx[i]]; } bool ret = true; if (!writeToConfig()) { qWarning() << "fail to reorder sub-directories in config" << p_sortedIdx; m_subDirs = ori; ret = false; } return ret; } QList VDirectory::collectFiles() { QList files; bool opened = isOpened(); if (!opened && !open()) { qWarning() << "fail to open directory" << fetchPath(); return files; } // Files. for (auto const & file : m_files) { files.append(file->fetchPath()); } // Subfolders. for (auto const & dir : m_subDirs) { files.append(dir->collectFiles()); } if (!opened) { close(); } return files; } QStringList VDirectory::collectTags() { QStringList tags; bool opened = isOpened(); if (!opened && !open()) { qWarning() << "fail to open directory" << fetchPath(); return tags; } // Files. for (auto const & file : m_files) { tags.append(file->getTags()); } // Subfolders. for (auto const & dir : m_subDirs) { tags.append(dir->collectTags()); } if (!opened) { close(); } return tags; } VDirectory *VDirectory::buildDirectory(const QString &p_path, VDirectory *p_parent, QString *p_errMsg) { VDirectory *ret = new VDirectory(p_parent->getNotebook(), p_parent, VUtils::directoryNameFromPath(p_path), QDateTime::currentDateTimeUtc()); QDir rootDir(p_path); // Process all the folders. QVector &subdirs = ret->getSubDirs(); QFileInfoList dirList = rootDir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); for (auto const & sub : dirList) { VDirectory *dir = VDirectory::buildDirectory(sub.absoluteFilePath(), ret, p_errMsg); if (dir) { subdirs.append(dir); } } // Process all the files. QVector &files = ret->getFiles(); QDateTime dateTime = QDateTime::currentDateTimeUtc(); QList suffixes = g_config->getDocSuffixes()[(int)DocType::Markdown]; QStringList filters; for (auto const & suf : suffixes) { filters << ("*." + suf); } QStringList fileList = rootDir.entryList(filters, QDir::Files); for (auto const & fileName : fileList) { VNoteFile *file = new VNoteFile(ret, fileName, FileType::Note, true, dateTime, dateTime); if (!file) { VUtils::addErrMsg(p_errMsg, tr("Skip file %1.").arg(rootDir.absoluteFilePath(fileName))); } else { files.append(file); } } if (subdirs.isEmpty() && files.isEmpty()) { delete ret; VUtils::addErrMsg(p_errMsg, tr("Skip folder %1.").arg(p_path)); return NULL; } if (!ret->writeToConfig()) { delete ret; VUtils::addErrMsg(p_errMsg, tr("Fail to write configuration of folder %1.").arg(p_path)); return NULL; } return ret; }