1use strum::{EnumIter, EnumString, IntoStaticStr};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, IntoStaticStr, EnumIter)]
8#[strum(serialize_all = "lowercase")]
9#[cfg_attr(feature = "mutants", derive(Default))]
10pub enum Format {
11 #[cfg_attr(feature = "mutants", default)]
12 Flac,
13 Mp3,
14 M4a,
15 Opus,
16 Vorbis,
17 OggFlac,
18 Wav,
19}
20
21impl Format {
22 pub fn as_str(self) -> &'static str {
23 self.into()
24 }
25}
26
27#[cfg(test)]
28mod tests {
29 use super::Format;
30 use strum::IntoEnumIterator;
31
32 #[test]
33 fn every_format_round_trips() {
34 for f in Format::iter() {
35 assert_eq!(f.as_str().parse::<Format>(), Ok(f));
36 }
37 }
38
39 #[test]
42 fn db_strings_are_pinned() {
43 let expected = [
44 (Format::Flac, "flac"),
45 (Format::Mp3, "mp3"),
46 (Format::M4a, "m4a"),
47 (Format::Opus, "opus"),
48 (Format::Vorbis, "vorbis"),
49 (Format::OggFlac, "oggflac"),
50 (Format::Wav, "wav"),
51 ];
52 assert_eq!(expected.len(), Format::iter().count());
53 for (f, s) in expected {
54 assert_eq!(f.as_str(), s);
55 }
56 }
57}
58
59#[cfg(test)]
60mod binary_tag_models_tests {
61 #[test]
62 fn binary_tag_constructs() {
63 let bt = super::BinaryTag {
64 key: "PRIV".to_string(),
65 payload: vec![1, 2, 3],
66 ordinal: 0,
67 };
68 assert_eq!(bt.payload.len(), 3);
69 let row = super::BinaryTagRow {
70 rowid: 7,
71 key: "PRIV".to_string(),
72 byte_len: 3,
73 };
74 assert_eq!(row.rowid, 7);
75 let sb = super::StructuralBlock {
76 kind: "STREAMINFO".to_string(),
77 ordinal: 0,
78 body: vec![0u8; 34],
79 };
80 assert_eq!(sb.body.len(), 34);
81 }
82}
83
84#[cfg_attr(feature = "mutants", derive(Default))]
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub struct TrackBounds {
90 audio_offset: u64,
91 audio_length: u64,
92}
93
94impl TrackBounds {
95 pub fn new(
97 audio_offset: u64,
98 audio_length: u64,
99 backing_size: u64,
100 ) -> Result<TrackBounds, crate::DbError> {
101 let end = audio_offset
102 .checked_add(audio_length)
103 .filter(|&end| end <= backing_size)
104 .ok_or(crate::DbError::AudioBoundsOutOfRange {
105 audio_offset,
106 audio_length,
107 backing_size,
108 })?;
109 let _ = end;
110 Ok(TrackBounds {
111 audio_offset,
112 audio_length,
113 })
114 }
115
116 pub fn audio_offset(&self) -> u64 {
117 self.audio_offset
118 }
119
120 pub fn audio_length(&self) -> u64 {
121 self.audio_length
122 }
123}
124
125#[cfg_attr(feature = "mutants", derive(Default))]
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct Track {
128 pub id: i64,
129 pub backing_path: String,
130 pub format: Format,
131 pub bounds: TrackBounds,
132 pub backing_size: u64,
133 pub backing_mtime_ns: i64,
134 pub backing_ctime_ns: i64,
135 pub content_version: i64,
136 pub updated_at: i64,
137 pub fingerprint: Option<String>,
138 pub content_hash: Option<String>,
139}
140
141#[derive(Debug, Clone)]
142pub struct NewTrack {
143 pub backing_path: String,
144 pub format: Format,
145 pub audio_offset: u64,
146 pub audio_length: u64,
147 pub backing_size: u64,
148 pub backing_mtime_ns: i64,
149 pub backing_ctime_ns: i64,
150}
151
152#[derive(Debug, Clone)]
153pub struct NewArt {
154 pub mime: String,
155 pub width: Option<u32>,
156 pub height: Option<u32>,
157 pub data: Vec<u8>,
158}
159
160#[cfg_attr(feature = "mutants", derive(Default))]
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub struct Tag {
163 pub key: String,
164 pub value: String,
165 pub ordinal: u64,
166}
167
168impl Tag {
169 pub fn new(key: &str, value: &str, ordinal: u64) -> Tag {
170 Tag {
171 key: key.to_string(),
172 value: value.to_string(),
173 ordinal,
174 }
175 }
176}
177
178#[cfg_attr(feature = "mutants", derive(Default))]
179#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct Art {
181 pub id: i64,
182 pub sha256: String,
183 pub mime: String,
184 pub width: Option<u32>,
185 pub height: Option<u32>,
186 pub byte_len: u64,
187 pub data: Vec<u8>,
188}
189
190#[cfg_attr(feature = "mutants", derive(Default))]
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct ArtMeta {
193 pub mime: String,
194 pub width: Option<u32>,
195 pub height: Option<u32>,
196 pub byte_len: u64,
197}
198
199#[cfg_attr(feature = "mutants", derive(Default))]
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct TrackArt {
202 pub art_id: i64,
203 pub picture_type: u32,
204 pub description: String,
205 pub ordinal: u64,
206}
207
208#[cfg_attr(feature = "mutants", derive(Default))]
212#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct BinaryTag {
214 pub key: String,
215 pub payload: Vec<u8>,
216 pub ordinal: u64,
217}
218
219#[cfg_attr(feature = "mutants", derive(Default))]
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub struct BinaryTagRow {
224 pub rowid: i64,
225 pub key: String,
226 pub byte_len: u64,
227}
228
229#[cfg_attr(feature = "mutants", derive(Default))]
232#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct StructuralBlock {
234 pub kind: String,
235 pub ordinal: u64,
236 pub body: Vec<u8>,
237}
238
239#[cfg(test)]
240mod track_bounds_tests {
241 use super::TrackBounds;
242
243 #[test]
244 fn accepts_in_range() {
245 let b = TrackBounds::new(10, 20, 100).unwrap();
246 assert_eq!(b.audio_offset(), 10);
247 assert_eq!(b.audio_length(), 20);
248 }
249
250 #[test]
251 fn accepts_exact_fit() {
252 let b = TrackBounds::new(30, 70, 100).unwrap();
253 assert_eq!(b.audio_offset(), 30);
254 assert_eq!(b.audio_length(), 70);
255 }
256
257 #[test]
258 fn accepts_zero_length() {
259 let b = TrackBounds::new(0, 0, 0).unwrap();
261 assert_eq!(b.audio_length(), 0);
262 }
263
264 #[test]
265 fn rejects_exceeding_backing_size() {
266 assert!(TrackBounds::new(50, 60, 100).is_err());
267 }
268
269 #[test]
270 fn rejects_offset_plus_length_overflow() {
271 assert!(TrackBounds::new(u64::MAX, 1, u64::MAX).is_err());
272 }
273}