use crate::error::Error;
use debian_control::apt::{Package, Source};
use debversion::Version;
use pyo3::exceptions::{PyModuleNotFoundError, PyStopIteration};
use pyo3::intern;
use pyo3::prelude::*;
pyo3::import_exception!(breezy.plugins.debian.apt_repo, NoAptSources);
lazy_static::lazy_static! {
static ref apt_mutex: std::sync::Mutex<()> = std::sync::Mutex::new(());
}
struct SourceIterator(Py<PyAny>);
impl Iterator for SourceIterator {
type Item = Source;
fn next(&mut self) -> Option<Self::Item> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let next = self.0.call_method0(py, "__next__");
match next {
Ok(next) => Some(next.extract(py).unwrap()),
Err(e) if e.is_instance_of::<PyStopIteration>(py) => None,
Err(e) if e.is_instance_of::<NoAptSources>(py) => None,
Err(e) => panic!("error iterating: {:?}", e),
}
})
}
}
struct PackageIterator(Py<PyAny>);
impl Iterator for PackageIterator {
type Item = Package;
fn next(&mut self) -> Option<Self::Item> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let next = self.0.call_method0(py, "__next__");
match next {
Ok(next) => Some(next.extract(py).unwrap()),
Err(e) if e.is_instance_of::<PyStopIteration>(py) => None,
Err(e) => panic!("error iterating: {:?}", e),
}
})
}
}
pub trait Apt {
fn as_pyobject(&self) -> &Py<PyAny>;
fn retrieve_orig(
&self,
source_name: &str,
target_directory: &std::path::Path,
orig_version: Option<&Version>,
) -> Result<(), Error> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let apt = self.as_pyobject();
apt.call_method1(
py,
"retrieve_orig",
(
source_name,
target_directory.to_string_lossy().to_string(),
orig_version.map(|v| v.to_string()),
),
)?;
Ok(())
})
}
fn retrieve_source(
&self,
source_name: &str,
target_directory: &std::path::Path,
source_version: Option<&Version>,
) -> Result<(), Error> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let apt = self.as_pyobject();
apt.call_method1(
py,
"retrieve_source",
(
source_name,
target_directory.to_string_lossy().to_string(),
source_version.map(|v| v.to_string()),
),
)?;
Ok(())
})
}
fn iter_sources(&self) -> Box<dyn Iterator<Item = Source>> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let apt = self.as_pyobject();
let iter = apt.call_method0(py, "iter_sources").unwrap();
Box::new(SourceIterator(iter))
})
}
fn iter_binaries(&self) -> Box<dyn Iterator<Item = Package>> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let apt = self.as_pyobject();
let iter = apt.call_method0(py, "iter_binaries").unwrap();
Box::new(PackageIterator(iter))
})
}
fn iter_source_by_name(&self, name: &str) -> Box<dyn Iterator<Item = Source>> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let apt = self.as_pyobject();
let iter = apt
.call_method1(py, "iter_source_by_name", (name,))
.unwrap();
Box::new(SourceIterator(iter))
})
}
fn iter_binary_by_name(&self, name: &str) -> Box<dyn Iterator<Item = Package>> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let apt = self.as_pyobject();
let iter = apt
.call_method1(py, "iter_binary_by_name", (name,))
.unwrap();
Box::new(PackageIterator(iter))
})
}
}
pub struct LocalApt(Py<PyAny>);
impl Apt for LocalApt {
fn as_pyobject(&self) -> &Py<PyAny> {
&self.0
}
}
impl<'py> IntoPyObject<'py> for LocalApt {
type Target = PyAny;
type Output = Bound<'py, PyAny>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.0.clone_ref(py).into_bound(py))
}
}
impl LocalApt {
pub fn new(rootdir: Option<&std::path::Path>) -> Result<Self, Error> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let m = match PyModule::import(py, "breezy.plugins.debian.apt_repo") {
Ok(m) => m,
Err(e) if e.is_instance_of::<PyModuleNotFoundError>(py) => {
return Err(Error::DependencyNotPresent(
"breezy.plugins.debian".to_string(),
"Install the brz-debian plugin".to_string(),
));
}
Err(e) => return Err(e.into()),
};
let apt = m.getattr("LocalApt")?;
let apt = apt.call1((rootdir.map(|p| p.to_string_lossy().to_string()),))?;
apt.call_method0(intern!(py, "__enter__"))?;
Ok(Self(apt.into()))
})
}
}
impl Default for LocalApt {
fn default() -> Self {
LocalApt::new(None).expect("Failed to create LocalApt instance")
}
}
impl Drop for LocalApt {
fn drop(&mut self) {
Python::attach(|py| {
self.0
.call_method1(
py,
intern!(py, "__exit__"),
(py.None(), py.None(), py.None()),
)
.unwrap();
});
}
}
pub struct RemoteApt(Py<PyAny>);
impl<'py> IntoPyObject<'py> for RemoteApt {
type Target = PyAny;
type Output = Bound<'py, PyAny>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.0.clone_ref(py).into_bound(py))
}
}
impl RemoteApt {
pub fn new(
mirror_uri: &url::Url,
distribution: Option<&str>,
components: Option<Vec<String>>,
key_path: Option<&std::path::Path>,
) -> Result<Self, Error> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let m = match PyModule::import(py, "breezy.plugins.debian.apt_repo") {
Ok(m) => m,
Err(e) if e.is_instance_of::<PyModuleNotFoundError>(py) => {
return Err(Error::DependencyNotPresent(
"breezy.plugins.debian".to_string(),
"Install the brz-debian plugin".to_string(),
));
}
Err(e) => return Err(e.into()),
};
let apt = m.getattr("RemoteApt")?;
let apt = apt.call1((
mirror_uri.as_str(),
distribution,
components,
key_path.map(|p| p.to_string_lossy().to_string()),
))?;
apt.call_method0(intern!(py, "__enter__"))?;
Ok(Self(apt.into()))
})
}
pub fn from_string(text: &str, key_path: Option<&std::path::Path>) -> Result<Self, Error> {
let _mutex = apt_mutex.lock().unwrap();
Python::attach(|py| {
let m = match PyModule::import(py, "breezy.plugins.debian.apt_repo") {
Ok(m) => m,
Err(e) if e.is_instance_of::<PyModuleNotFoundError>(py) => {
return Err(Error::DependencyNotPresent(
"breezy.plugins.debian".to_string(),
"Install the brz-debian plugin".to_string(),
));
}
Err(e) => return Err(e.into()),
};
let apt = m.getattr("RemoteApt")?;
let apt = apt.call_method1(
"from_string",
(text, key_path.map(|p| p.to_string_lossy().to_string())),
)?;
apt.call_method0(intern!(py, "__enter__"))?;
Ok(Self(apt.into()))
})
}
}
impl Apt for RemoteApt {
fn as_pyobject(&self) -> &Py<PyAny> {
&self.0
}
}
impl Drop for RemoteApt {
fn drop(&mut self) {
Python::attach(|py| {
self.0
.call_method1(
py,
intern!(py, "__exit__"),
(py.None(), py.None(), py.None()),
)
.unwrap();
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_local_apt_retrieve_orig() {
let apt = match LocalApt::new(None) {
Ok(apt) => apt,
Err(Error::DependencyNotPresent(dep, _)) if dep == "breezy.plugins.debian" => {
return;
}
Err(e) => panic!("Unexpected error creating LocalApt: {:?}", e),
};
let td = tempfile::tempdir().unwrap();
match apt.retrieve_orig("apt", td.path(), None) {
Ok(_) => {
let entries = td.path().read_dir().unwrap().collect::<Vec<_>>();
assert_eq!(entries.len(), 1);
let entry = entries[0].as_ref().unwrap();
assert!(entry.file_name().to_str().unwrap().starts_with("apt_"),);
assert!(entry
.file_name()
.to_str()
.unwrap()
.ends_with(".orig.tar.gz"),);
}
Err(Error::NotImplemented) => {
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
#[ignore] fn test_local_apt() {
let apt = match LocalApt::new(None) {
Ok(apt) => apt,
Err(Error::DependencyNotPresent(dep, _)) if dep == "breezy.plugins.debian" => {
return;
}
Err(e) => panic!("Unexpected error creating LocalApt: {:?}", e),
};
let package = apt.iter_binaries().next().unwrap();
assert!(package.name().is_some());
assert!(package.version().is_some());
let mut sources = apt.iter_sources();
if let Some(source) = sources.next() {
assert!(source.package().is_some());
let source = apt.iter_source_by_name("dpkg").next().unwrap();
assert_eq!(source.package().unwrap(), "dpkg");
let package = apt.iter_binary_by_name("dpkg").next().unwrap();
assert_eq!(package.name().unwrap(), "dpkg");
}
}
}