siglus_scene_vm/runtime/
net.rs1use 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}