//! One-shot write probe: PUT a tiny file, report the status, DELETE it. use dr_plat::PlatformSecretStore; use dr_sync::{RemoteBackend, RemoteId, RemotePath}; use dr_sync_nextcloud::{NextcloudBackend, SessionStore}; #[tokio::main(flavor = "current_thread")] async fn main() { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let store = SessionStore::open(Box::new(PlatformSecretStore::new())); let Some(session) = store.current() else { println!("no stored session"); return; }; let creds = match store.credentials(&session) { Ok(c) => c, Err(e) => { println!("credentials: {e}"); return; } }; println!("account: {} root={}", session.describe(), session.root); let backend = NextcloudBackend::new(&creds, &session.user_id).unwrap(); // Read, which we know works. let dir = RemotePath::new(&session.root); match backend.list(&dir, None).await { Ok(v) => println!("READ ok: {} entries", v.len()), Err(e) => println!("READ FAILED: {e}"), } // What is actually in the derived folder on the server? { let dir = RemotePath::new(format!("{}/.darkroom-derived", session.root)); match backend.list(&dir, None).await { Ok(entries) => { println!("\n[derived] {} entry/entries on the server:", entries.len()); for e in &entries { println!(" {:<28} {:>10} bytes", e.path.name(), e.size); } } Err(e) => println!("\n[derived] listing failed: {e}"), } } // Probe a file the sweep reported as 423 Locked: is it the file, the // range request, or the folder? { let c = dr_sync_nextcloud::http_client("DarkRoom").unwrap(); let base = format!( "{}/remote.php/dav/files/{}", creds.server.trim_end_matches('/'), session.user_id ); let f = "PhotosRaw/Darktable/20230629_no_name/20230629_0030.jpeg"; let enc: String = f .split('/') .map(|seg| { seg.bytes() .map(|b| match b { b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { (b as char).to_string() } _ => format!("%{b:02X}"), }) .collect::() }) .collect::>() .join("/"); let url = format!("{base}/{enc}"); for (what, range) in [ ("ranged 0-256k", Some("bytes=0-262143")), ("whole file", None), ] { let mut rq = c .get(&url) .basic_auth(&creds.login_name, Some(&creds.app_password)); if let Some(r) = range { rq = rq.header("Range", r); } match rq.send().await { Ok(r) => println!("GET {what}: {}", r.status()), Err(e) => println!("GET {what}: transport {e}"), } } } // Raw HTTP, to see the status the connector maps away. { let url = format!( "{}/remote.php/dav/files/{}/{}/.darkroom-write-test", creds.server.trim_end_matches('/'), session.user_id, session.root ); let c = dr_sync_nextcloud::http_client("DarkRoom").unwrap(); match c .put(&url) .basic_auth(&creds.login_name, Some(&creds.app_password)) .body("probe") .send() .await { Ok(r) => { println!("RAW PUT status: {}", r.status()); let body = r.text().await.unwrap_or_default(); let body = body.trim(); if !body.is_empty() { println!("RAW body: {}", &body[..body.len().min(400)]); } } Err(e) => println!("RAW PUT transport error: {e}"), } } // Is it the folder or the account? Probe the account root too: a // read-only *share* refuses writes inside it while the account writes // freely elsewhere, which is a different fix from a credential problem. { let c = dr_sync_nextcloud::http_client("DarkRoom").unwrap(); let base = format!( "{}/remote.php/dav/files/{}", creds.server.trim_end_matches('/'), session.user_id ); for (what, url) in [ ("account root", format!("{base}/.darkroom-write-test")), ( "library root", format!("{base}/{}/.darkroom-write-test", session.root), ), ] { match c .put(&url) .basic_auth(&creds.login_name, Some(&creds.app_password)) .body("probe") .send() .await { Ok(r) => { println!("PUT {what}: {}", r.status()); if r.status().is_success() { let _ = c .delete(&url) .basic_auth(&creds.login_name, Some(&creds.app_password)) .send() .await; } } Err(e) => println!("PUT {what}: transport error {e}"), } } } // Write into the library root. let p = RemotePath::new(format!("{}/.darkroom-write-test", session.root)); match backend .put(&p, b"darkroom write probe\n".to_vec(), None) .await { Ok(v) => { println!("WRITE ok: etag={}", v.as_str()); match backend.delete(&RemoteId::Path(p.clone()), None).await { Ok(()) => println!("CLEAN ok"), Err(e) => println!("CLEAN failed (harmless): {e}"), } } Err(e) => println!("WRITE FAILED: {e}"), } }