2025-12-08 20:50:20 +01:00
|
|
|
use crate::config::Config;
|
2025-12-22 20:10:17 +01:00
|
|
|
use crate::errors::{DepotError, Result};
|
2025-12-08 20:50:20 +01:00
|
|
|
use libips::fmri::Fmri;
|
2026-03-23 17:27:36 +01:00
|
|
|
use libips::repository::{FileBackend, IndexEntry, PackageInfo, ReadableRepository, WritableRepository};
|
2025-12-22 20:10:17 +01:00
|
|
|
use std::path::PathBuf;
|
2025-12-08 20:50:20 +01:00
|
|
|
use std::sync::Mutex;
|
|
|
|
|
|
|
|
|
|
pub struct DepotRepo {
|
|
|
|
|
pub backend: Mutex<FileBackend>,
|
|
|
|
|
pub root: PathBuf,
|
2025-12-09 20:23:00 +01:00
|
|
|
pub cache_max_age: u64,
|
2025-12-08 20:50:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DepotRepo {
|
|
|
|
|
pub fn new(config: &Config) -> Result<Self> {
|
|
|
|
|
let root = config.repository.root.clone();
|
|
|
|
|
let backend = FileBackend::open(&root).map_err(DepotError::Repo)?;
|
2025-12-22 20:10:17 +01:00
|
|
|
let cache_max_age = config.server.cache_max_age.unwrap_or(3600);
|
|
|
|
|
Ok(Self {
|
|
|
|
|
backend: Mutex::new(backend),
|
|
|
|
|
root,
|
|
|
|
|
cache_max_age,
|
|
|
|
|
})
|
2025-12-08 20:50:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 12:51:55 +01:00
|
|
|
pub fn search(
|
|
|
|
|
&self,
|
|
|
|
|
publisher: Option<&str>,
|
|
|
|
|
query: &str,
|
|
|
|
|
case_sensitive: bool,
|
|
|
|
|
) -> Result<Vec<IndexEntry>> {
|
2026-01-18 12:29:44 +01:00
|
|
|
let backend = self
|
|
|
|
|
.backend
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| DepotError::Server(format!("Lock poisoned: {}", e)))?;
|
|
|
|
|
backend
|
|
|
|
|
.search_detailed(query, publisher, None, case_sensitive)
|
|
|
|
|
.map_err(DepotError::Repo)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 20:50:20 +01:00
|
|
|
pub fn get_catalog_path(&self, publisher: &str) -> PathBuf {
|
|
|
|
|
FileBackend::construct_catalog_path(&self.root, publisher)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_file_path(&self, publisher: &str, hash: &str) -> Option<PathBuf> {
|
2025-12-22 20:10:17 +01:00
|
|
|
let cand_pub = FileBackend::construct_file_path_with_publisher(&self.root, publisher, hash);
|
|
|
|
|
if cand_pub.exists() {
|
|
|
|
|
return Some(cand_pub);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let cand_global = FileBackend::construct_file_path(&self.root, hash);
|
|
|
|
|
if cand_global.exists() {
|
|
|
|
|
return Some(cand_global);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
2025-12-08 20:50:20 +01:00
|
|
|
}
|
2025-12-22 20:10:17 +01:00
|
|
|
|
2025-12-08 20:50:20 +01:00
|
|
|
pub fn get_manifest_text(&self, publisher: &str, fmri: &Fmri) -> Result<String> {
|
2026-02-05 21:56:53 +01:00
|
|
|
let backend = self
|
2025-12-22 20:10:17 +01:00
|
|
|
.backend
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| DepotError::Server(format!("Lock poisoned: {}", e)))?;
|
|
|
|
|
backend
|
|
|
|
|
.fetch_manifest_text(publisher, fmri)
|
|
|
|
|
.map_err(DepotError::Repo)
|
2025-12-08 20:50:20 +01:00
|
|
|
}
|
2025-12-08 21:36:37 +01:00
|
|
|
|
2025-12-09 20:23:00 +01:00
|
|
|
pub fn get_manifest_path(&self, publisher: &str, fmri: &Fmri) -> Option<PathBuf> {
|
|
|
|
|
let version = fmri.version();
|
|
|
|
|
if version.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2025-12-22 20:10:17 +01:00
|
|
|
let path =
|
|
|
|
|
FileBackend::construct_manifest_path(&self.root, publisher, fmri.stem(), &version);
|
|
|
|
|
if path.exists() {
|
|
|
|
|
return Some(path);
|
|
|
|
|
}
|
2025-12-09 20:23:00 +01:00
|
|
|
// Fallbacks similar to lib logic
|
|
|
|
|
let encoded_stem = url_encode_filename(fmri.stem());
|
|
|
|
|
let encoded_version = url_encode_filename(&version);
|
2025-12-22 20:10:17 +01:00
|
|
|
let alt1 = self
|
|
|
|
|
.root
|
|
|
|
|
.join("pkg")
|
|
|
|
|
.join(&encoded_stem)
|
|
|
|
|
.join(&encoded_version);
|
|
|
|
|
if alt1.exists() {
|
|
|
|
|
return Some(alt1);
|
|
|
|
|
}
|
|
|
|
|
let alt2 = self
|
|
|
|
|
.root
|
|
|
|
|
.join("publisher")
|
|
|
|
|
.join(publisher)
|
|
|
|
|
.join("pkg")
|
|
|
|
|
.join(&encoded_stem)
|
|
|
|
|
.join(&encoded_version);
|
|
|
|
|
if alt2.exists() {
|
|
|
|
|
return Some(alt2);
|
|
|
|
|
}
|
2025-12-09 20:23:00 +01:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-23 17:27:36 +01:00
|
|
|
pub fn rebuild_index(&self, publisher: Option<&str>) -> Result<()> {
|
|
|
|
|
let backend = self
|
|
|
|
|
.backend
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| DepotError::Server(format!("Lock poisoned: {}", e)))?;
|
|
|
|
|
backend
|
|
|
|
|
.rebuild(publisher, true, false)
|
|
|
|
|
.map_err(DepotError::Repo)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 20:10:17 +01:00
|
|
|
pub fn cache_max_age(&self) -> u64 {
|
|
|
|
|
self.cache_max_age
|
|
|
|
|
}
|
2025-12-09 20:23:00 +01:00
|
|
|
|
2026-02-04 22:39:42 +01:00
|
|
|
pub fn shard_dir(&self, publisher: &str) -> PathBuf {
|
2026-02-04 22:40:51 +01:00
|
|
|
self.root.join("publisher").join(publisher).join("catalog2")
|
2026-02-04 22:39:42 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-08 22:45:39 +01:00
|
|
|
pub fn get_catalog_file_path(&self, publisher: &str, filename: &str) -> Result<PathBuf> {
|
2025-12-22 20:10:17 +01:00
|
|
|
let backend = self
|
|
|
|
|
.backend
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| DepotError::Server(format!("Lock poisoned: {}", e)))?;
|
|
|
|
|
backend
|
|
|
|
|
.get_catalog_file_path(publisher, filename)
|
|
|
|
|
.map_err(DepotError::Repo)
|
2025-12-08 21:36:37 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-15 21:55:10 +01:00
|
|
|
pub fn list_packages(
|
|
|
|
|
&self,
|
|
|
|
|
publisher: Option<&str>,
|
|
|
|
|
pattern: Option<&str>,
|
|
|
|
|
) -> Result<Vec<PackageInfo>> {
|
|
|
|
|
let backend = self
|
|
|
|
|
.backend
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| DepotError::Server(format!("Lock poisoned: {}", e)))?;
|
|
|
|
|
backend.list_packages(publisher, pattern).map_err(DepotError::Repo)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 21:36:37 +01:00
|
|
|
pub fn get_info(&self) -> Result<libips::repository::RepositoryInfo> {
|
2025-12-22 20:10:17 +01:00
|
|
|
let backend = self
|
|
|
|
|
.backend
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|e| DepotError::Server(format!("Lock poisoned: {}", e)))?;
|
2025-12-08 21:36:37 +01:00
|
|
|
backend.get_info().map_err(DepotError::Repo)
|
|
|
|
|
}
|
2025-12-08 20:50:20 +01:00
|
|
|
}
|
2025-12-09 20:23:00 +01:00
|
|
|
|
|
|
|
|
// Local percent-encoding for filenames similar to lib's private helper.
|
|
|
|
|
fn url_encode_filename(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('+'),
|
|
|
|
|
_ => {
|
|
|
|
|
let mut buf = [0u8; 4];
|
|
|
|
|
for b in c.encode_utf8(&mut buf).as_bytes() {
|
|
|
|
|
result.push('%');
|
|
|
|
|
result.push_str(&format!("{:02X}", b));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|