Files
DarkRoom/core/dr-sync-nextcloud/examples/writetest.rs
T
dtourolleandClaude Opus 5 03326242a1
Build and test / Desktop (Linux) (push) Successful in 1h23m26s
Build and test / Android (aarch64) (push) Failing after 2s
Build and test / Layer separation (push) Successful in 50s
Traceability / Requirement traces (push) Failing after 1m9s
Make the CI checks say what they mean, and format the workspace
The Android job's "Verify minimum API level" step has never verified the
minimum API level. It took the first `*.so` anywhere under the target
directory, which is a host proc-macro from debug/deps — an x86-64 object
built by the runner's gcc, whose .comment section cannot mention Android
and so can never contradict the expected value. It now reads the artifact
under the target triple, compares against MIN_API parsed from the
Dockerfile rather than a second copy of the number, and fails on a
mismatch. Both sides are checked non-empty first: two failed parses would
otherwise compare equal and pass, which is the same silent success in a
new costume.

The Android image installs one SDK package per layer and keeps the
output. sdkmanager is a JVM program that aborts when it cannot get memory,
and the single `> /dev/null` step reported that as a bare "exit code 134"
while a retry re-downloaded everything that had already succeeded.

tools/ci-local.sh runs all four jobs — desktop, android, layering,
traceability — against the host toolchain, which is pinned to the same
1.92.0 CI installs. Its matrix check compares regeneration against the
working tree rather than against HEAD: CI starts from a clean checkout, so
git's answer is the right one there and reports every local run stale here.

The rest is rustfmt across the workspace, and the clippy findings that
surfaced once it did: manual_contains in dr-thumbs and collections_ui, a
map iterated as pairs for its keys, an index loop over a slice, and two
runtime assertions on a constant now made at compile time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 12:02:51 +02:00

173 lines
6.0 KiB
Rust

//! 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::<String>()
})
.collect::<Vec<_>>()
.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}"),
}
}