anni_provider_drive_token_storage/
common.rs1use anni_google_drive3 as google_drive3;
2
3use async_trait::async_trait;
4use std::borrow::Cow;
5use std::collections::HashSet;
6use std::num::NonZeroU8;
7use std::path::PathBuf;
8use std::pin::Pin;
9use thiserror::Error;
10use tokio::io::AsyncRead;
11use tokio_stream::Stream;
12
13pub type Result<T> = std::result::Result<T, ProviderError>;
14pub type ResourceReader = Pin<Box<dyn AsyncRead + Send>>;
15
16pub struct AudioInfo {
17 pub extension: String,
19 pub size: usize,
21 pub duration: u64,
23}
24
25pub struct AudioResourceReader {
27 pub info: AudioInfo,
29 pub range: Range,
31 pub reader: ResourceReader,
33}
34
35#[derive(Clone, Copy)]
36pub struct Range {
37 pub start: u64,
38 pub end: Option<u64>,
39 pub total: Option<u64>,
40}
41
42impl Range {
43 pub const FULL: Range = Range {
44 start: 0,
45 end: None,
46 total: None,
47 };
48
49 pub const FLAC_HEADER: Range = Range {
50 start: 0,
51 end: Some(42),
52 total: None,
53 };
54
55 pub fn new(start: u64, end: Option<u64>) -> Self {
57 Self {
58 start,
59 end,
60 total: None,
61 }
62 }
63
64 pub fn length(&self) -> Option<u64> {
67 self.end.map(|end| end - self.start + 1)
68 }
69
70 pub fn length_limit(&self, limit: u64) -> u64 {
72 let end = match self.end {
73 Some(end) => std::cmp::min(end, limit),
74 None => limit,
75 };
76 end - self.start + 1
77 }
78
79 pub fn end_with(&self, end: u64) -> Self {
81 Self {
82 start: self.start,
83 end: match self.end {
84 Some(e) => Some(e.min(end - 1)),
85 None => Some(end - 1),
86 },
87 total: match self.total {
88 Some(total) => Some(total.min(end)),
89 None => Some(end),
90 },
91 }
92 }
93
94 pub fn is_full(&self) -> bool {
95 self.start == 0 && self.end.is_none()
96 }
97
98 pub fn contains_flac_header(&self) -> bool {
99 if self.start == 0 {
100 match self.end {
101 Some(end) => end + 1 >= 42,
102 None => true,
103 }
104 } else {
105 false
106 }
107 }
108
109 pub fn to_range_header(&self) -> Option<String> {
110 if self.is_full() {
111 None
112 } else {
113 Some(match self.end {
114 Some(end) => format!("bytes={}-{}", self.start, end),
115 None => format!("bytes={}-", self.start),
116 })
117 }
118 }
119
120 pub fn to_content_range_header(&self) -> String {
121 if self.is_full() {
122 "bytes */*".to_string()
123 } else {
124 match (self.end, self.total) {
125 (Some(end), Some(total)) => format!("bytes {}-{}/{}", self.start, end, total),
126 (Some(end), None) => format!("bytes {}-{}/*", self.start, end),
127 _ => format!("bytes {}-", self.start),
128 }
129 }
130 }
131}
132
133#[async_trait]
136pub trait AnniProvider: Sync {
140 async fn albums(&self) -> Result<HashSet<Cow<str>>>;
142
143 async fn has_album(&self, album_id: &str) -> bool {
145 self.albums()
146 .await
147 .unwrap_or(HashSet::new())
148 .contains(album_id)
149 }
150
151 async fn get_audio_info(
153 &self,
154 album_id: &str,
155 disc_id: NonZeroU8,
156 track_id: NonZeroU8,
157 ) -> Result<AudioInfo> {
158 Ok(self
159 .get_audio(album_id, disc_id, track_id, Range::FLAC_HEADER)
160 .await?
161 .info)
162 }
163
164 async fn get_audio(
166 &self,
167 album_id: &str,
168 disc_id: NonZeroU8,
169 track_id: NonZeroU8,
170 range: Range,
171 ) -> Result<AudioResourceReader>;
172
173 async fn get_cover(&self, album_id: &str, disc_id: Option<NonZeroU8>)
175 -> Result<ResourceReader>;
176
177 async fn reload(&mut self) -> Result<()>;
179}
180
181#[derive(Clone)]
182pub struct FileEntry {
183 pub name: String,
184 pub path: PathBuf,
185}
186
187#[async_trait]
188pub trait FileSystemProvider: Sync {
189 async fn children(
191 &self,
192 path: &PathBuf,
193 ) -> Result<Pin<Box<dyn Stream<Item = FileEntry> + Send>>>;
194
195 async fn get_file_entry_by_prefix(&self, parent: &PathBuf, prefix: &str) -> Result<FileEntry>;
197
198 async fn get_file(&self, path: &PathBuf, range: Range) -> Result<ResourceReader>;
200
201 async fn get_audio_info(&self, path: &PathBuf) -> Result<(String, usize)>;
203
204 async fn get_audio_file(&self, path: &PathBuf, range: Range) -> Result<AudioResourceReader> {
206 let reader = self.get_file(path, range).await?;
207 let metadata = self.get_audio_info(path).await?;
208 let (duration, reader) = crate::utils::read_duration(reader, range).await?;
209 Ok(AudioResourceReader {
210 info: AudioInfo {
211 extension: metadata.0,
212 size: metadata.1,
213 duration,
214 },
215 range: Range {
216 start: range.start,
217 end: Some(range.end.unwrap_or(metadata.1 as u64 - 1)),
218 total: Some(metadata.1 as u64),
219 },
220 reader,
221 })
222 }
223
224 async fn reload(&mut self) -> Result<()>;
226}
227
228#[derive(Debug, Error)]
229pub enum ProviderError {
230 #[error("invalid path")]
231 InvalidPath,
232
233 #[error("file not found")]
234 FileNotFound,
235
236 #[error(transparent)]
237 IOError(#[from] std::io::Error),
238
239 #[error(transparent)]
240 RepoError(#[from] anni_repo::error::Error),
241
242 #[error(transparent)]
243 OAuthError(#[from] google_drive3::oauth2::Error),
244
245 #[error(transparent)]
246 DriveError(#[from] google_drive3::Error),
247
248 #[error(transparent)]
249 RequestError(#[from] reqwest::Error),
250
251 #[error(transparent)]
252 FlacError(#[from] anni_flac::error::FlacError),
253
254 #[error("an error occurred")]
255 GeneralError,
256}
257
258pub fn strict_album_path(root: &PathBuf, album_id: &str, layer: usize) -> PathBuf {
259 let mut res = root.clone();
260 for i in 0..layer {
261 res.push(match &album_id[i * 2..=i * 2 + 1].trim_start_matches('0') {
262 &"" => "0",
263 s @ _ => s,
264 });
265 }
266 res.join(album_id)
267}