ot_tools_io/
lib.rs

1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2024 Mike Robeson [dijksterhuis]
4*/
5
6//! Serialization and Deserialization library for Elektron Octatrack data files.
7//!
8//! ## Important types
9//!
10//! | rust type    | octatrack filename pattern | description |
11//! | ------------ | -------------------------- | ------------|
12//! | [crate::arrangements::ArrangementFile] | `arr??.*` | data for arrangements |
13//! | [crate::banks::BankFile] | `bank??.*` | data for parts and patterns |
14//! | [crate::markers::MarkersFile] | `markers.*` | start trim/end trim/slices/loop points for sample slots |
15//! | [crate::projects::ProjectFile] | `project.*` | project level settings; state; sample slots |
16//! | [crate::samples::SampleAttributes] | `*.ot` | saved sample settings data, loops slices etc. |
17//!
18//!
19//! Only the above types implement the [crate::Encode] and [crate::Decode] traits,
20//! which means only these types can be read from / written to the filesystem using functions
21//! in this library.
22//!
23//! Read the relevant modules in this library for more detailed information on the data contained in each file.
24//!
25//! ## How do different Octatrack data files relate to each other?
26//!
27//! - A Bank stores zero-indexed sample slot IDs to indicate which sample should be played when on a given track
28//!   (part machine data and/or track p-lock trigs).
29//! - Changing the sample loaded into a sample slot updates both the `project.*` file (change the trig quantization
30//!   settings, file path etc) and the `markers.*` file (change the trim settings based on either initial load,
31//!   or any data in a relevant `*.ot` sample settings file).
32//! - Data from `project.*`and `markers.*` is written to an `*.ot` file when saving sample attributes data from the
33//!   octatrack's audio editing menu.
34//! - Loading a sample into a project sample slot (`project.*` and `markers.*` files) reads any data in an `*.ot` files
35//!   and configures the sample slot accordingly.
36//!
37//! ## When do `*.work` and `*.strd` files get modified by the Octatrack?
38//!
39//! - `*.work` files are created when creating a new project `PROJECT MENU -> CHANGE PROJECT -> CREATE NEW`.
40//! - `*.work` files are updated by using the `PROJECT MENU -> SYNC TO CARD` operation.
41//! - `*.work` files are updated when the user performs a `PROJECT MENU -> SAVE PROJECT` operation.
42//! - `*.strd` files are created/updated when the user performs a `PROJECT MENU -> SAVE PROJECT` operation.
43//! - `*.strd` files are not changed by the `PROJECT -> SYNC TO CARD` operation.
44//! - `arr??.strd` files can also be saved via the `ARRANGER MENU -> SAVE ARRANGEMENT` operation.
45//!
46//! ## Notable 'gotcha's
47//! - Project sample slots (`project.*` files) are one-indexed, but their references everywhere else are zero-indexed
48//!   (`bank??.*` and `markers.*` files).
49//! - A 'Disabled' loop point in either `markers.*` or `*.ot` files is a `0xFFFFFFFF` value, but the default
50//!   loop point when creating new `markers.*` file is always `0_u32`. The 'Disabled' value setting is only set
51//!   when a sample is loaded into a sample slot.
52//!
53//! ## Example code
54//! ```rust
55//! /*
56//! reading, mutating and writing a bank file
57//! */
58//!
59//! use std::path::PathBuf;
60//! use ot_tools_io::{read_type_from_bin_file, write_type_to_bin_file, banks::BankFile};
61//!
62//! let binfpath = PathBuf::from("test-data")
63//!     .join("blank-project")
64//!     .join("bank01.work");
65//!
66//! // read an editable version of the bank file
67//! let mut bank: BankFile = read_type_from_bin_file(&binfpath).unwrap();
68//!
69//! // change active scenes on the working copy of Part 4
70//! bank.parts.unsaved[3].active_scenes.scene_a = 2;
71//! bank.parts.unsaved[3].active_scenes.scene_b = 6;
72//!
73//! // write to a new bank file
74//! let outfpath = std::env::temp_dir()
75//!     .join("ot-tools-io")
76//!     .join("doctest")
77//!     .join("main_example_1");
78//! write_type_to_bin_file::<BankFile>(&bank, &outfpath).unwrap();
79//! ```
80
81pub mod arrangements;
82pub mod banks;
83pub mod markers;
84pub mod projects;
85pub mod samples;
86#[cfg(test)]
87mod test_utils;
88
89use serde::{Deserialize, Serialize};
90use serde_big_array::Array;
91use std::array::from_fn;
92use std::{
93    error::Error,
94    fmt::Debug,
95    fs::File,
96    io::{Read, Write},
97    path::Path,
98};
99
100// todo: sized errors so not necessary to keep Boxing error enum varients
101/// Shorthand type alias for a Result with a Boxed Error
102type RBoxErr<T> = Result<T, Box<dyn Error>>;
103
104/// Global error variants
105#[derive(Debug, PartialEq, Eq)]
106pub enum OtToolsIoErrors {
107    /// An 'Options' Enum (e.g. `SampleAttributesLoopMode`) does not have a matching variant for this value
108    NoMatchingOptionEnumValue,
109    /// Could not parse a sample slot string data when loading a project
110    ProjectSampleSlotParsingError,
111    /// A file does not exist at the provided location
112    FileNotFound,
113    /// I know an error exists here, but I'm busy yak shaving something else at the moment.
114    TodoError,
115}
116impl std::fmt::Display for OtToolsIoErrors {
117    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
118        match self {
119            Self::NoMatchingOptionEnumValue => {
120                write!(f, "no matching enum option for the provided value")
121            }
122            Self::ProjectSampleSlotParsingError => {
123                write!(f, "count not load sample slot from project string data")
124            }
125            Self::FileNotFound => {
126                write!(f, "file not found")
127            }
128            Self::TodoError => {
129                write!(
130                    f,
131                    "this error is handled, but an error variant is not created yet"
132                )
133            }
134        }
135    }
136}
137impl Error for OtToolsIoErrors {}
138
139// DO-NOT-DERIVE: Implementation details for each enum are always required.
140/// Trait to convert between Enum option instances and their corresponding value.
141trait OptionEnumValueConvert {
142    /// One of the enum types within the `octatrack::options` module.
143    type T;
144
145    /// Input type for `from_value` and return type for `value` method.
146    type V;
147
148    /// Get an Enum instance from a numeric value.
149    fn from_value(v: &Self::V) -> RBoxErr<Self::T>;
150
151    /// Get a numeric value for an Enum instance.
152    fn value(&self) -> RBoxErr<Self::V>;
153}
154
155fn u8_bytes_to_u16(bytes: &[u8; 2]) -> u16 {
156    ((bytes[0] as u16) << 8) | bytes[1] as u16
157}
158
159/// Adds deserialisation via `bincode` and `serde` to a type.
160/// Must be present on all major file types.
161pub trait Decode {
162    fn decode(bytes: &[u8]) -> RBoxErr<Self>
163    where
164        Self: Sized,
165        Self: for<'a> Deserialize<'a>,
166    {
167        let x: Self = bincode::deserialize(bytes)?;
168        Ok(x)
169    }
170}
171
172/// Adds serialisation via `bincode` and `serde` to a type.
173/// Must be present on all major file types.
174pub trait Encode {
175    fn encode(&self) -> RBoxErr<Vec<u8>>
176    where
177        Self: Serialize,
178    {
179        Ok(bincode::serialize(&self)?)
180    }
181}
182
183/// Used when we need a collection of default type instances
184/// e.g. when creating a default bank we need 16 default patterns.
185/// ```
186/// // usage example
187///
188/// use ot_tools_io::DefaultsArray;
189///
190/// struct SomeType {
191///     x: u8,
192/// }
193///
194/// impl Default for SomeType {
195///     fn default() -> Self {
196///         Self { x: 0 }
197///     }
198/// }
199///
200/// impl DefaultsArray for SomeType {}
201///
202/// let xs: [SomeType; 20] = SomeType::defaults();
203/// assert_eq!(xs.len(), 20);
204///
205/// let xs = SomeType::defaults::<25>();
206/// assert_eq!(xs.len(), 25);
207/// ```
208pub trait DefaultsArray {
209    /// Create an Array containing `N` default instances of `Self`.
210    fn defaults<const N: usize>() -> [Self; N]
211    where
212        Self: Default,
213    {
214        from_fn(|_| Self::default())
215    }
216}
217
218/// Same as `DefaultsArray`, but using a boxed [serde_big_array::Array] container
219/// type
220/// ```
221/// // usage example
222///
223/// use serde_big_array::Array;
224/// use ot_tools_io::DefaultsArrayBoxed;
225///
226/// struct SomeType {
227///     x: u8,
228/// }
229///
230/// impl Default for SomeType {
231///     fn default() -> Self {
232///         Self { x: 0 }
233///     }
234/// }
235///
236/// impl DefaultsArrayBoxed for SomeType {}
237///
238/// let xs: Box<Array<SomeType, 20>> = SomeType::defaults();
239/// assert_eq!(xs.len(), 20);
240///
241/// let xs = SomeType::defaults::<25>();
242/// assert_eq!(xs.len(), 25);
243/// ```
244pub trait DefaultsArrayBoxed {
245    /// Create a Boxed [serde_big_array::Array] containing `N` default instances
246    /// of `Self`.
247    fn defaults<const N: usize>() -> Box<Array<Self, N>>
248    where
249        Self: Default,
250    {
251        Box::new(Array(from_fn(|_| Self::default())))
252    }
253}
254
255/// Adds a method to check the current data structure matches the default for the type
256/// ```rust
257/// use ot_tools_io::{IsDefault, banks::BankFile};
258///
259/// let mut bank = BankFile::default();
260/// assert_eq!(bank.is_default(), true);
261///
262/// bank.datatype_version = 190;
263/// assert_eq!(bank.is_default(), false);
264/// ```
265pub trait IsDefault {
266    fn is_default(&self) -> bool;
267}
268
269/// Method for calculating the checksum value for types that have a checksum field
270/// ```rust
271/// # use std::path::PathBuf;
272/// use ot_tools_io::{CalculateChecksum, read_type_from_bin_file, banks::BankFile};
273/// # let fpath = PathBuf::from("test-data")
274/// #     .join("blank-project")
275/// #     .join("bank01.work");
276/// let bank: BankFile = read_type_from_bin_file(&fpath).unwrap();
277/// assert_eq!(bank.checksum, bank.calculate_checksum().unwrap())
278/// ```
279pub trait CalculateChecksum {
280    fn calculate_checksum(&self) -> RBoxErr<u16>;
281}
282
283/// Adds a method to verify if header(s) are valid in some data.
284/// See this thread to understand why this is useful:
285/// <https://www.elektronauts.com/t/bank-unavailable-octatrack/190647/27>
286/// ```rust
287/// # use std::path::PathBuf;
288/// use ot_tools_io::{CheckHeader, read_type_from_bin_file, banks::BankFile};
289/// # let fpath = PathBuf::from("test-data")
290/// #     .join("blank-project")
291/// #     .join("bank01.work");
292/// let bank: BankFile = read_type_from_bin_file(&fpath).unwrap();
293/// assert!(bank.check_header()) // true for valid header values
294/// ```
295// NOTE: ot-tools-io does not validate headers on file read, which means it is
296// possible to perform checks like this when a data file has been read.
297// otherwise we'd have to do a complicated check to verify headers on every
298// file we read, then throw out an error and probably do some complicated
299// error handling to explain to the end user exactly why we couldn't load the
300// file (bad header, which patterns, which track within patterns etc.).
301pub trait CheckHeader {
302    fn check_header(&self) -> bool;
303}
304
305/// Adds a method to verify if checksum is valid in some data type.
306/// See this thread to understand why this is useful:
307/// <https://www.elektronauts.com/t/bank-unavailable-octatrack/190647/27>
308/// ```rust
309/// # use std::path::PathBuf;
310/// use ot_tools_io::{CheckChecksum, read_type_from_bin_file, banks::BankFile};
311/// # let fpath = PathBuf::from("test-data")
312/// #     .join("blank-project")
313/// #     .join("bank01.work");
314/// let bank: BankFile = read_type_from_bin_file(&fpath).unwrap();
315/// assert!(bank.check_checksum().unwrap()) // true for valid checksum values
316/// ```
317pub trait CheckChecksum {
318    fn check_checksum(&self) -> RBoxErr<bool>;
319}
320
321/// Adds a single method using the [crate::CheckHeader] and [crate::CheckChecksum]
322/// methods to run a full integrity check.
323/// ```rust
324/// # use std::path::PathBuf;
325/// use ot_tools_io::{CheckIntegrity, read_type_from_bin_file, banks::BankFile};
326/// # let fpath = PathBuf::from("test-data")
327/// #     .join("blank-project")
328/// #     .join("bank01.work");
329/// let bank: BankFile = read_type_from_bin_file(&fpath).unwrap();
330/// assert!(bank.check_integrity().unwrap()) // true for valid checksum+header values
331/// ```
332pub trait CheckIntegrity: CheckHeader + CheckChecksum {
333    fn check_integrity(&self) -> RBoxErr<bool> {
334        Ok(self.check_header() && self.check_checksum()?)
335    }
336}
337
338/* SER/DE GENERICS ============================================================================== */
339
340/// Deserialize a bytes to a data structure of type `T`
341/// ```rust
342/// # use std::array::from_fn;
343/// # use ot_tools_io::{deserialize_bin_to_type, samples::SampleAttributes};
344/// let bytes: [u8; 832] = from_fn(|_| 0);
345/// let samples_type: SampleAttributes = deserialize_bin_to_type(&bytes).unwrap();
346/// assert_eq!(samples_type.gain, 0)
347/// ```
348pub fn deserialize_bin_to_type<T>(bytes: &[u8]) -> RBoxErr<T>
349where
350    T: Decode,
351    T: for<'a> Deserialize<'a>,
352{
353    let x: T = T::decode(bytes)?;
354    Ok(x)
355}
356
357/// Serialize to bytes from a data structure of type `T`
358/// ```rust
359/// # use std::array::from_fn;
360/// # use ot_tools_io::{serialize_bin_from_type, banks::BankFile};
361/// let bank = BankFile::default();
362/// let bytes = serialize_bin_from_type::<BankFile>(&bank).unwrap();
363/// assert_eq!(bytes.len(), 636113);
364/// ```
365pub fn serialize_bin_from_type<T>(data: &T) -> RBoxErr<Vec<u8>>
366where
367    T: Encode,
368    T: Serialize,
369{
370    data.encode()
371}
372
373/// Deserialize a JSON string to a data structure of type `T`
374pub fn deserialize_json_to_type<T>(data: &str) -> RBoxErr<T>
375where
376    T: for<'a> Deserialize<'a>,
377{
378    let x: T = serde_json::from_str(data)?;
379    Ok(x)
380}
381
382/// Serialize a JSON string from a data structure of type `T`
383/// ```rust
384/// # use ot_tools_io::{serialize_json_from_type, banks::BankFile};
385/// let bank = BankFile::default();
386/// let json_string = serialize_json_from_type::<BankFile>(&bank).unwrap();
387/// assert_eq!(json_string.len(), 7807730);
388/// assert_eq!(json_string[0..15], "{\"header\":[70,7".to_string());
389/// ```
390pub fn serialize_json_from_type<T>(data: &T) -> RBoxErr<String>
391where
392    T: Serialize,
393{
394    Ok(serde_json::to_string(&data)?)
395}
396
397/// Deserialize a YAML string to a data structure of type `T`
398pub fn deserialize_yaml_to_type<T>(data: &str) -> RBoxErr<T>
399where
400    T: for<'a> Deserialize<'a>,
401{
402    let x: T = serde_yml::from_str(data)?;
403    Ok(x)
404}
405/// Serialize a YAML string from a data structure of type `T`
406/// ```rust
407/// # use ot_tools_io::{serialize_yaml_from_type, banks::BankFile};
408/// let bank = BankFile::default();
409/// let yaml_string = serialize_yaml_from_type::<BankFile>(&bank).unwrap();
410/// assert_eq!(yaml_string.len(), 12578394);
411/// assert_eq!(yaml_string[0..15], "header:\n- 70\n- ".to_string());
412/// ```
413pub fn serialize_yaml_from_type<T>(data: &T) -> RBoxErr<String>
414where
415    T: Serialize,
416{
417    Ok(serde_yml::to_string(&data)?)
418}
419
420/* UTILS ======================================================================================== */
421
422/* NO TESTS BLOCK START */
423
424/// Read a YAML file into a data structure of type `<T>`
425/// ```rust
426/// # use std::path::PathBuf;
427/// use ot_tools_io::{yaml_file_to_type, banks::BankFile};
428/// let fpath = PathBuf::from("test-data").join("bank").join("blank.yaml");
429/// let bank: BankFile = yaml_file_to_type(&fpath).unwrap();
430/// assert_eq!(bank.datatype_version, 23);
431/// ```
432pub fn yaml_file_to_type<T>(path: &Path) -> RBoxErr<T>
433where
434    T: for<'a> Deserialize<'a>,
435{
436    let string = read_str_file(path)?;
437    let data = deserialize_yaml_to_type::<T>(&string)?;
438    Ok(data)
439}
440
441/// Write a data structure of type `<T>` into a YAML file
442/// ```rust
443/// use std::env::temp_dir;
444/// # use std::fs::create_dir_all;
445/// use ot_tools_io::{type_to_yaml_file, banks::BankFile};
446///
447/// let fpath = temp_dir()
448///    .join("ot-tools-io")
449///    .join("doctest")
450///    .join("type_to_yaml_file.yaml");
451/// # create_dir_all(&fpath.parent().unwrap()).unwrap();
452///
453/// let r = type_to_yaml_file(&BankFile::default(), &fpath);
454/// assert!(r.is_ok());
455/// assert!(fpath.exists());
456/// ```
457pub fn type_to_yaml_file<T>(data: &T, path: &Path) -> RBoxErr<()>
458where
459    T: Serialize,
460{
461    let yaml = serialize_yaml_from_type::<T>(data)?;
462    write_str_file(&yaml, path)?;
463    Ok(())
464}
465
466/// Read a JSON file into a data structure of type `<T>`
467pub fn json_file_to_type<T>(path: &Path) -> RBoxErr<T>
468where
469    T: for<'a> Deserialize<'a>,
470{
471    let string = read_str_file(path)?;
472    let data = deserialize_json_to_type::<T>(&string)?;
473    Ok(data)
474}
475
476/// Write a data structure of type `<T>` into a JSON file
477/// ```rust
478/// use std::env::temp_dir;
479/// # use std::fs::create_dir_all;
480/// use ot_tools_io::{type_to_json_file, banks::BankFile};
481///
482/// let fpath = temp_dir()
483///    .join("ot-tools-io")
484///    .join("doctest")
485///    .join("type_to_json_file.json");
486/// # create_dir_all(&fpath.parent().unwrap()).unwrap();
487///
488/// let r = type_to_json_file(&BankFile::default(), &fpath);
489/// assert!(r.is_ok());
490/// assert!(fpath.exists());
491/// ```
492pub fn type_to_json_file<T>(data: &T, path: &Path) -> RBoxErr<()>
493where
494    T: Serialize,
495{
496    let yaml = serialize_json_from_type::<T>(data)?;
497    write_str_file(&yaml, path)?;
498    Ok(())
499}
500
501/// Create a new type with default settings, and write the data to a file.
502/// ```rust
503/// use std::env::temp_dir;
504/// # use std::fs::create_dir_all;
505/// use ot_tools_io::{default_type_to_bin_file, banks::BankFile};
506///
507/// let fpath = temp_dir()
508///    .join("ot-tools-io")
509///    .join("doctest")
510///    .join("default_type_to_bin_file.bank");
511/// # create_dir_all(&fpath.parent().unwrap()).unwrap();
512///
513/// let r = default_type_to_bin_file::<BankFile>(&fpath);
514/// assert!(r.is_ok());
515/// assert!(fpath.exists());
516/// ```
517pub fn default_type_to_bin_file<T>(outpath: &Path) -> RBoxErr<()>
518where
519    T: Encode,
520    T: Default,
521    T: Serialize,
522{
523    write_type_to_bin_file(&T::default(), outpath)?;
524    Ok(())
525}
526
527/* NO TESTS BLOCK ENDS */
528
529/// Show deserialized representation of a binary data file of type `T` at `path`
530pub fn show_type<T>(path: &Path, newlines: Option<bool>) -> RBoxErr<()>
531where
532    T: Debug,
533    T: Decode,
534    T: for<'a> Deserialize<'a>,
535{
536    let data = read_type_from_bin_file::<T>(path)?;
537    if newlines.unwrap_or(true) {
538        println!("{data:#?}")
539    } else {
540        println!("{data:?}")
541    };
542
543    Ok(())
544}
545
546/// Read a YAML file then write the data to a new `<T>` type file
547/// ```rust
548/// # use std::{path::PathBuf, fs::create_dir_all};
549/// use std::env::temp_dir;
550/// use ot_tools_io::{yaml_file_to_bin_file, banks::BankFile};
551///
552/// let yaml_fpath = PathBuf::from("test-data")
553///     .join("bank")
554///     .join("blank.yaml");
555///
556/// let bin_fpath = temp_dir()
557///    .join("ot-tools-io")
558///    .join("doctest")
559///    .join("yaml_to_bin.bank_example");
560/// # create_dir_all(&bin_fpath.parent().unwrap()).unwrap();
561///
562/// let r = yaml_file_to_bin_file::<BankFile>(&yaml_fpath, &bin_fpath);
563/// assert!(r.is_ok());
564/// assert!(bin_fpath.exists());
565/// ```
566pub fn yaml_file_to_bin_file<T>(yaml_filepath: &Path, bin_filepath: &Path) -> RBoxErr<()>
567where
568    T: Encode,
569    T: Serialize,
570    T: for<'a> Deserialize<'a>,
571{
572    let yaml = read_str_file(yaml_filepath)?;
573    let data = deserialize_yaml_to_type::<T>(&yaml)?;
574    write_type_to_bin_file::<T>(&data, bin_filepath)?;
575    Ok(())
576}
577
578/// Read data of type `<T>` from  a binary data file and write it to a YAML file
579/// ```rust
580/// # use std::{path::PathBuf, fs::create_dir_all};
581/// use std::env::temp_dir;
582/// use ot_tools_io::{bin_file_to_yaml_file, banks::BankFile};
583///
584/// let bin_fpath = PathBuf::from("test-data")
585///     .join("blank-project")
586///     .join("bank01.work");
587///
588/// let yaml_fpath = temp_dir()
589///    .join("ot-tools-io")
590///    .join("doctest")
591///    .join("bin_to_yaml.bank_example");
592/// # create_dir_all(&yaml_fpath.parent().unwrap()).unwrap();
593///
594/// let r = bin_file_to_yaml_file::<BankFile>(&bin_fpath, &yaml_fpath);
595/// assert!(r.is_ok());
596/// assert!(bin_fpath.exists());
597/// ```
598pub fn bin_file_to_yaml_file<T>(bin_filepath: &Path, yaml_filepath: &Path) -> RBoxErr<()>
599where
600    T: Decode,
601    T: Serialize,
602    T: for<'a> Deserialize<'a>,
603{
604    let data = read_type_from_bin_file::<T>(bin_filepath)?;
605    let yaml = serialize_yaml_from_type::<T>(&data)?;
606    write_str_file(&yaml, yaml_filepath)?;
607    Ok(())
608}
609
610/// Read a JSON file then write the data to a new `<T>` type file
611pub fn json_file_to_bin_file<T>(json_filepath: &Path, bin_filepath: &Path) -> RBoxErr<()>
612where
613    T: Encode,
614    T: Serialize,
615    T: for<'a> Deserialize<'a>,
616{
617    let json = read_str_file(json_filepath)?;
618    let data = deserialize_json_to_type::<T>(&json)?;
619    write_type_to_bin_file::<T>(&data, bin_filepath)?;
620    Ok(())
621}
622
623/// Read data of type `<T>` from  a binary data file and write it to a JSON file
624/// ```rust
625/// # use std::{path::PathBuf, fs::create_dir_all};
626/// use std::env::temp_dir;
627/// use ot_tools_io::{bin_file_to_json_file, banks::BankFile};
628///
629/// let bin_fpath = PathBuf::from("test-data")
630///     .join("blank-project")
631///     .join("bank01.work");
632///
633/// let json_fpath = temp_dir()
634///    .join("ot-tools-io")
635///    .join("doctest")
636///    .join("bin_to_json.bank_example");
637/// # create_dir_all(&json_fpath.parent().unwrap()).unwrap();
638///
639/// let r = bin_file_to_json_file::<BankFile>(&bin_fpath, &json_fpath);
640/// assert!(r.is_ok());
641/// assert!(bin_fpath.exists());
642/// ```
643pub fn bin_file_to_json_file<T>(bin_filepath: &Path, json_filepath: &Path) -> RBoxErr<()>
644where
645    T: Decode,
646    T: Serialize,
647    T: for<'a> Deserialize<'a>,
648{
649    let data = read_type_from_bin_file::<T>(bin_filepath)?;
650    let yaml = serialize_json_from_type::<T>(&data)?;
651    write_str_file(&yaml, json_filepath)?;
652    Ok(())
653}
654
655/// Read a data structure of type `<T>` from a binary data file.
656/// ```rust
657/// use std::path::PathBuf;
658/// use ot_tools_io::{read_type_from_bin_file, banks::BankFile};
659///
660/// let fpath = PathBuf::from("test-data")
661///     .join("blank-project")
662///     .join("bank01.work");
663///
664/// let bank: BankFile = read_type_from_bin_file(&fpath).unwrap();
665/// assert_eq!(bank.datatype_version, 23);
666/// ```
667pub fn read_type_from_bin_file<T>(path: &Path) -> RBoxErr<T>
668where
669    T: Decode,
670    T: for<'a> Deserialize<'a>,
671{
672    let bytes = read_bin_file(path)?;
673    let data = deserialize_bin_to_type::<T>(&bytes)?;
674    Ok(data)
675}
676
677/// Write a data structue of type `<T>` to a binary data file.
678/// ```rust
679/// # use std::{path::PathBuf, fs::create_dir_all};
680/// use std::env::temp_dir;
681/// use ot_tools_io::{write_type_to_bin_file, banks::BankFile};
682///
683/// let fpath = temp_dir()
684///    .join("ot-tools-io")
685///    .join("doctest")
686///    .join("write_type_to_bin_file.bank_example");
687/// # create_dir_all(&fpath.parent().unwrap()).unwrap();
688///
689/// let r = write_type_to_bin_file::<BankFile>(&BankFile::default(), &fpath);
690/// assert!(r.is_ok());
691/// assert!(fpath.exists());
692/// ```
693pub fn write_type_to_bin_file<T>(data: &T, path: &Path) -> RBoxErr<()>
694where
695    T: Encode,
696    T: Serialize,
697{
698    let bytes = serialize_bin_from_type::<T>(data)?;
699    write_bin_file(&bytes, path)?;
700    Ok(())
701}
702
703/// Read bytes from a file at `path`.
704/// ```rust
705/// let fpath = std::path::PathBuf::from("test-data")
706///     .join("blank-project")
707///     .join("bank01.work");
708/// let r = ot_tools_io::read_bin_file(&fpath);
709/// assert!(r.is_ok());
710/// assert_eq!(r.unwrap().len(), 636113);
711pub fn read_bin_file(path: &Path) -> RBoxErr<Vec<u8>> {
712    let mut infile = File::open(path)?;
713    let mut bytes: Vec<u8> = vec![];
714    let _: usize = infile.read_to_end(&mut bytes)?;
715    Ok(bytes)
716}
717
718/// Write bytes to a file at `path`.
719/// ```rust
720/// use std::env::temp_dir;
721/// use std::array::from_fn;
722///
723/// let arr: [u8; 27] = from_fn(|_| 0);
724///
725/// let fpath = temp_dir()
726///    .join("ot-tools-io")
727///    .join("doctest")
728///    .join("write_bin_file.example");
729///
730/// let r = ot_tools_io::write_bin_file(&arr, &fpath);
731/// assert!(r.is_ok());
732/// assert!(fpath.exists());
733///
734/// ```
735pub fn write_bin_file(bytes: &[u8], path: &Path) -> RBoxErr<()> {
736    let mut file: File = File::create(path)?;
737    file.write_all(bytes)?;
738    Ok(())
739}
740
741/// Read a file at `path` as a string.
742pub fn read_str_file(path: &Path) -> RBoxErr<String> {
743    let mut file = File::open(path)?;
744    let mut string = String::new();
745    let _ = file.read_to_string(&mut string)?;
746    Ok(string)
747}
748
749/// Write a string to a file at `path`.
750pub fn write_str_file(string: &str, path: &Path) -> RBoxErr<()> {
751    let mut file: File = File::create(path)?;
752    write!(file, "{}", string)?;
753    Ok(())
754}
755
756#[cfg(test)]
757#[allow(unused_imports)]
758mod test {
759    use super::*;
760    use crate::arrangements::ArrangementFile;
761    use crate::banks::BankFile;
762    use crate::projects::ProjectFile;
763    use crate::samples::SampleAttributes;
764    use crate::test_utils::*;
765    mod show_ok {
766        use super::*;
767        #[test]
768        fn test_arrangement() {
769            let fp = get_blank_proj_dirpath().join("arr01.work");
770            let r = show_type::<ArrangementFile>(&fp, None);
771            assert!(r.is_ok())
772        }
773
774        #[test]
775        fn test_bank() {
776            let fp = get_blank_proj_dirpath().join("bank01.work");
777            let r = show_type::<BankFile>(&fp, None);
778            assert!(r.is_ok())
779        }
780
781        #[test]
782        fn test_project() {
783            let fp = get_blank_proj_dirpath().join("project.work");
784            let r = show_type::<ProjectFile>(&fp, None);
785            assert!(r.is_ok())
786        }
787
788        #[test]
789        fn test_sample() {
790            let fp = get_samples_dirpath().join("sample.ot");
791            let r = show_type::<SampleAttributes>(&fp, None);
792            assert!(r.is_ok())
793        }
794    }
795
796    // TODO: Add more cases
797    mod yaml_file_to_bin_file_ok {
798        use super::*;
799        use crate::arrangements::ArrangeRow;
800
801        mod arrangement {
802            use crate::arrangements::ArrangementFile;
803            use crate::test_utils::get_arrange_dirpath;
804            use crate::{read_type_from_bin_file, yaml_file_to_bin_file};
805
806            fn helper_arrangement(fname: String) {
807                let testfile = get_arrange_dirpath().join(format!["{fname}.work"]);
808                let outfile = std::env::temp_dir()
809                    .join(format!["ot-tools-io-arrangement-load-test-{fname}.work"]);
810                let yaml = get_arrange_dirpath().join(format!["{fname}.yaml"]);
811                let r = yaml_file_to_bin_file::<ArrangementFile>(&yaml, &outfile);
812                let written = read_type_from_bin_file::<ArrangementFile>(&outfile).unwrap();
813                let valid = read_type_from_bin_file::<ArrangementFile>(&testfile).unwrap();
814                let _ = std::fs::remove_file(&outfile);
815                println!("{r:?}");
816                assert!(r.is_ok());
817                assert_eq!(written, valid);
818            }
819            #[test]
820            fn blank() {
821                helper_arrangement("blank".to_string());
822            }
823
824            #[test]
825            fn full_options() {
826                helper_arrangement("full-options".to_string());
827            }
828
829            #[test]
830            fn full_options_no_rems() {
831                helper_arrangement("full-options-no-rems".to_string());
832            }
833
834            #[test]
835            fn no_saved_flag() {
836                helper_arrangement("no-saved-flag".to_string());
837            }
838
839            #[test]
840            fn with_saved_flag() {
841                helper_arrangement("with-saved-flag".to_string());
842            }
843
844            #[test]
845            fn blank_diffname_1() {
846                helper_arrangement("blank-diffname1".to_string());
847            }
848
849            #[test]
850            fn blank_diffname_2() {
851                helper_arrangement("blank-diffname2".to_string());
852            }
853
854            #[test]
855            fn four_patterns() {
856                helper_arrangement("4-patterns".to_string());
857            }
858
859            #[test]
860            fn one_pattern() {
861                helper_arrangement("1-pattern".to_string());
862            }
863            #[test]
864            fn one_halt() {
865                helper_arrangement("1-halt".to_string());
866            }
867            #[test]
868            fn one_pattern_1_loop() {
869                helper_arrangement("1-patt-1-loop".to_string());
870            }
871            #[test]
872            fn one_pattern_1_loop_1_jump() {
873                helper_arrangement("1-patt-1-jump-1-loop".to_string());
874            }
875            #[test]
876            fn one_pattern_1_loop_1_jump_1_halt() {
877                helper_arrangement("1-patt-1-jump-1-loop-1-halt".to_string());
878            }
879            #[test]
880            fn one_rems_no_txt() {
881                helper_arrangement("1-rem-blank-txt".to_string());
882            }
883            #[test]
884            fn one_rems_chain_txt() {
885                helper_arrangement("1-rem-CHAIN-txt".to_string());
886            }
887            #[test]
888            fn two_rems_chain_txt() {
889                helper_arrangement("2-rem-CHAIN-txt".to_string());
890            }
891            #[test]
892            fn blank_same_name_saved() {
893                helper_arrangement("blank-samename-saved-chktest".to_string());
894            }
895            #[test]
896            fn blank_same_name_unsaved() {
897                helper_arrangement("blank-samename-unsaved-chktest".to_string());
898            }
899        }
900
901        #[test]
902        fn test_project() {
903            let outfile = std::env::temp_dir().join("ot-tools-actions-project-load-test-ok.work");
904            let yaml = get_project_dirpath().join("project.yaml");
905            let r = yaml_file_to_bin_file::<ProjectFile>(&yaml, &outfile);
906            let _ = std::fs::remove_file(&outfile);
907            println!("{r:?}");
908            assert!(r.is_ok())
909        }
910
911        #[test]
912        fn test_project_matches_blank() {
913            let testfile = get_project_dirpath().join("blank.work");
914            let outfile = std::env::temp_dir().join("ot-tools-actions-project-load-test-full.work");
915            let yaml = get_project_dirpath().join("project.yaml");
916
917            let r = yaml_file_to_bin_file::<ProjectFile>(&yaml, &outfile);
918
919            let written = read_type_from_bin_file::<ProjectFile>(&outfile).unwrap();
920            let valid = read_type_from_bin_file::<ProjectFile>(&testfile).unwrap();
921
922            let _ = std::fs::remove_file(&outfile);
923            println!("{r:?}");
924            assert!(r.is_ok());
925            assert_eq!(written, valid)
926        }
927
928        #[test]
929        fn test_bank() {
930            let outfile = std::env::temp_dir().join("ot-tools-actions-bank-load-test-ok.work");
931            let yaml = get_bank_dirpath().join("blank.yaml");
932            let r = yaml_file_to_bin_file::<BankFile>(&yaml, &outfile);
933            let _ = std::fs::remove_file(&outfile);
934            println!("{r:?}");
935            assert!(r.is_ok())
936        }
937
938        #[test]
939        fn test_sample() {
940            let outfile = std::env::temp_dir().join("ot-tools-actions-sample-load-test-ok.work");
941            let yaml = get_samples_dirpath().join("chain.yaml");
942            let r = yaml_file_to_bin_file::<SampleAttributes>(&yaml, &outfile);
943            let _ = std::fs::remove_file(&outfile);
944            println!("{r:?}");
945            assert!(r.is_ok());
946        }
947    }
948
949    mod bin_file_to_yaml_file_ok {
950        use super::*;
951        #[test]
952        // Windows will add on carriage returns...
953        #[cfg(not(target_os = "windows"))]
954        fn arrangement_blank() {
955            let valid_yaml_path = get_arrange_dirpath().join("blank.yaml");
956            let binpath = get_arrange_dirpath().join("blank.work");
957            let outyaml = std::env::temp_dir().join("serde-ot-bin2yaml-arrange-blank.yaml");
958
959            let r = crate::bin_file_to_yaml_file::<super::ArrangementFile>(&binpath, &outyaml);
960            let written = crate::read_str_file(&outyaml).unwrap();
961            let valid = crate::read_str_file(&valid_yaml_path).unwrap();
962
963            let _ = std::fs::remove_file(&outyaml);
964            println!("{r:?}");
965            assert!(r.is_ok());
966            assert_eq!(valid, written);
967        }
968
969        #[test]
970        // Windows will add on carriage returns...
971        #[cfg(not(target_os = "windows"))]
972        fn arrangement_full_options() {
973            let valid_yaml_path = get_arrange_dirpath().join("full-options.yaml");
974            let binpath = get_arrange_dirpath().join("full-options.work");
975            let outyaml = std::env::temp_dir().join("serde-ot-bin2yaml-arrange-fulloptions.yaml");
976
977            let r = crate::bin_file_to_yaml_file::<super::ArrangementFile>(&binpath, &outyaml);
978            let written = crate::read_str_file(&outyaml).unwrap();
979            let valid = crate::read_str_file(&valid_yaml_path).unwrap();
980
981            let _ = std::fs::remove_file(&outyaml);
982            println!("{r:?}");
983            assert!(r.is_ok());
984            assert_eq!(valid, written);
985        }
986
987        #[test]
988        // Windows will add on carriage returns...
989        #[cfg(not(target_os = "windows"))]
990        fn arrangement_one_blank_reminder_row() {
991            let valid_yaml_path = get_arrange_dirpath().join("1-rem-blank-txt.yaml");
992            let binpath = get_arrange_dirpath().join("1-rem-blank-txt.work");
993            let outyaml =
994                std::env::temp_dir().join("serde-ot-bin2yaml-arrange-onereminderrow.yaml");
995
996            let r = crate::bin_file_to_yaml_file::<super::ArrangementFile>(&binpath, &outyaml);
997            let written = crate::read_str_file(&outyaml).unwrap();
998            let valid = crate::read_str_file(&valid_yaml_path).unwrap();
999
1000            let _ = std::fs::remove_file(&outyaml);
1001            println!("{r:?}");
1002            assert!(r.is_ok());
1003            assert_eq!(valid, written);
1004        }
1005
1006        #[test]
1007        fn test_bank() {
1008            let outfile = std::env::temp_dir().join("ot-tools-actions-bin2yaml-bank-ok.yaml");
1009            let binfile = get_blank_proj_dirpath().join("bank01.work");
1010            let r = bin_file_to_yaml_file::<BankFile>(&binfile, &outfile);
1011            let _ = std::fs::remove_file(&outfile);
1012            assert!(r.is_ok())
1013        }
1014
1015        #[test]
1016        fn test_project() {
1017            let outfile = std::env::temp_dir().join("ot-tools-actions-bin2yaml-project-ok.yaml");
1018            let binfile = get_blank_proj_dirpath().join("project.work");
1019            let r = bin_file_to_yaml_file::<ProjectFile>(&binfile, &outfile);
1020            let _ = std::fs::remove_file(&outfile);
1021            assert!(r.is_ok())
1022        }
1023
1024        #[test]
1025        fn test_sample() {
1026            let outfile = std::env::temp_dir().join("ot-tools-actions-bin2yaml-sample-ok.yaml");
1027            let binfile = get_samples_dirpath().join("sample.ot");
1028            let r = bin_file_to_yaml_file::<SampleAttributes>(&binfile, &outfile);
1029            let _ = std::fs::remove_file(&outfile);
1030            assert!(r.is_ok())
1031        }
1032    }
1033
1034    mod bin_file_to_json_file_ok {
1035        use super::*;
1036
1037        #[test]
1038        fn arrangement_blank() {
1039            // let valid_json_path = std::path::Path::new("TODO");
1040
1041            let binpath = get_arrange_dirpath().join("blank.work");
1042            let outjson = std::env::temp_dir().join("serde-ot-bin2yaml-arrange-blank.json");
1043
1044            let r = crate::bin_file_to_json_file::<super::ArrangementFile>(&binpath, &outjson);
1045            let written = crate::read_str_file(&outjson);
1046            // let valid = crate::read_str_file(&valid_json_path).unwrap();
1047
1048            let _ = std::fs::remove_file(&outjson);
1049            println!("{r:?}");
1050            assert!(r.is_ok());
1051            assert!(written.is_ok());
1052            // assert_eq!(valid, written);
1053        }
1054
1055        #[test]
1056        fn arrangement_full_options() {
1057            // let valid_json_path = std::path::Path::new("TODO");
1058            let binpath = get_arrange_dirpath().join("full-options.work");
1059            let outjson = std::env::temp_dir().join("serde-ot-bin2yaml-arrange-full.json");
1060
1061            let r = crate::bin_file_to_json_file::<super::ArrangementFile>(&binpath, &outjson);
1062            let written = crate::read_str_file(&outjson);
1063            // let valid = crate::read_str_file(&valid_json_path).unwrap();
1064
1065            let _ = std::fs::remove_file(&outjson);
1066            println!("{r:?}");
1067            assert!(r.is_ok());
1068            assert!(written.is_ok());
1069            // assert_eq!(valid, written);
1070        }
1071
1072        #[test]
1073        fn arrangement_one_reminder_row() {
1074            // let valid_json_path = std::path::Path::new("TODO");
1075            let binpath = get_arrange_dirpath().join("1-rem-blank-txt.work");
1076            let outjson =
1077                std::env::temp_dir().join("serde-ot-bin2yaml-arrange-one_rem_row_blank.json");
1078
1079            let r = crate::bin_file_to_json_file::<super::ArrangementFile>(&binpath, &outjson);
1080            let written = crate::read_str_file(&outjson);
1081            // let valid = crate::read_str_file(&valid_json_path).unwrap();
1082
1083            let _ = std::fs::remove_file(&outjson);
1084            println!("{r:?}");
1085            assert!(r.is_ok());
1086            assert!(written.is_ok());
1087            // assert_eq!(valid, written);
1088        }
1089
1090        #[test]
1091        fn test_bank() {
1092            let outfile = std::env::temp_dir().join("ot-tools-actions-bin2yaml-bank-ok.json");
1093            let binfile = get_blank_proj_dirpath().join("bank01.work");
1094            let r = bin_file_to_json_file::<BankFile>(&binfile, &outfile);
1095            let _ = std::fs::remove_file(&outfile);
1096            assert!(r.is_ok())
1097        }
1098
1099        #[test]
1100        fn test_project() {
1101            let outfile = std::env::temp_dir().join("ot-tools-actions-bin2yaml-project-ok.json");
1102            let binfile = get_blank_proj_dirpath().join("project.work");
1103            let r = bin_file_to_json_file::<ProjectFile>(&binfile, &outfile);
1104            let _ = std::fs::remove_file(&outfile);
1105            assert!(r.is_ok())
1106        }
1107
1108        #[test]
1109        fn test_sample() {
1110            let outfile = std::env::temp_dir().join("ot-tools-actions-bin2yaml-sample-ok.json");
1111            let binfile = get_samples_dirpath().join("sample.ot");
1112            let r = bin_file_to_json_file::<SampleAttributes>(&binfile, &outfile);
1113            let _ = std::fs::remove_file(&outfile);
1114            assert!(r.is_ok())
1115        }
1116    }
1117
1118    // TODO: Add more cases!
1119    mod json_file_to_bin_file_ok {
1120        // #[test]
1121        // fn test_arrangement() {
1122        //     let outfile = std::env::temp_dir().join("ot-tools-actions-arrangement-load-test-ok.work");
1123        //     let yaml = PathBuf::from("TODO");
1124        //     // TODO!
1125        //     let r = yaml_file_to_bin_file::<ArrangementFile>(&yaml, &outfile);
1126        //     let _ = std::fs::remove_file(&outfile);
1127        //     assert!(r.is_ok())
1128        // }
1129
1130        // #[test]
1131        // fn test_bank() {
1132        //     let outfile = std::env::temp_dir().join("ot-tools-actions-bank-load-test-ok.work");
1133        //     let yaml = PathBuf::from("TODO");
1134        //     let r = yaml_file_to_bin_file::<Bank>(&yaml, &outfile);
1135        //     let _ = std::fs::remove_file(&outfile);
1136        //     assert!(r.is_ok())
1137        // }
1138
1139        // #[test]
1140        // fn test_project() {
1141        //     let outfile = std::env::temp_dir().join("ot-tools-actions-project-load-test-ok.work");
1142        //     let yaml = PathBuf::from("../data/tests/projects/project.yaml");
1143        //     let r = yaml_file_to_bin_file::<Project>(&yaml, &outfile);
1144        //     let _ = std::fs::remove_file(&outfile);
1145        //     assert!(r.is_ok())
1146        // }
1147
1148        // #[test]
1149        // fn test_sample() {
1150        //     let outfile = std::env::temp_dir().join("ot-tools-actions-sample-load-test-ok.work");
1151        //     let yaml = PathBuf::from("TODO");
1152        //     let r = yaml_file_to_bin_file::<SampleAttributes>(&yaml, &outfile);
1153        //     let _ = std::fs::remove_file(&outfile);
1154        //     assert!(r.is_ok())
1155        // }
1156    }
1157
1158    mod read_type_from_bin_file_ok {
1159        use super::*;
1160
1161        #[test]
1162        fn arrangement_blank() {
1163            let binfile = get_arrange_dirpath().join("blank.work");
1164            let r = read_type_from_bin_file::<ArrangementFile>(&binfile);
1165            assert!(r.is_ok())
1166        }
1167
1168        #[test]
1169        fn arrangement_full_options() {
1170            let binfile = get_arrange_dirpath().join("full-options.work");
1171            let r = read_type_from_bin_file::<ArrangementFile>(&binfile);
1172            assert!(r.is_ok())
1173        }
1174
1175        #[test]
1176        fn arrangement_one_reminder() {
1177            let binfile = get_arrange_dirpath().join("1-rem-blank-txt.work");
1178            let r = read_type_from_bin_file::<ArrangementFile>(&binfile);
1179            assert!(r.is_ok())
1180        }
1181
1182        #[test]
1183        fn test_read_type_from_bin_file_bank() {
1184            let binfile = get_blank_proj_dirpath().join("bank01.work");
1185            let r = read_type_from_bin_file::<BankFile>(&binfile);
1186            assert!(r.is_ok())
1187        }
1188
1189        #[test]
1190        fn test_read_type_from_bin_file_project() {
1191            let binfile = get_blank_proj_dirpath().join("project.work");
1192            let r = read_type_from_bin_file::<ProjectFile>(&binfile);
1193            assert!(r.is_ok())
1194        }
1195
1196        #[test]
1197        fn test_read_type_from_bin_file_sample() {
1198            let binfile = get_samples_dirpath().join("sample.ot");
1199            let r = read_type_from_bin_file::<SampleAttributes>(&binfile);
1200            assert!(r.is_ok())
1201        }
1202    }
1203
1204    mod write_type_from_bin_file_ok {
1205        // TODO
1206    }
1207
1208    mod read_bin_file_ok {
1209        use super::*;
1210
1211        #[test]
1212        fn arrangement_blank() {
1213            let binfile = get_arrange_dirpath().join("blank.work");
1214            let r = read_bin_file(&binfile);
1215            assert!(r.is_ok())
1216        }
1217
1218        #[test]
1219        fn arrangement_full_options() {
1220            let binfile = get_arrange_dirpath().join("full-options.work");
1221            let r = read_bin_file(&binfile);
1222            assert!(r.is_ok())
1223        }
1224
1225        #[test]
1226        fn arrangement_one_reminder() {
1227            let binfile = get_arrange_dirpath().join("1-rem-blank-txt.work");
1228            let r = read_bin_file(&binfile);
1229            assert!(r.is_ok())
1230        }
1231
1232        #[test]
1233        fn test_read_bin_file_bank() {
1234            let binfile = get_blank_proj_dirpath().join("bank01.work");
1235            let r = read_bin_file(&binfile);
1236            assert!(r.is_ok())
1237        }
1238
1239        #[test]
1240        fn test_read_bin_file_project() {
1241            let binfile = get_blank_proj_dirpath().join("project.work");
1242            let r = read_bin_file(&binfile);
1243            assert!(r.is_ok())
1244        }
1245
1246        #[test]
1247        fn test_read_bin_file_sample() {
1248            let binfile = get_samples_dirpath().join("sample.ot");
1249            let r = read_bin_file(&binfile);
1250            assert!(r.is_ok())
1251        }
1252    }
1253
1254    mod write_bin_file_ok {
1255        // TODO
1256    }
1257
1258    mod read_str_file_ok {
1259        // TODO
1260    }
1261
1262    mod write_str_file_ok {
1263        // TODO
1264    }
1265
1266    // TODO: This probably shouldn't be here...
1267    mod project_read {
1268        use super::*;
1269        use crate::projects::metadata::ProjectMetadata;
1270        use crate::projects::settings::ProjectSettings;
1271        use crate::projects::slots::ProjectSampleSlot;
1272        use crate::projects::states::ProjectStates;
1273        use crate::projects::ProjectFile;
1274        // can read a project file without errors
1275        #[test]
1276        fn test_read_default_project_work_file() {
1277            let infile = get_blank_proj_dirpath().join("project.work");
1278            assert!(read_type_from_bin_file::<ProjectFile>(&infile).is_ok());
1279        }
1280
1281        // test that the metadata section is correct
1282        #[test]
1283        fn test_read_default_project_work_file_metadata() {
1284            let infile = get_blank_proj_dirpath().join("project.work");
1285            let p = read_type_from_bin_file::<ProjectFile>(&infile).unwrap();
1286
1287            let correct = ProjectMetadata::default();
1288
1289            assert_eq!(p.metadata, correct);
1290        }
1291
1292        // test that the states section is correct
1293        #[test]
1294        fn test_read_default_project_work_file_states() {
1295            let infile = get_blank_proj_dirpath().join("project.work");
1296            let p = read_type_from_bin_file::<ProjectFile>(&infile).unwrap();
1297
1298            let correct = ProjectStates::default();
1299
1300            assert_eq!(p.states, correct);
1301        }
1302
1303        // test that the states section is correct
1304        #[test]
1305        fn test_read_default_project_work_file_settings() {
1306            let infile = get_blank_proj_dirpath().join("project.work");
1307            let p = read_type_from_bin_file::<ProjectFile>(&infile).unwrap();
1308
1309            let correct = ProjectSettings::default();
1310
1311            assert_eq!(p.settings, correct);
1312        }
1313
1314        // test that the states section is correct
1315        #[test]
1316        fn test_read_default_project_work_file_sslots() {
1317            let infile = get_blank_proj_dirpath().join("project.work");
1318            let p = read_type_from_bin_file::<ProjectFile>(&infile).unwrap();
1319            let default_sslots = ProjectSampleSlot::defaults();
1320
1321            assert_eq!(p.slots, default_sslots);
1322        }
1323
1324        // test that reading and writing a single project gives the same outputs
1325        #[test]
1326        fn test_read_write_default_project_work_file() {
1327            let infile = get_blank_proj_dirpath().join("project.work");
1328            let outfile = std::env::temp_dir().join("default_1.work");
1329            let p = read_type_from_bin_file::<ProjectFile>(&infile).unwrap();
1330            let _ = write_type_to_bin_file::<ProjectFile>(&p, &outfile);
1331
1332            let p_reread = read_type_from_bin_file::<ProjectFile>(&infile).unwrap();
1333
1334            assert_eq!(p, p_reread)
1335        }
1336    }
1337}