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