make watchers into a struct

This commit is contained in:
Nikita Galaiko 2023-02-16 14:56:13 +01:00
parent 723823bccd
commit dfa16066c1
No known key found for this signature in database
GPG Key ID: EBAB54E845BA519D
6 changed files with 219 additions and 167 deletions

View File

@ -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<Vec<sessions::Session>, 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<Option<users::User>, Error> {
state.users_storage.get().map_err(|e| {
fn get_user(handle: tauri::AppHandle) -> Result<Option<users::User>, 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<Option<users::User>, 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<projects::Project, Error> {
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<R: Runtime>(
window: Window<R>,
state: State<'_, AppState>,
fn add_project(
handle: tauri::AppHandle,
window: Window,
path: &str,
) -> Result<projects::Project, Error> {
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::<watchers::WatcherCollection>();
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<R: Runtime>(
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<R: Runtime>(
}
#[tauri::command]
fn list_projects(state: State<'_, AppState>) -> Result<Vec<projects::Project>, Error> {
state.projects_storage.list_projects().map_err(|e| {
fn list_projects(handle: tauri::AppHandle) -> Result<Vec<projects::Project>, 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<Vec<projects::Project>, 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::<watchers::WatcherCollection>();
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<HashMap<String, String>, 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<HashMap<String, Vec<Delta>>, 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(())
})

View File

@ -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<project::ApiProject>,
}
impl Storage {
pub fn new(storage: storage::Storage) -> Self {
impl<'a> Storage<'a> {
pub fn new(storage: &'a storage::Storage) -> Self {
Self { storage }
}

View File

@ -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 }
}

View File

@ -13,73 +13,79 @@ use std::{collections::HashMap, sync::Mutex};
#[derive(Default)]
pub struct WatcherCollection(Mutex<HashMap<String, RecommendedWatcher>>);
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<R: tauri::Runtime>(
window: tauri::Window<R>,
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

View File

@ -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<R: tauri::Runtime>(
window: tauri::Window<R>,
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

View File

@ -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<R: Runtime>(
window: Window<R>,
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(())
}
}