lux_lib/operations/
pin.rs1use std::io;
2
3use fs_extra::dir::CopyOptions;
4use itertools::Itertools;
5use thiserror::Error;
6
7use crate::{
8 lockfile::{FlushLockfileError, LocalPackageId, PinnedState},
9 package::PackageSpec,
10 tree::{Tree, TreeError},
11};
12
13#[derive(Error, Debug)]
16pub enum PinError {
17 #[error("package with ID {0} not found in the lockfile")]
18 PackageNotFound(LocalPackageId),
19 #[error("rock {rock} is already {}pinned!", if *.pin_state == PinnedState::Unpinned { "un" } else { "" })]
20 PinStateUnchanged {
21 pin_state: PinnedState,
22 rock: PackageSpec,
23 },
24 #[error("cannot change pin state of {rock}, since a second version of {rock} is already installed with `pin: {}`", .pin_state.as_bool())]
25 PinStateConflict {
26 pin_state: PinnedState,
27 rock: PackageSpec,
28 },
29 #[error(transparent)]
30 FlushLockfile(#[from] FlushLockfileError),
31 #[error(transparent)]
32 Tree(#[from] TreeError),
33 #[error("failed to move old package: {0}")]
34 MoveItemsFailure(#[from] fs_extra::error::Error),
35 #[error("cannot change pin state of {rock}, because it is not an entrypoint")]
36 NotAnEntrypoint { rock: PackageSpec },
37 #[error("error reading directory {0}:\n{1}")]
38 ReadDir(String, io::Error),
39 #[error("error creating directory {0}:\n{1}")]
40 CreateDir(String, io::Error),
41}
42
43pub fn set_pinned_state(
44 package_id: &LocalPackageId,
45 tree: &Tree,
46 pin: PinnedState,
47) -> Result<(), PinError> {
48 let lockfile = tree.lockfile()?;
49 let mut package = lockfile
50 .get(package_id)
51 .ok_or_else(|| PinError::PackageNotFound(package_id.clone()))?
52 .clone();
53
54 if !lockfile.is_entrypoint(&package.id()) {
55 return Err(PinError::NotAnEntrypoint {
56 rock: package.to_package(),
57 });
58 }
59
60 if pin == package.pinned() {
61 return Err(PinError::PinStateUnchanged {
62 pin_state: package.pinned(),
63 rock: package.to_package(),
64 });
65 }
66
67 let old_package = package.clone();
68 let package_root = tree.root_for(&package);
69 let items = std::fs::read_dir(&package_root)
70 .map_err(|err| PinError::ReadDir(package_root.to_string_lossy().to_string(), err))?
71 .filter_map(Result::ok)
72 .map(|dir| dir.path())
73 .collect_vec();
74
75 package.spec.pinned = pin;
76
77 if lockfile.get(&package.id()).is_some() {
78 return Err(PinError::PinStateConflict {
79 pin_state: package.pinned(),
80 rock: package.to_package(),
81 });
82 }
83
84 let new_root = tree.root_for(&package);
85
86 std::fs::create_dir_all(&new_root)
87 .map_err(|err| PinError::CreateDir(new_root.to_string_lossy().to_string(), err))?;
88
89 fs_extra::move_items(&items, new_root, &CopyOptions::new())?;
90
91 lockfile.map_then_flush(|lockfile| {
92 lockfile.remove(&old_package);
93 lockfile.add_entrypoint(&package);
94
95 Ok::<_, io::Error>(())
96 })?;
97
98 Ok(())
99}