From 17f74130a93d5b5eb9fb33e453670e7247494104 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 9 Dec 2022 23:43:12 +0100 Subject: Matrix: continue from cache instead of initial sync everytime. Mainly improves pantalaimon sync speed --- src/QuickMedia.cpp | 80 ++++--------- src/plugins/Matrix.cpp | 223 ++++++++++-------------------------- src/plugins/utils/UniqueProcess.cpp | 113 ++++++++++++++++++ 3 files changed, 195 insertions(+), 221 deletions(-) create mode 100644 src/plugins/utils/UniqueProcess.cpp (limited to 'src') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 2c40580..0e9f152 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -39,6 +39,7 @@ #include "../include/Downloader.hpp" #include "../include/Storage.hpp" #include "../include/AsyncImageLoader.hpp" +#include "../plugins/utils/UniqueProcess.hpp" #include #include "../include/gui/Button.hpp" #include "../external/hash-library/sha256.h" @@ -407,7 +408,7 @@ namespace QuickMedia { video_max_height = 0; std::vector tabs; const char *url = nullptr; - std::string program_path = dirname(argv[0]); + std::string program_path = Path(argv[0]).parent().data; std::string instance; std::string download_filename; bool no_dialog = false; @@ -633,7 +634,7 @@ namespace QuickMedia { chat_login_page(); } after_matrix_login_page(); - return exit_code; + goto done; } page_loop(tabs, start_tab_index); @@ -650,6 +651,10 @@ namespace QuickMedia { } } + done: + if(plugin_name && strcmp(plugin_name, "matrix") == 0) + remove_quickmedia_instance_lock(get_cache_dir().join("matrix").data.c_str(), "matrix"); + return exit_code; } @@ -1327,7 +1332,7 @@ namespace QuickMedia { page_stack.push(current_page); current_page = PageType::IMAGE_BOARD_THREAD; image_board_thread_page(thread_page.get(), body.get()); - exit(0); + exit(exit_code); } else { auto boards_page = std::make_unique(this, resources_root); FourchanBoardsPage *boards_page_ptr = boards_page.get(); @@ -1424,10 +1429,22 @@ namespace QuickMedia { tabs.push_back(Tab{create_body(false, true), std::make_unique(this), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "matrix") == 0) { assert(!matrix); + if(create_directory_recursive(get_cache_dir().join("matrix").join("events")) != 0) { show_notification("QuickMedia", "Failed to create events cache directory", Urgency::CRITICAL); abort(); } + + if(is_quickmedia_instance_already_running(get_cache_dir().join("matrix").data.c_str(), "matrix")) { + show_notification("QuickMedia", "Only one instance of matrix can be run. Change $XDG_CACHE_HOME if you want to run multiple instances", Urgency::CRITICAL); + exit(exit_code); + } + + if(!set_quickmedia_instance_unique(get_cache_dir().join("matrix").data.c_str(), "matrix")) { + show_notification("QuickMedia", "Failed to set quickmedia process as unique", Urgency::CRITICAL); + exit(exit_code); + } + matrix = new Matrix(); } else { assert(false); @@ -7872,63 +7889,6 @@ namespace QuickMedia { tabs[selected_tab].body->on_bottom_reached(); } - if(matrix_chat_page->should_clear_data) { - matrix_chat_page->should_clear_data = false; - - std::string err_msg; - while(!matrix->is_initial_sync_finished()) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - if(matrix->did_initial_sync_fail(err_msg)) { - matrix_chat_page->set_current_room(nullptr, nullptr, nullptr); - fetch_messages_future.cancel(); - cleanup_tasks(); - tabs.clear(); - unreferenced_events.clear(); - unresolved_reactions.clear(); - all_messages.clear(); - show_notification("QuickMedia", "Initial matrix sync failed, error: " + err_msg, Urgency::CRITICAL); - matrix->logout(); - delete matrix; - matrix = new Matrix(); - current_page = PageType::CHAT_LOGIN; - chat_login_page(); - after_matrix_login_page(); - window.close(); - goto chat_page_end; - } - } - - //all_messages.clear(); - - tabs[MESSAGES_TAB_INDEX].body->clear_items(); - - Messages all_messages_new; - matrix->get_all_synced_room_messages(current_room, all_messages_new); - for(auto &message : all_messages_new) { - fetched_messages_set.insert(message->event_id); - } - all_messages.insert(all_messages.end(), all_messages_new.begin(), all_messages_new.end()); - //me = matrix->get_me(current_room); - filter_provisional_messages(all_messages_new); - add_new_messages_to_current_room(all_messages_new); - modify_related_messages_in_current_room(all_messages_new); - unresolved_reactions.clear(); - after_token.clear(); - before_token.clear(), - fetched_enough_messages_top = false; - fetched_enough_messages_bottom = false; - fetch_messages_future.cancel(); - process_reactions(all_messages_new); - if(current_room->initial_prev_messages_fetch) { - current_room->initial_prev_messages_fetch = false; - tabs[MESSAGES_TAB_INDEX].body->select_last_item(); - } - - std::vector pinned_events; - matrix->get_all_pinned_events(current_room, pinned_events); - process_pinned_events(std::move(pinned_events)); - } - if(go_to_previous_page) { go_to_previous_page = false; goto chat_page_end; diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 0d93bd0..11eca06 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "../../include/QuickMedia.hpp" #include @@ -38,7 +39,6 @@ namespace QuickMedia { // then we cant see room message preview. TODO: Fix this somehow. // TODO: What about state events in initial sync in timeline? such as user display name change. static const char* INITIAL_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"types\":[\"qm.emoji\",\"m.direct\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\",\"m.room.canonical_alias\",\"m.space.child\"],\"lazy_load_members\":true},\"timeline\":{\"types\":[\"m.room.message\"],\"limit\":1,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":1,\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; - static const char* ADDITIONAL_MESSAGES_FILTER = "{\"presence\":{\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\",\"m.room.canonical_alias\",\"m.space.child\"],\"lazy_load_members\":true},\"timeline\":{\"limit\":20,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true}}}"; static const char* CONTINUE_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"types\":[\"qm.emoji\",\"m.direct\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\",\"m.room.canonical_alias\",\"m.space.child\"],\"lazy_load_members\":true},\"timeline\":{\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; static bool is_gpg_installed = false; @@ -802,15 +802,6 @@ namespace QuickMedia { body->set_selected_item(found_item_index, false); } - void MatrixQuickMedia::clear_data() { - //room_body_item_by_room.clear(); - //pending_room_messages.clear(); - //rooms_page->clear_data(); - //room_tags_page->clear_data(); - invites_page->clear_data(); - //unread_notifications.clear(); - } - static std::shared_ptr get_last_message_by_timestamp(const Messages &messages) { if(messages.empty()) return nullptr; @@ -1038,12 +1029,6 @@ namespace QuickMedia { body->select_first_item(); } - void MatrixRoomsPage::clear_data() { - body->clear_items(); - if(current_chat_page) - current_chat_page->should_clear_data = true; - } - PluginResult MatrixRoomTagsPage::submit(const SubmitArgs &args, std::vector &result_tabs) { auto body = create_body(true); Body *body_ptr = body.get(); @@ -1131,13 +1116,6 @@ namespace QuickMedia { current_rooms_page = rooms_page; } - void MatrixRoomTagsPage::clear_data() { - tag_body_items_by_name.clear(); - body->clear_items(); - if(current_rooms_page) - current_rooms_page->clear_data(); - } - MatrixInvitesPage::MatrixInvitesPage(Program *program, Matrix *matrix, Body *body) : Page(program), matrix(matrix), body(body) { } @@ -1198,12 +1176,6 @@ namespace QuickMedia { } } - void MatrixInvitesPage::clear_data() { - body->clear_items(); - prev_invite_count = 0; - title = "Invites (0)"; - } - PluginResult MatrixSettingsPage::submit(const SubmitArgs &args, std::vector &result_tabs) { if(args.url == "join") { result_tabs.push_back(Tab{create_body(), std::make_unique(program, matrix), create_search_bar("Enter room id...", SEARCH_DELAY_FILTER)}); @@ -1677,44 +1649,6 @@ namespace QuickMedia { "M_MISSING_PARAM" }; - static void remove_empty_fields_in_sync_rooms_response(rapidjson::Value &rooms_json) { - for(const char *member_name : {"join", "invite", "leave"}) { - auto join_it = rooms_json.FindMember(member_name); - if(join_it != rooms_json.MemberEnd() && join_it->value.IsObject() && join_it->value.MemberCount() == 0) - rooms_json.RemoveMember(join_it); - } - } - - static void remove_empty_fields_in_sync_account_data_response(rapidjson::Value &account_data_json) { - for(const char *member_name : {"events"}) { - auto join_it = account_data_json.FindMember(member_name); - if(join_it != account_data_json.MemberEnd() && join_it->value.IsObject() && join_it->value.MemberCount() == 0) - account_data_json.RemoveMember(join_it); - } - } - - static void remove_unused_sync_data_fields(rapidjson::Value &json_root) { - for(auto it = json_root.MemberBegin(); it != json_root.MemberEnd();) { - if(strcmp(it->name.GetString(), "account_data") == 0 && it->value.IsObject()) { - remove_empty_fields_in_sync_account_data_response(it->value); - if(it->value.MemberCount() == 0) - it = json_root.RemoveMember(it); - else - ++it; - } else if(strcmp(it->name.GetString(), "rooms") == 0 && it->value.IsObject()) { - // TODO: Call this, but dont remove our read marker (needed for notifications on mentions for example). Or maybe we can get it from "account_data"? - //remove_ephemeral_field_in_sync_rooms_response(rooms_it->value); - remove_empty_fields_in_sync_rooms_response(it->value); - if(it->value.MemberCount() == 0) - it = json_root.RemoveMember(it); - else - ++it; - } else { - it = json_root.EraseMember(it); - } - } - } - bool Matrix::start_sync(MatrixDelegate *delegate, bool &cached) { cached = true; if(sync_running) @@ -1741,21 +1675,32 @@ namespace QuickMedia { load_custom_emoji_from_cache(); sync_thread = std::thread([this, matrix_cache_dir]() { + FILE *sync_cache_file; + const rapidjson::Value *next_batch_json = nullptr; sync_is_cache = true; - FILE *sync_cache_file = fopen(matrix_cache_dir.data.c_str(), "rb"); - if(sync_cache_file) { + + std::ifstream sync_cache_file_stream; + sync_cache_file_stream.open(matrix_cache_dir.data.c_str(), std::ifstream::in | std::ifstream::binary); + if(sync_cache_file_stream.good()) { rapidjson::Document doc; - char read_buffer[8192]; - rapidjson::FileReadStream is(sync_cache_file, read_buffer, sizeof(read_buffer)); - while(true) { - rapidjson::ParseResult parse_result = doc.ParseStream(is); + std::string line; + while(std::getline(sync_cache_file_stream, line)) { + rapidjson::ParseResult parse_result = doc.Parse(line.c_str(), line.size()); if(parse_result.IsError()) - break; - if(parse_sync_response(doc, false, false) != PluginResult::OK) + continue; // This should NEVER happen. Do initial sync if it does and remove cache? :( TODO + + if(parse_sync_response(doc, false) != PluginResult::OK) fprintf(stderr, "Failed to parse cached sync response\n"); + + next_batch_json = &GetMember(doc, "next_batch"); + if(next_batch_json->IsString()) { + set_next_batch(next_batch_json->GetString()); + //fprintf(stderr, "Matrix: next batch: %s\n", next_batch.c_str()); + } } - fclose(sync_cache_file); + malloc_trim(0); } + sync_cache_file_stream.close(); sync_is_cache = false; load_qm_read_markers_from_account_data(); // TODO: Remove when https://github.com/matrix-org/synapse/issues/14444 is fixed, if ever. @@ -1777,16 +1722,22 @@ namespace QuickMedia { else filter = FILTER; #endif - std::string filter_encoded = url_param_encode(INITIAL_FILTER); - std::vector additional_args = { { "-H", "Authorization: Bearer " + access_token }, { "-m", "35" } }; - const rapidjson::Value *next_batch_json; + next_batch_json = nullptr; PluginResult result; - bool initial_sync = true; + bool initial_sync = next_batch.empty(); + bool first_sync = true; + + std::string filter_encoded; + if(initial_sync) + filter_encoded = url_param_encode(INITIAL_FILTER); + else + filter_encoded = url_param_encode(CONTINUE_FILTER); + while(sync_running) { char url[2048]; if(next_batch.empty()) @@ -1821,10 +1772,7 @@ namespace QuickMedia { } } - if(next_batch.empty()) - clear_sync_cache_for_new_sync(); - - result = parse_sync_response(json_root, false, initial_sync); + result = parse_sync_response(json_root, initial_sync); if(result != PluginResult::OK) { fprintf(stderr, "Failed to parse sync response\n"); initial_sync = false; @@ -1842,7 +1790,10 @@ namespace QuickMedia { goto sync_end; } - if(initial_sync) { + if(first_sync) { + first_sync = false; + filter_encoded = url_param_encode(CONTINUE_FILTER); + notification_thread = std::thread([this]() { get_previous_notifications([this](const MatrixNotification ¬ification) { if(notification.read) @@ -1853,39 +1804,8 @@ namespace QuickMedia { delegate->add_unread_notification(std::move(notification)); }); }); - finished_fetching_notifications = true; - - { - std::vector additional_args = { - { "-H", "Authorization: Bearer " + access_token }, - { "-m", "35" } - }; - - char url[1024]; - std::string filter_encoded = url_param_encode(ADDITIONAL_MESSAGES_FILTER); - snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?filter=%s&timeout=0", homeserver.c_str(), filter_encoded.c_str()); - - rapidjson::Document json_root; - std::string err_msg; - DownloadResult download_result = download_json(json_root, url, additional_args, true, &err_msg); - if(download_result != DownloadResult::OK) { - fprintf(stderr, "/sync for additional messages failed\n"); - return; - } - - // TODO: Test? - //if(next_batch.empty()) - // clear_sync_cache_for_new_sync(); - - additional_messages_queue.pop_wait(); - parse_sync_response(json_root, true, false); - } }); - - filter_encoded = url_param_encode(CONTINUE_FILTER); - additional_messages_queue.push(true); - malloc_trim(0); } #if 0 @@ -1895,16 +1815,25 @@ namespace QuickMedia { } #endif - // TODO: Circulate file + // TODO: Use a NoSQL database. + // TODO: Remove very old cache. + // TODO: Find a way to remove this? this makes sync work like other clients but we dont want that! + // If the last sync was long ago then it has to sync ALL messages again. Maybe check if sync file + // is XX days old and then ignore it? sync_cache_file = fopen(matrix_cache_dir.data.c_str(), initial_sync ? "wb" : "ab"); initial_sync = false; if(sync_cache_file) { if(json_root.IsObject()) { - char buffer[4096]; - rapidjson::FileWriteStream file_write_stream(sync_cache_file, buffer, sizeof(buffer)); - rapidjson::Writer writer(file_write_stream); - remove_unused_sync_data_fields(json_root); + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); json_root.Accept(writer); + + std::string json_data(buffer.GetString(), buffer.GetSize()); + string_replace_all(json_data, '\n', ' '); + json_data += '\n'; + + fwrite(json_data.data(), 1, json_data.size(), sync_cache_file); + malloc_trim(0); } fclose(sync_cache_file); } @@ -1925,13 +1854,6 @@ namespace QuickMedia { program_kill_in_thread(sync_thread.get_id()); sync_thread.join(); } - - if(sync_additional_messages_thread.joinable()) { - program_kill_in_thread(sync_additional_messages_thread.get_id()); - additional_messages_queue.close(); - sync_additional_messages_thread.join(); - additional_messages_queue.restart(); - } if(notification_thread.joinable()) { program_kill_in_thread(notification_thread.get_id()); @@ -2113,12 +2035,12 @@ namespace QuickMedia { } } - PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool is_additional_messages_sync, bool initial_sync) { + PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool initial_sync) { if(!root.IsObject()) return PluginResult::ERR; const rapidjson::Value &rooms_json = GetMember(root, "rooms"); - parse_sync_room_data(rooms_json, is_additional_messages_sync, initial_sync); + parse_sync_room_data(rooms_json, initial_sync); const rapidjson::Value &account_data_json = GetMember(root, "account_data"); parse_sync_account_data(account_data_json); @@ -2300,7 +2222,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json, bool is_additional_messages_sync, bool initial_sync) { + PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json, bool initial_sync) { if(!rooms_json.IsObject()) return PluginResult::OK; @@ -2342,16 +2264,9 @@ namespace QuickMedia { const rapidjson::Value &timeline_json = GetMember(it.value, "timeline"); if(timeline_json.IsObject()) { - if(is_additional_messages_sync) { - // This may be non-existent if this is the first event in the room - const rapidjson::Value &prev_batch_json = GetMember(timeline_json, "prev_batch"); - if(prev_batch_json.IsString()) - room->set_prev_batch(prev_batch_json.GetString()); - } - bool has_unread_notifications = false; const rapidjson::Value &unread_notification_json = GetMember(it.value, "unread_notifications"); - if(unread_notification_json.IsObject() && !is_additional_messages_sync && !sync_is_cache) { + if(unread_notification_json.IsObject() && !sync_is_cache) { const rapidjson::Value &highlight_count_json = GetMember(unread_notification_json, "highlight_count"); if(highlight_count_json.IsInt64() && (highlight_count_json.GetInt64() > 0 || initial_sync)) { room->unread_notification_count = highlight_count_json.GetInt64(); @@ -2368,7 +2283,7 @@ namespace QuickMedia { if(account_data_json.IsObject()) { const rapidjson::Value &events_json = GetMember(account_data_json, "events"); auto me = get_me(room); - events_set_user_read_marker(events_json, room, me, is_additional_messages_sync); + events_set_user_read_marker(events_json, room, me); } if(is_new_room) @@ -2381,7 +2296,7 @@ namespace QuickMedia { if(account_data_json.IsObject()) { const rapidjson::Value &events_json = GetMember(account_data_json, "events"); auto me = get_me(room); - events_set_user_read_marker(events_json, room, me, is_additional_messages_sync); + events_set_user_read_marker(events_json, room, me); } if(is_new_room) @@ -2410,13 +2325,11 @@ namespace QuickMedia { } } - if(!is_additional_messages_sync) { - const rapidjson::Value &leave_json = GetMember(rooms_json, "leave"); - remove_rooms(leave_json); + const rapidjson::Value &leave_json = GetMember(rooms_json, "leave"); + remove_rooms(leave_json); - const rapidjson::Value &invite_json = GetMember(rooms_json, "invite"); - add_invites(invite_json); - } + const rapidjson::Value &invite_json = GetMember(rooms_json, "invite"); + add_invites(invite_json); if(initial_sync) { std::lock_guard lock(room_data_mutex); @@ -2549,7 +2462,7 @@ namespace QuickMedia { return user_info; } - void Matrix::events_set_user_read_marker(const rapidjson::Value &events_json, RoomData *room_data, std::shared_ptr &me, bool is_additional_messages_sync) { + void Matrix::events_set_user_read_marker(const rapidjson::Value &events_json, RoomData *room_data, std::shared_ptr &me) { assert(me); // TODO: Remove read marker from user and set it for the room instead. We need that in the matrix pages also if(!events_json.IsArray() || !me) return; @@ -2571,7 +2484,7 @@ namespace QuickMedia { if(!event_id_json.IsString()) continue; - if(!sync_is_cache && !is_additional_messages_sync) + if(!sync_is_cache) room_data->set_user_read_marker(me, std::string(event_id_json.GetString(), event_id_json.GetStringLength())); } else if(strcmp(type_json.GetString(), "qm.last_read_message_timestamp") == 0) { // TODO: Remove qm.last_read_message_timestamp in room level eventually when everybody has data in global level const rapidjson::Value &content_json = GetMember(event_json, "content"); @@ -5887,18 +5800,6 @@ namespace QuickMedia { return next_notifications_token; } - void Matrix::clear_sync_cache_for_new_sync() { - std::lock_guard room_data_lock(room_data_mutex); - std::lock_guard invites_lock(invite_mutex); - for(auto &room : rooms) { - room->clear_data(); - } - // We intentionally dont clear |rooms| here because we want the objects inside it to still be valid. TODO: Clear |rooms| here - //room_data_by_id.clear(); - invites.clear(); - ui_thread_tasks.push([this]{ delegate->clear_data(); }); - } - std::shared_ptr Matrix::get_user_by_id(RoomData *room, const std::string &user_id, bool *is_new_user, bool create_if_not_found) { auto user = room->get_user_by_id(user_id); if(user) { diff --git a/src/plugins/utils/UniqueProcess.cpp b/src/plugins/utils/UniqueProcess.cpp new file mode 100644 index 0000000..76b9cb1 --- /dev/null +++ b/src/plugins/utils/UniqueProcess.cpp @@ -0,0 +1,113 @@ +#include "../../../plugins/utils/UniqueProcess.hpp" +#include +#include +#include +#include +#include +#include + +namespace QuickMedia { + static bool is_process_running_program(pid_t pid, const char *program_name) { + char filepath[256]; + snprintf(filepath, sizeof(filepath), "/proc/%ld/cmdline", (long)pid); + + int fd = open(filepath, O_RDONLY); + if(fd == -1) + return false; + + char buffer[PATH_MAX + 1]; + ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); + if(bytes_read == -1) { + close(fd); + return false; + } + buffer[bytes_read] = '\0'; + + char resolved_path[PATH_MAX + 1]; + if(!realpath(buffer, resolved_path)) { + close(fd); + return false; + } + bytes_read = strlen(resolved_path); + resolved_path[bytes_read] = '\0'; + + const char *end = resolved_path + bytes_read; + const char *start = (const char*)memrchr(resolved_path, '/', bytes_read); + if(start) + start += 1; + else + start = buffer; + + const size_t cmd_arg0_len = end - start; + const size_t program_name_len = strlen(program_name); + bool running = (cmd_arg0_len == program_name_len && memcmp(start, program_name, program_name_len) == 0); + close(fd); + return running; + } + + bool is_quickmedia_instance_already_running(const char *pid_file_dir, const char *plugin_name) { + char pid_file[PATH_MAX]; + snprintf(pid_file, sizeof(pid_file), "%s/quickmedia.%s.pid", pid_file_dir, plugin_name); + + char buffer[256]; + int fd = open(pid_file, O_RDONLY); + if(fd == -1) + return false; + + ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); + if(bytes_read < 0) { + perror("failed to read quickmedia pid file"); + close(fd); + return false; + } + buffer[bytes_read] = '\0'; + close(fd); + + bool running = false; + long pid = 0; + if(sscanf(buffer, "%ld", &pid) == 1) { + if(is_process_running_program(pid, "quickmedia")) { + fprintf(stderr, "Error: quickmedia %s is already running\n", plugin_name); + running = true; + } + } else { + fprintf(stderr, "Warning: quickmedia pid file is in incorrect format, it's possible that its corrupt. Replacing file and continuing...\n"); + running = false; + } + + if(!running) + unlink(pid_file); + + return running; + } + + bool set_quickmedia_instance_unique(const char *pid_file_dir, const char *plugin_name) { + char pid_file[PATH_MAX]; + snprintf(pid_file, sizeof(pid_file), "%s/quickmedia.%s.pid", pid_file_dir, plugin_name); + + int fd = open(pid_file, O_WRONLY|O_CREAT|O_TRUNC, 0777); + if(fd == -1) { + perror("failed to create quickmedia pid file"); + return false; + } + + bool success = true; + char buffer[256]; + const int buffer_size = snprintf(buffer, sizeof(buffer), "%ld", (long)getpid()); + if(write(fd, buffer, buffer_size) == -1) { + perror("failed to write quickmedia pid file"); + success = false; + } + + close(fd); + if(!success) + unlink(pid_file); + return success; + } + + void remove_quickmedia_instance_lock(const char *pid_file_dir, const char *plugin_name) { + char pid_file[PATH_MAX]; + snprintf(pid_file, sizeof(pid_file), "%s/quickmedia.%s.pid", pid_file_dir, plugin_name); + unlink(pid_file); + } +} \ No newline at end of file -- cgit v1.2.3