Beispiel #1
0
def calcTeamStats(result_list: list, player_stats: dict,
                  account_id: int) -> list:
    return_list = []
    stat_types = player_stats[list(player_stats.keys())[0]].keys()
    for result in result_list:
        try:
            n_allies = collections.defaultdict(defaultvalueZero)
            allies_stats = collections.defaultdict(defaultvalueZero)
            bu.verbose('Allies')
            for ally in result['allies']:
                # Player itself is not in 'allies': see readReplayJSON()
                for stat in stat_types:
                    if player_stats[ally][stat] != None:
                        allies_stats[stat] += player_stats[ally][stat]
                        n_allies[stat] += 1
                        bu.verbose(
                            str(ally) + ':' + str(stat) + ':' +
                            str(player_stats[ally][stat]))

            for stat in stat_types:
                if n_allies[stat] != None:
                    if n_allies[stat] != 0:
                        result['allies_' +
                               str(stat)] = allies_stats[stat] / n_allies[stat]
                    else:
                        result['allies_' + str(stat)] = None

            n_enemies = collections.defaultdict(defaultvalueZero)
            enemies_stats = collections.defaultdict(defaultvalueZero)
            bu.verbose('Enemies')
            for enemy in result['enemies']:
                for stat in stat_types:
                    if player_stats[enemy][stat] != None:
                        enemies_stats[stat] += player_stats[enemy][stat]
                        n_enemies[stat] += 1
                        bu.verbose(
                            str(enemy) + ':' + str(stat) + ':' +
                            str(player_stats[enemy][stat]))

            for stat in stat_types:
                if n_enemies[stat] != None:
                    if n_enemies[stat] != 0:
                        result[
                            'enemies_' +
                            str(stat)] = enemies_stats[stat] / n_enemies[stat]
                    else:
                        result['enemies_' + str(stat)] = None

            bu.verbose('result:' + str(result))
            return_list.append(result)
        except KeyError as err:
            bu.error('Key :' + str(err) + ' not found')
        except Exception as err:
            bu.error(err)

    return return_list
Beispiel #2
0
async def replayWorker(queue: asyncio.Queue, workerID: int, account_id: int, priv = False):
	"""Async Worker to process the replay queue"""
	global SKIPPED_N

	while True:
		item = await queue.get()
		filename = item[0]
		N = item[1]
		title = item[2]

		replay_json_fn = filename +  '.json'
		msg_str = 'Replay[' + str(N) + ']: '

		#bu.debug(msg_str + replay_json_fn)
		try:
			#if os.path.exists(replay_json_fn) and os.path.isfile(replay_json_fn):
			if os.path.isfile(replay_json_fn):
				async with aiofiles.open(replay_json_fn) as fp:
					replay_json = json.loads(await fp.read())
					#if replay_json['status'] == 'ok':
					if wi.chkJSONreplay(replay_json):
						bu.verbose(msg_str + title + ' has already been posted. Skipping.' )
						SKIPPED_N += 1
						queue.task_done()						
						continue
		except asyncio.CancelledError as err:
			raise err
		except Exception as err:
			bu.error(msg_str + 'Unexpected error: ' + str(type(err)) + ' : '+ str(err))
		try:
			#bu.debug('Opening file [' + str(N) +']: ' + filename)
			async with aiofiles.open(filename,'rb') as fp:
				filename = os.path.basename(filename)
				bu.debug(msg_str + 'File:  ' + filename)
				json_resp = await wi.postReplay(await fp.read(), filename, account_id, title, priv, N)
				if json_resp != None:
					if (await bu.saveJSON(replay_json_fn,json_resp)):
						bu.debug(msg_str + 'Replay saved OK: ' + filename )
					else:
						bu.error(msg_str + 'Error saving replay: ' + filename)
		except Exception as err:
			bu.error(msg_str + 'Unexpected Exception: ' + str(type(err)) + ' : ' + str(err) )
		bu.debug(msg_str + 'Marking task done')
		queue.task_done()	

	return None
Beispiel #3
0
async def replayReader(queue: asyncio.Queue, readerID: int,
                       args: argparse.Namespace):
    """Async Worker to process the replay queue"""
    #global SKIPPED_N
    account_id = args.accountID
    results = []
    playerstanks = set()
    try:
        while True:
            item = await queue.get()
            filename = item[0]
            replayID = item[1]

            try:
                replay_json = await bu.readJSON(filename, wi.chkJSONreplay)
                if replay_json == None:
                    bu.verbose('Replay[' + str(replayID) + ']: ' + filename +
                               ' is invalid. Skipping.')
                    #SKIPPED_N += 1
                    queue.task_done()
                    continue

                ## Read the replay JSON
                bu.debug('[' + str(readerID) + '] reading replay: ' + filename)
                result = await readReplayJSON(replay_json, args)
                if result == None:
                    bu.error('[' + str(readerID) + '] Error reading replay: ' +
                             filename)
                    queue.task_done()
                    continue

                if (account_id != None):
                    playerstanks.update(set(result['allies']))
                    playerstanks.update(set(result['enemies']))

                results.append(result)
                bu.debug('Marking task ' + str(replayID) + ' done')

            except Exception as err:
                bu.error(str(err))
            queue.task_done()
    except asyncio.CancelledError:
        return results, playerstanks
    return None
Beispiel #4
0
async def main(argv):
	global wg, wi
	
	TASK_N = 7

	parser = argparse.ArgumentParser(description='ANalyze Blitz replay JSONs from WoTinspector.com')
	parser.add_argument('--output', default='plain', choices=['json', 'plain', 'db'], help='Select output mode: JSON, plain text or database')
	parser.add_argument('-id', dest='accountID', type=int, default=None, help='WG account_id to analyze')
	parser.add_argument('-a', '--account', dest='account', type=str, default=None, help='WG account nameto analyze. Format: ACCOUNT_NAME@SERVER')
	parser.add_argument('-u', '--url', dest= 'url', action='store_true', default=False, help='Print replay URLs')
	parser.add_argument('--tankfile', type=str, default='tanks.json', help='JSON file to read Tankopedia from. Default is "tanks.json"')
	parser.add_argument('--mapfile', type=str, default='maps.json', help='JSON file to read Blitz map names from. Default is "maps.json"')
	parser.add_argument('-o','--outfile', type=str, default='-', metavar="OUTPUT", help='File to write results. Default STDOUT')
	parser.add_argument('-d', '--debug', action='store_true', default=False, help='Debug mode')
	parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Verbose mode')
	parser.add_argument('files', metavar='FILE1 [FILE2 ...]', type=str, nargs='+', help='Files to read. Use \'-\' for STDIN"')
	
	args = parser.parse_args()
	bu.setVerbose(args.verbose)
	bu.setDebug(args.debug)
	wg = WG(WG_appID, args.tankfile, args.mapfile)
	wi = WoTinspector()

	if args.account != None:
		args.accountID = await wg.getAccountID(args.account)
		bu.debug('WG  account_id: ' + str(args.accountID))

	try:
		replayQ  = asyncio.Queue()	
		
		reader_tasks = []
		# Make replay Queue
		scanner_task = asyncio.create_task(mkReplayQ(replayQ, args.files))

		# Start tasks to process the Queue
		for i in range(TASK_N):
			reader_tasks.append(asyncio.create_task(replayReader(replayQ, i, args)))
			bu.debug('Task ' + str(i) + ' started')

		bu.debug('Waiting for the replay scanner to finish')
		await asyncio.wait([scanner_task])
		bu.debug('Scanner finished. Waiting for replay readers to finish the queue')
		await replayQ.join()
		bu.debug('Replays read. Cancelling Readers and analyzing results')
		for task in reader_tasks:
			task.cancel()	
		results = []
		playerstanks = set()
		for res in await asyncio.gather(*reader_tasks):
			results.extend(res[0])
			playerstanks.update(res[1])

		player_stats = await processTankStats(playerstanks, TASK_N)
		bu.verbose('')
		results = calcTeamStats(results, player_stats, args.accountID)
		processStats(results, args)	

	finally:
		## Need to close the aiohttp.session since Python destructors do not support async methods... 
		await wg.close()
		await wi.close()
	return None
async def main(argv):
	global wg, wi, WG_APP_ID
	# set the directory for the script
	current_dir = os.getcwd()
	os.chdir(os.path.dirname(sys.argv[0]))

	## Default options:
	OPT_DB			= False
	OPT_EXTENDED 	= False
	OPT_HIST		= False
	OPT_STAT_FUNC	= 'player'
	OPT_WORKERS_N 	= 10
	
	WG_ACCOUNT 		= None 	# format: nick@server, where server is either 'eu', 'ru', 'na', 'asia' or 'china'. 
	  					 	# China is not supported since WG API stats are not available there
	WG_ID			= None  # WG account_id in integer format. 
							# WG_ACCOUNT will be used to retrieve the account_id, but it can be set directly too
	# WG_APP_ID		= WG_APP_ID
	WG_RATE_LIMIT	= 10  ## WG standard. Do not edit unless you have your
						  ## own server app ID, it will REDUCE the performance
	
	## VERY unlikely you have a DB set up
	DB_SERVER 	= 'localhost'
	DB_PORT 	= 27017
	DB_SSL		= False
	DB_CERT_REQ = ssl.CERT_NONE
	DB_AUTH 	= 'admin'
	DB_NAME 	= 'BlitzStats'
	DB_USER		= '******'
	DB_PASSWD 	= 'PASSWORD'
	DB_CERT 	= None
	DB_CA 		= None
	
	try:
		## Read config
		if os.path.isfile(FILE_CONFIG):
			config = configparser.ConfigParser()
			config.read(FILE_CONFIG)

			try:
				if 'OPTIONS' in config.sections():
					configOptions 	= config['OPTIONS']
					# WG account id of the uploader: 
					# # Find it here: https://developers.wargaming.net/reference/all/wotb/account/list/
					OPT_DB			= configOptions.getboolean('opt_DB', OPT_DB)
					OPT_EXTENDED 	= configOptions.getboolean('opt_analyzer_extended', OPT_EXTENDED)
					OPT_HIST		= configOptions.getboolean('opt_analyzer_hist', OPT_HIST)
					OPT_STAT_FUNC	= configOptions.get('opt_analyzer_stat_func', fallback=OPT_STAT_FUNC)
					OPT_WORKERS_N 	= configOptions.getint('opt_analyzer_workers', OPT_WORKERS_N)
			except (KeyError, configparser.NoSectionError) as err:
				bu.error(exception=err)

			try:
				if 'ANALYZER' in config.sections():
					configAnalyzer	= config['ANALYZER']
					histogram_fields_str = configAnalyzer.get('histogram_buckets', None)
					if histogram_fields_str != None:
						set_histogram_buckets(json.loads(histogram_fields_str))

			except (KeyError, configparser.NoSectionError) as err:
				bu.error(exception=err)

			try:
				if 'WG' in config.sections():
					configWG 		= config['WG']
					WG_ID			= configWG.getint('wg_id', WG_ID)
					WG_ACCOUNT		= configWG.get('wg_account', WG_ACCOUNT)
					WG_APP_ID		= configWG.get('wg_app_id', WG_APP_ID)
					WG_RATE_LIMIT	= configWG.getint('wg_rate_limit', WG_RATE_LIMIT)
			except (KeyError, configparser.NoSectionError) as err:
				bu.error(exception=err)

			try:
				if 'DATABASE' in config.sections():
					configDB 	= config['DATABASE']
					DB_SERVER 	= configDB.get('db_server', DB_SERVER)
					DB_PORT 	= configDB.getint('db_port', DB_PORT)
					DB_SSL		= configDB.getboolean('db_ssl', DB_SSL)
					DB_CERT_REQ = configDB.getint('db_ssl_req', DB_CERT_REQ)
					DB_AUTH 	= configDB.get('db_auth', DB_AUTH)
					DB_NAME 	= configDB.get('db_name', DB_NAME)
					DB_USER		= configDB.get('db_user', DB_USER)
					DB_PASSWD 	= configDB.get('db_password', DB_PASSWD)
					DB_CERT 	= configDB.get('db_ssl_cert_file', DB_CERT)
					DB_CA 		= configDB.get('db_ssl_ca_file', DB_CA)
			except (KeyError, configparser.NoSectionError)  as err:
				bu.error(exception=err)

		parser = ErrorCatchingArgumentParser(description='Analyze Blitz replay JSON files from WoTinspector.com. Use \'upload_wotb_replays.py\' to upload the replay files first.')

		parser.add_argument('--output', default='plain', choices=['plain', 'db'], help='Select output mode: plain text or database')
		parser.add_argument('-id', dest='account_id', type=int, default=WG_ID, help='WG account_id to analyze')
		parser.add_argument('-a', '--account', type=str, default=WG_ACCOUNT, help='WG account nameto analyze. Format: ACCOUNT_NAME@SERVER')
		parser.add_argument('-x', '--extended', action='store_true', default=OPT_EXTENDED, help='Print Extended stats')
		parser.add_argument('-X', '--extra_categories', choices=BattleRecordCategory.get_extra_categories(), default=None, nargs='*', help='Print Extended categories')
		parser.add_argument('--hist', action='store_true', default=OPT_HIST, help='Print player histograms (WR/battles)')
		parser.add_argument('--stat_func', default=OPT_STAT_FUNC, choices=STAT_FUNC.keys(), help='Select how to calculate for ally/enemy performance: tank-tier stats, global player stats')
		parser.add_argument('-u', '--url', action='store_true', default=False, help='Print replay URLs')
		parser.add_argument('--tankfile', type=str, default='tanks.json', help='JSON file to read Tankopedia from. Default is "tanks.json"')
		parser.add_argument('--mapfile', type=str, default='maps.json', help='JSON file to read Blitz map names from. Default is "maps.json"')
		parser.add_argument('-o','--outfile', type=str, default='-', metavar="OUTPUT", help='File to write results. Default STDOUT')
		parser.add_argument('--db', action='store_true', default=OPT_DB, help='Use DB - You are unlikely to have it')
		parser.add_argument('--filters', type=str, default=None, help='Filters for DB based analyses. MongoDB find() filter JSON format.')
		parser.add_argument('-d', '--debug', action='store_true', default=False, help='Debug mode')
		parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Verbose mode')
		parser.add_argument('-s', '--silent', action='store_true', default=False, help='Silent mode')
		parser.add_argument('files', metavar='FILE1 [FILE2 ...]', type=str, nargs='+', help='Files/dirs to read. Use \'-\' for STDIN, "db:" for database')
		
		try:
			args = parser.parse_args()
		except Exception as err:
			raise

		bu.set_log_level(args.silent, args.verbose, args.debug)
		bu.set_progress_step(250)  # Set the frequency of the progress dots. 
		
		wg = WG(WG_APP_ID, args.tankfile, args.mapfile, stats_cache=True, rate_limit=WG_RATE_LIMIT)
		wi = WoTinspector(rate_limit=10)

		if args.account != None:
			args.account_id = await wg.get_account_id(args.account)
			bu.debug('WG  account_id: ' + str(args.account_id))

		BattleRecord.set_fields(args.extended)

		#### Connect to MongoDB (TBD)
		bu.debug('DB_SERVER: ' + DB_SERVER)
		bu.debug('DB_PORT: ' + str(DB_PORT))
		bu.debug('DB_SSL: ' + "True" if DB_SSL else "False")
		bu.debug('DB_AUTH: ' + DB_AUTH)
		bu.debug('DB_NAME: ' + DB_NAME)
		
		client = None
		db = None
		if args.db:
			try:
				client = motor.motor_asyncio.AsyncIOMotorClient(DB_SERVER,DB_PORT, authSource=DB_AUTH, username=DB_USER, password=DB_PASSWD, ssl=DB_SSL, ssl_cert_reqs=DB_CERT_REQ, ssl_certfile=DB_CERT, tlsCAFile=DB_CA)
				db = client[DB_NAME]
				args.account_id = None
				bu.debug('Database connection initiated')
			except Exception as err: 
				bu.error("Could no initiate DB connection: Disabling DB", err) 
				args.db = False
				pass
		else:
			bu.debug('No DB in use')

		# rebase file arguments due to moving the working directory to the script location
		args.files = bu.rebase_file_args(current_dir, args.files)

		try:
			replayQ  = asyncio.Queue(maxsize=1000)			
			reader_tasks = []
			# Make replay Queue

			scanner_task = asyncio.create_task(mk_replayQ(replayQ, args, db))
			bu.debug('Replay scanner started')
			# Start tasks to process the Queue
			for i in range(OPT_WORKERS_N):
				reader_tasks.append(asyncio.create_task(replay_reader(replayQ, i, args)))
				bu.debug('ReplayReader ' + str(i) + ' started')

			bu.debug('Waiting for the replay scanner to finish')
			await asyncio.wait([scanner_task])
			
			# bu.debug('Scanner finished. Waiting for replay readers to finish the queue')
			await replayQ.join()
			await asyncio.sleep(0.1)
			bu.debug('Replays read. Cancelling Readers and analyzing results')
			for task in reader_tasks:
				task.cancel()
				await asyncio.sleep(0.1)	
			results = []
			players = set()
			for res in await asyncio.gather(*reader_tasks):
				results.extend(res[0])
				players.update(res[1])
			if len(players) == 0:
				raise Exception("No players found to fetch stats for. No replays found?")

			(player_stats, stat_id_map) = await process_player_stats(players, OPT_WORKERS_N, args, db)
			bu.verbose('')
			bu.debug('Number of player stats: ' + str(len(player_stats)))
			teamresults = calc_team_stats(results, player_stats, stat_id_map, args)
			process_battle_results(teamresults, args)	
			if args.hist: 
				print('\nPlayer Histograms______', end='', flush=True)
				process_player_dist(results, player_stats, stat_id_map)
			bu.debug('Finished. Cleaning up..................')
		except Exception as err:
			bu.error(exception=err)
	except UserWarning as err:
		bu.verbose(str(err))
		pass
	except Exception as err:
			bu.error(exception=err)
	finally:
		## Need to close the aiohttp.session since Python destructors do not support async methods... 
		if wg != None: await wg.close()
		if wi != None: await wi.close()
	return None
async def mk_replayQ(queue : asyncio.Queue, args : argparse.Namespace, db : motor.motor_asyncio.AsyncIOMotorDatabase = None):
	"""Create queue of replays to post"""
	p_replayfile = re.compile('.*\\.wotbreplay\\.json$')
	files = args.files
	Nreplays = 0
	if files[0] == 'db:':
		if db == None:
			bu.error('No database connection opened')
			sys.exit(1)
		try:
			dbc = db[DB_C_REPLAYS]
			cursor = None
			if args.filters  != None:
				bu.debug(str(args.filters))
				filters = json.loads(args.filters)
				bu.debug(json.dumps(filters, indent=2))
				cursor = dbc.find(filters)
			else:
				# select all
				cursor = dbc.find({})
			bu.debug('Reading replays...')	
			async for replay_json in cursor:
				_id = replay_json['_id']
				del(replay_json['_id'])
				await queue.put(await mk_readerQ_item(replay_json, 'DB: _id = ' + _id))
				Nreplays += 1
			bu.debug('All the matching replays have been read from the DB')
		except Exception as err:
			bu.error(exception=err)

	elif files[0] == '-':
		bu.debug('reading replay file list from STDIN')
		stdin, _ = await aioconsole.get_standard_streams()
		while True:
			try:
				line = (await stdin.readline()).decode('utf-8').rstrip()
				if not line: break
				
				if (p_replayfile.match(line) != None):
					replay_json = await bu.open_JSON(line, wi.chk_JSON_replay)			
					await queue.put(await mk_readerQ_item(replay_json, line))
			except Exception as err:
				bu.error(exception=err)
	else:
		for fn in files:
			try:
				# bu.debug('Filename: ' + fn)
				if fn.endswith('"'):
					fn = fn[:-1] 
				if os.path.isfile(fn) and (p_replayfile.match(fn) != None):
					replay_json = await bu.open_JSON(fn, wi.chk_JSON_replay)
					await queue.put(await mk_readerQ_item(replay_json, fn))
					bu.debug('File added to queue: ' + fn)
					Nreplays += 1
				elif os.path.isdir(fn):
					with os.scandir(fn) as dirEntry:
						for entry in dirEntry:
							if entry.is_file() and (p_replayfile.match(entry.name) != None): 
								bu.debug(entry.name)
								replay_json = await bu.open_JSON(entry.path, wi.chk_JSON_replay)
								await queue.put(await mk_readerQ_item(replay_json, entry.name))
								bu.debug('File added to queue: ' + entry.path)
								Nreplays += 1
			except Exception as err:
				bu.error(exception=err)					
	bu.verbose('Finished scanning replays: ' + str(Nreplays)  + ' replays to process') 
	return Nreplays
Beispiel #7
0
async def main(argv):
	global wg, wi

	parser = argparse.ArgumentParser(description='Post replays(s) to WoTinspector.com and retrieve battle data')
	parser.add_argument('--output', default='single', choices=['file', 'files', 'db'] , help='Select output mode: single/multiple files or database')
	parser.add_argument('-id', dest='accountID', type=int, default=WG_ID, help='WG account_id')
	parser.add_argument('-a', '--account', dest='account', type=str, default=None, help='Uploader\'s WG account name. Format: ACCOUNT_NAME@SERVER')
	parser.add_argument('-t','--title', type=str, default=None, help='Title for replays. Use NN for continous numbering. Default is filename-based numbering')
	parser.add_argument('-p', '--private', dest="private", action='store_true', default=False, help='Set replays private on WoTinspector.com')
	parser.add_argument('--tasks', dest='N_tasks', type=int, default=10, help='Number of worker threads')
	parser.add_argument('--tankopedia', type=str, default='tanks.json', help='JSON file to read Tankopedia from. Default: "tanks.json"')
	parser.add_argument('--mapfile', type=str, default='maps.json', help='JSON file to read Blitz map names from. Default: "maps.json"')
	parser.add_argument('-d', '--debug', action='store_true', default=False, help='Debug mode')
	parser.add_argument('-v', '--verbose', action='store_true', default=True, help='Verbose mode')
	parser.add_argument('-s', '--silent', action='store_true', default=False, help='Silent mode')	
	parser.add_argument('files', metavar='FILE1 [FILE2 ...]', type=str, nargs='+', help='Files to read. Use \'-\' for STDIN"')
	args = parser.parse_args()

	bu.setVerbose(args.verbose)
	bu.setDebug(args.debug)
	if args.silent:
		bu.setVerbose(False)    	

	wg = WG(WG_appID, args.tankopedia, args.mapfile)
	wi = WoTinspector()

	if args.account != None:
		args.accountID = await wg.getAccountID(args.account)
		bu.debug('WG  account_id: ' + str(args.accountID))

	if args.accountID == None: 
		args.accountID = 0

	try:
		queue  = asyncio.Queue()	
		
		tasks = []
		# Make replay Queue
		tasks.append(asyncio.create_task(mkReplayQ(queue, args.files, args.title)))
		# Start tasks to process the Queue
		for i in range(args.N_tasks):
			tasks.append(asyncio.create_task(replayWorker(queue, i, args.accountID, args.private)))
			bu.debug('Task ' + str(i) + ' started')
		
		bu.debug('Waiting for the replay scanner to finish')
		await asyncio.wait([tasks[0]])
		bu.debug('Scanner finished. Waiting for workers to finish queue')
		await queue.join()
		bu.debug('Cancelling workers')
		for task in tasks:
			task.cancel()
		bu.debug('Waiting for workers to cancel')
		await asyncio.gather(*tasks, return_exceptions=True)
		bu.verbose(str(REPLAY_N) + ' replays: ' + str(REPLAY_N - SKIPPED_N) + ' uploaded, ' + str(SKIPPED_N) + ' skipped')
				
	except KeyboardInterrupt:
		print('Ctrl-c pressed ...')
		sys.exit(1)
	finally:
		## Need to close the aiohttp.session since Python destructors do not support async methods... 
		await wg.session.close()
		await wi.close()

	return None
Beispiel #8
0
async def main(argv):
    global wg, wi
    # set the directory for the script
    os.chdir(os.path.dirname(sys.argv[0]))

    ## Read config
    config = configparser.ConfigParser()
    config.read(FILE_CONFIG)

    configOptions = config['OPTIONS']
    OPT_WORKERS_N = configOptions.getint('opt_uploader_workers', 5)

    configWG = config['WG']
    # WG account id of the uploader:
    # # Find it here: https://developers.wargaming.net/reference/all/wotb/account/list/
    WG_ID = configWG.getint('wg_id', None)
    ## WG API Rules limit 10 request / sec. Higher rate of requests will return errors ==> extra delay
    WG_RATE_LIMIT = configWG.getint('wg_rate_limit', 10)

    parser = argparse.ArgumentParser(
        description=
        'Post replays(s) to WoTinspector.com and retrieve battle data')
    parser.add_argument('-id',
                        dest='accountID',
                        type=int,
                        default=WG_ID,
                        help='WG account_id')
    parser.add_argument(
        '-a',
        '--account',
        dest='account',
        type=str,
        default=None,
        help='Uploader\'s WG account name. Format: ACCOUNT_NAME@SERVER')
    parser.add_argument(
        '-t',
        '--title',
        type=str,
        default=None,
        help=
        'Title for replays. Use NN for continous numbering. Default is filename-based numbering'
    )
    parser.add_argument('-p',
                        '--private',
                        dest="private",
                        action='store_true',
                        default=False,
                        help='Set replays private on WoTinspector.com')
    parser.add_argument(
        '--tankopedia',
        type=str,
        default='tanks.json',
        help='JSON file to read Tankopedia from. Default: "tanks.json"')
    parser.add_argument(
        '--mapfile',
        type=str,
        default='maps.json',
        help='JSON file to read Blitz map names from. Default: "maps.json"')
    parser.add_argument('-d',
                        '--debug',
                        action='store_true',
                        default=False,
                        help='Debug mode')
    parser.add_argument('-v',
                        '--verbose',
                        action='store_true',
                        default=False,
                        help='Verbose mode')
    parser.add_argument('-s',
                        '--silent',
                        action='store_true',
                        default=False,
                        help='Silent mode')
    parser.add_argument('files',
                        metavar='FILE1 [FILE2 ...]',
                        type=str,
                        nargs='+',
                        help='Files to read. Use \'-\' for STDIN"')
    args = parser.parse_args()

    bu.set_verbose(args.verbose)
    bu.set_log_level(args.silent, args.verbose, args.debug)

    wg = WG(WG_appID, args.tankopedia, args.mapfile)
    wi = WoTinspector(rate_limit=WG_RATE_LIMIT)

    if args.account != None:
        args.accountID = await wg.get_account_id(args.account)
        bu.debug('WG  account_id: ' + str(args.accountID))

    if args.accountID == None:
        args.accountID = 0

    try:
        queue = asyncio.Queue()

        tasks = []
        # Make replay Queue
        tasks.append(
            asyncio.create_task(mkReplayQ(queue, args.files, args.title)))
        # Start tasks to process the Queue
        for i in range(OPT_WORKERS_N):
            tasks.append(
                asyncio.create_task(
                    replayWorker(queue, i, args.accountID, args.private)))
            bu.debug('Task ' + str(i) + ' started')

        bu.debug('Waiting for the replay scanner to finish')
        await asyncio.wait([tasks[0]])
        bu.debug('Scanner finished. Waiting for workers to finish queue')
        await queue.join()
        bu.debug('Cancelling workers')
        for task in tasks:
            task.cancel()
        bu.debug('Waiting for workers to cancel')
        await asyncio.gather(*tasks, return_exceptions=True)
        bu.verbose(
            str(REPLAY_N) + ' replays: ' +
            str(REPLAY_N - SKIPPED_N - ERROR_N) + ' uploaded, ' +
            str(SKIPPED_N) + ' skipped, ' + str(ERROR_N) + ' errors')

    except KeyboardInterrupt:
        print('Ctrl-c pressed ...')
        sys.exit(1)
    finally:
        ## Need to close the aiohttp.session since Python destructors do not support async methods...
        await wg.session.close()
        await wi.close()

    return None