#include "./tox_avatar_manager.hpp" // TODO: this whole thing needs a rewrite, seperating tcs and rng uid os #include #include //#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( ObjectStore2& os, ContactStore4I& cs, ConfigModelI& conf ) : _os(os), _os_sr(_os.newSubRef(this)), _cs(cs), _conf(conf), _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")) { // or on linux: $HOME/.config/tox/avatars/ _conf.set("ToxAvatarManager", "save_path", std::string_view{"tmp_avatar_dir"}); } 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 #if 0 _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); }); #else // HACK: assumed id is pubkey _cs.registry().view().each([this](auto c, const Contact::Components::ID& id) { // try addAvatarFileToContact(c, id.data); }); #endif // TODO: also for group peers? // TODO: conf? } } void ToxAvatarManager::iterate(void) { // cancel queue // accept queue for (auto& [o, path] : _accept_queue) { if (o.any_of()) { continue; // already accepted / done } o.emplace(path, true); std::cout << "TAM: auto accepted transfer\n"; _os.throwEventUpdate(o); } _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 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)) { 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.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"; } } void ToxAvatarManager::checkObj(ObjectHandle o) { if (o.any_of< ObjComp::Ephemeral::File::ActionTransferAccept, Components::TagAvatarImageHandled >()) { return; // already accepted or handled } if (!o.any_of< ObjComp::Ephemeral::File::TagTransferPaused, ObjComp::F::TagLocalHaveAll >()) { // we only handle unaccepted or finished return; } // TODO: non tox code path? if (!o.all_of< ObjComp::Tox::TagIncomming, ObjComp::F::SingleInfo, ObjComp::Tox::FileKind, ObjComp::Ephemeral::ToxContact >()) { return; } // TCS-2.2.11 (big list, should have been sub points ...) if (o.get().kind != 1) { // not an avatar return; } const auto& file_info = o.get(); //const auto contact = h.get().c; // TCS-2.2.4 if (file_info.file_size > 65536ul) { // TODO: mark handled? return; // too large } 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 return; } if (!o.all_of< ObjComp::Tox::FileID >()) { return; } if (o.all_of()) { std::cout << "TAM: full avatar received\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 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; // load file // hash file //_t.toxHash(); std::filesystem::remove(file_path); // hack, hard replace existing file } if (!_sb_tcs.attach(o)) { std::cerr << "TAM error: failed to attach backend??\n"; return; } 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"; } } bool ToxAvatarManager::onEvent(const ObjectStore::Events::ObjectConstruct& e) { checkObj(e.e); return false; } bool ToxAvatarManager::onEvent(const ObjectStore::Events::ObjectUpdate& e) { checkObj(e.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; }