// This file is part of GAnarchy - decentralized development hub
// Copyright (C) 2021 Soni L.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Standard managers.
//!
//! Managers combine multiple data sources together and apply any desired
//! filtering.
use std::collections::BTreeSet;
use std::error;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use impl_trait::impl_trait;
use qcell::{QCell, QCellOwner};
use super::DataSourceBase;
use super::DataSource;
#[cfg(doc)]
use super::effective::EffectiveDataSource;
use super::effective::EffectiveKind;
use super::kinds::{InstanceTitle, InstanceBaseUrl, RepoListUrl, ProjectFork};
use super::sources::DefaultsDataSource;
/// A wrapper around multiple [`DataSource`]s of [`ProjectFork`]s.
///
/// While a `RepoListManager` doesn't have to be wrapped in an
/// [`EffectiveDataSource`] for correctness, it's usually desired to do so for
/// consistency.
#[derive(Default)]
pub struct RepoListManager {
repos: Vec<Box<dyn DataSource<EffectiveKind<ProjectFork>> + Send + Sync>>,
durations: Vec<Option<Duration>>,
valid: usize,
}
/// A wrapper around multiple [`DataSource`]s of kinds generally used for
/// configuration.
///
/// `ConfigManager` wraps the following [`DataSource`] kinds:
///
/// - [`InstanceTitle`]
/// - [`InstanceBaseUrl`]
/// - [`RepoListUrl`]
///
/// Generally, a `ConfigManager` must be wrapped in an [`EffectiveDataSource`]
/// for correctness.
#[derive(Default)]
pub struct ConfigManager {
owner: QCellOwner,
resources: Vec<Resource>,
durations: Vec<Option<Duration>>,
// add_source can be called after update.
valid: usize,
}
/// Helper for adding [`DataSource`]s to [`ConfigManager`]s.
///
/// See the documentation for [`ConfigManager::add_source`] for details.
pub struct AddConfigSource<'a, T: DataSourceBase + Send + Sync + 'static> {
resource: &'a mut Resource,
source: Arc<QCell<T>>,
}
/// Error type for managers in general.
#[derive(Debug)]
pub struct MultiResult {
pub results: Vec<Result<(), Box<dyn error::Error + Send + Sync>>>,
}
struct Resource {
// actual resource
base: Arc<QCell<dyn DataSourceBase + Send + Sync>>,
// views of the actual resource
title: Option<Arc<QCell<dyn DataSource<InstanceTitle> + Send + Sync>>>,
url: Option<Arc<QCell<dyn DataSource<InstanceBaseUrl> + Send + Sync>>>,
repolist: Option<Arc<QCell<dyn DataSource<RepoListUrl> + Send + Sync>>>,
}
impl Resource {
fn new(base: Arc<QCell<dyn DataSourceBase + Send + Sync>>) -> Self {
Self {
base,
title: None,
url: None,
repolist: None,
}
}
}
impl_trait! {
impl MultiResult {
impl trait error::Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
// TODO return first error?
None
}
}
impl trait fmt::Display {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Multiple errors.")
}
}
}
}
impl_trait! {
impl RepoListManager {
/// Creates a new, empty `RepoListManager`. Equivalent to
/// `Default::default()`.
///
/// # Examples
///
/// ```
/// use ganarchy::data::managers::RepoListManager;
///
/// let repos = RepoListManager::new();
/// ```
pub fn new() -> Self {
Default::default()
}
/// Adds the given [`DataSource`] to this `RepoListManager`.
///
/// # Examples
///
/// ```
/// use ganarchy::data::sources::DefaultsDataSource;
/// use ganarchy::data::managers::RepoListManager;
///
/// let mut repos = RepoListManager::default();
/// repos.add_source(DefaultsDataSource);
/// ```
pub fn add_source<T>(&mut self, source: T)
where
T: DataSource<EffectiveKind<ProjectFork>> + Send + Sync + 'static,
{
self.repos.push(Box::new(source));
}
impl trait DataSourceBase {
/// Updates the contained `DataSource`s, and returns the shortest
/// duration for the next update.
///
/// # Errors
///
/// Returns an error if any `DataSource` returns an error. Always
/// updates all `DataSource`s.
///
/// # Examples
///
/// ```
/// use ganarchy::data::managers::RepoListManager;
/// use ganarchy::data::sources::DefaultsDataSource;
/// use ganarchy::data::DataSourceBase;
///
/// let mut repos = RepoListManager::default();
/// repos.add_source(DefaultsDataSource);
/// let (duration, result) = repos.update();
/// # assert!(duration.is_none());
/// # assert!(result.is_ok());
/// ```
fn update(&mut self) -> (
Option<Duration>,
Result<(), Box<dyn error::Error + Send + Sync + 'static>>,
) {
let mut results = Vec::with_capacity(self.repos.len());
let mut ok = true;
self.durations.resize(self.repos.len(), None);
Iterator::zip(
self.repos.iter_mut(),
self.durations.iter_mut(),
).for_each(|e| {
if !matches!(*e.1, Some(d) if d.is_zero()) {
results.push(Ok(()));
return;
}
let (duration, result) = e.0.update();
*e.1 = duration;
ok &= result.is_ok();
results.push(result);
});
let try_min = self.durations.iter().flatten().min().copied();
let min = try_min.unwrap_or(Duration::ZERO);
for duration in self.durations.iter_mut().flatten() {
*duration -= min;
}
self.valid = results.len();
(try_min, ok.then(|| ()).ok_or_else(|| {
Box::new(MultiResult { results }) as _
}))
}
/// Returns whether this data source contains any (valid) data.
///
/// # Examples
///
/// ```
/// use ganarchy::data::managers::RepoListManager;
/// use ganarchy::data::DataSourceBase;
///
/// let mut repos = RepoListManager::default();
/// // An empty RepoListManager has no data.
/// assert!(!repos.exists());
/// ```
fn exists(&self) -> bool {
self.repos[..self.valid].iter().any(|e| e.exists())
}
}
impl trait std::fmt::Display {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "repo list: [")?;
for e in self.repos.iter() {
write!(f, "{}, ", e)?;
}
write!(f, "]")
}
}
/// Returns all [`ProjectFork`]s.
impl trait DataSource<EffectiveKind<ProjectFork>> {
fn get_values(&self) -> BTreeSet<EffectiveKind<ProjectFork>> {
self.repos[..self.valid].iter().flat_map(|e| {
e.get_values()
}).collect()
}
}
}
}
impl_trait! {
impl ConfigManager {
/// Creates a new, empty `ConfigManager`. Equivalent to
/// `Default::default()`.
///
/// # Examples
///
/// ```
/// use ganarchy::data::managers::ConfigManager;
///
/// let cm = ConfigManager::new();
/// ```
pub fn new() -> Self {
Default::default()
}
/// Adds a [`DefaultsDataSource`] to this `ConfigManager`, for all
/// supported properties.
///
/// The added source will override any further data sources, so this
/// should be called last, after all other data sources have been
/// added.
///
/// # Examples
///
/// ```
/// use ganarchy::data::managers::ConfigManager;
///
/// let mut cm = ConfigManager::default();
/// cm.add_defaults();
/// ```
pub fn add_defaults(&mut self) {
self.add_source(DefaultsDataSource).for_all();
}
/// Adds the given [`DataSource`] to this `ConfigManager` and returns
/// a helper for setting which properties this `DataSource` will
/// provide through this `ConfigManager`.
///
/// # Examples
///
/// ```
/// use ganarchy::data::sources::DefaultsDataSource;
/// use ganarchy::data::managers::ConfigManager;
///
/// let mut cm = ConfigManager::default();
/// cm.add_source(DefaultsDataSource).for_repo_lists();
/// ```
pub fn add_source<T>(&mut self, source: T) -> AddConfigSource<'_, T>
where
T: DataSourceBase + Send + Sync + 'static,
{
let arc = Arc::new(QCell::new(&self.owner, source));
self.resources.push(Resource::new(arc.clone()));
AddConfigSource {
resource: self.resources.last_mut().unwrap(),
source: arc,
}
}
impl trait DataSourceBase {
/// Updates the contained `DataSource`s, and returns the shortest
/// duration for the next update.
///
/// # Errors
///
/// Returns an error if any `DataSource` returns an error. Always
/// updates all `DataSource`s.
///
/// # Examples
///
/// ```
/// use ganarchy::data::managers::ConfigManager;
/// use ganarchy::data::DataSourceBase;
///
/// let mut cm = ConfigManager::default();
/// cm.add_defaults();
/// let (duration, result) = cm.update();
/// # assert!(duration.is_none());
/// # assert!(result.is_ok());
/// ```
fn update(&mut self) -> (
Option<Duration>,
Result<(), Box<dyn error::Error + Send + Sync + 'static>>,
) {
let owner = &mut self.owner;
let mut results = Vec::with_capacity(self.resources.len());
let mut ok = true;
self.durations.resize(self.resources.len(), None);
Iterator::zip(
self.resources.iter(),
self.durations.iter_mut(),
).for_each(|e| {
if !matches!(*e.1, Some(d) if d.is_zero()) {
results.push(Ok(()));
return;
}
let ret = owner.rw(&*e.0.base).update();
let (duration, result) = ret;
*e.1 = duration;
ok &= result.is_ok();
results.push(result);
});
let try_min = self.durations.iter().flatten().min().copied();
let min = try_min.unwrap_or(Duration::ZERO);
for duration in self.durations.iter_mut().flatten() {
*duration -= min;
}
self.valid = results.len();
(try_min, ok.then(|| ()).ok_or_else(|| {
Box::new(MultiResult { results }) as _
}))
}
/// Returns whether this data source contains any (valid) data.
///
/// # Examples
///
/// ```
/// use ganarchy::data::managers::ConfigManager;
/// use ganarchy::data::DataSourceBase;
///
/// let mut cm = ConfigManager::default();
/// // An empty ConfigManager has no data.
/// assert!(!cm.exists());
/// ```
fn exists(&self) -> bool {
self.resources[..self.valid].iter().any(|e| {
self.owner.ro(&*e.base).exists()
})
}
}
impl trait std::fmt::Display {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "config: [")?;
for e in self.resources.iter() {
write!(f, "{}, ", self.owner.ro(&*e.base))?;
}
write!(f, "]")
}
}
/// Returns the first [`InstanceTitle`] available.
impl trait DataSource<InstanceTitle> {
fn get_values(&self) -> Option<InstanceTitle> {
self.resources[..self.valid].iter().flat_map(|e| {
e.title.as_ref()
}).flat_map(|e| {
self.owner.ro(&*e).get_values()
}).next()
}
}
/// Returns the first [`InstanceBaseUrl`] available.
impl trait DataSource<InstanceBaseUrl> {
fn get_values(&self) -> Option<InstanceBaseUrl> {
self.resources[..self.valid].iter().flat_map(|e| {
e.url.as_ref()
}).flat_map(|e| {
self.owner.ro(&*e).get_values()
}).next()
}
}
/// Returns all [`RepoListUrl`]s.
///
/// For correct results, wrap this [`ConfigManager`] in an
/// [`EffectiveDataSource`].
impl trait DataSource<RepoListUrl> {
fn get_values(&self) -> Vec<RepoListUrl> {
self.resources[..self.valid].iter().flat_map(|e| {
e.repolist.as_ref()
}).flat_map(|e| {
self.owner.ro(&*e).get_values()
}).collect()
}
}
}
}
impl<'a, T: DataSourceBase + Send + Sync + 'static> AddConfigSource<'a, T> {
/// Adds this data source as a provider for [`InstanceTitle`].
///
/// # Examples
///
/// ```
/// use ganarchy::data::sources::DefaultsDataSource;
/// use ganarchy::data::managers::ConfigManager;
///
/// let mut cm = ConfigManager::default();
/// cm.add_source(DefaultsDataSource).for_title();
/// ```
pub fn for_title(self) -> Self where T: DataSource<InstanceTitle> {
let arc = &self.source;
self.resource.title.get_or_insert_with(|| {
arc.clone()
});
self
}
/// Adds this data source as a provider for [`InstanceBaseUrl`].
///
/// # Examples
///
/// ```
/// use ganarchy::data::sources::DefaultsDataSource;
/// use ganarchy::data::managers::ConfigManager;
///
/// let mut cm = ConfigManager::default();
/// cm.add_source(DefaultsDataSource).for_base_url();
/// ```
pub fn for_base_url(self) -> Self where T: DataSource<InstanceBaseUrl> {
let arc = &self.source;
self.resource.url.get_or_insert_with(|| {
arc.clone()
});
self
}
/// Adds this data source as a provider for [`RepoListUrl`].
///
/// # Examples
///
/// ```
/// use ganarchy::data::sources::DefaultsDataSource;
/// use ganarchy::data::managers::ConfigManager;
///
/// let mut cm = ConfigManager::default();
/// cm.add_source(DefaultsDataSource).for_repo_lists();
/// ```
pub fn for_repo_lists(self) -> Self where T: DataSource<RepoListUrl> {
let arc = &self.source;
self.resource.repolist.get_or_insert_with(|| {
arc.clone()
});
self
}
// pub(crate): we wanna be able to make breaking changes to this function
// without affecting downstream users.
pub(crate) fn for_all(self) -> Self
where
T: DataSource<InstanceTitle>,
T: DataSource<InstanceBaseUrl>,
T: DataSource<RepoListUrl>,
{
self.for_title().for_base_url().for_repo_lists()
}
}