vnote/src/vdirectory.cpp
Le Tan 3724eb35dd refactor VDirectoryTree
- Refine folder deletion logics;
- Refine folder copy/paste logics;
- Add folder sort logics;
2017-09-30 09:51:40 +08:00

719 lines
17 KiB
C++

#include "vdirectory.h"
#include <QDir>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#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(fetchPath(parentDir)).filePath(p_dir->getName());
} else {
return m_notebook->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)
{
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);
m_files.append(ret);
if (!writeToConfig()) {
file.remove();
delete ret;
m_files.removeLast();
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);
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);
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;
}
qDebug() << "copyDirectory:" << p_dir << "to" << destDir;
*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)
{
qDebug() << "directory" << m_name << "tryLoadFile()" << p_filePath.join("/");
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)
{
qDebug() << "directory" << m_name << "tryLoadDirectory()" << p_filePath.join("/");
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<int> &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<int> &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;
}