From 66a97007eb36a112f31e923c20e434ba8b39c4ba Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 16 Oct 2020 03:49:52 +0200 Subject: Matrix: use rapidjson instead of jsoncpp to decrease memory usage from 58mb to 24mb --- .gitmodules | 3 + README.md | 2 +- TODO | 3 +- depends/rapidjson | 1 + include/Program.h | 4 +- include/Storage.hpp | 5 + plugins/Matrix.hpp | 14 +- project.conf | 5 +- src/Program.c | 10 +- src/QuickMedia.cpp | 6 +- src/Storage.cpp | 31 +- src/VideoPlayer.cpp | 2 +- src/plugins/Matrix.cpp | 770 +++++++++++++++++++++++++------------------------ 13 files changed, 454 insertions(+), 402 deletions(-) create mode 160000 depends/rapidjson diff --git a/.gitmodules b/.gitmodules index 036c9dc..97f7a6d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "depends/html-search"] path = depends/html-search url = https://git.dec05eba.com/html-search +[submodule "depends/rapidjson"] + path = depends/rapidjson + url = https://git.dec05eba.com/rapidjson diff --git a/README.md b/README.md index d586ef9..df78618 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Config data, including manga progress is stored under `$HOME/.config/quickmedia` Cache is stored under `$HOME/.cache/quickmedia`. ## Usage ``` -usage: QuickMedia [--tor] [--use-system-mpv-config] [--dir ] [-p ] +usage: QuickMedia [--tor] [--use-system-mpv-config] [--dir ] OPTIONS: plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, youtube, nyaa.si, matrix, file-manager --no-video Only play audio when playing a video. Disabled by default diff --git a/TODO b/TODO index 3e13fa9..950489e 100644 --- a/TODO +++ b/TODO @@ -105,4 +105,5 @@ Add file upload to 4chan. Retry download if it fails, at least 3 times (observed to be needed for mangadex images). Readd autocomplete, but make it better with a proper list. Also readd 4chan login page and manganelo creators page. Fix logout/login in matrix. Currently it doesn't work because data is cleared while sync is in progress, leading to the first sync sometimes being with previous data... -Modify sfml to use GL_COMPRESSED_LUMINANCE and other texture compression modes (in sf::Texture). This reduces memory usage by half. \ No newline at end of file +Modify sfml to use GL_COMPRESSED_LUMINANCE and other texture compression modes (in sf::Texture). This reduces memory usage by half. +Decrease memory usage even further (mostly in matrix /sync when part of large rooms) by using rapidjson SAX style API to stream downloaded json into the json parser, instead of using download_to_string with accumulate_string. \ No newline at end of file diff --git a/depends/rapidjson b/depends/rapidjson new file mode 160000 index 0000000..58dbb57 --- /dev/null +++ b/depends/rapidjson @@ -0,0 +1 @@ +Subproject commit 58dbb57768c257ea9cbb0112ad71b572906c8b72 diff --git a/include/Program.h b/include/Program.h index cd45b30..3cbf09e 100644 --- a/include/Program.h +++ b/include/Program.h @@ -2,7 +2,6 @@ #define QUICKMEDIA_PROGRAM_H #include -#include #ifdef __cplusplus extern "C" { @@ -26,7 +25,8 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void // Return the exit status, or a negative value if waiting failed int wait_program(pid_t process_id); -bool wait_program_non_blocking(pid_t process_id, int *status); +/* 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 diff --git a/include/Storage.hpp b/include/Storage.hpp index 8a1bbfa..10cbbb2 100644 --- a/include/Storage.hpp +++ b/include/Storage.hpp @@ -4,6 +4,10 @@ #include #include #include +#ifdef Bool +#undef Bool +#endif +#include namespace QuickMedia { // Return false to stop the iterator @@ -29,6 +33,7 @@ namespace QuickMedia { bool read_file_as_json(const Path &filepath, Json::Value &result); bool save_json_to_file_atomic(const Path &path, const Json::Value &json); + bool save_json_to_file_atomic(const Path &path, const rapidjson::Value &json); bool is_program_executable_by_name(const char *name); } \ No newline at end of file diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 5819420..09895b0 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include namespace QuickMedia { // Dummy, only play one video. TODO: Play all videos in room, as related videos? @@ -150,19 +150,19 @@ namespace QuickMedia { bool use_tor = false; private: - PluginResult sync_response_to_body_items(const Json::Value &root, RoomSyncMessages &room_messages); + PluginResult sync_response_to_body_items(const rapidjson::Document &root, RoomSyncMessages &room_messages); PluginResult get_previous_room_messages(std::shared_ptr &room_data); - void events_add_user_info(const Json::Value &events_json, RoomData *room_data); - void events_add_user_read_markers(const Json::Value &events_json, RoomData *room_data); - void events_add_messages(const Json::Value &events_json, std::shared_ptr &room_data, MessageDirection message_dir, RoomSyncMessages *room_messages, bool has_unread_notifications); - void events_set_room_name(const Json::Value &events_json, RoomData *room_data); + void events_add_user_info(const rapidjson::Value &events_json, RoomData *room_data); + void events_add_user_read_markers(const rapidjson::Value &events_json, RoomData *room_data); + void events_add_messages(const rapidjson::Value &events_json, std::shared_ptr &room_data, MessageDirection message_dir, RoomSyncMessages *room_messages, bool has_unread_notifications); + void events_set_room_name(const rapidjson::Value &events_json, RoomData *room_data); PluginResult upload_file(const std::string &room_id, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg); std::shared_ptr get_edited_message_original_message(RoomData *room_data, std::shared_ptr message); std::shared_ptr get_room_by_id(const std::string &id); void add_room(std::shared_ptr room); - DownloadResult download_json(Json::Value &result, const std::string &url, std::vector additional_args, bool use_browser_useragent = false, std::string *err_msg = nullptr) const; + DownloadResult download_json(rapidjson::Document &result, const std::string &url, std::vector additional_args, bool use_browser_useragent = false, std::string *err_msg = nullptr) const; private: std::unordered_map> room_data_by_id; std::mutex room_data_mutex; diff --git a/project.conf b/project.conf index e671b91..3950938 100644 --- a/project.conf +++ b/project.conf @@ -4,8 +4,9 @@ type = "executable" version = "0.1.0" platforms = ["posix"] -[config] -error_on_warning = "true" +# This needs to be commented out for now because rapidjson depends on undefined behavior according to gcc... +#[config] +#error_on_warning = "true" [lang.cpp] version = "c++17" diff --git a/src/Program.c b/src/Program.c index 0ad19f2..2307798 100644 --- a/src/Program.c +++ b/src/Program.c @@ -116,26 +116,26 @@ int wait_program(pid_t process_id) { return WEXITSTATUS(status); } -bool wait_program_non_blocking(pid_t process_id, int *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 false; + return 0; } else if(wait_result == 0) { /* the child process is still running */ *status = 0; - return false; + return 0; } if(!WIFEXITED(s)) { *status = -4; - return false; + return 0; } *status = WEXITSTATUS(s); - return true; + return 1; } int exec_program_async(const char **args, pid_t *result_process_id) { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 9a6ff3f..0139f37 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -391,7 +391,7 @@ namespace QuickMedia { } static void usage() { - fprintf(stderr, "usage: QuickMedia [--tor] [--no-video] [--use-system-mpv-config] [--dir ] [-p ]\n"); + fprintf(stderr, "usage: QuickMedia [--tor] [--no-video] [--use-system-mpv-config] [--dir ]\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, pornhub, youtube, nyaa.si, matrix, file-manager\n"); fprintf(stderr, " --no-video Only play audio when playing a video. Disabled by default\n"); @@ -466,6 +466,10 @@ namespace QuickMedia { if(i < argc - 1) { start_dir = argv[i + 1]; ++i; + } else { + fprintf(stderr, "Missing directory after --dir argument\n"); + usage(); + return -1; } } else if(argv[i][0] == '-') { fprintf(stderr, "Invalid option %s\n", argv[i]); diff --git a/src/Storage.cpp b/src/Storage.cpp index c9dfb17..a1dc777 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #if OS_FAMILY == OS_FAMILY_POSIX @@ -137,12 +139,12 @@ namespace QuickMedia { return -1; } - int file_overwrite(const Path &path, const std::string &data) { + static int file_overwrite(const Path &path, const char *str, size_t size) { FILE *file = fopen(path.data.c_str(), "wb"); if(!file) return -1; - if(fwrite(data.data(), 1, data.size(), file) != data.size()) { + if(fwrite(str, 1, size, file) != size) { fclose(file); return -1; } @@ -150,6 +152,10 @@ namespace QuickMedia { return fclose(file); } + int file_overwrite(const Path &path, const std::string &data) { + return file_overwrite(path, data.c_str(), data.size()); + } + void for_files_in_dir(const Path &path, FileIteratorCallback callback) { try { for(auto &p : std::filesystem::directory_iterator(path.data)) { @@ -226,6 +232,27 @@ namespace QuickMedia { return true; } + bool save_json_to_file_atomic(const Path &path, const rapidjson::Value &json) { + Path tmp_path = path; + tmp_path.append(".tmp"); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + json.Accept(writer); + + Json::StreamWriterBuilder json_builder; + if(file_overwrite(tmp_path, buffer.GetString(), buffer.GetSize()) != 0) + return false; + + // Rename is atomic under posix! + if(rename(tmp_path.data.c_str(), path.data.c_str()) != 0) { + perror("save_json_to_file_atomic rename"); + return false; + } + + return true; + } + bool is_program_executable_by_name(const char *name) { // TODO: Implement for Windows. Windows also uses semicolon instead of colon as a separator char *env = getenv("PATH"); diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index a79ffd7..ce2fa82 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -1,6 +1,6 @@ #include "../include/VideoPlayer.hpp" -#include "../include/Program.h" #include "../include/Storage.hpp" +#include "../include/Program.h" #include #include #include diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 4de7045..3637d41 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -1,8 +1,8 @@ #include "../../plugins/Matrix.hpp" #include "../../include/Storage.hpp" #include "../../include/StringUtils.hpp" -#include -#include +#include +#include #include #include @@ -21,6 +21,15 @@ static const char* SERVICE_NAME = "matrix"; +static rapidjson::Value nullValue(rapidjson::kNullType); + +static const rapidjson::Value& GetMember(const rapidjson::Value &obj, const char *key) { + auto it = obj.FindMember(key); + if(it != obj.MemberEnd()) + return it->value; + return nullValue; +} + namespace QuickMedia { std::shared_ptr RoomData::get_user_by_id(const std::string &user_id) { std::lock_guard lock(room_mutex); @@ -114,7 +123,7 @@ namespace QuickMedia { else snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=30000&since=%s", homeserver.c_str(), next_batch.c_str()); - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); @@ -122,9 +131,9 @@ namespace QuickMedia { if(result != PluginResult::OK) return result; - const Json::Value &next_batch_json = json_root["next_batch"]; - if(next_batch_json.isString()) { - next_batch = next_batch_json.asString(); + const rapidjson::Value &next_batch_json = GetMember(json_root, "next_batch"); + if(next_batch_json.IsString()) { + next_batch = next_batch_json.GetString(); fprintf(stderr, "Matrix: next batch: %s\n", next_batch.c_str()); } else { fprintf(stderr, "Matrix: missing next batch\n"); @@ -138,29 +147,29 @@ namespace QuickMedia { { "-H", "Authorization: Bearer " + access_token } }; - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, homeserver + "/_matrix/client/r0/joined_rooms", std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - if(!json_root.isObject()) + if(!json_root.IsObject()) return PluginResult::ERR; - const Json::Value &joined_rooms_json = json_root["joined_rooms"]; - if(!joined_rooms_json.isArray()) + const rapidjson::Value &joined_rooms_json = GetMember(json_root, "joined_rooms"); + if(!joined_rooms_json.IsArray()) return PluginResult::ERR; - for(const Json::Value &room_id_json : joined_rooms_json) { - if(!room_id_json.isString()) + for(const rapidjson::Value &room_id_json : joined_rooms_json.GetArray()) { + if(!room_id_json.IsString()) continue; - std::string room_id_str = room_id_json.asString(); + std::string room_id_str = room_id_json.GetString(); std::string room_name; std::string avatar_url; auto room = get_room_by_id(room_id_str); if(!room) { room = std::make_shared(); - room->id = room_id_json.asString(); + room->id = room_id_json.GetString(); add_room(std::move(room)); room_name = room_id_str; fprintf(stderr, "Missing room %s from /sync, adding in joined_rooms\n", room_id_str.c_str()); @@ -277,27 +286,27 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::sync_response_to_body_items(const Json::Value &root, RoomSyncMessages &room_messages) { - if(!root.isObject()) + PluginResult Matrix::sync_response_to_body_items(const rapidjson::Document &root, RoomSyncMessages &room_messages) { + if(!root.IsObject()) return PluginResult::ERR; - const Json::Value &rooms_json = root["rooms"]; - if(!rooms_json.isObject()) + const rapidjson::Value &rooms_json = GetMember(root, "rooms"); + if(!rooms_json.IsObject()) return PluginResult::OK; - const Json::Value &join_json = rooms_json["join"]; - if(!join_json.isObject()) + const rapidjson::Value &join_json = GetMember(rooms_json, "join"); + if(!join_json.IsObject()) return PluginResult::OK; - for(Json::Value::const_iterator it = join_json.begin(); it != join_json.end(); ++it) { - if(!it->isObject()) + for(auto const &it : join_json.GetObject()) { + if(!it.value.IsObject()) continue; - Json::Value room_id = it.key(); - if(!room_id.isString()) + const rapidjson::Value &room_id = it.name; + if(!room_id.IsString()) continue; - std::string room_id_str = room_id.asString(); + std::string room_id_str = room_id.GetString(); auto room = get_room_by_id(room_id_str); if(!room) { @@ -306,40 +315,40 @@ namespace QuickMedia { add_room(room); } - const Json::Value &state_json = (*it)["state"]; - if(state_json.isObject()) { - const Json::Value &events_json = state_json["events"]; + const rapidjson::Value &state_json = GetMember(it.value, "state"); + if(state_json.IsObject()) { + const rapidjson::Value &events_json = GetMember(state_json, "events"); events_add_user_info(events_json, room.get()); events_set_room_name(events_json, room.get()); } - const Json::Value &timeline_json = (*it)["timeline"]; - if(timeline_json.isObject()) { + const rapidjson::Value &timeline_json = GetMember(it.value, "timeline"); + if(timeline_json.IsObject()) { if(room->prev_batch.empty()) { // This may be non-existent if this is the first event in the room - const Json::Value &prev_batch_json = timeline_json["prev_batch"]; - if(prev_batch_json.isString()) - room->prev_batch = prev_batch_json.asString(); + const rapidjson::Value &prev_batch_json = GetMember(timeline_json, "prev_batch"); + if(prev_batch_json.IsString()) + room->prev_batch = prev_batch_json.GetString(); } // TODO: Is there no better way to check for notifications? this is not robust... bool has_unread_notifications = false; - const Json::Value &unread_notification_json = (*it)["unread_notifications"]; - if(unread_notification_json.isObject()) { - const Json::Value &highlight_count_json = unread_notification_json["highlight_count"]; - if(highlight_count_json.isNumeric() && highlight_count_json.asInt64() > 0) + const rapidjson::Value &unread_notification_json = GetMember(it.value, "unread_notifications"); + if(unread_notification_json.IsObject()) { + const rapidjson::Value &highlight_count_json = GetMember(unread_notification_json, "highlight_count"); + if(highlight_count_json.IsNumber() && highlight_count_json.GetInt64() > 0) has_unread_notifications = true; } - const Json::Value &events_json = timeline_json["events"]; + const rapidjson::Value &events_json = GetMember(timeline_json, "events"); events_add_user_info(events_json, room.get()); events_add_messages(events_json, room, MessageDirection::AFTER, &room_messages, has_unread_notifications); events_set_room_name(events_json, room.get()); } - const Json::Value &ephemeral_json = (*it)["ephemeral"]; - if(ephemeral_json.isObject()) { - const Json::Value &events_json = ephemeral_json["events"]; + const rapidjson::Value &ephemeral_json = GetMember(it.value, "ephemeral"); + if(ephemeral_json.IsObject()) { + const rapidjson::Value &events_json = GetMember(ephemeral_json, "events"); events_add_user_read_markers(events_json, room.get()); } } @@ -360,38 +369,38 @@ namespace QuickMedia { return result; } - void Matrix::events_add_user_info(const Json::Value &events_json, RoomData *room_data) { - if(!events_json.isArray()) + void Matrix::events_add_user_info(const rapidjson::Value &events_json, RoomData *room_data) { + if(!events_json.IsArray()) return; - for(const Json::Value &event_item_json : events_json) { - if(!event_item_json.isObject()) + for(const rapidjson::Value &event_item_json : events_json.GetArray()) { + if(!event_item_json.IsObject()) continue; - const Json::Value &type_json = event_item_json["type"]; - if(!type_json.isString() || strcmp(type_json.asCString(), "m.room.member") != 0) + const rapidjson::Value &type_json = GetMember(event_item_json, "type"); + if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.member") != 0) continue; - const Json::Value &sender_json = event_item_json["sender"]; - if(!sender_json.isString()) + const rapidjson::Value &sender_json = GetMember(event_item_json, "sender"); + if(!sender_json.IsString()) continue; - const Json::Value &content_json = event_item_json["content"]; - if(!content_json.isObject()) + const rapidjson::Value &content_json = GetMember(event_item_json, "content"); + if(!content_json.IsObject()) continue; - const Json::Value &membership_json = content_json["membership"]; - if(!membership_json.isString() || strcmp(membership_json.asCString(), "join") != 0) + const rapidjson::Value &membership_json = GetMember(content_json, "membership"); + if(!membership_json.IsString() || strcmp(membership_json.GetString(), "join") != 0) continue; std::string avatar_url_str; - const Json::Value &avatar_url_json = content_json["avatar_url"]; - if(avatar_url_json.isString()) - avatar_url_str = avatar_url_json.asString(); + const rapidjson::Value &avatar_url_json = GetMember(content_json, "avatar_url"); + if(avatar_url_json.IsString()) + avatar_url_str = avatar_url_json.GetString(); - const Json::Value &display_name_json = content_json["displayname"]; + const rapidjson::Value &display_name_json = GetMember(content_json, "displayname"); - std::string sender_json_str = sender_json.asString(); + std::string sender_json_str = sender_json.GetString(); auto user_info = std::make_shared(); user_info->user_id = sender_json_str; @@ -400,7 +409,7 @@ namespace QuickMedia { user_info->avatar_url.erase(user_info->avatar_url.begin(), user_info->avatar_url.begin() + 6); if(!user_info->avatar_url.empty()) user_info->avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + user_info->avatar_url + "?width=32&height=32&method=crop"; - user_info->display_name = display_name_json.isString() ? display_name_json.asString() : sender_json_str; + user_info->display_name = display_name_json.IsString() ? display_name_json.GetString() : sender_json_str; user_info->display_name_color = user_id_to_color(sender_json_str); // Overwrites user data @@ -408,47 +417,47 @@ namespace QuickMedia { } } - void Matrix::events_add_user_read_markers(const Json::Value &events_json, RoomData *room_data) { - if(!events_json.isArray()) + void Matrix::events_add_user_read_markers(const rapidjson::Value &events_json, RoomData *room_data) { + if(!events_json.IsArray()) return; - for(const Json::Value &event_item_json : events_json) { - if(!event_item_json.isObject()) + for(const rapidjson::Value &event_item_json : events_json.GetArray()) { + if(!event_item_json.IsObject()) continue; - const Json::Value &type_json = event_item_json["type"]; - if(!type_json.isString() || strcmp(type_json.asCString(), "m.receipt") != 0) + const rapidjson::Value &type_json = GetMember(event_item_json, "type"); + if(!type_json.IsString() || strcmp(type_json.GetString(), "m.receipt") != 0) continue; - const Json::Value &content_json = event_item_json["content"]; - if(!content_json.isObject()) + const rapidjson::Value &content_json = GetMember(event_item_json, "content"); + if(!content_json.IsObject()) continue; - for(Json::Value::const_iterator it = content_json.begin(); it != content_json.end(); ++it) { - if(!it->isObject()) + for(auto const &it2 : content_json.GetObject()) { + if(!it2.value.IsObject()) continue; - Json::Value event_id_json = it.key(); - if(!event_id_json.isString()) + const rapidjson::Value &event_id_json = it2.name; + if(!event_id_json.IsString()) continue; - const Json::Value &read_json = (*it)["m.read"]; - if(!read_json.isObject()) + const rapidjson::Value &read_json = GetMember(it2.value, "m.read"); + if(!read_json.IsObject()) continue; - std::string event_id_str = event_id_json.asString(); + std::string event_id_str = event_id_json.GetString(); - for(Json::Value::const_iterator user_id_it = read_json.begin(); user_id_it != read_json.end(); ++user_id_it) { - if(!user_id_it->isObject()) + for(auto const &it3 : read_json.GetObject()) { + if(!it3.value.IsObject()) continue; - Json::Value user_id_json = user_id_it.key(); - if(!user_id_json.isString()) + const rapidjson::Value &user_id_json = it3.name; + if(!user_id_json.IsString()) continue; - auto user = room_data->get_user_by_id(user_id_json.asString()); + auto user = room_data->get_user_by_id(user_id_json.GetString()); if(!user) { - fprintf(stderr, "Receipt read receipt for unknown user: %s, ignoring...\n", user_id_json.asCString()); + fprintf(stderr, "Receipt read receipt for unknown user: %s, ignoring...\n", user_id_json.GetString()); continue; } @@ -458,12 +467,12 @@ namespace QuickMedia { } } - static std::string message_content_extract_thumbnail_url(const Json::Value &content_json, const std::string &homeserver) { - const Json::Value &info_json = content_json["info"]; - if(info_json.isObject()) { - const Json::Value &thumbnail_url_json = info_json["thumbnail_url"]; - if(thumbnail_url_json.isString()) { - std::string thumbnail_str = thumbnail_url_json.asString(); + static std::string message_content_extract_thumbnail_url(const rapidjson::Value &content_json, const std::string &homeserver) { + const rapidjson::Value &info_json = GetMember(content_json, "info"); + if(info_json.IsObject()) { + const rapidjson::Value &thumbnail_url_json = GetMember(info_json, "thumbnail_url"); + if(thumbnail_url_json.IsString()) { + std::string thumbnail_str = thumbnail_url_json.GetString(); if(strncmp(thumbnail_str.c_str(), "mxc://", 6) == 0) { thumbnail_str.erase(thumbnail_str.begin(), thumbnail_str.begin() + 6); return homeserver + "/_matrix/media/r0/download/" + std::move(thumbnail_str); @@ -524,8 +533,8 @@ namespace QuickMedia { return false; } - void Matrix::events_add_messages(const Json::Value &events_json, std::shared_ptr &room_data, MessageDirection message_dir, RoomSyncMessages *room_messages, bool has_unread_notifications) { - if(!events_json.isArray()) + void Matrix::events_add_messages(const rapidjson::Value &events_json, std::shared_ptr &room_data, MessageDirection message_dir, RoomSyncMessages *room_messages, bool has_unread_notifications) { + if(!events_json.IsArray()) return; std::vector> *room_sync_messages = nullptr; @@ -534,32 +543,32 @@ namespace QuickMedia { std::vector> new_messages; - for(const Json::Value &event_item_json : events_json) { - if(!event_item_json.isObject()) + for(const rapidjson::Value &event_item_json : events_json.GetArray()) { + if(!event_item_json.IsObject()) continue; - const Json::Value &type_json = event_item_json["type"]; - if(!type_json.isString() || strcmp(type_json.asCString(), "m.room.message") != 0) + const rapidjson::Value &type_json = GetMember(event_item_json, "type"); + if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.message") != 0) continue; - const Json::Value &sender_json = event_item_json["sender"]; - if(!sender_json.isString()) + const rapidjson::Value &sender_json = GetMember(event_item_json, "sender"); + if(!sender_json.IsString()) continue; - std::string sender_json_str = sender_json.asString(); + std::string sender_json_str = sender_json.GetString(); - const Json::Value &event_id_json = event_item_json["event_id"]; - if(!event_id_json.isString()) + const rapidjson::Value &event_id_json = GetMember(event_item_json, "event_id"); + if(!event_id_json.IsString()) continue; - std::string event_id_str = event_id_json.asString(); + std::string event_id_str = event_id_json.GetString(); - const Json::Value &content_json = event_item_json["content"]; - if(!content_json.isObject()) + const rapidjson::Value &content_json = GetMember(event_item_json, "content"); + if(!content_json.IsObject()) continue; - const Json::Value &content_type = content_json["msgtype"]; - if(!content_type.isString()) + const rapidjson::Value &content_type = GetMember(content_json, "msgtype"); + if(!content_type.IsString()) continue; auto user = room_data->get_user_by_id(sender_json_str); @@ -569,22 +578,22 @@ namespace QuickMedia { continue; } - const Json::Value &body_json = content_json["body"]; - if(!body_json.isString()) + const rapidjson::Value &body_json = GetMember(content_json, "body"); + if(!body_json.IsString()) continue; time_t timestamp = 0; - const Json::Value &origin_server_ts = event_item_json["origin_server_ts"]; - if(origin_server_ts.isNumeric()) - timestamp = origin_server_ts.asInt64(); + const rapidjson::Value &origin_server_ts = GetMember(event_item_json, "origin_server_ts"); + if(origin_server_ts.IsNumber()) + timestamp = origin_server_ts.GetInt64(); std::string replaces_event_id; - const Json::Value &relates_to_json = content_json["m.relates_to"]; - if(relates_to_json.isObject()) { - const Json::Value &replaces_event_id_json = relates_to_json["event_id"]; - const Json::Value &rel_type_json = relates_to_json["rel_type"]; - if(replaces_event_id_json.isString() && rel_type_json.isString() && strcmp(rel_type_json.asCString(), "m.replace") == 0) - replaces_event_id = replaces_event_id_json.asString(); + const rapidjson::Value &relates_to_json = GetMember(content_json, "m.relates_to"); + if(relates_to_json.IsObject()) { + const rapidjson::Value &replaces_event_id_json = GetMember(relates_to_json, "event_id"); + const rapidjson::Value &rel_type_json = GetMember(relates_to_json, "rel_type"); + if(replaces_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) + replaces_event_id = replaces_event_id_json.GetString(); } auto message = std::make_shared(); @@ -592,48 +601,48 @@ namespace QuickMedia { // TODO: Also show joins, leave, invites, bans, kicks, mutes, etc - if(strcmp(content_type.asCString(), "m.text") == 0) { + if(strcmp(content_type.GetString(), "m.text") == 0) { message->type = MessageType::TEXT; - } else if(strcmp(content_type.asCString(), "m.image") == 0) { - const Json::Value &url_json = content_json["url"]; - if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0) + } else if(strcmp(content_type.GetString(), "m.image") == 0) { + const rapidjson::Value &url_json = GetMember(content_json, "url"); + if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; - message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6); + message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); message->type = MessageType::IMAGE; - } else if(strcmp(content_type.asCString(), "m.video") == 0) { - const Json::Value &url_json = content_json["url"]; - if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0) + } else if(strcmp(content_type.GetString(), "m.video") == 0) { + const rapidjson::Value &url_json = GetMember(content_json, "url"); + if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; - message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6); + message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); message->type = MessageType::VIDEO; - } else if(strcmp(content_type.asCString(), "m.audio") == 0) { - const Json::Value &url_json = content_json["url"]; - if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0) + } else if(strcmp(content_type.GetString(), "m.audio") == 0) { + const rapidjson::Value &url_json = GetMember(content_json, "url"); + if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; - message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6); + message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); message->type = MessageType::AUDIO; - } else if(strcmp(content_type.asCString(), "m.file") == 0) { - const Json::Value &url_json = content_json["url"]; - if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0) + } else if(strcmp(content_type.GetString(), "m.file") == 0) { + const rapidjson::Value &url_json = GetMember(content_json, "url"); + if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; - message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6); + message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); message->type = MessageType::FILE; - } else if(strcmp(content_type.asCString(), "m.emote") == 0) { // this is a /me message, TODO: show /me messages differently + } else if(strcmp(content_type.GetString(), "m.emote") == 0) { // this is a /me message, TODO: show /me messages differently message->type = MessageType::TEXT; prefix = "*" + user->display_name + "* "; - } else if(strcmp(content_type.asCString(), "m.notice") == 0) { // TODO: show notices differently + } else if(strcmp(content_type.GetString(), "m.notice") == 0) { // TODO: show notices differently message->type = MessageType::TEXT; prefix = "* NOTICE * "; - } else if(strcmp(content_type.asCString(), "m.location") == 0) { // TODO: show locations differently - const Json::Value &geo_uri_json = content_json["geo_uri"]; - if(geo_uri_json.isString()) - prefix = geo_uri_json.asString() + " | "; + } else if(strcmp(content_type.GetString(), "m.location") == 0) { // TODO: show locations differently + const rapidjson::Value &geo_uri_json = GetMember(content_json, "geo_uri"); + if(geo_uri_json.IsString()) + prefix = geo_uri_json.GetString() + std::string(" | "); message->type = MessageType::TEXT; message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); @@ -643,7 +652,7 @@ namespace QuickMedia { message->user = user; message->event_id = event_id_str; - message->body = prefix + body_json.asString(); + message->body = prefix + body_json.GetString(); message->replaces_event_id = std::move(replaces_event_id); // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions) if(has_unread_notifications && !username.empty()) @@ -686,55 +695,55 @@ namespace QuickMedia { return result; } - void Matrix::events_set_room_name(const Json::Value &events_json, RoomData *room_data) { - if(!events_json.isArray()) + void Matrix::events_set_room_name(const rapidjson::Value &events_json, RoomData *room_data) { + if(!events_json.IsArray()) return; - for(const Json::Value &event_item_json : events_json) { - if(!event_item_json.isObject()) + for(const rapidjson::Value &event_item_json : events_json.GetArray()) { + if(!event_item_json.IsObject()) continue; - const Json::Value &type_json = event_item_json["type"]; - if(!type_json.isString() || strcmp(type_json.asCString(), "m.room.name") != 0) + const rapidjson::Value &type_json = GetMember(event_item_json, "type"); + if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.name") != 0) continue; - const Json::Value &content_json = event_item_json["content"]; - if(!content_json.isObject()) + const rapidjson::Value &content_json = GetMember(event_item_json, "content"); + if(!content_json.IsObject()) continue; - const Json::Value &name_json = content_json["name"]; - if(!name_json.isString()) + const rapidjson::Value &name_json = GetMember(content_json, "name"); + if(!name_json.IsString()) continue; - room_data->name = name_json.asString(); + room_data->name = name_json.GetString(); } std::vector> users_excluding_me; if(room_data->name.empty() || room_data->avatar_url.empty()) users_excluding_me = room_data->get_users_excluding_me(user_id); // TODO: What about thread safety with user_id? its reset in /logout - for(const Json::Value &event_item_json : events_json) { - if(!event_item_json.isObject()) + for(const rapidjson::Value &event_item_json : events_json.GetArray()) { + if(!event_item_json.IsObject()) continue; - const Json::Value &type_json = event_item_json["type"]; - if(!type_json.isString() || strcmp(type_json.asCString(), "m.room.create") != 0) + const rapidjson::Value &type_json = GetMember(event_item_json, "type"); + if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.create") != 0) continue; - const Json::Value &content_json = event_item_json["content"]; - if(!content_json.isObject()) + const rapidjson::Value &content_json = GetMember(event_item_json, "content"); + if(!content_json.IsObject()) continue; - const Json::Value &creator_json = content_json["creator"]; - if(!creator_json.isString()) + const rapidjson::Value &creator_json = GetMember(content_json, "creator"); + if(!creator_json.IsString()) continue; if(room_data->name.empty()) - room_data->name = combine_user_display_names_for_room_name(users_excluding_me, creator_json.asString()); + room_data->name = combine_user_display_names_for_room_name(users_excluding_me, creator_json.GetString()); if(room_data->avatar_url.empty()) { if(users_excluding_me.empty()) { - auto user = room_data->get_user_by_id(creator_json.asString()); + auto user = room_data->get_user_by_id(creator_json.GetString()); if(user) room_data->avatar_url = user->avatar_url; } else { @@ -744,23 +753,23 @@ namespace QuickMedia { } } - for(const Json::Value &event_item_json : events_json) { - if(!event_item_json.isObject()) + for(const rapidjson::Value &event_item_json : events_json.GetArray()) { + if(!event_item_json.IsObject()) continue; - const Json::Value &type_json = event_item_json["type"]; - if(!type_json.isString() || strcmp(type_json.asCString(), "m.room.avatar") != 0) + const rapidjson::Value &type_json = GetMember(event_item_json, "type"); + if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.avatar") != 0) continue; - const Json::Value &content_json = event_item_json["content"]; - if(!content_json.isObject()) + const rapidjson::Value &content_json = GetMember(event_item_json, "content"); + if(!content_json.IsObject()) continue; - const Json::Value &url_json = content_json["url"]; - if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0) + const rapidjson::Value &url_json = GetMember(content_json, "url"); + if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; - std::string url_json_str = url_json.asCString() + 6; + std::string url_json_str = url_json.GetString() + 6; room_data->avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + std::move(url_json_str) + "?width=32&height=32&method=crop"; } } @@ -776,43 +785,43 @@ namespace QuickMedia { } } - Json::Value request_data(Json::objectValue); - request_data["lazy_load_members"] = true; + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("lazy_load_members", true, request_data.GetAllocator()); - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-H", "Authorization: Bearer " + access_token } }; - std::string filter = url_param_encode(Json::writeString(builder, std::move(request_data))); + std::string filter = url_param_encode(buffer.GetString()); char url[512]; snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/messages?from=%s&limit=20&dir=b&filter=%s", homeserver.c_str(), room_data->id.c_str(), from.c_str(), filter.c_str()); - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - if(!json_root.isObject()) + if(!json_root.IsObject()) return PluginResult::ERR; - const Json::Value &state_json = json_root["state"]; + const rapidjson::Value &state_json = GetMember(json_root, "state"); events_add_user_info(state_json, room_data.get()); events_set_room_name(state_json, room_data.get()); - const Json::Value &chunk_json = json_root["chunk"]; + const rapidjson::Value &chunk_json = GetMember(json_root, "chunk"); events_add_messages(chunk_json, room_data, MessageDirection::BEFORE, nullptr, false); - const Json::Value &end_json = json_root["end"]; - if(!end_json.isString()) { + const rapidjson::Value &end_json = GetMember(json_root, "end"); + if(!end_json.IsString()) { fprintf(stderr, "Warning: matrix messages response is missing 'end', this could happen if we received the very first messages in the room\n"); return PluginResult::OK; } - room_data->prev_batch = end_json.asString(); + room_data->prev_batch = end_json.GetString(); return PluginResult::OK; } @@ -881,71 +890,71 @@ namespace QuickMedia { }); } - Json::Value request_data(Json::objectValue); - request_data["msgtype"] = (file_info ? content_type_to_message_type(file_info->content_type) : "m.text"); - request_data["body"] = body; + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("msgtype", rapidjson::StringRef(file_info ? content_type_to_message_type(file_info->content_type) : "m.text"), request_data.GetAllocator()); + request_data.AddMember("body", rapidjson::StringRef(body.c_str()), request_data.GetAllocator()); if(contains_formatted_text) { - request_data["format"] = "org.matrix.custom.html"; - request_data["formatted_body"] = std::move(formatted_body); + request_data.AddMember("format", "org.matrix.custom.html", request_data.GetAllocator()); + request_data.AddMember("formatted_body", rapidjson::StringRef(formatted_body.c_str()), request_data.GetAllocator()); } // TODO: Add hashblur? if(file_info) { - Json::Value info_json(Json::objectValue); - info_json["size"] = file_info->file_size; - info_json["mimetype"] = content_type_to_string(file_info->content_type); + rapidjson::Value info_json(rapidjson::kObjectType); + info_json.AddMember("size", file_info->file_size, request_data.GetAllocator()); + info_json.AddMember("mimetype", rapidjson::StringRef(content_type_to_string(file_info->content_type)), request_data.GetAllocator()); if(file_info->dimensions) { - info_json["w"] = file_info->dimensions->width; - info_json["h"] = file_info->dimensions->height; + info_json.AddMember("w", file_info->dimensions->width, request_data.GetAllocator()); + info_json.AddMember("h", file_info->dimensions->height, request_data.GetAllocator()); } if(file_info->duration_seconds) { // TODO: Check for overflow? - info_json["duration"] = (int)file_info->duration_seconds.value() * 1000; + info_json.AddMember("duration", (int)file_info->duration_seconds.value() * 1000, request_data.GetAllocator()); } if(thumbnail_info) { - Json::Value thumbnail_info_json(Json::objectValue); - thumbnail_info_json["size"] = thumbnail_info->file_size; - thumbnail_info_json["mimetype"] = content_type_to_string(thumbnail_info->content_type); + rapidjson::Value thumbnail_info_json(rapidjson::kObjectType); + thumbnail_info_json.AddMember("size", thumbnail_info->file_size, request_data.GetAllocator()); + thumbnail_info_json.AddMember("mimetype", rapidjson::StringRef(content_type_to_string(thumbnail_info->content_type)), request_data.GetAllocator()); if(thumbnail_info->dimensions) { - thumbnail_info_json["w"] = thumbnail_info->dimensions->width; - thumbnail_info_json["h"] = thumbnail_info->dimensions->height; + thumbnail_info_json.AddMember("w", thumbnail_info->dimensions->width, request_data.GetAllocator()); + thumbnail_info_json.AddMember("h", thumbnail_info->dimensions->height, request_data.GetAllocator()); } - info_json["thumbnail_url"] = thumbnail_info->content_uri; - info_json["info"] = std::move(thumbnail_info_json); + info_json.AddMember("thumbnail_url", rapidjson::StringRef(thumbnail_info->content_uri.c_str()), request_data.GetAllocator()); + info_json.AddMember("info", std::move(thumbnail_info_json), request_data.GetAllocator()); } - request_data["info"] = std::move(info_json); - request_data["url"] = file_info->content_uri; + request_data.AddMember("info", std::move(info_json), request_data.GetAllocator()); + request_data.AddMember("url", rapidjson::StringRef(file_info->content_uri.c_str()), request_data.GetAllocator()); } - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-X", "PUT" }, { "-H", "content-type: application/json" }, { "-H", "Authorization: Bearer " + access_token }, - { "--data-binary", Json::writeString(builder, std::move(request_data)) } + { "--data-binary", buffer.GetString() } }; char request_url[512]; snprintf(request_url, sizeof(request_url), "%s/_matrix/client/r0/rooms/%s/send/m.room.message/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, request_url, std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - if(!json_root.isObject()) + if(!json_root.IsObject()) return PluginResult::ERR; - const Json::Value &event_id_json = json_root["event_id"]; - if(!event_id_json.isString()) + const rapidjson::Value &event_id_json = GetMember(json_root, "event_id"); + if(!event_id_json.IsString()) return PluginResult::ERR; - fprintf(stderr, "Matrix post message, response event id: %s\n", event_id_json.asCString()); + fprintf(stderr, "Matrix post message, response event id: %s\n", event_id_json.GetString()); return PluginResult::OK; } @@ -1041,45 +1050,48 @@ namespace QuickMedia { std::string random_readable_chars = random_characters_to_readable_string(random_characters, sizeof(random_characters)); - Json::Value in_reply_to_json(Json::objectValue); - in_reply_to_json["event_id"] = relates_to_message_original->event_id; + rapidjson::Document in_reply_to_json(rapidjson::kObjectType); + in_reply_to_json.AddMember("event_id", rapidjson::StringRef(relates_to_message_original->event_id.c_str()), in_reply_to_json.GetAllocator()); + + rapidjson::Document relates_to_json(rapidjson::kObjectType); + relates_to_json.AddMember("m.in_reply_to", std::move(in_reply_to_json), relates_to_json.GetAllocator()); - Json::Value relates_to_json(Json::objectValue); - relates_to_json["m.in_reply_to"] = std::move(in_reply_to_json); + std::string message_reply_body = create_body_for_message_reply(relates_to_message_raw, body); // Yes, the reply is to the edited message but the event_id reference is to the original message... + std::string formatted_message_reply_body = create_formatted_body_for_message_reply(relates_to_message_raw, body); - Json::Value request_data(Json::objectValue); - request_data["msgtype"] = "m.text"; // TODO: Allow image reply? element doesn't do that but we could! - request_data["body"] = create_body_for_message_reply(relates_to_message_raw, body); // Yes, the reply is to the edited message but the event_id reference is to the original message... - request_data["format"] = "org.matrix.custom.html"; - request_data["formatted_body"] = create_formatted_body_for_message_reply(relates_to_message_raw, body); - request_data["m.relates_to"] = std::move(relates_to_json); + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("msgtype", "m.text", request_data.GetAllocator()); // TODO: Allow image reply? element doesn't do that but we could! + request_data.AddMember("body", rapidjson::StringRef(message_reply_body.c_str()), request_data.GetAllocator()); + request_data.AddMember("format", "org.matrix.custom.html", request_data.GetAllocator()); + request_data.AddMember("formatted_body", rapidjson::StringRef(formatted_message_reply_body.c_str()), request_data.GetAllocator()); + request_data.AddMember("m.relates_to", std::move(relates_to_json), request_data.GetAllocator()); - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-X", "PUT" }, { "-H", "content-type: application/json" }, { "-H", "Authorization: Bearer " + access_token }, - { "--data-binary", Json::writeString(builder, std::move(request_data)) } + { "--data-binary", buffer.GetString() } }; char request_url[512]; snprintf(request_url, sizeof(request_url), "%s/_matrix/client/r0/rooms/%s/send/m.room.message/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, request_url, std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - if(!json_root.isObject()) + if(!json_root.IsObject()) return PluginResult::ERR; - const Json::Value &event_id_json = json_root["event_id"]; - if(!event_id_json.isString()) + const rapidjson::Value &event_id_json = GetMember(json_root, "event_id"); + if(!event_id_json.IsString()) return PluginResult::ERR; - fprintf(stderr, "Matrix post reply, response event id: %s\n", event_id_json.asCString()); + fprintf(stderr, "Matrix post reply, response event id: %s\n", event_id_json.GetString()); return PluginResult::OK; } @@ -1124,54 +1136,58 @@ namespace QuickMedia { return true; }); - Json::Value new_content_json(Json::objectValue); - new_content_json["msgtype"] = "m.text"; - new_content_json["body"] = body; + rapidjson::Document new_content_json(rapidjson::kObjectType); + new_content_json.AddMember("msgtype", "m.text", new_content_json.GetAllocator()); + new_content_json.AddMember("body", rapidjson::StringRef(body.c_str()), new_content_json.GetAllocator()); if(contains_formatted_text) { - new_content_json["format"] = "org.matrix.custom.html"; - new_content_json["formatted_body"] = formatted_body; + new_content_json.AddMember("format", "org.matrix.custom.html", new_content_json.GetAllocator()); + new_content_json.AddMember("formatted_body", rapidjson::StringRef(formatted_body.c_str()), new_content_json.GetAllocator()); } - Json::Value relates_to_json(Json::objectValue); - relates_to_json["event_id"] = relates_to_message_original->event_id; - relates_to_json["rel_type"] = "m.replace"; + rapidjson::Document relates_to_json(rapidjson::kObjectType); + relates_to_json.AddMember("event_id", rapidjson::StringRef(relates_to_message_original->event_id.c_str()), relates_to_json.GetAllocator()); + relates_to_json.AddMember("rel_type", "m.replace", relates_to_json.GetAllocator()); - Json::Value request_data(Json::objectValue); - request_data["msgtype"] = "m.text"; // TODO: Allow other types of edits - request_data["body"] = " * " + body; + std::string body_edit_str = " * " + body; + std::string formatted_body_edit_str; + + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("msgtype", "m.text", request_data.GetAllocator()); // TODO: Allow other types of edits + request_data.AddMember("body", rapidjson::StringRef(body_edit_str.c_str()), request_data.GetAllocator()); if(contains_formatted_text) { - request_data["format"] = "org.matrix.custom.html"; - request_data["formatted_body"] = " * " + formatted_body; + formatted_body_edit_str = " * " + formatted_body; + request_data.AddMember("format", "org.matrix.custom.html", request_data.GetAllocator()); + request_data.AddMember("formatted_body", rapidjson::StringRef(formatted_body_edit_str.c_str()), request_data.GetAllocator()); } - request_data["m.new_content"] = std::move(new_content_json); - request_data["m.relates_to"] = std::move(relates_to_json); + request_data.AddMember("m.new_content", std::move(new_content_json), request_data.GetAllocator()); + request_data.AddMember("m.relates_to", std::move(relates_to_json), request_data.GetAllocator()); - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-X", "PUT" }, { "-H", "content-type: application/json" }, { "-H", "Authorization: Bearer " + access_token }, - { "--data-binary", Json::writeString(builder, std::move(request_data)) } + { "--data-binary", buffer.GetString() } }; char request_url[512]; snprintf(request_url, sizeof(request_url), "%s/_matrix/client/r0/rooms/%s/send/m.room.message/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, request_url, std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - if(!json_root.isObject()) + if(!json_root.IsObject()) return PluginResult::ERR; - const Json::Value &event_id_json = json_root["event_id"]; - if(!event_id_json.isString()) + const rapidjson::Value &event_id_json = GetMember(json_root, "event_id"); + if(!event_id_json.IsString()) return PluginResult::ERR; - fprintf(stderr, "Matrix post edit, response event id: %s\n", event_id_json.asCString()); + fprintf(stderr, "Matrix post edit, response event id: %s\n", event_id_json.GetString()); return PluginResult::OK; } @@ -1183,70 +1199,70 @@ namespace QuickMedia { auto replaced_message = room_data->get_message_by_id(message->replaces_event_id); if(!replaced_message) { - Json::Value request_data(Json::objectValue); - request_data["lazy_load_members"] = true; + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("lazy_load_members", true, request_data.GetAllocator()); - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-H", "Authorization: Bearer " + access_token } }; - std::string filter = url_param_encode(Json::writeString(builder, std::move(request_data))); + std::string filter = url_param_encode(buffer.GetString()); char url[512]; snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/context/%s?limit=0&filter=%s", homeserver.c_str(), room_data->id.c_str(), message->event_id.c_str(), filter.c_str()); - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); if(download_result != DownloadResult::OK) return nullptr; - if(!json_root.isObject()) + if(!json_root.IsObject()) return nullptr; - const Json::Value &event_json = json_root["event"]; - if(!event_json.isObject()) + const rapidjson::Value &event_json = GetMember(json_root, "event"); + if(!event_json.IsObject()) return nullptr; - const Json::Value &event_id_json = event_json["event_id"]; - if(!event_id_json.isString()) + const rapidjson::Value &event_id_json = GetMember(event_json, "event_id"); + if(!event_id_json.IsString()) return nullptr; - const Json::Value &content_json = event_json["content"]; - if(!content_json.isObject()) + const rapidjson::Value &content_json = GetMember(event_json, "content"); + if(!content_json.IsObject()) return nullptr; - const Json::Value &body_json = content_json["body"]; - if(!body_json.isString()) + const rapidjson::Value &body_json = GetMember(content_json, "body"); + if(!body_json.IsString()) return nullptr; std::string replaces_event_id; - const Json::Value &relates_to_json = content_json["m.relates_to"]; - if(relates_to_json.isObject()) { - const Json::Value &event_id_json = relates_to_json["event_id"]; - const Json::Value &rel_type_json = relates_to_json["rel_type"]; - if(event_id_json.isString() && rel_type_json.isString() && strcmp(rel_type_json.asCString(), "m.replace") == 0) - replaces_event_id = event_id_json.asString(); + const rapidjson::Value &relates_to_json = GetMember(content_json, "m.relates_to"); + if(relates_to_json.IsObject()) { + const rapidjson::Value &event_id_json = GetMember(relates_to_json, "event_id"); + const rapidjson::Value &rel_type_json = GetMember(relates_to_json, "rel_type"); + if(event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) + replaces_event_id = event_id_json.GetString(); } - const Json::Value &content_type = content_json["msgtype"]; - if(!content_type.isString()) + const rapidjson::Value &content_type = GetMember(content_json, "msgtype"); + if(!content_type.IsString()) return nullptr; auto new_message = std::make_shared(); - new_message->event_id = event_id_json.asString(); + new_message->event_id = event_id_json.GetString(); new_message->replaces_event_id = std::move(replaces_event_id); - if(strcmp(content_type.asCString(), "m.text") == 0) { + if(strcmp(content_type.GetString(), "m.text") == 0) { new_message->type = MessageType::TEXT; - } else if(strcmp(content_type.asCString(), "m.image") == 0) { + } else if(strcmp(content_type.GetString(), "m.image") == 0) { new_message->type = MessageType::IMAGE; - } else if(strcmp(content_type.asCString(), "m.video") == 0) { + } else if(strcmp(content_type.GetString(), "m.video") == 0) { new_message->type = MessageType::VIDEO; - } else if(strcmp(content_type.asCString(), "m.audio") == 0) { + } else if(strcmp(content_type.GetString(), "m.audio") == 0) { new_message->type = MessageType::AUDIO; - } else if(strcmp(content_type.asCString(), "m.file") == 0) { + } else if(strcmp(content_type.GetString(), "m.file") == 0) { new_message->type = MessageType::FILE; } else { return nullptr; @@ -1351,87 +1367,87 @@ namespace QuickMedia { char url[512]; snprintf(url, sizeof(url), "%s/_matrix/media/r0/upload?filename=%s", homeserver.c_str(), filename_escaped.c_str()); - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true, &err_msg); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - if(!json_root.isObject()) { + if(!json_root.IsObject()) { err_msg = "Got corrupt response from server"; return PluginResult::ERR; } - const Json::Value &error_json = json_root["error"]; - if(error_json.isString()) { - err_msg = error_json.asString(); + const rapidjson::Value &error_json = GetMember(json_root, "error"); + if(error_json.IsString()) { + err_msg = error_json.GetString(); return PluginResult::ERR; } - const Json::Value &content_uri_json = json_root["content_uri"]; - if(!content_uri_json.isString()) { + const rapidjson::Value &content_uri_json = GetMember(json_root, "content_uri"); + if(!content_uri_json.IsString()) { err_msg = "Missing content_uri is server response"; return PluginResult::ERR; } - fprintf(stderr, "Matrix upload, response content uri: %s\n", content_uri_json.asCString()); - file_info.content_uri = content_uri_json.asString(); + fprintf(stderr, "Matrix upload, response content uri: %s\n", content_uri_json.GetString()); + file_info.content_uri = content_uri_json.GetString(); return PluginResult::OK; } PluginResult Matrix::login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg) { - Json::Value identifier_json(Json::objectValue); - identifier_json["type"] = "m.id.user"; // TODO: What if the server doesn't support this login type? redirect to sso web page etc - identifier_json["user"] = username; + rapidjson::Document identifier_json(rapidjson::kObjectType); + identifier_json.AddMember("type", "m.id.user", identifier_json.GetAllocator()); // TODO: What if the server doesn't support this login type? redirect to sso web page etc + identifier_json.AddMember("user", rapidjson::StringRef(username.c_str()), identifier_json.GetAllocator()); - Json::Value request_data(Json::objectValue); - request_data["type"] = "m.login.password"; - request_data["identifier"] = std::move(identifier_json); - request_data["password"] = password; - request_data["initial_device_display_name"] = "QuickMedia"; // :^) + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("type", "m.login.password", request_data.GetAllocator()); + request_data.AddMember("identifier", std::move(identifier_json), request_data.GetAllocator()); + request_data.AddMember("password", rapidjson::StringRef(password.c_str()), request_data.GetAllocator()); + request_data.AddMember("initial_device_display_name", "QuickMedia", request_data.GetAllocator()); // :^) - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-X", "POST" }, { "-H", "content-type: application/json" }, - { "--data-binary", Json::writeString(builder, std::move(request_data)) } + { "--data-binary", buffer.GetString() } }; - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, homeserver + "/_matrix/client/r0/login", std::move(additional_args), true, &err_msg); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - if(!json_root.isObject()) { + if(!json_root.IsObject()) { err_msg = "Failed to parse matrix login response"; return PluginResult::ERR; } - const Json::Value &error_json = json_root["error"]; - if(error_json.isString()) { - err_msg = error_json.asString(); + const rapidjson::Value &error_json = GetMember(json_root, "error"); + if(error_json.IsString()) { + err_msg = error_json.GetString(); return PluginResult::ERR; } - const Json::Value &user_id_json = json_root["user_id"]; - if(!user_id_json.isString()) { + const rapidjson::Value &user_id_json = GetMember(json_root, "user_id"); + if(!user_id_json.IsString()) { err_msg = "Failed to parse matrix login response"; return PluginResult::ERR; } - const Json::Value &access_token_json = json_root["access_token"]; - if(!access_token_json.isString()) { + const rapidjson::Value &access_token_json = GetMember(json_root, "access_token"); + if(!access_token_json.IsString()) { err_msg = "Failed to parse matrix login response"; return PluginResult::ERR; } // Use the user-provided homeserver instead of the one the server tells us about, otherwise this wont work with a proxy // such as pantalaimon - json_root["homeserver"] = homeserver; + json_root.AddMember("homeserver", rapidjson::StringRef(homeserver.c_str()), request_data.GetAllocator()); - this->user_id = user_id_json.asString(); + this->user_id = user_id_json.GetString(); this->username = extract_user_name_from_user_id(this->user_id); - this->access_token = access_token_json.asString(); + this->access_token = access_token_json.GetString(); this->homeserver = homeserver; // TODO: Handle well_known field. The spec says clients SHOULD handle it if its provided @@ -1484,41 +1500,41 @@ namespace QuickMedia { Message *message_typed = (Message*)message; // request_data could contains "reason", maybe it should be added sometime in the future? - Json::Value request_data(Json::objectValue); - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::Value request_data(rapidjson::kObjectType); + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-X", "PUT" }, { "-H", "content-type: application/json" }, { "-H", "Authorization: Bearer " + access_token }, - { "--data-binary", Json::writeString(builder, std::move(request_data)) } + { "--data-binary", buffer.GetString() } }; char url[512]; snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/redact/%s/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), message_typed->event_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true, &err_msg); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - if(!json_root.isObject()) { + if(!json_root.IsObject()) { err_msg = "Failed to parse matrix login response"; return PluginResult::ERR; } - const Json::Value &error_json = json_root["error"]; - if(error_json.isString()) { - err_msg = error_json.asString(); + const rapidjson::Value &error_json = GetMember(json_root, "error"); + if(error_json.IsString()) { + err_msg = error_json.GetString(); return PluginResult::ERR; } - const Json::Value &event_id_json = json_root["event_id"]; - if(!event_id_json.isString()) + const rapidjson::Value &event_id_json = GetMember(json_root, "event_id"); + if(!event_id_json.IsString()) return PluginResult::ERR; - fprintf(stderr, "Matrix delete message, response event id: %s\n", event_id_json.asCString()); + fprintf(stderr, "Matrix delete message, response event id: %s\n", event_id_json.GetString()); return PluginResult::OK; } @@ -1530,39 +1546,37 @@ namespace QuickMedia { return PluginResult::ERR; } - Json::Value json_root; - Json::CharReaderBuilder json_builder; - std::unique_ptr json_reader(json_builder.newCharReader()); - std::string json_errors; - if(!json_reader->parse(&session_json_content[0], &session_json_content[session_json_content.size()], &json_root, &json_errors)) { - fprintf(stderr, "Matrix cached session parse error: %s\n", json_errors.c_str()); + rapidjson::Document json_root; + rapidjson::ParseResult parse_result = json_root.Parse(session_json_content.c_str(), session_json_content.size()); + if(parse_result.IsError()) { + fprintf(stderr, "Matrix cached session parse error: %d\n", parse_result.Code()); return PluginResult::ERR; } - if(!json_root.isObject()) + if(!json_root.IsObject()) return PluginResult::ERR; - const Json::Value &user_id_json = json_root["user_id"]; - if(!user_id_json.isString()) { + const rapidjson::Value &user_id_json = GetMember(json_root, "user_id"); + if(!user_id_json.IsString()) { fprintf(stderr, "Failed to parse matrix cached session response\n"); return PluginResult::ERR; } - const Json::Value &access_token_json = json_root["access_token"]; - if(!access_token_json.isString()) { + const rapidjson::Value &access_token_json = GetMember(json_root, "access_token"); + if(!access_token_json.IsString()) { fprintf(stderr, "Failed to parse matrix cached session response\n"); return PluginResult::ERR; } - const Json::Value &homeserver_json = json_root["homeserver"]; - if(!homeserver_json.isString()) { + const rapidjson::Value &homeserver_json = GetMember(json_root, "homeserver"); + if(!homeserver_json.IsString()) { fprintf(stderr, "Failed to parse matrix cached session response\n"); return PluginResult::ERR; } - std::string user_id = user_id_json.asString(); - std::string access_token = access_token_json.asString(); - std::string homeserver = homeserver_json.asString(); + std::string user_id = user_id_json.GetString(); + std::string access_token = access_token_json.GetString(); + std::string homeserver = homeserver_json.GetString(); std::vector additional_args = { { "-H", "Authorization: Bearer " + access_token } @@ -1583,18 +1597,18 @@ namespace QuickMedia { } PluginResult Matrix::on_start_typing(const std::string &room_id) { - Json::Value request_data(Json::objectValue); - request_data["typing"] = true; - request_data["timeout"] = 30000; // 30 sec timeout + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("typing", true, request_data.GetAllocator()); + request_data.AddMember("timeout", 30000, request_data.GetAllocator()); // 30 sec timeout - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-X", "PUT" }, { "-H", "content-type: application/json" }, - { "--data-binary", Json::writeString(builder, std::move(request_data)) }, + { "--data-binary", buffer.GetString() }, { "-H", "Authorization: Bearer " + access_token } }; @@ -1606,17 +1620,17 @@ namespace QuickMedia { } PluginResult Matrix::on_stop_typing(const std::string &room_id) { - Json::Value request_data(Json::objectValue); - request_data["typing"] = false; + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("typing", false, request_data.GetAllocator()); - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-X", "PUT" }, { "-H", "content-type: application/json" }, - { "--data-binary", Json::writeString(builder, std::move(request_data)) }, + { "--data-binary", buffer.GetString() }, { "-H", "Authorization: Bearer " + access_token } }; @@ -1628,19 +1642,19 @@ namespace QuickMedia { } PluginResult Matrix::set_read_marker(const std::string &room_id, const Message *message) { - Json::Value request_data(Json::objectValue); - request_data["m.fully_read"] = message->event_id; - request_data["m.read"] = message->event_id; - request_data["m.hidden"] = false; // What is this for? element sends it but its not part of the documentation. Is it for hiding read receipt from other users? in that case, TODO: make it configurable + rapidjson::Document request_data(rapidjson::kObjectType); + request_data.AddMember("m.fully_read", rapidjson::StringRef(message->event_id.c_str()), request_data.GetAllocator()); + request_data.AddMember("m.read", rapidjson::StringRef(message->event_id.c_str()), request_data.GetAllocator()); + request_data.AddMember("m.hidden", false, request_data.GetAllocator()); // What is this for? element sends it but its not part of the documentation. Is it for hiding read receipt from other users? in that case, TODO: make it configurable - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + request_data.Accept(writer); std::vector additional_args = { { "-X", "POST" }, { "-H", "content-type: application/json" }, - { "--data-binary", Json::writeString(builder, std::move(request_data)) }, + { "--data-binary", buffer.GetString() }, { "-H", "Authorization: Bearer " + access_token } }; @@ -1677,18 +1691,18 @@ namespace QuickMedia { char url[512]; snprintf(url, sizeof(url), "%s/_matrix/media/r0/config", homeserver.c_str()); - Json::Value json_root; + rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - if(!json_root.isObject()) + if(!json_root.IsObject()) return PluginResult::ERR; - const Json::Value &upload_size_json = json_root["m.upload.size"]; - if(!upload_size_json.isNumeric()) + const rapidjson::Value &upload_size_json = GetMember(json_root, "m.upload.size"); + if(!upload_size_json.IsNumber()) return PluginResult::ERR; - upload_limit = upload_size_json.asInt(); + upload_limit = upload_size_json.GetInt(); *upload_size = upload_limit.value(); return PluginResult::OK; } @@ -1715,7 +1729,7 @@ namespace QuickMedia { room_data_by_id.insert(std::make_pair(room->id, room)); } - DownloadResult Matrix::download_json(Json::Value &result, const std::string &url, std::vector additional_args, bool use_browser_useragent, std::string *err_msg) const { + DownloadResult Matrix::download_json(rapidjson::Document &result, const std::string &url, std::vector additional_args, bool use_browser_useragent, std::string *err_msg) const { std::string server_response; if(download_to_string(url, server_response, std::move(additional_args), use_tor, use_browser_useragent, err_msg == nullptr) != DownloadResult::OK) { if(err_msg) @@ -1723,16 +1737,12 @@ namespace QuickMedia { return DownloadResult::NET_ERR; } - if(server_response.empty()) - return DownloadResult::OK; - - Json::CharReaderBuilder json_builder; - std::unique_ptr json_reader(json_builder.newCharReader()); - std::string json_errors; - if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &result, &json_errors)) { - fprintf(stderr, "download_json error: %s\n", json_errors.c_str()); + rapidjson::ParseResult parse_result = result.Parse(server_response.c_str(), server_response.size()); + if(parse_result.IsError()) { + std::string error_code_str = std::to_string(parse_result.Code()); + fprintf(stderr, "download_json error: %s\n", error_code_str.c_str()); if(err_msg) - *err_msg = std::move(json_errors); + *err_msg = "Json parse error: " + std::move(error_code_str); return DownloadResult::ERR; } -- cgit v1.2.3