1use std::{
2 io::{self, Write},
3 path::{Path, PathBuf},
4 sync::Arc,
5};
6
7use fs_err as fs;
8use tempfile::{TempDir, tempdir};
9
10#[derive(Debug, Clone)]
15pub struct StateStore {
16 root: PathBuf,
18 _temp_dir_drop: Option<Arc<TempDir>>,
23}
24
25impl StateStore {
26 pub fn from_path(root: impl Into<PathBuf>) -> Result<Self, io::Error> {
28 Ok(Self {
29 root: root.into(),
30 _temp_dir_drop: None,
31 })
32 }
33
34 pub fn temp() -> Result<Self, io::Error> {
36 let temp_dir = tempdir()?;
37 Ok(Self {
38 root: temp_dir.path().to_path_buf(),
39 _temp_dir_drop: Some(Arc::new(temp_dir)),
40 })
41 }
42
43 pub fn root(&self) -> &Path {
45 &self.root
46 }
47
48 pub fn init(self) -> Result<Self, io::Error> {
50 let root = &self.root;
51
52 fs::create_dir_all(root)?;
54
55 match fs::OpenOptions::new()
57 .write(true)
58 .create_new(true)
59 .open(root.join(".gitignore"))
60 {
61 Ok(mut file) => file.write_all(b"*")?,
62 Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
63 Err(err) => return Err(err),
64 }
65
66 Ok(Self {
67 root: fs::canonicalize(root)?,
68 ..self
69 })
70 }
71
72 pub fn bucket(&self, state_bucket: StateBucket) -> PathBuf {
74 self.root.join(state_bucket.to_str())
75 }
76
77 pub fn from_settings(state_dir: Option<PathBuf>) -> Result<Self, io::Error> {
85 if let Some(state_dir) = state_dir {
86 Self::from_path(state_dir)
87 } else if let Some(data_dir) = uv_dirs::legacy_user_state_dir().filter(|dir| dir.exists()) {
88 Self::from_path(data_dir)
92 } else if let Some(data_dir) = uv_dirs::user_state_dir() {
93 Self::from_path(data_dir)
94 } else {
95 Self::from_path(".uv")
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
103pub enum StateBucket {
104 ManagedPython,
106 Tools,
108 Credentials,
110}
111
112impl StateBucket {
113 fn to_str(self) -> &'static str {
114 match self {
115 Self::ManagedPython => "python",
116 Self::Tools => "tools",
117 Self::Credentials => "credentials",
118 }
119 }
120}