siglus_scene_vm/runtime/
net.rs1use anyhow::{anyhow, Context, Result};
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(all(target_arch = "wasm32", target_os = "unknown"))]
87 {
88 let msg = "network GET is disabled on wasm32-unknown-unknown";
89 self.set_error(url, msg);
90 log::error!("{}: {}", msg, url);
91 return Err(anyhow!(msg));
92 }
93
94 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
95 {
96 let response = ureq::get(url)
97 .call()
98 .with_context(|| format!("GET {url}"))?;
99 self.last_status = Some(response.status());
100 let mut reader = response.into_reader();
101 let mut bytes = Vec::new();
102 std::io::Read::read_to_end(&mut reader, &mut bytes)
103 .with_context(|| format!("read response body from {url}"))?;
104 Ok(bytes)
105 }
106 }
107
108 pub fn post_bytes(&mut self, url: &str, content_type: &str, body: &[u8]) -> Result<Vec<u8>> {
109 self.clear_status(url);
110
111 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
112 {
113 let msg = "network POST is disabled on wasm32-unknown-unknown";
114 self.set_error(url, msg);
115 log::error!("{}: {} content_type={}", msg, url, content_type);
116 let _ = body;
117 return Err(anyhow!(msg));
118 }
119
120 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
121 {
122 let response = ureq::post(url)
123 .set("Content-Type", content_type)
124 .send_bytes(body)
125 .with_context(|| format!("POST {url}"))?;
126 self.last_status = Some(response.status());
127 let mut reader = response.into_reader();
128 let mut bytes = Vec::new();
129 std::io::Read::read_to_end(&mut reader, &mut bytes)
130 .with_context(|| format!("read response body from {url}"))?;
131 Ok(bytes)
132 }
133 }
134}