""" Webapp handlers. """ import os import time import base64 import logging import xml.etree.ElementTree as ET from flask import request, jsonify from vasl_templates.webapp import app from vasl_templates.webapp.vassal import VassalShim from vasl_templates.webapp.utils import SimpleError, TempFile # weights for each possible roll value DEFAULT_LFA_DICE_HOTNESS_WEIGHTS = { "DR": { 2: 20, 3: 16, 4: 12, 5: 8, 6: 4, 7: 0, 8: -4, 9: -8, 10: -12, 11: -16, 12: -20 }, "dr": { 1: 3, 2: 2, 3: 1, 4: -1, 5: -2, 6: -3 } } # minimum number of rolls for dice hotness to be considered reasonable DEFAULT_LFA_DICE_HOTNESS_THRESHOLDS = { "DR": 100, "dr": 50 } # --------------------------------------------------------------------- @app.route( "/analyze-vlogs", methods=["POST"] ) def analyze_vlogs(): #pylint: disable=too-many-locals """Analyze VASL log file(s).""" # parse the request start_time = time.time() vlog_data = request.json # initialize logger = logging.getLogger( "analyze_vlogs" ) temp_files = [] try: # save each VLOG file in a temp file if not vlog_data: raise SimpleError( "No log files were submitted." ) for vlog_no, vlog in enumerate( vlog_data ): fname, data = vlog data = base64.b64decode( data ) logger.info( "Analyzing VLOG (#bytes=%d): %s", len(data), fname ) temp_file = TempFile() temp_file.open() temp_file.write( data ) temp_file.close( delete=False ) save_fname = app.config.get( "ANALYZE_VLOG_INPUT" ) if save_fname: if len(vlog_data) == 1: temp_file.save_copy( save_fname, logger, "VLOG data" ) else: parts = os.path.splitext( save_fname ) temp_file.save_copy( "{}-{}".format(parts[0],1+vlog_no) + parts[1], logger, "VLOG data" ) temp_files.append( temp_file ) # run the VASSAL shim to analyze the VLOG file(s) with TempFile() as report_file: report_file.close( delete=False ) vassal_shim = VassalShim() fnames = [ tf.name for tf in temp_files ] fnames.append( report_file.name ) vassal_shim.analyze_logfiles( *fnames ) report_file.save_copy( app.config.get("ANALYZE_VLOG_REPORT"), logger, "analysis report" ) report = parse_analysis_report( report_file.name, logger ) except Exception as ex: #pylint: disable=broad-except return VassalShim.translate_vassal_shim_exception( ex, logger ) finally: # clean up for tf in temp_files: tf.close( delete=True ) # insert the filenames for each log file, as they were passed in to us for vlog_no,vlog in enumerate( vlog_data ): report["logFiles"][ vlog_no ]["filename"] = vlog[0] # return the results logger.info( "Analyzed the VLOG file(s) OK: elapsed=%.3fs", time.time()-start_time ) return jsonify( report ) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def parse_analysis_report( fname, logger=None ): """Parse the analysis report generated by the VASSAL shim.""" # initialize doc = ET.parse( fname ) # get the complete list of players across all the log files players = {} for elem in doc.findall( ".//diceEvent" ): player_name = elem.attrib[ "player" ] if player_name not in players: # NOTE: ChartJS (in the frontend Javascript) identifies datasets using a 0-based index, # so to avoid accidentally mixing these up with player ID's, we generate non-numeric player ID's. player_id = "p:{}".format( len(players) + 1 ) players[ player_name ] = player_id # generate the results for each log file log_files = [] for logFileElem in doc.findall( ".//logFile" ): # process the events for the next log file events, scenario = [], {} for elem in logFileElem.find( ".//events" ): if elem.tag == "diceEvent": # found a DICE ROLL event player_id = players[ elem.attrib["player"] ] values = [ int(v) for v in elem.text.split(",") ] events.append( { "eventType": "roll", "playerId": player_id, "rollType": elem.attrib[ "rollType" ], "rollValue": values[0] if len(values) == 1 else values } ) elif elem.tag == "turnTrackEvent": # found a TURN TRACK event events.append( { "eventType": "turnTrack", "side": elem.attrib[ "side" ], "turnNo": elem.attrib[ "turnNo" ], "phase": elem.attrib[ "phase" ] } ) elif elem.tag == "customLabelEvent": # found a CUSTOM label events.append( { "eventType": "customLabel", "caption": elem.text } ) else: if logger: logger.warn( "Found an unknown analysis event: %s", elem.tag ) # extract the scenario details elem = logFileElem.find( ".//scenario" ) if elem is not None: scenario[ "scenarioName" ] = elem.text if "id" in elem.attrib: scenario[ "scenarioId" ] = elem.attrib["id"] log_files.append( { "filename": logFileElem.attrib[ "filename" ], "scenario": scenario, "events": events, } ) return { "players": { v: k for k,v in players.items() }, "logFiles": log_files }