commit df87cd9c8a2aec98693bc3de9954de3d10ed29be Author: Green Sky Date: Sun Jul 23 13:23:49 2023 +0200 inital commit, add tox_contacts (version 2) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b7d593a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(solanaceae) + +add_library(solanaceae_tox_contacts + ./solanaceae/tox_contacts/components.hpp + ./solanaceae/tox_contacts/components_id.inl + + ./solanaceae/tox_contacts/tox_contact_model2.hpp + ./solanaceae/tox_contacts/tox_contact_model2.cpp +) + +target_include_directories(solanaceae_tox_contacts PUBLIC .) +target_compile_features(solanaceae_tox_contacts PUBLIC cxx_std_17) +target_link_libraries(solanaceae_tox_contacts PUBLIC + solanaceae_util + solanaceae_contact + solanaceae_toxcore +) + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2780797 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +The Code is under the following License, if not stated otherwise: + +MIT License + +Copyright (c) 2023 Erik Scholz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..10af64d --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +`plant !` + +provides tox contacts and tox messages + +requires solanaceae_toxcore + diff --git a/solanaceae/tox_contacts/components.hpp b/solanaceae/tox_contacts/components.hpp new file mode 100644 index 0000000..c4fcf71 --- /dev/null +++ b/solanaceae/tox_contacts/components.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +namespace Contact::Components { + + // ========== + // friend + // ========== + + struct ToxFriendPersistent { + ToxKey key; + }; + + struct ToxFriendEphemeral { + uint32_t friend_number; + }; + + + // ========== + // TODO: conference (old groups) + // ========== + + struct ToxConfPersistent { + ToxKey key; + }; + + struct ToxConfEhpemeral { + uint32_t id; + }; + + + // ========== + // groups (ngc) + // ========== + + struct ToxGroupPersistent { + ToxKey chat_id; + }; + + struct ToxGroupEphemeral { + uint32_t group_number; + }; + + struct ToxGroupPeerPersistent { + ToxKey chat_id; + ToxKey peer_key; + }; + + struct ToxGroupPeerEphemeral { + uint32_t group_number; + uint32_t peer_number; + }; + +} // Contact::Components + +#include "./components_id.inl" + diff --git a/solanaceae/tox_contacts/components_id.inl b/solanaceae/tox_contacts/components_id.inl new file mode 100644 index 0000000..5af528e --- /dev/null +++ b/solanaceae/tox_contacts/components_id.inl @@ -0,0 +1,25 @@ +#include "./components.hpp" + +#include + +// TODO: move more central +#define DEFINE_COMP_ID(x) \ +template<> \ +constexpr entt::id_type entt::type_hash::value() noexcept { \ + using namespace entt::literals; \ + return #x##_hs; \ +} + +// cross compiler stable ids + +DEFINE_COMP_ID(Contact::Components::ToxFriendPersistent) +DEFINE_COMP_ID(Contact::Components::ToxFriendEphemeral) +DEFINE_COMP_ID(Contact::Components::ToxConfPersistent) +DEFINE_COMP_ID(Contact::Components::ToxConfEhpemeral) +DEFINE_COMP_ID(Contact::Components::ToxGroupPersistent) +DEFINE_COMP_ID(Contact::Components::ToxGroupEphemeral) +DEFINE_COMP_ID(Contact::Components::ToxGroupPeerPersistent) +DEFINE_COMP_ID(Contact::Components::ToxGroupPeerEphemeral) + +#undef DEFINE_COMP_ID + diff --git a/solanaceae/tox_contacts/tox_contact_model2.cpp b/solanaceae/tox_contacts/tox_contact_model2.cpp new file mode 100644 index 0000000..418898a --- /dev/null +++ b/solanaceae/tox_contacts/tox_contact_model2.cpp @@ -0,0 +1,409 @@ +#include "./tox_contact_model2.hpp" + +#include +#include + +#include "./components.hpp" + +#include +#include +#include + +ToxContactModel2::ToxContactModel2(Contact3Registry& cr, ToxI& t, ToxEventProviderI& tep) : _cr(cr), _t(t), _tep(tep) { + _tep.subscribe(this, Tox_Event::TOX_EVENT_FRIEND_CONNECTION_STATUS); + _tep.subscribe(this, Tox_Event::TOX_EVENT_FRIEND_STATUS); + _tep.subscribe(this, Tox_Event::TOX_EVENT_FRIEND_NAME); + + // TODO: conf + + _tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_SELF_JOIN); + _tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PEER_JOIN); + _tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PEER_EXIT); + _tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PEER_NAME); + + + // add self + Contact3 c = entt::null; + // TODO: if self exists + + c = _cr.create(); + _cr.emplace(c, this); + _cr.emplace(c); + _cr.emplace(c, _t.toxSelfGetName()); + + _friend_self = c; + + // fill in contacts + for (const uint32_t f_id : _t.toxSelfGetFriendList()) { + getContactFriend(f_id); + } + + for (const uint32_t g_id : _t.toxGroupGetList()) { + getContactGroup(g_id); + } +} + +Contact3Handle ToxContactModel2::getContactFriend(uint32_t friend_number) { + Contact3 c = entt::null; + + // first check contacts with friend id + // TODO: lookup table + //_cr.view().each([&c, friend_number](const Contact3 e, const Contact::Components::ToxFriendEphemeral& f_id) { + for (const auto e : _cr.view()) { + if (_cr.get(e).friend_number == friend_number) { + c = e; + break; + } + } + + if (_cr.valid(c)) { + return {_cr, c}; + } + + // else check by pubkey + auto f_key_opt = _t.toxFriendGetPublicKey(friend_number); + assert(f_key_opt.has_value()); // TODO: handle gracefully? + + const ToxKey& f_key = f_key_opt.value(); + //_cr.view().each([&c, &f_key](const Contact3 e, const Contact::Components::ToxFriendPersistent& f_key_comp) { + for (const auto e : _cr.view()) { + if (f_key == _cr.get(e).key) { + c = e; + break; + } + } + + if (_cr.valid(c)) { + return {_cr, c}; + } + + // else, new ent + c = _cr.create(); + + _cr.emplace(c); + _cr.emplace(c, this); + _cr.emplace(c, friend_number); + _cr.emplace(c, f_key); + _cr.emplace(c, _friend_self); + _cr.emplace(c, _t.toxFriendGetName(friend_number).value_or("")); + + std::cout << "TCM2: created friend contact " << friend_number << "\n"; + + return {_cr, c}; +} + +Contact3Handle ToxContactModel2::getContactGroup(uint32_t group_number) { + Contact3 c = entt::null; + + // first check contacts with group_number + // TODO: lookup table + //_cr.view().each([&c, group_number](const Contact3 e, const Contact::Components::ToxGroupEphemeral& g_e) { + for (const auto e : _cr.view()) { + if (_cr.get(e).group_number == group_number) { + c = e; + break; + } + } + + if (_cr.valid(c)) { + return {_cr, c}; + } + + // else check by pubkey + auto g_key_opt = _t.toxGroupGetChatId(group_number); + assert(g_key_opt.has_value()); // TODO: handle gracefully? + + const ToxKey& g_key = g_key_opt.value(); + //_cr.view().each([&c, &g_key](const Contact3 e, const Contact::Components::ToxGroupPersistent& g_key_comp) { + for (const auto e : _cr.view()) { + if (g_key == _cr.get(e).chat_id) { + c = e; + break; + } + } + + if (_cr.valid(c)) { + return {_cr, c}; + } + + // else, new ent + c = _cr.create(); + + _cr.emplace(c, this); + _cr.emplace(c); + _cr.emplace(c); // start empty + _cr.emplace(c, group_number); + _cr.emplace(c, g_key); + _cr.emplace(c, _t.toxGroupGetName(group_number).value_or("")); + _cr.emplace( + c, + _t.toxGroupIsConnected(group_number).value_or(false) + ? Contact::Components::ConnectionState::State::cloud + : Contact::Components::ConnectionState::State::disconnected + ); + + auto self_opt = _t.toxGroupSelfGetPeerId(group_number); + if (self_opt.has_value()) { + _cr.emplace(c, getContactGroupPeer(group_number, self_opt.value())); + } else { + std::cerr << "TCM2 error: getting self for group" << group_number << "!!\n"; + } + + std::cout << "TCM2: created group contact " << group_number << "\n"; + + return {_cr, c}; +} + +Contact3Handle ToxContactModel2::getContactGroupPeer(uint32_t group_number, uint32_t peer_number) { + Contact3 c = entt::null; + + Contact3Handle group_c = getContactGroup(group_number); + + assert(static_cast(group_c)); + + // first check contacts with peer id + // TODO: lookup table + //_cr.view().each([&c, group_number, peer_number](const Contact3 e, const Contact::Components::ToxGroupPeerEphemeral& p_comp) { + for (const auto e : _cr.view()) { + const auto& p_comp = _cr.get(e); + if (p_comp.group_number == group_number && p_comp.peer_number == peer_number) { + c = e; + break; + } + } + + if (_cr.valid(c)) { + return {_cr, c}; + } + + const auto& g_key = group_c.get().chat_id; + + // else check by key + auto [g_p_key_opt, _] = _t.toxGroupPeerGetPublicKey(group_number, peer_number); + if (!g_p_key_opt.has_value()) { + } + //assert(g_p_key_opt.has_value()); // TODO: handle gracefully? + if (!g_p_key_opt.has_value()) { + // if the key could not be retreived, that means the peer has exited (idk why the earlier search did not work, it should have) + // also exit here, to not create, pubkey less <.< + std::cerr << "TCM2 error: we did not have offline peer in db, which is worrying\n"; + return {}; + } + + const ToxKey& g_p_key = g_p_key_opt.value(); + //_cr.view().each([&c, &g_key, &g_p_key](const Contact3 e, const Contact::Components::ToxGroupPeerPersistent& g_p_key_comp) { + for (const auto e : _cr.view()) { + const auto& g_p_key_comp = _cr.get(e); + if (g_p_key == g_p_key_comp.peer_key && g_key == g_p_key_comp.chat_id) { + c = e; + break; + } + } + + if (_cr.valid(c)) { + return {_cr, c}; + } + + // else, new ent + c = _cr.create(); + + _cr.emplace(c, group_c); + { // add sub to parent + auto& parent_sub_list = group_c.get_or_emplace().subs; + if (std::find(parent_sub_list.cbegin(), parent_sub_list.cend(), c) == parent_sub_list.cend()) { + parent_sub_list.push_back(c); + } + } + _cr.emplace(c, this); + _cr.emplace(c, group_number, peer_number); + _cr.emplace(c, g_key, g_p_key); + const auto name_opt = std::get<0>(_t.toxGroupPeerGetName(group_number, peer_number)); + if (name_opt.has_value()) { + _cr.emplace(c, name_opt.value()); + } + + { // self + // TODO: this is very flaky + auto self_number_opt = _t.toxGroupSelfGetPeerId(group_number); + if (peer_number == self_number_opt.value()) { + _cr.emplace(c); + } else { + _cr.emplace(c, getContactGroupPeer(group_number, self_number_opt.value())); + } + } + + std::cout << "TCM2: created group peer contact " << group_number << " " << peer_number << "\n"; + + return {_cr, c}; +} + +//Contact3Handle ToxContactModel2::getContactGroupPeer(const ToxKey& group_key, const ToxKey& peer_key) { + //return {}; +//} + +Contact3Handle ToxContactModel2::getContactGroupPeer(uint32_t group_number, const ToxKey& peer_key) { + Contact3 c = entt::null; + + Contact3Handle group_c = getContactGroup(group_number); + + assert(static_cast(group_c)); + + const auto& g_key = group_c.get().chat_id; + + // search by key + //_cr.view().each([&c, &g_key, &peer_key](const Contact3 e, const Contact::Components::ToxGroupPeerPersistent& g_p_key_comp) { + for (const auto e : _cr.view()) { + const auto& g_p_key_comp = _cr.get(e); + if (peer_key == g_p_key_comp.peer_key && g_key == g_p_key_comp.chat_id) { + c = e; + break; + } + } + + if (_cr.valid(c)) { + return {_cr, c}; + } + + // TODO: maybe not create contacts via history sync + // else, new ent + c = _cr.create(); + + _cr.emplace(c, group_c); + _cr.emplace(c, this); + //_cr.emplace(c, group_number, peer_number); + _cr.emplace(c, g_key, peer_key); + //_cr.emplace(c, ""); + //_cr.emplace(c, std::get<0>(_t.toxGroupPeerGetName(group_number, peer_number)).value_or("")); + + { // self + // TODO: this is very flaky + auto self_number_opt = _t.toxGroupSelfGetPeerId(group_number); + _cr.emplace(c, getContactGroupPeer(group_number, self_number_opt.value())); + } + + std::cout << "TCM2: created group peer contact via pubkey " << group_number << "\n"; + + return {_cr, c}; +} + +bool ToxContactModel2::onToxEvent(const Tox_Event_Friend_Connection_Status* e) { + const Tox_Connection connection_status = tox_event_friend_connection_status_get_connection_status(e); + auto c = getContactFriend(tox_event_friend_connection_status_get_friend_number(e)); + + c.emplace_or_replace( + (connection_status == TOX_CONNECTION_NONE) ? Contact::Components::ConnectionState::State::disconnected : + (connection_status == TOX_CONNECTION_UDP) ? Contact::Components::ConnectionState::State::direct : + Contact::Components::ConnectionState::State::cloud + ); + + if (connection_status == TOX_CONNECTION_NONE) { + c.remove(); + } + + return false; +} + +bool ToxContactModel2::onToxEvent(const Tox_Event_Friend_Status* e) { + //tox_event_friend_status_get_status(e); + + //TOX_USER_STATUS_NONE, + //TOX_USER_STATUS_AWAY, + //TOX_USER_STATUS_BUSY, + + //auto c = getContactFriend(tox_event_friend_status_get_friend_number(e)); + return false; +} + +bool ToxContactModel2::onToxEvent(const Tox_Event_Friend_Name* e) { + const std::string_view name { + reinterpret_cast(tox_event_friend_name_get_name(e)), + tox_event_friend_name_get_name_length(e) + }; + + auto c = getContactFriend(tox_event_friend_name_get_friend_number(e)); + c.emplace_or_replace(std::string{name}); + + return false; // return true? +} + +bool ToxContactModel2::onToxEvent(const Tox_Event_Group_Self_Join* e) { + const uint32_t group_number = tox_event_group_self_join_get_group_number(e); + if (const auto self_id_opt = _t.toxGroupSelfGetPeerId(group_number); self_id_opt.has_value()) { + auto c = getContactGroupPeer(group_number, self_id_opt.value()); + c.emplace_or_replace(); + c.emplace_or_replace(Contact::Components::ConnectionState::State::direct); + } else { + assert(false); + } + + return false; +} + +bool ToxContactModel2::onToxEvent(const Tox_Event_Group_Peer_Join* e) { + const uint32_t group_number = tox_event_group_peer_join_get_group_number(e); + const uint32_t peer_number = tox_event_group_peer_join_get_peer_id(e); + + auto c = getContactGroupPeer(group_number, peer_number); + + // ensure its set + c.emplace_or_replace(group_number, peer_number); + + auto [peer_state_opt, _] = _t.toxGroupPeerGetConnectionStatus(group_number, peer_number); + c.emplace_or_replace( + (peer_state_opt.value_or(TOX_CONNECTION_NONE) == TOX_CONNECTION_NONE) ? Contact::Components::ConnectionState::State::disconnected : + (peer_state_opt.value_or(TOX_CONNECTION_NONE) == TOX_CONNECTION_UDP) ? Contact::Components::ConnectionState::State::direct : + Contact::Components::ConnectionState::State::cloud + ); + + // update name + const auto name_opt = std::get<0>(_t.toxGroupPeerGetName(group_number, peer_number)); + if (name_opt.has_value()) { + _cr.emplace_or_replace(c, name_opt.value()); + } + + return false; +} + +bool ToxContactModel2::onToxEvent(const Tox_Event_Group_Peer_Exit* e) { + const uint32_t group_number = tox_event_group_peer_exit_get_group_number(e); + const uint32_t peer_number = tox_event_group_peer_exit_get_peer_id(e); + const auto exit_type = tox_event_group_peer_exit_get_exit_type(e); + // set name? + // we dont care about the part messae? + + if (exit_type == Tox_Group_Exit_Type::TOX_GROUP_EXIT_TYPE_SELF_DISCONNECTED) { + // you disconnected intentionally, or you where kicked + // TODO: we need to remove all ToxGroupPeerEphemeral components of that group + } else { + auto c = getContactGroupPeer(group_number, peer_number); + + if (!static_cast(c)) { + return false; // we dont track this contact ????? + } + + c.emplace_or_replace(Contact::Components::ConnectionState::State::disconnected); + c.remove(); + + // they where kicked + // exit_type == Tox_Group_Exit_Type::TOX_GROUP_EXIT_TYPE_KICK + } + + return false; +} + +bool ToxContactModel2::onToxEvent(const Tox_Event_Group_Peer_Name* e) { + const uint32_t group_number = tox_event_group_peer_name_get_group_number(e); + const uint32_t peer_number = tox_event_group_peer_name_get_peer_id(e); + + const std::string_view name { + reinterpret_cast(tox_event_group_peer_name_get_name(e)), + tox_event_group_peer_name_get_name_length(e) + }; + + auto c = getContactGroupPeer(group_number, peer_number); + + c.emplace_or_replace(std::string{name}); + + return false; +} + diff --git a/solanaceae/tox_contacts/tox_contact_model2.hpp b/solanaceae/tox_contacts/tox_contact_model2.hpp new file mode 100644 index 0000000..0da844e --- /dev/null +++ b/solanaceae/tox_contacts/tox_contact_model2.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#include + +// fwd +struct ToxI; + +// tox contact model for ContactModel3I +class ToxContactModel2 : public ContactModel3I, public ToxEventI { + Contact3Registry& _cr; + ToxI& _t; + ToxEventProviderI& _tep; + + Contact3 _friend_self; + + public: + ToxContactModel2(Contact3Registry& cr, ToxI& t, ToxEventProviderI& tep); + virtual ~ToxContactModel2(void) {} + + // TODO: continually fetch group peer connection state, since JF does not want to add cb/event + //void iterate(void); + + public: // util for tox code + // also creates if non existant + Contact3Handle getContactFriend(uint32_t friend_number); + + Contact3Handle getContactGroup(uint32_t group_number); + Contact3Handle getContactGroupPeer(uint32_t group_number, uint32_t peer_number); + //Contact3Handle getContactGroupPeer(const ToxKey& group_key, const ToxKey& peer_key); + Contact3Handle getContactGroupPeer(uint32_t group_number, const ToxKey& peer_key); + + protected: // tox events + bool onToxEvent(const Tox_Event_Friend_Connection_Status* e) override; + bool onToxEvent(const Tox_Event_Friend_Status* e) override; + bool onToxEvent(const Tox_Event_Friend_Name* e) override; + + bool onToxEvent(const Tox_Event_Group_Self_Join* e) override; + bool onToxEvent(const Tox_Event_Group_Peer_Join* e) override; + bool onToxEvent(const Tox_Event_Group_Peer_Exit* e) override; + bool onToxEvent(const Tox_Event_Group_Peer_Name* e) override; +}; +