@@ -19,6 +19,7 @@ pub const CURRENT_VERSION: u32 = 1;
1919/// Migrate config to the current version.
2020fn migrate_config ( mut config : Config ) -> Config {
2121 // Placeholder for future migrations. For now, just ensure version is current.
22+ // Update version field if config is from an older schema (enables future migration logic).
2223 if config. version < CURRENT_VERSION {
2324 config. version = CURRENT_VERSION ;
2425 }
@@ -27,7 +28,10 @@ fn migrate_config(mut config: Config) -> Config {
2728
2829/// Compute the cross-platform path for the config file.
2930pub fn config_path ( ) -> PathBuf {
31+ // Get platform-specific config directory; fallback to current directory if unavailable.
32+ // Fallback to "." is defensive (rare edge case where config_dir() fails).
3033 let base = config_dir ( ) . unwrap_or_else ( || PathBuf :: from ( "." ) ) ;
34+ // Join app name and filename to form full path (e.g., ~/.config/keyless/config.json).
3135 let p = base. join ( "keyless" ) . join ( "config.json" ) ;
3236 tracing:: trace!( path=%p. display( ) , "config: computed config path" ) ;
3337 p
@@ -36,20 +40,25 @@ pub fn config_path() -> PathBuf {
3640/// Load config from disk (returns None if missing or invalid).
3741pub fn load_config ( ) -> Option < Config > {
3842 let path = config_path ( ) ;
43+ // Read entire file into memory; small file (<10KB) so one-shot read is fine.
3944 let data = match fs:: read ( & path) {
4045 Ok ( d) => d,
4146 Err ( e) => {
47+ // File missing is expected on first run; log as debug, not error.
4248 tracing:: debug!( path=%path. display( ) , error=%e, "config: no config file yet" ) ;
4349 return None ;
4450 }
4551 } ;
52+ // Parse JSON; handle parse errors gracefully (malformed config = None, not panic).
4653 match serde_json:: from_slice :: < Config > ( & data) {
4754 Ok ( config) => {
55+ // Apply migrations if needed (updates version field for older configs).
4856 let config = migrate_config ( config) ;
4957 tracing:: debug!( path=%path. display( ) , "config: loaded successfully" ) ;
5058 Some ( config)
5159 }
5260 Err ( e) => {
61+ // Log parse error as warning (user may have corrupted config manually).
5362 tracing:: warn!( path=%path. display( ) , error=%e, "config: failed to parse" ) ;
5463 None
5564 }
@@ -59,22 +68,29 @@ pub fn load_config() -> Option<Config> {
5968/// Save config to disk (atomic-ish: create parent, write file).
6069pub fn save_config ( config : & Config ) -> io:: Result < ( ) > {
6170 let path = config_path ( ) ;
71+ // Create parent directory if missing (e.g., ~/.config/keyless/).
72+ // Propagate errors to surface permission issues early (better than silent failure).
6273 if let Some ( dir) = path. parent ( ) {
6374 // Propagate directory creation errors to surface permission issues early
6475 fs:: create_dir_all ( dir) ?;
6576 }
77+ // Serialize to pretty-printed JSON (indented, human-readable).
78+ // Convert serde error to io::Error for consistent error handling.
6679 let data = serde_json:: to_vec_pretty ( config)
6780 . map_err ( |e| io:: Error :: other ( format ! ( "serialize config: {}" , e) ) ) ?;
68- // Atomic-ish write: write to a temp file then rename
81+ // Atomic-ish write: write to temp file then rename (prevents partial writes).
82+ // Temp file: config.json.tmp (same directory, atomic rename on most platforms).
6983 let tmp = path. with_extension ( "json.tmp" ) ;
7084 fs:: write ( & tmp, & data) ?;
7185 match fs:: rename ( & tmp, & path) {
7286 Ok ( ( ) ) => { }
7387 Err ( _) => {
74- // Retry once after a short delay (helps on platforms with transient locks)
88+ // Retry once after a short delay (helps on platforms with transient locks).
89+ // Some filesystems/antivirus temporarily lock files during writes.
7590 std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 10 ) ) ;
7691 if fs:: rename ( & tmp, & path) . is_err ( ) {
77- // Fallback: copy then remove the temp file
92+ // Fallback: copy then remove temp file (non-atomic but ensures write succeeds).
93+ // Prefer copy over leaving temp file (avoids stale temp files on disk).
7894 fs:: copy ( & tmp, & path) ?;
7995 let _ = fs:: remove_file ( & tmp) ;
8096 }
0 commit comments