faster hash plugin version

This commit is contained in:
Pierre
2026-01-18 19:53:18 +01:00
parent 443fd3c6e6
commit 7d3c6ef635
18 changed files with 2700 additions and 1115 deletions
+114
View File
@@ -0,0 +1,114 @@
use anyhow::{Context, Result};
use reqwest::Client;
/// WebDAV client for streaming file access
pub struct WebDavClient {
client: Client,
base_url: String,
}
impl WebDavClient {
pub fn new(base_url: &str) -> Self {
Self {
client: Client::new(),
base_url: base_url.trim_end_matches('/').to_string(),
}
}
/// Build full URL for a file path
/// The input path is a filesystem path like /files/watch/movie.mkv
/// The WebDAV server exposes /files as /webdav, so we strip /files prefix
fn file_url(&self, path: &str) -> String {
// Strip /files prefix since WebDAV mounts /files at the base URL
let clean_path = path
.trim_start_matches('/')
.strip_prefix("files/")
.unwrap_or(path.trim_start_matches('/'));
format!("{}/{}", self.base_url, clean_path)
}
/// Stream file contents via HTTP GET
pub async fn get_file(&self, path: &str) -> Result<WebDavReader> {
let url = self.file_url(path);
let response = self.client
.get(&url)
.send()
.await
.with_context(|| format!("Failed to GET {}", url))?;
if !response.status().is_success() {
anyhow::bail!("WebDAV GET failed: {} for {}", response.status(), url);
}
let content_length = response.content_length();
Ok(WebDavReader {
inner: response,
size: content_length,
})
}
/// Get file size without downloading content (HEAD request)
pub async fn get_size(&self, path: &str) -> Result<u64> {
let url = self.file_url(path);
let response = self.client
.head(&url)
.send()
.await
.with_context(|| format!("Failed to HEAD {}", url))?;
if !response.status().is_success() {
anyhow::bail!("WebDAV HEAD failed: {} for {}", response.status(), url);
}
response
.content_length()
.ok_or_else(|| anyhow::anyhow!("No Content-Length header for {}", url))
}
}
/// Async reader wrapper for WebDAV response
pub struct WebDavReader {
inner: reqwest::Response,
size: Option<u64>,
}
impl WebDavReader {
pub fn size(&self) -> Option<u64> {
self.size
}
/// Read all bytes (for smaller files or when you need the whole thing)
pub async fn bytes(self) -> Result<Vec<u8>> {
self.inner.bytes().await
.map(|b| b.to_vec())
.context("Failed to read response bytes")
}
/// Get the underlying response for streaming
pub fn into_response(self) -> reqwest::Response {
self.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_url() {
let client = WebDavClient::new("http://localhost/webdav");
// /files prefix should be stripped
assert_eq!(client.file_url("/files/watch/test.mkv"), "http://localhost/webdav/watch/test.mkv");
assert_eq!(client.file_url("files/watch/test.mkv"), "http://localhost/webdav/watch/test.mkv");
// Trailing slash in base URL should be handled
let client2 = WebDavClient::new("http://localhost/webdav/");
assert_eq!(client2.file_url("/files/watch/test.mkv"), "http://localhost/webdav/watch/test.mkv");
// Paths without /files prefix should work too
assert_eq!(client.file_url("watch/test.mkv"), "http://localhost/webdav/watch/test.mkv");
}
}