mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 21:30:41 +00:00
Introduce Catalog module with structs and functionality for managing package metadata, enhance file_backend and CLI to handle catalog operations, and update dependencies (chrono and others) to support new features.
This commit is contained in:
parent
63f2d1da62
commit
a0fcc13033
8 changed files with 862 additions and 45 deletions
156
Cargo.lock
generated
156
Cargo.lock
generated
|
|
@ -32,6 +32,21 @@ version = "0.2.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.19"
|
version = "0.6.19"
|
||||||
|
|
@ -246,6 +261,20 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.23"
|
version = "3.2.23"
|
||||||
|
|
@ -343,9 +372,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.4"
|
version = "0.8.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
|
|
@ -890,6 +919,30 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ident_case"
|
name = "ident_case"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
@ -955,10 +1008,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.61"
|
version = "0.3.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
|
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -994,6 +1048,7 @@ name = "libips"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
"diff-struct",
|
"diff-struct",
|
||||||
"flate2",
|
"flate2",
|
||||||
"lz4",
|
"lz4",
|
||||||
|
|
@ -1483,7 +1538,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
"specfile",
|
"specfile",
|
||||||
"thiserror 1.0.40",
|
"thiserror 2.0.12",
|
||||||
"url",
|
"url",
|
||||||
"which",
|
"which",
|
||||||
]
|
]
|
||||||
|
|
@ -2002,7 +2057,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"thiserror 1.0.40",
|
"thiserror 2.0.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2547,26 +2602,27 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.84"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
|
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.84"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
|
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 2.0.104",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2584,9 +2640,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.84"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
|
@ -2594,22 +2650,25 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.84"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 2.0.104",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.84"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmparser"
|
name = "wasmparser"
|
||||||
|
|
@ -2688,6 +2747,65 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.60.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.59.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.42.0"
|
version = "0.42.0"
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,4 @@ semver = { version = "1.0.20", features = ["serde"] }
|
||||||
diff-struct = "0.5.3"
|
diff-struct = "0.5.3"
|
||||||
searchy = "0.5.0"
|
searchy = "0.5.0"
|
||||||
tantivy = { version = "0.24.2", features = ["mmap"] }
|
tantivy = { version = "0.24.2", features = ["mmap"] }
|
||||||
|
chrono = "0.4.41"
|
||||||
|
|
|
||||||
|
|
@ -520,6 +520,19 @@ impl Fmri {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the stem of the FMRI (the package name without version)
|
||||||
|
pub fn stem(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the version of the FMRI as a string
|
||||||
|
pub fn version(&self) -> String {
|
||||||
|
match &self.version {
|
||||||
|
Some(v) => v.to_string(),
|
||||||
|
None => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse an FMRI string into an Fmri
|
/// Parse an FMRI string into an Fmri
|
||||||
///
|
///
|
||||||
/// The FMRI string should be in the format: [scheme://][publisher/]name[@version]
|
/// The FMRI string should be in the format: [scheme://][publisher/]name[@version]
|
||||||
|
|
|
||||||
457
libips/src/repository/catalog.rs
Normal file
457
libips/src/repository/catalog.rs
Normal file
|
|
@ -0,0 +1,457 @@
|
||||||
|
// This Source Code Form is subject to the terms of
|
||||||
|
// the Mozilla Public License, v. 2.0. If a copy of the
|
||||||
|
// MPL was not distributed with this file, You can
|
||||||
|
// obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use crate::fmri::Fmri;
|
||||||
|
|
||||||
|
/// Format a SystemTime as an ISO-8601 'basic format' date in UTC
|
||||||
|
fn format_iso8601_basic(time: &SystemTime) -> String {
|
||||||
|
let datetime = convert_system_time_to_datetime(time);
|
||||||
|
format!("{}Z", datetime.format("%Y%m%dT%H%M%S.%f"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert SystemTime to UTC DateTime, handling errors gracefully
|
||||||
|
fn convert_system_time_to_datetime(time: &SystemTime) -> chrono::DateTime<chrono::Utc> {
|
||||||
|
let duration = time
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap_or_else(|_| std::time::Duration::from_secs(0));
|
||||||
|
|
||||||
|
let secs = duration.as_secs() as i64;
|
||||||
|
let nanos = duration.subsec_nanos();
|
||||||
|
|
||||||
|
chrono::DateTime::from_timestamp(secs, nanos)
|
||||||
|
.unwrap_or_else(|| chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(
|
||||||
|
chrono::NaiveDateTime::default(),
|
||||||
|
chrono::Utc,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Catalog version
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum CatalogVersion {
|
||||||
|
V1 = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CatalogVersion {
|
||||||
|
fn default() -> Self {
|
||||||
|
CatalogVersion::V1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Catalog part information
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct CatalogPartInfo {
|
||||||
|
/// Last modified timestamp in ISO-8601 'basic format' date in UTC
|
||||||
|
#[serde(rename = "last-modified")]
|
||||||
|
pub last_modified: String,
|
||||||
|
|
||||||
|
/// Optional SHA-1 signature of the catalog part
|
||||||
|
#[serde(rename = "signature-sha-1", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub signature_sha1: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update log information
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct UpdateLogInfo {
|
||||||
|
/// Last modified timestamp in ISO-8601 'basic format' date in UTC
|
||||||
|
#[serde(rename = "last-modified")]
|
||||||
|
pub last_modified: String,
|
||||||
|
|
||||||
|
/// Optional SHA-1 signature of the update log
|
||||||
|
#[serde(rename = "signature-sha-1", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub signature_sha1: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Catalog attributes
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct CatalogAttrs {
|
||||||
|
/// Optional signature information
|
||||||
|
#[serde(rename = "_SIGNATURE", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub signature: Option<HashMap<String, String>>,
|
||||||
|
|
||||||
|
/// Creation timestamp in ISO-8601 'basic format' date in UTC
|
||||||
|
pub created: String,
|
||||||
|
|
||||||
|
/// Last modified timestamp in ISO-8601 'basic format' date in UTC
|
||||||
|
#[serde(rename = "last-modified")]
|
||||||
|
pub last_modified: String,
|
||||||
|
|
||||||
|
/// Number of unique package stems in the catalog
|
||||||
|
#[serde(rename = "package-count")]
|
||||||
|
pub package_count: usize,
|
||||||
|
|
||||||
|
/// Number of unique package versions in the catalog
|
||||||
|
#[serde(rename = "package-version-count")]
|
||||||
|
pub package_version_count: usize,
|
||||||
|
|
||||||
|
/// Available catalog parts
|
||||||
|
pub parts: HashMap<String, CatalogPartInfo>,
|
||||||
|
|
||||||
|
/// Available update logs
|
||||||
|
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
pub updates: HashMap<String, UpdateLogInfo>,
|
||||||
|
|
||||||
|
/// Catalog version
|
||||||
|
pub version: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CatalogAttrs {
|
||||||
|
/// Create a new catalog attributes structure
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let timestamp = format_iso8601_basic(&now);
|
||||||
|
|
||||||
|
CatalogAttrs {
|
||||||
|
signature: None,
|
||||||
|
created: timestamp.clone(),
|
||||||
|
last_modified: timestamp,
|
||||||
|
package_count: 0,
|
||||||
|
package_version_count: 0,
|
||||||
|
parts: HashMap::new(),
|
||||||
|
updates: HashMap::new(),
|
||||||
|
version: CatalogVersion::V1 as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save catalog attributes to a file
|
||||||
|
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
|
let json = serde_json::to_string_pretty(self)?;
|
||||||
|
fs::write(path, json)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load catalog attributes from a file
|
||||||
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
|
let json = fs::read_to_string(path)?;
|
||||||
|
let attrs: CatalogAttrs = serde_json::from_str(&json)?;
|
||||||
|
Ok(attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Package version entry in a catalog
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PackageVersionEntry {
|
||||||
|
/// Package version string
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
/// Optional actions associated with this package version
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub actions: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Optional SHA-1 signature of the package manifest
|
||||||
|
#[serde(rename = "signature-sha-1", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub signature_sha1: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Catalog part (base, dependency, summary)
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct CatalogPart {
|
||||||
|
/// Optional signature information
|
||||||
|
#[serde(rename = "_SIGNATURE", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub signature: Option<HashMap<String, String>>,
|
||||||
|
|
||||||
|
/// Packages by publisher and stem
|
||||||
|
pub packages: HashMap<String, HashMap<String, Vec<PackageVersionEntry>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CatalogPart {
|
||||||
|
/// Create a new catalog part
|
||||||
|
pub fn new() -> Self {
|
||||||
|
CatalogPart {
|
||||||
|
signature: None,
|
||||||
|
packages: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a package to the catalog part
|
||||||
|
pub fn add_package(&mut self, publisher: &str, fmri: &Fmri, actions: Option<Vec<String>>, signature: Option<String>) {
|
||||||
|
let publisher_packages = self.packages.entry(publisher.to_string()).or_insert_with(HashMap::new);
|
||||||
|
let stem_versions = publisher_packages.entry(fmri.stem().to_string()).or_insert_with(Vec::new);
|
||||||
|
|
||||||
|
// Check if this version already exists
|
||||||
|
for entry in stem_versions.iter_mut() {
|
||||||
|
if !fmri.version().is_empty() && entry.version == fmri.version() {
|
||||||
|
// Update existing entry
|
||||||
|
if let Some(acts) = actions {
|
||||||
|
entry.actions = Some(acts);
|
||||||
|
}
|
||||||
|
if let Some(sig) = signature {
|
||||||
|
entry.signature_sha1 = Some(sig);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new entry
|
||||||
|
stem_versions.push(PackageVersionEntry {
|
||||||
|
version: fmri.version(),
|
||||||
|
actions,
|
||||||
|
signature_sha1: signature,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort versions (should be in ascending order)
|
||||||
|
stem_versions.sort_by(|a, b| a.version.cmp(&b.version));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save a catalog part to a file
|
||||||
|
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
|
let json = serde_json::to_string_pretty(self)?;
|
||||||
|
fs::write(path, json)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load catalog part from a file
|
||||||
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
|
let json = fs::read_to_string(path)?;
|
||||||
|
let part: CatalogPart = serde_json::from_str(&json)?;
|
||||||
|
Ok(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Operation type for catalog updates
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum CatalogOperationType {
|
||||||
|
#[serde(rename = "add")]
|
||||||
|
Add,
|
||||||
|
#[serde(rename = "remove")]
|
||||||
|
Remove,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Package update entry in an update log
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PackageUpdateEntry {
|
||||||
|
/// Type of operation (add or remove)
|
||||||
|
#[serde(rename = "op-type")]
|
||||||
|
pub op_type: CatalogOperationType,
|
||||||
|
|
||||||
|
/// Timestamp of the operation in ISO-8601 'basic format' date in UTC
|
||||||
|
#[serde(rename = "op-time")]
|
||||||
|
pub op_time: String,
|
||||||
|
|
||||||
|
/// Package version string
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
/// Catalog part entries
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub catalog_parts: HashMap<String, HashMap<String, Vec<String>>>,
|
||||||
|
|
||||||
|
/// Optional SHA-1 signature of the package manifest
|
||||||
|
#[serde(rename = "signature-sha-1", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub signature_sha1: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update log
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct UpdateLog {
|
||||||
|
/// Optional signature information
|
||||||
|
#[serde(rename = "_SIGNATURE", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub signature: Option<HashMap<String, String>>,
|
||||||
|
|
||||||
|
/// Updates by publisher and stem
|
||||||
|
pub updates: HashMap<String, HashMap<String, Vec<PackageUpdateEntry>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateLog {
|
||||||
|
/// Create a new update log
|
||||||
|
pub fn new() -> Self {
|
||||||
|
UpdateLog {
|
||||||
|
signature: None,
|
||||||
|
updates: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a package update to the log
|
||||||
|
pub fn add_update(
|
||||||
|
&mut self,
|
||||||
|
publisher: &str,
|
||||||
|
fmri: &Fmri,
|
||||||
|
op_type: CatalogOperationType,
|
||||||
|
catalog_parts: HashMap<String, HashMap<String, Vec<String>>>,
|
||||||
|
signature: Option<String>,
|
||||||
|
) {
|
||||||
|
let publisher_updates = self.updates.entry(publisher.to_string()).or_insert_with(HashMap::new);
|
||||||
|
let stem_updates = publisher_updates.entry(fmri.stem().to_string()).or_insert_with(Vec::new);
|
||||||
|
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let timestamp = format_iso8601_basic(&now);
|
||||||
|
|
||||||
|
stem_updates.push(PackageUpdateEntry {
|
||||||
|
op_type,
|
||||||
|
op_time: timestamp,
|
||||||
|
version: fmri.version(),
|
||||||
|
catalog_parts,
|
||||||
|
signature_sha1: signature,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save update log to a file
|
||||||
|
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
|
let json = serde_json::to_string_pretty(self)?;
|
||||||
|
fs::write(path, json)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load update log from a file
|
||||||
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
|
let json = fs::read_to_string(path)?;
|
||||||
|
let log: UpdateLog = serde_json::from_str(&json)?;
|
||||||
|
Ok(log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Catalog manager
|
||||||
|
pub struct CatalogManager {
|
||||||
|
/// Path to the catalog directory
|
||||||
|
catalog_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Catalog attributes
|
||||||
|
attrs: CatalogAttrs,
|
||||||
|
|
||||||
|
/// Catalog parts
|
||||||
|
parts: HashMap<String, CatalogPart>,
|
||||||
|
|
||||||
|
/// Update logs
|
||||||
|
update_logs: HashMap<String, UpdateLog>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CatalogManager {
|
||||||
|
/// Create a new catalog manager
|
||||||
|
pub fn new<P: AsRef<Path>>(catalog_dir: P) -> Result<Self> {
|
||||||
|
let catalog_dir = catalog_dir.as_ref().to_path_buf();
|
||||||
|
|
||||||
|
// Create catalog directory if it doesn't exist
|
||||||
|
if !catalog_dir.exists() {
|
||||||
|
fs::create_dir_all(&catalog_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to load existing catalog attributes
|
||||||
|
let attrs_path = catalog_dir.join("catalog.attrs");
|
||||||
|
let attrs = if attrs_path.exists() {
|
||||||
|
CatalogAttrs::load(&attrs_path)?
|
||||||
|
} else {
|
||||||
|
CatalogAttrs::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(CatalogManager {
|
||||||
|
catalog_dir,
|
||||||
|
attrs,
|
||||||
|
parts: HashMap::new(),
|
||||||
|
update_logs: HashMap::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get catalog attributes
|
||||||
|
pub fn attrs(&self) -> &CatalogAttrs {
|
||||||
|
&self.attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get mutable catalog attributes
|
||||||
|
pub fn attrs_mut(&mut self) -> &mut CatalogAttrs {
|
||||||
|
&mut self.attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a catalog part
|
||||||
|
pub fn get_part(&self, name: &str) -> Option<&CatalogPart> {
|
||||||
|
self.parts.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable catalog part
|
||||||
|
pub fn get_part_mut(&mut self, name: &str) -> Option<&mut CatalogPart> {
|
||||||
|
self.parts.get_mut(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a catalog part
|
||||||
|
pub fn load_part(&mut self, name: &str) -> Result<()> {
|
||||||
|
let part_path = self.catalog_dir.join(name);
|
||||||
|
if part_path.exists() {
|
||||||
|
let part = CatalogPart::load(&part_path)?;
|
||||||
|
self.parts.insert(name.to_string(), part);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("Catalog part does not exist: {}", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save a catalog part
|
||||||
|
pub fn save_part(&self, name: &str) -> Result<()> {
|
||||||
|
if let Some(part) = self.parts.get(name) {
|
||||||
|
let part_path = self.catalog_dir.join(name);
|
||||||
|
part.save(&part_path)?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("Catalog part not loaded: {}", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new catalog part
|
||||||
|
pub fn create_part(&mut self, name: &str) -> &mut CatalogPart {
|
||||||
|
self.parts.entry(name.to_string()).or_insert_with(CatalogPart::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save catalog attributes
|
||||||
|
pub fn save_attrs(&self) -> Result<()> {
|
||||||
|
let attrs_path = self.catalog_dir.join("catalog.attrs");
|
||||||
|
self.attrs.save(&attrs_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new update log
|
||||||
|
pub fn create_update_log(&mut self, name: &str) -> &mut UpdateLog {
|
||||||
|
self.update_logs.entry(name.to_string()).or_insert_with(UpdateLog::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save an update log
|
||||||
|
pub fn save_update_log(&self, name: &str) -> Result<()> {
|
||||||
|
if let Some(log) = self.update_logs.get(name) {
|
||||||
|
let log_path = self.catalog_dir.join(name);
|
||||||
|
log.save(&log_path)?;
|
||||||
|
|
||||||
|
// Update catalog attributes
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let timestamp = format_iso8601_basic(&now);
|
||||||
|
|
||||||
|
let mut attrs = self.attrs.clone();
|
||||||
|
attrs.updates.insert(name.to_string(), UpdateLogInfo {
|
||||||
|
last_modified: timestamp,
|
||||||
|
signature_sha1: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let attrs_path = self.catalog_dir.join("catalog.attrs");
|
||||||
|
attrs.save(&attrs_path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("Update log not loaded: {}", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load an update log
|
||||||
|
pub fn load_update_log(&mut self, name: &str) -> Result<()> {
|
||||||
|
let log_path = self.catalog_dir.join(name);
|
||||||
|
if log_path.exists() {
|
||||||
|
let log = UpdateLog::load(&log_path)?;
|
||||||
|
self.update_logs.insert(name.to_string(), log);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("Update log does not exist: {}", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an update log
|
||||||
|
pub fn get_update_log(&self, name: &str) -> Option<&UpdateLog> {
|
||||||
|
self.update_logs.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable update log
|
||||||
|
pub fn get_update_log_mut(&mut self, name: &str) -> Option<&mut UpdateLog> {
|
||||||
|
self.update_logs.get_mut(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ use flate2::Compression as GzipCompression;
|
||||||
use lz4::EncoderBuilder;
|
use lz4::EncoderBuilder;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::cell::RefCell;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use crate::actions::{Manifest, File as FileAction};
|
use crate::actions::{Manifest, File as FileAction};
|
||||||
|
|
@ -69,16 +70,17 @@ impl SearchIndex {
|
||||||
let fmri = package.fmri.to_string();
|
let fmri = package.fmri.to_string();
|
||||||
|
|
||||||
// Add the package name as a term
|
// Add the package name as a term
|
||||||
self.add_term(&package.fmri.name, &fmri, &package.fmri.name);
|
self.add_term(package.fmri.stem(), &fmri, package.fmri.stem());
|
||||||
|
|
||||||
// Add the publisher as a term if available
|
// Add the publisher as a term if available
|
||||||
if let Some(publisher) = &package.fmri.publisher {
|
if let Some(publisher) = &package.fmri.publisher {
|
||||||
self.add_term(publisher, &fmri, &package.fmri.name);
|
self.add_term(publisher, &fmri, package.fmri.stem());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the version as a term if available
|
// Add the version as a term if available
|
||||||
if let Some(version) = &package.fmri.version {
|
let version = package.fmri.version();
|
||||||
self.add_term(&version.to_string(), &fmri, &package.fmri.name);
|
if !version.is_empty() {
|
||||||
|
self.add_term(&version, &fmri, &package.fmri.stem());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add contents if available
|
// Add contents if available
|
||||||
|
|
@ -86,21 +88,21 @@ impl SearchIndex {
|
||||||
// Add files
|
// Add files
|
||||||
if let Some(files) = &content.files {
|
if let Some(files) = &content.files {
|
||||||
for file in files {
|
for file in files {
|
||||||
self.add_term(file, &fmri, &package.fmri.name);
|
self.add_term(file, &fmri, package.fmri.stem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add directories
|
// Add directories
|
||||||
if let Some(directories) = &content.directories {
|
if let Some(directories) = &content.directories {
|
||||||
for dir in directories {
|
for dir in directories {
|
||||||
self.add_term(dir, &fmri, &package.fmri.name);
|
self.add_term(dir, &fmri, package.fmri.stem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add dependencies
|
// Add dependencies
|
||||||
if let Some(dependencies) = &content.dependencies {
|
if let Some(dependencies) = &content.dependencies {
|
||||||
for dep in dependencies {
|
for dep in dependencies {
|
||||||
self.add_term(dep, &fmri, &package.fmri.name);
|
self.add_term(dep, &fmri, package.fmri.stem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -197,6 +199,8 @@ impl SearchIndex {
|
||||||
pub struct FileBackend {
|
pub struct FileBackend {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub config: RepositoryConfig,
|
pub config: RepositoryConfig,
|
||||||
|
/// Catalog manager for handling catalog operations
|
||||||
|
catalog_manager: Option<crate::repository::catalog::CatalogManager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format a SystemTime as an ISO 8601 timestamp string
|
/// Format a SystemTime as an ISO 8601 timestamp string
|
||||||
|
|
@ -475,6 +479,7 @@ impl Repository for FileBackend {
|
||||||
let repo = FileBackend {
|
let repo = FileBackend {
|
||||||
path: path.to_path_buf(),
|
path: path.to_path_buf(),
|
||||||
config,
|
config,
|
||||||
|
catalog_manager: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the repository directories
|
// Create the repository directories
|
||||||
|
|
@ -503,6 +508,7 @@ impl Repository for FileBackend {
|
||||||
Ok(FileBackend {
|
Ok(FileBackend {
|
||||||
path: path.to_path_buf(),
|
path: path.to_path_buf(),
|
||||||
config,
|
config,
|
||||||
|
catalog_manager: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -700,14 +706,14 @@ impl Repository for FileBackend {
|
||||||
match Regex::new(pat) {
|
match Regex::new(pat) {
|
||||||
Ok(regex) => {
|
Ok(regex) => {
|
||||||
// Use regex matching
|
// Use regex matching
|
||||||
if !regex.is_match(&parsed_fmri.name) {
|
if !regex.is_match(parsed_fmri.stem()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// Log the error but fall back to simple string contains
|
// Log the error but fall back to simple string contains
|
||||||
eprintln!("Error compiling regex pattern '{}': {}", pat, err);
|
eprintln!("Error compiling regex pattern '{}': {}", pat, err);
|
||||||
if !parsed_fmri.name.contains(pat) {
|
if !parsed_fmri.stem().contains(pat) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -827,14 +833,14 @@ impl Repository for FileBackend {
|
||||||
match Regex::new(pat) {
|
match Regex::new(pat) {
|
||||||
Ok(regex) => {
|
Ok(regex) => {
|
||||||
// Use regex matching
|
// Use regex matching
|
||||||
if !regex.is_match(&parsed_fmri.name) {
|
if !regex.is_match(parsed_fmri.stem()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// Log the error but fall back to simple string contains
|
// Log the error but fall back to simple string contains
|
||||||
eprintln!("Error compiling regex pattern '{}': {}", pat, err);
|
eprintln!("Error compiling regex pattern '{}': {}", pat, err);
|
||||||
if !parsed_fmri.name.contains(pat) {
|
if !parsed_fmri.stem().contains(pat) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -842,10 +848,11 @@ impl Repository for FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format the package identifier using the FMRI
|
// Format the package identifier using the FMRI
|
||||||
pkg_id = if let Some(version) = &parsed_fmri.version {
|
let version = parsed_fmri.version();
|
||||||
format!("{}@{}", parsed_fmri.name, version)
|
pkg_id = if !version.is_empty() {
|
||||||
|
format!("{}@{}", parsed_fmri.stem(), version)
|
||||||
} else {
|
} else {
|
||||||
parsed_fmri.name.clone()
|
parsed_fmri.stem().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -1093,7 +1100,7 @@ impl Repository for FileBackend {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|pkg| {
|
.filter(|pkg| {
|
||||||
// Match against package name
|
// Match against package name
|
||||||
pkg.fmri.name.contains(query)
|
pkg.fmri.stem().contains(query)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
@ -1124,6 +1131,226 @@ impl FileBackend {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get or initialize the catalog manager
|
||||||
|
fn get_catalog_manager(&mut self) -> Result<&mut crate::repository::catalog::CatalogManager> {
|
||||||
|
if self.catalog_manager.is_none() {
|
||||||
|
let catalog_dir = self.path.join("catalog");
|
||||||
|
self.catalog_manager = Some(crate::repository::catalog::CatalogManager::new(&catalog_dir)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.catalog_manager.as_mut().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// URL encode a string for use in a filename
|
||||||
|
fn url_encode(s: &str) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
for c in s.chars() {
|
||||||
|
match c {
|
||||||
|
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '~' => result.push(c),
|
||||||
|
' ' => result.push('+'),
|
||||||
|
_ => {
|
||||||
|
result.push('%');
|
||||||
|
result.push_str(&format!("{:02X}", c as u8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate catalog parts for a publisher
|
||||||
|
fn generate_catalog_parts(&mut self, publisher: &str, create_update_log: bool) -> Result<()> {
|
||||||
|
println!("Generating catalog parts for publisher: {}", publisher);
|
||||||
|
|
||||||
|
// Collect package data first
|
||||||
|
let repo_path = self.path.clone();
|
||||||
|
let packages = self.list_packages(Some(publisher), None)?;
|
||||||
|
|
||||||
|
// Prepare data structures for catalog parts
|
||||||
|
let mut base_entries = Vec::new();
|
||||||
|
let mut dependency_entries = Vec::new();
|
||||||
|
let mut summary_entries = Vec::new();
|
||||||
|
let mut update_entries = Vec::new();
|
||||||
|
|
||||||
|
// Track package counts
|
||||||
|
let mut package_count = 0;
|
||||||
|
let mut package_version_count = 0;
|
||||||
|
|
||||||
|
// Process each package
|
||||||
|
for package in packages {
|
||||||
|
let fmri = &package.fmri;
|
||||||
|
let stem = fmri.stem();
|
||||||
|
|
||||||
|
// Skip if no version
|
||||||
|
if fmri.version().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the package manifest
|
||||||
|
let pkg_dir = repo_path.join("pkg").join(publisher).join(stem);
|
||||||
|
if !pkg_dir.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the package version
|
||||||
|
let version = fmri.version();
|
||||||
|
let encoded_version = Self::url_encode(&version);
|
||||||
|
let manifest_path = pkg_dir.join(encoded_version);
|
||||||
|
|
||||||
|
if !manifest_path.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the manifest
|
||||||
|
let manifest_content = std::fs::read_to_string(&manifest_path)?;
|
||||||
|
let manifest = crate::actions::Manifest::parse_string(manifest_content.clone())?;
|
||||||
|
|
||||||
|
// Calculate SHA-256 hash of the manifest (as a substitute for SHA-1)
|
||||||
|
let mut hasher = sha2::Sha256::new();
|
||||||
|
hasher.update(manifest_content.as_bytes());
|
||||||
|
let signature = format!("{:x}", hasher.finalize());
|
||||||
|
|
||||||
|
// Add to base entries
|
||||||
|
base_entries.push((fmri.clone(), None, signature.clone()));
|
||||||
|
|
||||||
|
// Extract dependency actions
|
||||||
|
let mut dependency_actions = Vec::new();
|
||||||
|
for dep in &manifest.dependencies {
|
||||||
|
if let Some(dep_fmri) = &dep.fmri {
|
||||||
|
dependency_actions.push(format!("depend fmri={} type={}", dep_fmri, dep.dependency_type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract variant and facet actions
|
||||||
|
for attr in &manifest.attributes {
|
||||||
|
if attr.key.starts_with("variant.") || attr.key.starts_with("facet.") {
|
||||||
|
let values_str = attr.values.join(" value=");
|
||||||
|
dependency_actions.push(format!("set name={} value={}", attr.key, values_str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to dependency entries if there are dependency actions
|
||||||
|
if !dependency_actions.is_empty() {
|
||||||
|
dependency_entries.push((fmri.clone(), Some(dependency_actions.clone()), signature.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract summary actions (set actions excluding variants and facets)
|
||||||
|
let mut summary_actions = Vec::new();
|
||||||
|
for attr in &manifest.attributes {
|
||||||
|
if !attr.key.starts_with("variant.") && !attr.key.starts_with("facet.") {
|
||||||
|
let values_str = attr.values.join(" value=");
|
||||||
|
summary_actions.push(format!("set name={} value={}", attr.key, values_str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to summary entries if there are summary actions
|
||||||
|
if !summary_actions.is_empty() {
|
||||||
|
summary_entries.push((fmri.clone(), Some(summary_actions.clone()), signature.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare update entry if needed
|
||||||
|
if create_update_log {
|
||||||
|
let mut catalog_parts = std::collections::HashMap::new();
|
||||||
|
|
||||||
|
// Add dependency actions to update entry
|
||||||
|
if !dependency_actions.is_empty() {
|
||||||
|
let mut actions = std::collections::HashMap::new();
|
||||||
|
actions.insert("actions".to_string(), dependency_actions);
|
||||||
|
catalog_parts.insert("catalog.dependency.C".to_string(), actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add summary actions to update entry
|
||||||
|
if !summary_actions.is_empty() {
|
||||||
|
let mut actions = std::collections::HashMap::new();
|
||||||
|
actions.insert("actions".to_string(), summary_actions);
|
||||||
|
catalog_parts.insert("catalog.summary.C".to_string(), actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to update entries
|
||||||
|
update_entries.push((fmri.clone(), catalog_parts, signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update counts
|
||||||
|
package_count += 1;
|
||||||
|
package_version_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now get the catalog manager and create the catalog parts
|
||||||
|
let catalog_manager = self.get_catalog_manager()?;
|
||||||
|
|
||||||
|
// Create and populate the base part
|
||||||
|
let base_part_name = "catalog.base.C".to_string();
|
||||||
|
let base_part = catalog_manager.create_part(&base_part_name);
|
||||||
|
for (fmri, actions, signature) in base_entries {
|
||||||
|
base_part.add_package(publisher, &fmri, actions, Some(signature));
|
||||||
|
}
|
||||||
|
catalog_manager.save_part(&base_part_name)?;
|
||||||
|
|
||||||
|
// Create and populate dependency part
|
||||||
|
let dependency_part_name = "catalog.dependency.C".to_string();
|
||||||
|
let dependency_part = catalog_manager.create_part(&dependency_part_name);
|
||||||
|
for (fmri, actions, signature) in dependency_entries {
|
||||||
|
dependency_part.add_package(publisher, &fmri, actions, Some(signature));
|
||||||
|
}
|
||||||
|
catalog_manager.save_part(&dependency_part_name)?;
|
||||||
|
|
||||||
|
// Create and populate summary part
|
||||||
|
let summary_part_name = "catalog.summary.C".to_string();
|
||||||
|
let summary_part = catalog_manager.create_part(&summary_part_name);
|
||||||
|
for (fmri, actions, signature) in summary_entries {
|
||||||
|
summary_part.add_package(publisher, &fmri, actions, Some(signature));
|
||||||
|
}
|
||||||
|
catalog_manager.save_part(&summary_part_name)?;
|
||||||
|
|
||||||
|
// Create and populate the update log if needed
|
||||||
|
if create_update_log {
|
||||||
|
let now = std::time::SystemTime::now();
|
||||||
|
let timestamp = format_iso8601_timestamp(&now);
|
||||||
|
let update_log_name = format!("update.{}Z.C", timestamp.split('.').next().unwrap());
|
||||||
|
|
||||||
|
let update_log = catalog_manager.create_update_log(&update_log_name);
|
||||||
|
for (fmri, catalog_parts, signature) in update_entries {
|
||||||
|
update_log.add_update(
|
||||||
|
publisher,
|
||||||
|
&fmri,
|
||||||
|
crate::repository::catalog::CatalogOperationType::Add,
|
||||||
|
catalog_parts,
|
||||||
|
Some(signature),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catalog_manager.save_update_log(&update_log_name)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update catalog attributes
|
||||||
|
let now = std::time::SystemTime::now();
|
||||||
|
let timestamp = format_iso8601_timestamp(&now);
|
||||||
|
|
||||||
|
let attrs = catalog_manager.attrs_mut();
|
||||||
|
attrs.last_modified = timestamp.clone();
|
||||||
|
attrs.package_count = package_count;
|
||||||
|
attrs.package_version_count = package_version_count;
|
||||||
|
|
||||||
|
// Add part information
|
||||||
|
attrs.parts.insert(base_part_name.clone(), crate::repository::catalog::CatalogPartInfo {
|
||||||
|
last_modified: timestamp.clone(),
|
||||||
|
signature_sha1: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
attrs.parts.insert(dependency_part_name.clone(), crate::repository::catalog::CatalogPartInfo {
|
||||||
|
last_modified: timestamp.clone(),
|
||||||
|
signature_sha1: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
attrs.parts.insert(summary_part_name.clone(), crate::repository::catalog::CatalogPartInfo {
|
||||||
|
last_modified: timestamp.clone(),
|
||||||
|
signature_sha1: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save catalog attributes
|
||||||
|
catalog_manager.save_attrs()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a search index for a publisher
|
/// Build a search index for a publisher
|
||||||
fn build_search_index(&self, publisher: &str) -> Result<()> {
|
fn build_search_index(&self, publisher: &str) -> Result<()> {
|
||||||
println!("Building search index for publisher: {}", publisher);
|
println!("Building search index for publisher: {}", publisher);
|
||||||
|
|
@ -1160,10 +1387,11 @@ impl FileBackend {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a PackageContents struct
|
// Create a PackageContents struct
|
||||||
let package_id = if let Some(version) = &parsed_fmri.version {
|
let version = parsed_fmri.version();
|
||||||
format!("{}@{}", parsed_fmri.name, version)
|
let package_id = if !version.is_empty() {
|
||||||
|
format!("{}@{}", parsed_fmri.stem(), version)
|
||||||
} else {
|
} else {
|
||||||
parsed_fmri.name.clone()
|
parsed_fmri.stem().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract content information
|
// Extract content information
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
mod file_backend;
|
mod file_backend;
|
||||||
mod rest_backend;
|
mod rest_backend;
|
||||||
|
mod catalog;
|
||||||
|
|
||||||
pub use file_backend::FileBackend;
|
pub use file_backend::FileBackend;
|
||||||
pub use rest_backend::RestBackend;
|
pub use rest_backend::RestBackend;
|
||||||
|
pub use catalog::{CatalogManager, CatalogAttrs, CatalogPart, UpdateLog, CatalogOperationType};
|
||||||
|
|
||||||
/// Repository configuration filename
|
/// Repository configuration filename
|
||||||
pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository";
|
pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository";
|
||||||
|
|
|
||||||
|
|
@ -212,10 +212,11 @@ impl Repository for RestBackend {
|
||||||
// In a real implementation, we would get this information from the REST API
|
// In a real implementation, we would get this information from the REST API
|
||||||
|
|
||||||
// Format the package identifier using the FMRI
|
// Format the package identifier using the FMRI
|
||||||
let pkg_id = if let Some(version) = &pkg_info.fmri.version {
|
let version = pkg_info.fmri.version();
|
||||||
format!("{}@{}", pkg_info.fmri.name, version)
|
let pkg_id = if !version.is_empty() {
|
||||||
|
format!("{}@{}", pkg_info.fmri.stem(), version)
|
||||||
} else {
|
} else {
|
||||||
pkg_info.fmri.name.clone()
|
pkg_info.fmri.stem().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Example content for each type
|
// Example content for each type
|
||||||
|
|
|
||||||
|
|
@ -379,17 +379,14 @@ fn main() -> Result<()> {
|
||||||
// Print packages
|
// Print packages
|
||||||
for pkg_info in packages {
|
for pkg_info in packages {
|
||||||
// Format version and publisher, handling optional fields
|
// Format version and publisher, handling optional fields
|
||||||
let version_str = match &pkg_info.fmri.version {
|
let version_str = pkg_info.fmri.version();
|
||||||
Some(version) => version.to_string(),
|
|
||||||
None => String::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let publisher_str = match &pkg_info.fmri.publisher {
|
let publisher_str = match &pkg_info.fmri.publisher {
|
||||||
Some(publisher) => publisher.clone(),
|
Some(publisher) => publisher.clone(),
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("{:<30} {:<15} {:<10}", pkg_info.fmri.name, version_str, publisher_str);
|
println!("{:<30} {:<15} {:<10}", pkg_info.fmri.stem(), version_str, publisher_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue