extern crate structopt; #[macro_use] extern crate quick_error; extern crate chrono; extern crate colored; extern crate evtclib; extern crate regex; extern crate walkdir; extern crate rayon; use std::fs::File; use std::io::{self, BufReader}; use std::path::PathBuf; use std::str::FromStr; use chrono::NaiveDateTime; use regex::Regex; use structopt::StructOpt; use walkdir::{DirEntry, WalkDir}; use rayon::prelude::*; use evtclib::{AgentKind, AgentName, EventKind, Log}; mod errors; use errors::RuntimeError; mod output; #[derive(StructOpt, Debug)] #[structopt(name = "raidgrep")] struct Opt { /// Path to the folder with logs. #[structopt( short = "d", long = "dir", default_value = ".", parse(from_os_str) )] path: PathBuf, /// The fields which should be searched. /// Possible values: all, account, character #[structopt(short = "f", long = "fields", default_value = "all")] field: SearchField, /// The expression to search for. #[structopt(name = "EXPR")] expression: Regex, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum SearchField { All, Account, Character, } impl FromStr for SearchField { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "all" => Ok(SearchField::All), "account" => Ok(SearchField::Account), "character" => Ok(SearchField::Character), _ => Err("Must be all, account or character"), } } } #[derive(Debug, Clone)] pub struct LogResult { log_file: PathBuf, time: NaiveDateTime, boss_name: String, players: Vec, } #[derive(Debug, Clone)] pub struct Player { account_name: String, character_name: String, profession: String, subgroup: u8, } fn main() { let opt = Opt::from_args(); let result = grep(&opt); match result { Ok(_) => {} Err(e) => { eprintln!("Error: {}", e); } } } fn is_log_file(entry: &DirEntry) -> bool { entry .file_name() .to_str() .map(|n| n.ends_with(".evtc") || n.ends_with(".evtc.zip")) .unwrap_or(false) } fn grep(opt: &Opt) -> Result<(), RuntimeError> { let walker = WalkDir::new(&opt.path); let entries = walker.into_iter().collect::>(); entries.into_par_iter().try_for_each(|e| { let entry = e?; if is_log_file(&entry) { if let Some(result) = search_log(&entry, opt)? { output::colored(io::stdout(), &result)?; } } Ok(()) }) } fn search_log(entry: &DirEntry, opt: &Opt) -> Result, RuntimeError> { let mut input = BufReader::new(File::open(entry.path())?); let raw = if entry .file_name() .to_str() .map(|n| n.ends_with(".zip")) .unwrap_or(false) { evtclib::raw::parse_zip(&mut input) } else { evtclib::raw::parse_file(&mut input) }; let parsed = raw.ok().and_then(|m| evtclib::process(&m).ok()); let log = if let Some(e) = parsed { e } else { return Ok(None); }; for player in log.players() { match player.name() { AgentName::Player { account_name, character_name, .. } => { if ((opt.field == SearchField::All || opt.field == SearchField::Account) && opt.expression.is_match(account_name)) || ((opt.field == SearchField::All || opt.field == SearchField::Character) && opt.expression.is_match(character_name)) { return Ok(Some(extract_info(entry, &log))); } } _ => unreachable!(), } } Ok(None) } fn extract_info(entry: &DirEntry, log: &Log) -> LogResult { let boss_name = match log.boss().name() { AgentName::Single(s) => s, _ => "", }.into(); let mut players = log .players() .map(|p| { if let AgentKind::Player { profession, elite } = p.kind() { if let AgentName::Player { account_name, character_name, subgroup, } = p.name() { Player { account_name: account_name.clone(), character_name: character_name.clone(), profession: get_profession_name(*profession, *elite).into(), subgroup: *subgroup, } } else { unreachable!() } } else { unreachable!() } }).collect::>(); players.sort_by_key(|p| p.subgroup); LogResult { log_file: entry.path().to_path_buf(), time: NaiveDateTime::from_timestamp(get_start_timestamp(log) as i64, 0), boss_name, players, } } fn get_start_timestamp(log: &Log) -> u32 { for event in log.events() { if let EventKind::LogStart { local_timestamp, .. } = event.kind { return local_timestamp; } } 0 } fn get_profession_name(profession: u32, elite: u32) -> &'static str { match (profession, elite) { (1, 0) => "Guardian", (2, 0) => "Warrior", (3, 0) => "Engineer", (4, 0) => "Ranger", (5, 0) => "Thief", (6, 0) => "Elementalist", (7, 0) => "Mesmer", (8, 0) => "Necromancer", (9, 0) => "Revenant", (1, 27) => "Dragonhunter", (2, 18) => "Berserker", (3, 43) => "Scrapper", (4, 5) => "Druid", (5, 7) => "Daredevil", (6, 48) => "Tempest", (7, 40) => "Chronomancer", (8, 34) => "Reaper", (9, 52) => "Herald", (1, 62) => "Firebrand", (2, 61) => "Spellbreaker", (3, 57) => "Holosmith", (4, 55) => "Soulbeast", (5, 58) => "Deadeye", (6, 56) => "Weaver", (7, 59) => "Mirage", (8, 60) => "Scourge", (9, 63) => "Renegade", _ => "Unknown", } }