1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use anyhow::{anyhow, Result};
use console::Emoji;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use reqwest::Client;
use stac::{Href, Object};
use stac_async::{AsyncRead, AsyncReader};
use std::path::{Path, PathBuf};
use tokio::{fs::File, io::AsyncWriteExt, task::JoinHandle};
use url::Url;

pub async fn download_item(href: Href, outdir: impl AsRef<Path>) -> Result<()> {
    let mut multi_progress = MultiProgress::new();
    multi_progress.println(format!(
        "[1/?] {}Reading {}...",
        Emoji::new("📘 ", ""),
        href.file_name()
    ))?;
    let reader = AsyncReader::new();
    let object = reader.read(href.clone()).await?;
    let mut item = if let Object::Item(item) = object.object {
        item
    } else {
        return Err(anyhow!("expected item, got {}", object.object.r#type()));
    };
    let count = item.assets.len();
    multi_progress.println(format!(
        "[2/{}] {}Creating {}...",
        count + 3,
        Emoji::new("📁 ", ""),
        outdir.as_ref().display()
    ))?;
    tokio::fs::create_dir_all(outdir.as_ref()).await?;
    let mut handles = Vec::new();
    for (i, asset) in item.assets.values_mut().enumerate() {
        let href = Href::new(&asset.href);
        asset.href = format!("./{}", href.file_name());
        let outfile = outdir.as_ref().join(href.file_name());
        match href {
            Href::Url(url) => {
                let handle = download_url(
                    reader.client(),
                    url,
                    outfile,
                    &mut multi_progress,
                    i + 3,
                    count + 3,
                );
                handles.push(handle);
            }
            Href::Path(_) => unimplemented!(),
        }
    }
    for handle in handles {
        handle.await??;
    }
    let outpath = outdir.as_ref().join(href.file_name());
    println!(
        "[{}/{}] {}Writing {}...",
        count + 3,
        count + 3,
        Emoji::new("📘 ", ""),
        outpath.display()
    );
    tokio::fs::write(outpath, serde_json::to_vec(&item)?).await?;
    Ok(())
}

fn download_url(
    client: Client,
    url: Url,
    outfile: PathBuf,
    multi_progress: &mut MultiProgress,
    index: usize,
    total: usize,
) -> JoinHandle<Result<()>> {
    let progress_bar = multi_progress.add(ProgressBar::new(0));
    let file_name = url.path_segments().unwrap().last().unwrap().to_string();
    tokio::spawn(async move {
        progress_bar.set_style(
            ProgressStyle::with_template(
                "{prefix} [{elapsed_precise}] [{bar:.cyan/blue}] {bytes}/{total_bytes} {wide_msg:>}",
            )?
            .with_key("eta", |state| format!("{:.1}s", state.eta().as_secs_f64()))
            .progress_chars("#>-"),
        );
        progress_bar.set_prefix(format!(
            "[{}/{}] {}Downloading...",
            index,
            total,
            Emoji::new("🔗 ", ""),
        ));
        progress_bar.set_message(file_name.to_string());
        let mut response = client.get(url.clone()).send().await?;
        if let Some(content_length) = response.content_length() {
            progress_bar.set_length(content_length);
        } else {
            return Err(anyhow!("empty content: {}", url));
        }
        let mut file = File::create(outfile).await?;
        while let Some(bytes) = response.chunk().await? {
            progress_bar.inc(bytes.len() as u64);
            file.write_all(&bytes).await?;
        }
        progress_bar.set_prefix(format!(
            "[{}/{}] {}Downloaded!   ",
            index,
            total,
            Emoji::new("✅ ", ""),
        ));
        progress_bar.finish();
        Ok(())
    })
}