From d7a888d651e8df3019f8352793b19632f3bc2a2b Mon Sep 17 00:00:00 2001 From: Mm2PL Date: Fri, 1 Dec 2023 16:54:23 +0100 Subject: [PATCH] Use pajlada::Settings for uploaded images (includes migration) --- src/singletons/ImageUploader.cpp | 111 +++++++++++++++++++------------ src/singletons/ImageUploader.hpp | 89 +++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 44 deletions(-) diff --git a/src/singletons/ImageUploader.cpp b/src/singletons/ImageUploader.cpp index d08067e26..6fd5fbaab 100644 --- a/src/singletons/ImageUploader.cpp +++ b/src/singletons/ImageUploader.cpp @@ -9,16 +9,21 @@ #include "singletons/Paths.hpp" #include "singletons/Settings.hpp" #include "util/CombinePath.hpp" +#include "util/RapidjsonHelpers.hpp" #include "widgets/helper/ResizingTextEdit.hpp" #include #include #include #include +#include #include #include #include #include +#include + +#include #define UPLOAD_DELAY 2000 // Delay between uploads in milliseconds @@ -48,50 +53,15 @@ void ImageUploader::logToFile(const QString &originalFilePath, const QString &imageLink, const QString &deletionLink, ChannelPtr channel) { - const QString logFileName = - combinePath((getSettings()->logPath.getValue().isEmpty() - ? getPaths()->messageLogDirectory - : getSettings()->logPath), - "ImageUploader.json"); - - //reading existing logs - QFile logReadFile(logFileName); - bool isLogFileOkay = - logReadFile.open(QIODevice::ReadWrite | QIODevice::Text); - if (!isLogFileOkay) - { - channel->addMessage(makeSystemMessage( - QString("Failed to open log file with links at ") + logFileName)); - return; - } - auto logs = logReadFile.readAll(); - if (logs.isEmpty()) - { - logs = QJsonDocument(QJsonArray()).toJson(); - } - logReadFile.close(); - - //writing new data to logs - QJsonObject newLogEntry; - newLogEntry["channelName"] = channel->getName(); - newLogEntry["deletionLink"] = - deletionLink.isEmpty() ? QJsonValue(QJsonValue::Null) : deletionLink; - newLogEntry["imageLink"] = imageLink; - newLogEntry["localPath"] = originalFilePath.isEmpty() - ? QJsonValue(QJsonValue::Null) - : originalFilePath; - newLogEntry["timestamp"] = QDateTime::currentSecsSinceEpoch(); - // channel name - // deletion link (can be empty) - // image link - // local path to an image (can be empty) - // timestamp - QSaveFile logSaveFile(logFileName); - logSaveFile.open(QIODevice::WriteOnly | QIODevice::Text); - QJsonArray entries = QJsonDocument::fromJson(logs).array(); - entries.push_back(newLogEntry); - logSaveFile.write(QJsonDocument(entries).toJson()); - logSaveFile.commit(); + this->imageLogSetting_->push_back(UploadedImage{ + .channelName = channel->getName(), + .deletionLink = deletionLink, + .imageLink = imageLink, + .localPath = originalFilePath, + .timestamp = QDateTime::currentSecsSinceEpoch(), + }); + qCDebug(chatterinoCommon) << "Saving ImageUploader.json"; + this->sm_->save(); } // extracting link to either image or its deletion from response body @@ -121,6 +91,59 @@ QString getLinkFromResponse(NetworkResult response, QString pattern) void ImageUploader::save() { + this->sm_->save(); +} + +void ImageUploader::initialize(Settings &settings, Paths &paths) +{ + auto logPath = (getSettings()->logPath.getValue().isEmpty() + ? getPaths()->messageLogDirectory + : getSettings()->logPath); + const QString oldLogName = combinePath(logPath, "ImageUploader.json"); + + // read/write new one + const QString path = combinePath(logPath, "ImageUploader2.json"); + this->sm_ = std::make_shared(); + this->sm_->setPath(qPrintable(path)); + this->sm_->setBackupEnabled(true); + this->sm_->setBackupSlots(9); + + this->imageLogSetting_ = std::make_unique< + pajlada::Settings::Setting>>( + "/uploadedImages", this->sm_); + + // try to read old log + QFile oldLogFile(oldLogName); + bool isOldLogFileOkay = + oldLogFile.open(QIODevice::ReadOnly | QIODevice::Text); + if (isOldLogFileOkay) + { + auto data = oldLogFile.readAll(); + rapidjson::Document doc; + doc.Parse(data.data(), data.size()); + if (doc.HasParseError()) + { + qCWarning(chatterinoCommon) << "Unable to read ImageUploader.json"; + return; + } + std::vector temporary; + if (!doc.IsArray()) + { + qCWarning(chatterinoCommon) + << "Unable to parse ImageUploader.json: not an array"; + return; + } + if (!rj::getSafe(doc, temporary)) + { + qCWarning(chatterinoCommon) + << "Unable to parse ImageUploader.json: getSafe failed"; + return; + } + this->imageLogSetting_->setValue(temporary); + oldLogFile.close(); + oldLogFile.rename(combinePath(logPath, "ImageUploader.old.json")); + this->sm_->save(); + } } void ImageUploader::sendImageUploadRequest(RawImageData imageData, diff --git a/src/singletons/ImageUploader.hpp b/src/singletons/ImageUploader.hpp index 260180583..cfc9e8f1e 100644 --- a/src/singletons/ImageUploader.hpp +++ b/src/singletons/ImageUploader.hpp @@ -1,11 +1,16 @@ #pragma once #include "common/Singleton.hpp" +#include "pajlada/settings/setting.hpp" +#include "pajlada/settings/settingmanager.hpp" +#include "util/RapidjsonHelpers.hpp" #include #include #include +#include +#include #include #include @@ -22,12 +27,21 @@ struct RawImageData { QString filePath; }; +struct UploadedImage { + QString channelName; + QString deletionLink; + QString imageLink; + QString localPath; + int64_t timestamp{}; +}; + class ImageUploader final : public Singleton { public: void save() override; void upload(const QMimeData *source, ChannelPtr channel, QPointer outputTextEdit); + void initialize(Settings &settings, Paths &paths) override; private: void sendImageUploadRequest(RawImageData imageData, ChannelPtr channel, @@ -45,5 +59,80 @@ private: // These variables are only used from the main thread. QMutex uploadMutex_; std::queue uploadQueue_; + + std::shared_ptr sm_; + std::unique_ptr>> + imageLogSetting_; }; } // namespace chatterino + // +namespace pajlada { +template <> +struct Serialize { + static rapidjson::Value get(const chatterino::UploadedImage &value, + rapidjson::Document::AllocatorType &a) + { + rapidjson::Value ret(rapidjson::kObjectType); + + chatterino::rj::set(ret, "channelName", value.channelName, a); + chatterino::rj::set(ret, "imageLink", value.imageLink, a); + chatterino::rj::set(ret, "timestamp", value.timestamp, a); + chatterino::rj::set(ret, "localPath", value.localPath, a); + chatterino::rj::set(ret, "deletionLink", value.deletionLink, a); + + return ret; + } +}; + +template <> +struct Deserialize { + static chatterino::UploadedImage get(const rapidjson::Value &value, + bool *error = nullptr) + { + chatterino::UploadedImage img; + + if (!value.IsObject()) + { + PAJLADA_REPORT_ERROR(error); + return img; + } + + if (value["localPath"].IsNull()) + { + img.localPath = QString(); + } + else if (!chatterino::rj::getSafe(value, "localPath", img.localPath)) + { + PAJLADA_REPORT_ERROR(error); + return img; + } + if (!chatterino::rj::getSafe(value, "imageLink", img.imageLink)) + { + PAJLADA_REPORT_ERROR(error); + return img; + } + if (value["deletionLink"].IsNull()) + { + img.deletionLink = QString(); + } + else if (!chatterino::rj::getSafe(value, "deletionLink", + img.deletionLink)) + { + PAJLADA_REPORT_ERROR(error); + return img; + } + if (!chatterino::rj::getSafe(value, "channelName", img.channelName)) + { + PAJLADA_REPORT_ERROR(error); + return img; + } + if (!chatterino::rj::getSafe(value, "timestamp", img.timestamp)) + { + PAJLADA_REPORT_ERROR(error); + return img; + } + + return img; + } +}; +} // namespace pajlada