212 lines
6.6 KiB
Rust
212 lines
6.6 KiB
Rust
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<P1: AsRef<Path>, P2: AsRef<Path>>(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::<String>()
|
|
.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<bool> {
|
|
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<bool> {
|
|
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<R: AsyncRead + std::marker::Unpin>(
|
|
&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
|
|
}
|
|
}
|
|
}
|