improving transition - config tool for oregyen :)

This commit is contained in:
Gabor Körber 2014-05-26 17:03:11 +02:00
parent 3ed80d40b2
commit d0ce5ef086
18 changed files with 1063 additions and 340 deletions

17
app.py Normal file
View File

@ -0,0 +1,17 @@
"""
Main application functions.
* backing up logs.
- from directory to directory
- compression as option
* log-sessions:
- contains one session of log
- has a source (directory, file)
- determines user
- parses logs
"""

69
backup.py Normal file
View File

@ -0,0 +1,69 @@
"""
Backup Directories, Handle Files...
"""
import os, logging, zipfile
def make_zipfile(output_filename, source_dir):
relroot = os.path.abspath(os.path.join(source_dir, os.pardir))
with zipfile.ZipFile(output_filename, "w", zipfile.ZIP_DEFLATED) as zip:
for root, dirs, files in os.walk(source_dir):
# add directory (needed for empty dirs)
zip.write(root, os.path.relpath(root, relroot))
for file in files:
filename = os.path.join(root, file)
if os.path.isfile(filename): # regular files only
arcname = os.path.join(os.path.relpath(root, relroot), file)
zip.write(filename, arcname)
def backup_log_directory(log_directory, backup_directory, compress=True,
ommit_level=2, verbose=False):
# @todo: raw copy
# ommit_level 0: overwrite.
# ommit_level 1: write if selected compression method not backuped yet
# ommit_level 2: write only if neither method contains directory.
nothing_found = True
# get all directory names in log_directory.
# zip them into backup_directory
for directory in os.listdir(log_directory):
full_dir = os.path.join(log_directory, directory)
nothing_found = False
if os.path.isdir(full_dir):
if os.path.exists(os.path.join(full_dir, 'combat.log'))\
and os.path.exists(os.path.join(full_dir, 'game.log'))\
and os.path.exists(os.path.join(full_dir, 'chat.log'))\
and os.path.exists(os.path.join(full_dir, 'game.net.log')):
output_filename = '%s.zip' % directory
if os.path.exists(os.path.join(backup_directory, output_filename))\
and ((ommit_level >= 1 and compress) or (ommit_level==2 and not compress)):
logging.warning('Log %s exists as zip backup, ommited.' % output_filename)
elif os.path.exists(os.path.join(backup_directory, directory))\
and ((ommit_level == 2 and compress) or (ommit_level>=1 and not compress)):
logging.warning('Log %s exists as directory backup, ommited.' % directory)
else:
# do the backup
if compress:
make_zipfile(os.path.join(backup_directory, output_filename),
full_dir)
logging.info('Backed up %s' % directory)
if verbose:
print "Backed up %s" % directory
else:
if verbose:
print "Directory Raw Backup not implemented yet."
raise NotImplementedError
else:
if verbose:
print "%s is not a directory." % full_dir
if verbose and nothing_found:
print "Nothing to backup found in %s" % log_directory
if __name__ == '__main__':
print "Performing Log Backup (Dev)"
log_source = os.path.join(os.path.expanduser('~'),
'Documents', 'My Games', 'StarConflict', 'logs')
log_dest = os.path.join(os.path.expanduser('~'),
'Documents', 'My Games', 'sc')
backup_log_directory(log_source, log_dest, verbose=True, compress=True)

View File

@ -1,80 +1,81 @@
""" """
Brainstorm File for Star Conflict Log Parsing Brainstorm File for Star Conflict Log Parsing
Needed Needed
- find steam/scon folder on windows - find steam/scon folder on windows
- find steam/scon folder on mac - find steam/scon folder on mac
- find steam/scon folder on linux - find steam/scon folder on linux
- what about steamless installs? - what about steamless installs?
Elaborate Elaborate
- which GUI to use? wx? PyQt4? PySide? - which GUI to use? wx? PyQt4? PySide?
- take over the database stuff from weltenfall.starconflict? - take over the database stuff from weltenfall.starconflict?
Investigate Investigate
- language based log files? - language based log files?
""" """
#from win32com.shell import shell, shellcon #from win32com.shell import shell, shellcon
import os, sys, logging import os, sys, logging
from logs.logresolver import LogFileResolver as LogFile from logs.logresolver import LogFileResolver as LogFile
from logs import combat from logs import combat
# for windows its kinda this: # for windows its kinda this:
settings = {'logfiles': os.path.join(os.path.expanduser('~'), settings = {'logfiles': os.path.join(os.path.expanduser('~'),
'Documents', 'Documents',
'My Games', 'My Games',
'StarConflict', 'StarConflict',
'logs' 'logs'
)} )}
def find_log_files(logpath): def find_log_files(logpath):
''' returns a list of 4-tuples representing ''' returns a list of 4-tuples representing
(combat.log, game.log, chat.log, game.net.log) (combat.log, game.log, chat.log, game.net.log)
for each directory in the logpath for each directory in the logpath
''' '''
ret = [] ret = []
for directory in os.listdir(logpath): for directory in os.listdir(logpath):
full_dir = os.path.join(logpath, directory) full_dir = os.path.join(logpath, directory)
if os.path.isdir(full_dir): if os.path.isdir(full_dir):
if os.path.exists(os.path.join(full_dir, 'combat.log'))\ if os.path.exists(os.path.join(full_dir, 'combat.log'))\
and os.path.exists(os.path.join(full_dir, 'game.log'))\ and os.path.exists(os.path.join(full_dir, 'game.log'))\
and os.path.exists(os.path.join(full_dir, 'chat.log'))\ and os.path.exists(os.path.join(full_dir, 'chat.log'))\
and os.path.exists(os.path.join(full_dir, 'game.net.log')): and os.path.exists(os.path.join(full_dir, 'game.net.log')):
ret.append(( ret.append((
os.path.join(full_dir, 'combat.log'), os.path.join(full_dir, 'combat.log'),
os.path.join(full_dir, 'game.log'), os.path.join(full_dir, 'game.log'),
os.path.join(full_dir, 'chat.log'), os.path.join(full_dir, 'chat.log'),
os.path.join(full_dir, 'game.net.log') os.path.join(full_dir, 'game.net.log')
)) ))
return ret return ret
def parse_games(logfiles): def parse_games(logfiles):
_logfiles = [] _logfiles = []
for logpack in logfiles: for logpack in logfiles:
combatlog, gamelog, chatlog, gamenetlog = logpack combatlog, gamelog, chatlog, gamenetlog = logpack
_logfiles.append(LogFile(combatlog)) _logfiles.append(LogFile(combatlog))
#_logfiles.append(LogFile(gamelog)) #_logfiles.append(LogFile(gamelog))
#_logfiles.append(LogFile(chatlog)) #_logfiles.append(LogFile(chatlog))
#_logfiles.append(LogFile(gamenetlog)) #_logfiles.append(LogFile(gamenetlog))
return _logfiles return _logfiles
if __name__ == '__main__': if __name__ == '__main__':
logfiles = find_log_files(settings['logfiles']) logfiles = find_log_files(settings['logfiles'])
logfiles = parse_games(logfiles) logfiles = parse_games(logfiles)
#f = open('output.txt', 'w') #f = open('output.txt', 'w')
rex = {} rex = {}
for logf in logfiles: for logf in logfiles:
logf.parse() logf.read()
for l in logf.lines: logf.parse()
if isinstance(l, dict): for l in logf.lines:
#print l if isinstance(l, dict):
pass #print l
else: pass
if not l.unpack(): else:
rex[l.__class__.__name__] = rex.get(l.__class__.__name__, 0) + 1 if not l.unpack():
if not isinstance(l, combat.UserEvent): rex[l.__class__.__name__] = rex.get(l.__class__.__name__, 0) + 1
print l.values['log'] if not isinstance(l, combat.UserEvent):
#f.write(l.values['log'] + '\n') print l.values['log']
#f.close() #f.write(l.values['log'] + '\n')
#print type(l) #f.close()
#print type(l)
print rex print rex

4
config/__init__.py Normal file
View File

@ -0,0 +1,4 @@
"""
Handle SCon's config file.
"""

52
config/display_config.py Normal file
View File

@ -0,0 +1,52 @@
"""
Simple brainstorm to display a config file.
"""
import os, logging
logging.basicConfig(level=logging.INFO)
# import ET:
try:
ET = None
import lxml.etree as ET
logging.info('Using LXML.')
except ImportError:
try:
import cElementTree as ET
logging.info('Using cElementTree')
except ImportError:
try:
import elementtree.ElementTree as ET
logging.info('Using ElementTree')
except ImportError:
import xml.etree.ElementTree as ET # python 2.5
logging.info('Using xml.ElementTree')
finally:
if not ET:
raise NotImplementedError, "XML Parser not found in your Python."
##################################################################################################
CONFIG_FILE = os.path.join(os.path.expanduser('~'),
'Documents',
'My Games',
'StarConflict',
'user_config.xml')
def read_config(config_file):
tree = ET.parse(config_file)
# doc = tree.getroot()
return tree
if __name__ == '__main__':
# Read the config
tree = read_config(CONFIG_FILE)
doc = tree.getroot()
if doc.tag == 'UserConfig' \
and len(doc) == 1\
and doc[0].tag == 'CVars'\
and doc[0].attrib['version'] == '4':
print "Found valid config file."
cvars = doc[0]
for child in cvars:
print '%s = %s' % (child.tag, child.attrib['val'])
else:
print "Not found valid config file."

0
game/__init__.py Normal file
View File

17
game/battle.py Normal file
View File

@ -0,0 +1,17 @@
"""
Represents a battle instance.
todo: finding battles. factory for missions, skirmishes?
"""
class Battle(object):
__slots__ = ['players',
'teams',
'time_start',
'time_end',]
def __init__(self, parent=None):
# parent is a log-session usually
pass

0
game/mission.py Normal file
View File

0
game/skirmish.py Normal file
View File

444
gui/treeview.py Normal file
View File

@ -0,0 +1,444 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
## FROM:
# https://github.com/cloudformdesign/cloudtb/blob/master/extra/PyQt/treeview.py
# ****** The Cloud Toolbox v0.1.2******
# This is the cloud toolbox -- a single module used in several packages
# found at <https://github.com/cloudformdesign>
# For more information see <cloudformdesign.com>
#
# This module may be a part of a python package, and may be out of date.
# This behavior is intentional, do NOT update it.
#
# You are encouraged to use this pacakge, or any code snippets in it, in
# your own projects. Hopefully they will be helpful to you!
#
# This project is Licenced under The MIT License (MIT)
#
# Copyright (c) 2013 Garrett Berg cloudformdesign.com
# An updated version of this file can be found at:
# <https://github.com/cloudformdesign/cloudtb>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
# http://opensource.org/licenses/MIT
# import pdb
import os
from PyQt4 import QtCore, QtGui
import sys
#import icons_rc
# from cloudtb import dbe
class Node(object):
'''A general node stucture to be used in treeview
the attrib_dict can store any information your overall treeview
needs it to store.
'''
def __init__(self, name, parent=None, icon = None,
attrib_dict = None):
self._name = name
self._attrib = attrib_dict
self._children = []
self._parent = parent
self.icon = icon
if parent is not None:
parent.addChild(self)
def addChild(self, child):
self._children.append(child)
def insertChild(self, position, child):
if position < 0 or position > len(self._children):
return False
self._children.insert(position, child)
child._parent = self
return True
def removeChild(self, position):
if position < 0 or position > len(self._children):
return False
child = self._children.pop(position)
child._parent = None
return True
def name(self):
return self._name
def setName(self, name):
self._name = name
def child(self, row):
return self._children[row]
def childCount(self):
return len(self._children)
def parent(self):
return self._parent
def row(self):
if self._parent is not None:
return self._parent._children.index(self)
def log(self, tabLevel=-1):
output = ""
tabLevel += 1
for i in range(tabLevel):
output += " "
output += "|-" + self._name + "\n"
for child in self._children:
output += child.log(tabLevel)
tabLevel -= 1
# output += "\n"
return output
def __repr__(self):
return self.log()
class TreeViewModel(QtCore.QAbstractItemModel):
"""INPUTS: Node, QObject"""
def __init__(self, root, parent=None, header_title = None):
super(TreeViewModel, self).__init__(parent)
self.is_editable = False
self.is_selectable = True
self.is_enabled = True
self.set_flags()
self._rootNode = root
self.header_title = header_title
# The following are created functions called in "data" -- which is a
# Qt defined funciton. This way of doing things is FAR more pythonic
# and allows classes to inherit this one and not have to rewrite the
# entire data method
# all of them recieve an index and a node
def role_display(self, index, node):
if index.column() == 0:
return node.name()
def role_edit(self, index, node):
return self.role_display(index, node)
def role_tool_tip(self, index, node):
return
def role_check_state(self, index, node):
return
def role_decoration(self, index, node):
if index.column() == 0:
icon = node.icon
if icon == None:
return False
else:
return icon
def role_flags(self, index, node):
'''While not technically a "role" it behaves in much the same way.
This method is called by the "flags" method for all indexes with
a node'''
return self.BASE_FLAGS
def set_flags(self, is_editable = None, is_selectable = None,
is_enabled = None):
''' Sets new flags to the BASE_FLAGS variable'''
if is_editable != None:
self.is_editable = is_editable
if is_selectable != None:
self.is_selectable = is_selectable
if is_enabled != None:
self.is_enabled = is_enabled
self.BASE_FLAGS = QtCore.Qt.ItemFlags(
(QtCore.Qt.ItemIsEnabled * bool(self.is_enabled))
| (QtCore.Qt.ItemIsSelectable * bool(self.is_selectable))
| (QtCore.Qt.ItemIsEditable * bool(self.is_editable))
)
"""INPUTS: QModelIndex"""
"""OUTPUT: int"""
def rowCount(self, parent):
if not parent.isValid():
parentNode = self._rootNode
else:
parentNode = parent.internalPointer()
return parentNode.childCount()
"""INPUTS: QModelIndex"""
"""OUTPUT: int"""
def columnCount(self, parent):
return 1
"""INPUTS: QModelIndex, int"""
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
def data(self, index, role):
'''index is an object that contains a pointer to the item inside
internPointer(). Note that this was set during the insertRows
method call, so you don't need to track them!
'''
if not index.isValid():
return None
node = index.internalPointer()
if role == QtCore.Qt.EditRole:
return self.role_edit(index, node)
if role == QtCore.Qt.ToolTipRole:
return self.role_tool_tip(index, node)
if role == QtCore.Qt.CheckStateRole:
return self.role_check_state(index, node)
if role == QtCore.Qt.DisplayRole:
return self.role_display(index, node)
if role == QtCore.Qt.DecorationRole:
return self.role_decoration(index, node)
"""INPUTS: QModelIndex, QVariant, int (flag)"""
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
if role == QtCore.Qt.EditRole:
node = index.internalPointer()
node.setName(value)
return True
return False
"""INPUTS: int, Qt::Orientation, int"""
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if self.header_title != None:
return self.header_title
if section == 0:
return "Scenegraph"
else:
return "Typeinfo"
"""INPUTS: QModelIndex"""
"""OUTPUT: int (flag)"""
# def flags(self, index):
# return (QtCore.Qt.ItemIsEnabled |
# QtCore.Qt.ItemIsSelectable #|
## QtCore.Qt.ItemIsEditable
# )
def flags(self, index):
if not index.isValid():
return self.BASE_FLAGS
node = index.internalPointer()
return self.role_flags(index, node)
"""INPUTS: QModelIndex"""
"""OUTPUT: QModelIndex"""
"""Should return the parent of the node with the given QModelIndex"""
def parent(self, index):
node = self.getNode(index)
parentNode = node.parent()
if parentNode == self._rootNode:
return QtCore.QModelIndex()
return self.createIndex(parentNode.row(), 0, parentNode)
"""INPUTS: int, int, QModelIndex"""
"""OUTPUT: QModelIndex"""
"""Should return a QModelIndex that corresponds to the given row,
column and parent node"""
def index(self, row, column, parent):
# This is how Qt creates the nested (tree) list. It knows how many
# rows it has because of insertRows, and it uses index and
# createIndex to build the tree.
# print 'Index called', row, column
parentNode = self.getNode(parent)
childItem = parentNode.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
"""CUSTOM"""
"""INPUTS: QModelIndex"""
def getNode(self, index):
if index.isValid():
node = index.internalPointer()
if node:
return node
return self._rootNode
"""INPUTS: int, List of Nodes, QModelIndex"""
def insertRows(self, position, rows, parent=QtCore.QModelIndex()):
parentNode = self.getNode(parent)
self.beginInsertRows(parent, position, position + len(rows) - 1)
for i, row in enumerate(rows):
# childCount = parentNode.childCount()
childNode = row
success = parentNode.insertChild(position + i, childNode)
self.endInsertRows()
return success
"""INPUTS: int, int, QModelIndex"""
def removeRows(self, position, rows, parent=QtCore.QModelIndex()):
if rows == 0:
return
parentNode = self.getNode(parent)
self.beginRemoveRows(parent, position, position + rows - 1)
for row in range(rows):
success = parentNode.removeChild(position)
# TODO: break if not success?
self.endRemoveRows()
return success
def clear_rows(self):
return self.removeRows(0, self._rootNode.childCount())
# TODO: doesn't work. Not sure how to get icons
ICON_FOLDER = QtGui.QIcon.fromTheme('folder')
def _node_compare(a, b):
return b.isdir - a.isdir
def get_file_folder_node(fdata, parent):
'''return the node structure of the data.
[[(dir_name, path),
[dir_name, path),
[(file, path),
(file, path)]]
]
]
'''
# TODO: set icons correctly
nodes = []
for fobj in fdata:
path = fobj[0]
name = os.path.split(path)[1]
if len(fobj) == 1:
fileobj = Node(name, parent = parent, icon = None)
fileobj.full_path = path
fileobj.isdir = False
nodes.append(fileobj)
continue
folderobj = Node(name, parent = parent, icon = ICON_FOLDER,
)
folderobj.full_path = path
folderobj.isdir = True
get_file_folder_node(fobj[1], parent = folderobj)
nodes.append(folderobj)
nodes.sort(cmp = _node_compare)
return nodes
import itertools
def _get_filelist_nodes(iter_file_list, dir_path = ''):
'''Takes a sorted file list iterator and returns the files in a
format that can be converted'''
files = []
dir_path = os.path.join(dir_path, '') # Put into directory syntax
len_dp = len(dir_path)
while True:
try:
fpath = next(iter_file_list)
except StopIteration:
break
if dir_path != fpath[:len_dp]:
iter_file_list = itertools.chain((fpath,), iter_file_list)
break
if os.path.isdir(fpath):
iter_file_list, new_files = _get_filelist_nodes(iter_file_list,
dir_path = fpath)
files.append((fpath, new_files))
else:
files.append((fpath,))
return iter_file_list, files
def get_filelist_nodes(file_list, parent = None):
file_list = sorted(file_list)
file_tuples = _get_filelist_nodes(iter(file_list))[1]
return get_file_folder_node(file_tuples, parent)
def dev_show_file_list(file_objects):
'''For developemnet'''
app = QtGui.QApplication(sys.argv)
rootNode = Node("Rootdir")
model = TreeViewModel(rootNode)
treeView = QtGui.QTreeView()
treeView.show()
treeView.setModel(model)
model.insertRows(0, file_objects, QtCore.QModelIndex())
sys.exit(app.exec_())
if __name__ == '__main__':
from pprint import pprint
files = '''/home/user/Projects/Learning/LearningQt/LearningQt.pro.user
/home/user/Projects/Learning/LearningQt/LearningQt.pro
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.h
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.cpp
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.pri
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer
/home/user/Projects/Learning/LearningQt/LearningQt64.png
/home/user/Projects/Learning/LearningQt/LearningQt_harmattan.desktop
/home/user/Projects/Learning/LearningQt/LearningQt.svg
/home/user/Projects/Learning/LearningQt/main.cpp
/home/user/Projects/Learning/LearningQt/LearningQt.desktop
/home/user/Projects/Learning/LearningQt/qml/LearningQt/main.qml
/home/user/Projects/Learning/LearningQt/qml/LearningQt
/home/user/Projects/Learning/LearningQt/qml
/home/user/Projects/Learning/LearningQt/LearningQt80.png'''
nodes = get_filelist_nodes(files.split('\n'))
for n in nodes:
print n
dev_show_file_list(nodes)

View File

@ -7,6 +7,19 @@
import sys import sys
from PyQt4 import QtCore, QtGui, QtWebKit from PyQt4 import QtCore, QtGui, QtWebKit
from treeview import TreeViewModel, Node
class MenuTree(QtGui.QTreeView):
def __init__(self, *args, **kwargs):
QtGui.QTreeView.__init__(self, *args, **kwargs)
self.test_tree()
def test_tree(self):
self.rootNode = Node("Rootdir")
model = TreeViewModel(self.rootNode)
self.setModel(model)
self.rootNode.addChild(Node('Hey'))
class Browser(QtGui.QMainWindow): class Browser(QtGui.QMainWindow):
@ -41,9 +54,14 @@ class Browser(QtGui.QMainWindow):
self.horizontalLayout.addWidget(self.bt_ahead) self.horizontalLayout.addWidget(self.bt_ahead)
self.horizontalLayout.addWidget(self.tb_url) self.horizontalLayout.addWidget(self.tb_url)
self.gridLayout.addLayout(self.horizontalLayout) self.gridLayout.addLayout(self.horizontalLayout)
self.horizontalMainLayout = QtGui.QHBoxLayout()
self.gridLayout.addLayout(self.horizontalMainLayout)
#
self.menu = MenuTree()
self.html = QtWebKit.QWebView() self.html = QtWebKit.QWebView()
self.gridLayout.addWidget(self.html) self.horizontalMainLayout.addWidget(self.menu)
self.horizontalMainLayout.addWidget(self.html)
self.mainLayout.addWidget(self.frame) self.mainLayout.addWidget(self.frame)
self.setCentralWidget(self.centralwidget) self.setCentralWidget(self.centralwidget)

9
logs/base.py Normal file
View File

@ -0,0 +1,9 @@
class Log(object):
matcher = None
@classmethod
def is_handler(cls, log):
return False

View File

@ -1,162 +1,157 @@
""" """
todo: todo:
- English implementation first. - English implementation first.
- parsing combat.log - parsing combat.log
Prosa. Prosa.
All logs start with something like All logs start with something like
23:53:29.137 | LOGDATA 23:53:29.137 | LOGDATA
LOGDATA can be quite different depending on the logfile. LOGDATA can be quite different depending on the logfile.
other forms encountered: other forms encountered:
23:54:00.600 WARNING| 23:54:00.600 WARNING|
combat logs: combat logs:
01:04:38.805 CMBT | 01:04:38.805 CMBT |
The typical log entry The typical log entry
""" """
import re import re
from base import Log
class Log(object):
matcher = None class CombatLog(Log):
@classmethod
@classmethod def _log_handler(cls, log):
def is_handler(cls, log): if log.get('log', '').strip().startswith(cls.__name__):
return False return True
return False
class CombatLog(Log):
@classmethod @classmethod
def _log_handler(cls, log): def is_handler(cls, log):
if log.get('log', '').strip().startswith(cls.__name__): if log.get('logtype', None) == 'CMBT':
return True return cls._log_handler(log)
return False return False
@classmethod def __init__(self, values=None):
def is_handler(cls, log): self.values = values
if log.get('logtype', None) == 'CMBT':
return cls._log_handler(log) def unpack(self):
return False # unpacks the data from the values.
if hasattr(self, 'matcher') and self.matcher:
def __init__(self, values=None): matchers = self.matcher
self.values = values if not isinstance(matchers, list):
matchers = [matchers,]
def unpack(self): for matcher in matchers:
# unpacks the data from the values. m = matcher.match(self.values.get('log', ''))
if hasattr(self, 'matcher') and self.matcher: if m:
matchers = self.matcher self.values.update(m.groupdict())
if not isinstance(matchers, list): return True
matchers = [matchers,]
for matcher in matchers: # @todo: where does this come from?
m = matcher.match(self.values.get('log', '')) class Action(CombatLog):
if m: pass
self.values.update(m.groupdict())
return True class Gameplay(CombatLog):
matcher = [
# @todo: where does this come from? # usual: team(reason). explained reason.
class Action(CombatLog): re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+)\((?P<winner_reason>\w+)\)\.\sFinish\sreason\:\s'(?P<reason_verbose>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
pass # team, unexplained reason (unknown, Timeout)
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+).\sFinish\sreason\:\s'(?P<winner_reason>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
class Gameplay(CombatLog): ]
matcher = [
# usual: team(reason). explained reason. class Apply(CombatLog): # Apply Aura.
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+)\((?P<winner_reason>\w+)\)\.\sFinish\sreason\:\s'(?P<reason_verbose>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"), matcher = re.compile(r"^Apply\saura\s'(?P<aura_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<aura_type>\w+)\sto\s'(?P<target_name>[^\']+)'")
# team, unexplained reason (unknown, Timeout)
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+).\sFinish\sreason\:\s'(?P<winner_reason>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"), class Damage(CombatLog):
] matcher = re.compile(r"^Damage\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))(?:\s(?P<module_class>[^\s]+)\s|\s{2,2})(?P<flags>(?:\w|\|)+)")
class Apply(CombatLog): # Apply Aura. class Spawn(CombatLog):
matcher = re.compile(r"^Apply\saura\s'(?P<aura_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<aura_type>\w+)\sto\s'(?P<target_name>[^\']+)'") matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P<player>\d+)\s\((?P<name>[^,]+),\s+(?P<hash>#\w+)\)\.\s+'(?P<ship_class>\w+)'")
class Damage(CombatLog): class Spell(CombatLog):
matcher = re.compile(r"^Damage\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))(?:\s(?P<module_class>[^\s]+)\s|\s{2,2})(?P<flags>(?:\w|\|)+)") matcher = re.compile(r"^Spell\s'(?P<spell_name>\w+)'\sby\s+(?P<source_name>.*)(?:\((?P<module_name>\w+)\)|)\stargets\((?P<target_num>\d+)\)\:(?:$|\s(?P<targets>.+))")
class Spawn(CombatLog): class Reward(CombatLog):
matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P<player>\d+)\s\((?P<name>[^,]+),\s+(?P<hash>#\w+)\)\.\s+'(?P<ship_class>\w+)'") matcher = re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)(?P<amount>\d+)\s(?P<reward_type>.*)\s+for\s(?P<reward_reason>.*)")
class Spell(CombatLog): class Participant(CombatLog):
matcher = re.compile(r"^Spell\s'(?P<spell_name>\w+)'\sby\s+(?P<source_name>.*)(?:\((?P<module_name>\w+)\)|)\stargets\((?P<target_num>\d+)\)\:(?:$|\s(?P<targets>.+))") matcher = re.compile(r"^\s+Participant\s+(?P<source_name>[^\s]+)(?:\s{2}(?P<ship_class>\w+)|\s{30,})\s+(?:totalDamage\s(?P<total_damage>(?:\d+|\d+\.\d+));\smostDamageWith\s'(?P<module_class>[^']+)';(?P<additional>.*)|<(?P<other>\w+)>)")
class Reward(CombatLog): class Rocket(CombatLog):
matcher = re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)(?P<amount>\d+)\s(?P<reward_type>.*)\s+for\s(?P<reward_reason>.*)") matcher = re.compile(r"^Rocket\s(?P<event>launch|detonation)\.\sowner\s'(?P<name>[^']+)'(?:,\s(?:def\s'(?P<missile_type>\w+)'|target\s'(?P<target>[^']+)'|reason\s'(?P<reason>\w+)'|directHit\s'(?P<direct_hit>[^']+)'))+")
class Participant(CombatLog): class Heal(CombatLog):
matcher = re.compile(r"^\s+Participant\s+(?P<source_name>[^\s]+)(?:\s{2}(?P<ship_class>\w+)|\s{30,})\s+(?:totalDamage\s(?P<total_damage>(?:\d+|\d+\.\d+));\smostDamageWith\s'(?P<module_class>[^']+)';(?P<additional>.*)|<(?P<other>\w+)>)") matcher = [
# heal by module
class Rocket(CombatLog): re.compile(r"^Heal\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))\s(?P<module_class>[^\s]+)"),
matcher = re.compile(r"^Rocket\s(?P<event>launch|detonation)\.\sowner\s'(?P<name>[^']+)'(?:,\s(?:def\s'(?P<missile_type>\w+)'|target\s'(?P<target>[^']+)'|reason\s'(?P<reason>\w+)'|directHit\s'(?P<direct_hit>[^']+)'))+") # direct heal by source or n/a (global buff)
re.compile(r"^Heal\s+(?:n/a|(?P<source_name>\w+))\s+\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))"),
class Heal(CombatLog): ]
matcher = [
# heal by module class Killed(CombatLog):
re.compile(r"^Heal\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))\s(?P<module_class>[^\s]+)"), matcher = [
# direct heal by source or n/a (global buff) re.compile(r"^Killed\s(?P<target_name>[^\s]+)\s+(?P<ship_class>\w+);\s+killer\s(?P<source_name>[^\s]+)\s*"),
re.compile(r"^Heal\s+(?:n/a|(?P<source_name>\w+))\s+\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))"), re.compile(r"^Killed\s(?P<object>[^\(]+)\((?P<target_name>\w+)\);\s+killer\s(?P<source_name>[^\s]+)\s*"),
] re.compile(r"^Killed\s(?P<object>[^\;]+);\s+killer\s(?P<source_name>[^\s]+)\s+.*"),
]
class Killed(CombatLog):
matcher = [ class Captured(CombatLog):
re.compile(r"^Killed\s(?P<target_name>[^\s]+)\s+(?P<ship_class>\w+);\s+killer\s(?P<source_name>[^\s]+)\s*"), matcher = re.compile(r"^Captured\s'(?P<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:(?P<attackers>.*)|.*)")
re.compile(r"^Killed\s(?P<object>[^\(]+)\((?P<target_name>\w+)\);\s+killer\s(?P<source_name>[^\s]+)\s*"),
re.compile(r"^Killed\s(?P<object>[^\;]+);\s+killer\s(?P<source_name>[^\s]+)\s+.*"), class AddStack(CombatLog):
] matcher = re.compile(r"^AddStack\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\.\snew\sstacks\scount\s(?P<stack_count>\d+)")
class Captured(CombatLog): class Cancel(CombatLog):
matcher = re.compile(r"^Captured\s'(?P<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:(?P<attackers>.*)|.*)") matcher = re.compile(r"^Cancel\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\sfrom\s'(?P<source_name>[^']+)'")
class AddStack(CombatLog): class Scores(CombatLog):
matcher = re.compile(r"^AddStack\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\.\snew\sstacks\scount\s(?P<stack_count>\d+)") matcher = re.compile(r"^Scores\s+-\sTeam1\((?P<team1_score>(?:\d+|\d+\.\d+))\)\sTeam2\((?P<team2_score>(?:\d+|\d+\.\d+))\)")
class Cancel(CombatLog): # Special classes
matcher = re.compile(r"^Cancel\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\sfrom\s'(?P<source_name>[^']+)'") class GameEvent(CombatLog):
matcher = [
class Scores(CombatLog): # game session identifier.
matcher = re.compile(r"^Scores\s+-\sTeam1\((?P<team1_score>(?:\d+|\d+\.\d+))\)\sTeam2\((?P<team2_score>(?:\d+|\d+\.\d+))\)") re.compile(r"^Connect\sto\sgame\ssession\s+(?P<game_session>\d+)"),
# start gameplay identifier.
# Special classes re.compile(r"^Start\sgameplay\s'(?P<gameplay_name>\w+)'\smap\s+'(?P<map_id>\w+)',\slocal\sclient\steam\s(?P<local_team>\d+)"),
class GameEvent(CombatLog): # pve mission identifier.
matcher = [ re.compile(r"^Start\sPVE\smission\s'(?P<pve_name>\w+)'\smap\s+'(?P<map_id>\w+)'"),
# game session identifier. ]
re.compile(r"^Connect\sto\sgame\ssession\s+(?P<game_session>\d+)"),
# start gameplay identifier. @classmethod
re.compile(r"^Start\sgameplay\s'(?P<gameplay_name>\w+)'\smap\s+'(?P<map_id>\w+)',\slocal\sclient\steam\s(?P<local_team>\d+)"), def _log_handler(cls, log):
# pve mission identifier. if log.get('log', '').strip().startswith('======='):
re.compile(r"^Start\sPVE\smission\s'(?P<pve_name>\w+)'\smap\s+'(?P<map_id>\w+)'"), return True
] return False
@classmethod def unpack(self):
def _log_handler(cls, log): # unpacks the data from the values.
if log.get('log', '').strip().startswith('======='): # small override to remove trailing "="s in the matching.
return True if hasattr(self, 'matcher') and self.matcher:
return False matchers = self.matcher
if not isinstance(matchers, list):
def unpack(self): matchers = [matchers,]
# unpacks the data from the values. for matcher in matchers:
if hasattr(self, 'matcher') and self.matcher: m = matcher.match(self.values.get('log', '').strip('=').strip())
matchers = self.matcher if m:
if not isinstance(matchers, list): self.values.update(m.groupdict())
matchers = [matchers,] return True
for matcher in matchers:
m = matcher.match(self.values.get('log', '').strip('=').strip()) class UserEvent(CombatLog):
if m: """ special class for combat logs that might be associated with the playing player """
self.values.update(m.groupdict()) @classmethod
return True def _log_handler(cls, log):
if log.get('log', '').strip():
class UserEvent(CombatLog): return True
""" special class for combat logs that might be associated with the playing player """ return False
@classmethod
def _log_handler(cls, log): # Action?
if log.get('log', '').strip(): COMBAT_LOGS = [ Apply, Damage, Spawn, Spell, Reward, Participant, Rocket, Heal,
return True Gameplay, #?
return False Scores,
Killed, Captured, AddStack, Cancel,
# Action? GameEvent, UserEvent
COMBAT_LOGS = [ Apply, Damage, Spawn, Spell, Reward, Participant, Rocket, Heal, ]
Gameplay, #?
Scores,
Killed, Captured, AddStack, Cancel,
GameEvent, UserEvent
]

4
logs/game.py Normal file
View File

@ -0,0 +1,4 @@
GAME_LOGS = []

View File

@ -1,78 +1,82 @@
# #
""" """
Author: Gabor Guzmics, 2013-2014 Author: Gabor Guzmics, 2013-2014
LogFile is an object capable to load SCon Logfiles and parse their ingredients LogFile is an object capable to load SCon Logfiles and parse their ingredients
It can be extended by overriding resolve to understand Logentries further. It can be extended by overriding resolve to understand Logentries further.
Each Logfile represents a physical file parsed, however theoretically, you can also parse arbitrary Each Logfile represents a physical file parsed, however theoretically, you can also parse arbitrary
data by setting the LogFile<instance>._data yourself. data by setting the LogFile<instance>._data yourself.
""" """
import re import re
RE_SCLOG = r'^(?P<hh>\d{2,2})\:(?P<mm>\d{2,2})\:(?P<ss>\d{2,2})\.(?P<ns>\d{3,3})\s(?P<logtype>\s*[^\|\s]+\s*|\s+)\|\s(?P<log>.*)' RE_SCLOG = r'^(?P<hh>\d{2,2})\:(?P<mm>\d{2,2})\:(?P<ss>\d{2,2})\.(?P<ns>\d{3,3})\s(?P<logtype>\s*[^\|\s]+\s*|\s+)\|\s(?P<log>.*)'
R_SCLOG = re.compile(RE_SCLOG) R_SCLOG = re.compile(RE_SCLOG)
class LogFile(object): class LogFile(object):
def __init__(self, fname=None, def __init__(self, fname=None,
folder=None): folder=None):
self.fname = fname self.fname = fname
self.folder = folder # only for custom tagging. self.folder = folder # only for custom tagging.
self.lines = [] self.lines = []
self._data = None self._data = None
if self.fname is not None:
self.open(self.fname) def read(self, fname=None):
fname = fname or self.fname
def open(self, fname): try:
f = open(fname, 'r') f = open(fname, 'r')
self._data = f.read() self._data = f.read()
f.close() finally:
f.close()
def parse(self):
# parse _data if we still have no lines. def set_data(self, data):
if self._data: self._data = data
data_lines = self._data.replace('\r', '\n').replace('\n\n', '\n').split('\n')
lines = [] def parse(self):
for line in data_lines: # parse _data if we still have no lines.
if not line: if self._data:
continue data_lines = self._data.replace('\r', '\n').replace('\n\n', '\n').split('\n')
elif not isinstance(line, basestring): lines = []
lines.append(line) for line in data_lines:
continue if not line:
elif line.startswith('---'): continue
continue elif not isinstance(line, basestring):
else: lines.append(line)
# get the timecode & logtype continue
m = R_SCLOG.match(line) elif line.startswith('---'):
if m: continue
g = m.groupdict() else:
if 'logtype' in g.keys(): # get the timecode & logtype
g['logtype'] = g['logtype'].strip() m = R_SCLOG.match(line)
lines.append(g) if m:
else: g = m.groupdict()
lines.append(line) if 'logtype' in g.keys():
self.lines = lines g['logtype'] = g['logtype'].strip()
# try to identify (resolve) lines. lines.append(g)
if self.lines: else:
lines = [] lines.append(line)
for line in self.lines: self.lines = lines
l = line # try to identify (resolve) lines.
if isinstance(line, basestring): if self.lines:
# Unknown Log? lines = []
pass for line in self.lines:
elif isinstance(line, dict): l = line
# Unresolved Log. if isinstance(line, basestring):
l = self.resolve(line) # Unknown Log?
elif line is None: pass
# dafuq? elif isinstance(line, dict):
pass # Unresolved Log.
else: l = self.resolve(line)
# might be an object? elif line is None:
pass # dafuq?
lines.append(l) pass
else:
self.lines = lines # might be an object?
pass
def resolve(self, line): lines.append(l)
# line is a dict.
# try to find a class that is responsible for this log. self.lines = lines
return line
def resolve(self, line):
# line is a dict.
# try to find a class that is responsible for this log.
return line

38
logs/logfiles.py Normal file
View File

@ -0,0 +1,38 @@
"""
Resolves Logs.
"""
from logfile import LogFile
from combat import COMBAT_LOGS
from game import GAME_LOGS
class LogFileResolver(LogFile):
''' dynamic logfile resolver '''
resolution_classes = COMBAT_LOGS
def __init__(self, *args, **kwargs):
super(LogFileResolver, self).__init__(*args, **kwargs)
self.resolution_classes = self.resolution_classes or []
def resolve(self, line):
for klass in self.resolution_classes:
if klass.is_handler(line):
return klass(line)
return line
class CombatLogFile(LogFile):
''' Combat Log '''
def resolve(self, line):
for klass in COMBAT_LOGS:
if klass.is_handler(line):
return klass(line)
return line
class GameLogFile(LogFile):
''' Game Log '''
def resolve(self, line):
for klass in GAME_LOGS:
if klass.is_handler(line):
return klass(line)
return line

View File

@ -1,19 +0,0 @@
"""
Resolves Logs.
"""
from logfile import LogFile
from combat import COMBAT_LOGS
class LogFileResolver(LogFile):
resolution_classes = COMBAT_LOGS
def __init__(self, *args, **kwargs):
super(LogFileResolver, self).__init__(*args, **kwargs)
self.resolution_classes = self.resolution_classes or []
def resolve(self, line):
for klass in self.resolution_classes:
if klass.is_handler(line):
return klass(line)
return line

70
logs/session.py Normal file
View File

@ -0,0 +1,70 @@
"""
Logging Session.
"""
import zipfile, logging, os
from logfiles import CombatLogFile, GameLogFile
class LogSession(object):
"""
The Log-Session is supposed to save one directory of logs.
It can parse its logs, and build up its internal structure into Battle Instances etc.
"""
def __init__(self, directory):
''' if directory is a file, it will be handled as a compressed folder '''
self.battles = []
self.user = None
# various logfiles used.
self.combat_log = None
self.game_log = None
self.chat_log = None
# self.net_log = None
self.directory = directory
self._zip_source = False
def parse_files(self):
''' parses the logfiles '''
# check if directory is a file
self._zip_source = os.path.isfile(self.directory) or False
if self._zip_source:
self._unzip_logs()
else:
self.combat_log = CombatLogFile(os.path.join(self.directory, 'combat.log'))
self.combat_log.read()
self.game_log = GameLogFile(os.path.join(self.directory, 'game.log'))
self.game_log.read()
# parse all files
self.combat_log.parse()
self.game_log.parse()
def determine_owner(self):
''' determines the user in the parsed gamelog '''
pass
def parse_battles(self):
''' parses the battles '''
pass
def _unzip_logs(self):
z = zipfile.ZipFile(self.directory, "r")
for filename in z.namelist():
fn = os.path.split(filename)[1] or ''
fn = fn.lower()
if fn:
if fn == 'combat.log':
self.combat_log = CombatLogFile(fn)
self.combat_log.set_data(z.read(filename))
elif fn == 'game.log':
self.game_log = GameLogFile(fn)
self.game_log.set_data(z.read(filename))
if __name__ == '__main__':
l_raw = LogSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28')
l_zip = LogSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip')
l_zip.parse_files()
print l_zip.combat_log.lines