1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-20 19:57:45 +00:00
astra/launcher/src/syncmanager.cpp
2024-07-30 21:02:22 -04:00

253 lines
8 KiB
C++

// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "syncmanager.h"
#include "astra_log.h"
#include <Quotient/accountregistry.h>
#include <Quotient/csapi/content-repo.h>
#include <Quotient/csapi/room_state.h>
#include <Quotient/events/stateevent.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/room.h>
#include <Quotient/settings.h>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QCoreApplication>
#include <QCoro>
#include <QTemporaryFile>
const QString roomType = QStringLiteral("zone.xiv.astra-sync");
const QString syncEventType = QStringLiteral("zone.xiv.astra.sync");
using namespace Quotient;
SyncManager::SyncManager(QObject *parent)
: QObject(parent)
{
m_accountRegistry.invokeLogin(); // TODO: port from invokeLogin
connect(&m_accountRegistry, &AccountRegistry::rowsInserted, this, [this]() {
connection()->setCacheState(false);
connection()->setLazyLoading(false);
connection()->setDirectChatEncryptionDefault(false);
connection()->setEncryptionDefault(false);
Q_EMIT connectedChanged();
Q_EMIT userIdChanged();
Q_EMIT connectionChanged();
sync();
});
connect(&m_accountRegistry, &AccountRegistry::rowsRemoved, this, [this]() {
Q_EMIT connectedChanged();
Q_EMIT userIdChanged();
Q_EMIT connectionChanged();
});
}
void SyncManager::login(const QString &matrixId, const QString &password)
{
auto connection = new Connection(this);
connection->resolveServer(matrixId);
connect(
connection,
&Connection::loginFlowsChanged,
this,
[connection, matrixId, password]() {
connection->loginWithPassword(matrixId, password, qAppName(), {});
},
Qt::SingleShotConnection);
connect(connection, &Connection::connected, this, [this, connection] {
qCDebug(ASTRA_LOG) << "Connected!";
// TODO: store somewhere else, not their QSettings
AccountSettings account(connection->userId());
account.setKeepLoggedIn(true);
account.setHomeserver(connection->homeserver());
account.setDeviceId(connection->deviceId());
account.setDeviceName(qAppName());
account.sync();
m_accountRegistry.add(connection);
});
connect(connection, &Connection::loginError, this, &SyncManager::loginError);
connect(connection, &Connection::resolveError, this, &SyncManager::loginError);
}
bool SyncManager::connected() const
{
return !m_accountRegistry.empty();
}
QString SyncManager::userId() const
{
return !m_accountRegistry.empty() ? m_accountRegistry.accounts().first()->userId() : QString();
}
void SyncManager::logout()
{
m_accountRegistry.accounts().first()->logout();
}
Quotient::Connection *SyncManager::connection() const
{
if (!m_accountRegistry.empty()) {
return m_accountRegistry.accounts().first();
}
return nullptr;
}
void SyncManager::sync()
{
auto connection = m_accountRegistry.accounts().first();
connection->sync();
connect(
connection,
&Connection::syncDone,
this,
[this]() {
m_accountRegistry.accounts().first()->stopSync();
qCDebug(ASTRA_LOG) << "Done with sync.";
// Find the room we need to sync with
findRoom();
},
Qt::SingleShotConnection);
}
QCoro::Task<void> SyncManager::findRoom()
{
qCDebug(ASTRA_LOG) << "Time to find the sync room!";
const QString roomId = this->roomId();
qCDebug(ASTRA_LOG) << "Stored room id:" << roomId;
// If we have no room id set, we need to find the correct room type
const bool needsFirstTimeRoom = roomId.isEmpty();
// Try to find our room
auto rooms = m_accountRegistry.accounts().first()->rooms(Quotient::JoinState::Join);
for (auto room : rooms) {
if (!room->currentState().contains<RoomCreateEvent>()) {
co_await qCoro(room, &Room::baseStateLoaded);
qCDebug(ASTRA_LOG) << "Loaded base state...";
}
if (needsFirstTimeRoom) {
const QJsonObject createEvent = room->currentState().eventsOfType(QStringLiteral("m.room.create")).first()->fullJson();
auto contentJson = createEvent[QStringLiteral("content")].toObject();
if (contentJson.contains(QStringLiteral("type"))) {
if (contentJson[QStringLiteral("type")] == roomType) {
qCDebug(ASTRA_LOG) << room << "matches!";
m_currentRoom = room;
setRoomId(room->id());
Q_EMIT isReadyChanged();
}
}
} else {
if (room->id() == roomId) {
qCDebug(ASTRA_LOG) << "Found pre-existing room!";
m_currentRoom = room;
Q_EMIT isReadyChanged();
co_return;
}
}
}
// We failed to find a room, and we need to create one
if (needsFirstTimeRoom && !m_currentRoom) {
qCDebug(ASTRA_LOG) << "Need to create room!";
auto job = connection()->createRoom(Quotient::Connection::RoomVisibility::UnpublishRoom,
QString{},
i18n("Astra Sync"),
i18n("Room used to sync Astra between devices"),
QStringList{},
QString{},
QString::number(10),
false,
{},
{},
QJsonObject{{QStringLiteral("type"), roomType}});
co_await qCoro(job, &BaseJob::finished);
setRoomId(job->roomId());
qCDebug(ASTRA_LOG) << "Created sync room at" << job->roomId();
// re-run sync to get the new room
sync();
}
co_return;
}
QString SyncManager::roomId() const
{
return KSharedConfig::openStateConfig()->group(QStringLiteral("Sync")).readEntry(QStringLiteral("RoomId"));
}
void SyncManager::setRoomId(const QString &roomId)
{
auto stateConfig = KSharedConfig::openStateConfig();
stateConfig->group(QStringLiteral("Sync")).writeEntry(QStringLiteral("RoomId"), roomId);
stateConfig->sync();
}
bool SyncManager::isReady() const
{
return connected() && m_currentRoom;
}
QCoro::Task<std::optional<SyncManager::PreviousCharacterData>> SyncManager::getUploadedCharacterData(const QString &id)
{
Q_ASSERT(m_currentRoom);
const auto syncEvent = m_currentRoom->currentState().contentJson(syncEventType, id);
if (syncEvent.isEmpty()) {
qCDebug(ASTRA_LOG) << "No previous sync for" << id;
co_return std::nullopt;
} else {
qCDebug(ASTRA_LOG) << "previous sync event:" << syncEvent;
co_return PreviousCharacterData{.mxcUri = syncEvent[QStringLiteral("content-uri")].toString(),
.hostname = syncEvent[QStringLiteral("hostname")].toString()};
}
}
QCoro::Task<bool> SyncManager::uploadedCharacterData(const QString &id, const QString &path)
{
Q_ASSERT(m_currentRoom);
auto uploadFileJob = connection()->uploadFile(path);
co_await qCoro(uploadFileJob, &BaseJob::finished);
// TODO: error handling
const QUrl contentUri = uploadFileJob->contentUri();
auto syncSetState = m_currentRoom->setState(
syncEventType,
id,
QJsonObject{{{QStringLiteral("content-uri"), contentUri.toString()}, {QStringLiteral("hostname"), QSysInfo::machineHostName()}}});
co_await qCoro(syncSetState, &BaseJob::finished);
co_return true;
}
QCoro::Task<bool> SyncManager::downloadCharacterData(const QString &mxcUri, const QString &destPath)
{
auto job = connection()->downloadFile(QUrl::fromUserInput(mxcUri), destPath);
co_await qCoro(job, &BaseJob::finished);
// TODO: error handling
co_return true;
}
#include "moc_syncmanager.cpp"