Page MenuHomePhorge

No OneTemporary

Size
22 KB
Referenced Files
None
Subscribers
None
diff --git a/src/contents/ui/KazvIOMenu.qml b/src/contents/ui/KazvIOMenu.qml
index 10187e9..9f7a978 100644
--- a/src/contents/ui/KazvIOMenu.qml
+++ b/src/contents/ui/KazvIOMenu.qml
@@ -1,132 +1,146 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2022 nannanko <nannanko@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.13 as Kirigami
import moe.kazv.mxc.kazv 0.0 as MK
ColumnLayout {
id: kazvIOProgressBar
required property var kazvIOJob
property var jobId
property bool isUpload
visible: kazvIOJob ? true : false
onKazvIOJobChanged: {
if (kazvIOJob == null) {
return
}
if (kazvIOJob.isResulted()) {
if (kazvIOJob.error()) {
jobSlots.onFailure()
} else {
jobSlots.onSuccess()
}
return
}
if (kazvIOJob.isSuspended()) {
pauseAction.suspended = true
}
}
Connections {
id: jobSlots
target: kazvIOJob
function onSuccess() {
// Successful upload job don't need prompt
if (isUpload) {
return
}
progressBarLayout.visible = false
promptMsgLayout.visible = true
promptMsg.text = l10n.get('kazv-io-download-success-prompt')
}
function onFailure() {
progressBarLayout.visible = false
promptMsgLayout.visible = true
if (isUpload) {
promptMsg.text = l10n.get('kazv-io-upload-failure-prompt')
} else {
promptMsg.text = l10n.get('kazv-io-download-failure-prompt')
}
+ switch (kazvIOJob.error()) {
+ case MK.KazvIOBaseJob.UserCancel:
+ detailMsg.text = l10n.get('kazv-io-failure-detail-user-cancel')
+ break
+ case MK.KazvIOBaseJob.KIOError:
+ detailMsg.text = l10n.get('kazv-io-failure-detail-network-error')
+ break
+ case MK.KazvIOBaseJob.QtError:
+ detailMsg.text = l10n.get('kazv-io-failure-detail-file-error')
+ break
+ }
}
}
Label {
property string promptPrefix: isUpload ? l10n.get('kazv-io-uploading-prefix') : l10n.get('kazv-io-downloading-prefix')
property string fileName: kazvIOJob.fileName()
text: kazvIOJob ? promptPrefix + ' ' + fileName : ''
}
RowLayout {
id: progressBarLayout
visible: true
Layout.preferredWidth: parent.width
ProgressBar {
id: progressBar
Layout.fillWidth: true
value: kazvIOJob ? kazvIOJob.progress : 0
}
Kirigami.Action {
id: pauseAction
iconName: suspended ? "media-playback-start" : "media-playback-pause"
property var suspended: false
onTriggered: {
if (pauseAction.suspended) {
kazvIOJob.resume()
} else {
kazvIOJob.suspend()
}
suspended = !suspended
}
}
Kirigami.Action {
id: cancelAction
iconName: "dialog-cancel"
onTriggered: {
pauseAction.suspended = false
kazvIOJob.cancel()
}
}
RoundButton {
id: pauseBtn
Accessible.name: pauseAction.suspended ? l10n.get('kazv-io-resume') : l10n.get('kazv-io-pause')
icon.name: pauseAction.suspended ? "media-playback-start" : "media-playback-pause"
onClicked: pauseAction.trigger()
}
RoundButton {
icon.name: "dialog-cancel"
Accessible.name: l10n.get('kazv-io-cancel')
onClicked: cancelAction.trigger()
}
}
RowLayout {
id: promptMsgLayout
visible: false
Text {
id: promptMsg
}
+ Text {
+ id: detailMsg
+ }
Button {
text: l10n.get('kazv-io-prompt-close')
onClicked: {
if (isUpload) {
kazvIOManager.deleteUploadJob(jobId, kazvIOJob)
} else {
kazvIOManager.deleteDownloadJob(jobId)
promptMsgLayout.visible = false
progressBarLayout.visible = true
kazvIOJob = null
}
}
}
}
}
diff --git a/src/kazv-io-job.cpp b/src/kazv-io-job.cpp
index b128c7e..3a1e3dd 100644
--- a/src/kazv-io-job.cpp
+++ b/src/kazv-io-job.cpp
@@ -1,234 +1,239 @@
#include "kazv-io-job.hpp"
#include "matrix-room.hpp"
#include <QObject>
#include <QPointer>
#include <QSharedPointer>
#include <QSaveFile>
#include <QFile>
#include <QByteArray>
#include <QIODevice>
#include <QString>
#include <KJob>
#include <KIO/TransferJob>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
using json = nlohmann::json;
struct KazvIOBaseJobPrivate
{
QPointer<KIO::TransferJob> job;
- std::optional<bool> result = std::nullopt;
+ std::optional<KazvIOBaseJob::ErrorCode> result = std::nullopt;
};
KazvIOBaseJob::KazvIOBaseJob(QObject *parent)
: QObject(parent)
, m_d(new KazvIOBaseJobPrivate)
{
connect(this, &KazvIOBaseJob::jobChanged, this, &KazvIOBaseJob::connectJob);
}
KazvIOBaseJob::~KazvIOBaseJob() = default;
QPointer<KIO::TransferJob> KazvIOBaseJob::job()
{
return m_d->job;
}
void KazvIOBaseJob::setJob(QPointer<KIO::TransferJob> job)
{
m_d->job = job;
Q_EMIT jobChanged();
}
float KazvIOBaseJob::progress() {
if (m_d->job.isNull()) {
return 0;
}
return static_cast<float>(m_d->job->percent()) / 100;
}
void KazvIOBaseJob::suspend()
{
m_d->job->suspend();
}
void KazvIOBaseJob::resume()
{
m_d->job->resume();
}
void KazvIOBaseJob::cancel()
{
- m_d->job->kill(KJob::EmitResult);
+ // https://api.kde.org/frameworks/kcoreaddons/html/classKJob.html#a4cf31ed005f4abf01c478fd26fba03ee
+ // KJob will not emit signal result.
+ m_d->job->kill();
+ m_d->result = UserCancel;
+ Q_EMIT result();
+ Q_EMIT failure();
}
bool KazvIOBaseJob::isSuspended()
{
return m_d->job->isSuspended();
}
bool KazvIOBaseJob::isResulted()
{
return m_d->result.has_value();
}
-bool KazvIOBaseJob::error()
+KazvIOBaseJob::ErrorCode KazvIOBaseJob::error()
{
if (m_d->result.has_value()) {
- return !m_d->result.value();
+ return m_d->result.value();
}
// Shouldn't call this function before result are emited
- return false;
+ return NoError;
}
void KazvIOBaseJob::connectJob()
{
connect(m_d->job, &KJob::result, this, [this](KJob *job) {
Q_EMIT result();
if (job->error()) {
- m_d->result = false;
+ m_d->result = KIOError;
Q_EMIT failure();
} else {
- m_d->result = true;
+ m_d->result = NoError;
Q_EMIT success();
}
});
connect(m_d->job, &KJob::percentChanged, this, &KazvIOBaseJob::emitProgressChanged);
}
void KazvIOBaseJob::emitProgressChanged(KJob * /* job */, unsigned long /* percent */) {
Q_EMIT progressChanged();
}
struct KazvIODownloadJobPrivate
{
QSharedPointer<QSaveFile> file;
};
KazvIODownloadJob::KazvIODownloadJob(QObject *parent)
: KazvIOBaseJob(parent)
, m_d(new KazvIODownloadJobPrivate)
{
}
KazvIODownloadJob::~KazvIODownloadJob() = default;
QPointer<QSaveFile> KazvIODownloadJob::file()
{
return m_d->file.data();
}
void KazvIODownloadJob::setFile(QPointer<QSaveFile> file)
{
if (!file->isOpen()) {
file->open(QIODevice::WriteOnly);
}
m_d->file.reset(file.data());
Q_EMIT fileChanged();
}
QString KazvIODownloadJob::fileName() {
return m_d->file->fileName();
}
void KazvIODownloadJob::connectJob()
{
KazvIOBaseJob::connectJob();
connect(this->job(), &KIO::TransferJob::data, this, [this](KJob * /* job */, const QByteArray &data) {
m_d->file->write(data);
});
connect(this->job(), &KJob::result, this, [this](KJob *job) {
if (job->error()) {
m_d->file->cancelWriting();
}
m_d->file->commit();
});
}
struct KazvIOUploadJobPrivate
{
QSharedPointer<QFile> file;
QString response;
QPointer<MatrixRoom> room;
QUrl localFileUrl; // For send media file message
};
KazvIOUploadJob::KazvIOUploadJob(QObject *parent)
: KazvIOBaseJob(parent)
, m_d(new KazvIOUploadJobPrivate)
{
}
KazvIOUploadJob::~KazvIOUploadJob() = default;
QPointer<QFile> KazvIOUploadJob::file()
{
return m_d->file.data();
}
void KazvIOUploadJob::setFile(QPointer<QFile> file)
{
if (!file->isOpen()) {
file->open(QIODevice::ReadOnly);
}
m_d->file.reset(file.data());
Q_EMIT fileChanged();
}
QString KazvIOUploadJob::response()
{
return m_d->response;
}
QPointer<MatrixRoom> KazvIOUploadJob::room()
{
return m_d->room;
}
void KazvIOUploadJob::setRoom(QPointer<MatrixRoom> room)
{
m_d->room = room;
Q_EMIT roomChanged();
}
QUrl KazvIOUploadJob::localFileUrl()
{
return m_d->localFileUrl;
}
void KazvIOUploadJob::setLocalFileUrl(QUrl localFileUrl)
{
m_d->localFileUrl = localFileUrl;
Q_EMIT localFileUrlChanged();
}
QString KazvIOUploadJob::fileName()
{
return m_d->file->fileName();
}
void KazvIOUploadJob::connectJob()
{
KazvIOBaseJob::connectJob();
connect(this->job(), &KIO::TransferJob::data, this, [this](KJob * /* job */, const QByteArray &data) {
m_d->response.append(data);
Q_EMIT responseChanged();
});
connect(this->job(), &KJob::result, this, [this](KJob *job) {
m_d->file->close();
if (job->error()) {
m_d->response.clear();
return;
}
json j = json::parse(m_d->response.toStdString());
auto mxcUri = j["content_uri"].get<std::string>();
m_d->room->sendMediaFileMessage(m_d->localFileUrl, QString(mxcUri.data()));
m_d->room->deleteLater();
});
}
diff --git a/src/kazv-io-job.hpp b/src/kazv-io-job.hpp
index 0296ec1..7673be2 100644
--- a/src/kazv-io-job.hpp
+++ b/src/kazv-io-job.hpp
@@ -1,121 +1,129 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2022 nannanko <nannanko@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include "matrix-room.hpp"
#include <QObject>
#include <QPointer>
#include <QSaveFile>
#include <QFile>
#include <QString>
#include <QByteArray>
#include <QUrl>
#include <KIO/TransferJob>
#include <memory>
#include <optional>
struct KazvIOBaseJobPrivate;
class KazvIOBaseJob : public QObject
{
Q_OBJECT
Q_PROPERTY(QPointer<KIO::TransferJob> job READ job WRITE setJob NOTIFY jobChanged)
Q_PROPERTY(float progress READ progress NOTIFY progressChanged)
std::unique_ptr<KazvIOBaseJobPrivate> m_d;
public:
+ enum ErrorCode {
+ NoError = 0,
+ UserCancel,
+ QtError,
+ KIOError
+ };
+ Q_ENUM(ErrorCode)
+
KazvIOBaseJob(QObject *parent = 0);
~KazvIOBaseJob();
QPointer<KIO::TransferJob> job();
void setJob(QPointer<KIO::TransferJob> job);
float progress();
/**
* Only use it after emitted the result signal
*/
Q_INVOKABLE void suspend();
Q_INVOKABLE void resume();
Q_INVOKABLE void cancel();
Q_INVOKABLE bool isSuspended();
Q_INVOKABLE bool isResulted();
- Q_INVOKABLE bool error();
+ Q_INVOKABLE ErrorCode error();
Q_SIGNALS:
void jobChanged();
void progressChanged();
void result();
void success();
void failure();
protected Q_SLOTS:
virtual void connectJob();
void emitProgressChanged(KJob * job, unsigned long percent);
};
struct KazvIODownloadJobPrivate;
class KazvIODownloadJob : public KazvIOBaseJob
{
Q_OBJECT
Q_PROPERTY(QPointer<QSaveFile> file READ file WRITE setFile NOTIFY fileChanged)
std::unique_ptr<KazvIODownloadJobPrivate> m_d;
public:
KazvIODownloadJob(QObject *parent = 0);
~KazvIODownloadJob();
QPointer<QSaveFile> file();
void setFile(QPointer<QSaveFile> file);
Q_INVOKABLE QString fileName();
Q_SIGNALS:
void fileChanged();
protected Q_SLOTS:
void connectJob() override;
};
struct KazvIOUploadJobPrivate;
class KazvIOUploadJob : public KazvIOBaseJob
{
Q_OBJECT
Q_PROPERTY(QPointer<QFile> file READ file WRITE setFile NOTIFY fileChanged)
Q_PROPERTY(QString response READ response NOTIFY responseChanged)
Q_PROPERTY(QPointer<MatrixRoom> room READ room WRITE setRoom NOTIFY roomChanged)
Q_PROPERTY(QUrl localFileUrl READ localFileUrl WRITE setLocalFileUrl NOTIFY localFileUrlChanged)
std::unique_ptr<KazvIOUploadJobPrivate> m_d;
public:
KazvIOUploadJob(QObject *parent = 0);
~KazvIOUploadJob();
QPointer<QFile> file();
void setFile(QPointer<QFile> file);
QString response();
QPointer<MatrixRoom> room();
void setRoom(QPointer<MatrixRoom> room);
QUrl localFileUrl();
void setLocalFileUrl(QUrl localFileUrl);
Q_INVOKABLE QString fileName();
Q_SIGNALS:
void fileChanged();
void responseChanged();
void roomChanged();
void localFileUrlChanged();
void result();
protected Q_SLOTS:
void connectJob() override;
};
diff --git a/src/l10n/cmn-Hans/100-ui.ftl b/src/l10n/cmn-Hans/100-ui.ftl
index 48e3b8c..c3410c5 100644
--- a/src/l10n/cmn-Hans/100-ui.ftl
+++ b/src/l10n/cmn-Hans/100-ui.ftl
@@ -1,97 +1,100 @@
### This file is part of kazv.
### SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
### SPDX-License-Identifier: AGPL-3.0-or-later
app-title = { -kt-app-name }
global-drawer-title = { -kt-app-name }
global-drawer-action-switch-account = 切换账号
global-drawer-action-hard-logout = 登出
global-drawer-action-save-session = 保存当前会话
global-drawer-action-configure-shortcuts = 配置快捷键
action-settings-page-title = 配置快捷键
action-settings-shortcut-prompt = 快捷键:{ $shortcut }
action-settings-shortcut-none = (无)
action-settings-shortcut-edit-action = 编辑
action-settings-shortcut-remove-action = 清除
action-settings-shortcut-conflict-modal-title = 冲突的快捷键
action-settings-shortcut-conflict = 快捷键 { $shortcut } 跟别的指令有冲突。<br>若要继续,别的指令的快捷键会被清除。<br><br>冲突的指令有:<br>{ $conflictingAction }
action-settings-shortcut-conflict-continue = 继续
action-settings-shortcut-conflict-cancel = 取消
empty-room-page-title = 没有选中房间
empty-room-page-description = 当前没有选中的房间。
login-page-title = 登录
login-page-userid-prompt = 用户 id:
login-page-userid-input-placeholder = 例如: @foo:example.org
login-page-password-prompt = 密码:
login-page-login-button = 登录
login-page-close-button = 关闭
login-page-existing-sessions-prompt = 从已有会话中选一个:
login-page-alternative-password-login-prompt = 或者用用户 id 和密码启动新会话:
login-page-restore-session-button = 恢复会话
login-page-request-failed-prompt = 登录失败。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
login-page-discover-failed-prompt = 不能检测此用户所在的服务器,或者服务器不可用。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
main-page-title = { -kt-app-name } - { $userId }
main-page-recent-tab-title = 最近
main-page-people-tab-title = 人们
main-page-rooms-tab-title = 房间
room-list-view-room-item-title-name = { $name }
room-list-view-room-item-title-heroes = { $hero } { $otherNum ->
[0] { "" }
[1] 和 { $secondHero }
*[other] 和别的 { $otherNum } 个人
}
room-list-view-room-item-title-id = 未命名房间({ $roomId })
room-list-view-room-item-fav-action = 设为最爱
room-list-view-room-item-fav-action-notification = 把 { $name } 设为了最爱
send-message-box-input-placeholder = 在此输入您的讯息...
## 状态事件
## 通用参数:
## gender = 发送者的性别(male/female/neutral)
## stateKeyUser = state key 用户的名字
## stateKeyUserGender = state key 用户的性别
member-state-joined-room = 加入了房间。
member-state-changed-name-and-avatar = 修改了名字和头像。
member-state-changed-name = 修改了名字。
member-state-changed-avatar = 修改了头像。
member-state-invited = 把 { $stateKeyUser } 邀请到了本房间。
member-state-left = 离开了房间。
member-state-kicked = 踢出了 { $stateKeyUser }。
member-state-banned = 封禁了 { $stateKeyUser }。
member-state-unbanned = 解封了 { $stateKeyUser }。
state-room-created = 创建了房间。
state-room-name-changed = 把房间名字改成了 { $newName }。
state-room-topic-changed = 把房间话题改成了 { $newTopic }。
state-room-avatar-changed = 修改了房间头像。
state-room-pinned-events-changed = 修改了房间的置顶讯息。
state-room-alias-changed = 修改了房间的别名。
state-room-join-rules-changed = 修改了房间的加入规则。
state-room-power-levels-changed = 修改了房间的权限。
state-room-encryption-activated = 对本房间启用了加密。
event-message-image-sent = 发送了图片「{ $body }」。
event-message-file-sent = 发送了文件「{ $body }」。
event-message-video-sent = 发送了视频「{ $body }」。
event-message-audio-sent = 发送了音频「{ $body }」。
event-message-audio-play-audio = 播放音频
media-file-menu-option-view = 查看
media-file-menu-option-save-as = 保存为
kazv-io-download-success-prompt = 下载成功
kazv-io-download-failure-prompt = 下载失败
+kazv-io-failure-detail-user-cancel = 用户已取消
+kazv-io-failure-detail-network-error = 网络错误
+kazv-io-failure-detail-file-error = 读写本地文件错误
kazv-io-upload-failure-prompt = 上传失败
kazv-io-downloading-prefix = 正在下载
kazv-io-uploading-prefix = 正在上传
kazv-io-prompt-close = 好的
kazv-io-pause = 暂停
kazv-io-resume = 继续
kazv-io-cancel = 取消
diff --git a/src/l10n/en/100-ui.ftl b/src/l10n/en/100-ui.ftl
index afd89fd..3ab9729 100644
--- a/src/l10n/en/100-ui.ftl
+++ b/src/l10n/en/100-ui.ftl
@@ -1,109 +1,112 @@
### This file is part of kazv.
### SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
### SPDX-License-Identifier: AGPL-3.0-or-later
app-title = { -kt-app-name }
global-drawer-title = { -kt-app-name }
global-drawer-action-switch-account = Switch account
global-drawer-action-hard-logout = Logout
global-drawer-action-save-session = Save current session
global-drawer-action-configure-shortcuts = Configure shortcuts
action-settings-page-title = Configure shortcuts
action-settings-shortcut-prompt = Shortcut: { $shortcut }
action-settings-shortcut-none = (none)
action-settings-shortcut-edit-action = Edit
action-settings-shortcut-remove-action = Clear
action-settings-shortcut-conflict-modal-title = Conflicting shortcuts
action-settings-shortcut-conflict = The shortcut { $shortcut } has conflicts with other actions. <br>If you continue, the shortcuts for other actions will be cleared. <br><br>Conflicting actions: <br>{ $conflictingAction }
action-settings-shortcut-conflict-continue = Continue
action-settings-shortcut-conflict-cancel = Cancel
empty-room-page-title = No rooms selected
empty-room-page-description = There is no room selected now.
login-page-title = Log in
login-page-userid-prompt = User id:
login-page-userid-input-placeholder = E.g.: @foo:example.org
login-page-password-prompt = Password:
login-page-login-button = Log in
login-page-close-button = Close
login-page-existing-sessions-prompt = Choose from one of the existing sessions:
login-page-alternative-password-login-prompt = Or start a new session with user id and password:
login-page-restore-session-button = Restore session
login-page-request-failed-prompt = Login failed. Error code: { $errorCode }. Error message: { $errorMsg }.
login-page-discover-failed-prompt = Unable to detect the server this user is on, or the server is unavailable. Error code: { $errorCode }. Error message: { $errorMsg }.
main-page-title = { -kt-app-name } - { $userId }
main-page-recent-tab-title = Recent
main-page-people-tab-title = People
main-page-rooms-tab-title = Rooms
room-list-view-room-item-title-name = { $name }
room-list-view-room-item-title-heroes = { $hero } { $otherNum ->
[0] { "" }
[1] and { $secondHero }
*[other] and { $otherNum } others
}
room-list-view-room-item-title-id = Unnamed room ({ $roomId })
room-list-view-room-item-fav-action = Set as favourite
room-list-view-room-item-fav-action-notification = Set { $name } as favourite
send-message-box-input-placeholder = Type your message here...
## State events
## Common parameters:
## gender = gender of the sender (male/female/neutral)
## stateKeyUser = name of the state key user
## stateKeyUserGender = gender of the state key user
member-state-joined-room = joined the room.
member-state-changed-name-and-avatar = changed { $gender ->
[male] his
[female] her
*[neutral] their
} name and avatar.
member-state-changed-name = changed { $gender ->
[male] his
[female] her
*[neutral] their
} name.
member-state-changed-avatar = changed { $gender ->
[male] his
[female] her
*[neutral] their
} avatar.
member-state-invited = invited { $stateKeyUser } to the room.
member-state-left = left the room.
member-state-kicked = kicked { $stateKeyUser }.
member-state-banned = banned { $stateKeyUser }.
member-state-unbanned = unbanned { $stateKeyUser }.
state-room-created = created the room.
state-room-name-changed = changed the name of the room to { $newName }.
state-room-topic-changed = changed the topic of the room to { $newTopic }.
state-room-avatar-changed = changed the avatar of the room.
state-room-pinned-events-changed = changed the pinned events of the room.
state-room-alias-changed = changed the aliases of the room.
state-room-join-rules-changed = changed join rules of the room.
state-room-power-levels-changed = changed power levels of the room.
state-room-encryption-activated = enabled encryption for this room.
event-message-image-sent = sent an image "{ $body }".
event-message-file-sent = sent a file "{ $body }".
event-message-video-sent = sent a video "{ $body }".
event-message-audio-sent = sent an audio "{ $body }".
event-message-audio-play-audio = Play audio
media-file-menu-option-view = View
media-file-menu-option-save-as = Save as
kazv-io-download-success-prompt = Download successful
kazv-io-download-failure-prompt = Download failure
+kazv-io-failure-detail-user-cancel = User canceled
+kazv-io-failure-detail-network-error = Network error
+kazv-io-failure-detail-file-error = Read/Write local file error
kazv-io-upload-failure-prompt = Upload failure
kazv-io-downloading-prefix = Downloading
kazv-io-uploading-prefix = Uploading
kazv-io-prompt-close = Got it
kazv-io-pause = Pause
kazv-io-resume = Resume
kazv-io-cancel = Cancel

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 8:31 AM (1 h, 20 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55029
Default Alt Text
(22 KB)

Event Timeline