use std::collections::{HashMap, HashSet}; use anyhow::{Result, anyhow}; use serde::{Deserialize, Serialize}; use tauri::{AppHandle, Emitter}; use tokio::fs; use tokio::task::JoinSet; use crate::model::local::{PackageList, PackageListEntry}; use crate::model::misc::Game; use crate::model::rainy; use crate::pkg::{Feature, Package, PackageSource, PkgKey, Remote, Status}; use crate::util; use crate::download_handler::DownloadHandler; pub struct PackageStore { store: HashMap, meta_list: PackageList, dlh: DownloadHandler, app: AppHandle, offline: bool, } #[derive(Clone, Serialize, Deserialize, Debug)] pub struct Payload { pub pkg: PkgKey, pub enable: bool, } #[derive(Clone, Copy, Serialize, Deserialize, Debug)] pub enum InstallResult { Ready, Deferred } impl PackageStore { pub fn new(app: AppHandle) -> PackageStore { let meta_list = std::fs::read_to_string(util::config_dir().join("package-list.json")) .map_err(|e| anyhow!(e)) .and_then(|s| serde_json::from_str::(&s).map_err(|e| anyhow!(e))) .unwrap_or_else(|e| { log::warn!("unable to read package-list: {e}"); PackageList::new() }); PackageStore { store: HashMap::new(), meta_list, app: app.clone(), dlh: DownloadHandler::new(app), offline: true } } 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 fn get_game_list(&self, game: Game) -> Vec { self.meta_list.iter() .filter(|(_, v)| v.games.contains(&game)) .map(|(k, _)| k.clone()) .collect() } #[allow(dead_code)] pub fn get_by_feature(&self, feature: Feature) -> Vec { self.store.iter() .filter(|(_, v)| { if let Some(loc) = &v.loc { if let Status::OK(flags, _) = loc.status { return flags.contains(feature); } } return false }) .map(|(k, _)| k.clone()) .collect() } 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, PackageSource::Rainy).await { self.update_nonremote(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, PackageSource::Rainy)); } } while let Some(res) = futures.join_next().await { if let Ok(Ok((pkg, locally_declared_games))) = res { if let Some(games) = locally_declared_games { self.meta_list.insert(pkg.key(), PackageListEntry { version: pkg.loc.as_ref().unwrap().version.clone(), games }); } self.update_nonremote(pkg.key(), pkg); } } Ok(()) } pub async fn save(&self) -> Result<()> { tokio::fs::write( util::config_dir().join("package-list.json"), serde_json::to_string_pretty(&self.meta_list)? ).await?; Ok(()) } pub async fn fetch_listings(game: Game) -> Result> { use async_compression::futures::bufread::GzipDecoder; use futures::{ io::{self, BufReader, ErrorKind}, prelude::*, }; let response = reqwest::get(format!("https://www.rainycolor.org/c/{game}/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?; Ok(serde_json::from_str(&data)?) } pub fn is_offline(&self) -> bool { self.offline } pub fn process_fetched_listings(&mut self, listings: Vec, game: Game) { for listing in listings { // This is None if the package has no versions for whatever reason if let Some(r) = Package::from_rainy(listing) { let mut meta_entry = self.meta_list.remove(&r.key()).unwrap_or_else(|| { PackageListEntry { // from_rainy() is guaranteed to include rmt version: r.rmt.as_ref().unwrap().version.clone(), games: vec![ game ], } }); if !meta_entry.games.contains(&game) { meta_entry.games.push(game); } self.meta_list.insert(r.key(), meta_entry); match self.store.get_mut(&r.key()) { Some(l) => { l.rmt = r.rmt; } None => { self.store.insert(r.key(), r); } } } } self.offline = false; } pub async fn install_package( &mut self, key: &PkgKey, force: bool, install_deps: bool, enable: bool ) -> Result { log::info!("installation request: {}/{}/{}", key, force, install_deps); let pkg = self.store.get(key) .ok_or_else(|| anyhow!("Attempted to install a nonexistent pkg"))? .clone(); if pkg.loc.is_some() && !force { log::debug!("installation skipped"); return Ok(InstallResult::Ready); } self.app.emit("install-start", Payload { pkg: key.to_owned(), enable })?; let rmt = pkg.rmt.as_ref() .ok_or_else(|| anyhow!("Attempted to install a pkg without remote data"))?; if install_deps { let mut set = HashSet::new(); self.resolve_deps(rmt.clone(), &mut set)?; for dep in set { Box::pin(self.install_package(&dep, false, false, enable)).await?; } } let zip_path = util::cache_dir().join(format!( "{}-{}-{}.zip", pkg.namespace, pkg.name, rmt.version )); let part_path = zip_path.join(".part"); if !zip_path.exists() && !part_path.exists() { self.dlh.download_zip(&zip_path, &pkg, enable)?; log::debug!("deferring {}", key); 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-prelude", Payload { pkg: key.to_owned(), enable })?; log::info!("installed {}", key); Ok(InstallResult::Ready) } pub async fn delete_package(&mut self, key: &PkgKey, force: bool) -> Result<()> { log::debug!("will delete {} {}", key, force); 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() { pkg.loc = None; let rv = util::remove_dir_all(&path).await; if rv.is_ok() { self.app.emit("install-end-prelude", Payload { pkg: key.to_owned(), enable: false })?; log::info!("deleted {}", key); } rv } else { if force { Err(anyhow!("Nothing to delete")) } else { Ok(()) } } } fn update_nonremote(&mut self, key: PkgKey, mut new: Package) { if let Some(old) = self.store.remove(&key) { new.rmt = old.rmt; } self.store.insert(key, new); } fn resolve_deps(&self, rmt: Remote, set: &mut HashSet) -> Result<()> { for d in rmt.dependencies { set.insert(d.clone()); let subrmt = self.store.get(&d) .ok_or_else(|| anyhow!("Attempted to delete a nonexistent pkg"))? .rmt .clone() .ok_or_else(|| anyhow!("Attempted to resolve deps without fetching"))?; self.resolve_deps(subrmt, set)?; } Ok(()) } }