hgame 0.26.4

CG production management structs, e.g. of assets, personnels, progress, etc.
Documentation
use super::*;

pub const EXCERPT_INTO_COMPLETE_ERR: &str =
    "No ProductionAsset can be constructed from AssetExcerpt";

#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, Hash)]
/// An asset in CG pipeline whose ID is a `BSON ObjectId`.
/// It should have a name, and more particularly, an [`AssetCategory`].
///
/// LEGACY DESIGN: DO NOT change field names or `serde(rename)` values.
pub struct StandardAsset {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<ObjectId>,

    category: AssetCategory,

    #[serde(rename = "asset_name", skip_serializing_if = "Option::is_none")]
    name: Option<String>,
}

impl Entity for StandardAsset {
    fn empty() -> Self {
        Self {
            id: None,
            category: AssetCategory::empty(),
            name: None,
        }
    }

    /// `Self::name` will be empty.
    fn as_group(sem: &str, _typ: &ProjectSource) -> Self {
        Self {
            category: AssetCategory::from_semantic(&(sem.into())),
            ..Self::empty()
        }
    }

    fn name(&self) -> Option<&String> {
        self.name.as_ref()
    }
}

impl BsonId for StandardAsset {
    fn bson_id_as_ref(&self) -> Option<&ObjectId> {
        self.id.as_ref()
    }

    fn bson_id(&self) -> AnyResult<&ObjectId> {
        self.id
            .as_ref()
            .context("StandardAsset without BSON ObjectId")
    }
}

impl StandardAsset {
    pub fn id(mut self, id: ObjectId) -> Self {
        self.id = Some(id);
        self
    }

    pub fn new(sem: &Semantic, asset_name: &str) -> Self {
        Self {
            category: AssetCategory::from_semantic(sem),
            name: Some(asset_name.to_owned()),
            ..Self::empty()
        }
    }

    pub fn with_criteria(mut self, criteria: &[&ObjectId]) -> Self {
        self.category = self.category.with_criteria(criteria);
        self
    }

    pub fn name_or_empty(&self) -> &str {
        if let Some(name) = &self.name {
            name
        } else {
            EMPTY_ASSET_NAME
        }
    }

    pub fn category_as_mut(&mut self) -> &mut AssetCategory {
        &mut self.category
    }

    pub fn semantic(&self) -> &Semantic {
        self.category.semantic()
    }

    pub fn criteria(&self) -> Option<&Vec<ObjectId>> {
        self.category.criteria()
    }

    fn has_name(&self) -> bool {
        self.name.is_some()
    }

    pub fn semantic_and_name(&self) -> AnyResult<(String, String)> {
        if self.category.is_semantic_empty() {
            Err(anyhow!("Missing category main type"))
        } else {
            if !self.has_name() {
                Err(anyhow!("Missing asset name"))
            } else {
                Ok((
                    self.semantic().to_string(),
                    self.name.as_ref().unwrap().clone(),
                ))
            }
        }
    }
}

impl PartialEq for StandardAsset {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name && self.category == other.category
    }
}

impl Ord for StandardAsset {
    fn cmp(&self, other: &Self) -> Ordering {
        self.name.cmp(&other.name)
    }
}

impl PartialOrd for StandardAsset {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

// ----------------------------------------------------------------------------
#[derive(Deserialize, Serialize, Debug, Clone, Default, Eq, PartialOrd)]
/// ATTENTION: To avoid deser fails, DO NOT change
/// field names of `serde(rename)` values, as well as be careful
/// for using "rich" types where inappropriate.
pub struct AssetExcerpt {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<ObjectId>,

    /// `Category` is essential for filesystem-related operations.
    pub category: AssetCategory,

    #[serde(rename = "asset_name")]
    pub name: String,
}

impl AssetExcerpt {
    /// Converts the excerpt into the fuller form of [`ProductionAsset`].
    pub fn into_complete(self, project: &Project) -> Option<ProductionAsset> {
        match project {
            Project::Manual(_) => Some(ProductionAsset::Manual(self.into())),
            #[cfg(feature = "mongo")]
            Project::Mongo(_) => Some(ProductionAsset::Mongo(self.into())),
            #[cfg(feature = "kitsu")]
            Project::Kitsu(_) => None,
        }
    }

    #[cfg(feature = "gui")]
    pub fn preview_name(&self, ui: &mut egui::Ui) {
        ui.strong(&self.name);
    }

    pub fn debug_name(assets: &[Self]) -> String {
        match assets.len().cmp(&0) {
            Ordering::Greater => assets
                .iter()
                .map(|a| a.name.to_owned())
                .collect::<Vec<String>>()
                .join(", "),
            _ => String::from("Empty"),
        }
    }
}

impl PartialEq for AssetExcerpt {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id && self.name == other.name
    }
}

impl std::hash::Hash for AssetExcerpt {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.id.hash(state);
        self.name.hash(state);
    }
}

/// `StandardAsset::category`, `StandardAsset::status_str`, and `StandardAsset::status` will be empty.
impl Into<StandardAsset> for AssetExcerpt {
    fn into(self) -> StandardAsset {
        StandardAsset {
            id: self.id,
            category: self.category,
            name: Some(self.name),
        }
    }
}

/// If we have a [`ProductionAsset`] we may or may not be able to make an [`AssetExcerpt`]
/// from it.
impl Into<Option<AssetExcerpt>> for ProductionAsset {
    fn into(self) -> Option<AssetExcerpt> {
        match self {
            Self::Null => None,
            Self::Manual(inner) => Some(AssetExcerpt {
                id: inner.id,
                category: inner.category,
                name: inner.name.unwrap_or(String::new()),
            }),
            #[cfg(feature = "kitsu")]
            Self::Kitsu(_) => {
                // TODO@kitsu
                None
            }
            #[cfg(feature = "mongo")]
            Self::Mongo(inner) => Some(AssetExcerpt {
                id: inner.id,
                category: inner.category,
                name: inner.name.unwrap_or(String::new()),
            }),
        }
    }
}

// ----------------------------------------------------------------------------
#[cfg(test)]
mod tests {}