mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Refactored the Image Uploader feature. (#4971)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
7898b97fc2
commit
fbc8aacabe
|
@ -39,6 +39,8 @@
|
||||||
- Bugfix: Fixed a freeze from a bad regex in _Ignores_. (#4965)
|
- Bugfix: Fixed a freeze from a bad regex in _Ignores_. (#4965)
|
||||||
- Bugfix: Fixed some emotes not appearing when using _Ignores_. (#4965)
|
- Bugfix: Fixed some emotes not appearing when using _Ignores_. (#4965)
|
||||||
- Bugfix: Fixed lookahead/-behind not working in _Ignores_. (#4965)
|
- Bugfix: Fixed lookahead/-behind not working in _Ignores_. (#4965)
|
||||||
|
- Bugfix: Fixed Image Uploader accidentally deleting images with some hosts when link resolver was enabled. (#4971)
|
||||||
|
- Bugfix: Fixed rare crash with Image Uploader when closing a split right after starting an upload. (#4971)
|
||||||
- Dev: Change clang-format from v14 to v16. (#4929)
|
- Dev: Change clang-format from v14 to v16. (#4929)
|
||||||
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)
|
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)
|
||||||
- Dev: Temporarily disable High DPI scaling on Qt6 builds on Windows. (#4767)
|
- Dev: Temporarily disable High DPI scaling on Qt6 builds on Windows. (#4767)
|
||||||
|
@ -66,6 +68,7 @@
|
||||||
- Dev: `Details` file properties tab is now populated on Windows. (#4912)
|
- Dev: `Details` file properties tab is now populated on Windows. (#4912)
|
||||||
- Dev: Removed `Outcome` from network requests. (#4959)
|
- Dev: Removed `Outcome` from network requests. (#4959)
|
||||||
- Dev: Added Tests for Windows and MacOS in CI. (#4970)
|
- Dev: Added Tests for Windows and MacOS in CI. (#4970)
|
||||||
|
- Dev: Refactored the Image Uploader feature. (#4971)
|
||||||
|
|
||||||
## 2.4.6
|
## 2.4.6
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,11 @@ public:
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageUploader *getImageUploader() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino::mock
|
} // namespace chatterino::mock
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "controllers/hotkeys/HotkeyController.hpp"
|
#include "controllers/hotkeys/HotkeyController.hpp"
|
||||||
#include "controllers/ignores/IgnoreController.hpp"
|
#include "controllers/ignores/IgnoreController.hpp"
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
|
#include "singletons/ImageUploader.hpp"
|
||||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||||
# include "controllers/plugins/PluginController.hpp"
|
# include "controllers/plugins/PluginController.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
@ -79,6 +80,7 @@ Application::Application(Settings &_settings, Paths &_paths)
|
||||||
, hotkeys(&this->emplace<HotkeyController>())
|
, hotkeys(&this->emplace<HotkeyController>())
|
||||||
, windows(&this->emplace<WindowManager>())
|
, windows(&this->emplace<WindowManager>())
|
||||||
, toasts(&this->emplace<Toasts>())
|
, toasts(&this->emplace<Toasts>())
|
||||||
|
, imageUploader(&this->emplace<ImageUploader>())
|
||||||
|
|
||||||
, commands(&this->emplace<CommandController>())
|
, commands(&this->emplace<CommandController>())
|
||||||
, notifications(&this->emplace<NotificationController>())
|
, notifications(&this->emplace<NotificationController>())
|
||||||
|
|
|
@ -41,6 +41,7 @@ class Toasts;
|
||||||
class ChatterinoBadges;
|
class ChatterinoBadges;
|
||||||
class FfzBadges;
|
class FfzBadges;
|
||||||
class SeventvBadges;
|
class SeventvBadges;
|
||||||
|
class ImageUploader;
|
||||||
|
|
||||||
class IApplication
|
class IApplication
|
||||||
{
|
{
|
||||||
|
@ -66,6 +67,7 @@ public:
|
||||||
virtual SeventvBadges *getSeventvBadges() = 0;
|
virtual SeventvBadges *getSeventvBadges() = 0;
|
||||||
virtual IUserDataController *getUserData() = 0;
|
virtual IUserDataController *getUserData() = 0;
|
||||||
virtual ITwitchLiveController *getTwitchLiveController() = 0;
|
virtual ITwitchLiveController *getTwitchLiveController() = 0;
|
||||||
|
virtual ImageUploader *getImageUploader() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Application : public IApplication
|
class Application : public IApplication
|
||||||
|
@ -94,6 +96,7 @@ public:
|
||||||
HotkeyController *const hotkeys{};
|
HotkeyController *const hotkeys{};
|
||||||
WindowManager *const windows{};
|
WindowManager *const windows{};
|
||||||
Toasts *const toasts{};
|
Toasts *const toasts{};
|
||||||
|
ImageUploader *const imageUploader{};
|
||||||
|
|
||||||
CommandController *const commands{};
|
CommandController *const commands{};
|
||||||
NotificationController *const notifications{};
|
NotificationController *const notifications{};
|
||||||
|
@ -167,6 +170,10 @@ public:
|
||||||
}
|
}
|
||||||
IUserDataController *getUserData() override;
|
IUserDataController *getUserData() override;
|
||||||
ITwitchLiveController *getTwitchLiveController() override;
|
ITwitchLiveController *getTwitchLiveController() override;
|
||||||
|
ImageUploader *getImageUploader() override
|
||||||
|
{
|
||||||
|
return this->imageUploader;
|
||||||
|
}
|
||||||
|
|
||||||
pajlada::Signals::NoArgSignal streamerModeChanged;
|
pajlada::Signals::NoArgSignal streamerModeChanged;
|
||||||
|
|
||||||
|
|
|
@ -424,6 +424,8 @@ set(SOURCE_FILES
|
||||||
singletons/Emotes.hpp
|
singletons/Emotes.hpp
|
||||||
singletons/Fonts.cpp
|
singletons/Fonts.cpp
|
||||||
singletons/Fonts.hpp
|
singletons/Fonts.hpp
|
||||||
|
singletons/ImageUploader.cpp
|
||||||
|
singletons/ImageUploader.hpp
|
||||||
singletons/Logging.cpp
|
singletons/Logging.cpp
|
||||||
singletons/Logging.hpp
|
singletons/Logging.hpp
|
||||||
singletons/NativeMessaging.cpp
|
singletons/NativeMessaging.cpp
|
||||||
|
@ -475,8 +477,6 @@ set(SOURCE_FILES
|
||||||
util/IpcQueue.hpp
|
util/IpcQueue.hpp
|
||||||
util/LayoutHelper.cpp
|
util/LayoutHelper.cpp
|
||||||
util/LayoutHelper.hpp
|
util/LayoutHelper.hpp
|
||||||
util/NuulsUploader.cpp
|
|
||||||
util/NuulsUploader.hpp
|
|
||||||
util/RapidjsonHelpers.cpp
|
util/RapidjsonHelpers.cpp
|
||||||
util/RapidjsonHelpers.hpp
|
util/RapidjsonHelpers.hpp
|
||||||
util/RatelimitBucket.cpp
|
util/RatelimitBucket.cpp
|
||||||
|
|
|
@ -32,7 +32,7 @@ Q_LOGGING_CATEGORY(chatterinoNativeMessage, "chatterino.nativemessage",
|
||||||
Q_LOGGING_CATEGORY(chatterinoNetwork, "chatterino.network", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoNetwork, "chatterino.network", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoNotification, "chatterino.notification",
|
Q_LOGGING_CATEGORY(chatterinoNotification, "chatterino.notification",
|
||||||
logThreshold);
|
logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoNuulsuploader, "chatterino.nuulsuploader",
|
Q_LOGGING_CATEGORY(chatterinoImageuploader, "chatterino.imageuploader",
|
||||||
logThreshold);
|
logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoPubSub, "chatterino.pubsub", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoPubSub, "chatterino.pubsub", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoRecentMessages, "chatterino.recentmessages",
|
Q_LOGGING_CATEGORY(chatterinoRecentMessages, "chatterino.recentmessages",
|
||||||
|
|
|
@ -25,7 +25,7 @@ Q_DECLARE_LOGGING_CATEGORY(chatterinoMessage);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoNativeMessage);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoNativeMessage);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoNetwork);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoNetwork);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoNotification);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoNotification);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoNuulsuploader);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoImageuploader);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoPubSub);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoPubSub);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoRecentMessages);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoRecentMessages);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoSettings);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoSettings);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "messages/Image.hpp"
|
#include "messages/Image.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
|
#include "messages/MessageColor.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/LinkResolver.hpp"
|
#include "providers/LinkResolver.hpp"
|
||||||
#include "providers/twitch/PubSubActions.hpp"
|
#include "providers/twitch/PubSubActions.hpp"
|
||||||
|
@ -660,6 +661,58 @@ MessageBuilder::MessageBuilder(LiveUpdatesUpdateEmoteSetMessageTag /*unused*/,
|
||||||
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
|
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageBuilder::MessageBuilder(ImageUploaderResultTag /*unused*/,
|
||||||
|
const QString &imageLink,
|
||||||
|
const QString &deletionLink,
|
||||||
|
size_t imagesStillQueued, size_t secondsLeft)
|
||||||
|
: MessageBuilder()
|
||||||
|
{
|
||||||
|
this->message().flags.set(MessageFlag::System);
|
||||||
|
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
|
||||||
|
|
||||||
|
this->emplace<TimestampElement>();
|
||||||
|
|
||||||
|
using MEF = MessageElementFlag;
|
||||||
|
auto addText = [this](QString text, MessageElementFlags mefs = MEF::Text,
|
||||||
|
MessageColor color =
|
||||||
|
MessageColor::System) -> TextElement * {
|
||||||
|
this->message().searchText += text;
|
||||||
|
this->message().messageText += text;
|
||||||
|
return this->emplace<TextElement>(text, mefs, color);
|
||||||
|
};
|
||||||
|
|
||||||
|
addText("Your image has been uploaded to");
|
||||||
|
|
||||||
|
// ASSUMPTION: the user gave this uploader configuration to the program
|
||||||
|
// therefore they trust that the host is not wrong/malicious. This doesn't obey getSettings()->lowercaseDomains.
|
||||||
|
// This also ensures that the LinkResolver doesn't get these links.
|
||||||
|
addText(imageLink, {MEF::OriginalLink, MEF::LowercaseLink},
|
||||||
|
MessageColor::Link)
|
||||||
|
->setLink({Link::Url, imageLink})
|
||||||
|
->setTrailingSpace(false);
|
||||||
|
|
||||||
|
if (!deletionLink.isEmpty())
|
||||||
|
{
|
||||||
|
addText("(Deletion link:");
|
||||||
|
addText(deletionLink, {MEF::OriginalLink, MEF::LowercaseLink},
|
||||||
|
MessageColor::Link)
|
||||||
|
->setLink({Link::Url, deletionLink})
|
||||||
|
->setTrailingSpace(false);
|
||||||
|
addText(")")->setTrailingSpace(false);
|
||||||
|
}
|
||||||
|
addText(".");
|
||||||
|
|
||||||
|
if (imagesStillQueued == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addText(QString("%1 left. Please wait until all of them are uploaded. "
|
||||||
|
"About %2 seconds left.")
|
||||||
|
.arg(imagesStillQueued)
|
||||||
|
.arg(secondsLeft));
|
||||||
|
}
|
||||||
|
|
||||||
Message *MessageBuilder::operator->()
|
Message *MessageBuilder::operator->()
|
||||||
{
|
{
|
||||||
return this->message_.get();
|
return this->message_.get();
|
||||||
|
|
|
@ -37,6 +37,9 @@ struct LiveUpdatesAddEmoteMessageTag {
|
||||||
};
|
};
|
||||||
struct LiveUpdatesUpdateEmoteSetMessageTag {
|
struct LiveUpdatesUpdateEmoteSetMessageTag {
|
||||||
};
|
};
|
||||||
|
struct ImageUploaderResultTag {
|
||||||
|
};
|
||||||
|
|
||||||
const SystemMessageTag systemMessage{};
|
const SystemMessageTag systemMessage{};
|
||||||
const TimeoutMessageTag timeoutMessage{};
|
const TimeoutMessageTag timeoutMessage{};
|
||||||
const LiveUpdatesUpdateEmoteMessageTag liveUpdatesUpdateEmoteMessage{};
|
const LiveUpdatesUpdateEmoteMessageTag liveUpdatesUpdateEmoteMessage{};
|
||||||
|
@ -44,6 +47,10 @@ const LiveUpdatesRemoveEmoteMessageTag liveUpdatesRemoveEmoteMessage{};
|
||||||
const LiveUpdatesAddEmoteMessageTag liveUpdatesAddEmoteMessage{};
|
const LiveUpdatesAddEmoteMessageTag liveUpdatesAddEmoteMessage{};
|
||||||
const LiveUpdatesUpdateEmoteSetMessageTag liveUpdatesUpdateEmoteSetMessage{};
|
const LiveUpdatesUpdateEmoteSetMessageTag liveUpdatesUpdateEmoteSetMessage{};
|
||||||
|
|
||||||
|
// This signifies that you want to construct a message containing the result of
|
||||||
|
// a successful image upload.
|
||||||
|
const ImageUploaderResultTag imageUploaderResultMessage{};
|
||||||
|
|
||||||
MessagePtr makeSystemMessage(const QString &text);
|
MessagePtr makeSystemMessage(const QString &text);
|
||||||
MessagePtr makeSystemMessage(const QString &text, const QTime &time);
|
MessagePtr makeSystemMessage(const QString &text, const QTime &time);
|
||||||
std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
|
std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
|
||||||
|
@ -88,6 +95,16 @@ public:
|
||||||
MessageBuilder(LiveUpdatesUpdateEmoteSetMessageTag, const QString &platform,
|
MessageBuilder(LiveUpdatesUpdateEmoteSetMessageTag, const QString &platform,
|
||||||
const QString &actor, const QString &emoteSetName);
|
const QString &actor, const QString &emoteSetName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Your image has been uploaded to %1[ (Deletion link: %2)]."
|
||||||
|
* or "Your image has been uploaded to %1 %2. %3 left. "
|
||||||
|
* "Please wait until all of them are uploaded. "
|
||||||
|
* "About %4 seconds left."
|
||||||
|
*/
|
||||||
|
MessageBuilder(ImageUploaderResultTag, const QString &imageLink,
|
||||||
|
const QString &deletionLink, size_t imagesStillQueued = 0,
|
||||||
|
size_t secondsLeft = 0);
|
||||||
|
|
||||||
virtual ~MessageBuilder() = default;
|
virtual ~MessageBuilder() = default;
|
||||||
|
|
||||||
Message *operator->();
|
Message *operator->();
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
#include "NuulsUploader.hpp"
|
#include "singletons/ImageUploader.hpp"
|
||||||
|
|
||||||
#include "common/Env.hpp"
|
#include "common/Env.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "common/NetworkResult.hpp"
|
#include "common/NetworkResult.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include <QPointer>
|
||||||
#include <QSaveFile>
|
#include <QSaveFile>
|
||||||
|
|
||||||
#define UPLOAD_DELAY 2000
|
#define UPLOAD_DELAY 2000
|
||||||
|
@ -41,13 +43,10 @@ std::optional<QByteArray> convertToPng(const QImage &image)
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
// These variables are only used from the main thread.
|
|
||||||
static auto uploadMutex = QMutex();
|
|
||||||
static std::queue<RawImageData> uploadQueue;
|
|
||||||
|
|
||||||
// logging information on successful uploads to a json file
|
// logging information on successful uploads to a json file
|
||||||
void logToFile(const QString originalFilePath, QString imageLink,
|
void ImageUploader::logToFile(const QString &originalFilePath,
|
||||||
QString deletionLink, ChannelPtr channel)
|
const QString &imageLink,
|
||||||
|
const QString &deletionLink, ChannelPtr channel)
|
||||||
{
|
{
|
||||||
const QString logFileName =
|
const QString logFileName =
|
||||||
combinePath((getSettings()->logPath.getValue().isEmpty()
|
combinePath((getSettings()->logPath.getValue().isEmpty()
|
||||||
|
@ -120,8 +119,13 @@ QString getLinkFromResponse(NetworkResult response, QString pattern)
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
void uploadImageToNuuls(RawImageData imageData, ChannelPtr channel,
|
void ImageUploader::save()
|
||||||
ResizingTextEdit &textEdit)
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageUploader::sendImageUploadRequest(RawImageData imageData,
|
||||||
|
ChannelPtr channel,
|
||||||
|
QPointer<ResizingTextEdit> textEdit)
|
||||||
{
|
{
|
||||||
const static char *const boundary = "thisistheboudaryasd";
|
const static char *const boundary = "thisistheboudaryasd";
|
||||||
const static QString contentType =
|
const static QString contentType =
|
||||||
|
@ -155,91 +159,103 @@ void uploadImageToNuuls(RawImageData imageData, ChannelPtr channel,
|
||||||
.header("Content-Type", contentType)
|
.header("Content-Type", contentType)
|
||||||
.headerList(extraHeaders)
|
.headerList(extraHeaders)
|
||||||
.multiPart(payload)
|
.multiPart(payload)
|
||||||
.onSuccess([&textEdit, channel,
|
.onSuccess(
|
||||||
originalFilePath](NetworkResult result) {
|
[textEdit, channel, originalFilePath, this](NetworkResult result) {
|
||||||
QString link = getSettings()->imageUploaderLink.getValue().isEmpty()
|
this->handleSuccessfulUpload(result, originalFilePath, channel,
|
||||||
? result.getData()
|
textEdit);
|
||||||
: getLinkFromResponse(
|
})
|
||||||
result, getSettings()->imageUploaderLink);
|
.onError([channel, this](NetworkResult result) -> bool {
|
||||||
QString deletionLink =
|
this->handleFailedUpload(result, channel);
|
||||||
getSettings()->imageUploaderDeletionLink.getValue().isEmpty()
|
|
||||||
? ""
|
|
||||||
: getLinkFromResponse(
|
|
||||||
result, getSettings()->imageUploaderDeletionLink);
|
|
||||||
qCDebug(chatterinoNuulsuploader) << link << deletionLink;
|
|
||||||
textEdit.insertPlainText(link + " ");
|
|
||||||
if (uploadQueue.empty())
|
|
||||||
{
|
|
||||||
channel->addMessage(makeSystemMessage(
|
|
||||||
QString("Your image has been uploaded to %1 %2.")
|
|
||||||
.arg(link)
|
|
||||||
.arg(deletionLink.isEmpty()
|
|
||||||
? ""
|
|
||||||
: QString("(Deletion link: %1 )")
|
|
||||||
.arg(deletionLink))));
|
|
||||||
uploadMutex.unlock();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
channel->addMessage(makeSystemMessage(
|
|
||||||
QString("Your image has been uploaded to %1 %2. %3 left. "
|
|
||||||
"Please wait until all of them are uploaded. "
|
|
||||||
"About %4 seconds left.")
|
|
||||||
.arg(link)
|
|
||||||
.arg(deletionLink.isEmpty()
|
|
||||||
? ""
|
|
||||||
: QString("(Deletion link: %1 )")
|
|
||||||
.arg(deletionLink))
|
|
||||||
.arg(uploadQueue.size())
|
|
||||||
.arg(uploadQueue.size() * (UPLOAD_DELAY / 1000 + 1))));
|
|
||||||
// 2 seconds for the timer that's there not to spam the remote server
|
|
||||||
// and 1 second of actual uploading.
|
|
||||||
|
|
||||||
QTimer::singleShot(UPLOAD_DELAY, [channel, &textEdit]() {
|
|
||||||
uploadImageToNuuls(uploadQueue.front(), channel, textEdit);
|
|
||||||
uploadQueue.pop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logToFile(originalFilePath, link, deletionLink, channel);
|
|
||||||
})
|
|
||||||
.onError([channel](NetworkResult result) -> bool {
|
|
||||||
auto errorMessage =
|
|
||||||
QString("An error happened while uploading your image: %1")
|
|
||||||
.arg(result.formatError());
|
|
||||||
|
|
||||||
// Try to read more information from the result body
|
|
||||||
auto obj = result.parseJson();
|
|
||||||
if (!obj.isEmpty())
|
|
||||||
{
|
|
||||||
auto apiCode = obj.value("code");
|
|
||||||
if (!apiCode.isUndefined())
|
|
||||||
{
|
|
||||||
auto codeString = apiCode.toVariant().toString();
|
|
||||||
codeString.truncate(20);
|
|
||||||
errorMessage += QString(" - code: %1").arg(codeString);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto apiError = obj.value("error").toString();
|
|
||||||
if (!apiError.isEmpty())
|
|
||||||
{
|
|
||||||
apiError.truncate(300);
|
|
||||||
errorMessage +=
|
|
||||||
QString(" - error: %1").arg(apiError.trimmed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
channel->addMessage(makeSystemMessage(errorMessage));
|
|
||||||
uploadMutex.unlock();
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
void upload(const QMimeData *source, ChannelPtr channel,
|
void ImageUploader::handleFailedUpload(const NetworkResult &result,
|
||||||
ResizingTextEdit &outputTextEdit)
|
ChannelPtr channel)
|
||||||
{
|
{
|
||||||
if (!uploadMutex.tryLock())
|
auto errorMessage =
|
||||||
|
QString("An error happened while uploading your image: %1")
|
||||||
|
.arg(result.formatError());
|
||||||
|
|
||||||
|
// Try to read more information from the result body
|
||||||
|
auto obj = result.parseJson();
|
||||||
|
if (!obj.isEmpty())
|
||||||
|
{
|
||||||
|
auto apiCode = obj.value("code");
|
||||||
|
if (!apiCode.isUndefined())
|
||||||
|
{
|
||||||
|
auto codeString = apiCode.toVariant().toString();
|
||||||
|
codeString.truncate(20);
|
||||||
|
errorMessage += QString(" - code: %1").arg(codeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto apiError = obj.value("error").toString();
|
||||||
|
if (!apiError.isEmpty())
|
||||||
|
{
|
||||||
|
apiError.truncate(300);
|
||||||
|
errorMessage += QString(" - error: %1").arg(apiError.trimmed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channel->addMessage(makeSystemMessage(errorMessage));
|
||||||
|
this->uploadMutex_.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageUploader::handleSuccessfulUpload(const NetworkResult &result,
|
||||||
|
QString originalFilePath,
|
||||||
|
ChannelPtr channel,
|
||||||
|
QPointer<ResizingTextEdit> textEdit)
|
||||||
|
{
|
||||||
|
if (textEdit == nullptr)
|
||||||
|
{
|
||||||
|
// Split was destroyed abort further uploads
|
||||||
|
|
||||||
|
while (!this->uploadQueue_.empty())
|
||||||
|
{
|
||||||
|
this->uploadQueue_.pop();
|
||||||
|
}
|
||||||
|
this->uploadMutex_.unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QString link =
|
||||||
|
getSettings()->imageUploaderLink.getValue().isEmpty()
|
||||||
|
? result.getData()
|
||||||
|
: getLinkFromResponse(result, getSettings()->imageUploaderLink);
|
||||||
|
QString deletionLink =
|
||||||
|
getSettings()->imageUploaderDeletionLink.getValue().isEmpty()
|
||||||
|
? ""
|
||||||
|
: getLinkFromResponse(result,
|
||||||
|
getSettings()->imageUploaderDeletionLink);
|
||||||
|
qCDebug(chatterinoImageuploader) << link << deletionLink;
|
||||||
|
textEdit->insertPlainText(link + " ");
|
||||||
|
|
||||||
|
// 2 seconds for the timer that's there not to spam the remote server
|
||||||
|
// and 1 second of actual uploading.
|
||||||
|
auto timeToUpload = this->uploadQueue_.size() * (UPLOAD_DELAY / 1000 + 1);
|
||||||
|
MessageBuilder builder(imageUploaderResultMessage, link, deletionLink,
|
||||||
|
this->uploadQueue_.size(), timeToUpload);
|
||||||
|
channel->addMessage(builder.release());
|
||||||
|
if (this->uploadQueue_.empty())
|
||||||
|
{
|
||||||
|
this->uploadMutex_.unlock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTimer::singleShot(UPLOAD_DELAY, [channel, &textEdit, this]() {
|
||||||
|
this->sendImageUploadRequest(this->uploadQueue_.front(), channel,
|
||||||
|
textEdit);
|
||||||
|
this->uploadQueue_.pop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this->logToFile(originalFilePath, link, deletionLink, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageUploader::upload(const QMimeData *source, ChannelPtr channel,
|
||||||
|
QPointer<ResizingTextEdit> outputTextEdit)
|
||||||
|
{
|
||||||
|
if (!this->uploadMutex_.tryLock())
|
||||||
{
|
{
|
||||||
channel->addMessage(makeSystemMessage(
|
channel->addMessage(makeSystemMessage(
|
||||||
QString("Please wait until the upload finishes.")));
|
QString("Please wait until the upload finishes.")));
|
||||||
|
@ -265,7 +281,7 @@ void upload(const QMimeData *source, ChannelPtr channel,
|
||||||
{
|
{
|
||||||
channel->addMessage(
|
channel->addMessage(
|
||||||
makeSystemMessage(QString("Couldn't load image :(")));
|
makeSystemMessage(QString("Couldn't load image :(")));
|
||||||
uploadMutex.unlock();
|
this->uploadMutex_.unlock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +289,7 @@ void upload(const QMimeData *source, ChannelPtr channel,
|
||||||
if (imageData)
|
if (imageData)
|
||||||
{
|
{
|
||||||
RawImageData data = {*imageData, "png", localPath};
|
RawImageData data = {*imageData, "png", localPath};
|
||||||
uploadQueue.push(data);
|
this->uploadQueue_.push(data);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -281,7 +297,7 @@ void upload(const QMimeData *source, ChannelPtr channel,
|
||||||
QString("Cannot upload file: %1. Couldn't convert "
|
QString("Cannot upload file: %1. Couldn't convert "
|
||||||
"image to png.")
|
"image to png.")
|
||||||
.arg(localPath)));
|
.arg(localPath)));
|
||||||
uploadMutex.unlock();
|
this->uploadMutex_.unlock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,11 +311,11 @@ void upload(const QMimeData *source, ChannelPtr channel,
|
||||||
{
|
{
|
||||||
channel->addMessage(
|
channel->addMessage(
|
||||||
makeSystemMessage(QString("Failed to open file. :(")));
|
makeSystemMessage(QString("Failed to open file. :(")));
|
||||||
uploadMutex.unlock();
|
this->uploadMutex_.unlock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RawImageData data = {file.readAll(), "gif", localPath};
|
RawImageData data = {file.readAll(), "gif", localPath};
|
||||||
uploadQueue.push(data);
|
this->uploadQueue_.push(data);
|
||||||
file.close();
|
file.close();
|
||||||
// file.readAll() => might be a bit big but it /should/ work
|
// file.readAll() => might be a bit big but it /should/ work
|
||||||
}
|
}
|
||||||
|
@ -308,31 +324,32 @@ void upload(const QMimeData *source, ChannelPtr channel,
|
||||||
channel->addMessage(makeSystemMessage(
|
channel->addMessage(makeSystemMessage(
|
||||||
QString("Cannot upload file: %1. Not an image.")
|
QString("Cannot upload file: %1. Not an image.")
|
||||||
.arg(localPath)));
|
.arg(localPath)));
|
||||||
uploadMutex.unlock();
|
this->uploadMutex_.unlock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!uploadQueue.empty())
|
if (!this->uploadQueue_.empty())
|
||||||
{
|
{
|
||||||
uploadImageToNuuls(uploadQueue.front(), channel, outputTextEdit);
|
this->sendImageUploadRequest(this->uploadQueue_.front(), channel,
|
||||||
uploadQueue.pop();
|
outputTextEdit);
|
||||||
|
this->uploadQueue_.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (source->hasFormat("image/png"))
|
else if (source->hasFormat("image/png"))
|
||||||
{
|
{
|
||||||
// the path to file is not present every time, thus the filePath is empty
|
// the path to file is not present every time, thus the filePath is empty
|
||||||
uploadImageToNuuls({source->data("image/png"), "png", ""}, channel,
|
this->sendImageUploadRequest({source->data("image/png"), "png", ""},
|
||||||
outputTextEdit);
|
channel, outputTextEdit);
|
||||||
}
|
}
|
||||||
else if (source->hasFormat("image/jpeg"))
|
else if (source->hasFormat("image/jpeg"))
|
||||||
{
|
{
|
||||||
uploadImageToNuuls({source->data("image/jpeg"), "jpeg", ""}, channel,
|
this->sendImageUploadRequest({source->data("image/jpeg"), "jpeg", ""},
|
||||||
outputTextEdit);
|
channel, outputTextEdit);
|
||||||
}
|
}
|
||||||
else if (source->hasFormat("image/gif"))
|
else if (source->hasFormat("image/gif"))
|
||||||
{
|
{
|
||||||
uploadImageToNuuls({source->data("image/gif"), "gif", ""}, channel,
|
this->sendImageUploadRequest({source->data("image/gif"), "gif", ""},
|
||||||
outputTextEdit);
|
channel, outputTextEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -341,14 +358,14 @@ void upload(const QMimeData *source, ChannelPtr channel,
|
||||||
auto imageData = convertToPng(image);
|
auto imageData = convertToPng(image);
|
||||||
if (imageData)
|
if (imageData)
|
||||||
{
|
{
|
||||||
uploadImageToNuuls({*imageData, "png", ""}, channel,
|
sendImageUploadRequest({*imageData, "png", ""}, channel,
|
||||||
outputTextEdit);
|
outputTextEdit);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
channel->addMessage(makeSystemMessage(
|
channel->addMessage(makeSystemMessage(
|
||||||
QString("Cannot upload file, failed to convert to png.")));
|
QString("Cannot upload file, failed to convert to png.")));
|
||||||
uploadMutex.unlock();
|
this->uploadMutex_.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
49
src/singletons/ImageUploader.hpp
Normal file
49
src/singletons/ImageUploader.hpp
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Singleton.hpp"
|
||||||
|
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class ResizingTextEdit;
|
||||||
|
class Channel;
|
||||||
|
class NetworkResult;
|
||||||
|
using ChannelPtr = std::shared_ptr<Channel>;
|
||||||
|
|
||||||
|
struct RawImageData {
|
||||||
|
QByteArray data;
|
||||||
|
QString format;
|
||||||
|
QString filePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageUploader final : public Singleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void save() override;
|
||||||
|
void upload(const QMimeData *source, ChannelPtr channel,
|
||||||
|
QPointer<ResizingTextEdit> outputTextEdit);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void sendImageUploadRequest(RawImageData imageData, ChannelPtr channel,
|
||||||
|
QPointer<ResizingTextEdit> textEdit);
|
||||||
|
|
||||||
|
// This is called from the onSuccess handler of the NetworkRequest in sendImageUploadRequest
|
||||||
|
void handleSuccessfulUpload(const NetworkResult &result,
|
||||||
|
QString originalFilePath, ChannelPtr channel,
|
||||||
|
QPointer<ResizingTextEdit> textEdit);
|
||||||
|
void handleFailedUpload(const NetworkResult &result, ChannelPtr channel);
|
||||||
|
|
||||||
|
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<RawImageData> uploadQueue_;
|
||||||
|
};
|
||||||
|
} // namespace chatterino
|
|
@ -1,27 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QMimeData>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class ResizingTextEdit;
|
|
||||||
class Channel;
|
|
||||||
using ChannelPtr = std::shared_ptr<Channel>;
|
|
||||||
|
|
||||||
struct RawImageData {
|
|
||||||
QByteArray data;
|
|
||||||
QString format;
|
|
||||||
QString filePath;
|
|
||||||
};
|
|
||||||
|
|
||||||
void upload(QByteArray imageData, ChannelPtr channel,
|
|
||||||
ResizingTextEdit &textEdit, std::string format);
|
|
||||||
void upload(RawImageData imageData, ChannelPtr channel,
|
|
||||||
ResizingTextEdit &textEdit);
|
|
||||||
void upload(const QMimeData *source, ChannelPtr channel,
|
|
||||||
ResizingTextEdit &outputTextEdit);
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -16,12 +16,12 @@
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
#include "singletons/Fonts.hpp"
|
#include "singletons/Fonts.hpp"
|
||||||
|
#include "singletons/ImageUploader.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/Clipboard.hpp"
|
#include "util/Clipboard.hpp"
|
||||||
#include "util/Helpers.hpp"
|
#include "util/Helpers.hpp"
|
||||||
#include "util/NuulsUploader.hpp"
|
|
||||||
#include "util/StreamLink.hpp"
|
#include "util/StreamLink.hpp"
|
||||||
#include "widgets/dialogs/QualityPopup.hpp"
|
#include "widgets/dialogs/QualityPopup.hpp"
|
||||||
#include "widgets/dialogs/SelectChannelDialog.hpp"
|
#include "widgets/dialogs/SelectChannelDialog.hpp"
|
||||||
|
@ -405,7 +405,8 @@ Split::Split(QWidget *parent)
|
||||||
getSettings()->askOnImageUpload.setValue(false);
|
getSettings()->askOnImageUpload.setValue(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
upload(source, this->getChannel(), *this->input_->ui_.textEdit);
|
QPointer<ResizingTextEdit> edit = this->input_->ui_.textEdit;
|
||||||
|
getApp()->imageUploader->upload(source, this->getChannel(), edit);
|
||||||
});
|
});
|
||||||
|
|
||||||
getSettings()->imageUploaderEnabled.connect(
|
getSettings()->imageUploaderEnabled.connect(
|
||||||
|
|
Loading…
Reference in a new issue