2019-09-24 16:08:12 +02:00
|
|
|
#include "NuulsUploader.hpp"
|
|
|
|
|
|
|
|
#include "common/Env.hpp"
|
|
|
|
#include "common/NetworkRequest.hpp"
|
|
|
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
|
|
|
|
2019-09-24 18:28:28 +02:00
|
|
|
#include <QHttpMultiPart>
|
2019-09-24 16:08:12 +02:00
|
|
|
|
2019-10-11 15:41:33 +02:00
|
|
|
#define UPLOAD_DELAY 2000
|
|
|
|
// Delay between uploads in milliseconds
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
QString getImageFileFormat(QString path)
|
|
|
|
{
|
|
|
|
static QStringList listOfImageFormats = {".png", ".jpg", ".jpeg"};
|
|
|
|
for (const QString &format : listOfImageFormats)
|
|
|
|
{
|
2019-10-11 17:00:26 +02:00
|
|
|
if (path.endsWith(format, Qt::CaseInsensitive))
|
2019-10-11 15:41:33 +02:00
|
|
|
{
|
|
|
|
return format.mid(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::optional<QByteArray> convertToPng(QImage image)
|
|
|
|
{
|
|
|
|
QByteArray imageData;
|
|
|
|
QBuffer buf(&imageData);
|
|
|
|
buf.open(QIODevice::WriteOnly);
|
|
|
|
bool success = image.save(&buf, "png");
|
|
|
|
if (success)
|
|
|
|
{
|
|
|
|
return boost::optional<QByteArray>(imageData);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return boost::optional<QByteArray>(boost::none);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2019-09-24 16:08:12 +02:00
|
|
|
namespace chatterino {
|
2019-10-19 11:41:23 +02:00
|
|
|
// These variables are only used from the main thread.
|
2019-09-24 16:08:12 +02:00
|
|
|
bool isUploading = false;
|
|
|
|
|
|
|
|
std::queue<TypedBytes> uploadQueue;
|
2019-09-24 18:28:28 +02:00
|
|
|
|
2019-09-24 16:08:12 +02:00
|
|
|
void uploadImageToNuuls(TypedBytes imageData, ChannelPtr channel,
|
|
|
|
ResizingTextEdit &textEdit)
|
|
|
|
{
|
|
|
|
const char *boundary = "thisistheboudaryasd";
|
2019-10-11 15:41:33 +02:00
|
|
|
QString contentType =
|
|
|
|
QString("multipart/form-data; boundary=%1").arg(boundary);
|
2019-09-25 12:51:17 +02:00
|
|
|
static QUrl url(Env::get().imageUploaderUrl);
|
2019-09-24 16:08:12 +02:00
|
|
|
|
2019-09-24 18:28:28 +02:00
|
|
|
QHttpMultiPart *payload = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
|
|
|
QHttpPart part = QHttpPart();
|
|
|
|
part.setBody(imageData.data);
|
|
|
|
part.setHeader(QNetworkRequest::ContentTypeHeader,
|
|
|
|
QString("image/%1").arg(imageData.type));
|
|
|
|
part.setHeader(QNetworkRequest::ContentLengthHeader,
|
|
|
|
QVariant(imageData.data.length()));
|
|
|
|
part.setHeader(
|
|
|
|
QNetworkRequest::ContentDispositionHeader,
|
2019-10-11 15:41:33 +02:00
|
|
|
QString("form-data; name=\"attachment\"; filename=\"control_v.%1\"")
|
|
|
|
.arg(imageData.type));
|
2019-09-24 18:28:28 +02:00
|
|
|
payload->setBoundary(boundary);
|
|
|
|
payload->append(part);
|
2019-09-24 16:08:12 +02:00
|
|
|
NetworkRequest(url, NetworkRequestType::Post)
|
2019-10-11 15:41:33 +02:00
|
|
|
.header("Content-Type", contentType)
|
2019-09-24 16:08:12 +02:00
|
|
|
|
2019-09-24 18:28:28 +02:00
|
|
|
.multiPart(payload)
|
2019-09-24 16:08:12 +02:00
|
|
|
.onSuccess([&textEdit, channel](NetworkResult result) -> Outcome {
|
|
|
|
textEdit.insertPlainText(result.getData() + QString(" "));
|
2019-10-11 15:41:33 +02:00
|
|
|
isUploading = false;
|
2019-09-25 22:21:26 +02:00
|
|
|
if (uploadQueue.empty())
|
|
|
|
{
|
|
|
|
channel->addMessage(makeSystemMessage(
|
|
|
|
QString("Your image has been uploaded.")));
|
|
|
|
}
|
|
|
|
else
|
2019-09-24 16:08:12 +02:00
|
|
|
{
|
|
|
|
channel->addMessage(makeSystemMessage(
|
|
|
|
QString("Your image has been uploaded. %1 left. Please "
|
|
|
|
"wait until all of them are uploaded. About %2 "
|
|
|
|
"seconds left.")
|
2019-10-11 15:41:33 +02:00
|
|
|
.arg(uploadQueue.size(),
|
|
|
|
uploadQueue.size() *
|
|
|
|
(UPLOAD_DELAY / 1000 +
|
|
|
|
1) // convert UPLOAD_DELAY to seconds
|
|
|
|
)));
|
2019-09-24 16:08:12 +02:00
|
|
|
// Argument number 2 is the ETA.
|
|
|
|
// 2 seconds for the timer that's there not to spam Nuuls' server
|
|
|
|
// and 1 second of actual uploading.
|
2019-10-11 15:41:33 +02:00
|
|
|
|
|
|
|
QTimer::singleShot(UPLOAD_DELAY, [channel, &textEdit]() {
|
2019-09-24 16:08:12 +02:00
|
|
|
uploadImageToNuuls(uploadQueue.front(), channel, textEdit);
|
|
|
|
uploadQueue.pop();
|
2019-09-25 22:21:26 +02:00
|
|
|
});
|
|
|
|
}
|
2019-09-24 16:08:12 +02:00
|
|
|
return Success;
|
|
|
|
})
|
|
|
|
.onError([channel](NetworkResult result) -> bool {
|
|
|
|
channel->addMessage(makeSystemMessage(
|
|
|
|
QString("An error happened while uploading your image: %1")
|
|
|
|
.arg(result.status())));
|
|
|
|
isUploading = false;
|
|
|
|
return true;
|
|
|
|
})
|
|
|
|
.execute();
|
|
|
|
}
|
|
|
|
|
2019-09-25 22:21:26 +02:00
|
|
|
void upload(const QMimeData *source, ChannelPtr channel,
|
|
|
|
ResizingTextEdit &outputTextEdit)
|
2019-09-24 16:08:12 +02:00
|
|
|
{
|
2019-10-19 11:41:23 +02:00
|
|
|
// There's no need to thread proof this function. It is called only from the main thread.
|
2019-09-24 16:08:12 +02:00
|
|
|
if (isUploading)
|
|
|
|
{
|
|
|
|
channel->addMessage(makeSystemMessage(
|
2019-09-25 22:21:26 +02:00
|
|
|
QString("Please wait until the upload finishes.")));
|
2019-09-24 16:08:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
isUploading = true;
|
|
|
|
channel->addMessage(makeSystemMessage(QString("Started upload...")));
|
|
|
|
|
|
|
|
if (source->hasFormat("image/png"))
|
|
|
|
{
|
2019-09-24 18:28:28 +02:00
|
|
|
uploadImageToNuuls({source->data("image/png"), "png"}, channel,
|
|
|
|
outputTextEdit);
|
2019-09-24 16:08:12 +02:00
|
|
|
}
|
2019-09-25 22:21:26 +02:00
|
|
|
else if (source->hasFormat("image/jpeg"))
|
2019-09-24 16:08:12 +02:00
|
|
|
{
|
2019-09-25 22:21:26 +02:00
|
|
|
uploadImageToNuuls({source->data("image/jpeg"), "jpeg"}, channel,
|
|
|
|
outputTextEdit);
|
|
|
|
}
|
|
|
|
else if (source->hasFormat("image/gif"))
|
|
|
|
{
|
|
|
|
uploadImageToNuuls({source->data("image/gif"), "gif"}, channel,
|
|
|
|
outputTextEdit);
|
|
|
|
}
|
|
|
|
else if (source->hasUrls())
|
|
|
|
{
|
2019-10-11 15:41:33 +02:00
|
|
|
// This path gets chosen when files are copied from a file manager, like explorer.exe, caja.
|
2019-10-19 11:41:23 +02:00
|
|
|
// Each entry in source->urls() is a QUrl pointing to a file that was copied.
|
2019-09-26 14:24:41 +02:00
|
|
|
for (const QUrl &path : source->urls())
|
2019-09-24 16:08:12 +02:00
|
|
|
{
|
2019-09-26 14:42:30 +02:00
|
|
|
if (!getImageFileFormat(path.toLocalFile()).isEmpty())
|
2019-09-24 16:08:12 +02:00
|
|
|
{
|
2019-09-25 22:21:26 +02:00
|
|
|
channel->addMessage(makeSystemMessage(
|
|
|
|
QString("Uploading image: %1").arg(path.toLocalFile())));
|
|
|
|
QImage img = QImage(path.toLocalFile());
|
|
|
|
if (img.isNull())
|
2019-09-24 16:08:12 +02:00
|
|
|
{
|
2019-09-25 22:21:26 +02:00
|
|
|
channel->addMessage(
|
|
|
|
makeSystemMessage(QString("Couldn't load image :(")));
|
|
|
|
return;
|
2019-09-24 16:08:12 +02:00
|
|
|
}
|
2019-09-25 22:21:26 +02:00
|
|
|
|
2019-10-11 15:41:33 +02:00
|
|
|
boost::optional<QByteArray> imageData = convertToPng(img);
|
|
|
|
if (imageData)
|
|
|
|
{
|
|
|
|
TypedBytes data = {imageData.get(), "png"};
|
|
|
|
uploadQueue.push(data);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
channel->addMessage(makeSystemMessage(
|
|
|
|
QString("Cannot upload file: %1, Couldn't convert "
|
|
|
|
"image to png.")
|
|
|
|
.arg(path.toLocalFile())));
|
|
|
|
}
|
2019-09-25 22:21:26 +02:00
|
|
|
}
|
|
|
|
else if (path.toLocalFile().endsWith(".gif"))
|
|
|
|
{
|
|
|
|
channel->addMessage(makeSystemMessage(
|
|
|
|
QString("Uploading GIF: %1").arg(path.toLocalFile())));
|
|
|
|
QFile file(path.toLocalFile());
|
|
|
|
bool isOkay = file.open(QIODevice::ReadOnly);
|
|
|
|
if (!isOkay)
|
2019-09-24 16:08:12 +02:00
|
|
|
{
|
2019-09-25 22:21:26 +02:00
|
|
|
channel->addMessage(
|
|
|
|
makeSystemMessage(QString("Failed to open file. :(")));
|
|
|
|
return;
|
2019-09-24 16:08:12 +02:00
|
|
|
}
|
2019-09-25 22:21:26 +02:00
|
|
|
TypedBytes data = {file.readAll(), "gif"};
|
|
|
|
uploadQueue.push(data);
|
|
|
|
file.close();
|
|
|
|
// file.readAll() => might be a bit big but it /should/ work
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
channel->addMessage(makeSystemMessage(
|
|
|
|
QString("Cannot upload file: %1, not an image")
|
|
|
|
.arg(path.toLocalFile())));
|
2019-09-26 14:42:30 +02:00
|
|
|
isUploading = false;
|
2019-09-24 16:08:12 +02:00
|
|
|
}
|
|
|
|
}
|
2019-10-11 15:41:33 +02:00
|
|
|
if (!uploadQueue.empty())
|
2019-09-24 16:08:12 +02:00
|
|
|
{
|
|
|
|
uploadImageToNuuls(uploadQueue.front(), channel, outputTextEdit);
|
|
|
|
uploadQueue.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // not PNG, try loading it into QImage and save it to a PNG.
|
|
|
|
QImage image = qvariant_cast<QImage>(source->imageData());
|
2019-10-11 15:41:33 +02:00
|
|
|
boost::optional<QByteArray> imageData = convertToPng(image);
|
|
|
|
if (imageData)
|
2019-09-25 22:21:26 +02:00
|
|
|
{
|
2019-10-11 15:41:33 +02:00
|
|
|
uploadImageToNuuls({imageData.get(), "png"}, channel,
|
|
|
|
outputTextEdit);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
channel->addMessage(makeSystemMessage(
|
|
|
|
QString("Cannot upload file, failed to convert to png.")));
|
2019-09-25 22:21:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-11 15:41:33 +02:00
|
|
|
} // namespace chatterino
|