Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added api/management/__init__.py
Empty file.
Empty file.
12 changes: 12 additions & 0 deletions api/management/commands/sync_rcon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.core.management.base import BaseCommand
from api.tasks import sync_server_rcon_task

class Command(BaseCommand):
help = 'Sync RCON for a specific server'

def add_arguments(self, parser):
parser.add_argument('server_id', nargs='+', type=int)

def handle(self, *args, **options):
for server_id in options['server_id']:
sync_server_rcon_task(server_id)
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,8 @@
import django.db.models.deletion


# Functions from the following migrations need manual copying.
# Move them and any dependencies into this file, then update the
# RunPython operations to refer to the local versions:
# api.migrations.0002_add_test_data

class Migration(migrations.Migration):

replaces = [
(b'api', '0001_initial'),
(b'api', '0002_add_test_data'),
(b'api', '0003_remove_server_public_secret'),
(b'api', '0004_characterhistory'),
(b'api', '0005_ginfocharacter'),
(b'api', '0006_clan'),
(b'api', 'add_clan_id'),
(b'api', '0001_server_ip_address'),
(b'api', '0002_clean_data_schema'),
(b'api', '0003_add_server_fields'),
(b'api', '0004_server_version'),
(b'api', '0005_make_location_nullable')
]

initial = True

dependencies = [
Expand Down
45 changes: 45 additions & 0 deletions api/migrations/0002_add_rcon_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2018-05-28 00:17
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='server',
name='rcon_host',
field=models.TextField(null=True),
),
migrations.AddField(
model_name='server',
name='rcon_password',
field=models.TextField(null=True),
),
migrations.AddField(
model_name='server',
name='rcon_port',
field=models.IntegerField(null=True),
),
migrations.AddField(
model_name='server',
name='sync_rcon',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='server',
name='ip_address',
field=models.TextField(null=True),
),
migrations.AlterField(
model_name='server',
name='name',
field=models.TextField(null=True),
),
]
12 changes: 8 additions & 4 deletions api/models/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@


class Server(models.Model):
name = models.TextField()
ip_address = models.TextField(default='')
private_secret = models.UUIDField()
last_sync = models.DateTimeField(null=True)
sync_rcon = models.BooleanField(default=False)
rcon_host = models.TextField(null=True)
rcon_port = models.IntegerField(null=True)
rcon_password = models.TextField(null=True)
name = models.TextField(null=True)
ip_address = models.TextField(null=True)
version = models.TextField(null=True)
query_port = models.TextField(null=True)
max_players = models.IntegerField(null=True)
tick_rate = models.IntegerField(null=True)
private_secret = models.UUIDField()
last_sync = models.DateTimeField(null=True)

objects = ServerManager.as_manager()
6 changes: 5 additions & 1 deletion api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,8 @@ def get_online_count(self, server):


class ServerAdminSerializer(ServerSerializer):
private_secret = serpy.Field()
private_secret = serpy.Field()
sync_rcon = serpy.Field()
rcon_host = serpy.Field()
rcon_password = serpy.Field()
rcon_port = serpy.Field()
1 change: 1 addition & 0 deletions api/sync/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flake8: noqa
from .ginfo import sync_ginfo
from .serverdata import sync_server_data
from .serverrcon import sync_server_rcon
3 changes: 0 additions & 3 deletions api/sync/serverdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,3 @@ def sync_server_data(sync_data_id, request_get_params):
sync_ginfo_task.delay(changed_character_ids,
request_get_params['ginfo_group_uid'],
request_get_params['ginfo_access_token'])

from api.tasks import delete_old_history_task
delete_old_history_task.delay()
191 changes: 191 additions & 0 deletions api/sync/serverrcon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
from api.models import Server, ServerSyncData
from valve.rcon import RCON, RCONError
import json

def from_int_bool(v):
return bool(int(v))

def execute_rcon(rcon_host, rcon_port, rcon_password, command):
print('Executing on %s:%s %s' % (rcon_host, rcon_port, command))

try:
with RCON((rcon_host, rcon_port), rcon_password) as rcon:
return rcon.execute(command)
except RCONError:
print('Error sending command ' + command)
except Exception as ex:
print('Error when exceuting RCON command')
raise ex

def execute_rcon_sql(server, build_sql, paginate_predicate, col_type_map):
last_col_paginate = -1
results = []

while True:
response = execute_rcon(
rcon_host=server.rcon_host,
rcon_port=server.rcon_port,
rcon_password=server.rcon_password,
command='sql %s' % build_sql(last_col_paginate))

if response is None:
return False, []

rows = parse_response_into_rows(response, col_type_map)

if len(rows) == 0:
break

last_col_paginate = paginate_predicate(rows[len(rows) - 1])
results = results + rows

return True, results

def parse_response_into_rows(response, col_type_map):
rows = []

for row_index, line in enumerate(response.body.splitlines()):

print line

if row_index == 0:
continue

# print "ROW `%s`" % (line)

row = []
for col_index, col in enumerate(line.split('|')):
col = col.strip()

if col_index >= len(col_type_map):
continue

if col_index == 0:
col = col.replace('#%s' % (row_index - 1), '').strip()

if col == 'void':
col = None

if col is not None:
type_transform = col_type_map[col_index]
col = type_transform(col)

row.append(col)

rows.append(row)

return rows

def get_characters(server):
def build_sql(paginate_value):
return '''
SELECT
acc.online,
ch.id,
ch.char_name,
ch.level,
ch.playerId,
ch.lastTimeOnline,
ch.killerName,
ch.guild,
act.x,
act.y,
act.z
FROM characters AS ch
LEFT JOIN account AS acc ON ch.playerId = acc.user
LEFT JOIN actor_position AS act ON ch.id = act.id
WHERE ch.id > %s
ORDER BY ch.id
LIMIT 20
''' % paginate_value

def paginate_predicate(r):
return r[1]

is_success, rows = execute_rcon_sql(server, build_sql, paginate_predicate,
[from_int_bool, int, str, int, str, int, str, int, float, float, float])

if not is_success:
return None

characters = []

for row in rows:
characters.append({
'is_online': row[0],
'conan_id': row[1],
'name': row[2],
'level': row[3],
'steam_id': row[4],
'last_online': row[5],
'last_killed_by': row[6],
'clan_id': row[7],
'x': row[8],
'y': row[9],
'z': row[10]})

return characters

def get_clans(server):
is_success, rows = execute_rcon_sql(server, '''
SELECT guildId, name, owner, messageOfTheDay FROM guilds
''', [int, unicode, int, unicode])

if not is_success:
return None

guilds = []

for row in rows:
guilds.append({
'id': row[0],
'name': row[1],
'owner_id': row[2],
'motd': row[3]
})

return guilds

def sync_server_rcon(server_id):
server = (Server.objects
.filter(id=server_id)
.first())

is_valid = (
server.sync_rcon and
server is not None and
server.rcon_host is not None and
server.rcon_password is not None and
server.rcon_port is not None)

if not is_valid:
return

characters = get_characters(server)
# clans = get_clans(server)

# if clans is not None:
# for character in characters:
# print character

# if clans is not None:
# for clan in clans:
# print clan

return

is_valid_sync = (
clans is not None and
characters is not None)

if not is_valid_sync:
return

sync_data = ServerSyncData.objects.create(server=server, data=json.dumps({
'version': 'api',
'characters': characters,
'clans': clans
}))

from api.tasks import sync_server_data_task
sync_server_data_task(sync_data.id, {})
21 changes: 14 additions & 7 deletions api/tasks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from .sync import sync_server_data, sync_ginfo
from api.models import CharacterHistory
from datetime import timedelta
from django.utils import timezone
from .sync import sync_server_data, sync_ginfo, sync_server_rcon
from api.models import Server
from serverthrallapi.celery import app


Expand All @@ -16,6 +14,15 @@ def sync_server_data_task(sync_data_id, request_get_params):


@app.task()
def delete_old_history_task():
history_threshold = timezone.now() - timedelta(days=5)
CharacterHistory.objects.filter(created__lt=history_threshold).delete()
def sync_server_rcon_task(server_id):
sync_server_rcon(server_id)


@app.task()
def sync_all_rcon_servers_task():
server_ids = (Server.objects
.filter(rcon_host__isnull=False)
.values_list('id', flat=True))

for server_id in server_ids:
sync_server_rcon_task(server_id)
19 changes: 16 additions & 3 deletions api/views/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from api.serializers import ServerSerializer, ServerAdminSerializer

from .base import BaseView
import json


class ServerView(BaseView):
Expand All @@ -20,16 +21,28 @@ def post(self, request, server_id):
if 'private_secret' not in request.GET:
return HttpResponse('missing required param private_secret')

data = request.GET
server = self.get_server_private(request, server_id)

if server is None:
return HttpResponse('server does not exist', status=404)

if 'name' in data:
server.name = data['name']
data = json.loads(request.body)

if 'sync_rcon' in data:
server.sync_rcon = data['sync_rcon']
if 'rcon_host' in data:
server.rcon_host = data['rcon_host']
server.ip_address = data['rcon_host']
if 'rcon_port' in data:
server.rcon_port = data['rcon_port']
if 'rcon_password' in data:
server.rcon_password = data['rcon_password']

# TODO: If sync_rcon is true, and credentials are different
# check RCON credentials work before creating or editing the server

server.save()
server.refresh_from_db()

serialized = ServerAdminSerializer(server).data
return JsonResponse(serialized, status=200)
Loading