2024-08-14 20:02:29 +02:00
|
|
|
mod properties;
|
2025-08-02 22:12:37 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests;
|
2024-08-14 20:02:29 +02:00
|
|
|
|
2025-07-26 15:33:39 +02:00
|
|
|
use miette::Diagnostic;
|
2025-07-27 15:22:49 +02:00
|
|
|
use properties::*;
|
2025-07-26 12:54:01 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-08-14 20:02:29 +02:00
|
|
|
use std::collections::HashMap;
|
2025-08-02 22:12:37 +02:00
|
|
|
use std::fs::{self, File};
|
2024-08-14 20:02:29 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
use thiserror::Error;
|
|
|
|
|
|
2025-07-26 15:33:39 +02:00
|
|
|
#[derive(Debug, Error, Diagnostic)]
|
2024-08-14 20:02:29 +02:00
|
|
|
pub enum ImageError {
|
2025-07-26 15:33:39 +02:00
|
|
|
#[error("I/O error: {0}")]
|
|
|
|
|
#[diagnostic(
|
|
|
|
|
code(ips::image_error::io),
|
|
|
|
|
help("Check system resources and permissions")
|
|
|
|
|
)]
|
2024-08-14 20:02:29 +02:00
|
|
|
IO(#[from] std::io::Error),
|
2025-07-27 15:22:49 +02:00
|
|
|
|
2025-07-26 15:33:39 +02:00
|
|
|
#[error("JSON error: {0}")]
|
|
|
|
|
#[diagnostic(
|
|
|
|
|
code(ips::image_error::json),
|
|
|
|
|
help("Check the JSON format and try again")
|
|
|
|
|
)]
|
2024-08-14 20:02:29 +02:00
|
|
|
Json(#[from] serde_json::Error),
|
2025-08-02 22:12:37 +02:00
|
|
|
|
|
|
|
|
#[error("Invalid image path: {0}")]
|
|
|
|
|
#[diagnostic(
|
|
|
|
|
code(ips::image_error::invalid_path),
|
|
|
|
|
help("Provide a valid path for the image")
|
|
|
|
|
)]
|
|
|
|
|
InvalidPath(String),
|
2024-08-14 20:02:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub type Result<T> = std::result::Result<T, ImageError>;
|
|
|
|
|
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Type of image, either Full (base path of "/") or Partial (attached to a full image)
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
|
|
|
|
pub enum ImageType {
|
|
|
|
|
/// Full image with base path of "/"
|
|
|
|
|
Full,
|
|
|
|
|
/// Partial image attached to a full image
|
|
|
|
|
Partial,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Represents an IPS image, which can be either a Full image or a Partial image
|
2024-08-14 20:02:29 +02:00
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
|
|
|
pub struct Image {
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Path to the image
|
2024-08-14 20:02:29 +02:00
|
|
|
path: PathBuf,
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Type of image (Full or Partial)
|
|
|
|
|
image_type: ImageType,
|
|
|
|
|
/// Image properties
|
2024-08-14 20:02:29 +02:00
|
|
|
props: Vec<ImageProperty>,
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Image version
|
2024-08-14 20:02:29 +02:00
|
|
|
version: i32,
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Variants
|
2024-08-14 20:02:29 +02:00
|
|
|
variants: HashMap<String, String>,
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Mediators
|
2024-08-14 20:02:29 +02:00
|
|
|
mediators: HashMap<String, String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Image {
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Creates a new Full image at the specified path
|
|
|
|
|
pub fn new_full<P: Into<PathBuf>>(path: P) -> Image {
|
2025-07-26 12:54:01 +02:00
|
|
|
Image {
|
2024-08-14 20:02:29 +02:00
|
|
|
path: path.into(),
|
2025-08-02 22:12:37 +02:00
|
|
|
image_type: ImageType::Full,
|
2024-08-14 20:02:29 +02:00
|
|
|
version: 5,
|
|
|
|
|
variants: HashMap::new(),
|
|
|
|
|
mediators: HashMap::new(),
|
|
|
|
|
props: vec![],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Creates a new Partial image at the specified path
|
|
|
|
|
pub fn new_partial<P: Into<PathBuf>>(path: P) -> Image {
|
|
|
|
|
Image {
|
|
|
|
|
path: path.into(),
|
|
|
|
|
image_type: ImageType::Partial,
|
|
|
|
|
version: 5,
|
|
|
|
|
variants: HashMap::new(),
|
|
|
|
|
mediators: HashMap::new(),
|
|
|
|
|
props: vec![],
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-14 20:02:29 +02:00
|
|
|
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Returns the path to the image
|
|
|
|
|
pub fn path(&self) -> &Path {
|
|
|
|
|
&self.path
|
2024-08-14 20:02:29 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-02 22:12:37 +02:00
|
|
|
/// Returns the type of the image
|
|
|
|
|
pub fn image_type(&self) -> &ImageType {
|
|
|
|
|
&self.image_type
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns the path to the metadata directory for this image
|
|
|
|
|
pub fn metadata_dir(&self) -> PathBuf {
|
|
|
|
|
match self.image_type {
|
|
|
|
|
ImageType::Full => self.path.join("var/pkg"),
|
|
|
|
|
ImageType::Partial => self.path.join(".pkg"),
|
2024-08-14 20:02:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-02 22:12:37 +02:00
|
|
|
|
|
|
|
|
/// Returns the path to the image JSON file
|
|
|
|
|
pub fn image_json_path(&self) -> PathBuf {
|
|
|
|
|
self.metadata_dir().join("pkg6.image.json")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Creates the metadata directory if it doesn't exist
|
|
|
|
|
pub fn create_metadata_dir(&self) -> Result<()> {
|
|
|
|
|
let metadata_dir = self.metadata_dir();
|
|
|
|
|
fs::create_dir_all(&metadata_dir).map_err(|e| {
|
|
|
|
|
ImageError::IO(std::io::Error::new(
|
|
|
|
|
std::io::ErrorKind::Other,
|
|
|
|
|
format!("Failed to create metadata directory at {:?}: {}", metadata_dir, e),
|
|
|
|
|
))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Saves the image data to the metadata directory
|
|
|
|
|
pub fn save(&self) -> Result<()> {
|
|
|
|
|
self.create_metadata_dir()?;
|
|
|
|
|
let json_path = self.image_json_path();
|
|
|
|
|
let file = File::create(&json_path).map_err(|e| {
|
|
|
|
|
ImageError::IO(std::io::Error::new(
|
|
|
|
|
std::io::ErrorKind::Other,
|
|
|
|
|
format!("Failed to create image JSON file at {:?}: {}", json_path, e),
|
|
|
|
|
))
|
|
|
|
|
})?;
|
|
|
|
|
serde_json::to_writer_pretty(file, self).map_err(ImageError::Json)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Loads an image from the specified path
|
|
|
|
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
|
|
|
|
let path = path.as_ref();
|
|
|
|
|
|
|
|
|
|
// Check for both full and partial image JSON files
|
|
|
|
|
let full_image = Image::new_full(path);
|
|
|
|
|
let partial_image = Image::new_partial(path);
|
|
|
|
|
|
|
|
|
|
let full_json_path = full_image.image_json_path();
|
|
|
|
|
let partial_json_path = partial_image.image_json_path();
|
|
|
|
|
|
|
|
|
|
// Determine which JSON file exists
|
|
|
|
|
let json_path = if full_json_path.exists() {
|
|
|
|
|
full_json_path
|
|
|
|
|
} else if partial_json_path.exists() {
|
|
|
|
|
partial_json_path
|
|
|
|
|
} else {
|
|
|
|
|
return Err(ImageError::InvalidPath(format!(
|
|
|
|
|
"Image JSON file not found at either {:?} or {:?}",
|
|
|
|
|
full_json_path, partial_json_path
|
|
|
|
|
)));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let file = File::open(&json_path).map_err(|e| {
|
|
|
|
|
ImageError::IO(std::io::Error::new(
|
|
|
|
|
std::io::ErrorKind::Other,
|
|
|
|
|
format!("Failed to open image JSON file at {:?}: {}", json_path, e),
|
|
|
|
|
))
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
serde_json::from_reader(file).map_err(ImageError::Json)
|
|
|
|
|
}
|
2025-07-26 12:54:01 +02:00
|
|
|
}
|