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:
parent
ff3e454565
commit
0b9aee57e7
2 changed files with 26 additions and 39 deletions
|
@ -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 {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue