use std::collections::HashMap;
use std::io::{Cursor, Read};
use std::sync::Arc;
use reqwest::{Client, Error as reqwest_Error};
use url::Url;
use zip::ZipArchive;
use zip::result::ZipError;
use crate::asset::{AssetError, AssetFileBox, AssetFileTrait, AssetManagerRc, AssetManagerTrait, AssetResult};
use crate::net::Request;
pub struct SongZipRequest {
url: Url,
}
impl SongZipRequest {
pub fn new(url: Url) -> Self {
Self {
url,
}
}
}
impl Request for SongZipRequest {
type Response = AssetManagerRc;
type Error = SongZipError;
async fn exec(self, client: Client) -> Result<Self::Response, Self::Error> {
let buf = client.get(self.url)
.send()
.await?
.error_for_status()?
.bytes()
.await?;
let mut zip = ZipArchive::new(Cursor::new(buf))?;
let names: Box<[_]> = zip.file_names().map(|name| name.to_string()).collect();
let mut content = HashMap::new();
for name in names {
let mut file = zip.by_name(&name)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf).map_err(ZipError::Io)?;
content.insert(format!("/{}", name.to_lowercase()), Arc::from(buf)); }
Ok(Arc::new(AssetManager::new(content)))
}
}
type Content = HashMap<String, BufRc>;
type BufRc = Arc<[u8]>;
struct AssetManager {
content: Content,
}
impl AssetManager {
fn new(content: Content) -> Self {
Self {
content,
}
}
}
impl AssetManagerTrait for AssetManager {
fn open(&self, name: &str) -> AssetResult<AssetFileBox> {
let buf = Arc::clone(self.content.get(&name.to_lowercase()).ok_or(AssetError::NotFound)?);
Ok(Box::new(AssetFile::new(buf)))
}
}
struct AssetFile {
buf: BufRc,
}
impl AssetFile {
fn new(buf: BufRc) -> Self {
Self {
buf,
}
}
}
impl AssetFileTrait for AssetFile {
fn read(&self) -> AssetResult<Box<dyn Read + Send + Sync>> {
Ok(Box::new(Cursor::new(Arc::clone(&self.buf))))
}
fn read_str(&self) -> AssetResult<String> {
Ok(String::from(str::from_utf8(&self.buf).map_err(|_| AssetError::Decode)?))
}
}
#[allow(unused)]
#[derive(Debug)]
pub enum SongZipError {
Fetch(reqwest_Error),
Decode(ZipError),
}
impl From<reqwest_Error> for SongZipError {
fn from(value: reqwest_Error) -> Self {
SongZipError::Fetch(value)
}
}
impl From<ZipError> for SongZipError {
fn from(value: ZipError) -> Self {
SongZipError::Decode(value)
}
}