From 442fc29582b5581111e3ffd286f4f3d282877f3c Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 16 Dec 2022 21:13:38 +0100 Subject: Fix bug where image are not removed from loading when not visible Matrix: fix invites not showing notification --- include/AsyncImageLoader.hpp | 1 + include/FileAnalyzer.hpp | 3 +++ include/MessageQueue.hpp | 10 +++++++--- include/Program.hpp | 1 + plugins/Matrix.hpp | 2 +- src/AsyncImageLoader.cpp | 16 ++++++++++------ src/FileAnalyzer.cpp | 32 ++++++++++++++++++++++---------- src/Program.cpp | 17 ++++++++++++++++- src/QuickMedia.cpp | 2 +- src/Text.cpp | 3 +-- src/plugins/Matrix.cpp | 27 +++++++++++++++++++++++---- 11 files changed, 86 insertions(+), 28 deletions(-) diff --git a/include/AsyncImageLoader.hpp b/include/AsyncImageLoader.hpp index 986430c..42e7915 100644 --- a/include/AsyncImageLoader.hpp +++ b/include/AsyncImageLoader.hpp @@ -27,6 +27,7 @@ namespace QuickMedia { std::unique_ptr image; // Set in another thread. This should be .reset after loading it into |texture|, to save memory size_t counter = 0; Path thumbnail_path; + std::string url; }; struct ThumbnailLoadData { diff --git a/include/FileAnalyzer.hpp b/include/FileAnalyzer.hpp index 7be154f..ed17140 100644 --- a/include/FileAnalyzer.hpp +++ b/include/FileAnalyzer.hpp @@ -43,6 +43,9 @@ namespace QuickMedia { bool is_video_ext(const char *ext); bool is_music_ext(const char *ext); + // Set |width| or |height| to 0 to disable scaling. + // TODO: Make this async + bool video_get_start_frame(const FileAnalyzer &file, const char *destination_path, int width = 0, int height = 0); // Set |width| or |height| to 0 to disable scaling. // TODO: Make this async bool video_get_middle_frame(const FileAnalyzer &file, const char *destination_path, int width = 0, int height = 0); diff --git a/include/MessageQueue.hpp b/include/MessageQueue.hpp index ba28431..1e48102 100644 --- a/include/MessageQueue.hpp +++ b/include/MessageQueue.hpp @@ -59,14 +59,18 @@ namespace QuickMedia { } // Return true from |callback| to remove the element - void erase_if(std::function callback) { + int erase_if(std::function callback) { std::unique_lock lock(mutex); + int removed = 0; for(auto it = data_queue.begin(); it != data_queue.end();) { - if(callback(*it)) + if(callback(*it)) { it = data_queue.erase(it); - else + ++removed; + } else { ++it; + } } + return removed; } bool is_running() const { diff --git a/include/Program.hpp b/include/Program.hpp index 674c834..5249de6 100644 --- a/include/Program.hpp +++ b/include/Program.hpp @@ -33,6 +33,7 @@ int exec_program_write_stdin(const char **args, const char *str, size_t size, Pr |buffer_size| has to be between 1 and 65536. */ int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata, int buffer_size = 16384); +int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata, int *allowed_exit_status, int num_allowed_exit_status, int buffer_size = 16384); // Return the exit status, or a negative value if waiting failed int wait_program(pid_t process_id); diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 4af8442..ccbd8c0 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -253,7 +253,6 @@ namespace QuickMedia { std::string room_avatar_url; std::shared_ptr invited_by; time_t timestamp = 0; // In milliseconds - bool new_invite = false; }; enum class MessageDirection { @@ -755,6 +754,7 @@ namespace QuickMedia { PluginResult parse_notifications(const rapidjson::Value ¬ifications_json, std::function callback_func); PluginResult parse_sync_account_data(const rapidjson::Value &account_data_json); PluginResult parse_sync_room_data(const rapidjson::Value &rooms_json, bool initial_sync); + void add_new_invites(); void parse_custom_emoji(const rapidjson::Value &custom_emoji_json); void load_custom_emoji_from_cache(); PluginResult get_previous_room_messages(RoomData *room_data, bool latest_messages, size_t &num_new_messages, bool *reached_end = nullptr); diff --git a/src/AsyncImageLoader.cpp b/src/AsyncImageLoader.cpp index 4021c55..8613cf9 100644 --- a/src/AsyncImageLoader.cpp +++ b/src/AsyncImageLoader.cpp @@ -312,15 +312,19 @@ namespace QuickMedia { } std::shared_ptr AsyncImageLoader::get_thumbnail(const std::string &url, bool local, mgl::vec2i resize_target_size) { - // TODO: Instead of generating a new hash everytime to access thumbnail, cache the hash of the thumbnail url - auto &thumbnail_data = thumbnails[url + "_" + std::to_string(resize_target_size.x) + "x" + std::to_string(resize_target_size.y)]; - if(!thumbnail_data) + // TODO: Instead of generating a new hash everytime to access thumbnail, cache the hash of the thumbnail url. + // TODO: Cache this resize_url + std::string resize_url = url + "_" + std::to_string(resize_target_size.x) + "x" + std::to_string(resize_target_size.y); + auto &thumbnail_data = thumbnails[resize_url]; + if(!thumbnail_data) { thumbnail_data = std::make_shared(); + thumbnail_data->url = url; + } thumbnail_data->counter = counter; if(thumbnail_data->thumbnail_path.data.empty()) { SHA256 sha256; - sha256.add(url.data(), url.size()); + sha256.add(resize_url.data(), resize_url.size()); thumbnail_data->thumbnail_path = get_cache_dir().join("thumbnails").join(sha256.getHash()); } @@ -358,7 +362,7 @@ namespace QuickMedia { if(download.read_program.pid == -1) continue; - if(download.url == it->first) { + if(download.url == it->second->url) { reset_download(download); break; } @@ -366,7 +370,7 @@ namespace QuickMedia { } image_thumbnail_create_queue.erase_if([&it](ThumbnailLoadData &load_data) { - return load_data.path.data == it->first; + return load_data.path.data == it->second->url; }); it = thumbnails.erase(it); diff --git a/src/FileAnalyzer.cpp b/src/FileAnalyzer.cpp index 361211f..61eff7b 100644 --- a/src/FileAnalyzer.cpp +++ b/src/FileAnalyzer.cpp @@ -21,10 +21,12 @@ namespace QuickMedia { // https://mimesniff.spec.whatwg.org/ // TODO: Test all of these - static const std::array magic_numbers = { + static const std::array magic_numbers = { MagicNumber{ {'R', 'I', 'F', 'F', -1, -1, -1, -1, 'A', 'V', 'I', ' '}, 12, ContentType::VIDEO_AVI }, MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', 'i', 's', 'o', 'm'}, 12, ContentType::VIDEO_MP4 }, + MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', 'i', 's', 'o', '5'}, 12, ContentType::VIDEO_MP4 }, MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', 'm', 'p', '4', '2'}, 12, ContentType::VIDEO_MP4 }, + MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', 'M', '4', 'V'}, 11, ContentType::VIDEO_MP4 }, MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', '3', 'g', 'p', '4'}, 12, ContentType::VIDEO_MP4 }, MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', '3', 'g', 'p', '5'}, 12, ContentType::VIDEO_MP4 }, MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'm', 'p', '4', '2'}, 11, ContentType::VIDEO_MP4 }, @@ -136,29 +138,29 @@ namespace QuickMedia { || strcase_equals(ext, ".wav") || strcase_equals(ext, ".wma") || strcase_equals(ext, ".mid"); - } - bool video_get_middle_frame(const FileAnalyzer &file, const char *destination_path, int width, int height) { + bool video_get_frame(const FileAnalyzer &file, const char *destination_path, int width, int height, int seconds) { Path destination_path_tmp = destination_path; destination_path_tmp.append(".tmp.jpg"); // TODO: .png, but the below code also needs to be changed for that - const int middle_seconds = file.get_duration_seconds().value_or(0.0) / 2.0; - char middle_seconds_str[32]; - snprintf(middle_seconds_str, sizeof(middle_seconds_str), "%d", middle_seconds); + char seconds_str[32]; + snprintf(seconds_str, sizeof(seconds_str), "%d", seconds); + + int allowed_exit_status[] = { 0, 69 }; if(width > 0 && height > 0) { char size_arg_str[512]; snprintf(size_arg_str, sizeof(size_arg_str), "scale=%d:%d:force_original_aspect_ratio=decrease", width, height); - const char *program_args[] = { "ffmpeg", "-y", "-v", "quiet", "-ss", middle_seconds_str, "-i", file.get_filepath().c_str(), "-vframes", "1", "-vf", size_arg_str, "--", destination_path_tmp.data.c_str(), nullptr }; - if(exec_program(program_args, nullptr, nullptr) != 0) { + const char *program_args[] = { "ffmpeg", "-y", "-v", "quiet", "-ss", seconds_str, "-i", file.get_filepath().c_str(), "-frames:v", "1", "-vf", size_arg_str, "--", destination_path_tmp.data.c_str(), nullptr }; + if(exec_program(program_args, nullptr, nullptr, allowed_exit_status, 2) != 0) { fprintf(stderr, "Failed to execute ffmpeg, maybe its not installed?\n"); return false; } } else { - const char *program_args[] = { "ffmpeg", "-y", "-v", "quiet", "-ss", middle_seconds_str, "-i", file.get_filepath().c_str(), "-vframes", "1", "--", destination_path_tmp.data.c_str(), nullptr }; - if(exec_program(program_args, nullptr, nullptr) != 0) { + const char *program_args[] = { "ffmpeg", "-y", "-v", "quiet", "-ss", seconds_str, "-i", file.get_filepath().c_str(), "-frames:v", "1", "--", destination_path_tmp.data.c_str(), nullptr }; + if(exec_program(program_args, nullptr, nullptr, allowed_exit_status, 2) != 0) { fprintf(stderr, "Failed to execute ffmpeg, maybe its not installed?\n"); return false; } @@ -167,6 +169,16 @@ namespace QuickMedia { return rename_atomic(destination_path_tmp.data.c_str(), destination_path) == 0; } + bool video_get_start_frame(const FileAnalyzer &file, const char *destination_path, int width, int height) { + const int start_seconds = std::max(0.0, std::min(3.0, file.get_duration_seconds().value_or(0.0) - 1.0)); + return video_get_frame(file, destination_path, width, height, start_seconds); + } + + bool video_get_middle_frame(const FileAnalyzer &file, const char *destination_path, int width, int height) { + const int middle_seconds = file.get_duration_seconds().value_or(0.0) / 2.0; + return video_get_frame(file, destination_path, width, height, middle_seconds); + } + // TODO: Remove dependency on ffprobe static bool ffprobe_extract_metadata(const char *filepath, std::optional &dimensions, std::optional &duration_seconds) { const char *program_args[] = { "ffprobe", "-v", "quiet", "-print_format", "json", "-show_streams", "-show_format", "--", filepath, nullptr }; diff --git a/src/Program.cpp b/src/Program.cpp index 8b3cc49..5e54971 100644 --- a/src/Program.cpp +++ b/src/Program.cpp @@ -303,6 +303,11 @@ int exec_program_write_stdin(const char **args, const char *str, size_t size, Pr } int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata, int buffer_size) { + int allowed_exit_status[1] = {0}; + return exec_program(args, output_callback, userdata, allowed_exit_status, 1, buffer_size); +} + +int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata, int *allowed_exit_status, int num_allowed_exit_status, int buffer_size) { ReadProgram read_program; int res = exec_program_pipe(args, &read_program); if(res != 0) @@ -311,6 +316,7 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void int result = 0; int status; int exit_status; + bool is_error = true; assert(buffer_size >= 1 && buffer_size <= 65536); char *buffer = (char*)alloca(buffer_size + 1); @@ -349,7 +355,14 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void } exit_status = WEXITSTATUS(status); - if(exit_status != 0) { + for(int i = 0; i < num_allowed_exit_status; ++i) { + if(exit_status == allowed_exit_status[i]) { + is_error = false; + break; + } + } + + if(is_error) { fprintf(stderr, "Failed to execute program ("); const char **arg = args; while(*arg) { @@ -360,6 +373,8 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void } fprintf(stderr, "), exit status %d\n", exit_status); result = -exit_status; + if(result == 0) + result = -1; } cleanup: diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 84b6cc4..9e360b7 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3007,7 +3007,7 @@ namespace QuickMedia { const char *args[] = { "curl", "-sLf", "-r", "0-40", "--no-buffer", "-H", useragent_str, "--", url, nullptr }; exec_program(args, accumulate_string_limit_head, &result, 42); return (result.size() >= 42) - && (memcmp(&result[4], "ftypisom", 8) == 0 || memcmp(&result[4], "ftypmp42", 8) == 0 || memcmp(&result[4], "ftymp42", 7) == 0 || memcmp(&result[4], "ftyp3gp4", 8) == 0 || memcmp(&result[4], "ftyp3gp5", 8) == 0 || memcmp(&result[4], "fty3gp5", 7) == 0 || memcmp(&result[4], "ftypqt", 6) == 0) + && (memcmp(&result[4], "ftypisom", 8) == 0 || memcmp(&result[4], "ftypiso5", 8) == 0 || memcmp(&result[4], "ftypmp42", 8) == 0 || memcmp(&result[4], "ftypM4V", 7) == 0 || memcmp(&result[4], "ftymp42", 7) == 0 || memcmp(&result[4], "ftyp3gp4", 8) == 0 || memcmp(&result[4], "ftyp3gp5", 8) == 0 || memcmp(&result[4], "fty3gp5", 7) == 0 || memcmp(&result[4], "ftypqt", 6) == 0) && (memmem(&result[0], result.size(), "moov", 4) == NULL); } diff --git a/src/Text.cpp b/src/Text.cpp index 0597080..787c3a4 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -277,7 +277,6 @@ namespace QuickMedia } } - fprintf(stderr, "old caret index: %d, advance: %d\n", caretIndex, caret_advance); caretIndex += caret_advance; dirtyCaret = true; } @@ -1577,7 +1576,7 @@ namespace QuickMedia if(textElement.text_type == TextElement::TextType::EMOJI) { // TODO: if(textElement.pos.to_vec2f().y + vspace > boundingBox.size.y + 10.0f) { - fprintf(stderr, "bounding box y: %f\n", boundingBox.size.y); + //fprintf(stderr, "bounding box y: %f\n", boundingBox.size.y); continue; } diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index fae8451..c93a22b 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -684,7 +684,7 @@ namespace QuickMedia { if(silenced_invite) body_item->add_reaction("Silenced", nullptr); invites_page->add_body_item(std::move(body_item)); - if(invite.new_invite && !silenced_invite) { + if(!silenced_invite) { show_notification("QuickMedia matrix - " + invite.room_name, "You were invited to " + invite.room_name + " by " + invited_by_display_name + " (" + invite.invited_by->user_id + ")"); } } @@ -1147,7 +1147,7 @@ namespace QuickMedia { } void MatrixInvitesPage::add_body_item(std::shared_ptr body_item) { - body->insert_item_by_timestamp(std::move(body_item)); + body->insert_item_by_timestamp_reverse(std::move(body_item)); if(body->get_num_items() != prev_invite_count) { prev_invite_count = body->get_num_items(); title = "Invites (" + std::to_string(body->get_num_items()) + ")"; @@ -1773,6 +1773,8 @@ namespace QuickMedia { goto sync_end; } + add_new_invites(); + next_batch_json = &GetMember(json_root, "next_batch"); if(next_batch_json->IsString()) { set_next_batch(next_batch_json->GetString(), true); @@ -2037,6 +2039,14 @@ namespace QuickMedia { } } + void Matrix::add_new_invites() { + std::lock_guard lock(invite_mutex); + for(auto &[room_id, invite] : invites) { + ui_thread_tasks.push([this, room_id{std::move(room_id)}, invite{std::move(invite)}]{ delegate->add_invite(room_id, std::move(invite)); }); + } + invites.clear(); + } + PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool initial_sync) { if(!root.IsObject()) return PluginResult::ERR; @@ -3673,8 +3683,7 @@ namespace QuickMedia { invite.timestamp = timestamp; std::string room_id_str(room_id.GetString(), room_id.GetStringLength()); - if(set_invite(room_id_str, invite)) - ui_thread_tasks.push([this, room_id_str{std::move(room_id_str)}, invite{std::move(invite)}]{ delegate->add_invite(room_id_str, std::move(invite)); }); + set_invite(room_id_str, invite); break; } @@ -3918,6 +3927,16 @@ namespace QuickMedia { case '\n': case '\r': case '\t': + case '?': + case ',': + case ')': + case '(': + case '[': + case ']': + case '{': + case '}': + case '!': + case ';': case '@': { user_id_finished = true; user_id_end = i; -- cgit v1.2.3