aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp80
-rw-r--r--src/plugins/Matrix.cpp223
-rw-r--r--src/plugins/utils/UniqueProcess.cpp113
3 files changed, 195 insertions, 221 deletions
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 <mglpp/system/FloatRect.hpp>
#include "../include/gui/Button.hpp"
#include "../external/hash-library/sha256.h"
@@ -407,7 +408,7 @@ namespace QuickMedia {
video_max_height = 0;
std::vector<Tab> 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<FourchanBoardsPage>(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<LbrySearchPage>(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<std::string> 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 <rapidjson/filewritestream.h>
#include <fcntl.h>
#include <unistd.h>
+#include <fstream>
#include <malloc.h>
#include "../../include/QuickMedia.hpp"
#include <HtmlParser.h>
@@ -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<Message> 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<Tab> &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<Tab> &result_tabs) {
if(args.url == "join") {
result_tabs.push_back(Tab{create_body(), std::make_unique<MatrixRoomInputPage>(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<rapidjson::kParseStopWhenDoneFlag>(is);
+ std::string line;
+ while(std::getline(sync_cache_file_stream, line)) {
+ rapidjson::ParseResult parse_result = doc.Parse<rapidjson::kParseStopWhenDoneFlag>(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<CommandArg> 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 &notification) {
if(notification.read)
@@ -1853,39 +1804,8 @@ namespace QuickMedia {
delegate->add_unread_notification(std::move(notification));
});
});
-
finished_fetching_notifications = true;
-
- {
- std::vector<CommandArg> 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<rapidjson::FileWriteStream> writer(file_write_stream);
- remove_unused_sync_data_fields(json_root);
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> 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<std::recursive_mutex> 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<UserInfo> &me, bool is_additional_messages_sync) {
+ void Matrix::events_set_user_read_marker(const rapidjson::Value &events_json, RoomData *room_data, std::shared_ptr<UserInfo> &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<std::recursive_mutex> room_data_lock(room_data_mutex);
- std::lock_guard<std::mutex> 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<UserInfo> 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 <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+
+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