quick_flash/
storage.rs

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
use crate::credentials::{Credentials, StorageType};
use anyhow::{self, Context};
use s3;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

#[derive(Serialize, Deserialize, Debug)]
pub struct Firmware {
    pub name: String,
    pub version: String,
    pub chip: String,
    pub path: PathBuf,
}

#[derive(Serialize, Deserialize)]
struct Manifest {
    chip: String,
}

pub struct Storage {
    bucket: Box<s3::Bucket>,
}

impl Storage {
    pub fn new(creds: &Credentials) -> Result<Self, s3::error::S3Error> {
        let region = match creds.storage_type {
            StorageType::R2 => s3::Region::R2 {
                account_id: creds.storage_account_id.clone(),
            },
        };

        let bucket = s3::Bucket::new(
            &creds.storage_name,
            region,
            s3::creds::Credentials {
                access_key: Some(creds.storage_access_key.clone()),
                secret_key: Some(creds.storage_secret_key.clone()),
                security_token: None,
                session_token: None,
                expiration: None,
            },
        )?;
        Ok(Storage { bucket })
    }

    fn list_common_prefixes(&self, prefix: String) -> anyhow::Result<Vec<String>> {
        Ok(self
            .bucket
            .list(prefix, Some("/".to_string()))?
            .first()
            .cloned()
            .context("No response data received")?
            .common_prefixes
            .context("No common prefixes received")?
            .iter()
            .map(|p| p.prefix.strip_suffix("/").unwrap_or(&p.prefix).to_owned())
            .collect())
    }

    pub fn list_firmwares(&self) -> anyhow::Result<Vec<String>> {
        self.list_common_prefixes("".to_string())
    }

    pub fn list_firmware_versions(&self, firmware_name: &str) -> anyhow::Result<Vec<String>> {
        let mut firmware_name = firmware_name.to_owned();
        firmware_name.push('/');
        Ok(self
            .list_common_prefixes(firmware_name.clone())?
            .iter()
            .map(|p| p.strip_prefix(&firmware_name).unwrap_or(p).to_owned())
            .collect())
    }

    pub fn download_firmware(
        &self,
        name: &str,
        version: &str,
        cache_base: &Path,
    ) -> anyhow::Result<Firmware> {
        let cache_base = cache_base.to_path_buf().join(name).join(version);
        let cache_firmware = cache_base.join("firmware.elf");
        let cache_manifest = cache_base.join("manifest.json");

        if !cache_base.exists() {
            std::fs::create_dir_all(&cache_base)?;
            let bucket_base = format!("{}/{}", name, version);
            let bucket_firmware = format!("{}/firmware.elf", bucket_base);
            let bucket_manifest = format!("{}/manifest.json", bucket_base);

            eprintln!("Downloading firmware to {}...", cache_base.display());
            let firmware = self.bucket.get_object(&bucket_firmware)?;
            std::fs::write(&cache_firmware, firmware.bytes())?;
            let manifest = self.bucket.get_object(&bucket_manifest)?;
            std::fs::write(&cache_manifest, manifest.bytes())?;
        }

        let manifest: Manifest = serde_json::from_str(&std::fs::read_to_string(cache_manifest)?)?;

        Ok(Firmware {
            name: name.to_owned(),
            version: version.to_owned(),
            chip: manifest.chip,
            path: cache_firmware,
        })
    }
}