everything_plugin/data/
mod.rs

1//! Everything data directory:
2//! - `app_data=1`: `%APPDATA%\Everything`, `%LOCALAPPDATA%\Everything` (also Scoop)
3//! - `app_data=0`: parent directory of `Everything.exe`/`Everything64.exe`
4//!
5//! Either `plugin_?et_setting_string` or `os_get_(local_)?app_data_path_cat_filename` can be used to read/write configs:
6//! - `plugin_?et_setting_string`:
7//!   - Depends on plugin message data
8//!   - Is in ini format
9//!   - Must use DLL name as the section and limited to that section
10//!   - Has built-in backup mechanism
11//! - `os_get_(local_)?app_data_path_cat_filename`:
12//!   - Is more flexible
13//!   - Can also be used to read/write general data
14//!   - But be careful with named instances
15
16use std::{
17    ffi::{CStr, CString, c_void},
18    fmt::Debug,
19    mem::MaybeUninit,
20    path::PathBuf,
21};
22
23use serde::{Serialize, de::DeserializeOwned};
24use tracing::{debug, error};
25
26use crate::{PluginApp, PluginHandler, PluginHost, sys};
27
28pub mod config;
29
30pub trait Config: Serialize + DeserializeOwned + Send + Debug + 'static {}
31
32impl<T: Serialize + DeserializeOwned + Send + Debug + 'static> Config for T {}
33
34impl<A: PluginApp> PluginHandler<A> {
35    pub fn load_settings(&self, data: *mut c_void) -> Option<A::Config> {
36        match self.get_host() {
37            Some(host) => {
38                let s = host.plugin_get_setting_string(data, "_", 0 as _);
39                if !s.is_null() {
40                    let config = unsafe { CStr::from_ptr(s as _) };
41                    debug!(
42                        config = %config.to_str().unwrap_or("Invalid UTF-8"),
43                        "Plugin config",
44                    );
45                    match serde_json::from_slice(config.to_bytes()) {
46                        Ok(config) => Some(config),
47                        Err(e) => {
48                            error!(%e, "Plugin config parse error");
49                            None
50                        }
51                    }
52                } else {
53                    None
54                }
55            }
56            None if !data.is_null() => {
57                // TODO: unstable Box::into_inner()
58                let config = *unsafe { Box::from_raw(data as *mut A::Config) };
59                debug!(?config, "Plugin config");
60                Some(config)
61            }
62            None => None,
63        }
64    }
65
66    pub fn save_settings(&self, data: *mut c_void) -> *mut c_void {
67        let config = unsafe { self.app() }.config();
68        let config = serde_json::to_string(config).unwrap();
69        debug!(%config, "Plugin save settings");
70
71        self.host().plugin_set_setting_string(data, "_", &config);
72        1 as _
73    }
74}
75
76impl PluginHost {
77    fn os_get_app_data_path_cat_filename_common(&self, name: &str, filename: &str) -> PathBuf {
78        let os_get_app_data_path_cat_filename: unsafe extern "system" fn(
79            filename: *const sys::everything_plugin_utf8_t,
80            cbuf: *mut sys::everything_plugin_utf8_buf_t,
81        ) = unsafe { self.get(name).unwrap_unchecked() };
82
83        let filename = CString::new(filename).unwrap();
84
85        let mut cbuf = MaybeUninit::uninit();
86        self.utf8_buf_init(cbuf.as_mut_ptr());
87
88        unsafe { os_get_app_data_path_cat_filename(filename.as_ptr() as _, cbuf.as_mut_ptr()) };
89
90        self.utf8_buf_into_string(cbuf.as_mut_ptr()).into()
91    }
92
93    pub fn os_get_app_data_path(&self) -> PathBuf {
94        self.os_get_app_data_path_cat_filename("")
95    }
96
97    /// Build the setting or data full path using the specified filename.
98    ///
99    /// The full path is stored in cbuf.  
100    /// This will either be `%APPDATA%\Everything\filename` or filename in the same location as your `Everything.exe`  
101    /// Depending on your app_data setting.  
102    /// cbuf must be initialized with [`Self::utf8_buf_init`]
103    ///
104    /// See also [`Self::utf8_buf_init`]
105    pub fn os_get_app_data_path_cat_filename(&self, filename: &str) -> PathBuf {
106        self.os_get_app_data_path_cat_filename_common("os_get_app_data_path_cat_filename", filename)
107    }
108
109    pub fn os_get_local_app_data_path(&self) -> PathBuf {
110        self.os_get_local_app_data_path_cat_filename("")
111    }
112
113    /// Build the data full path using the specified filename.
114    ///
115    /// The full path is stored in cbuf.  
116    /// This will either be `%LOCALAPPDATA%\Everything\filename` or filename in the same location as your `Everything.exe`  
117    /// Depending on your app_data setting.
118    pub fn os_get_local_app_data_path_cat_filename(&self, filename: &str) -> PathBuf {
119        self.os_get_app_data_path_cat_filename_common(
120            "os_get_local_app_data_path_cat_filename",
121            filename,
122        )
123    }
124
125    /// Get an string setting value by name from the specified setting sorted list.
126    ///
127    /// Returns a pointer to the string value.
128    ///
129    /// `current_value` is returned if the setting value was not found.
130    ///
131    /// ## Note
132    /// - `data`: On [`sys::EVERYTHING_PLUGIN_PM_START`]
133    pub fn plugin_get_setting_string(
134        &self,
135        data: *mut c_void,
136        name: &str,
137        current_string: *mut sys::everything_plugin_utf8_t,
138    ) -> *mut sys::everything_plugin_utf8_t {
139        // Not in header
140        let plugin_get_setting_string: unsafe extern "system" fn(
141            sorted_list: *mut c_void,
142            name: *const sys::everything_plugin_utf8_t,
143            current_string: *mut sys::everything_plugin_utf8_t,
144        ) -> *mut sys::everything_plugin_utf8_t =
145            unsafe { self.get("plugin_get_setting_string").unwrap_unchecked() };
146        let name = CString::new(name).unwrap();
147        unsafe { plugin_get_setting_string(data, name.as_ptr() as _, current_string) }
148    }
149
150    /// Writes a string setting value with the specified name to the specified output stream.
151    ///
152    /// ## Note
153    /// - `data`: On [`sys::EVERYTHING_PLUGIN_PM_SAVE_SETTINGS`]
154    /// - `value`: Must be single-line. Chars after the first newline cannot be read.
155    ///
156    /// `Plugins{-instance_name}.ini`:
157    /// ```ini
158    /// [{plugin_dll}]
159    /// {name}={value}
160    /// ...
161    /// ```
162    pub fn plugin_set_setting_string(&self, data: *mut c_void, name: &str, value: &str) {
163        debug_assert!(
164            !value.contains('\n'),
165            "setting string value must be single-line"
166        );
167
168        // Not in header
169        let plugin_set_setting_string: unsafe extern "system" fn(
170            output_stream: sys::everything_plugin_output_stream_t,
171            name: *const sys::everything_plugin_utf8_t,
172            value: *const sys::everything_plugin_utf8_t,
173        ) = unsafe { self.get("plugin_set_setting_string").unwrap_unchecked() };
174        let name = CString::new(name).unwrap();
175        let value = CString::new(value).unwrap();
176        unsafe { plugin_set_setting_string(data, name.as_ptr() as _, value.as_ptr() as _) };
177    }
178
179    /// Non-official `plugins.json` path.
180    ///
181    /// TODO: Named instances
182    pub fn plugin_setting_json_path(&self) -> PathBuf {
183        self.os_get_app_data_path().join("plugins.json")
184    }
185}