1use std::{
2 fmt::Display,
3 io,
4 path::{Path, PathBuf},
5 str::FromStr,
6};
7
8use chrono::DateTime;
9use serde::{Deserialize, Deserializer, Serialize};
10
11use crate::{
12 format::{DataFormat, DataFormatReader, DataReadError},
13 state::{InternedId, NetworkId},
14 util::sha256_file,
15};
16
17#[derive(Serialize, Deserialize, Debug, Clone)]
19pub struct BinaryEntry {
20 pub source: BinarySource,
21 #[serde(default)]
22 pub sha256: Option<String>,
23 #[serde(default)]
24 pub size: Option<u64>,
25}
26
27impl BinaryEntry {
28 pub fn with_api_path(
29 &self,
30 network: NetworkId,
31 storage_id: InternedId,
32 binary_id: InternedId,
33 ) -> BinaryEntry {
34 match &self.source {
35 BinarySource::Url(_) => self.clone(),
36 BinarySource::Path(_) => BinaryEntry {
37 source: BinarySource::Path(PathBuf::from(format!(
38 "/content/storage/{network}/{storage_id}/binaries/{binary_id}"
39 ))),
40 sha256: self.sha256.clone(),
41 size: self.size,
42 },
43 }
44 }
45
46 pub fn check_sha256(&self) -> bool {
48 self.sha256
49 .as_ref()
50 .map(|s| s.len() == 32 && s.chars().all(|c| c.is_ascii_hexdigit()))
51 .unwrap_or(false)
52 }
53
54 pub fn check_file_size(&self, path: &Path) -> Result<Option<u64>, io::Error> {
57 let Some(size) = self.size else {
58 return Ok(None);
59 };
60 Ok((path.metadata()?.len() != size).then_some(size))
61 }
62
63 pub fn check_file_sha256(&self, path: &PathBuf) -> Result<Option<String>, io::Error> {
66 let Some(sha256) = &self.sha256 else {
67 return Ok(None);
68 };
69 let file_hash = sha256_file(path)?;
70 Ok((&file_hash != sha256).then_some(file_hash))
71 }
72}
73
74impl Display for BinaryEntry {
75 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
76 writeln!(f, "source: {}", self.source)?;
77 writeln!(f, "sha256: {}", self.sha256.as_deref().unwrap_or("not set"))?;
78 writeln!(
79 f,
80 "size: {}",
81 self.size
82 .map(|s| format!("{s} bytes"))
83 .as_deref()
84 .unwrap_or("not set")
85 )?;
86 if let BinarySource::Path(path) = &self.source {
87 if let Ok(time) = path.metadata().and_then(|m| m.modified()) {
88 writeln!(f, "last modified: {}", DateTime::from(time).naive_local())?;
89 }
90 }
91 Ok(())
92 }
93}
94
95#[derive(Debug, Clone)]
96pub enum BinarySource {
97 Url(url::Url),
98 Path(PathBuf),
99}
100
101impl Display for BinarySource {
102 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
103 match self {
104 BinarySource::Url(url) => write!(f, "{}", url),
105 BinarySource::Path(path) => write!(f, "{}", path.display()),
106 }
107 }
108}
109
110impl FromStr for BinarySource {
111 type Err = url::ParseError;
112
113 fn from_str(s: &str) -> Result<Self, Self::Err> {
114 if s.starts_with("http://") || s.starts_with("https://") {
115 Ok(BinarySource::Url(url::Url::parse(s)?))
116 } else {
117 Ok(BinarySource::Path(PathBuf::from(s)))
118 }
119 }
120}
121
122impl Serialize for BinarySource {
123 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
124 where
125 S: serde::ser::Serializer,
126 {
127 match self {
128 BinarySource::Url(url) => url.to_string().serialize(serializer),
129 BinarySource::Path(path) => path.to_string_lossy().serialize(serializer),
130 }
131 }
132}
133
134impl<'de> Deserialize<'de> for BinarySource {
135 fn deserialize<D>(deserializer: D) -> Result<BinarySource, D::Error>
136 where
137 D: Deserializer<'de>,
138 {
139 String::deserialize(deserializer)?
140 .parse()
141 .map_err(serde::de::Error::custom)
142 }
143}
144
145impl DataFormat for BinaryEntry {
146 type Header = u8;
147 const LATEST_HEADER: Self::Header = 1;
148
149 fn write_data<W: std::io::Write>(
150 &self,
151 writer: &mut W,
152 ) -> Result<usize, crate::format::DataWriteError> {
153 Ok(self.source.to_string().write_data(writer)?
154 + self.sha256.write_data(writer)?
155 + self.size.write_data(writer)?)
156 }
157
158 fn read_data<R: std::io::Read>(
159 reader: &mut R,
160 header: &Self::Header,
161 ) -> Result<Self, crate::format::DataReadError> {
162 if *header != Self::LATEST_HEADER {
163 return Err(DataReadError::unsupported(
164 "BinaryEntry",
165 Self::LATEST_HEADER,
166 *header,
167 ));
168 }
169
170 Ok(BinaryEntry {
171 source: String::read_data(reader, &())?
172 .parse::<BinarySource>()
173 .map_err(|e| DataReadError::Custom(e.to_string()))?,
174 sha256: reader.read_data(&())?,
175 size: reader.read_data(&())?,
176 })
177 }
178}