fix(cloud-files): Timeout-Ursachen im FETCH_DATA-Callback beheben

- HTTP-Client bekommt 60s-Timeout (statt unendlich)
- Bei Send-/Netzwerkfehler wird CfExecute immer mit Failure-Status
  abgeschlossen, damit Explorer nicht ins OS-Timeout laeuft
- Wenn Server kein Range unterstuetzt (200 statt 206), wird aus dem
  Full-Body der angeforderte Bereich herausgeschnitten und die
  tatsaechliche Laenge an CfExecute uebergeben
- Fehler werden in <mount>\.minicloud-cloudfiles.log geschrieben,
  damit man das Problem bei Timeout ueberhaupt sehen kann

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker 2026-04-15 09:24:51 +02:00
parent 204dbb6ab5
commit 6274567219
1 changed files with 40 additions and 15 deletions

View File

@ -171,19 +171,40 @@ unsafe extern "system" fn on_fetch_data(
// HTTPS-Download im separaten Thread (Callback darf nicht blockieren).
let ctx = ctx_snapshot();
std::thread::spawn(move || {
let _ = transfer_range(connection_key, transfer_key, file_id, offset, length, ctx);
if let Err(e) = transfer_range(connection_key, transfer_key, file_id, offset, length, &ctx)
{
log_err(&ctx.mount_point, &format!(
"fetch file_id={file_id} offset={offset} len={length} FAILED: {e}"
));
// Garantiert Fehler-Completion, damit Windows nicht in Timeout laeuft.
let _ = complete_transfer(connection_key, transfer_key, None, offset, length);
}
});
}
fn log_err(mount: &Path, msg: &str) {
use std::io::Write;
let log = mount.join(".minicloud-cloudfiles.log");
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&log) {
let _ = writeln!(f, "[{}] {}", chrono::Utc::now().to_rfc3339(), msg);
}
}
fn transfer_range(
connection_key: CF::CF_CONNECTION_KEY,
transfer_key: i64,
file_id: i64,
offset: i64,
length: u64,
ctx: CloudContext,
ctx: &CloudContext,
) -> Result<(), String> {
let client = reqwest::blocking::Client::new();
if ctx.server_url.is_empty() || ctx.access_token.is_empty() {
return Err("CloudContext nicht gesetzt (Server/Token leer)".into());
}
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(60))
.build()
.map_err(|e| format!("client: {e}"))?;
let url = format!(
"{}/api/files/{}/download",
ctx.server_url.trim_end_matches('/'),
@ -195,22 +216,26 @@ fn transfer_range(
.bearer_auth(&ctx.access_token)
.header("Range", &range)
.send()
.map_err(|e| format!("download: {e}"))?;
.map_err(|e| format!("send: {e}"))?;
let status = resp.status();
if !status.is_success() && status.as_u16() != 206 {
let _ = complete_transfer(connection_key, transfer_key, None, offset, length);
return Err(format!("HTTP {}", status));
}
let bytes = resp
.bytes()
.map_err(|e: reqwest::Error| e.to_string())?;
complete_transfer(
connection_key,
transfer_key,
Some(&bytes),
offset,
length,
)
let bytes = resp.bytes().map_err(|e: reqwest::Error| e.to_string())?;
// Wenn Server kein Range unterstuetzt und volle Datei liefert,
// aus dem Body den angeforderten Bereich ausschneiden.
let slice: &[u8] = if status.as_u16() == 206 {
&bytes[..]
} else {
let start = offset as usize;
let end = (start + length as usize).min(bytes.len());
if start >= bytes.len() {
&[]
} else {
&bytes[start..end]
}
};
complete_transfer(connection_key, transfer_key, Some(slice), offset, slice.len() as u64)
}
fn complete_transfer(