1use crate::error::{Result, X86Error, io_error};
2use crate::image::{Image, ImageKind};
3use std::fs;
4use std::path::{Path, PathBuf};
5use std::time::Duration;
6
7#[derive(Debug, Clone)]
8pub enum Resource {
9 File(PathBuf),
10 Url(String),
11 Bytes { name: String, bytes: Vec<u8> },
12}
13
14impl Resource {
15 pub fn file(path: impl Into<PathBuf>) -> Self {
16 Self::File(path.into())
17 }
18
19 pub fn url(url: impl Into<String>) -> Self {
20 Self::Url(url.into())
21 }
22
23 pub fn bytes(name: impl Into<String>, bytes: impl Into<Vec<u8>>) -> Self {
24 Self::Bytes {
25 name: name.into(),
26 bytes: bytes.into(),
27 }
28 }
29}
30
31#[derive(Debug, Clone)]
32pub struct FetchOptions {
33 pub timeout: Duration,
34 pub user_agent: String,
35}
36
37impl Default for FetchOptions {
38 fn default() -> Self {
39 Self {
40 timeout: Duration::from_secs(60),
41 user_agent: format!("x86-native/{}", env!("CARGO_PKG_VERSION")),
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
47pub struct Bootloader {
48 pub image: Image,
49 pub source: Resource,
50}
51
52impl Bootloader {
53 pub fn load(source: Resource) -> Result<Self> {
54 Self::load_with_options(source, &FetchOptions::default())
55 }
56
57 pub fn load_with_options(source: Resource, options: &FetchOptions) -> Result<Self> {
58 let image = load_resource(&source, ImageKind::Bootloader, options)?;
59 Ok(Self { image, source })
60 }
61
62 pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
63 Self::load(Resource::file(path.as_ref().to_path_buf()))
64 }
65
66 #[cfg(feature = "remote")]
67 pub fn from_url(url: impl Into<String>) -> Result<Self> {
68 Self::load(Resource::url(url))
69 }
70}
71
72pub fn load_resource(source: &Resource, kind: ImageKind, options: &FetchOptions) -> Result<Image> {
73 match source {
74 Resource::File(path) => Image::from_file(kind, path),
75 Resource::Bytes { name, bytes } => Ok(Image::from_bytes(kind, name.clone(), bytes.clone())),
76 Resource::Url(url) => load_url(url, kind, options),
77 }
78}
79
80#[cfg(feature = "remote")]
81fn load_url(url: &str, kind: ImageKind, options: &FetchOptions) -> Result<Image> {
82 if !(url.starts_with("http://") || url.starts_with("https://")) {
83 return Err(X86Error::Remote {
84 url: url.to_owned(),
85 message: "only http:// and https:// URLs are supported".to_owned(),
86 });
87 }
88 let agent = ureq::AgentBuilder::new()
89 .timeout(options.timeout)
90 .user_agent(&options.user_agent)
91 .build();
92 let response = agent.get(url).call().map_err(|error| X86Error::Remote {
93 url: url.to_owned(),
94 message: error.to_string(),
95 })?;
96 let mut reader = response.into_reader();
97 let mut bytes = Vec::new();
98 std::io::Read::read_to_end(&mut reader, &mut bytes).map_err(|source| io_error(url, source))?;
99 let name = url
100 .rsplit('/')
101 .next()
102 .filter(|x| !x.is_empty())
103 .unwrap_or("remote-bootloader");
104 Ok(Image::from_bytes(kind, name, bytes))
105}
106
107#[cfg(not(feature = "remote"))]
108fn load_url(url: &str, _kind: ImageKind, _options: &FetchOptions) -> Result<Image> {
109 let _ = url;
110 Err(X86Error::RemoteDisabled)
111}
112
113pub fn copy_resource_to(source: &Resource, destination: impl AsRef<Path>) -> Result<PathBuf> {
114 let destination = destination.as_ref();
115 let image = load_resource(source, ImageKind::Other, &FetchOptions::default())?;
116 fs::write(destination, image.bytes()).map_err(|source| io_error(destination, source))?;
117 Ok(destination.to_path_buf())
118}