Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F140211
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
56 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 3:20 PM (19 h, 43 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55265
Default Alt Text
(56 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment