ot-tools-io 0.11.3

A library crate for reading/writing binary data files used by the Elektron Octatrack DPS-1.
Documentation
/*
SPDX-License-Identifier: GPL-3.0-or-later
Copyright © 2026 Mike Robeson [dijksterhuis]
*/

//! Type and parsing of a project's OS metadata.
//! Used in the [`crate::projects::ProjectFile`] type.

use ot_tools_io_derive::{AsMutDerive, AsRefDerive};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::projects::{
    parse_hashmap_string_value, string_to_hashmap, ProjectParseError, SectionHeader,
};

/*
Example data:
[META]\r\nTYPE=OCTATRACK DPS-1 PROJECT\r\nVERSION=19\r\nOS_VERSION=R0177     1.40B\r\n[/META]
------
[META]
TYPE=OCTATRACK DPS-1 PROJECT
VERSION=19
OS_VERSION=R0177     1.40B
[/META]
*/

/// Operating system metadata read from a `project.*` file.
/// The octatrack checks these fields on project load/open to ensure:
/// 1. it is possible to load the project (the project is not from an unrecognised OS version where a patch has not yet been installed)
/// 2. the project is upgraded to run on a newer OS version (need to patch the project data files as tey were created with an older OS)
///
/// No `Copy` trait as we (currently) use the `String` type for several fields
#[derive(
    Clone,
    Debug,
    Eq,
    Hash,
    Ord,
    PartialEq,
    PartialOrd,
    Serialize,
    Deserialize,
    AsMutDerive,
    AsRefDerive,
)]
pub struct OsMetadata {
    /// Type of file (always a 'project').
    ///
    /// Example ASCII data:
    /// ```text
    /// TYPE=OCTATRACK DPS-1 PROJECT
    /// ```
    pub filetype: String,

    /// Version of `project.*` data type in this file, a la the `datatype_version` field on other main file types (probably?)
    ///
    /// Example ASCII data:
    /// ```text
    /// VERSION=19
    /// ```
    pub project_version: u32,

    /// Version of the Octatrack OS (that the project was created with?).
    ///
    /// Example ASCII data:
    /// ```text
    /// OS_VERSION=R0177     1.40B
    /// ```
    pub os_version: String,
}

impl Default for OsMetadata {
    fn default() -> Self {
        Self {
            filetype: "OCTATRACK DPS-1 PROJECT".to_string(),
            project_version: 19,
            os_version: "R0177     1.40B".to_string(),
        }
    }
}

impl std::str::FromStr for OsMetadata {
    type Err = ProjectParseError;

    /// Extract `OctatrackProjectMetadata` fields from the project file's ASCII data
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let hmap: HashMap<String, String> = string_to_hashmap(s, &SectionHeader::Meta)?;

        Ok(Self {
            filetype: parse_hashmap_string_value::<String>(&hmap, "type", None)
                .map_err(|_| ProjectParseError::String)?,
            project_version: parse_hashmap_string_value::<u32>(&hmap, "version", None)?,
            os_version: parse_hashmap_string_value::<String>(&hmap, "os_version", None)
                .map_err(|_| ProjectParseError::String)?,
        })
    }
}

impl std::fmt::Display for OsMetadata {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        let mut s = "".to_string();
        s.push_str("[META]\r\n");
        s.push_str(format!("TYPE={}", self.filetype).as_str());
        s.push_str("\r\n");
        s.push_str(format!("VERSION={}", self.project_version).as_str());
        s.push_str("\r\n");
        s.push_str(format!("OS_VERSION={}", self.os_version).as_str());
        s.push_str("\r\n[/META]");
        write!(f, "{s:#}")
    }
}