Skip to main content

uv_state/
lib.rs

1use std::{io, path::PathBuf, sync::Arc};
2
3use tempfile::{TempDir, tempdir};
4
5/// The main state storage abstraction.
6///
7/// This is appropriate for storing persistent data that is not user-facing, such as managed Python
8/// installations or tool environments.
9#[derive(Debug, Clone)]
10pub struct StateStore {
11    /// The state storage.
12    root: PathBuf,
13    /// A temporary state storage.
14    ///
15    /// Included to ensure that the temporary store exists for the length of the operation, but
16    /// is dropped at the end as appropriate.
17    _temp_dir_drop: Option<Arc<TempDir>>,
18}
19
20impl StateStore {
21    /// A persistent state store at `root`.
22    fn from_path(root: impl Into<PathBuf>) -> Self {
23        Self {
24            root: root.into(),
25            _temp_dir_drop: None,
26        }
27    }
28
29    /// Create a temporary state store.
30    pub fn temp() -> Result<Self, io::Error> {
31        let temp_dir = tempdir()?;
32        Ok(Self {
33            root: temp_dir.path().to_path_buf(),
34            _temp_dir_drop: Some(Arc::new(temp_dir)),
35        })
36    }
37
38    /// The folder for a specific cache bucket
39    pub fn bucket(&self, state_bucket: StateBucket) -> PathBuf {
40        self.root.join(state_bucket.to_str())
41    }
42
43    /// Prefer, in order:
44    ///
45    /// 1. The specific state directory specified by the user.
46    /// 2. The system-appropriate user-level data directory.
47    /// 3. A `.uv` directory in the current working directory.
48    ///
49    /// Returns an absolute cache dir.
50    pub fn from_settings(state_dir: Option<PathBuf>) -> Result<Self, io::Error> {
51        if let Some(state_dir) = state_dir {
52            Ok(Self::from_path(state_dir))
53        } else if let Some(data_dir) = uv_dirs::legacy_user_state_dir().filter(|dir| dir.exists()) {
54            // If the user has an existing directory at (e.g.) `/Users/user/Library/Application Support/uv`,
55            // respect it for backwards compatibility. Otherwise, prefer the XDG strategy, even on
56            // macOS.
57            Ok(Self::from_path(data_dir))
58        } else if let Some(data_dir) = uv_dirs::user_state_dir() {
59            Ok(Self::from_path(data_dir))
60        } else {
61            Ok(Self::from_path(".uv"))
62        }
63    }
64}
65
66/// The different kinds of data in the state store are stored in different bucket, which in our case
67/// are subdirectories of the state store root.
68#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
69pub enum StateBucket {
70    /// Managed Python installations
71    ManagedPython,
72    /// Installed tools.
73    Tools,
74    /// Credentials.
75    Credentials,
76}
77
78impl StateBucket {
79    fn to_str(self) -> &'static str {
80        match self {
81            Self::ManagedPython => "python",
82            Self::Tools => "tools",
83            Self::Credentials => "credentials",
84        }
85    }
86}