Skip to main content

leyline/core/response/
download.rs

1use 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}