anni_provider_drive_token_storage/
common.rs

1use 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    /// File extension of the file
18    pub extension: String,
19    /// File size of the file
20    pub size: usize,
21    /// Audio duration of the file
22    pub duration: u64,
23}
24
25/// AudioResourceReader abstracts the file result a provider returns with extra information of audio
26pub struct AudioResourceReader {
27    /// Audio info
28    pub info: AudioInfo,
29    /// File range
30    pub range: Range,
31    /// Async Reader for the file
32    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    /// create a new range with given start and end offset
56    pub fn new(start: u64, end: Option<u64>) -> Self {
57        Self {
58            start,
59            end,
60            total: None,
61        }
62    }
63
64    /// get the length of the range
65    /// if the range is full, returns None
66    pub fn length(&self) -> Option<u64> {
67        self.end.map(|end| end - self.start + 1)
68    }
69
70    /// return length limited by a limit(usually actual file size)
71    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    /// return a new Range with updated end property
80    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/// AnniProvider is a common trait for anni resource providers.
134/// It provides functions to get cover, audio, album list and reload.
135#[async_trait]
136// work around to add a default implementation for has_albums()
137// https://github.com/rust-lang/rust/issues/51443
138// https://docs.rs/async-trait/latest/async_trait/index.html#dyn-traits
139pub trait AnniProvider: Sync {
140    /// Get album information provided by provider.
141    async fn albums(&self) -> Result<HashSet<Cow<str>>>;
142
143    /// Returns whether given album exists
144    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    /// Get audio info describing basic information of the audio file.
152    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    /// Returns a reader implements AsyncRead for content reading
165    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    /// Returns a cover of corresponding album
174    async fn get_cover(&self, album_id: &str, disc_id: Option<NonZeroU8>)
175        -> Result<ResourceReader>;
176
177    /// Reloads the provider for new albums
178    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    /// List sub folders
190    async fn children(
191        &self,
192        path: &PathBuf,
193    ) -> Result<Pin<Box<dyn Stream<Item = FileEntry> + Send>>>;
194
195    /// Get file entry in a folder with given prefix
196    async fn get_file_entry_by_prefix(&self, parent: &PathBuf, prefix: &str) -> Result<FileEntry>;
197
198    /// Get file reader
199    async fn get_file(&self, path: &PathBuf, range: Range) -> Result<ResourceReader>;
200
201    /// Get audio info: (extension ,size)
202    async fn get_audio_info(&self, path: &PathBuf) -> Result<(String, usize)>;
203
204    // TODO: move this method to a sub trait
205    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    /// Reload
225    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}