Skip to content

Commit 8488795

Browse files
fix: resolve android read-only file system error on sync
Android 10+ (API 29+) enforces Scoped Storage via the Storage Access Framework (SAF), meaning that standard `std::fs` operations cannot write to user-selected virtual URIs, causing a `Read-only file system (os error 30)` panic when the backend tried to initialize the hidden `.sync` directory inside the workspace. - Relocated the Automerge `.am` metadata files into the Tauri application's native, read-write enabled config directory (`app_config_dir()`). - Added MD5 hashing of the workspace path to ensure different workspace trees get their own isolated sub-folder of sync states to prevent cross-workspace collisions. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
1 parent e50bda3 commit 8488795

4 files changed

Lines changed: 18 additions & 4 deletions

File tree

apps/app/src-tauri/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/app/src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ rand = "0.9.2"
4747
dirs = "6.0.0"
4848
notify = "8.2.0"
4949
notify-debouncer-full = "0.6.0"
50+
md5 = "0.8.0"
5051

5152
[profile.dev]
5253
incremental = true # Compile your binary in smaller steps.

apps/app/src-tauri/src/sync/crdt.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@ pub struct CrdtManager {
1919

2020
#[allow(dead_code)]
2121
impl CrdtManager {
22-
pub async fn new(base_dir: PathBuf) -> Result<Self, String> {
23-
let sync_dir = base_dir.join(".sync");
22+
pub async fn new(base_dir: PathBuf, config_dir: PathBuf) -> Result<Self, String> {
23+
// Instead of writing to base_dir which causes "Read-only file system" errors
24+
// on Android SAF URIs, we store sync states in the app's native config directory.
25+
// We hash the base_dir path so multiple workspaces don't collide their sync states.
26+
27+
let workspace_hash = md5::compute(base_dir.to_string_lossy().as_bytes());
28+
let sync_dir = config_dir.join(format!("sync_states_{:x}", workspace_hash));
29+
2430
if !sync_dir.exists() {
2531
fs::create_dir_all(&sync_dir)
2632
.await
27-
.map_err(|e| format!("Failed to create .sync dir: {}", e))?;
33+
.map_err(|e| format!("Failed to create sync dir: {}", e))?;
2834
}
2935

3036
Ok(Self {

apps/app/src-tauri/src/sync/discovery.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ impl SyncState {
4040
}
4141
path_str = urlencoding::decode(&path_str).unwrap_or(std::borrow::Cow::Borrowed(&path_str)).to_string();
4242

43-
let crdt_manager = CrdtManager::new(std::path::PathBuf::from(path_str)).await?;
43+
let crdt_manager = CrdtManager::new(std::path::PathBuf::from(path_str), config_dir.clone()).await?;
4444

4545
// Channel for server shutdown
4646
let (tx, _) = tokio::sync::broadcast::channel(1);

0 commit comments

Comments
 (0)