use std::collections::HashSet;
use std::io::{Read, Seek, Write};
use std::ops::{Deref, DerefMut};
use super::SdkError;
#[derive(Clone, Debug, Default)]
pub struct MediaDataPart {
pub(crate) package_id: Option<crate::common::PackageId>,
pub(crate) id: Option<crate::common::PartId>,
pub inner_path: String,
pub part_content: Vec<u8>,
}
impl MediaDataPart {
#[inline]
pub(crate) fn from_part_id(
package_id: crate::common::PackageId,
part_id: crate::common::PartId,
path: impl Into<String>,
) -> Self {
Self {
package_id: Some(package_id),
id: Some(part_id),
inner_path: path.into(),
part_content: Vec::new(),
}
}
#[inline]
pub fn part_id(&self) -> Option<crate::common::PartId> {
self.id
}
#[inline]
pub(crate) fn part_id_for_package<P: crate::sdk::SdkPackage>(
&self,
package: &P,
) -> Result<crate::common::PartId, SdkError> {
let part_id = self
.id
.ok_or_else(|| SdkError::CommonError("media data part is not package-backed".to_string()))?;
if self.package_id != Some(package.storage().id()) {
return Err(SdkError::CommonError(
"media data part belongs to a different package".to_string(),
));
}
if package.storage().part(part_id).is_none() {
return Err(SdkError::CommonError(format!(
"part id {part_id:?} is not present in package storage"
)));
}
Ok(part_id)
}
#[inline]
pub fn path<'a, P: crate::sdk::SdkPackage>(&'a self, package: &'a P) -> Option<&'a str> {
let part_id = self.id?;
if self.package_id != Some(package.storage().id()) {
return None;
}
package.storage().part(part_id).map(|part| part.path())
}
#[inline]
pub fn content_type<'a, P: crate::sdk::SdkPackage>(&'a self, package: &'a P) -> Option<&'a str> {
let part_id = self.id?;
if self.package_id != Some(package.storage().id()) {
return None;
}
package
.storage()
.part(part_id)
.map(|part| part.content_type())
}
#[inline]
pub fn data<'a, P: crate::sdk::SdkPackage>(&'a self, package: &'a P) -> Option<&'a [u8]> {
let part_id = self.id?;
if self.package_id != Some(package.storage().id()) {
return None;
}
package
.storage()
.part(part_id)
.map(|part| part.data().bytes())
}
#[inline]
pub fn data_part_reference_relationships<'a, P: crate::sdk::SdkPackage>(
&'a self,
package: &'a P,
) -> impl Iterator<Item = crate::common::RelationshipRef<'a>> + 'a {
let part_id = (self.package_id == Some(package.storage().id()))
.then_some(self.id)
.flatten();
part_id.into_iter().flat_map(move |part_id| {
package
.storage()
.data_part_reference_relationships_to(part_id)
.map(Into::into)
})
}
#[inline]
pub fn is_orphan<P: crate::sdk::SdkPackage>(&self, package: &P) -> bool {
self
.data_part_reference_relationships(package)
.next()
.is_none()
}
#[inline]
pub fn set_data<P: crate::sdk::SdkPackage>(
&self,
package: &mut P,
data: Vec<u8>,
) -> Result<(), SdkError> {
let part_id = self.part_id_for_package(package)?;
let part = package.storage_mut().part_mut(part_id).ok_or_else(|| {
SdkError::CommonError(format!(
"part id {part_id:?} is not present in package storage"
))
})?;
*part.data_mut().bytes_mut() = data;
Ok(())
}
pub(crate) fn new_from_archive<R: Read + Seek>(
path: &str,
part_index: usize,
archive: &mut zip::ZipArchive<R>,
) -> Result<Self, SdkError> {
let mut zip_entry = archive.by_index(part_index)?;
let mut part_content = Vec::with_capacity(zip_entry.size() as usize);
zip_entry.read_to_end(&mut part_content)?;
Ok(Self {
package_id: None,
id: None,
inner_path: path.to_string(),
part_content,
})
}
pub(crate) fn save_zip<W: Write + Seek>(
&self,
parent_path: &str,
zip: &mut zip::ZipWriter<W>,
entry_set: &mut HashSet<String>,
) -> Result<(), SdkError> {
let options = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated)
.unix_permissions(0o755);
let directory_path = super::resolve_zip_file_path(parent_path);
if !directory_path.is_empty() && entry_set.insert(directory_path.clone()) {
zip.add_directory(&directory_path, options)?;
}
let dir_path = super::parent_zip_path(&self.inner_path);
let dir_path = dir_path.strip_suffix('/').unwrap_or(&dir_path);
if !dir_path.is_empty() && entry_set.insert(dir_path.to_string()) {
zip.add_directory(dir_path, options)?;
}
if entry_set.insert(self.inner_path.clone()) {
zip.start_file(&self.inner_path, options)?;
zip.write_all(&self.part_content)?;
}
Ok(())
}
}
#[cfg(feature = "validators")]
impl crate::validator::SdkValidator for MediaDataPart {}
macro_rules! define_media_reference_relationship {
($ident:ident, $relationship_type:literal) => {
#[derive(Clone, Debug, Default)]
pub struct $ident {
pub r_id: String,
pub media_data_part: MediaDataPart,
}
impl $ident {
pub const RELATIONSHIP_TYPE: &'static str = $relationship_type;
pub(crate) fn new_from_archive<R: Read + Seek>(
path: &str,
r_id: &str,
part_index: usize,
archive: &mut zip::ZipArchive<R>,
) -> Result<Self, SdkError> {
Ok(Self {
r_id: r_id.to_string(),
media_data_part: MediaDataPart::new_from_archive(path, part_index, archive)?,
})
}
pub(crate) fn save_zip<W: Write + Seek>(
&self,
parent_path: &str,
zip: &mut zip::ZipWriter<W>,
entry_set: &mut HashSet<String>,
) -> Result<(), SdkError> {
self.media_data_part.save_zip(parent_path, zip, entry_set)
}
}
impl crate::sdk::SdkDataPartReference for $ident {
const RELATIONSHIP_TYPE: &'static str = $relationship_type;
fn new_from_archive<R: Read + Seek>(
path: &str,
r_id: &str,
part_index: usize,
archive: &mut zip::ZipArchive<R>,
) -> Result<Self, SdkError> {
Self::new_from_archive(path, r_id, part_index, archive)
}
fn save_zip<W: Write + Seek>(
&self,
parent_path: &str,
zip: &mut zip::ZipWriter<W>,
entry_set: &mut HashSet<String>,
) -> Result<(), SdkError> {
Self::save_zip(self, parent_path, zip, entry_set)
}
}
impl Deref for $ident {
type Target = MediaDataPart;
fn deref(&self) -> &Self::Target {
&self.media_data_part
}
}
impl DerefMut for $ident {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.media_data_part
}
}
#[cfg(feature = "validators")]
impl crate::validator::SdkValidator for $ident {}
};
}
define_media_reference_relationship!(
AudioReferenceRelationship,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio"
);
define_media_reference_relationship!(
MediaReferenceRelationship,
"http://schemas.microsoft.com/office/2007/relationships/media"
);
define_media_reference_relationship!(
VideoReferenceRelationship,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/video"
);