1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 21:37:36 +00:00

ClipboardHistory: Store clipboard history as valid JSON

Previously, the ClipboardHistory.json file contained a series of
individual JSON objects for history items, separated by newlines. This
is convenient for appending a single item, but makes the file itself
invalid JSON.

This commit changes that file to instead contain those objects in a
proper JSON array.
This commit is contained in:
Sam Atkins 2024-02-07 12:33:26 +00:00 committed by Sam Atkins
parent ff3e454565
commit 0b9aee57e7
2 changed files with 26 additions and 39 deletions

View file

@ -111,40 +111,38 @@ void ClipboardHistoryModel::clipboard_content_did_change(ByteString const&)
add_item(data_and_type); add_item(data_and_type);
} }
ErrorOr<void> ClipboardHistoryModel::invalidate_model_and_file(bool rewrite_all) ErrorOr<void> ClipboardHistoryModel::invalidate_model_and_file()
{ {
invalidate(); invalidate();
TRY(write_to_file(rewrite_all)); TRY(write_to_file());
return {}; return {};
} }
void ClipboardHistoryModel::add_item(const GUI::Clipboard::DataAndType& item) void ClipboardHistoryModel::add_item(const GUI::Clipboard::DataAndType& item)
{ {
bool has_deleted_an_item = false;
m_history_items.remove_first_matching([&](ClipboardItem& existing) { m_history_items.remove_first_matching([&](ClipboardItem& existing) {
return existing.data_and_type.data == item.data && existing.data_and_type.mime_type == item.mime_type; return existing.data_and_type.data == item.data && existing.data_and_type.mime_type == item.mime_type;
}); });
if (m_history_items.size() == m_history_limit) { if (m_history_items.size() == m_history_limit) {
m_history_items.take_last(); m_history_items.take_last();
has_deleted_an_item = true;
} }
m_history_items.prepend({ item, Core::DateTime::now() }); m_history_items.prepend({ item, Core::DateTime::now() });
invalidate_model_and_file(has_deleted_an_item).release_value_but_fixme_should_propagate_errors(); invalidate_model_and_file().release_value_but_fixme_should_propagate_errors();
} }
void ClipboardHistoryModel::remove_item(int index) void ClipboardHistoryModel::remove_item(int index)
{ {
m_history_items.remove(index); m_history_items.remove(index);
invalidate_model_and_file(true).release_value_but_fixme_should_propagate_errors(); invalidate_model_and_file().release_value_but_fixme_should_propagate_errors();
} }
void ClipboardHistoryModel::clear() void ClipboardHistoryModel::clear()
{ {
m_history_items.clear(); m_history_items.clear();
invalidate_model_and_file(true).release_value_but_fixme_should_propagate_errors(); invalidate_model_and_file().release_value_but_fixme_should_propagate_errors();
} }
void ClipboardHistoryModel::config_i32_did_change(StringView domain, StringView group, StringView key, i32 value) void ClipboardHistoryModel::config_i32_did_change(StringView domain, StringView group, StringView key, i32 value)
@ -155,7 +153,7 @@ void ClipboardHistoryModel::config_i32_did_change(StringView domain, StringView
if (key == "NumHistoryItems") { if (key == "NumHistoryItems") {
if (value < (int)m_history_items.size()) { if (value < (int)m_history_items.size()) {
m_history_items.remove(value, m_history_items.size() - value); m_history_items.remove(value, m_history_items.size() - value);
invalidate_model_and_file(false).release_value_but_fixme_should_propagate_errors(); invalidate_model_and_file().release_value_but_fixme_should_propagate_errors();
} }
m_history_limit = value; m_history_limit = value;
return; return;
@ -190,14 +188,15 @@ ErrorOr<void> ClipboardHistoryModel::read_from_file(ByteString const& path)
auto read_from_file_impl = [this]() -> ErrorOr<void> { auto read_from_file_impl = [this]() -> ErrorOr<void> {
auto file = TRY(Core::File::open(m_path, Core::File::OpenMode::Read)); auto file = TRY(Core::File::open(m_path, Core::File::OpenMode::Read));
auto buffered_file = TRY(Core::InputBufferedFile::create(move(file))); auto contents = TRY(file->read_until_eof());
auto json = TRY(JsonValue::from_string(contents));
auto buffer = TRY(ByteBuffer::create_uninitialized(PAGE_SIZE)); if (!json.is_array())
return Error::from_string_literal("File contents is not a JSON array.");
while (TRY(buffered_file->can_read_line())) { auto& json_array = json.as_array();
auto line = TRY(buffered_file->read_line(buffer)); for (auto const& item : json_array.values()) {
auto object = TRY(JsonParser { line }.parse()).as_object(); if (!item.is_object())
TRY(m_history_items.try_append(TRY(ClipboardItem::from_json(object)))); return Error::from_string_literal("JSON entry is not an object.");
TRY(m_history_items.try_append(TRY(ClipboardItem::from_json(item.as_object()))));
} }
return {}; return {};
}; };
@ -209,30 +208,18 @@ ErrorOr<void> ClipboardHistoryModel::read_from_file(ByteString const& path)
return {}; return {};
} }
ErrorOr<void> ClipboardHistoryModel::write_to_file(bool rewrite_all) ErrorOr<void> ClipboardHistoryModel::write_to_file()
{ {
if (m_history_items.is_empty()) { auto file = TRY(Core::File::open(m_path, Core::File::OpenMode::Write | Core::File::OpenMode::Truncate));
// This will proceed to empty the file JsonArray array;
rewrite_all = true; array.ensure_capacity(m_history_items.size());
} for (auto const& item : m_history_items) {
auto const write_element = [](Core::File& file, ClipboardItem const& item) -> ErrorOr<void> {
if (!item.data_and_type.mime_type.starts_with("text/"sv)) if (!item.data_and_type.mime_type.starts_with("text/"sv))
return {}; continue;
TRY(file.write_until_depleted(TRY(item.to_json()).to_byte_string().bytes())); TRY(array.append(TRY(item.to_json())));
TRY(file.write_until_depleted("\n"sv.bytes()));
return {};
};
if (!rewrite_all) {
auto file = TRY(Core::File::open(m_path, Core::File::OpenMode::Write | Core::File::OpenMode::Append));
TRY(write_element(*file, m_history_items.first()));
} else {
auto file = TRY(Core::File::open(m_path, Core::File::OpenMode::Write | Core::File::OpenMode::Truncate));
for (auto const& item : m_history_items) {
TRY(write_element(*file, item));
}
} }
auto json_string = array.to_byte_string();
TRY(file->write_until_depleted(json_string.bytes()));
return {}; return {};
} }

View file

@ -45,9 +45,9 @@ public:
bool is_empty() { return m_history_items.is_empty(); } bool is_empty() { return m_history_items.is_empty(); }
ErrorOr<void> read_from_file(ByteString const& path); ErrorOr<void> read_from_file(ByteString const& path);
ErrorOr<void> write_to_file(bool rewrite_all); ErrorOr<void> write_to_file();
ErrorOr<void> invalidate_model_and_file(bool rewrite_all); ErrorOr<void> invalidate_model_and_file();
// ^GUI::Model // ^GUI::Model
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override; virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;