Skip to content

Commit

Permalink
Move mod store into own module
Browse files Browse the repository at this point in the history
  • Loading branch information
jieyouxu authored and trumank committed Feb 26, 2024
1 parent ec40cb1 commit af51a41
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 268 deletions.
273 changes: 5 additions & 268 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,249 +3,22 @@ pub mod http;
pub mod modio;
#[macro_use]
pub mod cache;
pub mod mod_store;

use crate::error::IntegrationError;
use crate::state::config::ConfigWrapper;

use anyhow::{Context, Result};
use fs_err as fs;
use serde::{Deserialize, Serialize};
use anyhow::Result;
use tokio::sync::mpsc::Sender;
use tracing::info;

use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::io::{Read, Seek};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::sync::{Arc, RwLock};

pub use cache::*;
pub use mint_lib::mod_info::*;
pub use mod_store::*;

type Providers = RwLock<HashMap<&'static str, Arc<dyn ModProvider>>>;

pub struct ModStore {
providers: Providers,
cache: ProviderCache,
blob_cache: BlobCache,
}

impl ModStore {
pub fn new<P: AsRef<Path>>(
cache_path: P,
parameters: &HashMap<String, HashMap<String, String>>,
) -> Result<Self> {
let providers = inventory::iter::<ProviderFactory>()
.flat_map(|f| {
let params = parameters.get(f.id).cloned().unwrap_or_default();
f.parameters
.iter()
.all(|p| params.contains_key(p.id))
.then(|| ((f.new)(&params).map(|p| (f.id, p))))
})
.collect::<Result<HashMap<_, _>>>()?;

let cache_metadata_path = cache_path.as_ref().join("cache.json");

let cache = read_cache_metadata_or_default(&cache_metadata_path)?;
let cache = ConfigWrapper::new(&cache_metadata_path, cache);
cache.save().unwrap();

Ok(Self {
providers: RwLock::new(providers),
cache: Arc::new(RwLock::new(cache)),
blob_cache: BlobCache::new(cache_path.as_ref().join("blobs")),
})
}

pub fn get_provider_factories() -> impl Iterator<Item = &'static ProviderFactory> {
inventory::iter::<ProviderFactory>()
}

pub fn add_provider(
&self,
provider_factory: &ProviderFactory,
parameters: &HashMap<String, String>,
) -> Result<()> {
let provider = (provider_factory.new)(parameters)?;
self.providers
.write()
.unwrap()
.insert(provider_factory.id, provider);
Ok(())
}

pub async fn add_provider_checked(
&self,
provider_factory: &ProviderFactory,
parameters: &HashMap<String, String>,
) -> Result<()> {
let provider = (provider_factory.new)(parameters)?;
provider.check().await?;
self.providers
.write()
.unwrap()
.insert(provider_factory.id, provider);
Ok(())
}

pub fn get_provider(&self, url: &str) -> Result<Arc<dyn ModProvider>> {
let factory = inventory::iter::<ProviderFactory>()
.find(|f| (f.can_provide)(url))
.with_context(|| format!("Could not find mod provider for {:?}", url))?;
let lock = self.providers.read().unwrap();
Ok(match lock.get(factory.id) {
Some(e) => e.clone(),
None => {
return Err(IntegrationError::NoProvider {
url: url.to_string(),
factory,
}
.into())
}
})
}

pub async fn resolve_mods(
&self,
mods: &[ModSpecification],
update: bool,
) -> Result<HashMap<ModSpecification, ModInfo>> {
use futures::stream::{self, StreamExt, TryStreamExt};

let mut to_resolve = mods.iter().cloned().collect::<HashSet<ModSpecification>>();
let mut mods_map = HashMap::new();

// used to deduplicate dependencies from mods already present in the mod list
let mut precise_mod_specs = HashSet::new();

while !to_resolve.is_empty() {
for (u, m) in stream::iter(
to_resolve
.iter()
.map(|u| self.resolve_mod(u.to_owned(), update)),
)
.boxed()
.buffer_unordered(5)
.try_collect::<Vec<_>>()
.await?
{
precise_mod_specs.insert(m.spec.clone());
mods_map.insert(u, m);
to_resolve.clear();
for m in mods_map.values() {
for d in &m.suggested_dependencies {
if !precise_mod_specs.contains(d) {
to_resolve.insert(d.clone());
}
}
}
}
}

Ok(mods_map)
}

pub async fn resolve_mod(
&self,
original_spec: ModSpecification,
update: bool,
) -> Result<(ModSpecification, ModInfo)> {
let mut spec = original_spec.clone();
loop {
match self
.get_provider(&spec.url)?
.resolve_mod(&spec, update, self.cache.clone())
.await?
{
ModResponse::Resolve(m) => {
return Ok((original_spec, m));
}
ModResponse::Redirect(redirected_spec) => spec = redirected_spec,
};
}
}

pub async fn fetch_mods(
&self,
mods: &[ModResolution],
update: bool,
tx: Option<Sender<FetchProgress>>,
) -> Result<Vec<PathBuf>> {
use futures::stream::{self, StreamExt, TryStreamExt};

stream::iter(
mods.iter()
.map(|res| self.fetch_mod(res, update, tx.clone())),
)
.boxed() // without this the future becomes !Send https://github.com/rust-lang/rust/issues/104382
.buffer_unordered(5)
.try_collect::<Vec<_>>()
.await
}

pub async fn fetch_mods_ordered(
&self,
mods: &[&ModResolution],
update: bool,
tx: Option<Sender<FetchProgress>>,
) -> Result<Vec<PathBuf>> {
use futures::stream::{self, StreamExt, TryStreamExt};

stream::iter(
mods.iter()
.map(|res| self.fetch_mod(res, update, tx.clone())),
)
.boxed() // without this the future becomes !Send https://github.com/rust-lang/rust/issues/104382
.buffered(5)
.try_collect::<Vec<_>>()
.await
}

pub async fn fetch_mod(
&self,
res: &ModResolution,
update: bool,
tx: Option<Sender<FetchProgress>>,
) -> Result<PathBuf> {
self.get_provider(&res.url.0)?
.fetch_mod(
res,
update,
self.cache.clone(),
&self.blob_cache.clone(),
tx,
)
.await
}

pub async fn update_cache(&self) -> Result<()> {
let providers = self.providers.read().unwrap().clone();
for (name, provider) in providers.iter() {
info!("updating cache for {name} provider");
provider.update_cache(self.cache.clone()).await?;
}
Ok(())
}

pub fn get_mod_info(&self, spec: &ModSpecification) -> Option<ModInfo> {
self.get_provider(&spec.url)
.ok()?
.get_mod_info(spec, self.cache.clone())
}

pub fn is_pinned(&self, spec: &ModSpecification) -> bool {
self.get_provider(&spec.url)
.unwrap()
.is_pinned(spec, self.cache.clone())
}

pub fn get_version_name(&self, spec: &ModSpecification) -> Option<String> {
self.get_provider(&spec.url)
.unwrap()
.get_version_name(spec, self.cache.clone())
}
}

pub trait ReadSeek: Read + Seek + Send {}
impl<T: Seek + Read + Send> ReadSeek for T {}

Expand Down Expand Up @@ -320,40 +93,4 @@ pub struct ProviderParameter<'a> {
pub link: Option<&'a str>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BlobRef(String);

#[derive(Debug, Clone)]
pub struct BlobCache {
path: PathBuf,
}

impl BlobCache {
fn new<P: AsRef<Path>>(path: P) -> Self {
fs::create_dir(&path).ok();
Self {
path: path.as_ref().to_path_buf(),
}
}

fn write(&self, blob: &[u8]) -> Result<BlobRef> {
use sha2::{Digest, Sha256};

let mut hasher = Sha256::new();
hasher.update(blob);
let hash = hex::encode(hasher.finalize());

let tmp = self.path.join(format!(".{hash}"));
fs::write(&tmp, blob)?;
fs::rename(tmp, self.path.join(&hash))?;

Ok(BlobRef(hash))
}

fn get_path(&self, blob: &BlobRef) -> Option<PathBuf> {
let path = self.path.join(&blob.0);
path.exists().then_some(path)
}
}

inventory::collect!(ProviderFactory);
Loading

0 comments on commit af51a41

Please sign in to comment.