ng_log/
lib.rs

1// The MIT License (MIT)
2//
3// Copyright (c) 2015 FaultyRAM
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22
23//! ngLog processing utilities.
24//!
25//! ngLog is a textual file format designed for recording gameplay events. Each
26//! line of text in an ngLog-formatted file represents an *event*, consisting
27//! of several parameters in the following order, each separated by an ASCII
28//! TAB control code:
29//!
30//! * A *timestamp*: a floating-point number representing the time in seconds
31//!   that have elapsed since gameplay began;
32//! * An optional *event class*, describing the category to which the event
33//!   belongs;
34//! * An *event ID*, describing the type of event which occurred;
35//! * Zero or more *event parameters*, each representing an arbitrary data
36//!   point associated with the event.
37//!
38//! ngLog was used in conjunction with ngStats and ngWorldStats to provide both
39//! local and online statistical analysis and tracking. Supported video games
40//! would create two copies of an ngLog file upon gameplay completion: a copy
41//! for local processing, and an encoded copy to be sent to a *world server*.
42//! This crate provides functionality for processing both forms, from either a
43//! `String` or a type that implements `std::io::Read`. For example:
44//!
45//! ```rust
46//! use ng_log::NgLog;
47//!
48//! use std::fs::OpenOptions;
49//! use std::io::Read;
50//! use std::string::ToString;
51//!
52//! let mut file = OpenOptions::new()
53//! 	.read(true)
54//! 	.open("./tests/ngLog_Example_Log_File.log.txt")
55//! 	.unwrap();
56//! let log = NgLog::local_from_reader(&mut file).unwrap();
57//! println!("{}", log.to_string());
58//! ```
59
60use std::io::Error as IoError;
61use std::io::ErrorKind as IoErrorKind;
62use std::io::Result as IoResult;
63use std::io::Read;
64use std::string::ToString;
65
66/// A type representing an ngLog-formatted file.
67pub struct NgLog {
68	/// A collection of ngLog events.
69	pub events: Vec<NgEvent>,
70}
71
72/// A type representing an ngLog event.
73pub struct NgEvent {
74	/// A floating-point value representing the elapsed time since gameplay began.
75	pub timestamp:    String,
76	/// The category to which this event belongs, if any.
77	pub event_class:  Option<String>,
78	/// The type of this event.
79	pub event_id:     String,
80	/// Optional data points associated with this event.
81	pub event_params: Vec<String>,
82}
83
84impl NgLog {
85	/// Constructs a new `NgLog` instance, allocating memory for at least
86	/// `capacity` events.
87	pub fn new(capacity: usize) -> NgLog {
88		NgLog {
89			events: Vec::with_capacity(capacity),
90		}
91	}
92
93	/// Constructs a new `NgLog` instance using data from a type implementing
94	/// `std::io::Read`. The data is interpreted as a UTF-8 string.
95	///
96	/// # Failures
97	///
98	/// If the input data is either not valid UTF-8 or malformed, this method
99	/// returns an `std::io::Error` instance describing the error.
100	pub fn local_from_reader<T>(reader: &mut T) -> IoResult<NgLog> where
101	T: Read {
102		let mut data: Vec<u8> = Vec::with_capacity(0);
103		try!(reader.read_to_end(&mut data));
104		NgLog::from_string(&try!(String::from_utf8(data).map_err(|e|
105			IoError::new(IoErrorKind::InvalidData, format!("{}", e))
106		)))
107	}
108
109	/// Constructs a new `NgLog` instance using data from a type implementing
110	/// `std::io::Error`. The data is fed through a decoding algorithm, then
111	/// interpreted as a UTF-8 string.
112	///
113	/// # Failures
114	///
115	/// If the input data is either not valid UTF-8 or malformed, this method
116	/// returns an `std::io::Error` instance describing the error.
117	pub fn world_from_reader<T>(reader: &mut T) -> IoResult<NgLog> where
118	T: Read {
119		let mut data: Vec<u8> = Vec::with_capacity(0);
120		try!(reader.read_to_end(&mut data));
121		if data.len() % 2 != 0 {
122			return Err(IoError::new(IoErrorKind::InvalidData, "Non-even log length"))
123		}
124		// TODO: check if this format is used by games other than UT99.
125		let mut decoded: Vec<u8> = Vec::with_capacity(data.len() / 2);
126		for v in data.chunks(2) {
127			decoded.push(v[0] ^ v[1]);
128		}
129		NgLog::from_string(&try!(String::from_utf8(decoded).map_err(|e|
130			IoError::new(IoErrorKind::InvalidData, format!("{}", e))
131		)))
132	}
133
134	/// Constructs a new `NgLog` instance from the given input string.
135	///
136	/// # Failures
137	///
138	/// If the input data is malformed, this method returns an `std::io::Error`
139	/// instance describing the error.
140	pub fn from_string(s: &String) -> IoResult<NgLog> {
141		let mut log = NgLog::new(s.len());
142		for line in s.lines() {
143			let event = try!(NgEvent::from_string(&String::from(line)));
144			log.events.push(event);
145		}
146		Ok(log)
147	}
148}
149
150impl ToString for NgLog {
151	fn to_string(&self) -> String {
152		let mut s = String::with_capacity(0);
153		for v in &self.events {
154			s = s + &v.to_string() + &"\n";
155		}
156		s
157	}
158}
159
160impl NgEvent {
161	/// Constructs a new `NgEvent` instance from the given arguments.
162	pub fn new(timestamp: String, class: Option<String>, id: String, params: Vec<String>) -> NgEvent {
163		NgEvent {
164			timestamp:    timestamp,
165			event_class:  class,
166			event_id:     id,
167			event_params: params,
168		}
169	}
170
171	/// Constructs a new `NgEvent` instance from the given input string.
172	///
173	/// # Failures
174	///
175	/// If the given data is malformed, this method returns an `std::io::Error`
176	/// instance describing the error.
177	pub fn from_string(s: &String) -> IoResult<NgEvent> {
178		let mut columns: Vec<String> = s.split('\t').map(|s| String::from(s)).collect();
179		if columns.len() < 2 {
180			return Err(IoError::new(IoErrorKind::InvalidData, "Bad event string"))
181		} else if columns.len() == 2 {
182			Ok(NgEvent::new(
183				columns.remove(0),
184				None,
185				columns.remove(0),
186				Vec::with_capacity(0)
187			))
188		} else {
189			Ok(NgEvent::new(
190				columns.remove(0),
191				Some(columns.remove(0)),
192				columns.remove(0),
193				columns.clone()
194			))
195		}
196	}
197}
198
199impl ToString for NgEvent {
200	fn to_string(&self) -> String {
201		let mut s = String::with_capacity(0);
202		s = s + &self.timestamp;
203		if let Some(v) = self.event_class.clone() {
204			s = s + &"\t" + &v;
205		}
206		s = s + &"\t" + &self.event_id;
207		for v in self.event_params.iter() {
208			s = s + &"\t" + &v;
209		}
210		s
211	}
212}