hard days for VNoteX project

Never say "refactor" again!!!
This commit is contained in:
Le Tan 2020-11-28 22:42:24 +08:00
parent 05d89745c1
commit 52702a32e9
770 changed files with 220079 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
vnotex.pro.user
vnotex.pro.user.*
.ccls
compile_commands.json
compile_flags.txt
*.plist

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "libs/vtextedit"]
path = libs/vtextedit
url = https://github.com/vnotex/vtextedit.git

33
coc_update.cmd Normal file
View File

@ -0,0 +1,33 @@
@echo off
rem Update .ccls project file for ccls LPS and compile_flags.txt for clangd
if "%~1"=="" (
echo missing argument: the location of Qt's include directory
EXIT /B 0
)
set qt_inc=%~1
set qt_inc=%qt_inc:\=\\%
(
echo clang
echo -fcxx-exceptions
echo -std=c++14
echo -Isrc\\core
echo -Isrc
echo -Ilibs\\vtextedit\\src\\editor\\include
echo -Ilibs\\vtitlebar\\src
echo -I%qt_inc%
echo -I%qt_inc%\\QtCore
echo -I%qt_inc%\\QtWebEngineWidgets
echo -I%qt_inc%\\QtSvg
echo -I%qt_inc%\\QtPrintSupport
echo -I%qt_inc%\\QtWidgets
echo -I%qt_inc%\\QtWebEngineCore
echo -I%qt_inc%\\QtGui
echo -I%qt_inc%\\QtWebChannel
echo -I%qt_inc%\\QtNetwork
echo -I%qt_inc%\\QtTest
) > ".ccls"
copy /Y .ccls compile_flags.txt

5
libs/libs.pro Normal file
View File

@ -0,0 +1,5 @@
TEMPLATE = subdirs
SUBDIRS += \
vtextedit \
vtitlebar

1
libs/vtextedit Submodule

@ -0,0 +1 @@
Subproject commit a75c9b8dd374dd4fed1cfd66b79408ecfea990c9

View File

@ -0,0 +1,22 @@
#include "vtitlebar.h"
#include <QStyleOption>
#include <QPainter>
using namespace vnotex;
VTitleBar::VTitleBar(QWidget *p_parent)
: QWidget(p_parent)
{
}
void VTitleBar::paintEvent(QPaintEvent *p_event)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
QWidget::paintEvent(p_event);
}

View File

@ -0,0 +1,19 @@
#ifndef VTITLEBAR_H
#define VTITLEBAR_H
#include <QWidget>
namespace vnotex
{
class VTitleBar : public QWidget
{
Q_OBJECT
public:
explicit VTitleBar(QWidget *p_parent = nullptr);
protected:
void paintEvent(QPaintEvent *p_event) Q_DECL_OVERRIDE;
};
}
#endif // VTITLEBAR_H

View File

@ -0,0 +1,118 @@
#include "vtoolbar.h"
#include <QDebug>
#include <QMouseEvent>
#include <QCoreApplication>
#include <QToolButton>
using namespace vnotex;
VToolBar::VToolBar(QWidget *p_parent)
: QToolBar(p_parent),
m_window(p_parent)
{
setupUI();
m_window->installEventFilter(this);
}
VToolBar::VToolBar(const QString &p_title, QWidget *p_parent)
: QToolBar(p_title, p_parent),
m_window(p_parent)
{
setupUI();
m_window->installEventFilter(this);
}
void VToolBar::setupUI()
{
}
void VToolBar::mousePressEvent(QMouseEvent *p_event)
{
QToolBar::mousePressEvent(p_event);
m_lastPos = p_event->pos();
}
void VToolBar::mouseDoubleClickEvent(QMouseEvent *p_event)
{
QToolBar::mouseDoubleClickEvent(p_event);
m_ignoreNextMove = true;
maximizeRestoreWindow();
}
void VToolBar::maximizeRestoreWindow()
{
m_window->isMaximized() ? m_window->showNormal() : m_window->showMaximized();
}
void VToolBar::mouseMoveEvent(QMouseEvent *p_event)
{
auto delta = p_event->pos() - m_lastPos;
if (!m_ignoreNextMove && !m_lastPos.isNull() && (qAbs(delta.x()) > 10 || qAbs(delta.y()) > 10)) {
if (m_window->isMaximized()) {
m_window->showNormal();
} else {
m_window->move(p_event->globalPos() - m_lastPos);
}
}
QToolBar::mouseMoveEvent(p_event);
}
void VToolBar::mouseReleaseEvent(QMouseEvent *p_event)
{
QToolBar::mouseReleaseEvent(p_event);
m_ignoreNextMove = false;
m_lastPos = QPoint();
}
void VToolBar::addTitleBarIcons(const QIcon &p_minimizeIcon,
const QIcon &p_maximizeIcon,
const QIcon &p_restoreIcon,
const QIcon &p_closeIcon)
{
addSeparator();
addAction(p_minimizeIcon, tr("Minimize"),
this, [this]() {
m_window->showMinimized();
});
m_maximizeIcon = p_maximizeIcon;
m_restoreIcon = p_restoreIcon;
m_maximizeAct = addAction(p_maximizeIcon, tr("Maximize"),
this, [this]() {
maximizeRestoreWindow();
});
{
auto closeAct = addAction(p_closeIcon, tr("Close"),
this, [this]() {
m_window->close();
});
auto btn = static_cast<QToolButton *>(widgetForAction(closeAct));
btn->setProperty("DangerousButton", true);
}
updateMaximizeAct();
}
bool VToolBar::eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_obj == m_window) {
if (p_event->type() == QEvent::WindowStateChange) {
updateMaximizeAct();
}
}
return QToolBar::eventFilter(p_obj, p_event);
}
void VToolBar::updateMaximizeAct()
{
if (m_window->isMaximized()) {
m_maximizeAct->setIcon(m_restoreIcon);
m_maximizeAct->setText(tr("Restore Down"));
} else {
m_maximizeAct->setIcon(m_maximizeIcon);
m_maximizeAct->setText(tr("Maximize"));
}
}

View File

@ -0,0 +1,54 @@
#ifndef VTOOLBAR_H
#define VTOOLBAR_H
#include <QToolBar>
#include <QIcon>
namespace vnotex
{
class VToolBar : public QToolBar
{
Q_OBJECT
public:
explicit VToolBar(QWidget *p_parent = nullptr);
VToolBar(const QString &p_title, QWidget *p_parent = nullptr);
void addTitleBarIcons(const QIcon &p_minimizeIcon,
const QIcon &p_maximizeIcon,
const QIcon &p_restoreIcon,
const QIcon &p_closeIcon);
protected:
void mousePressEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
void mouseDoubleClickEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
private:
void setupUI();
void maximizeRestoreWindow();
void updateMaximizeAct();
QPoint m_lastPos;
bool m_ignoreNextMove = false;
QWidget *m_window = nullptr;
QAction *m_maximizeAct = nullptr;
QIcon m_maximizeIcon;
QIcon m_restoreIcon;
};
}
#endif // VTOOLBAR_H

View File

@ -0,0 +1,16 @@
QT += core gui widgets
TARGET = vtitlebar
TEMPLATE = lib
# CONFIG += warn_off
CONFIG += staticlib
SOURCES += \
src/vtitlebar.cpp \
src/vtoolbar.cpp
HEADERS += \
src/vtitlebar.h \
src/vtoolbar.h

View File

@ -0,0 +1,17 @@
INCLUDEPATH *= $$PWD/src
DEPENDPATH *= $$PWD/src
OUT_FOLDER = $$absolute_path($$relative_path($$PWD, $$_PRO_FILE_PWD_), $$OUT_PWD)
win32:CONFIG(release, debug|release) {
LIBS += $$OUT_FOLDER/release/vtitlebar.lib
# For static library, we need to add this depends to let Qt re-build the target
# when there is a change in the library.
PRE_TARGETDEPS += $$OUT_FOLDER/release/vtitlebar.lib
} else:win32:CONFIG(debug, debug|release) {
LIBS += $$OUT_FOLDER/debug/vtitlebar.lib
PRE_TARGETDEPS += $$OUT_FOLDER/debug/vtitlebar.lib
} else:unix {
LIBS += $$OUT_FOLDER/libvtitlebar.a
PRE_TARGETDEPS += $$OUT_FOLDER/libvtitlebar.a
}

552
src/core/buffer/buffer.cpp Normal file
View File

@ -0,0 +1,552 @@
#include "buffer.h"
#include <QTimer>
#include <notebook/node.h>
#include <utils/fileutils.h>
#include <widgets/viewwindow.h>
#include <utils/pathutils.h>
#include <core/configmgr.h>
#include <core/editorconfig.h>
#include "bufferprovider.h"
#include "exception.h"
using namespace vnotex;
static vnotex::ID generateBufferID()
{
static vnotex::ID id = 0;
return ++id;
}
Buffer::Buffer(const BufferParameters &p_parameters,
QObject *p_parent)
: QObject(p_parent),
m_provider(p_parameters.m_provider),
c_id(generateBufferID()),
m_readOnly(m_provider->isReadOnly())
{
m_autoSaveTimer = new QTimer(this);
m_autoSaveTimer->setSingleShot(true);
m_autoSaveTimer->setInterval(1000);
connect(m_autoSaveTimer, &QTimer::timeout,
this, &Buffer::autoSave);
readContent();
checkBackupFileOfPreviousSession();
}
Buffer::~Buffer()
{
Q_ASSERT(m_attachedViewWindowCount == 0);
Q_ASSERT(!m_viewWindowToSync);
Q_ASSERT(!isModified());
Q_ASSERT(m_backupFilePath.isEmpty());
}
int Buffer::getAttachViewWindowCount() const
{
return m_attachedViewWindowCount;
}
void Buffer::attachViewWindow(ViewWindow *p_win)
{
Q_UNUSED(p_win);
Q_ASSERT(!(m_state & StateFlag::Discarded));
++m_attachedViewWindowCount;
}
void Buffer::detachViewWindow(ViewWindow *p_win)
{
Q_ASSERT(p_win != m_viewWindowToSync);
--m_attachedViewWindowCount;
Q_ASSERT(m_attachedViewWindowCount >= 0);
if (m_attachedViewWindowCount == 0) {
emit attachedViewWindowEmpty();
}
}
ViewWindow *Buffer::createViewWindow(const QSharedPointer<FileOpenParameters> &p_paras, QWidget *p_parent)
{
auto window = createViewWindowInternal(p_paras, p_parent);
Q_ASSERT(window);
window->attachToBuffer(this);
return window;
}
bool Buffer::match(const Node *p_node) const
{
Q_ASSERT(p_node);
return m_provider->match(p_node);
}
bool Buffer::match(const QString &p_filePath) const
{
return m_provider->match(p_filePath);
}
QString Buffer::getName() const
{
return m_provider->getName();
}
QString Buffer::getPath() const
{
return m_provider->getPath();
}
QString Buffer::getContentPath() const
{
return m_provider->getContentPath();
}
QString Buffer::getContentBasePath() const
{
return PathUtils::parentDirPath(getContentPath());
}
ID Buffer::getID() const
{
return c_id;
}
const QString &Buffer::getContent() const
{
const_cast<Buffer *>(this)->syncContent();
return m_content;
}
void Buffer::setContent(const QString &p_content, int &p_revision)
{
m_viewWindowToSync = nullptr;
m_content = p_content;
p_revision = ++m_revision;
setModified(true);
m_autoSaveTimer->start();
emit contentsChanged();
}
void Buffer::invalidateContent(const ViewWindow *p_win,
const std::function<void(int)> &p_setRevision)
{
Q_ASSERT(!m_viewWindowToSync || m_viewWindowToSync == p_win);
++m_revision;
p_setRevision(m_revision);
m_viewWindowToSync = p_win;
m_autoSaveTimer->start();
emit contentsChanged();
}
int Buffer::getRevision() const
{
return m_revision;
}
void Buffer::syncContent(const ViewWindow *p_win)
{
if (m_viewWindowToSync == p_win) {
syncContent();
}
}
void Buffer::syncContent()
{
if (m_viewWindowToSync) {
// Need to sync content.
m_content = m_viewWindowToSync->getLatestContent();
m_viewWindowToSync = nullptr;
}
}
bool Buffer::isModified() const
{
return m_modified;
}
void Buffer::setModified(bool p_modified)
{
if (m_modified == p_modified) {
return;
}
m_modified = p_modified;
emit modified(m_modified);
}
bool Buffer::isReadOnly() const
{
return m_readOnly;
}
Buffer::OperationCode Buffer::save(bool p_force)
{
Q_ASSERT(!m_readOnly);
if (m_readOnly) {
return OperationCode::Failed;
}
if (m_modified
|| p_force
|| m_state & (StateFlag::FileMissingOnDisk | StateFlag::FileChangedOutside)) {
syncContent();
// We do not involve user here to handle file missing and changed outside cases.
// The active ViewWindow will check this periodically.
// Check if file still exists.
if (!p_force && !checkFileExistsOnDisk()) {
qWarning() << "failed to save buffer due to file missing on disk" << getPath();
return OperationCode::FileMissingOnDisk;
}
// Check if file is modified outside.
if (!p_force && checkFileChangedOutside()) {
qWarning() << "failed to save buffer due to file changed from outside" << getPath();
return OperationCode::FileChangedOutside;
}
m_provider->write(m_content);
setModified(false);
m_state &= ~(StateFlag::FileMissingOnDisk | StateFlag::FileChangedOutside);
}
return OperationCode::Success;
}
Buffer::OperationCode Buffer::reload()
{
// Check if file is missing.
if (!checkFileExistsOnDisk()) {
qWarning() << "failed to save buffer due to file missing on disk" << getPath();
return OperationCode::FileMissingOnDisk;
}
if (m_modified
|| m_state & (StateFlag::FileMissingOnDisk | StateFlag::FileChangedOutside)) {
readContent();
emit modified(m_modified);
emit contentsChanged();
}
return OperationCode::Success;
}
void Buffer::readContent()
{
m_content = m_provider->read();
++m_revision;
// Reset state.
m_viewWindowToSync = nullptr;
m_modified = false;
}
void Buffer::discard()
{
Q_ASSERT(!(m_state & StateFlag::Discarded));
Q_ASSERT(m_attachedViewWindowCount == 1);
m_autoSaveTimer->stop();
m_content.clear();
m_state |= StateFlag::Discarded;
++m_revision;
m_viewWindowToSync = nullptr;
m_modified = false;
}
void Buffer::close()
{
// Delete the backup file if exists.
m_autoSaveTimer->stop();
if (!m_backupFilePath.isEmpty()) {
FileUtils::removeFile(m_backupFilePath);
m_backupFilePath.clear();
}
}
QString Buffer::getImageFolderPath() const
{
return const_cast<Buffer *>(this)->m_provider->fetchImageFolderPath();
}
QString Buffer::insertImage(const QString &p_srcImagePath, const QString &p_imageFileName)
{
Q_UNUSED(p_srcImagePath);
Q_UNUSED(p_imageFileName);
Q_ASSERT_X(false, "insertImage", "image insert is not supported");
return QString();
}
QString Buffer::insertImage(const QImage &p_image, const QString &p_imageFileName)
{
Q_UNUSED(p_image);
Q_UNUSED(p_imageFileName);
Q_ASSERT_X(false, "insertImage", "image insert is not supported");
return QString();
}
void Buffer::removeImage(const QString &p_imagePath)
{
Q_UNUSED(p_imagePath);
Q_ASSERT_X(false, "removeImage", "image remove is not supported");
}
void Buffer::autoSave()
{
if (m_readOnly) {
m_autoSaveTimer->stop();
return;
}
if (m_state & (StateFlag::FileMissingOnDisk | StateFlag::FileChangedOutside)) {
qDebug() << "disable AutoSave due to file missing on disk or changed outside";
return;
}
Q_ASSERT(!(m_state & StateFlag::Discarded));
auto policy = ConfigMgr::getInst().getEditorConfig().getAutoSavePolicy();
switch (policy) {
case EditorConfig::AutoSavePolicy::None:
return;
case EditorConfig::AutoSavePolicy::AutoSave:
save(false);
break;
case EditorConfig::AutoSavePolicy::BackupFile:
writeBackupFile();
break;
}
}
void Buffer::writeBackupFile()
{
if (m_backupFilePath.isEmpty()) {
const auto &config = ConfigMgr::getInst().getEditorConfig();
QString backupDirPath(QDir(getContentBasePath()).filePath(config.getBackupFileDirectory()));
backupDirPath = QDir::cleanPath(backupDirPath);
auto backupFileName = FileUtils::generateFileNameWithSequence(backupDirPath,
getName(),
config.getBackupFileExtension());
QDir backupDir(backupDirPath);
backupDir.mkpath(backupDirPath);
m_backupFilePath = backupDir.filePath(backupFileName);
}
Q_ASSERT(m_backupFilePathOfPreviousSession.isEmpty());
// Just use FileUtils instead of notebook backend.
FileUtils::writeFile(m_backupFilePath, generateBackupFileHead() + getContent());
}
QString Buffer::generateBackupFileHead() const
{
return QString("vnotex_backup_file %1|").arg(getContentPath());
}
void Buffer::checkBackupFileOfPreviousSession()
{
const auto &config = ConfigMgr::getInst().getEditorConfig();
if (config.getAutoSavePolicy() != EditorConfig::AutoSavePolicy::BackupFile) {
return;
}
QString backupDirPath(QDir(getContentBasePath()).filePath(config.getBackupFileDirectory()));
backupDirPath = QDir::cleanPath(backupDirPath);
QDir backupDir(backupDirPath);
QStringList backupFiles;
{
const QString nameFilter = QString("%1*%2").arg(getName(), config.getBackupFileExtension());
backupFiles = backupDir.entryList(QStringList(nameFilter),
QDir::Files | QDir::Hidden | QDir::NoSymLinks | QDir::NoDotAndDotDot);
}
if (backupFiles.isEmpty()) {
return;
}
for (const auto &file : backupFiles) {
const auto filePath = backupDir.filePath(file);
if (isBackupFileOfBuffer(filePath)) {
const auto backupContent = readBackupFile(filePath);
if (backupContent == getContent()) {
// Found backup file with identical content.
// Just discard the backup file.
FileUtils::removeFile(filePath);
qInfo() << "delete identical backup file of previous session" << filePath;
} else {
m_backupFilePathOfPreviousSession = filePath;
qInfo() << "found backup file of previous session" << filePath;
}
break;
}
}
}
bool Buffer::isBackupFileOfBuffer(const QString &p_file) const
{
QFile file(p_file);
if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
return false;
}
QTextStream st(&file);
const auto head = st.readLine();
return head.startsWith(generateBackupFileHead());
}
const QString &Buffer::getBackupFileOfPreviousSession() const
{
return m_backupFilePathOfPreviousSession;
}
QString Buffer::readBackupFile(const QString &p_filePath)
{
auto content = FileUtils::readTextFile(p_filePath);
return content.mid(content.indexOf(QLatin1Char('|')) + 1);
}
void Buffer::discardBackupFileOfPreviousSession()
{
Q_ASSERT(!m_backupFilePathOfPreviousSession.isEmpty());
FileUtils::removeFile(m_backupFilePathOfPreviousSession);
qInfo() << "discard backup file of previous session" << m_backupFilePathOfPreviousSession;
m_backupFilePathOfPreviousSession.clear();
}
void Buffer::recoverFromBackupFileOfPreviousSession()
{
Q_ASSERT(!m_backupFilePathOfPreviousSession.isEmpty());
m_content = readBackupFile(m_backupFilePathOfPreviousSession);
m_provider->write(m_content);
++m_revision;
FileUtils::removeFile(m_backupFilePathOfPreviousSession);
qInfo() << "recover from backup file of previous session" << m_backupFilePathOfPreviousSession;
m_backupFilePathOfPreviousSession.clear();
// Reset state.
m_viewWindowToSync = nullptr;
m_modified = false;
emit modified(m_modified);
emit contentsChanged();
}
bool Buffer::isChildOf(const Node *p_node) const
{
return m_provider->isChildOf(p_node);
}
bool Buffer::isAttachmentSupported() const
{
return !m_readOnly && m_provider->isAttachmentSupported();
}
bool Buffer::hasAttachment() const
{
if (!isAttachmentSupported()) {
return false;
}
if (m_provider->getAttachmentFolder().isEmpty()) {
return false;
}
QDir dir(getAttachmentFolderPath());
return !dir.isEmpty();
}
QString Buffer::getAttachmentFolderPath() const
{
Q_ASSERT(isAttachmentSupported());
return const_cast<Buffer *>(this)->m_provider->fetchAttachmentFolderPath();
}
QStringList Buffer::addAttachment(const QString &p_destFolderPath, const QStringList &p_files)
{
if (p_files.isEmpty()) {
return QStringList();
}
auto destFolderPath = p_destFolderPath.isEmpty() ? getAttachmentFolderPath() : p_destFolderPath;
Q_ASSERT(PathUtils::pathContains(getAttachmentFolderPath(), destFolderPath));
auto files = m_provider->addAttachment(destFolderPath, p_files);
if (!files.isEmpty()) {
emit attachmentChanged();
}
return files;
}
QString Buffer::newAttachmentFile(const QString &p_destFolderPath, const QString &p_name)
{
Q_ASSERT(PathUtils::pathContains(getAttachmentFolderPath(), p_destFolderPath));
auto filePath = m_provider->newAttachmentFile(p_destFolderPath, p_name);
emit attachmentChanged();
return filePath;
}
QString Buffer::newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name)
{
Q_ASSERT(PathUtils::pathContains(getAttachmentFolderPath(), p_destFolderPath));
auto folderPath = m_provider->newAttachmentFolder(p_destFolderPath, p_name);
emit attachmentChanged();
return folderPath;
}
QString Buffer::renameAttachment(const QString &p_path, const QString &p_name)
{
Q_ASSERT(PathUtils::pathContains(getAttachmentFolderPath(), p_path));
return m_provider->renameAttachment(p_path, p_name);
}
void Buffer::removeAttachment(const QStringList &p_paths)
{
m_provider->removeAttachment(p_paths);
emit attachmentChanged();
}
bool Buffer::isAttachment(const QString &p_path) const
{
return PathUtils::pathContains(getAttachmentFolderPath(), p_path);
}
Buffer::ProviderType Buffer::getProviderType() const
{
return m_provider->getType();
}
Node *Buffer::getNode() const
{
return m_provider->getNode();
}
bool Buffer::checkFileExistsOnDisk()
{
if (m_provider->checkFileExistsOnDisk()) {
m_state &= ~StateFlag::FileMissingOnDisk;
return true;
} else {
m_state |= StateFlag::FileMissingOnDisk;
return false;
}
}
bool Buffer::checkFileChangedOutside()
{
if (m_provider->checkFileChangedOutside()) {
m_state |= StateFlag::FileChangedOutside;
return true;
} else {
m_state &= ~StateFlag::FileChangedOutside;
return false;
}
}
Buffer::StateFlags Buffer::state() const
{
return m_state;
}

241
src/core/buffer/buffer.h Normal file
View File

@ -0,0 +1,241 @@
#ifndef BUFFER_H
#define BUFFER_H
#include <QObject>
#include <QSharedPointer>
#include <functional>
#include <global.h>
class QWidget;
class QTimer;
namespace vnotex
{
class Node;
class Buffer;
class ViewWindow;
struct FileOpenParameters;
class BufferProvider;
struct BufferParameters
{
QSharedPointer<BufferProvider> m_provider;
};
class Buffer : public QObject
{
Q_OBJECT
public:
enum class ProviderType
{
Internal,
External
};
enum class OperationCode
{
Success,
FileMissingOnDisk,
FileChangedOutside,
Failed
};
enum StateFlag
{
Normal = 0,
FileMissingOnDisk = 0x1,
FileChangedOutside = 0x2,
Discarded = 0x4
};
Q_DECLARE_FLAGS(StateFlags, StateFlag);
Buffer(const BufferParameters &p_parameters,
QObject *p_parent = nullptr);
virtual ~Buffer();
int getAttachViewWindowCount() const;
void attachViewWindow(ViewWindow *p_win);
void detachViewWindow(ViewWindow *p_win);
// Create a view window to show the content of this buffer.
// Attach the created view window to this buffer.
ViewWindow *createViewWindow(const QSharedPointer<FileOpenParameters> &p_paras, QWidget *p_parent);
// Whether this buffer matches @p_node.
bool match(const Node *p_node) const;
// Whether this buffer matches @p_filePath.
bool match(const QString &p_filePath) const;
// Buffer name.
QString getName() const;
QString getPath() const;
// In some cases, getPath() may point to a ocntainer containting all the stuffs.
// getContentPath() will return the real path to the file providing the content.
QString getContentPath() const;
// Get the base path to resolve resources.
QString getContentBasePath() const;
ID getID() const;
// Get buffer content.
// It may differ from the content on disk.
// For performance, we need to sync the content with ViewWindow before returning
// the latest content.
const QString &getContent() const;
// @p_revision will be set before contentsChanged is emitted.
void setContent(const QString &p_content, int &p_revision);
// Invalidate the content of buffer.
// Need to sync with @p_win to get the latest content.
// @p_setRevision will be called to set revision before contentsChanged is emitted.
void invalidateContent(const ViewWindow *p_win,
const std::function<void(int)> &p_setRevision);
// Sync content with @p_win if @p_win is the window needed to sync.
void syncContent(const ViewWindow *p_win);
int getRevision() const;
bool isModified() const;
void setModified(bool p_modified);
bool isReadOnly() const;
// Save buffer content to file.
OperationCode save(bool p_force);
// Discard changes and reload file.
OperationCode reload();
// Discard the buffer which will invalidate the buffer.
void discard();
// Buffer is about to be deleted.
void close();
// Insert image from @p_srcImagePath.
// Return inserted image file path.
virtual QString insertImage(const QString &p_srcImagePath, const QString &p_imageFileName);
virtual QString insertImage(const QImage &p_image, const QString &p_imageFileName);
virtual void removeImage(const QString &p_imagePath);
const QString &getBackupFileOfPreviousSession() const;
void discardBackupFileOfPreviousSession();
void recoverFromBackupFileOfPreviousSession();
// Whether this buffer's provider is a child of @p_node or an attachment of @p_node.
bool isChildOf(const Node *p_node) const;
Node *getNode() const;
bool isAttachmentSupported() const;
bool hasAttachment() const;
QString getAttachmentFolderPath() const;
// @p_destFolderPath: folder path locating in attachment folder. Use the root folder if empty.
QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files);
QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name);
QString newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name);
QString renameAttachment(const QString &p_path, const QString &p_name);
void removeAttachment(const QStringList &p_paths);
// Judge whether file @p_path is attachment.
bool isAttachment(const QString &p_path) const;
ProviderType getProviderType() const;
bool checkFileExistsOnDisk();
bool checkFileChangedOutside();
StateFlags state() const;
static QString readBackupFile(const QString &p_filePath);
signals:
void attachedViewWindowEmpty();
void modified(bool p_modified);
void contentsChanged();
void nameChanged();
void attachmentChanged();
protected:
virtual ViewWindow *createViewWindowInternal(const QSharedPointer<FileOpenParameters> &p_paras, QWidget *p_parent) = 0;
QSharedPointer<BufferProvider> m_provider;
private slots:
void autoSave();
private:
void syncContent();
void readContent();
// Get the path of the image folder.
QString getImageFolderPath() const;
void writeBackupFile();
// Generate backup file head.
QString generateBackupFileHead() const;
void checkBackupFileOfPreviousSession();
bool isBackupFileOfBuffer(const QString &p_file) const;
// Will be assigned uniquely once created.
const ID c_id = 0;
// Revision of contents.
int m_revision = 0;
// If the buffer is modified, m_content reflect the latest changes instead
// of the file content.
QString m_content;
bool m_readOnly = false;
bool m_modified = false;
int m_attachedViewWindowCount = 0;
const ViewWindow *m_viewWindowToSync = nullptr;
// Managed by QObject.
QTimer *m_autoSaveTimer = nullptr;
QString m_backupFilePath;
QString m_backupFilePathOfPreviousSession;
StateFlags m_state = StateFlag::Normal;
};
} // ns vnotex
Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::Buffer::StateFlags)
#endif // BUFFER_H

View File

@ -0,0 +1,22 @@
SOURCES += \
$$PWD/buffer.cpp \
$$PWD/bufferprovider.cpp \
$$PWD/filebufferprovider.cpp \
$$PWD/markdownbuffer.cpp \
$$PWD/markdownbufferfactory.cpp \
$$PWD/filetypehelper.cpp \
$$PWD/nodebufferprovider.cpp \
$$PWD/textbuffer.cpp \
$$PWD/textbufferfactory.cpp
HEADERS += \
$$PWD/bufferprovider.h \
$$PWD/buffer.h \
$$PWD/filebufferprovider.h \
$$PWD/ibufferfactory.h \
$$PWD/markdownbuffer.h \
$$PWD/markdownbufferfactory.h \
$$PWD/filetypehelper.h \
$$PWD/nodebufferprovider.h \
$$PWD/textbuffer.h \
$$PWD/textbufferfactory.h

View File

@ -0,0 +1,24 @@
#include "bufferprovider.h"
#include <QFileInfo>
using namespace vnotex;
bool BufferProvider::checkFileExistsOnDisk() const
{
return QFileInfo::exists(getContentPath());
}
QDateTime BufferProvider::getLastModifiedFromFile() const
{
return QFileInfo(getContentPath()).lastModified();
}
bool BufferProvider::checkFileChangedOutside() const
{
QFileInfo info(getContentPath());
if (!info.exists() || m_lastModified != info.lastModified()) {
return true;
}
return false;
}

View File

@ -0,0 +1,82 @@
#ifndef BUFFERPROVIDER_H
#define BUFFERPROVIDER_H
#include <QObject>
#include <QDateTime>
#include "buffer.h"
namespace vnotex
{
class Node;
// Content provider for Buffer.
class BufferProvider : public QObject
{
Q_OBJECT
public:
BufferProvider(QObject *p_parent = nullptr)
: QObject(p_parent)
{
}
virtual ~BufferProvider() {}
virtual Buffer::ProviderType getType() const = 0;
virtual bool match(const Node *p_node) const = 0;
virtual bool match(const QString &p_filePath) const = 0;
virtual QString getName() const = 0;
virtual QString getPath() const = 0;
virtual QString getContentPath() const = 0;
virtual void write(const QString &p_content) = 0;
virtual QString read() const = 0;
virtual QString fetchImageFolderPath() = 0;
virtual bool isChildOf(const Node *p_node) const = 0;
virtual Node *getNode() const = 0;
virtual QString getAttachmentFolder() const = 0;
virtual QString fetchAttachmentFolderPath() = 0;
virtual QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files) = 0;
virtual QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name) = 0;
virtual QString newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name) = 0;
virtual QString renameAttachment(const QString &p_path, const QString &p_name) = 0;
virtual void removeAttachment(const QStringList &p_paths) = 0;
virtual QString insertImage(const QString &p_srcImagePath, const QString &p_imageFileName) = 0;
virtual QString insertImage(const QImage &p_image, const QString &p_imageFileName) = 0;
virtual void removeImage(const QString &p_imagePath) = 0;
virtual bool isAttachmentSupported() const = 0;
virtual bool checkFileExistsOnDisk() const;
virtual bool checkFileChangedOutside() const;
virtual bool isReadOnly() const = 0;
protected:
virtual QDateTime getLastModifiedFromFile() const;
QDateTime m_lastModified;
};
}
#endif // BUFFERPROVIDER_H

View File

@ -0,0 +1,165 @@
#include "filebufferprovider.h"
#include <QFileInfo>
#include <utils/pathutils.h>
#include <utils/fileutils.h>
#include <notebook/node.h>
using namespace vnotex;
FileBufferProvider::FileBufferProvider(const QString &p_filePath,
Node *p_nodeAttachedTo,
bool p_readOnly,
QObject *p_parent)
: BufferProvider(p_parent),
c_filePath(p_filePath),
c_nodeAttachedTo(p_nodeAttachedTo),
m_readOnly(p_readOnly)
{
}
Buffer::ProviderType FileBufferProvider::getType() const
{
return Buffer::ProviderType::External;
}
bool FileBufferProvider::match(const Node *p_node) const
{
Q_UNUSED(p_node);
return false;
}
bool FileBufferProvider::match(const QString &p_filePath) const
{
return PathUtils::areSamePaths(c_filePath, p_filePath);
}
QString FileBufferProvider::getName() const
{
return PathUtils::fileName(c_filePath);
}
QString FileBufferProvider::getPath() const
{
return c_filePath;
}
QString FileBufferProvider::getContentPath() const
{
// TODO.
return getPath();
}
void FileBufferProvider::write(const QString &p_content)
{
FileUtils::writeFile(getContentPath(), p_content);
m_lastModified = getLastModifiedFromFile();
}
QString FileBufferProvider::read() const
{
const_cast<FileBufferProvider *>(this)->m_lastModified = getLastModifiedFromFile();
return FileUtils::readTextFile(getContentPath());
}
QString FileBufferProvider::fetchImageFolderPath()
{
auto pa = PathUtils::concatenateFilePath(PathUtils::parentDirPath(getContentPath()), QStringLiteral("vx_images"));
QDir().mkpath(pa);
return pa;
}
bool FileBufferProvider::isChildOf(const Node *p_node) const
{
if (c_nodeAttachedTo) {
return c_nodeAttachedTo == p_node || Node::isAncestor(p_node, c_nodeAttachedTo);
}
return false;
}
QString FileBufferProvider::getAttachmentFolder() const
{
Q_ASSERT(false);
return QString();
}
QString FileBufferProvider::fetchAttachmentFolderPath()
{
Q_ASSERT(false);
return QString();
}
QStringList FileBufferProvider::addAttachment(const QString &p_destFolderPath, const QStringList &p_files)
{
Q_UNUSED(p_destFolderPath);
Q_UNUSED(p_files);
Q_ASSERT(false);
return QStringList();
}
QString FileBufferProvider::newAttachmentFile(const QString &p_destFolderPath, const QString &p_name)
{
Q_UNUSED(p_destFolderPath);
Q_UNUSED(p_name);
Q_ASSERT(false);
return QString();
}
QString FileBufferProvider::newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name)
{
Q_UNUSED(p_destFolderPath);
Q_UNUSED(p_name);
Q_ASSERT(false);
return QString();
}
QString FileBufferProvider::renameAttachment(const QString &p_path, const QString &p_name)
{
Q_UNUSED(p_path);
Q_UNUSED(p_name);
Q_ASSERT(false);
return QString();
}
void FileBufferProvider::removeAttachment(const QStringList &p_paths)
{
Q_UNUSED(p_paths);
Q_ASSERT(false);
}
QString FileBufferProvider::insertImage(const QString &p_srcImagePath, const QString &p_imageFileName)
{
const auto imageFolderPath = fetchImageFolderPath();
auto destFilePath = FileUtils::renameIfExistsCaseInsensitive(PathUtils::concatenateFilePath(imageFolderPath, p_imageFileName));
FileUtils::copyFile(p_srcImagePath, destFilePath);
return destFilePath;
}
QString FileBufferProvider::insertImage(const QImage &p_image, const QString &p_imageFileName)
{
const auto imageFolderPath = fetchImageFolderPath();
auto destFilePath = FileUtils::renameIfExistsCaseInsensitive(PathUtils::concatenateFilePath(imageFolderPath, p_imageFileName));
p_image.save(destFilePath);
return destFilePath;
}
void FileBufferProvider::removeImage(const QString &p_imagePath)
{
FileUtils::removeFile(p_imagePath);
}
bool FileBufferProvider::isAttachmentSupported() const
{
return false;
}
Node *FileBufferProvider::getNode() const
{
return c_nodeAttachedTo;
}
bool FileBufferProvider::isReadOnly() const
{
return m_readOnly;
}

View File

@ -0,0 +1,73 @@
#ifndef FILEBUFFERPROVIDER_H
#define FILEBUFFERPROVIDER_H
#include "bufferprovider.h"
namespace vnotex
{
// Buffer provider based on external file.
class FileBufferProvider : public BufferProvider
{
Q_OBJECT
public:
FileBufferProvider(const QString &p_filePath,
Node *p_nodeAttachedTo,
bool p_readOnly,
QObject *p_parent = nullptr);
Buffer::ProviderType getType() const Q_DECL_OVERRIDE;
bool match(const Node *p_node) const Q_DECL_OVERRIDE;
bool match(const QString &p_filePath) const Q_DECL_OVERRIDE;
QString getName() const Q_DECL_OVERRIDE;
QString getPath() const Q_DECL_OVERRIDE;
QString getContentPath() const Q_DECL_OVERRIDE;
void write(const QString &p_content) Q_DECL_OVERRIDE;
QString read() const Q_DECL_OVERRIDE;
QString fetchImageFolderPath() Q_DECL_OVERRIDE;
bool isChildOf(const Node *p_node) const Q_DECL_OVERRIDE;
Node *getNode() const Q_DECL_OVERRIDE;
QString getAttachmentFolder() const Q_DECL_OVERRIDE;
QString fetchAttachmentFolderPath() Q_DECL_OVERRIDE;
QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files) Q_DECL_OVERRIDE;
QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name) Q_DECL_OVERRIDE;
QString newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name) Q_DECL_OVERRIDE;
QString renameAttachment(const QString &p_path, const QString &p_name) Q_DECL_OVERRIDE;
void removeAttachment(const QStringList &p_paths) Q_DECL_OVERRIDE;
QString insertImage(const QString &p_srcImagePath, const QString &p_imageFileName) Q_DECL_OVERRIDE;
QString insertImage(const QImage &p_image, const QString &p_imageFileName) Q_DECL_OVERRIDE;
void removeImage(const QString &p_imagePath) Q_DECL_OVERRIDE;
bool isAttachmentSupported() const Q_DECL_OVERRIDE;
bool isReadOnly() const Q_DECL_OVERRIDE;
private:
const QString c_filePath;
Node *c_nodeAttachedTo = nullptr;
bool m_readOnly = false;
};
}
#endif // FILEBUFFERPROVIDER_H

View File

@ -0,0 +1,54 @@
#include "filetypehelper.h"
#include <QFileInfo>
#include <utils/fileutils.h>
using namespace vnotex;
const FileType FileTypeHelper::s_markdownFileType = "markdown";
const FileType FileTypeHelper::s_textFileType = "text";
const FileType FileTypeHelper::s_unknownFileType = "unknown";
QSharedPointer<QMap<QString, FileType>> FileTypeHelper::s_fileTypeMap;
FileType FileTypeHelper::fileType(const QString &p_filePath)
{
Q_ASSERT(!p_filePath.isEmpty());
if (!s_fileTypeMap) {
init();
}
QFileInfo fi(p_filePath);
auto suffix = fi.suffix().toLower();
auto it = s_fileTypeMap->find(suffix);
if (it != s_fileTypeMap->end()) {
return it.value();
}
// Treat all unknown text files as plain text files.
if (FileUtils::isText(p_filePath)) {
return s_fileTypeMap->value(QStringLiteral("txt"));
}
return s_unknownFileType;
}
#define ADD(x, y) s_fileTypeMap->insert((x), (y))
void FileTypeHelper::init()
{
// TODO: load mapping from configuration file.
s_fileTypeMap.reset(new QMap<QString, FileType>());
ADD(QStringLiteral("md"), s_markdownFileType);
ADD(QStringLiteral("markdown"), s_markdownFileType);
ADD(QStringLiteral("mkd"), s_markdownFileType);
ADD(QStringLiteral("txt"), s_textFileType);
ADD(QStringLiteral("text"), s_textFileType);
ADD(QStringLiteral("log"), s_textFileType);
}

View File

@ -0,0 +1,33 @@
#ifndef FILETYPEHELPER_H
#define FILETYPEHELPER_H
#include <QString>
#include <QMap>
#include <QSharedPointer>
namespace vnotex
{
typedef QString FileType;
// Map file suffix to file type.
class FileTypeHelper
{
public:
FileTypeHelper() = delete;
static FileType fileType(const QString &p_filePath);
static const FileType s_markdownFileType;
static const FileType s_textFileType;
static const FileType s_unknownFileType;
private:
static void init();
static QSharedPointer<QMap<QString, FileType>> s_fileTypeMap;
};
} // ns vnotex
#endif // FILETYPEHELPER_H

View File

@ -0,0 +1,24 @@
#ifndef IBUFFERFACTORY_H
#define IBUFFERFACTORY_H
#include <QSharedPointer>
namespace vnotex
{
class Buffer;
struct BufferParameters;
// Abstract factory to create buffer.
class IBufferFactory
{
public:
virtual ~IBufferFactory()
{
}
virtual Buffer *createBuffer(const BufferParameters &p_parameters,
QObject *p_parent) = 0;
};
} // ns vnotex
#endif // IBUFFERFACTORY_H

View File

@ -0,0 +1,94 @@
#include "markdownbuffer.h"
#include <QDir>
#include <widgets/markdownviewwindow.h>
#include <notebook/node.h>
#include <utils/pathutils.h>
#include <buffer/bufferprovider.h>
using namespace vnotex;
MarkdownBuffer::MarkdownBuffer(const BufferParameters &p_parameters,
QObject *p_parent)
: Buffer(p_parameters, p_parent)
{
fetchInitialImages();
}
ViewWindow *MarkdownBuffer::createViewWindowInternal(const QSharedPointer<FileOpenParameters> &p_paras, QWidget *p_parent)
{
return new MarkdownViewWindow(p_paras, p_parent);
}
QString MarkdownBuffer::insertImage(const QString &p_srcImagePath, const QString &p_imageFileName)
{
return m_provider->insertImage(p_srcImagePath, p_imageFileName);
}
QString MarkdownBuffer::insertImage(const QImage &p_image, const QString &p_imageFileName)
{
return m_provider->insertImage(p_image, p_imageFileName);
}
void MarkdownBuffer::fetchInitialImages()
{
Q_ASSERT(m_initialImages.isEmpty());
m_initialImages = vte::MarkdownUtils::fetchImagesFromMarkdownText(getContent(),
getContentBasePath(),
vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
}
void MarkdownBuffer::addInsertedImage(const QString &p_imagePath, const QString &p_urlInLink)
{
vte::MarkdownLink link;
link.m_path = p_imagePath;
link.m_urlInLink = p_urlInLink;
link.m_type = vte::MarkdownLink::TypeFlag::LocalRelativeInternal;
m_insertedImages.append(link);
}
QSet<QString> MarkdownBuffer::clearObsoleteImages()
{
QSet<QString> obsoleteImages;
Q_ASSERT(!isModified());
const bool discarded = state() & StateFlag::Discarded;
const auto latestImages =
vte::MarkdownUtils::fetchImagesFromMarkdownText(!discarded ? getContent() : m_provider->read(),
getContentBasePath(),
vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
QSet<QString> latestImagesPath;
for (const auto &link : latestImages) {
latestImagesPath.insert(PathUtils::normalizePath(link.m_path));
}
for (const auto &link : m_insertedImages) {
if (!(link.m_type & vte::MarkdownLink::TypeFlag::LocalRelativeInternal)) {
continue;
}
if (!latestImagesPath.contains(PathUtils::normalizePath(link.m_path))) {
obsoleteImages.insert(link.m_path);
}
}
m_insertedImages.clear();
for (const auto &link : m_initialImages) {
Q_ASSERT(link.m_type & vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
if (!latestImagesPath.contains(PathUtils::normalizePath(link.m_path))) {
obsoleteImages.insert(link.m_path);
}
}
m_initialImages = latestImages;
return obsoleteImages;
}
void MarkdownBuffer::removeImage(const QString &p_imagePath)
{
qDebug() << "remove obsolete image" << p_imagePath;
m_provider->removeImage(p_imagePath);
}

View File

@ -0,0 +1,47 @@
#ifndef MARKDOWNBUFFER_H
#define MARKDOWNBUFFER_H
#include "buffer.h"
#include <QVector>
#include <QSet>
#include <vtextedit/markdownutils.h>
namespace vnotex
{
class MarkdownBuffer : public Buffer
{
Q_OBJECT
public:
MarkdownBuffer(const BufferParameters &p_parameters,
QObject *p_parent = nullptr);
QString insertImage(const QString &p_srcImagePath, const QString &p_imageFileName) Q_DECL_OVERRIDE;
QString insertImage(const QImage &p_image, const QString &p_imageFileName) Q_DECL_OVERRIDE;
void removeImage(const QString &p_imagePath) Q_DECL_OVERRIDE;
void addInsertedImage(const QString &p_imagePath, const QString &p_urlInLink);
// Clear obsolete images.
// Won't delete images, just return a list of obsolete images path.
// Will re-init m_initialImages and clear m_insertedImages.
QSet<QString> clearObsoleteImages();
protected:
ViewWindow *createViewWindowInternal(const QSharedPointer<FileOpenParameters> &p_paras, QWidget *p_parent) Q_DECL_OVERRIDE;
private:
void fetchInitialImages();
// Images referenced in the file before opening this buffer.
QVector<vte::MarkdownLink> m_initialImages;
// Images newly inserted during this buffer's lifetime.
QVector<vte::MarkdownLink> m_insertedImages;
};
} // ns vnotex
#endif // MARKDOWNBUFFER_H

View File

@ -0,0 +1,11 @@
#include "markdownbufferfactory.h"
#include "markdownbuffer.h"
using namespace vnotex;
Buffer *MarkdownBufferFactory::createBuffer(const BufferParameters &p_parameters,
QObject *p_parent)
{
return new MarkdownBuffer(p_parameters, p_parent);
}

View File

@ -0,0 +1,17 @@
#ifndef MARKDOWNBUFFERFACTORY_H
#define MARKDOWNBUFFERFACTORY_H
#include "ibufferfactory.h"
namespace vnotex
{
// Buffer factory for Markdown file.
class MarkdownBufferFactory : public IBufferFactory
{
public:
Buffer *createBuffer(const BufferParameters &p_parameters,
QObject *p_parent) Q_DECL_OVERRIDE;
};
} // vnotex
#endif // MARKDOWNBUFFERFACTORY_H

View File

@ -0,0 +1,133 @@
#include "nodebufferprovider.h"
#include <QFileInfo>
#include <notebook/node.h>
#include <utils/pathutils.h>
using namespace vnotex;
NodeBufferProvider::NodeBufferProvider(Node *p_node, QObject *p_parent)
: BufferProvider(p_parent),
m_node(p_node),
m_path(m_node->fetchAbsolutePath()),
m_contentPath(m_node->fetchContentPath())
{
}
Buffer::ProviderType NodeBufferProvider::getType() const
{
return Buffer::ProviderType::Internal;
}
bool NodeBufferProvider::match(const Node *p_node) const
{
return m_node == p_node;
}
bool NodeBufferProvider::match(const QString &p_filePath) const
{
return PathUtils::areSamePaths(getPath(), p_filePath);
}
QString NodeBufferProvider::getName() const
{
return m_node->getName();
}
QString NodeBufferProvider::getPath() const
{
return m_path;
}
QString NodeBufferProvider::getContentPath() const
{
return m_contentPath;
}
void NodeBufferProvider::write(const QString &p_content)
{
m_node->write(p_content);
m_lastModified = getLastModifiedFromFile();
}
QString NodeBufferProvider::read() const
{
const_cast<NodeBufferProvider *>(this)->m_lastModified = getLastModifiedFromFile();
return m_node->read();
}
QString NodeBufferProvider::fetchImageFolderPath()
{
return m_node->fetchImageFolderPath();
}
bool NodeBufferProvider::isChildOf(const Node *p_node) const
{
return Node::isAncestor(p_node, m_node);
}
QString NodeBufferProvider::getAttachmentFolder() const
{
return m_node->getAttachmentFolder();
}
QString NodeBufferProvider::fetchAttachmentFolderPath()
{
return m_node->fetchAttachmentFolderPath();
}
QStringList NodeBufferProvider::addAttachment(const QString &p_destFolderPath, const QStringList &p_files)
{
return m_node->addAttachment(p_destFolderPath, p_files);
}
QString NodeBufferProvider::newAttachmentFile(const QString &p_destFolderPath, const QString &p_name)
{
return m_node->newAttachmentFile(p_destFolderPath, p_name);
}
QString NodeBufferProvider::newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name)
{
return m_node->newAttachmentFolder(p_destFolderPath, p_name);
}
QString NodeBufferProvider::renameAttachment(const QString &p_path, const QString &p_name)
{
return m_node->renameAttachment(p_path, p_name);
}
void NodeBufferProvider::removeAttachment(const QStringList &p_paths)
{
return m_node->removeAttachment(p_paths);
}
QString NodeBufferProvider::insertImage(const QString &p_srcImagePath, const QString &p_imageFileName)
{
return m_node->insertImage(p_srcImagePath, p_imageFileName);
}
QString NodeBufferProvider::insertImage(const QImage &p_image, const QString &p_imageFileName)
{
return m_node->insertImage(p_image, p_imageFileName);
}
void NodeBufferProvider::removeImage(const QString &p_imagePath)
{
m_node->removeImage(p_imagePath);
}
bool NodeBufferProvider::isAttachmentSupported() const
{
return true;
}
Node *NodeBufferProvider::getNode() const
{
return m_node;
}
bool NodeBufferProvider::isReadOnly() const
{
return m_node->isReadOnly();
}

View File

@ -0,0 +1,72 @@
#ifndef NODEBUFFERPROVIDER_H
#define NODEBUFFERPROVIDER_H
#include "bufferprovider.h"
namespace vnotex
{
// Buffer provider based on an internal node.
class NodeBufferProvider : public BufferProvider
{
Q_OBJECT
public:
NodeBufferProvider(Node *p_node, QObject *p_parent = nullptr);
Buffer::ProviderType getType() const Q_DECL_OVERRIDE;
bool match(const Node *p_node) const Q_DECL_OVERRIDE;
bool match(const QString &p_filePath) const Q_DECL_OVERRIDE;
QString getName() const Q_DECL_OVERRIDE;
QString getPath() const Q_DECL_OVERRIDE;
QString getContentPath() const Q_DECL_OVERRIDE;
void write(const QString &p_content) Q_DECL_OVERRIDE;
QString read() const Q_DECL_OVERRIDE;
QString fetchImageFolderPath() Q_DECL_OVERRIDE;
bool isChildOf(const Node *p_node) const Q_DECL_OVERRIDE;
Node *getNode() const Q_DECL_OVERRIDE;
QString getAttachmentFolder() const Q_DECL_OVERRIDE;
QString fetchAttachmentFolderPath() Q_DECL_OVERRIDE;
QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files) Q_DECL_OVERRIDE;
QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name) Q_DECL_OVERRIDE;
QString newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name) Q_DECL_OVERRIDE;
QString renameAttachment(const QString &p_path, const QString &p_name) Q_DECL_OVERRIDE;
void removeAttachment(const QStringList &p_paths) Q_DECL_OVERRIDE;
QString insertImage(const QString &p_srcImagePath, const QString &p_imageFileName) Q_DECL_OVERRIDE;
QString insertImage(const QImage &p_image, const QString &p_imageFileName) Q_DECL_OVERRIDE;
void removeImage(const QString &p_imagePath) Q_DECL_OVERRIDE;
bool isAttachmentSupported() const Q_DECL_OVERRIDE;
bool isReadOnly() const Q_DECL_OVERRIDE;
private:
Node *m_node = nullptr;
// Used as cache.
QString m_path;
// Used as cache.
QString m_contentPath;
};
}
#endif // NODEBUFFERPROVIDER_H

View File

@ -0,0 +1,17 @@
#include "textbuffer.h"
#include <widgets/textviewwindow.h>
using namespace vnotex;
TextBuffer::TextBuffer(const BufferParameters &p_parameters,
QObject *p_parent)
: Buffer(p_parameters, p_parent)
{
}
ViewWindow *TextBuffer::createViewWindowInternal(const QSharedPointer<FileOpenParameters> &p_paras, QWidget *p_parent)
{
Q_UNUSED(p_paras);
return new TextViewWindow(p_parent);
}

View File

@ -0,0 +1,20 @@
#ifndef TEXTBUFFER_H
#define TEXTBUFFER_H
#include "buffer.h"
namespace vnotex
{
class TextBuffer : public Buffer
{
Q_OBJECT
public:
TextBuffer(const BufferParameters &p_parameters,
QObject *p_parent = nullptr);
protected:
ViewWindow *createViewWindowInternal(const QSharedPointer<FileOpenParameters> &p_paras, QWidget *p_parent) Q_DECL_OVERRIDE;
};
}
#endif // TEXTBUFFER_H

View File

@ -0,0 +1,11 @@
#include "textbufferfactory.h"
#include "textbuffer.h"
using namespace vnotex;
Buffer *TextBufferFactory::createBuffer(const BufferParameters &p_parameters,
QObject *p_parent)
{
return new TextBuffer(p_parameters, p_parent);
}

View File

@ -0,0 +1,17 @@
#ifndef TEXTBUFFERFACTORY_H
#define TEXTBUFFERFACTORY_H
#include "ibufferfactory.h"
namespace vnotex
{
// Buffer factory for text file.
class TextBufferFactory : public IBufferFactory
{
public:
Buffer *createBuffer(const BufferParameters &p_parameters,
QObject *p_parent) Q_DECL_OVERRIDE;
};
}
#endif // TEXTBUFFERFACTORY_H

178
src/core/buffermgr.cpp Normal file
View File

@ -0,0 +1,178 @@
#include "buffermgr.h"
#include <QUrl>
#include <QDebug>
#include <notebook/node.h>
#include <buffer/filetypehelper.h>
#include <buffer/markdownbufferfactory.h>
#include <buffer/textbufferfactory.h>
#include <buffer/buffer.h>
#include <buffer/nodebufferprovider.h>
#include <buffer/filebufferprovider.h>
#include <utils/widgetutils.h>
#include "notebookmgr.h"
#include "vnotex.h"
#include "fileopenparameters.h"
using namespace vnotex;
BufferMgr::BufferMgr(QObject *p_parent)
: QObject(p_parent)
{
}
BufferMgr::~BufferMgr()
{
Q_ASSERT(m_buffers.isEmpty());
}
void BufferMgr::init()
{
initBufferServer();
}
void BufferMgr::initBufferServer()
{
m_bufferServer.reset(new NameBasedServer<IBufferFactory>);
// Markdown.
auto markdownFactory = QSharedPointer<MarkdownBufferFactory>::create();
m_bufferServer->registerItem(FileTypeHelper::s_markdownFileType, markdownFactory);
// Text.
auto textFactory = QSharedPointer<TextBufferFactory>::create();
m_bufferServer->registerItem(FileTypeHelper::s_textFileType, textFactory);
}
void BufferMgr::open(Node *p_node, const QSharedPointer<FileOpenParameters> &p_paras)
{
if (!p_node) {
return;
}
if (p_node->getType() == Node::Type::Folder) {
return;
}
auto buffer = findBuffer(p_node);
if (!buffer) {
auto nodePath = p_node->fetchAbsolutePath();
auto fileType = FileTypeHelper::fileType(nodePath);
auto factory = m_bufferServer->getItem(fileType);
if (!factory) {
// No factory to open this file type.
qInfo() << "File will be opened by system:" << nodePath;
WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(nodePath));
return;
}
BufferParameters paras;
paras.m_provider.reset(new NodeBufferProvider(p_node));
buffer = factory->createBuffer(paras, this);
addBuffer(buffer);
}
Q_ASSERT(buffer);
emit bufferRequested(buffer, p_paras);
}
void BufferMgr::open(const QString &p_filePath, const QSharedPointer<FileOpenParameters> &p_paras)
{
if (p_filePath.isEmpty()) {
return;
}
{
QFileInfo info(p_filePath);
if (!info.exists() || info.isDir()) {
qWarning() << QString("failed to open file %1 exists:%2 isDir:%3").arg(p_filePath).arg(info.exists()).arg(info.isDir());
return;
}
}
// Check if it is an internal node or not.
auto node = loadNodeByPath(p_filePath);
if (node) {
open(node.data(), p_paras);
return;
}
auto buffer = findBuffer(p_filePath);
if (!buffer) {
// Open it as external file.
auto fileType = FileTypeHelper::fileType(p_filePath);
auto factory = m_bufferServer->getItem(fileType);
if (!factory) {
// No factory to open this file type.
qInfo() << "File will be opened by system:" << p_filePath;
WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(p_filePath));
return;
}
BufferParameters paras;
paras.m_provider.reset(new FileBufferProvider(p_filePath,
p_paras->m_nodeAttachedTo,
p_paras->m_readOnly));
buffer = factory->createBuffer(paras, this);
addBuffer(buffer);
}
Q_ASSERT(buffer);
emit bufferRequested(buffer, p_paras);
}
Buffer *BufferMgr::findBuffer(const Node *p_node) const
{
auto it = std::find_if(m_buffers.constBegin(),
m_buffers.constEnd(),
[p_node](const Buffer *p_buffer) {
return p_buffer->match(p_node);
});
if (it != m_buffers.constEnd()) {
return *it;
}
return nullptr;
}
Buffer *BufferMgr::findBuffer(const QString &p_filePath) const
{
auto it = std::find_if(m_buffers.constBegin(),
m_buffers.constEnd(),
[p_filePath](const Buffer *p_buffer) {
return p_buffer->match(p_filePath);
});
if (it != m_buffers.constEnd()) {
return *it;
}
return nullptr;
}
void BufferMgr::addBuffer(Buffer *p_buffer)
{
m_buffers.push_back(p_buffer);
connect(p_buffer, &Buffer::attachedViewWindowEmpty,
this, [this, p_buffer]() {
qDebug() << "delete buffer without attached view window"
<< p_buffer->getName();
m_buffers.removeAll(p_buffer);
p_buffer->close();
p_buffer->deleteLater();
});
}
QSharedPointer<Node> BufferMgr::loadNodeByPath(const QString &p_path)
{
const auto &notebooks = VNoteX::getInst().getNotebookMgr().getNotebooks();
for (const auto &nb : notebooks) {
auto node = nb->loadNodeByPath(p_path);
if (node) {
return node;
}
}
return nullptr;
}

55
src/core/buffermgr.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef BUFFERMGR_H
#define BUFFERMGR_H
#include <QObject>
#include <QScopedPointer>
#include <QSharedPointer>
#include <QVector>
#include "namebasedserver.h"
namespace vnotex
{
class IBufferFactory;
class Node;
class Buffer;
struct FileOpenParameters;
class BufferMgr : public QObject
{
Q_OBJECT
public:
explicit BufferMgr(QObject *p_parent = nullptr);
~BufferMgr();
void init();
public slots:
void open(Node *p_node, const QSharedPointer<FileOpenParameters> &p_paras);
void open(const QString &p_filePath, const QSharedPointer<FileOpenParameters> &p_paras);
signals:
void bufferRequested(Buffer *p_buffer, const QSharedPointer<FileOpenParameters> &p_paras);
private:
void initBufferServer();
Buffer *findBuffer(const Node *p_node) const;
Buffer *findBuffer(const QString &p_filePath) const;
void addBuffer(Buffer *p_buffer);
// Try to load @p_path as a node if it is within one notebook.
QSharedPointer<Node> loadNodeByPath(const QString &p_path);
QSharedPointer<NameBasedServer<IBufferFactory>> m_bufferServer;
// Managed by QObject.
QVector<Buffer *> m_buffers;
};
} // ns vnotex
#endif // BUFFERMGR_H

187
src/core/clipboarddata.cpp Normal file
View File

@ -0,0 +1,187 @@
#include "clipboarddata.h"
#include <QJsonArray>
#include <QJsonDocument>
#include "exception.h"
using namespace vnotex;
const QString NodeClipboardDataItem::c_notebookId = "notebook_id";
const QString NodeClipboardDataItem::c_nodePath = "node_path";
NodeClipboardDataItem::NodeClipboardDataItem()
{
}
NodeClipboardDataItem::NodeClipboardDataItem(ID p_notebookId, const QString &p_nodePath)
: m_notebookId(p_notebookId),
m_nodeRelativePath(p_nodePath)
{
}
QJsonObject NodeClipboardDataItem::toJson() const
{
QJsonObject jobj;
jobj[c_notebookId] = QString::number(m_notebookId);
jobj[c_nodePath] = m_nodeRelativePath;
return jobj;
}
void NodeClipboardDataItem::fromJson(const QJsonObject &p_jobj)
{
Q_ASSERT(p_jobj.contains(c_notebookId) && p_jobj.contains(c_nodePath));
auto idRet = stringToID(p_jobj[c_notebookId].toString());
Q_ASSERT(idRet.first);
m_notebookId = idRet.second;
m_nodeRelativePath = p_jobj[c_nodePath].toString();
}
const QString ClipboardData::c_instanceId = "instance_id";
const QString ClipboardData::c_action = "action";
const QString ClipboardData::c_data = "data";
ClipboardData::ClipboardData()
{
}
ClipboardData::ClipboardData(ID p_instanceId, Action p_action)
: m_instanceId(p_instanceId),
m_action(p_action)
{
}
void ClipboardData::fromJson(const QJsonObject &p_jobj)
{
clear();
if (!p_jobj.contains(c_instanceId)
|| !p_jobj.contains(c_action)
|| !p_jobj.contains(c_data)) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("fail to parse ClipboardData from json (%1)").arg(p_jobj.keys().join(',')));
return;
}
auto idRet = stringToID(p_jobj[c_instanceId].toString());
if (!idRet.first) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("fail to parse ClipboardData from json (%1)").arg(p_jobj.keys().join(',')));
return;
}
m_instanceId = idRet.second;
int act = p_jobj[c_action].toInt(Action::Invalid);
m_action = intToAction(act);
if (m_action == Action::Invalid) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("fail to parse ClipboardData from json (%1)").arg(p_jobj.keys().join(',')));
return;
}
for (const auto &item : p_jobj[c_data].toArray()) {
auto dataItem = createClipboardDataItem(m_action);
dataItem->fromJson(item.toObject());
m_data.push_back(dataItem);
}
}
QJsonObject ClipboardData::toJson() const
{
QJsonObject jobj;
jobj[c_instanceId] = QString::number(m_instanceId);
jobj[c_action] = static_cast<int>(m_action);
QJsonArray data;
for (const auto& item : m_data) {
data.append(item->toJson());
}
jobj[c_data] = data;
return jobj;
}
ClipboardData::Action ClipboardData::intToAction(int p_act) const
{
Action act = Action::Invalid;
if (p_act >= Action::CopyNode && p_act < Action::Invalid) {
act = static_cast<Action>(p_act);
}
return act;
}
void ClipboardData::clear()
{
m_instanceId = 0;
m_action = Action::Invalid;
m_data.clear();
}
QSharedPointer<ClipboardDataItem> ClipboardData::createClipboardDataItem(Action p_act)
{
switch (p_act) {
case Action::CopyNode:
case Action::MoveNode:
return QSharedPointer<NodeClipboardDataItem>::create();
case Action::Invalid:
Q_ASSERT(false);
return nullptr;
}
return nullptr;
}
void ClipboardData::addItem(const QSharedPointer<ClipboardDataItem> &p_item)
{
Q_ASSERT(p_item);
m_data.push_back(p_item);
}
QString ClipboardData::toJsonText() const
{
auto data = QJsonDocument(toJson()).toJson();
return QString::fromUtf8(data);
}
QSharedPointer<ClipboardData> ClipboardData::fromJsonText(const QString &p_json)
{
if (p_json.isEmpty()) {
return nullptr;
}
auto data = QSharedPointer<ClipboardData>::create();
auto jobj = QJsonDocument::fromJson(p_json.toUtf8()).object();
if (jobj.isEmpty()) {
return nullptr;
}
try {
data->fromJson(jobj);
} catch (Exception &p_e) {
Q_UNUSED(p_e);
return nullptr;
}
return data;
}
const QVector<QSharedPointer<ClipboardDataItem>> &ClipboardData::getData() const
{
return m_data;
}
ID ClipboardData::getInstanceId() const
{
return m_instanceId;
}
ClipboardData::Action ClipboardData::getAction() const
{
return m_action;
}

85
src/core/clipboarddata.h Normal file
View File

@ -0,0 +1,85 @@
#ifndef CLIPBOARDDATA_H
#define CLIPBOARDDATA_H
#include <QVector>
#include <QJsonObject>
#include <QSharedPointer>
#include "global.h"
namespace vnotex
{
class ClipboardDataItem
{
public:
virtual ~ClipboardDataItem()
{
}
virtual QJsonObject toJson() const = 0;
virtual void fromJson(const QJsonObject &p_jobj) = 0;
};
class NodeClipboardDataItem : public ClipboardDataItem
{
public:
NodeClipboardDataItem();
NodeClipboardDataItem(ID p_notebookId, const QString &p_nodePath);
QJsonObject toJson() const Q_DECL_OVERRIDE;
void fromJson(const QJsonObject &p_jobj) Q_DECL_OVERRIDE;
ID m_notebookId;
QString m_nodeRelativePath;
private:
static const QString c_notebookId;
static const QString c_nodePath;
};
class ClipboardData
{
public:
enum Action { CopyNode, MoveNode, Invalid };
ClipboardData();
ClipboardData(ID p_instanceId, Action p_action);
ID getInstanceId() const;
ClipboardData::Action getAction() const;
const QVector<QSharedPointer<ClipboardDataItem>> &getData() const;
void addItem(const QSharedPointer<ClipboardDataItem> &p_item);
QString toJsonText() const;
static QSharedPointer<ClipboardData> fromJsonText(const QString &p_json);
private:
void fromJson(const QJsonObject &p_jobj);
QJsonObject toJson() const;
ClipboardData::Action intToAction(int p_act) const;
void clear();
static QSharedPointer<ClipboardDataItem> createClipboardDataItem(Action p_act);
ID m_instanceId = 0;
Action m_action = Action::Invalid;
QVector<QSharedPointer<ClipboardDataItem>> m_data;
static const QString c_instanceId;
static const QString c_action;
static const QString c_data;
};
} // ns vnotex
#endif // CLIPBOARDDATA_H

376
src/core/configmgr.cpp Normal file
View File

@ -0,0 +1,376 @@
#include "configmgr.h"
#include <QDir>
#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>
#include <QStandardPaths>
#include <QJsonDocument>
#include <QScopeGuard>
#include <QResource>
#include <QPixmap>
#include <QSplashScreen>
#include <QScopedPointer>
#include <utils/pathutils.h>
#include <utils/fileutils.h>
#include "exception.h"
#include <utils/utils.h>
#include "mainconfig.h"
#include "coreconfig.h"
#include "sessionconfig.h"
using namespace vnotex;
#ifndef QT_NO_DEBUG
#define VX_DEBUG_WEB
#endif
const QString ConfigMgr::c_orgName = "VNote";
const QString ConfigMgr::c_appName = "VNoteX";
const QString ConfigMgr::c_configFileName = "vnotex.json";
const QString ConfigMgr::c_sessionFileName = "session.json";
const QJsonObject &ConfigMgr::Settings::getJson() const
{
return m_jobj;
}
QSharedPointer<ConfigMgr::Settings> ConfigMgr::Settings::fromFile(const QString &p_jsonFilePath)
{
if (!QFileInfo::exists(p_jsonFilePath)) {
qWarning() << "return empty Settings from non-exist config file" << p_jsonFilePath;
return QSharedPointer<Settings>::create();
}
auto bytes = FileUtils::readFile(p_jsonFilePath);
return QSharedPointer<Settings>::create(QJsonDocument::fromJson(bytes).object());
}
void ConfigMgr::Settings::writeToFile(const QString &p_jsonFilePath) const
{
FileUtils::writeFile(p_jsonFilePath, QJsonDocument(this->m_jobj).toJson());
}
ConfigMgr::ConfigMgr(QObject *p_parent)
: QObject(p_parent),
m_config(new MainConfig(this)),
m_sessionConfig(new SessionConfig(this))
{
locateConfigFolder();
checkAppConfig();
m_config->init();
m_sessionConfig->init();
}
ConfigMgr::~ConfigMgr()
{
}
void ConfigMgr::locateConfigFolder()
{
// Check app config.
{
const QString configFolderName("vnotex_files");
QString folderPath(QCoreApplication::applicationDirPath()
+ '/' + configFolderName);
if (QDir(folderPath).exists()) {
// Config folder in app/.
m_appConfigFolderPath = PathUtils::cleanPath(folderPath);
} else {
m_appConfigFolderPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
}
}
// Check user config.
{
const QString configFolderName("user_files");
QString folderPath(QCoreApplication::applicationDirPath()
+ '/' + configFolderName);
if (QDir(folderPath).exists()) {
// Config folder in app/.
m_userConfigFolderPath = PathUtils::cleanPath(folderPath);
} else {
m_userConfigFolderPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
// Make sure it exists.
QDir dir(m_userConfigFolderPath);
dir.mkpath(m_userConfigFolderPath);
}
}
Q_ASSERT(m_appConfigFolderPath != m_userConfigFolderPath);
qInfo() << "app config folder" << m_appConfigFolderPath;
qInfo() << "user config folder" << m_userConfigFolderPath;
}
void ConfigMgr::checkAppConfig()
{
bool needUpdate = false;
QDir appConfigDir(m_appConfigFolderPath);
if (!appConfigDir.exists()) {
needUpdate = true;
appConfigDir.mkpath(m_appConfigFolderPath);
} else {
if (!appConfigDir.exists(c_configFileName)) {
needUpdate = true;
} else {
// Check version of config file.
auto defaultSettings = getSettings(Source::Default);
auto appSettings = getSettings(Source::App);
auto defaultVersion = MainConfig::getVersion(defaultSettings->getJson());
auto appVersion = MainConfig::getVersion(appSettings->getJson());
if (defaultVersion != appVersion) {
needUpdate = true;
}
}
if (needUpdate) {
FileUtils::removeDir(m_appConfigFolderPath);
// Wait for the OS delete the folder.
Utils::sleepWait(1000);
appConfigDir.mkpath(m_appConfigFolderPath);
}
}
const auto mainConfigFilePath = appConfigDir.filePath(c_configFileName);
#ifndef VX_DEBUG_WEB
if (!needUpdate) {
return;
}
#endif
qInfo() << "update app config files in" << m_appConfigFolderPath;
Q_ASSERT(appConfigDir.exists());
QPixmap pixmap(":/vnotex/data/core/logo/vnote.png");
QScopedPointer<QSplashScreen> splash(new QSplashScreen(pixmap));
splash->show();
// Load extra data.
splash->showMessage("Loading extra resource data");
const QString extraRcc(QStringLiteral("extra.rcc"));
bool ret = QResource::registerResource(extraRcc);
if (!ret) {
Exception::throwOne(Exception::Type::FailToReadFile,
QString("failed to register resource file %1").arg(extraRcc));
}
auto cleanup = qScopeGuard([extraRcc]() {
QResource::unregisterResource(extraRcc);
});
const QString extraDataRoot(QStringLiteral(":/vnotex/data/extra"));
#ifdef VX_DEBUG_WEB
if (!needUpdate) {
// Always update main config file and web folder.
qDebug() << "forced to update main config file and web folder for debugging";
splash->showMessage("update main config file and web folder for debugging");
// Cancel the read-only permission of the main config file.
QFile::setPermissions(mainConfigFilePath, QFile::WriteUser);
FileUtils::removeFile(mainConfigFilePath);
FileUtils::removeDir(appConfigDir.filePath(QStringLiteral("web")));
// Wait for the OS delete the folder.
Utils::sleepWait(1000);
FileUtils::copyFile(getConfigFilePath(Source::Default), mainConfigFilePath);
FileUtils::copyDir(extraDataRoot + QStringLiteral("/web"),
appConfigDir.filePath(QStringLiteral("web")));
return;
}
#else
Q_ASSERT(needUpdate);
#endif
// Copy themes.
qApp->processEvents();
splash->showMessage("Copying themes");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/themes"),
appConfigDir.filePath(QStringLiteral("themes")));
// Copy docs.
qApp->processEvents();
splash->showMessage("Copying docs");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/docs"),
appConfigDir.filePath(QStringLiteral("docs")));
// Copy syntax-highlighting.
qApp->processEvents();
splash->showMessage("Copying syntax-highlighting");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/syntax-highlighting"),
appConfigDir.filePath(QStringLiteral("syntax-highlighting")));
// Copy web.
qApp->processEvents();
splash->showMessage("Copying web");
FileUtils::copyDir(extraDataRoot + QStringLiteral("/web"),
appConfigDir.filePath(QStringLiteral("web")));
// Main config file.
FileUtils::copyFile(getConfigFilePath(Source::Default), appConfigDir.filePath(c_configFileName));
}
QString ConfigMgr::getConfigFilePath(Source p_src) const
{
QString configPath;
switch (p_src) {
case Source::Default:
configPath = QStringLiteral(":/vnotex/data/core/") + c_configFileName;
break;
case Source::App:
configPath = m_appConfigFolderPath + QLatin1Char('/') + c_configFileName;
break;
case Source::User:
{
configPath = m_userConfigFolderPath + QLatin1Char('/') + c_configFileName;
break;
}
case Source::Session:
{
configPath = m_userConfigFolderPath + QLatin1Char('/') + c_sessionFileName;
break;
}
default:
Q_ASSERT(false);
}
return configPath;
}
QSharedPointer<ConfigMgr::Settings> ConfigMgr::getSettings(Source p_src) const
{
return ConfigMgr::Settings::fromFile(getConfigFilePath(p_src));
}
void ConfigMgr::writeUserSettings(const QJsonObject &p_jobj)
{
Settings settings(p_jobj);
settings.writeToFile(getConfigFilePath(Source::User));
}
void ConfigMgr::writeSessionSettings(const QJsonObject &p_jobj)
{
Settings settings(p_jobj);
settings.writeToFile(getConfigFilePath(Source::Session));
}
MainConfig &ConfigMgr::getConfig()
{
return *m_config;
}
SessionConfig &ConfigMgr::getSessionConfig()
{
return *m_sessionConfig;
}
CoreConfig &ConfigMgr::getCoreConfig()
{
return m_config->getCoreConfig();
}
EditorConfig &ConfigMgr::getEditorConfig()
{
return m_config->getEditorConfig();
}
WidgetConfig &ConfigMgr::getWidgetConfig()
{
return m_config->getWidgetConfig();
}
QString ConfigMgr::getAppFolder() const
{
return m_appConfigFolderPath;
}
QString ConfigMgr::getUserFolder() const
{
return m_userConfigFolderPath;
}
QString ConfigMgr::getAppThemeFolder() const
{
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("themes"));
}
QString ConfigMgr::getUserThemeFolder() const
{
return PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("themes"));
}
QString ConfigMgr::getAppDocsFolder() const
{
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("docs"));
}
QString ConfigMgr::getUserDocsFolder() const
{
return PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("docs"));
}
QString ConfigMgr::getAppSyntaxHighlightingFolder() const
{
return PathUtils::concatenateFilePath(m_appConfigFolderPath,
QStringLiteral("syntax-highlighting"));
}
QString ConfigMgr::getUserSyntaxHighlightingFolder() const
{
return PathUtils::concatenateFilePath(m_userConfigFolderPath,
QStringLiteral("syntax-highlighting"));
}
QString ConfigMgr::getUserOrAppFile(const QString &p_filePath) const
{
QFileInfo fi(p_filePath);
if (fi.isAbsolute()) {
return p_filePath;
}
// Check user folder first.
QDir userConfigDir(m_userConfigFolderPath);
if (userConfigDir.exists(p_filePath)) {
return userConfigDir.absoluteFilePath(p_filePath);
}
// App folder.
QDir appConfigDir(m_appConfigFolderPath);
return appConfigDir.absoluteFilePath(p_filePath);
}
QString ConfigMgr::locateSessionConfigFilePathAtBootstrap()
{
// QApplication is not init yet, so org and app name are empty here.
auto folderPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
folderPath = PathUtils::concatenateFilePath(folderPath, c_orgName + "/" + c_appName);
QDir dir(folderPath);
if (dir.exists(c_sessionFileName)) {
qInfo() << "locateSessionConfigFilePathAtBootstrap" << folderPath;
return dir.filePath(c_sessionFileName);
}
return QString();
}
QString ConfigMgr::getLogFile() const
{
return PathUtils::concatenateFilePath(ConfigMgr::getInst().getUserFolder(), "vnotex.log");
}

141
src/core/configmgr.h Normal file
View File

@ -0,0 +1,141 @@
#ifndef CONFIGMGR_H
#define CONFIGMGR_H
#include <QObject>
#include <QSharedPointer>
#include <QJsonObject>
#include <QScopedPointer>
namespace vnotex
{
class MainConfig;
class SessionConfig;
class CoreConfig;
class EditorConfig;
class WidgetConfig;
class ConfigMgr : public QObject
{
Q_OBJECT
public:
enum class Source
{
Default,
App,
User,
Session
};
class Settings
{
public:
Settings() = default;
Settings(const QJsonObject &p_jobj)
: m_jobj(p_jobj)
{
}
const QJsonObject &getJson() const;
void writeToFile(const QString &p_jsonFilePath) const;
static QSharedPointer<Settings> fromFile(const QString &p_jsonFilePath);
private:
QJsonObject m_jobj;
};
static ConfigMgr &getInst()
{
static ConfigMgr inst;
return inst;
}
~ConfigMgr();
ConfigMgr(const ConfigMgr &) = delete;
void operator=(const ConfigMgr &) = delete;
MainConfig &getConfig();
SessionConfig &getSessionConfig();
CoreConfig &getCoreConfig();
EditorConfig &getEditorConfig();
WidgetConfig &getWidgetConfig();
QString getAppFolder() const;
QString getUserFolder() const;
QString getLogFile() const;
QString getAppThemeFolder() const;
QString getUserThemeFolder() const;
QString getAppDocsFolder() const;
QString getUserDocsFolder() const;
QString getAppSyntaxHighlightingFolder() const;
QString getUserSyntaxHighlightingFolder() const;
// If @p_filePath is absolute, just return it.
// Otherwise, first try to find it in user folder, then in app folder.
QString getUserOrAppFile(const QString &p_filePath) const;
QString getConfigFilePath(Source p_src) const;
// Called at boostrap without QApplication instance.
static QString locateSessionConfigFilePathAtBootstrap();
static const QString c_orgName;
static const QString c_appName;
public:
// Used by IConfig.
QSharedPointer<Settings> getSettings(Source p_src) const;
void writeUserSettings(const QJsonObject &p_jobj);
void writeSessionSettings(const QJsonObject &p_jobj);
signals:
void editorConfigChanged();
private:
explicit ConfigMgr(QObject *p_parent = nullptr);
// Locate the folder path where the config file exists.
void locateConfigFolder();
// Check if app config exists and is updated.
// Update it if in need.
void checkAppConfig();
QScopedPointer<MainConfig> m_config;;
// Session config.
QScopedPointer<SessionConfig> m_sessionConfig;
// Absolute path of the app config folder.
QString m_appConfigFolderPath;
// Absolute path of the user config folder.
QString m_userConfigFolderPath;
// Name of the core config file.
static const QString c_configFileName;
// Name of the session config file.
static const QString c_sessionFileName;
};
} // ns vnotex
#endif // CONFIGMGR_H

57
src/core/core.pri Normal file
View File

@ -0,0 +1,57 @@
INCLUDEPATH *= $$PWD
include($$PWD/notebookbackend/notebookbackend.pri)
include($$PWD/versioncontroller/versioncontroller.pri)
include($$PWD/notebookconfigmgr/notebookconfigmgr.pri)
include($$PWD/notebook/notebook.pri)
include($$PWD/buffer/buffer.pri)
SOURCES += \
$$PWD/buffermgr.cpp \
$$PWD/configmgr.cpp \
$$PWD/coreconfig.cpp \
$$PWD/editorconfig.cpp \
$$PWD/htmltemplatehelper.cpp \
$$PWD/logger.cpp \
$$PWD/mainconfig.cpp \
$$PWD/markdowneditorconfig.cpp \
$$PWD/singleinstanceguard.cpp \
$$PWD/texteditorconfig.cpp \
$$PWD/vnotex.cpp \
$$PWD/thememgr.cpp \
$$PWD/notebookmgr.cpp \
$$PWD/theme.cpp \
$$PWD/sessionconfig.cpp \
$$PWD/clipboarddata.cpp \
$$PWD/widgetconfig.cpp
HEADERS += \
$$PWD/ViewerResource.h \
$$PWD/buffermgr.h \
$$PWD/configmgr.h \
$$PWD/coreconfig.h \
$$PWD/editorconfig.h \
$$PWD/events.h \
$$PWD/filelocator.h \
$$PWD/fileopenparameters.h \
$$PWD/htmltemplatehelper.h \
$$PWD/logger.h \
$$PWD/mainconfig.h \
$$PWD/markdowneditorconfig.h \
$$PWD/singleinstanceguard.h \
$$PWD/iconfig.h \
$$PWD/texteditorconfig.h \
$$PWD/vnotex.h \
$$PWD/thememgr.h \
$$PWD/global.h \
$$PWD/namebasedserver.h \
$$PWD/exception.h \
$$PWD/notebookmgr.h \
$$PWD/theme.h \
$$PWD/sessionconfig.h \
$$PWD/clipboarddata.h \
$$PWD/widgetconfig.h

124
src/core/coreconfig.cpp Normal file
View File

@ -0,0 +1,124 @@
#include "coreconfig.h"
#include <QMetaEnum>
#include <QLocale>
using namespace vnotex;
#define READSTR(key) readString(appObj, userObj, (key))
#define READINT(key) readInt(appObj, userObj, (key))
QStringList CoreConfig::s_availableLocales;
CoreConfig::CoreConfig(ConfigMgr *p_mgr, IConfig *p_topConfig)
: IConfig(p_mgr, p_topConfig)
{
m_sessionName = QStringLiteral("core");
}
const QString &CoreConfig::getTheme() const
{
return m_theme;
}
void CoreConfig::init(const QJsonObject &p_app,
const QJsonObject &p_user)
{
const auto appObj = p_app.value(m_sessionName).toObject();
const auto userObj = p_user.value(m_sessionName).toObject();
m_theme = READSTR(QStringLiteral("theme"));
m_locale = READSTR(QStringLiteral("locale"));
if (!m_locale.isEmpty() && !getAvailableLocales().contains(m_locale)) {
m_locale = QStringLiteral("en_US");
}
loadShortcuts(appObj, userObj);
m_toolBarIconSize = READINT(QStringLiteral("toolbar_icon_size"));
if (m_toolBarIconSize <= 0) {
m_toolBarIconSize = 16;
}
}
QJsonObject CoreConfig::toJson() const
{
QJsonObject obj;
obj[QStringLiteral("theme")] = m_theme;
obj[QStringLiteral("locale")] = m_locale;
obj[QStringLiteral("shortcuts")] = saveShortcuts();
obj[QStringLiteral("toolbar_icon_size")] = m_toolBarIconSize;
return obj;
}
const QString &CoreConfig::getLocale() const
{
return m_locale;
}
void CoreConfig::setLocale(const QString &p_locale)
{
updateConfig(m_locale,
p_locale,
this);
}
QString CoreConfig::getLocaleToUse() const
{
return QLocale().name();
}
const QStringList &CoreConfig::getAvailableLocales()
{
if (s_availableLocales.isEmpty()) {
s_availableLocales << QStringLiteral("en_US");
s_availableLocales << QStringLiteral("zh_CN");
}
return s_availableLocales;
}
void CoreConfig::loadShortcuts(const QJsonObject &p_app, const QJsonObject &p_user)
{
const auto appObj = p_app.value(QStringLiteral("shortcuts")).toObject();
const auto userObj = p_user.value(QStringLiteral("shortcuts")).toObject();
static const auto indexOfShortcutEnum = CoreConfig::staticMetaObject.indexOfEnumerator("Shortcut");
Q_ASSERT(indexOfShortcutEnum >= 0);
const auto metaEnum = CoreConfig::staticMetaObject.enumerator(indexOfShortcutEnum);
// Skip the Max flag.
for (int i = 0; i < metaEnum.keyCount() - 1; ++i) {
m_shortcuts[i] = READSTR(metaEnum.key(i));
}
}
QJsonObject CoreConfig::saveShortcuts() const
{
QJsonObject obj;
static const auto indexOfShortcutEnum = CoreConfig::staticMetaObject.indexOfEnumerator("Shortcut");
Q_ASSERT(indexOfShortcutEnum >= 0);
const auto metaEnum = CoreConfig::staticMetaObject.enumerator(indexOfShortcutEnum);
// Skip the Max flag.
for (int i = 0; i < metaEnum.keyCount() - 1; ++i) {
obj[metaEnum.key(i)] = m_shortcuts[i];
}
return obj;
}
const QString &CoreConfig::getShortcut(Shortcut p_shortcut) const
{
Q_ASSERT(p_shortcut < Shortcut::MaxShortcut);
return m_shortcuts[p_shortcut];
}
int CoreConfig::getToolBarIconSize() const
{
return m_toolBarIconSize;
}
void CoreConfig::setToolBarIconSize(int p_size)
{
Q_ASSERT(p_size > 0);
updateConfig(m_toolBarIconSize, p_size, this);
}

73
src/core/coreconfig.h Normal file
View File

@ -0,0 +1,73 @@
#ifndef CORECONFIG_H
#define CORECONFIG_H
#include "iconfig.h"
#include <QtGlobal>
#include <QString>
#include <QStringList>
namespace vnotex
{
class CoreConfig : public IConfig
{
Q_GADGET
public:
enum Shortcut
{
FullScreen,
ExpandContentArea,
Settings,
NewNote,
CloseTab,
NavigationDock,
OutlineDock,
NavigationMode,
LocateNode,
MaxShortcut
};
Q_ENUM(Shortcut)
CoreConfig(ConfigMgr *p_mgr, IConfig *p_topConfig);
void init(const QJsonObject &p_app, const QJsonObject &p_user) Q_DECL_OVERRIDE;
QJsonObject toJson() const Q_DECL_OVERRIDE;
const QString &getTheme() const;
const QString &getLocale() const;
void setLocale(const QString &p_locale);
// Should be called after locale is properly set.
QString getLocaleToUse() const;
const QString &getShortcut(Shortcut p_shortcut) const;
int getToolBarIconSize() const;
void setToolBarIconSize(int p_size);
static const QStringList &getAvailableLocales();
private:
void loadShortcuts(const QJsonObject &p_app, const QJsonObject &p_user);
QJsonObject saveShortcuts() const;
// Theme name.
QString m_theme;
// User-specified locale, such as zh_CN, en_US.
// Empty if not specified.
QString m_locale;
QString m_shortcuts[Shortcut::MaxShortcut];
// Icon size of MainWindow tool bar.
int m_toolBarIconSize = 16;
static QStringList s_availableLocales;
};
} // ns vnotex
#endif // CORECONFIG_H

184
src/core/editorconfig.cpp Normal file
View File

@ -0,0 +1,184 @@
#include "editorconfig.h"
#include <QMetaEnum>
#include <QDebug>
#include "texteditorconfig.h"
#include "markdowneditorconfig.h"
using namespace vnotex;
#define READINT(key) readInt(appObj, userObj, (key))
#define READSTR(key) readString(appObj, userObj, (key))
EditorConfig::EditorConfig(ConfigMgr *p_mgr, IConfig *p_topConfig)
: IConfig(p_mgr, p_topConfig),
m_textEditorConfig(new TextEditorConfig(p_mgr, p_topConfig)),
m_markdownEditorConfig(new MarkdownEditorConfig(p_mgr, p_topConfig, m_textEditorConfig))
{
m_sessionName = QStringLiteral("editor");
}
EditorConfig::~EditorConfig()
{
}
void EditorConfig::init(const QJsonObject &p_app,
const QJsonObject &p_user)
{
const auto appObj = p_app.value(m_sessionName).toObject();
const auto userObj = p_user.value(m_sessionName).toObject();
loadCore(appObj, userObj);
m_textEditorConfig->init(appObj, userObj);
m_markdownEditorConfig->init(appObj, userObj);
}
void EditorConfig::loadCore(const QJsonObject &p_app, const QJsonObject &p_user)
{
const auto appObj = p_app.value(QStringLiteral("core")).toObject();
const auto userObj = p_user.value(QStringLiteral("core")).toObject();
{
m_toolBarIconSize = READINT(QStringLiteral("toolbar_icon_size"));
if (m_toolBarIconSize <= 0) {
m_toolBarIconSize = 14;
}
}
{
auto autoSavePolicy = READSTR(QStringLiteral("auto_save_policy"));
m_autoSavePolicy = stringToAutoSavePolicy(autoSavePolicy);
}
m_backupFileDirectory = READSTR(QStringLiteral("backup_file_directory"));
m_backupFileExtension = READSTR(QStringLiteral("backup_file_extension"));
loadShortcuts(appObj, userObj);
}
QJsonObject EditorConfig::saveCore() const
{
QJsonObject obj;
obj[QStringLiteral("toolbar_icon_size")] = m_toolBarIconSize;
obj[QStringLiteral("auto_save_policy")] = autoSavePolicyToString(m_autoSavePolicy);
obj[QStringLiteral("backup_file_directory")] = m_backupFileDirectory;
obj[QStringLiteral("backup_file_extension")] = m_backupFileExtension;
obj[QStringLiteral("shortcuts")] = saveShortcuts();
return obj;
}
void EditorConfig::loadShortcuts(const QJsonObject &p_app, const QJsonObject &p_user)
{
const auto appObj = p_app.value(QStringLiteral("shortcuts")).toObject();
const auto userObj = p_user.value(QStringLiteral("shortcuts")).toObject();
static const auto indexOfShortcutEnum = EditorConfig::staticMetaObject.indexOfEnumerator("Shortcut");
Q_ASSERT(indexOfShortcutEnum >= 0);
const auto metaEnum = EditorConfig::staticMetaObject.enumerator(indexOfShortcutEnum);
// Skip the Max flag.
for (int i = 0; i < metaEnum.keyCount() - 1; ++i) {
m_shortcuts[i] = READSTR(metaEnum.key(i));
}
}
QJsonObject EditorConfig::saveShortcuts() const
{
QJsonObject obj;
static const auto indexOfShortcutEnum = EditorConfig::staticMetaObject.indexOfEnumerator("Shortcut");
Q_ASSERT(indexOfShortcutEnum >= 0);
const auto metaEnum = EditorConfig::staticMetaObject.enumerator(indexOfShortcutEnum);
// Skip the Max flag.
for (int i = 0; i < metaEnum.keyCount() - 1; ++i) {
obj[metaEnum.key(i)] = m_shortcuts[i];
}
return obj;
}
QJsonObject EditorConfig::toJson() const
{
QJsonObject obj;
obj[m_textEditorConfig->getSessionName()] = m_textEditorConfig->toJson();
obj[m_markdownEditorConfig->getSessionName()] = m_markdownEditorConfig->toJson();
obj[QStringLiteral("core")] = saveCore();
return obj;
}
TextEditorConfig &EditorConfig::getTextEditorConfig()
{
return *m_textEditorConfig;
}
const TextEditorConfig &EditorConfig::getTextEditorConfig() const
{
return *m_textEditorConfig;
}
MarkdownEditorConfig &EditorConfig::getMarkdownEditorConfig()
{
return *m_markdownEditorConfig;
}
const MarkdownEditorConfig &EditorConfig::getMarkdownEditorConfig() const
{
return *m_markdownEditorConfig;
}
int EditorConfig::getToolBarIconSize() const
{
return m_toolBarIconSize;
}
const QString &EditorConfig::getShortcut(Shortcut p_shortcut) const
{
Q_ASSERT(p_shortcut < Shortcut::MaxShortcut);
return m_shortcuts[p_shortcut];
}
QString EditorConfig::autoSavePolicyToString(AutoSavePolicy p_policy) const
{
switch (p_policy) {
case AutoSavePolicy::None:
return QStringLiteral("none");
case AutoSavePolicy::AutoSave:
return QStringLiteral("autosave");
default:
return QStringLiteral("backupfile");
}
}
EditorConfig::AutoSavePolicy EditorConfig::stringToAutoSavePolicy(const QString &p_str) const
{
auto policy = p_str.toLower();
if (policy == QStringLiteral("none")) {
return AutoSavePolicy::None;
} else if (policy == QStringLiteral("autosave")) {
return AutoSavePolicy::AutoSave;
} else {
return AutoSavePolicy::BackupFile;
}
}
EditorConfig::AutoSavePolicy EditorConfig::getAutoSavePolicy() const
{
return m_autoSavePolicy;
}
void EditorConfig::setAutoSavePolicy(EditorConfig::AutoSavePolicy p_policy)
{
updateConfig(m_autoSavePolicy, p_policy, this);
}
const QString &EditorConfig::getBackupFileDirectory() const
{
return m_backupFileDirectory;
}
const QString &EditorConfig::getBackupFileExtension() const
{
return m_backupFileExtension;
}

118
src/core/editorconfig.h Normal file
View File

@ -0,0 +1,118 @@
#ifndef VNOTEX_EDITORCONFIG_H
#define VNOTEX_EDITORCONFIG_H
#include "iconfig.h"
#include <QScopedPointer>
#include <QSharedPointer>
#include <QObject>
namespace vnotex
{
class TextEditorConfig;
class MarkdownEditorConfig;
class EditorConfig : public IConfig
{
Q_GADGET
public:
enum Shortcut
{
Save,
EditRead,
Discard,
TypeHeading1,
TypeHeading2,
TypeHeading3,
TypeHeading4,
TypeHeading5,
TypeHeading6,
TypeHeadingNone,
TypeBold,
TypeItalic,
TypeStrikethrough,
TypeUnorderedList,
TypeOrderedList,
TypeTodoList,
TypeCheckedTodoList,
TypeCode,
TypeCodeBlock,
TypeMath,
TypeMathBlock,
TypeQuote,
TypeLink,
TypeImage,
TypeTable,
Outline,
RichPaste,
FindAndReplace,
MaxShortcut
};
Q_ENUM(Shortcut)
enum AutoSavePolicy
{
None,
AutoSave,
BackupFile
};
Q_ENUM(AutoSavePolicy)
EditorConfig(ConfigMgr *p_mgr, IConfig *p_topConfig);
~EditorConfig();
TextEditorConfig &getTextEditorConfig();
const TextEditorConfig &getTextEditorConfig() const;
MarkdownEditorConfig &getMarkdownEditorConfig();
const MarkdownEditorConfig &getMarkdownEditorConfig() const;
void init(const QJsonObject &p_app, const QJsonObject &p_user) Q_DECL_OVERRIDE;
QJsonObject toJson() const Q_DECL_OVERRIDE;
int getToolBarIconSize() const;
EditorConfig::AutoSavePolicy getAutoSavePolicy() const;
void setAutoSavePolicy(EditorConfig::AutoSavePolicy p_policy);
const QString &getBackupFileDirectory() const;
const QString &getBackupFileExtension() const;
const QString &getShortcut(Shortcut p_shortcut) const;
private:
void loadCore(const QJsonObject &p_app, const QJsonObject &p_user);
QJsonObject saveCore() const;
void loadShortcuts(const QJsonObject &p_app, const QJsonObject &p_user);
QJsonObject saveShortcuts() const;
QString autoSavePolicyToString(AutoSavePolicy p_policy) const;
AutoSavePolicy stringToAutoSavePolicy(const QString &p_str) const;
// Icon size of editor tool bar.
int m_toolBarIconSize = 14;
QString m_shortcuts[Shortcut::MaxShortcut];
AutoSavePolicy m_autoSavePolicy = AutoSavePolicy::AutoSave;
// Where to put backup file, relative to the content file itself.
QString m_backupFileDirectory;
// Backup file extension.
QString m_backupFileExtension;
// Will be shared with MarkdownEditorConfig.
QSharedPointer<TextEditorConfig> m_textEditorConfig;
QScopedPointer<MarkdownEditorConfig> m_markdownEditorConfig;
};
}
#endif // EDITORCONFIG_H

26
src/core/events.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef EVENTS_H
#define EVENTS_H
#include <QVariant>
namespace vnotex
{
class Event
{
public:
void reset()
{
m_handled = false;
m_response.clear();
}
// Whether this event is handled.
// If it is handled, later handler should just ignore this event.
bool m_handled = false;
// Handler could use this field to return state to the event sender.
QVariant m_response = true;
};
}
#endif // EVENTS_H

89
src/core/exception.h Normal file
View File

@ -0,0 +1,89 @@
#ifndef EXCEPTION_H
#define EXCEPTION_H
#include <stdexcept>
#include <QString>
#include <QDebug>
namespace vnotex
{
class Exception : virtual public std::runtime_error
{
public:
enum class Type
{
InvalidPath,
FailToCreateDir,
FailToWriteFile,
FailToReadFile,
FailToRenameFile,
FailToCopyFile,
FailToCopyDir,
FailToRemoveFile,
FailToRemoveDir,
FileMissingOnDisk,
EssentialFileMissing,
InvalidArgument
};
Exception(Type p_type, const QString &p_what)
: std::runtime_error(p_what.toStdString()),
m_type(p_type)
{
}
Type m_type;
[[noreturn]] static void throwOne(Exception::Type p_type, const QString &p_what)
{
qCritical() << typeToString(p_type) << p_what;
throw Exception(p_type, p_what);
}
private:
static QString typeToString(Exception::Type p_type)
{
switch (p_type) {
case Type::InvalidPath:
return QString("InvalidPath");
case Type::FailToCreateDir:
return QString("FailToCreateDir");
case Type::FailToWriteFile:
return QString("FailToWriteFile");
case Type::FailToReadFile:
return QString("FailToReadFile");
case Type::FailToRenameFile:
return QString("FailToRenameFile");
case Type::FailToCopyFile:
return QString("FailToCopyFile");
case Type::FailToCopyDir:
return QString("FailToCopyDir");
case Type::FailToRemoveFile:
return QString("FailToRemoveFile");
case Type::FailToRemoveDir:
return QString("FailToRemoveDir");
case Type::FileMissingOnDisk:
return QString("FileMissingOnDisk");
case Type::EssentialFileMissing:
return QString("EssentialFileMissing");
case Type::InvalidArgument:
return QString("InvalidArgument");
}
return QString::number(static_cast<int>(p_type));
}
};
} // ns vnotex
#endif // EXCEPTION_H

48
src/core/filelocator.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef FILELOCATOR_H
#define FILELOCATOR_H
#include <QString>
namespace vnotex
{
class Node;
// A unique locator for both internal Node and external file.
class FileLocator
{
public:
FileLocator(Node *p_node)
: m_node(p_node)
{
}
FileLocator(const QString &p_filePath)
: m_filePath(p_filePath)
{
}
bool isNode() const
{
return m_node;
}
Node *node() const
{
Q_ASSERT(isNode());
return m_node;
}
const QString &filePath() const
{
Q_ASSERT(!isNode());
return m_filePath;
}
private:
Node *m_node = nullptr;
QString m_filePath;
};
}
#endif // FILELOCATOR_H

View File

@ -0,0 +1,35 @@
#ifndef FILEOPENPARAMETERS_H
#define FILEOPENPARAMETERS_H
namespace vnotex
{
class Node;
struct FileOpenParameters
{
// Some modes may be not supported by some editors.
enum Mode
{
Read,
Edit,
FullPreview,
FocusPreview
};
Mode m_mode = Mode::Read;
// Whether focus to the opened window.
bool m_focus = true;
// Whether it is a new file.
bool m_newFile = false;
// If this file is an attachment of a node, this field indicates it.
Node *m_nodeAttachedTo = nullptr;
// Open as read-only.
bool m_readOnly = false;
};
}
#endif // FILEOPENPARAMETERS_H

78
src/core/global.h Normal file
View File

@ -0,0 +1,78 @@
#ifndef VNOTEX_GLOBAL_H
#define VNOTEX_GLOBAL_H
#include <QString>
#include <QPair>
#include <QDebug>
#include <QJsonObject>
namespace vnotex
{
typedef quint64 ID;
static QPair<bool, ID> stringToID(const QString &p_str)
{
bool ok;
ID id = p_str.toULongLong(&ok);
return qMakePair(ok, id);
}
static QString IDToString(ID p_id)
{
return QString::number(p_id);
}
typedef quint64 TimeStamp;
struct Info
{
Info(const QString &p_name, const QString &p_displayName, const QString &p_description)
: m_name(p_name), m_displayName(p_displayName), m_description(p_description)
{
}
// Name for identification.
QString m_name;
// User-visible name.
QString m_displayName;
QString m_description;
};
enum { CONTENTS_MARGIN = 2 };
static QString QJsonObjectToString(const QJsonObject &p_obj) {
QString str = "{";
auto keys = p_obj.keys();
for (auto &key : keys) {
str += "\"" + key + "\": \"" + p_obj.value(key).toString() + "\";";
}
str += "}";
return str;
}
static QDebug operator<<(QDebug p_debug, const QJsonObject &p_obj)
{
QDebugStateSaver saver(p_debug);
p_debug << QJsonObjectToString(p_obj);
return p_debug;
}
enum FindOption
{
None = 0,
CaseSensitive = 0x1U,
WholeWordOnly = 0x2U,
RegularExpression = 0x4U,
IncrementalSearch = 0x8U
};
Q_DECLARE_FLAGS(FindOptions, FindOption);
} // ns vnotex
Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions);
#endif // GLOBAL_H

View File

@ -0,0 +1,161 @@
#include "htmltemplatehelper.h"
#include <QDebug>
#include <core/markdowneditorconfig.h>
#include <core/configmgr.h>
#include <utils/fileutils.h>
#include <utils/pathutils.h>
#include <core/thememgr.h>
#include <core/vnotex.h>
using namespace vnotex;
HtmlTemplateHelper::Template HtmlTemplateHelper::s_markdownViewerTemplate;
QString WebGlobalOptions::toJavascriptObject() const
{
return QStringLiteral("window.vxOptions = {\n")
+ QString("webPlantUml: %1,\n").arg(boolToString(m_webPlantUml))
+ QString("webGraphviz: %1,\n").arg(boolToString(m_webGraphviz))
+ QString("constrainImageWidthEnabled: %1,\n").arg(boolToString(m_constrainImageWidthEnabled))
+ QString("protectFromXss: %1,\n").arg(boolToString(m_protectFromXss))
+ QString("sectionNumberEnabled: %1\n").arg(boolToString(m_sectionNumberEnabled))
+ QStringLiteral("}");
}
static bool isGlobalStyles(const ViewerResource::Resource &p_resource)
{
return p_resource.m_name == QStringLiteral("global_styles");
}
// Read "global_styles" from resource and fill the holder with the content.
static void fillGlobalStyles(QString &p_template, const ViewerResource &p_resource)
{
QString styles;
for (const auto &ele : p_resource.m_resources) {
if (isGlobalStyles(ele)) {
if (ele.m_enabled) {
for (const auto &style : ele.m_styles) {
// Read the style file content.
auto styleFile = ConfigMgr::getInst().getUserOrAppFile(style);
styles += FileUtils::readTextFile(styleFile);
}
}
break;
}
}
if (!styles.isEmpty()) {
p_template.replace(QStringLiteral("/* VX_GLOBAL_STYLES_PLACEHOLDER */"),
styles);
}
}
static QString fillStyleTag(const QString &p_styleFile)
{
if (p_styleFile.isEmpty()) {
return "";
}
auto url = PathUtils::pathToUrl(p_styleFile);
return QString("<link rel=\"stylesheet\" type=\"text/css\" href=\"%1\">\n").arg(url.toString());
}
static QString fillScriptTag(const QString &p_scriptFile)
{
if (p_scriptFile.isEmpty()) {
return "";
}
auto url = PathUtils::pathToUrl(p_scriptFile);
return QString("<script type=\"text/javascript\" src=\"%1\"></script>\n").arg(url.toString());
}
static void fillThemeStyles(QString &p_template)
{
QString styles;
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
styles += fillStyleTag(themeMgr.getFile(Theme::File::WebStyleSheet));
styles += fillStyleTag(themeMgr.getFile(Theme::File::HighlightStyleSheet));
if (!styles.isEmpty()) {
p_template.replace(QStringLiteral("<!-- VX_THEME_STYLES_PLACEHOLDER -->"),
styles);
}
}
static void fillGlobalOptions(QString &p_template, const WebGlobalOptions &p_opts)
{
p_template.replace(QStringLiteral("/* VX_GLOBAL_OPTIONS_PLACEHOLDER */"),
p_opts.toJavascriptObject());
}
// Read all other resources in @p_resource and fill the holder with proper resource path.
static void fillResources(QString &p_template, const ViewerResource &p_resource)
{
QString styles;
QString scripts;
for (const auto &ele : p_resource.m_resources) {
if (ele.m_enabled && !isGlobalStyles(ele)) {
// Styles.
for (const auto &style : ele.m_styles) {
auto styleFile = ConfigMgr::getInst().getUserOrAppFile(style);
styles += fillStyleTag(styleFile);
}
// Scripts.
for (const auto &script : ele.m_scripts) {
auto scriptFile = ConfigMgr::getInst().getUserOrAppFile(script);
scripts += fillScriptTag(scriptFile);
}
}
}
if (!styles.isEmpty()) {
p_template.replace(QStringLiteral("<!-- VX_STYLES_PLACEHOLDER -->"),
styles);
}
if (!scripts.isEmpty()) {
p_template.replace(QStringLiteral("<!-- VX_SCRIPTS_PLACEHOLDER -->"),
scripts);
}
}
const QString &HtmlTemplateHelper::getMarkdownViewerTemplate()
{
return s_markdownViewerTemplate.m_template;
}
void HtmlTemplateHelper::updateMarkdownViewerTemplate(const MarkdownEditorConfig &p_config)
{
if (p_config.revision() == s_markdownViewerTemplate.m_revision) {
return;
}
s_markdownViewerTemplate.m_revision = p_config.revision();
const auto &viewerResource = p_config.getViewerResource();
{
auto templateFile = ConfigMgr::getInst().getUserOrAppFile(viewerResource.m_template);
s_markdownViewerTemplate.m_template = FileUtils::readTextFile(templateFile);
}
fillGlobalStyles(s_markdownViewerTemplate.m_template, viewerResource);
fillThemeStyles(s_markdownViewerTemplate.m_template);
{
WebGlobalOptions opts;
opts.m_webPlantUml = p_config.getWebPlantUml();
opts.m_webGraphviz = p_config.getWebGraphviz();
opts.m_sectionNumberEnabled = p_config.getSectionNumberEnabled();
opts.m_constrainImageWidthEnabled = p_config.getConstrainImageWidthEnabled();
opts.m_protectFromXss = p_config.getProtectFromXss();
fillGlobalOptions(s_markdownViewerTemplate.m_template, opts);
}
fillResources(s_markdownViewerTemplate.m_template, viewerResource);
}

View File

@ -0,0 +1,52 @@
#ifndef HTMLTEMPLATEHELPER_H
#define HTMLTEMPLATEHELPER_H
#include <QString>
namespace vnotex
{
class MarkdownEditorConfig;
// Global options to be passed to Web side at the very beginning.
struct WebGlobalOptions
{
bool m_webPlantUml = true;
bool m_webGraphviz = true;
bool m_sectionNumberEnabled = true;
bool m_constrainImageWidthEnabled = true;
bool m_protectFromXss = false;
QString boolToString(bool p_val) const
{
return p_val ? QStringLiteral("true") : QStringLiteral("false");
}
QString toJavascriptObject() const;
};
// Help to generate and update HTML templates.
class HtmlTemplateHelper
{
public:
HtmlTemplateHelper() = delete;
static const QString &getMarkdownViewerTemplate();
static void updateMarkdownViewerTemplate(const MarkdownEditorConfig &p_config);
private:
struct Template
{
int m_revision = -1;
QString m_template;
};
// Template for MarkdownViewer.
static Template s_markdownViewerTemplate;
};
}
#endif // HTMLTEMPLATEHELPER_H

155
src/core/iconfig.h Normal file
View File

@ -0,0 +1,155 @@
#ifndef ICONFIG_H
#define ICONFIG_H
#include <QSharedPointer>
#include <QJsonObject>
namespace vnotex
{
class ConfigMgr;
// Interface for Config.
class IConfig
{
public:
IConfig(ConfigMgr *p_mgr, IConfig *p_topConfig = nullptr)
: m_topConfig(p_topConfig),
m_mgr(p_mgr)
{
}
virtual ~IConfig()
{
}
// Called to init top level config.
virtual void init()
{
Q_ASSERT(false);
}
// Init from QJsonObject.
virtual void init(const QJsonObject &p_default, const QJsonObject &p_user)
{
Q_UNUSED(p_default);
Q_UNUSED(p_user);
Q_ASSERT(false);
}
virtual void writeToSettings() const
{
Q_ASSERT(m_topConfig);
m_topConfig->writeToSettings();
}
virtual QJsonObject toJson() const = 0;
const QString &getSessionName() const
{
return m_sessionName;
}
virtual int revision() const
{
return m_revision;
}
protected:
ConfigMgr *getMgr() const
{
return m_mgr;
}
// First read user config, then the default config.
static QJsonValue read(const QJsonObject &p_default,
const QJsonObject &p_user,
const QString &p_key)
{
auto it = p_user.find(p_key);
if (it != p_user.end()) {
return it.value();
} else {
return p_default.value(p_key);
}
}
static QString readString(const QJsonObject &p_default,
const QJsonObject &p_user,
const QString &p_key)
{
return read(p_default, p_user, p_key).toString();
}
static QString readString(const QJsonObject &p_obj,
const QString &p_key)
{
return p_obj.value(p_key).toString();
}
static QByteArray readByteArray(const QJsonObject &p_obj,
const QString &p_key)
{
return QByteArray::fromBase64(readString(p_obj, p_key).toLatin1());
}
static void writeByteArray(QJsonObject &p_obj,
const QString &p_key,
const QByteArray &p_bytes)
{
p_obj.insert(p_key, QLatin1String(p_bytes.toBase64()));
}
static bool readBool(const QJsonObject &p_default,
const QJsonObject &p_user,
const QString &p_key)
{
return read(p_default, p_user, p_key).toBool();
}
static bool readBool(const QJsonObject &p_obj,
const QString &p_key)
{
return p_obj.value(p_key).toBool();
}
static int readInt(const QJsonObject &p_default,
const QJsonObject &p_user,
const QString &p_key)
{
return read(p_default, p_user, p_key).toInt();
}
static qreal readReal(const QJsonObject &p_default,
const QJsonObject &p_user,
const QString &p_key)
{
return read(p_default, p_user, p_key).toDouble();
}
template <typename T>
static void updateConfig(T &p_cur,
const T &p_new,
IConfig *p_config)
{
if (p_cur == p_new) {
return;
}
++p_config->m_revision;
p_cur = p_new;
p_config->writeToSettings();
}
IConfig *m_topConfig = nullptr;
QString m_sessionName;
// Used to indicate whether there is change after last read.
int m_revision = 0;
private:
ConfigMgr *m_mgr = nullptr;
};
} // ns vnotex
#endif // ICONFIG_H

116
src/core/logger.cpp Normal file
View File

@ -0,0 +1,116 @@
#include "logger.h"
#include <QFile>
#include <QTextStream>
#include "configmgr.h"
using namespace vnotex;
QFile Logger::s_file;
bool Logger::s_debugLog = false;
void Logger::init(bool p_debugLog)
{
s_debugLog = p_debugLog;
#if defined(QT_NO_DEBUG)
s_file.setFileName(ConfigMgr::getInst().getLogFile());
if (s_file.size() >= 5 * 1024 * 1024) {
s_file.open(QIODevice::WriteOnly | QIODevice::Text);
} else {
s_file.open(QIODevice::Append | QIODevice::Text);
}
#endif
qInstallMessageHandler(Logger::log);
}
static QString getFileName(const char *p_file)
{
QString file(p_file);
int idx = file.lastIndexOf(QChar('/'));
if (idx == -1) {
idx = file.lastIndexOf(QChar('\\'));
}
if (idx == -1) {
return file;
} else {
return file.mid(idx + 1);
}
}
void Logger::log(QtMsgType p_type, const QMessageLogContext &p_context, const QString &p_msg)
{
#if defined(QT_NO_DEBUG)
if (!s_debugLog && p_type == QtDebugMsg) {
return;
}
#endif
QByteArray localMsg = p_msg.toUtf8();
QString header;
switch (p_type) {
case QtDebugMsg:
header = QStringLiteral("Debug:");
break;
case QtInfoMsg:
header = QStringLiteral("Info:");
break;
case QtWarningMsg:
header = QStringLiteral("Warning:");
break;
case QtCriticalMsg:
header = QStringLiteral("Critical:");
break;
case QtFatalMsg:
header = QStringLiteral("Fatal:");
}
QString fileName = getFileName(p_context.file);
#if defined(QT_NO_DEBUG)
QTextStream stream(&s_file);
stream << header << (QString("(%1:%2) ").arg(fileName).arg(p_context.line))
<< localMsg << "\n";
if (p_type == QtFatalMsg) {
s_file.close();
abort();
}
#else
std::string fileStr = fileName.toStdString();
const char *file = fileStr.c_str();
switch (p_type) {
case QtDebugMsg:
fprintf(stderr, "%s(%s:%u) %s\n",
header.toStdString().c_str(), file, p_context.line, localMsg.constData());
break;
case QtInfoMsg:
fprintf(stderr, "%s(%s:%u) %s\n",
header.toStdString().c_str(), file, p_context.line, localMsg.constData());
break;
case QtWarningMsg:
fprintf(stderr, "%s(%s:%u) %s\n",
header.toStdString().c_str(), file, p_context.line, localMsg.constData());
break;
case QtCriticalMsg:
fprintf(stderr, "%s(%s:%u) %s\n",
header.toStdString().c_str(), file, p_context.line, localMsg.constData());
break;
case QtFatalMsg:
fprintf(stderr, "%s(%s:%u) %s\n",
header.toStdString().c_str(), file, p_context.line, localMsg.constData());
abort();
}
fflush(stderr);
#endif
}

27
src/core/logger.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <QString>
#include <QMessageLogContext>
class QFile;
namespace vnotex
{
class Logger
{
public:
Logger() = delete;
static void init(bool p_debugLog);
private:
static void log(QtMsgType p_type, const QMessageLogContext &p_context, const QString &p_msg);
static QFile s_file;
static bool s_debugLog;
};
}
#endif // LOGGER_H

113
src/core/mainconfig.cpp Normal file
View File

@ -0,0 +1,113 @@
#include "mainconfig.h"
#include <QJsonObject>
#include <QDebug>
#include "configmgr.h"
#include "coreconfig.h"
#include "editorconfig.h"
#include "widgetconfig.h"
using namespace vnotex;
bool MainConfig::s_versionChanged = false;
MainConfig::MainConfig(ConfigMgr *p_mgr)
: IConfig(p_mgr, nullptr),
m_coreConfig(new CoreConfig(p_mgr, this)),
m_editorConfig(new EditorConfig(p_mgr, this)),
m_widgetConfig(new WidgetConfig(p_mgr, this))
{
}
MainConfig::~MainConfig()
{
}
void MainConfig::init()
{
auto mgr = getMgr();
auto appSettings = mgr->getSettings(ConfigMgr::Source::App);
auto userSettings = mgr->getSettings(ConfigMgr::Source::User);
const auto &appJobj = appSettings->getJson();
const auto &userJobj = userSettings->getJson();
loadMetadata(appJobj, userJobj);
m_coreConfig->init(appJobj, userJobj);
m_editorConfig->init(appJobj, userJobj);
m_widgetConfig->init(appJobj, userJobj);
if (isVersionChanged()) {
// Update user config.
writeToSettings();
}
}
void MainConfig::loadMetadata(const QJsonObject &p_app, const QJsonObject &p_user)
{
const auto appObj = p_app.value(QStringLiteral("metadata")).toObject();
const auto userObj = p_user.value(QStringLiteral("metadata")).toObject();
m_version = appObj.value(QStringLiteral("version")).toString();
m_userVersion = userObj.value(QStringLiteral("version")).toString();
s_versionChanged = m_version != m_userVersion;
qDebug() << "version" << m_version << "user version" << m_userVersion;
}
QJsonObject MainConfig::saveMetaData() const
{
QJsonObject metaObj;
metaObj[QStringLiteral("version")] = m_version;
return metaObj;
}
bool MainConfig::isVersionChanged()
{
return s_versionChanged;
}
CoreConfig &MainConfig::getCoreConfig()
{
return *m_coreConfig;
}
EditorConfig &MainConfig::getEditorConfig()
{
return *m_editorConfig;
}
WidgetConfig &MainConfig::getWidgetConfig()
{
return *m_widgetConfig;
}
void MainConfig::writeToSettings() const
{
getMgr()->writeUserSettings(toJson());
}
QJsonObject MainConfig::toJson() const
{
QJsonObject obj;
obj[QStringLiteral("metadata")] = saveMetaData();
obj[m_coreConfig->getSessionName()] = m_coreConfig->toJson();
obj[m_editorConfig->getSessionName()] = m_editorConfig->toJson();
obj[m_widgetConfig->getSessionName()] = m_widgetConfig->toJson();
return obj;
}
const QString &MainConfig::getVersion() const
{
return m_version;
}
QString MainConfig::getVersion(const QJsonObject &p_jobj)
{
const auto metadataObj = p_jobj.value(QStringLiteral("metadata")).toObject();
return metadataObj.value(QStringLiteral("version")).toString();
}

63
src/core/mainconfig.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef MAINCONFIG_H
#define MAINCONFIG_H
#include "iconfig.h"
#include <QtGlobal>
#include <QString>
class QJsonObject;
namespace vnotex
{
class CoreConfig;
class EditorConfig;
class WidgetConfig;
class MainConfig : public IConfig
{
public:
explicit MainConfig(ConfigMgr *p_mgr);
~MainConfig();
void init() Q_DECL_OVERRIDE;
const QString &getVersion() const;
CoreConfig &getCoreConfig();
EditorConfig &getEditorConfig();
WidgetConfig &getWidgetConfig();
void writeToSettings() const Q_DECL_OVERRIDE;
QJsonObject toJson() const Q_DECL_OVERRIDE;
static QString getVersion(const QJsonObject &p_jobj);
static bool isVersionChanged();
private:
void loadMetadata(const QJsonObject &p_app, const QJsonObject &p_user);
QJsonObject saveMetaData() const;
// Version of VNoteX.
QString m_version;
// Version of user's configuration.
QString m_userVersion;
QScopedPointer<CoreConfig> m_coreConfig;
QScopedPointer<EditorConfig> m_editorConfig;
QScopedPointer<WidgetConfig> m_widgetConfig;
static bool s_versionChanged;
};
} // ns vnotex
#endif // MAINCONFIG_H

View File

@ -0,0 +1,197 @@
#include "markdowneditorconfig.h"
#include <QDebug>
#include "texteditorconfig.h"
#include "mainconfig.h"
using namespace vnotex;
#define READSTR(key) readString(appObj, userObj, (key))
#define READBOOL(key) readBool(appObj, userObj, (key))
#define READREAL(key) readReal(appObj, userObj, (key))
MarkdownEditorConfig::MarkdownEditorConfig(ConfigMgr *p_mgr,
IConfig *p_topConfig,
const QSharedPointer<TextEditorConfig> &p_textEditorConfig)
: IConfig(p_mgr, p_topConfig),
m_textEditorConfig(p_textEditorConfig)
{
m_sessionName = QStringLiteral("markdown_editor");
}
void MarkdownEditorConfig::init(const QJsonObject &p_app, const QJsonObject &p_user)
{
const auto appObj = p_app.value(m_sessionName).toObject();
const auto userObj = p_user.value(m_sessionName).toObject();
loadViewerResource(appObj, userObj);
m_webPlantUml = READBOOL(QStringLiteral("web_plantuml"));
m_webGraphviz = READBOOL(QStringLiteral("web_graphviz"));
m_prependDotInRelativeLink = READBOOL(QStringLiteral("prepend_dot_in_relative_link"));
m_confirmBeforeClearObsoleteImages = READBOOL(QStringLiteral("confirm_before_clear_obsolete_images"));
m_insertFileNameAsTitle = READBOOL(QStringLiteral("insert_file_name_as_title"));
m_sectionNumberEnabled = READBOOL(QStringLiteral("section_number"));
m_constrainImageWidthEnabled = READBOOL(QStringLiteral("constrain_image_width"));
m_constrainInPlacePreviewWidthEnabled = READBOOL(QStringLiteral("constrain_inplace_preview_width"));
m_zoomFactorInReadMode = READREAL(QStringLiteral("zoom_factor_in_read_mode"));
m_fetchImagesInParseAndPaste = READBOOL(QStringLiteral("fetch_images_in_parse_and_paste"));
m_protectFromXss = READBOOL(QStringLiteral("protect_from_xss"));
}
QJsonObject MarkdownEditorConfig::toJson() const
{
QJsonObject obj;
obj[QStringLiteral("viewer_resource")] = saveViewerResource();
obj[QStringLiteral("web_plantuml")] = m_webPlantUml;
obj[QStringLiteral("web_graphviz")] = m_webGraphviz;
obj[QStringLiteral("prepend_dot_in_relative_link")] = m_prependDotInRelativeLink;
obj[QStringLiteral("confirm_before_clear_obsolete_images")] = m_confirmBeforeClearObsoleteImages;
obj[QStringLiteral("insert_file_name_as_title")] = m_insertFileNameAsTitle;
obj[QStringLiteral("section_number")] = m_sectionNumberEnabled;
obj[QStringLiteral("constrain_image_width")] = m_constrainImageWidthEnabled;
obj[QStringLiteral("constrain_inplace_preview_width")] = m_constrainInPlacePreviewWidthEnabled;
obj[QStringLiteral("zoom_factor_in_read_mode")] = m_zoomFactorInReadMode;
obj[QStringLiteral("fetch_images_in_parse_and_paste")] = m_fetchImagesInParseAndPaste;
obj[QStringLiteral("protect_from_xss")] = m_protectFromXss;
return obj;
}
TextEditorConfig &MarkdownEditorConfig::getTextEditorConfig()
{
return *m_textEditorConfig;
}
const TextEditorConfig &MarkdownEditorConfig::getTextEditorConfig() const
{
return *m_textEditorConfig;
}
int MarkdownEditorConfig::revision() const
{
return m_revision + m_textEditorConfig->revision();
}
void MarkdownEditorConfig::loadViewerResource(const QJsonObject &p_app, const QJsonObject &p_user)
{
const QString name(QStringLiteral("viewer_resource"));
if (MainConfig::isVersionChanged()) {
bool needOverride = p_app[QStringLiteral("override_viewer_resource")].toBool();
if (needOverride) {
qInfo() << "override \"viewer_resource\" in user configuration due to version change";
m_viewerResource.init(p_app[name].toObject());
return;
}
}
if (p_user.contains(name)) {
m_viewerResource.init(p_user[name].toObject());
} else {
m_viewerResource.init(p_app[name].toObject());
}
}
QJsonObject MarkdownEditorConfig::saveViewerResource() const
{
return m_viewerResource.toJson();
}
const ViewerResource &MarkdownEditorConfig::getViewerResource() const
{
return m_viewerResource;
}
bool MarkdownEditorConfig::getWebPlantUml() const
{
return m_webPlantUml;
}
bool MarkdownEditorConfig::getWebGraphviz() const
{
return m_webGraphviz;
}
bool MarkdownEditorConfig::getPrependDotInRelativeLink() const
{
return m_prependDotInRelativeLink;
}
bool MarkdownEditorConfig::getConfirmBeforeClearObsoleteImages() const
{
return m_confirmBeforeClearObsoleteImages;
}
void MarkdownEditorConfig::setConfirmBeforeClearObsoleteImages(bool p_confirm)
{
updateConfig(m_confirmBeforeClearObsoleteImages,
p_confirm,
this);
}
bool MarkdownEditorConfig::getInsertFileNameAsTitle() const
{
return m_insertFileNameAsTitle;
}
void MarkdownEditorConfig::setInsertFileNameAsTitle(bool p_enabled)
{
updateConfig(m_insertFileNameAsTitle, p_enabled, this);
}
bool MarkdownEditorConfig::getSectionNumberEnabled() const
{
return m_sectionNumberEnabled;
}
void MarkdownEditorConfig::setSectionNumberEnabled(bool p_enabled)
{
updateConfig(m_sectionNumberEnabled, p_enabled, this);
}
bool MarkdownEditorConfig::getConstrainImageWidthEnabled() const
{
return m_constrainImageWidthEnabled;
}
void MarkdownEditorConfig::setConstrainImageWidthEnabled(bool p_enabled)
{
updateConfig(m_constrainImageWidthEnabled, p_enabled, this);
}
bool MarkdownEditorConfig::getConstrainInPlacePreviewWidthEnabled() const
{
return m_constrainInPlacePreviewWidthEnabled;
}
void MarkdownEditorConfig::setConstrainInPlacePreviewWidthEnabled(bool p_enabled)
{
updateConfig(m_constrainInPlacePreviewWidthEnabled, p_enabled, this);
}
qreal MarkdownEditorConfig::getZoomFactorInReadMode() const
{
return m_zoomFactorInReadMode;
}
void MarkdownEditorConfig::setZoomFactorInReadMode(qreal p_factor)
{
updateConfig(m_zoomFactorInReadMode, p_factor, this);
}
bool MarkdownEditorConfig::getFetchImagesInParseAndPaste() const
{
return m_fetchImagesInParseAndPaste;
}
void MarkdownEditorConfig::setFetchImagesInParseAndPaste(bool p_enabled)
{
updateConfig(m_fetchImagesInParseAndPaste, p_enabled, this);
}
bool MarkdownEditorConfig::getProtectFromXss() const
{
return m_protectFromXss;
}

View File

@ -0,0 +1,103 @@
#ifndef MARKDOWNEDITORCONFIG_H
#define MARKDOWNEDITORCONFIG_H
#include "iconfig.h"
#include "viewerresource.h"
#include <QSharedPointer>
#include <QVector>
namespace vnotex
{
class TextEditorConfig;
class MarkdownEditorConfig : public IConfig
{
public:
MarkdownEditorConfig(ConfigMgr *p_mgr,
IConfig *p_topConfig,
const QSharedPointer<TextEditorConfig> &p_textEditorConfig);
void init(const QJsonObject &p_app, const QJsonObject &p_user) Q_DECL_OVERRIDE;
QJsonObject toJson() const Q_DECL_OVERRIDE;
void loadViewerResource(const QJsonObject &p_app, const QJsonObject &p_user);
QJsonObject saveViewerResource() const;
int revision() const Q_DECL_OVERRIDE;
TextEditorConfig &getTextEditorConfig();
const TextEditorConfig &getTextEditorConfig() const;
const ViewerResource &getViewerResource() const;
bool getWebPlantUml() const;
bool getWebGraphviz() const;
bool getPrependDotInRelativeLink() const;
bool getConfirmBeforeClearObsoleteImages() const;
void setConfirmBeforeClearObsoleteImages(bool p_confirm);
bool getInsertFileNameAsTitle() const;
void setInsertFileNameAsTitle(bool p_enabled);
bool getSectionNumberEnabled() const;
void setSectionNumberEnabled(bool p_enabled);
bool getConstrainImageWidthEnabled() const;
void setConstrainImageWidthEnabled(bool p_enabled);
bool getConstrainInPlacePreviewWidthEnabled() const;
void setConstrainInPlacePreviewWidthEnabled(bool p_enabled);
qreal getZoomFactorInReadMode() const;
void setZoomFactorInReadMode(qreal p_factor);
bool getFetchImagesInParseAndPaste() const;
void setFetchImagesInParseAndPaste(bool p_enabled);
bool getProtectFromXss() const;
private:
QSharedPointer<TextEditorConfig> m_textEditorConfig;
ViewerResource m_viewerResource;
// Whether use javascript or external program to render PlantUML.
bool m_webPlantUml = true;
bool m_webGraphviz = true;
// Whether prepend a dot in front of the relative link, like images.
bool m_prependDotInRelativeLink = false;
// Whether ask for user confirmation before clearing obsolete images.
bool m_confirmBeforeClearObsoleteImages = true;
// Whether insert the name of the new file as title.
bool m_insertFileNameAsTitle = true;
// Whether enable section numbering.
bool m_sectionNumberEnabled = true;
// Whether enable image width constraint.
bool m_constrainImageWidthEnabled = true;
// Whether enable in-place preview width constraint.
bool m_constrainInPlacePreviewWidthEnabled = false;
qreal m_zoomFactorInReadMode = 1.0;
// Whether fetch images to local in Parse To Markdown And Paste.
bool m_fetchImagesInParseAndPaste = true;
// Whether protect from Cross-Site Scripting.
bool m_protectFromXss = false;
};
}
#endif // MARKDOWNEDITORCONFIG_H

View File

@ -0,0 +1,50 @@
#ifndef NAMEBASEDSERVER_H
#define NAMEBASEDSERVER_H
#include <QHash>
#include <QSharedPointer>
#include <QDebug>
#include <QList>
namespace vnotex
{
template <typename T>
class NameBasedServer
{
public:
// Register an item.
bool registerItem(const QString &p_name, const QSharedPointer<T> &p_item)
{
if (m_data.contains(p_name)) {
qWarning() << "item to register already exists with name" << p_name;
return false;
}
m_data.insert(p_name, p_item);
return true;
}
// Get an item.
QSharedPointer<T> getItem(const QString &p_name)
{
auto it = m_data.find(p_name);
if (it != m_data.end()) {
return it.value();
}
return nullptr;
}
QList<QSharedPointer<T>> getAllItems() const
{
return m_data.values();
}
private:
// Name to item mapping.
QHash<QString, QSharedPointer<T>> m_data;
};
} // ns vnotex
#endif // NAMEBASEDSERVER_H

View File

@ -0,0 +1,60 @@
#include "bundlenotebook.h"
#include <QDebug>
#include <notebookconfigmgr/bundlenotebookconfigmgr.h>
#include <notebookconfigmgr/notebookconfig.h>
#include <utils/fileutils.h>
using namespace vnotex;
BundleNotebook::BundleNotebook(const NotebookParameters &p_paras,
QObject *p_parent)
: Notebook(p_paras, p_parent)
{
auto configMgr = getBundleNotebookConfigMgr();
auto config = configMgr->readNotebookConfig();
m_nextNodeId = config->m_nextNodeId;
}
BundleNotebookConfigMgr *BundleNotebook::getBundleNotebookConfigMgr() const
{
return dynamic_cast<BundleNotebookConfigMgr *>(getConfigMgr().data());
}
ID BundleNotebook::getNextNodeId() const
{
return m_nextNodeId;
}
ID BundleNotebook::getAndUpdateNextNodeId()
{
auto id = m_nextNodeId++;
getBundleNotebookConfigMgr()->writeNotebookConfig();
return id;
}
void BundleNotebook::updateNotebookConfig()
{
getBundleNotebookConfigMgr()->writeNotebookConfig();
}
void BundleNotebook::removeNotebookConfig()
{
getBundleNotebookConfigMgr()->removeNotebookConfig();
}
void BundleNotebook::remove()
{
// Remove all nodes.
removeNode(getRootNode());
// Remove notebook config.
removeNotebookConfig();
// Remove notebook root folder if it is empty.
if (!FileUtils::removeDirIfEmpty(getRootFolderAbsolutePath())) {
qInfo() << QString("root folder of notebook (%1) is not empty and needs manual clean up")
.arg(getRootFolderAbsolutePath());
}
}

View File

@ -0,0 +1,35 @@
#ifndef BUNDLENOTEBOOK_H
#define BUNDLENOTEBOOK_H
#include "notebook.h"
#include "global.h"
namespace vnotex
{
class BundleNotebookConfigMgr;
class BundleNotebook : public Notebook
{
Q_OBJECT
public:
BundleNotebook(const NotebookParameters &p_paras,
QObject *p_parent = nullptr);
ID getNextNodeId() const Q_DECL_OVERRIDE;
ID getAndUpdateNextNodeId() Q_DECL_OVERRIDE;
void updateNotebookConfig() Q_DECL_OVERRIDE;
void removeNotebookConfig() Q_DECL_OVERRIDE;
void remove() Q_DECL_OVERRIDE;
private:
BundleNotebookConfigMgr *getBundleNotebookConfigMgr() const;
ID m_nextNodeId = 1;
};
} // ns vnotex
#endif // BUNDLENOTEBOOK_H

View File

@ -0,0 +1,104 @@
#include "bundlenotebookfactory.h"
#include <QObject>
#include <QDebug>
#include <utils/pathutils.h>
#include "../exception.h"
#include "notebookconfigmgr/bundlenotebookconfigmgr.h"
#include "notebookparameters.h"
#include "bundlenotebook.h"
#include "notebookmgr.h"
#include "notebookconfigmgr/notebookconfig.h"
using namespace vnotex;
BundleNotebookFactory::BundleNotebookFactory()
{
}
QString BundleNotebookFactory::getName() const
{
return QStringLiteral("bundle.vnotex");
}
QString BundleNotebookFactory::getDisplayName() const
{
return QObject::tr("Bundled Notebook");
}
QString BundleNotebookFactory::getDescription() const
{
return QObject::tr("A notebook with configuration files to track its content");
}
// Check if root folder is valid for a new notebook.
static void checkRootFolderForNewNotebook(const NotebookParameters &p_paras)
{
if (p_paras.m_rootFolderPath.isEmpty()) {
QString msg("no local root folder is specified");
qCritical() << msg;
throw Exception(Exception::Type::InvalidPath, msg);
} else if (p_paras.m_ensureEmptyRootFolder && !PathUtils::isEmptyDir(p_paras.m_rootFolderPath)) {
QString msg = QString("local root folder must be empty: %1 (%2)")
.arg(p_paras.m_rootFolderPath, PathUtils::absolutePath(p_paras.m_rootFolderPath));
qCritical() << msg;
throw Exception(Exception::Type::InvalidPath, msg);
}
}
QSharedPointer<Notebook> BundleNotebookFactory::newNotebook(const NotebookParameters &p_paras)
{
checkParameters(p_paras);
checkRootFolderForNewNotebook(p_paras);
p_paras.m_notebookConfigMgr->createEmptySkeleton(p_paras);
auto notebook = QSharedPointer<BundleNotebook>::create(p_paras);
return notebook;
}
QSharedPointer<Notebook> BundleNotebookFactory::createNotebook(const NotebookMgr &p_mgr,
const QString &p_rootFolderPath,
const QSharedPointer<INotebookBackend> &p_backend)
{
// Read basic info about this notebook.
auto nbConfig = BundleNotebookConfigMgr::readNotebookConfig(p_backend);
auto paras = NotebookParameters::createNotebookParameters(p_mgr,
p_backend,
getName(),
nbConfig->m_name,
nbConfig->m_description,
p_rootFolderPath,
QIcon(),
nbConfig->m_imageFolder,
nbConfig->m_attachmentFolder,
nbConfig->m_createdTimeUtc,
nbConfig->m_versionController,
nbConfig->m_notebookConfigMgr);
checkParameters(*paras);
auto notebook = QSharedPointer<BundleNotebook>::create(*paras);
return notebook;
}
void BundleNotebookFactory::checkParameters(const NotebookParameters &p_paras) const
{
auto configMgr = dynamic_cast<BundleNotebookConfigMgr *>(p_paras.m_notebookConfigMgr.data());
if (!configMgr) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("Invalid notebook configuration manager"));
}
}
bool BundleNotebookFactory::checkRootFolder(const QSharedPointer<INotebookBackend> &p_backend)
{
try {
BundleNotebookConfigMgr::readNotebookConfig(p_backend);
} catch (Exception &p_e) {
Q_UNUSED(p_e);
return false;
}
return true;
}

View File

@ -0,0 +1,38 @@
#ifndef BUNDLENOTEBOOKFACTORY_H
#define BUNDLENOTEBOOKFACTORY_H
#include "inotebookfactory.h"
namespace vnotex
{
class BundleNotebookFactory : public INotebookFactory
{
public:
BundleNotebookFactory();
// Get the name of this factory.
QString getName() const Q_DECL_OVERRIDE;
// Get the display name of this factory.
QString getDisplayName() const Q_DECL_OVERRIDE;
// Get the description of this factory.
QString getDescription() const Q_DECL_OVERRIDE;
// New a notebook with given information and return an instance of that notebook.
QSharedPointer<Notebook> newNotebook(const NotebookParameters &p_paras) Q_DECL_OVERRIDE;
// Create a Notebook instance from existing root folder.
QSharedPointer<Notebook> createNotebook(const NotebookMgr &p_mgr,
const QString &p_rootFolderPath,
const QSharedPointer<INotebookBackend> &p_backend) Q_DECL_OVERRIDE;
bool checkRootFolder(const QSharedPointer<INotebookBackend> &p_backend) Q_DECL_OVERRIDE;
private:
void checkParameters(const NotebookParameters &p_paras) const;
};
} // ns vnotex
#endif // BUNDLENOTEBOOKFACTORY_H

View File

@ -0,0 +1,130 @@
#include "filenode.h"
#include <notebookconfigmgr/inotebookconfigmgr.h>
#include <notebookbackend/inotebookbackend.h>
#include <utils/pathutils.h>
#include <utils/fileutils.h>
#include "notebook.h"
using namespace vnotex;
FileNode::FileNode(ID p_id,
const QString &p_name,
const QDateTime &p_createdTimeUtc,
const QDateTime &p_modifiedTimeUtc,
const QString &p_attachmentFolder,
const QStringList &p_tags,
Notebook *p_notebook,
Node *p_parent)
: Node(Node::Type::File,
p_id,
p_name,
p_createdTimeUtc,
p_notebook,
p_parent),
m_modifiedTimeUtc(p_modifiedTimeUtc),
m_attachmentFolder(p_attachmentFolder),
m_tags(p_tags)
{
}
QVector<QSharedPointer<Node>> FileNode::getChildren() const
{
return QVector<QSharedPointer<Node>>();
}
int FileNode::getChildrenCount() const
{
return 0;
}
void FileNode::addChild(const QSharedPointer<Node> &p_node)
{
Q_ASSERT(false);
Q_UNUSED(p_node);
}
void FileNode::insertChild(int p_idx, const QSharedPointer<Node> &p_node)
{
Q_ASSERT(false);
Q_UNUSED(p_idx);
Q_UNUSED(p_node);
}
void FileNode::removeChild(const QSharedPointer<Node> &p_child)
{
Q_ASSERT(false);
Q_UNUSED(p_child);
}
QDateTime FileNode::getModifiedTimeUtc() const
{
return m_modifiedTimeUtc;
}
void FileNode::setModifiedTimeUtc()
{
m_modifiedTimeUtc = QDateTime::currentDateTimeUtc();
}
QString FileNode::getAttachmentFolder() const
{
return m_attachmentFolder;
}
void FileNode::setAttachmentFolder(const QString &p_folder)
{
m_attachmentFolder = p_folder;
}
QStringList FileNode::addAttachment(const QString &p_destFolderPath, const QStringList &p_files)
{
QStringList addedFiles;
for (const auto &file : p_files) {
auto destFilePath = m_backend->renameIfExistsCaseInsensitive(
PathUtils::concatenateFilePath(p_destFolderPath, PathUtils::fileName(file)));
m_backend->copyFile(file, destFilePath);
addedFiles << destFilePath;
}
return addedFiles;
}
QString FileNode::newAttachmentFile(const QString &p_destFolderPath, const QString &p_name)
{
auto destFilePath = m_backend->renameIfExistsCaseInsensitive(
PathUtils::concatenateFilePath(p_destFolderPath, p_name));
m_backend->writeFile(destFilePath, QByteArray());
return destFilePath;
}
QString FileNode::newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name)
{
auto destFilePath = m_backend->renameIfExistsCaseInsensitive(
PathUtils::concatenateFilePath(p_destFolderPath, p_name));
m_backend->makePath(destFilePath);
return destFilePath;
}
QString FileNode::renameAttachment(const QString &p_path, const QString &p_name)
{
m_backend->renameFile(p_path, p_name);
return p_name;
}
void FileNode::removeAttachment(const QStringList &p_paths)
{
// Just move it to recycle bin but not added as a child node of recycle bin.
for (const auto &pa : p_paths) {
if (QFileInfo(pa).isDir()) {
m_notebook->moveDirToRecycleBin(pa);
} else {
m_notebook->moveFileToRecycleBin(pa);
}
}
}
QStringList FileNode::getTags() const
{
return m_tags;
}

View File

@ -0,0 +1,60 @@
#ifndef FILENODE_H
#define FILENODE_H
#include "node.h"
namespace vnotex
{
// File node of notebook.
class FileNode : public Node
{
public:
FileNode(ID p_id,
const QString &p_name,
const QDateTime &p_createdTimeUtc,
const QDateTime &p_modifiedTimeUtc,
const QString &p_attachmentFolder,
const QStringList &p_tags,
Notebook *p_notebook,
Node *p_parent = nullptr);
QVector<QSharedPointer<Node>> getChildren() const Q_DECL_OVERRIDE;
int getChildrenCount() const Q_DECL_OVERRIDE;
void addChild(const QSharedPointer<Node> &p_node) Q_DECL_OVERRIDE;
void insertChild(int p_idx, const QSharedPointer<Node> &p_node) Q_DECL_OVERRIDE;
void removeChild(const QSharedPointer<Node> &p_child) Q_DECL_OVERRIDE;
QDateTime getModifiedTimeUtc() const Q_DECL_OVERRIDE;
void setModifiedTimeUtc() Q_DECL_OVERRIDE;
QString getAttachmentFolder() const Q_DECL_OVERRIDE;
void setAttachmentFolder(const QString &p_folder) Q_DECL_OVERRIDE;
QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files) Q_DECL_OVERRIDE;
QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name) Q_DECL_OVERRIDE;
QString newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name) Q_DECL_OVERRIDE;
QString renameAttachment(const QString &p_path, const QString &p_name) Q_DECL_OVERRIDE;
void removeAttachment(const QStringList &p_paths) Q_DECL_OVERRIDE;
QStringList getTags() const Q_DECL_OVERRIDE;
private:
QDateTime m_modifiedTimeUtc;
QString m_attachmentFolder;
QStringList m_tags;
};
}
#endif // FILENODE_H

View File

@ -0,0 +1,65 @@
#include "foldernode.h"
using namespace vnotex;
FolderNode::FolderNode(const QString &p_name,
Notebook *p_notebook,
Node *p_parent)
: Node(Node::Type::Folder,
p_name,
p_notebook,
p_parent)
{
}
void FolderNode::loadFolder(ID p_id,
const QDateTime &p_createdTimeUtc,
const QVector<QSharedPointer<Node>> &p_children)
{
Node::loadInfo(p_id, p_createdTimeUtc);
m_children = p_children;
}
QVector<QSharedPointer<Node>> FolderNode::getChildren() const
{
return m_children;
}
int FolderNode::getChildrenCount() const
{
return m_children.size();
}
void FolderNode::addChild(const QSharedPointer<Node> &p_node)
{
insertChild(m_children.size(), p_node);
}
void FolderNode::insertChild(int p_idx, const QSharedPointer<Node> &p_node)
{
p_node->setParent(this);
m_children.insert(p_idx, p_node);
}
void FolderNode::removeChild(const QSharedPointer<Node> &p_child)
{
if (m_children.removeOne(p_child)) {
p_child->setParent(nullptr);
}
}
QDateTime FolderNode::getModifiedTimeUtc() const
{
return getCreatedTimeUtc();
}
void FolderNode::setModifiedTimeUtc()
{
Q_ASSERT(false);
}
QDir FolderNode::toDir() const
{
return QDir(fetchAbsolutePath());
}

View File

@ -0,0 +1,40 @@
#ifndef FOLDERNODE_H
#define FOLDERNODE_H
#include "node.h"
namespace vnotex
{
class FolderNode : public Node
{
public:
FolderNode(const QString &p_name,
Notebook *p_notebook,
Node *p_parent = nullptr);
void loadFolder(ID p_id,
const QDateTime &p_createdTimeUtc,
const QVector<QSharedPointer<Node>> &p_children);
QVector<QSharedPointer<Node>> getChildren() const Q_DECL_OVERRIDE;
int getChildrenCount() const Q_DECL_OVERRIDE;
void addChild(const QSharedPointer<Node> &p_node) Q_DECL_OVERRIDE;
void insertChild(int p_idx, const QSharedPointer<Node> &p_node) Q_DECL_OVERRIDE;
void removeChild(const QSharedPointer<Node> &p_child) Q_DECL_OVERRIDE;
QDateTime getModifiedTimeUtc() const Q_DECL_OVERRIDE;
void setModifiedTimeUtc() Q_DECL_OVERRIDE;
QDir toDir() const Q_DECL_OVERRIDE;
private:
QVector<QSharedPointer<Node>> m_children;
};
} // ns vnotex
#endif // FOLDERNODE_H

View File

@ -0,0 +1,46 @@
#ifndef INOTEBOOKFACTORY_H
#define INOTEBOOKFACTORY_H
#include <QSharedPointer>
#include <QIcon>
namespace vnotex
{
class Notebook;
class NotebookParameters;
class INotebookBackend;
class NotebookMgr;
// Abstract factory to create notebook.
class INotebookFactory
{
public:
virtual ~INotebookFactory()
{
}
// Get the name of this factory.
virtual QString getName() const = 0;
// Get the display name of this factory.
virtual QString getDisplayName() const = 0;
// Get the description of this factory.
virtual QString getDescription() const = 0;
// New a notebook with given information and return an instance of that notebook.
// The root folder should be empty.
virtual QSharedPointer<Notebook> newNotebook(const NotebookParameters &p_paras) = 0;
// Create a Notebook instance from existing root folder.
virtual QSharedPointer<Notebook> createNotebook(const NotebookMgr &p_mgr,
const QString &p_rootFolderPath,
const QSharedPointer<INotebookBackend> &p_backend) = 0;
// Check if @p_rootFolderPath is a valid root folder to use by this factory
// to create a notebook.
virtual bool checkRootFolder(const QSharedPointer<INotebookBackend> &p_backend) = 0;
};
} // ns vnotex
#endif // INOTEBOOKFACTORY_H

335
src/core/notebook/node.cpp Normal file
View File

@ -0,0 +1,335 @@
#include "node.h"
#include <QDir>
#include <notebookconfigmgr/inotebookconfigmgr.h>
#include <notebookbackend/inotebookbackend.h>
#include <utils/pathutils.h>
#include <core/exception.h>
#include "notebook.h"
using namespace vnotex;
Node::Node(Type p_type,
ID p_id,
const QString &p_name,
const QDateTime &p_createdTimeUtc,
Notebook *p_notebook,
Node *p_parent)
: m_type(p_type),
m_id(p_id),
m_name(p_name),
m_createdTimeUtc(p_createdTimeUtc),
m_loaded(true),
m_notebook(p_notebook),
m_parent(p_parent)
{
if (m_notebook) {
m_configMgr = m_notebook->getConfigMgr();
m_backend = m_notebook->getBackend();
}
}
Node::Node(Type p_type,
const QString &p_name,
Notebook *p_notebook,
Node *p_parent)
: m_type(p_type),
m_name(p_name),
m_notebook(p_notebook),
m_parent(p_parent)
{
if (m_notebook) {
m_configMgr = m_notebook->getConfigMgr();
m_backend = m_notebook->getBackend();
}
}
Node::~Node()
{
}
bool Node::isLoaded() const
{
return m_loaded;
}
void Node::setLoaded(bool p_loaded)
{
m_loaded = p_loaded;
}
void Node::loadInfo(ID p_id, const QDateTime &p_createdTimeUtc)
{
Q_ASSERT(!m_loaded);
m_id = p_id;
m_createdTimeUtc = p_createdTimeUtc;
m_loaded = true;
}
bool Node::isRoot() const
{
return !m_parent;
}
const QString &Node::getName() const
{
return m_name;
}
bool Node::hasChild(const QString &p_name, bool p_caseSensitive) const
{
return findChild(p_name, p_caseSensitive) != nullptr;
}
bool Node::hasChild(const QSharedPointer<Node> &p_node) const
{
return getChildren().indexOf(p_node) != -1;
}
QSharedPointer<Node> Node::findChild(const QString &p_name, bool p_caseSensitive) const
{
auto targetName = p_caseSensitive ? p_name : p_name.toLower();
for (auto &child : getChildren()) {
if (p_caseSensitive ? child->getName() == targetName
: child->getName().toLower() == targetName) {
return child;
}
}
return nullptr;
}
void Node::setParent(Node *p_parent)
{
m_parent = p_parent;
}
Node *Node::getParent() const
{
return m_parent;
}
Node::Type Node::getType() const
{
return m_type;
}
Node::Flags Node::getFlags() const
{
return m_flags;
}
void Node::setFlags(Node::Flags p_flags)
{
m_flags = p_flags;
}
Node::Use Node::getUse() const
{
return m_use;
}
void Node::setUse(Node::Use p_use)
{
m_use = p_use;
}
ID Node::getId() const
{
return m_id;
}
const QDateTime &Node::getCreatedTimeUtc() const
{
return m_createdTimeUtc;
}
Notebook *Node::getNotebook() const
{
return m_notebook;
}
QString Node::fetchRelativePath() const
{
if (!m_parent) {
return QString();
} else {
return PathUtils::concatenateFilePath(m_parent->fetchRelativePath(), m_name);
}
}
QString Node::fetchAbsolutePath() const
{
return PathUtils::concatenateFilePath(m_notebook->getRootFolderAbsolutePath(),
fetchRelativePath());
}
QString Node::fetchContentPath() const
{
return fetchAbsolutePath();
}
void Node::load()
{
Q_ASSERT(m_notebook);
m_notebook->load(this);
}
void Node::save()
{
Q_ASSERT(m_notebook);
m_notebook->save(this);
}
void Node::setName(const QString &p_name)
{
m_name = p_name;
}
void Node::updateName(const QString &p_name)
{
if (m_name == p_name) {
return;
}
m_notebook->rename(this, p_name);
Q_ASSERT(m_name == p_name);
}
bool Node::isAncestor(const Node *p_ancestor, const Node *p_child)
{
if (!p_ancestor || !p_child) {
return false;
}
while (p_child) {
p_child = p_child->getParent();
if (p_child == p_ancestor) {
return true;
}
}
return false;
}
bool Node::existsOnDisk() const
{
return m_configMgr->nodeExistsOnDisk(this);
}
QString Node::read() const
{
return m_configMgr->readNode(this);
}
void Node::write(const QString &p_content)
{
m_configMgr->writeNode(this, p_content);
}
QString Node::fetchImageFolderPath()
{
return m_configMgr->fetchNodeImageFolderPath(this);
}
QString Node::insertImage(const QString &p_srcImagePath, const QString &p_imageFileName)
{
const auto imageFolderPath = fetchImageFolderPath();
auto destFilePath = m_backend->renameIfExistsCaseInsensitive(PathUtils::concatenateFilePath(imageFolderPath, p_imageFileName));
m_backend->copyFile(p_srcImagePath, destFilePath);
return destFilePath;
}
QString Node::insertImage(const QImage &p_image, const QString &p_imageFileName)
{
const auto imageFolderPath = fetchImageFolderPath();
auto destFilePath = m_backend->renameIfExistsCaseInsensitive(PathUtils::concatenateFilePath(imageFolderPath, p_imageFileName));
p_image.save(destFilePath);
m_backend->addFile(destFilePath);
return destFilePath;
}
void Node::removeImage(const QString &p_imagePath)
{
// Just move it to recycle bin but not added as a child node of recycle bin.
m_notebook->moveFileToRecycleBin(p_imagePath);
}
QString Node::getAttachmentFolder() const
{
Q_ASSERT(false);
return QString();
}
void Node::setAttachmentFolder(const QString &p_folder)
{
Q_UNUSED(p_folder);
Q_ASSERT(false);
}
QString Node::fetchAttachmentFolderPath()
{
return m_configMgr->fetchNodeAttachmentFolderPath(this);
}
QStringList Node::addAttachment(const QString &p_destFolderPath, const QStringList &p_files)
{
Q_UNUSED(p_destFolderPath);
Q_UNUSED(p_files);
Q_ASSERT(false);
return QStringList();
}
QString Node::newAttachmentFile(const QString &p_destFolderPath, const QString &p_name)
{
Q_UNUSED(p_destFolderPath);
Q_UNUSED(p_name);
Q_ASSERT(false);
return QString();
}
QString Node::newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name)
{
Q_UNUSED(p_destFolderPath);
Q_UNUSED(p_name);
Q_ASSERT(false);
return QString();
}
QString Node::renameAttachment(const QString &p_path, const QString &p_name)
{
Q_UNUSED(p_path);
Q_UNUSED(p_name);
Q_ASSERT(false);
return QString();
}
void Node::removeAttachment(const QStringList &p_paths)
{
Q_UNUSED(p_paths);
Q_ASSERT(false);
}
QStringList Node::getTags() const
{
Q_ASSERT(false);
return QStringList();
}
QDir Node::toDir() const
{
Q_ASSERT(false);
return QDir();
}
INotebookBackend *Node::getBackend() const
{
return m_backend.data();
}
bool Node::isReadOnly() const
{
return m_flags & Flag::ReadOnly;
}

196
src/core/notebook/node.h Normal file
View File

@ -0,0 +1,196 @@
#ifndef NODE_H
#define NODE_H
#include <QDateTime>
#include <QVector>
#include <QSharedPointer>
#include <QDir>
#include <global.h>
namespace vnotex
{
class Notebook;
class INotebookConfigMgr;
class INotebookBackend;
// Used when add/new a node.
struct NodeParameters
{
QDateTime m_createdTimeUtc = QDateTime::currentDateTimeUtc();
QDateTime m_modifiedTimeUtc = QDateTime::currentDateTimeUtc();
QString m_attachmentFolder;
QStringList m_tags;
};
// Node of notebook.
class Node
{
public:
enum Type {
Folder,
File
};
enum Flag {
None = 0,
ReadOnly = 0x1
};
Q_DECLARE_FLAGS(Flags, Flag)
enum Use {
Normal,
RecycleBin
};
// Constructor with all information loaded.
Node(Type p_type,
ID p_id,
const QString &p_name,
const QDateTime &p_createdTimeUtc,
Notebook *p_notebook,
Node *p_parent = nullptr);
// Constructor not loaded.
Node(Type p_type,
const QString &p_name,
Notebook *p_notebook,
Node *p_parent = nullptr);
virtual ~Node();
enum { InvalidId = 0 };
bool isLoaded() const;
bool isRoot() const;
const QString &getName() const;
void setName(const QString &p_name);
// Change the config and backend file as well.
void updateName(const QString &p_name);
Node::Type getType() const;
Node::Flags getFlags() const;
void setFlags(Node::Flags p_flags);
Node::Use getUse() const;
void setUse(Node::Use p_use);
ID getId() const;
const QDateTime &getCreatedTimeUtc() const;
virtual QDateTime getModifiedTimeUtc() const = 0;
virtual void setModifiedTimeUtc() = 0;
virtual QString getAttachmentFolder() const;
virtual void setAttachmentFolder(const QString &p_folder);
virtual QVector<QSharedPointer<Node>> getChildren() const = 0;
virtual int getChildrenCount() const = 0;
QSharedPointer<Node> findChild(const QString &p_name, bool p_caseSensitive = true) const;
bool hasChild(const QString &p_name, bool p_caseSensitive = true) const;
bool hasChild(const QSharedPointer<Node> &p_node) const;
virtual void addChild(const QSharedPointer<Node> &p_node) = 0;
virtual void insertChild(int p_idx, const QSharedPointer<Node> &p_node) = 0;
virtual void removeChild(const QSharedPointer<Node> &p_node) = 0;
void setParent(Node *p_parent);
Node *getParent() const;
Notebook *getNotebook() const;
// Path to the node.
QString fetchRelativePath() const;
QString fetchAbsolutePath() const;
// A node may be a container of all the stuffs, so the node's path may not be identical with
// the content file path, like TextBundle.
virtual QString fetchContentPath() const;
// Get image folder path.
virtual QString fetchImageFolderPath();
virtual void load();
virtual void save();
static bool isAncestor(const Node *p_ancestor, const Node *p_child);
bool existsOnDisk() const;
QString read() const;
void write(const QString &p_content);
// Insert image from @p_srcImagePath.
// Return inserted image file path.
virtual QString insertImage(const QString &p_srcImagePath, const QString &p_imageFileName);
virtual QString insertImage(const QImage &p_image, const QString &p_imageFileName);
virtual void removeImage(const QString &p_imagePath);
// Get attachment folder path.
virtual QString fetchAttachmentFolderPath();
virtual QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files);
virtual QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name);
virtual QString newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name);
virtual QString renameAttachment(const QString &p_path, const QString &p_name);
virtual void removeAttachment(const QStringList &p_paths);
virtual QStringList getTags() const;
virtual QDir toDir() const;
INotebookBackend *getBackend() const;
bool isReadOnly() const;
protected:
void loadInfo(ID p_id, const QDateTime &p_createdTimeUtc);
void setLoaded(bool p_loaded);
Notebook *m_notebook = nullptr;
QSharedPointer<INotebookConfigMgr> m_configMgr;
QSharedPointer<INotebookBackend> m_backend;
private:
Type m_type = Type::Folder;
Flags m_flags = Flag::None;
Use m_use = Use::Normal;
ID m_id = InvalidId;
QString m_name;
QDateTime m_createdTimeUtc;
bool m_loaded = false;
Node *m_parent = nullptr;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Node::Flags)
} // ns vnotex
#endif // NODE_H

View File

@ -0,0 +1,380 @@
#include "notebook.h"
#include <QFileInfo>
#include <versioncontroller/iversioncontroller.h>
#include <notebookbackend/inotebookbackend.h>
#include <notebookconfigmgr/inotebookconfigmgr.h>
#include <utils/pathutils.h>
#include <utils/fileutils.h>
#include "exception.h"
using namespace vnotex;
const QString Notebook::c_defaultAttachmentFolder = QStringLiteral("vx_attachments");
const QString Notebook::c_defaultImageFolder = QStringLiteral("vx_images");
static vnotex::ID generateNotebookID()
{
static vnotex::ID id = Notebook::InvalidId;
return ++id;
}
Notebook::Notebook(const NotebookParameters &p_paras,
QObject *p_parent)
: QObject(p_parent),
m_id(generateNotebookID()),
m_type(p_paras.m_type),
m_name(p_paras.m_name),
m_description(p_paras.m_description),
m_rootFolderPath(p_paras.m_rootFolderPath),
m_icon(p_paras.m_icon),
m_imageFolder(p_paras.m_imageFolder),
m_attachmentFolder(p_paras.m_attachmentFolder),
m_createdTimeUtc(p_paras.m_createdTimeUtc),
m_backend(p_paras.m_notebookBackend),
m_versionController(p_paras.m_versionController),
m_configMgr(p_paras.m_notebookConfigMgr)
{
if (m_imageFolder.isEmpty()) {
m_imageFolder = c_defaultImageFolder;
}
if (m_attachmentFolder.isEmpty()) {
m_attachmentFolder = c_defaultAttachmentFolder;
}
m_configMgr->setNotebook(this);
}
Notebook::~Notebook()
{
}
vnotex::ID Notebook::getId() const
{
return m_id;
}
const QString &Notebook::getType() const
{
return m_type;
}
const QString &Notebook::getName() const
{
return m_name;
}
void Notebook::setName(const QString &p_name)
{
m_name = p_name;
}
void Notebook::updateName(const QString &p_name)
{
Q_ASSERT(!p_name.isEmpty());
if (p_name == m_name) {
return;
}
m_name = p_name;
updateNotebookConfig();
emit updated();
}
const QString &Notebook::getDescription() const
{
return m_description;
}
void Notebook::setDescription(const QString &p_description)
{
m_description = p_description;
}
void Notebook::updateDescription(const QString &p_description)
{
if (p_description == m_description) {
return;
}
m_description = p_description;
updateNotebookConfig();
emit updated();
}
const QString &Notebook::getRootFolderPath() const
{
return m_rootFolderPath;
}
QString Notebook::getRootFolderAbsolutePath() const
{
return PathUtils::absolutePath(m_rootFolderPath);
}
const QIcon &Notebook::getIcon() const
{
return m_icon;
}
void Notebook::setIcon(const QIcon &p_icon)
{
m_icon = p_icon;
}
const QString &Notebook::getImageFolder() const
{
return m_imageFolder;
}
const QString &Notebook::getAttachmentFolder() const
{
return m_attachmentFolder;
}
const QSharedPointer<INotebookBackend> &Notebook::getBackend() const
{
return m_backend;
}
const QSharedPointer<IVersionController> &Notebook::getVersionController() const
{
return m_versionController;
}
const QSharedPointer<INotebookConfigMgr> &Notebook::getConfigMgr() const
{
return m_configMgr;
}
const QSharedPointer<Node> &Notebook::getRootNode() const
{
if (!m_root) {
const_cast<Notebook *>(this)->m_root = m_configMgr->loadRootNode();
}
return m_root;
}
QSharedPointer<Node> Notebook::getRecycleBinNode() const
{
auto root = getRootNode();
auto children = root->getChildren();
auto it = std::find_if(children.begin(),
children.end(),
[this](const QSharedPointer<Node> &p_node) {
return isRecycleBinNode(p_node.data());
});
if (it != children.end()) {
return *it;
}
return nullptr;
}
QSharedPointer<Node> Notebook::newNode(Node *p_parent, Node::Type p_type, const QString &p_name)
{
return m_configMgr->newNode(p_parent, p_type, p_name);
}
const QDateTime &Notebook::getCreatedTimeUtc() const
{
return m_createdTimeUtc;
}
void Notebook::load(Node *p_node)
{
Q_ASSERT(p_node->getNotebook() == this);
if (p_node->isLoaded()) {
return;
}
m_configMgr->loadNode(p_node);
}
void Notebook::save(const Node *p_node)
{
Q_ASSERT(p_node->getNotebook() == this);
m_configMgr->saveNode(p_node);
}
void Notebook::rename(Node *p_node, const QString &p_name)
{
Q_ASSERT(p_node->getNotebook() == this);
m_configMgr->renameNode(p_node, p_name);
emit nodeUpdated(p_node);
}
QSharedPointer<Node> Notebook::loadNodeByPath(const QString &p_path)
{
if (!PathUtils::pathContains(m_rootFolderPath, p_path)) {
return nullptr;
}
QString relativePath;
QFileInfo fi(p_path);
if (fi.isAbsolute()) {
if (!fi.exists()) {
return nullptr;
}
relativePath = PathUtils::relativePath(m_rootFolderPath, p_path);
} else {
relativePath = p_path;
}
return m_configMgr->loadNodeByPath(m_root, relativePath);
}
QSharedPointer<Node> Notebook::copyNodeAsChildOf(const QSharedPointer<Node> &p_src, Node *p_dest, bool p_move)
{
Q_ASSERT(p_src != p_dest);
Q_ASSERT(p_dest->getNotebook() == this);
if (Node::isAncestor(p_src.data(), p_dest)) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("source (%1) is the ancestor of destination (%2)")
.arg(p_src->fetchRelativePath(), p_dest->fetchRelativePath()));
return nullptr;
}
if (p_src->getParent() == p_dest && p_move) {
return p_src;
}
return m_configMgr->copyNodeAsChildOf(p_src, p_dest, p_move);
}
void Notebook::removeNode(const QSharedPointer<Node> &p_node, bool p_force, bool p_configOnly)
{
Q_ASSERT(p_node->getNotebook() == this);
m_configMgr->removeNode(p_node, p_force, p_configOnly);
}
void Notebook::removeNode(const Node *p_node, bool p_force, bool p_configOnly)
{
Q_ASSERT(p_node && !p_node->isRoot());
auto children = p_node->getParent()->getChildren();
auto it = std::find(children.begin(), children.end(), p_node);
Q_ASSERT(it != children.end());
removeNode(*it, p_force, p_configOnly);
}
bool Notebook::isRecycleBinNode(const Node *p_node) const
{
return p_node && p_node->getUse() == Node::Use::RecycleBin;
}
bool Notebook::isNodeInRecycleBin(const Node *p_node) const
{
if (p_node) {
p_node = p_node->getParent();
while (p_node) {
if (isRecycleBinNode(p_node)) {
return true;
}
p_node = p_node->getParent();
}
}
return false;
}
void Notebook::moveNodeToRecycleBin(const Node *p_node)
{
Q_ASSERT(p_node && !p_node->isRoot());
auto children = p_node->getParent()->getChildren();
for (auto &child : children) {
if (p_node == child) {
moveNodeToRecycleBin(child);
return;
}
}
Q_ASSERT(false);
}
void Notebook::moveNodeToRecycleBin(const QSharedPointer<Node> &p_node)
{
auto destNode = getOrCreateRecycleBinDateNode();
copyNodeAsChildOf(p_node, destNode.data(), true);
}
QSharedPointer<Node> Notebook::getOrCreateRecycleBinDateNode()
{
// Name after date.
auto dateNodeName = QDate::currentDate().toString(QStringLiteral("yyyyMMdd"));
auto recycleBinNode = getRecycleBinNode();
auto dateNode = recycleBinNode->findChild(dateNodeName,
FileUtils::isPlatformNameCaseSensitive());
if (!dateNode) {
// Create a date node.
dateNode = newNode(recycleBinNode.data(), Node::Type::Folder, dateNodeName);
}
return dateNode;
}
void Notebook::emptyNode(const Node *p_node, bool p_force)
{
auto children = p_node->getChildren();
for (auto &child : children) {
removeNode(child, p_force);
}
}
void Notebook::moveFileToRecycleBin(const QString &p_filePath)
{
auto node = getOrCreateRecycleBinDateNode();
auto destFilePath = PathUtils::concatenateFilePath(node->fetchRelativePath(),
PathUtils::fileName(p_filePath));
destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath);
m_backend->copyFile(p_filePath, destFilePath);
getBackend()->removeFile(p_filePath);
emit nodeUpdated(node.data());
}
void Notebook::moveDirToRecycleBin(const QString &p_dirPath)
{
auto node = getOrCreateRecycleBinDateNode();
auto destDirPath = PathUtils::concatenateFilePath(node->fetchRelativePath(),
PathUtils::fileName(p_dirPath));
destDirPath = getBackend()->renameIfExistsCaseInsensitive(destDirPath);
m_backend->copyDir(p_dirPath, destDirPath);
getBackend()->removeDir(p_dirPath);
emit nodeUpdated(node.data());
}
QSharedPointer<Node> Notebook::addAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_name,
const NodeParameters &p_paras)
{
return m_configMgr->addAsNode(p_parent, p_type, p_name, p_paras);
}
bool Notebook::isBuiltInFile(const Node *p_node, const QString &p_name) const
{
return m_configMgr->isBuiltInFile(p_node, p_name);
}
bool Notebook::isBuiltInFolder(const Node *p_node, const QString &p_name) const
{
return m_configMgr->isBuiltInFolder(p_node, p_name);
}
QSharedPointer<Node> Notebook::copyAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_path)
{
return m_configMgr->copyAsNode(p_parent, p_type, p_path);
}

View File

@ -0,0 +1,184 @@
#ifndef NOTEBOOK_H
#define NOTEBOOK_H
#include <QObject>
#include <QIcon>
#include <QSharedPointer>
#include "notebookparameters.h"
#include "../global.h"
#include "node.h"
namespace vnotex
{
class INotebookBackend;
class IVersionController;
class INotebookConfigMgr;
struct NodeParameters;
// Base class of notebook.
class Notebook : public QObject
{
Q_OBJECT
public:
Notebook(const NotebookParameters &p_paras,
QObject *p_parent = nullptr);
virtual ~Notebook();
enum { InvalidId = 0 };
ID getId() const;
const QString &getType() const;
const QString &getName() const;
void setName(const QString &p_name);
// Change the config and backend file as well.
void updateName(const QString &p_name);
const QString &getDescription() const;
void setDescription(const QString &p_description);
void updateDescription(const QString &p_description);
// Use getRootFolderAbsolutePath() instead for access.
const QString &getRootFolderPath() const;
QString getRootFolderAbsolutePath() const;
const QIcon &getIcon() const;
void setIcon(const QIcon &p_icon);
const QString &getImageFolder() const;
const QString &getAttachmentFolder() const;
const QDateTime &getCreatedTimeUtc() const;
const QSharedPointer<INotebookBackend> &getBackend() const;
const QSharedPointer<IVersionController> &getVersionController() const;
const QSharedPointer<INotebookConfigMgr> &getConfigMgr() const;
const QSharedPointer<Node> &getRootNode() const;
QSharedPointer<Node> getRecycleBinNode() const;
QSharedPointer<Node> newNode(Node *p_parent, Node::Type p_type, const QString &p_name);
// Add @p_name under @p_parent to add as a new node @p_type.
QSharedPointer<Node> addAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_name,
const NodeParameters &p_paras);
// Copy @p_path to @p_parent and add as a new node @p_type.
QSharedPointer<Node> copyAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_path);
virtual ID getNextNodeId() const = 0;
virtual ID getAndUpdateNextNodeId() = 0;
virtual void load(Node *p_node);
virtual void save(const Node *p_node);
virtual void rename(Node *p_node, const QString &p_name);
virtual void updateNotebookConfig() = 0;
virtual void removeNotebookConfig() = 0;
// @p_path could be absolute or relative.
virtual QSharedPointer<Node> loadNodeByPath(const QString &p_path);
// Copy @p_src as a child of @p_dest. They may belong to different notebooks.
virtual QSharedPointer<Node> copyNodeAsChildOf(const QSharedPointer<Node> &p_src, Node *p_dest, bool p_move);
// Remove @p_node and delete all related files from disk.
// @p_force: if true, will delete all files including files not tracked by configmgr.
// @p_configOnly: if true, will just remove node from config.
void removeNode(const QSharedPointer<Node> &p_node, bool p_force = false, bool p_configOnly = false);
void removeNode(const Node *p_node, bool p_force = false, bool p_configOnly = false);
void moveNodeToRecycleBin(const QSharedPointer<Node> &p_node);
void moveNodeToRecycleBin(const Node *p_node);
// Move @p_filePath to the recycle bin, without adding it as a child node.
void moveFileToRecycleBin(const QString &p_filePath);
// Move @p_dirPath to the recycle bin, without adding it as a child node.
void moveDirToRecycleBin(const QString &p_dirPath);
// Remove all files of this notebook from disk.
virtual void remove() = 0;
bool isRecycleBinNode(const Node *p_node) const;
bool isNodeInRecycleBin(const Node *p_node) const;
// Remove all children node of @p_node.
// @p_force: if true, just delete all folders and files under @p_node.
void emptyNode(const Node *p_node, bool p_force = false);
// Whether @p_name is a built-in file under @p_node.
bool isBuiltInFile(const Node *p_node, const QString &p_name) const;
bool isBuiltInFolder(const Node *p_node, const QString &p_name) const;
static const QString c_defaultAttachmentFolder;
static const QString c_defaultImageFolder;
signals:
void updated();
void nodeUpdated(const Node *p_node);
private:
QSharedPointer<Node> getOrCreateRecycleBinDateNode();
// ID of this notebook.
// Will be assigned uniquely once loaded.
ID m_id;
// Type of this notebook.
QString m_type;
// Name of this notebook.
QString m_name;
// Description of this notebook.
QString m_description;
// Path of the notebook root folder.
QString m_rootFolderPath;
QIcon m_icon;
// Name of the folder to hold images.
QString m_imageFolder;
// Name of the folder to hold attachments.
QString m_attachmentFolder;
QDateTime m_createdTimeUtc;
// Backend for file access and synchronization.
QSharedPointer<INotebookBackend> m_backend;
// Version controller.
QSharedPointer<IVersionController> m_versionController;
// Config manager to read/wirte config files.
QSharedPointer<INotebookConfigMgr> m_configMgr;
QSharedPointer<Node> m_root;
};
} // ns vnotex
#endif // NOTEBOOK_H

View File

@ -0,0 +1,18 @@
SOURCES += \
$$PWD/notebook.cpp \
$$PWD/bundlenotebookfactory.cpp \
$$PWD/notebookparameters.cpp \
$$PWD/bundlenotebook.cpp \
$$PWD/node.cpp \
$$PWD/filenode.cpp \
$$PWD/foldernode.cpp
HEADERS += \
$$PWD/notebook.h \
$$PWD/inotebookfactory.h \
$$PWD/bundlenotebookfactory.h \
$$PWD/notebookparameters.h \
$$PWD/bundlenotebook.h \
$$PWD/node.h \
$$PWD/filenode.h \
$$PWD/foldernode.h

View File

@ -0,0 +1,64 @@
#include "notebookparameters.h"
#include "notebookmgr.h"
using namespace vnotex;
QSharedPointer<NotebookParameters> NotebookParameters::createNotebookParameters(
const NotebookMgr &p_mgr,
const QString &p_type,
const QString &p_name,
const QString &p_description,
const QString &p_rootFolderPath,
const QIcon &p_icon,
const QString &p_imageFolder,
const QString &p_attachmentFolder,
const QDateTime &p_createdTimeUtc,
const QString &p_backend,
const QString &p_versionController,
const QString &p_configMgr)
{
auto backend = p_mgr.createNotebookBackend(p_backend, p_rootFolderPath);
return createNotebookParameters(p_mgr,
backend,
p_type,
p_name,
p_description,
p_rootFolderPath,
p_icon,
p_imageFolder,
p_attachmentFolder,
p_createdTimeUtc,
p_versionController,
p_configMgr);
}
QSharedPointer<NotebookParameters> NotebookParameters::createNotebookParameters(
const NotebookMgr &p_mgr,
const QSharedPointer<INotebookBackend> &p_backend,
const QString &p_type,
const QString &p_name,
const QString &p_description,
const QString &p_rootFolderPath,
const QIcon &p_icon,
const QString &p_imageFolder,
const QString &p_attachmentFolder,
const QDateTime &p_createdTimeUtc,
const QString &p_versionController,
const QString &p_configMgr)
{
auto paras = QSharedPointer<NotebookParameters>::create();
paras->m_type = p_type;
paras->m_name = p_name;
paras->m_description = p_description;
paras->m_rootFolderPath = p_rootFolderPath;
paras->m_icon = p_icon;
paras->m_imageFolder = p_imageFolder;
paras->m_attachmentFolder = p_attachmentFolder;
paras->m_createdTimeUtc = p_createdTimeUtc;
paras->m_notebookBackend = p_backend;
paras->m_versionController = p_mgr.createVersionController(p_versionController);
paras->m_notebookConfigMgr = p_mgr.createNotebookConfigMgr(p_configMgr,
paras->m_notebookBackend);
return paras;
}

View File

@ -0,0 +1,70 @@
#ifndef NOTEBOOKPARAMETERS_H
#define NOTEBOOKPARAMETERS_H
#include <QSharedPointer>
#include <QIcon>
#include <QDateTime>
namespace vnotex
{
class NotebookMgr;
class INotebookBackend;
class IVersionController;
class INotebookConfigMgr;
// Used to new a notebook.
class NotebookParameters
{
public:
virtual ~NotebookParameters() {}
static QSharedPointer<NotebookParameters> createNotebookParameters(
const NotebookMgr &p_mgr,
const QString &p_type,
const QString &p_name,
const QString &p_description,
const QString &p_rootFolderPath,
const QIcon &p_icon,
const QString &p_imageFolder,
const QString &p_attachmentFolder,
const QDateTime &p_createdTimeUtc,
const QString &p_backend,
const QString &p_versionController,
const QString &p_configMgr);
static QSharedPointer<NotebookParameters> createNotebookParameters(
const NotebookMgr &p_mgr,
const QSharedPointer<INotebookBackend> &p_backend,
const QString &p_type,
const QString &p_name,
const QString &p_description,
const QString &p_rootFolderPath,
const QIcon &p_icon,
const QString &p_imageFolder,
const QString &p_attachmentFolder,
const QDateTime &p_createdTimeUtc,
const QString &p_versionController,
const QString &p_configMgr);
QString m_type;
QString m_name;
QString m_description;
QString m_rootFolderPath;
QIcon m_icon;
// Name of image folder.
QString m_imageFolder;
// Name of attachment folder.
QString m_attachmentFolder;
QDateTime m_createdTimeUtc;
QSharedPointer<INotebookBackend> m_notebookBackend;
QSharedPointer<IVersionController> m_versionController;
QSharedPointer<INotebookConfigMgr> m_notebookConfigMgr;
bool m_ensureEmptyRootFolder = true;
};
} // ns vnotex
#endif // NOTEBOOKPARAMETERS_H

View File

@ -0,0 +1,23 @@
#include "inotebookbackend.h"
#include <QDir>
#include <exception.h>
#include <utils/pathutils.h>
using namespace vnotex;
void INotebookBackend::constrainPath(const QString &p_path) const
{
if (!PathUtils::pathContains(m_rootPath, p_path)) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("path (%1) does not locate in root folder (%2)")
.arg(p_path, m_rootPath));
}
}
QString INotebookBackend::getFullPath(const QString &p_path) const
{
constrainPath(p_path);
return QDir(m_rootPath).filePath(p_path);
}

View File

@ -0,0 +1,111 @@
#ifndef INOTEBOOKBACKEND_H
#define INOTEBOOKBACKEND_H
#include <QObject>
#include <utils/pathutils.h>
class QByteArray;
class QJsonObject;
namespace vnotex
{
// Abstract class for notebook backend, which is responsible for file access
// and synchronization.
class INotebookBackend : public QObject
{
Q_OBJECT
public:
INotebookBackend(const QString &p_rootPath, QObject *p_parent = nullptr)
: QObject(p_parent),
m_rootPath(PathUtils::absolutePath(p_rootPath))
{
}
virtual ~INotebookBackend()
{
}
virtual QString getName() const = 0;
virtual QString getDisplayName() const = 0;
virtual QString getDescription() const = 0;
const QString &getRootPath() const
{
return m_rootPath;
}
void setRootPath(const QString &p_rootPath)
{
m_rootPath = p_rootPath;
}
// Whether @p_dirPath is an empty directory.
virtual bool isEmptyDir(const QString &p_dirPath) const = 0;
// Create the directory path @p_dirPath. Create all parent directories if necessary.
virtual void makePath(const QString &p_dirPath) = 0;
// Write @p_data to @p_filePath.
virtual void writeFile(const QString &p_filePath, const QByteArray &p_data) = 0;
// Write @p_text to @p_filePath.
virtual void writeFile(const QString &p_filePath, const QString &p_text) = 0;
// Write @p_jobj to @p_filePath.
virtual void writeFile(const QString &p_filePath, const QJsonObject &p_jobj) = 0;
// Read content from @p_filePath.
virtual QString readTextFile(const QString &p_filePath) = 0;
// Read file @p_filePath.
virtual QByteArray readFile(const QString &p_filePath) = 0;
QString getFullPath(const QString &p_path) const;
virtual bool exists(const QString &p_path) const = 0;
virtual bool childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name) const = 0;
virtual bool isFile(const QString &p_path) const = 0;
virtual void renameFile(const QString &p_filePath, const QString &p_name) = 0;
virtual void renameDir(const QString &p_dirPath, const QString &p_name) = 0;
// Copy @p_filePath to @p_destPath.
// @p_filePath could be outside notebook.
virtual void copyFile(const QString &p_filePath, const QString &p_destPath) = 0;
// Delete @p_filePath from disk.
virtual void removeFile(const QString &p_filePath) = 0;
// Copy @p_dirPath to as @p_destPath.
virtual void copyDir(const QString &p_dirPath, const QString &p_destPath) = 0;
// Delete @p_dirPath from disk if it is empty.
// Return false if it is not deleted due to non-empty.
virtual bool removeDirIfEmpty(const QString &p_dirPath) = 0;
virtual void removeDir(const QString &p_dirPath) = 0;
virtual QString renameIfExistsCaseInsensitive(const QString &p_path) const = 0;
// Add one file to backend.
virtual void addFile(const QString &p_path) = 0;
virtual void removeEmptyDir(const QString &p_dirPath) = 0;
protected:
// Constrain @p_path within root path of the notebook.
void constrainPath(const QString &p_path) const;
private:
// Root path of the notebook.
QString m_rootPath;
};
} // ns vnotex
#endif // INOTEBOOKBACKEND_H

View File

@ -0,0 +1,31 @@
#ifndef INOTEBOOKBACKENDFACTORY_H
#define INOTEBOOKBACKENDFACTORY_H
#include <QSharedPointer>
namespace vnotex
{
class INotebookBackend;
class INotebookBackendFactory
{
public:
INotebookBackendFactory()
{
}
virtual ~INotebookBackendFactory()
{
}
virtual QString getName() const = 0;
virtual QString getDisplayName() const = 0;
virtual QString getDescription() const = 0;
virtual QSharedPointer<INotebookBackend> createNotebookBackend(const QString &p_rootPath) = 0;
};
} // ns vnotex
#endif // INOTEBOOKBACKENDFACTORY_H

View File

@ -0,0 +1,170 @@
#include "localnotebookbackend.h"
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QJsonObject>
#include <QJsonDocument>
#include <utils/pathutils.h>
#include "exception.h"
#include <utils/fileutils.h>
using namespace vnotex;
LocalNotebookBackend::LocalNotebookBackend(const QString &p_name,
const QString &p_displayName,
const QString &p_description,
const QString &p_rootPath,
QObject *p_parent)
: INotebookBackend(p_rootPath, p_parent),
m_info(p_name, p_displayName, p_description)
{
}
QString LocalNotebookBackend::getName() const
{
return m_info.m_name;
}
QString LocalNotebookBackend::getDisplayName() const
{
return m_info.m_displayName;
}
QString LocalNotebookBackend::getDescription() const
{
return m_info.m_description;
}
bool LocalNotebookBackend::isEmptyDir(const QString &p_dirPath) const
{
return PathUtils::isEmptyDir(getFullPath(p_dirPath));
}
void LocalNotebookBackend::makePath(const QString &p_dirPath)
{
constrainPath(p_dirPath);
QDir dir(getRootPath());
if (!dir.mkpath(p_dirPath)) {
Exception::throwOne(Exception::Type::FailToCreateDir,
QString("fail to create directory: %1").arg(p_dirPath));
}
}
void LocalNotebookBackend::writeFile(const QString &p_filePath, const QByteArray &p_data)
{
const auto filePath = getFullPath(p_filePath);
FileUtils::writeFile(filePath, p_data);
}
void LocalNotebookBackend::writeFile(const QString &p_filePath, const QString &p_text)
{
const auto filePath = getFullPath(p_filePath);
FileUtils::writeFile(filePath, p_text);
}
void LocalNotebookBackend::writeFile(const QString &p_filePath, const QJsonObject &p_jobj)
{
writeFile(p_filePath, QJsonDocument(p_jobj).toJson());
}
QString LocalNotebookBackend::readTextFile(const QString &p_filePath)
{
const auto filePath = getFullPath(p_filePath);
return FileUtils::readTextFile(filePath);
}
QByteArray LocalNotebookBackend::readFile(const QString &p_filePath)
{
const auto filePath = getFullPath(p_filePath);
return FileUtils::readFile(filePath);
}
bool LocalNotebookBackend::exists(const QString &p_path) const
{
return QFileInfo::exists(getFullPath(p_path));
}
bool LocalNotebookBackend::childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name) const
{
return FileUtils::childExistsCaseInsensitive(getFullPath(p_dirPath), p_name);
}
bool LocalNotebookBackend::isFile(const QString &p_path) const
{
QFileInfo fi(getFullPath(p_path));
return fi.isFile();
}
void LocalNotebookBackend::renameFile(const QString &p_filePath, const QString &p_name)
{
Q_ASSERT(isFile(p_filePath));
const auto filePath = getFullPath(p_filePath);
FileUtils::renameFile(filePath, p_name);
}
void LocalNotebookBackend::renameDir(const QString &p_dirPath, const QString &p_name)
{
Q_ASSERT(!isFile(p_dirPath));
const auto dirPath = getFullPath(p_dirPath);
FileUtils::renameFile(dirPath, p_name);
}
void LocalNotebookBackend::copyFile(const QString &p_filePath, const QString &p_destPath)
{
auto filePath = p_filePath;
if (QFileInfo(filePath).isRelative()) {
filePath = getFullPath(filePath);
}
Q_ASSERT(QFileInfo(filePath).isFile());
FileUtils::copyFile(filePath, getFullPath(p_destPath));
}
void LocalNotebookBackend::copyDir(const QString &p_dirPath, const QString &p_destPath)
{
auto dirPath = p_dirPath;
if (QFileInfo(dirPath).isRelative()) {
dirPath = getFullPath(dirPath);
}
Q_ASSERT(QFileInfo(dirPath).isDir());
FileUtils::copyDir(dirPath, getFullPath(p_destPath));
}
void LocalNotebookBackend::removeFile(const QString &p_filePath)
{
Q_ASSERT(isFile(p_filePath));
FileUtils::removeFile(getFullPath(p_filePath));
}
bool LocalNotebookBackend::removeDirIfEmpty(const QString &p_dirPath)
{
Q_ASSERT(!isFile(p_dirPath));
return FileUtils::removeDirIfEmpty(getFullPath(p_dirPath));
}
void LocalNotebookBackend::removeDir(const QString &p_dirPath)
{
Q_ASSERT(!isFile(p_dirPath));
return FileUtils::removeDir(getFullPath(p_dirPath));
}
QString LocalNotebookBackend::renameIfExistsCaseInsensitive(const QString &p_path) const
{
return FileUtils::renameIfExistsCaseInsensitive(getFullPath(p_path));
}
void LocalNotebookBackend::addFile(const QString &p_path)
{
Q_UNUSED(p_path);
// Do nothing for now.
}
void LocalNotebookBackend::removeEmptyDir(const QString &p_dirPath)
{
FileUtils::removeEmptyDir(getFullPath(p_dirPath));
}

View File

@ -0,0 +1,84 @@
#ifndef LOCALNOTEBOOKBACKEND_H
#define LOCALNOTEBOOKBACKEND_H
#include "inotebookbackend.h"
#include "../global.h"
namespace vnotex
{
// Backend to access local file system.
class LocalNotebookBackend : public INotebookBackend
{
Q_OBJECT
public:
explicit LocalNotebookBackend(const QString &p_name,
const QString &p_displayName,
const QString &p_description,
const QString &p_rootPath,
QObject *p_parent = nullptr);
QString getName() const Q_DECL_OVERRIDE;
QString getDisplayName() const Q_DECL_OVERRIDE;
QString getDescription() const Q_DECL_OVERRIDE;
// Whether @p_dirPath is an empty directory.
bool isEmptyDir(const QString &p_dirPath) const Q_DECL_OVERRIDE;
// Create the directory path @p_dirPath. Create all parent directories if necessary.
void makePath(const QString &p_dirPath) Q_DECL_OVERRIDE;
// Write @p_data to @p_filePath.
void writeFile(const QString &p_filePath, const QByteArray &p_data) Q_DECL_OVERRIDE;
// Write @p_text to @p_filePath.
void writeFile(const QString &p_filePath, const QString &p_text) Q_DECL_OVERRIDE;
// Write @p_jobj to @p_filePath.
void writeFile(const QString &p_filePath, const QJsonObject &p_jobj) Q_DECL_OVERRIDE;
// Read content from @p_filePath.
QString readTextFile(const QString &p_filePath) Q_DECL_OVERRIDE;
// Read file @p_filePath.
QByteArray readFile(const QString &p_filePath) Q_DECL_OVERRIDE;
bool exists(const QString &p_path) const Q_DECL_OVERRIDE;
bool childExistsCaseInsensitive(const QString &p_dirPath, const QString &p_name) const Q_DECL_OVERRIDE;
bool isFile(const QString &p_path) const Q_DECL_OVERRIDE;
void renameFile(const QString &p_filePath, const QString &p_name) Q_DECL_OVERRIDE;
void renameDir(const QString &p_dirPath, const QString &p_name) Q_DECL_OVERRIDE;
// Delete @p_filePath from disk.
void removeFile(const QString &p_filePath) Q_DECL_OVERRIDE;
// Delete @p_dirPath from disk if it is empty.
bool removeDirIfEmpty(const QString &p_dirPath) Q_DECL_OVERRIDE;
void removeDir(const QString &p_dirPath) Q_DECL_OVERRIDE;
// Copy @p_filePath to @p_destPath.
// @p_filePath may beyond this notebook backend.
void copyFile(const QString &p_filePath, const QString &p_destPath) Q_DECL_OVERRIDE;
// Copy @p_dirPath to as @p_destPath.
void copyDir(const QString &p_dirPath, const QString &p_destPath) Q_DECL_OVERRIDE;
QString renameIfExistsCaseInsensitive(const QString &p_path) const Q_DECL_OVERRIDE;
void addFile(const QString &p_path) Q_DECL_OVERRIDE;
void removeEmptyDir(const QString &p_dirPath) Q_DECL_OVERRIDE;
private:
Info m_info;
};
} // ns vnotex
#endif // LOCALNOTEBOOKBACKEND_H

View File

@ -0,0 +1,34 @@
#include "localnotebookbackendfactory.h"
#include <QObject>
#include "localnotebookbackend.h"
using namespace vnotex;
LocalNotebookBackendFactory::LocalNotebookBackendFactory()
{
}
QString LocalNotebookBackendFactory::getName() const
{
return QStringLiteral("local.vnotex");
}
QString LocalNotebookBackendFactory::getDisplayName() const
{
return QObject::tr("Local Notebook Backend");
}
QString LocalNotebookBackendFactory::getDescription() const
{
return QObject::tr("Local file system");
}
QSharedPointer<INotebookBackend> LocalNotebookBackendFactory::createNotebookBackend(const QString &p_rootPath)
{
return QSharedPointer<LocalNotebookBackend>::create(getName(),
getDisplayName(),
getDescription(),
p_rootPath);
}

View File

@ -0,0 +1,24 @@
#ifndef LOCALNOTEBOOKBACKENDFACTORY_H
#define LOCALNOTEBOOKBACKENDFACTORY_H
#include "inotebookbackendfactory.h"
namespace vnotex
{
class LocalNotebookBackendFactory : public INotebookBackendFactory
{
public:
LocalNotebookBackendFactory();
QString getName() const Q_DECL_OVERRIDE;
QString getDisplayName() const Q_DECL_OVERRIDE;
QString getDescription()const Q_DECL_OVERRIDE;
QSharedPointer<INotebookBackend> createNotebookBackend(const QString &p_rootPath) Q_DECL_OVERRIDE;
};
} // ns vnotex
#endif // LOCALNOTEBOOKBACKENDFACTORY_H

View File

@ -0,0 +1,10 @@
SOURCES += \
$$PWD/localnotebookbackend.cpp \
$$PWD/localnotebookbackendfactory.cpp \
$$PWD/inotebookbackend.cpp
HEADERS += \
$$PWD/inotebookbackend.h \
$$PWD/localnotebookbackend.h \
$$PWD/inotebookbackendfactory.h \
$$PWD/localnotebookbackendfactory.h

View File

@ -0,0 +1,96 @@
#include "bundlenotebookconfigmgr.h"
#include <QJsonDocument>
#include <notebookbackend/inotebookbackend.h>
#include <notebook/notebookparameters.h>
#include <notebook/bundlenotebook.h>
#include "notebookconfig.h"
#include <utils/pathutils.h>
using namespace vnotex;
const QString BundleNotebookConfigMgr::c_configFolderName = "vx_notebook";
const QString BundleNotebookConfigMgr::c_configName = "vx_notebook.json";
BundleNotebookConfigMgr::BundleNotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend,
QObject *p_parent)
: INotebookConfigMgr(p_backend, p_parent)
{
}
void BundleNotebookConfigMgr::createEmptySkeleton(const NotebookParameters &p_paras)
{
getBackend()->makePath(BundleNotebookConfigMgr::c_configFolderName);
auto config = NotebookConfig::fromNotebookParameters(getCodeVersion(), p_paras);
writeNotebookConfig(*config);
}
QSharedPointer<NotebookConfig> BundleNotebookConfigMgr::readNotebookConfig() const
{
return readNotebookConfig(getBackend());
}
void BundleNotebookConfigMgr::writeNotebookConfig()
{
auto config = NotebookConfig::fromNotebook(getCodeVersion(), getNotebook());
writeNotebookConfig(*config);
}
void BundleNotebookConfigMgr::writeNotebookConfig(const NotebookConfig &p_config)
{
getBackend()->writeFile(getConfigFilePath(), p_config.toJson());
}
void BundleNotebookConfigMgr::removeNotebookConfig()
{
getBackend()->removeDir(getConfigFolderName());
}
QSharedPointer<NotebookConfig> BundleNotebookConfigMgr::readNotebookConfig(
const QSharedPointer<INotebookBackend> &p_backend)
{
auto data = p_backend->readFile(getConfigFilePath());
auto config = QSharedPointer<NotebookConfig>::create();
config->fromJson(QJsonDocument::fromJson(data).object());
return config;
}
const QString &BundleNotebookConfigMgr::getConfigFolderName()
{
return c_configFolderName;
}
const QString &BundleNotebookConfigMgr::getConfigName()
{
return c_configName;
}
QString BundleNotebookConfigMgr::getConfigFilePath()
{
return PathUtils::concatenateFilePath(c_configFolderName, c_configName);
}
BundleNotebook *BundleNotebookConfigMgr::getBundleNotebook() const
{
return dynamic_cast<BundleNotebook *>(getNotebook());
}
bool BundleNotebookConfigMgr::isBuiltInFile(const Node *p_node, const QString &p_name) const
{
Q_UNUSED(p_node);
Q_UNUSED(p_name);
return false;
}
bool BundleNotebookConfigMgr::isBuiltInFolder(const Node *p_node, const QString &p_name) const
{
if (p_node->isRoot()) {
return p_name.toLower() == c_configFolderName;
}
return false;
}

View File

@ -0,0 +1,54 @@
#ifndef BUNDLENOTEBOOKCONFIGMGR_H
#define BUNDLENOTEBOOKCONFIGMGR_H
#include "inotebookconfigmgr.h"
namespace vnotex
{
class BundleNotebook;
class BundleNotebookConfigMgr : public INotebookConfigMgr
{
Q_OBJECT
public:
BundleNotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend,
QObject *p_parent = nullptr);
// Create an empty skeleton for an empty notebook.
virtual void createEmptySkeleton(const NotebookParameters &p_paras) Q_DECL_OVERRIDE;
QSharedPointer<NotebookConfig> readNotebookConfig() const;
void writeNotebookConfig();
void removeNotebookConfig();
bool isBuiltInFile(const Node *p_node, const QString &p_name) const Q_DECL_OVERRIDE;
bool isBuiltInFolder(const Node *p_node, const QString &p_name) const Q_DECL_OVERRIDE;
static const QString &getConfigFolderName();
static const QString &getConfigName();
static QString getConfigFilePath();
static QSharedPointer<NotebookConfig> readNotebookConfig(const QSharedPointer<INotebookBackend> &p_backend);
enum { RootNodeId = 1 };
protected:
BundleNotebook *getBundleNotebook() const;
private:
void writeNotebookConfig(const NotebookConfig &p_config);
// Folder name to store the notebook's config.
// This folder locates in the root folder of the notebook.
static const QString c_configFolderName;
// Name of the notebook's config file.
static const QString c_configName;
};
} // ns vnotex
#endif // BUNDLENOTEBOOKCONFIGMGR_H

View File

@ -0,0 +1,37 @@
#include "inotebookconfigmgr.h"
#include <notebookbackend/inotebookbackend.h>
using namespace vnotex;
INotebookConfigMgr::INotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend,
QObject *p_parent)
: QObject(p_parent),
m_backend(p_backend)
{
}
INotebookConfigMgr::~INotebookConfigMgr()
{
}
const QSharedPointer<INotebookBackend> &INotebookConfigMgr::getBackend() const
{
return m_backend;
}
QString INotebookConfigMgr::getCodeVersion() const
{
const QString version("1");
return version;
}
Notebook *INotebookConfigMgr::getNotebook() const
{
return m_notebook;
}
void INotebookConfigMgr::setNotebook(Notebook *p_notebook)
{
m_notebook = p_notebook;
}

View File

@ -0,0 +1,96 @@
#ifndef INOTEBOOKCONFIGMGR_H
#define INOTEBOOKCONFIGMGR_H
#include <QObject>
#include <QSharedPointer>
#include "notebook/node.h"
namespace vnotex
{
class NotebookConfig;
class INotebookBackend;
class NotebookParameters;
class Notebook;
struct NodeParameters;
// Abstract class for notebook config manager, which is responsible for config
// files access and note nodes access.
class INotebookConfigMgr : public QObject
{
Q_OBJECT
public:
INotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend,
QObject *p_parent = nullptr);
virtual ~INotebookConfigMgr();
virtual QString getName() const = 0;
virtual QString getDisplayName() const = 0;
virtual QString getDescription() const = 0;
// Create an empty skeleton for an empty notebook.
virtual void createEmptySkeleton(const NotebookParameters &p_paras) = 0;
const QSharedPointer<INotebookBackend> &getBackend() const;
virtual QSharedPointer<Node> loadRootNode() const = 0;
virtual void loadNode(Node *p_node) const = 0;
virtual void saveNode(const Node *p_node) = 0;
virtual void renameNode(Node *p_node, const QString &p_name) = 0;
virtual QSharedPointer<Node> newNode(Node *p_parent,
Node::Type p_type,
const QString &p_name) = 0;
virtual QSharedPointer<Node> addAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_name,
const NodeParameters &p_paras) = 0;
virtual QSharedPointer<Node> copyAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_path) = 0;
Notebook *getNotebook() const;
void setNotebook(Notebook *p_notebook);
virtual QSharedPointer<Node> loadNodeByPath(const QSharedPointer<Node> &p_root,
const QString &p_relativePath) = 0;
virtual QSharedPointer<Node> copyNodeAsChildOf(const QSharedPointer<Node> &p_src,
Node *p_dest,
bool p_move) = 0;
virtual void removeNode(const QSharedPointer<Node> &p_node, bool p_force, bool p_configOnly) = 0;
virtual bool nodeExistsOnDisk(const Node *p_node) const = 0;
virtual QString readNode(const Node *p_node) const = 0;
virtual void writeNode(Node *p_node, const QString &p_content) = 0;
virtual QString fetchNodeImageFolderPath(Node *p_node) = 0;
virtual QString fetchNodeAttachmentFolderPath(Node *p_node) = 0;
// Whether @p_name is a built-in file under @p_node.
virtual bool isBuiltInFile(const Node *p_node, const QString &p_name) const = 0;
virtual bool isBuiltInFolder(const Node *p_node, const QString &p_name) const = 0;
protected:
// Version of the config processing code.
virtual QString getCodeVersion() const;
private:
QSharedPointer<INotebookBackend> m_backend;
Notebook *m_notebook = nullptr;
};
} // ns vnotex
#endif // INOTEBOOKCONFIGMGR_H

View File

@ -0,0 +1,33 @@
#ifndef INOTEBOOKCONFIGMGRFACTORY_H
#define INOTEBOOKCONFIGMGRFACTORY_H
#include <QSharedPointer>
namespace vnotex
{
class INotebookConfigMgr;
class INotebookBackend;
class INotebookConfigMgrFactory
{
public:
INotebookConfigMgrFactory()
{
}
virtual ~INotebookConfigMgrFactory()
{
}
virtual QString getName() const = 0;
virtual QString getDisplayName() const = 0;
virtual QString getDescription() const = 0;
virtual QSharedPointer<INotebookConfigMgr> createNotebookConfigMgr(
const QSharedPointer<INotebookBackend> &p_backend) = 0;
};
} // ns vnotex
#endif // INOTEBOOKCONFIGMGRFACTORY_H

View File

@ -0,0 +1,177 @@
#include "nodecontentmediautils.h"
#include <QDebug>
#include <QSet>
#include <QFileInfo>
#include <QDir>
#include <QHash>
#include <notebookbackend/inotebookbackend.h>
#include <notebook/node.h>
#include <buffer/filetypehelper.h>
#include <vtextedit/markdownutils.h>
#include <utils/pathutils.h>
#include <utils/fileutils.h>
using namespace vnotex;
void NodeContentMediaUtils::copyMediaFiles(const Node *p_node,
INotebookBackend *p_backend,
const QString &p_destFilePath)
{
Q_ASSERT(p_node->getType() == Node::Type::File);
auto fileType = FileTypeHelper::fileType(p_node->fetchAbsolutePath());
if (fileType == QStringLiteral("markdown")) {
copyMarkdownMediaFiles(p_node->read(),
PathUtils::parentDirPath(p_node->fetchContentPath()),
p_backend,
p_destFilePath);
}
}
void NodeContentMediaUtils::copyMediaFiles(const QString &p_filePath,
INotebookBackend *p_backend,
const QString &p_destFilePath)
{
auto fileType = FileTypeHelper::fileType(p_filePath);
if (fileType == QStringLiteral("markdown")) {
copyMarkdownMediaFiles(FileUtils::readTextFile(p_filePath),
PathUtils::parentDirPath(p_filePath),
p_backend,
p_destFilePath);
}
}
void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
const QString &p_basePath,
INotebookBackend *p_backend,
const QString &p_destFilePath)
{
auto content = p_content;
// Images.
const auto images =
vte::MarkdownUtils::fetchImagesFromMarkdownText(content,
p_basePath,
vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
QDir destDir(PathUtils::parentDirPath(p_destFilePath));
QSet<QString> handledImages;
QHash<QString, QString> renamedImages;
int lastPos = content.size();
for (const auto &link : images) {
Q_ASSERT(link.m_urlInLinkPos < lastPos);
lastPos = link.m_urlInLinkPos;
if (handledImages.contains(link.m_path)) {
auto it = renamedImages.find(link.m_path);
if (it != renamedImages.end()) {
content.replace(link.m_urlInLinkPos, link.m_urlInLink.size(), it.value());
}
continue;
}
handledImages.insert(link.m_path);
if (!QFileInfo::exists(link.m_path)) {
qWarning() << "Image of Markdown file does not exist" << link.m_path << link.m_urlInLink;
continue;
}
// Get the relative path of the image and apply it to the dest file path.
const auto oldDestFilePath = destDir.filePath(link.m_urlInLink);
destDir.mkpath(PathUtils::parentDirPath(oldDestFilePath));
auto destFilePath = p_backend->renameIfExistsCaseInsensitive(oldDestFilePath);
if (oldDestFilePath != destFilePath) {
// Rename happens.
const auto oldFileName = PathUtils::fileName(oldDestFilePath);
const auto newFileName = PathUtils::fileName(destFilePath);
qWarning() << QString("Image name conflicts when copy. Renamed from (%1) to (%2)").arg(oldFileName, newFileName);
// Update the text content.
auto newUrlInLink(link.m_urlInLink);
newUrlInLink.replace(newUrlInLink.size() - oldFileName.size(),
oldFileName.size(),
newFileName);
content.replace(link.m_urlInLinkPos, link.m_urlInLink.size(), newUrlInLink);
renamedImages.insert(link.m_path, newUrlInLink);
}
p_backend->copyFile(link.m_path, destFilePath);
}
if (!renamedImages.isEmpty()) {
p_backend->writeFile(p_destFilePath, content);
}
}
void NodeContentMediaUtils::removeMediaFiles(const Node *p_node)
{
Q_ASSERT(p_node->getType() == Node::Type::File);
auto fileType = FileTypeHelper::fileType(p_node->fetchAbsolutePath());
if (fileType == QStringLiteral("markdown")) {
removeMarkdownMediaFiles(p_node);
}
}
void NodeContentMediaUtils::removeMarkdownMediaFiles(const Node *p_node)
{
auto content = p_node->read();
// Images.
const auto images =
vte::MarkdownUtils::fetchImagesFromMarkdownText(content,
PathUtils::parentDirPath(p_node->fetchContentPath()),
vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
auto backend = p_node->getBackend();
QSet<QString> handledImages;
for (const auto &link : images) {
if (handledImages.contains(link.m_path)) {
continue;
}
handledImages.insert(link.m_path);
if (!QFileInfo::exists(link.m_path)) {
qWarning() << "Image of Markdown file does not exist" << link.m_path << link.m_urlInLink;
continue;
}
backend->removeFile(link.m_path);
}
}
void NodeContentMediaUtils::copyAttachment(Node *p_node,
INotebookBackend *p_backend,
const QString &p_destFilePath,
const QString &p_destAttachmentFolderPath)
{
Q_ASSERT(p_node->getType() == Node::Type::File);
Q_ASSERT(!p_node->getAttachmentFolder().isEmpty());
// Copy the whole folder.
const auto srcAttachmentFolderPath = p_node->fetchAttachmentFolderPath();
p_backend->copyDir(srcAttachmentFolderPath, p_destAttachmentFolderPath);
// Check if we need to modify links in content.
if (p_node->getAttachmentFolder() == PathUtils::dirName(p_destAttachmentFolderPath)) {
return;
}
auto fileType = FileTypeHelper::fileType(p_node->fetchAbsolutePath());
if (fileType == QStringLiteral("markdown")) {
fixMarkdownLinks(srcAttachmentFolderPath, p_backend, p_destFilePath, p_destAttachmentFolderPath);
}
}
void NodeContentMediaUtils::fixMarkdownLinks(const QString &p_srcFolderPath,
INotebookBackend *p_backend,
const QString &p_destFilePath,
const QString &p_destFolderPath)
{
// TODO.
}

View File

@ -0,0 +1,52 @@
#ifndef NODECONTENTMEDIAUTILS_H
#define NODECONTENTMEDIAUTILS_H
#include <QString>
namespace vnotex
{
class INotebookBackend;
class Node;
// Utils to operate on the media files from node's content.
class NodeContentMediaUtils
{
public:
NodeContentMediaUtils() = delete;
// Fetch media files from @p_node and copy them to dest folder.
// @p_destFilePath: @p_node has been copied to @p_destFilePath.
static void copyMediaFiles(const Node *p_node,
INotebookBackend *p_backend,
const QString &p_destFilePath);
// @p_filePath: the file path to read the content for parse.
static void copyMediaFiles(const QString &p_filePath,
INotebookBackend *p_backend,
const QString &p_destFilePath);
static void removeMediaFiles(const Node *p_node);
// Copy attachment folder.
static void copyAttachment(Node *p_node,
INotebookBackend *p_backend,
const QString &p_destFilePath,
const QString &p_destAttachmentFolderPath);
private:
static void copyMarkdownMediaFiles(const QString &p_content,
const QString &p_basePath,
INotebookBackend *p_backend,
const QString &p_destFilePath);
static void removeMarkdownMediaFiles(const Node *p_node);
// Fix local relative internal links locating in @p_srcFolderPath.
static void fixMarkdownLinks(const QString &p_srcFolderPath,
INotebookBackend *p_backend,
const QString &p_destFilePath,
const QString &p_destFolderPath);
};
}
#endif // NODECONTENTMEDIAUTILS_H

View File

@ -0,0 +1,111 @@
#include "notebookconfig.h"
#include <notebook/notebookparameters.h>
#include <notebook/notebook.h>
#include <versioncontroller/iversioncontroller.h>
#include <utils/utils.h>
#include "exception.h"
#include "global.h"
using namespace vnotex;
const QString NotebookConfig::c_version = "version";
const QString NotebookConfig::c_name = "name";
const QString NotebookConfig::c_description = "description";
const QString NotebookConfig::c_imageFolder = "image_folder";
const QString NotebookConfig::c_attachmentFolder = "attachment_folder";
const QString NotebookConfig::c_createdTimeUtc = "created_time";
const QString NotebookConfig::c_versionController = "version_controller";
const QString NotebookConfig::c_configMgr = "config_mgr";
const QString NotebookConfig::c_nextNodeId = "next_node_id";
QSharedPointer<NotebookConfig> NotebookConfig::fromNotebookParameters(const QString &p_version,
const NotebookParameters &p_paras)
{
auto config = QSharedPointer<NotebookConfig>::create();
config->m_version = p_version;
config->m_name = p_paras.m_name;
config->m_description = p_paras.m_description;
config->m_imageFolder = p_paras.m_imageFolder;
config->m_attachmentFolder = p_paras.m_attachmentFolder;
config->m_createdTimeUtc = p_paras.m_createdTimeUtc;
config->m_versionController = p_paras.m_versionController->getName();
config->m_notebookConfigMgr = p_paras.m_notebookConfigMgr->getName();
return config;
}
QJsonObject NotebookConfig::toJson() const
{
QJsonObject jobj;
jobj[NotebookConfig::c_version] = m_version;
jobj[NotebookConfig::c_name] = m_name;
jobj[NotebookConfig::c_description] = m_description;
jobj[NotebookConfig::c_imageFolder] = m_imageFolder;
jobj[NotebookConfig::c_attachmentFolder] = m_attachmentFolder;
jobj[NotebookConfig::c_createdTimeUtc] = Utils::dateTimeStringUniform(m_createdTimeUtc);
jobj[NotebookConfig::c_versionController] = m_versionController;
jobj[NotebookConfig::c_configMgr] = m_notebookConfigMgr;
jobj[NotebookConfig::c_nextNodeId] = QString::number(m_nextNodeId);
return jobj;
}
void NotebookConfig::fromJson(const QJsonObject &p_jobj)
{
if (!p_jobj.contains(NotebookConfig::c_version)
|| !p_jobj.contains(NotebookConfig::c_name)
|| !p_jobj.contains(NotebookConfig::c_createdTimeUtc)
|| !p_jobj.contains(NotebookConfig::c_versionController)
|| !p_jobj.contains(NotebookConfig::c_configMgr)) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("fail to read notebook configuration from JSON (%1)").arg(QJsonObjectToString(p_jobj)));
return;
}
m_version = p_jobj[NotebookConfig::c_version].toString();
m_name = p_jobj[NotebookConfig::c_name].toString();
m_description = p_jobj[NotebookConfig::c_description].toString();
m_imageFolder = p_jobj[NotebookConfig::c_imageFolder].toString();
m_attachmentFolder = p_jobj[NotebookConfig::c_attachmentFolder].toString();
m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NotebookConfig::c_createdTimeUtc].toString());
m_versionController = p_jobj[NotebookConfig::c_versionController].toString();
m_notebookConfigMgr = p_jobj[NotebookConfig::c_configMgr].toString();
{
auto nextNodeIdStr = p_jobj[NotebookConfig::c_nextNodeId].toString();
bool ok;
m_nextNodeId = nextNodeIdStr.toULongLong(&ok);
if (!ok) {
m_nextNodeId = BundleNotebookConfigMgr::RootNodeId;
}
}
}
QSharedPointer<NotebookConfig> NotebookConfig::fromNotebook(const QString &p_version,
const Notebook *p_notebook)
{
auto config = QSharedPointer<NotebookConfig>::create();
config->m_version = p_version;
config->m_name = p_notebook->getName();
config->m_description = p_notebook->getDescription();
config->m_imageFolder = p_notebook->getImageFolder();
config->m_attachmentFolder = p_notebook->getAttachmentFolder();
config->m_createdTimeUtc = p_notebook->getCreatedTimeUtc();
config->m_versionController = p_notebook->getVersionController()->getName();
config->m_notebookConfigMgr = p_notebook->getConfigMgr()->getName();
config->m_nextNodeId = p_notebook->getNextNodeId();
return config;
}

View File

@ -0,0 +1,68 @@
#ifndef NOTEBOOKCONFIG_H
#define NOTEBOOKCONFIG_H
#include <QJsonObject>
#include <QSharedPointer>
#include <QDateTime>
#include "bundlenotebookconfigmgr.h"
#include "global.h"
namespace vnotex
{
class NotebookParameters;
class NotebookConfig
{
public:
virtual ~NotebookConfig() {}
static QSharedPointer<NotebookConfig> fromNotebookParameters(const QString &p_version,
const NotebookParameters &p_paras);
static QSharedPointer<NotebookConfig> fromNotebook(const QString &p_version,
const Notebook *p_notebook);
virtual QJsonObject toJson() const;
virtual void fromJson(const QJsonObject &p_jobj);
QString m_version;
QString m_name;
QString m_description;
QString m_imageFolder;
QString m_attachmentFolder;
QDateTime m_createdTimeUtc;
QString m_versionController;
QString m_notebookConfigMgr;
ID m_nextNodeId = BundleNotebookConfigMgr::RootNodeId + 1;
static const QString c_version;
static const QString c_name;
static const QString c_description;
static const QString c_imageFolder;
static const QString c_attachmentFolder;
static const QString c_createdTimeUtc;
static const QString c_versionController;
static const QString c_configMgr;
static const QString c_nextNodeId;
};
} // ns vnotex
#endif // NOTEBOOKCONFIG_H

View File

@ -0,0 +1,16 @@
SOURCES += \
$$PWD/nodecontentmediautils.cpp \
$$PWD/vxnotebookconfigmgr.cpp \
$$PWD/vxnotebookconfigmgrfactory.cpp \
$$PWD/inotebookconfigmgr.cpp \
$$PWD/notebookconfig.cpp \
$$PWD/bundlenotebookconfigmgr.cpp
HEADERS += \
$$PWD/inotebookconfigmgr.h \
$$PWD/nodecontentmediautils.h \
$$PWD/vxnotebookconfigmgr.h \
$$PWD/inotebookconfigmgrfactory.h \
$$PWD/vxnotebookconfigmgrfactory.h \
$$PWD/notebookconfig.h \
$$PWD/bundlenotebookconfigmgr.h

View File

@ -0,0 +1,886 @@
#include "vxnotebookconfigmgr.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QDebug>
#include <notebookbackend/inotebookbackend.h>
#include <notebook/notebookparameters.h>
#include <notebook/filenode.h>
#include <notebook/foldernode.h>
#include <notebook/bundlenotebook.h>
#include <utils/utils.h>
#include <utils/fileutils.h>
#include <utils/pathutils.h>
#include <exception.h>
#include "nodecontentmediautils.h"
using namespace vnotex;
const QString VXNotebookConfigMgr::NodeConfig::c_version = "version";
const QString VXNotebookConfigMgr::NodeConfig::c_id = "id";
const QString VXNotebookConfigMgr::NodeConfig::c_createdTimeUtc = "created_time";
const QString VXNotebookConfigMgr::NodeConfig::c_files = "files";
const QString VXNotebookConfigMgr::NodeConfig::c_folders = "folders";
const QString VXNotebookConfigMgr::NodeConfig::c_name = "name";
const QString VXNotebookConfigMgr::NodeConfig::c_modifiedTimeUtc = "modified_time";
const QString VXNotebookConfigMgr::NodeConfig::c_attachmentFolder = "attachment_folder";
const QString VXNotebookConfigMgr::NodeConfig::c_tags = "tags";
QJsonObject VXNotebookConfigMgr::NodeFileConfig::toJson() const
{
QJsonObject jobj;
jobj[NodeConfig::c_name] = m_name;
jobj[NodeConfig::c_id] = QString::number(m_id);
jobj[NodeConfig::c_createdTimeUtc] = Utils::dateTimeStringUniform(m_createdTimeUtc);
jobj[NodeConfig::c_modifiedTimeUtc] = Utils::dateTimeStringUniform(m_modifiedTimeUtc);
jobj[NodeConfig::c_attachmentFolder] = m_attachmentFolder;
jobj[NodeConfig::c_tags] = QJsonArray::fromStringList(m_tags);
return jobj;
}
void VXNotebookConfigMgr::NodeFileConfig::fromJson(const QJsonObject &p_jobj)
{
m_name = p_jobj[NodeConfig::c_name].toString();
{
auto idStr = p_jobj[NodeConfig::c_id].toString();
bool ok;
m_id = idStr.toULongLong(&ok);
if (!ok) {
m_id = Node::InvalidId;
}
}
m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_createdTimeUtc].toString());
m_modifiedTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_modifiedTimeUtc].toString());
m_attachmentFolder = p_jobj[NodeConfig::c_attachmentFolder].toString();
{
auto arr = p_jobj[NodeConfig::c_tags].toArray();
for (const auto &tag : arr) {
m_tags << tag.toString();
}
}
}
QJsonObject VXNotebookConfigMgr::NodeFolderConfig::toJson() const
{
QJsonObject jobj;
jobj[NodeConfig::c_name] = m_name;
return jobj;
}
void VXNotebookConfigMgr::NodeFolderConfig::fromJson(const QJsonObject &p_jobj)
{
m_name = p_jobj[NodeConfig::c_name].toString();
}
VXNotebookConfigMgr::NodeConfig::NodeConfig()
{
}
VXNotebookConfigMgr::NodeConfig::NodeConfig(const QString &p_version,
ID p_id,
const QDateTime &p_createdTimeUtc)
: m_version(p_version),
m_id(p_id),
m_createdTimeUtc(p_createdTimeUtc)
{
}
QJsonObject VXNotebookConfigMgr::NodeConfig::toJson() const
{
QJsonObject jobj;
jobj[NodeConfig::c_version] = m_version;
jobj[NodeConfig::c_id] = QString::number(m_id);
jobj[NodeConfig::c_createdTimeUtc] = Utils::dateTimeStringUniform(m_createdTimeUtc);
QJsonArray files;
for (const auto &file : m_files) {
files.append(file.toJson());
}
jobj[NodeConfig::c_files] = files;
QJsonArray folders;
for (const auto& folder : m_folders) {
folders.append(folder.toJson());
}
jobj[NodeConfig::c_folders] = folders;
return jobj;
}
void VXNotebookConfigMgr::NodeConfig::fromJson(const QJsonObject &p_jobj)
{
m_version = p_jobj[NodeConfig::c_version].toString();
{
auto idStr = p_jobj[NodeConfig::c_id].toString();
bool ok;
m_id = idStr.toULongLong(&ok);
if (!ok) {
m_id = Node::InvalidId;
}
}
m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_createdTimeUtc].toString());
auto filesJson = p_jobj[NodeConfig::c_files].toArray();
m_files.resize(filesJson.size());
for (int i = 0; i < filesJson.size(); ++i) {
m_files[i].fromJson(filesJson[i].toObject());
}
auto foldersJson = p_jobj[NodeConfig::c_folders].toArray();
m_folders.resize(foldersJson.size());
for (int i = 0; i < foldersJson.size(); ++i) {
m_folders[i].fromJson(foldersJson[i].toObject());
}
}
const QString VXNotebookConfigMgr::c_nodeConfigName = "vx.json";
const QString VXNotebookConfigMgr::c_recycleBinFolderName = "vx_recycle_bin";
VXNotebookConfigMgr::VXNotebookConfigMgr(const QString &p_name,
const QString &p_displayName,
const QString &p_description,
const QSharedPointer<INotebookBackend> &p_backend,
QObject *p_parent)
: BundleNotebookConfigMgr(p_backend, p_parent),
m_info(p_name, p_displayName, p_description)
{
}
QString VXNotebookConfigMgr::getName() const
{
return m_info.m_name;
}
QString VXNotebookConfigMgr::getDisplayName() const
{
return m_info.m_displayName;
}
QString VXNotebookConfigMgr::getDescription() const
{
return m_info.m_description;
}
void VXNotebookConfigMgr::createEmptySkeleton(const NotebookParameters &p_paras)
{
BundleNotebookConfigMgr::createEmptySkeleton(p_paras);
createEmptyRootNode();
}
void VXNotebookConfigMgr::createEmptyRootNode()
{
NodeConfig node(getCodeVersion(),
BundleNotebookConfigMgr::RootNodeId,
QDateTime::currentDateTimeUtc());
writeNodeConfig(c_nodeConfigName, node);
}
QSharedPointer<Node> VXNotebookConfigMgr::loadRootNode() const
{
auto nodeConfig = readNodeConfig("");
QSharedPointer<Node> root = nodeConfigToNode(*nodeConfig, "", nullptr);
Q_ASSERT(root->isLoaded());
if (!markRecycleBinNode(root)) {
const_cast<VXNotebookConfigMgr *>(this)->createRecycleBinNode(root);
}
return root;
}
bool VXNotebookConfigMgr::markRecycleBinNode(const QSharedPointer<Node> &p_root) const
{
auto node = p_root->findChild(c_recycleBinFolderName,
FileUtils::isPlatformNameCaseSensitive());
if (node) {
node->setUse(Node::Use::RecycleBin);
markNodeReadOnly(node.data());
return true;
}
return false;
}
void VXNotebookConfigMgr::markNodeReadOnly(Node *p_node) const
{
auto flags = p_node->getFlags();
if (flags & Node::Flag::ReadOnly) {
return;
}
p_node->setFlags(flags | Node::Flag::ReadOnly);
for (auto &child : p_node->getChildren()) {
markNodeReadOnly(child.data());
}
}
void VXNotebookConfigMgr::createRecycleBinNode(const QSharedPointer<Node> &p_root)
{
Q_ASSERT(p_root->isRoot());
auto node = newNode(p_root.data(), Node::Type::Folder, c_recycleBinFolderName);
node->setUse(Node::Use::RecycleBin);
markNodeReadOnly(node.data());
}
QSharedPointer<VXNotebookConfigMgr::NodeConfig> 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<NodeConfig>::create();
nodeConfig->fromJson(QJsonDocument::fromJson(data).object());
return nodeConfig;
}
return nullptr;
}
QString VXNotebookConfigMgr::getNodeConfigFilePath(const Node *p_node) const
{
Q_ASSERT(p_node->getType() == Node::Type::Folder);
return PathUtils::concatenateFilePath(p_node->fetchRelativePath(), 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<Node> VXNotebookConfigMgr::nodeConfigToNode(const NodeConfig &p_config,
const QString &p_name,
Node *p_parent) const
{
auto node = QSharedPointer<FolderNode>::create(p_name, getNotebook(), p_parent);
loadFolderNode(node.data(), p_config);
return node;
}
void VXNotebookConfigMgr::loadFolderNode(FolderNode *p_node, const NodeConfig &p_config) const
{
QVector<QSharedPointer<Node>> children;
children.reserve(p_config.m_files.size() + p_config.m_folders.size());
for (const auto &folder : p_config.m_folders) {
auto folderNode = QSharedPointer<FolderNode>::create(folder.m_name,
getNotebook(),
p_node);
inheritNodeFlags(p_node, folderNode.data());
children.push_back(folderNode);
}
for (const auto &file : p_config.m_files) {
auto fileNode = QSharedPointer<FileNode>::create(file.m_id,
file.m_name,
file.m_createdTimeUtc,
file.m_modifiedTimeUtc,
file.m_attachmentFolder,
file.m_tags,
getNotebook(),
p_node);
inheritNodeFlags(p_node, fileNode.data());
children.push_back(fileNode);
}
p_node->loadFolder(p_config.m_id, p_config.m_createdTimeUtc, children);
}
QSharedPointer<Node> VXNotebookConfigMgr::newNode(Node *p_parent,
Node::Type p_type,
const QString &p_name)
{
Q_ASSERT(p_parent && p_parent->getType() == Node::Type::Folder);
QSharedPointer<Node> node;
switch (p_type) {
case Node::Type::File:
node = newFileNode(p_parent, p_name, true, NodeParameters());
break;
case Node::Type::Folder:
node = newFolderNode(p_parent, p_name, true, NodeParameters());
break;
}
return node;
}
QSharedPointer<Node> VXNotebookConfigMgr::addAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_name,
const NodeParameters &p_paras)
{
Q_ASSERT(p_parent && p_parent->getType() == Node::Type::Folder);
QSharedPointer<Node> node;
switch (p_type) {
case Node::Type::File:
node = newFileNode(p_parent, p_name, false, p_paras);
break;
case Node::Type::Folder:
node = newFolderNode(p_parent, p_name, false, p_paras);
break;
}
return node;
}
QSharedPointer<Node> VXNotebookConfigMgr::copyAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_path)
{
Q_ASSERT(p_parent && p_parent->getType() == Node::Type::Folder);
QSharedPointer<Node> node;
switch (p_type) {
case Node::Type::File:
node = copyFileAsChildOf(p_path, p_parent);
break;
case Node::Type::Folder:
node = copyFolderAsChildOf(p_path, p_parent);
break;
}
return node;
}
QSharedPointer<Node> VXNotebookConfigMgr::newFileNode(Node *p_parent,
const QString &p_name,
bool p_create,
const NodeParameters &p_paras)
{
auto notebook = getNotebook();
// Create file node.
auto node = QSharedPointer<FileNode>::create(Node::InvalidId,
p_name,
p_paras.m_createdTimeUtc,
p_paras.m_modifiedTimeUtc,
p_paras.m_attachmentFolder,
p_paras.m_tags,
notebook,
p_parent);
// Write empty file.
if (p_create) {
getBackend()->writeFile(node->fetchRelativePath(), QString());
}
addChildNode(p_parent, node);
writeNodeConfig(p_parent);
return node;
}
QSharedPointer<Node> VXNotebookConfigMgr::newFolderNode(Node *p_parent,
const QString &p_name,
bool p_create,
const NodeParameters &p_paras)
{
auto notebook = getNotebook();
// Create folder node.
auto node = QSharedPointer<FolderNode>::create(p_name, notebook, p_parent);
node->loadFolder(Node::InvalidId,
p_paras.m_createdTimeUtc,
QVector<QSharedPointer<Node>>());
// Make folder.
if (p_create) {
getBackend()->makePath(node->fetchRelativePath());
}
writeNodeConfig(node.data());
addChildNode(p_parent, node);
writeNodeConfig(p_parent);
return node;
}
QSharedPointer<VXNotebookConfigMgr::NodeConfig> VXNotebookConfigMgr::nodeToNodeConfig(const Node *p_node) const
{
Q_ASSERT(p_node->getType() == Node::Type::Folder);
auto config = QSharedPointer<NodeConfig>::create(getCodeVersion(),
p_node->getId(),
p_node->getCreatedTimeUtc());
for (const auto &child : p_node->getChildren()) {
switch (child->getType()) {
case Node::Type::File:
{
NodeFileConfig fileConfig;
fileConfig.m_name = child->getName();
fileConfig.m_id = child->getId();
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);
break;
}
case Node::Type::Folder:
{
NodeFolderConfig folderConfig;
folderConfig.m_name = child->getName();
config->m_folders.push_back(folderConfig);
break;
}
}
}
return config;
}
void VXNotebookConfigMgr::loadNode(Node *p_node) const
{
if (p_node->isLoaded()) {
return;
}
auto config = readNodeConfig(p_node->fetchRelativePath());
auto folderNode = dynamic_cast<FolderNode *>(p_node);
loadFolderNode(folderNode, *config);
}
void VXNotebookConfigMgr::saveNode(const Node *p_node)
{
Q_ASSERT(!p_node->isRoot());
if (p_node->getType() == Node::Type::Folder) {
writeNodeConfig(p_node);
} else {
writeNodeConfig(p_node->getParent());
}
}
void VXNotebookConfigMgr::renameNode(Node *p_node, const QString &p_name)
{
Q_ASSERT(!p_node->isRoot());
switch (p_node->getType()) {
case Node::Type::Folder:
getBackend()->renameDir(p_node->fetchRelativePath(), p_name);
break;
case Node::Type::File:
getBackend()->renameFile(p_node->fetchRelativePath(), p_name);
break;
}
p_node->setName(p_name);
writeNodeConfig(p_node->getParent());
}
void VXNotebookConfigMgr::addChildNode(Node *p_parent, const QSharedPointer<Node> &p_child) const
{
// Add @p_child after the last node of same type.
const auto type = p_child->getType();
switch (type) {
case Node::Type::Folder:
{
int idx = 0;
auto children = p_parent->getChildren();
for (; idx < children.size(); ++idx) {
if (children[idx]->getType() != type) {
break;
}
}
p_parent->insertChild(idx, p_child);
break;
}
case Node::Type::File:
p_parent->addChild(p_child);
break;
}
inheritNodeFlags(p_parent, p_child.data());
}
QSharedPointer<Node> VXNotebookConfigMgr::loadNodeByPath(const QSharedPointer<Node> &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<Node> VXNotebookConfigMgr::copyNodeAsChildOf(const QSharedPointer<Node> &p_src,
Node *p_dest,
bool p_move)
{
Q_ASSERT(p_dest->getType() == Node::Type::Folder);
if (!p_src->existsOnDisk()) {
Exception::throwOne(Exception::Type::FileMissingOnDisk,
QString("source node missing on disk (%1)").arg(p_src->fetchAbsolutePath()));
return nullptr;
}
QSharedPointer<Node> node;
switch (p_src->getType()) {
case Node::Type::File:
node = copyFileNodeAsChildOf(p_src, p_dest, p_move);
break;
case Node::Type::Folder:
node = copyFolderNodeAsChildOf(p_src, p_dest, p_move);
break;
}
return node;
}
QSharedPointer<Node> VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPointer<Node> &p_src,
Node *p_dest,
bool p_move)
{
// Copy source file itself.
auto srcFilePath = p_src->fetchAbsolutePath();
auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchRelativePath(),
PathUtils::fileName(srcFilePath));
destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath);
getBackend()->copyFile(srcFilePath, destFilePath);
// Copy media files fetched from content.
NodeContentMediaUtils::copyMediaFiles(p_src.data(), getBackend().data(), destFilePath);
// Copy attachment folder. Rename attachment folder if conflicts.
QString attachmentFolder = p_src->getAttachmentFolder();
if (!attachmentFolder.isEmpty()) {
auto destAttachmentFolderPath = fetchNodeAttachmentFolder(destFilePath, attachmentFolder);
NodeContentMediaUtils::copyAttachment(p_src.data(), getBackend().data(), destFilePath, destAttachmentFolderPath);
}
// Create a file node.
auto notebook = getNotebook();
auto id = p_src->getId();
if (!p_move || p_src->getNotebook() != notebook) {
// Use a new id.
id = notebook->getAndUpdateNextNodeId();
}
auto destNode = QSharedPointer<FileNode>::create(id,
PathUtils::fileName(destFilePath),
p_src->getCreatedTimeUtc(),
p_src->getModifiedTimeUtc(),
attachmentFolder,
p_src->getTags(),
notebook,
p_dest);
addChildNode(p_dest, destNode);
writeNodeConfig(p_dest);
if (p_move) {
// Delete src node.
p_src->getNotebook()->removeNode(p_src);
}
return destNode;
}
QSharedPointer<Node> VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedPointer<Node> &p_src,
Node *p_dest,
bool p_move)
{
auto srcFolderPath = p_src->fetchAbsolutePath();
auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchRelativePath(),
PathUtils::fileName(srcFolderPath));
destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath);
// Make folder.
getBackend()->makePath(destFolderPath);
// Create a folder node.
auto notebook = getNotebook();
auto id = p_src->getId();
if (!p_move || p_src->getNotebook() != notebook) {
// Use a new id.
id = notebook->getAndUpdateNextNodeId();
}
auto destNode = QSharedPointer<FolderNode>::create(PathUtils::fileName(destFolderPath),
notebook,
p_dest);
destNode->loadFolder(id, p_src->getCreatedTimeUtc(), QVector<QSharedPointer<Node>>());
writeNodeConfig(destNode.data());
addChildNode(p_dest, destNode);
writeNodeConfig(p_dest);
// Copy children node.
for (const auto &childNode : p_src->getChildren()) {
copyNodeAsChildOf(childNode, destNode.data(), p_move);
}
if (p_move) {
p_src->getNotebook()->removeNode(p_src);
}
return destNode;
}
void VXNotebookConfigMgr::removeNode(const QSharedPointer<Node> &p_node, bool p_force, bool p_configOnly)
{
auto parentNode = p_node->getParent();
if (!p_configOnly) {
// Remove all children.
for (auto &childNode : p_node->getChildren()) {
removeNode(childNode, p_force, p_configOnly);
}
removeFilesOfNode(p_node.data(), p_force);
}
if (parentNode) {
parentNode->removeChild(p_node);
writeNodeConfig(parentNode);
}
}
void VXNotebookConfigMgr::removeFilesOfNode(Node *p_node, bool p_force)
{
Q_ASSERT(p_node->getNotebook() == getNotebook());
switch (p_node->getType()) {
case Node::Type::File:
{
// Delete attachment.
if (!p_node->getAttachmentFolder().isEmpty()) {
getBackend()->removeDir(p_node->fetchAttachmentFolderPath());
}
// Delete media files fetched from content.
NodeContentMediaUtils::removeMediaFiles(p_node);
// Delete node file itself.
auto filePath = p_node->fetchRelativePath();
getBackend()->removeFile(filePath);
break;
}
case Node::Type::Folder:
{
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->fetchRelativePath();
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;
}
}
break;
}
}
}
bool VXNotebookConfigMgr::nodeExistsOnDisk(const Node *p_node) const
{
return getBackend()->exists(p_node->fetchRelativePath());
}
QString VXNotebookConfigMgr::readNode(const Node *p_node) const
{
Q_ASSERT(p_node->getType() == Node::Type::File);
return getBackend()->readTextFile(p_node->fetchRelativePath());
}
void VXNotebookConfigMgr::writeNode(Node *p_node, const QString &p_content)
{
Q_ASSERT(p_node->getType() == Node::Type::File);
getBackend()->writeFile(p_node->fetchRelativePath(), p_content);
p_node->setModifiedTimeUtc();
writeNodeConfig(p_node->getParent());
}
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->getType() == Node::Type::File) {
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->getType() == Node::Type::File) {
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
{
const auto name = p_name.toLower();
if (name == c_nodeConfigName) {
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();
if (name == c_recycleBinFolderName
|| name == getNotebook()->getImageFolder().toLower()
|| name == getNotebook()->getAttachmentFolder().toLower()) {
return true;
}
return BundleNotebookConfigMgr::isBuiltInFolder(p_node, p_name);
}
QSharedPointer<Node> VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_srcPath, Node *p_dest)
{
// Copy source file itself.
auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchRelativePath(),
PathUtils::fileName(p_srcPath));
destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath);
getBackend()->copyFile(p_srcPath, destFilePath);
// Copy media files fetched from content.
NodeContentMediaUtils::copyMediaFiles(p_srcPath, getBackend().data(), destFilePath);
// Create a file node.
auto currentTime = QDateTime::currentDateTimeUtc();
auto destNode = QSharedPointer<FileNode>::create(getNotebook()->getAndUpdateNextNodeId(),
PathUtils::fileName(destFilePath),
currentTime,
currentTime,
QString(),
QStringList(),
getNotebook(),
p_dest);
addChildNode(p_dest, destNode);
writeNodeConfig(p_dest);
return destNode;
}
QSharedPointer<Node> VXNotebookConfigMgr::copyFolderAsChildOf(const QString &p_srcPath, Node *p_dest)
{
auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchRelativePath(),
PathUtils::fileName(p_srcPath));
destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath);
// Copy folder.
getBackend()->copyDir(p_srcPath, destFolderPath);
// Create a folder node.
auto notebook = getNotebook();
auto destNode = QSharedPointer<FolderNode>::create(PathUtils::fileName(destFolderPath),
notebook,
p_dest);
destNode->loadFolder(notebook->getAndUpdateNextNodeId(), QDateTime::currentDateTimeUtc(), QVector<QSharedPointer<Node>>());
writeNodeConfig(destNode.data());
addChildNode(p_dest, destNode);
writeNodeConfig(p_dest);
return destNode;
}
void VXNotebookConfigMgr::inheritNodeFlags(const Node *p_node, Node *p_child) const
{
if (p_node->getFlags() & Node::Flag::ReadOnly) {
markNodeReadOnly(p_child);
}
}

View File

@ -0,0 +1,205 @@
#ifndef VXNOTEBOOKCONFIGMGR_H
#define VXNOTEBOOKCONFIGMGR_H
#include "bundlenotebookconfigmgr.h"
#include <QDateTime>
#include <QVector>
#include "../global.h"
class QJsonObject;
namespace vnotex
{
class FolderNode;
// Config manager for VNoteX's bundle notebook.
class VXNotebookConfigMgr : public BundleNotebookConfigMgr
{
Q_OBJECT
public:
explicit VXNotebookConfigMgr(const QString &p_name,
const QString &p_displayName,
const QString &p_description,
const QSharedPointer<INotebookBackend> &p_backend,
QObject *p_parent = nullptr);
QString getName() const Q_DECL_OVERRIDE;
QString getDisplayName() const Q_DECL_OVERRIDE;
QString getDescription() const Q_DECL_OVERRIDE;
void createEmptySkeleton(const NotebookParameters &p_paras) Q_DECL_OVERRIDE;
QSharedPointer<Node> loadRootNode() const Q_DECL_OVERRIDE;
void loadNode(Node *p_node) const Q_DECL_OVERRIDE;
void saveNode(const Node *p_node) Q_DECL_OVERRIDE;
void renameNode(Node *p_node, const QString &p_name) Q_DECL_OVERRIDE;
QSharedPointer<Node> newNode(Node *p_parent,
Node::Type p_type,
const QString &p_name) Q_DECL_OVERRIDE;
QSharedPointer<Node> addAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_name,
const NodeParameters &p_paras) Q_DECL_OVERRIDE;
QSharedPointer<Node> copyAsNode(Node *p_parent,
Node::Type p_type,
const QString &p_path) Q_DECL_OVERRIDE;
QSharedPointer<Node> loadNodeByPath(const QSharedPointer<Node> &p_root,
const QString &p_relativePath) Q_DECL_OVERRIDE;
QSharedPointer<Node> copyNodeAsChildOf(const QSharedPointer<Node> &p_src,
Node *p_dest,
bool p_move) Q_DECL_OVERRIDE;
void removeNode(const QSharedPointer<Node> &p_node, bool p_force = false, bool p_configOnly = false) Q_DECL_OVERRIDE;
bool nodeExistsOnDisk(const Node *p_node) const Q_DECL_OVERRIDE;
QString readNode(const Node *p_node) const Q_DECL_OVERRIDE;
void writeNode(Node *p_node, const QString &p_content) Q_DECL_OVERRIDE;
QString fetchNodeImageFolderPath(Node *p_node) Q_DECL_OVERRIDE;
QString fetchNodeAttachmentFolderPath(Node *p_node) Q_DECL_OVERRIDE;
private:
// Config of a file child.
struct NodeFileConfig
{
QJsonObject toJson() const;
void fromJson(const QJsonObject &p_jobj);
QString m_name;
ID m_id = Node::InvalidId;
QDateTime m_createdTimeUtc;
QDateTime m_modifiedTimeUtc;
QString m_attachmentFolder;
QStringList m_tags;
};
// Config of a folder child.
struct NodeFolderConfig
{
QJsonObject toJson() const;
void fromJson(const QJsonObject &p_jobj);
QString m_name;
};
// Config of a folder node.
struct NodeConfig
{
NodeConfig();
NodeConfig(const QString &p_version,
ID p_id,
const QDateTime &p_createdTimeUtc);
QJsonObject toJson() const;
void fromJson(const QJsonObject &p_jobj);
QString m_version;
ID m_id = Node::InvalidId;
QDateTime m_createdTimeUtc;
QVector<NodeFileConfig> m_files;
QVector<NodeFolderConfig> m_folders;
static const QString c_version;
static const QString c_id;
static const QString c_createdTimeUtc;
static const QString c_files;
static const QString c_folders;
static const QString c_name;
static const QString c_modifiedTimeUtc;
static const QString c_attachmentFolder;
static const QString c_tags;
};
void createEmptyRootNode();
QSharedPointer<VXNotebookConfigMgr::NodeConfig> readNodeConfig(const QString &p_path) const;
void writeNodeConfig(const QString &p_path, const NodeConfig &p_config) const;
void writeNodeConfig(const Node *p_node);
QSharedPointer<Node> nodeConfigToNode(const NodeConfig &p_config,
const QString &p_name,
Node *p_parent = nullptr) const;
void loadFolderNode(FolderNode *p_node, const NodeConfig &p_config) const;
QSharedPointer<VXNotebookConfigMgr::NodeConfig> nodeToNodeConfig(const Node *p_node) const;
QSharedPointer<Node> newFileNode(Node *p_parent,
const QString &p_name,
bool p_create,
const NodeParameters &p_paras);
QSharedPointer<Node> newFolderNode(Node *p_parent,
const QString &p_name,
bool p_create,
const NodeParameters &p_paras);
QString getNodeConfigFilePath(const Node *p_node) const;
void addChildNode(Node *p_parent, const QSharedPointer<Node> &p_child) const;
QSharedPointer<Node> copyFileNodeAsChildOf(const QSharedPointer<Node> &p_src, Node *p_dest, bool p_move);
QSharedPointer<Node> copyFolderNodeAsChildOf(const QSharedPointer<Node> &p_src, Node *p_dest, bool p_move);
QSharedPointer<Node> copyFileAsChildOf(const QString &p_srcPath, Node *p_dest);
QSharedPointer<Node> copyFolderAsChildOf(const QString &p_srcPath, Node *p_dest);
void removeFilesOfNode(Node *p_node, bool p_force);
bool markRecycleBinNode(const QSharedPointer<Node> &p_root) const;
void markNodeReadOnly(Node *p_node) const;
void createRecycleBinNode(const QSharedPointer<Node> &p_root);
// Generate node attachment folder.
// @p_folderName: suggested folder name if not empty, may be renamed due to conflicts.
// Return the attachment folder path.
QString fetchNodeAttachmentFolder(const QString &p_nodePath, QString &p_folderName);
bool isBuiltInFile(const Node *p_node, const QString &p_name) const Q_DECL_OVERRIDE;
bool isBuiltInFolder(const Node *p_node, const QString &p_name) const Q_DECL_OVERRIDE;
void inheritNodeFlags(const Node *p_node, Node *p_child) const;
Info m_info;
// Name of the node's config file.
static const QString c_nodeConfigName;
// Name of the recycle bin folder which should be a child of the root node.
static const QString c_recycleBinFolderName;
};
} // ns vnotex
#endif // VXNOTEBOOKCONFIGMGR_H

View File

@ -0,0 +1,35 @@
#include "vxnotebookconfigmgrfactory.h"
#include <QObject>
#include "vxnotebookconfigmgr.h"
#include "../notebookbackend/inotebookbackend.h"
using namespace vnotex;
VXNotebookConfigMgrFactory::VXNotebookConfigMgrFactory()
{
}
QString VXNotebookConfigMgrFactory::getName() const
{
return QStringLiteral("vx.vnotex");
}
QString VXNotebookConfigMgrFactory::getDisplayName() const
{
return QObject::tr("VNoteX Notebook Configuration");
}
QString VXNotebookConfigMgrFactory::getDescription() const
{
return QObject::tr("Built-in VNoteX notebook configuration");
}
QSharedPointer<INotebookConfigMgr> VXNotebookConfigMgrFactory::createNotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend)
{
return QSharedPointer<VXNotebookConfigMgr>::create(getName(),
getDisplayName(),
getDescription(),
p_backend);
}

View File

@ -0,0 +1,25 @@
#ifndef VXNOTEBOOKCONFIGMGRFACTORY_H
#define VXNOTEBOOKCONFIGMGRFACTORY_H
#include "inotebookconfigmgrfactory.h"
namespace vnotex
{
class VXNotebookConfigMgrFactory : public INotebookConfigMgrFactory
{
public:
VXNotebookConfigMgrFactory();
QString getName() const Q_DECL_OVERRIDE;
QString getDisplayName() const Q_DECL_OVERRIDE;
QString getDescription()const Q_DECL_OVERRIDE;
QSharedPointer<INotebookConfigMgr> createNotebookConfigMgr(const QSharedPointer<INotebookBackend> &p_backend) Q_DECL_OVERRIDE;
};
} // ns vnotex
#endif // VXNOTEBOOKCONFIGMGRFACTORY_H

376
src/core/notebookmgr.cpp Normal file
View File

@ -0,0 +1,376 @@
#include "notebookmgr.h"
#include <versioncontroller/dummyversioncontrollerfactory.h>
#include <versioncontroller/iversioncontroller.h>
#include <notebookconfigmgr/vxnotebookconfigmgrfactory.h>
#include <notebookconfigmgr/inotebookconfigmgr.h>
#include <notebookbackend/localnotebookbackendfactory.h>
#include <notebookbackend/inotebookbackend.h>
#include <notebook/bundlenotebookfactory.h>
#include <notebook/notebook.h>
#include <notebook/notebookparameters.h>
#include "exception.h"
#include "configmgr.h"
#include <utils/pathutils.h>
using namespace vnotex;
NotebookMgr::NotebookMgr(QObject *p_parent)
: QObject(p_parent),
m_currentNotebookId(Notebook::InvalidId)
{
}
void NotebookMgr::init()
{
initVersionControllerServer();
initConfigMgrServer();
initBackendServer();
initNotebookServer();
}
void NotebookMgr::initVersionControllerServer()
{
m_versionControllerServer.reset(new NameBasedServer<IVersionControllerFactory>);
// Dummy Version Controller.
auto dummyFactory = QSharedPointer<DummyVersionControllerFactory>::create();
m_versionControllerServer->registerItem(dummyFactory->getName(), dummyFactory);
}
void NotebookMgr::initConfigMgrServer()
{
m_configMgrServer.reset(new NameBasedServer<INotebookConfigMgrFactory>);
// VX Notebook Config Manager.
auto vxFactory = QSharedPointer<VXNotebookConfigMgrFactory>::create();
m_configMgrServer->registerItem(vxFactory->getName(), vxFactory);
}
void NotebookMgr::initBackendServer()
{
m_backendServer.reset(new NameBasedServer<INotebookBackendFactory>);
// Local Notebook Backend.
auto localFactory = QSharedPointer<LocalNotebookBackendFactory>::create();
m_backendServer->registerItem(localFactory->getName(), localFactory);
}
void NotebookMgr::initNotebookServer()
{
m_notebookServer.reset(new NameBasedServer<INotebookFactory>);
// Bundle Notebook.
auto bundleFacotry = QSharedPointer<BundleNotebookFactory>::create();
m_notebookServer->registerItem(bundleFacotry->getName(), bundleFacotry);
}
QSharedPointer<INotebookFactory> NotebookMgr::getBundleNotebookFactory() const
{
return m_notebookServer->getItem(QStringLiteral("bundle.vnotex"));
}
QList<QSharedPointer<INotebookFactory>> NotebookMgr::getAllNotebookFactories() const
{
return m_notebookServer->getAllItems();
}
QList<QSharedPointer<IVersionControllerFactory>> NotebookMgr::getAllVersionControllerFactories() const
{
return m_versionControllerServer->getAllItems();
}
QList<QSharedPointer<INotebookConfigMgrFactory>> NotebookMgr::getAllNotebookConfigMgrFactories() const
{
return m_configMgrServer->getAllItems();
}
QList<QSharedPointer<INotebookBackendFactory>> NotebookMgr::getAllNotebookBackendFactories() const
{
return m_backendServer->getAllItems();
}
QSharedPointer<INotebookBackend> NotebookMgr::createNotebookBackend(const QString &p_backendName,
const QString &p_rootFolderPath) const
{
auto factory = m_backendServer->getItem(p_backendName);
if (factory) {
return factory->createNotebookBackend(p_rootFolderPath);
} else {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("fail to find notebook backend factory %1").arg(p_backendName));
}
return nullptr;
}
QSharedPointer<IVersionController> NotebookMgr::createVersionController(const QString &p_controllerName) const
{
auto factory = m_versionControllerServer->getItem(p_controllerName);
if (factory) {
return factory->createVersionController();
} else {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("fail to find version controller factory %1").arg(p_controllerName));
}
return nullptr;
}
QSharedPointer<INotebookConfigMgr> NotebookMgr::createNotebookConfigMgr(const QString &p_mgrName,
const QSharedPointer<INotebookBackend> &p_backend) const
{
auto factory = m_configMgrServer->getItem(p_mgrName);
if (factory) {
return factory->createNotebookConfigMgr(p_backend);
} else {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("fail to find notebook config manager factory %1").arg(p_mgrName));
}
return nullptr;
}
void NotebookMgr::loadNotebooks()
{
readNotebooksFromConfig();
loadCurrentNotebookId();
}
static SessionConfig &getSessionConfig()
{
return ConfigMgr::getInst().getSessionConfig();
}
void NotebookMgr::loadCurrentNotebookId()
{
auto &rootFolderPath = getSessionConfig().getCurrentNotebookRootFolderPath();
auto notebook = findNotebookByRootFolderPath(rootFolderPath);
if (notebook) {
m_currentNotebookId = notebook->getId();
} else {
m_currentNotebookId = Notebook::InvalidId;
}
emit currentNotebookChanged(notebook);
}
QSharedPointer<Notebook> NotebookMgr::newNotebook(const QSharedPointer<NotebookParameters> &p_parameters)
{
auto factory = m_notebookServer->getItem(p_parameters->m_type);
if (!factory) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("fail to find notebook factory %1").arg(p_parameters->m_type));
}
auto notebook = factory->newNotebook(*p_parameters);
addNotebook(notebook);
saveNotebooksToConfig();
emit notebooksUpdated();
setCurrentNotebook(notebook->getId());
return notebook;
}
void NotebookMgr::importNotebook(const QSharedPointer<Notebook> &p_notebook)
{
Q_ASSERT(p_notebook);
if (m_notebooks.indexOf(p_notebook) != -1) {
return;
}
addNotebook(p_notebook);
saveNotebooksToConfig();
emit notebooksUpdated();
setCurrentNotebook(p_notebook->getId());
}
static SessionConfig::NotebookItem notebookToSessionConfig(const QSharedPointer<const Notebook> &p_notebook)
{
SessionConfig::NotebookItem item;
item.m_type = p_notebook->getType();
item.m_rootFolderPath = p_notebook->getRootFolderPath();
item.m_backend = p_notebook->getBackend()->getName();
return item;
}
void NotebookMgr::saveNotebooksToConfig() const
{
QVector<SessionConfig::NotebookItem> items;
items.reserve(m_notebooks.size());
for (auto &nb : m_notebooks) {
items.push_back(notebookToSessionConfig(nb));
}
getSessionConfig().setNotebooks(items);
}
void NotebookMgr::readNotebooksFromConfig()
{
Q_ASSERT(m_notebooks.isEmpty());
auto items = getSessionConfig().getNotebooks();
for (auto &item : items) {
try {
auto nb = readNotebookFromConfig(item);
addNotebook(nb);
} catch (Exception &p_e) {
qCritical("fail to read notebook (%s) from config (%s)",
item.m_rootFolderPath.toStdString().c_str(),
p_e.what());
}
}
emit notebooksUpdated();
}
QSharedPointer<Notebook> NotebookMgr::readNotebookFromConfig(const SessionConfig::NotebookItem &p_item)
{
auto factory = m_notebookServer->getItem(p_item.m_type);
if (!factory) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("fail to find notebook factory %1").arg(p_item.m_type));
}
auto backend = createNotebookBackend(p_item.m_backend, p_item.m_rootFolderPath);
auto notebook = factory->createNotebook(*this, p_item.m_rootFolderPath, backend);
return notebook;
}
const QVector<QSharedPointer<Notebook>> &NotebookMgr::getNotebooks() const
{
return m_notebooks;
}
ID NotebookMgr::getCurrentNotebookId() const
{
return m_currentNotebookId;
}
void NotebookMgr::setCurrentNotebook(ID p_notebookId)
{
auto lastId = m_currentNotebookId;
m_currentNotebookId = p_notebookId;
auto nb = findNotebookById(p_notebookId);
if (!nb) {
m_currentNotebookId = Notebook::InvalidId;
}
if (lastId != m_currentNotebookId) {
emit currentNotebookChanged(nb);
}
getSessionConfig().setCurrentNotebookRootFolderPath(nb ? nb->getRootFolderPath() : "");
}
QSharedPointer<Notebook> NotebookMgr::findNotebookByRootFolderPath(const QString &p_rootFolderPath) const
{
for (auto &nb : m_notebooks) {
if (PathUtils::areSamePaths(nb->getRootFolderPath(), p_rootFolderPath)) {
return nb;
}
}
return nullptr;
}
QSharedPointer<Notebook> NotebookMgr::findNotebookById(ID p_id) const
{
for (auto &nb : m_notebooks) {
if (nb->getId() == p_id) {
return nb;
}
}
return nullptr;
}
void NotebookMgr::closeNotebook(ID p_id)
{
auto it = std::find_if(m_notebooks.begin(),
m_notebooks.end(),
[p_id](const QSharedPointer<Notebook> &p_nb) {
return p_nb->getId() == p_id;
});
if (it == m_notebooks.end()) {
qWarning() << "fail to find notebook of given id to close" << p_id;
return;
}
auto notebookToClose = *it;
emit notebookAboutToClose(notebookToClose.data());
m_notebooks.erase(it);
saveNotebooksToConfig();
emit notebooksUpdated();
setCurrentNotebookAfterUpdate();
qInfo() << QString("notebook %1 (%2) is closed").arg(notebookToClose->getName(),
notebookToClose->getRootFolderPath());
}
void NotebookMgr::removeNotebook(ID p_id)
{
auto it = std::find_if(m_notebooks.begin(),
m_notebooks.end(),
[p_id](const QSharedPointer<Notebook> &p_nb) {
return p_nb->getId() == p_id;
});
if (it == m_notebooks.end()) {
qWarning() << "fail to find notebook of given id to remove" << p_id;
return;
}
auto nbToRemove = *it;
emit notebookAboutToRemove(nbToRemove.data());
m_notebooks.erase(it);
saveNotebooksToConfig();
emit notebooksUpdated();
setCurrentNotebookAfterUpdate();
try {
nbToRemove->remove();
} catch (Exception &p_e) {
qWarning() << QString("fail to remove notebook %1 (%2) (%3)").arg(nbToRemove->getName(),
nbToRemove->getRootFolderPath(),
p_e.what());
throw;
}
qInfo() << QString("notebook %1 (%2) is removed").arg(nbToRemove->getName(),
nbToRemove->getRootFolderPath());
}
void NotebookMgr::setCurrentNotebookAfterUpdate()
{
if (!m_notebooks.isEmpty()) {
setCurrentNotebook(m_notebooks.first()->getId());
} else {
setCurrentNotebook(Notebook::InvalidId);
}
}
void NotebookMgr::addNotebook(const QSharedPointer<Notebook> &p_notebook)
{
m_notebooks.push_back(p_notebook);
connect(p_notebook.data(), &Notebook::updated,
this, [this, notebook = p_notebook.data()]() {
emit notebookUpdated(notebook);
});
}

118
src/core/notebookmgr.h Normal file
View File

@ -0,0 +1,118 @@
#ifndef NOTEBOOKMGR_H
#define NOTEBOOKMGR_H
#include <QObject>
#include <QScopedPointer>
#include <QList>
#include <QVector>
#include "namebasedserver.h"
#include "sessionconfig.h"
#include "global.h"
#include "notebook/notebook.h"
namespace vnotex
{
class IVersionController;
class IVersionControllerFactory;
class INotebookConfigMgr;
class INotebookConfigMgrFactory;
class INotebookBackend;
class INotebookBackendFactory;
class INotebookFactory;
class NotebookParameters;
class NotebookMgr : public QObject
{
Q_OBJECT
public:
explicit NotebookMgr(QObject *p_parent = nullptr);
void init();
QSharedPointer<INotebookFactory> getBundleNotebookFactory() const;
QList<QSharedPointer<INotebookFactory>> getAllNotebookFactories() const;
QList<QSharedPointer<IVersionControllerFactory>> getAllVersionControllerFactories() const;
QList<QSharedPointer<INotebookConfigMgrFactory>> getAllNotebookConfigMgrFactories() const;
QList<QSharedPointer<INotebookBackendFactory>> getAllNotebookBackendFactories() const;
QSharedPointer<INotebookBackend> createNotebookBackend(const QString &p_backendName,
const QString &p_rootFolderPath) const;
QSharedPointer<IVersionController> createVersionController(const QString &p_controllerName) const;
QSharedPointer<INotebookConfigMgr> createNotebookConfigMgr(const QString &p_mgrName,
const QSharedPointer<INotebookBackend> &p_backend) const;
void loadNotebooks();
QSharedPointer<Notebook> newNotebook(const QSharedPointer<NotebookParameters> &p_parameters);
void importNotebook(const QSharedPointer<Notebook> &p_notebook);
const QVector<QSharedPointer<Notebook>> &getNotebooks() const;
ID getCurrentNotebookId() const;
// Find the notebook with the same directory as root folder.
QSharedPointer<Notebook> findNotebookByRootFolderPath(const QString &p_rootFolderPath) const;
QSharedPointer<Notebook> findNotebookById(ID p_id) const;
void closeNotebook(ID p_id);
void removeNotebook(ID p_id);
public slots:
void setCurrentNotebook(ID p_notebookId);
signals:
void notebooksUpdated();
void notebookUpdated(const Notebook *p_notebook);
void notebookAboutToClose(const Notebook *p_notebook);
void notebookAboutToRemove(const Notebook *p_notebook);
void currentNotebookChanged(const QSharedPointer<Notebook> &p_notebook);
private:
void initVersionControllerServer();
void initConfigMgrServer();
void initBackendServer();
void initNotebookServer();
void saveNotebooksToConfig() const;
void readNotebooksFromConfig();
void loadCurrentNotebookId();
QSharedPointer<Notebook> readNotebookFromConfig(const SessionConfig::NotebookItem &p_item);
void setCurrentNotebookAfterUpdate();
void addNotebook(const QSharedPointer<Notebook> &p_notebook);
QSharedPointer<NameBasedServer<IVersionControllerFactory>> m_versionControllerServer;
QSharedPointer<NameBasedServer<INotebookConfigMgrFactory>> m_configMgrServer;
QSharedPointer<NameBasedServer<INotebookBackendFactory>> m_backendServer;
QSharedPointer<NameBasedServer<INotebookFactory>> m_notebookServer;
QVector<QSharedPointer<Notebook>> m_notebooks;
ID m_currentNotebookId = 0;
};
} // ns vnotex
#endif // NOTEBOOKMGR_H

253
src/core/sessionconfig.cpp Normal file
View File

@ -0,0 +1,253 @@
#include "sessionconfig.h"
#include <QDir>
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <utils/fileutils.h>
#include "configmgr.h"
using namespace vnotex;
bool SessionConfig::NotebookItem::operator==(const NotebookItem &p_other) const
{
return m_type == p_other.m_type
&& m_rootFolderPath == p_other.m_rootFolderPath
&& m_backend == p_other.m_backend;
}
void SessionConfig::NotebookItem::fromJson(const QJsonObject &p_jobj)
{
m_type = p_jobj[QStringLiteral("type")].toString();
m_rootFolderPath = p_jobj[QStringLiteral("root_folder")].toString();
m_backend = p_jobj[QStringLiteral("backend")].toString();
}
QJsonObject SessionConfig::NotebookItem::toJson() const
{
QJsonObject jobj;
jobj[QStringLiteral("type")] = m_type;
jobj[QStringLiteral("root_folder")] = m_rootFolderPath;
jobj[QStringLiteral("backend")] = m_backend;
return jobj;
}
SessionConfig::SessionConfig(ConfigMgr *p_mgr)
: IConfig(p_mgr, nullptr)
{
}
SessionConfig::~SessionConfig()
{
}
void SessionConfig::init()
{
auto mgr = getMgr();
auto sessionSettings = mgr->getSettings(ConfigMgr::Source::Session);
const auto &sessionJobj = sessionSettings->getJson();
loadCore(sessionJobj);
}
void SessionConfig::loadCore(const QJsonObject &p_session)
{
const auto coreObj = p_session.value(QStringLiteral("core")).toObject();
m_newNotebookDefaultRootFolderPath = readString(coreObj,
QStringLiteral("new_notebook_default_root_folder_path"));
if (m_newNotebookDefaultRootFolderPath.isEmpty()) {
m_newNotebookDefaultRootFolderPath = QDir::homePath();
}
m_currentNotebookRootFolderPath = readString(coreObj,
QStringLiteral("current_notebook_root_folder_path"));
{
auto option = readString(coreObj, QStringLiteral("opengl"));
m_openGL = stringToOpenGL(option);
}
m_systemTitleBarEnabled = readBool(coreObj, QStringLiteral("system_title_bar"));
}
QJsonObject SessionConfig::saveCore() const
{
QJsonObject coreObj;
coreObj[QStringLiteral("new_notebook_default_root_folder_path")] = m_newNotebookDefaultRootFolderPath;
coreObj[QStringLiteral("current_notebook_root_folder_path")] = m_currentNotebookRootFolderPath;
coreObj[QStringLiteral("opengl")] = openGLToString(m_openGL);
coreObj[QStringLiteral("system_title_bar")] = m_systemTitleBarEnabled;
return coreObj;
}
const QString &SessionConfig::getNewNotebookDefaultRootFolderPath() const
{
return m_newNotebookDefaultRootFolderPath;
}
void SessionConfig::setNewNotebookDefaultRootFolderPath(const QString &p_path)
{
updateConfig(m_newNotebookDefaultRootFolderPath,
p_path,
this);
}
const QVector<SessionConfig::NotebookItem> &SessionConfig::getNotebooks()
{
if (m_notebooks.isEmpty()) {
auto mgr = getMgr();
auto sessionSettings = mgr->getSettings(ConfigMgr::Source::Session);
const auto &sessionJobj = sessionSettings->getJson();
loadNotebooks(sessionJobj);
}
return m_notebooks;
}
void SessionConfig::setNotebooks(const QVector<SessionConfig::NotebookItem> &p_notebooks)
{
updateConfig(m_notebooks,
p_notebooks,
this);
}
void SessionConfig::loadNotebooks(const QJsonObject &p_session)
{
const auto notebooksJson = p_session.value(QStringLiteral("notebooks")).toArray();
m_notebooks.resize(notebooksJson.size());
for (int i = 0; i < notebooksJson.size(); ++i) {
m_notebooks[i].fromJson(notebooksJson[i].toObject());
}
}
QJsonArray SessionConfig::saveNotebooks() const
{
QJsonArray nbArray;
for (const auto &nb : m_notebooks) {
nbArray.append(nb.toJson());
}
return nbArray;
}
const QString &SessionConfig::getCurrentNotebookRootFolderPath() const
{
return m_currentNotebookRootFolderPath;
}
void SessionConfig::setCurrentNotebookRootFolderPath(const QString &p_path)
{
updateConfig(m_currentNotebookRootFolderPath,
p_path,
this);
}
void SessionConfig::writeToSettings() const
{
getMgr()->writeSessionSettings(toJson());
}
QJsonObject SessionConfig::toJson() const
{
QJsonObject obj;
obj[QStringLiteral("core")] = saveCore();
obj[QStringLiteral("notebooks")] = saveNotebooks();
obj[QStringLiteral("state_geometry")] = saveStateAndGeometry();
return obj;
}
QJsonObject SessionConfig::saveStateAndGeometry() const
{
QJsonObject obj;
writeByteArray(obj, QStringLiteral("main_window_state"), m_mainWindowStateGeometry.m_mainState);
writeByteArray(obj, QStringLiteral("main_window_geometry"), m_mainWindowStateGeometry.m_mainGeometry);
return obj;
}
SessionConfig::MainWindowStateGeometry SessionConfig::getMainWindowStateGeometry() const
{
auto sessionSettings = getMgr()->getSettings(ConfigMgr::Source::Session);
const auto &sessionJobj = sessionSettings->getJson();
const auto obj = sessionJobj.value(QStringLiteral("state_geometry")).toObject();
MainWindowStateGeometry sg;
sg.m_mainState = readByteArray(obj, QStringLiteral("main_window_state"));
sg.m_mainGeometry = readByteArray(obj, QStringLiteral("main_window_geometry"));
return sg;
}
void SessionConfig::setMainWindowStateGeometry(const SessionConfig::MainWindowStateGeometry &p_state)
{
m_mainWindowStateGeometry = p_state;
++m_revision;
writeToSettings();
}
SessionConfig::OpenGL SessionConfig::getOpenGLAtBootstrap()
{
auto userConfigFile = ConfigMgr::locateSessionConfigFilePathAtBootstrap();
if (!userConfigFile.isEmpty()) {
auto bytes = FileUtils::readFile(userConfigFile);
auto obj = QJsonDocument::fromJson(bytes).object();
auto coreObj = obj.value(QStringLiteral("core")).toObject();
auto str = coreObj.value(QStringLiteral("opengl")).toString();
return stringToOpenGL(str);
}
return OpenGL::None;
}
SessionConfig::OpenGL SessionConfig::getOpenGL() const
{
return m_openGL;
}
void SessionConfig::setOpenGL(OpenGL p_option)
{
updateConfig(m_openGL, p_option, this);
}
QString SessionConfig::openGLToString(OpenGL p_option)
{
switch (p_option) {
case OpenGL::Desktop:
return QStringLiteral("desktop");
case OpenGL::Angle:
return QStringLiteral("angle");
case OpenGL::Software:
return QStringLiteral("software");
default:
return QStringLiteral("none");
}
}
SessionConfig::OpenGL SessionConfig::stringToOpenGL(const QString &p_str)
{
auto option = p_str.toLower();
if (option == QStringLiteral("software")) {
return OpenGL::Software;
} else if (option == QStringLiteral("desktop")) {
return OpenGL::Desktop;
} else if (option == QStringLiteral("angle")) {
return OpenGL::Angle;
} else {
return OpenGL::None;
}
}
bool SessionConfig::getSystemTitleBarEnabled() const
{
return m_systemTitleBarEnabled;
}
void SessionConfig::setSystemTitleBarEnabled(bool p_enabled)
{
updateConfig(m_systemTitleBarEnabled, p_enabled, this);
}

111
src/core/sessionconfig.h Normal file
View File

@ -0,0 +1,111 @@
#ifndef SESSIONCONFIG_H
#define SESSIONCONFIG_H
#include "iconfig.h"
#include <QString>
#include <QVector>
namespace vnotex
{
class SessionConfig : public IConfig
{
public:
struct NotebookItem
{
NotebookItem() = default;
bool operator==(const NotebookItem &p_other) const;
void fromJson(const QJsonObject &p_jobj);
QJsonObject toJson() const;
QString m_type;
QString m_rootFolderPath;
QString m_backend;
};
struct MainWindowStateGeometry
{
bool operator==(const MainWindowStateGeometry &p_other) const
{
return m_mainState == p_other.m_mainState
&& m_mainGeometry == p_other.m_mainGeometry;
}
QByteArray m_mainState;
QByteArray m_mainGeometry;
};
enum OpenGL
{
None,
Desktop,
Angle,
Software
};
explicit SessionConfig(ConfigMgr *p_mgr);
~SessionConfig();
void init() Q_DECL_OVERRIDE;
const QString &getNewNotebookDefaultRootFolderPath() const;
void setNewNotebookDefaultRootFolderPath(const QString &p_path);
const QString &getCurrentNotebookRootFolderPath() const;
void setCurrentNotebookRootFolderPath(const QString &p_path);
const QVector<SessionConfig::NotebookItem> &getNotebooks();
void setNotebooks(const QVector<SessionConfig::NotebookItem> &p_notebooks);
void writeToSettings() const Q_DECL_OVERRIDE;
QJsonObject toJson() const Q_DECL_OVERRIDE;
SessionConfig::MainWindowStateGeometry getMainWindowStateGeometry() const;
void setMainWindowStateGeometry(const SessionConfig::MainWindowStateGeometry &p_state);
OpenGL getOpenGL() const;
void setOpenGL(OpenGL p_option);
bool getSystemTitleBarEnabled() const;
void setSystemTitleBarEnabled(bool p_enabled);
static OpenGL getOpenGLAtBootstrap();
static QString openGLToString(OpenGL p_option);
static OpenGL stringToOpenGL(const QString &p_str);
private:
void loadCore(const QJsonObject &p_session);
QJsonObject saveCore() const;
void loadNotebooks(const QJsonObject &p_session);
QJsonArray saveNotebooks() const;
QJsonObject saveStateAndGeometry() const;
QString m_newNotebookDefaultRootFolderPath;
// Use root folder to identify a notebook uniquely.
QString m_currentNotebookRootFolderPath;
QVector<SessionConfig::NotebookItem> m_notebooks;
// Used to store newly-set state and geometry, since there is no need to store the read-in
// data all the time.
MainWindowStateGeometry m_mainWindowStateGeometry;
OpenGL m_openGL = OpenGL::None;
// Whether use system's title bar or not.
bool m_systemTitleBarEnabled = false;
};
} // ns vnotex
#endif // SESSIONCONFIG_H

View File

@ -0,0 +1,184 @@
#include "singleinstanceguard.h"
#include <QDebug>
#include <utils/utils.h>
using namespace vnotex;
const QString SingleInstanceGuard::c_memKey = "vnotex_shared_memory";
const int SingleInstanceGuard::c_magic = 376686683;
SingleInstanceGuard::SingleInstanceGuard()
: m_online(false),
m_sharedMemory(c_memKey)
{
}
bool SingleInstanceGuard::tryRun()
{
m_online = false;
// If we can attach to the sharedmemory, there is another instance running.
// In Linux, crashes may cause the shared memory segment remains. In this case,
// this will attach to the old segment, then exit, freeing the old segment.
if (m_sharedMemory.attach()) {
qInfo() << "another instance is running";
return false;
}
// Try to create it.
bool ret = m_sharedMemory.create(sizeof(SharedStruct));
if (ret) {
// We created it.
m_sharedMemory.lock();
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
str->m_magic = c_magic;
str->m_filesBufIdx = 0;
str->m_askedToShow = false;
m_sharedMemory.unlock();
m_online = true;
return true;
} else {
qCritical() << "fail to create shared memory segment";
return false;
}
}
void SingleInstanceGuard::openExternalFiles(const QStringList &p_files)
{
if (p_files.isEmpty()) {
return;
}
if (!m_sharedMemory.isAttached()) {
if (!m_sharedMemory.attach()) {
qCritical() << "fail to attach to the shared memory segment"
<< (m_sharedMemory.error() ? m_sharedMemory.errorString() : "");
return;
}
}
int idx = 0;
int tryCount = 100;
while (tryCount--) {
m_sharedMemory.lock();
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
V_ASSERT(str->m_magic == c_magic);
for (; idx < p_files.size(); ++idx) {
if (p_files[idx].size() + 1 > FilesBufCount) {
// Skip this long long name file.
continue;
}
if (!appendFileToBuffer(str, p_files[idx])) {
break;
}
}
m_sharedMemory.unlock();
if (idx < p_files.size()) {
Utils::sleepWait(500);
} else {
break;
}
}
}
bool SingleInstanceGuard::appendFileToBuffer(SharedStruct *p_str, const QString &p_file)
{
if (p_file.isEmpty()) {
return true;
}
int strSize = p_file.size();
if (strSize + 1 > FilesBufCount - p_str->m_filesBufIdx) {
return false;
}
// Put the size first.
p_str->m_filesBuf[p_str->m_filesBufIdx++] = (ushort)strSize;
const QChar *data = p_file.constData();
for (int i = 0; i < strSize; ++i) {
p_str->m_filesBuf[p_str->m_filesBufIdx++] = data[i].unicode();
}
return true;
}
QStringList SingleInstanceGuard::fetchFilesToOpen()
{
QStringList files;
if (!m_online) {
return files;
}
Q_ASSERT(m_sharedMemory.isAttached());
m_sharedMemory.lock();
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
V_ASSERT(str->m_magic == c_magic);
Q_ASSERT(str->m_filesBufIdx <= FilesBufCount);
int idx = 0;
while (idx < str->m_filesBufIdx) {
int strSize = str->m_filesBuf[idx++];
Q_ASSERT(strSize <= str->m_filesBufIdx - idx);
QString file;
for (int i = 0; i < strSize; ++i) {
file.append(QChar(str->m_filesBuf[idx++]));
}
files.append(file);
}
str->m_filesBufIdx = 0;
m_sharedMemory.unlock();
return files;
}
void SingleInstanceGuard::showInstance()
{
if (!m_sharedMemory.isAttached()) {
if (!m_sharedMemory.attach()) {
qCritical() << "fail to attach to the shared memory segment"
<< (m_sharedMemory.error() ? m_sharedMemory.errorString() : "");
return;
}
}
m_sharedMemory.lock();
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
V_ASSERT(str->m_magic == c_magic);
str->m_askedToShow = true;
m_sharedMemory.unlock();
}
bool SingleInstanceGuard::fetchAskedToShow()
{
if (!m_online) {
return false;
}
Q_ASSERT(m_sharedMemory.isAttached());
m_sharedMemory.lock();
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
V_ASSERT(str->m_magic == c_magic);
bool ret = str->m_askedToShow;
str->m_askedToShow = false;
m_sharedMemory.unlock();
return ret;
}
void SingleInstanceGuard::exit()
{
if (!m_online) {
return;
}
Q_ASSERT(m_sharedMemory.isAttached());
m_sharedMemory.detach();
m_online = false;
}

Some files were not shown because too many files have changed in this diff Show More