use crate::metadata::{Entry, FileRead};
use std::fs;
use std::fs::ReadDir;
use std::io;
use std::path::{Path, PathBuf};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum DBType {
Files,
Database,
}
#[derive(Debug)]
pub struct PkgDB {
dbtype: DBType,
path: PathBuf,
readdir: Option<ReadDir>,
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct InstalledPackage {
path: PathBuf,
pkgbase: String,
pkgname: String,
pkgversion: String,
}
impl PkgDB {
pub fn open(path: impl AsRef<Path>) -> Result<PkgDB, io::Error> {
let path = path.as_ref();
if path.is_dir() {
let readdir = fs::read_dir(path)?;
Ok(PkgDB {
dbtype: DBType::Files,
path: path.to_path_buf(),
readdir: Some(readdir),
})
} else if path.is_file() {
Ok(PkgDB {
dbtype: DBType::Database,
path: path.to_path_buf(),
readdir: None,
})
} else {
Err(io::Error::new(io::ErrorKind::NotFound, "Invalid pkgdb"))
}
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub fn dbtype(&self) -> DBType {
self.dbtype
}
fn is_valid_pkgdir(&self, pkgdir: &Path) -> bool {
if !pkgdir.is_dir() {
return false;
}
pkgdir.join(Entry::Comment.to_filename()).exists()
&& pkgdir.join(Entry::Contents.to_filename()).exists()
&& pkgdir.join(Entry::Desc.to_filename()).exists()
}
}
impl InstalledPackage {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn pkgbase(&self) -> &str {
&self.pkgbase
}
#[must_use]
pub fn pkgname(&self) -> &str {
&self.pkgname
}
#[must_use]
pub fn pkgversion(&self) -> &str {
&self.pkgversion
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
fn read_file(&self, entry: Entry) -> io::Result<Option<String>> {
match fs::read_to_string(self.path.join(entry.to_filename())) {
Ok(content) => Ok(Some(content)),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
Err(e) => Err(e),
}
}
}
impl FileRead for InstalledPackage {
fn pkgname(&self) -> &str {
&self.pkgname
}
fn comment(&self) -> io::Result<String> {
fs::read_to_string(self.path.join(Entry::Comment.to_filename()))
.map(|s| s.trim().to_string())
}
fn contents(&self) -> io::Result<String> {
fs::read_to_string(self.path.join(Entry::Contents.to_filename()))
}
fn desc(&self) -> io::Result<String> {
fs::read_to_string(self.path.join(Entry::Desc.to_filename()))
}
fn build_info(&self) -> io::Result<Option<String>> {
self.read_file(Entry::BuildInfo)
}
fn build_version(&self) -> io::Result<Option<String>> {
self.read_file(Entry::BuildVersion)
}
fn deinstall(&self) -> io::Result<Option<String>> {
self.read_file(Entry::DeInstall)
}
fn display(&self) -> io::Result<Option<String>> {
self.read_file(Entry::Display)
}
fn install(&self) -> io::Result<Option<String>> {
self.read_file(Entry::Install)
}
fn installed_info(&self) -> io::Result<Option<String>> {
self.read_file(Entry::InstalledInfo)
}
fn mtree_dirs(&self) -> io::Result<Option<String>> {
self.read_file(Entry::MtreeDirs)
}
fn preserve(&self) -> io::Result<Option<String>> {
self.read_file(Entry::Preserve)
}
fn required_by(&self) -> io::Result<Option<String>> {
self.read_file(Entry::RequiredBy)
}
fn size_all(&self) -> io::Result<Option<String>> {
self.read_file(Entry::SizeAll)
}
fn size_pkg(&self) -> io::Result<Option<String>> {
self.read_file(Entry::SizePkg)
}
}
impl Iterator for PkgDB {
type Item = io::Result<InstalledPackage>;
fn next(&mut self) -> Option<Self::Item> {
match self.dbtype {
DBType::Files => loop {
let readdir = self.readdir.as_mut()?;
let entry = match readdir.next()? {
Ok(entry) => entry,
Err(e) => return Some(Err(e)),
};
let path = entry.path();
if !self.is_valid_pkgdir(&path) {
continue;
}
let filename = entry.file_name();
let dirname = match filename.to_str() {
Some(name) => name,
None => {
return Some(Err(io::Error::new(
io::ErrorKind::InvalidData,
"Could not parse package directory name",
)));
}
};
let (pkgbase, pkgversion) = match dirname.rsplit_once('-') {
Some((base, version)) => (base, version),
None => {
return Some(Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid package name: {}", dirname),
)));
}
};
return Some(Ok(InstalledPackage {
path,
pkgname: dirname.to_string(),
pkgbase: pkgbase.to_string(),
pkgversion: pkgversion.to_string(),
}));
},
DBType::Database => None,
}
}
}