Skip to content

Commit

Permalink
feat: move-pkg command and make clippy happy
Browse files Browse the repository at this point in the history
Signed-off-by: SoulHarsh007 <[email protected]>
  • Loading branch information
SoulHarsh007 committed Oct 28, 2024
1 parent 33b35c5 commit 853d79a
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 79 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ A command-line utility for managing Arch Linux repositories.
- **Reset:** Resets the repository database and removes outdated packages.
- **Update:** Updates the repository database with new packages and removes stale packages.
- **MovePkgsToRepo:** Moves packages from the current directory to the repository.
- **MovePkgs:** Moves packages from one repository to another repository.
- **IsPkgsUpToDate:** Checks if the packages in the repository are up-to-date.
- **CleanupBackupDir:** Cleans up the backup directory, removing older package versions.

Expand All @@ -19,6 +20,7 @@ You can install `repo-manage-util` directly from the AUR using your preferred AU
TBD

### Building from Source

Or can be built from source:

```bash
Expand Down Expand Up @@ -52,7 +54,7 @@ interactive = false

- **`[profiles.myrepo]`**: This defines a profile named "myrepo". You can have multiple profiles for different repositories.
- **`repo = "/path/to/myrepo.db.tar.zst"`**: This specifies the path to the repository database file (the `.db.tar.zst` file).
- **`add_params = ["--sign", "--include-sigs"]`**: These are additional parameters that will be passed to the `repo-add` command when adding packages to the repository. In this case, it's telling `repo-add` to sign the database and include signatures.
- **`add_params = ["--sign", "--include-sigs"]`**: These are additional parameters that will be passed to the `repo-add` command when adding packages to the repository. In this case, it's telling `repo-add` to sign the database and include signatures.
- **`rm_params = ["--sign"]`**: Similar to `add_params`, these are additional parameters passed to the `repo-remove` command, used when removing packages from the repository. Here, it tells `repo-remove` to sign the database after removal.
- **`require_signature = true`**: This setting enforces that packages must have valid signatures before being added to the repository. This is a good security practice.
- **`backup = true`**: This enables the backup feature. When enabled, outdated packages will be moved to the backup directory instead of being deleted.
Expand Down Expand Up @@ -84,6 +86,7 @@ repo-manage-util --profile <PROFILE> [COMMAND]
- **reset:** Resets the repository.
- **update:** Updates the repository.
- **move-pkgs-to-repo:** Moves packages from the current directory to the repository.
- **move-pkgs** Moves packages from one repository to another repository.
- **is-pkgs-up-to-date:** Checks if the packages in the repository are up-to-date.
- **cleanup-backup-dir:** Cleans up the backup directory.

Expand All @@ -102,4 +105,3 @@ Contributions are welcome! Please open an issue or submit a pull request on GitH
## License

This project is licensed under the GPLv3 License. See the LICENSE file for details.

40 changes: 36 additions & 4 deletions src/alpm_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,40 @@ fn init_profile_repo(repo_filepath: &str) -> Result<Alpm> {
init_alpm(&temp_dir, &repo_list)
}

// Gets names of packages from the repo DB using provided filepaths
pub fn get_packages_from_filepaths(
repo_db_path: &str,
filepaths: &[String],
) -> Result<Vec<String>> {
// we iterate through DB with alpm crate, and check for each package
// if the package file exist in the repo directory and is in the
// list of filepaths
let alpm_handle =
init_profile_repo(repo_db_path).context("Failed to init alpm for stale packages")?;

let repo_dir = Path::new(&repo_db_path).parent().unwrap();

// iterate through every package in the database using map iter
let found_pkgs: Vec<String> = alpm_handle
.syncdbs()
.iter()
.flat_map(alpm::Db::pkgs)
.filter(|x| {
// just check if those package exist in the repo and in the list of filepaths
// if not insert into found pkgs which contains package names
let pkg_filename = x.filename().expect("Invalid package doesn't have filename");
let pkg_filepath = format!("{}/{pkg_filename}", repo_dir.to_str().unwrap());
filepaths.contains(&pkg_filepath)
})
.map(|x| x.name().to_string())
.collect();

// cleanup temp dir after we are done
cleanup_alpm_tempdir(&alpm_handle)?;

Ok(found_pkgs)
}

// Gets names of stale packages from the repo DB
pub fn get_stale_packages(repo_db_path: &str) -> Result<Vec<String>> {
// we iterate through DB with alpm crate, and check for each package
Expand All @@ -60,8 +94,7 @@ pub fn get_stale_packages(repo_db_path: &str) -> Result<Vec<String>> {
let stale_pkgs: Vec<String> = alpm_handle
.syncdbs()
.iter()
.map(alpm::Db::pkgs)
.flatten()
.flat_map(alpm::Db::pkgs)
.filter(|x| {
// just check if those package exist, if not insert into state pkgs which contains
// package names
Expand Down Expand Up @@ -91,8 +124,7 @@ pub fn get_stale_filenames(repo_db_path: &str) -> Result<Vec<String>> {
let stale_pkgs: Vec<String> = alpm_handle
.syncdbs()
.iter()
.map(alpm::Db::pkgs)
.flatten()
.flat_map(alpm::Db::pkgs)
.filter(|x| {
// just check if those package exist, if not insert into state pkgs which contains
// package names
Expand Down
56 changes: 31 additions & 25 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,31 +95,37 @@ mod tests {

let expected_config = Config {
profiles: HashMap::from([
("repof".to_string(), Profile {
repo: "/home/testuser/repos/x86_64/os/repof/repof.db.tar.zst".to_string(),
add_params: vec!["--sign".to_string(), "--include-sigs".to_string()],
rm_params: vec!["--sign".to_string()],
require_signature: true,
backup: true,
backup_num: None,
backup_dir: Some("/home/testuser/backup_repos/repof".to_string()),
debug_dir: Some("/home/testuser/debug_repos/repof".to_string()),
interactive: false,
reference_repo: None,
}),
("reposecond".to_string(), Profile {
repo: "/home/testuser/repos/x86_64/os/reposecond/reposecond.db.tar.zst"
.to_string(),
add_params: vec!["--sign".to_string(), "--include-sigs".to_string()],
rm_params: vec!["--sign".to_string()],
require_signature: true,
backup: true,
backup_num: None,
backup_dir: Some("/home/testuser/backup_repos/reposecond".to_string()),
debug_dir: Some("/home/testuser/debug_repos/reposecond".to_string()),
interactive: false,
reference_repo: None,
}),
(
"repof".to_string(),
Profile {
repo: "/home/testuser/repos/x86_64/os/repof/repof.db.tar.zst".to_string(),
add_params: vec!["--sign".to_string(), "--include-sigs".to_string()],
rm_params: vec!["--sign".to_string()],
require_signature: true,
backup: true,
backup_num: None,
backup_dir: Some("/home/testuser/backup_repos/repof".to_string()),
debug_dir: Some("/home/testuser/debug_repos/repof".to_string()),
interactive: false,
reference_repo: None,
},
),
(
"reposecond".to_string(),
Profile {
repo: "/home/testuser/repos/x86_64/os/reposecond/reposecond.db.tar.zst"
.to_string(),
add_params: vec!["--sign".to_string(), "--include-sigs".to_string()],
rm_params: vec!["--sign".to_string()],
require_signature: true,
backup: true,
backup_num: None,
backup_dir: Some("/home/testuser/backup_repos/reposecond".to_string()),
debug_dir: Some("/home/testuser/debug_repos/reposecond".to_string()),
interactive: false,
reference_repo: None,
},
),
]),
};

Expand Down
139 changes: 102 additions & 37 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,63 @@ use std::fs;
use std::path::Path;

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use clap::{Args, Parser, Subcommand};
use config::Profile;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}

#[derive(Args, Debug)]
struct SingleProfileCli {
/// Profile to use from the configuration file
#[arg(short, long)]
profile: String,
}

#[derive(Args, Debug)]
struct FromToProfileCli {
/// Profile to use from the configuration file (for move-pkgs) FROM repo
#[arg(short, long)]
from: String,
/// Profile to use from the configuration file (for move-pkgs) TO repo
#[arg(short, long)]
to: String,
}

#[derive(Subcommand, Debug)]
enum Commands {
/// Reset the repository
Reset,
Reset(SingleProfileCli),
/// Update the repository
Update,
Update(SingleProfileCli),
/// Moves packages from current directory into the repository
MovePkgsToRepo,
MovePkgsToRepo(SingleProfileCli),
/// Moves packages from one repository to another repository
MovePkgs(FromToProfileCli),
/// Check if the packages are up-to-date
IsPkgsUpToDate,
IsPkgsUpToDate(SingleProfileCli),
/// Cleans up the backup directory,
/// removing the N amount of packages if configured to do so
CleanupBackupDir,
CleanupBackupDir(SingleProfileCli),
// Check if we have only certain amount of debug packages in the debug repository
// IsDebugPkgsOk, // ok maybe not implemented
}

fn get_profile_from_config<'a>(
profile_name: &'a str,
config: &'a config::Config,
) -> Result<&'a config::Profile> {
config.profiles.get(profile_name).ok_or(anyhow::anyhow!("Profile {} not found", profile_name))
}

fn get_repo_dir_from_profile(profile: &config::Profile) -> &Path {
Path::new(&profile.repo).parent().unwrap()
}

fn main() -> Result<()> {
let args = Cli::parse();

Expand All @@ -48,40 +76,54 @@ fn main() -> Result<()> {
let config_path = config::get_config_path()?;
let config = config::parse_config_file(&config_path)?;

// get profile from config
let profile = config
.profiles
.get(&args.profile)
.ok_or(anyhow::anyhow!("Profile {} not found", args.profile))?;

let repo_path = Path::new(&profile.repo);
let repo_dir = repo_path.parent().unwrap();
match &args.command {
Commands::Reset(args) => {
let profile = get_profile_from_config(&args.profile, &config)?;
let repo_dir = get_repo_dir_from_profile(profile);

let repo_db_prefix = pkg_utils::get_repo_db_prefix(&profile.repo);
let repo_db_pattern = format!("{}/{repo_db_prefix}.*", repo_dir.to_str().unwrap());
let repo_db_prefix = pkg_utils::get_repo_db_prefix(&profile.repo);
let repo_db_pattern = format!("{}/{repo_db_prefix}.*", repo_dir.to_str().unwrap());

log::debug!("repo db path := {repo_db_pattern}");
log::debug!("repo db path := {repo_db_pattern}");

match &args.command {
Commands::Reset => {
do_repo_reset(profile, &repo_db_pattern, repo_dir)?;
// TODO(vnepogodin): handle debug packages
// move them to debug folder if is set
},
Commands::Update => {
Commands::Update(args) => {
let profile = get_profile_from_config(&args.profile, &config)?;
let repo_dir = get_repo_dir_from_profile(profile);

do_repo_update(profile, repo_dir)?;
// TODO(vnepogodin): handle debug packages
// move them to debug folder if is set
},
Commands::MovePkgsToRepo => {
Commands::MovePkgsToRepo(args) => {
let profile = get_profile_from_config(&args.profile, &config)?;
let repo_dir = get_repo_dir_from_profile(profile);

do_repo_move_pkgs(profile, repo_dir)?;
},
Commands::IsPkgsUpToDate => {
Commands::IsPkgsUpToDate(args) => {
let profile = get_profile_from_config(&args.profile, &config)?;
let repo_dir = get_repo_dir_from_profile(profile);

do_repo_checkup(profile, repo_dir)?;
},
Commands::CleanupBackupDir => {
Commands::CleanupBackupDir(args) => {
let profile = get_profile_from_config(&args.profile, &config)?;

do_backup_repo_cleanup(profile)?;
},
Commands::MovePkgs(args) => {
let from_profile = get_profile_from_config(&args.from, &config)?;
let from_repo_dir = get_repo_dir_from_profile(from_profile);

let to_profile = get_profile_from_config(&args.to, &config)?;
let to_repo_dir = get_repo_dir_from_profile(to_profile);

move_packages_from_repo_to_repo(from_profile, from_repo_dir, to_profile, to_repo_dir)?;
},
}

Ok(())
Expand Down Expand Up @@ -255,14 +297,15 @@ fn do_repo_checkup(profile: &config::Profile, repo_dir: &Path) -> Result<()> {
// 3. handle ref repository
// Check for newer packages in the reference repository
if let Some(reference_repo_path) = &profile.reference_repo {
let packages_to_copy = alpm_helper::get_newer_packages_from_reference(
&profile.repo,
reference_repo_path,
)
.context("Failed to get newer packages from reference repo")?;
let packages_to_copy =
alpm_helper::get_newer_packages_from_reference(&profile.repo, reference_repo_path)
.context("Failed to get newer packages from reference repo")?;

if !packages_to_copy.is_empty() {
let new_pkgname_list = packages_to_copy.iter().map(|x| pkg_utils::get_pkg_db_pair_from_path(&x)).collect::<Vec<_>>();
let new_pkgname_list = packages_to_copy
.iter()
.map(|x| pkg_utils::get_pkg_db_pair_from_path(x))
.collect::<Vec<_>>();
log::info!("Found new pkgs from ref repo '{repo_db_prefix}': {new_pkgname_list:?}");
}

Expand Down Expand Up @@ -321,7 +364,7 @@ fn do_debug_packages_check(profile: &config::Profile, repo_dir: &Path) -> Result
for pkg_to_move in &pkgs_list
// .iter().map(|x| Path::new(x))
{
let pkg_pair = pkg_utils::get_pkg_db_pair_from_path(&pkg_to_move);
let pkg_pair = pkg_utils::get_pkg_db_pair_from_path(pkg_to_move);
log::debug!("Found debug package in repo: {pkg_pair}");
// log::debug!("Moving debug package into debug dir: {pkg_to_move}");
// if let Err(file_err) = fs::rename_file(filepath) {
Expand Down Expand Up @@ -389,11 +432,12 @@ fn do_backup_repo_cleanup(profile: &config::Profile) -> Result<()> {
// 1. moves package files in the src repo to the dest repo
// 2. removes packages from the src repo DB
// 3. adds packages to the dest repo DB
fn move_packages_from_repo_to_repo(src_repo_path: &str, dest_repo_path: &str) -> Result<()> {
// 1. moving packages from src dir
let src_repo_dir = Path::new(src_repo_path).parent().expect("Failed to get parent dir");
let dest_repo_dir = Path::new(dest_repo_path).parent().expect("Failed to get parent dir");

fn move_packages_from_repo_to_repo(
src_profile: &Profile,
src_repo_dir: &Path,
dest_profile: &Profile,
dest_repo_dir: &Path,
) -> Result<()> {
// here we get only packages without signature
let pkg_to_move_list =
glob::glob(&format!("{}/*.pkg.tar.zst", src_repo_dir.to_str().unwrap()))?
Expand All @@ -403,14 +447,35 @@ fn move_packages_from_repo_to_repo(src_repo_path: &str, dest_repo_path: &str) ->
// NOTE: probably we would rather want here to see filenames instead of full paths
log::info!("Found packages to move in src dir: {pkg_to_move_list:?}");

// lets invalidate packages if they are without signatures
let mut invalid_pkgs: Vec<String> = vec![];
for pkg_to_move in &pkg_to_move_list {
// check for signature if we require it
if dest_profile.require_signature && !Path::new(&format!("{pkg_to_move}.sig")).exists() {
let pkg_db_entry = pkg_utils::get_pkg_db_pair_from_path(pkg_to_move);
log::error!("Found package without required signature: '{pkg_db_entry}'");
invalid_pkgs.push(pkg_to_move.clone());
}
}
if !invalid_pkgs.is_empty() {
log::error!("Aborting due to found 'invalid' packages. Cannot proceed further");
return Ok(());
}

if let Err(pkg_move_err) =
handle_pkgfiles_move(&pkg_to_move_list, dest_repo_dir.to_str().unwrap())
{
log::error!("Error occured while moving package files: {pkg_move_err}");
log::error!("Error occurred while moving package files: {pkg_move_err}");
return Ok(());
}

// TODO(vnepogodin): modify source repo DB (e.g remove the moved packages from the db)
// modify source repo DB (e.g remove the moved packages from the db)
let added_pkgs_files = pkg_utils::replace_base_dir_for_pkgs(&pkg_to_move_list, dest_repo_dir);
let removal_pkgs =
alpm_helper::get_packages_from_filepaths(&src_profile.repo, &pkg_to_move_list)?;

repo_utils::handle_repo_remove(src_profile, &removal_pkgs)?;
repo_utils::handle_repo_add(dest_profile, &added_pkgs_files)?;

log::info!("Repo MovePkgsFromRepo2Repo is done!");

Expand Down
Loading

0 comments on commit 853d79a

Please sign in to comment.