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}