diff --git a/src-tauri/src/autonomi_ops.rs b/src-tauri/src/autonomi_ops.rs index bb59d87..99c382c 100644 --- a/src-tauri/src/autonomi_ops.rs +++ b/src-tauri/src/autonomi_ops.rs @@ -784,7 +784,7 @@ pub async fn confirm_upload( let data_map_json = serde_json::to_string(&result.data_map) .map_err(|e| format!("Failed to serialize DataMap: {e}"))?; let address = format!("0x{:x}", Sha256::digest(data_map_json.as_bytes())); - let data_map_file = crate::config::write_datamap_for(&file_name, &data_map_json)? + let data_map_file = crate::config::write_datamap_for(&file_name, &result.data_map)? .to_string_lossy() .into_owned(); let public_address = result @@ -850,7 +850,7 @@ pub async fn confirm_upload_merkle( let data_map_json = serde_json::to_string(&result.data_map) .map_err(|e| format!("Failed to serialize DataMap: {e}"))?; let address = format!("0x{:x}", Sha256::digest(data_map_json.as_bytes())); - let data_map_file = crate::config::write_datamap_for(&file_name, &data_map_json)? + let data_map_file = crate::config::write_datamap_for(&file_name, &result.data_map)? .to_string_lossy() .into_owned(); let public_address = result @@ -979,7 +979,7 @@ pub async fn wallet_upload( let data_map_json = serde_json::to_string(&result.data_map) .map_err(|e| format!("Failed to serialize DataMap: {e}"))?; let address = format!("0x{:x}", Sha256::digest(data_map_json.as_bytes())); - let data_map_file = crate::config::write_datamap_for(&file_name, &data_map_json)? + let data_map_file = crate::config::write_datamap_for(&file_name, &result.data_map)? .to_string_lossy() .into_owned(); let public_address = result diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 4755cc8..d72879a 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -1,10 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::io::Write; -use std::path::{Path, PathBuf}; - -/// Extension for persisted datamap files. Kept public so the frontend's file -/// picker and sanity checks can share a single source of truth. -pub const DATAMAP_EXTENSION: &str = "datamap"; +use std::path::PathBuf; #[derive(Clone, Serialize, Deserialize)] pub struct AppConfig { @@ -217,74 +212,32 @@ pub fn get_file_metas(paths: &[String]) -> Result, String> { .collect() } -/// Persist a serialized DataMap JSON string alongside `upload_history.json`. -/// -/// The file is named after the upload's original basename (with its extension -/// stripped) plus `.datamap` — e.g. `test.pdf` becomes `test.datamap`. When a -/// file of that name already exists, a numeric suffix is appended so we never -/// overwrite a prior datamap: `test (2).datamap`, `test (3).datamap`, … +/// Persist a serialized DataMap alongside `upload_history.json` using the +/// canonical msgpack format from `ant_core::data::write_datamap`. The file +/// is named after the upload's original basename with `.datamap` appended, +/// preserving the original extension (e.g. `holiday.jpg` → +/// `holiday.jpg.datamap`). On collision the upstream `NumericSuffix` policy +/// inserts `-N` between the basename and `.datamap`. /// /// Returns the absolute path to the written file. -pub fn write_datamap_for(original_name: &str, json: &str) -> Result { - let stem = sanitize_stem(original_name); +/// +/// This is the round-trip-safe path: ant-cli, ant-tui, and any other +/// consumer of the upstream module reads back the same on-disk format. The +/// read side (`autonomi_ops::read_datamap_file`) still accepts legacy JSON +/// datamaps written by older builds via the first-byte sniff in upstream's +/// `read_datamap`, so existing entries in `upload_history.json` keep +/// working. +pub fn write_datamap_for( + original_name: &str, + dm: &ant_core::data::DataMap, +) -> Result { let dir = config_path(); std::fs::create_dir_all(&dir).map_err(|e| format!("Failed to create config dir: {e}"))?; - - // Cap the number of collision attempts at a generous-but-finite limit so a - // pathological state (e.g. thousands of collisions) yields an error rather - // than an infinite loop. - const MAX_COLLISION_ATTEMPTS: u32 = 10_000; - for attempt in 0..MAX_COLLISION_ATTEMPTS { - let file_name = if attempt == 0 { - format!("{stem}.{DATAMAP_EXTENSION}") - } else { - format!("{stem} ({}).{DATAMAP_EXTENSION}", attempt + 1) - }; - let path = dir.join(&file_name); - match std::fs::OpenOptions::new() - .write(true) - .create_new(true) - .open(&path) - { - Ok(mut f) => { - f.write_all(json.as_bytes()) - .map_err(|e| format!("Failed to write datamap: {e}"))?; - return Ok(path); - } - Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue, - Err(e) => return Err(format!("Failed to create datamap file: {e}")), - } - } - Err("Unable to reserve a datamap filename after many attempts".into()) -} - -/// Reduce an arbitrary upload filename to a safe datamap filename stem: drop -/// the final extension, replace characters that are awkward on disk across -/// platforms with `_`, and fall back to "datamap" if the result is empty. -fn sanitize_stem(original_name: &str) -> String { - let raw = Path::new(original_name) - .file_stem() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_default(); - - let cleaned: String = raw - .chars() - .map(|c| { - if c.is_alphanumeric() || matches!(c, ' ' | '-' | '_' | '.' | '(' | ')') { - c - } else { - '_' - } - }) - .collect(); - - let trimmed = cleaned - .trim_matches(|c: char| c == '.' || c.is_whitespace()) - .to_string(); - - if trimmed.is_empty() { - "datamap".to_string() - } else { - trimmed - } + ant_core::data::write_datamap( + &dir, + original_name, + dm, + ant_core::data::CollisionPolicy::NumericSuffix, + ) + .map_err(|e| format!("Failed to write datamap: {e}")) }