diff --git a/external/solanaceae_contact b/external/solanaceae_contact index 738d2abe..5ff7d1ce 160000 --- a/external/solanaceae_contact +++ b/external/solanaceae_contact @@ -1 +1 @@ -Subproject commit 738d2abe7b4d6088412e56b89f4e26aab1a8bdba +Subproject commit 5ff7d1cee0c3ed22f9fe7d66021d95ad1c5a3f04 diff --git a/external/solanaceae_message3 b/external/solanaceae_message3 index c577a1fa..48fb5f08 160000 --- a/external/solanaceae_message3 +++ b/external/solanaceae_message3 @@ -1 +1 @@ -Subproject commit c577a1fa3d19272d481a0ed4a5b8715524204928 +Subproject commit 48fb5f0889404370006ae12b3637a77d7d4ba485 diff --git a/external/solanaceae_tox b/external/solanaceae_tox index 0e6556cd..0404ed84 160000 --- a/external/solanaceae_tox +++ b/external/solanaceae_tox @@ -1 +1 @@ -Subproject commit 0e6556cd86c558c86dfde60d82891a46bf32b64f +Subproject commit 0404ed84fcc31716918b90b3603c722a73e908cb diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c070f1f..23884a7b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,9 @@ add_executable(tomato ./message_image_loader.hpp ./message_image_loader.cpp + ./tox_avatar_manager.hpp + ./tox_avatar_manager.cpp + ./media_meta_info_loader.hpp ./media_meta_info_loader.cpp diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index 78ea3feb..877dca00 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -374,8 +374,8 @@ void ChatGui4::render(void) { //} else if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { } else if (ImGui::BeginPopupContextItem(nullptr, ImGuiMouseButton_Right)) { const static std::vector image_mime_types { - "image/webp", "image/png", + "image/webp", "image/gif", "image/jpeg", "image/bmp", @@ -459,6 +459,12 @@ void ChatGui4::renderMessageBodyText(Message3Registry& reg, const Message3 e) { } void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { + if (reg.all_of(e) && reg.get(e).kind == 1) { + // TODO: this looks ugly + ImGui::TextDisabled("set avatar"); + return; + } + #if 0 if (msg_reg.all_of(e)) { switch (msg_reg.get(e).state) { diff --git a/src/main_screen.cpp b/src/main_screen.cpp index df8b4a5b..d3795a77 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 tmm(rmm, cr, tcm, tc, tc), ttm(rmm, cr, tcm, tc, tc), mmil(rmm), + tam(rmm, cr, conf), sdlrtu(renderer_), cg(conf, rmm, cr, sdlrtu) { @@ -78,6 +79,8 @@ Screen* MainScreen::poll(bool& quit) { tcm.iterate(time_delta); + tam.iterate(); + pm.tick(time_delta); mts.iterate(); diff --git a/src/main_screen.hpp b/src/main_screen.hpp index 2830b5fd..2b87b518 100644 --- a/src/main_screen.hpp +++ b/src/main_screen.hpp @@ -17,6 +17,7 @@ #include "./auto_dirty.hpp" #include "./media_meta_info_loader.hpp" +#include "./tox_avatar_manager.hpp" #include "./sdlrenderer_texture_uploader.hpp" #include "./chat_gui4.hpp" @@ -50,6 +51,7 @@ struct MainScreen final : public Screen { ToxTransferManager ttm; MediaMetaInfoLoader mmil; + ToxAvatarManager tam; SDLRendererTextureUploader sdlrtu; //OpenGLTextureUploader ogltu; diff --git a/src/sdl_clipboard_utils.cpp b/src/sdl_clipboard_utils.cpp index 3968462d..564a17c4 100644 --- a/src/sdl_clipboard_utils.cpp +++ b/src/sdl_clipboard_utils.cpp @@ -6,8 +6,8 @@ const char* clipboardHasImage(void) { const static std::vector image_mime_types { - "image/webp", "image/png", + "image/webp", "image/gif", "image/jpeg", "image/bmp", diff --git a/src/texture_cache.hpp b/src/texture_cache.hpp index a736264f..5f09deb7 100644 --- a/src/texture_cache.hpp +++ b/src/texture_cache.hpp @@ -151,8 +151,10 @@ struct TextureCache { void invalidate(const std::vector& to_purge) { for (const auto& key : to_purge) { - for (const auto& tex_id : _cache.at(key).textures) { - _tu.destroy(tex_id); + if (_cache.count(key)) { + for (const auto& tex_id : _cache.at(key).textures) { + _tu.destroy(tex_id); + } } _cache.erase(key); } diff --git a/src/tox_avatar_loader.cpp b/src/tox_avatar_loader.cpp index 8adfc548..54058d3b 100644 --- a/src/tox_avatar_loader.cpp +++ b/src/tox_avatar_loader.cpp @@ -175,7 +175,7 @@ std::optional ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 return new_entry; } } - } + } // continues if loading img fails if (!_cr.any_of< Contact::Components::ToxFriendPersistent, diff --git a/src/tox_avatar_manager.cpp b/src/tox_avatar_manager.cpp new file mode 100644 index 00000000..8efa480a --- /dev/null +++ b/src/tox_avatar_manager.cpp @@ -0,0 +1,213 @@ +#include "./tox_avatar_manager.hpp" + +#include + +#include +// for comp transfer tox filekind (TODO: generalize -> content system?) +#include + +#include +#include + +#include + +#include +#include + +#include + +// see https://github.com/Tox/Tox-Client-Standard/blob/master/user_identification/avatar.md +// see (old) https://github.com/Tox-Archive/Tox-STS/blob/master/STS.md#avatars + +// https://youtu.be/_uuCLRqc9QA + +namespace Components { + struct TagAvatarImageHandled {}; +}; + +ToxAvatarManager::ToxAvatarManager( + RegistryMessageModel& rmm, + Contact3Registry& cr, + ConfigModelI& conf +) : _rmm(rmm), _cr(cr), _conf(conf) { + _rmm.subscribe(this, RegistryMessageModel_Event::message_construct); + _rmm.subscribe(this, RegistryMessageModel_Event::message_updated); + + if (!_conf.has_string("ToxAvatarManager", "save_path")) { + // or on linux: $HOME/.config/tox/avatars/ + _conf.set("ToxAvatarManager", "save_path", std::string_view{"tmp_avatar_dir"}); + } + + //_conf.set("TransferAutoAccept", "autoaccept_limit", int64_t(50l*1024l*1024l)); // sane default + + const std::string_view avatar_save_path {_conf.get_string("ToxAvatarManager", "save_path").value()}; + // make sure it exists + std::filesystem::create_directories(avatar_save_path); + + { // scan tox contacts for cached avatars + // old sts says pubkey.png + + _cr.view().each([this](auto c, const Contact::Components::ToxFriendPersistent& tox_pers) { + addAvatarFileToContact(c, tox_pers.key); + }); + + _cr.view().each([this](auto c, const Contact::Components::ToxGroupPersistent& tox_pers) { + addAvatarFileToContact(c, tox_pers.chat_id); + }); + + // TODO: also for group peers? + // TODO: conf? + } +} + +void ToxAvatarManager::iterate(void) { + // cancel queue + + // accept queue + for (auto& [m, path] : _accept_queue) { + if (m.all_of()) { + continue; // already accepted + } + + m.emplace(path, true); + std::cout << "TAM: auto accepted transfer\n"; + + _rmm.throwEventUpdate(m); + } + _accept_queue.clear(); + + // - add message/content/transfer listener for onComplete +} + +std::string ToxAvatarManager::getAvatarPath(const ToxKey& key) const { + const std::string_view avatar_save_path {_conf.get_string("ToxAvatarManager", "save_path").value()}; + const auto pub_key_string = bin2hex({key.data.cbegin(), key.data.cend()}); + const auto file_path = std::filesystem::path(avatar_save_path) / (pub_key_string + ".png"); + return file_path.u8string(); +} + +void ToxAvatarManager::addAvatarFileToContact(const Contact3 c, const ToxKey& key) { + //const std::string_view avatar_save_path {_conf.get_string("ToxAvatarManager", "save_path").value()}; + //const auto pub_key_string = bin2hex({key.data.cbegin(), key.data.cend()}); + //const auto file_path = std::filesystem::path(avatar_save_path) / (pub_key_string + ".png"); + const auto file_path = getAvatarPath(key); + if (std::filesystem::is_regular_file(file_path)) { + // avatar file png file exists + _cr.emplace_or_replace(c, file_path); + _cr.emplace_or_replace(c); + } +} + +void ToxAvatarManager::clearAvatarFromContact(const Contact3 c) { + if (_cr.all_of(c)) { + std::filesystem::remove(_cr.get(c).file_path); + _cr.remove(c); + _cr.emplace_or_replace(c); + } +} + +void ToxAvatarManager::checkMsg(Message3Handle h) { + if (h.any_of< + Message::Components::Transfer::ActionAccept, + Components::TagAvatarImageHandled + >()) { + return; // already accepted or handled + } + + if (!h.any_of< + Message::Components::Transfer::TagPaused, + Message::Components::Transfer::TagHaveAll + >()) { + // we only handle unaccepted or finished + return; + } + + if (!h.all_of< + Message::Components::Transfer::TagReceiving, + Message::Components::Transfer::FileInfo, + Message::Components::Transfer::FileKind, + Message::Components::ContactFrom // should always be there, just making sure + >()) { + return; + } + + // TCS-2.2.11 (big list, should have been sub points ...) + + if (h.get().kind != 1) { + // not an avatar + return; + } + + const auto& file_info = h.get(); + + // TCS-2.2.4 + if (file_info.total_size > 65536ul) { + // TODO: mark handled? + return; // too large + } + + const auto contact = h.get().c; + + // TCS-2.2.10 + if (file_info.file_list.empty() || file_info.file_list.front().file_name.empty() || file_info.total_size == 0) { + // reset + clearAvatarFromContact(contact); + // TODO: cancel + return; + } + + if (!h.all_of< + Message::Components::Transfer::FileID + >()) { + return; + } + + std::string file_path; + if (_cr.all_of(contact)) { + file_path = getAvatarPath(_cr.get(contact).key); + } else if (_cr.all_of(contact)) { + file_path = getAvatarPath(_cr.get(contact).chat_id); + } else { + std::cerr << "TAM error: cant get toxkey for contact\n"; + // TODO: mark handled? + return; + } + + if (h.all_of()) { + std::cout << "TAM: full avatar received\n"; + + if (_cr.all_of(contact)) { + addAvatarFileToContact(contact, _cr.get(contact).key); + } else if (_cr.all_of(contact)) { + addAvatarFileToContact(contact, _cr.get(contact).chat_id); + } else { + std::cerr << "TAM error: cant get toxkey for contact\n"; + } + + h.emplace_or_replace(); + } else { + const auto& supposed_file_hash = h.get().id; + + // check file id for existing hash + if (std::filesystem::is_regular_file(file_path)) { + // load file + // hash file + //_t.toxHash(); + + } + + // if not already on disk + _accept_queue.push_back(AcceptEntry{h, file_path}); + } +} + +bool ToxAvatarManager::onEvent(const Message::Events::MessageConstruct& e) { + checkMsg(e.e); + return false; +} + +bool ToxAvatarManager::onEvent(const Message::Events::MessageUpdated& e) { + checkMsg(e.e); + return false; +} + diff --git a/src/tox_avatar_manager.hpp b/src/tox_avatar_manager.hpp new file mode 100644 index 00000000..6b8bcd9f --- /dev/null +++ b/src/tox_avatar_manager.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "solanaceae/contact/contact_model3.hpp" +#include + + +// RIP fishy c=< + +struct ConfigModelI; +struct ToxKey; + +class ToxAvatarManager : public RegistryMessageModelEventI { + RegistryMessageModel& _rmm; + Contact3Registry& _cr; + ConfigModelI& _conf; + + struct AcceptEntry { + Message3Handle m; + std::string file_path; + }; + std::vector _accept_queue; + + public: + ToxAvatarManager( + RegistryMessageModel& rmm, + Contact3Registry& cr, + ConfigModelI& conf + ); + + void iterate(void); + + protected: + std::string getAvatarPath(const ToxKey& key) const; + void addAvatarFileToContact(const Contact3 c, const ToxKey& key); + void clearAvatarFromContact(const Contact3 c); + void checkMsg(Message3Handle h); + + protected: // mm + bool onEvent(const Message::Events::MessageConstruct& e) override; + bool onEvent(const Message::Events::MessageUpdated& e) override; +}; +