Skip to content

Commit

Permalink
Only watch a single project at a time.
Browse files Browse the repository at this point in the history
Previously it would watch every registered project, which could incur more work
on all parts of the application than necessary.

Now UI sends an event that indicates which project is active, allowing the
watch to be setup in that very moment. It's worth noting that the previously
watched project is automatically deregistered.
  • Loading branch information
Byron committed Apr 14, 2024
1 parent cd8e450 commit 50565d7
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 52 deletions.
2 changes: 2 additions & 0 deletions app/src/routes/[projectId]/+layout.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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) => {});

Check failure on line 31 in app/src/routes/[projectId]/+layout.ts

View workflow job for this annotation

GitHub Actions / lint-node

'r' is defined but never used. Allowed unused args must match /^_/u
} catch (err: any) {
throw error(400, {
message: err.message
Expand Down
4 changes: 2 additions & 2 deletions crates/gitbutler-core/src/projects/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 0 additions & 26 deletions crates/gitbutler-tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand All @@ -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,
Expand Down
4 changes: 1 addition & 3 deletions crates/gitbutler-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 19 additions & 1 deletion crates/gitbutler-tauri/src/projects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down Expand Up @@ -57,6 +58,23 @@ pub mod commands {
handle.state::<Controller>().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::<Controller>()
.get(&id)
.context("project not found")?;
Ok(handle.state::<Watchers>().watch(&project)?)
}

#[tauri::command(async)]
#[instrument(skip(handle), err(Debug))]
pub async fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> {
Expand Down
45 changes: 27 additions & 18 deletions crates/gitbutler-tauri/src/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<tokio::sync::Mutex<HashMap<ProjectId, WatcherHandle>>>,
/// 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<tokio::sync::Mutex<Option<WatcherHandle>>>,
}

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();
}
}
}

Expand All @@ -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<()> {
Expand All @@ -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<InternalEvent>,
/// 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,
}

Expand Down Expand Up @@ -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<()> {
Expand Down
4 changes: 2 additions & 2 deletions crates/gitbutler-tauri/src/watcher/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
}
Expand Down

0 comments on commit 50565d7

Please sign in to comment.