Page MenuHomePhorge

No OneTemporary

Size
22 KB
Referenced Files
None
Subscribers
None
diff --git a/src/client/client.hpp b/src/client/client.hpp
index c4b91c5..e547147 100644
--- a/src/client/client.hpp
+++ b/src/client/client.hpp
@@ -1,270 +1,269 @@
/*
* 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
{
/**
* Represent a Matrix client.
*/
class Client
{
public:
using PromiseT = SingleTypePromise<DefaultRetType>;
/**
* Constructor.
*
* Construct the client.
*/
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)
/**
* Get the room with @c id .
*
* This is equivalent to `roomByCursor(lager::make_constant(id))`.
*
* @param id The room id.
* @return A Room representing the room with `id`.
*/
Room room(std::string id) const;
/**
* Get the room with `id`.
*
* The Room returned will change as the content in `id` changes.
*
* For example, you can have the Room that is always the first
* alphabetically in all rooms by:
*
* \code{.cpp}
* auto someProcessing =
* zug::map([=](auto ids) {
* std::sort(ids.begin(), ids.end(), [=](auto id1, auto id2) {
* using namespace Kazv::CursorOp;
* return (+client.room(id1).name()) < (+client.room(id2).name());
* });
* return ids;
* });
* auto room =
* client.roomByCursor(
* client.roomIds().xform(someProcessing)[0]);
* \endcode
*
* @param id A lager::reader<std::string> containing the room id.
* @return A Room representing the room with `id`.
*/
Room roomByCursor(lager::reader<std::string> id) const;
/**
* Login using the password.
*
* This will create a new session on the homeserver.
*
* @param homeserver The base url of the homeserver. E.g. `https://tusooa.xyz`.
* @param username The username. This can be the full user id or
* just the local part. E.g. `tusooa`, `@tusooa:tusooa.xyz`.
* @param password The password.
* @param deviceName Optionally, a custom device name. If empty, `libkazv`
* will be used.
* @return A Promise that resolves when logging in successfully, or
* when there is an error.
*/
PromiseT passwordLogin(std::string homeserver, std::string username,
std::string password, std::string deviceName) const;
/**
* Login using `token` and `deviceId`.
*
* This will not make a request. Library users should make sure
* the information is correct and the token and the device id are valid.
*
* @param homeserver The base url of the homeserver. E.g. `https://tusooa.xyz`.
* @param username The full user id. E.g. `@tusooa:tusooa.xyz`.
* @param token The access token.
* @param deviceId The device id that is paired with `token`.
* @return A Promise that resolves when the account information is filled in.
*/
PromiseT tokenLogin(std::string homeserver, std::string username,
std::string token, std::string deviceId) const;
/**
* Create a room.
*
* @param v The visibility of the room.
* @param name The name of the room.
* @param alias The alias of the room.
* @param invite User ids to invite to this room.
* @param isDirect Whether this room is a direct chat.
* @param allowFederate Whether to allow users from other homeservers
* to join this room.
* @param topic The topic of the room.
* @param powerLevelContentOverride The content of the m.room.power_levels
* state event to override the default.
* @return A Promise that resolves when the room is created,
* or when there is an error.
*/
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;
/**
* Join a room by its id.
*
* @param roomId The id of the room to join.
* @return A Promise that resolves when the room is joined,
* or when there is an error.
*/
PromiseT joinRoomById(std::string roomId) const;
/**
* Join a room by its id or alias.
*
* @param roomId The id *or alias* of the room to join.
* @param serverName A list of servers to use when joining the room.
* This corresponds to the `via` parameter in a matrix.to url.
* @return A Promise that resolves when the room is joined,
* or when there is an error.
*/
PromiseT joinRoom(std::string roomId, immer::array<std::string> serverName) const;
/**
* Upload content to the content repository.
*
* @param content The content to upload.
* @param uploadId
* @param filename The name of the file.
* @param contentType The content type of the file.
* @return A Promise that resolves when the upload is successful,
* or when there is an error. If it successfully resolves to `r`,
- * `r.data().get().at("mxcUri").template get<std::string>()`
- * will be the MXC URI of the uploaded
+ * `r.dataStr("mxcUri")` will be the MXC URI of the uploaded
* 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;
/**
* Convert a MXC URI to an HTTP(s) URI.
*
* The converted URI will be using the homeserver of
* this Client.
*
* @param mxcUri The MXC URI to convert.
* @return The HTTP(s) URI that has the content indicated
* by `mxcUri`.
*/
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();
}
/**
* Download content from the content repository
*
* @param mxcUri The MXC URI of the content.
* @return A Promise that is resolved after the content
* is downloaded, or when there is an error.
*/
PromiseT downloadContent(std::string mxcUri) const;
/**
* Download a thumbnail from the content repository
*
* @param mxcUri The MXC URI of the content.
* @param width,height The dimension wanted for the thumbnail
* @param method The method to generate the thumbnail. Either `Crop`
* or `Scale`.
* @return A Promise that is resolved after the thumbnail
* is downloaded, or when there is an error.
*/
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];
}
/**
* Start syncing if the Client is not syncing.
*
* @return A Promise that resolves when the Client is syncing
* (more exactly, when syncing() contains true), or when there is an error.
*/
PromiseT startSyncing() const;
private:
lager::reader<SdkModel> m_sdk;
lager::reader<ClientModel> m_client;
Context<ClientAction> m_ctx;
};
}
diff --git a/src/store/context.hpp b/src/store/context.hpp
index 0a4bce5..44aa403 100644
--- a/src/store/context.hpp
+++ b/src/store/context.hpp
@@ -1,211 +1,278 @@
/*
* 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) {}
+ /**
+ * Get whether this is successful.
+ *
+ * @return Whether this is successful.
+ */
inline bool success() const { return m_succ; }
+ /**
+ * Conversion to bool.
+ *
+ * @return `success()`.
+ */
inline explicit operator bool() const { return success(); }
+ /**
+ * Get the attached data.
+ *
+ * @return A JsonWrap containing the attached data.
+ */
inline const JsonWrap &data() const { return m_data; }
+
+ /**
+ * Get the data at `key`.
+ *
+ * This requires `data()` to contain a json object.
+ *
+ * @param key The key.
+ * @return A json with the data at `key`.
+ */
+ inline const json &dataJson(std::string key) const {
+ return data().get().at(key);
+ }
+
+ /**
+ * Get the data at `index` at `key`.
+ *
+ * This requires `data()` to contain a json array of json objects.
+ *
+ * @param index The index.
+ * @param key The key.
+ * @return A json with the data at `index` at `key`.
+ */
+ inline const json &dataJson(int index, std::string key) const {
+ return data().get().at(index).at(key);
+ }
+
+ /**
+ * Get the data string at `key`.
+ *
+ * This requires `data()` to contain a json object, and
+ * `data().get().at(key)` to be a json string.
+ *
+ * @param key The key.
+ * @return A std::string for the data at `key`.
+ */
+ inline std::string dataStr(std::string key) const {
+ return dataJson(key).template get<std::string>();
+ }
+
+ /**
+ * Get the data at `index` at `key`.
+ *
+ * This requires `data()` to contain a json array of json objects,
+ * and `data().get().at(index).at(key)` to be a json string.
+ *
+ * @param index The index.
+ * @param key The key.
+ * @return A std::string for the data at `index` at `key`.
+ */
+ inline std::string dataStr(int index, std::string key) const {
+ return dataJson(index, key).template get<std::string>();
+ }
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(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<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>;
/**
* Constructor.
*
* @return An effect that runs `func` upon invocation.
* The effect will return a Promise that
* \arg resolves after the effect is invoked, to
* `PromiseCombination::defaultForPromiseThen(RetType())`,
* if `func(ctx)` returns void;
* \arg resolves after `func(ctx)` is resolved, to
* what `func(ctx)` resolves to, if `func(ctx)`
* returns a Promise;
* \arg resolves after the effect is invoked, to
* `func(ctx)` (converted to `RetType` if it is not already one),
* otherwise.
*/
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(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<DefaultRetType, A, D>;
template<class Reducer, class RetType, class Model, class Action, class Deps>
constexpr bool hasEffect = boost::hana::is_valid(
[]() -> decltype((void)EffectBase<RetType, Action, Deps>(
std::get<1>(std::declval<std::invoke_result_t<Reducer, Model, Action>>()))) {}
)();
}
diff --git a/src/tests/client/content-test.cpp b/src/tests/client/content-test.cpp
index dc2fe00..42c5ce0 100644
--- a/src/tests/client/content-test.cpp
+++ b/src/tests/client/content-test.cpp
@@ -1,58 +1,58 @@
/*
* 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 <catch2/catch.hpp>
#include <boost/asio.hpp>
#include <asio-promise-handler.hpp>
#include <cursorutil.hpp>
#include <sdk-model.hpp>
#include <client/client.hpp>
#include "client-test-util.hpp"
// The example response is adapted from https://matrix.org/docs/spec/client_server/latest
static json uploadContentResponseJson = R"({
"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
})"_json;
TEST_CASE("Upload content should give the mxc uri", "[client][content]")
{
using namespace Kazv::CursorOp;
boost::asio::io_context io;
AsioPromiseHandler ph{io.get_executor()};
auto store = createTestClientStore(ph);
auto resp = createResponse("UploadContent", uploadContentResponseJson, json{{"uploadId", "whatever"}});
store.dispatch(ProcessResponseAction{resp})
.then([](auto stat) {
REQUIRE(stat.success());
- auto mxcUri = stat.data().get().at("mxcUri").template get<std::string>();
+ auto mxcUri = stat.dataStr("mxcUri");
REQUIRE(mxcUri == std::string("mxc://example.com/AQwafuaFswefuhsfAFAgsw"));
});
io.run();
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 9, 4:11 PM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784787
Default Alt Text
(22 KB)

Event Timeline