mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-28 04:47:42 +03:00
programmaticaly control window and channel events
This commit is contained in:
parent
76b0989ece
commit
3c42525a13
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(()),
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user