754 lines
22 KiB
C++
Executable File
754 lines
22 KiB
C++
Executable File
#include "thp_script_httq.h"
|
|
#include "logger.h"
|
|
|
|
#include <httq/HttpRequest.h>
|
|
#include <httq/DataStream.h>
|
|
|
|
#include <nglsettings/nglsettings_connections.h>
|
|
#include <NGLLogger/NGLLoggerBootstrap.h>
|
|
#include <Common2Core/tpm_tools.h>
|
|
#include <Common2Core/tpm_js_parser.h>
|
|
|
|
#include <QWebSocket>
|
|
#include <QWebSocketServer>
|
|
#include <QFile>
|
|
#include <QJsonParseError>
|
|
#include <QJsonDocument>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include <QTemporaryFile>
|
|
//#include <QSaveFile>
|
|
#include <QDateTime>
|
|
#include <QFileInfo>
|
|
#include <QDir>
|
|
#include <QUrlQuery>
|
|
#include <QDebug>
|
|
|
|
|
|
#define L QStringLiteral
|
|
|
|
|
|
using namespace TakeHomePackageManager;
|
|
|
|
|
|
namespace FileManagement
|
|
{
|
|
// LoggerFactory
|
|
LoggerFactory::LoggerFactory(QObject *parent)
|
|
: httq::LoggerFactory(parent)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
Logger *LoggerFactory::createLogger(QObject *parent)
|
|
{
|
|
return new Logger(this, parent);
|
|
}
|
|
|
|
|
|
LoggerFactory *ThpScriptServer::createLoggerFactory()
|
|
{
|
|
return new LoggerFactory(this);
|
|
}
|
|
|
|
|
|
// Logger
|
|
Logger::Logger(LoggerFactory *loggerFactory, QObject *parent)
|
|
: httq::Logger(loggerFactory, parent)
|
|
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(QString::fromUtf8(parent->metaObject()->className()).replace(QStringLiteral("::"), QStringLiteral(".")).toStdString())) // man man
|
|
{}
|
|
|
|
|
|
void Logger::debug(const QString &message)
|
|
{
|
|
/*DEBUG_LOG*/ERROR_LOG(m_logger) << message;
|
|
}
|
|
|
|
|
|
void Logger::warning(const QString &message)
|
|
{
|
|
WARNING_LOG(m_logger) << message;
|
|
}
|
|
|
|
|
|
void Logger::error(const QString &message)
|
|
{
|
|
ERROR_LOG(m_logger) << message;
|
|
}
|
|
|
|
|
|
// ThpScriptServer
|
|
ThpScriptServer::ThpScriptServer(QObject *parent)
|
|
: AbstractServer(parent)
|
|
, mWsSvr(new QWebSocketServer({}, QWebSocketServer::NonSecureMode, this))
|
|
, m_logger(getLogger("ThpScriptServer"))
|
|
{
|
|
if (!listen(NGL::Connections::port(NGL::Connections::eConnection::FLM_THP_SERVER)))
|
|
{
|
|
ERROR_LOG(m_logger) << "cannot listen on port " << NGL::Connections::port(NGL::Connections::eConnection::FLM_THP_SERVER);
|
|
Q_ASSERT(false);
|
|
}
|
|
|
|
|
|
QDir().mkpath(scriptsLocation(serverThpDir()));
|
|
|
|
|
|
connect(this, &ThpScriptServer::signalBroadcastNewContentFile,
|
|
this, [this]()
|
|
{
|
|
emit signalBroadcastMessage(readContentFile());
|
|
}, Qt::UniqueConnection);
|
|
|
|
|
|
connect(this, &ThpScriptServer::signalBroadcastNewScriptsJsFile,
|
|
this, [this]()
|
|
{
|
|
emit signalBroadcastMessage2(readScriptsJsFile());
|
|
}, Qt::UniqueConnection);
|
|
|
|
|
|
connect(mWsSvr, &QWebSocketServer::newConnection,
|
|
this, [this]()
|
|
{
|
|
while (mWsSvr->hasPendingConnections())
|
|
{
|
|
QWebSocket *cli = mWsSvr->nextPendingConnection();
|
|
|
|
if (cli == nullptr)
|
|
return;
|
|
|
|
bool failed = false;
|
|
switch (QStringList{{L("/thp"), L("/thp2")}}.indexOf(cli->requestUrl().path()))
|
|
{
|
|
case 0: // old
|
|
{
|
|
connect(this, &ThpScriptServer::signalBroadcastMessage,
|
|
cli, [cli](const QString &msg)
|
|
{
|
|
cli->sendTextMessage(msg);
|
|
});
|
|
|
|
cli->sendTextMessage(readContentFile());
|
|
break;
|
|
}
|
|
|
|
|
|
case 1: // THP2
|
|
{
|
|
connect(this, &ThpScriptServer::signalBroadcastMessage2,
|
|
cli, [cli](const QString &msg)
|
|
{
|
|
cli->sendTextMessage(msg);
|
|
});
|
|
|
|
cli->sendTextMessage(readScriptsJsFile());
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
ERROR_LOG(m_logger) << "ws unknown path: " << cli->requestUrl().path();
|
|
failed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (failed)
|
|
{
|
|
cli->deleteLater();
|
|
continue;
|
|
}
|
|
|
|
|
|
connect(cli, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
|
|
this, [cli]()
|
|
{
|
|
cli->deleteLater();
|
|
//qWarning() << "ws error!!!";
|
|
});
|
|
|
|
connect(cli, &QWebSocket::disconnected,
|
|
this, [cli]()
|
|
{
|
|
cli->deleteLater();
|
|
//qWarning() << "ws disco!!!";
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
const QString ThpScriptServer::readScriptsJsFile() const
|
|
{
|
|
return QString::fromUtf8(QJsonDocument(ScriptsJsParser(serverThpDir(), false).get()).toJson());
|
|
}
|
|
|
|
|
|
const QString ThpScriptServer::readContentFile() const
|
|
{
|
|
QFile *f = new QFile(thpDirectory() + QStringLiteral("content.json"));
|
|
f->deleteLater();
|
|
if (!f->open(QIODevice::ReadOnly))
|
|
{
|
|
WARNING_LOG(m_logger) << "Error opening " << f->fileName() << " for reading: " << f->errorString();
|
|
return QStringLiteral("[]");
|
|
}
|
|
|
|
return QString::fromUtf8(f->readAll());
|
|
}
|
|
|
|
|
|
class DataStreamError : public std::runtime_error
|
|
{
|
|
public:
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
|
|
|
|
bool ThpScriptServer::newHttpConnection(httq::HttpRequest *request)
|
|
{
|
|
try
|
|
{
|
|
if (handleRequestSync(*request))
|
|
{
|
|
request->deleteLater();
|
|
}
|
|
else if (!handleRequest(request))
|
|
{
|
|
throw(L("unroutable"));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch(const DataStreamError &err)
|
|
{
|
|
DEBUG_LOG(m_logger) << "handler threw DataStreamError: " << err.what();
|
|
// do not delete request as a failed datastream deletes it itself
|
|
return false;
|
|
}
|
|
catch(const std::runtime_error &err)
|
|
{
|
|
errorHandler(request, HttpException(QString::fromUtf8(err.what()), 400));
|
|
return false;
|
|
}
|
|
catch(const QString &error)
|
|
{
|
|
errorHandler(request, HttpException(error, 400));
|
|
return false;
|
|
}
|
|
catch(const HttpException &he)
|
|
{
|
|
errorHandler(request, he);
|
|
return false;
|
|
}
|
|
catch(...)
|
|
{
|
|
errorHandler(request, HttpException(L("<unknown exception!>"), 400));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
QString ThpScriptServer::thpDirectory()
|
|
{
|
|
// TODO (what exactly?)
|
|
return QDir::tempPath() + QStringLiteral("/THP/");
|
|
}
|
|
|
|
|
|
QString ThpScriptServer::thpDirectory(int exerciseId)
|
|
{
|
|
return thpDirectory() + QStringLiteral("%1/").arg(exerciseId);
|
|
}
|
|
|
|
|
|
QString ThpScriptServer::thpFileName(int exerciseId, int scriptId)
|
|
{
|
|
return thpDirectory(exerciseId) + QString::number(scriptId) + QStringLiteral(".tar.bz2");
|
|
}
|
|
|
|
|
|
|
|
QJsonObject ThpScriptServer::toJson(const ThpServerInformation &tsi)
|
|
{
|
|
return QJsonObject({
|
|
{QStringLiteral("exerciseId"), tsi.exerciseId},
|
|
{QStringLiteral("exerciseName"), tsi.exerciseName},
|
|
{QStringLiteral("scriptId"), tsi.scriptId},
|
|
{QStringLiteral("uploadTime"), tsi.uploadTime},
|
|
{QStringLiteral("fileSize"), tsi.fileSize},
|
|
{QStringLiteral("name"), tsi.description}
|
|
//{QStringLiteral("md5sumHex"), tsi.md5sumHex}
|
|
});
|
|
}
|
|
|
|
|
|
ThpServerInformation ThpScriptServer::fromJson(const QJsonValue &val)
|
|
{
|
|
const QJsonObject obj = val.toObject();
|
|
|
|
ThpServerInformation tsi;
|
|
tsi.exerciseId = obj[QStringLiteral("exerciseId")].toInt();
|
|
tsi.exerciseName = obj[QStringLiteral("exerciseName")].toString();
|
|
tsi.scriptId = obj[QStringLiteral("scriptId")].toInt();
|
|
tsi.uploadTime = qRound64(obj[QStringLiteral("uploadTime")].toDouble());
|
|
tsi.fileSize = qRound64(obj[QStringLiteral("fileSize")].toDouble());
|
|
tsi.description = obj[QStringLiteral("name")].toString();
|
|
//tsi.md5sumHex = obj[QStringLiteral("md5sumHex")].toString();
|
|
|
|
return tsi;
|
|
}
|
|
|
|
|
|
void ThpScriptServer::errorHandler(httq::HttpRequest *request, const HttpException &e) const
|
|
{
|
|
DEBUG_LOG(m_logger) << "handler threw error: " << e.error << " (http status = " << e.status << ")";
|
|
|
|
QJsonObject root;
|
|
|
|
root[QStringLiteral("error")] = e.error;
|
|
#ifdef LTS_DEBUG
|
|
root[QStringLiteral("DEBUG")] = request->url().path();
|
|
#endif
|
|
|
|
request->write(e.status, root);
|
|
request->deleteLater();
|
|
}
|
|
|
|
|
|
void ThpScriptServer::changeList(const ThpServerInformation &tsi, httq::HttpRequest *request, bool remove)
|
|
{
|
|
try
|
|
{
|
|
QFile contentFile(thpDirectory() + QStringLiteral("content.json"), this);
|
|
if (!contentFile.open(QIODevice::ReadWrite))
|
|
{
|
|
ERROR_LOG(m_logger) << "Error opening file '" << contentFile.fileName() << "' for read/write: " << contentFile.errorString();
|
|
throw(HttpException(QStringLiteral("cannot open content file '%1': %2").arg(contentFile.fileName(), contentFile.errorString())));
|
|
}
|
|
|
|
if (contentFile.size() == 0) // file was just created by the open command - fill it with an valid, but empty json array
|
|
{
|
|
contentFile.write(QStringLiteral("[]").toUtf8());
|
|
contentFile.seek(0);
|
|
}
|
|
|
|
QJsonParseError err;
|
|
QJsonDocument doc(QJsonDocument::fromJson(contentFile.readAll(), &err));
|
|
|
|
if (err.error != QJsonParseError::NoError)
|
|
{
|
|
ERROR_LOG(m_logger) << "content file's json invalid at" << err.offset << ":" << err.errorString();
|
|
throw(HttpException(QStringLiteral("content file's json invalid at %1: %2").arg(err.offset).arg(err.errorString())));
|
|
}
|
|
|
|
QJsonArray arr(doc.array());
|
|
QVector<ThpServerInformation> tsiArray;
|
|
tsiArray.reserve(arr.size() + 1);
|
|
|
|
for (const QJsonValue &val : qAsConst(arr))
|
|
{
|
|
ThpServerInformation tsix(fromJson(val));
|
|
if (tsix.scriptId == tsi.scriptId && tsix.exerciseId == tsi.exerciseId)
|
|
continue;
|
|
tsiArray += std::move(tsix);
|
|
}
|
|
|
|
if (!remove)
|
|
tsiArray += tsi;
|
|
|
|
arr = {};
|
|
std::transform(tsiArray.constBegin(), tsiArray.constEnd(), std::back_inserter(arr),
|
|
[](const ThpServerInformation &tsix)
|
|
{
|
|
return toJson(tsix);
|
|
});
|
|
|
|
if (!contentFile.resize(0) || !contentFile.write(QJsonDocument(arr).toJson()))
|
|
{
|
|
ERROR_LOG(m_logger) << "Error writing content.json: " << contentFile.errorString();
|
|
throw(HttpException(QStringLiteral("error writing content file: %1").arg(contentFile.errorString())));
|
|
}
|
|
|
|
request->write(200, noError());
|
|
contentFile.flush(); // needed as signalBroadcastNewContentFile reads from the file, which has old data in it until it's flushed
|
|
|
|
emit signalBroadcastNewContentFile(); // content file has changed, as a new script was uploaded - tell all the clients
|
|
|
|
request->deleteLater();
|
|
}
|
|
catch (const HttpException &e)
|
|
{
|
|
errorHandler(request, e);
|
|
}
|
|
}
|
|
|
|
|
|
QJsonObject ThpScriptServer::noError()
|
|
{
|
|
static QJsonObject obj({{ QStringLiteral("error"), QString() }});
|
|
return obj;
|
|
}
|
|
|
|
|
|
QString ThpScriptServer::queryGetString(const QUrlQuery &q, const QString &key)
|
|
{
|
|
if (!q.hasQueryItem(key))
|
|
throw(HttpException(QStringLiteral("%1 missing").arg(key), 400));
|
|
|
|
return q.queryItemValue(key);
|
|
}
|
|
|
|
|
|
int ThpScriptServer::queryGetInt(const QUrlQuery &q, const QString &key)
|
|
{
|
|
const QString value(queryGetString(q, key));
|
|
|
|
bool k = false;
|
|
int num = value.toInt(&k);
|
|
|
|
if (!k)
|
|
throw(HttpException(QStringLiteral("%1 not an int").arg(key), 400));
|
|
|
|
return num;
|
|
}
|
|
|
|
|
|
bool ThpScriptServer::handleRequestSync(httq::HttpRequest &request) noexcept(false)
|
|
{
|
|
//qWarning() << "url" << request->url() << "method" << (request->method() == HTTP_GET ? "GET" : request->method() == HTTP_POST ? "POST" : "?");
|
|
|
|
const QUrl &url = request.url();
|
|
|
|
if (url.path() == L("/thp2"))
|
|
{
|
|
switch (request.method())
|
|
{
|
|
case HTTP_GET: // DOWNLOAD THP
|
|
{
|
|
const QUrlQuery &q(request.query());
|
|
const QString scriptId(queryGetString(q, L("scriptId")));
|
|
|
|
auto esi = ScriptsJsParser(serverThpDir()).getExportedScriptInformation(scriptId);
|
|
request.addHeader(L("x-thp-info"), QString::fromUtf8(QJsonDocument(esi.toJson().toObject()).toJson(QJsonDocument::JsonFormat::Compact)));
|
|
|
|
QFile f(scriptArchiveLocation(serverThpDir(), scriptId));
|
|
openFile(f, true, true);
|
|
httq::DataStream *ds = request.createDataStreamToClient(200, L("application/octet-stream"), &f, f.size());
|
|
|
|
try
|
|
{
|
|
awaitWithCancelationSignalx(ds, &httq::DataStream::signalDone, ds, &QObject::destroyed);
|
|
}
|
|
catch (const std::runtime_error &err)
|
|
{
|
|
throw DataStreamError("Writing DataStream failed! Client disconnect?");
|
|
}
|
|
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
|
|
case HTTP_POST: // UPLOAD THP
|
|
{
|
|
const QString thpInfo(request.requestHeaders().value(L("x-thp-info")));
|
|
|
|
if (thpInfo.isEmpty())
|
|
{
|
|
qWarning() << "headers" << request.requestHeaders();
|
|
throw L("x-thp-info header missing");
|
|
}
|
|
|
|
auto esi = ExportedScriptInformation::fromJson(JsonObject::fromJson(thpInfo.toUtf8()).object());
|
|
|
|
const auto destDir(scriptLocation(serverThpDir(), esi.id()));
|
|
if (!QDir().mkpath(destDir)) // NOTE: we don't check if id is evil here
|
|
{
|
|
throw(L("Cannot create directory %1 - THP upload not possible").arg(destDir));
|
|
}
|
|
|
|
const auto destFile(scriptArchiveLocation(serverThpDir(), esi.id()));
|
|
QFileInfo fi(destFile);
|
|
if (fi.exists() && !QFile::remove(destFile))
|
|
{
|
|
throw L("Cannot remove old THP upload file %1 - THP upload not possible").arg(destDir);
|
|
}
|
|
|
|
auto saveFile = SaveFile::open(destFile);
|
|
|
|
qWarning() << "######### STEP 1" << request.contentLength();
|
|
httq::DataStream *ds = request.createDataStreamFromClient(saveFile.get(), request.contentLength()); // NOTE: auto-deletes itself
|
|
qWarning() << "######### STEP 2";
|
|
|
|
try
|
|
{
|
|
qWarning() << "######### STEP 3";
|
|
awaitWithCancelationSignalx(ds, &httq::DataStream::signalDone, ds, &QObject::destroyed); // destroyed before signalDone? => error
|
|
qWarning() << "######### STEP 4";
|
|
}
|
|
catch (const std::runtime_error &err)
|
|
{
|
|
throw DataStreamError("Reading DataStream failed! Client disconnect?");
|
|
}
|
|
|
|
qWarning() << "######### STEP 5";
|
|
auto parser = ScriptsJsParser(serverThpDir(), false);
|
|
parser.addExportedScriptInformation(esi);
|
|
parser.save();
|
|
|
|
qWarning() << "######### STEP 6";
|
|
request.write(201, QJsonObject());
|
|
qWarning() << "######### STEP 7";
|
|
emit signalBroadcastNewScriptsJsFile(); // scripts.js file has changed, as a new script was uploaded - tell all the clients
|
|
return true;
|
|
}
|
|
|
|
case HTTP_DELETE:
|
|
{
|
|
const QUrlQuery &q(request.query());
|
|
const QString scriptId(queryGetString(q, L("scriptId")));
|
|
|
|
auto parser = ScriptsJsParser(serverThpDir(), true);
|
|
parser.removeExportedScriptInformation(scriptId);
|
|
parser.save();
|
|
|
|
const auto scriptDir(scriptLocation(serverThpDir(), scriptId));
|
|
if (!QDir().rmdir(scriptDir)) // NOTE: we don't check if id is evil here
|
|
{
|
|
throw L("Failed to remove script dir '%1' - THP delete not possible").arg(scriptDir);
|
|
}
|
|
|
|
const auto scriptArchive(scriptArchiveLocation(serverThpDir(), scriptId));
|
|
if (!QFile::remove(scriptArchive)) // NOTE: we don't check if id is evil here
|
|
{
|
|
throw L("Failed to remove script archive '%1' - THP delete not possible").arg(scriptArchive);
|
|
}
|
|
|
|
request.write(200, QJsonObject());
|
|
emit signalBroadcastNewScriptsJsFile(); // scripts.js file has changed, as a new script was uploaded - tell all the clients
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
{
|
|
WARNING_LOG(m_logger) << "unsupported method for /thp2";
|
|
throw HttpException(L("unsupported method for /thp2: %1").arg((int)request.method()), 405);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool ThpScriptServer::handleRequest(httq::HttpRequest *request) noexcept(false)
|
|
{
|
|
//qWarning() << "url" << request->url() << "method" << (request->method() == HTTP_GET ? "GET" : request->method() == HTTP_POST ? "POST" : "?");
|
|
|
|
const QUrl &url = request->url();
|
|
|
|
switch (request->method())
|
|
{
|
|
case HTTP_GET:
|
|
{
|
|
if (url.path() == QStringLiteral("/list"))
|
|
{
|
|
QFile *f = new QFile(thpDirectory() + QStringLiteral("content.json"));
|
|
|
|
if (!f->open(QIODevice::ReadOnly))
|
|
{
|
|
f->deleteLater();
|
|
request->write(200, noError());
|
|
request->deleteLater();
|
|
return true;
|
|
}
|
|
|
|
httq::DataStream *ds = request->createDataStreamToClient(200, QStringLiteral("application/json"), f, f->size());
|
|
|
|
connect(ds, &httq::DataStream::signalDone,
|
|
this, [request, f]()
|
|
{
|
|
f->deleteLater();
|
|
request->deleteLater();
|
|
});
|
|
|
|
return true;
|
|
}
|
|
else if (url.path() == QStringLiteral("/thp"))
|
|
{
|
|
const QUrlQuery &q(request->query());
|
|
|
|
int exerciseId(queryGetInt(q, QStringLiteral("exerciseId")));
|
|
int scriptId(queryGetInt(q, QStringLiteral("scriptId")));
|
|
|
|
QFile *f = new QFile(thpFileName(exerciseId, scriptId));
|
|
|
|
if (!f->open(QIODevice::ReadOnly))
|
|
{
|
|
f->deleteLater();
|
|
throw(HttpException(QStringLiteral("thp for exercise %1, script %2 not found").arg(exerciseId).arg(scriptId), 404));
|
|
return true;
|
|
}
|
|
|
|
|
|
httq::DataStream *ds = request->createDataStreamToClient(200, QStringLiteral("application/octet-stream"), f, f->size());
|
|
|
|
connect(ds, &httq::DataStream::signalDone,
|
|
this, [request, f]()
|
|
{
|
|
f->deleteLater();
|
|
request->deleteLater();
|
|
});
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case HTTP_DELETE: // REMOVE THP
|
|
{
|
|
if (url.path() == QStringLiteral("/thp"))
|
|
{
|
|
const QUrlQuery &q(request->query());
|
|
ThpServerInformation tsi;
|
|
|
|
tsi.exerciseId = queryGetInt(q, QStringLiteral("exerciseId"));
|
|
tsi.scriptId = queryGetInt(q, QStringLiteral("scriptId"));
|
|
|
|
const QString destFile = thpFileName(tsi.exerciseId, tsi.scriptId);
|
|
if (!QFile::remove(destFile))
|
|
{
|
|
const QString err(QStringLiteral("Cannot remove old THP upload file %1 - removing THP not possible").arg(destFile));
|
|
ERROR_LOG(m_logger) << err;
|
|
throw(HttpException(err));
|
|
}
|
|
|
|
changeList(tsi, request, true);
|
|
|
|
request->write(200, noError());
|
|
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case HTTP_POST: // UPLOAD THP
|
|
{
|
|
if (url.path() == QStringLiteral("/thp"))
|
|
{
|
|
const QUrlQuery &q(request->query());
|
|
ThpServerInformation tsi;
|
|
|
|
tsi.exerciseId = queryGetInt(q, QStringLiteral("exerciseId"));
|
|
tsi.scriptId = queryGetInt(q, QStringLiteral("scriptId"));
|
|
tsi.exerciseName = queryGetString(q, QStringLiteral("exerciseName"));
|
|
tsi.description = q.queryItemValue(QStringLiteral("name"));
|
|
tsi.fileSize = request->contentLength();
|
|
tsi.uploadTime = QDateTime::currentMSecsSinceEpoch();
|
|
|
|
// qWarning() << "tsi.exerciseName=" << tsi.exerciseName
|
|
// << " tsi.scriptId=" << tsi.scriptId
|
|
// << " tsi.description (name)=" << tsi.description
|
|
// << " tsi.fileSize=" << tsi.fileSize
|
|
// //<< " tsi.md5sumHex=" << tsi.md5sumHex
|
|
// << " tsi.uploadTime=" << tsi.uploadTime;
|
|
|
|
const QString destDir = thpDirectory(tsi.exerciseId);
|
|
if (!QDir().mkpath(destDir))
|
|
{
|
|
const QString err(QStringLiteral("Cannot create directory %1 - THP upload not possible").arg(destDir));
|
|
ERROR_LOG(m_logger) << err;
|
|
throw(HttpException(err));
|
|
}
|
|
|
|
const QString destFile = thpFileName(tsi.exerciseId, tsi.scriptId);
|
|
QFileInfo fi(destFile);
|
|
if (fi.exists() && !QFile::remove(destFile))
|
|
{
|
|
const QString err(QStringLiteral("Cannot remove old THP upload file %1 - THP upload not possible").arg(destDir));
|
|
ERROR_LOG(m_logger) << err;
|
|
throw(HttpException(err));
|
|
}
|
|
|
|
// qWarning() << "New upload request - scriptId: " << tsi.scriptId
|
|
// << " exerciseId: " << tsi.exerciseId
|
|
// << " fileSize: " << tsi.fileSize
|
|
// //<< " md5sum: " << tsi.md5sumHex
|
|
// ;
|
|
|
|
QTemporaryFile *f = new QTemporaryFile(this);
|
|
if (!f->open())
|
|
{
|
|
f->deleteLater();
|
|
throw(HttpException(QStringLiteral("Error opening temp file '%1' for writing: %2").arg(f->fileName(), f->errorString())));
|
|
}
|
|
|
|
httq::DataStream *ds = request->createDataStreamFromClient(f, request->contentLength());
|
|
|
|
connect(ds, &httq::DataStream::signalDone, // step 2 =>
|
|
this, [this, f, request, tsi]()
|
|
{
|
|
try
|
|
{
|
|
const QString destDir = thpDirectory(tsi.exerciseId);
|
|
QDir d(destDir);
|
|
if (!d.exists() && !d.mkpath(destDir))
|
|
{
|
|
const QString errstr(QStringLiteral("Can create directory '%1' - THP upload not possible").arg(destDir));
|
|
ERROR_LOG(m_logger) << errstr;
|
|
throw(HttpException(errstr));
|
|
}
|
|
|
|
const QString destFile = thpFileName(tsi.exerciseId, tsi.scriptId);
|
|
QFileInfo fi(destFile);
|
|
if (fi.exists() && !QFile::remove(destFile))
|
|
{
|
|
const QString errstr(QStringLiteral("Can not remove old file '%1'").arg(destDir));
|
|
ERROR_LOG(m_logger) << errstr;
|
|
throw(HttpException(errstr));
|
|
}
|
|
|
|
// calling fileName() makes sure setAutoRemove(false) is honored on all systems
|
|
const QString tmpFilename = f->fileName();
|
|
if (!f->rename(destFile))
|
|
{
|
|
const QString errstr(QStringLiteral("Can not rename %1 to %2: %3").arg(tmpFilename, destFile, f->errorString()));
|
|
ERROR_LOG(m_logger) << errstr;
|
|
throw(HttpException(errstr));
|
|
}
|
|
|
|
f->setAutoRemove(false);
|
|
//qWarning() << QStringLiteral("Renamed %1 to %2").arg(tmpFilename, destFile);
|
|
// now update info.json
|
|
}
|
|
catch (const HttpException &e)
|
|
{
|
|
errorHandler(request, e);
|
|
}
|
|
|
|
changeList(tsi, request, false);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
WARNING_LOG(m_logger) << "unsupported method";
|
|
throw(HttpException(QStringLiteral("unsupported method"), 405));
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
} // namespace FileManagement
|