16 Commits
5 changed files with 251 additions and 104 deletions
+6
View File
@@ -1,3 +1,9 @@
# build system
.built
.released
.stripped
.tested
# ---> Rust # ---> Rust
# Generated by Cargo # Generated by Cargo
# will have compiled files and executables # will have compiled files and executables
+3 -3
View File
@@ -1,11 +1,11 @@
[package] [package]
name = "tacview-splitter" name = "tacview-splitter"
version = "0.3.0" version = "0.3.1"
authors = ["132nd-Professor <132nd-Professor>"] authors = ["132nd-Professor <132nd-Professor>"]
edition = "2018" edition = "2021"
[dependencies] [dependencies]
zip = ">= 0.5.13" zip = ">= 0.6.2"
[profile.release] [profile.release]
lto = true lto = true
+31 -4
View File
@@ -6,20 +6,47 @@ RELEASE_WINDOWS=tacview-splitter-win10-x86_64.zip
BIN_LINUX=target/x86_64-unknown-linux-gnu/release/tacview-splitter BIN_LINUX=target/x86_64-unknown-linux-gnu/release/tacview-splitter
BIN_WINDOWS=target/x86_64-pc-windows-gnu/release/tacview-splitter.exe BIN_WINDOWS=target/x86_64-pc-windows-gnu/release/tacview-splitter.exe
TEST_FILES=Tacview-20210606-222650-DCS-ATRM_2.7.0.443.txt.acmi Tacview-20210606-222650-DCS-ATRM_2.7.0.443.zip.acmi Tacview-20210608-085030-DCS-Georgia_At_War_v3.0.24_afternoon.txt.acmi Tacview-20210608-085030-DCS-Georgia_At_War_v3.0.24_afternoon.zip.acmi
default: release default: release
build: .built: src/lib.rs src/main.rs Cargo.toml
cargo build --release --target x86_64-unknown-linux-gnu cargo build --release --target x86_64-unknown-linux-gnu
cargo build --release --target x86_64-pc-windows-gnu cargo build --release --target x86_64-pc-windows-gnu
touch .built
strip: build build: .built
.stripped: .built
strip ${BIN_LINUX} ${BIN_WINDOWS} strip ${BIN_LINUX} ${BIN_WINDOWS}
touch .stripped
release: build strip strip: .stripped
.tested: .stripped
for file in ${TEST_FILES}; do \
mkdir -p test; \
cp testfiles/$$file test; \
cd test; \
../${BIN_LINUX}; \
cd ../ ; \
rm -rf test ; \
done
touch .tested
test: .tested
.released: .tested
rm -rf release; rm -rf release;
mkdir -p release/{windows,linux}/tacview-splitter-${VERSION}; mkdir -p release/{windows,linux}/tacview-splitter-${VERSION};
cp ${BIN_LINUX} release/linux/tacview-splitter-${VERSION} cp ${BIN_LINUX} release/linux/tacview-splitter-${VERSION}
cp ${BIN_WINDOWS} release/windows/tacview-splitter-${VERSION} cp ${BIN_WINDOWS} release/windows/tacview-splitter-${VERSION}
cd release/linux; tar -zcvf ${RELEASE_LINUX} tacview-splitter-${VERSION}; cp ${RELEASE_LINUX} .. cd release/linux; tar -zcvf ${RELEASE_LINUX} tacview-splitter-${VERSION}; cp ${RELEASE_LINUX} ..
cd release/windows; 7z a ${RELEASE_WINDOWS} tacview-splitter-${VERSION}; cp ${RELEASE_WINDOWS} .. cd release/windows; 7z a ${RELEASE_WINDOWS} tacview-splitter-${VERSION}; cp ${RELEASE_WINDOWS} ..
cd release; sha256sum ${RELEASE_LINUX} ${RELEASE_WINDOWS} > sha256sums.txt cd release; rm -rf linux windows; sha256sum ${RELEASE_LINUX} ${RELEASE_WINDOWS} > sha256sums.txt
touch .released
release: .released
clean:
rm -rf release target test .released .tested .stripped .built
+70 -21
View File
@@ -1,16 +1,19 @@
pub mod lib { pub mod lib {
use std::io::Write; use std::collections::HashSet;
use std::fs; use std::fs;
use std::io::Write;
use zip; use zip;
const ERR_CANNOT_WRITE_DATA: &str = "Could not write data"; const ERR_CANNOT_WRITE_DATA: &str = "Could not write data";
const ERR_CANNOT_OPEN_OUTPUT: &str = "Could not open output file"; const ERR_CANNOT_OPEN_OUTPUT: &str = "Could not open output file";
const ERR_CANNOT_BEGIN_FILE: &str = "Could not begin file in zip archive";
pub struct IDs<'a> { pub struct IDs<'a> {
pub blue: Vec<&'a str>, pub blue: HashSet<&'a str>,
pub red: Vec<&'a str>, pub red: HashSet<&'a str>,
pub violet: Vec<&'a str>, pub violet: HashSet<&'a str>,
pub unknown: Vec<&'a str>, pub unknown: HashSet<&'a str>,
} }
pub struct Descriptors<T: Write> { pub struct Descriptors<T: Write> {
@@ -49,50 +52,96 @@ pub mod lib {
impl<T: Write> Handling for Descriptors<T> { impl<T: Write> Handling for Descriptors<T> {
fn write(&mut self, header: Vec<String>, bodies_by_coalition: BodiesByCoalition) { fn write(&mut self, header: Vec<String>, bodies_by_coalition: BodiesByCoalition) {
for line in &header { for line in &header {
write!(self.blue, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); writeln!(self.blue, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
write!(self.red, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); writeln!(self.red, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
write!(self.violet, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); writeln!(self.violet, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
} }
for line in &bodies_by_coalition.blue { for line in &bodies_by_coalition.blue {
write!(self.blue, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); writeln!(self.blue, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
} }
for line in &bodies_by_coalition.red { for line in &bodies_by_coalition.red {
write!(self.red, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); writeln!(self.red, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
} }
for line in &bodies_by_coalition.violet { for line in &bodies_by_coalition.violet {
write!(self.violet, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); writeln!(self.violet, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
} }
} }
} }
impl Descriptors<fs::File> { impl Descriptors<fs::File> {
pub fn new_for_file(filenames: OutputFilenames) -> Descriptors<fs::File> { pub fn new(filenames: OutputFilenames) -> Descriptors<fs::File> {
let blue = fs::File::create(&filenames.txt.blue).expect(ERR_CANNOT_OPEN_OUTPUT); let blue = fs::File::create(&filenames.txt.blue).expect(ERR_CANNOT_OPEN_OUTPUT);
let red = fs::File::create(&filenames.txt.red).expect(ERR_CANNOT_OPEN_OUTPUT); let red = fs::File::create(&filenames.txt.red).expect(ERR_CANNOT_OPEN_OUTPUT);
let violet = fs::File::create(&filenames.txt.violet).expect(ERR_CANNOT_OPEN_OUTPUT); let violet = fs::File::create(&filenames.txt.violet).expect(ERR_CANNOT_OPEN_OUTPUT);
let descriptors = Descriptors { blue, red, violet }; Descriptors { blue, red, violet }
return descriptors
} }
} }
impl Descriptors<zip::ZipWriter<fs::File>> { impl Descriptors<zip::ZipWriter<fs::File>> {
pub fn new_for_zip(filenames: OutputFilenames) -> Descriptors<zip::ZipWriter<fs::File>> { pub fn new(filenames: OutputFilenames) -> Descriptors<zip::ZipWriter<fs::File>> {
let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated); let options = zip::write::FileOptions::default()
.compression_method(zip::CompressionMethod::Deflated);
let file = fs::File::create(&filenames.zip.blue).expect(ERR_CANNOT_OPEN_OUTPUT); let file = fs::File::create(&filenames.zip.blue).expect(ERR_CANNOT_OPEN_OUTPUT);
let mut blue = zip::ZipWriter::new(file); let mut blue = zip::ZipWriter::new(file);
blue.start_file(&filenames.txt.blue, options).unwrap(); blue.start_file(&filenames.txt.blue, options)
.expect(ERR_CANNOT_BEGIN_FILE);
let file = fs::File::create(&filenames.zip.red).expect(ERR_CANNOT_OPEN_OUTPUT); let file = fs::File::create(&filenames.zip.red).expect(ERR_CANNOT_OPEN_OUTPUT);
let mut red = zip::ZipWriter::new(file); let mut red = zip::ZipWriter::new(file);
red.start_file(&filenames.txt.red, options).unwrap(); red.start_file(&filenames.txt.red, options)
.expect(ERR_CANNOT_BEGIN_FILE);
let file = fs::File::create(&filenames.zip.violet).expect(ERR_CANNOT_OPEN_OUTPUT); let file = fs::File::create(&filenames.zip.violet).expect(ERR_CANNOT_OPEN_OUTPUT);
let mut violet = zip::ZipWriter::new(file); let mut violet = zip::ZipWriter::new(file);
violet.start_file(&filenames.txt.violet, options).unwrap(); violet
.start_file(&filenames.txt.violet, options)
.expect(ERR_CANNOT_BEGIN_FILE);
let descriptors = Descriptors{blue, red, violet}; Descriptors { blue, red, violet }
descriptors
} }
} }
pub fn sanity_check_output_filenames(
input_filename: &String,
output_filenames: &FilenamesVariant,
) {
if input_filename == &output_filenames.blue
|| input_filename == &output_filenames.red
|| input_filename == &output_filenames.violet
{
panic!("Output filenames were the same as input filenames")
}
}
pub fn get_output_filenames_individual(
input_filename: &str,
old_extension: &str,
new_extension: &str,
coalition: &str,
) -> String {
let mut output_extension = coalition.to_owned();
output_extension.push_str(new_extension);
input_filename.replace(old_extension, &output_extension)
}
}
#[cfg(test)]
mod tests {
use crate::lib::*;
#[test]
fn test_get_output_filenames_individual() {
let input_filename = "something.txt.acmi".to_string();
let extension = ".txt.acmi";
let coalition = "_blue";
let result =
get_output_filenames_individual(&input_filename, extension, extension, coalition);
let correct_result = "something_blue.txt.acmi";
assert_eq!(result, correct_result);
let extension_wrong = ".tXT.acmi";
let result =
get_output_filenames_individual(&input_filename, extension_wrong, extension, coalition);
assert_ne!(result, correct_result);
}
} }
+138 -73
View File
@@ -1,19 +1,23 @@
use std::collections::HashSet;
use std::io::{BufRead, BufReader};
use std::{fs, str}; use std::{fs, str};
use std::io::{BufRead, BufReader};
use zip;
use tacview_splitter::lib; use tacview_splitter::lib;
use tacview_splitter::lib::Handling; use tacview_splitter::lib::Handling;
const COMMENT: char = '#'; const COMMENT: char = '#';
const MINUS: char = '-'; const MINUS: char = '-';
const EXTENSION_ZIP: &str = ".zip.acmi"; const EXTENSION_ZIP: &str = ".zip.acmi";
const EXTENSION_TXT: &str = ".txt.acmi"; const EXTENSION_TXT: &str = ".txt.acmi";
const UNKNOWN: u8 = 0; #[derive(PartialEq)]
const TIMESTAMP: u8 = 1; enum LineType {
const DESTRUCTION: u8 = 2; Unknown,
const TELEMETRY: u8 = 3; Timestamp,
Destruction,
Telemetry,
}
fn main() { fn main() {
let (input_filename, is_zip) = find_input_file(); let (input_filename, is_zip) = find_input_file();
@@ -22,12 +26,12 @@ fn main() {
let (header, body) = split_into_header_and_body(lines); let (header, body) = split_into_header_and_body(lines);
let bodies_by_coalition = divide_body_by_coalition(&body); let bodies_by_coalition = divide_body_by_coalition(&body);
let output_filenames = get_output_filenames(&input_filename); let output_filenames = get_output_filenames(&input_filename, is_zip);
if is_zip { if is_zip {
let mut descriptors = lib::Descriptors::new_for_zip(output_filenames); let mut descriptors = lib::Descriptors::<zip::ZipWriter<fs::File>>::new(output_filenames);
descriptors.write(header, bodies_by_coalition); descriptors.write(header, bodies_by_coalition);
} else { } else {
let mut descriptors = lib::Descriptors::new_for_file(output_filenames); let mut descriptors = lib::Descriptors::<fs::File>::new(output_filenames);
descriptors.write(header, bodies_by_coalition); descriptors.write(header, bodies_by_coalition);
} }
} }
@@ -35,34 +39,44 @@ fn main() {
fn split_into_header_and_body(lines: Vec<String>) -> (Vec<String>, Vec<String>) { fn split_into_header_and_body(lines: Vec<String>) -> (Vec<String>, Vec<String>) {
let mut i = 0; let mut i = 0;
for line in &lines { for line in &lines {
if line.chars().nth(0).expect("malformed line") == COMMENT { if line.chars().next().expect("malformed line") == COMMENT {
break break;
} }
i += 1; i += 1;
} }
return (lines[..i].to_vec(), lines[i..].to_vec()); (lines[..i].to_vec(), lines[i..].to_vec())
} }
fn divide_body_by_coalition(body: &Vec<String>) -> lib::BodiesByCoalition { fn divide_body_by_coalition(body: &Vec<String>) -> lib::BodiesByCoalition {
let mut bbc = lib::BodiesByCoalition{blue: Vec::new(), red: Vec::new(), violet: Vec::new()}; let mut bbc = lib::BodiesByCoalition {
blue: Vec::new(),
red: Vec::new(),
violet: Vec::new(),
};
let mut continued = false; let mut continued = false;
let mut line_type: u8 = UNKNOWN; let mut line_type = LineType::Unknown;
let mut ids = lib::IDs{blue: Vec::new(), red: Vec::new(), violet: Vec::new(), unknown: Vec::new()}; let mut coalitions = lib::IDs {
blue: HashSet::new(),
red: HashSet::new(),
violet: HashSet::new(),
unknown: HashSet::new(),
};
for line in body { for line in body {
let result = process_line(continued, &mut ids, line, line_type); let result = process_line(continued, &mut coalitions, line, line_type);
line_type = result.0; line_type = result.0;
continued = result.1; continued = result.1;
let id = result.2; let id = result.2;
if line_type == TIMESTAMP { if line_type == LineType::Timestamp {
bbc.blue.push(line); bbc.blue.push(line);
bbc.red.push(line); bbc.red.push(line);
bbc.violet.push(line); bbc.violet.push(line);
} else { // destruction or telemetry } else {
if ids.blue.contains(&id) { // destruction or telemetry
if coalitions.blue.contains(&id) {
bbc.blue.push(line); bbc.blue.push(line);
} else if ids.red.contains(&id) { } else if coalitions.red.contains(&id) {
bbc.red.push(line); bbc.red.push(line);
} else if ids.violet.contains(&id) { } else if coalitions.violet.contains(&id) {
bbc.violet.push(line); bbc.violet.push(line);
} }
} }
@@ -70,63 +84,111 @@ fn divide_body_by_coalition(body: &Vec<String>) -> lib::BodiesByCoalition {
bbc bbc
} }
fn process_line<'a>(continued: bool, ids: &mut lib::IDs<'a>, line: &'a String, last_line_type: u8) -> (u8, bool, &'a str) { fn process_line<'a>(
let mut id = "both"; continued: bool,
coalitions: &mut lib::IDs<'a>,
line: &'a str,
last_line_type: LineType,
) -> (LineType, bool, &'a str) {
let mut id = "";
let line_type; let line_type;
let will_continue: bool;
if !continued { if !continued {
let first_char = line.chars().nth(0).expect("malformed line"); line_type = determine_line_type(line);
if first_char == COMMENT { if line_type == LineType::Telemetry {
line_type = TIMESTAMP; id = get_id_from_line(line);
} else if first_char == MINUS { assign_id_to_coalitions(coalitions, line, id)
line_type = DESTRUCTION;
} else {
line_type = TELEMETRY;
}
if line_type == TELEMETRY {
id = line
.split_once(',') // TODO catch the None
.unwrap()
.0;
//let id_str = id.to_string();
if line.contains("Color=") {
if line.contains("Color=Blue") {
ids.blue.push(id);
} else if line.contains("Color=Red") {
ids.red.push(id);
} else if line.contains("Color=Violet") {
ids.violet.push(id);
} else {
ids.unknown.push(id);
}
}
} }
} else { } else {
line_type = last_line_type; line_type = last_line_type;
} }
if line.ends_with("\\") { let line_will_continue = will_line_continue(line);
will_continue = true; (line_type, line_will_continue, id)
}
fn will_line_continue(line: &str) -> bool {
line.ends_with('\\')
}
fn get_id_from_line(line: &str) -> &str {
let result = line.split_once(',');
let split = match result {
Some(t) => t,
None => panic!("Could not get ID from line!"),
};
split.0 as _
}
fn assign_id_to_coalitions<'a>(coalitions: &mut lib::IDs<'a>, line: &'a str, id: &'a str) {
if line.contains("Color=") {
if line.contains("Color=Blue") {
coalitions.blue.insert(id);
} else if line.contains("Color=Red") {
coalitions.red.insert(id);
} else if line.contains("Color=Violet") {
coalitions.violet.insert(id);
} else { } else {
will_continue = false; coalitions.unknown.insert(id);
}
} }
(line_type, will_continue, id)
} }
fn get_output_filenames(input_filename: &String) -> lib::OutputFilenames { fn determine_line_type(line: &str) -> LineType {
let blue = input_filename.replace(".zip", "_blue.zip"); let first_char = line.chars().next().expect("malformed line");
let red = input_filename.replace(".zip", "_red.zip"); if first_char == COMMENT {
let violet = input_filename.replace(".zip", "_violet.zip"); LineType::Timestamp
} else if first_char == MINUS {
LineType::Destruction
} else {
LineType::Telemetry
}
}
fn get_output_filenames(input_filename: &String, is_zip: bool) -> lib::OutputFilenames {
let output_filenames_zip: lib::FilenamesVariant;
let output_filenames_txt: lib::FilenamesVariant;
if is_zip {
output_filenames_zip =
get_output_filenames_for_extension(input_filename, EXTENSION_ZIP, EXTENSION_ZIP);
output_filenames_txt =
get_output_filenames_for_extension(input_filename, EXTENSION_ZIP, EXTENSION_TXT);
} else {
output_filenames_zip = get_output_filenames_dummy();
output_filenames_txt =
get_output_filenames_for_extension(input_filename, EXTENSION_TXT, EXTENSION_TXT);
}
lib::OutputFilenames {
txt: output_filenames_txt,
zip: output_filenames_zip,
}
}
fn get_output_filenames_dummy() -> lib::FilenamesVariant {
let blue = "".to_string();
let red = "".to_string();
let violet = "".to_string();
lib::FilenamesVariant { blue, red, violet }
}
fn get_output_filenames_for_extension(
input_filename: &String,
old_extension: &str,
new_extension: &str,
) -> lib::FilenamesVariant {
let blue =
lib::get_output_filenames_individual(input_filename, old_extension, new_extension, "_blue");
let red =
lib::get_output_filenames_individual(input_filename, old_extension, new_extension, "_red");
let violet = lib::get_output_filenames_individual(
input_filename,
old_extension,
new_extension,
"_violet",
);
let output_filenames_zip = lib::FilenamesVariant { blue, red, violet }; let output_filenames_zip = lib::FilenamesVariant { blue, red, violet };
lib::sanity_check_output_filenames(input_filename, &output_filenames_zip);
// TODO make sure the replace was successful output_filenames_zip
let blue = input_filename.replace(".txt", "_blue.txt");
let red = input_filename.replace(".txt", "_red.txt");
let violet = input_filename.replace(".txt", "_violet.txt");
let output_filenames_txt = lib::FilenamesVariant{blue, red, violet};
let output_filenames = lib::OutputFilenames{txt: output_filenames_txt, zip: output_filenames_zip};
output_filenames
} }
fn find_input_file() -> (String, bool) { fn find_input_file() -> (String, bool) {
@@ -140,27 +202,30 @@ fn find_input_file() -> (String, bool) {
} else if filename.ends_with(EXTENSION_ZIP) { } else if filename.ends_with(EXTENSION_ZIP) {
return (filename, true); return (filename, true);
} }
}; }
println!("No tacview input file found in current directory."); println!("No tacview input file found in current directory.");
std::process::exit(1); std::process::exit(1);
} }
fn read_data(filename: &String, is_zip: bool) -> Vec<String> { fn read_data(filename: &String, is_zip: bool) -> Vec<String> {
let file = fs::File::open(filename).expect("Could not read from input file"); let file = fs::File::open(filename).expect("Could not read from input file");
let buf = BufReader::new(file); let buf = BufReader::new(file);
return if is_zip { return if is_zip {
let mut archive = zip::ZipArchive::new(buf).expect("Could not read zip data"); let mut archive = zip::ZipArchive::new(buf).expect("Could not read zip data");
let inner_file = archive.by_index(0).unwrap(); let inner_file = archive
.by_index(0)
.expect("Could not read telemetry file from zip archive");
let inner_buf = BufReader::new(inner_file); let inner_buf = BufReader::new(inner_file);
let lines: Vec<String> = inner_buf.lines() let lines: Vec<String> = inner_buf
.lines()
.map(|l| l.expect("Could not parse line")) .map(|l| l.expect("Could not parse line"))
.collect(); .collect();
lines lines
} else { } else {
let lines: Vec<String> = buf.lines() let lines: Vec<String> = buf
.lines()
.map(|l| l.expect("Could not parse line")) .map(|l| l.expect("Could not parse line"))
.collect(); .collect();
lines lines
} };
} }