leyline/core/response/
download.rs1use std::path::{Path, PathBuf};
2
3use tokio::io::AsyncWriteExt;
4
5use super::Response;
6use crate::core::error::{Error, Result};
7use crate::util::atomic;
8
9struct PartialFile {
10 path: PathBuf,
11 armed: bool,
12}
13
14impl PartialFile {
15 fn new(path: PathBuf) -> Self {
16 Self { path, armed: true }
17 }
18
19 fn disarm(mut self) -> PathBuf {
20 self.armed = false;
21 std::mem::take(&mut self.path)
22 }
23}
24
25impl Drop for PartialFile {
26 fn drop(&mut self) {
27 if self.armed {
28 drop(std::fs::remove_file(&self.path));
29 }
30 }
31}
32
33impl Response {
34 pub async fn download_to(self, path: impl AsRef<Path>, limit: Option<u64>) -> Result<u64> {
35 let path = path.as_ref().to_path_buf();
36 let partial = PartialFile::new(atomic::temp_path(&path)?);
37 let written = self.write_file(&partial.path, &path, limit).await?;
38 let temp = partial.disarm();
39 tokio::task::spawn_blocking(move || atomic::commit(&temp, &path))
40 .await
41 .map_err(|err| Error::from(std::io::Error::other(err)))??;
42 Ok(written)
43 }
44
45 async fn write_file(self, temp: &Path, target: &Path, limit: Option<u64>) -> Result<u64> {
46 let mut file = tokio::fs::File::from_std(atomic::create_temp(
47 temp,
48 target,
49 atomic::FileMode::KeepExisting,
50 )?);
51 let written = self.copy_decoded_to(&mut file, limit).await?;
52 file.flush().await?;
53 file.sync_all().await?;
54 Ok(written)
55 }
56}