programmaticaly control window and channel events

This commit is contained in:
Nikita Galaiko 2023-02-22 11:49:25 +01:00
parent 76b0989ece
commit 3c42525a13
No known key found for this signature in database
GPG Key ID: EBAB54E845BA519D
6 changed files with 192 additions and 120 deletions

View File

@ -1,41 +1,34 @@
use crate::{deltas, projects, sessions};
use serde;
pub fn session<R: tauri::Runtime>(
window: &tauri::Window<R>,
project: &projects::Project,
session: &sessions::Session,
) {
let event_name = format!("project://{}/sessions", project.id);
match window.emit(&event_name, &session) {
Ok(_) => {}
Err(e) => log::error!("Error: {:?}", e),
};
#[derive(Debug)]
pub struct Event {
pub name: String,
pub payload: serde_json::Value,
}
#[derive(serde::Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct DeltasEvent {
file_path: String,
deltas: Vec<deltas::Delta>,
}
impl Event {
pub fn session(project: &projects::Project, session: &sessions::Session) -> Self {
let event_name = format!("project://{}/sessions", project.id);
Event {
name: event_name,
payload: serde_json::to_value(session).unwrap(),
}
}
pub fn deltas<R: tauri::Runtime>(
window: &tauri::Window<R>,
project: &projects::Project,
session: &sessions::Session,
deltas: &Vec<deltas::Delta>,
relative_file_path: &std::path::Path,
) {
let event_name = format!("project://{}/deltas/{}", project.id, session.id);
match window.emit(
&event_name,
&DeltasEvent {
deltas: deltas.clone(),
file_path: relative_file_path.to_str().unwrap().to_string(),
},
) {
Ok(_) => {}
Err(e) => log::error!("Error: {:?}", e),
};
pub fn detlas(
project: &projects::Project,
session: &sessions::Session,
deltas: &Vec<deltas::Delta>,
relative_file_path: &std::path::Path,
) -> Self {
let event_name = format!("project://{}/deltas/{}", project.id, session.id);
let payload = serde_json::json!({
"deltas": deltas,
"relative_file_path": relative_file_path,
});
Event {
name: event_name,
payload,
}
}
}

View File

@ -8,12 +8,13 @@ mod storage;
mod users;
mod watchers;
use anyhow::Result;
use deltas::Delta;
use log;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::{collections::HashMap, sync::mpsc, thread};
use storage::Storage;
use tauri::{Manager, Window};
use tauri::Manager;
use tauri_plugin_log::{
fern::colors::{Color, ColoredLevelConfig},
LogTarget,
@ -124,11 +125,7 @@ fn update_project(
}
#[tauri::command]
fn add_project(
handle: tauri::AppHandle,
window: Window,
path: &str,
) -> Result<projects::Project, Error> {
fn add_project(handle: tauri::AppHandle, path: &str) -> Result<projects::Project, Error> {
let path_resolver = handle.path_resolver();
let storage = storage::Storage::from_path_resolver(&path_resolver);
let projects_storage = projects::Storage::new(storage.clone());
@ -178,12 +175,19 @@ fn add_project(
message: "Failed to add project".to_string(),
}
})?;
watchers.watch(window, &project).map_err(|e| {
let (tx, rx): (mpsc::Sender<events::Event>, mpsc::Receiver<events::Event>) =
mpsc::channel();
watchers.watch(tx, &project).map_err(|e| {
log::error!("{}", e);
Error {
message: "Failed to watch project".to_string(),
}
})?;
watch_events(handle, rx);
return Ok(project);
} else {
return Err(Error {
@ -341,51 +345,59 @@ fn main() {
tauri::Builder::default()
.system_tray(tray)
.on_window_event(|event| match event.event() {
// Hide window instead of closing.
tauri::WindowEvent::CloseRequested { api, .. } => {
api.prevent_close();
event.window().hide().unwrap();
event
.window()
.app_handle()
.tray_handle()
.get_item("toggle")
.set_title(format!("Show {}", app_title()))
.unwrap();
}
_ => {}
})
.on_system_tray_event(|app, event| match event {
.on_system_tray_event(|app_handle, event| match event {
tauri::SystemTrayEvent::MenuItemClick { id, .. } => {
let item_handle = app.tray_handle().get_item(&id);
let item_handle = app_handle.tray_handle().get_item(&id);
match id.as_str() {
"quit" => {
app.exit(0);
app_handle.exit(0);
}
"toggle" => {
let main_window = app.get_window("main").unwrap();
if main_window.is_visible().unwrap() {
main_window.hide().unwrap();
item_handle
.set_title(format!("Show {}", app_title()))
.unwrap();
} else {
main_window.show().unwrap();
main_window.set_focus().unwrap();
"toggle" => match get_window(&app_handle) {
Some(window) => {
if window.is_visible().unwrap() {
window.hide().unwrap();
item_handle
.set_title(format!("Show {}", app_title()))
.unwrap();
} else {
window.show().unwrap();
item_handle
.set_title(format!("Hide {}", app_title()))
.unwrap();
}
}
None => {
create_window(&app_handle).expect("Failed to create window");
item_handle
.set_title(format!("Hide {}", app_title()))
.unwrap();
}
}
},
_ => {}
}
}
_ => {}
})
.on_window_event(|event| match event.event() {
tauri::WindowEvent::CloseRequested { api, .. } => {
api.prevent_close();
let window = event.window();
window
.app_handle()
.tray_handle()
.get_item("toggle")
.set_title(format!("Show {}", app_title()))
.expect("Failed to set tray item title");
window.hide().expect("Failed to hide window");
}
_ => {}
})
.setup(move |app| {
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
let window = create_window(&app.handle()).expect("Failed to create window");
#[cfg(debug_assertions)]
window.open_devtools();
let resolver = app.path_resolver();
log::info!(
@ -403,18 +415,20 @@ fn main() {
users_storage.clone(),
);
let (tx, rx): (mpsc::Sender<events::Event>, mpsc::Receiver<events::Event>) =
mpsc::channel();
if let Ok(projects) = projects_storage.list_projects() {
for project in projects {
watchers
.watch(app.get_window("main").unwrap(), &project)
.watch(tx.clone(), &project)
.map_err(|e| e.to_string())?;
}
} else {
log::error!("Failed to list projects");
}
#[cfg(debug_assertions)]
app.get_window("main").unwrap().open_devtools();
watch_events(app.handle(), rx);
app.manage(watcher_collection);
@ -444,8 +458,62 @@ fn main() {
delete_user,
get_user
])
.run(tauri::generate_context!())
.build(tauri::generate_context!())
.expect("error while running tauri application")
.run(|app_handle, event| match event {
tauri::RunEvent::ExitRequested { api, .. } => {
hide_window(&app_handle).expect("Failed to hide window");
api.prevent_exit();
}
_ => {}
});
},
);
}
fn watch_events(handle: tauri::AppHandle, rx: mpsc::Receiver<events::Event>) {
thread::spawn(move || {
while let Ok(event) = rx.recv() {
if let Some(window) = handle.get_window("main") {
match window.emit(&event.name, event.payload) {
Err(e) => log::error!("Failed to emit event: {}", e),
_ => {}
}
}
}
});
}
fn get_window(handle: &tauri::AppHandle) -> Option<tauri::Window> {
handle.get_window("main")
}
fn create_window(handle: &tauri::AppHandle) -> tauri::Result<tauri::Window> {
log::info!("Creating window");
tauri::WindowBuilder::new(handle, "main", tauri::WindowUrl::App("index.html".into()))
.resizable(true)
.hidden_title(true)
.theme(Some(tauri::Theme::Dark))
.min_inner_size(600.0, 300.0)
.inner_size(800.0, 600.0)
.title_bar_style(tauri::TitleBarStyle::Overlay)
.build()
}
fn hide_window(handle: &tauri::AppHandle) -> tauri::Result<()> {
handle
.tray_handle()
.get_item("toggle")
.set_title(format!("Show {}", app_title()))?;
match get_window(handle) {
Some(window) => {
if window.is_visible()? {
window.hide()
} else {
Ok(())
}
}
None => Ok(()),
}
}

View File

@ -6,7 +6,7 @@ use git2;
use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
use std::sync::mpsc;
use std::thread;
use std::{collections::HashMap, sync::Mutex};
@ -22,11 +22,15 @@ impl<'a> DeltaWatchers<'a> {
Self { watchers }
}
pub fn watch(&self, window: tauri::Window, project: projects::Project) -> Result<()> {
pub fn watch(
&self,
sender: mpsc::Sender<events::Event>,
project: projects::Project,
) -> Result<()> {
log::info!("Watching deltas for {}", project.path);
let project_path = Path::new(&project.path);
let (tx, rx) = channel();
let (tx, rx) = mpsc::channel();
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
watcher.watch(project_path, RecursiveMode::Recursive)?;
@ -38,6 +42,7 @@ impl<'a> DeltaWatchers<'a> {
.insert(project.path.clone(), watcher);
let repo = git2::Repository::open(project_path);
thread::spawn(move || {
if repo.is_err() {
log::error!("failed to open git repo: {:?}", repo.err());
@ -51,20 +56,32 @@ impl<'a> DeltaWatchers<'a> {
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,
);
match sender.send(events::Event::session(&project, &session)) {
Err(e) => log::error!("filed to send session event: {:?}", e),
Ok(_) => {}
}
match deltas {
Some(deltas) => {
match sender.send(events::Event::detlas(
&project,
&session,
&deltas,
&relative_file_path,
)) {
Err(e) => {
log::error!("filed to send deltas event: {:?}", e)
}
Ok(_) => {}
}
}
None => {}
}
}
Ok(None) => {}
Err(e) => log::error!("Error: {:?}", e),
@ -92,13 +109,12 @@ impl<'a> DeltaWatchers<'a> {
// it should figure out delta data (crdt) and update the file at .git/gb/session/deltas/path/to/file
// it also writes the metadata stuff which marks the beginning of a session if a session is not yet started
// returns updated project deltas
fn register_file_change<R: tauri::Runtime>(
window: &tauri::Window<R>,
fn register_file_change(
project: &projects::Project,
repo: &git2::Repository,
kind: &EventKind,
relative_file_path: &Path,
) -> Result<Option<(sessions::Session, Vec<Delta>)>, Box<dyn std::error::Error>> {
) -> Result<Option<(sessions::Session, Option<Vec<Delta>>)>, Box<dyn std::error::Error>> {
if repo.is_path_ignored(&relative_file_path).unwrap_or(true) {
// make sure we're not watching ignored files
return Ok(None);
@ -121,7 +137,7 @@ fn register_file_change<R: tauri::Runtime>(
};
// update meta files every time file change is detected
let session = write_beginning_meta_files(&window, &project, &repo)?;
let session = write_beginning_meta_files(&project, &repo)?;
if EventKind::is_modify(&kind) {
log::info!("File modified: {:?}", file_path);
@ -157,13 +173,13 @@ fn register_file_change<R: tauri::Runtime>(
};
if !text_doc.update(&file_contents) {
return Ok(None);
return Ok(Some((session, None)));
}
// if the file was modified, save the deltas
let deltas = text_doc.get_deltas();
write(repo, project, relative_file_path, &deltas)?;
return Ok(Some((session, deltas)));
return Ok(Some((session, Some(deltas))));
}
// returns last commited file contents from refs/gitbutler/current ref
@ -224,8 +240,7 @@ fn get_latest_file_contents(
// this function is called when the user modifies a file, it writes starting metadata if not there
// and also touches the last activity timestamp, so we can tell when we are idle
fn write_beginning_meta_files<R: tauri::Runtime>(
window: &tauri::Window<R>,
fn write_beginning_meta_files(
project: &projects::Project,
repo: &git2::Repository,
) -> Result<sessions::Session, Box<dyn std::error::Error>> {
@ -236,12 +251,10 @@ fn write_beginning_meta_files<R: tauri::Runtime>(
session
.update(project)
.with_context(|| "failed to update session")?;
events::session(&window, &project, &session);
Ok(session)
}
None => {
let session = sessions::Session::from_head(repo, project)?;
events::session(&window, &project, &session);
Ok(session)
}
}

View File

@ -2,6 +2,7 @@ use crate::{events, projects, sessions, users};
use anyhow::{Context, Result};
use git2::Repository;
use std::{
sync::mpsc,
thread,
time::{Duration, SystemTime},
};
@ -23,7 +24,11 @@ impl GitWatcher {
}
}
pub fn watch(&self, window: tauri::Window, project: projects::Project) -> Result<()> {
pub fn watch(
&self,
sender: mpsc::Sender<events::Event>,
project: projects::Project,
) -> Result<()> {
log::info!("Watching git for {}", project.path);
let shared_self = std::sync::Arc::new(self.clone());
@ -62,7 +67,10 @@ impl GitWatcher {
match local_self.check_for_changes(&project, &user) {
Ok(Some(session)) => {
events::session(&window, &project, &session);
match sender.send(events::Event::session(&project, &session)) {
Err(e) => log::error!("filed to send session event: {:?}", e),
Ok(_) => {}
}
}
Ok(None) => {}
Err(error) => {

View File

@ -2,9 +2,9 @@ mod delta;
mod git;
pub use self::delta::WatcherCollection;
use crate::{projects, users};
use crate::{events, projects, users};
use anyhow::Result;
use tauri;
use std::sync::mpsc;
pub struct Watcher<'a> {
git_watcher: git::GitWatcher,
@ -25,9 +25,13 @@ impl<'a> Watcher<'a> {
}
}
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())?;
pub fn watch(
&self,
sender: mpsc::Sender<events::Event>,
project: &projects::Project,
) -> Result<()> {
self.delta_watcher.watch(sender.clone(), project.clone())?;
self.git_watcher.watch(sender.clone(), project.clone())?;
Ok(())
}

View File

@ -45,20 +45,6 @@
"security": {
"csp": null
},
"windows": [
{
"fullscreen": false,
"height": 600,
"resizable": true,
"title": "git-butler-tauri",
"width": 800,
"hiddenTitle": true,
"theme": "Dark",
"titleBarStyle": "Overlay",
"minWidth": 600,
"minHeight": 300
}
],
"systemTray": {
"iconPath": "icons/tray.png",
"iconAsTemplate": true