initial commit
This commit is contained in:
commit
30bb08712a
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# python
|
||||||
|
__pycache__/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# editor
|
||||||
|
.vscode/
|
17
WSGI.py
Normal file
17
WSGI.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import gevent.monkey
|
||||||
|
gevent.monkey.patch_all()
|
||||||
|
|
||||||
|
from demweb.app import create_app
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
if app.config['DEBUG']:
|
||||||
|
from werkzeug.debug import DebuggedApplication
|
||||||
|
app.wsgi_app = DebuggedApplication(app.wsgi_app, True)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import gevent.pywsgi
|
||||||
|
gevent_server = gevent.pywsgi.WSGIServer(("localhost", 5000), app.wsgi_app)
|
||||||
|
gevent_server.serve_forever()
|
11
app.py
Executable file
11
app.py
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import gevent.monkey # noqa isort:skip
|
||||||
|
gevent.monkey.patch_all() # noqa isort:skip
|
||||||
|
|
||||||
|
from demweb.app import create_app
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = create_app()
|
||||||
|
host = '0.0.0.0'
|
||||||
|
port = 5000
|
||||||
|
app.run(host, port)
|
4
config.py
Normal file
4
config.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# pylint: disable=line-too-long
|
||||||
|
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'mysql://demweb:demweb@127.0.0.1:3306/demweb?charset=utf8mb4'
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
9
db_create.py
Executable file
9
db_create.py
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from demweb.app import create_app
|
||||||
|
from demweb.extensions import db
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = create_app()
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
db.session.commit()
|
0
demweb/__init__.py
Normal file
0
demweb/__init__.py
Normal file
85
demweb/app.py
Normal file
85
demweb/app.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
from contextlib import nullcontext
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from flask import Flask, g, has_app_context, request
|
||||||
|
from .extensions import default_config
|
||||||
|
|
||||||
|
|
||||||
|
def register_extensions(app):
|
||||||
|
from .extensions import db
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
|
||||||
|
def register_blueprints(app):
|
||||||
|
with app.app_context():
|
||||||
|
# Register blueprints
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(app):
|
||||||
|
# Logging
|
||||||
|
if app.debug:
|
||||||
|
@app.before_request
|
||||||
|
def log_before_request():
|
||||||
|
g.log_datetime = datetime.now()
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
def log_after_request(response):
|
||||||
|
now = datetime.now()
|
||||||
|
delta = (now - g.log_datetime).total_seconds() * 1_000
|
||||||
|
dt = now.isoformat().replace('T', ' ').split('.')[0]
|
||||||
|
print(f'[{dt}] {request.method} {request.path} -> {response.status} ({delta:.2f}ms)')
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def register_shell_context(app):
|
||||||
|
import inspect
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from flask_sqlalchemy import get_debug_queries
|
||||||
|
|
||||||
|
from demweb import models
|
||||||
|
from demweb.extensions import db
|
||||||
|
|
||||||
|
@app.shell_context_processor
|
||||||
|
def make_shell_context():
|
||||||
|
return {
|
||||||
|
'app': app,
|
||||||
|
'db': db,
|
||||||
|
'query': get_debug_queries,
|
||||||
|
'print': pprint,
|
||||||
|
**dict(inspect.getmembers(models, inspect.isclass))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fix_sqlalchemy_uwsgi_multiprocess_bug(app):
|
||||||
|
from demweb.extensions import db
|
||||||
|
|
||||||
|
def _dispose_db_pool():
|
||||||
|
print('uWSGI+SQLAlchemy: Disposing forked() db pool!')
|
||||||
|
with app.app_context() if not has_app_context() else nullcontext():
|
||||||
|
db.engine.dispose()
|
||||||
|
|
||||||
|
try:
|
||||||
|
from uwsgidecorators import postfork
|
||||||
|
postfork(_dispose_db_pool)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(test_config=None):
|
||||||
|
# Create flask application object
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
if test_config is None:
|
||||||
|
app.config.update(default_config)
|
||||||
|
else:
|
||||||
|
app.config.update(test_config)
|
||||||
|
|
||||||
|
register_extensions(app)
|
||||||
|
register_blueprints(app)
|
||||||
|
register_shell_context(app)
|
||||||
|
setup_logging(app)
|
||||||
|
fix_sqlalchemy_uwsgi_multiprocess_bug(app)
|
||||||
|
|
||||||
|
return app
|
10
demweb/extensions.py
Normal file
10
demweb/extensions.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
import config
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
default_config = dict()
|
||||||
|
for key in dir(config):
|
||||||
|
if key.isupper():
|
||||||
|
default_config[key] = getattr(config, key)
|
84
demweb/models.py
Normal file
84
demweb/models.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# pylint: disable=no-member
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from sqlalchemy.types import JSON
|
||||||
|
|
||||||
|
from demweb.extensions import db
|
||||||
|
|
||||||
|
|
||||||
|
class Session(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
time = db.Column(db.DateTime, index=True)
|
||||||
|
length = db.Column(db.Float, index=True)
|
||||||
|
demoname = db.Column(db.String(255), unique=True)
|
||||||
|
mapname = db.Column(db.String(255))
|
||||||
|
mapmd5 = db.Column(db.String(32))
|
||||||
|
servername = db.Column(db.String(255))
|
||||||
|
frames = db.Column(db.Integer)
|
||||||
|
ticks = db.Column(db.Integer)
|
||||||
|
tickinterval = db.Column(db.Float)
|
||||||
|
dirty = db.Column(db.Boolean)
|
||||||
|
|
||||||
|
playtime = db.Column(db.Float, index=True)
|
||||||
|
rounds = db.Column(db.Integer, index=True)
|
||||||
|
ct_wins = db.Column(db.Integer, index=True)
|
||||||
|
t_wins = db.Column(db.Integer, index=True)
|
||||||
|
chats = db.Column(db.Integer, index=True)
|
||||||
|
deaths = db.Column(db.Integer, index=True)
|
||||||
|
kills = db.Column(db.Integer, index=True)
|
||||||
|
voice_active = db.Column(db.Float, index=True)
|
||||||
|
voice_total = db.Column(db.Float, index=True)
|
||||||
|
silence_chunks = db.Column(db.JSON(65535))
|
||||||
|
|
||||||
|
|
||||||
|
class Player(db.Model):
|
||||||
|
guid = db.Column(db.String(32), primary_key=True)
|
||||||
|
name = db.Column(db.String(32), primary_key=True)
|
||||||
|
first_seen = db.Column(db.DateTime, index=True)
|
||||||
|
last_seen = db.Column(db.DateTime, index=True)
|
||||||
|
playtime = db.Column(db.Float, index=True)
|
||||||
|
chats = db.Column(db.Integer, index=True)
|
||||||
|
deaths = db.Column(db.Integer, index=True)
|
||||||
|
kills = db.Column(db.Integer, index=True)
|
||||||
|
voicetime = db.Column(db.Float, index=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerNames(db.Model):
|
||||||
|
guid = db.Column(db.String(32), primary_key=True)
|
||||||
|
name = db.Column(db.String(32), primary_key=True)
|
||||||
|
time = db.Column(db.Float)
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerSprays(db.Model):
|
||||||
|
guid = db.Column(db.String(32), primary_key=True)
|
||||||
|
spray = db.Column(db.String(32), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerSession(db.Model):
|
||||||
|
player_guid = db.Column(db.String(32), db.ForeignKey('player.guid'), primary_key=True)
|
||||||
|
session_id = db.Column(db.Integer, db.ForeignKey('session.id'), primary_key=True)
|
||||||
|
playtime = db.Column(db.Float)
|
||||||
|
chats = db.Column(db.Integer)
|
||||||
|
deaths = db.Column(db.Integer)
|
||||||
|
kills = db.Column(db.Integer)
|
||||||
|
voicetime = db.Column(db.Float)
|
||||||
|
voice_chunks = db.Column(db.JSON(65535))
|
||||||
|
|
||||||
|
|
||||||
|
class Chat(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
player_guid = db.Column(db.String(32), db.ForeignKey('player.guid'))
|
||||||
|
session_id = db.Column(db.Integer, db.ForeignKey('session.id'))
|
||||||
|
time = db.Column(db.DateTime, index=True)
|
||||||
|
name = db.Column(db.String(255))
|
||||||
|
chat = db.Column(db.String(1024))
|
||||||
|
|
||||||
|
|
||||||
|
class Event(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
player_guid = db.Column(db.String(32), db.ForeignKey('player.guid'))
|
||||||
|
session_id = db.Column(db.Integer, db.ForeignKey('session.id'))
|
||||||
|
time = db.Column(db.DateTime, index=True)
|
||||||
|
event = db.Column(db.String(32), index=True)
|
||||||
|
data = db.Column(db.JSON(1024))
|
||||||
|
|
1
oofsgi/.gitignore
vendored
Normal file
1
oofsgi/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
uwsgi.sock
|
29
oofsgi/uwsgi.ini
Normal file
29
oofsgi/uwsgi.ini
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[uwsgi]
|
||||||
|
# emperor
|
||||||
|
chdir = ..
|
||||||
|
|
||||||
|
# socket = [addr:port]
|
||||||
|
socket = oofsgi/uwsgi.sock
|
||||||
|
chmod-socket = 664
|
||||||
|
|
||||||
|
# WSGI module and callable
|
||||||
|
# module = [wsgi_module_name]:[application_callable_name]
|
||||||
|
module = WSGI:app
|
||||||
|
|
||||||
|
# master = [master process (true of false)]
|
||||||
|
master = true
|
||||||
|
|
||||||
|
# debugging
|
||||||
|
catch-exceptions = True
|
||||||
|
|
||||||
|
# disable request logging
|
||||||
|
disable-logging=True
|
||||||
|
|
||||||
|
# performance
|
||||||
|
processes = 4
|
||||||
|
buffer-size = 8192
|
||||||
|
|
||||||
|
# async
|
||||||
|
loop = gevent
|
||||||
|
gevent = 2048
|
||||||
|
gevent-monkey-patch = true
|
434
parser.py
Normal file
434
parser.py
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import orjson
|
||||||
|
import time
|
||||||
|
import os.path
|
||||||
|
import multiprocessing
|
||||||
|
import signal
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from sqlalchemy import exc, func
|
||||||
|
from sqlalchemy.sql.expression import bindparam
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from demweb.app import create_app
|
||||||
|
from demweb.extensions import db
|
||||||
|
from demweb import models
|
||||||
|
|
||||||
|
DEMO_PATH_PREFIX = ''
|
||||||
|
DEMO_REGEX = re.compile(r'auto-(\d+-\d+)-(\w+)')
|
||||||
|
|
||||||
|
def _itery(x):
|
||||||
|
if x is None:
|
||||||
|
return ()
|
||||||
|
if isinstance(x, list):
|
||||||
|
return x
|
||||||
|
return (x,)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_chatcolors(message):
|
||||||
|
output = ''
|
||||||
|
msglen = len(message)
|
||||||
|
i = 0
|
||||||
|
while i < msglen:
|
||||||
|
c = message[i]
|
||||||
|
if ord(c) <= 8:
|
||||||
|
if ord(c) == 7:
|
||||||
|
i += 6
|
||||||
|
elif ord(c) == 8:
|
||||||
|
i += 8
|
||||||
|
else:
|
||||||
|
output += c
|
||||||
|
i += 1
|
||||||
|
return output
|
||||||
|
|
||||||
|
silence_start_re = re.compile(' silence_start: (?P<start>[0-9]+(\.?[0-9]*))$')
|
||||||
|
silence_end_re = re.compile(' silence_end: (?P<end>[0-9]+(\.?[0-9]*)) ')
|
||||||
|
total_duration_re = re.compile('size=[^ ]+ time=(?P<hours>[0-9]{2}):(?P<minutes>[0-9]{2}):(?P<seconds>[0-9\.]{5}) bitrate=')
|
||||||
|
|
||||||
|
def get_chunk_times(path):
|
||||||
|
proc = subprocess.Popen([
|
||||||
|
'ffmpeg',
|
||||||
|
'-i', path,
|
||||||
|
'-af', 'silencedetect=n=-80dB:d=0.1',
|
||||||
|
'-f', 'null',
|
||||||
|
'-'],
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
stdout=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
output = proc.communicate()[1].decode('utf-8')
|
||||||
|
lines = output.splitlines()
|
||||||
|
|
||||||
|
# Chunks start when silence ends, and chunks end when silence starts.
|
||||||
|
chunk_starts = []
|
||||||
|
chunk_ends = []
|
||||||
|
for line in lines:
|
||||||
|
silence_start_match = silence_start_re.search(line)
|
||||||
|
silence_end_match = silence_end_re.search(line)
|
||||||
|
total_duration_match = total_duration_re.search(line)
|
||||||
|
if silence_start_match:
|
||||||
|
start = float(silence_start_match.group('start'))
|
||||||
|
if start == 0.:
|
||||||
|
# Ignore initial silence.
|
||||||
|
continue
|
||||||
|
chunk_ends.append(start)
|
||||||
|
if len(chunk_starts) == 0:
|
||||||
|
# Started with non-silence.
|
||||||
|
chunk_starts.append(0.)
|
||||||
|
elif silence_end_match:
|
||||||
|
chunk_starts.append(float(silence_end_match.group('end')))
|
||||||
|
elif total_duration_match:
|
||||||
|
hours = int(total_duration_match.group('hours'))
|
||||||
|
minutes = int(total_duration_match.group('minutes'))
|
||||||
|
seconds = float(total_duration_match.group('seconds'))
|
||||||
|
end_time = hours * 3600 + minutes * 60 + seconds
|
||||||
|
|
||||||
|
if len(chunk_starts) == 0:
|
||||||
|
# No silence found.
|
||||||
|
chunk_starts.append(0)
|
||||||
|
|
||||||
|
if len(chunk_starts) > len(chunk_ends):
|
||||||
|
if abs(chunk_starts[-1] - end_time) < 0.1:
|
||||||
|
# Last chunk starts at very the end? nah.
|
||||||
|
del chunk_starts[-1]
|
||||||
|
else:
|
||||||
|
# Finished with non-silence.
|
||||||
|
chunk_ends.append(end_time)
|
||||||
|
|
||||||
|
return list(zip(chunk_starts, chunk_ends))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_demo(path):
|
||||||
|
jmodified = False
|
||||||
|
with open(DEMO_PATH_PREFIX + path + '/out.json', 'r') as fp:
|
||||||
|
data = orjson.loads(fp.read())
|
||||||
|
|
||||||
|
demoname = os.path.basename(path)
|
||||||
|
match = DEMO_REGEX.match(demoname)
|
||||||
|
|
||||||
|
starttime = datetime.strptime(match.group(1), '%Y%m%d-%H%M%S')
|
||||||
|
tickinterval = data['serverinfo']['tickInterval']
|
||||||
|
|
||||||
|
session = models.Session(
|
||||||
|
time=starttime,
|
||||||
|
length=data['demoheader']['playback_time'],
|
||||||
|
demoname=demoname,
|
||||||
|
mapname=match.group(2),
|
||||||
|
mapmd5=data['serverinfo']['mapMD5'],
|
||||||
|
servername=data['demoheader']['servername'],
|
||||||
|
frames=data['demoheader']['playback_frames'],
|
||||||
|
ticks=data['demoheader']['playback_ticks'],
|
||||||
|
tickinterval=tickinterval,
|
||||||
|
dirty=data['demoheader']['dirty'],
|
||||||
|
playtime=0,
|
||||||
|
rounds=0,
|
||||||
|
ct_wins=0,
|
||||||
|
t_wins=0,
|
||||||
|
chats=0,
|
||||||
|
deaths=0,
|
||||||
|
kills=0,
|
||||||
|
voice_active=data['voice']['active_time'],
|
||||||
|
voice_total=data['voice']['total_time'],
|
||||||
|
silence_chunks=data['voice']['silence'] if 'silence' in data['voice'] else []
|
||||||
|
)
|
||||||
|
db.session.add(session)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
session_playtime = 0
|
||||||
|
session_chats = 0
|
||||||
|
session_deaths = 0
|
||||||
|
session_kills = 0
|
||||||
|
|
||||||
|
if not data['players']:
|
||||||
|
data['players'] = dict()
|
||||||
|
|
||||||
|
for guid, obj in data['players'].items():
|
||||||
|
# convert to seconds
|
||||||
|
obj['playtime'] *= tickinterval
|
||||||
|
|
||||||
|
# try insert new player
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
db.session.execute(
|
||||||
|
models.Player.__table__.insert().prefix_with('IGNORE').values(
|
||||||
|
guid=guid,
|
||||||
|
name=None,
|
||||||
|
first_seen=starttime,
|
||||||
|
last_seen=starttime,
|
||||||
|
playtime=0,
|
||||||
|
chats=0,
|
||||||
|
deaths=0,
|
||||||
|
kills=0,
|
||||||
|
voicetime=0
|
||||||
|
))
|
||||||
|
db.session.commit()
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
db.session.rollback()
|
||||||
|
print('deadlock 1')
|
||||||
|
|
||||||
|
# update player stats atomically
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
db.session.execute(
|
||||||
|
models.Player.__table__.update(models.Player.guid == guid).values(
|
||||||
|
playtime = models.Player.playtime + obj['playtime'],
|
||||||
|
chats = models.Player.chats + obj['chats'],
|
||||||
|
deaths = models.Player.deaths + obj['deaths'],
|
||||||
|
kills = models.Player.kills + obj['kills'],
|
||||||
|
voicetime = models.Player.voicetime + obj['voicetime'],
|
||||||
|
first_seen = func.least(models.Player.first_seen, starttime),
|
||||||
|
last_seen = func.greatest(models.Player.first_seen, starttime),
|
||||||
|
))
|
||||||
|
db.session.commit()
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
db.session.rollback()
|
||||||
|
print('deadlock 2')
|
||||||
|
|
||||||
|
# try insert new player names
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
db.session.execute(
|
||||||
|
models.PlayerNames.__table__.insert().prefix_with('IGNORE').values([
|
||||||
|
dict(
|
||||||
|
guid = guid,
|
||||||
|
name = name,
|
||||||
|
time = 0
|
||||||
|
)
|
||||||
|
for name in obj['names'].keys()
|
||||||
|
]))
|
||||||
|
db.session.commit()
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
db.session.rollback()
|
||||||
|
print('deadlock 3')
|
||||||
|
|
||||||
|
# update player names atomically
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
db.session.execute(
|
||||||
|
models.PlayerNames.__table__.update()
|
||||||
|
.where(models.PlayerNames.guid == bindparam('_guid'))
|
||||||
|
.where(models.PlayerNames.name == bindparam('_name'))
|
||||||
|
.values(time = models.PlayerNames.time + bindparam('_nametime')),
|
||||||
|
[
|
||||||
|
dict(
|
||||||
|
_guid = guid,
|
||||||
|
_name = name,
|
||||||
|
_nametime = nametime
|
||||||
|
) for name, nametime in obj['names'].items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
db.session.rollback()
|
||||||
|
print('deadlock 4')
|
||||||
|
|
||||||
|
# try insert new player sprays
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if obj['sprays']:
|
||||||
|
db.session.execute(
|
||||||
|
models.PlayerSprays.__table__.insert().prefix_with('IGNORE').values(
|
||||||
|
[(guid, spray) for spray in obj['sprays']]
|
||||||
|
))
|
||||||
|
db.session.commit()
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
db.session.rollback()
|
||||||
|
print('deadlock 5')
|
||||||
|
|
||||||
|
# calculate player voice chunks/timing if possible and not exists
|
||||||
|
if obj['voicetime'] > 0 and 'voice_chunks' not in obj:
|
||||||
|
voicefile = DEMO_PATH_PREFIX + path + '/voice/' + guid + '.opus'
|
||||||
|
if os.path.isfile(voicefile):
|
||||||
|
obj['voice_chunks'] = get_chunk_times(voicefile)
|
||||||
|
jmodified = True
|
||||||
|
|
||||||
|
# insert new player session
|
||||||
|
player_session = models.PlayerSession(
|
||||||
|
player_guid=guid,
|
||||||
|
session_id=session.id,
|
||||||
|
playtime=obj['playtime'],
|
||||||
|
chats=obj['chats'],
|
||||||
|
deaths=obj['deaths'],
|
||||||
|
kills=obj['kills'],
|
||||||
|
voicetime=obj['voicetime'],
|
||||||
|
voice_chunks=obj['voice_chunks'] if 'voice_chunks' in obj else None
|
||||||
|
)
|
||||||
|
db.session.add(player_session)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if guid != "BOT":
|
||||||
|
session_playtime += player_session.playtime
|
||||||
|
session_chats += player_session.chats
|
||||||
|
session_deaths += player_session.deaths
|
||||||
|
session_kills += player_session.kills
|
||||||
|
|
||||||
|
session.playtime = session_playtime
|
||||||
|
session.chats = session_chats
|
||||||
|
session.deaths = session_deaths
|
||||||
|
session.kills = session_kills
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
for obj in _itery(data['events']):
|
||||||
|
if obj['event'] == 'round_start':
|
||||||
|
session.rounds += 1
|
||||||
|
elif obj['event'] == 'round_end':
|
||||||
|
if obj['winner'] == 2:
|
||||||
|
session.t_wins += 1
|
||||||
|
elif obj['winner'] == 3:
|
||||||
|
session.ct_wins += 1
|
||||||
|
|
||||||
|
event = models.Event(
|
||||||
|
player_guid=guid,
|
||||||
|
session_id=session.id,
|
||||||
|
time=starttime + timedelta(seconds=obj['tick'] / tickinterval),
|
||||||
|
event=obj['event'],
|
||||||
|
data=obj
|
||||||
|
)
|
||||||
|
db.session.add(event)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
for obj in _itery(data['chat']):
|
||||||
|
msg = obj['msgName']
|
||||||
|
if msg == '#Cstrike_Name_Change':
|
||||||
|
nick = obj['msgSender']
|
||||||
|
msg = 'changed their name to "{}"'.format(obj['msgText'])
|
||||||
|
elif obj['steamid'] == 'BOT':
|
||||||
|
nick = 'Console'
|
||||||
|
msg = obj['msgName'].lstrip('\x01\x07FF0000Console: ')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
nick, msg = obj['msgName'][1:].split('\x01', 1)
|
||||||
|
except Exception:
|
||||||
|
msg = None
|
||||||
|
if msg:
|
||||||
|
msg = msg.lstrip(': ')
|
||||||
|
|
||||||
|
if msg:
|
||||||
|
nick = remove_chatcolors(nick)
|
||||||
|
msg = remove_chatcolors(msg)
|
||||||
|
|
||||||
|
chat = models.Chat(
|
||||||
|
player_guid=obj['steamid'],
|
||||||
|
session_id=session.id,
|
||||||
|
time=starttime + timedelta(seconds=obj['tick'] / tickinterval),
|
||||||
|
name=nick,
|
||||||
|
chat=msg
|
||||||
|
)
|
||||||
|
db.session.add(chat)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if jmodified:
|
||||||
|
with open(DEMO_PATH_PREFIX + path + '/out.json', 'wb') as fp:
|
||||||
|
fp.write(orjson.dumps(data, option=orjson.OPT_INDENT_2))
|
||||||
|
|
||||||
|
|
||||||
|
def worker(queue, error_queue):
|
||||||
|
app = create_app()
|
||||||
|
with app.app_context():
|
||||||
|
while True:
|
||||||
|
item = queue.get()
|
||||||
|
if not item:
|
||||||
|
queue.task_done()
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.session.commit()
|
||||||
|
parse_demo(item)
|
||||||
|
except Exception as e:
|
||||||
|
error_queue.put(item)
|
||||||
|
print(f'vvv error from: {item} vvv')
|
||||||
|
traceback.print_exception(type(e), e, e.__traceback__)
|
||||||
|
print(f'^^^ error from: {item} ^^^')
|
||||||
|
|
||||||
|
queue.task_done()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) >= 2:
|
||||||
|
DEMO_PATH_PREFIX = sys.argv[1]
|
||||||
|
|
||||||
|
with open('work.txt', 'r') as fp:
|
||||||
|
work = set(fp.read().splitlines())
|
||||||
|
|
||||||
|
with open('done.txt', 'r') as fp:
|
||||||
|
done = set(fp.read().splitlines())
|
||||||
|
|
||||||
|
if True:
|
||||||
|
done = set()
|
||||||
|
app = create_app()
|
||||||
|
with app.app_context():
|
||||||
|
meta = db.metadata
|
||||||
|
for table in reversed(meta.sorted_tables):
|
||||||
|
db.session.execute(table.delete())
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
todo = work - done
|
||||||
|
|
||||||
|
q = multiprocessing.JoinableQueue()
|
||||||
|
q_err = multiprocessing.SimpleQueue()
|
||||||
|
|
||||||
|
# populate queue with jobs
|
||||||
|
print('Populating queue...')
|
||||||
|
for task in todo:
|
||||||
|
q.put(task)
|
||||||
|
|
||||||
|
# ignore STDINT(^C) and store original sigint handler
|
||||||
|
# this is so our forked processes ignore ^C and can finish their work cleanly
|
||||||
|
original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
|
# start workers
|
||||||
|
print('Starting workers...')
|
||||||
|
workers = []
|
||||||
|
for i in range(20):
|
||||||
|
p = multiprocessing.Process(target=worker, args=(q, q_err))
|
||||||
|
workers.append(p)
|
||||||
|
p.start()
|
||||||
|
|
||||||
|
# restore original sigint handler (don't ignore ^C)
|
||||||
|
signal.signal(signal.SIGINT, original_sigint_handler)
|
||||||
|
|
||||||
|
# wait for queue to empty or ^C
|
||||||
|
print('Waiting for jobs to complete or ^C...')
|
||||||
|
try:
|
||||||
|
q.join()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('Quit!')
|
||||||
|
|
||||||
|
# get any jobs which were left in the queue
|
||||||
|
print('Emptying queue...')
|
||||||
|
left = set()
|
||||||
|
while not q.empty():
|
||||||
|
item = q.get()
|
||||||
|
left.add(item)
|
||||||
|
q.task_done()
|
||||||
|
|
||||||
|
# send N kill signals for each worker
|
||||||
|
print('Putting kill jobs into queue...')
|
||||||
|
for _ in workers:
|
||||||
|
q.put(None)
|
||||||
|
q.close()
|
||||||
|
|
||||||
|
# wait until workers are dead
|
||||||
|
print('Waiting for workers to finish...')
|
||||||
|
for w in workers:
|
||||||
|
w.join()
|
||||||
|
|
||||||
|
# get the failed jobs
|
||||||
|
print('Emptying failed queue...')
|
||||||
|
while not q_err.empty():
|
||||||
|
item = q_err.get()
|
||||||
|
left.add(item)
|
||||||
|
q_err.close()
|
||||||
|
|
||||||
|
done.update(todo - left)
|
||||||
|
|
||||||
|
with open('done.txt', 'w') as fp:
|
||||||
|
fp.write('\n'.join(done))
|
16
requirements.txt
Normal file
16
requirements.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
click==8.0.0
|
||||||
|
Flask==2.0.0
|
||||||
|
Flask-SQLAlchemy==2.5.1
|
||||||
|
gevent==21.1.2
|
||||||
|
greenlet==1.1.0
|
||||||
|
itsdangerous==2.0.0
|
||||||
|
Jinja2==3.0.0
|
||||||
|
MarkupSafe==2.0.0
|
||||||
|
mysqlclient==2.0.3
|
||||||
|
orjson==3.5.2
|
||||||
|
python-dotenv==0.17.1
|
||||||
|
SQLAlchemy==1.4.15
|
||||||
|
tqdm==4.60.0
|
||||||
|
Werkzeug==2.0.0
|
||||||
|
zope.event==4.5.0
|
||||||
|
zope.interface==5.4.0
|
Loading…
Reference in New Issue
Block a user