ricecoder_storage/
first_run.rs

1//! First-run initialization and storage confirmation
2//!
3//! Handles first-time initialization of RiceCoder storage,
4//! including user confirmation of storage location.
5
6use crate::error::StorageResult;
7use crate::manager::PathResolver;
8use std::fs;
9use std::path::{Path, PathBuf};
10
11/// Marker file name for tracking first-run status
12const FIRST_RUN_MARKER: &str = ".ricecoder-initialized";
13
14/// First-run handler for storage initialization
15pub struct FirstRunHandler;
16
17impl FirstRunHandler {
18    /// Check if this is the first run
19    ///
20    /// Returns true if the marker file doesn't exist in the global storage path
21    pub fn is_first_run(global_path: &Path) -> StorageResult<bool> {
22        let marker_path = global_path.join(FIRST_RUN_MARKER);
23        Ok(!marker_path.exists())
24    }
25
26    /// Mark the first run as complete
27    ///
28    /// Creates the marker file to indicate initialization is done
29    pub fn mark_first_run_complete(global_path: &Path) -> StorageResult<()> {
30        let marker_path = global_path.join(FIRST_RUN_MARKER);
31
32        // Ensure parent directory exists
33        if let Some(parent) = marker_path.parent() {
34            if !parent.exists() {
35                fs::create_dir_all(parent).map_err(|e| {
36                    crate::error::StorageError::directory_creation_failed(parent.to_path_buf(), e)
37                })?;
38            }
39        }
40
41        // Create the marker file
42        fs::write(&marker_path, "").map_err(|e| {
43            crate::error::StorageError::io_error(marker_path, crate::error::IoOperation::Write, e)
44        })?;
45
46        Ok(())
47    }
48
49    /// Get the suggested global storage path
50    ///
51    /// Returns the path that would be used for global storage
52    pub fn get_suggested_path() -> StorageResult<PathBuf> {
53        PathResolver::resolve_global_path()
54    }
55
56    /// Detect first-time initialization
57    ///
58    /// Returns true if:
59    /// - The global storage directory doesn't exist, OR
60    /// - The marker file doesn't exist in the global storage directory
61    pub fn detect_first_run() -> StorageResult<bool> {
62        let global_path = PathResolver::resolve_global_path()?;
63
64        if !global_path.exists() {
65            return Ok(true);
66        }
67
68        Self::is_first_run(&global_path)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use tempfile::TempDir;
76
77    #[test]
78    fn test_is_first_run_no_marker() {
79        let temp_dir = TempDir::new().expect("Failed to create temp dir");
80        let is_first =
81            FirstRunHandler::is_first_run(temp_dir.path()).expect("Failed to check first run");
82        assert!(is_first, "Should be first run when marker doesn't exist");
83    }
84
85    #[test]
86    fn test_is_first_run_with_marker() {
87        let temp_dir = TempDir::new().expect("Failed to create temp dir");
88        let marker_path = temp_dir.path().join(FIRST_RUN_MARKER);
89        fs::write(&marker_path, "").expect("Failed to create marker");
90
91        let is_first =
92            FirstRunHandler::is_first_run(temp_dir.path()).expect("Failed to check first run");
93        assert!(!is_first, "Should not be first run when marker exists");
94    }
95
96    #[test]
97    fn test_mark_first_run_complete() {
98        let temp_dir = TempDir::new().expect("Failed to create temp dir");
99        let path = temp_dir.path().to_path_buf();
100
101        // Initially should be first run
102        let is_first_before =
103            FirstRunHandler::is_first_run(&path).expect("Failed to check first run");
104        assert!(is_first_before);
105
106        // Mark as complete
107        FirstRunHandler::mark_first_run_complete(&path).expect("Failed to mark first run complete");
108
109        // Should no longer be first run
110        let is_first_after =
111            FirstRunHandler::is_first_run(&path).expect("Failed to check first run");
112        assert!(!is_first_after);
113    }
114
115    #[test]
116    fn test_get_suggested_path() {
117        let path = FirstRunHandler::get_suggested_path().expect("Failed to get suggested path");
118        assert!(path.to_string_lossy().contains(".ricecoder"));
119    }
120
121    #[test]
122    fn test_detect_first_run_nonexistent_dir() {
123        // This test uses the actual path resolution, so we just verify it returns a result
124        let result = FirstRunHandler::detect_first_run();
125        assert!(
126            result.is_ok(),
127            "Should successfully detect first run status"
128        );
129    }
130}