pubky_homeserver/data_directory/
persistent_data_dir.rs1use super::{data_dir::DataDir, ConfigToml};
2
3use std::{
4 io::Write,
5 path::{Path, PathBuf},
6};
7
8#[derive(Debug, Clone)]
13pub struct PersistentDataDir {
14 expanded_path: PathBuf,
15}
16
17impl PersistentDataDir {
18 pub fn new(path: PathBuf) -> Self {
21 Self {
22 expanded_path: Self::expand_home_dir(path),
23 }
24 }
25
26 fn expand_home_dir(path: PathBuf) -> PathBuf {
29 let path = match path.to_str() {
30 Some(path) => path,
31 None => {
32 return path;
34 }
35 };
36
37 if path.starts_with("~/") {
38 if let Some(home) = dirs::home_dir() {
39 let without_home = path.strip_prefix("~/").expect("Invalid ~ prefix");
40 let joined = home.join(without_home);
41 return joined;
42 }
43 }
44 PathBuf::from(path)
45 }
46
47 pub fn get_config_file_path(&self) -> PathBuf {
49 self.expanded_path.join("config.toml")
50 }
51
52 fn write_sample_config_file(&self) -> anyhow::Result<()> {
53 let config_string = ConfigToml::sample_string();
54 let config_file_path = self.get_config_file_path();
55 let mut config_file = std::fs::File::create(config_file_path)?;
56 config_file.write_all(config_string.as_bytes())?;
57 Ok(())
58 }
59
60 pub fn get_secret_file_path(&self) -> PathBuf {
62 self.expanded_path.join("secret")
63 }
64}
65
66impl Default for PersistentDataDir {
67 fn default() -> Self {
68 Self::new(PathBuf::from("~/.pubky"))
69 }
70}
71
72impl DataDir for PersistentDataDir {
73 fn path(&self) -> &Path {
75 &self.expanded_path
76 }
77
78 fn ensure_data_dir_exists_and_is_writable(&self) -> anyhow::Result<()> {
81 std::fs::create_dir_all(&self.expanded_path)?;
82
83 let test_file_path = self
85 .expanded_path
86 .join("test_write_f2d560932f9b437fa9ef430ba436d611"); std::fs::write(test_file_path.clone(), b"test")
88 .map_err(|err| anyhow::anyhow!("Failed to write to data directory: {}", err))?;
89 std::fs::remove_file(test_file_path)
90 .map_err(|err| anyhow::anyhow!("Failed to write to data directory: {}", err))?;
91 Ok(())
92 }
93
94 fn read_or_create_config_file(&self) -> anyhow::Result<ConfigToml> {
97 let config_file_path = self.get_config_file_path();
98 if !config_file_path.exists() {
99 self.write_sample_config_file()?;
100 }
101 let config = ConfigToml::from_file(config_file_path)?;
102 Ok(config)
103 }
104
105 fn read_or_create_keypair(&self) -> anyhow::Result<pubky_common::crypto::Keypair> {
107 let secret_file_path = self.get_secret_file_path();
108 if !secret_file_path.exists() {
109 pubky_common::crypto::Keypair::random().write_secret_key_file(&secret_file_path)?;
111 tracing::info!("Secret file created at {}", secret_file_path.display());
112 }
113 let keypair = pubky_common::crypto::Keypair::from_secret_key_file(&secret_file_path)?;
115 Ok(keypair)
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use std::io::Write;
122
123 use super::*;
124 use tempfile::TempDir;
125
126 #[test]
128 pub fn test_expand_home_dir() {
129 let data_dir = PersistentDataDir::new(PathBuf::from("~/.pubky"));
130 let homedir = dirs::home_dir().unwrap();
131 let expanded_path = homedir.join(".pubky");
132 assert_eq!(data_dir.expanded_path, expanded_path);
133 }
134
135 #[test]
137 pub fn test_ensure_data_dir_exists_and_is_accessible() {
138 let temp_dir = TempDir::new().unwrap();
139 let test_path = temp_dir.path().join(".pubky");
140 let data_dir = PersistentDataDir::new(test_path.clone());
141
142 data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
143 assert!(test_path.exists());
144 }
146
147 #[test]
148 pub fn test_get_default_config_file_path_exists() {
149 let temp_dir = TempDir::new().unwrap();
150 let test_path = temp_dir.path().join(".pubky");
151 let data_dir = PersistentDataDir::new(test_path.clone());
152 data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
153 let config_file_path = data_dir.get_config_file_path();
154 assert!(!config_file_path.exists()); let mut config_file = std::fs::File::create(config_file_path.clone()).unwrap();
157 config_file.write_all(b"test").unwrap();
158 assert!(config_file_path.exists()); }
161
162 #[test]
163 pub fn test_read_or_create_config_file() {
164 let temp_dir = TempDir::new().unwrap();
165 let test_path = temp_dir.path().join(".pubky");
166 let data_dir = PersistentDataDir::new(test_path.clone());
167 data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
168 let _ = data_dir.read_or_create_config_file().unwrap(); assert!(data_dir.get_config_file_path().exists());
170
171 let _ = data_dir.read_or_create_config_file().unwrap(); assert!(data_dir.get_config_file_path().exists());
173 }
174
175 #[test]
176 pub fn test_read_or_create_config_file_dont_override_existing_file() {
177 let temp_dir = TempDir::new().unwrap();
178 let test_path = temp_dir.path().join(".pubky");
179 let data_dir = PersistentDataDir::new(test_path.clone());
180 data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
181
182 let config_file_path = data_dir.get_config_file_path();
184 std::fs::write(config_file_path.clone(), b"test").unwrap();
185 assert!(config_file_path.exists()); let read_result = data_dir.read_or_create_config_file();
189 assert!(read_result.is_err());
190
191 let content = std::fs::read_to_string(config_file_path).unwrap();
193 assert_eq!(content, "test");
194 }
195
196 #[test]
197 pub fn test_create_secret_file() {
198 let temp_dir = TempDir::new().unwrap();
199 let test_path = temp_dir.path().join(".pubky");
200 let data_dir = PersistentDataDir::new(test_path.clone());
201 data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
202
203 let _ = data_dir.read_or_create_keypair().unwrap();
204 assert!(data_dir.get_secret_file_path().exists());
205 }
206
207 #[test]
208 pub fn test_dont_override_existing_secret_file() {
209 let temp_dir = TempDir::new().unwrap();
210 let test_path = temp_dir.path().join(".pubky");
211 let data_dir = PersistentDataDir::new(test_path.clone());
212 data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
213
214 let secret_file_path = data_dir.get_secret_file_path();
216 std::fs::write(secret_file_path.clone(), b"test").unwrap();
217
218 let result = data_dir.read_or_create_keypair();
219 assert!(result.is_err());
220 assert!(data_dir.get_secret_file_path().exists());
221 let content = std::fs::read_to_string(secret_file_path).unwrap();
222 assert_eq!(content, "test");
223 }
224
225 #[test]
226 pub fn test_trim_secret_file_content() {
227 let temp_dir = TempDir::new().unwrap();
228 let test_path = temp_dir.path().join(".pubky");
229 let data_dir = PersistentDataDir::new(test_path.clone());
230 data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
231
232 let keypair = pubky_common::crypto::Keypair::random();
234 let secret_file_path = data_dir.get_secret_file_path();
235 let file_content = format!("\n {}\n \n", hex::encode(keypair.secret_key()));
236 std::fs::write(secret_file_path.clone(), file_content).unwrap();
237
238 let result = data_dir.read_or_create_keypair();
239 assert!(result.is_ok());
240 let read_keypair = result.unwrap();
241 assert_eq!(read_keypair.secret_key(), keypair.secret_key());
242 }
243}