summary refs log blame commit diff stats
path: root/src/data/managers.rs
blob: 7b34f80d633c36670158941402683266f8859aa1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16















                                                                              





                                                                         
               
             
                   

                           
                               


                          

                                          
                                    
                                                                             
                                       
                  
 




                                                                              


                                                                              


                 










                                                                              

                          

                             



                                             

































                                                                            
             


























                                                                       



                              










                                                                    

                                                  





                                                                              

                                                                             
                








                                                                

                                                    
                   




                                                    
                   


                                              
                 


























































































                                                                             
         





                                                                


                                   

                                                                             
                







                                                            

                                                    
                   
                                            
                                            



                                                          
                   


                                                  
                 


                                                                           










                                                            
                                      


































                                                                            

             



















































































                                                                             

     
// 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 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;
use super::Update;

/// 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>>,
    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>,
    // 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 any relevant
            /// update stats.
            /// 
            /// # 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 update = repos.update();
            /// # assert!(update.errors.is_empty());
            /// ```
            fn update(&mut self) -> Update {
                let mut errors = Vec::new();
                self.repos.iter_mut().for_each(|e| {
                    let ret = e.update();
                    errors.extend(ret.errors);
                });
                self.valid = self.repos.len();
                Update {
                    errors: errors,
                }
            }

            /// 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 any relevant
            /// update stats.
            /// 
            /// # Examples
            ///
            /// ```
            /// use ganarchy::data::managers::ConfigManager;
            /// use ganarchy::data::DataSourceBase;
            ///
            /// let mut cm = ConfigManager::default();
            /// cm.add_defaults();
            /// let update = cm.update();
            /// # assert!(update.errors.is_empty());
            /// ```
            fn update(&mut self) -> Update {
                let owner = &mut self.owner;
                let mut errors = Vec::new();
                self.resources.iter().for_each(|e| {
                    let ret = owner.rw(&*e.base).update();
                    errors.extend(ret.errors);
                });
                self.valid = self.resources.len();
                Update {
                    errors: errors,
                }
            }

            /// 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()
    }
}