use crate::compo::Compo;
use crate::content::Content;
use crate::package::package::PackageTrait;
use crate::{Loader, PackageInfo, LIB_ROOT_FILENAME};
use bzip2_rs::DecoderReader;
use melodium_common::descriptor::{
Collection, Identifier, IdentifierRequirement, LoadingError, LoadingResult, PackageRequirement,
Version,
};
use std::collections::HashMap;
use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;
use tar::Archive;
#[derive(Debug)]
pub struct JeuPackage {
name: String,
version: Version,
requirements: Vec<PackageRequirement>,
entrypoints: HashMap<String, Identifier>,
contents: HashMap<PathBuf, Arc<Content>>,
}
impl JeuPackage {
pub fn new<R: Read>(mut reader: R) -> LoadingResult<Self> {
let mut result = LoadingResult::new_success(());
if let None = result.merge_degrade_failure(Self::skip_heading(&mut reader)) {
return Err(result.failure().unwrap().clone()).into();
}
let mut archive = Archive::new(DecoderReader::new(reader));
let mut composition = None;
let mut contents = HashMap::new();
for entry in match archive.entries() {
Ok(entries) => entries,
Err(err) => {
return result.and_degrade_failure(LoadingResult::new_failure(
LoadingError::jeu_format_error(225, err.to_string()),
))
}
} {
match entry {
Ok(mut entry) => match entry.path().map(|path| path.to_path_buf()) {
Ok(path) => {
if composition.is_none() && path.to_string_lossy() == "Compo.toml" {
let mut compo = String::new();
match entry.read_to_string(&mut compo) {
Ok(_) => {
if let Some(compo) =
result.merge_degrade_failure(Compo::parse(&compo))
{
composition = Some(compo);
}
}
Err(err) => {
result = result.and_degrade_failure(LoadingResult::new_failure(
LoadingError::jeu_format_error(229, err.to_string()),
))
}
}
} else if let Some(composition) = &composition {
let mut content = Vec::new();
match entry.read_to_end(&mut content) {
Ok(_) => {
if let Some(content) = result.merge_degrade_failure(
Content::new(
&if entry.path().unwrap()
== PathBuf::from(LIB_ROOT_FILENAME)
{
composition.name.clone()
} else {
format!(
"{}/{}",
composition.name,
path.to_string_lossy()
)
},
&content,
&composition.version,
&composition
.requirements
.iter()
.map(|pkg_req| {
(
pkg_req.package.clone(),
pkg_req.version_requirement.clone(),
)
})
.collect(),
)
.convert_failure_errors(|err| {
LoadingError::content_error(231, Arc::new(err))
}),
) {
contents.insert(path.to_path_buf(), Arc::new(content));
}
}
Err(err) => {
result = result.and_degrade_failure(LoadingResult::new_failure(
LoadingError::jeu_format_error(230, err.to_string()),
))
}
}
} else {
result = result.and_degrade_failure(LoadingResult::new_failure(
LoadingError::jeu_format_error(
231,
format!(
"Content '{}' appearing before 'Compo.toml'",
path.to_string_lossy()
),
),
));
}
}
Err(err) => {
result = result.and_degrade_failure(LoadingResult::new_failure(
LoadingError::jeu_format_error(228, err.to_string()),
))
}
},
Err(err) => {
result = result.and_degrade_failure(LoadingResult::new_failure(
LoadingError::jeu_format_error(226, err.to_string()),
))
}
}
}
if let Some(compo) = composition {
result.and(LoadingResult::new_success(Self {
name: compo.name,
entrypoints: compo.entrypoints,
version: compo.version,
requirements: compo.requirements,
contents,
}))
} else {
result.and_degrade_failure(LoadingResult::new_failure(LoadingError::jeu_format_error(
232,
"Jeu file does not contain 'Compo.toml' entry".to_string(),
)))
}
}
fn skip_heading<R: Read>(reader: &mut R) -> LoadingResult<()> {
let mut encountered_lf = 0;
let mut byte: u8 = 0;
while encountered_lf < 2 {
if let Ok(_) = reader.read_exact(std::slice::from_mut(&mut byte)) {
if byte == 0x0A {
encountered_lf += 1;
}
} else {
return LoadingResult::new_failure(LoadingError::jeu_format_error(
224,
"Data ended too early".to_string(),
));
}
}
LoadingResult::new_success(())
}
fn designation(identifier: &Identifier) -> PathBuf {
if identifier.path().len() == 1 {
PathBuf::from(LIB_ROOT_FILENAME)
} else {
PathBuf::from(format!(
"{}.mel",
identifier
.path()
.clone()
.into_iter()
.skip(1)
.collect::<Vec<_>>()
.join("/")
))
}
}
fn insure_loading(
loader: &Loader,
identifiers: Vec<IdentifierRequirement>,
) -> LoadingResult<()> {
let mut result = LoadingResult::new_success(());
for identifier in &identifiers {
result.merge_degrade_failure(loader.get_with_load(identifier));
}
result
}
#[cfg(feature = "filesystem")]
pub fn extract<R: Read>(mut reader: R, output: &std::path::Path) -> std::io::Result<()> {
if Self::skip_heading(&mut reader).is_failure() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Input data are not Jeu format",
));
}
let mut archive = Archive::new(DecoderReader::new(reader));
archive.unpack(&output)
}
#[cfg(feature = "filesystem")]
pub fn build<W: std::io::Write>(
fs_package: &super::FsPackage,
mut writer: std::io::BufWriter<W>,
) -> std::io::Result<()> {
use std::io::Write;
fs_package.all_contents();
writer.write_all(b"#!/usr/bin/env melodium\n#! version 0\n")?;
let mut tar_data = Vec::new();
{
let mut tar = tar::Builder::new(&mut tar_data);
let base_path = fs_package.path().to_path_buf();
{
let mut compo = std::fs::File::open({
let mut path = base_path.clone();
path.push("Compo.toml");
path
})?;
tar.append_file("Compo.toml", &mut compo)?;
}
for content_relative_path in fs_package.contents().keys() {
let content_full_path = {
let mut path = base_path.clone();
path.push(content_relative_path);
path
};
let mut content = std::fs::File::open(content_full_path)?;
tar.append_file(content_relative_path, &mut content)?;
}
}
banzai::encode(&*tar_data, writer, 9).map(|_| ())
}
}
impl PackageInfo for JeuPackage {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &semver::Version {
&self.version
}
fn requirements(&self) -> &Vec<PackageRequirement> {
&self.requirements
}
fn entrypoints(&self) -> &HashMap<String, Identifier> {
&self.entrypoints
}
}
impl PackageTrait for JeuPackage {
fn embedded_collection(&self, _loader: &Loader) -> LoadingResult<Collection> {
LoadingResult::new_success(Collection::new())
}
fn full_collection(&self, loader: &Loader) -> LoadingResult<Collection> {
let mut collection = Collection::new();
let mut result = LoadingResult::new_success(());
if let Some(identifiers) = result.merge_degrade_failure(self.all_identifiers(loader)) {
for identifier in &identifiers {
let identifier_requirement = identifier.into();
if collection.get(&identifier_requirement).is_none() {
if let Some(specific_collection) =
result.merge_degrade_failure(self.element(loader, &identifier_requirement))
{
for identifier in &specific_collection.identifiers() {
collection.insert(
specific_collection.get(&identifier.into()).unwrap().clone(),
);
}
}
}
}
}
result.and(LoadingResult::new_success(collection))
}
fn all_identifiers(&self, _loader: &Loader) -> LoadingResult<Vec<Identifier>> {
let mut identifiers = Vec::new();
self.contents.iter().for_each(|(_, content)| {
identifiers.extend(
content
.provide()
.into_iter()
.map(|id| id.with_version(&self.version))
.collect::<Vec<_>>(),
)
});
LoadingResult::new_success(identifiers)
}
fn element(
&self,
loader: &Loader,
identifier_requirement: &IdentifierRequirement,
) -> LoadingResult<Collection> {
let mut result = LoadingResult::new_success(Collection::new());
let designation = Self::designation(&identifier_requirement.to_identifier());
if let Some(content) = self.contents.get(&designation) {
if let Ok(_guard) = content.try_lock() {
let needs = content.require();
result.merge_degrade_failure(Self::insure_loading(loader, needs));
let mut collection = loader.collection().clone();
result.merge_degrade_failure(
content
.insert_descriptors(&mut collection)
.convert_failure_errors(|err| {
LoadingError::content_error(233, Arc::new(err))
}),
);
result = result.and_degrade_failure(LoadingResult::new_success(collection));
} else {
result = result.and_degrade_failure(LoadingResult::new_failure(
LoadingError::circular_reference(234, identifier_requirement.clone()),
));
}
} else {
result = result.and_degrade_failure(LoadingResult::new_failure(
LoadingError::not_found(235, identifier_requirement.to_string()),
));
}
result
}
fn make_building(&self, collection: &Arc<Collection>) -> LoadingResult<()> {
let mut result = LoadingResult::new_success(());
for (_, content) in self.contents.iter() {
result.merge_degrade_failure(
content
.make_design(collection)
.convert_failure_errors(|err| LoadingError::content_error(236, Arc::new(err))),
);
}
result
}
}