diff --git a/external/solanaceae_message3 b/external/solanaceae_message3 index f1dd5107..9728f71c 160000 --- a/external/solanaceae_message3 +++ b/external/solanaceae_message3 @@ -1 +1 @@ -Subproject commit f1dd5107f820fe86cb6b6b9ca22d42e0a3a3cf30 +Subproject commit 9728f71c9833baa65995e19e993d3450da750c20 diff --git a/external/solanaceae_object_store b/external/solanaceae_object_store index 4605d64d..2801fc21 160000 --- a/external/solanaceae_object_store +++ b/external/solanaceae_object_store @@ -1 +1 @@ -Subproject commit 4605d64df28c45096cef7748d5c143e942aefeb1 +Subproject commit 2801fc21fbd6f69479f6638ab1725d00238698f8 diff --git a/external/solanaceae_tox b/external/solanaceae_tox index 676e50c6..1a3d9dd1 160000 --- a/external/solanaceae_tox +++ b/external/solanaceae_tox @@ -1 +1 @@ -Subproject commit 676e50c61aa7dd816dca846fd06493d2e3ae4aab +Subproject commit 1a3d9dd1870b1f45e252ff636adfd0c1f0ccf521 diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index 6563ab7a..4f41e9a2 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -1,7 +1,11 @@ #include "./chat_gui4.hpp" +#include + #include -#include +#include +#include +#include #include #include @@ -191,13 +195,14 @@ void ChatGui4::setClipboardData(std::vector mime_types, std::shared ChatGui4::ChatGui4( ConfigModelI& conf, + ObjectStore2& os, RegistryMessageModel& rmm, Contact3Registry& cr, TextureUploaderI& tu, ContactTextureCache& contact_tc, MessageTextureCache& msg_tc, Theme& theme -) : _conf(conf), _rmm(rmm), _cr(cr), _contact_tc(contact_tc), _msg_tc(msg_tc), _theme(theme), _sip(tu) { +) : _conf(conf), _os(os), _rmm(rmm), _cr(cr), _contact_tc(contact_tc), _msg_tc(msg_tc), _theme(theme), _sip(tu) { } ChatGui4::~ChatGui4(void) { @@ -511,7 +516,7 @@ float ChatGui4::render(float time_delta) { ImGui::TableNextColumn(); if (msg_reg.all_of(e)) { renderMessageBodyText(msg_reg, e); - } else if (msg_reg.any_of(e)) { // add more comps? + } else if (msg_reg.any_of(e)) { renderMessageBodyFile(msg_reg, e); } else { ImGui::TextDisabled("---"); @@ -829,8 +834,9 @@ float ChatGui4::render(float time_delta) { } void ChatGui4::sendFilePath(const char* file_path) { - if (_selected_contact && std::filesystem::is_regular_file(file_path)) { - _rmm.sendFilePath(*_selected_contact, std::filesystem::path(file_path).filename().generic_u8string(), file_path); + const auto path = std::filesystem::path(file_path); + if (_selected_contact && std::filesystem::is_regular_file(path)) { + _rmm.sendFilePath(*_selected_contact, path.filename().generic_u8string(), path.generic_u8string()); } } @@ -940,11 +946,12 @@ void ChatGui4::renderMessageBodyText(Message3Registry& reg, const Message3 e) { } void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { + auto o = reg.get(e).o; if ( !_show_chat_avatar_tf && ( - reg.all_of(e) - && reg.get(e).kind == 1 + o.all_of() + && o.get().kind == 1 ) ) { // TODO: this looks ugly @@ -952,6 +959,8 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { return; } + ImGui::BeginGroup(); + #if 0 if (msg_reg.all_of(e)) { switch (msg_reg.get(e).state) { @@ -965,51 +974,24 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { } #endif // TODO: better way to display state - if (reg.all_of(e)) { + if (o.all_of()) { ImGui::TextUnformatted("paused"); - } else if (reg.all_of(e)) { - ImGui::TextUnformatted("done"); + //} else if (reg.all_of(e)) { + // ImGui::TextUnformatted("done"); } else { // TODO: missing other states ImGui::TextUnformatted("running"); } - if (reg.all_of(e)) { - // hack lul - ImGui::SameLine(); - if (ImGui::SmallButton("forward")) { - ImGui::OpenPopup("forward to contact"); - } - - if (ImGui::BeginPopup("forward to contact")) { - for (const auto& c : _cr.view()) { - // filter - if (_cr.any_of(c)) { - continue; - } - // TODO: check for contact capability - - if (renderContactBig(_theme, _contact_tc, {_cr, c}, 1, false, true, false)) { - //if (renderContactListContactSmall(c, false)) { - //_rmm.sendFilePath(*_selected_contact, path.filename().generic_u8string(), path.generic_u8string()); - const auto& fil = reg.get(e); - for (const auto& path : fil.file_list) { - _rmm.sendFilePath(c, std::filesystem::path{path}.filename().generic_u8string(), path); - } - } - } - ImGui::EndPopup(); - } - } - // if in offered state // paused, never started if ( - reg.all_of(e) && - reg.all_of(e) && + !o.all_of() && + //reg.all_of(e) && + o.all_of() && // TODO: how does restarting a broken/incomplete transfer look like? - !reg.all_of(e) && - !reg.all_of(e) + !o.all_of() && + !o.all_of() ) { if (ImGui::Button("save to")) { _fss.requestFile( @@ -1018,11 +1000,13 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { path.remove_filename(); return std::filesystem::is_directory(path); }, - [this, ®, e](const auto& path) { - if (reg.valid(e)) { // still valid + [this, o](const auto& path) { + if (static_cast(o)) { // still valid // TODO: trim file? - reg.emplace(e, path.string()); - _rmm.throwEventUpdate(reg, e); + o.emplace(path.generic_u8string()); + //_rmm.throwEventUpdate(reg, e); + // TODO: block recursion + _os.throwEventUpdate(o); } }, [](){} @@ -1030,97 +1014,75 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { } } - // down progress - if (reg.all_of(e)) { - ImGui::TextUnformatted("down"); - if (reg.all_of(e)) { - ImGui::SameLine(); + // hacky + const auto* fts = o.try_get(); + if (fts != nullptr && o.any_of()) { + const auto total_size = + o.all_of() ? + o.get().file_size : + o.get().total_size + ; - float fraction = float(reg.get(e).total) / reg.get(e).total_size; + uint64_t total {0u}; + float rate {0.f}; + if (o.all_of() && fts->total_down <= 0) { + // if have all AND no dl -> show upload progress + ImGui::TextUnformatted(" up"); + total = fts->total_up; + rate = fts->rate_up; + } else { + // else show download progress + ImGui::TextUnformatted("down"); + total = fts->total_down; + rate = fts->rate_down; + } + ImGui::SameLine(); - char overlay_buf[32]; + float fraction = float(total) / total_size; + + char overlay_buf[32]; + if (rate > 0.000001f) { + const char* byte_suffix = "???"; + int64_t byte_divider = sizeToHumanReadable(rate, byte_suffix); + std::snprintf(overlay_buf, sizeof(overlay_buf), "%.1f%% @ %.1f%s/s", fraction * 100 + 0.01f, rate/byte_divider, byte_suffix); + } else { std::snprintf(overlay_buf, sizeof(overlay_buf), "%.1f%%", fraction * 100 + 0.01f); - - ImGui::ProgressBar( - fraction, - {-FLT_MIN, TEXT_BASE_HEIGHT}, - overlay_buf - ); - // TODO: numbers } + + ImGui::ProgressBar( + fraction, + {-FLT_MIN, TEXT_BASE_HEIGHT}, + overlay_buf + ); + } else { + // infinite scrolling progressbar fallback + ImGui::TextUnformatted(" ??"); + ImGui::SameLine(); + ImGui::ProgressBar( + -0.333f * ImGui::GetTime(), + {-FLT_MIN, TEXT_BASE_HEIGHT}, + "?%" + ); } - // (can be both) - // up progess - if (reg.all_of(e)) { - ImGui::TextUnformatted(" up"); - if (reg.all_of(e)) { - ImGui::SameLine(); + if (!o.all_of() && o.all_of()) { + // texture based on have bitset + // TODO: missing have chunks/chunksize to get the correct size - float fraction = float(reg.get(e).total) / reg.get(e).total_size; - - char overlay_buf[32]; - std::snprintf(overlay_buf, sizeof(overlay_buf), "%.1f%%", fraction * 100 + 0.01f); - - ImGui::ProgressBar( - fraction, - {-FLT_MIN, TEXT_BASE_HEIGHT}, - overlay_buf - ); - // TODO: numbers - } + //const auto& bitest = o.get().have; + // generate 1bit sdlsurface zerocopy using bitset.data() and bitset.size_bytes() + // optionally scale down filtered (would copy) + // update texture? in cache? } - const auto file_list = reg.get(e).file_list; - - // if has local, display save base path? - - for (size_t i = 0; i < file_list.size(); i++) { - ImGui::PushID(i); - - const char* byte_suffix = "???"; - int64_t byte_divider = sizeToHumanReadable(file_list[i].file_size, byte_suffix); - - // TODO: selectable text widget ? - ImGui::Bullet(); ImGui::Text("%s (%.2lf %s)", file_list[i].file_name.c_str(), double(file_list[i].file_size)/byte_divider, byte_suffix); - if (ImGui::BeginItemTooltip()) { - ImGui::Text("TODO: file path?"); - ImGui::Text("%lu bytes", file_list[i].file_size); - ImGui::EndTooltip(); - } - - if (reg.all_of(e)) { - const auto& local_info = reg.get(e); - if (local_info.file_list.size() > i && ImGui::BeginPopupContextItem("##file_c")) { - if (ImGui::MenuItem("open")) { - const std::string url {file_path_to_file_url(local_info.file_list.at(i))}; - std::cout << "opening file '" << url << "'\n"; - SDL_OpenURL(url.c_str()); - } - if (ImGui::MenuItem("copy file")) { - const std::string url {file_path_to_file_url(local_info.file_list.at(i))}; - //ImGui::SetClipboardText(url.c_str()); - setClipboardData({"text/uri-list", "text/x-moz-url"}, std::make_shared>(url.begin(), url.end())); - } - if (ImGui::MenuItem("copy filepath")) { - const auto file_path = std::filesystem::canonical(local_info.file_list.at(i)).u8string(); //TODO: use generic over native? - ImGui::SetClipboardText(file_path.c_str()); - } - ImGui::EndPopup(); - } - } - - ImGui::PopID(); - } - - if (reg.all_of(e)) { - const auto& frame_dims = reg.get(e); + if (o.all_of()) { + const auto& frame_dims = o.get(); // TODO: config const auto max_inline_height = 10*TEXT_BASE_HEIGHT; - float height = frame_dims.height; - float width = frame_dims.width; + float width = frame_dims.w; + float height = frame_dims.h; if (height > max_inline_height) { const float scale = max_inline_height / height; @@ -1129,13 +1091,10 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { } ImVec2 orig_curser_pos = ImGui::GetCursorPos(); - ImGui::Dummy(ImVec2{width, height}); - // deploy dummy of framedim size and check visibility - bool image_preview_visible = ImGui::IsItemVisible(); - - - if (image_preview_visible && file_list.size() == 1 && reg.all_of(e)) { + // +2 for border + ImGui::Dummy(ImVec2{width+2, height+2}); + if (ImGui::IsItemVisible() && o.all_of()) { ImGui::SetCursorPos(orig_curser_pos); // reset for actual img auto [id, img_width, img_height] = _msg_tc.get(Message3Handle{reg, e}); @@ -1152,19 +1111,152 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { //width *= scale; //} - ImGui::Image(id, ImVec2{width, height}); + ImGui::Image( + id, + ImVec2{width, height}, + {0.f, 0.f}, // default + {1.f, 1.f}, // default + {1.f, 1.f, 1.f, 1.f}, // default + {0.5f, 0.5f, 0.5f, 0.8f} // border + ); // TODO: clickable to open in internal image viewer } + } else if (o.all_of()) { // only show info if not inlined image + // just filename + const auto& si = o.get(); + const char* byte_suffix = "???"; + int64_t byte_divider = sizeToHumanReadable(si.file_size, byte_suffix); + ImGui::Text("%s (%.2lf %s)", si.file_name.c_str(), double(si.file_size)/byte_divider, byte_suffix); + } else if (o.all_of()) { + // same old bulletpoint list + const auto& file_list = o.get().file_list; + + // if has local, display save base path?, do we have base save path? + + for (size_t i = 0; i < file_list.size(); i++) { + ImGui::PushID(i); + + const char* byte_suffix = "???"; + int64_t byte_divider = sizeToHumanReadable(file_list[i].file_size, byte_suffix); + + // TODO: selectable text widget ? + ImGui::Bullet(); ImGui::Text("%s (%.2lf %s)", file_list[i].file_name.c_str(), double(file_list[i].file_size)/byte_divider, byte_suffix); + + if (o.all_of()) { + const auto& local_info = o.get(); + if (local_info.file_list.size() > i && ImGui::BeginPopupContextItem("##file_c")) { + + if (ImGui::MenuItem("open")) { + const std::string url {file_path_to_file_url(local_info.file_list.at(i).file_path)}; + std::cout << "opening file '" << url << "'\n"; + SDL_OpenURL(url.c_str()); + } + if (ImGui::MenuItem("copy file")) { + const std::string url {file_path_to_file_url(local_info.file_list.at(i).file_path)}; + //ImGui::SetClipboardText(url.c_str()); + setClipboardData({"text/uri-list", "text/x-moz-url"}, std::make_shared>(url.begin(), url.end())); + } + if (ImGui::MenuItem("copy filepath")) { + const auto file_path = std::filesystem::canonical(local_info.file_list.at(i).file_path).u8string(); //TODO: use generic over native? + ImGui::SetClipboardText(file_path.c_str()); + } + ImGui::EndPopup(); + } + } + + ImGui::PopID(); + } + } else { + ImGui::TextDisabled("neither info available"); + } + + ImGui::EndGroup(); + + if (o.all_of()) { + const auto& local_info = o.get(); + if (!local_info.file_path.empty() && ImGui::BeginPopupContextItem("##file_c")) { + if (o.all_of()) { + if (ImGui::BeginMenu("forward")) { + for (const auto& c : _cr.view()) { + // filter + if (_cr.any_of(c)) { + continue; + } + // TODO: check for contact capability + // or just error popup?/noti/toast + + if (renderContactBig(_theme, _contact_tc, {_cr, c}, 1, false, true, false)) { + // TODO: try object interface first instead, then fall back to send with SingleInfoLocal + //_rmm.sendFileObj(c, o); + std::filesystem::path path = o.get().file_path; + _rmm.sendFilePath(c, path.filename().generic_u8string(), path.generic_u8string()); + } + } + ImGui::EndMenu(); + } + } + + ImGui::Separator(); + + if (ImGui::MenuItem("open")) { + const std::string url {file_path_to_file_url(local_info.file_path)}; + std::cout << "opening file '" << url << "'\n"; + SDL_OpenURL(url.c_str()); + } + if (ImGui::MenuItem("copy file")) { + const std::string url {file_path_to_file_url(local_info.file_path)}; + //ImGui::SetClipboardText(url.c_str()); + setClipboardData({"text/uri-list", "text/x-moz-url"}, std::make_shared>(url.begin(), url.end())); + } + if (ImGui::MenuItem("copy filepath")) { + const auto file_path = std::filesystem::canonical(local_info.file_path).u8string(); //TODO: use generic over native? + ImGui::SetClipboardText(file_path.c_str()); + } + ImGui::EndPopup(); + } + } + // TODO: how to collections? + + if (ImGui::BeginItemTooltip()) { + if (o.all_of()) { + ImGui::SeparatorText("single info"); + const auto& si = o.get(); + ImGui::Text("file name: '%s'", si.file_name.c_str()); + ImGui::Text("file size: %lu Bytes", si.file_size); + if (o.all_of()) { + ImGui::Text("local path: '%s'", o.get().file_path.c_str()); + } + } else if (o.all_of()) { + ImGui::SeparatorText("collection info"); + const auto& ci = o.get(); + ImGui::Text("total size: %lu Bytes", ci.total_size); + } + + if (fts != nullptr) { + ImGui::SeparatorText("transfer stats"); + ImGui::Text("rate up : %.1f Bytes/s", fts->rate_up); + ImGui::Text("rate down : %.1f Bytes/s", fts->rate_down); + ImGui::Text("total up : %lu Bytes", fts->total_up); + ImGui::Text("total down: %lu Bytes", fts->total_down); + } + + ImGui::EndTooltip(); } } void ChatGui4::renderMessageExtra(Message3Registry& reg, const Message3 e) { - if (reg.all_of(e)) { - ImGui::TextDisabled("fk:%lu", reg.get(e).kind); - } - if (reg.all_of(e)) { - ImGui::TextDisabled("ttf:%u", reg.get(e).transfer_number); + if (reg.all_of(e)) { + const auto o = reg.get(e).o; + + ImGui::TextDisabled("o:%u", entt::to_integral(o.entity())); + + if (o.all_of()) { + ImGui::TextDisabled("fk:%lu", o.get().kind); + } + if (o.all_of()) { + ImGui::TextDisabled("ttf:%u", o.get().transfer_number); + } } if (reg.all_of(e)) { @@ -1244,31 +1336,6 @@ bool ChatGui4::renderContactListContactSmall(const Contact3 c, const bool select return ImGui::Selectable(label.c_str(), selected); } -#if 0 -bool ChatGui4::renderSubContactListContact(const Contact3 c, const bool selected) const { - std::string label; - - if (_cr.all_of(c)) { - const auto c_state = _cr.get(c).state; - if (c_state == Contact::Components::ConnectionState::State::direct) { - label += "[D] "; - } else if (c_state == Contact::Components::ConnectionState::State::cloud) { - label += "[C] "; - } else { - label += "[-] "; - } - } else { - label += "[?] "; - } - - label += (_cr.all_of(c) ? _cr.get(c).name.c_str() : ""); - label += "###"; - label += std::to_string(entt::to_integral(c)); - - return ImGui::Selectable(label.c_str(), selected); -} -#endif - void ChatGui4::pasteFile(const char* mime_type) { size_t data_size = 0; void* data = SDL_GetClipboardData(mime_type, &data_size); diff --git a/src/chat_gui4.hpp b/src/chat_gui4.hpp index 11618525..032e211b 100644 --- a/src/chat_gui4.hpp +++ b/src/chat_gui4.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -25,6 +26,7 @@ using MessageTextureCache = TextureCache #include +#include ImageLoaderSDLBMP::ImageInfo ImageLoaderSDLBMP::loadInfoFromMemory(const uint8_t* data, uint64_t data_size) { ImageInfo res; @@ -14,6 +15,9 @@ ImageLoaderSDLBMP::ImageInfo ImageLoaderSDLBMP::loadInfoFromMemory(const uint8_t return res; } + assert(surf->w >= 0); + assert(surf->h >= 0); + res.width = surf->w; res.height = surf->h; res.file_ext = "bmp"; @@ -24,32 +28,48 @@ ImageLoaderSDLBMP::ImageInfo ImageLoaderSDLBMP::loadInfoFromMemory(const uint8_t } ImageLoaderSDLBMP::ImageResult ImageLoaderSDLBMP::loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) { - ImageResult res; auto* ios = SDL_IOFromConstMem(data, data_size); SDL_Surface* surf = SDL_LoadBMP_IO(ios, SDL_TRUE); if (surf == nullptr) { - return res; + return {}; } SDL_Surface* conv_surf = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA32); SDL_DestroySurface(surf); if (conv_surf == nullptr) { - return res; + return {}; } - res.width = surf->w; - res.height = surf->h; - res.file_ext = "bmp"; + assert(conv_surf->w >= 0); + assert(conv_surf->h >= 0); - SDL_LockSurface(conv_surf); + if (conv_surf->w > 16*1024 || conv_surf->h > 10*1024) { + std::cerr << "IL_SDLBMP error: image too large\n"; + return {}; + } + + ImageResult res; + if (SDL_MUSTLOCK(conv_surf)) { + if (SDL_LockSurface(conv_surf) < 0) { + std::cerr << "IL_SDLBMP error: " << SDL_GetError() << "\n"; + SDL_DestroySurface(conv_surf); + return {}; + } + } + + res.width = conv_surf->w; + res.height = conv_surf->h; + res.file_ext = "bmp"; auto& new_frame = res.frames.emplace_back(); new_frame.ms = 0; - new_frame.data = {(const uint8_t*)conv_surf->pixels, ((const uint8_t*)conv_surf->pixels) + (surf->w*surf->h*4)}; + new_frame.data = {(const uint8_t*)conv_surf->pixels, ((const uint8_t*)conv_surf->pixels) + (conv_surf->w*conv_surf->h*4)}; - SDL_UnlockSurface(conv_surf); + if (SDL_MUSTLOCK(conv_surf)) { + SDL_UnlockSurface(conv_surf); + } SDL_DestroySurface(conv_surf); std::cout << "IL_SDLBMP: loaded img " << res.width << "x" << res.height << "\n"; diff --git a/src/main_screen.cpp b/src/main_screen.cpp index 986ef0db..940f3fd4 100644 --- a/src/main_screen.cpp +++ b/src/main_screen.cpp @@ -26,17 +26,17 @@ MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme #endif tcm(cr, tc, tc), tmm(rmm, cr, tcm, tc, tc), - ttm(rmm, cr, tcm, tc, tc), + ttm(rmm, cr, tcm, tc, tc, os), tffom(cr, rmm, tcm, tc, tc), theme(theme_), mmil(rmm), - tam(rmm, cr, conf), + tam(/*rmm, */ os, cr, conf), sdlrtu(renderer_), tal(cr), contact_tc(tal, sdlrtu), mil(), msg_tc(mil, sdlrtu), - cg(conf, rmm, cr, sdlrtu, contact_tc, msg_tc, theme), + cg(conf, os, rmm, cr, sdlrtu, contact_tc, msg_tc, theme), sw(conf), osui(os), tuiu(tc, conf), diff --git a/src/media_meta_info_loader.cpp b/src/media_meta_info_loader.cpp index b1b9ed53..d1cf3a8b 100644 --- a/src/media_meta_info_loader.cpp +++ b/src/media_meta_info_loader.cpp @@ -7,71 +7,117 @@ #include -#include +#include "./os_comps.hpp" + +#include + +#include + +#include #include void MediaMetaInfoLoader::handleMessage(const Message3Handle& m) { - if (m.any_of()) { + if (!static_cast(m)) { return; } - if (!m.all_of()) { + // move to obj + if (m.any_of()) { return; } - const auto& fil = m.get(); - if (fil.file_list.size() != 1) { + if (!m.all_of()) { + // not a file message + return; + } + const auto& o = m.get().o; + + if (!static_cast(o)) { + std::cerr << "MMIL error: invalid object in file message\n"; return; } - std::ifstream file(fil.file_list.front(), std::ios::binary); - if (file.is_open()) { - // figure out size - file.seekg(0, file.end); - if (file.tellg() > 50*1024*1024) { - // TODO: conf - // dont try load files larger 50mb - return; - } - file.seekg(0, file.beg); - - std::vector tmp_buffer; - while (file.good()) { - auto ch = file.get(); - if (ch == EOF) { - break; - } else { - tmp_buffer.push_back(ch); - } - } - - bool could_load {false}; - // try all loaders after another - for (auto& il : _image_loaders) { - // TODO: impl callback based load - auto res = il->loadInfoFromMemory(tmp_buffer.data(), tmp_buffer.size()); - if (res.height == 0 || res.width == 0) { - continue; - } - - m.emplace(res.width, res.height); - - could_load = true; - - std::cout << "MMIL loaded image info " << fil.file_list.front() << "\n"; - - _rmm.throwEventUpdate(m); - break; - } - - if (!could_load) { - m.emplace(); - - std::cout << "MMIL loading failed image info " << fil.file_list.front() << "\n"; - - _rmm.throwEventUpdate(m); - } + if (o.any_of()) { + return; } + + if (!o.all_of()) { + return; // we dont have all data + } + + if (!o.all_of()) { + std::cerr << "MMIL error: object missing backend/file info (?)\n"; + return; + } + + // TODO: handle collections + const auto file_size = o.get().file_size; + + if (file_size > 50*1024*1024) { + std::cerr << "MMIL error: image file too large\n"; + return; + } + + if (file_size == 0) { + std::cerr << "MMIL warning: empty file\n"; + return; + } + + if (!o.all_of()) { + // not ready yet + return; + } + + auto* file_backend = o.get().ptr; + if (file_backend == nullptr) { + std::cerr << "MMIL error: object backend nullptr\n"; + return; + } + + + auto file2 = file_backend->file2(o, StorageBackendI::FILE2_READ); + if (!file2 || !file2->isGood() || !file2->can_read) { + std::cerr << "MMIL error: creating file2 from object via backendI\n"; + return; + } + + auto read_data = file2->read(file_size, 0); + if (read_data.ptr == nullptr) { + std::cerr << "MMIL error: reading from file2 returned nullptr\n"; + return; + } + + if (read_data.size != file_size) { + std::cerr << "MMIL error: reading from file2 size missmatch, should be " << file_size << ", is " << read_data.size << "\n"; + return; + } + + // try all loaders after another + for (auto& il : _image_loaders) { + // TODO: impl callback based load + auto res = il->loadInfoFromMemory(read_data.ptr, read_data.size); + if (res.height == 0 || res.width == 0) { + continue; + } + + o.emplace( + static_cast(std::min(res.width, std::numeric_limits::max())), + static_cast(std::min(res.height, std::numeric_limits::max())) + ); + + std::cout << "MMIL: loaded image file o:" << /*file_path*/ entt::to_integral(o.entity()) << "\n"; + + _rmm.throwEventUpdate(m); + return; + } + + m.emplace(); + + std::cout << "MMIL: loading failed image info o:" << /*file_path*/ entt::to_integral(o.entity()) << "\n"; + + // TODO: update object too + // recursion + _rmm.throwEventUpdate(m); } MediaMetaInfoLoader::MediaMetaInfoLoader(RegistryMessageModel& rmm) : _rmm(rmm) { diff --git a/src/media_meta_info_loader.hpp b/src/media_meta_info_loader.hpp index 0ff30acb..8923b643 100644 --- a/src/media_meta_info_loader.hpp +++ b/src/media_meta_info_loader.hpp @@ -6,11 +6,6 @@ namespace Message::Components { - struct FrameDims { - uint32_t width {0}; - uint32_t height {0}; - }; - struct TagNotImage {}; } // Message::Components @@ -31,5 +26,10 @@ class MediaMetaInfoLoader : public RegistryMessageModelEventI { protected: // rmm bool onEvent(const Message::Events::MessageConstruct& e) override; bool onEvent(const Message::Events::MessageUpdated& e) override; + + //protected: // os + // bool onEvent(const ObjectStore::Events::ObjectConstruct& e) override; + // should listen on update + // bool onEvent(const ObjectStore::Events::ObjectUpdate& e) override; }; diff --git a/src/message_image_loader.cpp b/src/message_image_loader.cpp index 39e4ffb5..baca8280 100644 --- a/src/message_image_loader.cpp +++ b/src/message_image_loader.cpp @@ -8,10 +8,15 @@ #include +#include "./os_comps.hpp" + +#include + +#include + +#include + #include -#include -#include -#include // fwd namespace Message { @@ -34,51 +39,88 @@ std::optional MessageImageLoader::load(TextureUploaderI& tu, Messa return std::nullopt; } - if (m.all_of()) { - const auto& file_list = m.get().file_list; - assert(!file_list.empty()); - const auto& file_path = file_list.front(); + if (!m.all_of()) { + // not a file message + return std::nullopt; + } + const auto& o = m.get().o; - std::ifstream file(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); - } - } - - // try all loaders after another - for (auto& il : _image_loaders) { - auto res = il->loadFromMemoryRGBA(tmp_buffer.data(), tmp_buffer.size()); - if (res.frames.empty() || res.height == 0 || res.width == 0) { - continue; - } - - TextureEntry new_entry; - new_entry.timestamp_last_rendered = Message::getTimeMS(); - new_entry.current_texture = 0; - for (const auto& [ms, data] : res.frames) { - const auto n_t = tu.uploadRGBA(data.data(), res.width, res.height); - new_entry.textures.push_back(n_t); - new_entry.frame_duration.push_back(ms); - } - - new_entry.width = res.width; - new_entry.height = res.height; - - std::cout << "MIL: loaded image file " << file_path << "\n"; - - return new_entry; - } - } + if (!static_cast(o)) { + std::cerr << "MIL error: invalid object in file message\n"; + return std::nullopt; } - std::cerr << "MIL: failed to load message\n"; + if (!o.all_of()) { + std::cerr << "MIL error: object missing backend (?)\n"; + return std::nullopt; + } + // TODO: handle collections + const auto file_size = o.get().file_size; + + if (file_size > 50*1024*1024) { + std::cerr << "MIL error: image file too large\n"; + return std::nullopt; + } + + if (file_size == 0) { + std::cerr << "MIL warning: empty file\n"; + return std::nullopt; + } + + if (!o.all_of()) { + // not ready yet + return std::nullopt; + } + + auto* file_backend = o.get().ptr; + if (file_backend == nullptr) { + std::cerr << "MIL error: object backend nullptr\n"; + return std::nullopt; + } + + auto file2 = file_backend->file2(o, StorageBackendI::FILE2_READ); + if (!file2 || !file2->isGood() || !file2->can_read) { + std::cerr << "MIL error: creating file2 from object via backendI\n"; + return std::nullopt; + } + + auto read_data = file2->read(file_size, 0); + if (read_data.ptr == nullptr) { + std::cerr << "MMIL error: reading from file2 returned nullptr\n"; + return std::nullopt; + } + + if (read_data.size != file_size) { + std::cerr << "MIL error: reading from file2 size missmatch, should be " << file_size << ", is " << read_data.size << "\n"; + return std::nullopt; + } + + // try all loaders after another + for (auto& il : _image_loaders) { + auto res = il->loadFromMemoryRGBA(read_data.ptr, read_data.size); + if (res.frames.empty() || res.height == 0 || res.width == 0) { + continue; + } + + TextureEntry new_entry; + new_entry.timestamp_last_rendered = Message::getTimeMS(); + new_entry.current_texture = 0; + for (const auto& [ms, data] : res.frames) { + const auto n_t = tu.uploadRGBA(data.data(), res.width, res.height); + new_entry.textures.push_back(n_t); + new_entry.frame_duration.push_back(ms); + } + + new_entry.width = res.width; + new_entry.height = res.height; + + std::cout << "MIL: loaded image file o:" << /*file_path*/ entt::to_integral(o.entity()) << "\n"; + + return new_entry; + } + + std::cerr << "MIL error: failed to load message (unhandled format)\n"; return std::nullopt; } diff --git a/src/message_image_loader.hpp b/src/message_image_loader.hpp index 7210157f..5dabdeeb 100644 --- a/src/message_image_loader.hpp +++ b/src/message_image_loader.hpp @@ -15,13 +15,3 @@ class MessageImageLoader { std::optional load(TextureUploaderI& tu, Message3Handle c); }; -// TODO: move to rmm -template<> -struct std::hash { - std::size_t operator()(Message3Handle const& m) const noexcept { - const std::size_t h1 = reinterpret_cast(m.registry()); - const std::size_t h2 = entt::to_integral(m.entity()); - return (h1 << 3) ^ (h2 * 11400714819323198485llu); - } -}; - diff --git a/src/object_store_ui.cpp b/src/object_store_ui.cpp index 277ce23a..edf14c06 100644 --- a/src/object_store_ui.cpp +++ b/src/object_store_ui.cpp @@ -2,20 +2,25 @@ #include #include +#include +#include "./os_comps.hpp" #include -#include - namespace MM { template<> void ComponentEditorWidget(entt::basic_registry& registry, Object entity) { auto& c = registry.get(entity); const auto str = bin2hex(c.v); + if (ImGui::SmallButton("copy")) { + ImGui::SetClipboardText(str.c_str()); + } + ImGui::SameLine(); ImGui::TextUnformatted(str.c_str()); } +#if 0 template<> void ComponentEditorWidget(entt::basic_registry& registry, Object entity) { auto& c = registry.get(entity); @@ -54,14 +59,30 @@ template<> void ComponentEditorWidget void ComponentEditorWidget(entt::basic_registry& registry, Object entity) { - auto& c = registry.get(entity); - ImGui::Text("total bytes received: %lu", c.total); +#endif + +template<> void ComponentEditorWidget(entt::basic_registry& registry, Object entity) { + auto& c = registry.get(entity); + if (!c.file_name.empty()) { + ImGui::Text("file name: %s", c.file_name.c_str()); + } + + ImGui::Text("file size: %lu", c.file_size); } -template<> void ComponentEditorWidget(entt::basic_registry& registry, Object entity) { - auto& c = registry.get(entity); - ImGui::Text("total bytes sent: %lu", c.total); +template<> void ComponentEditorWidget(entt::basic_registry& registry, Object entity) { + auto& c = registry.get(entity); + if (!c.file_path.empty()) { + ImGui::Text("file path: %s", c.file_path.c_str()); + } +} + +template<> void ComponentEditorWidget(entt::basic_registry& registry, Object entity) { + auto& c = registry.get(entity); + ImGui::Text("upload rate : %.1f bytes/s", c.rate_up); + ImGui::Text("download rate: %.1f bytes/s", c.rate_down); + ImGui::Text("total bytes uploaded : %lu bytes", c.total_up); + ImGui::Text("total bytes downloaded: %lu bytes", c.total_down); } } // MM @@ -74,10 +95,20 @@ ObjectStoreUI::ObjectStoreUI( _ee.registerComponent("ID"); _ee.registerComponent("DataCompressionType"); - _ee.registerComponent("Transfer::FileInfo"); - _ee.registerComponent("Transfer::FileInfoLocal"); - _ee.registerComponent("Transfer::BytesReceived"); - _ee.registerComponent("Transfer::BytesSent"); + _ee.registerComponent("Ephemeral::FilePath"); + + _ee.registerComponent("File::SingleInfo"); + _ee.registerComponent("File::SingleInfoLocal"); + _ee.registerComponent("File::Collection"); + _ee.registerComponent("File::CollectionInfo"); + _ee.registerComponent("File::CollectionInfoLocal"); + _ee.registerComponent("File::LocalHaveBitset"); + _ee.registerComponent("File::RemoteHaveBitset"); + + _ee.registerComponent("Ephemeral::File::DownloadPriority"); + _ee.registerComponent("Ephemeral::File::ReadHeadHint"); + _ee.registerComponent("Ephemeral::File::TransferStats"); + _ee.registerComponent("Ephemeral::File::TransferStatsSeparated"); } void ObjectStoreUI::render(void) { diff --git a/src/os_comps.hpp b/src/os_comps.hpp new file mode 100644 index 00000000..133e70c2 --- /dev/null +++ b/src/os_comps.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include + +#include + +namespace ObjectStore::Components { + + // until i find a better name + namespace File { + + // ephemeral?, not sure saving this to disk makes sense + // tag remove have all? + struct RemoteHaveBitset { + struct Entry { + bool have_all {false}; + BitSet have; + }; + entt::dense_map others; + }; + + } // File + + namespace Ephemeral { + + namespace File { + + struct TransferStatsSeparated { + entt::dense_map stats; + }; + + } // File + + } // Ephemeral + +} // ObjectStore::Components + +#include "./os_comps_id.inl" + diff --git a/src/os_comps_id.inl b/src/os_comps_id.inl new file mode 100644 index 00000000..51b54ff4 --- /dev/null +++ b/src/os_comps_id.inl @@ -0,0 +1,26 @@ +#pragma once + +#include "./os_comps.hpp" + +#include + +// TODO: move more central +#define DEFINE_COMP_ID(x) \ +template<> \ +constexpr entt::id_type entt::type_hash::value() noexcept { \ + using namespace entt::literals; \ + return #x##_hs; \ +} \ +template<> \ +constexpr std::string_view entt::type_name::value() noexcept { \ + return #x; \ +} + +// cross compile(r) stable ids + +DEFINE_COMP_ID(ObjComp::F::RemoteHaveBitset) + +DEFINE_COMP_ID(ObjComp::Ephemeral::File::TransferStatsSeparated) + +#undef DEFINE_COMP_ID + diff --git a/src/tox_avatar_manager.cpp b/src/tox_avatar_manager.cpp index 20e77329..bd75c8b3 100644 --- a/src/tox_avatar_manager.cpp +++ b/src/tox_avatar_manager.cpp @@ -1,10 +1,13 @@ #include "./tox_avatar_manager.hpp" +// TODO: this whole thing needs a rewrite and is currently disabled + #include -#include +//#include +#include // for comp transfer tox filekind (TODO: generalize -> content system?) -#include +#include #include #include @@ -26,12 +29,13 @@ namespace Components { }; ToxAvatarManager::ToxAvatarManager( - RegistryMessageModel& rmm, + //RegistryMessageModel& rmm, + ObjectStore2& os, Contact3Registry& cr, ConfigModelI& conf -) : _rmm(rmm), _cr(cr), _conf(conf) { - _rmm.subscribe(this, RegistryMessageModel_Event::message_construct); - _rmm.subscribe(this, RegistryMessageModel_Event::message_updated); +) : /*_rmm(rmm)*/ _os(os), _cr(cr), _conf(conf) { + _os.subscribe(this, ObjectStore_Event::object_construct); + _os.subscribe(this, ObjectStore_Event::object_update); if (!_conf.has_string("ToxAvatarManager", "save_path")) { // or on linux: $HOME/.config/tox/avatars/ @@ -64,15 +68,15 @@ void ToxAvatarManager::iterate(void) { // cancel queue // accept queue - for (auto& [m, path] : _accept_queue) { - if (m.all_of()) { - continue; // already accepted + for (auto& [o, path] : _accept_queue) { + if (o.any_of()) { + continue; // already accepted / done } - m.emplace(path, true); + o.emplace(path, true); std::cout << "TAM: auto accepted transfer\n"; - _rmm.throwEventUpdate(m); + _os.throwEventUpdate(o); } _accept_queue.clear(); @@ -87,9 +91,6 @@ std::string ToxAvatarManager::getAvatarPath(const ToxKey& key) const { } 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 @@ -106,58 +107,62 @@ void ToxAvatarManager::clearAvatarFromContact(const Contact3 c) { } } -void ToxAvatarManager::checkMsg(Message3Handle h) { - if (h.any_of< - Message::Components::Transfer::ActionAccept, +void ToxAvatarManager::checkObj(ObjectHandle o) { + if (o.any_of< + ObjComp::Ephemeral::File::ActionTransferAccept, Components::TagAvatarImageHandled >()) { return; // already accepted or handled } - if (!h.any_of< - Message::Components::Transfer::TagPaused, - Message::Components::Transfer::TagHaveAll + if (!o.any_of< + ObjComp::Ephemeral::File::TagTransferPaused, + ObjComp::F::TagLocalHaveAll >()) { // 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 + 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 >()) { return; } // TCS-2.2.11 (big list, should have been sub points ...) - if (h.get().kind != 1) { + if (o.get().kind != 1) { // not an avatar return; } - const auto& file_info = h.get(); + const auto& file_info = o.get(); - const auto contact = h.get().c; + //const auto contact = h.get().c; // TCS-2.2.4 - if (file_info.total_size > 65536ul) { + if (file_info.file_size > 65536ul) { // TODO: mark handled? return; // too large } +#if 0 // TODO: make avatars work again !!!!! + // TCS-2.2.10 - if (file_info.file_list.empty() || file_info.file_list.front().file_name.empty() || file_info.total_size == 0) { + if (file_info.file_name.empty() || file_info.file_size == 0) { // reset clearAvatarFromContact(contact); // TODO: cancel return; } - if (!h.all_of< - Message::Components::Transfer::FileID + if (!o.all_of< + ObjComp::Tox::FileID >()) { return; } @@ -173,7 +178,7 @@ void ToxAvatarManager::checkMsg(Message3Handle h) { return; } - if (h.all_of()) { + if (o.all_of()) { std::cout << "TAM: full avatar received\n"; if (_cr.all_of(contact)) { @@ -184,7 +189,7 @@ void ToxAvatarManager::checkMsg(Message3Handle h) { std::cerr << "TAM error: cant get toxkey for contact\n"; } - h.emplace_or_replace(); + o.emplace_or_replace(); } else { // check file id for existing hash if (std::filesystem::is_regular_file(file_path)) { @@ -199,17 +204,18 @@ void ToxAvatarManager::checkMsg(Message3Handle h) { std::cout << "TAM: accepted avatar ft\n"; // if not already on disk - _accept_queue.push_back(AcceptEntry{h, file_path}); + _accept_queue.push_back(AcceptEntry{o, file_path}); } +#endif } -bool ToxAvatarManager::onEvent(const Message::Events::MessageConstruct& e) { - checkMsg(e.e); +bool ToxAvatarManager::onEvent(const ObjectStore::Events::ObjectConstruct& e) { + checkObj(e.e); return false; } -bool ToxAvatarManager::onEvent(const Message::Events::MessageUpdated& e) { - checkMsg(e.e); +bool ToxAvatarManager::onEvent(const ObjectStore::Events::ObjectUpdate& e) { + checkObj(e.e); return false; } diff --git a/src/tox_avatar_manager.hpp b/src/tox_avatar_manager.hpp index 48528476..69a96d0f 100644 --- a/src/tox_avatar_manager.hpp +++ b/src/tox_avatar_manager.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -12,20 +12,20 @@ struct ConfigModelI; struct ToxKey; -class ToxAvatarManager : public RegistryMessageModelEventI { - RegistryMessageModel& _rmm; +class ToxAvatarManager : public ObjectStoreEventI { + ObjectStore2& _os; Contact3Registry& _cr; ConfigModelI& _conf; struct AcceptEntry { - Message3Handle m; + ObjectHandle m; std::string file_path; }; std::vector _accept_queue; public: ToxAvatarManager( - RegistryMessageModel& rmm, + ObjectStore2& os, Contact3Registry& cr, ConfigModelI& conf ); @@ -33,13 +33,14 @@ class ToxAvatarManager : public RegistryMessageModelEventI { void iterate(void); protected: + // TODO: become backend and work in objects instead std::string getAvatarPath(const ToxKey& key) const; void addAvatarFileToContact(const Contact3 c, const ToxKey& key); void clearAvatarFromContact(const Contact3 c); - void checkMsg(Message3Handle h); + void checkObj(ObjectHandle o); - protected: // mm - bool onEvent(const Message::Events::MessageConstruct& e) override; - bool onEvent(const Message::Events::MessageUpdated& e) override; + protected: // os + bool onEvent(const ObjectStore::Events::ObjectConstruct& e) override; + bool onEvent(const ObjectStore::Events::ObjectUpdate& e) override; }; diff --git a/src/tox_friend_faux_offline_messaging.cpp b/src/tox_friend_faux_offline_messaging.cpp index 9ba1964a..72b0a8ec 100644 --- a/src/tox_friend_faux_offline_messaging.cpp +++ b/src/tox_friend_faux_offline_messaging.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include