Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85832335
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
18 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/contents/ui/main.qml b/src/contents/ui/main.qml
index c136a22..143f2be 100644
--- a/src/contents/ui/main.qml
+++ b/src/contents/ui/main.qml
@@ -1,262 +1,266 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.1
import org.kde.kirigami 2.4 as Kirigami
import QtQuick.Controls 2.0 as Controls
import moe.kazv.mxc.kazv 0.0 as MK
import '.' as Kazv
import 'shortcuts' as KazvShortcuts
import 'l10n.js' as L10n
Kirigami.ApplicationWindow {
id: root
property var kazvIOManager: MK.KazvIOManager {
cacheDirectory: kazvConfig.cacheDirectory
}
property var matrixSdk: MK.MatrixSdk {
onLoginSuccessful: {
switchToMainPage();
recordLastSession();
}
onLoginFailed: {
console.log("Login Failed");
showPassiveNotification(l10n.get('login-page-request-failed-prompt', { errorCode, errorMsg }));
}
onDiscoverFailed: {
console.log("Discover Failed");
showPassiveNotification(l10n.get('login-page-discover-failed-prompt', { errorCode, errorMsg }));
}
onSessionChanged: {
console.log('session changed');
reloadSdkVariables();
}
}
property var loggedIn: !!matrixSdk.token
property var sdkVars: QtObject {
property var roomList: matrixSdk.roomList()
property string currentRoomId: ''
}
property var l10nProvider: MK.L10nProvider {
}
property var kazvConfig: MK.KazvConfig {
}
function initializeL10n () {
const availableLocales = l10nProvider.availableLocaleCodes();
console.log('available locales:', availableLocales);
const desiredLanguages = [Qt.uiLanguage];
console.log('ui language is: ', desiredLanguages);
const defaultLocale = 'en';
const wantedLocales = L10n.negotiateLanguages(desiredLanguages, availableLocales, defaultLocale);
console.log('wanted locales:', wantedLocales);
const bundles = L10n.generateBundles(wantedLocales, l10nProvider.getFtlData(wantedLocales));
const provider = new L10n.FluentProvider(bundles);
return provider;
}
property var l10n: initializeL10n();
title: l10n.get('app-title')
globalDrawer: Kirigami.GlobalDrawer {
title: l10n.get('global-drawer-title')
titleIcon: "applications-graphics"
property var actionGroupName: 'global-drawer-actions'
actions: [
Kirigami.Action {
objectName: 'switch-account'
text: l10n.get('global-drawer-action-switch-account')
iconName: 'system-switch-user'
onTriggered: switchAccount()
},
Kirigami.Action {
objectName: 'hard-logout'
text: l10n.get('global-drawer-action-hard-logout')
onTriggered: hardLogout()
},
Kirigami.Action {
objectName: 'save'
text: l10n.get('global-drawer-action-save-session')
onTriggered: {
recordLastSession()
matrixSdk.serializeToFile()
}
},
Kirigami.Action {
objectName: 'configure-shortcuts'
text: l10n.get('global-drawer-action-configure-shortcuts')
onTriggered: {
pushActionSettingsPage();
}
},
Kirigami.Action {
objectName: 'settings'
text: l10n.get('global-drawer-action-settings')
onTriggered: {
pushSettingsPage();
}
},
Kirigami.Action {
objectName: 'create-room'
enabled: root.loggedIn
iconName: 'contact-new'
text: l10n.get('global-drawer-action-create-room')
onTriggered: {
pushCreateRoomPage();
}
},
Kirigami.Action {
objectName: 'join-room'
enabled: root.loggedIn
text: l10n.get('global-drawer-action-join-room')
onTriggered: {
pushJoinRoomPage();
}
}
]
}
contextDrawer: Kirigami.ContextDrawer {
id: contextDrawer
}
property var actionCollection: KazvShortcuts.ActionCollection {
id: globalActionCollection
children: [
root.globalDrawer,
root.contextDrawer,
]
shortcutsConfig: kazvConfig
}
property var notifier: Kazv.Notifier {
}
pageStack.initialPage: Qt.resolvedUrl("LoginPage.qml")
property var mainPage: Kazv.MainPage {
id: mainPage
}
property var emptyPage: Kirigami.Page {
title: l10n.get('empty-room-page-title')
Controls.Label {
text: l10n.get('empty-room-page-description')
}
}
function activateRoomPage() {
pageStack.push(Qt.resolvedUrl("RoomPage.qml"), {
roomId: root.sdkVars.currentRoomId,
});
}
function activateRoomSettingsPage(room) {
pageStack.push(Qt.resolvedUrl("room-settings/RoomSettingsPage.qml"), {
room
});
}
function switchToMainPage() {
pageStack.replace([mainPage])
pageStack.currentIndex = 0
}
function switchToLoginPage() {
pageStack.replace([Qt.resolvedUrl("LoginPage.qml")])
pageStack.currentIndex = 0
}
function pushLoginPage() {
pageStack.push(Qt.resolvedUrl("LoginPage.qml"), {
isSwitchingAccount: true,
});
}
function pushActionSettingsPage() {
pageStack.push(Qt.resolvedUrl("ActionSettingsPage.qml"), {
actions: globalActionCollection.allActions(),
});
}
function pushSettingsPage() {
pageStack.push(Qt.resolvedUrl("SettingsPage.qml"));
}
function pushCreateRoomPage() {
pageStack.push(Qt.resolvedUrl("CreateRoomPage.qml"), {});
}
function pushJoinRoomPage() {
pageStack.push(Qt.resolvedUrl("JoinRoomPage.qml"), {});
}
function sessionNameFor(userId, deviceId) {
return userId + '/' + deviceId;
}
function loadSession(sessionName) {
const succ = matrixSdk.loadSession(sessionName);
if (succ) {
console.log('load session successful');
switchToMainPage();
recordLastSession();
} else {
console.log('load session failed');
}
return succ;
}
function loadLastSession() {
console.log('last session is:', kazvConfig.lastSession);
return loadSession(kazvConfig.lastSession);
}
function recordLastSession() {
kazvConfig.lastSession = sessionNameFor(matrixSdk.userId, matrixSdk.deviceId);
}
function reloadSdkVariables() {
sdkVars.roomList = matrixSdk.roomList();
sdkVars.currentRoomId = '';
}
Connections {
target: sdkVars
function onCurrentRoomIdChanged() {
if (sdkVars.currentRoomId.length) {
activateRoomPage()
}
}
}
function switchAccount() {
pushLoginPage();
}
function hardLogout() {
}
Component.onCompleted: {
actionCollection.setupShortcuts();
loadLastSession();
}
+
+ onClosing: {
+ kazvIOManager.clearJobs()
+ }
}
diff --git a/src/kazv-io-manager.cpp b/src/kazv-io-manager.cpp
index f1c99ad..b498bb6 100644
--- a/src/kazv-io-manager.cpp
+++ b/src/kazv-io-manager.cpp
@@ -1,176 +1,189 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2022-2023 nannanko <nannanko@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "kazv-io-manager.hpp"
#include "kazv-io-job.hpp"
#include "matrix-room.hpp"
#include "upload-job-model.hpp"
#include "qt-rand-adapter.hpp"
#include <api/csapi/content-repo.hpp>
#include <file-desc.hpp>
#include <QObject>
#include <QIODevice>
#include <QFile>
#include <QSaveFile>
#include <QPointer>
#include <QSharedPointer>
#include <QUrl>
#include <QMap>
#include <QMultiMap>
#include <QTemporaryDir>
#include <string>
+#include <algorithm>
static const QString ROOMLESS = QStringLiteral("not-a-room");
struct KazvIOManagerPrivate
{
/**
* Used to store various temporary files.
* If the user does not specify a cache directory,
* this directory is also used as a cache directory.
*/
QTemporaryDir tmpDir = QTemporaryDir{};
/**
* Stores files that need to be previewed (e.g. in a timeline)
* and are not downloaded by the user, such as images, audio, video covers, and videos.
*/
QDir cacheDir{tmpDir.path()};
Kazv::RandomInterface randomGenerator = QtRandAdapter{};
QMap<QString, QSharedPointer<KazvIODownloadJob>> downloadJobs; // 用了 parent 不能用 QsharedPointer
QMap<QString, QSharedPointer<UploadJobModel>> uploadJobs;
};
KazvIOManager::KazvIOManager(QObject *parent)
: QObject(parent)
, m_d(new KazvIOManagerPrivate)
{
}
KazvIOManager::~KazvIOManager() = default;
QUrl KazvIOManager::cacheFile(const QUrl &fileUrl, const QString &id, const QString &hash,
const QString &key, const QString &iv)
{
const QString filePath = m_d->cacheDir.filePath(id);
QDir dir(m_d->cacheDir.path());
if (!dir.exists(id) && this->getDownloadJob(id) == nullptr) {
auto job = this->startNewDownloadJob(fileUrl, QUrl::fromLocalFile(filePath), id, hash, key, iv);
connect(job, &KazvIOBaseJob::result, this,
[this, id](auto ec) {
if (ec == KazvIOBaseJob::NoError) {
m_d->downloadJobs.remove(id);
}
});
}
return QUrl::fromLocalFile(filePath);
}
KazvIOBaseJob *KazvIOManager::startNewDownloadJob(const QUrl &serverUrl, const QUrl &localFileUrl,
const QString &jobId, const QString &hash, const QString &key, const QString &iv)
{
auto downloadJob = QSharedPointer<KazvIODownloadJob>{};
if (key == "") {
downloadJob.reset(new KazvIODownloadJob{localFileUrl.toLocalFile(), serverUrl, hash, std::nullopt});
} else {
auto aes = Kazv::AES256CTRDesc{key.toStdString(), iv.toStdString()};
downloadJob.reset(new KazvIODownloadJob{localFileUrl.toLocalFile(), serverUrl, hash, aes});
}
m_d->downloadJobs[jobId] = downloadJob;
return downloadJob.data();
}
KazvIOBaseJob *KazvIOManager::startNewUploadJob(const QUrl &serverUrl, const QUrl &localFileUrl,
const QString &token, const QString &roomId, MatrixRoomList *roomList, const bool encrypted)
{
auto uploadJob = QPointer<KazvIOUploadJob>();
if (encrypted) {
auto aes = Kazv::AES256CTRDesc::fromRandom(
m_d->randomGenerator.generateRange<Kazv::RandomData>(Kazv::AES256CTRDesc::randomSize));
uploadJob = new KazvIOUploadJob(localFileUrl.toLocalFile(), serverUrl,
roomList, roomId, token, aes);
} else {
uploadJob = new KazvIOUploadJob(localFileUrl.toLocalFile(), serverUrl,
roomList, roomId, token, std::nullopt);
}
if (!m_d->uploadJobs.contains(roomId) || m_d->uploadJobs[roomId].isNull()) {
m_d->uploadJobs[roomId].reset(new UploadJobModel());
}
m_d->uploadJobs[roomId]->addJob(uploadJob);
return uploadJob.data();
}
KazvIOBaseJob *KazvIOManager::startNewRoomlessUploadJob(const QUrl &serverUrl, const QUrl &localFileUrl, const QString &token)
{
return startNewUploadJob(serverUrl, localFileUrl, token, ROOMLESS, 0, false);
}
KazvIOBaseJob *KazvIOManager::getDownloadJob(const QString &jobId)
{
auto targetJob = m_d->downloadJobs.find(jobId);
if (targetJob == m_d->downloadJobs.end()) {
return nullptr;
}
QQmlEngine::setObjectOwnership(targetJob.value().data(), QQmlEngine::CppOwnership);
return targetJob.value().data();
}
void KazvIOManager::deleteDownloadJob(const QString &jobId)
{
m_d->downloadJobs.remove(jobId);
}
UploadJobModel *KazvIOManager::getUploadJobs(const QString &roomId)
{
if (!m_d->uploadJobs.contains(roomId) || m_d->uploadJobs[roomId].isNull()) {
m_d->uploadJobs[roomId].reset(new UploadJobModel());
}
QQmlEngine::setObjectOwnership(m_d->uploadJobs[roomId].data(), QQmlEngine::CppOwnership);
return m_d->uploadJobs[roomId].data();
}
void KazvIOManager::deleteUploadJob(const QString &roomId, KazvIOBaseJob *job)
{
m_d->uploadJobs[roomId]->removeJob(QPointer<KazvIOUploadJob>(qobject_cast<KazvIOUploadJob*>(job)));
}
void KazvIOManager::deleteRoomlessUploadJob(KazvIOBaseJob *job)
{
deleteUploadJob(ROOMLESS, job);
}
void KazvIOManager::deleteModelIfEmpty(const QString &roomId)
{
if (!m_d->uploadJobs.contains(roomId)) {
return;
}
if (m_d->uploadJobs[roomId]->rowCount() == 0) {
m_d->uploadJobs.remove(roomId);
}
}
+void KazvIOManager::clearJobs()
+{
+ std::for_each(m_d->downloadJobs.begin(), m_d->downloadJobs.end(), [this](auto job){
+ job->cancel();
+ });
+ std::for_each(m_d->uploadJobs.begin(), m_d->uploadJobs.end(), [this](auto room) {
+ room->clearJobs();
+ });
+ m_d->downloadJobs.clear();
+ m_d->uploadJobs.clear();
+}
+
QString KazvIOManager::cacheDirectory() const
{
return m_d->cacheDir.path();
}
void KazvIOManager::setCacheDirectory(QString cacheDirectory)
{
if (cacheDirectory == "") {
m_d->cacheDir = QDir{m_d->tmpDir.path()};
} else {
auto cacheDir = QDir{cacheDirectory};
if (!cacheDir.mkpath(cacheDirectory)) {
return;
}
m_d->cacheDir = cacheDir;
}
m_d->downloadJobs.clear();
Q_EMIT cacheDirectoryChanged();
}
diff --git a/src/kazv-io-manager.hpp b/src/kazv-io-manager.hpp
index 992bd06..499db71 100644
--- a/src/kazv-io-manager.hpp
+++ b/src/kazv-io-manager.hpp
@@ -1,62 +1,63 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2022-2023 nannanko <nannanko@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include "kazv-io-job.hpp"
#include "matrix-room.hpp"
#include "upload-job-model.hpp"
#include "matrix-room-list.hpp"
#include "matrix-sdk.hpp"
#include <QObject>
#include <QtQml>
#include <QUrl>
#include <QString>
#include <KIO/TransferJob>
#include <QPointer>
#include <memory>
struct KazvIOManagerPrivate;
class KazvIOManager : public QObject
{
Q_OBJECT
QML_ELEMENT
std::unique_ptr<KazvIOManagerPrivate> m_d;
Q_PROPERTY(QString cacheDirectory READ cacheDirectory WRITE setCacheDirectory NOTIFY cacheDirectoryChanged)
public:
explicit KazvIOManager(QObject *parent = 0);
~KazvIOManager() override;
/**
* Cache the file specified by fileUrl into a temporary folder
* Returns the local url of the cached file.
*/
Q_INVOKABLE QUrl cacheFile(const QUrl &fileUrl, const QString &id, const QString &hash = QStringLiteral(""),
const QString &key = QStringLiteral(""), const QString &iv = QStringLiteral(""));
Q_INVOKABLE KazvIOBaseJob *startNewDownloadJob(const QUrl &serverUrl, const QUrl &localFileName,
const QString &jobId, const QString &hash,
const QString &key = QStringLiteral(""), const QString &iv = QStringLiteral(""));
Q_INVOKABLE KazvIOBaseJob *startNewUploadJob(const QUrl &serverUrl, const QUrl &localFileName,
const QString &token, const QString &roomId, MatrixRoomList *roomList, const bool encrypted);
Q_INVOKABLE KazvIOBaseJob *startNewRoomlessUploadJob(const QUrl &serverUrl, const QUrl &localFileName, const QString &token);
Q_INVOKABLE KazvIOBaseJob *getDownloadJob(const QString &jobId);
Q_INVOKABLE void deleteDownloadJob(const QString &jobId);
Q_INVOKABLE UploadJobModel *getUploadJobs(const QString &roomId);
Q_INVOKABLE void deleteUploadJob(const QString &roomId, KazvIOBaseJob *job);
Q_INVOKABLE void deleteRoomlessUploadJob(KazvIOBaseJob *job);
Q_INVOKABLE void deleteModelIfEmpty(const QString &roomId);
+ Q_INVOKABLE void clearJobs();
QString cacheDirectory() const;
void setCacheDirectory(QString cacheDirectory);
Q_SIGNALS:
void cacheDirectoryChanged();
};
diff --git a/src/upload-job-model.cpp b/src/upload-job-model.cpp
index cf52cb6..a6bf2a9 100644
--- a/src/upload-job-model.cpp
+++ b/src/upload-job-model.cpp
@@ -1,62 +1,73 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2023 nannanko <nannanko@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "upload-job-model.hpp"
#include "kazv-io-job.hpp"
struct UploadJobModelPrivate {
QList<QPointer<KazvIOUploadJob>> uploadJobs;
};
UploadJobModel::UploadJobModel(QObject *parent)
: QAbstractListModel(parent)
, m_d(new UploadJobModelPrivate)
{
}
UploadJobModel::~UploadJobModel() = default;
QVariant UploadJobModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() > m_d->uploadJobs.size() || role != JobRole) {
return QVariant();
} else {
return QVariant::fromValue(m_d->uploadJobs[index.row()].data());
}
}
int UploadJobModel::rowCount(const QModelIndex & /* parent */) const
{
return m_d->uploadJobs.size();
}
QHash<int, QByteArray> UploadJobModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[JobRole] = "kazvIOJob";
return roles;
}
void UploadJobModel::addJob(QPointer<KazvIOUploadJob> job)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_d->uploadJobs.push_back(job.data());
connect(job.data(), &KazvIOBaseJob::result, this,
[this, job](KazvIOBaseJob::ErrorCode ec) {
if (!ec) {
this->removeJob(job.data());
}
});
endInsertRows();
}
void UploadJobModel::removeJob(QPointer<KazvIOUploadJob> job)
{
const int index = m_d->uploadJobs.indexOf(job);
beginRemoveRows(QModelIndex(), index, index);
m_d->uploadJobs.removeOne(job);
job->deleteLater();
endRemoveRows();
}
+
+void UploadJobModel::clearJobs()
+{
+ beginRemoveRows(QModelIndex(), 0, m_d->uploadJobs.size() - 1);
+ std::for_each(m_d->uploadJobs.begin(), m_d->uploadJobs.end(), [this](auto job) {
+ job->cancel();
+ m_d->uploadJobs.removeOne(job);
+ job->deleteLater();
+ });
+ endRemoveRows();
+}
diff --git a/src/upload-job-model.hpp b/src/upload-job-model.hpp
index 5a1b260..4d89cc5 100644
--- a/src/upload-job-model.hpp
+++ b/src/upload-job-model.hpp
@@ -1,41 +1,43 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2023 nannanko <nannanko@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include "kazv-io-job.hpp"
#include <QAbstractListModel>
#include <QtQml>
#include <QVariant>
#include <QModelIndex>
#include <QHash>
#include <QByteArray>
#include <memory>
struct UploadJobModelPrivate;
class UploadJobModel : public QAbstractListModel {
Q_OBJECT
QML_ELEMENT
std::unique_ptr<UploadJobModelPrivate> m_d;
public:
enum Roles {
JobRole = Qt::UserRole + 1,
};
UploadJobModel(QObject *parent = nullptr);
~UploadJobModel() override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
void addJob(QPointer<KazvIOUploadJob> job);
void removeJob(QPointer<KazvIOUploadJob> job);
+
+ void clearJobs();
};
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 2, 8:10 AM (13 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1803723
Default Alt Text
(18 KB)
Attached To
Mode
rK kazv
Attached
Detach File
Event Timeline