diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-01-13 15:37:09 +0900 |
---|---|---|
committer | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-01-14 10:02:45 +0900 |
commit | 93b170ceaced07a3de64708094bb68303a3d4440 (patch) | |
tree | f31f49f9bad24aaffb0852c1a37f1d76090c79a1 /room.cpp | |
parent | b9f4b655273481e64d7d7ead6a30dbf85a901063 (diff) | |
download | libquotient-93b170ceaced07a3de64708094bb68303a3d4440.tar.gz libquotient-93b170ceaced07a3de64708094bb68303a3d4440.zip |
RoomEvent-aware file up/downloads along with status tracking
Closes #121; closes #122.
Diffstat (limited to 'room.cpp')
-rw-r--r-- | room.cpp | 147 |
1 files changed, 147 insertions, 0 deletions
@@ -33,6 +33,7 @@ #include "events/redactionevent.h" #include "jobs/sendeventjob.h" #include "jobs/roommessagesjob.h" +#include "jobs/downloadfilejob.h" #include "avatar.h" #include "connection.h" #include "user.h" @@ -40,9 +41,12 @@ #include <QtCore/QHash> #include <QtCore/QStringBuilder> // for efficient string concats (operator%) #include <QtCore/QElapsedTimer> +#include <QtCore/QPointer> +#include <QtCore/QDir> #include <array> #include <functional> +#include <cmath> using namespace QMatrixClient; using namespace std::placeholders; @@ -90,6 +94,37 @@ class Room::Private QString prevBatch; RoomMessagesJob* roomMessagesJob; + struct FileTransferPrivateInfo + { + QPointer<BaseJob> job = nullptr; + QFileInfo localFileInfo { }; + FileTransferInfo::Status status = FileTransferInfo::Started; + qint64 progress = 0; + qint64 total = -1; + + void update(qint64 p, qint64 t) + { + progress = p; total = t; + if (t == 0) + { + t = -1; + if (p == 0) + p = -1; + } + } + }; + void failedTransfer(const QString& tid, const QString& errorMessage = {}) + { + qCWarning(MAIN) << "File transfer failed for id" << tid; + if (!errorMessage.isEmpty()) + qCWarning(MAIN) << "Message:" << errorMessage; + fileTransfers[tid].status = FileTransferInfo::Failed; + emit q->fileTransferFailed(tid, errorMessage); + } + // A map from event/txn ids to information about the long operation; + // used for both download and upload operations + QHash<QString, FileTransferPrivateInfo> fileTransfers; + // Convenience methods to work with the membersMap and usersLeft. // addMember() and removeMember() emit respective Room:: signals // after a succesful operation. @@ -413,6 +448,30 @@ void Room::resetHighlightCount() emit highlightCountChanged(this); } +FileTransferInfo Room::fileTransferInfo(const QString& id) const +{ + auto infoIt = d->fileTransfers.find(id); + if (infoIt == d->fileTransfers.end()) + return {}; + + // FIXME: Add lib tests to make sure FileTransferInfo::status stays + // consistent with FileTransferInfo::job + + qint64 progress = infoIt->progress; + qint64 total = infoIt->total; + if (total > INT_MAX) + { + // JavaScript doesn't deal with 64-bit integers; scale down if necessary + progress = std::llround(double(progress) / total * INT_MAX); + total = INT_MAX; + } + + return { infoIt->status, int(progress), int(total), + QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()), + QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()) + }; +} + QList< User* > Room::usersTyping() const { return d->usersTyping; @@ -713,6 +772,94 @@ void Room::redactEvent(const QString& eventId, const QString& reason) id(), eventId, connection()->generateTxnId(), reason); } +void Room::uploadFile(const QString& id, const QUrl& localFilename, + const QString& overrideContentType) +{ + Q_ASSERT_X(localFilename.isLocalFile(), __FUNCTION__, + "localFilename should point at a local file"); + auto fileName = localFilename.toLocalFile(); + auto job = connection()->uploadFile(fileName, overrideContentType); + if (isJobRunning(job)) + { + d->fileTransfers.insert(id, { job, fileName }); + connect(job, &BaseJob::uploadProgress, this, + [this,id] (qint64 sent, qint64 total) { + d->fileTransfers[id].update(sent, total); + emit fileTransferProgress(id, sent, total); + }); + connect(job, &BaseJob::success, this, [this,id,localFilename,job] { + d->fileTransfers[id].status = FileTransferInfo::Completed; + emit fileTransferCompleted(id, localFilename, job->contentUri()); + }); + connect(job, &BaseJob::failure, this, + std::bind(&Private::failedTransfer, d, id, job->errorString())); + emit newFileTransfer(id, localFilename); + } else + d->failedTransfer(id); +} + +void Room::downloadFile(const QString& eventId, const QUrl& localFilename) +{ + Q_ASSERT_X(localFilename.isEmpty() || localFilename.isLocalFile(), + __FUNCTION__, "localFilename should point at a local file"); + auto evtIt = findInTimeline(eventId); + if (evtIt == timelineEdge() || + evtIt->event()->type() != EventType::RoomMessage) + { + qCritical() << "Cannot download a file from event" << eventId + << "(there's no such message event in the local timeline)"; + Q_ASSERT(false); + return; + } + auto* event = static_cast<const RoomMessageEvent*>(evtIt->event()); + if (!event->hasFileContent()) + { + qCritical() << eventId << "has no file content; nothing to download"; + Q_ASSERT(false); + return; + } + auto* fileInfo = event->content()->fileInfo(); + auto fileName = !localFilename.isEmpty() ? localFilename.toLocalFile() : + !fileInfo->originalName.isEmpty() ? + (QDir::tempPath() + '/' + fileInfo->originalName) : + !event->plainBody().isEmpty() ? + (QDir::tempPath() + '/' + event->plainBody()) : QString(); + auto job = connection()->downloadFile(fileInfo->url, fileName); + if (isJobRunning(job)) + { + d->fileTransfers.insert(eventId, { job, job->targetFileName() }); + connect(job, &BaseJob::downloadProgress, this, + [this,eventId] (qint64 received, qint64 total) { + d->fileTransfers[eventId].update(received, total); + emit fileTransferProgress(eventId, received, total); + }); + connect(job, &BaseJob::success, this, [this,eventId,fileInfo,job] { + d->fileTransfers[eventId].status = FileTransferInfo::Completed; + emit fileTransferCompleted(eventId, fileInfo->url, + QUrl::fromLocalFile(job->targetFileName())); + }); + connect(job, &BaseJob::failure, this, + std::bind(&Private::failedTransfer, d, + eventId, job->errorString())); + } else + d->failedTransfer(eventId); +} + +void Room::cancelFileTransfer(const QString& id) +{ + auto it = d->fileTransfers.find(id); + if (it == d->fileTransfers.end()) + { + qCWarning(MAIN) << "No information on file transfer" << id + << "in room" << d->id; + return; + } + if (isJobRunning(it->job)) + it->job->abandon(); + d->fileTransfers.remove(id); + emit fileTransferCancelled(id); +} + void Room::Private::dropDuplicateEvents(RoomEvents* events) const { if (events->empty()) |