aboutsummaryrefslogtreecommitdiff
path: root/lib/room.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/room.cpp')
-rw-r--r--lib/room.cpp187
1 files changed, 163 insertions, 24 deletions
diff --git a/lib/room.cpp b/lib/room.cpp
index 156b5b1f..8f50607f 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -53,6 +53,7 @@
#include <QtCore/QPointer>
#include <QtCore/QDir>
#include <QtCore/QTemporaryFile>
+#include <QtCore/QMimeDatabase>
#include <array>
#include <functional>
@@ -67,9 +68,11 @@ using std::llround;
enum EventsPlacement : int { Older = -1, Newer = 1 };
-// A workaround for MSVC 2015 that fails with "error C2440: 'return':
-// cannot convert from 'initializer list' to 'QMatrixClient::FileTransferInfo'"
-#if (defined(_MSC_VER) && _MSC_VER < 1910) || (defined(__GNUC__) && __GNUC__ <= 4)
+// A workaround for MSVC 2015 and older GCC's that don't handle initializer
+// lists right (MSVC 2015, notably, fails with "error C2440: 'return':
+// cannot convert from 'initializer list' to 'QMatrixClient::FileTransferInfo'")
+#if (defined(_MSC_VER) && _MSC_VER < 1910) || \
+ (defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 4)
# define WORKAROUND_EXTENDED_INITIALIZER_LIST
#endif
@@ -126,15 +129,17 @@ class Room::Private
struct FileTransferPrivateInfo
{
-#ifdef WORKAROUND_EXTENDED_INITIALIZER_LIST
FileTransferPrivateInfo() = default;
- FileTransferPrivateInfo(BaseJob* j, QString fileName)
- : job(j), localFileInfo(fileName)
+ FileTransferPrivateInfo(BaseJob* j, const QString& fileName,
+ bool isUploading = false)
+ : status(FileTransferInfo::Started), job(j)
+ , localFileInfo(fileName), isUpload(isUploading)
{ }
-#endif
+
+ FileTransferInfo::Status status = FileTransferInfo::None;
QPointer<BaseJob> job = nullptr;
QFileInfo localFileInfo { };
- FileTransferInfo::Status status = FileTransferInfo::Started;
+ bool isUpload = false;
qint64 progress = 0;
qint64 total = -1;
@@ -234,6 +239,8 @@ class Room::Private
return sendEvent(makeEvent<EventT>(std::forward<ArgTs>(eventArgs)...));
}
+ RoomEvent* addAsPending(RoomEventPtr&& event);
+
QString doSendEvent(const RoomEvent* pEvent);
PendingEvents::iterator findAsPending(const RoomEvent* rawEvtPtr);
void onEventSendingFailure(const RoomEvent* pEvent,
@@ -592,6 +599,19 @@ Room::rev_iter_t Room::findInTimeline(const QString& evtId) const
return timelineEdge();
}
+Room::PendingEvents::iterator Room::findPendingEvent(const QString& txnId)
+{
+ return std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(),
+ [txnId] (const auto& item) { return item->transactionId() == txnId; });
+}
+
+Room::PendingEvents::const_iterator
+Room::findPendingEvent(const QString& txnId) const
+{
+ return std::find_if(d->unsyncedEvents.cbegin(), d->unsyncedEvents.cend(),
+ [txnId] (const auto& item) { return item->transactionId() == txnId; });
+}
+
void Room::Private::getAllMembers()
{
// If already loaded or already loading, there's nothing to do here.
@@ -909,7 +929,7 @@ QString Room::Private::fileNameToDownload(const RoomMessageEvent* event) const
return fileName;
}
-QUrl Room::urlToThumbnail(const QString& eventId)
+QUrl Room::urlToThumbnail(const QString& eventId) const
{
if (auto* event = d->getEventWithFile(eventId))
if (event->hasThumbnail())
@@ -923,7 +943,7 @@ QUrl Room::urlToThumbnail(const QString& eventId)
return {};
}
-QUrl Room::urlToDownload(const QString& eventId)
+QUrl Room::urlToDownload(const QString& eventId) const
{
if (auto* event = d->getEventWithFile(eventId))
{
@@ -935,7 +955,7 @@ QUrl Room::urlToDownload(const QString& eventId)
return {};
}
-QString Room::fileNameToDownload(const QString& eventId)
+QString Room::fileNameToDownload(const QString& eventId) const
{
if (auto* event = d->getEventWithFile(eventId))
return d->fileNameToDownload(event);
@@ -969,13 +989,28 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const
fti.localPath = QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath());
return fti;
#else
- return { infoIt->status, int(progress), int(total),
+ return { infoIt->status, infoIt->isUpload, int(progress), int(total),
QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()),
QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath())
};
#endif
}
+QUrl Room::fileSource(const QString& id) const
+{
+ auto url = urlToDownload(id);
+ if (url.isValid())
+ return url;
+
+ // No urlToDownload means it's a pending or completed upload.
+ auto infoIt = d->fileTransfers.find(id);
+ if (infoIt != d->fileTransfers.end())
+ return QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath());
+
+ qCWarning(MAIN) << "File source for identifier" << id << "not found";
+ return {};
+}
+
QString Room::prettyPrint(const QString& plainText) const
{
return QMatrixClient::prettyPrint(plainText);
@@ -1255,7 +1290,7 @@ void Room::updateData(SyncRoomData&& data, bool fromCache)
}
}
-QString Room::Private::sendEvent(RoomEventPtr&& event)
+RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event)
{
if (event->transactionId().isEmpty())
event->setTransactionId(connection->generateTxnId());
@@ -1263,7 +1298,12 @@ QString Room::Private::sendEvent(RoomEventPtr&& event)
emit q->pendingEventAboutToAdd(pEvent);
unsyncedEvents.emplace_back(move(event));
emit q->pendingEventAdded();
- return doSendEvent(pEvent);
+ return pEvent;
+}
+
+QString Room::Private::sendEvent(RoomEventPtr&& event)
+{
+ return doSendEvent(addAsPending(std::move(event)));
}
QString Room::Private::doSendEvent(const RoomEvent* pEvent)
@@ -1336,10 +1376,35 @@ void Room::Private::onEventSendingFailure(const RoomEvent* pEvent,
QString Room::retryMessage(const QString& txnId)
{
- auto it = std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(),
- [txnId] (const auto& evt) { return evt->transactionId() == txnId; });
+ const auto it = findPendingEvent(txnId);
Q_ASSERT(it != d->unsyncedEvents.end());
qDebug(EVENTS) << "Retrying transaction" << txnId;
+ const auto& transferIt = d->fileTransfers.find(txnId);
+ if (transferIt != d->fileTransfers.end())
+ {
+ Q_ASSERT(transferIt->isUpload);
+ if (transferIt->status == FileTransferInfo::Completed)
+ {
+ qCDebug(MAIN) << "File for transaction" << txnId
+ << "has already been uploaded, bypassing re-upload";
+ } else {
+ if (isJobRunning(transferIt->job))
+ {
+ qCDebug(MAIN) << "Abandoning the upload job for transaction"
+ << txnId << "and starting again";
+ transferIt->job->abandon();
+ emit fileTransferFailed(txnId, tr("File upload will be retried"));
+ }
+ uploadFile(txnId,
+ QUrl::fromLocalFile(transferIt->localFileInfo.absoluteFilePath()));
+ // FIXME: Content type is no more passed here but it should
+ }
+ }
+ if (it->deliveryStatus() == EventStatus::ReachedServer)
+ {
+ qCWarning(MAIN) << "The previous attempt has reached the server; two"
+ " events are likely to be in the timeline after retry";
+ }
it->resetStatus();
return d->doSendEvent(it->event());
}
@@ -1350,7 +1415,22 @@ void Room::discardMessage(const QString& txnId)
[txnId] (const auto& evt) { return evt->transactionId() == txnId; });
Q_ASSERT(it != d->unsyncedEvents.end());
qDebug(EVENTS) << "Discarding transaction" << txnId;
- emit pendingEventAboutToDiscard(it - d->unsyncedEvents.begin());
+ const auto& transferIt = d->fileTransfers.find(txnId);
+ if (transferIt != d->fileTransfers.end())
+ {
+ Q_ASSERT(transferIt->isUpload);
+ if (isJobRunning(transferIt->job))
+ {
+ transferIt->status = FileTransferInfo::Cancelled;
+ transferIt->job->abandon();
+ emit fileTransferFailed(txnId, tr("File upload cancelled"));
+ } else if (transferIt->status == FileTransferInfo::Completed)
+ {
+ qCWarning(MAIN) << "File for transaction" << txnId
+ << "has been uploaded but the message was discarded";
+ }
+ }
+ emit pendingEventAboutToDiscard(int(it - d->unsyncedEvents.begin()));
d->unsyncedEvents.erase(it);
emit pendingEventDiscarded();
}
@@ -1377,6 +1457,61 @@ QString Room::postHtmlText(const QString& plainText, const QString& html)
return postHtmlMessage(plainText, html, MessageEventType::Text);
}
+QString Room::postFile(const QString& plainText, const QUrl& localPath,
+ bool asGenericFile)
+{
+ QFileInfo localFile { localPath.toLocalFile() };
+ Q_ASSERT(localFile.isFile());
+ // Remote URL will only be known after upload; fill in the local path
+ // to enable the preview while the event is pending.
+ auto* pEvent = d->addAsPending(
+ makeEvent<RoomMessageEvent>(plainText, localFile, asGenericFile));
+ const auto txnId = pEvent->transactionId();
+ uploadFile(txnId, localPath);
+ QMetaObject::Connection cCompleted, cCancelled;
+ cCompleted = connect(this, &Room::fileTransferCompleted, this,
+ [cCompleted,cCancelled,this,pEvent,txnId]
+ (const QString& id, QUrl, const QUrl& mxcUri) {
+ if (id == txnId)
+ {
+ auto it = d->findAsPending(pEvent);
+ if (it != d->unsyncedEvents.end())
+ {
+ it->setFileUploaded(mxcUri);
+ emit pendingEventChanged(
+ int(it - d->unsyncedEvents.begin()));
+ d->doSendEvent(pEvent);
+ } else {
+ // Normally in this situation we should instruct
+ // the media server to delete the file; alas, there's no
+ // API specced for that.
+ qCWarning(MAIN) << "File uploaded to" << mxcUri
+ << "but the event referring to it was cancelled";
+ }
+ disconnect(cCompleted);
+ disconnect(cCancelled);
+ }
+ });
+ cCancelled = connect(this, &Room::fileTransferCancelled, this,
+ [cCompleted,cCancelled,this,pEvent,txnId] (const QString& id) {
+ if (id == txnId)
+ {
+ auto it = d->findAsPending(pEvent);
+ if (it != d->unsyncedEvents.end())
+ {
+ emit pendingEventAboutToDiscard(
+ int(it - d->unsyncedEvents.begin()));
+ d->unsyncedEvents.erase(it);
+ emit pendingEventDiscarded();
+ }
+ disconnect(cCompleted);
+ disconnect(cCancelled);
+ }
+ });
+
+ return txnId;
+}
+
QString Room::postEvent(RoomEvent* event)
{
if (usesEncryption())
@@ -1532,7 +1667,7 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename,
auto job = connection()->uploadFile(fileName, overrideContentType);
if (isJobRunning(job))
{
- d->fileTransfers.insert(id, { job, fileName });
+ d->fileTransfers.insert(id, { job, fileName, true });
connect(job, &BaseJob::uploadProgress, this,
[this,id] (qint64 sent, qint64 total) {
d->fileTransfers[id].update(sent, total);
@@ -1555,8 +1690,8 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename)
if (ongoingTransfer != d->fileTransfers.end() &&
ongoingTransfer->status == FileTransferInfo::Started)
{
- qCWarning(MAIN) << "Download for" << eventId
- << "already started; to restart, cancel it first";
+ qCWarning(MAIN) << "Transfer for" << eventId
+ << "is ongoing; download won't start";
return;
}
@@ -1819,11 +1954,15 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events)
break;
it = nextPending + 1;
- emit q->pendingEventAboutToMerge(nextPending->get(),
- nextPendingPair.second - unsyncedEvents.begin());
+ auto* nextPendingEvt = nextPending->get();
+ emit q->pendingEventAboutToMerge(nextPendingEvt,
+ int(nextPendingPair.second - unsyncedEvents.begin()));
qDebug(EVENTS) << "Merging pending event from transaction"
- << (*nextPending)->transactionId() << "into"
- << (*nextPending)->id();
+ << nextPendingEvt->transactionId() << "into"
+ << nextPendingEvt->id();
+ auto transfer = fileTransfers.take(nextPendingEvt->transactionId());
+ if (transfer.status != FileTransferInfo::None)
+ fileTransfers.insert(nextPendingEvt->id(), transfer);
unsyncedEvents.erase(nextPendingPair.second);
if (auto insertedSize = moveEventsToTimeline({nextPending, it}, Newer))
{