diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c2af2c3..e14f7345 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -61,6 +61,9 @@ add_executable(tomato ./tox_dht_cap_histo.hpp ./tox_dht_cap_histo.cpp + ./tox_friend_faux_offline_messaging.hpp + ./tox_friend_faux_offline_messaging.cpp + ./chat_gui4.hpp ./chat_gui4.cpp ) diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index eaccac33..c91783a1 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -96,13 +96,21 @@ const void* clipboard_callback(void* userdata, const char* mime_type, size_t* si } void ChatGui4::setClipboardData(std::vector mime_types, std::shared_ptr>&& data) { - std::vector tmp_mimetype_list; - for (const auto& mime_type : mime_types) { - tmp_mimetype_list.push_back(mime_type.data()); + if (!static_cast(data)) { + std::cerr << "CG error: tried to set clipboard with empty shp\n"; + return; } + if (data->empty()) { + std::cerr << "CG error: tried to set clipboard with empty data\n"; + return; + } + + std::vector tmp_mimetype_list; + std::lock_guard lg{_set_clipboard_data_mutex}; for (const auto& mime_type : mime_types) { + tmp_mimetype_list.push_back(mime_type.data()); _set_clipboard_data[mime_type] = data; } diff --git a/src/main_screen.cpp b/src/main_screen.cpp index f5eb477d..d68c5e1b 100644 --- a/src/main_screen.cpp +++ b/src/main_screen.cpp @@ -16,6 +16,7 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::stri tcm(cr, tc, tc), tmm(rmm, cr, tcm, tc, tc), ttm(rmm, cr, tcm, tc, tc), + tffom(cr, rmm, tcm, tc, tc), mmil(rmm), tam(rmm, cr, conf), sdlrtu(renderer_), @@ -241,6 +242,8 @@ Screen* MainScreen::tick(float time_delta, bool& quit) { tcm.iterate(time_delta); // compute + const float fo_interval = tffom.tick(time_delta); + tam.iterate(); // compute const float pm_interval = pm.tick(time_delta); // compute @@ -253,6 +256,10 @@ Screen* MainScreen::tick(float time_delta, bool& quit) { tc.toxIterationInterval()/1000.f, pm_interval ); + _min_tick_interval = std::min( + _min_tick_interval, + fo_interval + ); switch (_compute_perf_mode) { // normal 1ms lower bound diff --git a/src/main_screen.hpp b/src/main_screen.hpp index 84facd66..8dfaf7cc 100644 --- a/src/main_screen.hpp +++ b/src/main_screen.hpp @@ -25,6 +25,7 @@ #include "./settings_window.hpp" #include "./tox_ui_utils.hpp" #include "./tox_dht_cap_histo.hpp" +#include "./tox_friend_faux_offline_messaging.hpp" #include #include @@ -52,6 +53,7 @@ struct MainScreen final : public Screen { ToxContactModel2 tcm; ToxMessageManager tmm; ToxTransferManager ttm; + ToxFriendFauxOfflineMessaging tffom; MediaMetaInfoLoader mmil; ToxAvatarManager tam; diff --git a/src/tox_friend_faux_offline_messaging.cpp b/src/tox_friend_faux_offline_messaging.cpp new file mode 100644 index 00000000..ca93336d --- /dev/null +++ b/src/tox_friend_faux_offline_messaging.cpp @@ -0,0 +1,188 @@ +#include "./tox_friend_faux_offline_messaging.hpp" + +#include + +#include +#include +#include +#include + +#include +#include + +namespace Message::Components { + struct LastSendAttempt { + uint64_t ts {0}; + }; +} // Message::Components + +namespace Contact::Components { + struct NextSendAttempt { + uint64_t ts {0}; + }; +} // Contact::Components + +ToxFriendFauxOfflineMessaging::ToxFriendFauxOfflineMessaging( + Contact3Registry& cr, + RegistryMessageModel& rmm, + ToxContactModel2& tcm, + ToxI& t, + ToxEventProviderI& tep +) : _cr(cr), _rmm(rmm), _tcm(tcm), _t(t), _tep(tep) { +} + +float ToxFriendFauxOfflineMessaging::tick(float time_delta) { + // hard limit interval to once per minute + _interval_timer += time_delta; + if (_interval_timer < 1.f * 60.f) { + return std::max(60.f - _interval_timer, 0.001f); // TODO: min next timer + } + _interval_timer = 0.f; + + const uint64_t ts_now = Message::getTimeMS(); + + // check ALL + // for each online tox friend + uint64_t min_next_attempt_ts {std::numeric_limits::max()}; + _cr.view() + .each([this, &min_next_attempt_ts, ts_now](const Contact3 c, const auto& tfe, const auto& cs) { + if (cs.state == Contact::Components::ConnectionState::disconnected) { + // cleanup + if (_cr.all_of(c)) { + _cr.remove(c); + } + } else { + if (!_cr.all_of(c)) { + const auto& nsa = _cr.emplace(c, ts_now + uint64_t(_delay_after_cc*1000)); // wait before first message is sent + min_next_attempt_ts = std::min(min_next_attempt_ts, nsa.ts); + } else { + auto& next_attempt = _cr.get(c).ts; + + if (doFriendMessageCheck(c, tfe)) { + next_attempt = ts_now + uint64_t(_delay_inbetween*1000); + } + + min_next_attempt_ts = std::min(min_next_attempt_ts, next_attempt); + } + } + }); + + if (min_next_attempt_ts <= ts_now) { + // we (probably) sent this iterate + _interval_timer = 60.f - 0.1f; // TODO: ugly magic + return 0.1f; + } else if (min_next_attempt_ts == std::numeric_limits::max()) { + // nothing to sync or all offline that need syncing + return 60.f; // TODO: ugly magic + } else { + // TODO: ugly magic + return _interval_timer = 60.f - std::min(60.f, (min_next_attempt_ts - ts_now) / 1000.f); + } +} + +bool ToxFriendFauxOfflineMessaging::doFriendMessageCheck(const Contact3 c, const Contact::Components::ToxFriendEphemeral& tfe) { + // walk all messages and check if + // unacked message + // timeouts for exising unacked messages expired (send) + + auto* mr = static_cast(_rmm).get(c); + if (mr == nullptr) { + // no messages + return false; + } + + const uint64_t ts_now = Message::getTimeMS(); + + // filter for unconfirmed messages + + // we assume sorted + // ("reverse" iteration <.<) + auto msg_view = mr->view(); + // we search for the oldest, not too recently sent, unconfirmed message + for (auto it = msg_view.rbegin(), view_end = msg_view.rend(); it != view_end; it++) { + const Message3 msg = *it; + + // require + if (!mr->all_of< + Message::Components::MessageText, // text only for now + Message::Components::ContactTo + >(msg) + ) { + continue; // skip + } + + // exclude + if (mr->any_of< + Message::Components::Remote::TimestampReceived // this acts like a tag, which is wrong in groups + >(msg) + ) { + continue; // skip + } + + uint64_t msg_ts = msg_view.get(msg).ts; + if (mr->all_of(msg)) { + msg_ts = mr->get(msg).ts; + } + if (mr->all_of(msg)) { + const auto lsa = mr->get(msg).ts; + if (lsa > msg_ts) { + msg_ts = lsa; + } + } + + if (ts_now < (msg_ts + uint64_t(_delay_retry * 1000))) { + // not time yet + continue; + } + + // it is time + const auto [msg_id, _] = _t.toxFriendSendMessage( + tfe.friend_number, + ( + mr->all_of(msg) + ? Tox_Message_Type::TOX_MESSAGE_TYPE_ACTION + : Tox_Message_Type::TOX_MESSAGE_TYPE_NORMAL + ), + mr->get(msg).text + ); + + // TODO: this is ugly + mr->emplace_or_replace(msg, ts_now); + + if (msg_id.has_value()) { + // tmm will pick this up for us + mr->emplace_or_replace(msg, msg_id.value()); + } // else error + + // we sent our message, no point further iterating + return true; + } + + // TODO: somehow cleanup lsa + + return false; +} + +bool ToxFriendFauxOfflineMessaging::onToxEvent(const Tox_Event_Friend_Connection_Status* e) { + const auto friend_number = tox_event_friend_connection_status_get_friend_number(e); + const auto friend_status = tox_event_friend_connection_status_get_connection_status(e); + + if (friend_status == Tox_Connection::TOX_CONNECTION_NONE) { + return false; // skip + // maybe cleanup? + } + + auto c = _tcm.getContactFriend(friend_number); + if (!static_cast(c) || !c.all_of()) { + // UH error?? + return false; + } + + _cr.emplace_or_replace(c, Message::getTimeMS() + uint64_t(_delay_after_cc*1000)); // wait before first message is sent + + // TODO: ugly magic + _interval_timer = 60.f - 0.1f; + + return false; +} + diff --git a/src/tox_friend_faux_offline_messaging.hpp b/src/tox_friend_faux_offline_messaging.hpp new file mode 100644 index 00000000..4829216d --- /dev/null +++ b/src/tox_friend_faux_offline_messaging.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +// fwd +struct ToxI; +namespace Contact::Components { + struct ToxFriendEphemeral; +} + +// resends unconfirmed messages. +// timers get reset on connection changes, and send order is preserved. +class ToxFriendFauxOfflineMessaging : public ToxEventI { + Contact3Registry& _cr; + RegistryMessageModel& _rmm; + ToxContactModel2& _tcm; + ToxI& _t; + ToxEventProviderI& _tep; + + float _interval_timer{0.f}; + + // TODO: increase timer? + const float _delay_after_cc {4.5f}; + const float _delay_inbetween {0.3f}; + const float _delay_retry {10.f}; // retry sending after 10s + + public: + ToxFriendFauxOfflineMessaging( + Contact3Registry& cr, + RegistryMessageModel& rmm, + ToxContactModel2& tcm, + ToxI& t, + ToxEventProviderI& tep + ); + + float tick(float time_delta); + + private: + // only called for online friends + // returns true if a message was sent + // dont call this too often + bool doFriendMessageCheck(const Contact3 c, const Contact::Components::ToxFriendEphemeral& tfe); + + protected: + bool onToxEvent(const Tox_Event_Friend_Connection_Status* e) override; +}; +