Search code examples
eventsrusttauri

Tauri: Is there some way to access AppHandler or Window in regular struct or state managed one?


I'm tring to use hotwatch to monitor folders for backups.

If any changes happen, expect to emit an event to frontend to update pages.

Then i found that it's not able to access AppHandle or Window in regular struct.

I have tried using lifetime, but as Tauri: accessing-an-apphandle-in-commands mention, it seems there's no way to mantain a long enough life time.

pub struct MonitorHandler<'a> {
    pub watcher: Hotwatch;
    pub app_handler: Option<&'a AppHandle>
}

impl MonitorHandler<'_> {
    pub fn initialize_app_handler(&mut self, handler: AppHandle) {
        self.app_handler = Some(&handler);
    }
    
    pub fn append_target(path: &str) {
        self.watcher.watch(path, |event| {
            self.app_handler.emit_all("update");
        })
    }
}

// whitch shows error
89 |     pub fn initialize_app_handler(&mut self, handler: AppHandle) {
   |                                   --------- has type `&mut MonitorHandler<'1>`
90 |         self.app_handler = Some(&handler);
   |         ------------------------^^^^^^^^-
   |         |                       |
   |         |                       borrowed value does not live long enough
   |         assignment requires that `handler` is borrowed for `'1`
...
93 |     }
   |     - `handler` dropped here while still borrowed

I have tried add app handle when setup, but it still not work, and with the same life time problem.

// main.rs
...
fn main() {
    let monitor = Hotwatch::new().expect("failed to initialize monitor_handler");
    let store = MonitorHandler(Mutex::new(MonitorHandler{ watcher: monitor }));
    
    let context = tauri::generate_context!();
    tauri::Builder::default()
                .manage(store)
                .setup(|app| {
                       store.0.lock().unwrap().initialize_app_handler(app.app_handle());
                })
                .invoke_handler(tauri::generate_handler![
                    initialize_app_handler
                ])
                .run(context)
                .expect("error while running tauri application");
}

// which will occurs
   |
40 |   let store = MonitorHandler(Mutex::new(MonitorHandler {
   |       ----- move occurs because `store` has type `MonitorHandler<'_>`, which does not implement the `Copy` trait
...
51 |     .manage(store)
   |             ----- value moved here
52 |     .setup(|app| {
   |            ^^^^^^^^^^ value used here after move
53 |       store.0.lock().unwrap().initialize_app_handler(app.app_handle());
   |       ------- use occurs due to use in closure

I'm not familiar with rust and tauri, so is there some way to access the AppHandle or Window in my situation? or i have to do this in another way?

Here's my origin code

// handler.rs
// in this case, whether it's AppHandle or Window, the program will just stuck and not able to anything
pub struct MonitorHandler {
    pub watcher: Hotwatch;
    pub app_handler: Option<AppHandle>
}

impl MonitorHandler {
    pub fn initialize_app_handler(&mut self, handler: AppHandle) {
        self.app_handler = Some(handler.clone());
    }
}

pub struct MonitorHandler(pub Mutex<MonitorHandler>);

// main.rs
use hotwatch::Hotwatch;
use tauri::{ Manager, AppHandle };
use crate::handler::MonitorHandler;

#[tauri::command]
pub fn initialize_app_handler(monitor: State<MonitorHandler>, app_handler: AppHandle) -> bool {
    monitor.0.lock().unwrap().initialize_app_handler(app_handler);
    true
}

fn main() {
    let monitor = Hotwatch::new().expect("failed to initialize monitor_handler");
    let store = MonitorHandler(Mutex::new(MonitorHandler{ watcher: monitor }));
    
    let context = tauri::generate_context!();
    tauri::Builder::default()
                .manage(store)
                .setup()
                .invoke_handler(tauri::generate_handler![
                    initialize_app_handler
                ])
                .run(context)
                .expect("error while running tauri application");
}


Solution

  • The reason it get stuck is that the lock action cause a dead lock in the AppHandle since the stored sturct is assigned to the instance of the AppHandle.

    How to solve the problem is simple, greet thanks for @FabianLars, here's the discussion Is there some way for backend to emit event actively?

    To be brief, just initialize the struct and manage it in setup, like this

    // handler.rs
    pub struct MonitorHandler {
        pub watcher: Hotwatch;
        pub app_handler: AppHandle
    }
    
    impl MonitorHandler {
        pub fn do_something(&self) {
            // do something
            self.app_handler.emit_all("events", some_payload { ... });
        }
    }
    
    pub struct MonitorHandler(pub Mutex<MonitorHandler>);
    
    // main.rs
    use hotwatch::Hotwatch;
    use tauri::{ Manager, AppHandle };
    use crate::handler::MonitorHandler;
    
    fn main() {
        // let monitor = Hotwatch::new().expect("failed to initialize monitor_handler");
        // let store = MonitorHandler(Mutex::new(MonitorHandler{ watcher: monitor }));
        
        let context = tauri::generate_context!();
        tauri::Builder::default()
                    .setup(move |app| {
                       let monitor = Hotwatch::new().expect("failed to initialize monitor_handler");
                       let struct_app_handle = app.handle().clone();
                       let store = MonitorHandler(Mutex::new(MonitorHandler{ watcher: monitor, app_handler: struct_app_handle }));
    
                       app.manage(store);
                    })
                    .invoke_handler(tauri::generate_handler![
                        initialize_app_handler
                    ])
                    .run(context)
                    .expect("error while running tauri application");
    }