use std::io::prelude::*;
use log::{debug, info};
use crate::manifest::{KyaniteManifest, KyaniteManifestItem};
use crate::stats::StatsContainer;
use crate::utility::KyaniteUtility;
#[derive(Clone, Debug)]
pub struct KyaniteItemMD5 {
pub url: String,
pub image: String,
}
#[derive(Clone, Debug)]
pub struct KyaniteItem {
pub url: String,
pub ext: String,
pub md5: KyaniteItemMD5,
pub data: Option<Vec<u8>>,
pub size: f64,
pub tags: Vec<String>,
pub coll: String,
}
impl KyaniteItem {
pub fn new(url: String, tags: Vec<String>, coll: String) -> Self {
let raw_pieces = url.split('.');
let mut pieces = Vec::<String>::new();
for rp in raw_pieces {
pieces.push(rp.to_owned());
}
let last_piece = &pieces[pieces.len() - 1];
let last_piece_pieces = last_piece.split('?');
let mut clean_last_piece_pieces = Vec::<String>::new();
for lpp in last_piece_pieces {
clean_last_piece_pieces.push(lpp.to_owned());
}
let clean_last_piece = clean_last_piece_pieces[0].clone();
Self {
url: url.to_owned(),
ext: clean_last_piece,
md5: KyaniteItemMD5 {
url: format!("{:x}", md5::compute(&url)),
image: "".to_owned(),
},
data: None,
size: 0.0,
tags,
coll,
}
}
pub fn name(&self) -> String {
format!("{}.{}", &self.md5.clone().url, &self.ext)
}
pub async fn download(&mut self) -> anyhow::Result<()> {
let resp = reqwest::get(&self.url).await?;
let bytes = resp.bytes().await?;
let data = bytes.to_vec();
let item_url_md5 = format!("{:x}", md5::compute(&self.url));
let item_data_md5 = format!("{:x}", md5::compute(&data));
self.md5 = KyaniteItemMD5 {
url: item_url_md5,
image: item_data_md5,
};
self.size = data.len() as f64;
self.data = Some(data);
Ok(())
}
pub fn describe(&self) -> String {
format!(
"{}.{} [{}]",
&self.md5.clone().url,
&self.ext,
KyaniteUtility::human_size(self.size, 2f64, "MiB"),
)
}
pub fn expunge(&mut self) {
self.data = None;
}
pub fn path(&self) -> anyhow::Result<String> {
let folder = format!(
"downloads/{}/{}",
&self.coll,
slug::slugify(&self.tags.join("-"))
);
if !std::path::Path::new(&folder).exists() {
std::fs::create_dir_all(&folder)?;
}
Ok(format!(
"{}/{}.{}",
folder,
&self.md5.clone().url,
&self.ext
))
}
pub fn indexed(&self, manifest: &KyaniteManifest) -> Option<String> {
let mut location = None;
for item in &manifest.files {
if item.name == self.name() {
location = Some(item.file.clone())
}
}
location
}
pub async fn store(&mut self, path: String) -> anyhow::Result<()> {
self.download().await?;
let mut file = std::fs::File::create(&path)?;
file.write_all(&self.data.clone().unwrap())?;
file.sync_all()?;
Ok(())
}
pub async fn save(
&mut self,
stats: &mut StatsContainer,
manifest: &mut KyaniteManifest,
) -> anyhow::Result<String> {
let response: &'static str;
let path = self.path()?;
match self.indexed(manifest) {
Some(idx) => {
let source = std::path::Path::new(&idx);
let destination = std::path::Path::new(&path);
if source.exists() && !destination.exists() {
std::fs::copy(source, destination)?;
response = stats.add_inherited();
} else {
response = stats.add_skipped();
}
}
None => {
if !std::path::Path::new(&path).exists() {
match &self.store(path).await {
Ok(_) => {
stats.add_size(self.size);
response = stats.add_ok();
let item = KyaniteManifestItem::new(
self.name(),
self.path()?,
self.tags.clone(),
);
manifest.add(item);
}
Err(_) => {
response = stats.add_failed();
}
}
self.expunge();
} else {
response = stats.add_skipped();
}
}
}
Ok(response.to_owned())
}
pub fn trim(items: Vec<Self>) -> Vec<Self> {
debug!("Trimming vector of items, starting count: {}", items.len());
let mut clean = Vec::<Self>::new();
for item in &items {
let mut exists = false;
for ci in &clean {
if item.url == ci.url {
exists = true;
break;
}
}
if !exists {
clean.push(item.to_owned());
}
}
debug!("Item trimming complete, final count: {}", items.len());
clean
}
pub fn skip(items: Vec<Self>) -> anyhow::Result<Vec<Self>> {
let mut new = Vec::<Self>::new();
let mut stats = StatsContainer::new();
for item in &items {
if std::path::Path::new(&item.path()?).exists() {
let _ = stats.add_skipped();
} else {
new.push(item.clone());
}
}
info!("Pre-Skipped: {}/{} items.", stats.skipped, items.len());
Ok(new)
}
pub fn sort(mut items: Vec<Self>) -> Vec<Self> {
items.sort_by_key(|item| item.name());
items
}
}