Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
23b25d52bd | |||
7d0d5fa06b | |||
19e3093f3b | |||
461a8587ad | |||
23e07c376a | |||
bf18c0a9cc | |||
0ec0045301 | |||
540bd616a8 | |||
3496759430 | |||
4c6310343d | |||
|
7bf2881145 | ||
|
3850cf3032 | ||
|
832e4b97a9 | ||
|
336352a335 | ||
|
f09c05fd1a | ||
|
7bfa64ca81 | ||
|
41e6be61e7 | ||
|
2c01baef1b | ||
|
7b2120d122 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
*.exe
|
*.exe
|
||||||
|
|
||||||
*.o
|
*.o
|
||||||
*.txt
|
|
||||||
*.log
|
|
||||||
|
10
Makefile
10
Makefile
@ -1,21 +1,15 @@
|
|||||||
CFLAGS= -Ofast -I"include"
|
CFLAGS= -Ofast -I"include"
|
||||||
BIN=cts
|
BIN=cts
|
||||||
|
|
||||||
all: colors4python staticgen
|
all: main
|
||||||
|
|
||||||
# build for static page generator
|
|
||||||
staticgen: colors.o src/dbquery.c
|
staticgen: colors.o src/dbquery.c
|
||||||
echo "\nCompiling executable as static page generator\n"
|
echo "\nCompiling executable as static page generator\n"
|
||||||
gcc -c src/dbquery.c $(CFLAGS) -DSTATICGEN
|
gcc -c src/dbquery.c $(CFLAGS) -DSTATICGEN
|
||||||
gcc -c src/main.c $(CFLAGS) -DSTATICGEN
|
gcc -c src/main.c $(CFLAGS) -DSTATICGEN
|
||||||
gcc colors.o dbquery.o main.o -lsqlite3 -o $(BIN)
|
gcc colors.o dbquery.o main.o -lsqlite3 -o $(BIN)
|
||||||
|
|
||||||
# used by python script to colorize names (html)
|
main: main.o
|
||||||
colors4python:
|
|
||||||
gcc $(CFLAGS) src/colors.c -o colors -DCOLORS4PYTHON
|
|
||||||
|
|
||||||
# build for cgi
|
|
||||||
cgi: main.o
|
|
||||||
gcc colors.o dbquery.o main.o -lsqlite3 -o $(BIN)
|
gcc colors.o dbquery.o main.o -lsqlite3 -o $(BIN)
|
||||||
|
|
||||||
main.o: dbquery.o src/main.c
|
main.o: dbquery.o src/main.c
|
||||||
|
44
README.md
44
README.md
@ -5,45 +5,33 @@ A common gateway inferface (CGI) program written in C to display Race CTS leader
|
|||||||
## Requirements
|
## Requirements
|
||||||
sqlite-devel python3 python-sqlite
|
sqlite-devel python3 python-sqlite
|
||||||
|
|
||||||
|
The first is only needed for compilation of the C program. The latter two are only for the auxiliary script `allmaps.py`.
|
||||||
|
|
||||||
## Compiling
|
## Compiling
|
||||||
`make` makes a static page generator.
|
`make` makes a CGI program.
|
||||||
|
|
||||||
`make cgi` makes a CGI program.
|
`make staticgen` makes a static page generator.
|
||||||
|
|
||||||
## Usage: Import data from Xonotic
|
## Usage: CGI Query Strings
|
||||||
|
The program queries the database `db/cts.db` (`./src/dbquery.c`, function `static bool executequery`)
|
||||||
This program uses an sqlite3 database file created from `~/.xonotic/data/data/server.db` (text).
|
|
||||||
|
|
||||||
sqlite3 my-new.db
|
|
||||||
sqlite > .read schema.sql
|
|
||||||
|
|
||||||
python scripts/import-from-xon.py my-new.db ~/.xonotic/data/data/server.db
|
|
||||||
|
|
||||||
## Usage: (CGI) Queries
|
|
||||||
|
|
||||||
* `(none)`
|
* `(none)`
|
||||||
- file: `queries/mranks.sql`
|
- Query file: `queries/mranks.sql`
|
||||||
- Requests the map list of the server, the best times scored per map and by which player.
|
- Requests the map list of the server and related data.
|
||||||
|
|
||||||
* `?fastest-players`
|
|
||||||
- file: `queries/fastest-players.sql`
|
|
||||||
- Requests the map list of the server, the highest velocities attained per map and by which player.
|
|
||||||
|
|
||||||
* `?map=[map name]`
|
* `?map=[map name]`
|
||||||
- file: `queries/mleaderboard-ojoin.sql`
|
- Query file: `queries/mleaderboard-ojoin.sql`
|
||||||
- Requests the leaderboard of the map.
|
- Requests the leaderboard of the map.
|
||||||
|
|
||||||
* `?player=[clientid]`
|
* `?player=[clientid]`
|
||||||
- file: `queries/rplayers.sql`
|
- Query file: `queries/rplayers.sql`
|
||||||
- Requests a player's ranks for all maps leaderboards s/he is present on.
|
- Requests a player's ranks for all maps leaderboards s/he is present on.
|
||||||
|
|
||||||
`queries/fastest-player-of-map.sql` is used exclusively by the python script `scripts/allmaps.py`.
|
|
||||||
|
|
||||||
## Usage: Static Page Generation
|
## Usage: Static Page Generation
|
||||||
|
|
||||||
python scripts/allmaps.py
|
python scripts/allmaps.py
|
||||||
|
|
||||||
The files `allmaps.py`, `output/leaderboard.css`, `overview.html`, `map.html` produce the output.
|
The CGI program is still invoked in static generation. The files `allmaps.py`, `output/leaderboard.css`, `overview.html`, `map.html` produce the output.
|
||||||
|
|
||||||
Before executing `allmaps.py`, copy and modify the templates.
|
Before executing `allmaps.py`, copy and modify the templates.
|
||||||
|
|
||||||
@ -53,15 +41,15 @@ Before executing `allmaps.py`, copy and modify the templates.
|
|||||||
`allmaps.py` outputs an html file for all distinct maps in the database. The leaderboards for each map (equivalent to `?map=[map name]`) are in `output/maps/`.
|
`allmaps.py` outputs an html file for all distinct maps in the database. The leaderboards for each map (equivalent to `?map=[map name]`) are in `output/maps/`.
|
||||||
|
|
||||||
## Game Versions Used Under:
|
## Game Versions Used Under:
|
||||||
|
* Xonotic 0.8.1
|
||||||
* Xonotic 0.8.1
|
* Xonotic 0.8.2
|
||||||
* Xonotic 0.8.2
|
|
||||||
|
|
||||||
## Compilers
|
## Compilers
|
||||||
|
|
||||||
* gcc (GCC) 10.2.1
|
* gcc (GCC) 10.2.1
|
||||||
* MinGW, GCC 4.7.1
|
* MinGW, GCC 4.7.1
|
||||||
|
|
||||||
__________________
|
__________________
|
||||||
|
|
||||||
The script `scripts/import-from-xon.py` is based on `https://git.teknik.io/antares/xonotic-py-sqlite3-defrag2db` by [Antares](https://antares.neocities.org/).
|
This program uses an sqlite3 database file created from `~/.xonotic/data/data/server.db`.
|
||||||
|
|
||||||
|
The database may be built using [xonotic-py-sqlite3-defrag2db](https://git.teknik.io/antares/xonotic-py-sqlite3-defrag2db).
|
||||||
|
@ -14,18 +14,12 @@ void hsl2rgb(struct Rgb *, const struct Hls const *);
|
|||||||
|
|
||||||
void rgb2hsl(struct Hls *, const struct Rgb const *);
|
void rgb2hsl(struct Hls *, const struct Rgb const *);
|
||||||
|
|
||||||
static const char *decspan(const int);
|
static void decspan(const int);
|
||||||
|
|
||||||
static void hexspan(char *, int, const char *);
|
static void hexspan(const char *);
|
||||||
|
|
||||||
static void colorize_noalloc(char * const);
|
static void b(char * const);
|
||||||
|
|
||||||
static void sanitize(char *);
|
|
||||||
|
|
||||||
void print_plname(const char*);
|
void print_plname(const char*);
|
||||||
|
|
||||||
static char* append_to_str(char *, const char *);
|
|
||||||
|
|
||||||
char* colorize_name(char *, char * const);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
select speed, ifnull(alias, 'Unregistered Player')
|
|
||||||
from Speed, Fastest_players
|
|
||||||
left join Id2alias
|
|
||||||
on idvalue = cryptokey
|
|
||||||
where Speed.mapid = Fastest_players. mapid and Speed.mapid = ?
|
|
@ -1,12 +0,0 @@
|
|||||||
select Speed.mapid, max(trank), speed, ifnull(alias, 'Unregistered Player')
|
|
||||||
from Speed, Fastest_players, Cts_times
|
|
||||||
left join Id2alias
|
|
||||||
on Fastest_players.idvalue = cryptokey
|
|
||||||
where Speed.mapid = Fastest_players.mapid
|
|
||||||
and Cts_times.mapid = Speed.mapid
|
|
||||||
and tvalue != 0
|
|
||||||
group by Cts_times.mapid
|
|
||||||
order by count(trank) DESC;
|
|
||||||
|
|
||||||
-- if condition tvalue != 0 is not present
|
|
||||||
-- database will return that maps have 99 records
|
|
@ -1,53 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS Cts_times;
|
|
||||||
CREATE TABLE Cts_times(
|
|
||||||
mapid TEXT,
|
|
||||||
gametype TEXT,
|
|
||||||
trank INT,
|
|
||||||
tvalue INT,
|
|
||||||
PRIMARY KEY (mapid, gametype, trank),
|
|
||||||
FOREIGN KEY (mapid, gametype, trank) REFERENCES Cts_ranks(mapid, gametype, idrank)
|
|
||||||
);
|
|
||||||
DROP TABLE IF EXISTS Cts_ranks;
|
|
||||||
CREATE TABLE Cts_ranks(
|
|
||||||
mapid TEXT,
|
|
||||||
gametype TEXT,
|
|
||||||
idrank INT,
|
|
||||||
idvalue TEXT,
|
|
||||||
PRIMARY KEY (mapid, gametype, idrank)
|
|
||||||
);
|
|
||||||
DROP TABLE IF EXISTS Id2alias;
|
|
||||||
CREATE TABLE Id2alias(
|
|
||||||
rtype TEXT,
|
|
||||||
cryptokey TEXT,
|
|
||||||
alias TEXT,
|
|
||||||
PRIMARY KEY (cryptokey)
|
|
||||||
);
|
|
||||||
drop table if exists Speed;
|
|
||||||
create table Speed(
|
|
||||||
mapid text,
|
|
||||||
speed float,
|
|
||||||
primary key (mapid)
|
|
||||||
);
|
|
||||||
drop table if exists Fastest_players;
|
|
||||||
create table Fastest_players (
|
|
||||||
mapid text,
|
|
||||||
idvalue text,
|
|
||||||
primary key (mapid)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- These table fields are unaltered.
|
|
||||||
-- Exerpts from source/qcsrc/race.qc
|
|
||||||
-- // player improved his existing record, only have to iterate on ranks between new and old recs
|
|
||||||
-- for (i = prevpos; i > newpos; --i)
|
|
||||||
-- {
|
|
||||||
-- db_put(ServerProgsDB, strcat(map, rr, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
|
|
||||||
-- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
|
|
||||||
-- }
|
|
||||||
-- ....
|
|
||||||
-- // store new time itself
|
|
||||||
-- db_put(ServerProgsDB, strcat(map, rr, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
|
|
||||||
-- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
|
|
||||||
|
|
||||||
-- re: foreign key from & to Cts_ranks & Id2alias.
|
|
||||||
-- An ranked unregistered player will have a row in Cts_ranks, but will not have a row in Id2alias.
|
|
||||||
-- A registered player may have a row in Id2alias, but may not necessary have a rank.
|
|
@ -1,13 +1,6 @@
|
|||||||
import sqlite3 as sql
|
import sqlite3 as sql
|
||||||
import subprocess, traceback
|
import subprocess, traceback
|
||||||
|
|
||||||
# import contextlib
|
|
||||||
#
|
|
||||||
import sys, io, os
|
|
||||||
# import ctypes
|
|
||||||
# colors = ctypes.CDLL('./colors.so')
|
|
||||||
# colors.colorize_name.argtypes = (ctypes.char_p, ctypes.int, ctypes.char_p)
|
|
||||||
|
|
||||||
# get all maps in database
|
# get all maps in database
|
||||||
def getmaps(database):
|
def getmaps(database):
|
||||||
output = []
|
output = []
|
||||||
@ -22,50 +15,26 @@ def getmaps(database):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
# if there is no query then it outputs the index file.
|
# if there is no query then it outputs the index file.
|
||||||
def run_cgi(query=None):
|
def getcontent(query=None):
|
||||||
cmd = [("./cts")]
|
cmd = [("./cts")]
|
||||||
proc = subprocess.Popen(cmd, env=query, stdout=subprocess.PIPE, shell=True)
|
proc = subprocess.Popen(cmd, env=query, stdout=subprocess.PIPE, shell=True)
|
||||||
# communicate returns 'bytes' class with function 'decode'
|
# communicate returns 'bytes' class with function 'decode'
|
||||||
return proc.communicate()[0].decode('utf-8')
|
return proc.communicate()[0].decode('utf-8')
|
||||||
|
|
||||||
def run_colors(player_name):
|
def renderindex(template):
|
||||||
ret = player_name
|
# no env variable
|
||||||
result = subprocess.run(['./colors', player_name], capture_output=True, text=True)
|
table = getcontent()
|
||||||
if result.returncode == 0:
|
filename = "./output/index.html"
|
||||||
ret = result.stdout
|
with open(filename, 'w+') as fout:
|
||||||
return ret
|
fout.write(template % (table))
|
||||||
|
fout.close
|
||||||
def get_speed_record(database, map_id):
|
pass
|
||||||
message = "{name} traveled the fastest at {speed} qu/s."
|
|
||||||
query = str()
|
|
||||||
result = []
|
|
||||||
with open("queries/fastest-player-of-map.sql") as f:
|
|
||||||
query = f.read()
|
|
||||||
# q = query.replace('?', map_id)
|
|
||||||
# print(q)
|
|
||||||
with sql.connect(database) as con:
|
|
||||||
cursor = con.cursor()
|
|
||||||
try:
|
|
||||||
cursor.execute(query, (map_id,))
|
|
||||||
result = cursor.fetchall()
|
|
||||||
except sql.Error:
|
|
||||||
pass
|
|
||||||
player_name = result[0][1]
|
|
||||||
colored = (run_colors(player_name)).strip()
|
|
||||||
velocity = round(result[0][0], 2)
|
|
||||||
return message.format(name=colored, speed=velocity)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
template = ""
|
template = ""
|
||||||
with open("overview.html", 'r') as fin:
|
with open("overview.html", 'r') as fin:
|
||||||
template = fin.read()
|
template = fin.read()
|
||||||
with open("output/index.html", 'w') as fout:
|
renderindex(template)
|
||||||
fout.write(template % run_cgi())
|
|
||||||
# use same template for fastest-players
|
|
||||||
query = {"QUERY_STRING" : "fastest-players"}
|
|
||||||
with open("output/fastest-players.html", 'w') as fout:
|
|
||||||
fout.write(template % run_cgi(query))
|
|
||||||
maps = getmaps("db/cts.db")
|
maps = getmaps("db/cts.db")
|
||||||
with open("map.html", 'r') as fin:
|
with open("map.html", 'r') as fin:
|
||||||
template = fin.read()
|
template = fin.read()
|
||||||
@ -74,16 +43,14 @@ def main():
|
|||||||
# game_map is a tuple obj.
|
# game_map is a tuple obj.
|
||||||
map_name = game_map[0]
|
map_name = game_map[0]
|
||||||
query = {"QUERY_STRING" : ("map=%s" % map_name)}
|
query = {"QUERY_STRING" : ("map=%s" % map_name)}
|
||||||
filename = ("output/maps/%s.html" % map_name)
|
table = getcontent(query)
|
||||||
sentence = get_speed_record("db/cts.db", map_name)
|
filename = ("./output/maps/%s.html" % map_name)
|
||||||
with open(filename, 'w+') as fout:
|
with open(filename, 'w+') as fout:
|
||||||
title = map_name
|
title = map_name
|
||||||
fout.write(template.format(
|
fout.write(template.format(
|
||||||
title=title,
|
title=title,
|
||||||
map_name=map_name,
|
map_name=map_name,
|
||||||
table=run_cgi(query),
|
table=table)
|
||||||
speed=sentence
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# fout.write(template % (title, map_name, table))
|
# fout.write(template % (title, map_name, table))
|
||||||
return True
|
return True
|
||||||
|
@ -1,255 +0,0 @@
|
|||||||
import re, argparse
|
|
||||||
import sqlite3 as sql
|
|
||||||
import logging
|
|
||||||
import logging.handlers
|
|
||||||
|
|
||||||
from os.path import isfile, exists
|
|
||||||
from urllib.parse import unquote
|
|
||||||
|
|
||||||
import sys, traceback
|
|
||||||
|
|
||||||
#------------------------------------------------+
|
|
||||||
# get_list_from_server_txt
|
|
||||||
#------------------------------------------------+
|
|
||||||
# Rows in the game server database are
|
|
||||||
# occasionally concatenated into one line.
|
|
||||||
# To simplify, they are unmerged.
|
|
||||||
#
|
|
||||||
# The final result is every row in the game server
|
|
||||||
# database with its own index in a list.
|
|
||||||
#------------------------------------------------+
|
|
||||||
|
|
||||||
def get_list_from_server_txt(filename):
|
|
||||||
def unmerge_rows(line, char, x):
|
|
||||||
chunks = line.split(char)
|
|
||||||
newrows = [char.join(chunks[:x]), char.join(chunks[x:])]
|
|
||||||
# need to prefix each row with a char.
|
|
||||||
# only the last row will be missing it.
|
|
||||||
newrows[-1] = char + newrows[-1]
|
|
||||||
if newrows[-1].count(char) > (x - 1):
|
|
||||||
newrows += unmerge_rows(newrows.pop(), char, x)
|
|
||||||
return newrows
|
|
||||||
rows = []
|
|
||||||
with open(filename, 'r') as f:
|
|
||||||
# server database has a lot of newlines to ignore
|
|
||||||
rows = [line for line in f if line != "\n"]
|
|
||||||
output = []
|
|
||||||
n = 3
|
|
||||||
backslash = '\\'
|
|
||||||
for row in rows:
|
|
||||||
# The first and last column is prefixed with a backslash.
|
|
||||||
# So multiple rows on one line should be split at the 3rd backslash.
|
|
||||||
if row.count(backslash) > (n - 1):
|
|
||||||
unmerged = unmerge_rows(row, backslash, n)
|
|
||||||
for u in unmerged:
|
|
||||||
output.append(u)
|
|
||||||
else:
|
|
||||||
output.append(row)
|
|
||||||
return output
|
|
||||||
|
|
||||||
def init_logging(folder, base_file_name="dbimport-%s.log"):
|
|
||||||
if not exists(folder):
|
|
||||||
return False
|
|
||||||
filename = "%s/%s" % (folder, base_file_name)
|
|
||||||
i = 0
|
|
||||||
while exists(filename % i):
|
|
||||||
i += 1
|
|
||||||
filename = filename % i
|
|
||||||
f = open(filename, mode='a', encoding='utf-8')
|
|
||||||
logging.basicConfig(stream=f, level=logging.DEBUG)
|
|
||||||
return filename
|
|
||||||
|
|
||||||
#------------------------------------------------+
|
|
||||||
# uid2namefix
|
|
||||||
#------------------------------------------------+
|
|
||||||
# Unlike other rows,
|
|
||||||
# the separator character, '/' is part of the value of the second column.
|
|
||||||
# so an ordinary match for '/' or '\' can not be done like the other types of rows.
|
|
||||||
# example from game server db:
|
|
||||||
# \/uid2name/Mnumg2Yh/yxNFDTqGI+YyhlM7QDI0fpEmAaBJ8cI5dU=\Tuxxy
|
|
||||||
# it should become:
|
|
||||||
# ["uid2name", "Mnumg2Yh/yxNFDTqGI+YyhlM7QDI0fpEmAaBJ8cI5dU=", "Tuxxy"]
|
|
||||||
|
|
||||||
def uid2namefix(row):
|
|
||||||
# quick fix
|
|
||||||
# replace first and last occurrence of backslash
|
|
||||||
# this results in [,/uid2name/cryptoid_fp, name]
|
|
||||||
e = re.sub(r'^([^\\]*)\\|\\(?=[^\\]*$)', ',', row)
|
|
||||||
# replace first two occurence of forward slash
|
|
||||||
# this results in [,,uid2name,cryptoid_fp, name]
|
|
||||||
ee = e.replace('/', ',', 2)
|
|
||||||
# split on comma
|
|
||||||
# but start from index 2 because the first commas are left over
|
|
||||||
# c is now a list of strings.
|
|
||||||
# ["uid2name", <crypto_idfp value>, <player name value>]
|
|
||||||
c = ee[2:].split(',')
|
|
||||||
c[2] = unquote(c[2])
|
|
||||||
c[2] = c[2].strip('\n')
|
|
||||||
return c
|
|
||||||
|
|
||||||
# O(n) and organize cts related data into list of rows.
|
|
||||||
def filters(db):
|
|
||||||
tt = [] # time (seconds)
|
|
||||||
tr = [] # ranks
|
|
||||||
ti = [] # id
|
|
||||||
# xonotic only stores one player per map
|
|
||||||
# for speed records (fastest player only)
|
|
||||||
s = [] # speed
|
|
||||||
sid = [] # speed id
|
|
||||||
rank_index = 2
|
|
||||||
for d in db:
|
|
||||||
if d.find("uid2name") != -1:
|
|
||||||
ti.append(uid2namefix(d))
|
|
||||||
else:
|
|
||||||
# regex:
|
|
||||||
# find substrings that do not contain backslash, forwardslash, or newline.
|
|
||||||
e = re.findall(r'[^\\/\n]+', d)
|
|
||||||
if d.find("cts100record/time") != -1:
|
|
||||||
e[rank_index] = int(e[rank_index].replace("time", ""))
|
|
||||||
tt.append(e)
|
|
||||||
elif d.find("cts100record/crypto_idfp") != -1:
|
|
||||||
e[3] = unquote(e[3])
|
|
||||||
e[rank_index] = int(e[rank_index].replace("crypto_idfp", ""))
|
|
||||||
tr.append(e)
|
|
||||||
elif d.find("cts100record/speed/speed") != -1:
|
|
||||||
# example:
|
|
||||||
# ['zeel-omnitek', 'cts100record', 'speed', 'speed', '1584.598511']
|
|
||||||
# --- note, index 1, 2, 3 are unneeded
|
|
||||||
s.append([ e[0], unquote(e[-1]) ])
|
|
||||||
elif d.find("cts100record/speed/crypto_idfp") != -1:
|
|
||||||
# example:
|
|
||||||
# ['minideck_cts_v4r4', 'cts100record', 'speed', 'crypto_idfp', 'duHTyaSGpdTk7oebwPFoo899xPoTwP9bja4DUjCjTLo%3D']
|
|
||||||
sid.append([ e[0], unquote(e[-1]) ])
|
|
||||||
return tt, tr, ti, s, sid
|
|
||||||
|
|
||||||
def insert_to_database(d, s):
|
|
||||||
def insert(c, q, d):
|
|
||||||
for x in d:
|
|
||||||
# possible to do executemany
|
|
||||||
# but want to be able to catch the problematic rows
|
|
||||||
# as it is iterated through.
|
|
||||||
# and proceed with adding OK rows.
|
|
||||||
try:
|
|
||||||
c.execute(q, x)
|
|
||||||
except sql.ProgrammingError as e:
|
|
||||||
print(e)
|
|
||||||
print(x)
|
|
||||||
return
|
|
||||||
|
|
||||||
con = sql.connect(d)
|
|
||||||
with con:
|
|
||||||
csr = con.cursor()
|
|
||||||
try:
|
|
||||||
times, \
|
|
||||||
ranks, \
|
|
||||||
ids, \
|
|
||||||
speed, \
|
|
||||||
speed_ids = filters(get_list_from_server_txt(s))
|
|
||||||
if times:
|
|
||||||
insert(csr, "INSERT OR REPLACE INTO Cts_times VALUES(?, ?, ?, ?)", times)
|
|
||||||
logging.info('\n'.join(y for y in [str(x) for x in times]))
|
|
||||||
if ranks:
|
|
||||||
insert(csr, "INSERT OR REPLACE INTO Cts_ranks VALUES(?, ?, ?, ?)", ranks)
|
|
||||||
logging.info('\n'.join(y for y in [str(x) for x in ranks]))
|
|
||||||
if ids:
|
|
||||||
insert(csr, "INSERT OR REPLACE INTO Id2alias VALUES(?, ?, ?)", ids)
|
|
||||||
logging.info('\n'.join(y for y in [str(x) for x in ids]))
|
|
||||||
if speed:
|
|
||||||
insert(csr, "INSERT OR REPLACE INTO Speed VALUES(?, ?)", speed)
|
|
||||||
if speed_ids:
|
|
||||||
insert(csr, "INSERT OR REPLACE INTO Fastest_players VALUES(?, ?)", speed_ids)
|
|
||||||
except sql.Error:
|
|
||||||
logging.exception("sql error encountered in function 'i'")
|
|
||||||
if con:
|
|
||||||
con.rollback()
|
|
||||||
|
|
||||||
def write_query(out_file, data):
|
|
||||||
if exists(out_file):
|
|
||||||
print("stopped: output file already exists", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
times, \
|
|
||||||
ranks, \
|
|
||||||
ids, \
|
|
||||||
speed, \
|
|
||||||
speed_ids = filters(get_list_from_server_txt(data))
|
|
||||||
with open(out_file, 'w', encoding='utf-8') as file_handle:
|
|
||||||
for t in times:
|
|
||||||
file_handle.write("INSERT OR REPLACE INTO Cts_times VALUES(\'%s\', \'%s\', %s, %s);\n" % tuple(t))
|
|
||||||
for r in ranks:
|
|
||||||
file_handle.write("INSERT OR REPLACE INTO Cts_ranks VALUES(\'%s\', \'%s\', %s, \'%s\');\n" % tuple(r))
|
|
||||||
for i in ids:
|
|
||||||
file_handle.write("INSERT OR REPLACE INTO Id2alias VALUES(\'%s\', \'%s\', \'%s\');\n" % tuple(i))
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Test whether repeat rows are added.
|
|
||||||
def check_duplicates(database, data):
|
|
||||||
c = sql.connect(database)
|
|
||||||
p = True
|
|
||||||
with c:
|
|
||||||
cs = c.cursor()
|
|
||||||
try:
|
|
||||||
logging.info("Inserting into database (1/2)")
|
|
||||||
insert_to_database(database, data)
|
|
||||||
logging.info("Querying (1/2)")
|
|
||||||
cs.execute("SELECT * FROM Cts_times")
|
|
||||||
a = cs.fetchall()
|
|
||||||
cs.execute("SELECT * FROM Cts_ranks")
|
|
||||||
b = cs.fetchall()
|
|
||||||
cs.execute("SELECT * FROM Id2alias")
|
|
||||||
c = cs.fetchall()
|
|
||||||
logging.info("Inserting into database (2/2)")
|
|
||||||
insert_to_database(database, data)
|
|
||||||
logging.info("Querying (2/2)")
|
|
||||||
cs.execute("SELECT * FROM Cts_times")
|
|
||||||
x = cs.fetchall()
|
|
||||||
cs.execute("SELECT * FROM Cts_ranks")
|
|
||||||
y = cs.fetchall()
|
|
||||||
cs.execute("SELECT * FROM Id2alias")
|
|
||||||
z = cs.fetchall()
|
|
||||||
if len(a) != len(x):
|
|
||||||
logging.error("Issue with Cts_times")
|
|
||||||
p = False
|
|
||||||
if len(b) != len(y):
|
|
||||||
logging.error("Issue with Cts_ranks")
|
|
||||||
p = False
|
|
||||||
if len(c) != len(z):
|
|
||||||
logging.error("Issue with Id2alias")
|
|
||||||
p = False
|
|
||||||
if p:
|
|
||||||
logging.info("Database ok - no repeat rows added.")
|
|
||||||
except sql.Error:
|
|
||||||
logging.exception("encountered sql error in function 'duplicate test'.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
ap = argparse.ArgumentParser()
|
|
||||||
ap.add_argument('dest',
|
|
||||||
help="destination, a sqlite3 database (or query file, if given -q flag)")
|
|
||||||
ap.add_argument('src',
|
|
||||||
help="source, should be data generated by a Xonotic server")
|
|
||||||
ap.add_argument('-q', '--export-query',
|
|
||||||
action='store_true',
|
|
||||||
help="write query file (as opposed to executing / inserting rows into database)")
|
|
||||||
ap.add_argument('-t', '--test',
|
|
||||||
action='store_true',
|
|
||||||
help="test database for duplicates")
|
|
||||||
ap.add_argument('-l', '--log-dir',
|
|
||||||
type=str,
|
|
||||||
help="set folder to store log files")
|
|
||||||
args = ap.parse_args()
|
|
||||||
log_dir = args.log_dir or "logs"
|
|
||||||
log_file = init_logging(log_dir)
|
|
||||||
if log_file:
|
|
||||||
print("writing log to folder '%s'," % log_dir, log_file, file=sys.stderr)
|
|
||||||
else:
|
|
||||||
print("exited: logging not initialized (folder '%s' does not exist)" % log_dir, file=sys.stderr)
|
|
||||||
exit()
|
|
||||||
try:
|
|
||||||
if args.test:
|
|
||||||
check_duplicates(args.dest, args.src)
|
|
||||||
if args.export_query:
|
|
||||||
write_query(args.dest, args.src)
|
|
||||||
else:
|
|
||||||
insert_to_database(args.dest, args.src)
|
|
||||||
except FileNotFoundError:
|
|
||||||
traceback.print_exc()
|
|
||||||
print("\n\t exited: no input file to work with.", file=sys.stderr)
|
|
149
src/colors.c
149
src/colors.c
@ -80,36 +80,42 @@ void rgb2hsl(struct Hls *dest, const struct Rgb const *src) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* decspan(const int d) {
|
static void decspan(const int d) {
|
||||||
switch(d) {
|
switch(d) {
|
||||||
case 0:
|
case 0:
|
||||||
return "<span style='color:rgb(128,128,128)'>";
|
printf("<span style='color:rgb(128,128,128)'>");
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
return "<span style='color:rgb(255,0,0)'>";
|
printf("<span style='color:rgb(255,0,0)'>");
|
||||||
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
return "<span style='color:rgb(51,255,0)'>";
|
printf("<span style='color:rgb(51,255,0)'>");
|
||||||
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
return "<span style='color:rgb(255,255,0)'>";
|
printf("<span style='color:rgb(255,255,0)'>");
|
||||||
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
return "<span style='color:rgb(51,102,255)'>";
|
printf("<span style='color:rgb(51,102,255)'>");
|
||||||
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
return "<span style='color:rgb(51,255,255)'>";
|
printf("<span style='color:rgb(51,255,255)'>");
|
||||||
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
return "<span style='color:rgb(255,51,102)'>";
|
printf("<span style='color:rgb(255,51,102)'>");
|
||||||
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
return "<span style='color:rgb(255,255,255)'>";
|
printf("<span style='color:rgb(255,255,255)'>");
|
||||||
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
return "<span style='color:rgb(153,153,153)'>";
|
printf("<span style='color:rgb(153,153,153)'>");
|
||||||
|
break;
|
||||||
case 9:
|
case 9:
|
||||||
return "<span style='color:rgb(128,128,128)'>";
|
printf("<span style='color:rgb(128,128,128)'>");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hexspan(char *buf, int bufsize, const char *str) {
|
static void hexspan(const char *str) {
|
||||||
// length of ...
|
|
||||||
// "<span style=\"color:rgb(%d,%d,%d)\">"
|
|
||||||
// where each %d ranges from 0 to 255
|
|
||||||
// char buf[40];
|
|
||||||
const char h1[2] = {str[0], '\0'};
|
const char h1[2] = {str[0], '\0'};
|
||||||
const char h2[2] = {str[1], '\0'};
|
const char h2[2] = {str[1], '\0'};
|
||||||
const char h3[2] = {str[2], '\0'};
|
const char h3[2] = {str[2], '\0'};
|
||||||
@ -125,21 +131,17 @@ static void hexspan(char *buf, int bufsize, const char *str) {
|
|||||||
nhls.l = MIN_CONTRAST;
|
nhls.l = MIN_CONTRAST;
|
||||||
hsl2rgb(&nrgb, &nhls);
|
hsl2rgb(&nrgb, &nhls);
|
||||||
}
|
}
|
||||||
int wrote = snprintf(
|
printf("<span style=\"color:rgb(%d,%d,%d)\">", nrgb.r, nrgb.g, nrgb.b);
|
||||||
buf, bufsize,
|
|
||||||
"<span style=\"color:rgb(%d,%d,%d)\">",
|
|
||||||
nrgb.r, nrgb.g, nrgb.b);
|
|
||||||
// output = buf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TAG_LEN 40
|
static void b(char * const str) {
|
||||||
static void colorize_noalloc(char * const str) {
|
|
||||||
char *token = strtok(str, "^");
|
char *token = strtok(str, "^");
|
||||||
char c;
|
char c;
|
||||||
|
printf("<TD>");
|
||||||
while (token) {
|
while (token) {
|
||||||
c = token[0];
|
c = token[0];
|
||||||
if (isdigit(c)) {
|
if (isdigit(c)) {
|
||||||
printf( decspan(c - '0') );
|
decspan(c - '0');
|
||||||
if (strlen(token) > 1) {
|
if (strlen(token) > 1) {
|
||||||
printf("%s", token + 1);
|
printf("%s", token + 1);
|
||||||
}
|
}
|
||||||
@ -148,9 +150,7 @@ static void colorize_noalloc(char * const str) {
|
|||||||
(isxdigit(token[1]) &&
|
(isxdigit(token[1]) &&
|
||||||
isxdigit(token[2]) &&
|
isxdigit(token[2]) &&
|
||||||
isxdigit(token[3]))) {
|
isxdigit(token[3]))) {
|
||||||
char tag[TAG_LEN];
|
hexspan(token + 1); //exclude x
|
||||||
hexspan(tag, TAG_LEN, token + 1);
|
|
||||||
printf( tag ); //exclude x
|
|
||||||
if (strlen(token) > 4){
|
if (strlen(token) > 4){
|
||||||
printf("%s", token + 4);
|
printf("%s", token + 4);
|
||||||
}
|
}
|
||||||
@ -160,16 +160,7 @@ static void colorize_noalloc(char * const str) {
|
|||||||
}
|
}
|
||||||
token = strtok(NULL, "^");
|
token = strtok(NULL, "^");
|
||||||
}
|
}
|
||||||
}
|
printf("</TD>");
|
||||||
|
|
||||||
static void sanitize(char *user_name) {
|
|
||||||
if (user_name == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char *pos = user_name;
|
|
||||||
while (pos = strstr(pos, "^^")) {
|
|
||||||
strcpy(pos, (pos + 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_plname(const char* str) {
|
void print_plname(const char* str) {
|
||||||
@ -179,85 +170,11 @@ void print_plname(const char* str) {
|
|||||||
char *copy;
|
char *copy;
|
||||||
copy = calloc(strlen(str) + 1, sizeof(char));
|
copy = calloc(strlen(str) + 1, sizeof(char));
|
||||||
strcpy(copy, str);
|
strcpy(copy, str);
|
||||||
sanitize(copy);
|
char *pos = copy;
|
||||||
colorize_noalloc(copy);
|
while (pos = strstr(pos, "^^")) {
|
||||||
fflush(stdout);
|
strcpy(pos, (pos + 1));
|
||||||
|
}
|
||||||
|
b(copy);
|
||||||
free(copy);
|
free(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* append_to_str(char *dest, const char *src) {
|
|
||||||
if (dest == NULL || src == NULL) {
|
|
||||||
fprintf(stderr, "append_to_str(): warning - received null ptr" );
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
size_t new_len = strlen(dest) + strlen(src) + 1;
|
|
||||||
char *new_str = realloc(dest, new_len);
|
|
||||||
if (new_str != NULL) {
|
|
||||||
strcat(new_str, src);
|
|
||||||
}
|
|
||||||
return new_str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the most colorful names are the longest
|
|
||||||
// names with 8 colors can go to 400 chars
|
|
||||||
char* colorize_name(char *buf, /*int bufsize,*/ char * const str) {
|
|
||||||
char *token = strtok(str, "^");
|
|
||||||
char c;
|
|
||||||
// unsigned int i = 0;
|
|
||||||
while (token) {
|
|
||||||
c = token[0];
|
|
||||||
if (isdigit(c)) {
|
|
||||||
// printf("%i : %s\n", i, buf);;
|
|
||||||
buf = append_to_str(buf, decspan(c - '0') );
|
|
||||||
if (strlen(token) > 1) {
|
|
||||||
buf = append_to_str(buf, token + 1);
|
|
||||||
}
|
|
||||||
buf = append_to_str(buf, "</span>");
|
|
||||||
} else if ((c == 'x' && strlen(token) > 3) &&
|
|
||||||
(isxdigit(token[1]) &&
|
|
||||||
isxdigit(token[2]) &&
|
|
||||||
isxdigit(token[3]))) {
|
|
||||||
char tag[TAG_LEN];
|
|
||||||
hexspan(tag, TAG_LEN, token + 1); //exclude x
|
|
||||||
buf = append_to_str(buf, tag );
|
|
||||||
if (strlen(token) > 4){
|
|
||||||
buf = append_to_str(buf, token + 4);
|
|
||||||
}
|
|
||||||
buf = append_to_str(buf, "</span>");
|
|
||||||
} else {
|
|
||||||
buf = append_to_str(buf, token);
|
|
||||||
}
|
|
||||||
token = strtok(NULL, "^");
|
|
||||||
// i++;
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* test:
|
|
||||||
|
|
||||||
./colors ^9[^1S^9]^x469Kom^0ier^7
|
|
||||||
./colors ^9[^1S^9]^^x469Kom^0ier^7
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef COLORS4PYTHON
|
|
||||||
int main(int argc, const char **argv) {
|
|
||||||
if (argc < 1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
char *colored = (char*)calloc(strlen(argv[1]) + 1, sizeof(char));
|
|
||||||
char *player_name = (char*)calloc(strlen(argv[1]) + 1, sizeof(char));
|
|
||||||
strcpy(player_name, argv[1]);
|
|
||||||
sanitize(player_name);
|
|
||||||
|
|
||||||
colored = colorize_name(colored, /*sizeof(colored),*/ player_name);
|
|
||||||
fprintf(stdout, "%s\n", colored);
|
|
||||||
|
|
||||||
// clean up
|
|
||||||
fflush(stdout);
|
|
||||||
free(colored);
|
|
||||||
free(player_name);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
@ -5,10 +5,9 @@
|
|||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#include "colors.h"
|
#include "colors.h"
|
||||||
|
|
||||||
#define QOVERVIEW 'o' // default case - see get_filename()
|
#define QOVERVIEW 'o'
|
||||||
#define QRPLAYER 'p' // ?player=
|
#define QRPLAYER 'p'
|
||||||
#define QMLEADERBOARD 'm' // ?map=
|
#define QMLEADERBOARD 'm'
|
||||||
#define QFASTEST 'f'
|
|
||||||
|
|
||||||
static inline char *get_filename(char * const c) {
|
static inline char *get_filename(char * const c) {
|
||||||
char *qout = "queries/mranks.sql";
|
char *qout = "queries/mranks.sql";
|
||||||
@ -23,9 +22,6 @@ static inline char *get_filename(char * const c) {
|
|||||||
case QRPLAYER:
|
case QRPLAYER:
|
||||||
qout = "queries/rplayers.sql";
|
qout = "queries/rplayers.sql";
|
||||||
break;
|
break;
|
||||||
case QFASTEST:
|
|
||||||
qout = "queries/fastest-players.sql";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return qout;
|
return qout;
|
||||||
@ -62,15 +58,6 @@ static inline void print_tblheader(const char *c) {
|
|||||||
<TH class='columnname'>Rank</TH>\
|
<TH class='columnname'>Rank</TH>\
|
||||||
</TR>";
|
</TR>";
|
||||||
break;
|
break;
|
||||||
case QFASTEST:
|
|
||||||
labels = "<table class='leaderboard'>\
|
|
||||||
<th class='tablename' COLSPAN='4'> <H3><BR>Map List</H3> </th>\
|
|
||||||
<tr>\
|
|
||||||
<th class='columnname'>Name</th>\
|
|
||||||
<th class='columnname'>Records</th>\
|
|
||||||
<th class='columnname'>Highest Velocity (qu/s)</th>\
|
|
||||||
<th class='columnname'>Held By</th>\
|
|
||||||
</tr>";
|
|
||||||
}
|
}
|
||||||
printf("%s", labels);
|
printf("%s", labels);
|
||||||
}
|
}
|
||||||
@ -97,11 +84,9 @@ static void print_time(const unsigned char *strcs) {
|
|||||||
static void qresult(sqlite3_stmt * const sp, const char *c) {
|
static void qresult(sqlite3_stmt * const sp, const char *c) {
|
||||||
#define ISPLAYERNAME(x, y) (y == 1 && *x == QMLEADERBOARD) || \
|
#define ISPLAYERNAME(x, y) (y == 1 && *x == QMLEADERBOARD) || \
|
||||||
(y == 3 && *x == QOVERVIEW)|| \
|
(y == 3 && *x == QOVERVIEW)|| \
|
||||||
(y == 0 && *x == QRPLAYER) || \
|
(y == 0 && *x == QRPLAYER)
|
||||||
(y == 3 && *x == QFASTEST)
|
|
||||||
#define ISMAPNAME(x, y) (y == 0 && *x == QOVERVIEW) ||\
|
#define ISMAPNAME(x, y) (y == 0 && *x == QOVERVIEW) ||\
|
||||||
(y == 1 && *x == QRPLAYER) || \
|
(y == 1 && *x == QRPLAYER)
|
||||||
(y == 0 && *x == QFASTEST)
|
|
||||||
int e;
|
int e;
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
const unsigned int cc = sqlite3_column_count(sp);
|
const unsigned int cc = sqlite3_column_count(sp);
|
||||||
@ -111,9 +96,7 @@ static void qresult(sqlite3_stmt * const sp, const char *c) {
|
|||||||
for (i = 0; i < cc; ++i) {
|
for (i = 0; i < cc; ++i) {
|
||||||
unsigned const char * const field = sqlite3_column_text(sp, i);
|
unsigned const char * const field = sqlite3_column_text(sp, i);
|
||||||
if (ISPLAYERNAME(c, i)) {
|
if (ISPLAYERNAME(c, i)) {
|
||||||
printf("<TD>");
|
|
||||||
print_plname(field);
|
print_plname(field);
|
||||||
printf("</TD>");
|
|
||||||
} else if (ISMAPNAME(c, i)) {
|
} else if (ISMAPNAME(c, i)) {
|
||||||
#ifdef STATICGEN
|
#ifdef STATICGEN
|
||||||
printf("<TD><a href='./maps/%s.html'>%s</a></TD>", field, field);
|
printf("<TD><a href='./maps/%s.html'>%s</a></TD>", field, field);
|
||||||
@ -122,13 +105,11 @@ static void qresult(sqlite3_stmt * const sp, const char *c) {
|
|||||||
#endif
|
#endif
|
||||||
} else if (i == 2 && (*c == QMLEADERBOARD || *c == QOVERVIEW)) {
|
} else if (i == 2 && (*c == QMLEADERBOARD || *c == QOVERVIEW)) {
|
||||||
print_time(field);
|
print_time(field);
|
||||||
} else if (i == 2 && *c == QFASTEST) { // velocity
|
|
||||||
printf("<TD>%.2f</TD>", atof(field) );
|
|
||||||
} else {
|
} else {
|
||||||
printf("<TD>%s</TD>", field);
|
printf("<TD>%s</TD>", field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
printf("</TR>\n");
|
printf("</TR>");
|
||||||
}
|
}
|
||||||
printf("</TABLE>");
|
printf("</TABLE>");
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
<!-- code generated table goes here -->
|
<!-- code generated table goes here -->
|
||||||
{table}
|
{table}
|
||||||
|
|
||||||
<p>{speed}</p>
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>Page generated using <a href="https://notabug.org/scuti/xdfcgi">xdfcgi</a> by <a href="https://scuti.neocities.org/">scuti</a></p>
|
<p>Page generated using <a href="https://notabug.org/scuti/xdfcgi">xdfcgi</a> by <a href="https://scuti.neocities.org/">scuti</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
Loading…
Reference in New Issue
Block a user