Page MenuHomePhorge

No OneTemporary

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

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)

Event Timeline