183 lines
6.3 KiB
Rust
183 lines
6.3 KiB
Rust
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
|
|
use notify::{Event, RecursiveMode, Watcher};
|
|
use std::path::Path;
|
|
use std::time::{Duration, Instant};
|
|
use tray_icon::{
|
|
menu::{Menu, MenuItem},
|
|
Icon, TrayIconBuilder,
|
|
};
|
|
use winit::event::StartCause;
|
|
use winit::{
|
|
application::ApplicationHandler,
|
|
event::WindowEvent,
|
|
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
|
|
};
|
|
|
|
mod filesync;
|
|
mod git;
|
|
|
|
const PROTECT_PREFIX: &str = ".git"; // only at first directory level
|
|
const SOURCE_VAULT: &str = "D:\\Cloud\\Syncthing\\obsidian-vault"; // syncthing vault folder
|
|
const REPO_VAULT: &str =
|
|
"C:\\Users\\robin\\AppData\\Roaming\\obsidian-git-backup\\obsidian-vault-clone"; // git repo folder
|
|
const GIT_COMMIT_DELAY_AFTER_FILE_CHANGE: Duration = Duration::from_secs(30 * 60); // 30 min
|
|
|
|
#[derive(Debug)]
|
|
enum UserEvent {
|
|
MenuEvent(tray_icon::menu::MenuEvent),
|
|
FileEvent(Event),
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct App {
|
|
tray_icon: Option<tray_icon::TrayIcon>,
|
|
menu_changed_status: Option<MenuItem>,
|
|
menu_backup_now: Option<MenuItem>,
|
|
menu_exit: Option<MenuItem>,
|
|
last_change_time: Option<Instant>,
|
|
current_change_count: usize,
|
|
}
|
|
|
|
impl App {
|
|
fn create_tray_icon_menu(&mut self) {
|
|
// Create and show the tray icon
|
|
let menu_item_changed_status = MenuItem::new("Changed: 0", true, None);
|
|
let menu_item_backup_now = MenuItem::new("Backup Now", true, None);
|
|
let menu_item_exit = MenuItem::new("Exit", true, None);
|
|
|
|
let tray_menu = Menu::new();
|
|
tray_menu.append(&menu_item_changed_status).unwrap();
|
|
tray_menu.append(&menu_item_backup_now).unwrap();
|
|
tray_menu.append(&menu_item_exit).unwrap();
|
|
|
|
let icon_path = Path::new(".\\sync.ico");
|
|
let icon = Icon::from_path(icon_path, None).unwrap();
|
|
let tray_icon = TrayIconBuilder::new()
|
|
.with_menu(Box::new(tray_menu))
|
|
.with_tooltip("Obsidian Git Backup")
|
|
.with_icon(icon)
|
|
.build()
|
|
.unwrap();
|
|
|
|
self.tray_icon = Some(tray_icon);
|
|
self.menu_changed_status = Some(menu_item_changed_status);
|
|
self.menu_backup_now = Some(menu_item_backup_now);
|
|
self.menu_exit = Some(menu_item_exit);
|
|
}
|
|
|
|
fn start_backup(&mut self) {
|
|
self.last_change_time = None;
|
|
let repo_dir = Path::new(REPO_VAULT);
|
|
git::backup_changes(repo_dir).expect("Changes could not be pushed");
|
|
self.current_change_count = 0;
|
|
self.menu_update_change_count();
|
|
}
|
|
|
|
fn process_file_event(&mut self) {
|
|
self.last_change_time = Some(Instant::now());
|
|
let source = Path::new(SOURCE_VAULT);
|
|
let target = Path::new(REPO_VAULT);
|
|
filesync::sync_directory(source, target, Some(PROTECT_PREFIX))
|
|
.expect("Directories could not be synced");
|
|
self.current_change_count =
|
|
git::current_change_count(target).expect("Changes could not get counted using git");
|
|
self.menu_update_change_count();
|
|
println!("Currently changed files: {:?}", self.current_change_count);
|
|
}
|
|
|
|
fn menu_update_change_count(&mut self) {
|
|
if let Some(menu_item) = &mut self.menu_changed_status {
|
|
menu_item.set_text(format!("Changed: {:?}", self.current_change_count));
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ApplicationHandler<UserEvent> for App {
|
|
fn resumed(&mut self, _event_loop: &ActiveEventLoop) {
|
|
self.create_tray_icon_menu();
|
|
self.last_change_time = None;
|
|
}
|
|
|
|
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
|
|
match event {
|
|
UserEvent::MenuEvent(menu_event) => {
|
|
match menu_event.id() {
|
|
id if id == self.menu_backup_now.as_ref().unwrap().id() => {
|
|
println!("---> Backup triggered by menu");
|
|
// Handle push now menu item
|
|
self.start_backup();
|
|
}
|
|
id if id == self.menu_exit.as_ref().unwrap().id() => {
|
|
println!("---> Exit triggert by menu");
|
|
// Handle exit menu item
|
|
event_loop.exit();
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
UserEvent::FileEvent(_event) => {
|
|
println!("---> File change detected");
|
|
self.process_file_event();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn new_events(&mut self, event_loop: &ActiveEventLoop, _cause: StartCause) {
|
|
let now = Instant::now();
|
|
|
|
// Backup changes if last file change time exceeded threshold
|
|
if let Some(last_change_time) = self.last_change_time {
|
|
if now.duration_since(last_change_time) > GIT_COMMIT_DELAY_AFTER_FILE_CHANGE {
|
|
println!("---> Backup triggered by file change");
|
|
self.start_backup();
|
|
}
|
|
}
|
|
|
|
// Reset timer
|
|
event_loop.set_control_flow(ControlFlow::WaitUntil(now + Duration::from_secs(5)));
|
|
}
|
|
|
|
fn window_event(
|
|
&mut self,
|
|
_event_loop: &ActiveEventLoop,
|
|
_window_id: winit::window::WindowId,
|
|
_event: WindowEvent,
|
|
) {
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
// Sync folder and check for changes to back up
|
|
let source_dir = Path::new(SOURCE_VAULT);
|
|
let repo_dir = Path::new(REPO_VAULT);
|
|
filesync::sync_directory(source_dir, repo_dir, Some(PROTECT_PREFIX))
|
|
.expect("Directories could not be synced");
|
|
git::backup_changes(repo_dir).expect("Changes could not be pushed");
|
|
|
|
// Create event loop
|
|
let event_loop = EventLoop::<UserEvent>::with_user_event().build().unwrap();
|
|
event_loop.set_control_flow(ControlFlow::Wait);
|
|
|
|
// Set up menu event handler
|
|
let proxy = event_loop.create_proxy();
|
|
tray_icon::menu::MenuEvent::set_event_handler(Some(move |event| {
|
|
proxy.send_event(UserEvent::MenuEvent(event)).unwrap();
|
|
}));
|
|
|
|
// Set up file change event handler
|
|
let proxy = event_loop.create_proxy();
|
|
let mut watcher = notify::recommended_watcher(move |event| {
|
|
if let Ok(event) = event {
|
|
proxy.send_event(UserEvent::FileEvent(event)).unwrap();
|
|
}
|
|
})
|
|
.unwrap();
|
|
watcher.watch(source_dir, RecursiveMode::Recursive).unwrap();
|
|
|
|
// Start event loop
|
|
println!("Starting event loop...");
|
|
let mut app = App::default();
|
|
event_loop.run_app(&mut app).unwrap();
|
|
}
|