use std::{collections::HashSet, path::PathBuf}; use serde::{Deserialize, Serialize}; use tauri::{AppHandle, Emitter}; use tokio::fs::File; use anyhow::{anyhow, Result}; use crate::pkg::{Package, PkgKey, Remote}; pub struct DownloadHandler { paths: HashSet, app: AppHandle } #[derive(Serialize, Deserialize, Clone)] pub struct DownloadTick { pkg_key: PkgKey, ratio: f32, } impl DownloadHandler { pub fn new(app: AppHandle) -> DownloadHandler { DownloadHandler { paths: HashSet::new(), app } } pub fn download_zip(&mut self, zip_path: &PathBuf, pkg: &Package) -> Result<()> { let rmt = pkg.rmt.as_ref() .ok_or_else(|| anyhow!("Attempted to download a package without remote data"))? .clone(); if self.paths.contains(zip_path) { Ok(()) } else { // TODO clear cache button should clear this self.paths.insert(zip_path.clone()); tauri::async_runtime::spawn(Self::download_zip_proc(self.app.clone(), zip_path.clone(), pkg.key(), rmt)); Ok(()) } } async fn download_zip_proc(app: AppHandle, zip_path: PathBuf, pkg_key: PkgKey, rmt: Remote) -> Result<()> { use futures::StreamExt; use tokio::io::AsyncWriteExt; // let zip_path_part = zip_path.add_extension("part"); let mut zip_path_part = zip_path.to_owned(); zip_path_part.set_extension("zip.part"); let mut cache_file_w = File::create(&zip_path_part).await?; let mut byte_stream = reqwest::get(&rmt.download_url).await?.bytes_stream(); let mut total_bytes = 0; log::info!("downloading: {}", rmt.download_url); while let Some(item) = byte_stream.next().await { let i = item?; total_bytes += i.len(); _ = app.emit("download-progress", DownloadTick { pkg_key: pkg_key.clone(), ratio: (total_bytes as f32) / (rmt.file_size as f32), })?; cache_file_w.write_all(&mut i.as_ref()).await?; } cache_file_w.sync_all().await?; tokio::fs::rename(&zip_path_part, &zip_path).await?; log::debug!("downloaded to {:?}", zip_path); app.emit("download-end", pkg_key)?; Ok(()) } }