use once_cell::sync::OnceCell;
use rayon::prelude::{IntoParallelIterator, ParallelBridge, ParallelIterator};
use std::fs::DirEntry;
use std::path::{Path, PathBuf};
use tracing::{debug, warn};
use crate::error::{Error, Fallible};
use crate::internal;
use crate::Session;
#[derive(Clone, Debug)]
pub struct Bucket {
path: PathBuf,
name: String,
remote_url: OnceCell<Option<String>>,
}
impl Bucket {
pub fn from(path: &Path) -> Fallible<Bucket> {
let path = path.to_owned();
let name = path
.file_name()
.map(|n| n.to_str().unwrap().to_string())
.unwrap();
if !path.exists() {
return Err(Error::BucketNotFound(name));
}
let bucket = Bucket {
path,
name,
remote_url: OnceCell::new(),
};
Ok(bucket)
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn path(&self) -> &Path {
&self.path
}
#[inline]
pub fn manifest_count(&self) -> usize {
self.manifests().map(|v| v.len()).unwrap_or(0)
}
pub fn remote_url(&self) -> Option<&str> {
self.remote_url
.get_or_init(|| internal::git::remote_url_of(self.path(), "origin").unwrap_or(None))
.as_deref()
}
#[inline]
pub fn source(&self) -> &str {
self.remote_url().unwrap_or(self.path().to_str().unwrap())
}
pub(crate) fn path_of_manifest(&self, name: &str) -> Option<PathBuf> {
let filename = format!("{}.json", name);
let mut path = self.path().to_path_buf();
path.push(&filename);
if path.exists() {
return Some(path);
} else {
path.pop();
path.push("bucket");
path.push(&filename);
if path.exists() {
return Some(path);
} else {
let first = name.chars().take(1).last().unwrap();
let category = if first.is_ascii_lowercase() {
first.to_string()
} else {
"#".to_owned()
};
path.pop();
path.push(&category);
path.push(&filename);
if path.exists() {
return Some(path);
}
}
}
None
}
pub(crate) fn manifests(&self) -> Fallible<Vec<DirEntry>> {
let mut path = self.path().to_owned();
path.push("bucket");
let iter = if let Ok(entries) = par_read_dir(&path) {
let (dirs, files): (Vec<DirEntry>, Vec<DirEntry>) =
entries.partition(|de| de.file_type().unwrap().is_dir());
if dirs.is_empty() {
files.into_par_iter()
} else {
dirs.into_par_iter()
.filter_map(|de| par_read_dir(&de.path()).ok())
.flatten()
.collect::<Vec<_>>()
.into_par_iter()
}
} else {
path.pop();
par_read_dir(&path)?.collect::<Vec<_>>().into_par_iter()
};
let ret = iter.filter(is_manifest).collect::<Vec<_>>();
Ok(ret)
}
}
fn par_read_dir(path: &Path) -> std::io::Result<impl ParallelIterator<Item = DirEntry>> {
Ok(path.read_dir()?.par_bridge().filter_map(|de| de.ok()))
}
fn is_manifest(dir_entry: &DirEntry) -> bool {
let filename = dir_entry.file_name();
let name = filename.to_str().unwrap();
let is_file = dir_entry.file_type().unwrap().is_file();
is_file && name.ends_with(".json") && name != "package.json"
}
pub fn bucket_added(session: &Session) -> Fallible<Vec<Bucket>> {
let mut buckets = vec![];
let buckets_dir = session.config().root_path().join("buckets");
match buckets_dir.read_dir() {
Err(err) => {
warn!("failed to read buckets dir (err: {})", err);
}
Ok(entries) => {
buckets = entries
.par_bridge()
.filter_map(|entry| {
if let Ok(entry) = entry {
let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
let path = entry.path();
if is_dir {
match Bucket::from(&path) {
Err(err) => {
warn!(
"failed to parse bucket {} (err: {})",
path.display(),
err
)
}
Ok(bucket) => return Some(bucket),
}
}
}
None
})
.collect::<Vec<_>>();
}
};
Ok(buckets)
}
#[derive(Clone)]
pub struct BucketUpdateProgressContext {
name: String,
state: BucketUpdateState,
}
impl BucketUpdateProgressContext {
pub fn new(name: &str) -> BucketUpdateProgressContext {
BucketUpdateProgressContext {
name: name.to_owned(),
state: BucketUpdateState::Started,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn state(&self) -> &BucketUpdateState {
&self.state
}
pub(crate) fn set_succeeded(&mut self) {
self.state = BucketUpdateState::Succeeded;
}
pub(crate) fn set_failed(&mut self, msg: &str) {
self.state = BucketUpdateState::Failed(msg.to_owned());
}
}
#[derive(Clone, PartialEq)]
pub enum BucketUpdateState {
Started,
Failed(String),
Succeeded,
}
impl BucketUpdateState {
pub fn started(&self) -> bool {
self == &BucketUpdateState::Started
}
pub fn succeeded(&self) -> bool {
self == &BucketUpdateState::Succeeded
}
pub fn failed(&self) -> Option<&str> {
match self {
BucketUpdateState::Failed(msg) => Some(msg),
_ => None,
}
}
}