From a304370df4f998f7054731bac173113f91bf5cb1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 6 Apr 2020 14:43:28 +0200 Subject: implement guild display & filtering options Filtering based on guilds is slow, as it will have to retrieve every guild name from the GW2 API, and it has to parse every log file instead of bailing early. Therefore, guilds are not searched by default, and have to be explicitely turned on with --guilds. In addition, this means that raidgrep will now need network access when --guilds is passed, which was not the case before. --- src/main.rs | 81 +++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 24 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 838a518..f82e522 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,20 @@ +use std::collections::HashMap; use std::fs::File; use std::io::{BufReader, Read, Seek}; use std::path::PathBuf; use std::str::FromStr; +use anyhow::{anyhow, Result}; use chrono::{Duration, NaiveDateTime, Weekday}; use num_traits::cast::FromPrimitive; use regex::Regex; use structopt::StructOpt; use walkdir::{DirEntry, WalkDir}; -use anyhow::{anyhow, Result}; use evtclib::{AgentKind, AgentName, EventKind, Log}; +mod guilds; mod output; - mod filters; mod csl; @@ -55,16 +56,11 @@ fn debug_enabled() -> bool { #[structopt(name = "raidgrep")] pub struct Opt { /// Path to the folder with logs. - #[structopt( - short = "d", - long = "dir", - default_value = ".", - parse(from_os_str) - )] + #[structopt(short = "d", long = "dir", default_value = ".", parse(from_os_str))] path: PathBuf, /// The fields which should be searched. - #[structopt(short = "f", long = "fields", default_value = "*")] + #[structopt(short = "f", long = "fields", default_value = "account,character")] field: CommaSeparatedList, /// Only display fights with the given outcome. @@ -109,17 +105,17 @@ pub struct Opt { weekdays: CommaSeparatedList, /// Only show logs from the given encounters. - #[structopt( - short = "e", - long = "bosses", - default_value = "*", - )] + #[structopt(short = "e", long = "bosses", default_value = "*")] bosses: CommaSeparatedList, /// Print more debugging information to stderr. #[structopt(long = "debug")] debug: bool, + /// Load guild information from the API. + #[structopt(long = "guilds")] + guilds: bool, + /// The regular expression to search for. #[structopt(name = "EXPR")] expression: Regex, @@ -132,6 +128,8 @@ enum SearchField { Account, /// Only search the character name. Character, + /// Only search the guild name or tag. + Guild, } impl FromStr for SearchField { @@ -141,6 +139,7 @@ impl FromStr for SearchField { match s { "account" => Ok(SearchField::Account), "character" => Ok(SearchField::Character), + "guild" => Ok(SearchField::Guild), _ => Err("Must be account or character"), } } @@ -172,6 +171,8 @@ pub struct Player { profession: String, /// Subsquad that the player was in. subgroup: u8, + /// Guild ID, ready for API consumption. + guild_id: Option, } /// Outcome of the fight. @@ -209,12 +210,10 @@ fn parse_time_arg(input: &str) -> Result { Err(anyhow!("unknown time format")) } -fn try_from_str_simple_error(input: &str) -> Result -{ +fn try_from_str_simple_error(input: &str) -> Result { T::from_str(input).map_err(|_| format!("'{}' is an invalid value", input)) } - enum ZipWrapper { Raw(Option), Zipped(zip::ZipArchive), @@ -237,7 +236,6 @@ impl ZipWrapper { } } - fn main() { let opt = Opt::from_args(); @@ -250,6 +248,10 @@ fn main() { unsafe { DEBUG_ENABLED = true }; } + if opt.guilds { + guilds::prepare_cache(); + } + let result = grep(&opt); match result { Ok(_) => {} @@ -257,6 +259,10 @@ fn main() { eprintln!("Error: {}", e); } } + + if opt.guilds { + guilds::save_cache(); + } } /// Check if the given entry represents a log file, based on the file name. @@ -311,11 +317,11 @@ fn search_log(entry: &DirEntry, opt: &Opt) -> Result> { let mut stream = wrapper.get_stream(); let partial = evtclib::raw::parser::parse_partial_file(&mut stream)?; - let early_ok = filters::filter_name(&partial, opt) != opt.invert - && filters::filter_boss(&partial, opt); + let early_ok = + filters::filter_name(&partial, opt) != opt.invert && filters::filter_boss(&partial, opt); if !early_ok { - return Ok(None) + return Ok(None); } let raw = evtclib::raw::parser::finish_parsing(partial, &mut stream)?; @@ -331,7 +337,8 @@ fn search_log(entry: &DirEntry, opt: &Opt) -> Result> { let take_log = filters::filter_outcome(&info, opt) && filters::filter_weekday(&info, opt) - && filters::filter_time(&info, opt); + && filters::filter_time(&info, opt) + && filters::filter_guilds(&info, opt); if take_log { Ok(Some(info)) @@ -350,7 +357,10 @@ fn extract_info(entry: &DirEntry, log: &Log) -> LogResult { log.boss_id() ); "unknown" - }).into(); + }) + .into(); + + let guild_ids = get_guild_mapping(log); let mut players = log .players() @@ -367,9 +377,11 @@ fn extract_info(entry: &DirEntry, log: &Log) -> LogResult { character_name: character_name.clone(), profession: get_profession_name(*profession, *elite).into(), subgroup: *subgroup, + guild_id: guild_ids.get(p.addr()).cloned(), } }}}} - }).collect::>(); + }) + .collect::>(); players.sort_by_key(|p| p.subgroup); LogResult { @@ -381,6 +393,27 @@ fn extract_info(entry: &DirEntry, log: &Log) -> LogResult { } } +/// Get a mapping of agent IDs to guild API strings. +fn get_guild_mapping(log: &Log) -> HashMap { + log.events() + .iter() + .filter_map(|event| { + if let EventKind::Guild { + source_agent_addr, + ref api_guild_id, + .. + } = event.kind + { + api_guild_id + .as_ref() + .map(|api_id| (source_agent_addr, api_id.clone())) + } else { + None + } + }) + .collect() +} + /// Get the timestamp of the log start time. fn get_start_timestamp(log: &Log) -> u32 { for event in log.events() { -- cgit v1.2.3