use rand::{thread_rng, Rng};
use regex::Regex;
use std::fs::File;
use std::io;
use std::path::Path;
use std::str;
use Archive::*;
use diesel::pg::PgConnection;
use diesel::result::Error;
use crate::concerns::CortexInsertable;
use crate::models::{
LogError, LogFatal, LogInfo, LogInvalid, LogRecord, LogWarning, NewLogError, NewLogFatal,
NewLogInfo, NewLogInvalid, NewLogWarning, Task,
};
const BUFFER_SIZE: usize = 10_240;
lazy_static! {
static ref MESSAGE_LINE_REGEX: Regex =
Regex::new(r"^([^ :]+):([^ :]+):([^ ]+)(\s(.*))?$").unwrap();
pub static ref LOADING_LINE_REGEX: Regex =
Regex::new(r"^\(Loading\s(.+/)?([^/]+[^.])\.\.\.(\s|$)").unwrap();
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TaskStatus {
TODO,
NoProblem,
Warning,
Error,
Fatal,
Invalid,
Blocked(i32),
Queued(i32),
}
#[derive(Clone, Debug)]
pub struct TaskProgress {
pub task: Task,
pub created_at: i64,
pub retries: i64,
}
impl TaskProgress {
pub fn expected_at(&self) -> i64 { self.created_at + ((self.retries + 1) * 3600) }
}
#[derive(Clone, Debug)]
pub struct TaskReport {
pub task: Task,
pub status: TaskStatus,
pub messages: Vec<NewTaskMessage>,
}
#[derive(Clone, Debug)]
pub enum TaskMessage {
Info(LogInfo),
Warning(LogWarning),
Error(LogError),
Fatal(LogFatal),
Invalid(LogInvalid),
}
impl LogRecord for TaskMessage {
fn task_id(&self) -> i64 {
use crate::helpers::TaskMessage::*;
match *self {
Info(ref record) => record.task_id(),
Warning(ref record) => record.task_id(),
Error(ref record) => record.task_id(),
Fatal(ref record) => record.task_id(),
Invalid(ref record) => record.task_id(),
}
}
fn category(&self) -> &str {
use crate::helpers::TaskMessage::*;
match *self {
Info(ref record) => record.category(),
Warning(ref record) => record.category(),
Error(ref record) => record.category(),
Fatal(ref record) => record.category(),
Invalid(ref record) => record.category(),
}
}
fn what(&self) -> &str {
use crate::helpers::TaskMessage::*;
match *self {
Info(ref record) => record.what(),
Warning(ref record) => record.what(),
Error(ref record) => record.what(),
Fatal(ref record) => record.what(),
Invalid(ref record) => record.what(),
}
}
fn details(&self) -> &str {
use crate::helpers::TaskMessage::*;
match *self {
Info(ref record) => record.details(),
Warning(ref record) => record.details(),
Error(ref record) => record.details(),
Fatal(ref record) => record.details(),
Invalid(ref record) => record.details(),
}
}
fn set_details(&mut self, new_details: String) {
use crate::helpers::TaskMessage::*;
match *self {
Info(ref mut record) => record.set_details(new_details),
Warning(ref mut record) => record.set_details(new_details),
Error(ref mut record) => record.set_details(new_details),
Fatal(ref mut record) => record.set_details(new_details),
Invalid(ref mut record) => record.set_details(new_details),
}
}
fn severity(&self) -> &str {
use crate::helpers::TaskMessage::*;
match *self {
Info(ref record) => record.severity(),
Warning(ref record) => record.severity(),
Error(ref record) => record.severity(),
Fatal(ref record) => record.severity(),
Invalid(ref record) => record.severity(),
}
}
}
impl TaskStatus {
pub fn raw(&self) -> i32 {
match *self {
TaskStatus::TODO => 0,
TaskStatus::NoProblem => -1,
TaskStatus::Warning => -2,
TaskStatus::Error => -3,
TaskStatus::Fatal => -4,
TaskStatus::Invalid => -5,
TaskStatus::Blocked(x) | TaskStatus::Queued(x) => x,
}
}
pub fn to_key(&self) -> String {
match *self {
TaskStatus::NoProblem => "no_problem",
TaskStatus::Warning => "warning",
TaskStatus::Error => "error",
TaskStatus::Fatal => "fatal",
TaskStatus::TODO => "todo",
TaskStatus::Invalid => "invalid",
TaskStatus::Blocked(_) => "blocked",
TaskStatus::Queued(_) => "queued",
}
.to_string()
}
pub fn to_table(&self) -> String {
match *self {
TaskStatus::Warning => "log_warnings",
TaskStatus::Error => "log_errors",
TaskStatus::Fatal => "log_fatals",
TaskStatus::Invalid => "log_invalids",
_ => "log_infos",
}
.to_string()
}
pub fn from_raw(num: i32) -> Self {
match num {
0 => TaskStatus::TODO,
-1 => TaskStatus::NoProblem,
-2 => TaskStatus::Warning,
-3 => TaskStatus::Error,
-4 => TaskStatus::Fatal,
-5 => TaskStatus::Invalid,
num if num < -5 => TaskStatus::Blocked(num),
_ => TaskStatus::Queued(num),
}
}
pub fn from_key(key: &str) -> Option<Self> {
match key.to_lowercase().as_str() {
"no_problem" => Some(TaskStatus::NoProblem),
"warning" => Some(TaskStatus::Warning),
"error" => Some(TaskStatus::Error),
"todo" => Some(TaskStatus::TODO),
"in_progress" => Some(TaskStatus::TODO),
"invalid" => Some(TaskStatus::Invalid),
"blocked" => Some(TaskStatus::Blocked(-6)),
"queued" => Some(TaskStatus::Queued(1)),
"fatal" => Some(TaskStatus::Fatal),
_ => None,
}
}
pub fn keys() -> Vec<String> {
[
"no_problem",
"warning",
"error",
"fatal",
"invalid",
"todo",
"blocked",
"queued",
]
.iter()
.map(|&x| x.to_string())
.collect::<Vec<_>>()
}
}
#[derive(Clone, Debug)]
pub enum NewTaskMessage {
Info(NewLogInfo),
Warning(NewLogWarning),
Error(NewLogError),
Fatal(NewLogFatal),
Invalid(NewLogInvalid),
}
impl LogRecord for NewTaskMessage {
fn task_id(&self) -> i64 {
use crate::helpers::NewTaskMessage::*;
match *self {
Info(ref record) => record.task_id(),
Warning(ref record) => record.task_id(),
Error(ref record) => record.task_id(),
Fatal(ref record) => record.task_id(),
Invalid(ref record) => record.task_id(),
}
}
fn category(&self) -> &str {
use crate::helpers::NewTaskMessage::*;
match *self {
Info(ref record) => record.category(),
Warning(ref record) => record.category(),
Error(ref record) => record.category(),
Fatal(ref record) => record.category(),
Invalid(ref record) => record.category(),
}
}
fn what(&self) -> &str {
use crate::helpers::NewTaskMessage::*;
match *self {
Info(ref record) => record.what(),
Warning(ref record) => record.what(),
Error(ref record) => record.what(),
Fatal(ref record) => record.what(),
Invalid(ref record) => record.what(),
}
}
fn details(&self) -> &str {
use crate::helpers::NewTaskMessage::*;
match *self {
Info(ref record) => record.details(),
Warning(ref record) => record.details(),
Error(ref record) => record.details(),
Fatal(ref record) => record.details(),
Invalid(ref record) => record.details(),
}
}
fn set_details(&mut self, new_details: String) {
use crate::helpers::NewTaskMessage::*;
match *self {
Info(ref mut record) => record.set_details(new_details),
Warning(ref mut record) => record.set_details(new_details),
Error(ref mut record) => record.set_details(new_details),
Fatal(ref mut record) => record.set_details(new_details),
Invalid(ref mut record) => record.set_details(new_details),
}
}
fn severity(&self) -> &str {
use crate::helpers::NewTaskMessage::*;
match *self {
Info(ref record) => record.severity(),
Warning(ref record) => record.severity(),
Error(ref record) => record.severity(),
Fatal(ref record) => record.severity(),
Invalid(ref record) => record.severity(),
}
}
}
impl CortexInsertable for NewTaskMessage {
fn create(&self, connection: &PgConnection) -> Result<usize, Error> {
use crate::helpers::NewTaskMessage::*;
match *self {
Info(ref record) => record.create(connection),
Warning(ref record) => record.create(connection),
Error(ref record) => record.create(connection),
Fatal(ref record) => record.create(connection),
Invalid(ref record) => record.create(connection),
}
}
}
impl NewTaskMessage {
pub fn new(
task_id: i64,
severity: &str,
category: String,
what: String,
details: String,
) -> NewTaskMessage
{
match severity.to_lowercase().as_str() {
"warning" => NewTaskMessage::Warning(NewLogWarning {
task_id,
category,
what,
details,
}),
"error" => NewTaskMessage::Error(NewLogError {
task_id,
category,
what,
details,
}),
"fatal" => NewTaskMessage::Fatal(NewLogFatal {
task_id,
category,
what,
details,
}),
"invalid" => NewTaskMessage::Invalid(NewLogInvalid {
task_id,
category,
what,
details,
}),
_ => NewTaskMessage::Info(NewLogInfo {
task_id,
category,
what,
details,
}),
}
}
}
pub fn parse_log(task_id: i64, log: &str) -> Vec<NewTaskMessage> {
let mut messages: Vec<NewTaskMessage> = Vec::new();
let mut in_details_mode = false;
for line in log.lines() {
if line.is_empty() {
continue;
}
if in_details_mode {
if line.starts_with('\t') {
let mut last_message = messages.pop().unwrap_or_else(|| {
panic!("parse_log tried to parse details without having a log message, invalid log file?")
});
let mut truncated_details = last_message.details().to_string() + "\n" + line;
utf_truncate(&mut truncated_details, 2000);
last_message.set_details(truncated_details);
messages.push(last_message);
continue;
} else {
in_details_mode = false;
if in_details_mode {}
}
}
if let Some(cap) = MESSAGE_LINE_REGEX.captures(line) {
let mut truncated_severity = cap
.get(1)
.map_or("", |m| m.as_str())
.to_string()
.to_lowercase();
utf_truncate(&mut truncated_severity, 50);
let mut truncated_category = cap.get(2).map_or("", |m| m.as_str()).to_string();
utf_truncate(&mut truncated_category, 50);
let mut truncated_what = cap.get(3).map_or("", |m| m.as_str()).to_string();
utf_truncate(&mut truncated_what, 50);
let mut truncated_details = cap.get(5).map_or("", |m| m.as_str()).to_string();
utf_truncate(&mut truncated_details, 2000);
if truncated_severity == "fatal" && truncated_category == "invalid" {
truncated_severity = "invalid".to_string();
truncated_category = truncated_what;
truncated_what = "all".to_string();
}
let message = NewTaskMessage::new(
task_id,
&truncated_severity,
truncated_category,
truncated_what,
truncated_details,
);
in_details_mode = true;
messages.push(message);
} else {
in_details_mode = false;
if let Some(cap) = LOADING_LINE_REGEX.captures(line) {
let mut filepath = cap.get(1).map_or("", |m| m.as_str()).to_string();
let mut filename = cap.get(2).map_or("", |m| m.as_str()).to_string();
utf_truncate(&mut filename, 50);
filepath += &filename;
utf_truncate(&mut filepath, 50);
messages.push(NewTaskMessage::new(
task_id,
"info",
"loaded_file".to_string(),
filename,
filepath,
));
} else {
}
}
}
messages
}
pub fn generate_report(task: Task, result: &Path) -> TaskReport {
let mut messages = Vec::new();
let mut status = TaskStatus::Fatal;
{
let log_name = "cortex.log";
match Reader::new()
.unwrap_or_else(|_| panic!("Could not create libarchive Reader struct"))
.support_filter_all()
.support_format_all()
.open_filename(result.to_str().unwrap_or_default(), BUFFER_SIZE)
{
Err(e) => {
println!("Error TODO: Couldn't open archive_reader: {:?}", e);
},
Ok(archive_reader) => {
while let Ok(entry) = archive_reader.next_header() {
if entry.pathname() != log_name {
continue;
}
let mut raw_log_data = Vec::new();
while let Ok(chunk) = archive_reader.read_data(BUFFER_SIZE) {
raw_log_data.extend(chunk.into_iter());
}
let log_string: String = match str::from_utf8(&raw_log_data) {
Ok(some_utf_string) => some_utf_string.to_string(),
Err(e) => {
"Fatal:cortex:unicode_parse_error ".to_string()
+ &e.to_string()
+ "\nStatus:conversion:3"
},
};
for message in parse_log(task.id, &log_string).into_iter() {
let mut skip_message = false;
match message {
NewTaskMessage::Invalid(ref _log_invalid) => {
status = TaskStatus::Invalid;
},
NewTaskMessage::Info(ref _log_info) => {
let message_what = message.what();
if message.category() == "conversion" && !message_what.is_empty() {
let latexml_scheme_status = match message_what.parse::<i32>() {
Ok(num) => num,
Err(e) => {
println!(
"Error TODO: Failed to parse conversion status {:?}: {:?}",
message_what, e
);
3
},
};
let cortex_scheme_status = -(latexml_scheme_status + 1);
if status != TaskStatus::Invalid {
status = TaskStatus::from_raw(cortex_scheme_status);
}
skip_message = true;
}
},
_ => {},
};
if !skip_message {
messages.push(message);
}
}
break;
}
drop(archive_reader);
},
}
}
TaskReport {
task,
status,
messages,
}
}
pub fn prepare_input_stream(task: &Task) -> Result<File, io::Error> {
let entry_path = Path::new(&task.entry);
File::open(entry_path)
}
pub fn utf_truncate(input: &mut String, maxsize: usize) {
let mut utf_maxsize = input.len();
if utf_maxsize >= maxsize {
{
let mut char_iter = input.char_indices();
while utf_maxsize >= maxsize {
utf_maxsize = match char_iter.next_back() {
Some((index, _)) => index,
_ => 0,
};
}
}
input.truncate(utf_maxsize);
}
*input = input.replace("\x00", "");
}
pub fn random_mark() -> i32 {
let mut rng = thread_rng();
let mark_rng: u16 = rng.gen();
i32::from(mark_rng)
}
pub fn rand_in_range(from: u16, to: u16) -> u16 {
let mut rng = thread_rng();
let mark_rng: u16 = rng.gen_range(from, to);
mark_rng
}