Skip to main content

embedded_wg_display/widget/manager/
mod.rs

1//! Widget installation and removal.
2use alloc::string::String;
3use alloc::vec::Vec;
4use common::models::SystemConfiguration;
5
6use crate::runtime::Runtime;
7use crate::runtime::http_sync::{self};
8use crate::storage::StorageError;
9use crate::util::globals;
10use defmt::error;
11
12/// Errors returned by widget management operations.
13#[derive(Debug, defmt::Format)]
14pub enum WidgetManagerError {
15    /// A storage read/write failed.
16    Storage(StorageError),
17    /// An HTTP request failed.
18    HttpError(&'static str),
19    /// A WASM load, instantiation, or metadata extraction failed.
20    WasmError(&'static str),
21    AlreadyInstalled(&'static str),
22}
23
24impl From<StorageError> for WidgetManagerError {
25    fn from(e: StorageError) -> Self {
26        WidgetManagerError::Storage(e)
27    }
28}
29
30impl From<&'static str> for WidgetManagerError {
31    fn from(e: &'static str) -> Self {
32        WidgetManagerError::HttpError(e)
33    }
34}
35
36impl From<reqwless::Error> for WidgetManagerError {
37    fn from(_: reqwless::Error) -> Self {
38        WidgetManagerError::HttpError("HTTP request failed")
39    }
40}
41
42/// Zero-sized helper that provides static methods for widget lifecycle management.
43pub struct WidgetManager;
44
45impl WidgetManager {
46    /// Download and persist a widget
47    /// Also adds a default configuration for the widget
48    /// # Arguments
49    /// * `download_url` - The URL to download the widget from
50    /// * `description` - A description of the widget
51    /// # Returns
52    /// An error if the download or persisting failed
53    pub async fn install_widget(
54        download_url: &str,
55        description: &str,
56    ) -> Result<(), WidgetManagerError> {
57        // let http_client = globals::http_client();
58        let response = http_sync::http_request_async(http_sync::HttpRequest {
59            method: reqwless::request::Method::GET,
60            url: alloc::string::String::from(download_url),
61            body: None,
62        })
63        .await
64        .map_err(|_| WidgetManagerError::HttpError("HTTP bridge request failed"))?;
65
66        let mut runtime = Runtime::new();
67
68        let widget_metadata_result = unsafe { runtime.get_widget_metadata(&response.bytes).await };
69
70        let mut widget_metadata = match widget_metadata_result {
71            Ok(config) => config,
72            Err(_) => {
73                error!("Failed to get config schema for '{}'", download_url);
74                return Err(WidgetManagerError::WasmError(
75                    "Failed to get widget config schema",
76                ));
77            }
78        };
79
80        // check if widget has already been installed
81        let system_config: SystemConfiguration =
82            globals::with_storage(|storage| storage.get_system_config())
83                .await
84                .unwrap_or_default();
85
86        if system_config
87            .widgets
88            .iter()
89            .any(|w| w.name == widget_metadata.name)
90        {
91            return Err(WidgetManagerError::AlreadyInstalled(
92                "Widget with same name has already been installed",
93            ));
94        }
95
96        widget_metadata.description = String::from(description);
97
98        // simplify storage by just having one call that handles everything
99        globals::with_storage(|storage| {
100            storage.save_compiled_widget(widget_metadata, &response.bytes)
101        })
102        .await?;
103        Ok(())
104    }
105
106    /// Deinstall a widget
107    /// # Arguments
108    /// * `widget_name` - The name of the widget to deinstall
109    /// # Returns
110    /// An error if the deinstallation failed
111    pub async fn deinstall_widget(widget_name: &str) -> Result<(), WidgetManagerError> {
112        globals::with_storage(|storage| storage.deinstall_widget(widget_name)).await?;
113        Ok(())
114    }
115
116    /// Get a previously installed widget
117    /// # Arguments
118    /// * `widget_name` - The name of the widget to get
119    /// # Returns
120    /// The compiled widget
121    /// TODO: return Vec for now, could use CompiledWidget when runtime is ready
122    #[allow(dead_code)]
123    pub async fn get_widget(widget_name: &str) -> Result<Vec<u8>, WidgetManagerError> {
124        let widget = globals::with_storage(|storage| storage.wasm_read(widget_name)).await?;
125        Ok(widget)
126    }
127
128    // Get all names of installed widgets
129    // Returns: A vector of widget names
130    // Will be used by the rendere for gettin all widgets to display them in the UI
131    // TODO: maybe use to check if system config is allinged with actual stored binaries
132    #[allow(dead_code)]
133    pub async fn get_widgets() -> Result<Vec<String>, WidgetManagerError> {
134        let widget_names = globals::with_storage(|storage| {
135            storage
136                .get_system_config()
137                .map(|config| config.widgets.into_iter().map(|w| w.name).collect())
138        })
139        .await?;
140        Ok(widget_names)
141    }
142}