Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85630817
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
34 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/changelogs/49.fix b/changelogs/49.fix
new file mode 100644
index 0000000..c9b9d4a
--- /dev/null
+++ b/changelogs/49.fix
@@ -0,0 +1 @@
+Get rid of spin-wait Promises
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6564e5a..bf793cf 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,75 +1,76 @@
set(KAZV_DATA_DIR ${KDE_INSTALL_DATADIR})
set(KAZV_L10N_DIR ${KAZV_DATA_DIR}/l10n)
configure_file(kazv-path-config.hpp.in kazv-path-config.hpp)
configure_file(kazv-version.cpp.in kazv-version.cpp)
set(kazvprivlib_SRCS
qt-job-handler.cpp
qt-job.cpp
${CMAKE_CURRENT_BINARY_DIR}/kazv-version.cpp
matrix-sdk.cpp
matrix-room.cpp
matrix-room-list.cpp
matrix-room-timeline.cpp
matrix-room-member.cpp
matrix-room-member-list-model.cpp
matrix-event.cpp
meta-types.cpp
l10n-provider.cpp
qt-rand-adapter.cpp
helper.cpp
matrix-promise.cpp
+ qt-promise-handler.cpp
device-mgmt/matrix-device.cpp
device-mgmt/matrix-device-list.cpp
kazv-config.cpp
kazv-io-manager.cpp
kazv-io-job.cpp
kazv-file.cpp
upload-job-model.cpp
shortcuts/shortcut-util.cpp
register-types.cpp
)
ecm_qt_declare_logging_category(kazvprivlib_SRCS
HEADER kazv-log.hpp
IDENTIFIER kazvLog
CATEGORY_NAME moe.kazv.mxc.kazv
)
add_library(kazvprivlib STATIC ${kazvprivlib_SRCS})
target_link_libraries(kazvprivlib PUBLIC
libkazv::kazvall
Qt5::Core
Qt5::Network
Threads::Threads
Qt5::Gui Qt5::Qml Qt5::Quick Qt5::QuickControls2 Qt5::Svg Qt5::Concurrent Qt5::Widgets
KF5::ConfigCore KF5::KIOCore KF5::Notifications
)
target_include_directories(kazvprivlib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(kazvprivlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/device-mgmt)
set(kazv_SRCS
main.cpp
resources.qrc
)
add_executable(kazv ${kazv_SRCS})
target_link_libraries(kazv
PRIVATE
kazvprivlib
)
install(TARGETS kazv ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/l10n/
DESTINATION ${KAZV_L10N_DIR}
FILES_MATCHING PATTERN "*.ftl"
PATTERN "*.json"
)
add_subdirectory(tests)
diff --git a/src/matrix-sdk.cpp b/src/matrix-sdk.cpp
index 1abaad3..a6831d9 100644
--- a/src/matrix-sdk.cpp
+++ b/src/matrix-sdk.cpp
@@ -1,568 +1,569 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <fstream>
#include <filesystem>
#include <chrono>
#include <QMutex>
#include <QMutexLocker>
#include <QtConcurrent>
#include <QThreadPool>
#include <KConfig>
#include <KConfigGroup>
#include <eventemitter/lagerstoreeventemitter.hpp>
#include <client/sdk.hpp>
#include <base/descendent.hpp>
#include <zug/util.hpp>
#include <lager/event_loop/qt.hpp>
#include "matrix-sdk.hpp"
#include "matrix-room-list.hpp"
#include "matrix-promise.hpp"
#include "helper.hpp"
#include "kazv-path-config.hpp"
#include "kazv-version.hpp"
#include "qt-json.hpp"
#include "qt-rand-adapter.hpp"
#include "qt-promise-handler.hpp"
#include "qt-job-handler.hpp"
#include "device-mgmt/matrix-device-list.hpp"
+#include "kazv-log.hpp"
using namespace Kazv;
// Sdk with qt event loop, identity transform and no enhancers
using SdkT =
decltype(makeSdk(
SdkModel{},
detail::declref<JobInterface>(),
detail::declref<EventInterface>(),
QtPromiseHandler(detail::declref<QObject>()),
zug::identity,
withRandomGenerator(detail::declref<RandomInterface>())));
static void serializeClientToFile(Client c);
struct MatrixSdkPrivate
{
MatrixSdkPrivate(MatrixSdk *q, bool testing);
MatrixSdkPrivate(MatrixSdk *q, bool testing, SdkModel model);
bool testing;
RandomInterface randomGenerator;
QThread thread;
QThreadPool pool;
QObject *obj;
QtJobHandler *jobHandler;
LagerStoreEventEmitter ee;
LagerStoreEventEmitter::Watchable watchable;
SdkT sdk;
QTimer saveTimer;
using SecondaryRootT = decltype(sdk.createSecondaryRoot(std::declval<lager::with_qt_event_loop>()));
SecondaryRootT secondaryRoot;
Client clientOnSecondaryRoot;
void runIoContext() {
thread.start();
}
void maybeSerialize()
{
if (!testing) {
serializeClientToFile(clientOnSecondaryRoot);
}
}
};
class CleanupHelper : public QObject
{
Q_OBJECT
public:
explicit CleanupHelper(std::unique_ptr<MatrixSdkPrivate> d)
: oldD(std::move(d))
{
connect(oldD->obj, &QObject::destroyed, this, &CleanupHelper::stopThread);
}
std::shared_ptr<MatrixSdkPrivate> oldD;
void cleanup()
{
qCInfo(kazvLog) << "start to clean up everything";
// Not running io_context::stop() because it will
// disregard any remaining work guards. Here we just
// want to end any periodic jobs but still wait for
// current remaining jobs.
oldD->clientOnSecondaryRoot.stopSyncing();
qCDebug(kazvLog) << "stopped syncing";
oldD->jobHandler->deleteLater();
qCDebug(kazvLog) << "deleted jobhandler";
oldD->obj->deleteLater();
qCDebug(kazvLog) << "deleted object";
}
void stopThread()
{
QTimer::singleShot(0, this, &CleanupHelper::doStopThread);
}
void doStopThread()
{
qCDebug(kazvLog) << "stopping thread";
oldD->thread.quit();
qCInfo(kazvLog) << "from here, the old data can be destructed";
deleteLater();
}
};
static void serializeClientToFile(Client c)
{
using namespace Kazv::CursorOp;
auto userId = +c.userId();
auto deviceId = +c.deviceId();
if (userId.empty() || deviceId.empty()) {
qDebug() << "Not logged in, nothing to serialize";
return;
}
using StdPath = std::filesystem::path;
auto userDataDir = StdPath(kazvUserDataDir().toStdString());
auto sessionDir = userDataDir / "sessions"
/ userId / deviceId;
auto storeFile = sessionDir / "store";
auto metadataFile = sessionDir / "metadata";
qDebug() << "storeFile=" << QString::fromStdString(storeFile.native());
std::error_code err;
if ((! std::filesystem::create_directories(sessionDir, err))
&& err) {
qDebug() << "Unable to create sessionDir";
return;
}
{
auto storeStream = std::ofstream(storeFile);
if (! storeStream) {
qDebug() << "Unable to open storeFile";
return;
}
using OAr = boost::archive::text_oarchive;
auto archive = OAr{storeStream};
c.serializeTo(archive);
qDebug() << "Serialization done";
}
// store metadata
{
KConfig metadata(QString::fromStdString(metadataFile.native()));
KConfigGroup mdGroup(&metadata, "Metadata");
mdGroup.writeEntry("kazvVersion", QString::fromStdString(kazvVersionString()));
mdGroup.writeEntry("archiveFormat", "text");
}
}
MatrixSdkPrivate::MatrixSdkPrivate(MatrixSdk *q, bool testing)
: testing(testing)
, randomGenerator(QtRandAdapter{})
, thread()
, pool()
, obj(new QObject(&thread))
, jobHandler(new QtJobHandler(obj))
, ee{lager::with_qt_event_loop{*obj, pool}}
, watchable(ee.watchable())
, sdk(makeDefaultSdkWithCryptoRandom(
randomGenerator.generateRange<std::string>(makeDefaultSdkWithCryptoRandomSize()),
static_cast<JobInterface &>(*jobHandler),
static_cast<EventInterface &>(ee),
QtPromiseHandler(*obj),
zug::identity,
withRandomGenerator(randomGenerator)))
, secondaryRoot(sdk.createSecondaryRoot(lager::with_qt_event_loop{*q}))
, clientOnSecondaryRoot(sdk.clientFromSecondaryRoot(secondaryRoot))
{
}
MatrixSdkPrivate::MatrixSdkPrivate(MatrixSdk *q, bool testing, SdkModel model)
: testing(testing)
, randomGenerator(QtRandAdapter{})
, thread()
, pool()
, obj(new QObject(&thread))
, jobHandler(new QtJobHandler(obj))
, ee{lager::with_qt_event_loop{*obj, pool}}
, watchable(ee.watchable())
, sdk(makeSdk(
model,
static_cast<JobInterface &>(*jobHandler),
static_cast<EventInterface &>(ee),
QtPromiseHandler(*obj),
zug::identity,
withRandomGenerator(randomGenerator)))
, secondaryRoot(sdk.createSecondaryRoot(lager::with_qt_event_loop{*q, pool}, std::move(model)))
, clientOnSecondaryRoot(sdk.clientFromSecondaryRoot(secondaryRoot))
{
}
MatrixSdk::MatrixSdk(std::unique_ptr<MatrixSdkPrivate> d, QObject *parent)
: QObject(parent)
, m_d(std::move(d))
{
init();
connect(this, &MatrixSdk::trigger,
this, [](KazvEvent e) {
qDebug() << "receiving trigger:";
if (std::holds_alternative<LoginSuccessful>(e)) {
qDebug() << "Login successful";
}
});
}
void MatrixSdk::init()
{
LAGER_QT(serverUrl) = m_d->clientOnSecondaryRoot.serverUrl().xform(strToQt); Q_EMIT serverUrlChanged(serverUrl());
LAGER_QT(userId) = m_d->clientOnSecondaryRoot.userId().xform(strToQt); Q_EMIT userIdChanged(userId());
LAGER_QT(token) = m_d->clientOnSecondaryRoot.token().xform(strToQt); Q_EMIT tokenChanged(token());
LAGER_QT(deviceId) = m_d->clientOnSecondaryRoot.deviceId().xform(strToQt); Q_EMIT deviceIdChanged(deviceId());
m_d->watchable.afterAll(
[this](KazvEvent e) {
Q_EMIT this->trigger(e);
});
m_d->watchable.after<LoginSuccessful>(
[this](LoginSuccessful e) {
Q_EMIT this->loginSuccessful(e);
});
m_d->watchable.after<LoginFailed>(
[this](LoginFailed e) {
Q_EMIT this->loginFailed(
QString::fromStdString(e.errorCode),
QString::fromStdString(e.error)
);
});
m_d->watchable.after<ReceivingRoomTimelineEvent>(
[this](ReceivingRoomTimelineEvent e) {
Q_EMIT this->receivedMessage(
QString::fromStdString(e.roomId),
QString::fromStdString(e.event.id())
);
});
connect(&m_d->saveTimer, &QTimer::timeout, &m_d->saveTimer, [m_d=m_d.get()]() {
m_d->maybeSerialize();
});
const int saveIntervalMs = 1000 * 60 * 5;
m_d->saveTimer.start(std::chrono::milliseconds{saveIntervalMs});
}
MatrixSdk::MatrixSdk(QObject *parent)
: MatrixSdk(std::make_unique<MatrixSdkPrivate>(this, /* testing = */ false), parent)
{
}
MatrixSdk::MatrixSdk(SdkModel model, bool testing, QObject *parent)
: MatrixSdk(std::make_unique<MatrixSdkPrivate>(this, testing, std::move(model)), parent)
{
}
static void cleanupDPointer(std::unique_ptr<MatrixSdkPrivate> oldD)
{
oldD->saveTimer.disconnect();
oldD->saveTimer.stop();
auto helper = new CleanupHelper(std::move(oldD));
helper->cleanup();
}
MatrixSdk::~MatrixSdk()
{
if (m_d) {
serializeToFile();
cleanupDPointer(std::move(m_d));
}
}
QString MatrixSdk::mxcUriToHttp(QString mxcUri) const
{
return QString::fromStdString(m_d->clientOnSecondaryRoot.mxcUriToHttp(mxcUri.toStdString()));
}
MatrixDeviceList *MatrixSdk::devicesOfUser(QString userId) const
{
return new MatrixDeviceList(m_d->clientOnSecondaryRoot.devicesOfUser(userId.toStdString()));
}
void MatrixSdk::login(const QString &userId, const QString &password)
{
m_d->clientOnSecondaryRoot
.autoDiscover(userId.toStdString())
.then([
this,
client=m_d->clientOnSecondaryRoot.toEventLoop(),
userId,
password
](auto res) {
if (!res.success()) {
// FIXME use real error codes and msgs when available in libkazv
Q_EMIT this->discoverFailed("", "");
return res;
}
auto serverUrl = res.dataStr("homeserverUrl");
client.passwordLogin(
serverUrl,
userId.toStdString(),
password.toStdString(),
"kazv 0.0.0"
);
return res;
});
m_d->runIoContext();
}
MatrixRoomList *MatrixSdk::roomList() const
{
return new MatrixRoomList(m_d->clientOnSecondaryRoot);
}
void MatrixSdk::emplace(std::optional<SdkModel> model)
{
auto testing = m_d->testing;
if (m_d) {
cleanupDPointer(std::move(m_d));
}
m_d = (model.has_value()
? std::make_unique<MatrixSdkPrivate>(this, testing, std::move(model.value()))
: std::make_unique<MatrixSdkPrivate>(this, testing));
// Re-initialize lager-qt cursors and watchable connections
init();
m_d->runIoContext();
m_d->clientOnSecondaryRoot.startSyncing();
Q_EMIT sessionChanged();
}
QStringList MatrixSdk::allSessions() const
{
using StdPath = std::filesystem::path;
auto userDataDir = StdPath(kazvUserDataDir().toStdString());
auto allSessionsDir = userDataDir / "sessions";
QStringList sessionNames;
try {
for (const auto &p : std::filesystem::directory_iterator(allSessionsDir)) {
if (p.is_directory()) {
auto userId = p.path().filename().native();
for (const auto &q : std::filesystem::directory_iterator(p.path())) {
auto path = q.path();
auto deviceId = path.filename().native();
std::error_code err;
if (std::filesystem::exists(path / "store", err)) {
sessionNames.append(QString::fromStdString(userId + "/" + deviceId));
}
}
}
}
} catch (const std::filesystem::filesystem_error &) {
qDebug() << "sessionDir not available, ignoring";
}
return sessionNames;
}
void MatrixSdk::serializeToFile() const
{
m_d->maybeSerialize();
}
bool MatrixSdk::loadSession(QString sessionName)
{
qDebug() << "in loadSession(), sessionName=" << sessionName;
using StdPath = std::filesystem::path;
auto userDataDir = StdPath(kazvUserDataDir().toStdString());
auto sessionDir = userDataDir / "sessions" / sessionName.toStdString();
auto storeFile = sessionDir / "store";
auto metadataFile = sessionDir / "metadata";
if (! std::filesystem::exists(storeFile)) {
qDebug() << "storeFile does not exist, skip loading session " << sessionName;
return false;
}
if (std::filesystem::exists(metadataFile)) {
KConfig metadata(QString::fromStdString(metadataFile.native()));
KConfigGroup mdGroup(&metadata, "Metadata");
auto format = mdGroup.readEntry("archiveFormat");
if (format != QStringLiteral("text")) {
qDebug() << "Unknown archive format:" << format;
return false;
}
auto version = mdGroup.readEntry("kazvVersion");
auto curVersion = kazvVersionString();
if (version != QString::fromStdString(curVersion)) {
qDebug() << "A different version from the current one, making a backup";
std::error_code err;
auto now = std::chrono::system_clock::now();
auto backupName =
std::to_string(std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count());
auto backupDir = sessionDir / "backup" / backupName;
if (! std::filesystem::create_directories(backupDir, err)
&& err) {
qDebug() << "Cannot create backup directory";
return false;
}
std::filesystem::copy_file(storeFile, backupDir / "store");
std::filesystem::copy_file(metadataFile, backupDir / "metadata");
}
}
SdkModel model;
try {
auto storeStream = std::ifstream(storeFile);
if (! storeStream) {
qDebug() << "Unable to open storeFile";
return false;
}
using IAr = boost::archive::text_iarchive;
auto archive = IAr{storeStream};
archive >> model;
qDebug() << "Finished loading session";
} catch (const std::exception &e) {
qDebug() << "Error when loading session:" << QString::fromStdString(e.what());
return false;
}
emplace(std::move(model));
return true;
}
bool MatrixSdk::startNewSession()
{
emplace(std::nullopt);
return true;
}
static std::optional<std::string> optMaybe(QString s)
{
if (s.isEmpty()) {
return std::nullopt;
} else {
return s.toStdString();
}
}
void MatrixSdk::createRoom(
bool isPrivate,
const QString &name,
const QString &alias,
const QStringList &invite,
bool isDirect,
bool allowFederate,
const QString &topic,
const QJsonValue &powerLevelContentOverride
)
{
m_d->clientOnSecondaryRoot.createRoom(
isPrivate ? Kazv::RoomVisibility::Private : Kazv::RoomVisibility::Public,
optMaybe(name),
optMaybe(alias),
qStringListToStdF(invite),
isDirect,
allowFederate,
optMaybe(topic),
nlohmann::json(powerLevelContentOverride)
)
.then([this, client=m_d->clientOnSecondaryRoot.toEventLoop()](auto stat) {
if (stat.success()) {
Q_EMIT createRoomSuccessful();
} else {
Q_EMIT createRoomFailed("", "");
}
});
}
MatrixPromise *MatrixSdk::joinRoom(const QString &idOrAlias, const QStringList &servers)
{
return new MatrixPromise(m_d->clientOnSecondaryRoot.joinRoom(
idOrAlias.toStdString(),
qStringListToStdF(servers)
)
.then([this, idOrAlias, client=m_d->clientOnSecondaryRoot.toEventLoop()](auto stat) {
if (stat.success()) {
Q_EMIT joinRoomSuccessful(idOrAlias);
} else {
Q_EMIT joinRoomFailed(idOrAlias, "", "");
}
}));
}
MatrixPromise *MatrixSdk::setDeviceTrustLevel(QString userId, QString deviceId, QString trustLevel)
{
return new MatrixPromise(
m_d->clientOnSecondaryRoot.setDeviceTrustLevel(
userId.toStdString(),
deviceId.toStdString(),
qStringToTrustLevelFunc(trustLevel)
)
);
}
MatrixPromise *MatrixSdk::getSelfProfile()
{
return new MatrixPromise(
m_d->clientOnSecondaryRoot.getProfile(userId().toStdString())
);
}
MatrixPromise *MatrixSdk::setDisplayName(QString displayName)
{
return new MatrixPromise(
m_d->clientOnSecondaryRoot.setDisplayName(
displayName.isEmpty() ? std::nullopt : std::optional<std::string>(displayName.toStdString())
)
);
}
MatrixPromise *MatrixSdk::setAvatarUrl(QString avatarUrl)
{
return new MatrixPromise(
m_d->clientOnSecondaryRoot.setAvatarUrl(
avatarUrl.isEmpty() ? std::nullopt : std::optional<std::string>(avatarUrl.toStdString())
)
);
}
void MatrixSdk::startThread()
{
m_d->runIoContext();
}
RandomInterface &MatrixSdk::randomGenerator() const
{
return m_d->randomGenerator;
}
#include "matrix-sdk.moc"
diff --git a/src/qt-promise-handler.cpp b/src/qt-promise-handler.cpp
new file mode 100644
index 0000000..c881739
--- /dev/null
+++ b/src/qt-promise-handler.cpp
@@ -0,0 +1,15 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libkazv-config.hpp>
+
+#include "qt-promise-handler.hpp"
+
+QtPromiseSignalTrigger::QtPromiseSignalTrigger(QObject *parent)
+ : QObject(parent)
+{}
+
+QtPromiseSignalTrigger::~QtPromiseSignalTrigger() = default;
diff --git a/src/qt-promise-handler.hpp b/src/qt-promise-handler.hpp
index c98b644..e8b643a 100644
--- a/src/qt-promise-handler.hpp
+++ b/src/qt-promise-handler.hpp
@@ -1,214 +1,253 @@
/*
* This file is part of kazv.
- * SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
+ * SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <future>
#include <QObject>
#include <QMetaObject>
#include <QPointer>
#include <QTimer>
#include <promise-interface.hpp>
-#include "kazv-log.hpp"
-
template<class T>
class QtPromise;
namespace QtPromiseDetail
{
struct QtPromiseHelper
{
template<class T>
using PromiseType = QtPromise<T>;
};
struct IdentityFunc
{
template<class T>
constexpr T &&operator()(T &&t) const {
return std::forward<T>(t);
}
};
template<class Func>
void post(QObject *obj, Func &&func)
{
QMetaObject::invokeMethod(obj, std::forward<Func>(func), Qt::QueuedConnection);
}
}
+class QtPromiseSignalTrigger : public QObject
+{
+ Q_OBJECT
+public:
+ QtPromiseSignalTrigger(QObject *parent = 0);
+
+ ~QtPromiseSignalTrigger();
+
+Q_SIGNALS:
+ void finished();
+};
+
template<class T>
class QtPromise : public Kazv::AbstractPromise<QtPromiseDetail::QtPromiseHelper::PromiseType, T>
{
using BaseT = Kazv::AbstractPromise<QtPromiseDetail::QtPromiseHelper::PromiseType, T>;
template<class FuncT, class PromiseT, class ResolveT>
struct WaitHelper
{
- void wait() const {
- if (p.ready()) {
- auto res = func(p.get());
- using ResT = decltype(res);
- if constexpr (Kazv::isPromise<ResT>) {
- QtPromiseDetail::post(
- executor,
- [w=WaitHelper<QtPromiseDetail::IdentityFunc, ResT, ResolveT>{
- executor,
- res,
- QtPromiseDetail::IdentityFunc{},
- resolve,
- }]() mutable {
- w.wait();
- });
- } else {
- resolve(res);
- }
+ void resolveOrWait()
+ {
+ auto res = func(p.get());
+ using ResT = decltype(res);
+ if constexpr (Kazv::isPromise<ResT>) {
+ res.then([resolve=resolve](auto val) {
+ resolve(std::move(val));
+ return std::decay_t<decltype(val)>(); // we don't care about this value
+ });
} else {
+ resolve(res);
+ }
+ }
+
+ void wait()
+ {
+ if (p.ready()) {
QtPromiseDetail::post(
executor,
- [*this]() mutable {
- wait();
+ [*this]() mutable { resolveOrWait(); }
+ );
+ } else {
+ QObject::connect(
+ p.m_signalTrigger, &QtPromiseSignalTrigger::finished,
+ executor, [*this]() mutable {
+ resolveOrWait();
}
);
}
}
QPointer<QObject> executor;
PromiseT p;
FuncT func;
ResolveT resolve;
};
struct ResolveHelper
{
template<class ValT>
- void operator()(ValT val) const {
+ void operator()(ValT val) const
+ {
using ResT = std::decay_t<decltype(val)>;
if constexpr (Kazv::isPromise<ResT>) {
- auto w = WaitHelper<QtPromiseDetail::IdentityFunc, ResT, ResolveHelper>{
+ QtPromiseDetail::post(
executor,
- val,
- QtPromiseDetail::IdentityFunc{},
- *this
- };
- w.wait();
+ [qtPromise=std::move(val), *this]() mutable {
+ if (qtPromise.ready()) {
+ setValue(qtPromise.get());
+ } else {
+ qtPromise.then([*this](auto val) mutable {
+ setValue(std::move(val));
+ return std::decay_t<decltype(val)>(); // we don't care about this value
+ });
+ }
+ });
} else {
- p->set_value(std::move(val));
+ QtPromiseDetail::post(
+ executor,
+ [*this, val=std::move(val)]() mutable {
+ setValue(std::move(val));
+ });
+ }
+ }
+
+ void setValue(T resolvedValue) const
+ {
+ p->set_value(std::move(resolvedValue));
+ if (signalTrigger) {
+ Q_EMIT signalTrigger->finished();
+ signalTrigger->deleteLater();
}
}
QPointer<QObject> executor;
+ QPointer<QtPromiseSignalTrigger> signalTrigger;
std::shared_ptr<std::promise<T>> p;
};
public:
QtPromise(QObject *executor, T value)
: BaseT(this)
- , m_executor(executor) {
+ , m_executor(executor)
+ , m_signalTrigger()
+ {
std::promise<T> p;
m_val = p.get_future().share();
p.set_value(std::move(value));
}
template<class Func>
QtPromise(QObject *executor, Func &&callback)
: BaseT(this)
- , m_executor(executor) {
+ , m_executor(executor)
+ , m_signalTrigger(new QtPromiseSignalTrigger(m_executor))
+ {
auto p = std::make_shared<std::promise<T>>();
m_val = p->get_future().share();
- auto resolve = ResolveHelper{m_executor, p};
+
+ auto resolve = ResolveHelper{m_executor, m_signalTrigger, p};
QtPromiseDetail::post(
m_executor,
[=, callback=std::forward<Func>(callback),
resolve=std::move(resolve)]() {
callback(resolve);
});
}
// FuncT: (DataT) -> AnotherDataT
// where AnotherDataT = PromiseThenResult<FuncT, typename BaseT::DataT>
template<class FuncT>
auto then(FuncT &&func)
-> QtPromise<Kazv::PromiseThenResult<FuncT, typename BaseT::DataT>> {
return QtPromise<Kazv::PromiseThenResult<FuncT, typename BaseT::DataT>>(
m_executor,
[=, func=std::forward<FuncT>(func), *this](auto resolve) {
auto waitHelper = WaitHelper<std::decay_t<FuncT>,
QtPromise,
std::decay_t<decltype(resolve)>>{
m_executor,
*this,
func,
resolve
};
waitHelper.wait();
});
}
bool ready() const {
return m_val.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
}
T get() const {
return m_val.get();
}
private:
QPointer<QObject> m_executor;
+ QPointer<QtPromiseSignalTrigger> m_signalTrigger;
std::shared_future<T> m_val;
};
class QtPromiseHandler : public Kazv::PromiseInterface<QtPromiseHandler,
QtPromiseDetail::QtPromiseHelper::PromiseType>
{
using BaseT = Kazv::PromiseInterface<QtPromiseHandler,
QtPromiseDetail::QtPromiseHelper::PromiseType>;
public:
template<class T>
using PromiseT = QtPromise<T>;
QtPromiseHandler(std::reference_wrapper<QObject> executor)
: BaseT(this)
, m_executor(&executor.get())
{}
QtPromiseHandler(const QtPromiseHandler &that)
: BaseT(this)
, m_executor(that.m_executor)
{}
QtPromiseHandler(QtPromiseHandler &&that)
: BaseT(this)
, m_executor(std::move(that.m_executor))
{}
QtPromiseHandler &operator=(const QtPromiseHandler &that) {
m_executor = that.m_executor;
return *this;
}
QtPromiseHandler &operator=(QtPromiseHandler &&that) {
m_executor = std::move(that.m_executor);
return *this;
}
template<class T, class FuncT>
PromiseT<T> create(FuncT &&func) {
return PromiseT<T>(m_executor, std::forward<FuncT>(func));
}
template<class T>
PromiseT<T> createResolved(T val) {
return PromiseT<T>(m_executor, std::move(val));
}
private:
QPointer<QObject> m_executor;
};
diff --git a/src/tests/qt-promise-handler-test.cpp b/src/tests/qt-promise-handler-test.cpp
index e2ceafd..2abac85 100644
--- a/src/tests/qt-promise-handler-test.cpp
+++ b/src/tests/qt-promise-handler-test.cpp
@@ -1,138 +1,212 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <memory>
#include <vector>
#include <QTimer>
+#include "promise-interface.hpp"
+
#include "qt-promise-handler-test.hpp"
#include "qt-promise-handler.hpp"
namespace
{
struct MockDataStruct
{
int i{};
};
}
void QtPromiseHandlerTest::testPromise()
{
QEventLoop loop;
QObject *obj = new QObject(&loop);
auto ph = QtPromiseHandler(std::ref(*obj));
std::vector<int> v;
auto p1 = ph.create<int>([&v](auto resolve) {
qDebug() << "p1";
v.push_back(1);
resolve(2);
});
auto p2 = p1.then([&v, &ph](int val) {
qDebug() << "p2";
v.push_back(val);
return ph.createResolved(3);
});
auto p3 = p2.then([&v, &ph](int val) {
qDebug() << "p3";
v.push_back(val);
return ph.createResolved(-1);
});
auto p4 = p3.then([](int val) {
qDebug() << "p4" << val;
[=] { QVERIFY(val == -1); }();
return 5;
});
auto p5 = p4.then([](int val) {
qDebug() << "p5" << val;
[=] { QVERIFY(val == 5); }();
return MockDataStruct{6};
});
auto p6 = p5.then([obj, &loop](MockDataStruct m) {
qDebug() << "p6" << m.i;
[=] { QVERIFY(m.i == 6); }();
obj->deleteLater();
loop.quit();
return 0;
});
loop.exec();
QVERIFY((v == std::vector<int>{ 1, 2, 3 }));
}
void QtPromiseHandlerTest::testTimer()
{
QEventLoop loop;
QObject *obj = new QObject(&loop);
auto ph = QtPromiseHandler(std::ref(*obj));
auto pTimer = ph.create<int>([](auto resolve) {
QTimer::singleShot(300, [resolve]() { resolve(20); });
});
int i;
auto pTimer2 = pTimer.then([&i, &loop, obj](int val) -> int {
i = val;
obj->deleteLater();
loop.quit();
return 0;
});
loop.exec();
QVERIFY(i == 20);
}
void QtPromiseHandlerTest::testStop()
{
QEventLoop loop;
QObject *obj = new QObject(&loop);
auto ph = QtPromiseHandler(std::ref(*obj));
auto pTimer = ph.create<int>([](auto resolve) {
QTimer::singleShot(500, [resolve]() { resolve(20); });
});
int i = 0;
auto pTimer2 = pTimer.then([&i, &loop, obj](int val) -> int {
qDebug() << "then-continuation";
i = val;
obj->deleteLater();
loop.quit();
return 0;
});
QTimer::singleShot(300, this, [obj]() {
qDebug() << "delete obj";
obj->deleteLater();
});
QObject::connect(obj, &QObject::destroyed,
this, [&i] {
qDebug() << "QObject::destroyed()";
});
QTimer::singleShot(2000, this, [&loop]() {
qDebug() << "stop loop";
loop.quit();
});
loop.exec();
}
+void QtPromiseHandlerTest::testSingleTypePromise()
+{
+ QEventLoop loop;
+ QObject *obj = new QObject(&loop);
+ auto ph = Kazv::SingleTypePromiseInterface<int>(QtPromiseHandler(std::ref(*obj)));
+
+ std::vector<int> v;
+
+ auto p1 = ph.create([&v](auto resolve) {
+ qDebug() << "p1";
+ v.push_back(1);
+ resolve(2);
+ });
+
+ auto p2 = p1.then([&v, &ph](int val) {
+ qDebug() << "p2";
+ v.push_back(val);
+ return ph.createResolved(0)
+ .then([&ph](int) {
+ qDebug() << "within p2";
+ return ph.createResolved(3);
+ });
+ });
+
+ auto p3 = p2.then([&v, &ph](int val) {
+ qDebug() << "p3";
+ v.push_back(val);
+ return ph.createResolved(-1);
+ });
+
+ auto p4 = p3.then([](int val) {
+ qDebug() << "p4" << val;
+ [=] { QVERIFY(val == -1); }();
+ return 5;
+ });
+
+ auto p5 = p4.then([obj, &loop](int val) {
+ qDebug() << "p5" << val;
+ [=] { QVERIFY(val == 5); }();
+ obj->deleteLater();
+ loop.quit();
+ return 0;
+ });
+
+ loop.exec();
+
+ QVERIFY((v == std::vector<int>{ 1, 2, 3 }));
+}
+
+void QtPromiseHandlerTest::testResolveToPromise()
+{
+ QEventLoop loop;
+ QObject *obj = new QObject(&loop);
+ auto ph = Kazv::SingleTypePromiseInterface<int>(QtPromiseHandler(std::ref(*obj)));
+
+ auto p1 = ph.createResolveToPromise([&ph](auto resolve) {
+ qDebug() << "p1";
+ resolve(ph.createResolved(1)
+ .then([&ph](auto) {
+ return ph.createResolved(2);
+ }));
+ });
+
+ p1.then([obj, &loop](int val) {
+ [=] { QVERIFY(val == 2); }();
+ obj->deleteLater();
+ loop.quit();
+ return 0;
+ });
+ loop.exec();
+}
+
QTEST_MAIN(QtPromiseHandlerTest)
diff --git a/src/tests/qt-promise-handler-test.hpp b/src/tests/qt-promise-handler-test.hpp
index d2094de..1bd76d0 100644
--- a/src/tests/qt-promise-handler-test.hpp
+++ b/src/tests/qt-promise-handler-test.hpp
@@ -1,19 +1,21 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <QtTest>
class QtPromiseHandlerTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testPromise();
void testTimer();
void testStop();
+ void testSingleTypePromise();
+ void testResolveToPromise();
};
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Aug 9, 9:41 PM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1724801
Default Alt Text
(34 KB)
Attached To
Mode
rK kazv
Attached
Detach File
Event Timeline
Log In to Comment