Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src-tauri/src/autonomi_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
97 changes: 25 additions & 72 deletions src-tauri/src/config.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -217,74 +212,32 @@ pub fn get_file_metas(paths: &[String]) -> Result<Vec<FileMetaResult>, 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<PathBuf, String> {
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<PathBuf, String> {
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}"))
}