From 2d5c421fd9233c43dfa9da2a9d4dfbb5376b1cfd Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Wed, 25 Aug 2021 15:07:22 +0200
Subject: [PATCH] LIB: Add versions to VivyDocument save files

VivyDocument save json files have versions. Be future proof and add a
way to load old save files with new runtime versions. Also improve the
load process as the it depend on the save version.

Also be sure to use string keys in the json document to avoid strange
things...
---
 src/Lib/Document/VivyDocument.cc | 60 ++++++++++++++++++++++----------
 src/Lib/Document/VivyDocument.hh | 31 ++++++++++++-----
 2 files changed, 64 insertions(+), 27 deletions(-)

diff --git a/src/Lib/Document/VivyDocument.cc b/src/Lib/Document/VivyDocument.cc
index e643e0ca..e5e4302e 100644
--- a/src/Lib/Document/VivyDocument.cc
+++ b/src/Lib/Document/VivyDocument.cc
@@ -12,6 +12,9 @@
 
 using namespace Vivy;
 
+// Runtime document version, don't set it in the header!!!
+QString VivyDocument::runtimeVersion = QStringLiteral("alpha");
+
 VivyDocument::VivyDocument(const QString &documentPath, Options opt)
     : AbstractDocument(AbstractDocument::Type::Vivy, documentPath)
     , documentOptions(opt)
@@ -54,21 +57,17 @@ VivyDocument::save() const
 }
 
 void
-VivyDocument::loadSaveJsonDocumentFile(const QJsonDocument &json)
+VivyDocument::loadSaveJsonDocumentFile_ALPHA(VivyDocument *const self, const QJsonDocument &json)
 {
-    if (json.isNull())
-        throw std::logic_error("Json is null, you can't pass a null json "
-                               "document to this function");
-
     // Init from the JSON
-    documentType = json[KeyCapabilities].toString().toULongLong() & possibleCapabilities;
-    if (json[KeyName].toString() != documentName)
+    self->documentType = json[KeyCapabilities].toString().toULongLong() & possibleCapabilities;
+    if (json[KeyName].toString() != self->documentName)
         throw std::runtime_error("The document was edited outside of Vivy");
 
     if (QJsonValue audio = json[KeySubDocuments][KeySubAudio]; !audio.isNull())
-        loadSubDocument(audio.toString(), Capabilities::AudioAble);
+        self->loadSubDocument(audio.toString(), Capabilities::AudioAble);
     if (QJsonValue video = json[KeySubDocuments][KeySubVideo]; !video.isNull())
-        loadSubDocument(video.toString(), Capabilities::VideoAble);
+        self->loadSubDocument(video.toString(), Capabilities::VideoAble);
 
     if (QJsonValue internalAssSource = json[KeySubDocuments][KeyInternalAssSource];
         !internalAssSource.isNull() && internalAssSource.toBool()) {
@@ -76,10 +75,31 @@ VivyDocument::loadSaveJsonDocumentFile(const QJsonDocument &json)
         throw std::runtime_error("The internal ASS feature is not supported for now");
     } else if (QJsonValue ass = json[KeySubDocuments][KeySubAss]; !ass.isNull()) {
         // ASS in its own ASS file
-        loadSubDocument(ass.toString(), Capabilities::AssAble);
+        self->loadSubDocument(ass.toString(), Capabilities::AssAble);
     }
 }
 
+void
+VivyDocument::loadSaveJsonDocumentFile(const QJsonDocument &json)
+{
+    if (json.isNull())
+        throw std::logic_error("Json is null, you can't pass a null json "
+                               "document to this function");
+
+    const QJsonValue savedVersionJson = json[KeyDocumentVersion];
+    if (savedVersionJson.isNull())
+        throw std::runtime_error("No version present in the saved VivyDocument");
+
+    const QString savedVersion = savedVersionJson.toString();
+    for (const auto &[version, callback] : loadFunctions) {
+        if (savedVersion == version)
+            return callback(this, json);
+    }
+
+    throw std::runtime_error("Version is undefined! No way has been "
+                             "found to load the document");
+}
+
 QJsonDocument
 VivyDocument::getSaveJsonDocumentFile() const
 {
@@ -87,25 +107,29 @@ VivyDocument::getSaveJsonDocumentFile() const
     QJsonObject json;
     QJsonObject subDocumentJson;
 
-    json.insert(QString::number(KeyCapabilities),
-                QString::number(possibleCapabilities & documentType));
-    json.insert(QString::number(KeyName), documentName);
+    json.insert(KeyCapabilities, QString::number(possibleCapabilities & documentType));
+    json.insert(KeyName, documentName);
+    json.insert(KeyDocumentVersion, runtimeVersion);
 
     if (documentType & Capabilities::AudioAble)
-        subDocumentJson.insert(QString::number(KeySubAudio), audioDocument->getFilePath());
+        subDocumentJson.insert(KeySubAudio, audioDocument->getFilePath());
+
     if (documentType & Capabilities::VideoAble)
-        subDocumentJson.insert(QString::number(KeySubAudio), videoDocument->getFilePath());
+        subDocumentJson.insert(KeySubAudio, videoDocument->getFilePath());
+
     if (documentType & Capabilities::AssAble) {
         // ASS is inside Vivy document
-        if (assDocument->getFilePath() == getName())
+        if ((assDocument->getFilePath() == getName())) {
+            subDocumentJson.insert(KeyInternalAssSource, true);
             throw std::runtime_error("Unsupported save of internal ASS for now");
+        }
 
         // ASS is ints own ASS file
         else
-            subDocumentJson.insert(QString::number(KeySubAudio), assDocument->getFilePath());
+            subDocumentJson.insert(KeySubAudio, assDocument->getFilePath());
     }
 
-    json.insert(QString::number(KeySubDocuments), subDocumentJson);
+    json.insert(KeySubDocuments, subDocumentJson);
     ret.setObject(json);
     return ret;
 }
diff --git a/src/Lib/Document/VivyDocument.hh b/src/Lib/Document/VivyDocument.hh
index 692902f9..42e40ca3 100644
--- a/src/Lib/Document/VivyDocument.hh
+++ b/src/Lib/Document/VivyDocument.hh
@@ -11,30 +11,38 @@
 #include "CRTPSubDocument.hh"
 
 #include <memory>
+#include <functional>
+#include <initializer_list>
 #include <QString>
 #include <QDir>
 #include <QJsonDocument>
 
+#define DCL_VIVY_SAVE_KEY(name) static const inline QString Key##name = QStringLiteral(#name);
+
 namespace Vivy
 {
 class VivyDocument final : public AbstractDocument {
     Q_OBJECT
     VIVY_UNMOVABLE_OBJECT(VivyDocument)
 
-    enum JsonKey {
-        KeyCapabilities,
-        KeyName,
-        KeySubDocuments,
-        KeySubAudio,
-        KeySubAss,
-        KeySubVideo,
-        KeyInternalAssSource
-    };
+    DCL_VIVY_SAVE_KEY(Capabilities)
+    DCL_VIVY_SAVE_KEY(Name)
+    DCL_VIVY_SAVE_KEY(SubDocuments)
+    DCL_VIVY_SAVE_KEY(SubAudio)
+    DCL_VIVY_SAVE_KEY(SubAss)
+    DCL_VIVY_SAVE_KEY(SubVideo)
+    DCL_VIVY_SAVE_KEY(InternalAssSource)
+    DCL_VIVY_SAVE_KEY(DocumentVersion)
+
+    using LoadFunctionForVersion =
+        std::pair<const char *, void (*)(VivyDocument *const, const QJsonDocument &)>;
 
 public:
     enum Capabilities : quint64 { AudioAble = (1 << 1), VideoAble = (1 << 2), AssAble = (1 << 3) };
     enum Options : quint64 { NoOption = 0, UntouchedByDefault = (1 << 1) };
 
+    static QString runtimeVersion;
+
     static inline const QString fileSuffix{ "vivy" };
     static inline constexpr Utils::DocumentType type     = Utils::DocumentType::Vivy;
     static constexpr inline quint64 possibleCapabilities = AudioAble | VideoAble | AssAble;
@@ -59,6 +67,11 @@ private:
     QJsonDocument getSaveJsonDocumentFile() const;
     void loadSaveJsonDocumentFile(const QJsonDocument &);
 
+    static void loadSaveJsonDocumentFile_ALPHA(VivyDocument *const, const QJsonDocument &);
+
+    static constexpr inline auto loadFunctions = { LoadFunctionForVersion{
+        "alpha", loadSaveJsonDocumentFile_ALPHA } };
+
 public:
     // Create an empty document
     explicit VivyDocument(const QString &name, Options opt = NoOption);
-- 
GitLab