MoonBit WebDAV 客户端:文件上传/下载(流式+进度)、目录、移动/复制、PROPFIND/PROPPATCH。
moon add q2316367743/webdavasync fn main {
let client = @webdav.create_webdav_client({
base_url: "http://127.0.0.1:8080/dav/",
username: Some("user"),
password: Some("pass"),
timeout_ms: None,
})
// 递归建目录(mkdir -p 语义:逐级 MKCOL,父级 / 本级已存在都跳过)
client.mkdir_all(path="/demo/相册/2024")
// 上传 · 全量字节(进度按 64 KiB 分块节奏回调;返回值带 ETag 等元数据)
let put = client.put_file(
path="/demo/hello.txt",
data=@utf8.encode("你好,WebDAV!"),
content_type="text/plain; charset=utf-8",
conditions=@webdav.Conditions::new(if_none_match="*"), // 仅当不存在时创建
on_progress=fn(p) { println("上传 (\{p.loaded}/\{p.total})") },
)
println("已创建:\{put.created},ETag:\{put.etag}")
// 上传 · 本地文件(内部边读边发,内容不整份读进内存)
ignore(
client.put_file_from_path(
path="/demo/big.bin",
local_path="/Users/me/big.bin",
content_type="application/octet-stream",
on_progress=fn(p) { println("上传 big.bin \{p.loaded}/\{p.total}") },
),
)
// 上传 · 任意读取流(@fs.File / @io.pipe() 读端 / MemoryReader 都能接;
// 传 content_length 走定长,不传走 chunked)
// client.put_file_stream(path="/demo/stream.bin", reader=some_reader)
// 列目录
let items = client.list(path="/demo")
for item in items {
println("\{if item.is_dir { "[目录]" } else { "[文件]" }} \{item.name}")
}
// 流式下载 · 分块回调(内存消费)
client.download_file(
path="/demo/hello.txt",
on_chunk=fn(chunk) { /* 逐块处理,如拼 Buffer / 算哈希 */ },
on_progress=fn(p) { println("下载 \{p.loaded} 字节") },
)
// 流式下载 · 直接写本地文件(写盘是 async,客户端内部完成)
client.download_to_path(path="/demo/big.bin", local_path="/Users/me/big.copy")
// 断点续传:中断残留的本地文件从断点继续(只拉剩余部分);
// 带 ETag 发 If-Range,服务端文件已变时自动回退全文重写
client.download_to_path_resume(
path="/demo/big.bin",
local_path="/Users/me/big.copy",
etag=put.etag.unwrap_or(""),
)
// 区间下载:只取前 8 字节(服务端支持时回 206)
let head = client.get_file(path="/demo/hello.txt",
range=@webdav.ByteRange::from(end=7))
}| 类别 | 方法 |
|---|---|
| 上传 | put_file(全量 Bytes)、put_file_stream(&@io.Reader,可选长度)、put_file_from_path(本地文件路径);三者都流式发送、都回 PutResult |
| 下载 | get_file(全量到内存,回 GetResult)、download_file(同步分块回调)、download_to_path(流式写本地文件);三者都支持 range? : ByteRange 区间下载(服务端接受时 206,GetResult.content_range 携带区间与总长) |
| 断点续传 | download_to_path_resume:以本地残留为起点发 Range: bytes=N-,206 追加 / 200 回退重写 / 416 且已下完视为成功;etag? 发 If-Range 防服务端文件已变,进度为绝对值,成功返回 = 文件完整 |
| 目录 | mkdir(单级)、mkdir_all(逐级 mkdir -p,已存在跳过)、list(可指定 Depth)、list_recursive(整树)、stat(属性)、exists(HEAD,不支持时自动回退 PROPFIND) |
| 移动 / 复制 | move_path、copy_path(覆盖语义 + Depth) |
| 自定义属性 | set_properties / get_properties / get_properties_with_status(PROPPATCH / 按名 PROPFIND,保留 404 状态) |
| 能力探测 | options(DAV / Allow / Server → ServerCapabilities) |
| 锁 | lock(独占 / 共享写锁)、refresh_lock(续期)、unlock、get_locks(lockdiscovery 锁发现)、cleanup_lock_null(lock-null 空资源收尾);令牌传给 Conditions::new(lock_token=…) 后写操作自动带 If: (<令牌>),if_lists 支持多令牌 / Not / [etag] / 带标签列表 |
| 协议控制 | 所有方法的 conditions?(If-Match / If-None-Match / lock_token / if_lists)与 signal?(AbortController 取消);WebdavConfig::retry_on_423 可选开启「423 自动补令牌重试一次」 |
bash src/main/run_tests.sh # 一键测试:起 WebDAV 服务端 → moon test → 真实全流程 → 关闭并汇总
moon test # 仅单元 / 黑盒测试(Mock 传输层,不触网)
moon run src/main # 单独跑真实全流程演示(环境变量见 src/main/moon.pkg 头注释)
moon info && moon fmtlet client = @webdav.create_webdav_client({
base_url: "http://127.0.0.1:8080/dav/",
username: Some("user"),
password: Some("pass"),
timeout_ms: None,
})fn create_webdav_client_with_transport(config : WebdavConfig, transport : &Transport) -> WebdavClient