Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F140283
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
62 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0494ed1..5064c7c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,85 +1,85 @@
include(linklibsys)
include_directories(job . csapi)
set(FULL_CSAPI_DIR csapi)
file(GLOB_RECURSE api_SRCS ${FULL_CSAPI_DIR}/*.cpp)
set(libkazv_SRCS
# api/wellknown.cpp
${api_SRCS}
debug.cpp
event.cpp
job/basejob.cpp
client/client.cpp
client/sync.cpp
client/paginate.cpp
- client/data/room.cpp
+ client/room/room.cpp
)
add_library(kazv ${libkazv_SRCS})
add_library(libkazv::kazv ALIAS kazv)
target_link_libraries_system(kazv
PUBLIC nlohmann_json::nlohmann_json
immer
zug
lager
cereal)
target_include_directories(kazv
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/kazv>
)
install(
TARGETS kazv
LIBRARY
)
set(libkazvjob_SRCS
job/cprjobhandler.cpp
)
if(libkazv_BUILD_KAZVJOB)
add_library(kazvjob ${libkazvjob_SRCS})
add_library(libkazv::kazvjob ALIAS kazvjob)
target_link_libraries(kazvjob PUBLIC Threads::Threads)
target_link_libraries_system(kazvjob PUBLIC
cpr::cpr
nlohmann_json::nlohmann_json
immer
zug
lager
cereal
)
target_include_directories(kazvjob
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/kazv>
)
install( TARGETS kazvjob LIBRARY )
endif()
if(libkazv_INSTALL_HEADERS)
install(
DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/kazv"
FILES_MATCHING PATTERN "*.hpp"
PATTERN "tests" EXCLUDE
)
endif()
# export(TARGETS kazv)
if(libkazv_BUILD_TESTS)
add_subdirectory(tests)
endif()
if(libkazv_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
diff --git a/src/client/client.cpp b/src/client/client.cpp
index 9a31990..67044a8 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -1,235 +1,238 @@
/*
* 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 <lager/util.hpp>
#include <lager/context.hpp>
#include <functional>
// eager requires tuplify but did not include it.
// This is a bug.
#include <zug/tuplify.hpp>
#include <zug/transducer/eager.hpp>
#include <zug/into.hpp>
#include <immer/flex_vector_transient.hpp>
#include "client.hpp"
#include "csapi/login.hpp"
#include "types.hpp"
#include "debug.hpp"
#include "job/jobinterface.hpp"
+#include "eventemitter/eventinterface.hpp"
#include "client/util.hpp"
namespace Kazv
{
Client::Effect loginEffect(Client::LoginAction a)
{
return
[=](auto &&ctx) {
LoginJob job(a.serverUrl,
"m.login.password"s, // type
UserIdentifier{ "m.id.user"s, json{{"user", a.username}} }, // identifier
a.password,
{}, // token, not used
{}, // device id, not used
a.deviceName.value_or("libkazv"));
auto &jobHandler = lager::get<JobInterface &>(ctx);
jobHandler.fetch(
job,
[=](BaseJob::Response r) {
if (LoginJob::success(r)) {
dbgClient << "Job success" << std::endl;
const json &j = jsonBody(r).get();
std::string serverUrl = j.contains("well_known")
? j.at("well_known").at("m.homeserver").at("base_url").get<std::string>()
: a.serverUrl;
ctx.dispatch(Client::LoadUserInfoAction{
serverUrl,
j.at("user_id"),
j.at("access_token"),
j.at("device_id"),
/* loggedIn = */ true
});
- // after user info is loaded, do first sync
- ctx.dispatch(Client::SyncAction{});
}
});
};
}
lager::effect<Client::Action> logoutEffect(Client::LogoutAction)
{
return
[=](auto &&ctx) {
ctx.dispatch(Client::LoadUserInfoAction{
""s,
""s,
""s,
""s,
/* loggedIn = */ true
});
};
}
static void loadRoomsFromSyncInPlace(Client &m, SyncJob::Rooms rooms)
{
auto l = m.roomList;
auto updateRoomImpl =
[&l](auto id, auto a) {
l = RoomList::update(
std::move(l),
RoomList::UpdateRoomAction{id, a});
};
auto updateSingleRoom =
[updateRoomImpl](auto id, auto room, auto membership) {
updateRoomImpl(id, Room::ChangeMembershipAction{membership});
updateRoomImpl(id, Room::AppendTimelineAction{room.timeline.events});
if (room.state) {
updateRoomImpl(id, Room::AddStateEventsAction{room.state.value().events});
}
if (room.accountData) {
updateRoomImpl(id, Room::AddAccountDataAction{room.accountData.value().events});
}
};
auto updateJoinedRoom =
[=](auto id, auto room) {
updateSingleRoom(id, room, Room::Membership::Join);
};
auto updateLeftRoom =
[=](auto id, auto room) {
updateSingleRoom(id, room, Room::Membership::Leave);
};
for (auto &&[id, room]: rooms.join) {
updateJoinedRoom(id, room);
// TODO update other info such as
// ephemeral, notification and summary
}
// TODO update info for invited rooms
for (auto &&[id, room]: rooms.leave) {
updateLeftRoom(id, room);
}
m.roomList = l;
}
static void loadPresenceFromSyncInPlace(Client &m, EventList presence)
{
m.presence = merge(std::move(m.presence), presence, keyOfPresence);
}
static void loadAccountDataFromSyncInPlace(Client &m, EventList accountData)
{
m.accountData = merge(std::move(m.accountData), accountData, keyOfAccountData);
}
auto Client::update(Client m, Action a) -> Result
{
return lager::match(std::move(a))(
[=](Error::Action a) mutable -> Result {
m.error = Error::update(m.error, a);
return {std::move(m), lager::noop};
},
[=](RoomList::Action a) mutable -> Result {
m.roomList = RoomList::update(std::move(m.roomList), a);
return {std::move(m), lager::noop};
},
[=](LoginAction a) mutable -> Result {
return {std::move(m), loginEffect(std::move(a))};
},
[=](LogoutAction a) mutable -> Result {
return {std::move(m), logoutEffect(std::move(a))};
},
[=](SyncAction a) -> Result {
return {m, syncEffect(m, a)};
},
[=](LoadUserInfoAction a) mutable -> Result {
dbgClient << "LoadUserInfoAction: " << a.userId << std::endl;
m.serverUrl = a.serverUrl;
m.userId = a.userId;
m.token = a.token;
m.deviceId = a.deviceId;
m.loggedIn = a.loggedIn;
return { std::move(m),
[](auto &&ctx) {
+ auto &eventEmitter = lager::get<EventInterface &>(ctx);
+ eventEmitter.emit(LoginSuccessful{});
ctx.dispatch(Error::SetErrorAction{Error::NoError{}});
+ // after user info is loaded, do first sync
+ ctx.dispatch(Client::SyncAction{});
}
};
},
[=](LoadSyncResultAction a) mutable -> Result {
m.syncToken = a.syncToken;
if (a.rooms) {
loadRoomsFromSyncInPlace(m, a.rooms.value());
}
if (a.presence) {
loadPresenceFromSyncInPlace(m, a.presence.value().events);
}
if (a.accountData) {
loadAccountDataFromSyncInPlace(m, a.accountData.value().events);
}
return { std::move(m), lager::noop };
},
[&](Client::PaginateTimelineAction a) -> Result {
auto eff = paginateTimelineEffect(m, a);
return { std::move(m), eff };
},
[&](Client::LoadPaginateTimelineResultAction a) -> Result {
try {
auto room = m.roomList.at(a.roomId);
immer::flex_vector_transient<Event> events{};
// The timeline from paginate backwards is returned
// in reversed order, so restore the order.
zug::into(events, zug::reversed, a.events);
Room::PrependTimelineAction action
{events.persistent(), a.paginateBackToken};
auto roomList = RoomList::update(
std::move(m.roomList),
RoomList::UpdateRoomAction{a.roomId, action});
m.roomList = std::move(roomList);
return { std::move(m), lager::noop };
} catch (const std::out_of_range &e) {
// Ignore it, the client model is modified
// such that it knows nothing about this room.
// May happen in debugger.
return { std::move(m), lager::noop };
}
}
);
}
}
diff --git a/src/client/client.hpp b/src/client/client.hpp
index 7ca3032..f5aa2d8 100644
--- a/src/client/client.hpp
+++ b/src/client/client.hpp
@@ -1,182 +1,182 @@
/*
* 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 <tuple>
#include <variant>
#include <string>
#include <optional>
#include <lager/context.hpp>
#include <boost/hana.hpp>
#ifndef NDEBUG
#include <lager/debug/cereal/struct.hpp>
#endif
#include "csapi/sync.hpp"
#include "job/jobinterface.hpp"
#include "eventemitter/eventinterface.hpp"
#include "error.hpp"
-#include "data/room.hpp"
+#include "room/room.hpp"
namespace Kazv
{
struct Client
{
std::string serverUrl;
std::string userId;
std::string token;
std::string deviceId;
bool loggedIn;
Error error;
std::string syncToken;
RoomList roomList;
immer::map<std::string /* sender */, Event> presence;
immer::map<std::string /* type */, Event> accountData;
// helpers
template<class Job>
struct MakeJobT
{
template<class ...Args>
constexpr auto make(Args &&...args) const {
if constexpr (Job::needsAuth()) {
return Job(
serverUrl,
token,
std::forward<Args>(args)...);
} else {
return Job(
serverUrl,
std::forward<Args>(args)...);
}
}
std::string serverUrl;
std::string token;
};
template<class Job>
constexpr auto job() const {
return MakeJobT<Job>{serverUrl, token};
}
// actions:
struct LoginAction {
std::string serverUrl;
std::string username;
std::string password;
std::optional<std::string> deviceName;
};
struct LoadUserInfoAction {
std::string serverUrl;
std::string userId;
std::string token;
std::string deviceId;
bool loggedIn;
};
struct LogoutAction {};
struct SyncAction {};
struct LoadSyncResultAction
{
std::string syncToken;
std::optional<SyncJob::Rooms> rooms;
std::optional<EventBatch> presence;
std::optional<EventBatch> accountData;
JsonWrap toDevice;
JsonWrap deviceLists;
immer::map<std::string, int> deviceOneTimeKeysCount;
};
struct PaginateTimelineAction
{
std::string roomId;
std::optional<int> limit;
};
struct LoadPaginateTimelineResultAction
{
std::string roomId;
EventList events;
std::string paginateBackToken;
};
using Action = std::variant<LoginAction,
LogoutAction,
LoadUserInfoAction,
SyncAction,
Error::Action,
LoadSyncResultAction,
PaginateTimelineAction,
LoadPaginateTimelineResultAction,
RoomList::Action
>;
using Effect = lager::effect<Action, lager::deps<JobInterface &, EventInterface &>>;
using Result = std::pair<Client, Effect>;
static Result update(Client m, Action a);
};
inline bool operator==(Client a, Client b)
{
return a.serverUrl == b.serverUrl
&& a.userId == b.userId
&& a.token == b.token
&& a.deviceId == b.deviceId
&& a.loggedIn == b.loggedIn
&& a.error == b.error
&& a.syncToken == b.syncToken
&& a.roomList == b.roomList
&& a.presence == b.presence
&& a.accountData == b.accountData;
}
Client::Effect syncEffect(Client m, Client::SyncAction a);
Client::Effect paginateTimelineEffect(Client m, Client::PaginateTimelineAction a);
#ifndef NDEBUG
LAGER_CEREAL_STRUCT(Client::LoginAction);
LAGER_CEREAL_STRUCT(Client::LoadUserInfoAction);
LAGER_CEREAL_STRUCT(Client::LogoutAction);
LAGER_CEREAL_STRUCT(Client::SyncAction);
LAGER_CEREAL_STRUCT(Client::LoadSyncResultAction);
LAGER_CEREAL_STRUCT(Client::PaginateTimelineAction);
LAGER_CEREAL_STRUCT(Client::LoadPaginateTimelineResultAction);
#endif
template<class Archive>
void serialize(Archive &ar, Client &m, std::uint32_t const /*version*/)
{
ar(m.serverUrl, m.userId, m.token, m.deviceId, m.loggedIn,
m.error,
m.syncToken,
m.roomList,
m.presence,
m.accountData);
}
}
CEREAL_CLASS_VERSION(Kazv::Client, 0);
diff --git a/src/client/clientwrap.cpp b/src/client/clientwrap.cpp
new file mode 100644
index 0000000..d2a151d
--- /dev/null
+++ b/src/client/clientwrap.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 <lager/lenses/optional.hpp>
+
+#include "clientwrap.hpp"
+
+namespace Kazv
+{
+ struct ClientWrap::Private
+ {
+ lager::reader<Client> client;
+ lager::context<Client::Action> ctx;
+ };
+
+ ClientWrap::ClientWrap(lager::reader<Client> client,
+ lager::context<Client::Action> ctx)
+ : m_d{client, ctx}
+ {}
+
+ ClientWrap::~ClientWrap() = default;
+
+ lager::reader<immer::map<std::string, Room>> ClientWrap::rooms() const
+ {
+ return m_d->client
+ [&Client::roomList]
+ [&RoomList::rooms];
+ }
+
+ lager::reader<immer::flex_vector<std::string>> ClientWrap::roomIds() const
+ {
+ return rooms().xform(
+ zug::map([](auto m) {
+ return zug::into(
+ immer::flex_vector_transient{},
+ zug::map([](auto val) { return val.first; }),
+ m)
+ .persistent();
+ }));
+ }
+
+ lager::reader<RoomWrap> roomAt(std::string id) const
+ {
+ return rooms()[id];
+ }
+
+ lager::reader<Room> roomAtOrDefault(std::string id) const {
+ return roomAt(std::move(id))[lager::lenses::or_default];
+ }
+
+}
diff --git a/src/client/clientwrap.hpp b/src/client/clientwrap.hpp
new file mode 100644
index 0000000..70d67c6
--- /dev/null
+++ b/src/client/clientwrap.hpp
@@ -0,0 +1,89 @@
+/*
+ * 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 <lager/reader.hpp>
+#include <immer/box.hpp>
+#include <immer/map.hpp>
+#include <immer/flex_vector.hpp>
+#include <immer/flex_vector_transient.hpp>
+
+#include "client/client.hpp"
+#include "room/roomwrap.hpp"
+
+namespace Kazv
+{
+ class ClientWrap
+ {
+ public:
+ inline ClientWrap(lager::reader<Client> client,
+ lager::context<Client::Action> ctx)
+ : m_client(std::move(client))
+ , m_ctx(std::move(ctx)) {}
+
+ /* lager::reader<immer::map<std::string, Room>> */
+ inline auto rooms() const {
+ return m_client
+ [&Client::roomList]
+ [&RoomList::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(Client, m_client, serverUrl)
+ KAZV_WRAP_ATTR(Client, m_client, loggedIn)
+ KAZV_WRAP_ATTR(Client, m_client, userId)
+ KAZV_WRAP_ATTR(Client, m_client, token)
+ KAZV_WRAP_ATTR(Client, m_client, deviceId)
+
+ /* RoomWrap */
+ inline auto room(std::string id) const {
+ return RoomWrap(rooms()[std::move(id)]
+ [lager::lenses::or_default].make(),
+ m_ctx);
+ }
+
+ inline void passwordLogin(std::string homeserver, std::string username,
+ std::string password, std::string deviceName) const {
+ m_ctx.dispatch(Client::LoginAction{
+ homeserver, username, password, deviceName});
+ }
+
+ inline void tokenLogin(std::string homeserver, std::string username,
+ std::string token, std::string deviceId) const {
+ m_ctx.dispatch(Client::LoadUserInfoAction{
+ homeserver, username, token, deviceId, /* loggedIn = */ true});
+ }
+
+ private:
+ lager::reader<Client> m_client;
+ lager::context<Client::Action> m_ctx;
+ };
+
+}
diff --git a/src/client/cursorutil.hpp b/src/client/cursorutil.hpp
new file mode 100644
index 0000000..9873e06
--- /dev/null
+++ b/src/client/cursorutil.hpp
@@ -0,0 +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/>.
+ */
+
+#pragma once
+
+#include <iterator>
+#include <boost/hana/type.hpp>
+
+#include <lager/reader.hpp>
+#include <lager/lenses/optional.hpp>
+
+#include <zug/into.hpp>
+#include <zug/tuplify.hpp>
+#include <zug/transducer/each.hpp>
+#include <zug/run.hpp>
+
+namespace Kazv
+{
+ namespace detail
+ {
+ inline auto hasPushBack = boost::hana::is_valid(
+ [](auto t) -> decltype(
+ (void)
+ t.push_back(std::declval<typename decltype(t)::value_type>())) { });
+
+ inline auto hasInsert = boost::hana::is_valid(
+ [](auto t) -> decltype(
+ (void)
+ t.insert(std::declval<typename decltype(t)::value_type>())) { });
+
+ struct ImmerPushBackOrInsertT
+ {
+ template<class Container, class ...InputRanges>
+ auto operator()(Container container, InputRanges &&...ins) {
+ if constexpr (hasPushBack(container)) {
+ return std::move(container)
+ .push_back(zug::tuplify(std::forward<InputRanges>(ins)...));
+ } else {
+ static_assert(hasInsert(container),
+ "Container must have either push_back() or insert() method");
+ return std::move(container)
+ .insert(zug::tuplify(std::forward<InputRanges>(ins)...));
+ }
+ }
+ };
+
+ inline ImmerPushBackOrInsertT immerPushBackOrInsert{};
+
+ struct IntoImmerT
+ {
+ // We check for both transient_type and persistent_type
+ // because for some containers, transient_type is not
+ // implemented.
+ inline static auto hasTransientType = boost::hana::is_valid(
+ [](auto t) -> boost::hana::type<typename decltype(t)::transient_type::persistent_type> {});
+
+ template<class Container, class Func, class ...InputRanges>
+ auto operator()(Container &&m, Func &&transducer, InputRanges &&...ins) const {
+ using ContainerT = std::decay_t<Container>;
+ if constexpr (hasTransientType(m)) {
+ using TransientT = typename ContainerT::transient_type;
+ return zug::into(
+ std::forward<TransientT>(m.transient()),
+ std::forward<Func>(transducer),
+ std::forward<InputRanges>(ins)...)
+ .persistent();
+ } else {
+ ContainerT c;
+ zug::run(
+ std::forward<Func>(transducer)
+ | zug::each(
+ [&](auto ...ins) {
+ c = immerPushBackOrInsert(std::move(c), std::move(ins)...);
+ }),
+ std::forward<InputRanges>(ins)...);
+ return c;
+ }
+ }
+
+ };
+ }
+ inline detail::IntoImmerT intoImmer{};
+
+ namespace detail
+ {
+ inline auto looksLikeImmer = boost::hana::is_valid(
+ [](auto t) -> boost::hana::type<typename decltype(t)::transient_type> { });
+ struct ContainerMapT
+ {
+ template<class ResultContainer, class Func>
+ auto operator()(ResultContainer c, Func transducer) {
+ return zug::map(
+ [=](auto m) {
+ if constexpr (looksLikeImmer(m)) {
+ return intoImmer(c, transducer, m);
+ } else {
+ return zug::into(c, transducer, m);
+ }
+ });
+ }
+ };
+ }
+
+ inline detail::ContainerMapT containerMap{};
+
+ namespace CursorOp
+ {
+ template<class Cursor>
+ inline auto operator+(Cursor &&c)
+ -> decltype(std::forward<Cursor>(c).make().get())
+ {
+ return std::forward<Cursor>(c).make().get();
+ }
+
+ template<class Cursor>
+ inline auto operator~(Cursor &&c)
+ -> decltype(std::forward<Cursor>(c).make())
+ {
+ return std::forward<Cursor>(c).make();
+ }
+ }
+}
+
+#define KAZV_WRAP_ATTR(_type, _d, _attr) \
+ inline auto _attr() const { \
+ return (_d)[&_type::_attr]; \
+ }
diff --git a/src/client/data/room.cpp b/src/client/room/room.cpp
similarity index 77%
rename from src/client/data/room.cpp
rename to src/client/room/room.cpp
index 73422b6..8c6346d 100644
--- a/src/client/data/room.cpp
+++ b/src/client/room/room.cpp
@@ -1,71 +1,80 @@
/*
* 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 <lager/util.hpp>
+#include <zug/sequence.hpp>
+#include <zug/transducer/map.hpp>
#include "room.hpp"
+#include "client/cursorutil.hpp"
namespace Kazv
{
Room Room::update(Room r, Action a)
{
return lager::match(std::move(a))(
[&](AddStateEventsAction a) {
r.stateEvents = merge(std::move(r.stateEvents), a.stateEvents, keyOfState);
return r;
},
[&](AppendTimelineAction a) {
- r.timeline = r.timeline + a.events;
+ auto eventIds = intoImmer(immer::flex_vector<std::string>(),
+ zug::map(keyOfTimeline), a.events);
+ r.timeline = r.timeline + eventIds;
+ r.messages = merge(std::move(r.messages), a.events, keyOfTimeline);
return r;
},
[&](PrependTimelineAction a) {
- r.timeline = a.events + r.timeline;
+ auto eventIds = intoImmer(immer::flex_vector<std::string>(),
+ zug::map(keyOfTimeline), a.events);
+ r.timeline = eventIds + r.timeline;
+ r.messages = merge(std::move(r.messages), a.events, keyOfTimeline);
r.paginateBackToken = a.paginateBackToken;
// if there are no more events we should not allow further paginating
r.canPaginateBack = a.events.size() != 0;
return r;
},
[&](AddAccountDataAction a) {
r.accountData = merge(std::move(r.accountData), a.events, keyOfAccountData);
return r;
},
[&](ChangeMembershipAction a) {
r.membership = a.membership;
return r;
}
);
}
RoomList RoomList::update(RoomList l, Action a)
{
return lager::match(std::move(a))(
[=](UpdateRoomAction a) mutable {
l.rooms = std::move(l.rooms)
.update(a.roomId,
[=](Room oldRoom) {
oldRoom.roomId = a.roomId; // in case it is a new room
return Room::update(std::move(oldRoom), a.roomAction);
});
return l;
}
);
}
}
diff --git a/src/client/data/room.hpp b/src/client/room/room.hpp
similarity index 95%
rename from src/client/data/room.hpp
rename to src/client/room/room.hpp
index 886da09..c5510ba 100644
--- a/src/client/data/room.hpp
+++ b/src/client/room/room.hpp
@@ -1,154 +1,157 @@
/*
* 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 <lager/debug/cereal/struct.hpp>
#include <lager/debug/cereal/immer_flex_vector.hpp>
-#include "cereal_map.hpp"
+#include "client/data/cereal_map.hpp"
#include <string>
#include <variant>
#include <immer/flex_vector.hpp>
#include <immer/map.hpp>
#include "csapi/sync.hpp"
#include "event.hpp"
#include "client/util.hpp"
namespace Kazv
{
struct Room
{
enum Membership
{
Invite, Join, Leave
};
std::string roomId;
immer::map<KeyOfState, Event> stateEvents;
- immer::flex_vector<Event> timeline;
+ immer::flex_vector<std::string> timeline;
+ immer::map<std::string, Event> messages;
immer::map<std::string, Event> accountData;
Membership membership;
std::string paginateBackToken;
/// whether this room has earlier events to be fetched
bool canPaginateBack{true};
struct AddStateEventsAction
{
immer::flex_vector<Event> stateEvents;
};
struct AppendTimelineAction
{
immer::flex_vector<Event> events;
};
struct PrependTimelineAction
{
immer::flex_vector<Event> events;
std::string paginateBackToken;
};
struct AddAccountDataAction
{
immer::flex_vector<Event> events;
};
struct ChangeMembershipAction
{
Membership membership;
};
using Action = std::variant<
AddStateEventsAction,
AppendTimelineAction,
PrependTimelineAction,
AddAccountDataAction,
ChangeMembershipAction
>;
static Room update(Room r, Action a);
};
inline bool operator==(Room a, Room b)
{
return a.roomId == b.roomId
&& a.stateEvents == b.stateEvents
&& a.timeline == b.timeline
+ && a.messages == b.messages
&& a.accountData == b.accountData
&& a.membership == b.membership
&& a.paginateBackToken == b.paginateBackToken
&& a.canPaginateBack == b.canPaginateBack;
}
struct RoomList
{
immer::map<std::string, Room> rooms;
inline auto at(std::string id) const { return rooms.at(id); }
struct UpdateRoomAction
{
std::string roomId;
Room::Action roomAction;
};
using Action = std::variant<
UpdateRoomAction
>;
static RoomList update(RoomList l, Action a);
};
inline bool operator==(RoomList a, RoomList b)
{
return a.rooms == b.rooms;
}
#ifndef NDEBUG
LAGER_CEREAL_STRUCT(Room::AddStateEventsAction);
LAGER_CEREAL_STRUCT(Room::AppendTimelineAction);
LAGER_CEREAL_STRUCT(Room::PrependTimelineAction);
LAGER_CEREAL_STRUCT(Room::AddAccountDataAction);
LAGER_CEREAL_STRUCT(Room::ChangeMembershipAction);
LAGER_CEREAL_STRUCT(RoomList::UpdateRoomAction);
#endif
template<class Archive>
void serialize(Archive &ar, Room &r, std::uint32_t const /*version*/)
{
ar(r.roomId,
r.stateEvents,
r.timeline,
+ r.messages,
r.accountData,
r.membership,
r.paginateBackToken,
r.canPaginateBack);
}
template<class Archive>
void serialize(Archive &ar, RoomList &l, std::uint32_t const /*version*/)
{
ar(l.rooms);
}
}
CEREAL_CLASS_VERSION(Kazv::Room, 0);
CEREAL_CLASS_VERSION(Kazv::RoomList, 0);
diff --git a/src/client/room/roomwrap.hpp b/src/client/room/roomwrap.hpp
new file mode 100644
index 0000000..f936a42
--- /dev/null
+++ b/src/client/room/roomwrap.hpp
@@ -0,0 +1,130 @@
+/*
+ * 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 <lager/reader.hpp>
+#include <lager/with.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 "room.hpp"
+#include "client/cursorutil.hpp"
+
+namespace Kazv
+{
+ class RoomWrap
+ {
+ public:
+ inline RoomWrap(lager::reader<Room> room, lager::context<Client::Action> ctx)
+ : m_room(room)
+ , m_ctx(ctx) {}
+
+ /* lager::reader<MapT<KeyOfState, Event>> */
+ inline auto stateEvents() const {
+ return m_room
+ [&Room::stateEvents];
+ }
+
+ /* 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 m_room
+ [&Room::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<RangeT<std::string>> */
+ inline auto members() const {
+ using MemberNode = std::pair<std::string, Kazv::Event>;
+ auto memberNameTransducer =
+ zug::filter(
+ [](auto val) {
+ auto [k, v] = val;
+ auto [type, stateKey] = k;
+ return type == "m.room.member"s;
+ })
+ | zug::map(
+ [](auto val) {
+ auto [k, v] = val;
+ auto [type, stateKey] = k;
+ return MemberNode{stateKey, v};
+ })
+ | zug::filter(
+ [](auto val) {
+ auto [stateKey, ev] = val;
+ return ev.content().get()
+ .at("membership"s) == "join"s;
+ })
+ | zug::map(
+ [](auto val) {
+ auto [stateKey, ev] = val;
+ return stateKey;
+ });
+
+ return m_room
+ [&Room::stateEvents]
+ .xform(zug::map(
+ [=](auto eventMap) {
+ return intoImmer(
+ immer::flex_vector<std::string>{},
+ memberNameTransducer,
+ eventMap);
+ }));
+
+ }
+
+ lager::reader<bool> encrypted() const;
+
+ void sendMessage(Event msg) const;
+
+ void sendStateEvent(Event state) const;
+
+ private:
+ lager::reader<Room> m_room;
+ lager::context<Client::Action> m_ctx;
+ };
+}
diff --git a/src/client/sdk.hpp b/src/client/sdk.hpp
new file mode 100644
index 0000000..51de0c8
--- /dev/null
+++ b/src/client/sdk.hpp
@@ -0,0 +1,100 @@
+/*
+ * 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 <lager/store.hpp>
+
+#include "client/client.hpp"
+#include "clientwrap.hpp"
+
+namespace Kazv
+{
+ namespace detail
+ {
+ // emulates declval() but returns lvalue reference
+ template<class T>
+ typename std::add_lvalue_reference<T>::type declref() noexcept;
+ }
+
+ template<class EventLoop, class Xform, class ...Enhancers>
+ class Sdk
+ {
+ using ClientT = ::Kazv::Client;
+ using ActionT = typename ClientT::Action;
+
+ using StoreT = decltype(
+ lager::make_store<ActionT>(
+ std::declval<ClientT>(),
+ &ClientT::update,
+ std::declval<EventLoop>(),
+ lager::with_deps(
+ std::ref(detail::declref<JobInterface>()),
+ std::ref(detail::declref<EventInterface>())
+ ),
+ std::declval<Enhancers>()...)
+ );
+
+ using ContextT = lager::context<Client::Action>;
+ public:
+ Sdk(ClientT client,
+ JobInterface &jobHandler,
+ EventInterface &eventEmitter,
+ EventLoop &&eventLoop,
+ Xform &&xform,
+ Enhancers &&...enhancers)
+ : m_store(lager::make_store<ActionT>(
+ std::move(client),
+ &ClientT::update,
+ std::forward<EventLoop>(eventLoop),
+ lager::with_deps(
+ std::ref(jobHandler),
+ std::ref(eventEmitter)),
+ std::forward<Enhancers>(enhancers)...))
+ , m_client(m_store.xform(std::forward<Xform>(xform))) {}
+
+ ContextT context() const {
+ return m_store;
+ }
+
+ ClientWrap client() const {
+ return {m_client, m_store};
+ }
+
+ private:
+ StoreT m_store;
+ lager::reader<ClientT> m_client;
+ };
+
+ template<class EventLoop, class Xform, class ...Enhancers>
+ inline auto makeSdk(Client client,
+ JobInterface &jobHandler,
+ EventInterface &eventEmitter,
+ EventLoop &&eventLoop,
+ Xform &&xform,
+ Enhancers &&...enhancers)
+ -> Sdk<EventLoop, Xform, Enhancers...>
+ {
+ return { std::move(client),
+ jobHandler,
+ eventEmitter,
+ std::forward<EventLoop>(eventLoop),
+ std::forward<Xform>(xform),
+ std::forward<Enhancers>(enhancers)... };
+ }
+}
diff --git a/src/eventemitter/eventinterface.hpp b/src/eventemitter/eventinterface.hpp
index b236c19..4124376 100644
--- a/src/eventemitter/eventinterface.hpp
+++ b/src/eventemitter/eventinterface.hpp
@@ -1,70 +1,73 @@
/*
* 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 <variant>
#include "types.hpp"
#include "event.hpp"
namespace Kazv
{
struct LoginSuccessful {};
inline bool operator==(LoginSuccessful, LoginSuccessful)
{
return true;
}
struct ReceivingPresenceEvent { Event event; };
inline bool operator==(ReceivingPresenceEvent a, ReceivingPresenceEvent b)
{
return a.event == b.event;
}
struct ReceivingAccountDataEvent { Event event; };
inline bool operator==(ReceivingAccountDataEvent a, ReceivingAccountDataEvent b)
{
return a.event == b.event;
}
struct ReceivingRoomTimelineEvent {
Event event;
std::string roomId;
};
inline bool operator==(ReceivingRoomTimelineEvent a, ReceivingRoomTimelineEvent b)
{
return a.event == b.event && a.roomId == b.roomId;
}
using KazvEvent = std::variant<
+ // use this for placeholder of "no events yet"
+ // otherwise the first LoginSuccessful event cannot be detected
+ std::monostate,
LoginSuccessful,
ReceivingPresenceEvent,
ReceivingAccountDataEvent,
ReceivingRoomTimelineEvent
>;
class EventInterface
{
public:
virtual ~EventInterface() = default;
virtual void emit(KazvEvent e) = 0;
};
}
diff --git a/src/examples/basic/commands.cpp b/src/examples/basic/commands.cpp
index 59de29b..4b5cd9b 100644
--- a/src/examples/basic/commands.cpp
+++ b/src/examples/basic/commands.cpp
@@ -1,154 +1,111 @@
/*
* 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 <regex>
#include <tuple>
#include <cereal/archives/json.hpp>
#include <lager/lenses/optional.hpp>
-#include <zug/transducer/filter.hpp>
#include <zug/into.hpp>
#include <zug/compose.hpp>
#include <immer/flex_vector_transient.hpp>
#include <immer/flex_vector.hpp>
+#include <client/clientwrap.hpp>
#include "commands.hpp"
using namespace std::string_literals;
static std::regex roomMsgsRegex("room msgs (.+)");
static std::regex roomStatesRegex("room states (.+)");
static std::regex roomMemsRegex("room mems (.+)");
-lager::reader<Kazv::Room> roomFor(lager::reader<Kazv::Client> c,
- std::string roomId) {
- return c[&Kazv::Client::roomList]
- [&Kazv::RoomList::rooms]
- [roomId]
- [lager::lenses::or_default];
-}
-
-std::optional<Kazv::Client::Action> intent(std::string l, lager::reader<Kazv::Client> c)
+void parse(std::string l, Kazv::ClientWrap c)
{
+ using namespace Kazv::CursorOp;
std::smatch m;
if (l == "rooms") {
- lager::reader<immer::map<std::string, Kazv::Room>> rooms =
- c[&Kazv::Client::roomList]
- [&Kazv::RoomList::rooms];
+ auto roomIds = c.roomIds().make().get();
std::cout << "Room Id\tRoom Name\n";
- for (auto [id, room] : rooms.get()) {
- Kazv::KeyOfState nameKey{"m.room.name"s, ""s};
- auto ev = room.stateEvents[nameKey];
- Kazv::json content = ev.content();
- std::string roomName = "<no name>";
- if (content.contains("name"s)) {
- roomName = content["name"s];
- }
+ for (auto id : roomIds) {
+ auto roomName = c.room(id).name().make().get();
std::cout << id << "\t" << roomName << "\n";
}
- return std::nullopt;
} else if (std::regex_match(l, m, roomMsgsRegex)) {
auto roomId = m[1].str();
- lager::reader<Kazv::Room> room = roomFor(c, roomId);
+ auto room = c.room(roomId);
- auto msgs = room.get().timeline;
+ auto msgs = room.timelineEvents().make().get();
std::cout << "Messages in " << roomId << ":\n";
for (auto msg : msgs) {
std::cout << "Event "
<< msg.id() << " from "
<< msg.sender() << " at "
<< msg.originServerTs() << " typed "
<< msg.type() << "\n";
{
cereal::JSONOutputArchive archive( std::cout );
archive(msg.content());
}
std::cout << std::endl;
}
- return std::nullopt;
} else if (std::regex_match(l, m, roomMemsRegex)) {
auto roomId = m[1].str();
- lager::reader<Kazv::Room> room = roomFor(c, roomId);
-
- using MemberNode = std::pair<std::string, Kazv::Event>;
- auto memberEvents =
- zug::into(
- immer::flex_vector_transient<MemberNode>{},
- zug::filter(
- [](auto val) {
- auto [k, v] = val;
- auto [type, stateKey] = k;
- return type == "m.room.member"s;
- })
- | zug::map(
- [](auto val) {
- auto [k, v] = val;
- auto [type, stateKey] = k;
- return MemberNode{stateKey, v};
- })
- | zug::filter(
- [](auto val) {
- auto [stateKey, ev] = val;
- return ev.content().get()
- .at("membership"s) == "join"s;
- }),
- room.get().stateEvents)
- .persistent();
+ auto room = c.room(roomId);
+ auto members = +room.members();
std::cout << "Members in " << roomId << ":\n";
- for (auto [userId, a] : memberEvents) {
+ for (auto userId : members) {
std::cout << userId << std::endl;
}
- return std::nullopt;
} else if (std::regex_match(l, m, roomStatesRegex)) {
auto roomId = m[1].str();
- auto room = roomFor(c, roomId);
- auto states = room.get().stateEvents;
+ auto room = c.room(roomId);
+ auto states = room.stateEvents().make().get();
std::cout << "States in " << roomId << ":\n";
for (auto [k, st] : states) {
auto [type, stateKey] = k;
std::cout << "State typed "
<< type << " with stateKey "
<< stateKey << "\n";
{
cereal::JSONOutputArchive archive( std::cout );
archive(st);
}
std::cout << std::endl;
}
- return std::nullopt;
} else {
// no valid action, display help
std::cout << "Commands:\n"
<< "rooms -- List rooms\n"
<< "room msgs <roomId> -- List room messages\n"
<< "room states <roomId> -- List room states\n"
<< "room mems <roomId> -- List room members\n\n";
- return std::nullopt;
+
}
}
diff --git a/src/examples/basic/commands.hpp b/src/examples/basic/commands.hpp
index a27bb8b..b43afee 100644
--- a/src/examples/basic/commands.hpp
+++ b/src/examples/basic/commands.hpp
@@ -1,29 +1,27 @@
/*
* 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 <optional>
-#include <string>
-#include <lager/reader.hpp>
-#include <client/client.hpp>
+#include <string>
+#include <client/clientwrap.hpp>
-std::optional<Kazv::Client::Action> intent(std::string l, lager::reader<Kazv::Client> c);
+void parse(std::string l, Kazv::ClientWrap c);
diff --git a/src/examples/basic/main.cpp b/src/examples/basic/main.cpp
index 7436a42..8b8aa6f 100644
--- a/src/examples/basic/main.cpp
+++ b/src/examples/basic/main.cpp
@@ -1,133 +1,134 @@
/*
* 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 <string>
#include <iostream>
#include <fstream>
#include <lager/store.hpp>
#include <lager/event_loop/boost_asio.hpp>
#include <boost/asio.hpp>
#ifndef NDEBUG
#include <lager/debug/debugger.hpp>
#include <lager/debug/http_server.hpp>
#endif
-#include <client/client.hpp>
#include <job/cprjobhandler.hpp>
#include <eventemitter/lagerstoreeventemitter.hpp>
+#include <client/sdk.hpp>
+
#include "commands.hpp"
using namespace std::string_literals;
int main(int argc, char *argv[])
{
+
+ if (argc <= 1) {
+ std::cerr << "Usage: basicexample <auth-file-name>\n\n"
+ << "auth file is a text file with these lines:\n"
+ << "mode(pw or token)\n"
+ << "homeserver address\n"
+ << "username (if pw) or userid (if token)\n"
+ << "password (if pw) or token (if token)\n"
+ << "deviceId (if token) or blank (if pw)\n\n"
+ << "For example:\n"
+ << "pw\n"
+ << "https://some.server.org\n"
+ << "someUserName\n"
+ << "somePa$$w0rd\n";
+ return 1;
+ }
+
boost::asio::io_context ioContext;
auto eventEmitter =
Kazv::LagerStoreEventEmitter(lager::with_boost_asio_event_loop{ioContext.get_executor()});
Kazv::Descendent<Kazv::JobInterface> jobHandler(Kazv::CprJobHandler{ioContext.get_executor()});
#ifndef NDEBUG
auto debugger = lager::http_debug_server{argc, (const char **)argv, 8080, "./_deps/lager-src/resources"};
#endif
- auto store = lager::make_store<Kazv::Client::Action>(
+ auto sdk = Kazv::makeSdk(
Kazv::Client{},
- &Kazv::Client::update,
+ *jobHandler.data(),
+ static_cast<Kazv::EventInterface &>(eventEmitter),
lager::with_boost_asio_event_loop{ioContext.get_executor()},
- lager::with_deps(
- std::ref(*jobHandler.data()),
- std::ref(static_cast<Kazv::EventInterface &>(eventEmitter)))
#ifndef NDEBUG
- , lager::with_debugger(debugger)
+ zug::map([](auto &&m) -> Kazv::Client {
+ return std::forward<decltype(m)>(m);
+ }),
+ lager::with_debugger(debugger)
+#else
+ zug::identity
#endif
);
- if (argc <= 1) {
- std::cerr << "Usage: basicexample <auth-file-name>\n\n"
- << "auth file is a text file with these three lines:\n"
- << "homeserver address\n"
- << "username\n"
- << "password\n\n"
- << "For example:\n"
- << "https://some.server.org\n"
- << "someUserName\n"
- << "somePa$$w0rd\n";
- return 1;
- }
-
- std::string homeserver;
- std::string username;
- std::string password;
- {
- std::ifstream auth(argv[1]);
- if (! auth) {
- std::cerr << "Cannot open auth file " << argv[1] << "\n";
- return 1;
- }
- std::getline(auth, homeserver);
- std::getline(auth, username);
- std::getline(auth, password);
- }
- store.dispatch(Kazv::Client::LoginAction{homeserver, username, password, "libkazv basic example"s});
+ auto store = sdk.context();
+ auto c = sdk.client();
auto watchable = eventEmitter.watchable();
watchable.after<Kazv::ReceivingRoomTimelineEvent>(
[](auto &&e) {
auto [event, roomId] = e;
std::cout << "\033[1;32mreceiving event " << event.id()
<< " in " << roomId
<< " from " << event.sender()
<< ": " << event.content().get().dump() << "\033[0m"
<< std::endl;
});
- /*lager::reader<Kazv::RoomList> roomList = store
- .zoom(lager::lenses::attr(&Kazv::Client::roomList));
+ {
+ std::ifstream auth(argv[1]);
+ if (! auth) {
+ std::cerr << "Cannot open auth file " << argv[1] << "\n";
+ return 1;
+ }
+ std::string mode;
+ std::string homeserver;
+ std::string username;
+ std::getline(auth, mode);
+ std::getline(auth, homeserver);
+ std::getline(auth, username);
- lager::watch(
- roomList,
- [](Kazv::RoomList l) {
- std::cout << "Room list updated." << std::endl;
- std::cout << "Rooms: ";
- for (auto [id, room]: l.rooms) {
- std::cout << id << " ";
- }
- std::cout << std::endl;
- });*/
+ if (mode == "token") {
+ std::string token;
+ std::string deviceId;
+ std::getline(auth, token);
+ std::getline(auth, deviceId);
+ c.tokenLogin(homeserver, username, token, deviceId);
+ } else {
+ std::string password;
+ std::getline(auth, password);
+ c.passwordLogin(homeserver, username, password, "libkazv basic example");
+ }
+ }
std::thread([&] { ioContext.run(); }).detach();
- lager::reader<Kazv::Client> c = store
-#ifndef NDEBUG
- // get the client from the debugger
- .xform(zug::map([](auto m) -> Kazv::Client { return m; }))
-#endif
- ;
-
std::size_t command = 1;
while (true) {
std::cout << "\033[1;33mCommand[" << command << "]: \033[0m\n";
std::string l;
std::getline(std::cin, l);
- std::optional<Kazv::Client::Action> a = intent(l, c);
+ parse(l, c);
++command;
}
}
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index ec0062b..d2a78b1 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,17 +1,18 @@
include(CTest)
-add_executable(basejobtest
- testallapi.cpp
-
+add_executable(kazvtest
+ testmain.cpp
basejobtest.cpp
+ cursorutiltest.cpp
)
-target_link_libraries(basejobtest
+
+target_link_libraries(kazvtest
PRIVATE Catch2::Catch2
PRIVATE kazv
PRIVATE kazvjob
PRIVATE nlohmann_json::nlohmann_json
PRIVATE immer
PRIVATE lager
PRIVATE zug)
diff --git a/src/tests/cursorutiltest.cpp b/src/tests/cursorutiltest.cpp
new file mode 100644
index 0000000..ed0bc8b
--- /dev/null
+++ b/src/tests/cursorutiltest.cpp
@@ -0,0 +1,76 @@
+/*
+ * 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 <string>
+#include <tuple>
+#include <catch2/catch.hpp>
+
+#include <immer/array.hpp>
+#include <immer/array_transient.hpp>
+#include <immer/map.hpp>
+
+#include <client/cursorutil.hpp>
+
+using namespace Kazv;
+
+TEST_CASE("cursorutil should perform correctly", "[cursorutil]")
+{
+ immer::array<int> src1{4, 8, 7, 6};
+ immer::array<std::string> exp1{"4", "8", "7", "6"};
+ auto res1 = intoImmer(
+ immer::array<std::string>{},
+ zug::map(
+ [](int x) {
+ return std::to_string(x);
+ }),
+ src1);
+
+ REQUIRE( exp1 == res1 );
+
+ immer::map<int, int> src2;
+ src2 = std::move(src2).set(4, 8).set(7, 6);
+ immer::map<std::string, int> exp2;
+ exp2 = std::move(exp2).set("4", 8).set("7", 6);
+ auto res2 = intoImmer(
+ immer::map<std::string, int>{},
+ zug::map(
+ [](auto x) {
+ return std::make_pair(std::to_string(x.first), x.second);
+ }),
+ src2);
+
+ REQUIRE( exp2 == res2 );
+
+ immer::map<std::string, int> res3;
+
+ auto rf =
+ [&](auto st, auto in) {
+ *st = in;
+ return st;
+ };
+
+ containerMap(
+ immer::map<std::string, int>{},
+ zug::map(
+ [](auto x) {
+ return std::make_pair(std::to_string(x.first), x.second);
+ }))(rf)(&res3, src2);
+
+ REQUIRE( res3 == exp2 );
+}
diff --git a/src/tests/testallapi.cpp b/src/tests/testmain.cpp
similarity index 100%
rename from src/tests/testallapi.cpp
rename to src/tests/testmain.cpp
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 4:34 PM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55326
Default Alt Text
(62 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment