diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fe5ddf821..5b256a103 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -446,7 +446,6 @@ set(SOURCE_FILES singletons/Fonts.hpp singletons/imageuploader/ImageUploader.cpp singletons/imageuploader/ImageUploader.hpp - singletons/imageuploader/UploadedImage.hpp singletons/imageuploader/UploadedImageModel.cpp singletons/imageuploader/UploadedImageModel.hpp singletons/Logging.cpp diff --git a/src/singletons/imageuploader/ImageUploader.cpp b/src/singletons/imageuploader/ImageUploader.cpp index d27f635e9..c179048de 100644 --- a/src/singletons/imageuploader/ImageUploader.cpp +++ b/src/singletons/imageuploader/ImageUploader.cpp @@ -13,6 +13,7 @@ #include "singletons/Settings.hpp" #include "util/CombinePath.hpp" #include "util/RapidjsonHelpers.hpp" +#include "util/Result.hpp" #include "widgets/helper/ResizingTextEdit.hpp" #include @@ -28,6 +29,7 @@ #include #include +#include #include @@ -50,6 +52,48 @@ std::optional convertToPng(const QImage &image) return std::nullopt; } +using namespace chatterino; +Result, QString> loadLogFile( + const QString &logFileName) +{ + //reading existing logs + QFile logReadFile(logFileName); + bool isLogFileOkay = + logReadFile.open(QIODevice::ReadWrite | QIODevice::Text); + if (!isLogFileOkay) + { + return QString("Failed to open log file with links at ") + logFileName; + } + auto logs = logReadFile.readAll(); + if (logs.isEmpty()) + { + logs = QJsonDocument(QJsonArray()).toJson(); + } + logReadFile.close(); + QJsonArray entries = QJsonDocument::fromJson(logs).array(); + std::vector images; + for (const auto &entry : entries) + { + if (!entry.isObject()) + { + qCWarning(chatterinoImageuploader) + << "History file contains non-Object JSON data!"; + continue; + } + auto obj = entry.toObject(); + images.emplace_back(obj); + } + return images; +} + +QString getLogFilePath() +{ + return combinePath((getSettings()->logPath.getValue().isEmpty() + ? getIApp()->getPaths().messageLogDirectory + : getSettings()->logPath), + "ImageUploader.json"); +} + } // namespace namespace chatterino { @@ -59,51 +103,36 @@ void ImageUploader::logToFile(const QString &originalFilePath, const QString &imageLink, const QString &deletionLink, ChannelPtr channel) { - const QString logFileName = - combinePath((getSettings()->logPath.getValue().isEmpty() - ? getIApp()->getPaths().messageLogDirectory - : getSettings()->logPath), - "ImageUploader.json"); - - //reading existing logs - QFile logReadFile(logFileName); - bool isLogFileOkay = - logReadFile.open(QIODevice::ReadWrite | QIODevice::Text); - if (!isLogFileOkay) + const QString logFileName = getLogFilePath(); + auto res = loadLogFile(logFileName); + if (!res.isOk()) { - channel->addMessage(makeSystemMessage( - QString("Failed to open log file with links at ") + logFileName)); + channel->addMessage(makeSystemMessage(res.error())); return; } - auto logs = logReadFile.readAll(); - if (logs.isEmpty()) - { - logs = QJsonDocument(QJsonArray()).toJson(); - } - logReadFile.close(); - + auto entries = res.value(); //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(); + UploadedImage img; + img.channelName = channel->getName(); + img.deletionLink = deletionLink; + img.imageLink = imageLink; + img.localPath = originalFilePath; + img.timestamp = QDateTime::currentSecsSinceEpoch(); + entries.push_back(img); // channel name // deletion link (can be empty) // image link // local path to an image (can be empty) // timestamp + QJsonArray arr; + for (auto &entry : entries) + { + arr.append(entry.toJson()); + } 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.write(QJsonDocument(arr).toJson()); logSaveFile.commit(); - //>>>>>>> e7508332ff1399a89424bfdc0997f979fd0c9acc:src/singletons/ImageUploader.cpp } // extracting link to either image or its deletion from response body @@ -133,86 +162,30 @@ QString getLinkFromResponse(NetworkResult response, QString pattern) void ImageUploader::save() { - this->sm_->save(); } UploadedImageModel *ImageUploader::createModel(QObject *parent) { auto *model = new UploadedImageModel(parent); + auto res = loadLogFile(getLogFilePath()); + + // Replace content of images_ + auto len = this->images_.raw().size(); + for (int i = 0; i < len; i++) + { + this->images_.removeAt(0); + } + + std::vector vec = res.valueOr({}); + for (const auto &img : vec) + { + this->images_.append(img); + } + model->initialize(&this->images_); return model; } -void ImageUploader::initialize(Settings &settings, const Paths &paths) -{ - auto logPath = (getSettings()->logPath.getValue().isEmpty() - ? paths.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->uploadedImagesSetting_ = std::make_unique< - pajlada::Settings::Setting>>( - "/uploadedImages", this->sm_); - this->sm_->load(); - - // 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; - } - for (const auto &t : temporary) - { - this->images_.append(t); - } - oldLogFile.close(); - oldLogFile.rename(combinePath(logPath, "ImageUploader.old.json")); - } - - for (const auto &item : this->uploadedImagesSetting_->getValue()) - { - this->images_.append(item); - } - this->signals_.addConnection( - this->images_.delayedItemsChanged.connect([this]() { - this->uploadedImagesSetting_->setValue(this->images_.raw()); - })); - - if (isOldLogFileOkay) - { - this->uploadedImagesSetting_->setValue(this->images_.raw()); - this->sm_->save(); - } -} - void ImageUploader::sendImageUploadRequest(RawImageData imageData, ChannelPtr channel, QPointer textEdit) diff --git a/src/singletons/imageuploader/ImageUploader.hpp b/src/singletons/imageuploader/ImageUploader.hpp index d04dbd081..f104eff73 100644 --- a/src/singletons/imageuploader/ImageUploader.hpp +++ b/src/singletons/imageuploader/ImageUploader.hpp @@ -2,19 +2,17 @@ #include "common/SignalVector.hpp" #include "common/Singleton.hpp" -#include "pajlada/settings/setting.hpp" -#include "pajlada/settings/settingmanager.hpp" -#include "singletons/imageuploader/UploadedImage.hpp" +#include "singletons/imageuploader/UploadedImageModel.hpp" #include +#include #include #include -#include +#include #include #include #include -#include namespace chatterino { @@ -23,12 +21,58 @@ class Channel; class NetworkResult; using ChannelPtr = std::shared_ptr; +struct UploadedImage { + QString channelName; + QString deletionLink; + QString imageLink; + QString localPath; + int64_t timestamp{}; + + UploadedImage() = default; + UploadedImage(QJsonObject obj) + : channelName(obj["channelName"].toString()) + , imageLink(obj["imageLink"].toString()) + , timestamp(obj["timestamp"].toInt()) + + { + auto del = obj["deletionLink"]; + if (!del.isNull()) + { + this->deletionLink = del.toString(); + } + auto path = obj["localPath"]; + if (!path.isNull()) + { + this->localPath = path.toString(); + } + } + + QJsonObject toJson() const + { + QJsonObject out; + out["channelName"] = this->channelName; + out["deletionLink"] = this->deletionLink.isEmpty() + ? QJsonValue(QJsonValue::Null) + : this->deletionLink; + out["imageLink"] = this->imageLink; + out["localPath"] = this->localPath.isEmpty() + ? QJsonValue(QJsonValue::Null) + : this->localPath; + + // without cast, this is ambiguous + out["timestamp"] = (qint64)this->timestamp; + return out; + } +}; + +class UploadedImageModel; + struct RawImageData { QByteArray data; QString format; QString filePath; }; -class UploadedImageModel; + class ImageUploader final : public Singleton { public: @@ -43,7 +87,6 @@ public: void save() override; void upload(std::queue images, ChannelPtr channel, QPointer outputTextEdit); - void initialize(Settings &settings, const Paths &paths) override; UploadedImageModel *createModel(QObject *parent); private: @@ -58,15 +101,11 @@ private: void logToFile(const QString &originalFilePath, const QString &imageLink, const QString &deletionLink, ChannelPtr channel); - // These variables are only used from the main thread. QMutex uploadMutex_; std::queue uploadQueue_; - std::shared_ptr sm_; SignalVector images_; - std::unique_ptr>> - uploadedImagesSetting_; pajlada::Signals::SignalHolder signals_; }; } // namespace chatterino diff --git a/src/singletons/imageuploader/UploadedImage.hpp b/src/singletons/imageuploader/UploadedImage.hpp deleted file mode 100644 index a2a5ad0fd..000000000 --- a/src/singletons/imageuploader/UploadedImage.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once -#include "util/RapidjsonHelpers.hpp" - -#include -#include - -#include - -namespace chatterino { -struct UploadedImage { - QString channelName; - QString deletionLink; - QString imageLink; - QString localPath; - int64_t timestamp{}; -}; -} // 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