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}