Explorar o código

refactor Node interface (#1681)

Le Tan %!s(int64=4) %!d(string=hai) anos
pai
achega
7595b03639
Modificáronse 44 ficheiros con 1019 adicións e 873 borrados
  1. 4 4
      src/core/buffer/buffer.cpp
  2. 1 1
      src/core/buffer/buffer.h
  3. 2 0
      src/core/buffer/bufferprovider.h
  4. 36 21
      src/core/buffer/filebufferprovider.cpp
  5. 6 2
      src/core/buffer/filebufferprovider.h
  6. 2 2
      src/core/buffer/markdownbuffer.cpp
  7. 39 14
      src/core/buffer/nodebufferprovider.cpp
  8. 10 7
      src/core/buffer/nodebufferprovider.h
  9. 5 4
      src/core/buffermgr.cpp
  10. 4 0
      src/core/core.pri
  11. 79 0
      src/core/externalfile.cpp
  12. 48 0
      src/core/externalfile.h
  13. 13 0
      src/core/file.cpp
  14. 70 0
      src/core/file.h
  15. 0 135
      src/core/notebook/filenode.cpp
  16. 0 60
      src/core/notebook/filenode.h
  17. 0 65
      src/core/notebook/foldernode.cpp
  18. 0 40
      src/core/notebook/foldernode.h
  19. 100 138
      src/core/notebook/node.cpp
  20. 69 74
      src/core/notebook/node.h
  21. 12 35
      src/core/notebook/notebook.cpp
  22. 5 8
      src/core/notebook/notebook.h
  23. 4 4
      src/core/notebook/notebook.pri
  24. 117 0
      src/core/notebook/vxnode.cpp
  25. 45 0
      src/core/notebook/vxnode.h
  26. 92 0
      src/core/notebook/vxnodefile.cpp
  27. 52 0
      src/core/notebook/vxnodefile.h
  28. 5 12
      src/core/notebookconfigmgr/inotebookconfigmgr.h
  29. 13 1
      src/core/notebookconfigmgr/nodecontentmediautils.cpp
  30. 130 174
      src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp
  31. 10 16
      src/core/notebookconfigmgr/vxnotebookconfigmgr.h
  32. 12 12
      src/utils/fileutils.cpp
  33. 1 1
      src/widgets/dialogs/folderpropertiesdialog.cpp
  34. 1 1
      src/widgets/dialogs/importfolderdialog.cpp
  35. 4 4
      src/widgets/dialogs/importfolderutils.cpp
  36. 3 3
      src/widgets/dialogs/newfolderdialog.cpp
  37. 3 3
      src/widgets/dialogs/newnotedialog.cpp
  38. 5 5
      src/widgets/dialogs/nodeinfowidget.cpp
  39. 2 2
      src/widgets/dialogs/nodeinfowidget.h
  40. 1 1
      src/widgets/dialogs/nodelabelwithupbutton.cpp
  41. 1 1
      src/widgets/dialogs/notepropertiesdialog.cpp
  42. 1 1
      src/widgets/markdownviewwindow.cpp
  43. 3 3
      src/widgets/notebookexplorer.cpp
  44. 9 19
      src/widgets/notebooknodeexplorer.cpp

+ 4 - 4
src/core/buffer/buffer.cpp

@@ -105,9 +105,9 @@ QString Buffer::getContentPath() const
     return m_provider->getContentPath();
 }
 
-QString Buffer::getContentBasePath() const
+QString Buffer::getResourcePath() const
 {
-    return PathUtils::parentDirPath(getContentPath());
+    return m_provider->getResourcePath();
 }
 
 ID Buffer::getID() const
@@ -337,7 +337,7 @@ void Buffer::writeBackupFile()
 {
     if (m_backupFilePath.isEmpty()) {
         const auto &config = ConfigMgr::getInst().getEditorConfig();
-        QString backupDirPath(QDir(getContentBasePath()).filePath(config.getBackupFileDirectory()));
+        QString backupDirPath(QDir(getResourcePath()).filePath(config.getBackupFileDirectory()));
         backupDirPath = QDir::cleanPath(backupDirPath);
         auto backupFileName = FileUtils::generateFileNameWithSequence(backupDirPath,
                                                                       getName(),
@@ -365,7 +365,7 @@ void Buffer::checkBackupFileOfPreviousSession()
         return;
     }
 
-    QString backupDirPath(QDir(getContentBasePath()).filePath(config.getBackupFileDirectory()));
+    QString backupDirPath(QDir(getResourcePath()).filePath(config.getBackupFileDirectory()));
     backupDirPath = QDir::cleanPath(backupDirPath);
     QDir backupDir(backupDirPath);
     QStringList backupFiles;

+ 1 - 1
src/core/buffer/buffer.h

@@ -81,7 +81,7 @@ namespace vnotex
         QString getContentPath() const;
 
         // Get the base path to resolve resources.
-        QString getContentBasePath() const;
+        QString getResourcePath() const;
 
         ID getID() const;
 

+ 2 - 0
src/core/buffer/bufferprovider.h

@@ -34,6 +34,8 @@ namespace vnotex
 
         virtual QString getContentPath() const = 0;
 
+        virtual QString getResourcePath() const = 0;
+
         virtual void write(const QString &p_content) = 0;
 
         virtual QString read() const = 0;

+ 36 - 21
src/core/buffer/filebufferprovider.cpp

@@ -5,15 +5,16 @@
 #include <utils/pathutils.h>
 #include <utils/fileutils.h>
 #include <notebook/node.h>
+#include <core/file.h>
 
 using namespace vnotex;
 
-FileBufferProvider::FileBufferProvider(const QString &p_filePath,
+FileBufferProvider::FileBufferProvider(const QSharedPointer<File> &p_file,
                                        Node *p_nodeAttachedTo,
                                        bool p_readOnly,
                                        QObject *p_parent)
     : BufferProvider(p_parent),
-      c_filePath(p_filePath),
+      m_file(p_file),
       c_nodeAttachedTo(p_nodeAttachedTo),
       m_readOnly(p_readOnly)
 {
@@ -32,42 +33,49 @@ bool FileBufferProvider::match(const Node *p_node) const
 
 bool FileBufferProvider::match(const QString &p_filePath) const
 {
-    return PathUtils::areSamePaths(c_filePath, p_filePath);
+    return PathUtils::areSamePaths(m_file->getFilePath(), p_filePath);
 }
 
 QString FileBufferProvider::getName() const
 {
-    return PathUtils::fileName(c_filePath);
+    return m_file->getName();
 }
 
 QString FileBufferProvider::getPath() const
 {
-    return c_filePath;
+    return m_file->getFilePath();
 }
 
 QString FileBufferProvider::getContentPath() const
 {
-    // TODO.
-    return getPath();
+    return m_file->getContentPath();
+}
+
+QString FileBufferProvider::getResourcePath() const
+{
+    return m_file->getResourcePath();
 }
 
 void FileBufferProvider::write(const QString &p_content)
 {
-    FileUtils::writeFile(getContentPath(), p_content);
+    m_file->write(p_content);
     m_lastModified = getLastModifiedFromFile();
 }
 
 QString FileBufferProvider::read() const
 {
     const_cast<FileBufferProvider *>(this)->m_lastModified = getLastModifiedFromFile();
-    return FileUtils::readTextFile(getContentPath());
+    return m_file->read();
 }
 
 QString FileBufferProvider::fetchImageFolderPath()
 {
-    auto pa = PathUtils::concatenateFilePath(PathUtils::parentDirPath(getContentPath()), QStringLiteral("vx_images"));
-    QDir().mkpath(pa);
-    return pa;
+    auto file = m_file->getImageInterface();
+    if (file) {
+        return file->fetchImageFolderPath();
+    } else {
+        return QString();
+    }
 }
 
 bool FileBufferProvider::isChildOf(const Node *p_node) const
@@ -130,23 +138,30 @@ void FileBufferProvider::removeAttachment(const QStringList &p_paths)
 
 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;
+    auto file = m_file->getImageInterface();
+    if (file) {
+        return file->insertImage(p_srcImagePath, p_imageFileName);
+    } else {
+        return QString();
+    }
 }
 
 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;
+    auto file = m_file->getImageInterface();
+    if (file) {
+        return file->insertImage(p_image, p_imageFileName);
+    } else {
+        return QString();
+    }
 }
 
 void FileBufferProvider::removeImage(const QString &p_imagePath)
 {
-    FileUtils::removeFile(p_imagePath);
+    auto file = m_file->getImageInterface();
+    if (file) {
+        file->removeImage(p_imagePath);
+    }
 }
 
 bool FileBufferProvider::isAttachmentSupported() const

+ 6 - 2
src/core/buffer/filebufferprovider.h

@@ -5,12 +5,14 @@
 
 namespace vnotex
 {
+    class File;
+
     // Buffer provider based on external file.
     class FileBufferProvider : public BufferProvider
     {
         Q_OBJECT
     public:
-        FileBufferProvider(const QString &p_filePath,
+        FileBufferProvider(const QSharedPointer<File> &m_file,
                            Node *p_nodeAttachedTo,
                            bool p_readOnly,
                            QObject *p_parent = nullptr);
@@ -27,6 +29,8 @@ namespace vnotex
 
         QString getContentPath() const Q_DECL_OVERRIDE;
 
+        QString getResourcePath() const Q_DECL_OVERRIDE;
+
         void write(const QString &p_content) Q_DECL_OVERRIDE;
 
         QString read() const Q_DECL_OVERRIDE;
@@ -62,7 +66,7 @@ namespace vnotex
         bool isReadOnly() const Q_DECL_OVERRIDE;
 
     private:
-        const QString c_filePath;
+        QSharedPointer<File> m_file;
 
         Node *c_nodeAttachedTo = nullptr;
 

+ 2 - 2
src/core/buffer/markdownbuffer.cpp

@@ -35,7 +35,7 @@ void MarkdownBuffer::fetchInitialImages()
 {
     Q_ASSERT(m_initialImages.isEmpty());
     m_initialImages = vte::MarkdownUtils::fetchImagesFromMarkdownText(getContent(),
-                                                                      getContentBasePath(),
+                                                                      getResourcePath(),
                                                                       vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
 }
 
@@ -56,7 +56,7 @@ QSet<QString> MarkdownBuffer::clearObsoleteImages()
     const bool discarded = state() & StateFlag::Discarded;
     const auto latestImages =
         vte::MarkdownUtils::fetchImagesFromMarkdownText(!discarded ? getContent() : m_provider->read(),
-                                                        getContentBasePath(),
+                                                        getResourcePath(),
                                                         vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
     QSet<QString> latestImagesPath;
     for (const auto &link : latestImages) {

+ 39 - 14
src/core/buffer/nodebufferprovider.cpp

@@ -4,15 +4,16 @@
 
 #include <notebook/node.h>
 #include <utils/pathutils.h>
+#include <core/file.h>
 
 using namespace vnotex;
 
-NodeBufferProvider::NodeBufferProvider(Node *p_node, QObject *p_parent)
+NodeBufferProvider::NodeBufferProvider(const QSharedPointer<Node> &p_node, QObject *p_parent)
     : BufferProvider(p_parent),
       m_node(p_node),
-      m_path(m_node->fetchAbsolutePath()),
-      m_contentPath(m_node->fetchContentPath())
+      m_nodeFile(p_node->getContentFile())
 {
+    Q_ASSERT(m_nodeFile);
 }
 
 Buffer::ProviderType NodeBufferProvider::getType() const
@@ -22,7 +23,7 @@ Buffer::ProviderType NodeBufferProvider::getType() const
 
 bool NodeBufferProvider::match(const Node *p_node) const
 {
-    return m_node == p_node;
+    return m_node.data() == p_node;
 }
 
 bool NodeBufferProvider::match(const QString &p_filePath) const
@@ -37,34 +38,45 @@ QString NodeBufferProvider::getName() const
 
 QString NodeBufferProvider::getPath() const
 {
-    return m_path;
+    return m_nodeFile->getFilePath();
 }
 
 QString NodeBufferProvider::getContentPath() const
 {
-    return m_contentPath;
+    return m_nodeFile->getContentPath();
+}
+
+QString NodeBufferProvider::getResourcePath() const
+{
+    return m_nodeFile->getResourcePath();
 }
 
 void NodeBufferProvider::write(const QString &p_content)
 {
-    m_node->write(p_content);
+    m_nodeFile->write(p_content);
     m_lastModified = getLastModifiedFromFile();
 }
 
 QString NodeBufferProvider::read() const
 {
     const_cast<NodeBufferProvider *>(this)->m_lastModified = getLastModifiedFromFile();
-    return m_node->read();
+    return m_nodeFile->read();
 }
 
 QString NodeBufferProvider::fetchImageFolderPath()
 {
-    return m_node->fetchImageFolderPath();
+    auto file = m_nodeFile->getImageInterface();
+    if (file) {
+        return file->fetchImageFolderPath();
+    } else {
+        Q_ASSERT(false);
+        return getContentPath();
+    }
 }
 
 bool NodeBufferProvider::isChildOf(const Node *p_node) const
 {
-    return Node::isAncestor(p_node, m_node);
+    return Node::isAncestor(p_node, m_node.data());
 }
 
 QString NodeBufferProvider::getAttachmentFolder() const
@@ -104,17 +116,30 @@ void NodeBufferProvider::removeAttachment(const QStringList &p_paths)
 
 QString NodeBufferProvider::insertImage(const QString &p_srcImagePath, const QString &p_imageFileName)
 {
-    return m_node->insertImage(p_srcImagePath, p_imageFileName);
+    auto file = m_nodeFile->getImageInterface();
+    if (file) {
+        return file->insertImage(p_srcImagePath, p_imageFileName);
+    } else {
+        return QString();
+    }
 }
 
 QString NodeBufferProvider::insertImage(const QImage &p_image, const QString &p_imageFileName)
 {
-    return m_node->insertImage(p_image, p_imageFileName);
+    auto file = m_nodeFile->getImageInterface();
+    if (file) {
+        return file->insertImage(p_image, p_imageFileName);
+    } else {
+        return QString();
+    }
 }
 
 void NodeBufferProvider::removeImage(const QString &p_imagePath)
 {
-    m_node->removeImage(p_imagePath);
+    auto file = m_nodeFile->getImageInterface();
+    if (file) {
+        file->removeImage(p_imagePath);
+    }
 }
 
 bool NodeBufferProvider::isAttachmentSupported() const
@@ -124,7 +149,7 @@ bool NodeBufferProvider::isAttachmentSupported() const
 
 Node *NodeBufferProvider::getNode() const
 {
-    return m_node;
+    return m_node.data();
 }
 
 bool NodeBufferProvider::isReadOnly() const

+ 10 - 7
src/core/buffer/nodebufferprovider.h

@@ -1,16 +1,21 @@
 #ifndef NODEBUFFERPROVIDER_H
 #define NODEBUFFERPROVIDER_H
 
+#include <QSharedPointer>
+
 #include "bufferprovider.h"
 
 namespace vnotex
 {
+    class File;
+    class IFileWithImage;
+
     // Buffer provider based on an internal node.
     class NodeBufferProvider : public BufferProvider
     {
         Q_OBJECT
     public:
-        NodeBufferProvider(Node *p_node, QObject *p_parent = nullptr);
+        NodeBufferProvider(const QSharedPointer<Node> &p_node, QObject *p_parent = nullptr);
 
         Buffer::ProviderType getType() const Q_DECL_OVERRIDE;
 
@@ -24,6 +29,8 @@ namespace vnotex
 
         QString getContentPath() const Q_DECL_OVERRIDE;
 
+        QString getResourcePath() const Q_DECL_OVERRIDE;
+
         void write(const QString &p_content) Q_DECL_OVERRIDE;
 
         QString read() const Q_DECL_OVERRIDE;
@@ -59,13 +66,9 @@ namespace vnotex
         bool isReadOnly() const Q_DECL_OVERRIDE;
 
     private:
-        Node *m_node = nullptr;
-
-        // Used as cache.
-        QString m_path;
+        QSharedPointer<Node> m_node;
 
-        // Used as cache.
-        QString m_contentPath;
+        QSharedPointer<File> m_nodeFile;
     };
 }
 

+ 5 - 4
src/core/buffermgr.cpp

@@ -13,6 +13,7 @@
 #include <utils/widgetutils.h>
 #include "notebookmgr.h"
 #include "vnotex.h"
+#include "externalfile.h"
 
 #include "fileopenparameters.h"
 
@@ -54,7 +55,7 @@ void BufferMgr::open(Node *p_node, const QSharedPointer<FileOpenParameters> &p_p
         return;
     }
 
-    if (p_node->getType() == Node::Type::Folder) {
+    if (p_node->isContainer()) {
         return;
     }
 
@@ -71,7 +72,7 @@ void BufferMgr::open(Node *p_node, const QSharedPointer<FileOpenParameters> &p_p
         }
 
         BufferParameters paras;
-        paras.m_provider.reset(new NodeBufferProvider(p_node));
+        paras.m_provider.reset(new NodeBufferProvider(p_node->sharedFromThis()));
         buffer = factory->createBuffer(paras, this);
         addBuffer(buffer);
     }
@@ -95,7 +96,7 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointer<FileOpenPar
     // Check if it is an internal node or not.
     auto node = loadNodeByPath(p_filePath);
     if (node) {
-        if (node->getType() == Node::File) {
+        if (node->hasContent()) {
             open(node.data(), p_paras);
             return;
         } else {
@@ -123,7 +124,7 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointer<FileOpenPar
         }
 
         BufferParameters paras;
-        paras.m_provider.reset(new FileBufferProvider(p_filePath,
+        paras.m_provider.reset(new FileBufferProvider(QSharedPointer<ExternalFile>::create(p_filePath),
                                                       p_paras->m_nodeAttachedTo,
                                                       p_paras->m_readOnly));
         buffer = factory->createBuffer(paras, this);

+ 4 - 0
src/core/core.pri

@@ -15,6 +15,8 @@ SOURCES += \
     $$PWD/configmgr.cpp \
     $$PWD/coreconfig.cpp \
     $$PWD/editorconfig.cpp \
+    $$PWD/externalfile.cpp \
+    $$PWD/file.cpp \
     $$PWD/htmltemplatehelper.cpp \
     $$PWD/logger.cpp \
     $$PWD/mainconfig.cpp \
@@ -36,6 +38,8 @@ HEADERS += \
     $$PWD/coreconfig.h \
     $$PWD/editorconfig.h \
     $$PWD/events.h \
+    $$PWD/externalfile.h \
+    $$PWD/file.h \
     $$PWD/filelocator.h \
     $$PWD/fileopenparameters.h \
     $$PWD/htmltemplatehelper.h \

+ 79 - 0
src/core/externalfile.cpp

@@ -0,0 +1,79 @@
+#include "externalfile.h"
+
+#include <utils/fileutils.h>
+#include <utils/pathutils.h>
+
+using namespace vnotex;
+
+ExternalFile::ExternalFile(const QString &p_filePath)
+    : c_filePath(p_filePath)
+{
+}
+
+QString ExternalFile::read() const
+{
+    return FileUtils::readTextFile(getContentPath());
+}
+
+void ExternalFile::write(const QString &p_content)
+{
+    FileUtils::writeFile(getContentPath(), p_content);
+}
+
+QString ExternalFile::getName() const
+{
+    return PathUtils::fileName(c_filePath);
+}
+
+QString ExternalFile::getFilePath() const
+{
+    return c_filePath;
+}
+
+QString ExternalFile::getContentPath() const
+{
+    return c_filePath;
+}
+
+QString ExternalFile::getResourcePath() const
+{
+    return PathUtils::parentDirPath(getContentPath());
+}
+
+IFileWithImage *ExternalFile::getImageInterface()
+{
+    return this;
+}
+
+Node *ExternalFile::getNode() const
+{
+    return nullptr;
+}
+
+QString ExternalFile::fetchImageFolderPath()
+{
+    auto pa = PathUtils::concatenateFilePath(getResourcePath(), QStringLiteral("vx_images"));
+    QDir().mkpath(pa);
+    return pa;
+}
+
+QString ExternalFile::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 ExternalFile::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 ExternalFile::removeImage(const QString &p_imagePath)
+{
+    FileUtils::removeFile(p_imagePath);
+}

+ 48 - 0
src/core/externalfile.h

@@ -0,0 +1,48 @@
+#ifndef EXTERNALFILE_H
+#define EXTERNALFILE_H
+
+#include "file.h"
+
+namespace vnotex
+{
+    class ExternalFile : public File, public IFileWithImage
+    {
+    public:
+        explicit ExternalFile(const QString &p_filePath);
+
+        QString read() const Q_DECL_OVERRIDE;
+
+        void write(const QString &p_content) Q_DECL_OVERRIDE;
+
+        QString getName() const Q_DECL_OVERRIDE;
+
+        QString getFilePath() const Q_DECL_OVERRIDE;
+
+        QString getContentPath() const Q_DECL_OVERRIDE;
+
+        // Path to resolve resources.
+        QString getResourcePath() const Q_DECL_OVERRIDE;
+
+        IFileWithImage *getImageInterface() Q_DECL_OVERRIDE;
+
+        // Get the corresponding node if available.
+        Node *getNode() const Q_DECL_OVERRIDE;
+
+        // IFileWithImage interfaces.
+    public:
+        QString fetchImageFolderPath() Q_DECL_OVERRIDE;
+
+        // Insert image from @p_srcImagePath.
+        // Return inserted image file path.
+        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;
+
+    private:
+        QString c_filePath;
+    };
+}
+
+#endif // EXTERNALFILE_H

+ 13 - 0
src/core/file.cpp

@@ -0,0 +1,13 @@
+#include "file.h"
+
+using namespace vnotex;
+
+const FileType &File::getContentType() const
+{
+    return FileTypeHelper::getInst().getFileType(m_contentType);
+}
+
+void File::setContentType(FileTypeHelper::Type p_type)
+{
+    m_contentType = p_type;
+}

+ 70 - 0
src/core/file.h

@@ -0,0 +1,70 @@
+#ifndef FILE_H
+#define FILE_H
+
+#include <QString>
+
+#include <buffer/filetypehelper.h>
+
+class QImage;
+
+namespace vnotex
+{
+    class Node;
+
+    class IFileWithImage
+    {
+    public:
+        IFileWithImage() = default;
+
+        virtual ~IFileWithImage() = default;
+
+        virtual QString fetchImageFolderPath() = 0;
+
+        // Insert image from @p_srcImagePath.
+        // Return inserted image file path.
+        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;
+    };
+
+    // Abstract file interface.
+    class File
+    {
+    public:
+        File() = default;
+
+        virtual ~File() = default;
+
+        virtual QString read() const = 0;
+
+        virtual void write(const QString &p_content) = 0;
+
+        virtual QString getName() const = 0;
+
+        virtual QString getFilePath() const = 0;
+
+        // The main content file of File.
+        // In bundle case, getFilePath() may point to a folder, getContentPath() points to the real content file.
+        virtual QString getContentPath() const = 0;
+
+        // Path to resolve resources.
+        virtual QString getResourcePath() const = 0;
+
+        virtual IFileWithImage *getImageInterface() = 0;
+
+        // Get the corresponding node if available.
+        virtual Node *getNode() const = 0;
+
+        const FileType &getContentType() const;
+
+    protected:
+        void setContentType(FileTypeHelper::Type p_type);
+
+    private:
+        FileTypeHelper::Type m_contentType = FileTypeHelper::Others;
+    };
+}
+
+#endif // FILE_H

+ 0 - 135
src/core/notebook/filenode.cpp

@@ -1,135 +0,0 @@
-#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) {
-        if (PathUtils::isDir(file)) {
-            qWarning() << "skip adding folder as attachment" << file;
-            continue;
-        }
-
-        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;
-}

+ 0 - 60
src/core/notebook/filenode.h

@@ -1,60 +0,0 @@
-#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

+ 0 - 65
src/core/notebook/foldernode.cpp

@@ -1,65 +0,0 @@
-#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());
-}

+ 0 - 40
src/core/notebook/foldernode.h

@@ -1,40 +0,0 @@
-#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

+ 100 - 138
src/core/notebook/node.cpp

@@ -10,39 +10,39 @@
 
 using namespace vnotex;
 
-Node::Node(Type p_type,
+Node::Node(Flags p_flags,
            ID p_id,
            const QString &p_name,
            const QDateTime &p_createdTimeUtc,
+           const QDateTime &p_modifiedTimeUtc,
+           const QStringList &p_tags,
+           const QString &p_attachmentFolder,
            Notebook *p_notebook,
            Node *p_parent)
     : m_notebook(p_notebook),
-      m_type(p_type),
+      m_loaded(true),
+      m_flags(p_flags),
       m_id(p_id),
       m_name(p_name),
       m_createdTimeUtc(p_createdTimeUtc),
-      m_loaded(true),
+      m_modifiedTimeUtc(p_modifiedTimeUtc),
+      m_tags(p_tags),
+      m_attachmentFolder(p_attachmentFolder),
       m_parent(p_parent)
 {
-    if (m_notebook) {
-        m_configMgr = m_notebook->getConfigMgr();
-        m_backend = m_notebook->getBackend();
-    }
+    Q_ASSERT(m_notebook);
 }
 
-Node::Node(Type p_type,
+Node::Node(Flags p_flags,
            const QString &p_name,
            Notebook *p_notebook,
            Node *p_parent)
     : m_notebook(p_notebook),
-      m_type(p_type),
+      m_flags(p_flags),
       m_name(p_name),
       m_parent(p_parent)
 {
-    if (m_notebook) {
-        m_configMgr = m_notebook->getConfigMgr();
-        m_backend = m_notebook->getBackend();
-    }
+    Q_ASSERT(m_notebook);
 }
 
 Node::~Node()
@@ -54,23 +54,24 @@ 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)
+void Node::loadCompleteInfo(ID p_id,
+                            const QDateTime &p_createdTimeUtc,
+                            const QDateTime &p_modifiedTimeUtc,
+                            const QStringList &p_tags,
+                            const QVector<QSharedPointer<Node>> &p_children)
 {
     Q_ASSERT(!m_loaded);
-
     m_id = p_id;
     m_createdTimeUtc = p_createdTimeUtc;
+    m_modifiedTimeUtc = p_modifiedTimeUtc;
+    m_tags = p_tags;
+    m_children = p_children;
     m_loaded = true;
 }
 
 bool Node::isRoot() const
 {
-    return !m_parent;
+    return !m_parent && m_use == Use::Root;
 }
 
 const QString &Node::getName() const
@@ -78,12 +79,29 @@ const QString &Node::getName() const
     return m_name;
 }
 
-bool Node::hasChild(const QString &p_name, bool p_caseSensitive) const
+void Node::setName(const QString &p_name)
+{
+    m_name = p_name;
+}
+
+void Node::updateName(const QString &p_name)
+{
+    if (m_name == p_name) {
+        return;
+    }
+
+    getConfigMgr()->renameNode(this, p_name);
+    Q_ASSERT(m_name == p_name);
+
+    emit m_notebook->nodeUpdated(this);
+}
+
+bool Node::containsChild(const QString &p_name, bool p_caseSensitive) const
 {
     return findChild(p_name, p_caseSensitive) != nullptr;
 }
 
-bool Node::hasChild(const QSharedPointer<Node> &p_node) const
+bool Node::containsChild(const QSharedPointer<Node> &p_node) const
 {
     return getChildren().indexOf(p_node) != -1;
 }
@@ -111,21 +129,11 @@ 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;
@@ -146,56 +154,50 @@ const QDateTime &Node::getCreatedTimeUtc() const
     return m_createdTimeUtc;
 }
 
-Notebook *Node::getNotebook() const
+const QDateTime &Node::getModifiedTimeUtc() const
 {
-    return m_notebook;
+    return m_modifiedTimeUtc;
 }
 
-QString Node::fetchRelativePath() const
+void Node::setModifiedTimeUtc()
 {
-    if (!m_parent) {
-        return QString();
-    } else {
-        return PathUtils::concatenateFilePath(m_parent->fetchRelativePath(), m_name);
-    }
+    m_modifiedTimeUtc = QDateTime::currentDateTimeUtc();
 }
 
-QString Node::fetchAbsolutePath() const
+const QVector<QSharedPointer<Node>> &Node::getChildren() const
 {
-    return PathUtils::concatenateFilePath(m_notebook->getRootFolderAbsolutePath(),
-                                          fetchRelativePath());
+    return m_children;
 }
 
-QString Node::fetchContentPath() const
+int Node::getChildrenCount() const
 {
-    return fetchAbsolutePath();
+    return m_children.size();
 }
 
-void Node::load()
+void Node::addChild(const QSharedPointer<Node> &p_node)
 {
-    Q_ASSERT(m_notebook);
-    m_notebook->load(this);
+    insertChild(m_children.size(), p_node);
 }
 
-void Node::save()
+void Node::insertChild(int p_idx, const QSharedPointer<Node> &p_node)
 {
-    Q_ASSERT(m_notebook);
-    m_notebook->save(this);
-}
+    Q_ASSERT(isContainer());
 
-void Node::setName(const QString &p_name)
-{
-    m_name = p_name;
+    p_node->setParent(this);
+
+    m_children.insert(p_idx, p_node);
 }
 
-void Node::updateName(const QString &p_name)
+void Node::removeChild(const QSharedPointer<Node> &p_child)
 {
-    if (m_name == p_name) {
-        return;
+    if (m_children.removeOne(p_child)) {
+        p_child->setParent(nullptr);
     }
+}
 
-    m_notebook->rename(this, p_name);
-    Q_ASSERT(m_name == p_name);
+Notebook *Node::getNotebook() const
+{
+    return m_notebook;
 }
 
 bool Node::isAncestor(const Node *p_ancestor, const Node *p_child)
@@ -214,122 +216,82 @@ bool Node::isAncestor(const Node *p_ancestor, const Node *p_child)
     return false;
 }
 
-bool Node::existsOnDisk() const
+const QStringList &Node::getTags() const
 {
-    return m_configMgr->nodeExistsOnDisk(this);
+    return m_tags;
 }
 
-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)
+bool Node::isReadOnly() const
 {
-    const auto imageFolderPath = fetchImageFolderPath();
-    auto destFilePath = m_backend->renameIfExistsCaseInsensitive(PathUtils::concatenateFilePath(imageFolderPath, p_imageFileName));
-    p_image.save(destFilePath);
-    m_backend->addFile(destFilePath);
-    return destFilePath;
+    return m_flags & Flag::ReadOnly;
 }
 
-void Node::removeImage(const QString &p_imagePath)
+void Node::setReadOnly(bool p_readOnly)
 {
-    // Just move it to recycle bin but not added as a child node of recycle bin.
-    m_notebook->moveFileToRecycleBin(p_imagePath);
+    if (p_readOnly) {
+        m_flags |= Flag::ReadOnly;
+    } else {
+        m_flags &= ~Flag::ReadOnly;
+    }
 }
 
-QString Node::getAttachmentFolder() const
+QString Node::fetchPath() const
 {
-    Q_ASSERT(false);
-    return QString();
+    if (!m_parent) {
+        return QString();
+    } else {
+        return PathUtils::concatenateFilePath(m_parent->fetchPath(), m_name);
+    }
 }
 
-void Node::setAttachmentFolder(const QString &p_folder)
+bool Node::isContainer() const
 {
-    Q_UNUSED(p_folder);
-    Q_ASSERT(false);
+    return m_flags & Flag::Container;
 }
 
-QString Node::fetchAttachmentFolderPath()
+bool Node::hasContent() const
 {
-    return m_configMgr->fetchNodeAttachmentFolderPath(this);
+    return m_flags & Flag::Content;
 }
 
-QStringList Node::addAttachment(const QString &p_destFolderPath, const QStringList &p_files)
+QDir Node::toDir() const
 {
-    Q_UNUSED(p_destFolderPath);
-    Q_UNUSED(p_files);
-    Q_ASSERT(false);
-    return QStringList();
+    if (isContainer()) {
+        return QDir(fetchAbsolutePath());
+    }
 }
 
-QString Node::newAttachmentFile(const QString &p_destFolderPath, const QString &p_name)
+void Node::load()
 {
-    Q_UNUSED(p_destFolderPath);
-    Q_UNUSED(p_name);
-    Q_ASSERT(false);
-    return QString();
+    getConfigMgr()->loadNode(this);
 }
 
-QString Node::newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name)
+void Node::save()
 {
-    Q_UNUSED(p_destFolderPath);
-    Q_UNUSED(p_name);
-    Q_ASSERT(false);
-    return QString();
+    getConfigMgr()->saveNode(this);
 }
 
-QString Node::renameAttachment(const QString &p_path, const QString &p_name)
+INotebookConfigMgr *Node::getConfigMgr() const
 {
-    Q_UNUSED(p_path);
-    Q_UNUSED(p_name);
-    Q_ASSERT(false);
-    return QString();
+    return m_notebook->getConfigMgr().data();
 }
 
-void Node::removeAttachment(const QStringList &p_paths)
+const QString &Node::getAttachmentFolder() const
 {
-    Q_UNUSED(p_paths);
-    Q_ASSERT(false);
+    return m_attachmentFolder;
 }
 
-QStringList Node::getTags() const
+void Node::setAttachmentFolder(const QString &p_attachmentFolder)
 {
-    Q_ASSERT(false);
-    return QStringList();
+    m_attachmentFolder = p_attachmentFolder;
 }
 
-QDir Node::toDir() const
+QString Node::fetchAttachmentFolderPath()
 {
-    Q_ASSERT(false);
-    return QDir();
+    return getConfigMgr()->fetchNodeAttachmentFolderPath(this);
 }
 
 INotebookBackend *Node::getBackend() const
 {
-    return m_backend.data();
-}
-
-bool Node::isReadOnly() const
-{
-    return m_flags & Flag::ReadOnly;
+    return m_notebook->getBackend().data();
 }

+ 69 - 74
src/core/notebook/node.h

@@ -5,6 +5,7 @@
 #include <QVector>
 #include <QSharedPointer>
 #include <QDir>
+#include <QEnableSharedFromThis>
 
 #include <global.h>
 
@@ -13,6 +14,7 @@ namespace vnotex
     class Notebook;
     class INotebookConfigMgr;
     class INotebookBackend;
+    class File;
 
     // Used when add/new a node.
     struct NodeParameters
@@ -24,43 +26,46 @@ namespace vnotex
     };
 
     // Node of notebook.
-    class Node
+    class Node : public QEnableSharedFromThis<Node>
     {
     public:
-        enum Type {
-            Folder,
-            File
-        };
-
         enum Flag {
             None = 0,
-            ReadOnly = 0x1
+            // A node with content.
+            Content = 0x1,
+            // A node with children.
+            Container = 0x2,
+            ReadOnly = 0x4
         };
         Q_DECLARE_FLAGS(Flags, Flag)
 
         enum Use {
             Normal,
-            RecycleBin
+            RecycleBin,
+            Root
         };
 
+        enum { InvalidId = 0 };
+
         // Constructor with all information loaded.
-        Node(Type p_type,
+        Node(Flags p_flags,
              ID p_id,
              const QString &p_name,
              const QDateTime &p_createdTimeUtc,
+             const QDateTime &p_modifiedTimeUtc,
+             const QStringList &p_tags,
+             const QString &p_attachmentFolder,
              Notebook *p_notebook,
-             Node *p_parent = nullptr);
+             Node *p_parent);
 
         // Constructor not loaded.
-        Node(Type p_type,
+        Node(Flags p_flags,
              const QString &p_name,
              Notebook *p_notebook,
-             Node *p_parent = nullptr);
+             Node *p_parent);
 
         virtual ~Node();
 
-        enum { InvalidId = 0 };
-
         bool isLoaded() const;
 
         bool isRoot() const;
@@ -68,13 +73,20 @@ namespace vnotex
         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;
+        // Fetch path of this node within notebook.
+        // This may not be the same as the actual file path. It depends on the config mgr.
+        virtual QString fetchPath() const;
+
+        // Fetch absolute file path if available.
+        virtual QString fetchAbsolutePath() const = 0;
+
+        bool isContainer() const;
+
+        bool hasContent() const;
 
         Node::Flags getFlags() const;
-        void setFlags(Node::Flags p_flags);
 
         Node::Use getUse() const;
         void setUse(Node::Use p_use);
@@ -83,97 +95,74 @@ namespace vnotex
 
         const QDateTime &getCreatedTimeUtc() const;
 
-        virtual QDateTime getModifiedTimeUtc() const = 0;
-        virtual void setModifiedTimeUtc() = 0;
-
-        virtual QString getAttachmentFolder() const;
-        virtual void setAttachmentFolder(const QString &p_folder);
+        const QDateTime &getModifiedTimeUtc() const;
+        void setModifiedTimeUtc();
 
-        virtual QVector<QSharedPointer<Node>> getChildren() const = 0;
-
-        virtual int getChildrenCount() const = 0;
+        const QVector<QSharedPointer<Node>> &getChildren() const;
+        int getChildrenCount() const;
 
         QSharedPointer<Node> findChild(const QString &p_name, bool p_caseSensitive = true) const;
 
-        bool hasChild(const QString &p_name, bool p_caseSensitive = true) const;
+        bool containsChild(const QString &p_name, bool p_caseSensitive = true) const;
 
-        bool hasChild(const QSharedPointer<Node> &p_node) const;
+        bool containsChild(const QSharedPointer<Node> &p_node) const;
 
-        virtual void addChild(const QSharedPointer<Node> &p_node) = 0;
+        void addChild(const QSharedPointer<Node> &p_node);
 
-        virtual void insertChild(int p_idx, const QSharedPointer<Node> &p_node) = 0;
+        void insertChild(int p_idx, const QSharedPointer<Node> &p_node);
 
-        virtual void removeChild(const QSharedPointer<Node> &p_node) = 0;
+        void removeChild(const QSharedPointer<Node> &p_node);
 
         void setParent(Node *p_parent);
         Node *getParent() const;
 
         Notebook *getNotebook() const;
 
-        // Path to the node.
-        QString fetchRelativePath() const;
-
-        QString fetchAbsolutePath() const;
+        void load();
+        void save();
 
-        // 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);
+        const QStringList &getTags() const;
 
-        bool existsOnDisk() const;
+        const QString &getAttachmentFolder() const;
+        void setAttachmentFolder(const QString &p_attachmentFolder);
 
-        QString read() const;
-        void write(const QString &p_content);
+        QString fetchAttachmentFolderPath();
 
-        // Insert image from @p_srcImagePath.
-        // Return inserted image file path.
-        virtual QString insertImage(const QString &p_srcImagePath, const QString &p_imageFileName);
+        virtual QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files) = 0;
 
-        virtual QString insertImage(const QImage &p_image, const QString &p_imageFileName);
+        virtual QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name) = 0;
 
-        virtual void removeImage(const QString &p_imagePath);
+        virtual QString newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name) = 0;
 
-        // Get attachment folder path.
-        virtual QString fetchAttachmentFolderPath();
+        virtual QString renameAttachment(const QString &p_path, const QString &p_name) = 0;
 
-        virtual QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files);
+        virtual void removeAttachment(const QStringList &p_paths) = 0;
 
-        virtual QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name);
+        QDir toDir() const;
 
-        virtual QString newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name);
-
-        virtual QString renameAttachment(const QString &p_path, const QString &p_name);
+        bool isReadOnly() const;
+        void setReadOnly(bool p_readOnly);
 
-        virtual void removeAttachment(const QStringList &p_paths);
+        // Get File if this node has content.
+        virtual QSharedPointer<File> getContentFile() = 0;
 
-        virtual QStringList getTags() const;
+        void loadCompleteInfo(ID p_id,
+                              const QDateTime &p_createdTimeUtc,
+                              const QDateTime &p_modifiedTimeUtc,
+                              const QStringList &p_tags,
+                              const QVector<QSharedPointer<Node>> &p_children);
 
-        virtual QDir toDir() const;
+        INotebookConfigMgr *getConfigMgr() const;
 
         INotebookBackend *getBackend() const;
 
-        bool isReadOnly() const;
+        static bool isAncestor(const Node *p_ancestor, const Node *p_child);
 
     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;
+        bool m_loaded = false;
 
         Flags m_flags = Flag::None;
 
@@ -185,9 +174,15 @@ namespace vnotex
 
         QDateTime m_createdTimeUtc;
 
-        bool m_loaded = false;
+        QDateTime m_modifiedTimeUtc;
+
+        QStringList m_tags;
+
+        QString m_attachmentFolder;
 
         Node *m_parent = nullptr;
+
+        QVector<QSharedPointer<Node>> m_children;
     };
 
     Q_DECLARE_OPERATORS_FOR_FLAGS(Node::Flags)

+ 12 - 35
src/core/notebook/notebook.cpp

@@ -152,6 +152,7 @@ const QSharedPointer<Node> &Notebook::getRootNode() const
 {
     if (!m_root) {
         const_cast<Notebook *>(this)->m_root = m_configMgr->loadRootNode();
+        Q_ASSERT(m_root->isRoot());
     }
 
     return m_root;
@@ -174,9 +175,9 @@ QSharedPointer<Node> Notebook::getRecycleBinNode() const
     return nullptr;
 }
 
-QSharedPointer<Node> Notebook::newNode(Node *p_parent, Node::Type p_type, const QString &p_name)
+QSharedPointer<Node> Notebook::newNode(Node *p_parent, Node::Flags p_flags, const QString &p_name)
 {
-    return m_configMgr->newNode(p_parent, p_type, p_name);
+    return m_configMgr->newNode(p_parent, p_flags, p_name);
 }
 
 const QDateTime &Notebook::getCreatedTimeUtc() const
@@ -184,30 +185,6 @@ 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)) {
@@ -237,7 +214,7 @@ QSharedPointer<Node> Notebook::copyNodeAsChildOf(const QSharedPointer<Node> &p_s
     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()));
+                                   .arg(p_src->fetchPath(), p_dest->fetchPath()));
         return nullptr;
     }
 
@@ -314,7 +291,7 @@ QSharedPointer<Node> Notebook::getOrCreateRecycleBinDateNode()
                                               FileUtils::isPlatformNameCaseSensitive());
     if (!dateNode) {
         // Create a date node.
-        dateNode = newNode(recycleBinNode.data(), Node::Type::Folder, dateNodeName);
+        dateNode = newNode(recycleBinNode.data(), Node::Flag::Container, dateNodeName);
     }
 
     return dateNode;
@@ -331,7 +308,7 @@ void Notebook::emptyNode(const Node *p_node, bool p_force)
 void Notebook::moveFileToRecycleBin(const QString &p_filePath)
 {
     auto node = getOrCreateRecycleBinDateNode();
-    auto destFilePath = PathUtils::concatenateFilePath(node->fetchRelativePath(),
+    auto destFilePath = PathUtils::concatenateFilePath(node->fetchPath(),
                                                        PathUtils::fileName(p_filePath));
     destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath);
     m_backend->copyFile(p_filePath, destFilePath);
@@ -344,8 +321,8 @@ void Notebook::moveFileToRecycleBin(const QString &p_filePath)
 void Notebook::moveDirToRecycleBin(const QString &p_dirPath)
 {
     auto node = getOrCreateRecycleBinDateNode();
-    auto destDirPath = PathUtils::concatenateFilePath(node->fetchRelativePath(),
-                                                       PathUtils::fileName(p_dirPath));
+    auto destDirPath = PathUtils::concatenateFilePath(node->fetchPath(),
+                                                      PathUtils::fileName(p_dirPath));
     destDirPath = getBackend()->renameIfExistsCaseInsensitive(destDirPath);
     m_backend->copyDir(p_dirPath, destDirPath);
 
@@ -355,11 +332,11 @@ void Notebook::moveDirToRecycleBin(const QString &p_dirPath)
 }
 
 QSharedPointer<Node> Notebook::addAsNode(Node *p_parent,
-                                         Node::Type p_type,
+                                         Node::Flags p_flags,
                                          const QString &p_name,
                                          const NodeParameters &p_paras)
 {
-    return m_configMgr->addAsNode(p_parent, p_type, p_name, p_paras);
+    return m_configMgr->addAsNode(p_parent, p_flags, p_name, p_paras);
 }
 
 bool Notebook::isBuiltInFile(const Node *p_node, const QString &p_name) const
@@ -373,8 +350,8 @@ bool Notebook::isBuiltInFolder(const Node *p_node, const QString &p_name) const
 }
 
 QSharedPointer<Node> Notebook::copyAsNode(Node *p_parent,
-                                          Node::Type p_type,
+                                          Node::Flags p_flags,
                                           const QString &p_path)
 {
-    return m_configMgr->copyAsNode(p_parent, p_type, p_path);
+    return m_configMgr->copyAsNode(p_parent, p_flags, p_path);
 }

+ 5 - 8
src/core/notebook/notebook.h

@@ -65,28 +65,25 @@ namespace vnotex
 
         QSharedPointer<Node> getRecycleBinNode() const;
 
-        QSharedPointer<Node> newNode(Node *p_parent, Node::Type p_type, const QString &p_name);
+        QSharedPointer<Node> newNode(Node *p_parent,
+                                     Node::Flags p_flags,
+                                     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,
+                                       Node::Flags p_flags,
                                        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,
+                                        Node::Flags p_flags,
                                         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;

+ 4 - 4
src/core/notebook/notebook.pri

@@ -4,8 +4,8 @@ SOURCES += \
     $$PWD/notebookparameters.cpp \
     $$PWD/bundlenotebook.cpp \
     $$PWD/node.cpp \
-    $$PWD/filenode.cpp \
-    $$PWD/foldernode.cpp
+    $$PWD/vxnode.cpp \
+    $$PWD/vxnodefile.cpp
 
 HEADERS += \
     $$PWD/notebook.h \
@@ -14,5 +14,5 @@ HEADERS += \
     $$PWD/notebookparameters.h \
     $$PWD/bundlenotebook.h \
     $$PWD/node.h \
-    $$PWD/filenode.h \
-    $$PWD/foldernode.h
+    $$PWD/vxnode.h \
+    $$PWD/vxnodefile.h

+ 117 - 0
src/core/notebook/vxnode.cpp

@@ -0,0 +1,117 @@
+#include "vxnode.h"
+
+#include <QDir>
+
+#include <utils/pathutils.h>
+#include <notebookbackend/inotebookbackend.h>
+#include <notebookconfigmgr/inotebookconfigmgr.h>
+#include "notebook.h"
+#include "vxnodefile.h"
+
+using namespace vnotex;
+
+VXNode::VXNode(ID p_id,
+               const QString &p_name,
+               const QDateTime &p_createdTimeUtc,
+               const QDateTime &p_modifiedTimeUtc,
+               const QStringList &p_tags,
+               const QString &p_attachmentFolder,
+               Notebook *p_notebook,
+               Node *p_parent)
+    : Node(Node::Flag::Content,
+           p_id,
+           p_name,
+           p_createdTimeUtc,
+           p_modifiedTimeUtc,
+           p_tags,
+           p_attachmentFolder,
+           p_notebook,
+           p_parent)
+{
+}
+
+VXNode::VXNode(const QString &p_name,
+               Notebook *p_notebook,
+               Node *p_parent)
+    : Node(Node::Flag::Container,
+           p_name,
+           p_notebook,
+           p_parent)
+{
+}
+
+QString VXNode::fetchAbsolutePath() const
+{
+    return PathUtils::concatenateFilePath(m_notebook->getRootFolderAbsolutePath(),
+                                          fetchPath());
+}
+
+QSharedPointer<File> VXNode::getContentFile()
+{
+    // We should not keep the shared ptr of VXNodeFile, or there is a cyclic ref.
+    return QSharedPointer<VXNodeFile>::create(sharedFromThis().dynamicCast<VXNode>());
+}
+
+QStringList VXNode::addAttachment(const QString &p_destFolderPath, const QStringList &p_files)
+{
+    Q_ASSERT(PathUtils::pathContains(fetchAttachmentFolderPath(), p_destFolderPath));
+
+    auto backend = getBackend();
+    QStringList addedFiles;
+    for (const auto &file : p_files) {
+        if (PathUtils::isDir(file)) {
+            qWarning() << "skip adding folder as attachment" << file;
+            continue;
+        }
+
+        auto destFilePath = backend->renameIfExistsCaseInsensitive(
+            PathUtils::concatenateFilePath(p_destFolderPath, PathUtils::fileName(file)));
+        backend->copyFile(file, destFilePath);
+        addedFiles << destFilePath;
+    }
+
+    return addedFiles;
+}
+
+QString VXNode::newAttachmentFile(const QString &p_destFolderPath, const QString &p_name)
+{
+    Q_ASSERT(PathUtils::pathContains(fetchAttachmentFolderPath(), p_destFolderPath));
+
+    auto backend = getBackend();
+    auto destFilePath = backend->renameIfExistsCaseInsensitive(
+        PathUtils::concatenateFilePath(p_destFolderPath, p_name));
+    backend->writeFile(destFilePath, QByteArray());
+    return destFilePath;
+}
+
+QString VXNode::newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name)
+{
+    Q_ASSERT(PathUtils::pathContains(fetchAttachmentFolderPath(), p_destFolderPath));
+
+    auto backend = getBackend();
+    auto destFilePath = backend->renameIfExistsCaseInsensitive(
+        PathUtils::concatenateFilePath(p_destFolderPath, p_name));
+    backend->makePath(destFilePath);
+    return destFilePath;
+}
+
+QString VXNode::renameAttachment(const QString &p_path, const QString &p_name)
+{
+    Q_ASSERT(PathUtils::pathContains(fetchAttachmentFolderPath(), p_path));
+    getBackend()->renameFile(p_path, p_name);
+    return p_name;
+}
+
+void VXNode::removeAttachment(const QStringList &p_paths)
+{
+    auto attaFolderPath = fetchAttachmentFolderPath();
+    // Just move it to recycle bin but not added as a child node of recycle bin.
+    for (const auto &pa : p_paths) {
+        Q_ASSERT(PathUtils::pathContains(attaFolderPath, pa));
+        if (QFileInfo(pa).isDir()) {
+            m_notebook->moveDirToRecycleBin(pa);
+        } else {
+            m_notebook->moveFileToRecycleBin(pa);
+        }
+    }
+}

+ 45 - 0
src/core/notebook/vxnode.h

@@ -0,0 +1,45 @@
+#ifndef VXNODE_H
+#define VXNODE_H
+
+#include "node.h"
+
+namespace vnotex
+{
+    // Node of VXNotebookConfigMgr.
+    class VXNode : public Node
+    {
+    public:
+        // For content node.
+        VXNode(ID p_id,
+               const QString &p_name,
+               const QDateTime &p_createdTimeUtc,
+               const QDateTime &p_modifiedTimeUtc,
+               const QStringList &p_tags,
+               const QString &p_attachmentFolder,
+               Notebook *p_notebook,
+               Node *p_parent);
+
+        // For container node.
+        VXNode(const QString &p_name,
+               Notebook *p_notebook,
+               Node *p_parent);
+
+        QString fetchAbsolutePath() const Q_DECL_OVERRIDE;
+
+        QSharedPointer<File> getContentFile() 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;
+
+    private:
+    };
+}
+
+#endif // VXNODE_H

+ 92 - 0
src/core/notebook/vxnodefile.cpp

@@ -0,0 +1,92 @@
+#include "vxnodefile.h"
+
+#include <QImage>
+
+#include <notebookbackend/inotebookbackend.h>
+#include <notebookconfigmgr/inotebookconfigmgr.h>
+#include <notebookconfigmgr/vxnotebookconfigmgr.h>
+#include <utils/pathutils.h>
+#include "vxnode.h"
+#include "notebook.h"
+
+using namespace vnotex;
+
+VXNodeFile::VXNodeFile(const QSharedPointer<VXNode> &p_node)
+    : m_node(p_node)
+{
+    Q_ASSERT(m_node && m_node->hasContent());
+}
+
+QString VXNodeFile::read() const
+{
+    return m_node->getBackend()->readTextFile(m_node->fetchPath());
+}
+
+void VXNodeFile::write(const QString &p_content)
+{
+    m_node->getBackend()->writeFile(m_node->fetchPath(), p_content);
+
+    m_node->setModifiedTimeUtc();
+    m_node->save();
+}
+
+QString VXNodeFile::getName() const
+{
+    return m_node->getName();
+}
+
+QString VXNodeFile::getFilePath() const
+{
+    return m_node->fetchAbsolutePath();
+}
+
+QString VXNodeFile::getContentPath() const
+{
+    return m_node->fetchAbsolutePath();
+}
+
+QString VXNodeFile::getResourcePath() const
+{
+    return PathUtils::parentDirPath(getContentPath());
+}
+
+IFileWithImage *VXNodeFile::getImageInterface()
+{
+    return this;
+}
+
+Node *VXNodeFile::getNode() const
+{
+    return m_node.data();
+}
+
+QString VXNodeFile::fetchImageFolderPath()
+{
+    auto configMgr = dynamic_cast<VXNotebookConfigMgr *>(m_node->getConfigMgr());
+    return configMgr->fetchNodeImageFolderPath(m_node.data());
+}
+
+QString VXNodeFile::insertImage(const QString &p_srcImagePath, const QString &p_imageFileName)
+{
+    auto backend = m_node->getBackend();
+    const auto imageFolderPath = fetchImageFolderPath();
+    auto destFilePath = backend->renameIfExistsCaseInsensitive(PathUtils::concatenateFilePath(imageFolderPath, p_imageFileName));
+    backend->copyFile(p_srcImagePath, destFilePath);
+    return destFilePath;
+}
+
+QString VXNodeFile::insertImage(const QImage &p_image, const QString &p_imageFileName)
+{
+    auto backend = m_node->getBackend();
+    const auto imageFolderPath = fetchImageFolderPath();
+    auto destFilePath = backend->renameIfExistsCaseInsensitive(PathUtils::concatenateFilePath(imageFolderPath, p_imageFileName));
+    p_image.save(destFilePath);
+    backend->addFile(destFilePath);
+    return destFilePath;
+}
+
+void VXNodeFile::removeImage(const QString &p_imagePath)
+{
+    // Just move it to recycle bin but not added as a child node of recycle bin.
+    m_node->getNotebook()->moveFileToRecycleBin(p_imagePath);
+}

+ 52 - 0
src/core/notebook/vxnodefile.h

@@ -0,0 +1,52 @@
+#ifndef VXNODEFILE_H
+#define VXNODEFILE_H
+
+#include <QSharedPointer>
+
+#include <core/file.h>
+
+namespace vnotex
+{
+    class VXNode;
+
+    // File from VXNode.
+    class VXNodeFile : public File, public IFileWithImage
+    {
+    public:
+        explicit VXNodeFile(const QSharedPointer<VXNode> &p_node);
+
+        QString read() const Q_DECL_OVERRIDE;
+
+        void write(const QString &p_content) Q_DECL_OVERRIDE;
+
+        QString getName() const Q_DECL_OVERRIDE;
+
+        QString getFilePath() const Q_DECL_OVERRIDE;
+
+        QString getContentPath() const Q_DECL_OVERRIDE;
+
+        QString getResourcePath() const Q_DECL_OVERRIDE;
+
+        IFileWithImage *getImageInterface() Q_DECL_OVERRIDE;
+
+        // Get the corresponding node if available.
+        Node *getNode() const Q_DECL_OVERRIDE;
+
+        // IFileWithImage interfaces.
+    public:
+        QString fetchImageFolderPath() Q_DECL_OVERRIDE;
+
+        // Insert image from @p_srcImagePath.
+        // Return inserted image file path.
+        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;
+
+    private:
+        QSharedPointer<VXNode> m_node;
+    };
+}
+
+#endif // VXNODEFILE_H

+ 5 - 12
src/core/notebookconfigmgr/inotebookconfigmgr.h

@@ -44,16 +44,16 @@ namespace vnotex
         virtual void renameNode(Node *p_node, const QString &p_name) = 0;
 
         virtual QSharedPointer<Node> newNode(Node *p_parent,
-                                             Node::Type p_type,
+                                             Node::Flags p_flags,
                                              const QString &p_name) = 0;
 
         virtual QSharedPointer<Node> addAsNode(Node *p_parent,
-                                               Node::Type p_type,
+                                               Node::Flags p_flags,
                                                const QString &p_name,
                                                const NodeParameters &p_paras) = 0;
 
         virtual QSharedPointer<Node> copyAsNode(Node *p_parent,
-                                                Node::Type p_type,
+                                                Node::Flags p_flags,
                                                 const QString &p_path) = 0;
 
         Notebook *getNotebook() const;
@@ -68,20 +68,13 @@ namespace vnotex
 
         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;
 
+        virtual QString fetchNodeAttachmentFolderPath(Node *p_node) = 0;
+
     protected:
         // Version of the config processing code.
         virtual QString getCodeVersion() const;

+ 13 - 1
src/core/notebookconfigmgr/nodecontentmediautils.cpp

@@ -22,7 +22,8 @@ void NodeContentMediaUtils::copyMediaFiles(const Node *p_node,
                                            INotebookBackend *p_backend,
                                            const QString &p_destFilePath)
 {
-    Q_ASSERT(p_node->getType() == Node::Type::File);
+    Q_ASSERT(p_node->hasContent());
+    /*
     const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath());
     if (fileType.m_type == FileTypeHelper::Markdown) {
         copyMarkdownMediaFiles(p_node->read(),
@@ -30,12 +31,14 @@ void NodeContentMediaUtils::copyMediaFiles(const Node *p_node,
                                p_backend,
                                p_destFilePath);
     }
+    */
 }
 
 void NodeContentMediaUtils::copyMediaFiles(const QString &p_filePath,
                                            INotebookBackend *p_backend,
                                            const QString &p_destFilePath)
 {
+    /*
     const auto &fileType = FileTypeHelper::getInst().getFileType(p_filePath);
     if (fileType.m_type == FileTypeHelper::Markdown) {
         copyMarkdownMediaFiles(FileUtils::readTextFile(p_filePath),
@@ -43,6 +46,7 @@ void NodeContentMediaUtils::copyMediaFiles(const QString &p_filePath,
                                p_backend,
                                p_destFilePath);
     }
+    */
 }
 
 void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
@@ -50,6 +54,7 @@ void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
                                                    INotebookBackend *p_backend,
                                                    const QString &p_destFilePath)
 {
+    /*
     auto content = p_content;
 
     // Images.
@@ -107,19 +112,23 @@ void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
     if (!renamedImages.isEmpty()) {
         p_backend->writeFile(p_destFilePath, content);
     }
+    */
 }
 
 void NodeContentMediaUtils::removeMediaFiles(const Node *p_node)
 {
+    /*
     Q_ASSERT(p_node->getType() == Node::Type::File);
     const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath());
     if (fileType.m_type == FileTypeHelper::Markdown) {
         removeMarkdownMediaFiles(p_node);
     }
+    */
 }
 
 void NodeContentMediaUtils::removeMarkdownMediaFiles(const Node *p_node)
 {
+    /*
     auto content = p_node->read();
 
     // Images.
@@ -143,6 +152,7 @@ void NodeContentMediaUtils::removeMarkdownMediaFiles(const Node *p_node)
         }
         backend->removeFile(link.m_path);
     }
+    */
 }
 
 void NodeContentMediaUtils::copyAttachment(Node *p_node,
@@ -150,6 +160,7 @@ void NodeContentMediaUtils::copyAttachment(Node *p_node,
                                            const QString &p_destFilePath,
                                            const QString &p_destAttachmentFolderPath)
 {
+    /*
     Q_ASSERT(p_node->getType() == Node::Type::File);
     Q_ASSERT(!p_node->getAttachmentFolder().isEmpty());
 
@@ -166,6 +177,7 @@ void NodeContentMediaUtils::copyAttachment(Node *p_node,
     if (fileType.m_type == FileTypeHelper::Markdown) {
         fixMarkdownLinks(srcAttachmentFolderPath, p_backend, p_destFilePath, p_destAttachmentFolderPath);
     }
+    */
 }
 
 void NodeContentMediaUtils::fixMarkdownLinks(const QString &p_srcFolderPath,

+ 130 - 174
src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp

@@ -7,8 +7,7 @@
 
 #include <notebookbackend/inotebookbackend.h>
 #include <notebook/notebookparameters.h>
-#include <notebook/filenode.h>
-#include <notebook/foldernode.h>
+#include <notebook/vxnode.h>
 #include <notebook/bundlenotebook.h>
 #include <utils/utils.h>
 #include <utils/fileutils.h>
@@ -97,10 +96,12 @@ VXNotebookConfigMgr::NodeConfig::NodeConfig()
 
 VXNotebookConfigMgr::NodeConfig::NodeConfig(const QString &p_version,
                                             ID p_id,
-                                            const QDateTime &p_createdTimeUtc)
+                                            const QDateTime &p_createdTimeUtc,
+                                            const QDateTime &p_modifiedTimeUtc)
     : m_version(p_version),
       m_id(p_id),
-      m_createdTimeUtc(p_createdTimeUtc)
+      m_createdTimeUtc(p_createdTimeUtc),
+      m_modifiedTimeUtc(p_modifiedTimeUtc)
 {
 }
 
@@ -111,6 +112,7 @@ QJsonObject VXNotebookConfigMgr::NodeConfig::toJson() const
     jobj[NodeConfig::c_version] = m_version;
     jobj[NodeConfig::c_id] = QString::number(m_id);
     jobj[NodeConfig::c_createdTimeUtc] = Utils::dateTimeStringUniform(m_createdTimeUtc);
+    jobj[NodeConfig::c_modifiedTimeUtc] = Utils::dateTimeStringUniform(m_modifiedTimeUtc);
 
     QJsonArray files;
     for (const auto &file : m_files) {
@@ -141,6 +143,7 @@ void VXNotebookConfigMgr::NodeConfig::fromJson(const QJsonObject &p_jobj)
     }
 
     m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_createdTimeUtc].toString());
+    m_modifiedTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NodeConfig::c_modifiedTimeUtc].toString());
 
     auto filesJson = p_jobj[NodeConfig::c_files].toArray();
     m_files.resize(filesJson.size());
@@ -194,9 +197,11 @@ void VXNotebookConfigMgr::createEmptySkeleton(const NotebookParameters &p_paras)
 
 void VXNotebookConfigMgr::createEmptyRootNode()
 {
+    auto currentTime = QDateTime::currentDateTimeUtc();
     NodeConfig node(getCodeVersion(),
                     BundleNotebookConfigMgr::RootNodeId,
-                    QDateTime::currentDateTimeUtc());
+                    currentTime,
+                    currentTime);
     writeNodeConfig(c_nodeConfigName, node);
 }
 
@@ -204,6 +209,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::loadRootNode() const
 {
     auto nodeConfig = readNodeConfig("");
     QSharedPointer<Node> root = nodeConfigToNode(*nodeConfig, "", nullptr);
+    root->setUse(Node::Use::Root);
     Q_ASSERT(root->isLoaded());
 
     if (!markRecycleBinNode(root)) {
@@ -228,12 +234,11 @@ bool VXNotebookConfigMgr::markRecycleBinNode(const QSharedPointer<Node> &p_root)
 
 void VXNotebookConfigMgr::markNodeReadOnly(Node *p_node) const
 {
-    auto flags = p_node->getFlags();
-    if (flags & Node::Flag::ReadOnly) {
+    if (p_node->isReadOnly()) {
         return;
     }
 
-    p_node->setFlags(flags | Node::Flag::ReadOnly);
+    p_node->setReadOnly(true);
     for (auto &child : p_node->getChildren()) {
         markNodeReadOnly(child.data());
     }
@@ -243,7 +248,7 @@ void VXNotebookConfigMgr::createRecycleBinNode(const QSharedPointer<Node> &p_roo
 {
     Q_ASSERT(p_root->isRoot());
 
-    auto node = newNode(p_root.data(), Node::Type::Folder, c_recycleBinFolderName);
+    auto node = newNode(p_root.data(), Node::Flag::Container, c_recycleBinFolderName);
     node->setUse(Node::Use::RecycleBin);
     markNodeReadOnly(node.data());
 }
@@ -272,8 +277,8 @@ QSharedPointer<VXNotebookConfigMgr::NodeConfig> VXNotebookConfigMgr::readNodeCon
 
 QString VXNotebookConfigMgr::getNodeConfigFilePath(const Node *p_node) const
 {
-    Q_ASSERT(p_node->getType() == Node::Type::Folder);
-    return PathUtils::concatenateFilePath(p_node->fetchRelativePath(), c_nodeConfigName);
+    Q_ASSERT(p_node->isContainer());
+    return PathUtils::concatenateFilePath(p_node->fetchPath(), c_nodeConfigName);
 }
 
 void VXNotebookConfigMgr::writeNodeConfig(const QString &p_path, const NodeConfig &p_config) const
@@ -291,97 +296,92 @@ QSharedPointer<Node> VXNotebookConfigMgr::nodeConfigToNode(const NodeConfig &p_c
                                                            const QString &p_name,
                                                            Node *p_parent) const
 {
-    auto node = QSharedPointer<FolderNode>::create(p_name, getNotebook(), p_parent);
+    auto node = QSharedPointer<VXNode>::create(p_name, getNotebook(), p_parent);
     loadFolderNode(node.data(), p_config);
     return node;
 }
 
-void VXNotebookConfigMgr::loadFolderNode(FolderNode *p_node, const NodeConfig &p_config) const
+void VXNotebookConfigMgr::loadFolderNode(Node *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);
+        auto folderNode = QSharedPointer<VXNode>::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);
+        auto fileNode = QSharedPointer<VXNode>::create(file.m_id,
+                                                       file.m_name,
+                                                       file.m_createdTimeUtc,
+                                                       file.m_modifiedTimeUtc,
+                                                       file.m_tags,
+                                                       file.m_attachmentFolder,
+                                                       getNotebook(),
+                                                       p_node);
         inheritNodeFlags(p_node, fileNode.data());
         children.push_back(fileNode);
     }
 
-    p_node->loadFolder(p_config.m_id, p_config.m_createdTimeUtc, children);
+    p_node->loadCompleteInfo(p_config.m_id,
+                             p_config.m_createdTimeUtc,
+                             p_config.m_modifiedTimeUtc,
+                             QStringList(),
+                             children);
 }
 
 QSharedPointer<Node> VXNotebookConfigMgr::newNode(Node *p_parent,
-                                                  Node::Type p_type,
+                                                  Node::Flags p_flags,
                                                   const QString &p_name)
 {
-    Q_ASSERT(p_parent && p_parent->getType() == Node::Type::Folder);
+    Q_ASSERT(p_parent && p_parent->isContainer());
 
     QSharedPointer<Node> node;
 
-    switch (p_type) {
-    case Node::Type::File:
+    if (p_flags & Node::Flag::Content) {
+        Q_ASSERT(!(p_flags & Node::Flag::Container));
         node = newFileNode(p_parent, p_name, true, NodeParameters());
-        break;
-
-    case Node::Type::Folder:
+    } else {
         node = newFolderNode(p_parent, p_name, true, NodeParameters());
-        break;
     }
 
     return node;
 }
 
 QSharedPointer<Node> VXNotebookConfigMgr::addAsNode(Node *p_parent,
-                                                    Node::Type p_type,
+                                                    Node::Flags p_flags,
                                                     const QString &p_name,
                                                     const NodeParameters &p_paras)
 {
-    Q_ASSERT(p_parent && p_parent->getType() == Node::Type::Folder);
+    Q_ASSERT(p_parent && p_parent->isContainer());
 
     QSharedPointer<Node> node;
-    switch (p_type) {
-    case Node::Type::File:
+    if (p_flags & Node::Flag::Content) {
+        Q_ASSERT(!(p_flags & Node::Flag::Container));
         node = newFileNode(p_parent, p_name, false, p_paras);
-        break;
-
-    case Node::Type::Folder:
+    } else {
         node = newFolderNode(p_parent, p_name, false, p_paras);
-        break;
     }
 
     return node;
 }
 
 QSharedPointer<Node> VXNotebookConfigMgr::copyAsNode(Node *p_parent,
-                                                     Node::Type p_type,
+                                                     Node::Flags p_flags,
                                                      const QString &p_path)
 {
-    Q_ASSERT(p_parent && p_parent->getType() == Node::Type::Folder);
+    Q_ASSERT(p_parent && p_parent->isContainer());
 
     QSharedPointer<Node> node;
-    switch (p_type) {
-    case Node::Type::File:
+    if (p_flags & Node::Flag::Content) {
+        Q_ASSERT(!(p_flags & Node::Flag::Container));
         node = copyFileAsChildOf(p_path, p_parent);
-        break;
-
-    case Node::Type::Folder:
+    } else {
         node = copyFolderAsChildOf(p_path, p_parent);
-        break;
     }
 
     return node;
@@ -395,18 +395,18 @@ QSharedPointer<Node> VXNotebookConfigMgr::newFileNode(Node *p_parent,
     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);
+    auto node = QSharedPointer<VXNode>::create(Node::InvalidId,
+                                               p_name,
+                                               p_paras.m_createdTimeUtc,
+                                               p_paras.m_modifiedTimeUtc,
+                                               p_paras.m_tags,
+                                               p_paras.m_attachmentFolder,
+                                               notebook,
+                                               p_parent);
 
     // Write empty file.
     if (p_create) {
-        getBackend()->writeFile(node->fetchRelativePath(), QString());
+        getBackend()->writeFile(node->fetchPath(), QString());
     }
 
     addChildNode(p_parent, node);
@@ -423,14 +423,16 @@ QSharedPointer<Node> VXNotebookConfigMgr::newFolderNode(Node *p_parent,
     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>>());
+    auto node = QSharedPointer<VXNode>::create(p_name, notebook, p_parent);
+    node->loadCompleteInfo(Node::InvalidId,
+                           p_paras.m_createdTimeUtc,
+                           p_paras.m_modifiedTimeUtc,
+                           QStringList(),
+                           QVector<QSharedPointer<Node>>());
 
     // Make folder.
     if (p_create) {
-        getBackend()->makePath(node->fetchRelativePath());
+        getBackend()->makePath(node->fetchPath());
     }
 
     writeNodeConfig(node.data());
@@ -443,16 +445,15 @@ QSharedPointer<Node> VXNotebookConfigMgr::newFolderNode(Node *p_parent,
 
 QSharedPointer<VXNotebookConfigMgr::NodeConfig> VXNotebookConfigMgr::nodeToNodeConfig(const Node *p_node) const
 {
-    Q_ASSERT(p_node->getType() == Node::Type::Folder);
+    Q_ASSERT(p_node->isContainer());
 
     auto config = QSharedPointer<NodeConfig>::create(getCodeVersion(),
                                                      p_node->getId(),
-                                                     p_node->getCreatedTimeUtc());
+                                                     p_node->getCreatedTimeUtc(),
+                                                     p_node->getModifiedTimeUtc());
 
     for (const auto &child : p_node->getChildren()) {
-        switch (child->getType()) {
-        case Node::Type::File:
-        {
+        if (child->hasContent()) {
             NodeFileConfig fileConfig;
             fileConfig.m_name = child->getName();
             fileConfig.m_id = child->getId();
@@ -462,17 +463,12 @@ QSharedPointer<VXNotebookConfigMgr::NodeConfig> VXNotebookConfigMgr::nodeToNodeC
             fileConfig.m_tags = child->getTags();
 
             config->m_files.push_back(fileConfig);
-            break;
-        }
-
-        case Node::Type::Folder:
-        {
+        } else {
+            Q_ASSERT(child->isContainer());
             NodeFolderConfig folderConfig;
             folderConfig.m_name = child->getName();
 
             config->m_folders.push_back(folderConfig);
-            break;
-        }
         }
     }
 
@@ -485,16 +481,16 @@ void VXNotebookConfigMgr::loadNode(Node *p_node) const
         return;
     }
 
-    auto config = readNodeConfig(p_node->fetchRelativePath());
-    auto folderNode = dynamic_cast<FolderNode *>(p_node);
-    loadFolderNode(folderNode, *config);
+    auto config = readNodeConfig(p_node->fetchPath());
+    Q_ASSERT(p_node->isContainer());
+    loadFolderNode(p_node, *config);
 }
 
 void VXNotebookConfigMgr::saveNode(const Node *p_node)
 {
     Q_ASSERT(!p_node->isRoot());
 
-    if (p_node->getType() == Node::Type::Folder) {
+    if (p_node->isContainer()) {
         writeNodeConfig(p_node);
     } else {
         writeNodeConfig(p_node->getParent());
@@ -504,14 +500,10 @@ void VXNotebookConfigMgr::saveNode(const Node *p_node)
 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;
+    if (p_node->isContainer()) {
+        getBackend()->renameDir(p_node->fetchPath(), p_name);
+    } else {
+        getBackend()->renameFile(p_node->fetchPath(), p_name);
     }
 
     p_node->setName(p_name);
@@ -520,26 +512,18 @@ void VXNotebookConfigMgr::renameNode(Node *p_node, const QString &p_name)
 
 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:
-    {
+    if (p_child->isContainer()) {
         int idx = 0;
         auto children = p_parent->getChildren();
         for (; idx < children.size(); ++idx) {
-            if (children[idx]->getType() != type) {
+            if (!children[idx]->isContainer()) {
                 break;
             }
         }
 
         p_parent->insertChild(idx, p_child);
-        break;
-    }
-
-    case Node::Type::File:
+    } else {
         p_parent->addChild(p_child);
-        break;
     }
 
     inheritNodeFlags(p_parent, p_child.data());
@@ -573,22 +557,13 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyNodeAsChildOf(const QSharedPointer
                                                             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;
-    }
+    Q_ASSERT(p_dest->isContainer());
 
     QSharedPointer<Node> node;
-    switch (p_src->getType()) {
-    case Node::Type::File:
-        node = copyFileNodeAsChildOf(p_src, p_dest, p_move);
-        break;
-
-    case Node::Type::Folder:
+    if (p_src->isContainer()) {
         node = copyFolderNodeAsChildOf(p_src, p_dest, p_move);
-        break;
+    } else {
+        node = copyFileNodeAsChildOf(p_src, p_dest, p_move);
     }
 
     return node;
@@ -600,7 +575,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPoi
 {
     // Copy source file itself.
     auto srcFilePath = p_src->fetchAbsolutePath();
-    auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchRelativePath(),
+    auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchPath(),
                                                        PathUtils::fileName(srcFilePath));
     destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath);
     getBackend()->copyFile(srcFilePath, destFilePath);
@@ -623,14 +598,14 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPoi
         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);
+    auto destNode = QSharedPointer<VXNode>::create(id,
+                                                   PathUtils::fileName(destFilePath),
+                                                   p_src->getCreatedTimeUtc(),
+                                                   p_src->getModifiedTimeUtc(),
+                                                   p_src->getTags(),
+                                                   attachmentFolder,
+                                                   notebook,
+                                                   p_dest);
     addChildNode(p_dest, destNode);
     writeNodeConfig(p_dest);
 
@@ -647,7 +622,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedP
                                                                   bool p_move)
 {
     auto srcFolderPath = p_src->fetchAbsolutePath();
-    auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchRelativePath(),
+    auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchPath(),
                                                          PathUtils::fileName(srcFolderPath));
     destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath);
 
@@ -661,10 +636,14 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFolderNodeAsChildOf(const QSharedP
         // 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>>());
+    auto destNode = QSharedPointer<VXNode>::create(PathUtils::fileName(destFolderPath),
+                                                   notebook,
+                                                   p_dest);
+    destNode->loadCompleteInfo(id,
+                               p_src->getCreatedTimeUtc(),
+                               p_src->getModifiedTimeUtc(),
+                               QStringList(),
+                               QVector<QSharedPointer<Node>>());
 
     writeNodeConfig(destNode.data());
 
@@ -704,9 +683,7 @@ void VXNotebookConfigMgr::removeNode(const QSharedPointer<Node> &p_node, bool p_
 void VXNotebookConfigMgr::removeFilesOfNode(Node *p_node, bool p_force)
 {
     Q_ASSERT(p_node->getNotebook() == getNotebook());
-    switch (p_node->getType()) {
-    case Node::Type::File:
-    {
+    if (!p_node->isContainer()) {
         // Delete attachment.
         if (!p_node->getAttachmentFolder().isEmpty()) {
             getBackend()->removeDir(p_node->fetchAttachmentFolderPath());
@@ -716,18 +693,14 @@ void VXNotebookConfigMgr::removeFilesOfNode(Node *p_node, bool p_force)
         NodeContentMediaUtils::removeMediaFiles(p_node);
 
         // Delete node file itself.
-        auto filePath = p_node->fetchRelativePath();
+        auto filePath = p_node->fetchPath();
         getBackend()->removeFile(filePath);
-        break;
-    }
-
-    case Node::Type::Folder:
-    {
+    } else {
         Q_ASSERT(p_node->getChildrenCount() == 0);
         // Delete node config file and the dir if it is empty.
         auto configFilePath = getNodeConfigFilePath(p_node);
         getBackend()->removeFile(configFilePath);
-        auto folderPath = p_node->fetchRelativePath();
+        auto folderPath = p_node->fetchPath();
         if (p_force) {
             getBackend()->removeDir(folderPath);
         } else {
@@ -737,37 +710,15 @@ void VXNotebookConfigMgr::removeFilesOfNode(Node *p_node, bool p_force)
                 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) {
+    if (p_node->hasContent()) {
         getBackend()->makePath(pa);
     }
     return pa;
@@ -777,7 +728,7 @@ QString VXNotebookConfigMgr::fetchNodeAttachmentFolderPath(Node *p_node)
 {
     auto notebookFolder = PathUtils::concatenateFilePath(PathUtils::parentDirPath(p_node->fetchAbsolutePath()),
                                                          getNotebook()->getAttachmentFolder());
-    if (p_node->getType() == Node::Type::File) {
+    if (p_node->hasContent()) {
         auto nodeFolder = p_node->getAttachmentFolder();
         if (nodeFolder.isEmpty()) {
             auto folderPath = fetchNodeAttachmentFolder(p_node->fetchAbsolutePath(), nodeFolder);
@@ -830,7 +781,7 @@ bool VXNotebookConfigMgr::isBuiltInFolder(const Node *p_node, const QString &p_n
 QSharedPointer<Node> VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_srcPath, Node *p_dest)
 {
     // Copy source file itself.
-    auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchRelativePath(),
+    auto destFilePath = PathUtils::concatenateFilePath(p_dest->fetchPath(),
                                                        PathUtils::fileName(p_srcPath));
     destFilePath = getBackend()->renameIfExistsCaseInsensitive(destFilePath);
     getBackend()->copyFile(p_srcPath, destFilePath);
@@ -840,14 +791,14 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_src
 
     // 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);
+    auto destNode = QSharedPointer<VXNode>::create(getNotebook()->getAndUpdateNextNodeId(),
+                                                   PathUtils::fileName(destFilePath),
+                                                   currentTime,
+                                                   currentTime,
+                                                   QStringList(),
+                                                   QString(),
+                                                   getNotebook(),
+                                                   p_dest);
     addChildNode(p_dest, destNode);
     writeNodeConfig(p_dest);
 
@@ -856,7 +807,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_src
 
 QSharedPointer<Node> VXNotebookConfigMgr::copyFolderAsChildOf(const QString &p_srcPath, Node *p_dest)
 {
-    auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchRelativePath(),
+    auto destFolderPath = PathUtils::concatenateFilePath(p_dest->fetchPath(),
                                                          PathUtils::fileName(p_srcPath));
     destFolderPath = getBackend()->renameIfExistsCaseInsensitive(destFolderPath);
 
@@ -865,10 +816,15 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFolderAsChildOf(const QString &p_s
 
     // 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>>());
+    auto destNode = QSharedPointer<VXNode>::create(PathUtils::fileName(destFolderPath),
+                                                   notebook,
+                                                   p_dest);
+    auto currentTime = QDateTime::currentDateTimeUtc();
+    destNode->loadCompleteInfo(notebook->getAndUpdateNextNodeId(),
+                               currentTime,
+                               currentTime,
+                               QStringList(),
+                               QVector<QSharedPointer<Node>>());
 
     writeNodeConfig(destNode.data());
 
@@ -880,7 +836,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFolderAsChildOf(const QString &p_s
 
 void VXNotebookConfigMgr::inheritNodeFlags(const Node *p_node, Node *p_child) const
 {
-    if (p_node->getFlags() & Node::Flag::ReadOnly) {
+    if (p_node->isReadOnly()) {
         markNodeReadOnly(p_child);
     }
 }

+ 10 - 16
src/core/notebookconfigmgr/vxnotebookconfigmgr.h

@@ -12,8 +12,6 @@ class QJsonObject;
 
 namespace vnotex
 {
-    class FolderNode;
-
     // Config manager for VNoteX's bundle notebook.
     class VXNotebookConfigMgr : public BundleNotebookConfigMgr
     {
@@ -41,16 +39,16 @@ namespace vnotex
         void renameNode(Node *p_node, const QString &p_name) Q_DECL_OVERRIDE;
 
         QSharedPointer<Node> newNode(Node *p_parent,
-                                     Node::Type p_type,
+                                     Node::Flags p_flags,
                                      const QString &p_name) Q_DECL_OVERRIDE;
 
         QSharedPointer<Node> addAsNode(Node *p_parent,
-                                       Node::Type p_type,
+                                       Node::Flags p_flags,
                                        const QString &p_name,
                                        const NodeParameters &p_paras) Q_DECL_OVERRIDE;
 
         QSharedPointer<Node> copyAsNode(Node *p_parent,
-                                        Node::Type p_type,
+                                        Node::Flags p_flags,
                                         const QString &p_path) Q_DECL_OVERRIDE;
 
         QSharedPointer<Node> loadNodeByPath(const QSharedPointer<Node> &p_root,
@@ -62,13 +60,11 @@ namespace vnotex
 
         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;
+        bool isBuiltInFile(const Node *p_node, const QString &p_name) const Q_DECL_OVERRIDE;
 
-        void writeNode(Node *p_node, const QString &p_content) Q_DECL_OVERRIDE;
+        bool isBuiltInFolder(const Node *p_node, const QString &p_name) const Q_DECL_OVERRIDE;
 
-        QString fetchNodeImageFolderPath(Node *p_node) Q_DECL_OVERRIDE;
+        QString fetchNodeImageFolderPath(Node *p_node);
 
         QString fetchNodeAttachmentFolderPath(Node *p_node) Q_DECL_OVERRIDE;
 
@@ -105,7 +101,8 @@ namespace vnotex
 
             NodeConfig(const QString &p_version,
                        ID p_id,
-                       const QDateTime &p_createdTimeUtc);
+                       const QDateTime &p_createdTimeUtc,
+                       const QDateTime &p_modifiedTimeUtc);
 
             QJsonObject toJson() const;
 
@@ -114,6 +111,7 @@ namespace vnotex
             QString m_version;
             ID m_id = Node::InvalidId;
             QDateTime m_createdTimeUtc;
+            QDateTime m_modifiedTimeUtc;
             QVector<NodeFileConfig> m_files;
             QVector<NodeFolderConfig> m_folders;
 
@@ -147,7 +145,7 @@ namespace vnotex
                                               const QString &p_name,
                                               Node *p_parent = nullptr) const;
 
-        void loadFolderNode(FolderNode *p_node, const NodeConfig &p_config) const;
+        void loadFolderNode(Node *p_node, const NodeConfig &p_config) const;
 
         QSharedPointer<VXNotebookConfigMgr::NodeConfig> nodeToNodeConfig(const Node *p_node) const;
 
@@ -186,10 +184,6 @@ namespace vnotex
         // 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;

+ 12 - 12
src/utils/fileutils.cpp

@@ -15,7 +15,7 @@ QByteArray FileUtils::readFile(const QString &p_filePath)
     QFile file(p_filePath);
     if (!file.open(QIODevice::ReadOnly)) {
         Exception::throwOne(Exception::Type::FailToReadFile,
-                            QString("fail to read file: %1").arg(p_filePath));
+                            QString("failed to read file: %1").arg(p_filePath));
     }
 
     return file.readAll();
@@ -26,7 +26,7 @@ QString FileUtils::readTextFile(const QString &p_filePath)
     QFile file(p_filePath);
     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
         Exception::throwOne(Exception::Type::FailToReadFile,
-                            QString("fail to read file: %1").arg(p_filePath));
+                            QString("failed to read file: %1").arg(p_filePath));
     }
 
     QString text(file.readAll());
@@ -39,7 +39,7 @@ void FileUtils::writeFile(const QString &p_filePath, const QByteArray &p_data)
     QFile file(p_filePath);
     if (!file.open(QIODevice::WriteOnly)) {
         Exception::throwOne(Exception::Type::FailToWriteFile,
-                            QString("fail to write to file: %1").arg(p_filePath));
+                            QString("failed to write to file: %1").arg(p_filePath));
     }
 
     file.write(p_data);
@@ -51,7 +51,7 @@ void FileUtils::writeFile(const QString &p_filePath, const QString &p_text)
     QFile file(p_filePath);
     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
         Exception::throwOne(Exception::Type::FailToWriteFile,
-                            QString("fail to write to file: %1").arg(p_filePath));
+                            QString("failed to write to file: %1").arg(p_filePath));
     }
 
     QTextStream stream(&file);
@@ -66,7 +66,7 @@ void FileUtils::renameFile(const QString &p_path, const QString &p_name)
     QFile file(p_path);
     if (!file.exists() || !file.rename(newFilePath)) {
         Exception::throwOne(Exception::Type::FailToRenameFile,
-                            QString("fail to rename file: %1").arg(p_path));
+                            QString("failed to rename file: %1").arg(p_path));
     }
 }
 
@@ -104,7 +104,7 @@ void FileUtils::copyFile(const QString &p_filePath,
     QDir dir;
     if (!dir.mkpath(PathUtils::parentDirPath(p_destPath))) {
         Exception::throwOne(Exception::Type::FailToCreateDir,
-            QString("fail to create directory: %1").arg(PathUtils::parentDirPath(p_destPath)));
+            QString("failed to create directory: %1").arg(PathUtils::parentDirPath(p_destPath)));
     }
 
     bool failed = false;
@@ -121,7 +121,7 @@ void FileUtils::copyFile(const QString &p_filePath,
 
     if (failed) {
         Exception::throwOne(Exception::Type::FailToCopyFile,
-                            QString("fail to copy file: %1 %2").arg(p_filePath, p_destPath));
+                            QString("failed to copy file: %1 %2").arg(p_filePath, p_destPath));
     }
 }
 
@@ -144,7 +144,7 @@ void FileUtils::copyDir(const QString &p_dirPath,
     QDir destDir(p_destPath);
     if (!destDir.mkpath(p_destPath)) {
         Exception::throwOne(Exception::Type::FailToCreateDir,
-            QString("fail to create directory: %1").arg(p_destPath));
+            QString("failed to create directory: %1").arg(p_destPath));
     }
 
     // Copy directory contents recursively.
@@ -167,7 +167,7 @@ void FileUtils::copyDir(const QString &p_dirPath,
     if (p_move) {
         if (!destDir.rmdir(p_dirPath)) {
             Exception::throwOne(Exception::Type::FailToRemoveDir,
-                QString("fail to remove source directory after move: %1").arg(p_dirPath));
+                QString("failed to remove source directory after move: %1").arg(p_dirPath));
         }
     }
 }
@@ -198,7 +198,7 @@ void FileUtils::removeFile(const QString &p_filePath)
     QFile file(p_filePath);
     if (!file.remove()) {
         Exception::throwOne(Exception::Type::FailToRemoveFile,
-                            QString("fail to remove file: %1").arg(p_filePath));
+                            QString("failed to remove file: %1").arg(p_filePath));
     }
 }
 
@@ -211,7 +211,7 @@ bool FileUtils::removeDirIfEmpty(const QString &p_dirPath)
 
     if (!dir.rmdir(p_dirPath)) {
         Exception::throwOne(Exception::Type::FailToRemoveFile,
-                            QString("fail to remove directory: %1").arg(p_dirPath));
+                            QString("failed to remove directory: %1").arg(p_dirPath));
         return false;
     }
 
@@ -223,7 +223,7 @@ void FileUtils::removeDir(const QString &p_dirPath)
     QDir dir(p_dirPath);
     if (!dir.removeRecursively()) {
         Exception::throwOne(Exception::Type::FailToRemoveFile,
-                            QString("fail to remove directory recursively: %1").arg(p_dirPath));
+                            QString("failed to remove directory recursively: %1").arg(p_dirPath));
     }
 }
 

+ 1 - 1
src/widgets/dialogs/folderpropertiesdialog.cpp

@@ -64,7 +64,7 @@ bool FolderPropertiesDialog::validateNameInput(QString &p_msg)
     }
 
     if (name != m_node->getName()
-        && m_infoWidget->getParentNode()->hasChild(name, false)) {
+        && m_infoWidget->getParentNode()->containsChild(name, false)) {
         p_msg = tr("Name conflicts with existing folder.");
         return false;
     }

+ 1 - 1
src/widgets/dialogs/importfolderdialog.cpp

@@ -86,7 +86,7 @@ bool ImportFolderDialog::importFolder()
     auto nb = m_parentNode->getNotebook();
     m_newNode = nullptr;
     try {
-        m_newNode = nb->copyAsNode(m_parentNode, Node::Type::Folder, folder);
+        m_newNode = nb->copyAsNode(m_parentNode, Node::Flag::Container, folder);
     } catch (Exception &p_e) {
         auto msg = tr("Failed to add folder (%1) as node (%2).").arg(folder, p_e.what());
         qCritical() << msg;

+ 4 - 4
src/widgets/dialogs/importfolderutils.cpp

@@ -23,7 +23,7 @@ void ImportFolderUtils::importFolderContents(Notebook *p_notebook,
 
             QSharedPointer<Node> node;
             try {
-                node = p_notebook->addAsNode(p_node, Node::Type::Folder, child.fileName(), NodeParameters());
+                node = p_notebook->addAsNode(p_node, Node::Flag::Container, child.fileName(), NodeParameters());
             } catch (Exception &p_e) {
                 Utils::appendMsg(p_errMsg, ImportFolderUtilsTranslate::tr("Failed to add folder (%1) as node (%2).").arg(child.fileName(), p_e.what()));
                 continue;
@@ -33,7 +33,7 @@ void ImportFolderUtils::importFolderContents(Notebook *p_notebook,
         } else if (!p_notebook->isBuiltInFile(p_node, child.fileName())) {
             if (p_suffixes.contains(child.suffix())) {
                 try {
-                    p_notebook->addAsNode(p_node, Node::Type::File, child.fileName(), NodeParameters());
+                    p_notebook->addAsNode(p_node, Node::Flag::Content, child.fileName(), NodeParameters());
                 } catch (Exception &p_e) {
                     Utils::appendMsg(p_errMsg, ImportFolderUtilsTranslate::tr("Failed to add file (%1) as node (%2).").arg(child.filePath(), p_e.what()));
                 }
@@ -69,7 +69,7 @@ void ImportFolderUtils::importFolderContentsByLegacyConfig(Notebook *p_notebook,
                 try {
                     NodeParameters paras;
                     paras.m_createdTimeUtc = LegacyNotebookUtils::getCreatedTimeUtcOfFolder(rootDir.filePath(name));
-                    node = p_notebook->addAsNode(p_node, Node::Type::Folder, name, paras);
+                    node = p_notebook->addAsNode(p_node, Node::Flag::Container, name, paras);
                 } catch (Exception &p_e) {
                     Utils::appendMsg(p_errMsg, ImportFolderUtilsTranslate::tr("Failed to add folder (%1) as node (%2).").arg(name, p_e.what()));
                     return;
@@ -97,7 +97,7 @@ void ImportFolderUtils::importFolderContentsByLegacyConfig(Notebook *p_notebook,
                     paras.m_modifiedTimeUtc = info.m_modifiedTimeUtc;
                     paras.m_attachmentFolder = info.m_attachmentFolder;
                     paras.m_tags = info.m_tags;
-                    node = p_notebook->addAsNode(p_node, Node::Type::File, info.m_name, paras);
+                    node = p_notebook->addAsNode(p_node, Node::Flag::Content, info.m_name, paras);
                 } catch (Exception &p_e) {
                     Utils::appendMsg(p_errMsg, ImportFolderUtilsTranslate::tr("Failed to add file (%1) as node (%2).").arg(info.m_name, p_e.what()));
                     return;

+ 3 - 3
src/widgets/dialogs/newfolderdialog.cpp

@@ -33,7 +33,7 @@ void NewFolderDialog::setupUI(const Node *p_node)
 
 void NewFolderDialog::setupNodeInfoWidget(const Node *p_node, QWidget *p_parent)
 {
-    m_infoWidget = new NodeInfoWidget(p_node, Node::Type::Folder, p_parent);
+    m_infoWidget = new NodeInfoWidget(p_node, Node::Flag::Container, p_parent);
     connect(m_infoWidget, &NodeInfoWidget::inputEdited,
             this, &NewFolderDialog::validateInputs);
 }
@@ -59,7 +59,7 @@ bool NewFolderDialog::validateNameInput(QString &p_msg)
         return false;
     }
 
-    if (m_infoWidget->getParentNode()->hasChild(name, false)) {
+    if (m_infoWidget->getParentNode()->containsChild(name, false)) {
         p_msg = tr("Name conflicts with existing folder.");
         return false;
     }
@@ -81,7 +81,7 @@ bool NewFolderDialog::newFolder()
     Notebook *notebook = const_cast<Notebook *>(m_infoWidget->getNotebook());
     Node *parentNode = const_cast<Node *>(m_infoWidget->getParentNode());
     try {
-        m_newNode = notebook->newNode(parentNode, Node::Type::Folder, m_infoWidget->getName());
+        m_newNode = notebook->newNode(parentNode, Node::Flag::Container, m_infoWidget->getName());
     } catch (Exception &p_e) {
         QString msg = tr("Failed to create folder under (%1) in (%2) (%3).").arg(parentNode->getName(),
                                                                                  notebook->getName(),

+ 3 - 3
src/widgets/dialogs/newnotedialog.cpp

@@ -37,7 +37,7 @@ void NewNoteDialog::setupUI(const Node *p_node)
 
 void NewNoteDialog::setupNodeInfoWidget(const Node *p_node, QWidget *p_parent)
 {
-    m_infoWidget = new NodeInfoWidget(p_node, Node::Type::File, p_parent);
+    m_infoWidget = new NodeInfoWidget(p_node, Node::Flag::Content, p_parent);
     connect(m_infoWidget, &NodeInfoWidget::inputEdited,
             this, &NewNoteDialog::validateInputs);
 }
@@ -63,7 +63,7 @@ bool NewNoteDialog::validateNameInput(QString &p_msg)
         return false;
     }
 
-    if (m_infoWidget->getParentNode()->hasChild(name, false)) {
+    if (m_infoWidget->getParentNode()->containsChild(name, false)) {
         p_msg = tr("Name conflicts with existing note.");
         return false;
     }
@@ -85,7 +85,7 @@ bool NewNoteDialog::newNote()
     Notebook *notebook = const_cast<Notebook *>(m_infoWidget->getNotebook());
     Node *parentNode = const_cast<Node *>(m_infoWidget->getParentNode());
     try {
-        m_newNode = notebook->newNode(parentNode, Node::Type::File, m_infoWidget->getName());
+        m_newNode = notebook->newNode(parentNode, Node::Flag::Content, m_infoWidget->getName());
     } catch (Exception &p_e) {
         QString msg = tr("Failed to create note under (%1) in (%2) (%3).").arg(parentNode->getName(),
                                                                                notebook->getName(),

+ 5 - 5
src/widgets/dialogs/nodeinfowidget.cpp

@@ -17,24 +17,24 @@ NodeInfoWidget::NodeInfoWidget(const Node *p_node, QWidget *p_parent)
     : QWidget(p_parent),
       m_mode(Mode::Edit)
 {
-    setupUI(p_node->getParent(), p_node->getType());
+    setupUI(p_node->getParent(), p_node->getFlags());
 
     setNode(p_node);
 }
 
 NodeInfoWidget::NodeInfoWidget(const Node *p_parentNode,
-                               Node::Type p_typeToCreate,
+                               Node::Flags p_flags,
                                QWidget *p_parent)
     : QWidget(p_parent),
       m_mode(Mode::Create)
 {
-    setupUI(p_parentNode, p_typeToCreate);
+    setupUI(p_parentNode, p_flags);
 }
 
-void NodeInfoWidget::setupUI(const Node *p_parentNode, Node::Type p_newNodeType)
+void NodeInfoWidget::setupUI(const Node *p_parentNode, Node::Flags p_newNodeFlags)
 {
     const bool createMode = m_mode == Mode::Create;
-    const bool isNote = p_newNodeType == Node::Type::File;
+    const bool isNote = p_newNodeFlags & Node::Flag::Content;
 
     m_mainLayout = WidgetUtils::createFormLayout(this);
 

+ 2 - 2
src/widgets/dialogs/nodeinfowidget.h

@@ -24,7 +24,7 @@ namespace vnotex
         NodeInfoWidget(const Node *p_node, QWidget *p_parent = nullptr);
 
         NodeInfoWidget(const Node *p_parentNode,
-                       Node::Type p_typeToCreate,
+                       Node::Flags p_flags,
                        QWidget *p_parent = nullptr);
 
         QLineEdit *getNameLineEdit() const;
@@ -39,7 +39,7 @@ namespace vnotex
         void inputEdited();
 
     private:
-        void setupUI(const Node *p_parentNode, Node::Type p_newNodeType);
+        void setupUI(const Node *p_parentNode, Node::Flags p_newNodeFlags);
 
         void setupFileTypeComboBox(QWidget *p_parent);
 

+ 1 - 1
src/widgets/dialogs/nodelabelwithupbutton.cpp

@@ -45,7 +45,7 @@ void NodeLabelWithUpButton::setupUI()
 
 void NodeLabelWithUpButton::updateLabelAndButton()
 {
-    m_label->setText(m_node->fetchRelativePath());
+    m_label->setText(m_node->fetchPath());
     m_upButton->setVisible(!m_readOnly && !m_node->isRoot());
 }
 

+ 1 - 1
src/widgets/dialogs/notepropertiesdialog.cpp

@@ -66,7 +66,7 @@ bool NotePropertiesDialog::validateNameInput(QString &p_msg)
     }
 
     if (name != m_node->getName()
-        && m_infoWidget->getParentNode()->hasChild(name, false)) {
+        && m_infoWidget->getParentNode()->containsChild(name, false)) {
         p_msg = tr("Name conflicts with existing note.");
         return false;
     }

+ 1 - 1
src/widgets/markdownviewwindow.cpp

@@ -460,7 +460,7 @@ void MarkdownViewWindow::syncTextEditorFromBuffer(bool p_syncPositionFromReadMod
     m_editor->setBuffer(buffer);
     if (buffer) {
         m_editor->setReadOnly(buffer->isReadOnly());
-        m_editor->setBasePath(buffer->getContentBasePath());
+        m_editor->setBasePath(buffer->getResourcePath());
         m_editor->setText(buffer->getContent());
         m_editor->setModified(buffer->isModified());
 

+ 3 - 3
src/widgets/notebookexplorer.cpp

@@ -183,10 +183,10 @@ Node *NotebookExplorer::currentExploredFolderNode() const
 
     auto node = m_nodeExplorer->getCurrentNode();
     if (node) {
-        if (node->getType() == Node::Type::File) {
+        if (!node->isContainer()) {
             node = node->getParent();
         }
-        Q_ASSERT(node && node->getType() == Node::Type::Folder);
+        Q_ASSERT(node && node->isContainer());
     } else {
         node = m_currentNotebook->getRootNode().data();
     }
@@ -241,7 +241,7 @@ void NotebookExplorer::importFile()
     QString errMsg;
     for (const auto &file : files) {
         try {
-            m_currentNotebook->copyAsNode(node, Node::Type::File, file);
+            m_currentNotebook->copyAsNode(node, Node::Flag::Content, file);
         } catch (Exception &p_e) {
             errMsg += tr("Failed to add file (%1) as node (%2).\n").arg(file, p_e.what());
         }

+ 9 - 19
src/widgets/notebooknodeexplorer.cpp

@@ -419,19 +419,15 @@ void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, Node *p_node, b
 
 QIcon NotebookNodeExplorer::getNodeItemIcon(const Node *p_node) const
 {
-    switch (p_node->getType()) {
-    case Node::Type::File:
+    if (p_node->hasContent()) {
         return s_fileNodeIcon;
-
-    case Node::Type::Folder:
-    {
+    } else {
         if (p_node->getUse() == Node::Use::RecycleBin) {
             return s_recycleBinNodeIcon;
         }
 
         return s_folderNodeIcon;
     }
-    }
 
     return QIcon();
 }
@@ -794,18 +790,12 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
                     }
 
                     int ret = QDialog::Rejected;
-                    switch (node->getType()) {
-                    case Node::Type::File:
-                    {
+                    if (node->hasContent()) {
                         NotePropertiesDialog dialog(node, VNoteX::getInst().getMainWindow());
                         ret = dialog.exec();
-                        break;
-                    }
-
-                    case Node::Type::Folder:
+                    } else {
                         FolderPropertiesDialog dialog(node, VNoteX::getInst().getMainWindow());
                         ret = dialog.exec();
-                        break;
                     }
 
                     if (ret == QDialog::Accepted) {
@@ -822,7 +812,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
                     auto node = getCurrentNode();
                     if (node) {
                         locationPath = node->fetchAbsolutePath();
-                        if (node->getType() == Node::Type::File) {
+                        if (!node->isContainer()) {
                             locationPath = PathUtils::parentDirPath(locationPath);
                         }
                     } else if (m_notebook) {
@@ -940,7 +930,7 @@ void NotebookNodeExplorer::copySelectedNodes(bool p_move)
                         p_move ? ClipboardData::MoveNode : ClipboardData::CopyNode);
     for (auto node : nodes) {
         auto item = QSharedPointer<NodeClipboardDataItem>::create(node->getNotebook()->getId(),
-                                                                  node->fetchRelativePath());
+                                                                  node->fetchPath());
         cdata.addItem(item);
     }
 
@@ -1012,7 +1002,7 @@ static QSharedPointer<Node> getNodeFromClipboardDataItem(const NodeClipboardData
     }
 
     auto node = notebook->loadNodeByPath(p_item->m_nodeRelativePath);
-    Q_ASSERT(!node || node->fetchRelativePath() == p_item->m_nodeRelativePath);
+    Q_ASSERT(!node || node->fetchPath() == p_item->m_nodeRelativePath);
     return node;
 }
 
@@ -1024,12 +1014,12 @@ void NotebookNodeExplorer::pasteNodesFromClipboard()
         destNode = m_notebook->getRootNode().data();
     } else {
         // Current node may be a file node.
-        if (destNode->getType() == Node::Type::File) {
+        if (!destNode->isContainer()) {
             destNode = destNode->getParent();
         }
     }
 
-    Q_ASSERT(destNode && destNode->getType() == Node::Type::Folder);
+    Q_ASSERT(destNode && destNode->isContainer());
 
     // Fetch source nodes from clipboard.
     auto cdata = tryFetchClipboardData();