tauri_plugin_pytauri/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! This crate is currently only used internally by pytauri to
//! implement IPC communication between the frontend and Python.

mod commands;
mod gil_runtime;

use std::error::Error;
use std::fmt::Display;
use std::ops::Deref;

use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use pytauri_core::tauri_runtime::Runtime as PyTauriRuntime;
use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Manager, Runtime};

use crate::commands::invoke_handler;

const PLUGIN_NAME: &str = "pytauri";

type PyInvokeHandlerType = PyObject;

struct PyInvokeHandler(PyInvokeHandlerType);

impl Deref for PyInvokeHandler {
    type Target = PyInvokeHandlerType;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl PyInvokeHandler {
    fn new(handler: PyInvokeHandlerType) -> Self {
        Self(handler)
    }
}

/// Initializes the plugin.
///
/// The `py_invoke_handler` should have the following signature:
///
/// > def py_invoke_handler(invoke: [Invoke][pytauri_core::ext_mod::ipc::Invoke], /) -> None:
/// >     ...
///
/// It will be stored in the tauri app state and used to handle ipc requests from the frontend.
/// You can get its reference through [PyInvokeHandlerExt].
///
/// # NOTE:
///
/// - `py_invoke_handler` will be called in a tokio runtime, so it must not block for a long time.
///     - `tokio runtime` means it is running on an external thread.
/// - `py_invoke_handler` must not raise exceptions, otherwise it will result in logical undefined behavior.
pub fn init(py_invoke_handler: PyInvokeHandlerType) -> TauriPlugin<PyTauriRuntime> {
    Builder::<PyTauriRuntime>::new(PLUGIN_NAME)
        .invoke_handler(invoke_handler)
        .setup(|app_handle, _plugin_api| {
            // if false, there has already state set for the app instance.
            if !app_handle.manage(PyInvokeHandler::new(py_invoke_handler)) {
                unreachable!(
                    "`PyInvokeHandler` is private, so it is impossible for other crates to manage it"
                )
            }
            Ok(())
        })
        .build()
}

mod sealed {
    use super::*;

    pub trait SealedTrait<R> {}

    impl<R: Runtime, T: Manager<R>> SealedTrait<R> for T {}
}

/// This error indicates that the extension is not initialized
#[derive(Debug)]
pub struct PyInvokeHandlerStateError;

impl Display for PyInvokeHandlerStateError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Failed to get `PyInvokeHandler` from state, maybe `{}` is not initialized",
            env!("CARGO_PKG_NAME")
        )
    }
}

impl Error for PyInvokeHandlerStateError {}

impl From<PyInvokeHandlerStateError> for PyErr {
    fn from(value: PyInvokeHandlerStateError) -> Self {
        PyRuntimeError::new_err(format!("{value}"))
    }
}

pub type PyInvokeHandlerStateResult<T> = Result<T, PyInvokeHandlerStateError>;

/// Gets the `py_invoke_handler` passed in when initializing the plugin.
pub trait PyInvokeHandlerExt<R: Runtime>: Manager<R> + sealed::SealedTrait<R> {
    fn try_py_invoke_handler(
        &self,
    ) -> PyInvokeHandlerStateResult<impl Deref<Target = PyInvokeHandlerType>> {
        self.try_state::<PyInvokeHandler>()
            .map(|state| state.inner().deref())
            .ok_or(PyInvokeHandlerStateError)
    }

    /// # Panic
    ///
    /// If [PyInvokeHandlerExt::try_py_invoke_handler] returns an error.
    fn py_invoke_handler(&self) -> impl Deref<Target = PyInvokeHandlerType> {
        self.try_py_invoke_handler().unwrap()
    }
}

impl<R: Runtime, T: Manager<R>> PyInvokeHandlerExt<R> for T {}