use std::path::{Path, PathBuf}; use sea_orm::{ColumnTrait, DbConn, ModelTrait, QueryFilter, QuerySelect}; use uuid::Uuid; use futures::StreamExt; use tokio::io::AsyncRead; use tokio::io::AsyncWriteExt; use super::archive; use super::package; use crate::db; use crate::error::Result; pub const ANY_ARCH: &'static str = "any"; pub struct MetaRepoMgr { repo_dir: PathBuf, pkg_dir: PathBuf, } impl MetaRepoMgr { pub fn new, P2: AsRef>(repo_dir: P1, pkg_dir: P2) -> Self { MetaRepoMgr { repo_dir: repo_dir.as_ref().to_path_buf(), pkg_dir: pkg_dir.as_ref().to_path_buf(), } } /// Generate archive databases for all known architectures in the repository, including the /// "any" architecture. pub async fn generate_archives_all(&self, conn: &DbConn, repo: &str) -> Result<()> { let repo = crate::db::query::repo::by_name(conn, repo).await?; if repo.is_none() { return Ok(()); } let repo = repo.unwrap(); let mut archs = repo .find_related(crate::db::Package) .select_only() .column(crate::db::package::Column::Arch) .distinct() .into_tuple::() .stream(conn) .await?; while let Some(arch) = archs.next().await.transpose()? { self.generate_archives(conn, &repo.name, &arch).await?; } Ok(()) } /// Generate the archive databases for the given repository and architecture. pub async fn generate_archives(&self, conn: &DbConn, repo: &str, arch: &str) -> Result<()> { let repo = crate::db::query::repo::by_name(conn, repo).await?; if repo.is_none() { return Ok(()); } let repo = repo.unwrap(); let parent_dir = self.repo_dir.join(&repo.name).join(arch); tokio::fs::create_dir_all(&parent_dir).await?; let ar_files = archive::RepoArchiveWriter::open(parent_dir.join(format!("{}.db.tar.gz", repo.name))) .await?; // Query all packages in the repo that have the given architecture or the "any" // architecture let mut pkgs = repo .find_related(crate::db::Package) .filter(db::package::Column::Arch.is_in([arch, ANY_ARCH])) .stream(conn) .await?; let uuid: uuid::fmt::Simple = Uuid::new_v4().into(); let files_tmp_file_path = self.pkg_dir.join(uuid.to_string()); let uuid: uuid::fmt::Simple = Uuid::new_v4().into(); let desc_tmp_file_path = self.pkg_dir.join(uuid.to_string()); while let Some(pkg) = pkgs.next().await.transpose()? { let mut files_tmp_file = tokio::fs::File::create(&files_tmp_file_path).await?; let mut desc_tmp_file = tokio::fs::File::create(&desc_tmp_file_path).await?; package::write_files(conn, &mut files_tmp_file, &pkg).await?; package::write_desc(conn, &mut desc_tmp_file, &pkg).await?; let full_name = format!("{}-{}", pkg.name, pkg.version); ar_files .add_entry(&full_name, &desc_tmp_file_path, true) .await?; ar_files .add_entry(&full_name, &files_tmp_file_path, false) .await?; } ar_files.close().await?; tokio::fs::remove_file(files_tmp_file_path).await?; Ok(()) } /// Remove the repo with the given name, if it existed pub async fn remove_repo(&self, conn: &DbConn, repo: &str) -> Result { let res = db::query::repo::by_name(conn, repo).await?; if let Some(repo_entry) = res { // Remove repository from database repo_entry.delete(conn).await?; // Remove files from file system tokio::fs::remove_dir_all(self.repo_dir.join(repo)).await?; tokio::fs::remove_dir_all(self.pkg_dir.join(repo)).await?; Ok(true) } else { Ok(false) } } pub async fn remove_pkg( &self, conn: &DbConn, repo: &str, arch: &str, name: &str, ) -> Result { let repo = db::query::repo::by_name(conn, repo).await?; if let Some(repo) = repo { let pkg = db::query::package::by_fields(conn, repo.id, arch, name).await?; if let Some(pkg) = pkg { // Remove package from database pkg.delete(conn).await?; Ok(true) } else { Ok(false) } } else { Ok(false) } } pub async fn add_pkg_from_reader( &self, conn: &DbConn, reader: &mut R, repo: &str, ) -> crate::Result<()> { // Copy file contents to temporary path so libarchive can work with it let uuid: uuid::fmt::Simple = Uuid::new_v4().into(); let path = self.pkg_dir.join(uuid.to_string()); let mut temp_file = tokio::fs::File::create(&path).await?; tokio::io::copy(reader, &mut temp_file).await?; // Parse the package let path_clone = path.clone(); let pkg = tokio::task::spawn_blocking(move || package::Package::open(path_clone)) .await .unwrap()?; // Query the repo for its ID, or create it if it does not already exist let res = db::query::repo::by_name(conn, &repo).await?; let repo_id = if let Some(repo_entity) = res { repo_entity.id } else { db::query::repo::insert(conn, repo, None) .await? .last_insert_id }; // If the package already exists in the database, we remove it first let res = db::query::package::by_fields(conn, repo_id, &pkg.info.arch, &pkg.info.name).await?; if let Some(entry) = res { entry.delete(conn).await?; } let dest_pkg_path = self .pkg_dir .join(repo) .join(&pkg.info.arch) .join(pkg.file_name()); // Insert new package into database let arch = pkg.info.arch.clone(); db::query::package::insert(conn, repo_id, pkg).await?; // Move the package to its final resting place tokio::fs::create_dir_all(dest_pkg_path.parent().unwrap()).await?; tokio::fs::rename(path, dest_pkg_path).await?; // Synchronize archive databases if arch == ANY_ARCH { self.generate_archives_all(conn, repo).await } else { self.generate_archives(conn, repo, &arch).await } } }