use chrono::{DateTime, Utc};
use std::cmp::Ordering;
use std::path::PathBuf;
use super::detector::ChangeType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Trigger {
User,
Save,
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Priority {
High,
Medium,
Low,
}
impl Priority {
pub fn from_trigger(trigger: Trigger) -> Self {
match trigger {
Trigger::User => Priority::High,
Trigger::Save => Priority::Medium,
Trigger::Auto => Priority::Low,
}
}
fn as_value(&self) -> u8 {
match self {
Priority::High => 3,
Priority::Medium => 2,
Priority::Low => 1,
}
}
}
impl PartialOrd for Priority {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Priority {
fn cmp(&self, other: &Self) -> Ordering {
self.as_value().cmp(&other.as_value())
}
}
#[derive(Debug, Clone)]
pub struct UpdateTask {
pub path: PathBuf,
pub change_type: ChangeType,
pub trigger: Trigger,
pub priority: Priority,
pub created_at: DateTime<Utc>,
pub retry_count: u32,
}
impl UpdateTask {
pub fn new(path: PathBuf, change_type: ChangeType, trigger: Trigger) -> Self {
Self {
path,
change_type,
trigger,
priority: Priority::from_trigger(trigger),
created_at: Utc::now(),
retry_count: 0,
}
}
pub fn merge(&mut self, other: UpdateTask) {
if other.priority > self.priority {
self.priority = other.priority;
self.trigger = other.trigger;
}
self.change_type = Self::merge_change_types(&self.change_type, &other.change_type);
if other.created_at > self.created_at {
self.created_at = other.created_at;
}
self.retry_count = 0;
}
fn merge_change_types(first: &ChangeType, second: &ChangeType) -> ChangeType {
match (first, second) {
(ChangeType::New(_), ChangeType::Deleted(_)) => ChangeType::None,
(ChangeType::Deleted(old), ChangeType::New(new)) => ChangeType::Modified {
old: *old,
new: *new,
},
(ChangeType::New(_), ChangeType::Modified { new, .. }) => ChangeType::New(*new),
(ChangeType::Modified { old, .. }, ChangeType::Modified { new, .. }) => {
ChangeType::Modified {
old: *old,
new: *new,
}
}
(ChangeType::Modified { old, .. }, ChangeType::Deleted(_)) => ChangeType::Deleted(*old),
(ChangeType::Modified { old, .. }, ChangeType::New(new)) => ChangeType::Modified {
old: *old,
new: *new,
},
(ChangeType::Deleted(hash), ChangeType::Modified { .. })
| (ChangeType::Deleted(hash), ChangeType::Deleted(_)) => ChangeType::Deleted(*hash),
(ChangeType::None, change) => change.clone(),
(change, ChangeType::None) => change.clone(),
(ChangeType::New(_), ChangeType::New(new)) => ChangeType::New(*new),
}
}
pub fn increment_retry(&mut self) {
self.retry_count += 1;
}
pub fn has_exceeded_retries(&self, max_retries: u32) -> bool {
self.retry_count >= max_retries
}
}
impl PartialEq for UpdateTask {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl Eq for UpdateTask {}
impl std::hash::Hash for UpdateTask {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.path.hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::incremental::hash::FileHasher;
#[test]
fn test_priority_from_trigger() {
assert_eq!(Priority::from_trigger(Trigger::User), Priority::High);
assert_eq!(Priority::from_trigger(Trigger::Save), Priority::Medium);
assert_eq!(Priority::from_trigger(Trigger::Auto), Priority::Low);
}
#[test]
fn test_priority_ordering() {
assert!(Priority::High > Priority::Medium);
assert!(Priority::Medium > Priority::Low);
assert!(Priority::High > Priority::Low);
}
#[test]
fn test_task_creation() {
let path = PathBuf::from("/test/file.rs");
let hash = FileHasher::hash_bytes(b"test content");
let change = ChangeType::New(hash);
let task = UpdateTask::new(path.clone(), change.clone(), Trigger::Save);
assert_eq!(task.path, path);
assert_eq!(task.priority, Priority::Medium);
assert_eq!(task.trigger, Trigger::Save);
assert_eq!(task.retry_count, 0);
}
#[test]
fn test_task_merge_priority() {
let path = PathBuf::from("/test/file.rs");
let hash = FileHasher::hash_bytes(b"test");
let mut task1 = UpdateTask::new(path.clone(), ChangeType::New(hash), Trigger::Auto);
let task2 = UpdateTask::new(path.clone(), ChangeType::New(hash), Trigger::User);
assert_eq!(task1.priority, Priority::Low);
task1.merge(task2);
assert_eq!(task1.priority, Priority::High);
assert_eq!(task1.trigger, Trigger::User);
}
#[test]
fn test_merge_change_types_new_deleted() {
let hash1 = FileHasher::hash_bytes(b"content1");
let hash2 = FileHasher::hash_bytes(b"content2");
let result =
UpdateTask::merge_change_types(&ChangeType::New(hash1), &ChangeType::Deleted(hash2));
assert_eq!(result, ChangeType::None);
}
#[test]
fn test_merge_change_types_modified_modified() {
let hash1 = FileHasher::hash_bytes(b"v1");
let hash2 = FileHasher::hash_bytes(b"v2");
let hash3 = FileHasher::hash_bytes(b"v3");
let result = UpdateTask::merge_change_types(
&ChangeType::Modified {
old: hash1,
new: hash2,
},
&ChangeType::Modified {
old: hash2,
new: hash3,
},
);
match result {
ChangeType::Modified { old, new } => {
assert_eq!(old, hash1);
assert_eq!(new, hash3);
}
_ => panic!("Expected Modified change type"),
}
}
#[test]
fn test_merge_change_types_new_modified() {
let hash1 = FileHasher::hash_bytes(b"v1");
let hash2 = FileHasher::hash_bytes(b"v2");
let result = UpdateTask::merge_change_types(
&ChangeType::New(hash1),
&ChangeType::Modified {
old: hash1,
new: hash2,
},
);
assert_eq!(result, ChangeType::New(hash2));
}
#[test]
fn test_merge_change_types_deleted_new() {
let hash1 = FileHasher::hash_bytes(b"v1");
let hash2 = FileHasher::hash_bytes(b"v2");
let result =
UpdateTask::merge_change_types(&ChangeType::Deleted(hash1), &ChangeType::New(hash2));
match result {
ChangeType::Modified { old, new } => {
assert_eq!(old, hash1);
assert_eq!(new, hash2);
}
_ => panic!("Expected Modified change type"),
}
}
#[test]
fn test_retry_count() {
let path = PathBuf::from("/test/file.rs");
let hash = FileHasher::hash_bytes(b"test");
let mut task = UpdateTask::new(path, ChangeType::New(hash), Trigger::Auto);
assert_eq!(task.retry_count, 0);
assert!(!task.has_exceeded_retries(3));
task.increment_retry();
assert_eq!(task.retry_count, 1);
assert!(!task.has_exceeded_retries(3));
task.increment_retry();
task.increment_retry();
assert_eq!(task.retry_count, 3);
assert!(task.has_exceeded_retries(3));
}
#[test]
fn test_task_equality() {
let path1 = PathBuf::from("/test/file1.rs");
let path2 = PathBuf::from("/test/file2.rs");
let hash = FileHasher::hash_bytes(b"test");
let task1 = UpdateTask::new(path1.clone(), ChangeType::New(hash), Trigger::Auto);
let task2 = UpdateTask::new(path1.clone(), ChangeType::New(hash), Trigger::User);
let task3 = UpdateTask::new(path2, ChangeType::New(hash), Trigger::Auto);
assert_eq!(task1, task2);
assert_ne!(task1, task3);
}
}