Skip to main content

ralph/queue/
size_check.rs

1//! Queue file size checking and warning generation.
2//!
3//! Responsibilities:
4//! - Check if queue file exceeds size or task count thresholds.
5//! - Generate user-friendly warning messages with remediation suggestions.
6//!
7//! Not handled here:
8//! - Configuration loading (passed in by caller).
9//! - Actual queue operations (archive/prune) - only suggestions.
10
11use crate::constants::limits::{
12    DEFAULT_SIZE_WARNING_THRESHOLD_KB, DEFAULT_TASK_COUNT_WARNING_THRESHOLD,
13};
14use std::path::Path;
15
16use anyhow::{Context, Result};
17
18/// Information about the queue file size and task count.
19#[derive(Debug, Clone, Copy)]
20pub struct QueueSizeInfo {
21    /// File size in kilobytes.
22    pub file_size_kb: u64,
23    /// Number of tasks in the queue.
24    pub task_count: usize,
25}
26
27/// Result of checking queue size against thresholds.
28#[derive(Debug, Clone, Copy)]
29pub struct SizeCheckResult {
30    /// Whether the file size exceeds the threshold.
31    pub exceeds_size_threshold: bool,
32    /// Whether the task count exceeds the threshold.
33    pub exceeds_count_threshold: bool,
34    /// The size information that was checked.
35    pub info: QueueSizeInfo,
36    /// The size threshold that was used for the check (in KB).
37    pub size_threshold_kb: u32,
38    /// The task count threshold that was used for the check.
39    pub count_threshold: u32,
40}
41
42/// Check if queue exceeds configured thresholds.
43///
44/// # Arguments
45/// * `queue_path` - Path to the queue file.
46/// * `task_count` - Number of tasks in the queue.
47/// * `size_threshold_kb` - Threshold for file size warning in KB.
48/// * `count_threshold` - Threshold for task count warning.
49///
50/// # Returns
51/// A `SizeCheckResult` indicating which thresholds (if any) were exceeded.
52pub fn check_queue_size(
53    queue_path: &Path,
54    task_count: usize,
55    size_threshold_kb: u32,
56    count_threshold: u32,
57) -> Result<SizeCheckResult> {
58    let metadata = std::fs::metadata(queue_path)
59        .with_context(|| format!("read metadata for {}", queue_path.display()))?;
60
61    let file_size_bytes = metadata.len();
62    let file_size_kb = file_size_bytes / 1024;
63
64    let exceeds_size_threshold = file_size_kb > u64::from(size_threshold_kb);
65    let exceeds_count_threshold = task_count > count_threshold as usize;
66
67    Ok(SizeCheckResult {
68        exceeds_size_threshold,
69        exceeds_count_threshold,
70        info: QueueSizeInfo {
71            file_size_kb,
72            task_count,
73        },
74        size_threshold_kb,
75        count_threshold,
76    })
77}
78
79/// Print warning if thresholds exceeded (respects quiet flag).
80///
81/// # Arguments
82/// * `result` - The result from `check_queue_size`.
83/// * `quiet` - If true, suppresses the warning output.
84pub fn print_size_warning_if_needed(result: &SizeCheckResult, quiet: bool) {
85    if quiet {
86        return;
87    }
88
89    if !result.exceeds_size_threshold && !result.exceeds_count_threshold {
90        return;
91    }
92
93    eprintln!();
94    eprintln!("⚠️  Queue size warning:");
95
96    if result.exceeds_size_threshold {
97        eprintln!(
98            "   Queue file size is {}KB (threshold: {}KB)",
99            result.info.file_size_kb, result.size_threshold_kb
100        );
101    }
102
103    if result.exceeds_count_threshold {
104        eprintln!(
105            "   Queue has {} tasks (threshold: {})",
106            result.info.task_count, result.count_threshold
107        );
108    }
109
110    eprintln!();
111    eprintln!("   Consider running maintenance commands:");
112    eprintln!("     ralph queue archive    # Move completed tasks to the done archive");
113    eprintln!("     ralph queue prune      # Remove old tasks from the done archive");
114    eprintln!();
115}
116
117/// Get the configured size threshold, or the default.
118pub fn size_threshold_or_default(threshold: Option<u32>) -> u32 {
119    threshold.unwrap_or(DEFAULT_SIZE_WARNING_THRESHOLD_KB)
120}
121
122/// Get the configured task count threshold, or the default.
123pub fn count_threshold_or_default(threshold: Option<u32>) -> u32 {
124    threshold.unwrap_or(DEFAULT_TASK_COUNT_WARNING_THRESHOLD)
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use tempfile::TempDir;
131
132    #[test]
133    fn check_queue_size_detects_size_threshold() -> Result<()> {
134        let temp = TempDir::new()?;
135        let queue_path = temp.path().join("queue.json");
136
137        // Create a file that's 1KB
138        let content = "x".repeat(1024);
139        std::fs::write(&queue_path, content)?;
140
141        // Threshold of 500 bytes (0.5KB) - should trigger
142        let result = check_queue_size(&queue_path, 10, 0, 100)?;
143        assert!(result.exceeds_size_threshold);
144        assert!(!result.exceeds_count_threshold);
145        assert_eq!(result.info.file_size_kb, 1);
146
147        Ok(())
148    }
149
150    #[test]
151    fn check_queue_size_detects_count_threshold() -> Result<()> {
152        let temp = TempDir::new()?;
153        let queue_path = temp.path().join("queue.json");
154
155        // Small file
156        std::fs::write(&queue_path, r#"{"tasks": []}"#)?;
157
158        // Threshold of 10 tasks - should trigger with 15 tasks
159        let result = check_queue_size(&queue_path, 15, 1000, 10)?;
160        assert!(!result.exceeds_size_threshold);
161        assert!(result.exceeds_count_threshold);
162        assert_eq!(result.info.task_count, 15);
163
164        Ok(())
165    }
166
167    #[test]
168    fn check_queue_size_no_threshold_exceeded() -> Result<()> {
169        let temp = TempDir::new()?;
170        let queue_path = temp.path().join("queue.json");
171
172        // Small file
173        std::fs::write(&queue_path, r#"{"tasks": []}"#)?;
174
175        // High thresholds - should not trigger
176        let result = check_queue_size(&queue_path, 10, 10000, 5000)?;
177        assert!(!result.exceeds_size_threshold);
178        assert!(!result.exceeds_count_threshold);
179
180        Ok(())
181    }
182
183    #[test]
184    fn check_queue_size_detects_both_thresholds() -> Result<()> {
185        let temp = TempDir::new()?;
186        let queue_path = temp.path().join("queue.json");
187
188        // Create a file larger than 1KB
189        let content = "x".repeat(2048);
190        std::fs::write(&queue_path, content)?;
191
192        // Both thresholds should trigger
193        let result = check_queue_size(&queue_path, 1000, 1, 500)?;
194        assert!(result.exceeds_size_threshold);
195        assert!(result.exceeds_count_threshold);
196
197        Ok(())
198    }
199
200    #[test]
201    fn threshold_helpers_return_defaults() {
202        assert_eq!(
203            size_threshold_or_default(None),
204            DEFAULT_SIZE_WARNING_THRESHOLD_KB
205        );
206        assert_eq!(
207            count_threshold_or_default(None),
208            DEFAULT_TASK_COUNT_WARNING_THRESHOLD
209        );
210    }
211
212    #[test]
213    fn threshold_helpers_return_configured() {
214        assert_eq!(size_threshold_or_default(Some(1000)), 1000);
215        assert_eq!(count_threshold_or_default(Some(1000)), 1000);
216    }
217}