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}
138
139#[derive(Debug, Clone)]
140pub struct NewTrack {
141 pub backing_path: String,
142 pub format: Format,
143 pub audio_offset: u64,
144 pub audio_length: u64,
145 pub backing_size: u64,
146 pub backing_mtime_ns: i64,
147 pub backing_ctime_ns: i64,
148}
149
150#[derive(Debug, Clone)]
151pub struct NewArt {
152 pub mime: String,
153 pub width: Option<u32>,
154 pub height: Option<u32>,
155 pub data: Vec<u8>,
156}
157
158#[cfg_attr(feature = "mutants", derive(Default))]
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct Tag {
161 pub key: String,
162 pub value: String,
163 pub ordinal: u64,
164}
165
166impl Tag {
167 pub fn new(key: &str, value: &str, ordinal: u64) -> Tag {
168 Tag {
169 key: key.to_string(),
170 value: value.to_string(),
171 ordinal,
172 }
173 }
174}
175
176#[cfg_attr(feature = "mutants", derive(Default))]
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct Art {
179 pub id: i64,
180 pub sha256: String,
181 pub mime: String,
182 pub width: Option<u32>,
183 pub height: Option<u32>,
184 pub byte_len: u64,
185 pub data: Vec<u8>,
186}
187
188#[cfg_attr(feature = "mutants", derive(Default))]
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct ArtMeta {
191 pub mime: String,
192 pub width: Option<u32>,
193 pub height: Option<u32>,
194 pub byte_len: u64,
195}
196
197#[cfg_attr(feature = "mutants", derive(Default))]
198#[derive(Debug, Clone, PartialEq, Eq)]
199pub struct TrackArt {
200 pub art_id: i64,
201 pub picture_type: u32,
202 pub description: String,
203 pub ordinal: u64,
204}
205
206#[cfg_attr(feature = "mutants", derive(Default))]
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct BinaryTag {
212 pub key: String,
213 pub payload: Vec<u8>,
214 pub ordinal: u64,
215}
216
217#[cfg_attr(feature = "mutants", derive(Default))]
220#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct BinaryTagRow {
222 pub rowid: i64,
223 pub key: String,
224 pub byte_len: u64,
225}
226
227#[cfg_attr(feature = "mutants", derive(Default))]
230#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct StructuralBlock {
232 pub kind: String,
233 pub ordinal: u64,
234 pub body: Vec<u8>,
235}
236
237#[cfg(test)]
238mod track_bounds_tests {
239 use super::TrackBounds;
240
241 #[test]
242 fn accepts_in_range() {
243 let b = TrackBounds::new(10, 20, 100).unwrap();
244 assert_eq!(b.audio_offset(), 10);
245 assert_eq!(b.audio_length(), 20);
246 }
247
248 #[test]
249 fn accepts_exact_fit() {
250 let b = TrackBounds::new(30, 70, 100).unwrap();
251 assert_eq!(b.audio_offset(), 30);
252 assert_eq!(b.audio_length(), 70);
253 }
254
255 #[test]
256 fn accepts_zero_length() {
257 let b = TrackBounds::new(0, 0, 0).unwrap();
259 assert_eq!(b.audio_length(), 0);
260 }
261
262 #[test]
263 fn rejects_exceeding_backing_size() {
264 assert!(TrackBounds::new(50, 60, 100).is_err());
265 }
266
267 #[test]
268 fn rejects_offset_plus_length_overflow() {
269 assert!(TrackBounds::new(u64::MAX, 1, u64::MAX).is_err());
270 }
271}