1use crate::core::{BraidError, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::PathBuf;
5use tokio::fs;
6
7#[derive(Debug, Serialize, Deserialize, Clone)]
8pub struct Config {
9 #[serde(default)]
10 pub peer_id: String,
11 #[serde(default)]
12 pub sync: HashMap<String, bool>,
13 #[serde(default)]
14 pub cookies: HashMap<String, String>,
15 #[serde(default)]
16 pub identities: HashMap<String, String>,
17 #[serde(default = "default_port")]
18 pub port: u16,
19 #[serde(default)]
21 pub ignore_patterns: Vec<String>,
22 #[serde(default = "default_debounce_ms")]
24 pub debounce_ms: u64,
25}
26
27fn default_debounce_ms() -> u64 {
28 100
29}
30
31fn default_port() -> u16 {
32 45678
33}
34
35impl Config {
36 pub async fn load() -> Result<Self> {
37 let config_path = get_config_path()?;
38
39 if !config_path.exists() {
40 return Ok(Config::default());
41 }
42
43 let content = fs::read_to_string(&config_path)
44 .await
45 .map_err(|e| BraidError::Io(e))?;
46
47 let mut config: Config = serde_json::from_str(&content).map_err(|e| BraidError::Json(e))?;
48
49 if config.peer_id.is_empty() {
50 config.peer_id = format!("braidfs_{}", &uuid::Uuid::new_v4().to_string()[..8]);
51 config.save().await?;
52 }
53
54 Ok(config)
55 }
56
57 pub async fn save(&self) -> Result<()> {
58 let config_path = get_config_path()?;
59
60 if let Some(parent) = config_path.parent() {
61 fs::create_dir_all(parent)
62 .await
63 .map_err(|e| BraidError::Io(e))?;
64 }
65
66 let content = serde_json::to_string_pretty(self).map_err(|e| BraidError::Json(e))?;
67 let _ = fs::write(&config_path, content)
68 .await
69 .map_err(|e| BraidError::Io(e))?;
70
71 Ok(())
72 }
73}
74
75impl Default for Config {
76 fn default() -> Self {
77 Self {
78 peer_id: format!("braidfs_{}", &uuid::Uuid::new_v4().to_string()[..8]),
79 sync: HashMap::new(),
80 cookies: HashMap::new(),
81 identities: HashMap::new(),
82 port: default_port(),
83 ignore_patterns: default_ignore_patterns(),
84 debounce_ms: default_debounce_ms(),
85 }
86 }
87}
88
89fn default_ignore_patterns() -> Vec<String> {
91 vec![
92 ".git".to_string(),
93 ".git/**".to_string(),
94 "node_modules/**".to_string(),
95 ".DS_Store".to_string(),
96 "*.swp".to_string(),
97 "*.swo".to_string(),
98 "*~".to_string(),
99 ".braidfs/**".to_string(),
100 ]
101}
102
103pub fn get_config_path() -> Result<PathBuf> {
104 let root = get_root_dir()?;
105 Ok(root.join(".braidfs").join("config"))
106}
107
108pub fn get_root_dir() -> Result<PathBuf> {
109 let root_str = std::env::var("BRAID_ROOT").unwrap_or_else(|_| {
110 let home = dirs::home_dir().expect("Could not find home directory");
111 home.join("http").to_string_lossy().to_string()
112 });
113
114 let root = PathBuf::from(root_str);
115 if let Ok(abs) = std::fs::canonicalize(&root) {
116 Ok(abs)
117 } else {
118 Ok(std::env::current_dir()
119 .map_err(|e| BraidError::Io(e))?
120 .join(root))
121 }
122}
123
124pub fn get_trash_dir() -> Result<PathBuf> {
126 let root = get_root_dir()?;
127 Ok(root.join(".braidfs").join("trash"))
128}
129
130pub fn is_binary(filename: &str) -> bool {
132 let binary_extensions = [
133 ".jpg", ".jpeg", ".png", ".gif", ".mp4", ".mp3", ".zip", ".tar", ".rar", ".pdf", ".doc",
134 ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".exe", ".dll", ".so", ".dylib", ".bin", ".iso",
135 ".img", ".bmp", ".tiff", ".svg", ".webp", ".avi", ".mov", ".wmv", ".flv", ".mkv", ".wav",
136 ".flac", ".aac", ".ogg", ".wma", ".7z", ".gz", ".bz2", ".xz",
137 ];
138
139 let filename_lower = filename.to_lowercase();
140 binary_extensions
141 .iter()
142 .any(|ext| filename_lower.ends_with(ext))
143}
144
145pub fn skip_file(path: &str) -> bool {
147 if path.contains('#') {
148 return true;
149 }
150 if path.ends_with(".DS_Store") {
151 return true;
152 }
153 if path.starts_with(".braidfs")
154 && !path.starts_with(".braidfs/config")
155 && !path.starts_with(".braidfs/errors")
156 {
157 return true;
158 }
159 false
160}
161
162pub async fn trash_file(fullpath: &std::path::Path, path: &str) -> Result<PathBuf> {
164 let trash_dir = get_trash_dir()?;
165 tokio::fs::create_dir_all(&trash_dir).await?;
166
167 let random = uuid::Uuid::new_v4().to_string()[..8].to_string();
168 let filename = path.replace(['/', '\\'], "_");
169 let dest = trash_dir.join(format!("{}_{}", filename, random));
170
171 tokio::fs::rename(fullpath, &dest).await?;
172 tracing::warn!("Moved unsynced file to trash: {:?} -> {:?}", fullpath, dest);
173
174 Ok(dest)
175}