@@ -9,6 +9,7 @@ path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
console = "0.16"
|
||||
nix = { version = "0.31", features = ["sched"] }
|
||||
@@ -16,3 +17,4 @@ itertools = "0.15"
|
||||
packie = { path = "../" }
|
||||
rust-i18n = "4"
|
||||
indicatif = "0.18"
|
||||
dialoguer = "0.12"
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
use clap::Parser;
|
||||
use packie::PackieBuilder;
|
||||
use rust_i18n::t;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
#[arg(short, long, default_value = "true")]
|
||||
packages: bool,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let packie = PackieBuilder::new().build()?;
|
||||
|
||||
let repos_dir = packie.cache().repos_dir();
|
||||
|
||||
if cli.packages {
|
||||
clean_packages(&cli, &repos_dir);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clean_packages(cli: &Cli, repos_dir: &PathBuf) {
|
||||
let read_repos_dir_errx =
|
||||
|e: &std::io::Error| eprintln!("{}", t!("clean.warn_repos_dir", error = e));
|
||||
let Ok(repos) = std::fs::read_dir(&repos_dir).inspect_err(read_repos_dir_errx) else {
|
||||
return;
|
||||
};
|
||||
for repo in repos {
|
||||
let Ok(repo) = repo.inspect_err(read_repos_dir_errx) else {
|
||||
break;
|
||||
};
|
||||
let read_repo_dir_errx = |e: &std::io::Error| {
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!(
|
||||
"clean.warn_repo_dir",
|
||||
name = repo.file_name().display().to_string(),
|
||||
error = e
|
||||
)
|
||||
)
|
||||
};
|
||||
let Ok(repo) = std::fs::read_dir(repo.path()).inspect_err(read_repo_dir_errx) else {
|
||||
continue;
|
||||
};
|
||||
for file in repo {
|
||||
let Ok(file) = file.inspect_err(read_repo_dir_errx) else {
|
||||
break;
|
||||
};
|
||||
if file.file_name().as_encoded_bytes().ends_with(b".pkg") {
|
||||
match std::fs::remove_file(file.path()) {
|
||||
Ok(()) => {
|
||||
if cli.verbose {
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!("clean.info_remove_file", path = file.path().display())
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!(
|
||||
"clean.warn_remove_file",
|
||||
path = file.path().display(),
|
||||
error = err
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-3
@@ -1,4 +1,32 @@
|
||||
use console::Term;
|
||||
use chrono::{Local, TimeZone};
|
||||
use console::{Term, style};
|
||||
use indicatif::ProgressStyle;
|
||||
use rust_i18n::t;
|
||||
|
||||
pub fn progress_style_multidownload() -> ProgressStyle {
|
||||
let mut lines = Vec::with_capacity(3);
|
||||
let (_, cols) = Term::stderr().size();
|
||||
|
||||
let short_first_line = format!("{{spinner}} {prefix}", prefix = style("{prefix}").green());
|
||||
let info_line = format!(
|
||||
"{speed}{{bytes_per_sec}} {current}{{bytes}} {total}{{total_bytes}} {eta}{{eta}}",
|
||||
speed = t!("common.progress_style.speed"),
|
||||
current = t!("common.progress_style.current"),
|
||||
total = t!("common.progress_style.total"),
|
||||
eta = t!("common.progress_style.eta"),
|
||||
);
|
||||
let long_first_line = format!("{short_first_line} {info_line}");
|
||||
if cols > 100 {
|
||||
lines.push(&long_first_line[..]);
|
||||
} else {
|
||||
lines.push(&short_first_line[..]);
|
||||
lines.push(&info_line[..]);
|
||||
}
|
||||
|
||||
lines.push("{wide_bar}");
|
||||
|
||||
ProgressStyle::with_template(&lines.join("\n")).unwrap()
|
||||
}
|
||||
|
||||
pub fn yesno(prompt: &str) -> bool {
|
||||
eprint!("{prompt} [y/N] ");
|
||||
@@ -14,11 +42,11 @@ pub fn yesno(prompt: &str) -> bool {
|
||||
}
|
||||
|
||||
pub fn print_list(indent: u32, list: impl Iterator<Item = impl AsRef<str>>) {
|
||||
let (row, _) = Term::stdout().size();
|
||||
let (_, cols) = Term::stdout().size();
|
||||
let mut line_used: usize = 0;
|
||||
for i in list {
|
||||
let mut i = i.as_ref().to_string();
|
||||
if line_used >= row as _ {
|
||||
if line_used >= cols as _ {
|
||||
line_used = 0;
|
||||
println!();
|
||||
}
|
||||
@@ -40,3 +68,10 @@ pub fn format_size(bytes: u64) -> String {
|
||||
_ => format!("{:.3} GB", (bytes as f64) / 1000000000.),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_datetime(timestamp: i64) -> String {
|
||||
let Some(datetime) = Local.timestamp_opt(timestamp, 0).earliest() else {
|
||||
return "?".into();
|
||||
};
|
||||
datetime.to_string()
|
||||
}
|
||||
|
||||
+15
-6
@@ -1,5 +1,4 @@
|
||||
use crate::common::format_size;
|
||||
use anyhow::anyhow;
|
||||
use crate::{common::format_size, error};
|
||||
use clap::Parser;
|
||||
use packie::{
|
||||
PackieBuilder,
|
||||
@@ -7,20 +6,30 @@ use packie::{
|
||||
package::{PkgManifest, PkgSpec},
|
||||
};
|
||||
use rust_i18n::t;
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
pkgspec: PkgSpec,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().readonly(true).build()?;
|
||||
let mut found = packie.search_installation(&cli.pkgspec)?;
|
||||
if found.len() == 0 {
|
||||
return Err(anyhow!("{}", t!("info.no_package_found")));
|
||||
return Err(error!("info.no_package_found"));
|
||||
}
|
||||
if found.len() != 1 {
|
||||
return Err(anyhow!("{}", t!("info.pkgspec_not_unique")));
|
||||
let mut note = String::new();
|
||||
writeln!(&mut note, "{}", t!("info.note_pkgspec_not_unique")).unwrap();
|
||||
for i in found.iter() {
|
||||
writeln!(&mut note, " - {}", i.pkg_manifest.pkg_ident()).unwrap();
|
||||
}
|
||||
|
||||
return Err(crate::Error {
|
||||
message: t!("info.error_pkgspec_not_unique").into(),
|
||||
note: Some(note),
|
||||
});
|
||||
}
|
||||
let found = found.remove(0);
|
||||
|
||||
@@ -72,7 +81,7 @@ fn print_install_misc(installation: &Installation) {
|
||||
println!(
|
||||
"{}: {}",
|
||||
t!("info.print_install_misc.install_date"),
|
||||
installation.install_date,
|
||||
crate::common::format_datetime(installation.install_date),
|
||||
);
|
||||
println!(
|
||||
"{}: {}",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use packie::PackieBuilder;
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {}
|
||||
|
||||
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
pub fn main(_: Cli) -> Result<(), crate::Error> {
|
||||
PackieBuilder::new().build()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+15
-4
@@ -1,5 +1,6 @@
|
||||
use crate::common::{format_size, print_list, yesno};
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
use indicatif::ProgressBar;
|
||||
use itertools::Itertools;
|
||||
use packie::{
|
||||
@@ -9,16 +10,19 @@ use rust_i18n::t;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
/// Force the specified items to be files
|
||||
#[arg(short, long)]
|
||||
file: bool,
|
||||
|
||||
/// Don't require manual confirmation
|
||||
#[arg(short, long)]
|
||||
yes: bool,
|
||||
|
||||
/// Items to be installed
|
||||
items: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().build()?;
|
||||
if cli.file {
|
||||
for item in cli.items {
|
||||
@@ -37,7 +41,7 @@ pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
.collect();
|
||||
|
||||
if packages.is_empty() {
|
||||
eprintln!("No packages to install.");
|
||||
eprintln!("{}", t!("install.no_package_to_install"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -45,7 +49,12 @@ pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
let installed_size: u64 = packages.iter().map(|x| x.repo_pkg.installed_size).sum();
|
||||
|
||||
println!("{}", t!("install.prompt_to_install"));
|
||||
print_list(4, packages.iter().map(|x| x.pkgspec.to_string()));
|
||||
print_list(
|
||||
4,
|
||||
packages
|
||||
.iter()
|
||||
.map(|x| style(&x.pkgspec).green().to_string()),
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
t!("install.download_size", size = format_size(download_size))
|
||||
@@ -61,11 +70,13 @@ pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let progress_bar = ProgressBar::new(download_size);
|
||||
let progress_bar =
|
||||
ProgressBar::new(download_size).with_style(crate::common::progress_style_multidownload());
|
||||
let mut package_files = Vec::with_capacity(packages.len());
|
||||
for package in packages.iter() {
|
||||
let init_pos = progress_bar.position();
|
||||
progress_bar.println(format!("Downloading \"{}\"...", package.pkgspec));
|
||||
progress_bar.set_prefix(package.repo_pkg.pkg_manifest.pkg_ident().to_string());
|
||||
let path = packie.download_package(
|
||||
&package.repo_name,
|
||||
&package.repo_pkg.pkg_manifest.pkg_ident(),
|
||||
|
||||
+41
-2
@@ -1,3 +1,4 @@
|
||||
mod clean;
|
||||
mod common;
|
||||
mod info;
|
||||
mod initdb;
|
||||
@@ -5,9 +6,11 @@ mod install;
|
||||
mod print;
|
||||
mod remove;
|
||||
mod repo_admin;
|
||||
mod select;
|
||||
mod update;
|
||||
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
use rust_i18n::t;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -24,6 +27,9 @@ struct Cli {
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
enum Subcommand {
|
||||
/// Clean cache data
|
||||
Clean(clean::Cli),
|
||||
|
||||
/// Get package information
|
||||
Info(info::Cli),
|
||||
|
||||
@@ -44,11 +50,15 @@ enum Subcommand {
|
||||
|
||||
/// Update repository indexes
|
||||
Update(update::Cli),
|
||||
|
||||
/// Select global link
|
||||
Select(select::Cli),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
rust_i18n::set_locale(match std::env::var("LANG").as_deref() {
|
||||
Ok("zh_CN.UTF-8") => "zh-CN",
|
||||
Ok("ja_JP.UTF-8") => "ja",
|
||||
_ => "en",
|
||||
});
|
||||
|
||||
@@ -56,10 +66,11 @@ fn main() {
|
||||
if let Some(new_root) = cli.chroot
|
||||
&& let Err(err) = chroot(&new_root)
|
||||
{
|
||||
eprintln!("{}", t!("main.error", error = err));
|
||||
eprintln!("{}", style(t!("main.error", error = err)).red());
|
||||
std::process::exit(1);
|
||||
}
|
||||
let result = match cli.subcommand {
|
||||
Subcommand::Clean(cli) => clean::main(cli),
|
||||
Subcommand::Info(cli) => info::main(cli),
|
||||
Subcommand::InitDb(cli) => initdb::main(cli),
|
||||
Subcommand::Install(cli) => install::main(cli),
|
||||
@@ -67,9 +78,13 @@ fn main() {
|
||||
Subcommand::Remove(cli) => remove::main(cli),
|
||||
Subcommand::RepoAdmin(cli) => repo_admin::main(cli),
|
||||
Subcommand::Update(cli) => update::main(cli),
|
||||
Subcommand::Select(cli) => select::main(cli),
|
||||
};
|
||||
if let Err(err) = result {
|
||||
eprintln!("{}", t!("main.error", error = err));
|
||||
eprintln!("{}", style(t!("main.error", error = err.message)).red());
|
||||
if let Some(note) = err.note {
|
||||
eprintln!("{}", style(t!("main.note", note = note)).blue());
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -89,3 +104,27 @@ fn chroot(new_root: &Path) -> std::io::Result<()> {
|
||||
std::env::set_current_dir("/")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Error {
|
||||
message: String,
|
||||
note: Option<String>,
|
||||
}
|
||||
impl<E: ToString> From<E> for Error {
|
||||
fn from(value: E) -> Self {
|
||||
Self {
|
||||
message: value.to_string(),
|
||||
note: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($all:tt)*) => {
|
||||
$crate::Error {
|
||||
message: ::rust_i18n::t!($($all)*).into(),
|
||||
note: ::std::option::Option::None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ pub struct Cli {
|
||||
key: String,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let key: Vec<&str> = cli.key.split('.').collect();
|
||||
let result = match key.get(0).copied().unwrap() {
|
||||
"profile" => profile(&key[1..]),
|
||||
|
||||
+7
-4
@@ -1,4 +1,7 @@
|
||||
use crate::common::{print_list, yesno};
|
||||
use crate::{
|
||||
common::{print_list, yesno},
|
||||
error,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
use packie::{PackieBuilder, package::PkgSpec};
|
||||
@@ -12,16 +15,16 @@ pub struct Cli {
|
||||
items: Vec<PkgSpec>,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().build()?;
|
||||
let mut packages = Vec::with_capacity(cli.items.len());
|
||||
for pkgspec in cli.items {
|
||||
let mut found = packie.search_installation(&pkgspec)?;
|
||||
if found.is_empty() {
|
||||
return Err(anyhow!("{}", t!("remove.package_not_found")));
|
||||
return Err(error!("remove.package_not_found"));
|
||||
}
|
||||
if found.len() != 1 {
|
||||
return Err(anyhow!("{}", t!("remove.pkgspec_not_unique")));
|
||||
return Err(error!("remove.pkgspec_not_unique"));
|
||||
}
|
||||
packages.push(found.remove(0));
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub struct Cli {
|
||||
clone: Option<String>,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
if let Some(clone) = cli.clone {
|
||||
let progress_bar = ProgressBar::new(0);
|
||||
RepoServeDir::clone(&clone.parse()?, &cli.repo, |pkg, url, sum, total| {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
use crate::error;
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
use dialoguer::Select;
|
||||
use packie::PackieBuilder;
|
||||
use rust_i18n::t;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().build()?;
|
||||
let file = if cli.file.is_absolute() {
|
||||
cli.file.clone()
|
||||
} else {
|
||||
std::env::current_dir()?.join(&cli.file)
|
||||
};
|
||||
let mut all_pkgs = Vec::with_capacity(1024);
|
||||
let mut candidates = Vec::new();
|
||||
packie.for_each_installation::<anyhow::Error>(|inst| {
|
||||
all_pkgs.push(inst.pkg_manifest.pkg_ident());
|
||||
Ok(())
|
||||
})?;
|
||||
for pkgident in all_pkgs {
|
||||
let links = packie.links_of_pkg(&pkgident);
|
||||
let Ok(links) = links.iter() else {
|
||||
return Ok(());
|
||||
};
|
||||
if let Some(link) = links
|
||||
.filter_map(|x| x.ok())
|
||||
.find(|x| x.to == file.to_string_lossy())
|
||||
{
|
||||
candidates.push((pkgident, link));
|
||||
}
|
||||
}
|
||||
if candidates.is_empty() {
|
||||
return Err(crate::Error {
|
||||
message: t!("select.empty_list").into(),
|
||||
note: Some(t!("select.note_relative_path").into()),
|
||||
});
|
||||
}
|
||||
let items = std::iter::once(t!("select.disabled").into())
|
||||
.chain(candidates.iter().map(|x| x.0.to_string()))
|
||||
.map(|x| style(x).green());
|
||||
let choice = Select::new()
|
||||
.with_prompt(t!("select.prompt_select_package"))
|
||||
.items(items)
|
||||
.interact()?;
|
||||
if choice == 0 {
|
||||
if let Err(err) = packie::link::remove(&file) {
|
||||
if err.kind() == std::io::ErrorKind::InvalidData {
|
||||
return Err(error!("select.not_a_link"));
|
||||
}
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
return Ok(());
|
||||
}
|
||||
Err(err)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let (pkgident, linkdes) = candidates.remove(choice - 1);
|
||||
_ = packie::link::remove(&file);
|
||||
_ = packie::link::deactivate_by_description(&mut packie, &pkgident, &linkdes);
|
||||
packie::link::activate_by_description(&mut packie, &pkgident, &linkdes)?;
|
||||
Ok(())
|
||||
}
|
||||
+8
-2
@@ -1,16 +1,22 @@
|
||||
use clap::Parser;
|
||||
use indicatif::ProgressBar;
|
||||
use packie::{PackieBuilder, repo::SyncEvent};
|
||||
use rust_i18n::t;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {}
|
||||
|
||||
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
pub fn main(cli: Cli) -> Result<(), crate::Error> {
|
||||
let mut packie = PackieBuilder::new().build()?;
|
||||
let progress_bar = ProgressBar::new(0);
|
||||
let progress_bar =
|
||||
ProgressBar::new(0).with_style(crate::common::progress_style_multidownload());
|
||||
packie
|
||||
.sync_repo()
|
||||
.on_event(|ev| match ev {
|
||||
SyncEvent::BeginRepo(repo) => {
|
||||
progress_bar.set_prefix(repo.clone());
|
||||
progress_bar.println(t!("update.begin_repo", repo = repo));
|
||||
}
|
||||
SyncEvent::Progress(progress) => {
|
||||
progress_bar.set_length(progress.total_bytes);
|
||||
progress_bar.set_position(progress.downloaded_bytes);
|
||||
|
||||
@@ -2,45 +2,60 @@ _version: 2
|
||||
common.CopyError:
|
||||
en: 'failed to copy from "%{src}" to "%{dst}": %{error}'
|
||||
zh-CN: '无法将 "%{src}" 复制到 "%{dst}":%{error}'
|
||||
ja: '"%{src}" から "%{dst}" へのコピーに失敗しました:%{error}'
|
||||
install.InstallError.Database:
|
||||
en: "local database error: %{error}"
|
||||
zh-CN: "本地数据库错误:%{error}"
|
||||
ja: "ローカルデータベースエラー:%{error}"
|
||||
install.InstallError.NotFound:
|
||||
en: "item not found: %{item}"
|
||||
zh-CN: "找不到项目:%{item}"
|
||||
ja: "項目が見つかりません:%{item}"
|
||||
install.InstallError.DependencyRing:
|
||||
en: "dependency ring detected"
|
||||
zh-CN: "检测到依赖环"
|
||||
ja: "依存関係の循環が検出されました"
|
||||
remove.RemoveError.NotUniquePkgSpec:
|
||||
en: "not unique package spec"
|
||||
zh-CN: "指定的包范围匹配到了多个包"
|
||||
ja: "パッケージ指定が一意ではありません"
|
||||
remove.RemoveError.Database:
|
||||
en: "local database error: %{error}"
|
||||
zh-CN: "本地数据库错误:%{error}"
|
||||
ja: "ローカルデータベースエラー:%{error}"
|
||||
remove.RemoveError.NotFound:
|
||||
en: "package not found"
|
||||
zh-CN: "找不到包"
|
||||
ja: "パッケージが見つかりません"
|
||||
remove.RemoveError.RemoveFiles:
|
||||
en: "failed to remove package files: %{error}"
|
||||
zh-CN: "无法删除包文件:%{error}"
|
||||
ja: "パッケージファイルの削除に失敗しました:%{error}"
|
||||
install.InstallError.AlreadyInstalled:
|
||||
en: "package already installed"
|
||||
zh-CN: "此包已经安装"
|
||||
ja: "パッケージはすでにインストールされています"
|
||||
repo.RepoError.Database:
|
||||
en: "repository database error: %{error}"
|
||||
zh-CN: "软件仓库数据库错误:%{error}"
|
||||
zh-CN: "软件包仓库数据库错误:%{error}"
|
||||
ja: "リポジトリデータベースエラー:%{error}"
|
||||
repo.RepoError.NoSuchRepo:
|
||||
en: "no such repository"
|
||||
zh-CN: "找不到仓库"
|
||||
ja: "指定されたリポジトリが存在しません"
|
||||
repo.RepoError.NoSuchPackage:
|
||||
en: "no such package"
|
||||
zh-CN: "找不到包"
|
||||
ja: "指定されたパッケージが存在しません"
|
||||
repo.RepoError.AllTriesFailed:
|
||||
en: "all tries failed"
|
||||
zh-CN: "所有尝试均失败"
|
||||
ja: "すべての試行に失敗しました"
|
||||
repo.download.DownloadError.Checksum:
|
||||
en: "checksum failed"
|
||||
zh-CN: "文件完整性检查失败"
|
||||
ja: "チェックサム検証に失敗しました"
|
||||
BuildPackieError.Lock:
|
||||
en: "failed to lock packie database: %{error}"
|
||||
zh-CN: "无法锁定 Packie 数据库:%{error}"
|
||||
ja: "Packie データベースのロックに失敗しました:%{error}"
|
||||
|
||||
@@ -2,72 +2,164 @@ _version: 2
|
||||
main.error:
|
||||
en: "error: %{error}"
|
||||
zh-CN: "错误:%{error}"
|
||||
ja: "エラー:%{error}"
|
||||
main.note:
|
||||
en: "note: %{note}"
|
||||
zh-CN: "备注:%{note}"
|
||||
ja: "備考:%{note}"
|
||||
update.begin_repo:
|
||||
en: "Synchronizing repository \"%{repo}\"..."
|
||||
zh-CN: "正在同步软件包仓库 \"%{repo}\"..."
|
||||
ja: "リポジトリ \"%{repo}\" を同期中..."
|
||||
install.prompt_to_install:
|
||||
en: "The following packages are to be installed:"
|
||||
zh-CN: "将要安装如下的包:"
|
||||
ja: "以下のパッケージをインストールします:"
|
||||
install.download_size:
|
||||
en: "Download size: %{size}"
|
||||
zh-CN: "下载体积:%{size}"
|
||||
ja: "ダウンロードサイズ:%{size}"
|
||||
install.installed_size:
|
||||
en: "Installed size: %{size}"
|
||||
zh-CN: "安装体积:%{size}"
|
||||
ja: "インストール後サイズ:%{size}"
|
||||
install.confirm_installation:
|
||||
en: "Confirm to install?"
|
||||
zh-CN: "确认安装?"
|
||||
ja: "インストールを続行しますか?"
|
||||
install.aborting:
|
||||
en: "Aborting."
|
||||
zh-CN: "中止。"
|
||||
ja: "中止します。"
|
||||
install.no_package_to_install:
|
||||
en: "No packages to install."
|
||||
zh-CN: "没有要安装的包。"
|
||||
ja: "インストールするパッケージがありません。"
|
||||
info.no_package_found:
|
||||
en: "The specified package was not found."
|
||||
zh-CN: "找不到指定的包。"
|
||||
info.pkgspec_not_unique:
|
||||
ja: "指定されたパッケージが見つかりません。"
|
||||
info.error_pkgspec_not_unique:
|
||||
en: "The specified package spec was not unique."
|
||||
zh-CN: "指定的包规范匹配了多个包。"
|
||||
ja: "指定されたパッケージ指定が複数のパッケージに一致しました。"
|
||||
info.note_pkgspec_not_unique:
|
||||
en: "The following packages were matched:"
|
||||
zh-CN: "匹配到了下面的包:"
|
||||
ja: "以下のパッケージが一致しました:"
|
||||
info.print_pkgmanifest.name:
|
||||
en: "Package name"
|
||||
zh-CN: "包名"
|
||||
ja: "パッケージ名"
|
||||
info.print_pkgmanifest.version:
|
||||
en: "Package version"
|
||||
zh-CN: "版本"
|
||||
ja: "バージョン"
|
||||
info.print_pkgmanifest.arch:
|
||||
en: "Package architecture"
|
||||
zh-CN: "架构"
|
||||
ja: "アーキテクチャ"
|
||||
info.print_pkgmanifest.dependencies:
|
||||
en: "Dependencies"
|
||||
zh-CN: "依赖"
|
||||
ja: "依存関係"
|
||||
info.print_pkgmanifest.recommendations:
|
||||
en: "Recommendations"
|
||||
zh-CN: "建议安装"
|
||||
ja: "推奨パッケージ"
|
||||
info.print_pkgmanifest.description:
|
||||
en: "Description"
|
||||
zh-CN: "描述"
|
||||
ja: "説明"
|
||||
info.print_install_misc.install_pkgspec:
|
||||
en: "Install package spec"
|
||||
zh-CN: "安装请求"
|
||||
ja: "インストール要求"
|
||||
info.print_install_misc.installed_size:
|
||||
en: "Installed size"
|
||||
zh-CN: "安装后大小"
|
||||
ja: "インストール後サイズ"
|
||||
info.print_install_misc.install_date:
|
||||
en: "Install date"
|
||||
zh-CN: "安装日期"
|
||||
ja: "インストール日時"
|
||||
remove.package_not_found:
|
||||
en: "The specified package was not found."
|
||||
zh-CN: "找不到指定的包。"
|
||||
ja: "指定されたパッケージが見つかりません。"
|
||||
remove.pkgspec_not_unique:
|
||||
en: "The specified package spec was not unique."
|
||||
zh-CN: "指定的包规范匹配了多个包。"
|
||||
ja: "指定されたパッケージ指定が複数のパッケージに一致しました。"
|
||||
remove.action_failure:
|
||||
en: "Failed to remove the specified package: %{error}"
|
||||
zh-CN: "无法移除指定的包:%{error}"
|
||||
ja: "指定されたパッケージの削除に失敗しました:%{error}"
|
||||
remove.prompt_to_remove:
|
||||
en: "The following packages are to be removed:"
|
||||
zh-CN: "将要移除下面的包:"
|
||||
ja: "以下のパッケージを削除します:"
|
||||
remove.confirm_removal:
|
||||
en: "Confirm to remove?"
|
||||
zh-CN: "确认移除?"
|
||||
ja: "削除を続行しますか?"
|
||||
remove.aborting:
|
||||
en: "Aborting."
|
||||
zh-CN: "中止。"
|
||||
ja: "中止します。"
|
||||
remove.action_hint:
|
||||
en: "Removing package %{package} ..."
|
||||
zh-CN: "正在移除包 %{package} ..."
|
||||
ja: "パッケージ %{package} を削除中 ..."
|
||||
common.progress_style.speed:
|
||||
en: "Speed: "
|
||||
zh-CN: "速度:"
|
||||
ja: "速度:"
|
||||
common.progress_style.current:
|
||||
en: "Current: "
|
||||
zh-CN: "已下载:"
|
||||
ja: "現在:"
|
||||
common.progress_style.total:
|
||||
en: "Total: "
|
||||
zh-CN: "总共:"
|
||||
ja: "合計:"
|
||||
common.progress_style.eta:
|
||||
en: "ETA: "
|
||||
zh-CN: "剩余时间:"
|
||||
ja: "残り時間:"
|
||||
clean.warn_repos_dir:
|
||||
en: "warning: failed to read repository cache directory: %{error}"
|
||||
zh-CN: "警告:无法读取软件包仓库缓存目录:%{error}"
|
||||
ja: "警告:リポジトリキャッシュディレクトリの読み取りに失敗しました:%{error}"
|
||||
clean.warn_repo_dir:
|
||||
en: "warning: failed to read cache directory of repository \"%{name}\": %{error}"
|
||||
zh-CN: "警告:无法读取软件包仓库 \"%{name}\" 的缓存目录:%{error}"
|
||||
ja: "警告:リポジトリ \"%{name}\" のキャッシュディレクトリの読み取りに失敗しました:%{error}"
|
||||
clean.info_remove_file:
|
||||
en: "Removing file \"%{path}\"."
|
||||
zh-CN: "正在删除文件 \"%{path}\"。"
|
||||
ja: "ファイル \"%{path}\" を削除中。"
|
||||
clean.warn_remove_file:
|
||||
en: "warning: failed to remove file \"%{path}\": %{error}"
|
||||
zh-CN: "警告:无法删除文件 \"%{path}\":%{error}"
|
||||
ja: "警告:ファイル \"%{path}\" の削除に失敗しました:%{error}"
|
||||
select.prompt_select_package:
|
||||
en: "Select the package to use for the link"
|
||||
zh-CN: "选择要为此链接使用的包"
|
||||
ja: "リンクに使用するパッケージを選択してください"
|
||||
select.empty_list:
|
||||
en: "No package provided the given link"
|
||||
zh-CN: "没有包提供要求的链接"
|
||||
ja: "指定されたリンクを提供するパッケージはありません"
|
||||
select.note_relative_path:
|
||||
en: "A relative path is specified, and Packie resolves path based on your current directory. If this is not expected, provide an absolute path instead."
|
||||
zh-CN: "指定了相对路径,Packie 会根据您当前目录解析路径。如果不符合预期,请改用绝对路径。"
|
||||
ja: "相対パスが指定されています。Packieはカレントディレクトリに基づいてパスを解決します。意図と異なる場合は、絶対パスを指定してください。"
|
||||
select.disabled:
|
||||
en: "Disabled"
|
||||
zh-CN: "禁用"
|
||||
ja: "無効"
|
||||
select.not_a_link:
|
||||
en: "The existing file is not a link managed by Packie. Please remove it manually."
|
||||
zh-CN: "已存在的文件不是一个被 Packie 管理的链接。请手动删除该文件。"
|
||||
ja: "既存のファイルはPackieで管理されているリンクではありません。手動で削除してください。"
|
||||
|
||||
+5
-3
@@ -163,11 +163,13 @@ pub struct CopyError {
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[derive(Debug)]
|
||||
pub struct LockGuard(Option<nix::fcntl::Flock<std::fs::File>>);
|
||||
pub struct LockGuard {
|
||||
_inner: Option<nix::fcntl::Flock<std::fs::File>>,
|
||||
}
|
||||
#[cfg(target_family = "unix")]
|
||||
impl LockGuard {
|
||||
pub fn noop() -> Self {
|
||||
Self(None)
|
||||
Self { _inner: None }
|
||||
}
|
||||
|
||||
pub fn open(path: impl AsRef<Path>, nonblocking: bool) -> std::io::Result<Self> {
|
||||
@@ -181,6 +183,6 @@ impl LockGuard {
|
||||
nix::fcntl::FlockArg::LockExclusive
|
||||
};
|
||||
let lock = nix::fcntl::Flock::lock(file, flags).map_err(|x| x.1)?;
|
||||
Ok(Self(Some(lock)))
|
||||
Ok(Self { _inner: Some(lock) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ impl Packie {
|
||||
pub fn profile(&self) -> &Profile {
|
||||
&self.profile
|
||||
}
|
||||
|
||||
/// Returns cache manager for the Packie instance.
|
||||
pub fn cache(&self) -> &Cache {
|
||||
&self.cache
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
+20
-1
@@ -6,6 +6,12 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
impl super::Packie {
|
||||
pub fn links_of_pkg(&self, pkgident: &PkgIdent) -> LinksFile {
|
||||
LinksFile(self.local_data.links_file(pkgident))
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over link descriptions in a links file.
|
||||
#[derive(Debug)]
|
||||
pub struct LinksFile(pub PathBuf);
|
||||
@@ -73,14 +79,27 @@ pub fn create(from: impl AsRef<Path>, to: impl AsRef<Path>) -> std::io::Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to remove a link at a given path. Also removes all its parent directories if they become empty after
|
||||
/// this deletion.
|
||||
///
|
||||
/// Note that this is not an atomic operation. Take care of TOCTOU and race condition.
|
||||
///
|
||||
/// # Errors
|
||||
/// It would return an standard library IO error in given error kind:
|
||||
///
|
||||
/// - `NotFound`: The file could not be located.
|
||||
/// - `InvalidData`: The file is not a link.
|
||||
pub fn remove(path: impl Into<PathBuf>) -> std::io::Result<()> {
|
||||
let mut path = path.into();
|
||||
if !path.exists() {
|
||||
return Err(std::io::ErrorKind::NotFound.into());
|
||||
}
|
||||
if std::fs::read_link(&path).is_err() {
|
||||
return Err(std::io::ErrorKind::InvalidData.into());
|
||||
}
|
||||
std::fs::remove_file(&path)?;
|
||||
while path.pop() {
|
||||
_ = std::fs::remove_dir(&path)?;
|
||||
_ = std::fs::remove_dir(&path);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -38,6 +38,13 @@ impl super::Packie {
|
||||
.unique_by(|x| x.pkg_manifest.pkg_ident())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn for_each_installation<E: From<rusqlite::Error>>(
|
||||
&mut self,
|
||||
f: impl FnMut(Installation) -> Result<(), E>,
|
||||
) -> Result<(), E> {
|
||||
self.local_db.for_each_installation(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -199,6 +206,17 @@ CREATE TABLE IF NOT EXISTS "abspkg"(
|
||||
Ok(abspkg)
|
||||
}
|
||||
|
||||
pub fn for_each_installation<E: From<rusqlite::Error>>(
|
||||
&mut self,
|
||||
mut f: impl FnMut(Installation) -> Result<(), E>,
|
||||
) -> Result<(), E> {
|
||||
let mut stmt = self.0.prepare_cached("SELECT * FROM \"installation\"")?;
|
||||
for i in stmt.query_map(params![], map_installation)? {
|
||||
f(i?)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_installation(&mut self, pkg_ident: &PkgIdent) -> rusqlite::Result<bool> {
|
||||
self
|
||||
.0
|
||||
|
||||
@@ -152,6 +152,14 @@ impl FromStr for PkgSpec {
|
||||
type Err = crate::version::VersionError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(pkgident) = PkgIdent::from_str(s) {
|
||||
return Ok(Self {
|
||||
name: pkgident.name,
|
||||
version: VersionFilter::Eq(pkgident.version),
|
||||
arch: Some(pkgident.arch),
|
||||
});
|
||||
}
|
||||
|
||||
let s = s.trim();
|
||||
let mut name = String::new();
|
||||
let mut version = None;
|
||||
|
||||
@@ -10,6 +10,7 @@ pub struct Download<P> {
|
||||
to: PathBuf,
|
||||
on_progress: P,
|
||||
no_existence_check: bool,
|
||||
checksum_required: Option<String>,
|
||||
}
|
||||
impl Download<fn(u64, u64)> {
|
||||
pub fn new(from: String, to: PathBuf) -> Self {
|
||||
@@ -18,6 +19,7 @@ impl Download<fn(u64, u64)> {
|
||||
to,
|
||||
no_existence_check: false,
|
||||
on_progress: |_, _| (),
|
||||
checksum_required: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +30,7 @@ impl<P> Download<P> {
|
||||
to: self.to,
|
||||
no_existence_check: self.no_existence_check,
|
||||
on_progress,
|
||||
checksum_required: self.checksum_required,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +38,11 @@ impl<P> Download<P> {
|
||||
self.no_existence_check = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn checksum_required(mut self, val: String) -> Self {
|
||||
self.checksum_required = Some(val);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<P: FnMut(u64, u64)> Download<P> {
|
||||
pub fn run(&mut self) -> Result<(), DownloadError> {
|
||||
@@ -77,6 +85,16 @@ impl<P: FnMut(u64, u64)> Download<P> {
|
||||
sum += len as u64;
|
||||
(self.on_progress)(sum, total);
|
||||
}
|
||||
|
||||
if let Some(expected) = self.checksum_required.as_ref() {
|
||||
let verified =
|
||||
crate::common::checksum::verify_file(&temp_file_path, expected).unwrap_or_default();
|
||||
if !verified {
|
||||
_ = std::fs::remove_file(&temp_file_path);
|
||||
return Err(DownloadError::Checksum);
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::rename(temp_file_path, &self.to).map_err(DownloadError::Filesystem)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
+5
-13
@@ -3,9 +3,6 @@ mod serve;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use download::DownloadError;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashMap;
|
||||
#[doc(inline)]
|
||||
pub use serve::RepoServeDir;
|
||||
|
||||
@@ -14,8 +11,10 @@ use crate::{
|
||||
package::{AbsPkgIdent, PkgIdent, PkgManifest, PkgSpec},
|
||||
};
|
||||
use download::Download;
|
||||
use itertools::Itertools;
|
||||
use rusqlite::{OpenFlags, Row, params};
|
||||
use rust_i18n::t;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use url::Url;
|
||||
@@ -66,6 +65,7 @@ impl super::Packie {
|
||||
.on_progress(|sum, total| {
|
||||
on_event(DownloadPackageEvent::Progress(repo_url.clone(), sum, total))
|
||||
})
|
||||
.checksum_required(repo_package.checksum.clone())
|
||||
.run();
|
||||
if let Err(err) = download_result {
|
||||
on_event(DownloadPackageEvent::Error(
|
||||
@@ -74,16 +74,6 @@ impl super::Packie {
|
||||
));
|
||||
continue;
|
||||
}
|
||||
let checksum_passed =
|
||||
crate::common::checksum::verify_file(&download_path, &repo_package.checksum)
|
||||
.unwrap_or_default();
|
||||
if !checksum_passed {
|
||||
on_event(DownloadPackageEvent::Error(
|
||||
download_url.clone(),
|
||||
RepoError::Download(DownloadError::Checksum),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
return Ok(download_path);
|
||||
}
|
||||
|
||||
@@ -304,6 +294,7 @@ impl<E: FnMut(SyncEvent)> SyncSession<'_, E> {
|
||||
self.raise_error(Some(repo_name), None, RepoError::NoSuchRepo);
|
||||
continue;
|
||||
};
|
||||
(self.on_event)(SyncEvent::BeginRepo(repo_name.clone()));
|
||||
for url in repo.urls() {
|
||||
match self.sync_url(&repo_name, &url) {
|
||||
Ok(()) => break,
|
||||
@@ -351,6 +342,7 @@ impl<E: FnMut(SyncEvent)> SyncSession<'_, E> {
|
||||
/// An event during running [`SyncSession`], which may report progress changes or errors.
|
||||
#[derive(Debug)]
|
||||
pub enum SyncEvent {
|
||||
BeginRepo(String),
|
||||
Progress(SyncProgress),
|
||||
Error(Option<String>, Option<Url>, RepoError),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user