pytauri_core/ext_mod_impl/lib/
manager.rs

1use std::{collections::HashMap, iter::Iterator};
2
3use pyo3::{
4    exceptions::PyValueError,
5    marker::Ungil,
6    prelude::*,
7    types::{PyDict, PyType},
8    FromPyObject, IntoPyObject,
9};
10use pyo3_utils::ungil::UnsafeUngilExt;
11use tauri::{Manager as TauriManager, State};
12
13use crate::{
14    ext_mod::{
15        path::PathResolver,
16        webview::{TauriWebviewWindow, WebviewWindow},
17        App, AppHandle, PyAppHandleExt, TauriApp, TauriAppHandle,
18    },
19    tauri_runtime::Runtime,
20};
21
22pub(crate) struct StateManager(Py<PyDict>);
23
24impl StateManager {
25    pub(crate) fn get_or_init<'a>(
26        py: Python<'_>,
27        manager: &'a impl TauriManager<Runtime>,
28    ) -> State<'a, Self> {
29        if let Some(state) = manager.try_state::<Self>() {
30            return state;
31        }
32        manager.manage(Self(PyDict::new(py).into()));
33        manager.state::<Self>()
34    }
35
36    pub(crate) fn manage(&self, py: Python<'_>, state: &Bound<PyAny>) -> PyResult<bool> {
37        let this = self.0.bind(py);
38        let py_type = state.get_type();
39        // If the state for the T type has previously been set, the state is unchanged and false is returned.
40        // Otherwise true is returned.
41        if this.contains(&py_type)? {
42            return Ok(false);
43        }
44        this.set_item(py_type, state)?;
45        Ok(true)
46    }
47
48    pub(crate) fn state<'py>(
49        &self,
50        py: Python<'py>,
51        state_type: &Bound<'py, PyType>,
52    ) -> PyResult<Bound<'py, PyAny>> {
53        let state = self.try_state(py, state_type)?;
54        state.ok_or_else(|| {
55            PyValueError::new_err(format!("state() called before manage() for {state_type}"))
56        })
57    }
58
59    pub(crate) fn try_state<'py>(
60        &self,
61        py: Python<'py>,
62        state_type: &Bound<'py, PyType>,
63    ) -> PyResult<Option<Bound<'py, PyAny>>> {
64        let this = self.0.bind(py);
65        PyDictMethods::get_item(this, state_type)
66    }
67}
68
69/// The Implementers of [tauri::Manager].
70#[derive(FromPyObject, IntoPyObject, IntoPyObjectRef)]
71#[non_exhaustive]
72// TODO: more types
73pub enum ImplManager {
74    AppHandle(Py<AppHandle>),
75    WebviewWindow(Py<WebviewWindow>),
76    // NOTE: Put `App` at the end because it is the least likely to appear
77    App(Py<App>),
78}
79
80impl ImplManager {
81    #[inline]
82    pub(crate) fn _delegate_app<'py, R>(
83        py: Python<'py>,
84        app: &Py<App>,
85        f: impl FnOnce(Python<'py>, &TauriApp) -> R,
86    ) -> PyResult<R> {
87        let py_app = app.borrow(py);
88        let rs_app = py_app.0.try_lock_inner_ref()??;
89        Ok(f(py, &rs_app))
90    }
91
92    #[inline]
93    pub(crate) fn _delegate_app_handle<'py, R>(
94        py: Python<'py>,
95        app_handle: &Py<AppHandle>,
96        f: impl FnOnce(Python<'py>, &TauriAppHandle) -> R,
97    ) -> PyResult<R> {
98        let app_handle = app_handle.get().0.inner_ref();
99        Ok(f(py, &app_handle))
100    }
101
102    #[inline]
103    pub(crate) fn _delegate_webview_window<'py, R>(
104        py: Python<'py>,
105        webview_window: &Py<WebviewWindow>,
106        f: impl FnOnce(Python<'py>, &TauriWebviewWindow) -> R,
107    ) -> PyResult<R> {
108        let webview_window = webview_window.get().0.inner_ref();
109        Ok(f(py, &webview_window))
110    }
111
112    #[inline]
113    pub(crate) fn _delegate_manager_ungil<M, F, R>(py: Python<'_>, manager: &M, f: F) -> R
114    where
115        M: tauri::Manager<Runtime>,
116        F: FnOnce(&M) -> R + Ungil + Send,
117        R: Ungil,
118    {
119        unsafe {
120            // safety: `tauri::Manager` does not hold the GIL, so this is safe
121            py.allow_threads_unsend(manager, f)
122        }
123    }
124}
125
126/**
127```ignore
128fn manager_method_impl(py: Python<'_>, manager: &ImplManager) -> PyResult<()> {
129    manager_method_impl!(py, manager, |_py, manager| {
130        // here the `manager` is equivalent to `&impl tauri::Manager<Runtime>`
131        manager.get_webview_window("main")
132    })?;
133
134    manager_method_impl!(py, manager, [ungil], |manager| {
135        // here equivalent to `Python::allow_threads_unsend`
136        manager.get_webview_window("main")
137    })?;
138
139    Ok(())
140}
141```
142*/
143macro_rules! manager_method_impl {
144    // impl
145    ($py:expr, $manager:expr, $f0:expr, $f1:expr, $f2:expr) => {{
146        use $crate::ext_mod::ImplManager;
147
148        let manager: &ImplManager = $manager;
149        match manager {
150            ImplManager::App(v) => {
151                ImplManager::_delegate_app($py, v, $f0)
152            }
153            ImplManager::AppHandle(v) => {
154                ImplManager::_delegate_app_handle($py, v, $f1)
155            }
156            ImplManager::WebviewWindow(v) => {
157                ImplManager::_delegate_webview_window($py, v, $f2)
158            }
159        }
160    }};
161
162    // entry1 -> entry0
163    ($py:expr, $manager:expr, [ungil], $($f:tt)*) => {{
164        manager_method_impl!($py, $manager, |py, manager| {
165            $crate::ext_mod::ImplManager::_delegate_manager_ungil(py, manager, $($f)*)
166        })
167    }};
168    // entry0
169    ($py:expr, $manager:expr, $($f:tt)*) => {
170        manager_method_impl!($py, $manager, $($f)*, $($f)*, $($f)*)
171    };
172}
173
174// if export this macro, remember to enable doctest 👆
175pub(crate) use manager_method_impl;
176
177/// See also: [tauri::Manager].
178#[pyclass(frozen)]
179#[non_exhaustive]
180pub struct Manager;
181
182#[pymethods]
183impl Manager {
184    #[staticmethod]
185    fn app_handle(py: Python<'_>, slf: ImplManager) -> PyResult<Py<AppHandle>> {
186        manager_method_impl!(py, &slf, |py, manager| manager
187            // TODO, PERF: release the GIL?
188            .py_app_handle()
189            .clone_ref(py))
190    }
191
192    #[staticmethod]
193    fn get_webview_window(
194        py: Python<'_>,
195        slf: ImplManager,
196        label: &str,
197    ) -> PyResult<Option<WebviewWindow>> {
198        manager_method_impl!(py, &slf, [ungil], |manager| {
199            manager.get_webview_window(label).map(WebviewWindow::new)
200        })
201    }
202
203    #[staticmethod]
204    fn webview_windows(
205        py: Python<'_>,
206        slf: ImplManager,
207    ) -> PyResult<HashMap<String, WebviewWindow>> {
208        manager_method_impl!(py, &slf, [ungil], |manager| {
209            manager
210                .webview_windows()
211                .into_iter()
212                .map(|(label, window)| (label, WebviewWindow::new(window)))
213                .collect::<_>()
214        })
215    }
216
217    #[staticmethod]
218    fn manage(py: Python<'_>, slf: ImplManager, state: &Bound<PyAny>) -> PyResult<bool> {
219        manager_method_impl!(py, &slf, |py, manager| {
220            let state_manager = StateManager::get_or_init(py, manager);
221            state_manager.manage(py, state)
222        })?
223    }
224
225    #[staticmethod]
226    fn state<'py>(
227        py: Python<'py>,
228        slf: ImplManager,
229        state_type: &Bound<'py, PyType>,
230    ) -> PyResult<Bound<'py, PyAny>> {
231        manager_method_impl!(py, &slf, |py, manager| {
232            let state_manager = StateManager::get_or_init(py, manager);
233            state_manager.state(py, state_type)
234        })?
235    }
236
237    #[staticmethod]
238    fn try_state<'py>(
239        py: Python<'py>,
240        slf: ImplManager,
241        state_type: &Bound<'py, PyType>,
242    ) -> PyResult<Option<Bound<'py, PyAny>>> {
243        manager_method_impl!(py, &slf, |py, manager| {
244            let state_manager = StateManager::get_or_init(py, manager);
245            state_manager.try_state(py, state_type)
246        })?
247    }
248
249    #[staticmethod]
250    fn path(py: Python<'_>, slf: ImplManager) -> PyResult<PathResolver> {
251        manager_method_impl!(py, &slf, [ungil], |manager| {
252            let path_resolver = manager.path().clone();
253            PathResolver::new(path_resolver)
254        })
255    }
256}