use crate::error::{Result, RustDriveSyncError};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use tracing::{debug, info};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileState {
pub relative_path: String,
pub drive_file_id: String,
pub size: u64,
pub modified: i64,
pub md5_hash: String,
pub last_synced: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncState {
pub version: String,
pub local_directory: String,
pub drive_folder_id: String,
pub last_full_sync: Option<i64>,
pub files: HashMap<String, FileState>,
}
impl SyncState {
pub fn new(local_directory: String, drive_folder_id: String) -> Self {
Self {
version: "1.0".to_string(),
local_directory,
drive_folder_id,
last_full_sync: None,
files: HashMap::new(),
}
}
pub fn update_file(&mut self, file_state: FileState) {
self.files
.insert(file_state.relative_path.clone(), file_state);
}
pub fn remove_file(&mut self, relative_path: &str) -> Option<FileState> {
self.files.remove(relative_path)
}
pub fn get_file(&self, relative_path: &str) -> Option<&FileState> {
self.files.get(relative_path)
}
pub fn is_synced(&self, relative_path: &str) -> bool {
self.files.contains_key(relative_path)
}
pub fn mark_full_sync(&mut self) {
self.last_full_sync = Some(chrono::Utc::now().timestamp());
}
pub fn file_count(&self) -> usize {
self.files.len()
}
}
pub struct SyncStateManager {
state_file_path: PathBuf,
state: SyncState,
}
impl SyncStateManager {
pub fn new<P: AsRef<Path>>(
state_file_path: P,
local_directory: String,
drive_folder_id: String,
) -> Self {
Self {
state_file_path: state_file_path.as_ref().to_path_buf(),
state: SyncState::new(local_directory, drive_folder_id),
}
}
pub fn load_or_create(
state_file_path: PathBuf,
local_directory: String,
drive_folder_id: String,
) -> Result<Self> {
if state_file_path.exists() {
info!("Carregando estado de: {}", state_file_path.display());
Self::load(state_file_path)
} else {
info!("Criando novo estado em: {}", state_file_path.display());
Ok(Self::new(state_file_path, local_directory, drive_folder_id))
}
}
pub fn load<P: AsRef<Path>>(state_file_path: P) -> Result<Self> {
let state_file_path = state_file_path.as_ref();
let content = fs::read_to_string(state_file_path).map_err(|e| {
RustDriveSyncError::FileReadError {
path: state_file_path.display().to_string(),
message: e.to_string(),
}
})?;
let state: SyncState = serde_json::from_str(&content).map_err(|e| {
RustDriveSyncError::ConfigInvalid {
message: format!("Erro ao parsear estado: {}", e),
}
})?;
debug!("Estado carregado: {} arquivos", state.file_count());
Ok(Self {
state_file_path: state_file_path.to_path_buf(),
state,
})
}
pub fn save(&self) -> Result<()> {
if let Some(parent) = self.state_file_path.parent() {
fs::create_dir_all(parent).map_err(|e| RustDriveSyncError::FileReadError {
path: parent.display().to_string(),
message: e.to_string(),
})?;
}
let content = serde_json::to_string_pretty(&self.state).map_err(|e| {
RustDriveSyncError::ConfigInvalid {
message: format!("Erro ao serializar estado: {}", e),
}
})?;
fs::write(&self.state_file_path, content).map_err(|e| {
RustDriveSyncError::FileReadError {
path: self.state_file_path.display().to_string(),
message: e.to_string(),
}
})?;
debug!("Estado salvo em: {}", self.state_file_path.display());
Ok(())
}
pub fn state(&self) -> &SyncState {
&self.state
}
pub fn state_mut(&mut self) -> &mut SyncState {
&mut self.state
}
pub fn update_and_save(&mut self, file_state: FileState) -> Result<()> {
self.state.update_file(file_state);
self.save()
}
pub fn remove_and_save(&mut self, relative_path: &str) -> Result<()> {
self.state.remove_file(relative_path);
self.save()
}
pub fn mark_full_sync_and_save(&mut self) -> Result<()> {
self.state.mark_full_sync();
self.save()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sync_state_creation() {
let state = SyncState::new("/local/path".to_string(), "drive_id_123".to_string());
assert_eq!(state.version, "1.0");
assert_eq!(state.file_count(), 0);
assert!(state.last_full_sync.is_none());
}
#[test]
fn test_file_state_operations() {
let mut state = SyncState::new("/local/path".to_string(), "drive_id_123".to_string());
let file_state = FileState {
relative_path: "test.txt".to_string(),
drive_file_id: "file_123".to_string(),
size: 1024,
modified: 1234567890,
md5_hash: "abc123".to_string(),
last_synced: 1234567900,
};
state.update_file(file_state.clone());
assert_eq!(state.file_count(), 1);
assert!(state.is_synced("test.txt"));
let retrieved = state.get_file("test.txt").unwrap();
assert_eq!(retrieved.drive_file_id, "file_123");
state.remove_file("test.txt");
assert_eq!(state.file_count(), 0);
assert!(!state.is_synced("test.txt"));
}
#[test]
fn test_mark_full_sync() {
let mut state = SyncState::new("/local/path".to_string(), "drive_id_123".to_string());
assert!(state.last_full_sync.is_none());
state.mark_full_sync();
assert!(state.last_full_sync.is_some());
}
}