diff --git a/Cargo.lock b/Cargo.lock index ba27691..e70cc91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,6 +461,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -526,6 +532,7 @@ dependencies = [ "hex", "memchr", "mmap-io", + "pretty_assertions", "rand 0.10.2", "ratatui", "regex", @@ -1347,6 +1354,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -2387,3 +2404,9 @@ name = "x11rb-protocol" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml index 79997e0..a15a3d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,6 @@ tui-input = "0.15.0" [profile.dist] inherits = "release" lto = "thin" + +[dev-dependencies] +pretty_assertions = "1.4.1" diff --git a/src/database.rs b/src/database.rs index 80e593c..6cf8dab 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,12 +1,15 @@ +use std::collections::BTreeMap; use std::error::Error; use std::fs; use std::path::{Path, PathBuf}; +use serde::{Deserialize, Serialize}; + use crate::app::App; +use crate::hex::{blocks::ColoredBlock, comment::Comment, hex_view::HexView}; impl App { pub fn save_database(&self) -> Result<(), Box> { - let toml_string = toml::to_string_pretty(&self.hex_view)?; let target_dir: &Path = Path::new(&self.file_info.path) .parent() .unwrap_or(Path::new(".")); @@ -23,6 +26,10 @@ impl App { return Ok(()); } + // serialize after checking for empty + let db = hex_view_to_db(&self.hex_view); + let toml_string = toml::to_string_pretty(&db)?; + // try target's path or else current directory fs::write(&target_db, &toml_string).or_else(|_| fs::write(&cwd_db, &toml_string))?; @@ -36,10 +43,127 @@ impl App { let target_db: PathBuf = target_dir.join(&cwd_db); let data = fs::read_to_string(&cwd_db).or_else(|_| fs::read_to_string(&target_db))?; - // TODO: Although we're only interested in comments and bookmarks, other fields might - // be loaded if they are defined in the TOML file. How to prevent that? - self.hex_view = toml::from_str(&data)?; - self.hex_view.editing_hex = true; // otherwise it defaults to false if a .dz6 file exists for the target + let db = toml::from_str::(&data)?; + self.hex_view = hex_view_from_db(db); Ok(()) } } + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct Database { + // blocks are ByteBlock structs -- ranges with different colors + pub blocks: Vec, + pub bookmarks: Vec, + + // `comment_name_list` is used to show comments in Names list + // and also on the conversion from selected item on the list + // to file offset passed to goto() + pub comment_name_list: Vec, + + // TODO: comments and comment_name_list are redundant, we should only store one of them + // but to avoid breaking existing .dz6 files, we will keep both for now + // The `comments` format is more compact and doesn't require a new type + // so maybe later we default to using only it + pub comments: BTreeMap, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct DbComment { + pub offset: usize, + pub comment: String, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct DbColoredBlock { + pub start: usize, + pub end: usize, + pub bg_color: u32, + pub fg_color: u32, +} + +fn hex_view_from_db(db: Database) -> HexView { + HexView { + blocks: db.blocks.into_iter().map(colored_block_from_db).collect(), + bookmarks: db.bookmarks, + comment_name_list: db + .comment_name_list + .into_iter() + .map(comment_from_db) + .collect(), + comments: db.comments.into_iter().collect(), + editing_hex: true, // otherwise it defaults to false if a .dz6 file exists for the target + ..Default::default() + } +} +fn colored_block_from_db(db_block: DbColoredBlock) -> ColoredBlock { + ColoredBlock { + start: db_block.start, + end: db_block.end, + bg_color: db_block.bg_color, + fg_color: db_block.fg_color, + } +} +fn comment_from_db(db_comment: DbComment) -> Comment { + Comment { + offset: db_comment.offset, + comment: db_comment.comment, + } +} + +/// HexView to Database conversion, +/// takes a reference because we don't want to consume the live HexView that is +/// being used in the app. +fn hex_view_to_db(hex_view: &HexView) -> Database { + Database { + blocks: hex_view.blocks.iter().map(colored_block_to_db).collect(), + bookmarks: hex_view.bookmarks.clone(), + comment_name_list: hex_view + .comment_name_list + .iter() + .map(comment_to_db) + .collect(), + comments: hex_view + .comments + .iter() + .map(|(k, v)| (*k, v.clone())) + .collect(), + } +} +fn colored_block_to_db(block: &ColoredBlock) -> DbColoredBlock { + DbColoredBlock { + start: block.start, + end: block.end, + bg_color: block.bg_color, + fg_color: block.fg_color, + } +} +fn comment_to_db(comment: &Comment) -> DbComment { + DbComment { + offset: comment.offset, + comment: comment.comment.clone(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_database_round_trip() { + // db file generated with the hexview serialization + let db_file = "test_data/db.toml"; + let db_str = fs::read_to_string(db_file).expect("Failed to read test database file"); + + let db: Database = toml::from_str(&db_str).expect("Failed to deserialize database"); + + // tests that we don't drop any data from the original file and don't add any + let serialized = toml::to_string_pretty(&db).expect("Failed to serialize database"); + assert_eq!(db_str, serialized); + + // tests that we can deserialize the serialized string and get the same data back + let re_deserialized: Database = + toml::from_str(&serialized).expect("Failed to deserialize database"); + assert_eq!(db, re_deserialized); + } +} diff --git a/src/hex/hex_view.rs b/src/hex/hex_view.rs index edd02df..73508ef 100644 --- a/src/hex/hex_view.rs +++ b/src/hex/hex_view.rs @@ -1,7 +1,6 @@ use std::collections::{HashMap, HashSet}; use ratatui::widgets::{ListState, TableState}; -use serde::{Deserialize, Serialize}; use tui_input::Input; use crate::hex::{blocks::ColoredBlock, comment::Comment}; @@ -13,18 +12,14 @@ pub struct Point { pub y: usize, } -#[derive(Default, Serialize, Deserialize)] +#[derive(Default)] pub struct HexView { - #[serde(skip)] pub ascii_state: TableState, // blocks are ByteBlock structs -- ranges with different colors pub blocks: Vec, pub bookmarks: Vec, - #[serde(skip)] pub changed_bytes: HashMap, - #[serde(skip)] pub changed_history: Vec, - #[serde(skip)] pub comment_input: Input, // the input comment widget (tui-input) // `comment_name_list` is used to show comments in Names list @@ -36,30 +31,17 @@ pub struct HexView { // to handle that with a hash map pub comments: HashMap, - #[serde(skip)] pub cursor: Point, - #[serde(skip)] pub editing_hex: bool, - #[serde(skip)] pub highlights: HashSet, // byte highlight - #[serde(skip)] pub last_visited_offset: usize, - #[serde(skip)] pub names_list_state: ListState, - #[serde(skip)] pub names_regex_input: Input, - #[serde(skip)] pub names_regex: String, - #[serde(skip)] pub offset_state: TableState, - #[serde(skip)] pub offset: usize, - #[serde(skip)] pub search: crate::hex::search::Search, - #[serde(skip)] pub selection: crate::hex::selection::Selection, - #[serde(skip)] pub strings_regex_input: Input, - #[serde(skip)] pub table_state: TableState, } diff --git a/test_data/db.toml b/test_data/db.toml new file mode 100644 index 0000000..d2fbbac --- /dev/null +++ b/test_data/db.toml @@ -0,0 +1,34 @@ +bookmarks = [ + 288, + 325, +] + +[[blocks]] +start = 16 +end = 21 +bg_color = 446956584 +fg_color = 2910089301 + +[[blocks]] +start = 128 +end = 134 +bg_color = 3843912424 +fg_color = 2232455935 + +[[blocks]] +start = 182 +end = 214 +bg_color = 4160309102 +fg_color = 4169677532 + +[[comment_name_list]] +offset = 21 +comment = " one comment" + +[[comment_name_list]] +offset = 101 +comment = "comment" + +[comments] +21 = " one comment" +101 = "comment"