From 72b46f923e47f1241d18635d52ee255b791fcbc6 Mon Sep 17 00:00:00 2001 From: Nic-dorman Date: Tue, 12 May 2026 11:44:32 +0100 Subject: [PATCH] feat(datamap): write new uploads to ~/Documents/Autonomi/Datamaps/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the macOS gap where NSOpenPanel hides ~/Library by default, leaving users unable to navigate the OS file picker to their own datamaps. New uploads now land in ~/Documents/Autonomi/Datamaps/ (or the platform equivalent of Documents), where every OS picker can reach them. Also helps users sharing datamaps across machines via iCloud Drive / OneDrive — Documents is the natural sync target for that flow. Detection is stateless on every write: * If config_path() already contains any .datamap files → keep writing there. Existing installs are not disrupted; their old datamaps stay alongside any new ones, and the row affordance (Copy Path / Reveal / click basename in DownloadByDatamapDialog) keeps finding them by the absolute path stored in upload_history.json. * Otherwise → write to the new user-addressable dir. Fresh installs get the picker fix immediately. The detection is stable after each write: existing installs keep the .datamap files in the legacy dir forever (the rule continues to find them); fresh installs never accumulate .datamap files in the legacy dir (the rule continues to choose the new one). No migration, no config field, no breaking change. Existing users see zero behavior change unless they manually delete all their old datamap files — in which case the next write flips to the new location, which is the right behavior at that point. Privacy note: ~/Documents is commonly synced by iCloud Drive / OneDrive by default. A datamap is the key to private data on the network. Users who don't want cloud sync exposure for new datamaps can opt out at the sync layer (exclude the folder from sync) — same control surface as any other file in Documents. Co-Authored-By: Claude Opus 4.7 (1M context) --- src-tauri/src/config.rs | 75 +++++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 619242d..3def067 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -138,6 +138,60 @@ pub(crate) fn config_path() -> PathBuf { .join("ant-gui") } +/// User-addressable directory for persisted datamap files on **fresh +/// installs**. Lives under `~/Documents/Autonomi/Datamaps/` (or the +/// platform equivalent of Documents) so the OS file picker can navigate +/// to them — fixes the macOS gap where `~/Library` is hidden by default +/// in NSOpenPanel. +/// +/// Existing installs keep writing into `config_path()` via +/// `resolve_datamap_output_dir()` so the upgrade does not split their +/// datamaps across two locations. Only callers that need the "where do +/// new writes go" answer should use `resolve_datamap_output_dir()`; +/// this function is the new-install default. +/// +/// Falls back to `/Documents/Autonomi/Datamaps` if `dirs::document_dir` +/// can't resolve the platform path (very unusual), and to `./Autonomi/Datamaps` +/// only if even `dirs::home_dir` fails (effectively a no-home embedded env). +pub fn datamap_dir() -> PathBuf { + dirs::document_dir() + .or_else(|| dirs::home_dir().map(|h| h.join("Documents"))) + .unwrap_or_else(|| PathBuf::from(".")) + .join("Autonomi") + .join("Datamaps") +} + +/// Decide where the *next* datamap write should land. Existing installs +/// (those whose `config_path()` already contains one or more `.datamap` +/// files) keep writing into the legacy location so a single user does not +/// end up with their datamaps scattered across two directories after the +/// upgrade. Fresh installs go to `datamap_dir()` so the OS file picker can +/// reach them. +/// +/// Detection is stateless: on every write, we look at the legacy dir's +/// current contents. After the first new-install write the rule continues +/// to return `datamap_dir()` because the legacy dir never gains a +/// `.datamap` file. After the first existing-install write the rule +/// continues to return `config_path()` because the legacy dir still has +/// the original `.datamap` files alongside the new one. +fn resolve_datamap_output_dir() -> PathBuf { + let legacy = config_path(); + let has_legacy_datamaps = legacy + .read_dir() + .ok() + .map(|entries| { + entries + .filter_map(|e| e.ok()) + .any(|entry| entry.path().extension().and_then(|s| s.to_str()) == Some("datamap")) + }) + .unwrap_or(false); + if has_legacy_datamaps { + legacy + } else { + datamap_dir() + } +} + /// Resolve the OS-appropriate default downloads directory. Returns /// `~/Downloads` on macOS/Linux and `C:\Users\\Downloads` on Windows, /// falling back to `/Downloads` if the platform-specific lookup fails. @@ -219,8 +273,9 @@ pub fn get_file_metas(paths: &[String]) -> Result, String> { .collect() } -/// Persist a serialized DataMap alongside `upload_history.json` using the -/// canonical msgpack format from `ant_core::data::write_datamap`. The file +/// Persist a serialized DataMap in the user-addressable datamap directory +/// (`~/Documents/Autonomi/Datamaps/` on fresh installs, the legacy config +/// dir on existing installs — see `resolve_datamap_output_dir`). 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 @@ -228,18 +283,18 @@ pub fn get_file_metas(paths: &[String]) -> Result, String> { /// /// Returns the absolute path to the written file. /// -/// 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. +/// Format is the canonical msgpack produced by `ant_core::data::write_datamap`, +/// so ant-cli, ant-tui, and any other consumer of the upstream module reads +/// back the same on-disk bytes. 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}"))?; + let dir = resolve_datamap_output_dir(); + std::fs::create_dir_all(&dir).map_err(|e| format!("Failed to create datamap dir: {e}"))?; ant_core::data::write_datamap( &dir, original_name,