diff --git a/app/src/routes/[projectId]/+layout.ts b/app/src/routes/[projectId]/+layout.ts index f4ae10cccd..540b97819f 100644 --- a/app/src/routes/[projectId]/+layout.ts +++ b/app/src/routes/[projectId]/+layout.ts @@ -1,3 +1,4 @@ +import { invoke } from '$lib/backend/ipc'; import { BranchService } from '$lib/branches/service'; import { getFetchNotifications } from '$lib/stores/fetches'; import { getHeads } from '$lib/stores/head'; @@ -27,6 +28,7 @@ export async function load({ params, parent }) { let project: Project | undefined = undefined; try { project = await projectService.getProject(projectId); + invoke('set_project_active', { id: projectId }).then((r) => {}); } catch (err: any) { throw error(400, { message: err.message diff --git a/crates/gitbutler-core/src/projects/controller.rs b/crates/gitbutler-core/src/projects/controller.rs index 2347c37c15..cd129d2873 100644 --- a/crates/gitbutler-core/src/projects/controller.rs +++ b/crates/gitbutler-core/src/projects/controller.rs @@ -111,8 +111,8 @@ impl Controller { tracing::error!(project_id = %project.id, ?error, "failed to create {:?} on project add", project.gb_dir()); } - if let Some(watchers) = &self.watchers { - watchers.watch(&project)?; + if let Some(watcher) = &self.watchers { + watcher.watch(&project)?; } Ok(project) diff --git a/crates/gitbutler-tauri/src/app.rs b/crates/gitbutler-tauri/src/app.rs index 433ec65e9a..d3e0e45fa5 100644 --- a/crates/gitbutler-tauri/src/app.rs +++ b/crates/gitbutler-tauri/src/app.rs @@ -14,14 +14,12 @@ use gitbutler_core::{ }; use crate::error::Error; -use crate::watcher; #[derive(Clone)] pub struct App { local_data_dir: path::PathBuf, projects: projects::Controller, users: users::Controller, - watchers: watcher::Watchers, sessions_database: sessions::Database, } @@ -30,40 +28,16 @@ impl App { local_data_dir: path::PathBuf, projects: projects::Controller, users: users::Controller, - watchers: watcher::Watchers, sessions_database: sessions::Database, ) -> Self { Self { local_data_dir, projects, users, - watchers, sessions_database, } } - pub fn init_project(&self, project: &projects::Project) -> Result<()> { - self.watchers.watch(project).context(format!( - "failed to start watcher for project {}", - &project.id - ))?; - - Ok(()) - } - - pub fn init(&self) -> Result<()> { - for project in self - .projects - .list() - .with_context(|| "failed to list projects")? - { - if let Err(error) = self.init_project(&project) { - tracing::error!(%project.id, ?error, "failed to init project"); - } - } - Ok(()) - } - pub fn list_session_files( &self, project_id: &ProjectId, diff --git a/crates/gitbutler-tauri/src/main.rs b/crates/gitbutler-tauri/src/main.rs index 835dddccc2..30f003c71b 100644 --- a/crates/gitbutler-tauri/src/main.rs +++ b/crates/gitbutler-tauri/src/main.rs @@ -195,12 +195,9 @@ fn main() { app_data_dir, projects_controller, users_controller, - watcher_controller, sessions_database_controller, ); - app.init().context("failed to init app")?; - app_handle.manage(app); Ok(()) @@ -233,6 +230,7 @@ fn main() { projects::commands::update_project, projects::commands::delete_project, projects::commands::list_projects, + projects::commands::set_project_active, projects::commands::git_get_local_config, projects::commands::git_set_local_config, sessions::commands::list_sessions, diff --git a/crates/gitbutler-tauri/src/projects.rs b/crates/gitbutler-tauri/src/projects.rs index f718c9e13f..e930d20664 100644 --- a/crates/gitbutler-tauri/src/projects.rs +++ b/crates/gitbutler-tauri/src/projects.rs @@ -4,11 +4,12 @@ pub mod commands { use gitbutler_core::error; use gitbutler_core::error::Code; - use gitbutler_core::projects::{self, controller::Controller}; + use gitbutler_core::projects::{self, controller::Controller, ProjectId}; use tauri::Manager; use tracing::instrument; use crate::error::Error; + use crate::watcher::Watchers; #[tauri::command(async)] #[instrument(skip(handle), err(Debug))] @@ -57,6 +58,23 @@ pub mod commands { handle.state::().list().map_err(Into::into) } + /// This trigger is the GUI telling us that the project with `id` is now displayed. + /// + /// We use it to start watching for filesystem events. + #[tauri::command(async)] + #[instrument(skip(handle), err(Debug))] + pub async fn set_project_active(handle: tauri::AppHandle, id: &str) -> Result<(), Error> { + let id: ProjectId = id.parse().context(error::Context::new_static( + Code::Validation, + "Malformed project id", + ))?; + let project = handle + .state::() + .get(&id) + .context("project not found")?; + Ok(handle.state::().watch(&project)?) + } + #[tauri::command(async)] #[instrument(skip(handle), err(Debug))] pub async fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> { diff --git a/crates/gitbutler-tauri/src/watcher.rs b/crates/gitbutler-tauri/src/watcher.rs index bb61be3f19..c918bfebda 100644 --- a/crates/gitbutler-tauri/src/watcher.rs +++ b/crates/gitbutler-tauri/src/watcher.rs @@ -7,7 +7,7 @@ mod handler; pub use handler::Handler; use std::path::Path; -use std::{collections::HashMap, sync::Arc, time}; +use std::{sync::Arc, time}; use anyhow::{Context, Result}; use futures::executor::block_on; @@ -25,47 +25,51 @@ use tracing::instrument; pub struct Watchers { /// NOTE: This handle is required for this type to be self-contained as it's used by `core` through a trait. app_handle: AppHandle, - // NOTE: This is a `tokio` mutex as this needs to lock a hashmap currently from within async. - watchers: Arc>>, + /// The watcher of the currently active project. + /// NOTE: This is a `tokio` mutex as this needs to lock the inner option from within async. + watcher: Arc>>, } impl Watchers { pub fn new(app_handle: AppHandle) -> Self { Self { app_handle, - watchers: Default::default(), + watcher: Default::default(), } } - #[instrument(skip(self, project), err)] + #[instrument(skip(self, project), err(Debug))] pub fn watch(&self, project: &projects::Project) -> Result<()> { let handler = handler::Handler::from_app(&self.app_handle)?; let project_id = project.id; let project_path = project.path.clone(); - match watch_in_background(handler, project_path, project_id) { - Ok(handle) => { - block_on(self.watchers.lock()).insert(project_id, handle); - } - Err(err) => { - tracing::error!(?err, %project_id, "watcher error"); - } - } + let handle = watch_in_background(handler, project_path, project_id)?; + block_on(self.watcher.lock()).replace(handle); Ok(()) } pub async fn post(&self, event: Event) -> Result<()> { - let watchers = self.watchers.lock().await; - if let Some(handle) = watchers.get(event.project_id()) { + let watcher = self.watcher.lock().await; + if let Some(handle) = watcher + .as_ref() + .filter(|watcher| watcher.project_id == event.project_id()) + { handle.post(event).await.context("failed to post event") } else { Err(anyhow::anyhow!("watcher not found",)) } } - pub async fn stop(&self, project_id: &ProjectId) { - self.watchers.lock().await.remove(project_id); + pub async fn stop(&self, project_id: ProjectId) { + let mut handle = self.watcher.lock().await; + if handle + .as_ref() + .map_or(false, |handle| handle.project_id == project_id) + { + handle.take(); + } } } @@ -76,7 +80,7 @@ impl gitbutler_core::projects::Watchers for Watchers { } async fn stop(&self, id: ProjectId) { - Watchers::stop(self, &id).await + Watchers::stop(self, id).await } async fn fetch_gb_data(&self, id: ProjectId) -> Result<()> { @@ -90,7 +94,11 @@ impl gitbutler_core::projects::Watchers for Watchers { /// An abstraction over a link to the spawned watcher, which runs in the background. struct WatcherHandle { + /// A way to post events and interact with the actual handler in the background. tx: UnboundedSender, + /// The id of the project we are watching. + project_id: ProjectId, + /// A way to tell the background process to stop handling events. cancellation_token: CancellationToken, } @@ -128,6 +136,7 @@ fn watch_in_background( let cancellation_token = CancellationToken::new(); let handle = WatcherHandle { tx: events_out, + project_id, cancellation_token: cancellation_token.clone(), }; let handle_event = move |event: InternalEvent| -> Result<()> { diff --git a/crates/gitbutler-tauri/src/watcher/events.rs b/crates/gitbutler-tauri/src/watcher/events.rs index d0b5e45bbb..dcd32ea24a 100644 --- a/crates/gitbutler-tauri/src/watcher/events.rs +++ b/crates/gitbutler-tauri/src/watcher/events.rs @@ -29,12 +29,12 @@ pub enum Event { } impl Event { - pub fn project_id(&self) -> &ProjectId { + pub fn project_id(&self) -> ProjectId { match self { Event::FetchGitbutlerData(project_id) | Event::Flush(project_id, _) | Event::CalculateVirtualBranches(project_id) - | Event::PushGitbutlerData(project_id) => project_id, + | Event::PushGitbutlerData(project_id) => *project_id, } } }