From 589c280bdcb5938ae95c3a32fe3efcc72addf4c6 Mon Sep 17 00:00:00 2001 From: Alexander Shaduri Date: Sat, 15 Feb 2025 13:22:47 +0400 Subject: [PATCH] Support saving smartctl output to text (using old text format) instead of json (#76). --- src/applib/smartctl_json_ata_parser.cpp | 20 +++++++++++++++++ src/applib/smartctl_json_basic_parser.cpp | 20 +++++++++++++++++ src/applib/smartctl_json_nvme_parser.cpp | 20 +++++++++++++++++ src/applib/smartctl_text_ata_parser.cpp | 10 +++++++++ src/applib/smartctl_text_basic_parser.cpp | 10 +++++++++ src/gui/gsc_info_window.cpp | 27 ++++++++++++++++++++++- 6 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/applib/smartctl_json_ata_parser.cpp b/src/applib/smartctl_json_ata_parser.cpp index f3d6a0c..62fea7c 100644 --- a/src/applib/smartctl_json_ata_parser.cpp +++ b/src/applib/smartctl_json_ata_parser.cpp @@ -198,6 +198,26 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_info( static const std::vector> json_keys = { + {"smartctl/output", _("Smartctl Text Output"), // the old text format + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto table_node = get_node(root_node, "smartctl/output"); + if (table_node.has_value() && table_node->is_array() && !table_node.value().empty()) { + std::vector lines; + for (const auto& entry : table_node.value()) { + lines.emplace_back(entry.get()); + } + StorageProperty p; + p.set_name(key, displayable_name); + p.value = hz::string_join(lines, "\n"); + p.show_in_ui = false; + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format("Error getting key {} from JSON data.", key)); + } + }, + {"device/type", _("Smartctl Device Type"), // nvme, sat, etc. [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) -> hz::ExpectedValue diff --git a/src/applib/smartctl_json_basic_parser.cpp b/src/applib/smartctl_json_basic_parser.cpp index 811edd5..c5f01cd 100644 --- a/src/applib/smartctl_json_basic_parser.cpp +++ b/src/applib/smartctl_json_basic_parser.cpp @@ -75,6 +75,26 @@ hz::ExpectedVoid SmartctlJsonBasicParser::parse_section_bas // 2. Present in devices for which we do not have specialized parsers (USB, etc.) static const std::vector> info_keys = { + {"smartctl/output", _("Smartctl Text Output"), // the old text format + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto table_node = get_node(root_node, "smartctl/output"); + if (table_node.has_value() && table_node->is_array() && !table_node.value().empty()) { + std::vector lines; + for (const auto& entry : table_node.value()) { + lines.emplace_back(entry.get()); + } + StorageProperty p; + p.set_name(key, displayable_name); + p.value = hz::string_join(lines, "\n"); + p.show_in_ui = false; + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format("Error getting key {} from JSON data.", key)); + } + }, + {"device/type", _("Smartctl Device Type"), // nvme, sat, etc. [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) -> hz::ExpectedValue diff --git a/src/applib/smartctl_json_nvme_parser.cpp b/src/applib/smartctl_json_nvme_parser.cpp index e7fc793..c2192dc 100644 --- a/src/applib/smartctl_json_nvme_parser.cpp +++ b/src/applib/smartctl_json_nvme_parser.cpp @@ -114,6 +114,26 @@ hz::ExpectedVoid SmartctlJsonNvmeParser::parse_section_info static const std::vector> json_keys = { + {"smartctl/output", _("Smartctl Text Output"), // the old text format + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto table_node = get_node(root_node, "smartctl/output"); + if (table_node.has_value() && table_node->is_array() && !table_node.value().empty()) { + std::vector lines; + for (const auto& entry : table_node.value()) { + lines.emplace_back(entry.get()); + } + StorageProperty p; + p.set_name(key, displayable_name); + p.value = hz::string_join(lines, "\n"); + p.show_in_ui = false; + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format("Error getting key {} from JSON data.", key)); + } + }, + {"device/type", _("Smartctl Device Type"), // nvme, sat, etc. [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) -> hz::ExpectedValue diff --git a/src/applib/smartctl_text_ata_parser.cpp b/src/applib/smartctl_text_ata_parser.cpp index 60ad664..66bbb08 100644 --- a/src/applib/smartctl_text_ata_parser.cpp +++ b/src/applib/smartctl_text_ata_parser.cpp @@ -231,6 +231,16 @@ hz::ExpectedVoid SmartctlTextAtaParser::parse(std::string_v return hz::Unexpected(SmartctlParserError::IncompatibleVersion, "Incompatible smartctl version."); } + // Full text output + { + StorageProperty p; + p.set_name("smartctl/output", "Smartctl Text Output"); + p.reported_value = smartctl_output; + p.value = p.reported_value; // string-type value + p.show_in_ui = false; + add_property(p); + } + // sections diff --git a/src/applib/smartctl_text_basic_parser.cpp b/src/applib/smartctl_text_basic_parser.cpp index 6dcf658..132fea3 100644 --- a/src/applib/smartctl_text_basic_parser.cpp +++ b/src/applib/smartctl_text_basic_parser.cpp @@ -67,6 +67,16 @@ hz::ExpectedVoid SmartctlTextBasicParser::parse(std::string add_property(p); } + // Full text output + { + StorageProperty p; + p.set_name("smartctl/output", "Smartctl Text Output"); + p.reported_value = output; + p.value = p.reported_value; // string-type value + p.show_in_ui = false; + add_property(p); + } + bool is_raid = false; // Detect type. note: we can't distinguish between sata and scsi (on linux, for -d ata switch). diff --git a/src/gui/gsc_info_window.cpp b/src/gui/gsc_info_window.cpp index 8d7ce02..a240b3b 100644 --- a/src/gui/gsc_info_window.cpp +++ b/src/gui/gsc_info_window.cpp @@ -848,6 +848,14 @@ void GscInfoWindow::on_save_info_button_clicked() specific_filter->add_pattern("*.json"); specific_filter->add_pattern("*.txt"); + Glib::RefPtr json_filter = Gtk::FileFilter::create(); + json_filter->set_name(_("JSON Files")); + json_filter->add_pattern("*.json"); + + Glib::RefPtr txt_filter = Gtk::FileFilter::create(); + txt_filter->set_name(_("Text Files")); + txt_filter->add_pattern("*.txt"); + Glib::RefPtr all_filter = Gtk::FileFilter::create(); all_filter->set_name(_("All Files")); all_filter->add_pattern("*"); @@ -860,6 +868,8 @@ void GscInfoWindow::on_save_info_button_clicked() gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog.get()), TRUE); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj()); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), json_filter->gobj()); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), txt_filter->gobj()); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj()); if (!last_dir.empty()) @@ -881,6 +891,8 @@ void GscInfoWindow::on_save_info_button_clicked() dialog.set_do_overwrite_confirmation(true); dialog.add_filter(specific_filter); + dialog.add_filter(json_filter); + dialog.add_filter(txt_filter); dialog.add_filter(all_filter); if (!last_dir.empty()) @@ -907,14 +919,27 @@ void GscInfoWindow::on_save_info_button_clicked() #endif rconfig::set_data("gui/drive_data_open_save_dir", last_dir); + bool txt_selected = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog.get())) == txt_filter->gobj(); + if (file.extension() != ".json" && file.extension() != ".txt") { - file += ".json"; + file += (txt_selected ? ".txt" : ".json"); } + bool save_txt = txt_selected || file.extension() == ".txt"; + std::string data = this->drive_->get_full_output(); if (data.empty()) { data = this->drive_->get_basic_output(); } + if (save_txt) { + if (auto p = this->drive_->get_property_repository().lookup_property("smartctl/output"); !p.empty()) { + const std::string text_output = p.get_value(); + if (!text_output.empty()) { + data = text_output; + } + } + } + const std::error_code ec = hz::fs_file_put_contents(file, data); if (ec) { gui_show_error_dialog(_("Cannot save SMART data to file"), ec.message(), this);