pkgutils/
database.rs

1use std::error;
2use std::fmt;
3use std::fs::File;
4use std::io;
5use std::io::Read;
6use std::path::{Path, PathBuf};
7
8use petgraph;
9use petgraph::graphmap::DiGraphMap;
10
11use bidir_map::BidirMap;
12
13use indexmap::IndexMap;
14
15use toml::de;
16
17use crate::PackageMeta;
18use crate::Repo;
19
20/// Error type for the `Database`. It's a combination of an `std::io::Error`,
21/// `toml::de::Error`, and a cyclic error that can occur during dependency
22/// resolution.
23#[derive(Debug)]
24pub enum DatabaseError {
25    Io(io::Error),
26    Toml(de::Error),
27    Cycle(String),
28}
29
30impl fmt::Display for DatabaseError {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        match *self {
33            DatabaseError::Io(ref err) => write!(f, "IO error: {}", err),
34            DatabaseError::Toml(ref err) => write!(f, "TOML parsing error: {}", err),
35            DatabaseError::Cycle(ref err) => write!(f, "Cyclic dependency: {}", err),
36        }
37    }
38}
39
40impl error::Error for DatabaseError {}
41
42impl From<io::Error> for DatabaseError {
43    fn from(err: io::Error) -> DatabaseError {
44        DatabaseError::Io(err)
45    }
46}
47
48impl From<de::Error> for DatabaseError {
49    fn from(err: de::Error) -> DatabaseError {
50        DatabaseError::Toml(err)
51    }
52}
53
54#[derive(Debug)]
55pub enum PackageDepends {
56    Directory(PathBuf),
57    Repository(Repo),
58}
59
60impl PackageDepends {
61    /// Retrieves the dependencies of a package that are listed in its manifest
62    /// file.
63    pub fn get_depends(&self, pkg_name: &str) -> Result<Vec<String>, DatabaseError> {
64        match *self {
65            PackageDepends::Directory(ref pathbuf) => {
66                let path = pathbuf.as_path().join(format!("{}.toml", pkg_name));
67
68                let mut input = String::new();
69                File::open(path.as_path().to_str().unwrap())
70                    .and_then(|mut f| f.read_to_string(&mut input))?;
71
72                Ok(PackageMeta::from_toml(&input)?.depends)
73            }
74            PackageDepends::Repository(ref repo) => Ok(repo.fetch_meta(pkg_name)?.depends),
75        }
76    }
77}
78
79/// The `Database` contains a list of all packages that are available for
80/// install, as well as a list of all the packages installed on the system.
81/// It is used to calculate the dependencies of a package and for checking if
82/// a package is installed.
83#[derive(Debug)]
84pub struct Database {
85    /// The path to the directory that contains the manifests of the packages
86    /// installed
87    installed_path: PathBuf,
88
89    /// The path to the directory that contains the manifests of the packages
90    /// available for install
91    pkgdepends: PackageDepends,
92}
93
94/// The `Database` contains a list of all packages that are available for
95/// install, as well as a list of all the packages installed on the system.
96/// It is used to calculate the dependencies of a package and for checking if
97/// a package is installed.
98impl Database {
99    /// Opens a database from the specified path.
100    pub fn open<P: AsRef<Path>>(installed_path: P, pkgdepends: PackageDepends) -> Self {
101        Database {
102            installed_path: installed_path.as_ref().to_path_buf(),
103            pkgdepends: pkgdepends,
104        }
105    }
106
107    /// Checks if a package is installed
108    pub fn is_pkg_installed(&self, pkg_name: &str) -> bool {
109        let pkg_path_buf = self
110            .installed_path
111            .as_path()
112            .join(format!("{}.toml", pkg_name));
113        let installed = pkg_path_buf.as_path().exists();
114        installed
115    }
116
117    /// Retrieves the dependencies of a package that are listed in its manifest
118    /// file.
119    pub fn get_pkg_depends(&self, pkg_name: &str) -> Result<Vec<String>, DatabaseError> {
120        self.pkgdepends.get_depends(pkg_name)
121    }
122
123    /// Calculates the dependencies of the specified package, and appends them to
124    /// `ordered_dependencies`.
125    pub fn calculate_depends(
126        &self,
127        pkg_name: &str,
128        ordered_dependencies: &mut IndexMap<String, ()>,
129    ) -> Result<(), DatabaseError> {
130        let mut graph = DiGraphMap::new();
131
132        // Use bimap to intern strings and use integers for keys in graph because
133        // String doesn't implement Copy and graphmap requires Copy
134        let mut map = BidirMap::new();
135
136        map.insert(pkg_name.to_string(), 0);
137
138        self.calculate_depends_rec(pkg_name, &mut map, &mut graph)?;
139
140        // Convert integers back to package names and calculate install order
141        let dependency_ids = petgraph::algo::toposort(&graph, None).or_else(|err| {
142            // There was a cyclic dependency. Since the graph is made up of numbers, the
143            // name of the package that caused the cyclic dependency must be retrieved for
144            // human readability.
145            Err(DatabaseError::Cycle(
146                map.get_by_second(&err.node_id()).unwrap().to_string(),
147            ))
148        })?;
149
150        for i in dependency_ids {
151            if !ordered_dependencies.contains_key(map.get_by_second(&i).unwrap()) {
152                if let Some((name, _)) = map.remove_by_second(&i) {
153                    ordered_dependencies.insert(name, ());
154                }
155            }
156        }
157
158        Ok(())
159    }
160
161    /// Helper function to calculate package dependencies.
162    fn calculate_depends_rec(
163        &self,
164        pkg_name: &str,
165        map: &mut BidirMap<String, usize>,
166        graph: &mut DiGraphMap<usize, u8>,
167    ) -> Result<(), DatabaseError> {
168        let curr_node = *map.get_by_first(pkg_name).unwrap();
169
170        let mut depends = self.get_pkg_depends(pkg_name)?;
171
172        if depends.len() == 0 {
173            return Ok(());
174        }
175
176        // Copy all dependencies from vector into map, using the map length as the key
177        while !depends.is_empty() {
178            let index = depends.len() - 1;
179            let dependency = depends.remove(index);
180
181            // Check if package is already installed
182            if !self.is_pkg_installed(&dependency) {
183                // Check if the package is already in the graph. If it is, its
184                // dependencies don't need to be calculated.
185                if !map.contains_first_key(&dependency) {
186                    let dependency_node = map.len();
187                    graph.add_node(dependency_node);
188                    map.insert(dependency, dependency_node);
189
190                    graph.add_edge(dependency_node, curr_node, 0);
191                    let dependency_name = map.get_mut_by_second(&dependency_node).unwrap().clone();
192                    self.calculate_depends_rec(&dependency_name, map, graph)?;
193                } else {
194                    // Dependencies don't need to be calculated; the package only needs to get
195                    // linked to the current node
196                    let dependency_node = *map.get_by_first(&dependency).unwrap();
197                    graph.add_edge(dependency_node, curr_node, 0);
198                }
199            }
200        }
201
202        Ok(())
203    }
204}