Page MenuHomePhorge

No OneTemporary

Size
56 KB
Referenced Files
None
Subscribers
None
diff --git a/src/base/promise-interface.hpp b/src/base/promise-interface.hpp
index 7a8febc..2be31c4 100644
--- a/src/base/promise-interface.hpp
+++ b/src/base/promise-interface.hpp
@@ -1,349 +1,349 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <type_traits>
#include <functional>
namespace Kazv
{
class TypelessPromise
{
};
template<template<class> class DeriveT, class T>
class AbstractPromise;
namespace detail
{
template<class T, class = void>
struct PromiseParameterT
{
using type = T;
};
template<class PromiseT>
struct PromiseParameterT<PromiseT,
std::void_t<std::enable_if_t<std::is_base_of_v<TypelessPromise, PromiseT>, int>>>
{
using type = typename PromiseT::DataT;
};
template<class T>
using PromiseParameter = typename PromiseParameterT<T>::type;
template<class T, class = void>
struct PromiseResult
{
};
template<class T>
struct PromiseResult<T, std::void_t<std::invoke_result_t<T>>>
{
using Res = std::invoke_result_t<T>;
using type = PromiseParameter<Res>;
};
template<class T>
using PromiseResultT = typename PromiseResult<T>::type;
template<class T, class P>
struct PromiseThenResultT
{
using Res = std::invoke_result_t<T, PromiseParameter<P>>;
using type = PromiseParameter<Res>;
};
}
template<class T, class P>
using PromiseThenResult = typename detail::PromiseThenResultT<T, P>::type;
template<class T>
constexpr auto isPromise = std::is_base_of_v<TypelessPromise, T>;
template<template<class> class DeriveT, class T>
class AbstractPromise : TypelessPromise
{
public:
using DataT = T;
static_assert(!isPromise<DataT>, "Cannot create a promise of promise");
AbstractPromise(DeriveT<T> *obj) : m_derived(obj) {}
template<class FuncT>
auto then(FuncT &&func)
-> DeriveT<PromiseThenResult<FuncT, DataT>> {
return m_derived->then(std::forward<FuncT>(func));
}
private:
DeriveT<T> *m_derived;
};
namespace PromiseCombination
{
constexpr bool createDefaultForPromiseThen(bool) { return true; }
constexpr bool dataCombine(bool a, bool b) { return a && b; }
constexpr bool dataCombineNone(bool) { return true; }
template<class T, class = void>
struct DefaultForPromiseT
{
constexpr T operator()() const { return T(); }
};
template<class T>
struct DefaultForPromiseT<
T,
std::void_t<decltype(createDefaultForPromiseThen(std::declval<T>()))>>
{
constexpr T operator()() const { return createDefaultForPromiseThen(T()); }
};
template<class T>
constexpr T defaultForPromiseThen(T)
{
return DefaultForPromiseT<T>{}();
}
}
template<class T>
class SingleTypePromise : TypelessPromise
{
public:
using DataT = T;
template<class DeriveT>
SingleTypePromise(DeriveT obj)
: m_d(std::unique_ptr<Concept>(new Model<DeriveT>(std::move(obj)))) {}
SingleTypePromise(const SingleTypePromise &that)
: m_d(that.m_d->clone())
{}
SingleTypePromise(SingleTypePromise &&that)
: m_d(std::move(that.m_d))
{}
SingleTypePromise &operator=(const SingleTypePromise &that) {
m_d = that.m_d->clone();
return *this;
}
SingleTypePromise &operator=(SingleTypePromise &&that) {
m_d = std::move(that.m_d);
return *this;
}
template<class F>
SingleTypePromise then(F &&f) {
if constexpr (std::is_same_v<std::invoke_result_t<F, DataT>, void>) {
return m_d->thenVoid(f);
} else if constexpr(isPromise<std::invoke_result_t<F, DataT>>) {
return m_d->thenPromise(f);
} else {
return m_d->thenData(f);
}
}
bool ready() const { return m_d->ready(); }
- bool get() const { return m_d->get(); }
+ DataT get() const { return m_d->get(); }
private:
struct Concept
{
virtual ~Concept() = default;
virtual SingleTypePromise thenVoid(std::function<void(DataT)> f) = 0;
virtual SingleTypePromise thenData(std::function<DataT(DataT)> f) = 0;
virtual SingleTypePromise thenPromise(std::function<SingleTypePromise(DataT)> f) = 0;
virtual std::unique_ptr<Concept> clone() const = 0;
virtual bool ready() const = 0;
virtual DataT get() const = 0;
};
template<class DeriveT>
struct Model : public Concept
{
Model(DeriveT obj) : instance(std::move(obj)) {}
~Model() override = default;
SingleTypePromise thenVoid(std::function<void(DataT)> f) override {
return instance.then([=](DataT v) {
f(v);
return PromiseCombination::defaultForPromiseThen(DataT());
});
}
SingleTypePromise thenData(std::function<DataT(DataT)> f) override {
return instance.then([=](DataT v) { return f(v); });
}
SingleTypePromise thenPromise(std::function<SingleTypePromise(DataT)> f) override {
return instance.then([=](DataT v) { return f(v); });
}
std::unique_ptr<Concept> clone() const override {
return std::unique_ptr<Concept>(new Model<DeriveT>(instance));
}
bool ready() const override {
return instance.ready();
}
DataT get() const override {
return instance.get();
}
DeriveT instance;
};
std::unique_ptr<Concept> m_d;
};
using BoolPromise = SingleTypePromise<bool>;
template<class DeriveT, template<class> class PromiseT>
class PromiseInterface
{
public:
PromiseInterface(DeriveT *obj) : m_derived(obj) {}
template<class T, class FuncT>
auto create(FuncT &&func) -> PromiseT<T>;
template<class T>
auto createResolved(T &&val) -> PromiseT<T>;
private:
DeriveT *m_derived;
};
template<class DeriveT, template<class> class PromiseT>
template<class T, class FuncT>
auto PromiseInterface<DeriveT, PromiseT>::create(FuncT &&func) -> PromiseT<T> {
return m_derived->create(std::forward<FuncT>(func));
}
template<class DeriveT, template<class> class PromiseT>
template<class T>
auto PromiseInterface<DeriveT, PromiseT>::createResolved(T &&val) -> PromiseT<T> {
return m_derived->createResolved(std::forward<T>(val));
}
template<class T>
class SingleTypePromiseInterface
{
public:
using DataT = T;
using PromiseT = SingleTypePromise<DataT>;
using ResolveT = std::function<void(DataT)>;
using ResolveToPromiseT = std::function<void(PromiseT)>;
template<class DeriveT>
SingleTypePromiseInterface(DeriveT obj)
: m_d(std::unique_ptr<Concept>(new Model<std::decay_t<DeriveT>>(std::move(obj)))) {
if (! m_d) {
throw std::logic_error("promise handler is empty");
}
}
SingleTypePromiseInterface(const SingleTypePromiseInterface &that)
: m_d(that.m_d) {
if (! m_d) {
throw std::logic_error("promise handler is empty");
}
}
SingleTypePromiseInterface(SingleTypePromiseInterface &&that)
: m_d(std::move(that.m_d)) {
if (! m_d) {
throw std::logic_error("promise handler is empty");
}
}
SingleTypePromiseInterface &operator=(const SingleTypePromiseInterface &that) {
m_d = that.m_d;
return *this;
}
SingleTypePromiseInterface &operator=(SingleTypePromiseInterface &&that) {
m_d = std::move(that.m_d);
return *this;
}
PromiseT create(std::function<void(ResolveT)> f) const {
return m_d->create(f);
}
PromiseT createResolveToPromise(std::function<void(ResolveToPromiseT)> f) const {
return m_d->createResolveToPromise(f);
}
- PromiseT createResolved(bool v) const {
+ PromiseT createResolved(DataT v) const {
return m_d->createResolved(v);
}
template<class RangeT>
PromiseT all(RangeT promises) const {
using PromiseCombination::dataCombine;
using PromiseCombination::dataCombineNone;
if (promises.empty()) {
- return createResolved(dataCombineNone(bool{}));
+ return createResolved(dataCombineNone(DataT{}));
}
auto p1 = *(promises.begin());
promises.erase(promises.begin());
return p1.then([*this, promises=std::move(promises)](DataT val) mutable {
return all(std::move(promises))
.then([=](DataT val2) {
return dataCombine(val, val2);
});
});
}
private:
struct Concept
{
virtual ~Concept() = default;
virtual PromiseT create(std::function<void(ResolveT)> f) = 0;
virtual PromiseT createResolveToPromise(std::function<void(ResolveToPromiseT)> f) = 0;
virtual PromiseT createResolved(DataT v) = 0;
};
template<class DeriveT>
struct Model : public Concept
{
static_assert(std::is_same_v<std::decay_t<DeriveT>, DeriveT>, "DeriveT must not be a reference");
Model(DeriveT obj) : instance(std::move(obj)) {}
~Model() override = default;
PromiseT create(std::function<void(ResolveT)> f) override {
return instance.template create<DataT>(f);
}
PromiseT createResolveToPromise(std::function<void(ResolveToPromiseT)> f) override {
return instance.template create<DataT>(f);
}
PromiseT createResolved(DataT v) override {
return instance.createResolved(v);
}
DeriveT instance;
};
std::shared_ptr<Concept> m_d;
};
using BoolPromiseInterface = SingleTypePromiseInterface<bool>;
}
diff --git a/src/client/client.cpp b/src/client/client.cpp
index 08ef6b6..5e6071d 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -1,136 +1,143 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <libkazv-config.hpp>
#include <lager/constant.hpp>
#include "client.hpp"
namespace Kazv
{
Client::Client(lager::reader<SdkModel> sdk,
Context<ClientAction> ctx)
: m_sdk(sdk)
, m_client(sdk.map(&SdkModel::c))
, m_ctx(std::move(ctx))
{
}
Room Client::room(std::string id) const
{
return Room(m_sdk, lager::make_constant(id), m_ctx);
}
Room Client::roomByCursor(lager::reader<std::string> id) const
{
return Room(m_sdk, id, m_ctx);
}
- BoolPromise Client::passwordLogin(std::string homeserver, std::string username,
- std::string password, std::string deviceName) const
+ auto Client::passwordLogin(std::string homeserver, std::string username,
+ std::string password, std::string deviceName) const
+ -> PromiseT
{
return m_ctx.dispatch(LoginAction{
homeserver, username, password, deviceName});
}
- BoolPromise Client::tokenLogin(std::string homeserver, std::string username,
- std::string token, std::string deviceId) const
+ auto Client::tokenLogin(std::string homeserver, std::string username,
+ std::string token, std::string deviceId) const
+ -> PromiseT
{
return m_ctx.dispatch(TokenLoginAction{
homeserver, username, token, deviceId});
}
- BoolPromise Client::createRoom(RoomVisibility v,
- std::optional<std::string> name,
- std::optional<std::string> alias,
- immer::array<std::string> invite,
- std::optional<bool> isDirect,
- bool allowFederate,
- std::optional<std::string> topic,
- JsonWrap powerLevelContentOverride) const
+ auto Client::createRoom(RoomVisibility v,
+ std::optional<std::string> name,
+ std::optional<std::string> alias,
+ immer::array<std::string> invite,
+ std::optional<bool> isDirect,
+ bool allowFederate,
+ std::optional<std::string> topic,
+ JsonWrap powerLevelContentOverride) const
+ -> PromiseT
{
CreateRoomAction a;
a.visibility = v;
a.name = name;
a.roomAliasName = alias;
a.invite = invite;
a.isDirect = isDirect;
a.topic = topic;
a.powerLevelContentOverride = powerLevelContentOverride;
// Synapse won't buy it if we do not provide
// a creationContent object.
a.creationContent = json{
{"m.federate", allowFederate}
};
return m_ctx.dispatch(std::move(a));
}
- BoolPromise Client::joinRoomById(std::string roomId) const
+ auto Client::joinRoomById(std::string roomId) const -> PromiseT
{
return m_ctx.dispatch(JoinRoomByIdAction{roomId});
}
- BoolPromise Client::joinRoom(std::string roomId, immer::array<std::string> serverName) const
+ auto Client::joinRoom(std::string roomId, immer::array<std::string> serverName) const
+ -> PromiseT
{
return m_ctx.dispatch(JoinRoomAction{roomId, serverName});
}
- BoolPromise Client::uploadContent(immer::box<Bytes> content,
+ auto Client::uploadContent(immer::box<Bytes> content,
std::string uploadId,
std::optional<std::string> filename,
std::optional<std::string> contentType) const
+ -> PromiseT
{
return m_ctx.dispatch(UploadContentAction{content, filename, contentType, uploadId});
}
- BoolPromise Client::downloadContent(std::string mxcUri) const
+ auto Client::downloadContent(std::string mxcUri) const
+ -> PromiseT
{
return m_ctx.dispatch(DownloadContentAction{mxcUri});
}
- BoolPromise Client::downloadThumbnail(
+ auto Client::downloadThumbnail(
std::string mxcUri,
int width,
int height,
std::optional<ThumbnailResizingMethod> method) const
+ -> PromiseT
{
return m_ctx.dispatch(DownloadThumbnailAction{mxcUri, width, height, method, std::nullopt});
}
- BoolPromise Client::startSyncing() const
+ auto Client::startSyncing() const -> PromiseT
{
using namespace Kazv::CursorOp;
if (+syncing()) {
return m_ctx.createResolvedPromise(true);
}
// filters are incomplete
if ((+m_client[&ClientModel::initialSyncFilterId]).empty()
|| (+m_client[&ClientModel::incrementalSyncFilterId]).empty()) {
return m_ctx.dispatch(PostInitialFiltersAction{});
} else if (+m_client[&ClientModel::crypto] // encryption is on
&& ! +m_client[&ClientModel::identityKeysUploaded]) { // but identity keys are not published
return m_ctx.dispatch(UploadIdentityKeysAction{});
} else { // sync is just interrupted
return m_ctx.dispatch(SyncAction{});
}
}
}
diff --git a/src/client/client.hpp b/src/client/client.hpp
index 2908b67..4626a6f 100644
--- a/src/client/client.hpp
+++ b/src/client/client.hpp
@@ -1,124 +1,125 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <libkazv-config.hpp>
#include <lager/reader.hpp>
#include <immer/box.hpp>
#include <immer/map.hpp>
#include <immer/flex_vector.hpp>
#include <immer/flex_vector_transient.hpp>
#include "sdk-model.hpp"
#include "client/client-model.hpp"
#include "client/actions/content.hpp"
#include "room/room.hpp"
namespace Kazv
{
class Client
{
public:
+ using PromiseT = SingleTypePromise<DefaultRetType>;
Client(lager::reader<SdkModel> sdk,
Context<ClientAction> ctx);
/* lager::reader<immer::map<std::string, Room>> */
inline auto rooms() const {
return m_client
[&ClientModel::roomList]
[&RoomListModel::rooms];
}
/* lager::reader<RangeT<std::string>> */
inline auto roomIds() const {
return rooms().xform(
zug::map([](auto m) {
return intoImmer(
immer::flex_vector<std::string>{},
zug::map([](auto val) { return val.first; }),
m);
}));
}
KAZV_WRAP_ATTR(ClientModel, m_client, serverUrl)
KAZV_WRAP_ATTR(ClientModel, m_client, loggedIn)
KAZV_WRAP_ATTR(ClientModel, m_client, userId)
KAZV_WRAP_ATTR(ClientModel, m_client, token)
KAZV_WRAP_ATTR(ClientModel, m_client, deviceId)
KAZV_WRAP_ATTR(ClientModel, m_client, toDevice)
Room room(std::string id) const;
Room roomByCursor(lager::reader<std::string> id) const;
- BoolPromise passwordLogin(std::string homeserver, std::string username,
+ PromiseT passwordLogin(std::string homeserver, std::string username,
std::string password, std::string deviceName) const;
- BoolPromise tokenLogin(std::string homeserver, std::string username,
+ PromiseT tokenLogin(std::string homeserver, std::string username,
std::string token, std::string deviceId) const;
- BoolPromise createRoom(RoomVisibility v,
+ PromiseT createRoom(RoomVisibility v,
std::optional<std::string> name = {},
std::optional<std::string> alias = {},
immer::array<std::string> invite = {},
std::optional<bool> isDirect = {},
bool allowFederate = true,
std::optional<std::string> topic = {},
JsonWrap powerLevelContentOverride = json::object()) const;
- BoolPromise joinRoomById(std::string roomId) const;
+ PromiseT joinRoomById(std::string roomId) const;
- BoolPromise joinRoom(std::string roomId, immer::array<std::string> serverName) const;
+ PromiseT joinRoom(std::string roomId, immer::array<std::string> serverName) const;
- BoolPromise uploadContent(immer::box<Bytes> content,
+ PromiseT uploadContent(immer::box<Bytes> content,
std::string uploadId,
std::optional<std::string> filename = std::nullopt,
std::optional<std::string> contentType = std::nullopt) const;
inline std::string mxcUriToHttp(std::string mxcUri) const {
using namespace CursorOp;
auto [serverName, mediaId] = mxcUriToMediaDesc(mxcUri);
return (+m_client)
.job<GetContentJob>()
.make(serverName, mediaId).url();
}
- BoolPromise downloadContent(std::string mxcUri) const;
+ PromiseT downloadContent(std::string mxcUri) const;
- BoolPromise downloadThumbnail(std::string mxcUri,
+ PromiseT downloadThumbnail(std::string mxcUri,
int width,
int height,
std::optional<ThumbnailResizingMethod> method = std::nullopt) const;
// lager::reader<bool>
inline auto syncing() const {
return m_client[&ClientModel::syncing];
}
- BoolPromise startSyncing() const;
+ PromiseT startSyncing() const;
private:
lager::reader<SdkModel> m_sdk;
lager::reader<ClientModel> m_client;
Context<ClientAction> m_ctx;
};
}
diff --git a/src/client/room/room.cpp b/src/client/room/room.cpp
index 40a9220..c9144be 100644
--- a/src/client/room/room.cpp
+++ b/src/client/room/room.cpp
@@ -1,193 +1,207 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <libkazv-config.hpp>
#include <debug.hpp>
#include "room.hpp"
namespace Kazv
{
Room::Room(lager::reader<SdkModel> sdk,
lager::reader<std::string> roomId,
Context<ClientAction> ctx)
: m_sdk(sdk)
, m_room(lager::with(m_sdk.map(&SdkModel::c)[&ClientModel::roomList], roomId)
.map([](auto rooms, auto id) {
return rooms[id];
}).make())
, m_ctx(ctx)
{
}
- BoolPromise Room::setLocalDraft(std::string localDraft) const
+ auto Room::setLocalDraft(std::string localDraft) const
+ -> PromiseT
{
using namespace CursorOp;
return m_ctx.dispatch(UpdateRoomAction{+roomId(), SetLocalDraftAction{localDraft}});
}
- BoolPromise Room::sendMessage(Event msg) const {
+ auto Room::sendMessage(Event msg) const
+ -> PromiseT
+ {
using namespace CursorOp;
auto hasCrypto = ~m_sdk.map([](const auto &sdk) -> bool {
return sdk.c().crypto.has_value();
});
auto roomEncrypted = ~m_room[&RoomModel::encrypted];
auto noFullMembers = ~m_room[&RoomModel::membersFullyLoaded]
.map([](auto b) { return !b; });
auto rid = +roomId();
// Don't use m_ctx directly in the callbacks
// as `this` may have been destroyed when
// the callbacks are called.
auto ctx = m_ctx;
auto promise = ctx.createResolvedPromise(true);
if (+allCursors(hasCrypto, roomEncrypted, noFullMembers)) {
kzo.client.dbg() << "The members of " << rid
<< " are not fully loaded." << std::endl;
promise = promise
.then([=](auto) {
return ctx.dispatch(GetRoomStatesAction{rid});
})
.then([=](auto succ) {
if (! succ) {
kzo.client.warn() << "Loading members of " << rid
<< " failed." << std::endl;
return ctx.createResolvedPromise(false);
} else {
// XXX remove the hard-coded initialSync parameter
return ctx.dispatch(QueryKeysAction{true})
.then([](auto succ) {
if (! succ) {
kzo.client.warn() << "Query keys failed" << std::endl;
}
return succ;
});
}
});
}
return promise
.then([=](auto succ) {
if (! succ) {
return ctx.createResolvedPromise(false);
}
return ctx.dispatch(SendMessageAction{rid, msg});
});
}
- BoolPromise Room::sendTextMessage(std::string text) const
+ auto Room::sendTextMessage(std::string text) const
+ -> PromiseT
{
json j{
{"type", "m.room.message"},
{"content", {
{"msgtype", "m.text"},
{"body", text}
}
}
};
Event e{j};
return sendMessage(e);
}
- BoolPromise Room::refreshRoomState() const
+ auto Room::refreshRoomState() const
+ -> PromiseT
{
using namespace CursorOp;
return m_ctx.dispatch(GetRoomStatesAction{+roomId()});
}
- BoolPromise Room::getStateEvent(std::string type, std::string stateKey) const
+ auto Room::getStateEvent(std::string type, std::string stateKey) const
+ -> PromiseT
{
using namespace CursorOp;
return m_ctx.dispatch(GetStateEventAction{+roomId(), type, stateKey});
}
- BoolPromise Room::sendStateEvent(Event state) const
+ auto Room::sendStateEvent(Event state) const
+ -> PromiseT
{
using namespace CursorOp;
return m_ctx.dispatch(SendStateEventAction{+roomId(), state});
}
- BoolPromise Room::setName(std::string name) const
+ auto Room::setName(std::string name) const
+ -> PromiseT
{
json j{
{"type", "m.room.name"},
{"content", {
{"name", name}
}
}
};
Event e{j};
return sendStateEvent(e);
}
- BoolPromise Room::setTopic(std::string topic) const
+ auto Room::setTopic(std::string topic) const
+ -> PromiseT
{
json j{
{"type", "m.room.topic"},
{"content", {
{"topic", topic}
}
}
};
Event e{j};
return sendStateEvent(e);
}
- BoolPromise Room::invite(std::string userId) const
+ auto Room::invite(std::string userId) const
+ -> PromiseT
{
using namespace CursorOp;
return m_ctx.dispatch(InviteToRoomAction{+roomId(), userId});
}
- BoolPromise Room::setTyping(bool typing, std::optional<int> timeoutMs) const
+ auto Room::setTyping(bool typing, std::optional<int> timeoutMs) const
+ -> PromiseT
{
using namespace CursorOp;
return m_ctx.dispatch(SetTypingAction{+roomId(), typing, timeoutMs});
}
- BoolPromise Room::leave() const
+ auto Room::leave() const
+ -> PromiseT
{
using namespace CursorOp;
return m_ctx.dispatch(LeaveRoomAction{+roomId()});
}
- BoolPromise Room::forget() const
+ auto Room::forget() const
+ -> PromiseT
{
using namespace CursorOp;
return m_ctx.dispatch(ForgetRoomAction{+roomId()});
}
- BoolPromise Room::setPinnedEvents(immer::flex_vector<std::string> eventIds) const
+ auto Room::setPinnedEvents(immer::flex_vector<std::string> eventIds) const
+ -> PromiseT
{
json j{
{"type", "m.room.pinned_events"},
{"content", {
{"pinned", eventIds}
}
}
};
Event e{j};
return sendStateEvent(e);
}
}
diff --git a/src/client/room/room.hpp b/src/client/room/room.hpp
index f670ea1..7d327d0 100644
--- a/src/client/room/room.hpp
+++ b/src/client/room/room.hpp
@@ -1,256 +1,257 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <libkazv-config.hpp>
#include <lager/reader.hpp>
#include <lager/context.hpp>
#include <lager/with.hpp>
#include <lager/constant.hpp>
#include <lager/lenses/optional.hpp>
#include <zug/transducer/map.hpp>
#include <zug/transducer/filter.hpp>
#include <zug/sequence.hpp>
#include <immer/flex_vector_transient.hpp>
#include "debug.hpp"
#include "sdk-model.hpp"
#include "client-model.hpp"
#include "room-model.hpp"
#include <cursorutil.hpp>
namespace Kazv
{
class Room
{
public:
+ using PromiseT = SingleTypePromise<DefaultRetType>;
Room(lager::reader<SdkModel> sdk,
lager::reader<std::string> roomId,
Context<ClientAction> ctx);
/* lager::reader<MapT<KeyOfState, Event>> */
inline auto stateEvents() const {
return m_room
[&RoomModel::stateEvents];
}
/* lager::reader<std::optional<Event>> */
inline auto stateOpt(KeyOfState k) const {
return stateEvents()
[std::move(k)];
}
/* lager::reader<Event> */
inline auto state(KeyOfState k) const {
return stateOpt(k)
[lager::lenses::or_default];
}
/* lager::reader<RangeT<Event>> */
inline auto timelineEvents() const {
return m_room
.xform(zug::map([](auto r) {
auto messages = r.messages;
auto timeline = r.timeline;
return intoImmer(
immer::flex_vector<Event>{},
zug::map([=](auto eventId) {
return messages[eventId];
}),
timeline);
}));
}
/* lager::reader<std::string> */
inline auto name() const {
using namespace lager::lenses;
return stateEvents()
[KeyOfState{"m.room.name", ""}]
[or_default]
.xform(zug::map([](Event ev) {
auto content = ev.content().get();
return
content.contains("name")
? std::string(content["name"])
// TODO: use heroes to generate a name
: "<no name>";
}));
}
/* lager::reader<std::string> */
inline auto avatarMxcUri() const {
using namespace lager::lenses;
return stateEvents()
[KeyOfState{"m.room.avatar", ""}]
[or_default]
.xform(zug::map([](Event ev) {
auto content = ev.content().get();
return
content.contains("avatar")
? std::string(content["avatar"])
: "";
}));
}
/* lager::reader<RangeT<std::string>> */
inline auto members() const {
return m_room.xform(zug::map([=](auto room) {
return room.joinedMemberIds();
}));
}
inline auto memberEventByCursor(lager::reader<std::string> userId) const {
return lager::with(m_room[&RoomModel::stateEvents], userId)
.xform(zug::map([](auto events, auto userId) {
auto k = KeyOfState{"m.room.member", userId};
return events[k];
}));
}
/* lager::reader<std::optional<Event>> */
inline auto memberEventFor(std::string userId) const {
return memberEventByCursor(lager::make_constant(userId));
}
lager::reader<bool> encrypted() const;
/*lager::reader<std::string>*/
KAZV_WRAP_ATTR(RoomModel, m_room, roomId);
/*lager::reader<RoomMembership>*/
KAZV_WRAP_ATTR(RoomModel, m_room, membership);
/*lager::reader<std::string>*/
KAZV_WRAP_ATTR(RoomModel, m_room, localDraft);
/* lager::reader<bool> */
KAZV_WRAP_ATTR(RoomModel, m_room, membersFullyLoaded);
- BoolPromise setLocalDraft(std::string localDraft) const;
+ PromiseT setLocalDraft(std::string localDraft) const;
- BoolPromise sendMessage(Event msg) const;
+ PromiseT sendMessage(Event msg) const;
- BoolPromise sendTextMessage(std::string text) const;
+ PromiseT sendTextMessage(std::string text) const;
- BoolPromise refreshRoomState() const;
+ PromiseT refreshRoomState() const;
- BoolPromise getStateEvent(std::string type, std::string stateKey) const;
+ PromiseT getStateEvent(std::string type, std::string stateKey) const;
- BoolPromise sendStateEvent(Event state) const;
+ PromiseT sendStateEvent(Event state) const;
- BoolPromise setName(std::string name) const;
+ PromiseT setName(std::string name) const;
// lager::reader<std::string>
inline auto topic() const {
using namespace lager::lenses;
return stateEvents()
[KeyOfState{"m.room.topic", ""}]
[or_default]
.xform(eventContent
| jsonAtOr("topic"s, ""s));
}
- BoolPromise setTopic(std::string topic) const;
+ PromiseT setTopic(std::string topic) const;
- BoolPromise invite(std::string userId) const;
+ PromiseT invite(std::string userId) const;
/* lager::reader<MapT<std::string, Event>> */
inline auto ephemeralEvents() const {
return m_room
[&RoomModel::ephemeral];
}
/* lager::reader<std::optional<Event>> */
inline auto ephemeralOpt(std::string type) const {
return m_room
[&RoomModel::ephemeral]
[type];
}
/* lager::reader<Event> */
inline auto ephemeral(std::string type) const {
return m_room
[&RoomModel::ephemeral]
[type]
[lager::lenses::or_default];
}
/* lager::reader<RangeT<std::string>> */
inline auto typingUsers() const {
using namespace lager::lenses;
return ephemeral("m.typing")
.xform(eventContent
| jsonAtOr("user_ids",
immer::flex_vector<std::string>{}));
}
- BoolPromise setTyping(bool typing, std::optional<int> timeoutMs) const;
+ PromiseT setTyping(bool typing, std::optional<int> timeoutMs) const;
/* lager::reader<MapT<std::string, Event>> */
inline auto accountDataEvents() const {
return m_room
[&RoomModel::accountData];
}
/* lager::reader<std::optional<Event>> */
inline auto accountDataOpt(std::string type) const {
return m_room
[&RoomModel::accountData]
[type];
}
/* lager::reader<Event> */
inline auto accountData(std::string type) const {
return m_room
[&RoomModel::accountData]
[type]
[lager::lenses::or_default];
}
/* lager::reader<std::string> */
inline auto readMarker() const {
using namespace lager::lenses;
return accountData("m.fully_read")
.xform(eventContent
| jsonAtOr("event_id", std::string{}));
}
- BoolPromise leave() const;
+ PromiseT leave() const;
- BoolPromise forget() const;
+ PromiseT forget() const;
/* lager::reader<JsonWrap> */
inline auto avatar() const {
return state(KeyOfState{"m.room.avatar", ""})
.xform(eventContent);
}
/* lager::reader<RangeT<std::string>> */
inline auto pinnedEvents() const {
return state(KeyOfState{"m.room.pinned_events", ""})
.xform(eventContent
| jsonAtOr("pinned", immer::flex_vector<std::string>{}));
}
- BoolPromise setPinnedEvents(immer::flex_vector<std::string> eventIds) const;
+ PromiseT setPinnedEvents(immer::flex_vector<std::string> eventIds) const;
private:
lager::reader<SdkModel> m_sdk;
lager::reader<RoomModel> m_room;
Context<ClientAction> m_ctx;
};
}
diff --git a/src/store/context.hpp b/src/store/context.hpp
index 46dbc87..73fa1cf 100644
--- a/src/store/context.hpp
+++ b/src/store/context.hpp
@@ -1,152 +1,196 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <lager/deps.hpp>
#include <boost/hana.hpp>
+#include <jsonwrap.hpp>
#include <promise-interface.hpp>
namespace Kazv
{
+ class EffectStatus
+ {
+ public:
+ inline EffectStatus() : m_succ(true) {}
+ inline EffectStatus(bool succ) : m_succ(succ) {}
+ inline EffectStatus(bool succ, JsonWrap d) : m_succ(succ), m_data(d) {}
+
+ inline bool success() const { return m_succ; }
+ inline explicit operator bool() const { return success(); }
+ inline const JsonWrap &data() const { return m_data; }
+ private:
+ bool m_succ;
+ JsonWrap m_data;
+ };
+
+ inline EffectStatus createDefaultForPromiseThen(EffectStatus)
+ {
+ return EffectStatus{true};
+ }
+
+ inline EffectStatus dataCombine(EffectStatus a, EffectStatus b)
+ {
+ auto succ = a.success() && b.success();
+ auto data = a.data().get().is_array()
+ ? a.data().get()
+ : json::array({a.data().get()});
+ if (b.data().get().is_array()) {
+ for (const auto &i: b.data().get()) {
+ data.push_back(i);
+ }
+ } else {
+ data.push_back(b.data().get());
+ }
+ return {succ, data};
+ }
+
+ inline EffectStatus dataCombineNone(EffectStatus)
+ {
+ return EffectStatus(true);
+ }
+
+ using DefaultRetType = EffectStatus;
+
template<class T, class Action, class Deps = lager::deps<>>
class ContextBase : public Deps
{
public:
using RetType = T;
using PromiseInterfaceT = SingleTypePromiseInterface<RetType>;
using PromiseT = SingleTypePromise<RetType>;
template<class Func>
ContextBase(Func &&dispatcher, PromiseInterfaceT ph, Deps deps)
: Deps(std::move(deps))
, m_ph(ph)
, m_dispatcher(std::forward<Func>(dispatcher))
{
}
template<class AnotherAction, class AnotherDeps,
std::enable_if_t<std::is_convertible_v<Action, AnotherAction>
&& std::is_convertible_v<AnotherDeps, Deps>, int> = 0>
ContextBase(const ContextBase<RetType, AnotherAction, AnotherDeps> &that)
: Deps(that)
, m_ph(that.m_ph)
, m_dispatcher(that.m_dispatcher)
{
}
template<class AnotherAction, class AnotherDeps, class Conv,
std::enable_if_t<std::is_convertible_v<std::invoke_result_t<Conv, Action>, AnotherAction>
&& std::is_convertible_v<AnotherDeps, Deps>, int> = 0>
ContextBase(const ContextBase<RetType, AnotherAction, AnotherDeps> &that, Conv &&conv)
: Deps(that)
, m_ph(that.m_ph)
, m_dispatcher([=,
thatDispatcher=that.m_dispatcher,
conv=std::forward<Conv>(conv)](Action a) {
thatDispatcher(conv(std::move(a)));
})
{
}
PromiseT dispatch(Action a) const {
return m_dispatcher(std::move(a));
}
decltype(auto) deps() {
return static_cast<Deps &>(*this);
}
template<class Func>
PromiseT createPromise(Func func) const {
return m_ph.create(std::move(func));
}
template<class Func>
PromiseT createWaitingPromise(Func func) const {
return m_ph.createResolveToPromise(std::move(func));
}
- PromiseT createResolvedPromise(bool v) const {
+ PromiseT createResolvedPromise(RetType v) const {
return m_ph.createResolved(v);
}
PromiseInterfaceT promiseInterface() const {
return m_ph;
}
private:
template<class R2, class A2, typename D2>
friend class ContextBase;
PromiseInterfaceT m_ph;
std::function<PromiseT(Action)> m_dispatcher;
};
template<class A, class D = lager::deps<>>
- using Context = ContextBase<bool, A, D>;
+ using Context = ContextBase<DefaultRetType, A, D>;
template<class T, class Action, class Deps = lager::deps<>>
class EffectBase
{
public:
using RetType = T;
using PromiseT = SingleTypePromise<RetType>;
using ContextT = ContextBase<RetType, Action, Deps>;
template<class Func>
EffectBase(Func func) {
if constexpr (std::is_same_v<std::invoke_result_t<Func, ContextT>, void>) {
m_d = [func=std::move(func)](const auto &ctx) {
return ctx.createPromise(
[ctx,
func](auto resolve) {
func(ctx);
resolve(PromiseCombination::defaultForPromiseThen(RetType()));
});
};
} else {
m_d = [func=std::move(func)](const auto &ctx) {
- return ctx.createResolvedPromise(true)
+ return ctx.createResolvedPromise(PromiseCombination::defaultForPromiseThen(RetType()))
.then([ctx,
func](auto) {
return func(ctx);
});
};
}
}
PromiseT operator()(const ContextT &ctx) const {
return m_d(ctx);
}
private:
std::function<PromiseT(const ContextT &)> m_d;
};
template<class A, class D = lager::deps<>>
- using Effect = EffectBase<bool, A, D>;
+ using Effect = EffectBase<DefaultRetType, A, D>;
- template<class Reducer, class Model, class Action, class Deps>
+ template<class Reducer, class RetType, class Model, class Action, class Deps>
constexpr bool hasEffect = boost::hana::is_valid(
- []() -> decltype((void)Effect<Action, Deps>(
+ []() -> decltype((void)EffectBase<RetType, Action, Deps>(
std::get<1>(std::declval<std::invoke_result_t<Reducer, Model, Action>>()))) {}
)();
}
diff --git a/src/store/store.hpp b/src/store/store.hpp
index 704abf7..dbca3b4 100644
--- a/src/store/store.hpp
+++ b/src/store/store.hpp
@@ -1,172 +1,183 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <lager/state.hpp>
#include <lager/deps.hpp>
#include <lager/util.hpp>
#include <lager/context.hpp>
#include <lager/store.hpp>
#include "context.hpp"
namespace Kazv
{
template<class Action,
+ class EffectRetType,
class Model,
class Reducer,
class Deps = lager::deps<>,
class Tag = lager::automatic_tag>
- class Store
+ class StoreBase
{
public:
- using ContextT = Context<Action, Deps>;
+ using RetType = EffectRetType;
+ using PromiseInterfaceT = SingleTypePromiseInterface<RetType>;
+ using PromiseT = SingleTypePromise<RetType>;
+ using ContextT = ContextBase<RetType, Action, Deps>;
template<class PH>
- Store(Model initialModel, Reducer reducer, PH &&ph, Deps deps)
+ StoreBase(Model initialModel, Reducer reducer, PH &&ph, Deps deps)
: m_d{std::make_shared<Private>(
lager::make_state(std::move(initialModel), Tag{}),
reducer,
- BoolPromiseInterface{std::forward<PH>(ph)},
+ PromiseInterfaceT{std::forward<PH>(ph)},
deps)}
{}
- Store(const Store &that) = delete;
- Store &operator=(const Store &that) = delete;
+ StoreBase(const StoreBase &that) = delete;
+ StoreBase &operator=(const StoreBase &that) = delete;
- Store(Store &&that)
+ StoreBase(StoreBase &&that)
: m_d(std::move(that.m_d))
{}
- Store &operator=(Store &&that) {
+ StoreBase &operator=(StoreBase &&that) {
m_d = std::move(that.m_d);
return *this;
}
- auto dispatch(Action a) -> BoolPromise {
+ auto dispatch(Action a) -> PromiseT {
return m_d->dispatch(std::move(a));
};
operator lager::reader<Model>() const {
return m_d->state;
}
lager::reader<Model> reader() const {
return m_d->state;
}
template<class A2, class D2>
operator Context<A2, D2>() const {
return m_d->ctx;
}
ContextT context() const {
return m_d->ctx;
}
private:
struct Private
{
Private(lager::state<Model, Tag> state,
Reducer reducer,
- BoolPromiseInterface ph,
+ PromiseInterfaceT ph,
Deps deps)
: state(std::move(state))
, reducer(std::move(reducer))
, ph(std::move(ph))
- // this Private will remain untouched between moves of Stores
+ // this Private will remain untouched between moves of StoreBases
// so it is safe to capture by referenced
, ctx([this](auto a) { return dispatch(std::move(a)); }, this->ph, deps)
{}
~Private() = default;
lager::state<Model, Tag> state;
Reducer reducer;
- BoolPromiseInterface ph;
+ PromiseInterfaceT ph;
ContextT ctx;
- auto dispatch(Action a) -> BoolPromise {
+ auto dispatch(Action a) -> PromiseT {
return ph.createResolved(true)
.then(
[this, a=std::move(a)](auto) {
- if constexpr (hasEffect<Reducer, Model, Action, Deps>) {
+ if constexpr (hasEffect<Reducer, RetType, Model, Action, Deps>) {
auto [newModel, eff] = reducer(state.get(), a);
state.set(newModel);
return eff(ctx);
} else {
auto newModel = reducer(state.get(), a);
state.set(newModel);
return ph.createResolved(true);
}
});
}
};
std::shared_ptr<Private> m_d;
};
+ template<class Action,
+ class Model,
+ class Reducer,
+ class Deps = lager::deps<>,
+ class Tag = lager::automatic_tag>
+ using Store = StoreBase<Action, DefaultRetType, Model, Reducer, Deps, Tag>;
+
namespace detail
{
constexpr auto compose()
{
return zug::identity;
}
template<class F1, class ...Fs>
constexpr auto compose(F1 &&f1, Fs &&...fs)
{
return zug::comp(std::forward<F1>(f1), std::forward<Fs>(fs)...);
}
}
template<class Action,
class Tag = lager::automatic_tag,
class Model,
class Reducer,
class PH,
class ...Enhancers
>
auto makeStore(Model &&initialModel, Reducer &&reducer, PH &&ph, Enhancers &&...enhancers)
{
auto enhancer = detail::compose(std::forward<Enhancers>(enhancers)...);
auto factory = enhancer(
[&](auto actionTraits,
auto &&model,
auto &&reducer,
auto &&ph,
auto &&deps) {
using ActionTraits = decltype(actionTraits);
using ModelF = decltype(model);
using ReducerF = decltype(reducer);
using PHF = decltype(ph);
using DepsF = decltype(deps);
using ActionT = typename ActionTraits::type;
using ModelT = std::decay_t<ModelF>;
using ReducerT = std::decay_t<ReducerF>;
using DepsT = std::decay_t<DepsF>;
return Store<ActionT, ModelT, ReducerT, DepsT, Tag>(
std::forward<ModelF>(initialModel), std::forward<ReducerF>(reducer),
std::forward<PHF>(ph), std::forward<DepsF>(deps));
});
return factory(
lager::type_<Action>{},
std::forward<Model>(initialModel), std::forward<Reducer>(reducer),
std::forward<PH>(ph), lager::deps<>{});
}
}
diff --git a/src/tests/client/client-test-util.hpp b/src/tests/client/client-test-util.hpp
index 1eea34c..86496d9 100644
--- a/src/tests/client/client-test-util.hpp
+++ b/src/tests/client/client-test-util.hpp
@@ -1,42 +1,42 @@
/*
* Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <libkazv-config.hpp>
#include <lager/store.hpp>
#include <lager/event_loop/manual.hpp>
#include <store.hpp>
#include <client/client-model.hpp>
#include <base/basejob.hpp>
using namespace Kazv;
ClientModel createTestClientModel();
Response createResponse(std::string jobId, JsonWrap j, JsonWrap data = {});
-inline auto createTestClientStore(BoolPromiseInterface ph)
+inline auto createTestClientStore(SingleTypePromiseInterface<DefaultRetType> ph)
{
return makeStore<ClientAction>(
createTestClientModel(),
&ClientModel::update,
std::move(ph));
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 3:20 PM (23 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55265
Default Alt Text
(56 KB)

Event Timeline