Skip to main content

siglus_scene_vm/runtime/
net.rs

1use anyhow::{Context, Result, anyhow};
2use std::path::Path;
3#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
4use std::process::Command;
5
6#[derive(Debug, Default, Clone)]
7pub struct TnmNet {
8    pub last_target: Option<String>,
9    pub last_status: Option<u16>,
10    pub last_error: Option<String>,
11}
12
13impl TnmNet {
14    fn clear_status(&mut self, target: &str) {
15        self.last_target = Some(target.to_string());
16        self.last_status = None;
17        self.last_error = None;
18    }
19
20    fn set_error(&mut self, target: &str, err: &str) {
21        self.last_target = Some(target.to_string());
22        self.last_status = None;
23        self.last_error = Some(err.to_string());
24    }
25
26    pub fn open_target(&mut self, target: &str) -> Result<()> {
27        self.clear_status(target);
28
29        #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
30        {
31            #[cfg(target_os = "macos")]
32            let mut cmd = {
33                let mut c = Command::new("open");
34                c.arg(target);
35                c
36            };
37            #[cfg(target_os = "linux")]
38            let mut cmd = {
39                let mut c = Command::new("xdg-open");
40                c.arg(target);
41                c
42            };
43            #[cfg(target_os = "windows")]
44            let mut cmd = {
45                let mut c = Command::new("cmd");
46                c.args(["/C", "start", "", target]);
47                c
48            };
49
50            let status = cmd
51                .status()
52                .with_context(|| format!("open external target {target}"))?;
53            if status.success() {
54                Ok(())
55            } else {
56                let msg = format!("external opener exited with status {status}");
57                self.set_error(target, &msg);
58                Err(anyhow!(msg))
59            }
60        }
61
62        #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
63        {
64            let msg = "external opener is not implemented on this platform";
65            self.set_error(target, msg);
66            log::error!("{}: {}", msg, target);
67            Err(anyhow!(msg))
68        }
69    }
70
71    pub fn open_file(&mut self, path: &Path) -> Result<()> {
72        let target = path
73            .to_str()
74            .ok_or_else(|| anyhow!("non-utf8 file path"))?
75            .to_string();
76        self.open_target(&target)
77    }
78
79    pub fn open_url(&mut self, url: &str) -> Result<()> {
80        self.open_target(url)
81    }
82
83    pub fn get_bytes(&mut self, url: &str) -> Result<Vec<u8>> {
84        self.clear_status(url);
85
86        #[cfg(any(
87            all(target_arch = "wasm32", target_os = "unknown"),
88            target_os = "horizon",
89            target_os = "vita"
90        ))]
91        {
92            let msg = "network GET is unavailable on this platform";
93            self.set_error(url, msg);
94            log::error!("{}: {}", msg, url);
95            return Err(anyhow!(msg));
96        }
97
98        #[cfg(not(any(
99            all(target_arch = "wasm32", target_os = "unknown"),
100            target_os = "horizon",
101            target_os = "vita"
102        )))]
103        {
104            let response = ureq::get(url)
105                .call()
106                .with_context(|| format!("GET {url}"))?;
107            self.last_status = Some(response.status());
108            let mut reader = response.into_reader();
109            let mut bytes = Vec::new();
110            std::io::Read::read_to_end(&mut reader, &mut bytes)
111                .with_context(|| format!("read response body from {url}"))?;
112            Ok(bytes)
113        }
114    }
115
116    pub fn post_bytes(&mut self, url: &str, content_type: &str, body: &[u8]) -> Result<Vec<u8>> {
117        self.clear_status(url);
118
119        #[cfg(any(
120            all(target_arch = "wasm32", target_os = "unknown"),
121            target_os = "horizon",
122            target_os = "vita"
123        ))]
124        {
125            let msg = "network POST is unavailable on this platform";
126            self.set_error(url, msg);
127            log::error!("{}: {} content_type={}", msg, url, content_type);
128            let _ = body;
129            return Err(anyhow!(msg));
130        }
131
132        #[cfg(not(any(
133            all(target_arch = "wasm32", target_os = "unknown"),
134            target_os = "horizon",
135            target_os = "vita"
136        )))]
137        {
138            let response = ureq::post(url)
139                .set("Content-Type", content_type)
140                .send_bytes(body)
141                .with_context(|| format!("POST {url}"))?;
142            self.last_status = Some(response.status());
143            let mut reader = response.into_reader();
144            let mut bytes = Vec::new();
145            std::io::Read::read_to_end(&mut reader, &mut bytes)
146                .with_context(|| format!("read response body from {url}"))?;
147            Ok(bytes)
148        }
149    }
150}