use std::collections::HashMap; use anyhow::{Result, anyhow}; use serde::{Deserialize, Serialize}; use tauri::{AppHandle, Emitter}; use tokio::fs; use tokio::task::JoinSet; use crate::model::rainy; use crate::pkg::{Package, PkgKey}; use crate::util; use crate::download_handler::DownloadHandler; pub struct PackageStore { store: HashMap, has_fetched: bool, app: AppHandle, dlh: DownloadHandler } #[derive(Clone, Serialize, Deserialize)] pub struct Payload { pub pkg: PkgKey } #[derive(Clone, Serialize, Deserialize)] pub enum InstallResult { Ready, Deferred } impl PackageStore { pub fn new(app: AppHandle) -> PackageStore { PackageStore { store: HashMap::new(), has_fetched: false, app: app.clone(), dlh: DownloadHandler::new(app) } } pub fn get(&self, key: PkgKey) -> Result<&Package> { self.store.get(&key) .ok_or_else(|| anyhow!("Invalid package key")) } pub fn get_all(&self) -> HashMap { self.store.clone() } pub async fn reload_package(&mut self, key: PkgKey) { let dir = util::pkg_dir().join(&key.0); if let Ok(pkg) = Package::from_dir(dir).await { self.update_package(key, pkg); } else { log::error!("couldn't reload {}", key); } } pub async fn reload_all(&mut self) -> Result<()> { let dirents = std::fs::read_dir(util::pkg_dir())?; let mut futures = JoinSet::new(); for dir in dirents { if let Ok(dir) = dir { let path = dir.path(); futures.spawn(Package::from_dir(path)); } } while let Some(res) = futures.join_next().await { if let Ok(Ok(pkg)) = res { self.update_package(pkg.key(), pkg); } } Ok(()) } pub async fn fetch_listings(&mut self) -> Result<()> { if self.has_fetched { return Ok(()); } use async_compression::futures::bufread::GzipDecoder; use futures::{ io::{self, BufReader, ErrorKind}, prelude::*, }; let response = reqwest::get("https://rainy.patafour.zip/c/ongeki/api/v1/package/").await?; let reader = response .bytes_stream() .map_err(|e| io::Error::new(ErrorKind::Other, e)) .into_async_read(); let mut decoder = GzipDecoder::new(BufReader::new(reader)); let mut data = String::new(); decoder.read_to_string(&mut data).await?; let listings: Vec = serde_json::from_str(&data) .expect("Invalid JSON"); for listing in listings { // This is None if the package has no versions for whatever reason if let Some(r) = Package::from_rainy(listing) { //log::warn!("D {}", &r.rmt.as_ref().unwrap().dependencies.first().unwrap_or(&"Nothing".to_owned())); match self.store.get_mut(&r.key()) { Some(l) => { l.rmt = r.rmt; } None => { self.store.insert(r.key(), r); } } } } self.has_fetched = true; Ok(()) } pub async fn install_package(&mut self, key: &PkgKey, force: bool) -> Result { log::debug!("Installing {}", key); let pkg = self.store.get(key) .ok_or_else(|| anyhow!("Attempted to install a nonexistent pkg"))? .clone(); if pkg.loc.is_some() && !force { return Ok(InstallResult::Ready); } let rmt = pkg.rmt.as_ref() //clone() .ok_or_else(|| anyhow!("Attempted to install a pkg without remote data"))?; for dep in &rmt.dependencies { self.app.emit("install-start", Payload { pkg: dep.to_owned() })?; Box::pin(self.install_package(&dep, false)).await?; } let zip_path = util::cache_dir().join(format!( "{}-{}-{}.zip", pkg.namespace, pkg.name, rmt.version )); if !zip_path.exists() { self.dlh.download_zip(&zip_path, &pkg)?; return Ok(InstallResult::Deferred); } let cache_file_r = std::fs::File::open(&zip_path)?; let mut archive = zip::ZipArchive::new(cache_file_r)?; self.delete_package(key, false).await?; let path = pkg.path(); fs::create_dir(&path).await?; archive.extract(path)?; self.reload_package(key.to_owned()).await; self.app.emit("install-end", Payload { pkg: key.to_owned() })?; log::info!("Installed {}", key); Ok(InstallResult::Ready) } pub async fn delete_package(&mut self, key: &PkgKey, force: bool) -> Result<()> { let pkg = self.store.get_mut(key) .ok_or_else(|| anyhow!("Attempted to delete a nonexistent pkg"))?; let path = pkg.path(); if path.exists() && path.join("manifest.json").exists() { // TODO don't rm -r - use a file whitelist log::debug!("rm -r'ing {}", path.to_string_lossy()); pkg.loc = None; let rv = tokio::fs::remove_dir_all(&path).await .map_err(|e| anyhow!("Could not delete a package: {}", e)); if rv.is_ok() { self.app.emit("install-end", Payload { pkg: key.to_owned() })?; log::info!("Deleted {}", key); } rv } else { if force { Err(anyhow!("Nothing to delete")) } else { Ok(()) } } } fn update_package(&mut self, key: PkgKey, mut new: Package) { if let Some(old) = self.store.get(&key) { new.rmt = old.rmt.clone(); } self.store.insert(key, new); } }