binstalk_manifests/
crates_manifests.rs

1use std::{
2    collections::BTreeMap,
3    fs,
4    io::{self, Seek},
5    path::Path,
6};
7
8use fs_lock::FileLock;
9use miette::Diagnostic;
10use thiserror::Error as ThisError;
11
12use crate::{
13    binstall_crates_v1::{Error as BinstallCratesV1Error, Records as BinstallCratesV1Records},
14    cargo_crates_v1::{CratesToml, CratesTomlParseError},
15    crate_info::CrateInfo,
16    CompactString, Version,
17};
18
19#[derive(Debug, Diagnostic, ThisError)]
20#[non_exhaustive]
21pub enum ManifestsError {
22    #[error("failed to parse binstall crates-v1 manifest: {0}")]
23    #[diagnostic(transparent)]
24    BinstallCratesV1(#[from] BinstallCratesV1Error),
25
26    #[error("failed to parse cargo v1 manifest: {0}")]
27    #[diagnostic(transparent)]
28    CargoManifestV1(#[from] CratesTomlParseError),
29
30    #[error("I/O error: {0}")]
31    Io(#[from] io::Error),
32}
33
34pub struct Manifests {
35    binstall: BinstallCratesV1Records,
36    cargo_crates_v1: FileLock,
37}
38
39impl Manifests {
40    pub fn open_exclusive(cargo_roots: &Path) -> Result<Self, ManifestsError> {
41        // Read cargo_binstall_metadata
42        let metadata_path = cargo_roots.join("binstall/crates-v1.json");
43        fs::create_dir_all(metadata_path.parent().unwrap())?;
44
45        let binstall = BinstallCratesV1Records::load_from_path(&metadata_path)?;
46
47        // Read cargo_install_v1_metadata
48        let manifest_path = cargo_roots.join(".crates.toml");
49
50        let cargo_crates_v1 = fs::File::options()
51            .read(true)
52            .write(true)
53            .create(true)
54            .truncate(false)
55            .open(manifest_path)
56            .and_then(FileLock::new_exclusive)?;
57
58        Ok(Self {
59            binstall,
60            cargo_crates_v1,
61        })
62    }
63
64    fn rewind_cargo_crates_v1(&mut self) -> Result<(), ManifestsError> {
65        self.cargo_crates_v1.rewind().map_err(ManifestsError::from)
66    }
67
68    /// `cargo-uninstall` can be called to uninstall crates,
69    /// but it only updates .crates.toml.
70    ///
71    /// So here we will honour .crates.toml only.
72    pub fn load_installed_crates(
73        &mut self,
74    ) -> Result<BTreeMap<CompactString, Version>, ManifestsError> {
75        self.rewind_cargo_crates_v1()?;
76
77        CratesToml::load_from_reader(&mut self.cargo_crates_v1)
78            .and_then(CratesToml::collect_into_crates_versions)
79            .map_err(ManifestsError::from)
80    }
81
82    pub fn update(mut self, metadata_vec: Vec<CrateInfo>) -> Result<(), ManifestsError> {
83        self.rewind_cargo_crates_v1()?;
84
85        CratesToml::append_to_file(&mut self.cargo_crates_v1, &metadata_vec)?;
86
87        for metadata in metadata_vec {
88            self.binstall.replace(metadata);
89        }
90        self.binstall.overwrite()?;
91
92        Ok(())
93    }
94}