diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 692ef7399..24830ed35 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -14,18 +14,11 @@ use log; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use storage::Storage; -use tauri::{Manager, Runtime, State, Window}; +use tauri::{Manager, Window}; use tauri_plugin_log::{ fern::colors::{Color, ColoredLevelConfig}, LogTarget, }; -use watchers::WatcherCollection; - -struct AppState { - watchers: WatcherCollection, - projects_storage: projects::Storage, - users_storage: users::Storage, -} #[derive(Debug, Serialize, Deserialize)] pub struct Error { @@ -44,10 +37,14 @@ fn app_title() -> String { #[tauri::command] fn list_sessions( - state: State<'_, AppState>, + handle: tauri::AppHandle, project_id: &str, ) -> Result, Error> { - match state.projects_storage.get_project(project_id) { + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let projects_storage = projects::Storage::new(&storage); + + match projects_storage.get_project(project_id) { Ok(Some(project)) => { let repo = Repository::open(project.path).map_err(|e| { log::error!("{}", e); @@ -76,8 +73,12 @@ fn list_sessions( } #[tauri::command] -fn get_user(state: State<'_, AppState>) -> Result, Error> { - state.users_storage.get().map_err(|e| { +fn get_user(handle: tauri::AppHandle) -> Result, Error> { + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let users_storage = users::Storage::new(&storage); + + users_storage.get().map_err(|e| { log::error!("{}", e); Error { message: "Failed to get user".to_string(), @@ -86,8 +87,12 @@ fn get_user(state: State<'_, AppState>) -> Result, Error> { } #[tauri::command] -fn set_user(state: State<'_, AppState>, user: users::User) -> Result<(), Error> { - state.users_storage.set(&user).map_err(|e| { +fn set_user(handle: tauri::AppHandle, user: users::User) -> Result<(), Error> { + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let users_storage = users::Storage::new(&storage); + + users_storage.set(&user).map_err(|e| { log::error!("{}", e); Error { message: "Failed to save user".to_string(), @@ -97,8 +102,12 @@ fn set_user(state: State<'_, AppState>, user: users::User) -> Result<(), Error> } #[tauri::command] -fn delete_user(state: State<'_, AppState>) -> Result<(), Error> { - state.users_storage.delete().map_err(|e| { +fn delete_user(handle: tauri::AppHandle) -> Result<(), Error> { + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let users_storage = users::Storage::new(&storage); + + users_storage.delete().map_err(|e| { log::error!("{}", e); Error { message: "Failed to delete user".to_string(), @@ -109,27 +118,35 @@ fn delete_user(state: State<'_, AppState>) -> Result<(), Error> { #[tauri::command] fn update_project( - state: State<'_, AppState>, + handle: tauri::AppHandle, project: projects::UpdateRequest, ) -> Result { - state - .projects_storage - .update_project(&project) - .map_err(|e| { - log::error!("{}", e); - Error { - message: "Failed to update project".to_string(), - } - }) + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let projects_storage = projects::Storage::new(&storage); + + projects_storage.update_project(&project).map_err(|e| { + log::error!("{}", e); + Error { + message: "Failed to update project".to_string(), + } + }) } #[tauri::command] -fn add_project( - window: Window, - state: State<'_, AppState>, +fn add_project( + handle: tauri::AppHandle, + window: Window, path: &str, ) -> Result { - for project in state.projects_storage.list_projects().map_err(|e| { + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let projects_storage = projects::Storage::new(&storage); + + let watchers_collection = handle.state::(); + let watchers = watchers::Watcher::new(&watchers_collection); + + for project in projects_storage.list_projects().map_err(|e| { log::error!("{}", e); Error { message: "Failed to list projects".to_string(), @@ -145,13 +162,13 @@ fn add_project( let project = projects::Project::from_path(path.to_string()); if project.is_ok() { let project = project.unwrap(); - state.projects_storage.add_project(&project).map_err(|e| { + projects_storage.add_project(&project).map_err(|e| { log::error!("{}", e); Error { message: "Failed to add project".to_string(), } })?; - watchers::watch(window, &state.watchers, &project).map_err(|e| { + watchers.watch(window, &project).map_err(|e| { log::error!("{}", e); Error { message: "Failed to watch project".to_string(), @@ -166,8 +183,12 @@ fn add_project( } #[tauri::command] -fn list_projects(state: State<'_, AppState>) -> Result, Error> { - state.projects_storage.list_projects().map_err(|e| { +fn list_projects(handle: tauri::AppHandle) -> Result, Error> { + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let projects_storage = projects::Storage::new(&storage); + + projects_storage.list_projects().map_err(|e| { log::error!("{}", e); Error { message: "Failed to list projects".to_string(), @@ -176,17 +197,24 @@ fn list_projects(state: State<'_, AppState>) -> Result, E } #[tauri::command] -fn delete_project(state: State<'_, AppState>, id: &str) -> Result<(), Error> { - match state.projects_storage.get_project(id) { +fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> { + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let projects_storage = projects::Storage::new(&storage); + + let watchers_collection = handle.state::(); + let watchers = watchers::Watcher::new(&watchers_collection); + + match projects_storage.get_project(id) { Ok(Some(project)) => { - watchers::unwatch(&state.watchers, project).map_err(|e| { + watchers.unwatch(project).map_err(|e| { log::error!("{}", e); Error { message: "Failed to unwatch project".to_string(), } })?; - state.projects_storage.delete_project(id).map_err(|e| { + projects_storage.delete_project(id).map_err(|e| { log::error!("{}", e); Error { message: "Failed to delete project".to_string(), @@ -207,11 +235,15 @@ fn delete_project(state: State<'_, AppState>, id: &str) -> Result<(), Error> { #[tauri::command] fn list_session_files( - state: State<'_, AppState>, + handle: tauri::AppHandle, project_id: &str, session_id: &str, ) -> Result, Error> { - match state.projects_storage.get_project(project_id) { + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let projects_storage = projects::Storage::new(&storage); + + match projects_storage.get_project(project_id) { Ok(Some(project)) => { let repo = Repository::open(&project.path).map_err(|e| { log::error!("{}", e); @@ -243,11 +275,15 @@ fn list_session_files( #[tauri::command] fn list_deltas( - state: State<'_, AppState>, + handle: tauri::AppHandle, project_id: &str, session_id: &str, ) -> Result>, Error> { - match state.projects_storage.get_project(project_id) { + let path_resolver = handle.path_resolver(); + let storage = storage::Storage::new(&path_resolver); + let projects_storage = projects::Storage::new(&storage); + + match projects_storage.get_project(project_id) { Ok(Some(project)) => { let repo = Repository::open(&project.path).map_err(|e| { log::error!("{}", e); @@ -350,14 +386,15 @@ fn main() { .setup(move |app| { let resolver = app.path_resolver(); let storage = Storage::new(&resolver); - let projects_storage = projects::Storage::new(storage.clone()); - let users_storage = users::Storage::new(storage.clone()); + let projects_storage = projects::Storage::new(&storage); - let watchers = watchers::WatcherCollection::default(); + let watcher_collection = watchers::WatcherCollection::default(); + let watchers = watchers::Watcher::new(&watcher_collection); if let Ok(projects) = projects_storage.list_projects() { for project in projects { - watchers::watch(app.get_window("main").unwrap(), &watchers, &project) + watchers + .watch(app.get_window("main").unwrap(), &project) .map_err(|e| e.to_string())?; } } else { @@ -367,11 +404,7 @@ fn main() { #[cfg(debug_assertions)] app.get_window("main").unwrap().open_devtools(); - app.manage(AppState { - watchers, - projects_storage, - users_storage, - }); + app.manage(watcher_collection); Ok(()) }) diff --git a/src-tauri/src/projects/storage.rs b/src-tauri/src/projects/storage.rs index 0ff034f02..fca8fb49e 100644 --- a/src-tauri/src/projects/storage.rs +++ b/src-tauri/src/projects/storage.rs @@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize}; const PROJECTS_FILE: &str = "projects.json"; -pub struct Storage { - storage: storage::Storage, +pub struct Storage<'a> { + storage: &'a storage::Storage, } #[derive(Debug, Serialize, Deserialize)] @@ -16,8 +16,8 @@ pub struct UpdateRequest { api: Option, } -impl Storage { - pub fn new(storage: storage::Storage) -> Self { +impl<'a> Storage<'a> { + pub fn new(storage: &'a storage::Storage) -> Self { Self { storage } } diff --git a/src-tauri/src/users/storage.rs b/src-tauri/src/users/storage.rs index 38c54fc86..14fa31865 100644 --- a/src-tauri/src/users/storage.rs +++ b/src-tauri/src/users/storage.rs @@ -4,12 +4,12 @@ use anyhow::Result; const USER_FILE: &str = "user.json"; -pub struct Storage { - storage: storage::Storage, +pub struct Storage<'a> { + storage: &'a storage::Storage, } -impl Storage { - pub fn new(storage: storage::Storage) -> Self { +impl<'a> Storage<'a> { + pub fn new(storage: &'a storage::Storage) -> Self { Self { storage } } diff --git a/src-tauri/src/watchers/delta.rs b/src-tauri/src/watchers/delta.rs index 6b71f0512..cf51def03 100644 --- a/src-tauri/src/watchers/delta.rs +++ b/src-tauri/src/watchers/delta.rs @@ -13,73 +13,79 @@ use std::{collections::HashMap, sync::Mutex}; #[derive(Default)] pub struct WatcherCollection(Mutex>); -pub fn unwatch(watchers: &WatcherCollection, project: projects::Project) -> Result<()> { - let mut watchers = watchers.0.lock().unwrap(); - if let Some(mut watcher) = watchers.remove(&project.path) { - watcher.unwatch(Path::new(&project.path))?; - } - Ok(()) +pub struct DeltaWatchers<'a> { + watchers: &'a WatcherCollection, } -pub fn watch( - window: tauri::Window, - watchers: &WatcherCollection, - project: projects::Project, -) -> Result<()> { - log::info!("Watching deltas for {}", project.path); - let project_path = Path::new(&project.path); +impl<'a> DeltaWatchers<'a> { + pub fn new(watchers: &'a WatcherCollection) -> Self { + Self { watchers } + } - let (tx, rx) = channel(); - let mut watcher = RecommendedWatcher::new(tx, Config::default())?; + pub fn watch(&self, window: tauri::Window, project: projects::Project) -> Result<()> { + log::info!("Watching deltas for {}", project.path); + let project_path = Path::new(&project.path); - watcher.watch(project_path, RecursiveMode::Recursive)?; + let (tx, rx) = channel(); + let mut watcher = RecommendedWatcher::new(tx, Config::default())?; - watchers - .0 - .lock() - .unwrap() - .insert(project.path.clone(), watcher); + watcher.watch(project_path, RecursiveMode::Recursive)?; - let repo = Repository::open(project_path); - thread::spawn(move || { - if repo.is_err() { - log::error!("failed to open git repo: {:?}", repo.err()); - return; - } - let repo = repo.unwrap(); + self.watchers + .0 + .lock() + .unwrap() + .insert(project.path.clone(), watcher); - while let Ok(event) = rx.recv() { - if let Ok(event) = event { - for file_path in event.paths { - let relative_file_path = - file_path.strip_prefix(repo.workdir().unwrap()).unwrap(); - match register_file_change( - &window, - &project, - &repo, - &event.kind, - &relative_file_path, - ) { - Ok(Some((session, deltas))) => { - events::deltas( - &window, - &project, - &session, - &deltas, - &relative_file_path, - ); - } - Ok(None) => {} - Err(e) => log::error!("Error: {:?}", e), - } - } - } else { - log::error!("Error: {:?}", event); + let repo = Repository::open(project_path); + thread::spawn(move || { + if repo.is_err() { + log::error!("failed to open git repo: {:?}", repo.err()); + return; } - } - }); + let repo = repo.unwrap(); - Ok(()) + while let Ok(event) = rx.recv() { + if let Ok(event) = event { + for file_path in event.paths { + let relative_file_path = + file_path.strip_prefix(repo.workdir().unwrap()).unwrap(); + match register_file_change( + &window, + &project, + &repo, + &event.kind, + &relative_file_path, + ) { + Ok(Some((session, deltas))) => { + events::deltas( + &window, + &project, + &session, + &deltas, + &relative_file_path, + ); + } + Ok(None) => {} + Err(e) => log::error!("Error: {:?}", e), + } + } + } else { + log::error!("Error: {:?}", event); + } + } + }); + + Ok(()) + } + + pub fn unwatch(&self, project: projects::Project) -> Result<()> { + let mut watchers = self.watchers.0.lock().unwrap(); + if let Some(mut watcher) = watchers.remove(&project.path) { + watcher.unwatch(Path::new(&project.path))?; + } + Ok(()) + } } // this is what is called when the FS watcher detects a change diff --git a/src-tauri/src/watchers/git.rs b/src-tauri/src/watchers/git.rs index 13153b8d2..eb47dc332 100644 --- a/src-tauri/src/watchers/git.rs +++ b/src-tauri/src/watchers/git.rs @@ -1,6 +1,6 @@ use crate::{butler, events, projects, sessions}; -use git2::Repository; use anyhow::Result; +use git2::Repository; use std::{ thread, time::{Duration, SystemTime}, @@ -9,48 +9,49 @@ use std::{ const FIVE_MINUTES: u64 = Duration::new(5 * 60, 0).as_secs(); const ONE_HOUR: u64 = Duration::new(60 * 60, 0).as_secs(); -pub fn watch( - window: tauri::Window, - project: projects::Project, -) -> Result<() > { - let repo = git2::Repository::open(&project.path)?; - thread::spawn(move || loop { - match repo.revparse_single(format!("refs/{}/current", butler::refname()).as_str()) { - Ok(_) => {} - Err(_) => { - // make sure all the files are tracked by gitbutler session - if sessions::Session::from_head(&repo).is_err() { - log::error!( - "Error while creating session for {}", - repo.workdir().unwrap().display() - ); +pub struct GitWatcher {} + +impl GitWatcher { + pub fn watch(&self, window: tauri::Window, project: projects::Project) -> Result<()> { + let repo = git2::Repository::open(&project.path)?; + thread::spawn(move || loop { + match repo.revparse_single(format!("refs/{}/current", butler::refname()).as_str()) { + Ok(_) => {} + Err(_) => { + // make sure all the files are tracked by gitbutler session + if sessions::Session::from_head(&repo).is_err() { + log::error!( + "Error while creating session for {}", + repo.workdir().unwrap().display() + ); + } + if sessions::flush_current_session(&repo).is_err() { + log::error!( + "Error while flushing current session for {}", + repo.workdir().unwrap().display() + ); + } } - if sessions::flush_current_session(&repo).is_err() { + } + + match check_for_changes(&repo) { + Ok(Some(session)) => { + events::session(&window, &project, &session); + } + Ok(None) => {} + Err(error) => { log::error!( - "Error while flushing current session for {}", - repo.workdir().unwrap().display() + "Error while checking {} for changes: {}", + repo.workdir().unwrap().display(), + error ); } } - } + thread::sleep(Duration::from_secs(10)); + }); - match check_for_changes(&repo) { - Ok(Some(session)) => { - events::session(&window, &project, &session); - } - Ok(None) => {} - Err(error) => { - log::error!( - "Error while checking {} for changes: {}", - repo.workdir().unwrap().display(), - error - ); - } - } - thread::sleep(Duration::from_secs(10)); - }); - - Ok(()) + Ok(()) + } } // main thing called in a loop to check for changes and write our custom commit data diff --git a/src-tauri/src/watchers/mod.rs b/src-tauri/src/watchers/mod.rs index 05ccfb2a3..2a910cc81 100644 --- a/src-tauri/src/watchers/mod.rs +++ b/src-tauri/src/watchers/mod.rs @@ -4,20 +4,32 @@ mod git; pub use self::delta::WatcherCollection; use crate::projects; use anyhow::Result; -use tauri::{Runtime, Window}; +use tauri; -pub fn watch( - window: Window, - watchers: &WatcherCollection, - project: &projects::Project, -) -> Result<()> { - self::delta::watch(window.clone(), watchers, project.clone())?; - self::git::watch(window.clone(), project.clone())?; - Ok(()) +pub struct Watcher<'a> { + git_watcher: git::GitWatcher, + delta_watcher: delta::DeltaWatchers<'a>, } -pub fn unwatch(watchers: &WatcherCollection, project: projects::Project) -> Result<()> { - delta::unwatch(watchers, project)?; - // TODO: how to unwatch git ? - Ok(()) +impl<'a> Watcher<'a> { + pub fn new(watchers: &'a delta::WatcherCollection) -> Self { + let git_watcher = git::GitWatcher {}; + let delta_watcher = delta::DeltaWatchers::new(watchers); + Self { + git_watcher, + delta_watcher, + } + } + + pub fn watch(&self, window: tauri::Window, project: &projects::Project) -> Result<()> { + self.delta_watcher.watch(window.clone(), project.clone())?; + self.git_watcher.watch(window.clone(), project.clone())?; + Ok(()) + } + + pub fn unwatch(&self, project: projects::Project) -> Result<()> { + self.delta_watcher.unwatch(project)?; + // TODO: how to unwatch git ? + Ok(()) + } }