#include "./tox_friend_faux_offline_messaging.hpp" #include #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, RegistryMessageModelI& rmm, ToxContactModel2& tcm, ToxI& t, ToxEventProviderI& tep ) : _cr(cr), _rmm(rmm), _tcm(tcm), _t(t), _tep_sr(tep.newSubRef(this)) { _tep_sr.subscribe(Tox_Event_Type::TOX_EVENT_FRIEND_CONNECTION_STATUS); } float ToxFriendFauxOfflineMessaging::tick(float time_delta) { _interval_timer -= time_delta; if (_interval_timer > 0.f) { return std::max(_interval_timer, 0.001f); // TODO: min next timer } // interval ~ once per minute _interval_timer = 60.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); auto* mr = static_cast(_rmm).get(c); if (mr != nullptr) { mr->storage().clear(); } } } else { if (!_cr.all_of(c)) { if (false) { // has unsent messages 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 ret = doFriendMessageCheck(c, tfe); if (ret == dfmc_Ret::SENT_THIS_TICK) { const auto ts = _cr.get(c).ts = ts_now + uint64_t(_delay_inbetween*1000); min_next_attempt_ts = std::min(min_next_attempt_ts, ts); } else if (ret == dfmc_Ret::TOO_SOON) { // TODO: set to _delay_inbetween? prob expensive for no good reason min_next_attempt_ts = std::min(min_next_attempt_ts, _cr.get(c).ts); } else { _cr.remove(c); } } } }); if (min_next_attempt_ts <= ts_now) { // we (probably) sent this iterate _interval_timer = 0.1f; // TODO: ugly magic } else if (min_next_attempt_ts == std::numeric_limits::max()) { // nothing to sync or all offline that need syncing } else { _interval_timer = std::min(_interval_timer, (min_next_attempt_ts - ts_now) / 1000.f); } //std::cout << "TFFOM: iterate (i:" << _interval_timer << ")\n"; return _interval_timer; } ToxFriendFauxOfflineMessaging::dfmc_Ret 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 dfmc_Ret::NO_MSG; } const uint64_t ts_now = Message::getTimeMS(); if (!_cr.all_of(c)) { return dfmc_Ret::NO_MSG; // error } const auto self_c = _cr.get(c).self; // filter for unconfirmed messages // we assume sorted // ("reverse" iteration <.<) auto msg_view = mr->view(); bool valid_unsent {false}; // 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, Message::Components::ToxFriendMessageID // yes, needs fake ids >(msg) ) { continue; // skip } if (!mr->any_of< Message::Components::ReceivedBy >(msg) ) { continue; // skip } if (mr->get(msg).c != c) { continue; // not outbound (in private) } const auto& ts_received = mr->get(msg).ts; // not target if (ts_received.contains(c)) { continue; } // needs to contain self if (!ts_received.contains(self_c)) { continue; } valid_unsent = true; 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 dfmc_Ret::SENT_THIS_TICK; } if (!valid_unsent) { // somehow cleanup lsa mr->storage().clear(); //std::cout << "TFFOM: all sent, deleting lsa\n"; return dfmc_Ret::NO_MSG; } return dfmc_Ret::TOO_SOON; } 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 _interval_timer = 0.f; return false; }