Page MenuHomePhorge

No OneTemporary

Size
34 KB
Referenced Files
None
Subscribers
None
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

Mime Type
text/x-diff
Expires
Sun, Aug 9, 9:41 PM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1724801
Default Alt Text
(34 KB)

Event Timeline