use std::{mem, time::{Duration, Instant}}; use anyhow::Result; use futures::{StreamExt, stream::FuturesUnordered}; use hashbrown::HashSet; use yazi_core::mgr::OpenOpt; use yazi_fs::{File, FsScheme, provider::{Provider, local::Local}}; use yazi_macro::succ; use yazi_parser::mgr::DownloadForm; use yazi_proxy::MgrProxy; use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}}; use yazi_vfs::VfsFile; use crate::{Actor, Ctx}; pub struct Download; impl Actor for Download { type Form = DownloadForm; const NAME: &str = "download"; fn act(cx: &mut Ctx, form: Self::Form) -> Result { let cwd = cx.cwd().clone(); let scheduler = cx.tasks.scheduler.clone(); tokio::spawn(async move { Self::prepare(&form.urls).await; let mut wg1 = FuturesUnordered::new(); for url in form.urls { let done = scheduler.file_download(url.clone()); wg1.push(async move { (done.future().await, url) }); } let mut wg2 = vec![]; let mut urls = Vec::with_capacity(wg1.len()); let mut files = Vec::with_capacity(wg1.len()); let mut instant = Instant::now(); while let Some((success, url)) = wg1.next().await { if !success { continue; } let Ok(f) = File::new(&url).await else { continue }; urls.push(url); files.push(f); if instant.elapsed() >= Duration::from_secs(1) { wg2.push(scheduler.fetch_mimetype(mem::take(&mut files))); instant = Instant::now(); } } if !files.is_empty() { wg2.push(scheduler.fetch_mimetype(files)); } if futures::future::join_all(wg2).await.into_iter().any(|b| !b) { return; } if form.open && !urls.is_empty() { MgrProxy::open(OpenOpt { cwd: Some(cwd), targets: urls, interactive: false, hovered: false, }); } }); succ!(); } } impl Download { async fn prepare(urls: &[UrlBuf]) { let roots: HashSet<_> = urls.iter().filter_map(|u| u.scheme().cache()).collect(); for mut root in roots { root.push("%lock"); Local::regular(&root).create_dir_all().await.ok(); } } }