prtgn_encoding 0.3.1

Encoding and decoding for the PRTGN file format
Documentation
//! # Overview
//!
//! `PRTGN_encoding` (listed as `prtgn_encoding` for Cargo.TOML) is a library used by programs compatible with PRTGN files to encode and decode them.
//!
//! Any file can be encoded with PRTGN, though for user convenience it is highly recommended to use the .prtgn extension. What is being written isn't a text file, simply add the original file extension to the end. Such as .prtgn_wav does.
//!
//! Going along with that, anything can be encoded with PRTGN. As long as what is given to the `write` function as a string.
//!     An example of this is used by PRTGN for the wav file. [wav_converter.rs | PRTGN version 0.3.1, added in version 0.3.0](https://github.com/PRTGN-Development-Team/.prtgn/blob/83d6a200cdf14e82b84684480198a63ae40c63da/src/command/prtgn_wav/wav_converter.rs).
//!     `wav_converter.rs` takes a wav file, reads its data, converts the data buffer to a string, and then writes it to a `prtgn_wav` file. The opposite is done for playing the wav file.
//!
//! Simply add PRTGN_encoding to your Cargo.TOML and add the following to your file to access commands depending on what you need!
//!
//! ## Read Only
//! ```Rust
//! use prtgn_encoding::read;
//! ```
//!
//! ## Write Only
//! ```Rust
//! use prtgn_encoding::write;
//! ```
//!
//! ## Write With Compression
//! ```Rust
//! use prtgn_encoding::compress_write;
//! ```
//! 
//! ## Read and Write
//! ```Rust
//! use prtgn_encoding::{read, write};
//! ```
//!
//! ## Read and Write With Compression
//! ```Rust
//! use prtgn_encoding::{read, compress_write};
//! ```
//!
//!
//! ---
//! ---
//!
//!
//! # Example File Usage
//!
//!
//! ## Basic Write Example
//! ```commandline
//! cargo run --example basic_write
//! ```
//!
//! ## Basic Write Example With Compression
//! ```commandline
//! cargo run --example compressed_write
//! ```
//!
//! ## Basic Read Example
//!
//! **Run a write example first in order to have a correctly encoded PRTGN file with the right name**
//!
//! ```commandline
//! cargo run --example basic_read
//! ```
//!
//! ## Read Write Combo Example
//! ```commandline
//! cargo run --example read_write
//! ```
//!
//! ## Compressed Read Write Combo Example
//! ```commandline
//! cargo run --example compressed_read_write
//! ```
//!
//!
//! # Development Note
//!
//! To test documentation, run the following.
//! ```commandline
//! cargo doc --open
//! ```

use std::fs::File;
use std::io::prelude::*;
use std::io::Result;
use std::io::{Error, ErrorKind};
use std::string::FromUtf8Error;
use lz4_flex::{frame};
use lz4_flex::frame::FrameDecoder;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn read_write_test() {

        println!("Read write test");
        println!();
        println!("Hello from the PRTGN Development Team");
        println!();

        let filename ="test.prtgn".to_string();
        let text = "Hello from PRTGN Encoding!".to_string();
        let og_txt = text.clone();
        write(filename.clone(), text).unwrap();

        let decoded = read(filename.clone()).unwrap().to_string();

        println!("Original : {:?}", og_txt);

        println!("Decoded  : {:?}", decoded);
        assert_eq!(decoded, og_txt);

        println!("Test passed!");
        println!();
        println!("------------------------------------------------------------");
        println!();

        std::fs::remove_file(filename).unwrap();
    }

    #[test]
    fn read_write_test_compressed() {

        println!("Read write test with compression");
        println!();
        println!("Hello from the PRTGN Development Team");
        println!();

        let filename ="test_compressed.prtgn".to_string();
        let text = "Hello from PRTGN Encoding. Now with lossless compression!".to_string();
        let og_txt = text.clone();
        compress_write(filename.clone(), text).unwrap();

        let decoded = read(filename.clone()).unwrap().to_string();

        println!("Original : {:?}", og_txt);

        println!("Decoded  : {:?}", decoded);

        assert_eq!(decoded, og_txt);

        println!("Test passed!");
        println!();
        println!("------------------------------------------------------------");
        println!();

        std::fs::remove_file(filename).unwrap();
    }
}

const XOR_KEY: u8 = 0x66;
const FILE_HEADER: &[u8] = b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team | Version 0.3.0 | \x66 ";
const FILE_HEADER_COMPRESSED: &[u8] = b"Encoded with PRTGN, compressed with lz4_flex | https://github.com/PRTGN-Development-Team | Version 0.3.0 | \x66 ";

    /// Writes to a file with PRTGN encoding.
    ///
    /// # Examples
    ///
    /// ```Rust
    ///     let filename = "test.prtgn".to_string();
    ///
    ///     let text = "Hello world! This is some example text.".to_string();
    ///
    ///     write(filename, text).unwrap();
    /// ```
    pub fn write(filename: String, text: String) -> Result<()> {

        let mut file = File::create(filename)?;

        file.write_all(FILE_HEADER)?;

        let encoded_bytes: Vec<u8> = text.as_bytes().iter().map(|byte| byte ^ XOR_KEY).collect();

        file.write_all(&encoded_bytes)?;

        Ok(())
    }


    /// Writes a PRTGN encoded file with lossless compression
    ///
    ///
    /// ```Rust
    ///     let filename = "test.prtgn".to_string();
    ///
    ///     let text = "Hello world! This is some example text.".to_string();
    ///
    ///     compress_write(filename, text).unwrap();
    /// ```
    pub fn compress_write(filename: String, text: String) -> Result<()> {

        let mut file = frame::FrameEncoder::new(File::create(filename)?);

        file.write_all(FILE_HEADER_COMPRESSED)?;

        let encoded_bytes: Vec<u8> = text.as_bytes().iter().map(|byte| byte ^ XOR_KEY).collect();

        file.write_all(&encoded_bytes)?;

        file.finish()?;

        Ok(())
    }

    /// Reads a PRTGN encoded file.
    ///
    /// # Examples
    ///
    /// ```Rust
    ///     let filename = "test.prtgn".to_string();
    ///
    ///     let read_text = read(filename).unwrap().to_string();
    ///
    ///     println!("{}", read_text);
    /// ```
    ///
    /// If the file is using an older encoding standard it is automatically updated to the newest version.
    // pub fn read(filename: String) -> Result<String> {
    //     let mut file = File::open(filename.clone())?;
    //
    //     let mut header_buffer = vec![0u8; FILE_HEADER.len()];
    //     file.read_exact(&mut header_buffer)?;
    //
    //     println!("Header buffer:            {:?}", header_buffer);
    //     println!("FILE_HEADER:              {:?}", FILE_HEADER);
    //     println!("FILE_HEADER_COMPRESSED:   {:?}", FILE_HEADER_COMPRESSED);
    //
    //     let version = if header_buffer.starts_with(FILE_HEADER) {
    //         "newest"
    //     } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team | Version 0.2.0 | \x66 ") {
    //         "0.2.0"
    //     } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team \x66 ") {
    //         "0.1.3"
    //     } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team\x01\xFF\x00 ") {
    //         "0.1.2"
    //     } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team\x01\xFF\x00") {
    //         "0.1.1"
    //     } else {
    //         let temp = File::open(filename.clone())?;
    //
    //         let mut decoder = FrameDecoder::new(temp);
    //         let mut decompressed_buffer = Vec::new();
    //         decoder.read_to_end(&mut decompressed_buffer)?;
    //
    //         println!("decompressed buffer:      {:?}", decompressed_buffer);
    //
    //         if decompressed_buffer.starts_with(FILE_HEADER_COMPRESSED) {
    //             "newest.24601"
    //         } else if !decompressed_buffer.starts_with(FILE_HEADER_COMPRESSED) {
    //             return Err(Error::new(ErrorKind::InvalidData, "Not a valid PRTGN encoded file."));
    //         } else {
    //             return Err(Error::new(ErrorKind::Unsupported,"Wait, how did we get here?"));
    //         }
    //     };
    //
    //
    //     println!("Detected version: {}", version);
    //
    //     if version != "newest" && version != "newest.24601" {
    //
    //         println!("The version of encoding is outdated. Automatically rewriting the file after read.");
    //         println!("---------------------------------------------");
    //         print!("");
    //
    //     };
    //
    //     let mut file_buffer = Vec::new();
    //     file.read_to_end(&mut file_buffer)?;
    //
    //     let mut decompresser = FrameDecoder::new(file);
    //     let mut decompressed_buffer = Vec::new();
    //     decompresser.read_to_end(&mut decompressed_buffer)?;
    //
    //
    //     let decoded_byte: Vec<u8> = if version == "newest" {
    //         file_buffer.iter().map(|byte| byte ^ XOR_KEY).collect()
    //     } else if version == "0.1.3" {
    //         file_buffer.iter().map(|byte| byte ^ 0x66).collect()
    //     } else if version == "0.1.2" {
    //         file_buffer.iter().map(|byte| byte ^ 0xA3).collect()
    //     } else if version == "0.1.1" {
    //         file_buffer.iter().map(|byte| byte ^ 0xA3).collect()
    //     } else if version == "newest.24601" {
    //         decompressed_buffer.iter().map(|byte| byte ^ XOR_KEY).collect()
    //     } else {
    //         println!("No decoding for you. Old sport.");
    //         unreachable!()
    //     };
    //
    //     String::from_utf8(decoded_byte).map_err(|e: FromUtf8Error| Error::new(ErrorKind::InvalidData, e.to_string()))
    // }
    //


    pub fn read(filename: String) -> Result<String> {
        let mut file = File::open(filename.clone())?;

        let mut header_buffer = vec![0u8; FILE_HEADER.len()];
        file.read_exact(&mut header_buffer)?;

        // println!("Header buffer:            {:?}", header_buffer);
        // println!("FILE_HEADER:              {:?}", FILE_HEADER);
        // println!("FILE_HEADER_COMPRESSED:   {:?}", FILE_HEADER_COMPRESSED);

        let mut outer_decompressed: Vec<u8> = Vec::new();

        let version = if header_buffer.starts_with(FILE_HEADER) {
            "newest"
        } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team | Version 0.2.0 | \x66 ") {
            "0.2.0"
        } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team \x66 ") {
            "0.1.3"
        } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team\x01\xFF\x00 ") {
            "0.1.2"
        } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team\x01\xFF\x00") {
            "0.1.1"
        } else {
            let temp = File::open(filename.clone())?;
            let mut decoder = FrameDecoder::new(temp);
            decoder.read_to_end(&mut outer_decompressed)?;

            println!("decompressed buffer:      {:?}", outer_decompressed);

            if outer_decompressed.starts_with(FILE_HEADER_COMPRESSED) {
                "newest.24601"
            } else {
                return Err(Error::new(ErrorKind::InvalidData, "Not a valid PRTGN encoded file."));
            }
        };

        println!("Detected version: {}", version);

        if version != "newest" && version != "newest.24601" {
            println!("The version of encoding is outdated. Automatically rewriting the file after read.");
            println!("---------------------------------------------");
            print!("");
        };

        let mut file_buffer = Vec::new();
        file.read_to_end(&mut file_buffer)?;


        let decoded_byte: Vec<u8> = if version == "newest" {
            file_buffer.iter().map(|byte| byte ^ XOR_KEY).collect()
        } else if version == "0.1.3" {
            file_buffer.iter().map(|byte| byte ^ 0x66).collect()
        } else if version == "0.1.2" {
            file_buffer.iter().map(|byte| byte ^ 0xA3).collect()
        } else if version == "0.1.1" {
            file_buffer.iter().map(|byte| byte ^ 0xA3).collect()
        } else if version == "newest.24601" {
            // Skip the header bytes, then XOR the rest
            outer_decompressed[FILE_HEADER_COMPRESSED.len()..]
                .iter()
                .map(|byte| byte ^ XOR_KEY)
                .collect()
        } else {
            println!("No decoding for you. Old sport.");
            unreachable!()
        };

        String::from_utf8(decoded_byte)
            .map_err(|e: FromUtf8Error| Error::new(ErrorKind::InvalidData, e.to_string()))
    }