From 2654cd1b1902d8b228fd29d1a642a2966fc6f43b Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sun, 4 May 2025 19:11:28 +0200 Subject: [PATCH] continue transfer object refactor, re-enabling avatar receiving --- external/solanaceae_contact | 2 +- external/solanaceae_message3 | 2 +- external/solanaceae_object_store | 2 +- external/solanaceae_tox | 2 +- src/CMakeLists.txt | 3 + src/backends/std_fs.cpp | 107 ++++++++++++++++++ src/backends/std_fs.hpp | 24 ++++ src/main_screen.cpp | 4 +- src/tox_avatar_loader.cpp | 105 ++++++++++++++--- src/tox_avatar_loader.hpp | 9 +- src/tox_avatar_manager.cpp | 187 +++++++++++++++++++++++-------- src/tox_avatar_manager.hpp | 12 +- 12 files changed, 390 insertions(+), 69 deletions(-) create mode 100644 src/backends/std_fs.cpp create mode 100644 src/backends/std_fs.hpp diff --git a/external/solanaceae_contact b/external/solanaceae_contact index 99d9633..a5508ff 160000 --- a/external/solanaceae_contact +++ b/external/solanaceae_contact @@ -1 +1 @@ -Subproject commit 99d9633ff109321e78cf2817d3b8631d364a2483 +Subproject commit a5508ff6f3443ca5ca22ef0d7d91ac0f9319230f diff --git a/external/solanaceae_message3 b/external/solanaceae_message3 index 2a3828b..c8e3e93 160000 --- a/external/solanaceae_message3 +++ b/external/solanaceae_message3 @@ -1 +1 @@ -Subproject commit 2a3828b30d7ec5fb9e7c8a8a82d1be66e040ea74 +Subproject commit c8e3e9374360fa241ccebd070e1d70abf6ba665b diff --git a/external/solanaceae_object_store b/external/solanaceae_object_store index 738e1a2..889761f 160000 --- a/external/solanaceae_object_store +++ b/external/solanaceae_object_store @@ -1 +1 @@ -Subproject commit 738e1a2071b95000bbed64e2262261cfbdc7dbab +Subproject commit 889761f538eda8feb7ee0ea224c98e6ea05f6246 diff --git a/external/solanaceae_tox b/external/solanaceae_tox index 4e75bd6..7e12cdf 160000 --- a/external/solanaceae_tox +++ b/external/solanaceae_tox @@ -1 +1 @@ -Subproject commit 4e75bd64aedf6c252456da0ca7c30a414f0fcaaf +Subproject commit 7e12cdfb232a412f6052102fb9d8d3ceb04db4f4 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba0383a..9dea7d8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,6 +30,9 @@ target_sources(tomato PUBLIC ./main_screen.hpp ./main_screen.cpp + ./backends/std_fs.hpp + ./backends/std_fs.cpp + ./tox_client.hpp ./tox_client.cpp ./auto_dirty.hpp diff --git a/src/backends/std_fs.cpp b/src/backends/std_fs.cpp new file mode 100644 index 0000000..510da34 --- /dev/null +++ b/src/backends/std_fs.cpp @@ -0,0 +1,107 @@ +#include "./std_fs.hpp" + +#include +#include + +#include + +#include + +namespace Backends { + +STDFS::STDFS( + ObjectStore2& os +) : _os(os) { +} + +STDFS::~STDFS(void) { +} + +ObjectHandle STDFS::newObject(ByteSpan id, bool throw_construct) { + ObjectHandle o{_os.registry(), _os.registry().create()}; + + o.emplace(this); // ? + o.emplace(this); + o.emplace(std::vector{id}); + //o.emplace(object_file_path.generic_u8string()); + + if (throw_construct) { + _os.throwEventConstruct(o); + } + + return o; +} + +bool STDFS::attach(Object ov) { + auto o = _os.objectHandle(ov); + if (!static_cast(o)) { + return false; + } + + if (o.any_of< + ObjComp::Ephemeral::BackendMeta, + ObjComp::Ephemeral::BackendFile2 + >()) { + return false; + } + + o.emplace(this); // ? + o.emplace(this); + + return true; +} + +std::unique_ptr STDFS::file2(Object ov, FILE2_FLAGS flags) { + if (flags & FILE2_RAW) { + std::cerr << "STDFS error: does not support raw modes\n"; + return nullptr; + } + + if (flags == FILE2_NONE) { + std::cerr << "STDFS error: no file mode set\n"; + assert(false); + return nullptr; + } + + ObjectHandle o{_os.registry(), ov}; + + if (!static_cast(o)) { + return nullptr; + } + + // will this do if we go and support enc? + // use ObjComp::Ephemeral::FilePath instead?? + if (!o.all_of()) { + std::cerr << "STDFS error: no SingleInfoLocal\n"; + return nullptr; + } + + const auto& file_path = o.get().file_path; + if (file_path.empty()) { + std::cerr << "STDFS error: SingleInfoLocal path empty\n"; + return nullptr; + } + + std::unique_ptr res; + if ((flags & FILE2_WRITE) != 0 && (flags & FILE2_READ) != 0) { + res = std::make_unique(file_path); + } else if (flags & FILE2_READ) { + res = std::make_unique(file_path); + } else if ((flags & FILE2_WRITE) && o.all_of()) { + // HACK: use info and presize the file AND truncate + // TODO: actually support streaming :P + res = std::make_unique(file_path, o.get().file_size, true); + } else if (flags & FILE2_WRITE) { + res = std::make_unique(file_path); + } + + if (!res || !res->isGood()) { + std::cerr << "STDFS error: failed constructing file '" << file_path << "'\n"; + return nullptr; + } + + return res; +} + +} // Backends + diff --git a/src/backends/std_fs.hpp b/src/backends/std_fs.hpp new file mode 100644 index 0000000..953e47a --- /dev/null +++ b/src/backends/std_fs.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace Backends { + +struct STDFS : public StorageBackendIMeta, public StorageBackendIFile2 { + ObjectStore2& _os; + + STDFS( + ObjectStore2& os + ); + ~STDFS(void); + + ObjectHandle newObject(ByteSpan id, bool throw_construct = true) override; + + // TODO: interface? + bool attach(Object ov); + + std::unique_ptr file2(Object o, FILE2_FLAGS flags) override; +}; + +} // Backends + diff --git a/src/main_screen.cpp b/src/main_screen.cpp index d1e84ca..e07b231 100644 --- a/src/main_screen.cpp +++ b/src/main_screen.cpp @@ -55,9 +55,9 @@ MainScreen::MainScreen(const SimpleConfigModel& conf_, SDL_Renderer* renderer_, #endif theme(theme_), mmil(rmm), - tam(/*rmm, */ os, cs, conf), + tam(os, cs, conf, tcm), sdlrtu(renderer_), - tal(cs), + tal(cs, os), contact_tc(tal, sdlrtu), mil(), msg_tc(mil, sdlrtu), diff --git a/src/tox_avatar_loader.cpp b/src/tox_avatar_loader.cpp index 370bfd9..0fc7f6c 100644 --- a/src/tox_avatar_loader.cpp +++ b/src/tox_avatar_loader.cpp @@ -8,6 +8,9 @@ #include #include #include +#include +#include +#include #include @@ -18,7 +21,82 @@ #include #include -ToxAvatarLoader::ToxAvatarLoader(ContactStore4I& cs) : _cs(cs) { +ByteSpanWithOwnership ToxAvatarLoader::loadDataFromObj(Contact4 cv) { + auto c = _cs.contactHandle(cv); + auto o = _os.objectHandle(c.get().obj); + if (!static_cast(o)) { + std::cerr << "TAL error: avatar object set, but invalid\n"; + return ByteSpan{}; + } + + if (!o.all_of()) { + return ByteSpan{}; // we dont have all data + } + + if (!o.all_of()) { + std::cerr << "TAL error: object missing file backend/file info (?)\n"; + return ByteSpan{}; + } + + // TODO: handle collections + const auto file_size = o.get().file_size; + if (file_size > 2*1024*1024) { + std::cerr << "TAL error: image file too large (" << file_size << " > 2MiB)\n"; + return ByteSpan{}; + } + + auto* file_backend = o.get().ptr; + if (file_backend == nullptr) { + std::cerr << "TAL error: object backend nullptr\n"; + return ByteSpan{}; + } + + auto file2 = file_backend->file2(o, StorageBackendIFile2::FILE2_READ); + if (!file2 || !file2->isGood() || !file2->can_read) { + std::cerr << "TAL error: creating file2 from object via backendI\n"; + return ByteSpan{}; + } + + auto read_data = file2->read(file_size, 0); + if (read_data.ptr == nullptr) { + std::cerr << "TAL error: reading from file2 returned nullptr\n"; + return ByteSpan{}; + } + + if (read_data.size != file_size) { + std::cerr << "TAL error: reading from file2 size missmatch, should be " << file_size << ", is " << read_data.size << "\n"; + return ByteSpan{}; + } + + return read_data; +} + +ByteSpanWithOwnership ToxAvatarLoader::loadData(Contact4 cv) { + auto c = _cs.contactHandle(cv); + if (c.all_of()) { + // TODO: factor out + const auto& a_f = c.get(); + + std::vector tmp_buffer; + std::ifstream file(a_f.file_path, std::ios::binary); + if (file.is_open()) { + tmp_buffer = std::vector{}; + while (file.good()) { + auto ch = file.get(); + if (ch == EOF) { + break; + } else { + tmp_buffer.push_back(ch); + } + } + } + return tmp_buffer; + } else { // obj assumed + return loadDataFromObj(cv); + } +} + +ToxAvatarLoader::ToxAvatarLoader(ContactStore4I& cs, ObjectStore2& os) : _cs(cs), _os(os) { _image_loaders.push_back(std::make_unique()); _image_loaders.push_back(std::make_unique()); _image_loaders.push_back(std::make_unique()); @@ -142,24 +220,13 @@ TextureLoaderResult ToxAvatarLoader::load(TextureUploaderI& tu, Contact4 c) { return {new_entry}; } - if (cr.all_of(c)) { - const auto& a_f = cr.get(c); - - std::ifstream file(a_f.file_path, std::ios::binary); - if (file.is_open()) { - std::vector tmp_buffer; - while (file.good()) { - auto ch = file.get(); - if (ch == EOF) { - break; - } else { - tmp_buffer.push_back(ch); - } - } + if (cr.any_of(c)) { + const auto tmp_buffer = loadData(c); + if (!tmp_buffer.empty()) { // try all loaders after another for (auto& il : _image_loaders) { - auto res = il->loadFromMemoryRGBA(tmp_buffer.data(), tmp_buffer.size()); + auto res = il->loadFromMemoryRGBA(tmp_buffer.ptr, tmp_buffer.size); if (res.frames.empty() || res.height == 0 || res.width == 0) { continue; } @@ -176,7 +243,11 @@ TextureLoaderResult ToxAvatarLoader::load(TextureUploaderI& tu, Contact4 c) { new_entry.width = res.width; new_entry.height = res.height; - std::cout << "TAL: loaded image file " << a_f.file_path << "\n"; + if (cr.all_of(c)) { + std::cout << "TAL: loaded image file " << cr.get(c).file_path << "\n"; + } else { + std::cout << "TAL: loaded image object " << entt::to_integral(cr.get(c).obj) << "\n"; + } return {new_entry}; } diff --git a/src/tox_avatar_loader.hpp b/src/tox_avatar_loader.hpp index 39c71f8..f7a77cc 100644 --- a/src/tox_avatar_loader.hpp +++ b/src/tox_avatar_loader.hpp @@ -1,6 +1,9 @@ #pragma once #include +#include + +#include #include "./image_loader.hpp" #include "./texture_cache.hpp" @@ -9,11 +12,15 @@ class ToxAvatarLoader { ContactStore4I& _cs; + ObjectStore2& _os; std::vector> _image_loaders; + ByteSpanWithOwnership loadDataFromObj(Contact4 cv); + ByteSpanWithOwnership loadData(Contact4 cv); + public: - ToxAvatarLoader(ContactStore4I& cr); + ToxAvatarLoader(ContactStore4I& cs, ObjectStore2& os); TextureLoaderResult load(TextureUploaderI& tu, Contact4 c); }; diff --git a/src/tox_avatar_manager.cpp b/src/tox_avatar_manager.cpp index dbc054f..ff7dd07 100644 --- a/src/tox_avatar_manager.cpp +++ b/src/tox_avatar_manager.cpp @@ -1,6 +1,6 @@ #include "./tox_avatar_manager.hpp" -// TODO: this whole thing needs a rewrite and is currently disabled +// TODO: this whole thing needs a rewrite, seperating tcs and rng uid os #include @@ -33,11 +33,13 @@ namespace Components { ToxAvatarManager::ToxAvatarManager( ObjectStore2& os, ContactStore4I& cs, - ConfigModelI& conf -) : _os(os), _os_sr(_os.newSubRef(this)), _cs(cs), _conf(conf) { + ConfigModelI& conf, + ToxContactModel2& tcm +) : _os(os), _os_sr(_os.newSubRef(this)), _cs(cs), _conf(conf), _tcm(tcm), _sb_tcs(os) { _os_sr .subscribe(ObjectStore_Event::object_construct) .subscribe(ObjectStore_Event::object_update) + .subscribe(ObjectStore_Event::object_destroy) ; if (!_conf.has_string("ToxAvatarManager", "save_path")) { @@ -45,20 +47,21 @@ ToxAvatarManager::ToxAvatarManager( _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); + // TODO: instead listen for new contacts, and attach { // scan tox contacts for cached avatars // old sts says pubkey.png _cs.registry().view().each([this](auto c, const Contact::Components::ToxFriendPersistent& tox_pers) { + // try addAvatarFileToContact(c, tox_pers.key); }); _cs.registry().view().each([this](auto c, const Contact::Components::ToxGroupPersistent& tox_pers) { + // try addAvatarFileToContact(c, tox_pers.chat_id); }); @@ -88,30 +91,70 @@ void ToxAvatarManager::iterate(void) { 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"); + const auto file_path = std::filesystem::path(avatar_save_path) / getAvatarFileName(key); return file_path.generic_u8string(); } +std::string ToxAvatarManager::getAvatarFileName(const ToxKey& key) const { + const auto pub_key_string = bin2hex({key.data.cbegin(), key.data.cend()}); + return pub_key_string + ".png"; // TODO: remove png? +} + void ToxAvatarManager::addAvatarFileToContact(const Contact4 c, const ToxKey& key) { const auto file_path = getAvatarPath(key); - if (std::filesystem::is_regular_file(file_path)) { - // avatar file png file exists - _cs.registry().emplace_or_replace(c, file_path); - _cs.registry().emplace_or_replace(c); - - _cs.throwEventUpdate(c); + if (!std::filesystem::is_regular_file(file_path)) { + return; } + + //std::cout << "TAM: found '" << file_path << "'\n"; + + // TODO: use guid instead + auto o = _sb_tcs.newObject(ByteSpan{key.data}, false); + o.emplace_or_replace(file_path); + o.emplace_or_replace(); + + // for file size + o.emplace_or_replace( + getAvatarFileName(key), + std::filesystem::file_size(file_path) + ); + + _os.throwEventConstruct(o); + + // avatar file "png" exists + //_cs.registry().emplace_or_replace(c, file_path); + _cs.registry().emplace_or_replace(c, o); + _cs.registry().emplace_or_replace(c); + + _cs.throwEventUpdate(c); } void ToxAvatarManager::clearAvatarFromContact(const Contact4 c) { auto& cr = _cs.registry(); - if (cr.all_of(c)) { - std::filesystem::remove(cr.get(c).file_path); - cr.remove(c); + if (cr.any_of(c)) { + if (cr.all_of(c)) { + std::filesystem::remove(cr.get(c).file_path); + } else if (cr.all_of(c)) { + auto o = _os.objectHandle(cr.get(c).obj); + if (o) { + if (o.all_of()) { + std::filesystem::remove(o.get().file_path); + } + // TODO: make destruction more ergonomic + //_sb_tcs.destroy() ?? + _os.throwEventDestroy(o); + o.destroy(); + } + } + cr.remove< + Contact::Components::AvatarFile, + Contact::Components::AvatarObj + >(c); cr.emplace_or_replace(c); _cs.throwEventUpdate(c); + + std::cout << "TAM: cleared avatar from " << entt::to_integral(c) << "\n"; } } @@ -131,13 +174,12 @@ void ToxAvatarManager::checkObj(ObjectHandle o) { return; } + // TODO: non tox code path? if (!o.all_of< ObjComp::Tox::TagIncomming, - //ObjComp::Ephemeral::Backend, ObjComp::F::SingleInfo, - ObjComp::Tox::FileKind - // TODO: mesage? how do we know where a file is from?? - //Message::Components::ContactFrom // should always be there, just making sure + ObjComp::Tox::FileKind, + ObjComp::Ephemeral::ToxContact >()) { return; } @@ -159,10 +201,14 @@ void ToxAvatarManager::checkObj(ObjectHandle o) { return; // too large } -#if 0 // TODO: make avatars work again !!!!! + auto contact = o.get().c; + if (!static_cast(contact)) { + std::cerr << "TAM error: failed to attribute object to contact\n"; + } // TCS-2.2.10 if (file_info.file_name.empty() || file_info.file_size == 0) { + std::cout << "TAM: no filename or filesize, deleting avatar\n"; // reset clearAvatarFromContact(contact); // TODO: cancel @@ -175,30 +221,38 @@ void ToxAvatarManager::checkObj(ObjectHandle o) { 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 (o.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"; - } + // old code no longer works, we already have the object (right here lol) + + contact.emplace_or_replace(o); + contact.emplace_or_replace(); o.emplace_or_replace(); - } else { + } else if (!o.all_of< + ObjComp::Ephemeral::BackendMeta, // hmm + ObjComp::Ephemeral::BackendFile2, + ObjComp::Ephemeral::BackendAtomic // to be safe + >()) { + std::string file_path; + if (contact.all_of()) { + file_path = getAvatarPath(contact.get().key); + } else if (contact.all_of()) { + file_path = getAvatarPath(contact.get().chat_id); + } else { + std::cerr << "TAM error: cant get toxkey for contact\n"; + // TODO: mark handled? + return; + } + + // already has avatar, delete old (TODO: or check hash) + if (contact.all_of()) { + clearAvatarFromContact(contact); + } + + // crude + // TODO: queue/async instead // check file id for existing hash if (std::filesystem::is_regular_file(file_path)) { //const auto& supposed_file_hash = h.get().id; @@ -209,12 +263,18 @@ void ToxAvatarManager::checkObj(ObjectHandle o) { std::filesystem::remove(file_path); // hack, hard replace existing file } - std::cout << "TAM: accepted avatar ft\n"; + if (!_sb_tcs.attach(o)) { + std::cerr << "TAM error: failed to attach backend??\n"; + return; + } - // if not already on disk - _accept_queue.push_back(AcceptEntry{o, file_path}); + o.emplace_or_replace(file_path); + + // ... do we do anything here? + // like set "accepted" tag comp or something + + std::cout << "TAM: accepted avatar ft\n"; } -#endif } bool ToxAvatarManager::onEvent(const ObjectStore::Events::ObjectConstruct& e) { @@ -227,3 +287,42 @@ bool ToxAvatarManager::onEvent(const ObjectStore::Events::ObjectUpdate& e) { return false; } +bool ToxAvatarManager::onEvent(const ObjectStore::Events::ObjectDestory& e) { + // TODO: avatar contact comp instead? + // TODO: generic contact comp? (hard, very usecase dep) +#if 0 + if (!e.e.all_of()) { + // cant be reasonable be attributed to a contact + return false; + } + + // TODO: remove obj from contact +#endif + if (!e.e.all_of()) { + // cant be reasonable be attributed to a contact + return false; + } + + auto c = e.e.get().c; + if (!static_cast(c)) { + // meh + return false; + } + + if (!c.all_of()) { + // funny + return false; + } + + if (c.get().obj != e.e) { + // maybe got replace? + // TODO: make error and do proper cleanup + return false; + } + + c.remove(); + c.emplace_or_replace(); + // TODO: throw contact update!!! + + return false; +} diff --git a/src/tox_avatar_manager.hpp b/src/tox_avatar_manager.hpp index 776c788..c78b55f 100644 --- a/src/tox_avatar_manager.hpp +++ b/src/tox_avatar_manager.hpp @@ -2,6 +2,9 @@ #include #include +#include + +#include "./backends/std_fs.hpp" #include #include @@ -17,6 +20,9 @@ class ToxAvatarManager : public ObjectStoreEventI { ObjectStore2::SubscriptionReference _os_sr; ContactStore4I& _cs; ConfigModelI& _conf; + ToxContactModel2& _tcm; + + Backends::STDFS _sb_tcs; struct AcceptEntry { ObjectHandle m; @@ -28,7 +34,8 @@ class ToxAvatarManager : public ObjectStoreEventI { ToxAvatarManager( ObjectStore2& os, ContactStore4I& cs, - ConfigModelI& conf + ConfigModelI& conf, + ToxContactModel2& tcm ); void iterate(void); @@ -36,12 +43,15 @@ class ToxAvatarManager : public ObjectStoreEventI { protected: // TODO: become backend and work in objects instead std::string getAvatarPath(const ToxKey& key) const; + std::string getAvatarFileName(const ToxKey& key) const; void addAvatarFileToContact(const Contact4 c, const ToxKey& key); void clearAvatarFromContact(const Contact4 c); void checkObj(ObjectHandle o); protected: // os + // on new obj, check for ToxTransferFriend and emplace own contact tracker bool onEvent(const ObjectStore::Events::ObjectConstruct& e) override; bool onEvent(const ObjectStore::Events::ObjectUpdate& e) override; + bool onEvent(const ObjectStore::Events::ObjectDestory& e) override; };