From 928f2525c29929de0c2ab520f48c82b5cb882aa7 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 30 Oct 2020 16:47:40 +0100 Subject: Matrix: re-add /logout, cancel task immediately Cancel video download when pressing escape, other fixes.. --- README.md | 1 + TODO | 7 +- images/matrix_logo.png | Bin 1199 -> 6323 bytes include/AsyncTask.hpp | 9 ++ include/Body.hpp | 3 +- include/Program.h | 48 ---------- include/Program.hpp | 44 +++++++++ include/QuickMedia.hpp | 1 + plugins/Manga.hpp | 2 +- plugins/Page.hpp | 2 +- src/Body.cpp | 10 +- src/DownloadUtils.cpp | 3 +- src/FileAnalyzer.cpp | 2 +- src/Notification.cpp | 2 +- src/Program.c | 203 ---------------------------------------- src/Program.cpp | 246 +++++++++++++++++++++++++++++++++++++++++++++++++ src/QuickMedia.cpp | 222 ++++++++++++++++++++++++++------------------ src/VideoPlayer.cpp | 2 +- src/plugins/Manga.cpp | 5 +- src/plugins/Matrix.cpp | 11 ++- src/plugins/NyaaSi.cpp | 2 +- 21 files changed, 461 insertions(+), 364 deletions(-) create mode 100644 include/AsyncTask.hpp delete mode 100644 include/Program.h create mode 100644 include/Program.hpp delete mode 100644 src/Program.c create mode 100644 src/Program.cpp diff --git a/README.md b/README.md index 1ee7f5e..53a2335 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Press `M` to begin writing a message in a matrix room, press `ESC` to cancel.\ Press `R` to reply to a message on matrix, press `ESC` to cancel.\ Press `E` to edit a message on matrix, press `ESC` to cancel. Currently only works for your own messages.\ Press `D` to delete a message on matrix. Currently deleting a message only deletes the event, so if you delete an edit then the original message wont be deleted.\ +Press `Ctrl + C` to copy the message of the selected item in matrix to the clipboard.\ Press `Ctrl + V` to upload media to room in matrix if the clipboard contains a valid absolute filepath. In matrix you can select a message with enter to open the url in the message (or if there are multiple urls then a menu will appear for selecting which to open). diff --git a/TODO b/TODO index d87feb1..b7c43d2 100644 --- a/TODO +++ b/TODO @@ -55,7 +55,7 @@ Scroll to bottom when receiving a new message even if the selected message is no Also add a tab for common directories and recently accessed files/directories (the directories would be the directory of used files). Provide a way to go to the first unread message in matrix and also show a marker in the body (maybe a red line?) where the first unread message is. Allow scrolling body item. A body item can be long and we wont be able to read all of it otherwise (such as a message on matrix). Pressing up/down should scroll such a large body item rather than moving to another one. -Cleanup keybindings. Some require ctrl, some dont (4chan vs matrix for example). +Cleanup keybindings. Some require ctrl, some dont. Add room topic beside room name in matrix (in messages tab). Move rooms in matrix to previous page instead, then messages can be beside users, pinned messages, settings, etc and they would all be connected to that one room, then beside rooms tab there would be a global settings tab. Add /me to matrix, emoji, reactions... @@ -123,4 +123,7 @@ Replying to edited message shows incorrect body in matrix. Show in room tags list when there is a message in any of the rooms in the tag. Apply current search filter when adding new rooms to the room list. Cancel video download when pressing escape or closing window (important in matrix). -Support webp. \ No newline at end of file +Support webp. +Reload history/recommendations after closing a video. +Show images while they download by showing them as scanlines starting from the top. Needed for slow websites such as 4chan. +Use curl parallel download instead of downloading with multiple threads. \ No newline at end of file diff --git a/images/matrix_logo.png b/images/matrix_logo.png index 055a9c7..29a2ee3 100644 Binary files a/images/matrix_logo.png and b/images/matrix_logo.png differ diff --git a/include/AsyncTask.hpp b/include/AsyncTask.hpp new file mode 100644 index 0000000..81be1ee --- /dev/null +++ b/include/AsyncTask.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +namespace QuickMedia { + +} \ No newline at end of file diff --git a/include/Body.hpp b/include/Body.hpp index 9cdcd7b..1dc1fe8 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -147,14 +147,13 @@ namespace QuickMedia { // Select next item, ignoring invisible items. Returns true if the item was changed. This can be used to check if the bottom was hit when wrap_around is set to false bool select_next_item(); - void set_selected_item(int item); + void set_selected_item(int item, bool reset_prev_selected_item = true); // Returns -1 if item can't be found int get_index_by_body_item(BodyItem *body_item); void select_first_item(); void select_last_item(); - void reset_selected(); void clear_items(); void prepend_items(BodyItems new_items); void append_items(BodyItems new_items); diff --git a/include/Program.h b/include/Program.h deleted file mode 100644 index cab8d26..0000000 --- a/include/Program.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef QUICKMEDIA_PROGRAM_H -#define QUICKMEDIA_PROGRAM_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - pid_t pid; - int read_fd; -} ReadProgram; - -/* Return 0 if you want to continue reading. @data is null-terminated */ -typedef int (*ProgramOutputCallback)(char *data, int size, void *userdata); - -/* - @args need to have at least 2 arguments. The first which is the program name - and the last which is NULL, which indicates end of args -*/ -int exec_program_pipe(const char **args, ReadProgram *read_program); - -/* - @args need to have at least 2 arguments. The first which is the program name - and the last which is NULL, which indicates end of args -*/ -int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata); - -// Return the exit status, or a negative value if waiting failed -int wait_program(pid_t process_id); - -/* Returns 1 if the program quit and exited properly (non-0 exit codes also count as exiting properly) */ -int wait_program_non_blocking(pid_t process_id, int *status); - -/* - @args need to have at least 2 arguments. The first which is the program name - and the last which is NULL, which indicates end of args. - @result_process_id should be set to NULL if you are not interested in the exit status of the child process - and you want the child process to be cleaned up automatically when it dies. -*/ -int exec_program_async(const char **args, pid_t *result_process_id); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/include/Program.hpp b/include/Program.hpp new file mode 100644 index 0000000..8ac2d2d --- /dev/null +++ b/include/Program.hpp @@ -0,0 +1,44 @@ +#ifndef QUICKMEDIA_PROGRAM_HPP +#define QUICKMEDIA_PROGRAM_HPP + +#include +#include + +typedef struct { + pid_t pid; + int read_fd; +} ReadProgram; + +/* Return 0 if you want to continue reading. @data is null-terminated */ +typedef int (*ProgramOutputCallback)(char *data, int size, void *userdata); + +/* + @args need to have at least 2 arguments. The first which is the program name + and the last which is NULL, which indicates end of args +*/ +int exec_program_pipe(const char **args, ReadProgram *read_program); + +/* + @args need to have at least 2 arguments. The first which is the program name + and the last which is NULL, which indicates end of args +*/ +int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata); + +// Return the exit status, or a negative value if waiting failed +int wait_program(pid_t process_id); + +/* Returns 1 if the program quit and exited properly (non-0 exit codes also count as exiting properly) */ +int wait_program_non_blocking(pid_t process_id, int *status); + +/* + @args need to have at least 2 arguments. The first which is the program name + and the last which is NULL, which indicates end of args. + @result_process_id should be set to NULL if you are not interested in the exit status of the child process + and you want the child process to be cleaned up automatically when it dies. +*/ +int exec_program_async(const char **args, pid_t *result_process_id); + +void program_clear_current_thread(); +void program_kill_in_thread(const std::thread::id &thread_id); + +#endif /* QUICKMEDIA_PROGRAM_HPP */ diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 99bab7a..6f8e3ff 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -63,6 +63,7 @@ namespace QuickMedia { void image_board_thread_page(ImageBoardThreadPage *thread_page, Body *thread_body); void chat_login_page(); void chat_page(MatrixChatPage *chat_page, RoomData *current_room); + void after_matrix_login_page(); enum class LoadImageResult { OK, diff --git a/plugins/Manga.hpp b/plugins/Manga.hpp index d3725da..afd8a3c 100644 --- a/plugins/Manga.hpp +++ b/plugins/Manga.hpp @@ -53,7 +53,7 @@ namespace QuickMedia { public: MangaChaptersPage(Program *program, std::string manga_name, std::string manga_url) : TrackablePage(program, std::move(manga_name), std::move(manga_url)) {} TrackResult track(const std::string &str) override; - void on_navigate_to_page() override; + void on_navigate_to_page(BodyItems &body_items) override; protected: virtual bool extract_id_from_url(const std::string &url, std::string &manga_id) const = 0; virtual const char* get_service_name() const = 0; diff --git a/plugins/Page.hpp b/plugins/Page.hpp index cc7dad6..db844c5 100644 --- a/plugins/Page.hpp +++ b/plugins/Page.hpp @@ -45,7 +45,7 @@ namespace QuickMedia { virtual bool is_lazy_fetch_page() const { return false; } // This is called both when first navigating to page and when going back to page - virtual void on_navigate_to_page() {}; + virtual void on_navigate_to_page(BodyItems &body_items) { (void)body_items; } // Called periodically (every frame right now) if this page is the currently active one virtual void update() {} diff --git a/src/Body.cpp b/src/Body.cpp index 7f96263..3f5c755 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -186,10 +186,11 @@ namespace QuickMedia { return true; } - void Body::set_selected_item(int item) { + void Body::set_selected_item(int item, bool reset_prev_selected_item) { //assert(item >= 0 && item < (int)items.size()); selected_item = item; - prev_selected_item = selected_item; + if(reset_prev_selected_item) + prev_selected_item = selected_item; clamp_selection(); //page_scroll = 0.0f; } @@ -216,10 +217,6 @@ namespace QuickMedia { clamp_selection(); } - void Body::reset_selected() { - select_first_item(); - } - void Body::clear_items() { items.clear(); selected_item = 0; @@ -235,6 +232,7 @@ namespace QuickMedia { items.insert(items.end(), std::make_move_iterator(new_items.begin()), std::make_move_iterator(new_items.end())); } + // TODO: Binary search and use hint to start search from start or end (for example when adding "previous" items or "next" items) void Body::insert_item_by_timestamp(std::shared_ptr body_item) { for(size_t i = 0; i < items.size(); ++i) { if(body_item->get_timestamp() < items[i]->get_timestamp()) { diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp index fd7e7d1..cbdded6 100644 --- a/src/DownloadUtils.cpp +++ b/src/DownloadUtils.cpp @@ -1,5 +1,5 @@ #include "../include/DownloadUtils.hpp" -#include "../include/Program.h" +#include "../include/Program.hpp" #include "../include/Storage.hpp" #include "../include/base64_url.hpp" #include @@ -141,6 +141,7 @@ namespace QuickMedia { char read_buffer[8192]; rapidjson::FileReadStream is(file, read_buffer, sizeof(read_buffer)); rapidjson::ParseResult parse_result = result.ParseStream(is); + program_clear_current_thread(); fclose(file); wait_program(read_program.pid); diff --git a/src/FileAnalyzer.cpp b/src/FileAnalyzer.cpp index 82d2c06..8d5eae8 100644 --- a/src/FileAnalyzer.cpp +++ b/src/FileAnalyzer.cpp @@ -1,5 +1,5 @@ #include "../include/FileAnalyzer.hpp" -#include "../include/Program.h" +#include "../include/Program.hpp" #include #include #include diff --git a/src/Notification.cpp b/src/Notification.cpp index 1201557..9ffbaac 100644 --- a/src/Notification.cpp +++ b/src/Notification.cpp @@ -1,5 +1,5 @@ #include "../include/Notification.hpp" -#include "../include/Program.h" +#include "../include/Program.hpp" #include #include diff --git a/src/Program.c b/src/Program.c deleted file mode 100644 index a82bcd2..0000000 --- a/src/Program.c +++ /dev/null @@ -1,203 +0,0 @@ -#include "../include/Program.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#define READ_END 0 -#define WRITE_END 1 - -int exec_program_pipe(const char **args, ReadProgram *read_program) { - /* 1 arguments */ - if(args[0] == NULL) - return -1; - - int fd[2]; - if(pipe(fd) == -1) { - perror("Failed to open pipe"); - return -2; - } - - pid_t parent_pid = getpid(); - - pid_t pid = fork(); - if(pid == -1) { - perror("Failed to fork"); - close(fd[READ_END]); - close(fd[WRITE_END]); - return -3; - } else if(pid == 0) { /* child */ - if(prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) { - perror("prctl(PR_SET_PDEATHSIG, SIGTERM) failed"); - _exit(127); - } - - /* Test if the parent died before the above call to prctl */ - if(getppid() != parent_pid) - _exit(127); - - dup2(fd[WRITE_END], STDOUT_FILENO); - close(fd[READ_END]); - close(fd[WRITE_END]); - - execvp(args[0], (char* const*)args); - perror("execvp"); - _exit(127); - } else { /* parent */ - close(fd[WRITE_END]); - read_program->pid = pid; - read_program->read_fd = fd[READ_END]; - return 0; - } -} - -int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata) { - ReadProgram read_program; - int res = exec_program_pipe(args, &read_program); - if(res != 0) - return res; - - int result = 0; - int status; - - char buffer[4097]; - - for(;;) { - ssize_t bytes_read = read(read_program.read_fd, buffer, sizeof(buffer) - 1); - if(bytes_read == 0) { - break; - } else if(bytes_read == -1) { - int err = errno; - fprintf(stderr, "Failed to read from pipe to program %s, error: %s\n", args[0], strerror(err)); - result = -err; - break; - } - - buffer[bytes_read] = '\0'; - if(output_callback) { - result = output_callback(buffer, bytes_read, userdata); - if(result != 0) - break; - } - } - - if(result != 0) - kill(read_program.pid, SIGTERM); - - if(waitpid(read_program.pid, &status, 0) == -1) { - perror("waitpid failed"); - result = -5; - goto cleanup; - } - - if(!WIFEXITED(status)) { - result = -4; - goto cleanup; - } - - int exit_status = WEXITSTATUS(status); - if(exit_status != 0) { - fprintf(stderr, "Failed to execute program ("); - const char **arg = args; - while(*arg) { - if(arg != args) - fputc(' ', stderr); - fprintf(stderr, "'%s'", *arg); - ++arg; - } - fprintf(stderr, "), exit status %d\n", exit_status); - result = -exit_status; - } - - cleanup: - close(read_program.read_fd); - return result; -} - -int wait_program(pid_t process_id) { - int status; - if(waitpid(process_id, &status, 0) == -1) { - perror("waitpid failed"); - return -errno; - } - - if(!WIFEXITED(status)) - return -4; - - return WEXITSTATUS(status); -} - -int wait_program_non_blocking(pid_t process_id, int *status) { - int s; - int wait_result = waitpid(process_id, &s, WNOHANG); - if(wait_result == -1) { - perror("waitpid failed"); - *status = -errno; - return 0; - } else if(wait_result == 0) { - /* the child process is still running */ - *status = 0; - return 0; - } - - if(!WIFEXITED(s)) { - *status = -4; - return 0; - } - - *status = WEXITSTATUS(s); - return 1; -} - -int exec_program_async(const char **args, pid_t *result_process_id) { - /* 1 arguments */ - if(args[0] == NULL) - return -1; - - pid_t parent_pid = getpid(); - - pid_t pid = fork(); - if(pid == -1) { - int err = errno; - perror("Failed to fork"); - return -err; - } else if(pid == 0) { /* child */ - if(result_process_id) { - if(prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) { - perror("prctl(PR_SET_PDEATHSIG, SIGTERM) failed"); - _exit(127); - } - - /* Test if the parent died before the above call to prctl */ - if(getppid() != parent_pid) - _exit(127); - - execvp(args[0], (char* const*)args); - perror("execvp"); - _exit(127); - } else { - setsid(); - signal(SIGHUP, SIG_IGN); - - // TODO: Still creates zombie??? find a fix! - - // Daemonize child to make the parent the init process which will reap the zombie child - pid_t second_child = fork(); - if(second_child == 0) { // child - execvp(args[0], (char* const*)args); - perror("execvp"); - _exit(127); - } else if(second_child != -1) { - _exit(0); - } - } - } else { /* parent */ - if(result_process_id) - *result_process_id = pid; - } - return 0; -} diff --git a/src/Program.cpp b/src/Program.cpp new file mode 100644 index 0000000..136a494 --- /dev/null +++ b/src/Program.cpp @@ -0,0 +1,246 @@ +#include "../include/Program.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define READ_END 0 +#define WRITE_END 1 + +class CurrentThreadProgram { +public: + void set(ReadProgram read_program) { + std::lock_guard lock(mutex); + thread_current_program[std::this_thread::get_id()] = read_program; + } + + void clear() { + std::lock_guard lock(mutex); + thread_current_program.erase(std::this_thread::get_id()); + } + + void kill_in_thread(const std::thread::id &thread_id) { + std::lock_guard lock(mutex); + auto it = thread_current_program.find(thread_id); + if(it != thread_current_program.end()) { + close(it->second.read_fd); + kill(it->second.pid, SIGTERM); + } + } +private: + std::unordered_map thread_current_program; + std::mutex mutex; +}; + +static CurrentThreadProgram current_thread_program; + +int exec_program_pipe(const char **args, ReadProgram *read_program) { + /* 1 arguments */ + if(args[0] == NULL) + return -1; + + int fd[2]; + if(pipe(fd) == -1) { + perror("Failed to open pipe"); + return -2; + } + + pid_t parent_pid = getpid(); + + pid_t pid = fork(); + if(pid == -1) { + perror("Failed to fork"); + close(fd[READ_END]); + close(fd[WRITE_END]); + return -3; + } else if(pid == 0) { /* child */ + if(prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) { + perror("prctl(PR_SET_PDEATHSIG, SIGTERM) failed"); + _exit(127); + } + + /* Test if the parent died before the above call to prctl */ + if(getppid() != parent_pid) + _exit(127); + + dup2(fd[WRITE_END], STDOUT_FILENO); + close(fd[READ_END]); + close(fd[WRITE_END]); + + execvp(args[0], (char* const*)args); + perror("execvp"); + _exit(127); + } else { /* parent */ + close(fd[WRITE_END]); + read_program->pid = pid; + read_program->read_fd = fd[READ_END]; + current_thread_program.set(*read_program); + return 0; + } +} + +int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata) { + ReadProgram read_program; + int res = exec_program_pipe(args, &read_program); + if(res != 0) + return res; + + int result = 0; + int status; + int exit_status; + + char buffer[4097]; + + for(;;) { + ssize_t bytes_read = read(read_program.read_fd, buffer, sizeof(buffer) - 1); + if(bytes_read == 0) { + break; + } else if(bytes_read == -1) { + int err = errno; + fprintf(stderr, "Failed to read from pipe to program %s, error: %s\n", args[0], strerror(err)); + result = -err; + break; + } + + //check if running. Also do the same in download_to_json + + buffer[bytes_read] = '\0'; + if(output_callback) { + result = output_callback(buffer, bytes_read, userdata); + if(result != 0) + break; + } + } + program_clear_current_thread(); + + if(result != 0) + kill(read_program.pid, SIGTERM); + + if(waitpid(read_program.pid, &status, 0) == -1) { + perror("waitpid failed"); + result = -5; + goto cleanup; + } + + if(!WIFEXITED(status)) { + result = -4; + goto cleanup; + } + + exit_status = WEXITSTATUS(status); + if(exit_status != 0) { + fprintf(stderr, "Failed to execute program ("); + const char **arg = args; + while(*arg) { + if(arg != args) + fputc(' ', stderr); + fprintf(stderr, "'%s'", *arg); + ++arg; + } + fprintf(stderr, "), exit status %d\n", exit_status); + result = -exit_status; + } + + cleanup: + close(read_program.read_fd); + return result; +} + +int wait_program(pid_t process_id) { + int status; + if(waitpid(process_id, &status, 0) == -1) { + perror("waitpid failed"); + return -errno; + } + + if(!WIFEXITED(status)) + return -4; + + return WEXITSTATUS(status); +} + +int wait_program_non_blocking(pid_t process_id, int *status) { + int s; + int wait_result = waitpid(process_id, &s, WNOHANG); + if(wait_result == -1) { + perror("waitpid failed"); + *status = -errno; + return 0; + } else if(wait_result == 0) { + /* the child process is still running */ + *status = 0; + return 0; + } + + if(!WIFEXITED(s)) { + *status = -4; + return 0; + } + + *status = WEXITSTATUS(s); + return 1; +} + +int exec_program_async(const char **args, pid_t *result_process_id) { + /* 1 arguments */ + if(args[0] == NULL) + return -1; + + pid_t parent_pid = getpid(); + + pid_t pid = fork(); + if(pid == -1) { + int err = errno; + perror("Failed to fork"); + return -err; + } else if(pid == 0) { /* child */ + if(result_process_id) { + if(prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) { + perror("prctl(PR_SET_PDEATHSIG, SIGTERM) failed"); + _exit(127); + } + + /* Test if the parent died before the above call to prctl */ + if(getppid() != parent_pid) + _exit(127); + + execvp(args[0], (char* const*)args); + perror("execvp"); + _exit(127); + } else { + setsid(); + signal(SIGHUP, SIG_IGN); + + // TODO: Still creates zombie??? find a fix! + + // Daemonize child to make the parent the init process which will reap the zombie child + pid_t second_child = fork(); + if(second_child == 0) { // child + execvp(args[0], (char* const*)args); + perror("execvp"); + _exit(127); + } else if(second_child != -1) { + _exit(0); + } + } + } else { /* parent */ + if(result_process_id) + *result_process_id = pid; + } + return 0; +} + +void program_clear_current_thread() { + current_thread_program.clear(); +} + +void program_kill_in_thread(const std::thread::id &thread_id) { + current_thread_program.kill_in_thread(thread_id); +} diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index ba9a680..3b99d55 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -9,7 +9,7 @@ #include "../plugins/Matrix.hpp" #include "../plugins/FileManager.hpp" #include "../include/Scale.hpp" -#include "../include/Program.h" +#include "../include/Program.hpp" #include "../include/VideoPlayer.hpp" #include "../include/StringUtils.hpp" #include "../include/GoogleCaptcha.hpp" @@ -641,51 +641,7 @@ namespace QuickMedia { chat_login_page(); } - if(!window.isOpen()) - return exit_code; - - auto rooms_body = create_body(); - rooms_body->thumbnail_mask_shader = &circle_mask_shader; - auto matrix_rooms_page = std::make_unique(this, rooms_body.get(), "All rooms"); - - auto rooms_tags_body = create_body(); - rooms_tags_body->thumbnail_mask_shader = &circle_mask_shader; - auto matrix_rooms_tag_page = std::make_unique(this, rooms_tags_body.get()); - - MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get()); - matrix->start_sync(&matrix_handler); - - tabs.push_back(Tab{std::move(rooms_body), std::move(matrix_rooms_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - tabs.push_back(Tab{std::move(rooms_tags_body), std::move(matrix_rooms_tag_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - - sf::Sprite load_sprite(loading_icon); - sf::Vector2u loading_icon_size = loading_icon.getSize(); - load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f); - - sf::Clock timer; - sf::Event event; - while(window.isOpen() && !matrix->is_initial_sync_finished()) { - while(window.pollEvent(event)) { - if(event.type == sf::Event::Closed) - window.close(); - else if(event.type == sf::Event::Resized) { - window_size.x = event.size.width; - window_size.y = event.size.height; - sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); - window.setView(sf::View(visible_area)); - } - } - window.clear(back_color); - load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f); - load_sprite.setRotation(timer.getElapsedTime().asSeconds() * 400.0); - window.draw(load_sprite); - window.display(); - } - - while(window.isOpen()) { - page_loop(tabs); - } - + after_matrix_login_page(); exit(exit_code); // Exit immediately without waiting for anything to finish //matrix->stop_sync(); } @@ -975,6 +931,16 @@ namespace QuickMedia { return current_chat_room; } + static void select_body_item_by_room(Body *body, RoomData *room) { + for(size_t i = 0; i < body->items.size(); ++i) { + auto &body_item = body->items[i]; + if(body_item->userdata == room) { + body->set_selected_item(i, false); + return; + } + } + } + void Program::page_loop(std::vector &tabs) { if(tabs.empty()) { show_notification("QuickMedia", "No tabs provided!", Urgency::CRITICAL); @@ -983,7 +949,7 @@ namespace QuickMedia { for(Tab &tab : tabs) { tab.body->thumbnail_max_size = tab.page->get_thumbnail_max_size(); - tab.page->on_navigate_to_page(); + tab.page->on_navigate_to_page(tab.body->items); } const Json::Value *json_chapters = &Json::Value::nullSingleton(); @@ -1106,11 +1072,12 @@ namespace QuickMedia { current_page = PageType::CHAT; current_chat_room = matrix->get_room_by_id(selected_item->url); chat_page(static_cast(new_tabs[0].page.get()), current_chat_room); + select_body_item_by_room(tabs[selected_tab].body.get(), current_chat_room); current_chat_room = nullptr; } else { page_loop(new_tabs); } - tabs[selected_tab].page->on_navigate_to_page(); + tabs[selected_tab].page->on_navigate_to_page(tabs[selected_tab].body->items); if(content_storage_json.isObject()) { const Json::Value &chapters_json = content_storage_json["chapters"]; if(chapters_json.isObject()) @@ -1614,9 +1581,11 @@ namespace QuickMedia { video_url = std::move(video_path.data); video_url_is_local = true; } else { - std::future download_future = std::async(std::launch::async, [this, &video_path, video_url]() { - return download_to_file(video_url, video_path.data, {}, use_tor, true); - }); + std::promise download_result_promise; + std::future download_future = download_result_promise.get_future(); + std::thread download_thread([this, &video_path, video_url](std::promise &&promise) { + promise.set_value(download_to_file(video_url, video_path.data, {}, use_tor, true)); + }, std::move(download_result_promise)); window_size.x = window.getSize().x; window_size.y = window.getSize().y; @@ -1625,9 +1594,8 @@ namespace QuickMedia { while(window.isOpen()) { while(window.pollEvent(event)) { if(event.type == sf::Event::Closed) { - // TODO: Remove this - if(download_future.valid()) - download_future.get(); + program_kill_in_thread(download_thread.get_id()); + download_thread.join(); current_page = previous_page; window.close(); return; @@ -1637,16 +1605,17 @@ namespace QuickMedia { sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); window.setView(sf::View(visible_area)); } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) { - // TODO: Remove this - if(download_future.valid()) - download_future.get(); + program_kill_in_thread(download_thread.get_id()); + download_thread.join(); current_page = previous_page; return; } } if(is_future_ready(download_future)) { - if(download_future.get() != DownloadResult::OK) { + DownloadResult download_result = download_future.get(); + download_thread.join(); + if(download_result != DownloadResult::OK) { show_notification("QuickMedia", "Failed to download " + video_url, Urgency::CRITICAL); current_page = previous_page; return; @@ -3307,7 +3276,9 @@ namespace QuickMedia { chat_state = ChatState::NAVIGATING; return true; } else if(text == "/logout") { - show_notification("QuickMedia", "/logout command is temporary disabled. Delete " + get_storage_dir().join("matrix").join("session.json").data + " and restart QuickMedia to logout", Urgency::CRITICAL); + new_page = PageType::CHAT_LOGIN; + chat_input.set_editable(false); + chat_state = ChatState::NAVIGATING; return true; } else if(strncmp(text.c_str(), "/me ", 4) == 0) { msgtype = "m.emote"; @@ -3460,14 +3431,20 @@ namespace QuickMedia { float tab_vertical_offset = 0.0f; - auto typing_async_func = [this](bool new_state, RoomData *room) { - if(new_state) { - matrix->on_start_typing(room); - } else { - matrix->on_stop_typing(room); + MessageQueue typing_state_queue; + std::thread typing_state_thread([this, ¤t_room, &typing_state_queue]() { + while(true) { + std::optional state_opt = typing_state_queue.pop_wait(); + if(!state_opt) + break; + + bool state = state_opt.value(); + if(state) + matrix->on_start_typing(current_room); + else + matrix->on_stop_typing(current_room); } - }; - std::vector> typing_futures; + }); sf::Clock frame_timer; @@ -3638,7 +3615,7 @@ namespace QuickMedia { if(typing && current_room) { fprintf(stderr, "Stopped typing\n"); typing = false; - typing_futures.push_back(std::async(typing_async_func, false, current_room)); + typing_state_queue.push(false); } } else if((event.key.code == sf::Keyboard::Right) && selected_tab < (int)tabs.size() - 1) { tabs[selected_tab].body->clear_cache(); @@ -3648,7 +3625,7 @@ namespace QuickMedia { if(typing && current_room) { fprintf(stderr, "Stopped typing\n"); typing = false; - typing_futures.push_back(std::async(typing_async_func, false, current_room)); + typing_state_queue.push(false); } } else if(event.key.code == sf::Keyboard::Escape) { goto chat_page_end; @@ -3662,6 +3639,14 @@ namespace QuickMedia { } } + if(current_room) { + if(event.key.control && event.key.code == sf::Keyboard::C) { + BodyItem *selected = tabs[selected_tab].body->get_selected(); + if(selected) + sf::Clipboard::setString(selected->get_description()); + } + } + if(selected_tab == MESSAGES_TAB_INDEX && current_room) { if(event.key.code == sf::Keyboard::U) { frame_skip_text_entry = true; @@ -3783,7 +3768,7 @@ namespace QuickMedia { start_typing_timer.restart(); if(!typing && current_room) { fprintf(stderr, "Started typing\n"); - typing_futures.push_back(std::async(typing_async_func, true, current_room)); + typing_state_queue.push(true); } typing = true; } @@ -3795,7 +3780,7 @@ namespace QuickMedia { if(typing && current_room) { fprintf(stderr, "Stopped typing\n"); typing = false; - typing_futures.push_back(std::async(typing_async_func, false, current_room)); + typing_state_queue.push(false); } } //chat_input.on_event(event); @@ -3840,7 +3825,30 @@ namespace QuickMedia { break; } case PageType::CHAT_LOGIN: { - abort(); + // TODO: Cancel these instead + if(set_read_marker_future.valid()) + set_read_marker_future.get(); + if(previous_messages_future.valid()) + previous_messages_future.get(); + if(fetch_message_future.valid()) + fetch_message_future.get(); + typing_state_queue.close(); + if(typing_state_thread.joinable()) + typing_state_thread.join(); + new_page = PageType::CHAT; + matrix->stop_sync(); + matrix->logout(); + tabs[MESSAGES_TAB_INDEX].body->clear_cache(); + // TODO: Instead of doing this, exit this current function and navigate to chat login page instead. + // This doesn't currently work because at the end of this function there are futures that need to wait + // and one of them is /sync, which has a timeout of 30 seconds. That timeout has to be killed somehow. + //delete current_plugin; + //current_plugin = new Matrix(); + current_page = PageType::CHAT_LOGIN; + chat_login_page(); + if(current_page == PageType::CHAT) + after_matrix_login_page(); + exit(0); break; } default: @@ -3850,22 +3858,7 @@ namespace QuickMedia { if(typing && start_typing_timer.getElapsedTime().asSeconds() >= typing_timeout_seconds && current_room) { fprintf(stderr, "Stopped typing\n"); typing = false; - typing_futures.push_back(std::async(typing_async_func, false, current_room)); - } - - for(auto it = typing_futures.begin(); it != typing_futures.end(); ) { - if(!it->valid()) { - it = typing_futures.erase(it); - continue; - } - - if(it->wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - it->get(); - it = typing_futures.erase(it); - continue; - } - - ++it; + typing_state_queue.push(false); } if(current_room && current_room->userdata && room_avatar_thumbnail_data->loading_state == LoadingState::NOT_LOADED) @@ -4146,13 +4139,60 @@ namespace QuickMedia { previous_messages_future.get(); if(fetch_message_future.valid()) fetch_message_future.get(); - for(auto &typing_future : typing_futures) { - if(typing_future.valid()) - typing_future.get(); - } + typing_state_queue.close(); + if(typing_state_thread.joinable()) + typing_state_thread.join(); for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) { delete (PinnedEventData*)body_item->userdata; } } + + void Program::after_matrix_login_page() { + if(!window.isOpen()) + exit(exit_code); + + auto rooms_body = create_body(); + rooms_body->thumbnail_mask_shader = &circle_mask_shader; + auto matrix_rooms_page = std::make_unique(this, rooms_body.get(), "All rooms"); + + auto rooms_tags_body = create_body(); + rooms_tags_body->thumbnail_mask_shader = &circle_mask_shader; + auto matrix_rooms_tag_page = std::make_unique(this, rooms_tags_body.get()); + + MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get()); + matrix->start_sync(&matrix_handler); + + std::vector tabs; + tabs.push_back(Tab{std::move(rooms_body), std::move(matrix_rooms_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{std::move(rooms_tags_body), std::move(matrix_rooms_tag_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + sf::Sprite load_sprite(loading_icon); + sf::Vector2u loading_icon_size = loading_icon.getSize(); + load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f); + + sf::Clock timer; + sf::Event event; + while(window.isOpen() && !matrix->is_initial_sync_finished()) { + while(window.pollEvent(event)) { + if(event.type == sf::Event::Closed) + window.close(); + else if(event.type == sf::Event::Resized) { + window_size.x = event.size.width; + window_size.y = event.size.height; + sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); + window.setView(sf::View(visible_area)); + } + } + window.clear(back_color); + load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f); + load_sprite.setRotation(timer.getElapsedTime().asSeconds() * 400.0); + window.draw(load_sprite); + window.display(); + } + + while(window.isOpen()) { + page_loop(tabs); + } + } } diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index cbb9634..c7d2697 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -1,6 +1,6 @@ #include "../include/VideoPlayer.hpp" #include "../include/Storage.hpp" -#include "../include/Program.h" +#include "../include/Program.hpp" #include #include #include diff --git a/src/plugins/Manga.cpp b/src/plugins/Manga.cpp index 323794c..4685cec 100644 --- a/src/plugins/Manga.cpp +++ b/src/plugins/Manga.cpp @@ -1,5 +1,5 @@ #include "../../plugins/Manga.hpp" -#include "../../include/Program.h" +#include "../../include/Program.hpp" namespace QuickMedia { TrackResult MangaChaptersPage::track(const std::string &str) { @@ -10,7 +10,8 @@ namespace QuickMedia { return TrackResult::ERR; } - void MangaChaptersPage::on_navigate_to_page() { + void MangaChaptersPage::on_navigate_to_page(BodyItems &body_items) { + (void)body_items; std::string manga_id; if(extract_id_from_url(content_url, manga_id)) load_manga_content_storage(get_service_name(), content_title, manga_id); diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index a702841..93eb1d6 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -3,6 +3,7 @@ #include "../../include/StringUtils.hpp" #include "../../include/NetUtils.hpp" #include "../../include/Notification.hpp" +#include "../../include/Program.hpp" #include #include #include @@ -521,8 +522,8 @@ namespace QuickMedia { } void Matrix::stop_sync() { - // TODO: Kill the running download in |sync_thread| instead of waiting until sync returns (which can be up to 30 seconds) sync_running = false; + program_kill_in_thread(sync_thread.get_id()); if(sync_thread.joinable()) sync_thread.join(); } @@ -534,8 +535,12 @@ namespace QuickMedia { void Matrix::get_room_sync_data(RoomData *room, SyncData &sync_data) { room->acquire_room_lock(); auto &room_messages = room->get_messages_thread_unsafe(); - sync_data.messages.insert(sync_data.messages.end(), room_messages.begin() + room->messages_read_index, room_messages.end()); - room->messages_read_index = room_messages.size(); + if(room->messages_read_index <= room_messages.size()) { + sync_data.messages.insert(sync_data.messages.end(), room_messages.begin() + room->messages_read_index, room_messages.end()); + room->messages_read_index = room_messages.size(); + } else { + fprintf(stderr, "Unexpected behavior!!!! get_room_sync_data said read index is %zu but we only have %zu messages\n", room->messages_read_index, room_messages.size()); + } if(room->pinned_events_updated) { sync_data.pinned_events = room->get_pinned_events_unsafe(); room->pinned_events_updated = false; diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp index 8b1efc7..082aaa8 100644 --- a/src/plugins/NyaaSi.cpp +++ b/src/plugins/NyaaSi.cpp @@ -1,5 +1,5 @@ #include "../../plugins/NyaaSi.hpp" -#include "../../include/Program.h" +#include "../../include/Program.hpp" #include "../../include/Storage.hpp" #include "../../include/Notification.hpp" #include "../../include/StringUtils.hpp" -- cgit v1.2.3