use std::fs::File;
use std::io;
use std::iter::FromIterator;
use futures::{Stream, StreamExt, TryFutureExt};
use indexmap::map::Entry;
use indexmap::IndexMap;
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use crate::errors::{Error, Result};
use crate::files::FileTree;
use crate::hydra::Fetcher;
use crate::nixpkgs;
use crate::package::StorePath;
use crate::workset::{WorkSet, WorkSetHandle, WorkSetWatch};
pub trait FileListingStream: Stream<Item = Result<Option<(StorePath, String, FileTree)>>> {}
impl<T> FileListingStream for T where T: Stream<Item = Result<Option<(StorePath, String, FileTree)>>>
{}
#[allow(clippy::result_large_err)]
fn fetch_listings_impl(
fetcher: &Fetcher,
jobs: usize,
starting_set: Vec<StorePath>,
) -> (impl FileListingStream + '_, WorkSetWatch) {
let mut map: IndexMap<String, StorePath> = IndexMap::with_capacity(starting_set.len());
for path in starting_set {
let hash = path.hash().into();
match map.entry(hash) {
Entry::Occupied(mut e) => {
if e.get().origin().attr.len() > path.origin().attr.len() {
e.insert(path);
}
}
Entry::Vacant(e) => {
e.insert(path);
}
};
}
let workset = WorkSet::from_queue(map);
let process = move |mut handle: WorkSetHandle<_, _>, path: StorePath| async move {
let Some(parsed) = fetcher
.fetch_references(path.clone())
.map_err(|e| Error::FetchReferences { path, source: e })
.await?
else {
return Ok(None);
};
for reference in parsed.references {
let hash = reference.hash().into_owned();
handle.add_work(hash, reference);
}
let path = parsed.store_path.clone();
let nar_path = parsed.nar_path;
match fetcher.fetch_files(&parsed.store_path).await {
Err(e) => Err(Error::FetchFiles {
path: parsed.store_path,
source: e,
}),
Ok(Some(files)) => Ok(Some((path, nar_path, files))),
Ok(None) => Ok(None),
}
};
let watch = workset.watch();
let stream = workset
.map(move |(handle, path)| process(handle, path))
.buffer_unordered(jobs);
(stream, watch)
}
#[allow(clippy::result_large_err)]
pub fn try_load_paths_cache() -> Result<Option<(impl FileListingStream, WorkSetWatch)>> {
let file = match File::open("paths.cache") {
Ok(file) => file,
Err(ref e) if e.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(Error::LoadPathsCache { source: e })?,
};
let mut input = io::BufReader::new(file);
let fetched: Vec<(StorePath, String, FileTree)> =
bincode::serde::decode_from_std_read(&mut input, bincode::config::standard())
.map_err(|e| Error::ParsePathsCache { source: e })?;
let workset = WorkSet::from_iter(
fetched
.into_iter()
.map(|(path, nar, tree)| (path.hash().to_string(), Some((path, nar, tree)))),
);
let watch = workset.watch();
let stream = workset.map(|r| {
let (_handle, v) = r;
Ok(v)
});
Ok(Some((stream, watch)))
}
#[allow(clippy::result_large_err)]
pub fn fetch<'a>(
fetcher: &'a Fetcher,
jobs: usize,
nixpkgs: &str,
systems: Vec<Option<&str>>,
extra_scopes: &[String],
show_trace: bool,
) -> Result<(impl FileListingStream + 'a, WorkSetWatch)> {
let mut scopes = vec![None];
scopes.extend(
extra_scopes
.iter()
.filter(|x| !x.is_empty())
.cloned()
.map(Some),
);
let mut all_queries = vec![];
for system in systems {
for scope in &scopes {
all_queries.push((system, scope));
}
}
let all_paths = all_queries
.par_iter()
.flat_map_iter(|&(system, scope)| {
nixpkgs::query_packages(nixpkgs, system, scope.as_deref(), show_trace)
})
.collect::<std::result::Result<_, nixpkgs::Error>>()
.map_err(|e| Error::QueryPackages { source: e })?;
Ok(fetch_listings_impl(fetcher, jobs, all_paths))
}