aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Matrix.cpp224
-rw-r--r--src/plugins/utils/UniqueProcess.cpp111
2 files changed, 161 insertions, 174 deletions
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index fca4332..0d93bd0 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -17,7 +17,6 @@
#include <rapidjson/filewritestream.h>
#include <fcntl.h>
#include <unistd.h>
-#include <fstream>
#include <malloc.h>
#include "../../include/QuickMedia.hpp"
#include <HtmlParser.h>
@@ -39,6 +38,7 @@ 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,6 +802,15 @@ 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;
@@ -1029,6 +1038,12 @@ 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();
@@ -1116,6 +1131,13 @@ 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) {
}
@@ -1176,6 +1198,12 @@ 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)});
@@ -1649,6 +1677,44 @@ 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)
@@ -1675,32 +1741,21 @@ 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;
-
- 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() && get_file_type(get_cache_dir().join("matrix").join("updated-cache-version")) == FileType::REGULAR) {
+ FILE *sync_cache_file = fopen(matrix_cache_dir.data.c_str(), "rb");
+ if(sync_cache_file) {
rapidjson::Document doc;
- std::string line;
- while(std::getline(sync_cache_file_stream, line)) {
- rapidjson::ParseResult parse_result = doc.Parse<rapidjson::kParseStopWhenDoneFlag>(line.c_str(), line.size());
+ 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);
if(parse_result.IsError())
- continue; // This should NEVER happen. Do initial sync if it does and remove cache? :( TODO
-
- if(parse_sync_response(doc, false) != PluginResult::OK)
+ break;
+ if(parse_sync_response(doc, false, 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());
- }
}
- malloc_trim(0);
+ fclose(sync_cache_file);
}
- 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.
@@ -1722,22 +1777,16 @@ 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" }
};
- next_batch_json = nullptr;
+ const rapidjson::Value *next_batch_json;
PluginResult result;
- 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);
-
+ bool initial_sync = true;
while(sync_running) {
char url[2048];
if(next_batch.empty())
@@ -1772,7 +1821,10 @@ namespace QuickMedia {
}
}
- result = parse_sync_response(json_root, initial_sync);
+ if(next_batch.empty())
+ clear_sync_cache_for_new_sync();
+
+ result = parse_sync_response(json_root, false, initial_sync);
if(result != PluginResult::OK) {
fprintf(stderr, "Failed to parse sync response\n");
initial_sync = false;
@@ -1790,10 +1842,7 @@ namespace QuickMedia {
goto sync_end;
}
- if(first_sync) {
- first_sync = false;
- filter_encoded = url_param_encode(CONTINUE_FILTER);
-
+ if(initial_sync) {
notification_thread = std::thread([this]() {
get_previous_notifications([this](const MatrixNotification &notification) {
if(notification.read)
@@ -1804,8 +1853,39 @@ 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
@@ -1815,26 +1895,16 @@ namespace QuickMedia {
}
#endif
- // 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?
+ // TODO: Circulate file
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()) {
- rapidjson::StringBuffer buffer;
- rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ 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);
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);
- file_overwrite(get_cache_dir().join("matrix").join("updated-cache-version"), "1"); // To make sure the cache format is up to date
- malloc_trim(0);
}
fclose(sync_cache_file);
}
@@ -1855,6 +1925,13 @@ 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());
@@ -2036,12 +2113,12 @@ namespace QuickMedia {
}
}
- PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool initial_sync) {
+ PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool is_additional_messages_sync, bool initial_sync) {
if(!root.IsObject())
return PluginResult::ERR;
const rapidjson::Value &rooms_json = GetMember(root, "rooms");
- parse_sync_room_data(rooms_json, initial_sync);
+ parse_sync_room_data(rooms_json, is_additional_messages_sync, initial_sync);
const rapidjson::Value &account_data_json = GetMember(root, "account_data");
parse_sync_account_data(account_data_json);
@@ -2223,7 +2300,7 @@ namespace QuickMedia {
return PluginResult::OK;
}
- PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json, bool initial_sync) {
+ PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json, bool is_additional_messages_sync, bool initial_sync) {
if(!rooms_json.IsObject())
return PluginResult::OK;
@@ -2265,9 +2342,16 @@ 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() && !sync_is_cache) {
+ if(unread_notification_json.IsObject() && !is_additional_messages_sync && !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();
@@ -2284,7 +2368,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);
+ events_set_user_read_marker(events_json, room, me, is_additional_messages_sync);
}
if(is_new_room)
@@ -2297,7 +2381,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);
+ events_set_user_read_marker(events_json, room, me, is_additional_messages_sync);
}
if(is_new_room)
@@ -2326,11 +2410,13 @@ namespace QuickMedia {
}
}
- const rapidjson::Value &leave_json = GetMember(rooms_json, "leave");
- remove_rooms(leave_json);
+ if(!is_additional_messages_sync) {
+ 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);
@@ -2463,7 +2549,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) {
+ 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) {
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;
@@ -2485,7 +2571,7 @@ namespace QuickMedia {
if(!event_id_json.IsString())
continue;
- if(!sync_is_cache)
+ if(!sync_is_cache && !is_additional_messages_sync)
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");
@@ -5801,6 +5887,18 @@ 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
deleted file mode 100644
index d2025f5..0000000
--- a/src/plugins/utils/UniqueProcess.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-#include "../../../plugins/utils/UniqueProcess.hpp"
-#include "../../../include/Storage.hpp"
-#include <stdio.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-
-namespace QuickMedia {
- bool is_quickmedia_instance_already_running(const char *sock_file_dir, const char *plugin_name) {
- char sock_file[PATH_MAX];
- snprintf(sock_file, sizeof(sock_file), "%s/quickmedia.%s.sock", sock_file_dir, plugin_name);
-
- std::string resolved_path;
- if(file_get_content(sock_file, resolved_path) != 0)
- return false;
-
- resolved_path.resize(108); // sizeof(addr.sun_path) is 108
-
- int fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if(fd == -1) {
- fprintf(stderr, "Error: failed to create unix domain socket, error: %s\n", strerror(errno));
- return true;
- }
-
- fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- strcpy(addr.sun_path, resolved_path.c_str());
-
- bool running = connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0;
- int err = errno;
- if(err == EAGAIN)
- running = true;
- else if(err == ENOENT)
- running = false;
- close(fd);
- return running;
- }
-
- bool set_quickmedia_instance_unique(const char *sock_file_dir, const char *plugin_name) {
- char socket_file[] = "/tmp/quickmedia.XXXXXX";
- int tmp_file_fd = mkstemp(socket_file);
- if(tmp_file_fd == -1) {
- fprintf(stderr, "Error: failed to create temporary file for unix domain socket, error: %s\n", strerror(errno));
- return false;
- }
- unlink(socket_file);
- close(tmp_file_fd);
-
- int fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if(fd == -1) {
- fprintf(stderr, "Error: failed to create unix domain socket, error: %s\n", strerror(errno));
- return false;
- }
-
- struct sockaddr_un addr;
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- strcpy(addr.sun_path, socket_file);
-
- if(bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
- fprintf(stderr, "Error: failed to bind unix domain socket, error: %s\n", strerror(errno));
- unlink(socket_file);
- close(fd);
- return false;
- }
-
- if(listen(fd, 0) == -1) {
- fprintf(stderr, "Error: failed to listen to unix domain socket, error: %s\n", strerror(errno));
- unlink(socket_file);
- close(fd);
- return false;
- }
-
- char sock_file[PATH_MAX];
- snprintf(sock_file, sizeof(sock_file), "%s/quickmedia.%s.sock", sock_file_dir, plugin_name);
- bool success = file_overwrite(sock_file, socket_file) == 0;
- if(!success) {
- fprintf(stderr, "Error: failed to create %s unix domain socket link file\n", sock_file);
- unlink(socket_file);
- close(fd);
- }
- return success;
- }
-
- void remove_quickmedia_instance_lock(const char *sock_file_dir, const char *plugin_name) {
- char sock_file[PATH_MAX];
- snprintf(sock_file, sizeof(sock_file), "%s/quickmedia.%s.sock", sock_file_dir, plugin_name);
-
- std::string resolved_path;
- if(file_get_content(sock_file, resolved_path) != 0) {
- unlink(sock_file);
- return;
- }
-
- resolved_path.resize(108); // sizeof(addr.sun_path) is 108
-
- if(resolved_path.size() < 4 || memcmp(resolved_path.data(), "/tmp", 4) != 0) {
- unlink(sock_file);
- return;
- }
-
- unlink(sock_file);
- unlink(resolved_path.c_str());
- }
-} \ No newline at end of file