From 059c0f9ec909cb68685471b30501e6938aced571 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Mon, 24 Jul 2023 13:20:53 +0200 Subject: [PATCH] add tox messages --- CMakeLists.txt | 18 + solanaceae/tox_messages/components.hpp | 46 ++ solanaceae/tox_messages/components_id.inl | 22 + .../tox_messages/tox_message_manager.cpp | 302 +++++++++ .../tox_messages/tox_message_manager.hpp | 30 + .../tox_messages/tox_transfer_manager.cpp | 603 ++++++++++++++++++ .../tox_messages/tox_transfer_manager.hpp | 65 ++ 7 files changed, 1086 insertions(+) create mode 100644 solanaceae/tox_messages/components.hpp create mode 100644 solanaceae/tox_messages/components_id.inl create mode 100644 solanaceae/tox_messages/tox_message_manager.cpp create mode 100644 solanaceae/tox_messages/tox_message_manager.hpp create mode 100644 solanaceae/tox_messages/tox_transfer_manager.cpp create mode 100644 solanaceae/tox_messages/tox_transfer_manager.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b7d593a..32c517c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,3 +18,21 @@ target_link_libraries(solanaceae_tox_contacts PUBLIC solanaceae_toxcore ) +add_library(solanaceae_tox_messages + ./solanaceae/tox_messages/components.hpp + ./solanaceae/tox_messages/components_id.inl + + ./solanaceae/tox_messages/tox_message_manager.hpp + ./solanaceae/tox_messages/tox_message_manager.cpp + + # TODO: seperate tf? + ./solanaceae/tox_messages/tox_transfer_manager.hpp + ./solanaceae/tox_messages/tox_transfer_manager.cpp +) + +target_include_directories(solanaceae_tox_messages PUBLIC .) +target_compile_features(solanaceae_tox_messages PUBLIC cxx_std_17) +target_link_libraries(solanaceae_tox_messages PUBLIC + solanaceae_tox_contacts +) + diff --git a/solanaceae/tox_messages/components.hpp b/solanaceae/tox_messages/components.hpp new file mode 100644 index 0000000..ba0b9fd --- /dev/null +++ b/solanaceae/tox_messages/components.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include + +#include +#include + +namespace Message::Components { + +struct ToxGroupMessageID { + uint32_t id = 0u; +}; + +// TODO: move all those comps + +struct SyncedBy { + std::set list; +}; + +namespace Transfer { + + struct ToxTransferFriend { + uint32_t friend_number; + uint32_t transfer_number; + }; + + struct FileID { + // persistent ID + // sometimes called file_id or hash + ToxKey id; + // TODO: variable length + }; + + struct FileKind { + // TODO: use tox file kind + uint64_t kind {0}; + }; + +} // Transfer + +} // Message::Components + +#include "./components_id.inl" + diff --git a/solanaceae/tox_messages/components_id.inl b/solanaceae/tox_messages/components_id.inl new file mode 100644 index 0000000..c700e27 --- /dev/null +++ b/solanaceae/tox_messages/components_id.inl @@ -0,0 +1,22 @@ +#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 compile(r) stable ids + +DEFINE_COMP_ID(Message::Components::ToxGroupMessageID) +DEFINE_COMP_ID(Message::Components::SyncedBy) +DEFINE_COMP_ID(Message::Components::Transfer::ToxTransferFriend) +DEFINE_COMP_ID(Message::Components::Transfer::FileID) +DEFINE_COMP_ID(Message::Components::Transfer::FileKind) + +#undef DEFINE_COMP_ID + diff --git a/solanaceae/tox_messages/tox_message_manager.cpp b/solanaceae/tox_messages/tox_message_manager.cpp new file mode 100644 index 0000000..9e8f8d7 --- /dev/null +++ b/solanaceae/tox_messages/tox_message_manager.cpp @@ -0,0 +1,302 @@ +#include "./tox_message_manager.hpp" + +#include + +#include +#include +#include +#include "./components.hpp" + +#include + +#include +#include +#include + +ToxMessageManager::ToxMessageManager(RegistryMessageModel& rmm, Contact3Registry& cr, ToxContactModel2& tcm, ToxI& t, ToxEventProviderI& tep) : _rmm(rmm), _cr(cr), _tcm(tcm), _t(t) { + // TODO: system messages? + //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_MESSAGE); + + // TODO: conf + + // TODO: system messages? + //tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PEER_JOIN); + //tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_SELF_JOIN); + //tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PEER_NAME); + tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_MESSAGE); + tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PRIVATE_MESSAGE); + + _rmm.subscribe(this, RegistryMessageModel_Event::send_text); +} + +ToxMessageManager::~ToxMessageManager(void) { +} + +bool ToxMessageManager::sendText(const Contact3 c, std::string_view message, bool action) { + if (!_cr.valid(c)) { + return false; + } + + if (message.empty()) { + return false; // TODO: empty messages allowed? + } + + if (_cr.all_of(c)) { + return false; // message to self? not with tox + } + + // testing for persistent is enough + if (!_cr.any_of< + Contact::Components::ToxFriendPersistent, + // TODO: conf + Contact::Components::ToxGroupPersistent, + Contact::Components::ToxGroupPeerPersistent + >(c)) { + return false; + } + + auto* reg_ptr = _rmm.get(c); + if (reg_ptr == nullptr) { + return false; // nope + } + + Message3Registry& reg = *reg_ptr; + + if (!_cr.all_of(c)) { + std::cerr << "TMM error: cant get self\n"; + return false; + } + const Contact3 c_self = _cr.get(c).self; + + // get current time unix epoch utc + uint64_t ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + auto new_msg_e = reg.create(); + reg.emplace(new_msg_e, c_self); + reg.emplace(new_msg_e, c); + + reg.emplace(new_msg_e, message); + + if (action) { + reg.emplace(new_msg_e); + } + + reg.emplace(new_msg_e, ts); + reg.emplace(new_msg_e, ts); // reactive? + + // if sent? + reg.emplace(new_msg_e, ts); + + if (_cr.any_of(c)) { + // TODO: add friend offline messaging + const uint32_t friend_number = _cr.get(c).friend_number; + + auto [res, _] = _t.toxFriendSendMessage( + friend_number, + action ? Tox_Message_Type::TOX_MESSAGE_TYPE_ACTION : Tox_Message_Type::TOX_MESSAGE_TYPE_NORMAL, + message + ); + + if (!res.has_value()) { + return true; // not online? TODO: check for other errors + } + } else if ( + _cr.any_of(c) + ) { + const uint32_t group_number = _cr.get(c).group_number; + + auto [message_id_opt, _] = _t.toxGroupSendMessage( + group_number, + action ? Tox_Message_Type::TOX_MESSAGE_TYPE_ACTION : Tox_Message_Type::TOX_MESSAGE_TYPE_NORMAL, + message + ); + + if (!message_id_opt.has_value()) { + // set manually, so it can still be synced + const uint32_t msg_id = randombytes_random(); + reg.emplace(new_msg_e, msg_id); + + std::cerr << "TMM: failed to send group message!\n"; + } else { + // TODO: does group msg without msgid make sense??? + reg.emplace(new_msg_e, message_id_opt.value()); + + // TODO: generalize? + auto& synced_by = reg.emplace(new_msg_e).list; + synced_by.emplace(c_self); + } + } else if ( + // non online group + _cr.any_of(c) + ) { + // create msg_id + const uint32_t msg_id = randombytes_random(); + reg.emplace(new_msg_e, msg_id); + + // TODO: generalize? + auto& synced_by = reg.emplace(new_msg_e).list; + synced_by.emplace(c_self); + } else if ( + _cr.any_of(c) + ) { + const auto& numbers = _cr.get(c); + + auto res = _t.toxGroupSendPrivateMessage( + numbers.group_number, + numbers.peer_number, + action ? Tox_Message_Type::TOX_MESSAGE_TYPE_ACTION : Tox_Message_Type::TOX_MESSAGE_TYPE_NORMAL, + message + ); + + if (res != TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK) { + // TODO: add offline messaging + //return true; // not online? TODO: check for other errors + } + } + + _rmm.throwEventConstruct(reg, new_msg_e); + return true; +} + +bool ToxMessageManager::onToxEvent(const Tox_Event_Friend_Message* e) { + uint32_t friend_number = tox_event_friend_message_get_friend_number(e); + Tox_Message_Type type = tox_event_friend_message_get_type(e); + + // get current time unix epoch utc + uint64_t ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + std::string_view message {reinterpret_cast(tox_event_friend_message_get_message(e)), tox_event_friend_message_get_message_length(e)}; + message = message.substr(0, message.find_first_of('\0')); // trim \0 // hi zoff + // TODO: low-p, extract ts from zofftrim + // TODO: sanitize utf8 + + std::cout << "TMM friend message " << message << "\n"; + + const auto c = _tcm.getContactFriend(friend_number); + const auto self_c = c.get().self; + + auto* reg_ptr = _rmm.get(c); + if (reg_ptr == nullptr) { + std::cerr << "TMM error: cant find reg\n"; + return false; + } + + Message3Registry& reg = *reg_ptr; + auto new_msg_e = reg.create(); + + reg.emplace(new_msg_e, c); + reg.emplace(new_msg_e, self_c); + + reg.emplace(new_msg_e, message); + + if (type == Tox_Message_Type::TOX_MESSAGE_TYPE_ACTION) { + reg.emplace(new_msg_e); + } + + reg.emplace(new_msg_e, ts); + //reg.emplace(new_msg_e, 0); + reg.emplace(new_msg_e, ts); // reactive? + + _rmm.throwEventConstruct(reg, new_msg_e); + return false; // TODO: return true? +} + +bool ToxMessageManager::onToxEvent(const Tox_Event_Group_Message* e) { + const uint32_t group_number = tox_event_group_message_get_group_number(e); + const uint32_t peer_number = tox_event_group_message_get_peer_id(e); + const uint32_t message_id = tox_event_group_message_get_message_id(e); + const Tox_Message_Type type = tox_event_group_message_get_type(e); + + uint64_t ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + auto message = std::string_view{reinterpret_cast(tox_event_group_message_get_message(e)), tox_event_group_message_get_message_length(e)}; + std::cout << "TMM group message: " << message << "\n"; + + const auto c = _tcm.getContactGroupPeer(group_number, peer_number); + const auto self_c = c.get().self; + + auto* reg_ptr = _rmm.get(c); + //auto* reg_ptr = _rmm.get({ContactGroupPeerEphemeral{group_number, peer_number}}); + if (reg_ptr == nullptr) { + std::cerr << "TMM error: cant find reg\n"; + return false; + } + + Message3Registry& reg = *reg_ptr; + // TODO: check for existence, hs or other syncing mechanics might have sent it already (or like, it arrived 2x or whatever) + auto new_msg_e = reg.create(); + + { // contact + // from + reg.emplace(new_msg_e, c); + + // to + reg.emplace(new_msg_e, c.get().parent); + } + + reg.emplace(new_msg_e, message_id); + + reg.emplace(new_msg_e, message); + if (type == Tox_Message_Type::TOX_MESSAGE_TYPE_ACTION) { + reg.emplace(new_msg_e); + } + + reg.emplace(new_msg_e, ts); + //reg.emplace(new_msg_e, 0); + reg.emplace(new_msg_e, ts); // reactive? + + { // by whom + auto& synced_by = reg.get_or_emplace(new_msg_e).list; + synced_by.emplace(self_c); + } + + _rmm.throwEventConstruct(reg, new_msg_e); + return false; // TODO: true? +} + +bool ToxMessageManager::onToxEvent(const Tox_Event_Group_Private_Message* e) { + const uint32_t group_number = tox_event_group_private_message_get_group_number(e); + const uint32_t peer_number = tox_event_group_private_message_get_peer_id(e); + const Tox_Message_Type type = tox_event_group_private_message_get_type(e); + + uint64_t ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + auto message = std::string_view{reinterpret_cast(tox_event_group_private_message_get_message(e)), tox_event_group_private_message_get_message_length(e)}; + std::cout << "TMM group private message: " << message << "\n"; + + const auto c = _tcm.getContactGroupPeer(group_number, peer_number); + const auto self_c = c.get().self; + + auto* reg_ptr = _rmm.get(c); + if (reg_ptr == nullptr) { + std::cerr << "TMM error: cant find reg\n"; + return false; + } + + Message3Registry& reg = *reg_ptr; + auto new_msg_e = reg.create(); + + { // contact + // from + reg.emplace(new_msg_e, c); + + // to + reg.emplace(new_msg_e, self_c); + } + + reg.emplace(new_msg_e, message); + if (type == Tox_Message_Type::TOX_MESSAGE_TYPE_ACTION) { + reg.emplace(new_msg_e); + } + + reg.emplace(new_msg_e, ts); + //reg.emplace(new_msg_e, 0); + reg.emplace(new_msg_e, ts); // reactive? + + // private does not track synced by + + _rmm.throwEventConstruct(reg, new_msg_e); + return false; +} diff --git a/solanaceae/tox_messages/tox_message_manager.hpp b/solanaceae/tox_messages/tox_message_manager.hpp new file mode 100644 index 0000000..ab4a917 --- /dev/null +++ b/solanaceae/tox_messages/tox_message_manager.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +// fwd +struct ToxI; + +class ToxMessageManager : public RegistryMessageModelEventI, public ToxEventI { + protected: + RegistryMessageModel& _rmm; + Contact3Registry& _cr; + ToxContactModel2& _tcm; + ToxI& _t; + + public: + ToxMessageManager(RegistryMessageModel& rmm, Contact3Registry& cr, ToxContactModel2& tcm, ToxI& t, ToxEventProviderI& tep); + virtual ~ToxMessageManager(void); + + public: // mm3 + bool sendText(const Contact3 c, std::string_view message, bool action = false) override; + + protected: // tox events + bool onToxEvent(const Tox_Event_Friend_Message* e) override; + + bool onToxEvent(const Tox_Event_Group_Message* e) override; + bool onToxEvent(const Tox_Event_Group_Private_Message* e) override; +}; + diff --git a/solanaceae/tox_messages/tox_transfer_manager.cpp b/solanaceae/tox_messages/tox_transfer_manager.cpp new file mode 100644 index 0000000..df49cbd --- /dev/null +++ b/solanaceae/tox_messages/tox_transfer_manager.cpp @@ -0,0 +1,603 @@ +#include "./tox_transfer_manager.hpp" + +#include + +#include +#include + +#include +#include +#include +#include "./components.hpp" + +#include + +#include +#include +#include + +// https://youtu.be/4XsL5iYHS6c + +void ToxTransferManager::toxFriendLookupAdd(Message3Handle h) { + const auto& comp = h.get(); + const uint64_t key {(uint64_t(comp.friend_number) << 32) | comp.transfer_number}; + + if (h.all_of()) { + assert(!_friend_sending_lookup.count(key)); + _friend_sending_lookup[key] = h; + } + + if (h.all_of()) { + assert(!_friend_receiving_lookup.count(key)); + _friend_receiving_lookup[key] = h; + } +} + +void ToxTransferManager::toxFriendLookupRemove(Message3Handle h) { + const auto& comp = h.get(); + const uint64_t key {(uint64_t(comp.friend_number) << 32) | comp.transfer_number}; + + if (h.all_of()) { + assert(_friend_sending_lookup.count(key)); + _friend_sending_lookup.erase(key); + } + + if (h.all_of()) { + assert(_friend_receiving_lookup.count(key)); + _friend_receiving_lookup.erase(key); + } +} + +Message3Handle ToxTransferManager::toxFriendLookupSending(const uint32_t friend_number, const uint32_t file_number) const { + const auto lookup_it = _friend_sending_lookup.find((uint64_t(friend_number) << 32) | file_number); + if (lookup_it != _friend_sending_lookup.end()) { + return lookup_it->second; + } else { + return {}; + } +} + +Message3Handle ToxTransferManager::toxFriendLookupReceiving(const uint32_t friend_number, const uint32_t file_number) const { + const auto lookup_it = _friend_receiving_lookup.find((uint64_t(friend_number) << 32) | file_number); + if (lookup_it != _friend_receiving_lookup.end()) { + return lookup_it->second; + } else { + return {}; + } +} + +ToxTransferManager::ToxTransferManager(RegistryMessageModel& rmm, Contact3Registry& cr, ToxContactModel2& tcm, ToxI& t, ToxEventProviderI& tep) : _rmm(rmm), _cr(cr), _tcm(tcm), _t(t) { + tep.subscribe(this, Tox_Event::TOX_EVENT_FRIEND_CONNECTION_STATUS); + tep.subscribe(this, Tox_Event::TOX_EVENT_FILE_RECV); + tep.subscribe(this, Tox_Event::TOX_EVENT_FILE_RECV_CONTROL); + tep.subscribe(this, Tox_Event::TOX_EVENT_FILE_RECV_CHUNK); + tep.subscribe(this, Tox_Event::TOX_EVENT_FILE_CHUNK_REQUEST); + + _rmm.subscribe(this, RegistryMessageModel_Event::message_construct); + _rmm.subscribe(this, RegistryMessageModel_Event::message_updated); + _rmm.subscribe(this, RegistryMessageModel_Event::message_destroy); + + _rmm.subscribe(this, RegistryMessageModel_Event::send_file_path); +} + +ToxTransferManager::~ToxTransferManager(void) { +} + +void ToxTransferManager::iterate(void) { + // TODO: time out transfers +} + +Message3Handle ToxTransferManager::toxSendFilePath(const Contact3 c, uint32_t file_kind, std::string_view file_name, std::string_view file_path) { + if ( + // TODO: add support of offline queuing + !_cr.all_of(c) + ) { + std::cerr << "TTM error: unsupported contact type\n"; + return {}; + } + + auto* reg_ptr = _rmm.get(c); + if (reg_ptr == nullptr) { + return {}; + } + + auto file_impl = std::make_unique(file_path); + if (!file_impl->isGood()) { + std::cerr << "TTM error: failed opening file '" << file_path << "'!\n"; + return {}; + } + + // get current time unix epoch utc + uint64_t ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // TODO: expose file id + std::vector file_id(32); assert(file_id.size() == 32); + randombytes_buf(file_id.data(), file_id.size()); + + const auto c_self = _cr.get(c).self; + if (!_cr.valid(c_self)) { + std::cerr << "TTM error: failed to get self!\n"; + return {}; + } + + const auto e = reg_ptr->create(); + reg_ptr->emplace(e, c); + reg_ptr->emplace(e, c_self); + reg_ptr->emplace(e, ts); // reactive? + + reg_ptr->emplace(e); + reg_ptr->emplace(e); + reg_ptr->emplace(e, file_kind); + reg_ptr->emplace(e, file_id); + + { // file info + auto& file_info = reg_ptr->emplace(e); + file_info.file_list.emplace_back() = {std::string{file_name}, file_impl->_file_size}; + file_info.total_size = file_impl->_file_size; + + reg_ptr->emplace(e, std::vector{std::string{file_path}}); + } + + reg_ptr->emplace(e); + + // TODO: determine if this is true + reg_ptr->emplace(e); + + const auto friend_number = _cr.get(c).friend_number; + const auto&& [transfer_id, err] = _t.toxFileSend(friend_number, file_kind, file_impl->_file_size, file_id, file_name); + if (err == TOX_ERR_FILE_SEND_OK) { + reg_ptr->emplace(e, friend_number, transfer_id.value()); + reg_ptr->emplace(e, std::move(file_impl)); + // TODO: add tag signifying init sent status? + + toxFriendLookupAdd({*reg_ptr, e}); + } // else queue? + + _rmm.throwEventConstruct(*reg_ptr, e); + return {*reg_ptr, e}; +} + +bool ToxTransferManager::resume(Message3Handle transfer) { + if (!static_cast(transfer)) { + std::cerr << "TTM error: resume() transfer " << entt::to_integral(transfer.entity()) << " is not a valid transfer\n"; + return false; + } + + // TODO: test for paused? + + if (!transfer.all_of()) { + std::cerr << "TTM error: resume() transfer " << entt::to_integral(transfer.entity()) << " ent does not have toxtransfer info\n"; + return false; + } + + const auto [friend_number, transfer_number] = transfer.get(); + + const auto err = _t.toxFileControl(friend_number, transfer_number, TOX_FILE_CONTROL_RESUME); + if (err != TOX_ERR_FILE_CONTROL_OK) { + std::cerr << "TTM error: resume() transfer " << entt::to_integral(transfer.entity()) << " tox file control error " << err << "\n"; + return false; + } + + transfer.remove(); + + _rmm.throwEventUpdate(transfer); + + return true; +} + +bool ToxTransferManager::pause(Message3Handle transfer) { + if (!static_cast(transfer)) { + std::cerr << "TTM error: pause() transfer " << entt::to_integral(transfer.entity()) << " is not a valid transfer\n"; + return false; + } + + // TODO: test for paused? + + if (!transfer.all_of()) { + std::cerr << "TTM error: pause() transfer " << entt::to_integral(transfer.entity()) << " ent does not have toxtransfer info\n"; + return false; + } + + const auto [friend_number, transfer_number] = transfer.get(); + + const auto err = _t.toxFileControl(friend_number, transfer_number, TOX_FILE_CONTROL_PAUSE); + if (err != TOX_ERR_FILE_CONTROL_OK) { + std::cerr << "TTM error: pause() transfer " << entt::to_integral(transfer.entity()) << " tox file control error " << err << "\n"; + return false; + } + + transfer.emplace_or_replace(); + + _rmm.throwEventUpdate(transfer); + + return true; +} + +bool ToxTransferManager::setFileI(Message3Handle transfer, std::unique_ptr&& new_file) { + if (!static_cast(transfer)) { + std::cerr << "TTM error: setFileI() transfer " << entt::to_integral(transfer.entity()) << " is not a valid transfer\n"; + return false; + } + + if (!new_file->isGood()) { + std::cerr << "TTM error: failed setting new_file_impl!\n"; + return false; + } + + transfer.emplace_or_replace(std::move(new_file)); + + _rmm.throwEventUpdate(transfer); + + return true; +} + +bool ToxTransferManager::setFilePathDir(Message3Handle transfer, std::string_view file_path) { + if (!static_cast(transfer)) { + std::cerr << "TTM error: setFilePathDir() transfer " << entt::to_integral(transfer.entity()) << " is not a valid transfer\n"; + return false; + } + + uint64_t file_size {0}; + std::string full_file_path{file_path}; + // TODO: replace with filesystem or something + // TODO: ensure dir exists + if (full_file_path.back() != '/') { + full_file_path += "/"; + } + + // TODO: read file name(s) from comp + if (transfer.all_of()) { + const auto& file_info = transfer.get(); + file_size = file_info.total_size; // hack + // HACK: use fist enty + assert(file_info.file_list.size() == 1); + full_file_path += file_info.file_list.front().file_name; + + } else { + std::cerr << "TTM warning: no FileInfo on transfer, using default\n"; + full_file_path += "file_recv.bin"; + } + + transfer.emplace(std::vector{full_file_path}); + + auto file_impl = std::make_unique(full_file_path, file_size); + if (!file_impl->isGood()) { + std::cerr << "TTM error: failed opening file '" << file_path << "'!\n"; + return false; + } + + transfer.emplace_or_replace(std::move(file_impl)); + + // TODO: is this a good idea???? + _rmm.throwEventUpdate(transfer); + + return true; +} + +bool ToxTransferManager::accept(Message3Handle transfer, std::string_view file_path) { + if (!static_cast(transfer)) { + std::cerr << "TTM error: accepted transfer " << entt::to_integral(transfer.entity()) << " is not a valid transfer\n"; + return false; + } + + if (!transfer.all_of()) { + std::cerr << "TTM error: accepted transfer " << entt::to_integral(transfer.entity()) << " is not a receiving transfer\n"; + return false; + } + + if (transfer.any_of()) { + std::cerr << "TTM warning: overwriting existing file_impl " << entt::to_integral(transfer.entity()) << "\n"; + } + + if (!setFilePathDir(transfer, file_path)) { + std::cerr << "TTM error: accepted transfer " << entt::to_integral(transfer.entity()) << " failed setting path\n"; + return false; + } + + if (!resume(transfer)) { + std::cerr << "TTM error: accepted transfer " << entt::to_integral(transfer.entity()) << " failed to resume\n"; + return false; + } + + std::cout << "TTM info: accepted " << entt::to_integral(transfer.entity()) << ", saving to " << file_path << "\n"; + + // setFilePathDir() and resume() throw events + + return true; +} + +bool ToxTransferManager::onEvent(const Message::Events::MessageConstruct&) { + return false; +} + +bool ToxTransferManager::onEvent(const Message::Events::MessageUpdated& e) { + if (e.e.all_of()) { + accept(e.e, e.e.get().save_to_path); + + // should? + e.e.remove(); + + // TODO: recursion?? + // oh no, accept calls it 2x + //_rmm.throwEventUpdate( + } + + return false; +} + +bool ToxTransferManager::onEvent(const Message::Events::MessageDestory& e) { + if (e.e.all_of()) { + toxFriendLookupRemove(e.e); + } + + return false; +} + +bool ToxTransferManager::onToxEvent(const Tox_Event_Friend_Connection_Status* e) { + const auto friend_number = tox_event_friend_connection_status_get_friend_number(e); + const auto connection_status = tox_event_friend_connection_status_get_connection_status(e); + + if (connection_status == TOX_CONNECTION_NONE) { + auto c = _tcm.getContactFriend(friend_number); + auto* reg_ptr = _rmm.get(c); + if (reg_ptr == nullptr) { + return false; + } + + std::vector to_destory; + reg_ptr->view().each([&](const Message3 ent, const auto& ttfs) { + assert(ttfs.friend_number == friend_number); + //if (ttfs.friend_number == friend_number) { + to_destory.push_back(ent); + std::cerr << "TTM warning: friend disconnected, forcefully removing e:" << entt::to_integral(ent) << " frd:" << friend_number << " fnb:" << ttfs.transfer_number << "\n"; + }); + + for (const auto ent : to_destory) { + // update lookup table + toxFriendLookupRemove({*reg_ptr, ent}); + + // TODO: removing file a good idea? + reg_ptr->remove(ent); + + reg_ptr->emplace_or_replace(ent); + + _rmm.throwEventUpdate(*reg_ptr, ent); + } + } + + return false; // always continue +} + +bool ToxTransferManager::onToxEvent(const Tox_Event_File_Recv* e) { + const auto friend_number = tox_event_file_recv_get_friend_number(e); + const std::string_view file_name { + reinterpret_cast(tox_event_file_recv_get_filename(e)), + tox_event_file_recv_get_filename_length(e) + }; + const auto file_number = tox_event_file_recv_get_file_number(e); + const auto file_size = tox_event_file_recv_get_file_size(e); + const auto file_kind = tox_event_file_recv_get_kind(e); + + auto c = _tcm.getContactFriend(friend_number); + auto* reg_ptr = _rmm.get(c); + if (reg_ptr == nullptr) { + return false; + } + + // making sure, we dont have a dup + Message3Handle transfer {}; + reg_ptr->view().each([&](Message3 ent, const Message::Components::Transfer::ToxTransferFriend& ttf) { + if (ttf.friend_number == friend_number && ttf.transfer_number == file_number) { + transfer = {*reg_ptr, ent}; + } + }); + if (static_cast(transfer)) { + std::cerr << "TTM error: existing file transfer frd:" << friend_number << " fnb:" << file_number << "\n"; + return false; + } + assert(!_friend_receiving_lookup.count((uint64_t(friend_number) << 32) | file_number)); + + // TODO: also check for file id + + // get current time unix epoch utc + uint64_t ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // create ent + transfer = {*reg_ptr, reg_ptr->create()}; + + auto self_c = _cr.get(c).self; + + transfer.emplace(self_c); + transfer.emplace(c); + transfer.emplace(ts); // reactive? + + transfer.emplace(); + transfer.emplace(); + transfer.emplace(friend_number, file_number); + transfer.emplace(file_kind); + + auto [f_id_opt, _] = _t.toxFileGetFileID(friend_number, file_number); + assert(f_id_opt.has_value()); + transfer.emplace(f_id_opt.value()); + + { // file info + auto& file_info = transfer.emplace(); + file_info.file_list.push_back({std::string{file_name}, file_size}); + file_info.total_size = file_size; + } + + transfer.emplace(); + + toxFriendLookupAdd(transfer); + + _rmm.throwEventConstruct(transfer); + + return true; +} + +bool ToxTransferManager::onToxEvent(const Tox_Event_File_Recv_Control* e) { + const auto friend_number = tox_event_file_recv_control_get_friend_number(e); + const auto file_number = tox_event_file_recv_control_get_file_number(e); + const auto control = tox_event_file_recv_control_get_control(e); + + // first try sending + Message3Handle transfer = toxFriendLookupSending(friend_number, file_number); + if (!static_cast(transfer)) { + // then receiving + transfer = toxFriendLookupReceiving(friend_number, file_number); + } + + if (!static_cast(transfer)) { + std::cerr << "TMM waring: control for unk ft\n"; + return false; // shrug, we don't know about it, might be someone else's + } + + if (control == TOX_FILE_CONTROL_CANCEL) { + std::cerr << "TTM: friend transfer canceled frd:" << friend_number << " fnb:" << file_number << "\n"; + + // update lookup table + toxFriendLookupRemove(transfer); + + transfer.remove< + Message::Components::Transfer::ToxTransferFriend, + // TODO: removing file a good idea? + Message::Components::Transfer::File + >(); + + _rmm.throwEventUpdate(transfer); + } else if (control == TOX_FILE_CONTROL_PAUSE) { + std::cerr << "TTM: friend transfer paused frd:" << friend_number << " fnb:" << file_number << "\n"; + // TODO: add distinction between local and remote pause + transfer.emplace_or_replace(); + _rmm.throwEventUpdate(transfer); + } else if (control == TOX_FILE_CONTROL_RESUME) { + std::cerr << "TTM: friend transfer resumed frd:" << friend_number << " fnb:" << file_number << "\n"; + transfer.remove(); + _rmm.throwEventUpdate(transfer); + } + + return true; +} + +bool ToxTransferManager::onToxEvent(const Tox_Event_File_Recv_Chunk* e) { + const auto friend_number = tox_event_file_recv_chunk_get_friend_number(e); + const auto file_number = tox_event_file_recv_chunk_get_file_number(e); + const uint8_t* data = tox_event_file_recv_chunk_get_data(e); + const auto data_size = tox_event_file_recv_chunk_get_length(e); + const auto position = tox_event_file_recv_chunk_get_position(e); + + Message3Handle transfer = toxFriendLookupReceiving(friend_number, file_number); + if (!static_cast(transfer)) { + return false; // shrug, we don't know about it, might be someone else's + } + + if (data_size == 0) { + std::cout << "TTM finished friend " << friend_number << " transfer " << file_number << ", closing\n"; + + // update lookup table + toxFriendLookupRemove(transfer); + + transfer.remove< + Message::Components::Transfer::ToxTransferFriend, + // TODO: removing file a good idea? + Message::Components::Transfer::File + >(); + + transfer.emplace(); + + _rmm.throwEventUpdate(transfer); + } else if (!transfer.all_of() || !transfer.get()->isGood()) { + std::cerr << "TTM error: file not good f" << friend_number << " t" << file_number << ", closing\n"; + _t.toxFileControl(friend_number, file_number, Tox_File_Control::TOX_FILE_CONTROL_CANCEL); + + // update lookup table + toxFriendLookupRemove(transfer); + + transfer.remove< + Message::Components::Transfer::ToxTransferFriend, + // TODO: removing file a good idea? + Message::Components::Transfer::File + >(); + + _rmm.throwEventUpdate(transfer); + } else { + auto* file = transfer.get().get(); + const auto res = file->write(position, std::vector{data, data+data_size}); + transfer.get().total += data_size; + + // queue? + _rmm.throwEventUpdate(transfer); + } + + return true; +} + +bool ToxTransferManager::onToxEvent(const Tox_Event_File_Chunk_Request* e) { + const auto friend_number = tox_event_file_chunk_request_get_friend_number(e); + const auto file_number = tox_event_file_chunk_request_get_file_number(e); + const auto position = tox_event_file_chunk_request_get_position(e); + const auto data_size = tox_event_file_chunk_request_get_length(e); + + Message3Handle transfer = toxFriendLookupSending(friend_number, file_number); + if (!static_cast(transfer)) { + std::cerr << "TTM warning: chunk request for unk ft\n"; + return false; // shrug, we don't know about it, might be someone else's + } + + // tox wants us to end the transmission + if (data_size == 0) { + std::cout << "TTM finished friend " << friend_number << " transfer " << file_number << ", closing\n"; + + // update lookup table + toxFriendLookupRemove(transfer); + + transfer.remove< + Message::Components::Transfer::ToxTransferFriend, + // TODO: removing file a good idea? + Message::Components::Transfer::File + >(); + + // TODO: add tag finished? + _rmm.throwEventUpdate(transfer); + } else if (!transfer.all_of() || !transfer.get()->isGood()) { + std::cerr << "TTM error: file not good f" << friend_number << " t" << file_number << ", closing\n"; + _t.toxFileControl(friend_number, file_number, Tox_File_Control::TOX_FILE_CONTROL_CANCEL); + + // update lookup table + toxFriendLookupRemove(transfer); + + transfer.remove< + Message::Components::Transfer::ToxTransferFriend, + // TODO: removing file a good idea? + Message::Components::Transfer::File + >(); + + _rmm.throwEventUpdate(transfer); + } else { + auto* file = transfer.get().get(); + const auto data = file->read(position, data_size); + + const auto err = _t.toxFileSendChunk(friend_number, file_number, position, data); + // TODO: investigate if i need to retry if sendq full + if (err == TOX_ERR_FILE_SEND_CHUNK_OK) { + transfer.get().total += data.size(); + _rmm.throwEventUpdate(transfer); + } + } + + return true; +} + +bool ToxTransferManager::sendFilePath(const Contact3 c, std::string_view file_name, std::string_view file_path) { + if ( + // TODO: add support of offline queuing + !_cr.all_of(c) + ) { + // TODO: add support for persistant friend filesends + return false; + } + + toxSendFilePath(c, 0, file_name, file_path); + + return false; +} diff --git a/solanaceae/tox_messages/tox_transfer_manager.hpp b/solanaceae/tox_messages/tox_transfer_manager.hpp new file mode 100644 index 0000000..ec29e74 --- /dev/null +++ b/solanaceae/tox_messages/tox_transfer_manager.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +#include +#include + +// fwd +struct ToxI; + +class ToxTransferManager : public RegistryMessageModelEventI, public ToxEventI { + protected: + RegistryMessageModel& _rmm; + Contact3Registry& _cr; + ToxContactModel2& _tcm; + ToxI& _t; + + entt::dense_map _friend_sending_lookup; + entt::dense_map _friend_receiving_lookup; + + protected: + void toxFriendLookupAdd(Message3Handle h); + void toxFriendLookupRemove(Message3Handle h); + + Message3Handle toxFriendLookupSending(const uint32_t friend_number, const uint32_t file_number) const; + Message3Handle toxFriendLookupReceiving(const uint32_t friend_number, const uint32_t file_number) const; + + public: + ToxTransferManager(RegistryMessageModel& rmm, Contact3Registry& cr, ToxContactModel2& tcm, ToxI& t, ToxEventProviderI& tep); + virtual ~ToxTransferManager(void); + + virtual void iterate(void); + + public: // TODO: private? + Message3Handle toxSendFilePath(const Contact3 c, uint32_t file_kind, std::string_view file_name, std::string_view file_path); + + bool resume(Message3Handle transfer); + bool pause(Message3Handle transfer); + bool setFileI(Message3Handle transfer, std::unique_ptr&& new_file); // note, does not emplace FileInfoLocal + bool setFilePathDir(Message3Handle transfer, std::string_view file_path); + + // calls setFileI() and resume() + bool accept(Message3Handle transfer, std::string_view file_path); + + protected: + bool onEvent(const Message::Events::MessageConstruct&) override; + bool onEvent(const Message::Events::MessageUpdated&) override; + bool onEvent(const Message::Events::MessageDestory&) override; + + protected: // events + virtual bool onToxEvent(const Tox_Event_Friend_Connection_Status* e) override; + virtual bool onToxEvent(const Tox_Event_File_Recv* e) override; + virtual bool onToxEvent(const Tox_Event_File_Recv_Control* e) override; + virtual bool onToxEvent(const Tox_Event_File_Recv_Chunk* e) override; + virtual bool onToxEvent(const Tox_Event_File_Chunk_Request* e) override; + + bool sendFilePath(const Contact3 c, std::string_view file_name, std::string_view file_path) override; +}; +