From 149fc122d0466299a3f9702f1b8102ff7f145999 Mon Sep 17 00:00:00 2001 From: Gabor Guzmics Date: Fri, 12 May 2017 18:19:36 +0200 Subject: [PATCH] 2to3 conversion tool actual converted files. --- src/scon/analyze.py | 180 ++-- src/scon/archive/localbrowser.py | 282 +++--- src/scon/backup.py | 142 +-- src/scon/battle.py | 66 +- src/scon/brainstorm.py | 170 ++-- src/scon/config/display_config.py | 236 ++--- src/scon/config/settings.py | 62 +- src/scon/dejaqt/folders.py | 166 ++-- src/scon/dejaqt/qweb.py | 396 ++++----- src/scon/dj/scon/admin.py | 2 +- src/scon/dj/scon/generate_fixtures.py | 1182 ++++++++++++------------- src/scon/dj/scon/views.py | 4 +- src/scon/game/pieces.py | 200 ++--- src/scon/gui/treeview.py | 886 +++++++++--------- src/scon/gui/viewer.py | 2 +- src/scon/logs/base.py | 176 ++-- src/scon/logs/chat.py | 412 ++++----- src/scon/logs/combat.py | 608 ++++++------- src/scon/logs/game.py | 388 ++++---- src/scon/logs/logfiles.py | 98 +- src/scon/logs/logstream.py | 288 +++--- src/scon/logs/session.py | 418 ++++----- src/scon/monitor.py | 320 +++---- src/scon/qscon.py | 204 ++--- src/scon/test/regexes.py | 2 +- 25 files changed, 3445 insertions(+), 3445 deletions(-) diff --git a/src/scon/analyze.py b/src/scon/analyze.py index 43f92ed..f0438d6 100644 --- a/src/scon/analyze.py +++ b/src/scon/analyze.py @@ -1,90 +1,90 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" - Tool to analyze Logs in general. -""" -import os, sys, logging -from logs.logfiles import LogFileResolver as LogFile -from logs import combat, game, chat -from logs.session import LogSessionCollector -from logs.game import ClientInfo - -# for windows its kinda this: -settings = {'root_path': os.path.join(os.path.expanduser('~'), - 'Documents', - 'My Games', - 'StarConflict',), - 'logfiles': os.path.join(os.path.expanduser('~'), - 'Documents', - 'My Games', - 'StarConflict', - 'logs' - )} - - -if __name__ == '__main__': - import logging - logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S') - coll = LogSessionCollector(os.path.join(os.path.expanduser('~'), - 'Documents', 'My Games', 'sc')) - coll.collect_unique() - #f = open('output.txt', 'w') - rex_combat = {} - rex_game = {} - rex_chat = {} - LOG_GOOD = True # Log good packets. - for logf in coll.sessions: - logf.parse_files(['game.log', 'combat.log', 'chat.log']) - - print "----- Log %s -----" % logf.idstr - if logf.combat_log: - for l in logf.combat_log.lines: - if isinstance(l, dict): - #print l - rex_combat['dict'] = rex_combat.get('dict', 0) + 1 - else: - if not l.unpack() or LOG_GOOD: - rex_combat[l.__class__.__name__] = rex_combat.get(l.__class__.__name__, 0) + 1 - if not isinstance(l, combat.UserEvent): - if not LOG_GOOD: - print l.values['log'] - if logf.game_log: - for l in logf.game_log.lines: - if isinstance(l, dict): - rex_game['dict'] = rex_game.get('dict', 0) + 1 - elif isinstance(l, str): - print l - else: - if l.unpack() and not LOG_GOOD: - pass - else: - rex_game[l.__class__.__name__] = rex_game.get(l.__class__.__name__, 0) + 1 - if not LOG_GOOD: - print l.values['log'] - if logf.chat_log: - for l in logf.chat_log.lines: - if isinstance(l, dict): - rex_chat['dict'] = rex_chat.get('dict', 0) + 1 - elif isinstance(l, str): - print l - else: - if l.unpack() and not LOG_GOOD: - pass - else: - rex_chat[l.__class__.__name__] = rex_chat.get(l.__class__.__name__, 0) + 1 - if not LOG_GOOD: - print l.values['log'] - logf.clean(True) - # additional cleanup: - logf.chat_log.lines = [] - logf.game_log.lines = [] - logf.combat_log.lines = [] - print 'Analysis complete:' - print '#'*20+' RexCombat ' + '#' *20 - print rex_combat - print '#'*20+' RexGame ' + '#' *20 - print rex_game - print '#'*20+' RexChat ' + '#' *20 - print rex_chat +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Tool to analyze Logs in general. +""" +import os, sys, logging +from .logs.logfiles import LogFileResolver as LogFile +from .logs import combat, game, chat +from .logs.session import LogSessionCollector +from .logs.game import ClientInfo + +# for windows its kinda this: +settings = {'root_path': os.path.join(os.path.expanduser('~'), + 'Documents', + 'My Games', + 'StarConflict',), + 'logfiles': os.path.join(os.path.expanduser('~'), + 'Documents', + 'My Games', + 'StarConflict', + 'logs' + )} + + +if __name__ == '__main__': + import logging + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + coll = LogSessionCollector(os.path.join(os.path.expanduser('~'), + 'Documents', 'My Games', 'sc')) + coll.collect_unique() + #f = open('output.txt', 'w') + rex_combat = {} + rex_game = {} + rex_chat = {} + LOG_GOOD = True # Log good packets. + for logf in coll.sessions: + logf.parse_files(['game.log', 'combat.log', 'chat.log']) + + print(("----- Log %s -----" % logf.idstr)) + if logf.combat_log: + for l in logf.combat_log.lines: + if isinstance(l, dict): + #print l + rex_combat['dict'] = rex_combat.get('dict', 0) + 1 + else: + if not l.unpack() or LOG_GOOD: + rex_combat[l.__class__.__name__] = rex_combat.get(l.__class__.__name__, 0) + 1 + if not isinstance(l, combat.UserEvent): + if not LOG_GOOD: + print((l.values['log'])) + if logf.game_log: + for l in logf.game_log.lines: + if isinstance(l, dict): + rex_game['dict'] = rex_game.get('dict', 0) + 1 + elif isinstance(l, str): + print(l) + else: + if l.unpack() and not LOG_GOOD: + pass + else: + rex_game[l.__class__.__name__] = rex_game.get(l.__class__.__name__, 0) + 1 + if not LOG_GOOD: + print((l.values['log'])) + if logf.chat_log: + for l in logf.chat_log.lines: + if isinstance(l, dict): + rex_chat['dict'] = rex_chat.get('dict', 0) + 1 + elif isinstance(l, str): + print(l) + else: + if l.unpack() and not LOG_GOOD: + pass + else: + rex_chat[l.__class__.__name__] = rex_chat.get(l.__class__.__name__, 0) + 1 + if not LOG_GOOD: + print((l.values['log'])) + logf.clean(True) + # additional cleanup: + logf.chat_log.lines = [] + logf.game_log.lines = [] + logf.combat_log.lines = [] + print('Analysis complete:') + print(('#'*20+' RexCombat ' + '#' *20)) + print(rex_combat) + print(('#'*20+' RexGame ' + '#' *20)) + print(rex_game) + print(('#'*20+' RexChat ' + '#' *20)) + print(rex_chat) diff --git a/src/scon/archive/localbrowser.py b/src/scon/archive/localbrowser.py index 31a995b..ba51a03 100644 --- a/src/scon/archive/localbrowser.py +++ b/src/scon/archive/localbrowser.py @@ -1,142 +1,142 @@ -import os, logging -from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork -from treeview import TreeViewModel, Node -from django.test import Client - -class DebugPage(QtWebKit.QWebPage): - def sayMyName(self): - return 'DebugPage' - -class LocalWebView(QtWebKit.QWebView): - def __init__(self, *args, **kwargs): - basedir = kwargs.pop('basedir', None) - QtWebKit.QWebView.__init__(self, *args, **kwargs) - oldManager = self.page().networkAccessManager() - self.setPage(DebugPage()) - self.page().setNetworkAccessManager(LocalNetworkAccessManager(self, basedir)) - - def set_basedir(self, basedir): - self.page().setNetworkAccessManager(LocalNetworkAccessManager(self, basedir)) - -class LocalNetworkAccessManager(QtNetwork.QNetworkAccessManager): - USE_NETWORK = False - def __init__(self, parent=None, basedir=None): - QtNetwork.QNetworkAccessManager.__init__(self, parent=None) - if not basedir: - # take current dir as basedir. - self.basedir = os.path.dirname(os.path.abspath(__file__)) - else: - self.basedir = basedir - - def createRequest(self, operation, request, data): - scheme = request.url().scheme() - if scheme != 'page' and scheme != 'image': - if self.USE_NETWORK: - return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data) - elif scheme == 'page': - if operation == self.GetOperation: - # Handle page:// URLs separately by creating custom - # QNetworkReply objects. - reply = PageReply(self, request.url(), self.GetOperation) - #print('here') - #print reply - return reply - elif operation == self.PostOperation: - #print data.readAll() - #print request - reply = PageReply(self, request.url(), self.PostOperation) - return reply - elif scheme == 'image': - if operation == self.GetOperation: - return ImageReply(self, request.url(), self.GetOperation, self.basedir) - else: - if self.USE_NETWORK: - return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data) - return NoNetworkReply(self, request.url(), self.GetOperation) - -class BasePageReply(QtNetwork.QNetworkReply): - content_type = 'text/html; charset=utf-8' - def __init__(self, parent, url, operation): - QtNetwork.QNetworkReply.__init__(self, parent) - self.content = self.initialize_content(url, operation) - self.offset = 0 - self.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, self.get_content_type()) - self.setHeader(QtNetwork.QNetworkRequest.ContentLengthHeader, len(self.content)) - QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('readyRead()')) - QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('finished()')) - self.open(self.ReadOnly | self.Unbuffered) - self.setUrl(url) - - def get_content_type(self): - return self.content_type - - def initialize_content(self, url, operation): - return ''' - - Test -
- - - -
- - ''' - - def abort(self): - pass - - def bytesAvailable(self): - return len(self.content) - self.offset + QtNetwork.QNetworkReply.bytesAvailable(self) - - def isSequential(self): - return True - - def readData(self, maxSize): - if self.offset < len(self.content): - end = min(self.offset + maxSize, len(self.content)) - data = self.content[self.offset:end] - self.offset = end - return data - -class PageReply(BasePageReply): - def initialize_content(self, url, operation): - c = Client() - print "Response for %s, method %s" % (url.path(), operation) - if operation == LocalNetworkAccessManager.GetOperation: - response = c.get(unicode(url.path()), ) - elif operation == LocalNetworkAccessManager.PostOperation: - response = c.post(unicode(url.path())) - # response code - print "Response Status: %s" % response.status_code - # note: on a 404, we might need to trigger file response. - return response.content - -class NoNetworkReply(BasePageReply): - def initialize_content(self, url, operation): - return ''' - - No Network Access. - - Internal access to the network has been disabled. - - - ''' - -class ImageReply(BasePageReply): - content_type = 'image/png' - def __init__(self, parent, url, operation, basedir): - self.basedir = basedir - BasePageReply.__init__(self, parent, url, operation) - - def initialize_content(self, url, operation): - path = os.path.join(self.basedir, unicode(url.path()).lstrip('/')) - if not os.path.exists(path): - logging.error('Image does not exist: %s' % path) - return '' - h = url.host() - try: - f = open(path, 'rb') - return f.read() - finally: - f.close() +import os, logging +from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork +from treeview import TreeViewModel, Node +from django.test import Client + +class DebugPage(QtWebKit.QWebPage): + def sayMyName(self): + return 'DebugPage' + +class LocalWebView(QtWebKit.QWebView): + def __init__(self, *args, **kwargs): + basedir = kwargs.pop('basedir', None) + QtWebKit.QWebView.__init__(self, *args, **kwargs) + oldManager = self.page().networkAccessManager() + self.setPage(DebugPage()) + self.page().setNetworkAccessManager(LocalNetworkAccessManager(self, basedir)) + + def set_basedir(self, basedir): + self.page().setNetworkAccessManager(LocalNetworkAccessManager(self, basedir)) + +class LocalNetworkAccessManager(QtNetwork.QNetworkAccessManager): + USE_NETWORK = False + def __init__(self, parent=None, basedir=None): + QtNetwork.QNetworkAccessManager.__init__(self, parent=None) + if not basedir: + # take current dir as basedir. + self.basedir = os.path.dirname(os.path.abspath(__file__)) + else: + self.basedir = basedir + + def createRequest(self, operation, request, data): + scheme = request.url().scheme() + if scheme != 'page' and scheme != 'image': + if self.USE_NETWORK: + return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data) + elif scheme == 'page': + if operation == self.GetOperation: + # Handle page:// URLs separately by creating custom + # QNetworkReply objects. + reply = PageReply(self, request.url(), self.GetOperation) + #print('here') + #print reply + return reply + elif operation == self.PostOperation: + #print data.readAll() + #print request + reply = PageReply(self, request.url(), self.PostOperation) + return reply + elif scheme == 'image': + if operation == self.GetOperation: + return ImageReply(self, request.url(), self.GetOperation, self.basedir) + else: + if self.USE_NETWORK: + return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data) + return NoNetworkReply(self, request.url(), self.GetOperation) + +class BasePageReply(QtNetwork.QNetworkReply): + content_type = 'text/html; charset=utf-8' + def __init__(self, parent, url, operation): + QtNetwork.QNetworkReply.__init__(self, parent) + self.content = self.initialize_content(url, operation) + self.offset = 0 + self.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, self.get_content_type()) + self.setHeader(QtNetwork.QNetworkRequest.ContentLengthHeader, len(self.content)) + QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('readyRead()')) + QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('finished()')) + self.open(self.ReadOnly | self.Unbuffered) + self.setUrl(url) + + def get_content_type(self): + return self.content_type + + def initialize_content(self, url, operation): + return ''' + + Test +
+ + + +
+ + ''' + + def abort(self): + pass + + def bytesAvailable(self): + return len(self.content) - self.offset + QtNetwork.QNetworkReply.bytesAvailable(self) + + def isSequential(self): + return True + + def readData(self, maxSize): + if self.offset < len(self.content): + end = min(self.offset + maxSize, len(self.content)) + data = self.content[self.offset:end] + self.offset = end + return data + +class PageReply(BasePageReply): + def initialize_content(self, url, operation): + c = Client() + print(("Response for %s, method %s" % (url.path(), operation))) + if operation == LocalNetworkAccessManager.GetOperation: + response = c.get(str(url.path()), ) + elif operation == LocalNetworkAccessManager.PostOperation: + response = c.post(str(url.path())) + # response code + print(("Response Status: %s" % response.status_code)) + # note: on a 404, we might need to trigger file response. + return response.content + +class NoNetworkReply(BasePageReply): + def initialize_content(self, url, operation): + return ''' + + No Network Access. + + Internal access to the network has been disabled. + + + ''' + +class ImageReply(BasePageReply): + content_type = 'image/png' + def __init__(self, parent, url, operation, basedir): + self.basedir = basedir + BasePageReply.__init__(self, parent, url, operation) + + def initialize_content(self, url, operation): + path = os.path.join(self.basedir, str(url.path()).lstrip('/')) + if not os.path.exists(path): + logging.error('Image does not exist: %s' % path) + return '' + h = url.host() + try: + f = open(path, 'rb') + return f.read() + finally: + f.close() \ No newline at end of file diff --git a/src/scon/backup.py b/src/scon/backup.py index 29bb191..5ee21be 100644 --- a/src/scon/backup.py +++ b/src/scon/backup.py @@ -1,71 +1,71 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" - 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) - +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + 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) + diff --git a/src/scon/battle.py b/src/scon/battle.py index 8b81c05..c88f37b 100644 --- a/src/scon/battle.py +++ b/src/scon/battle.py @@ -1,33 +1,33 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" - Tool to analyze Logs in general. -""" -import os, sys, logging -from logs.logfiles import LogFileResolver as LogFile -from logs import combat, game, chat -from logs.session import LogSessionCollector -from logs.game import ClientInfo - -# for windows its kinda this: -settings = {'root_path': os.path.join(os.path.expanduser('~'), - 'Documents', - 'My Games', - 'StarConflict',), - 'logfiles': os.path.join(os.path.expanduser('~'), - 'Documents', - 'My Games', - 'StarConflict', - 'logs' - )} -if __name__ == '__main__': - coll = LogSessionCollector(os.path.join(os.path.expanduser('~'), - 'Documents', 'My Games', 'sc')) - coll.collect_unique() - for logf in coll.sessions: - logf.parse_files(['game.log', 'combat.log']) - logf.clean() - if logf.combat_log: - print 'length combat log ', len(logf.combat_log.lines) - if logf.game_log: - print 'length game log ', len(logf.game_log.lines) \ No newline at end of file +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Tool to analyze Logs in general. +""" +import os, sys, logging +from .logs.logfiles import LogFileResolver as LogFile +from .logs import combat, game, chat +from .logs.session import LogSessionCollector +from .logs.game import ClientInfo + +# for windows its kinda this: +settings = {'root_path': os.path.join(os.path.expanduser('~'), + 'Documents', + 'My Games', + 'StarConflict',), + 'logfiles': os.path.join(os.path.expanduser('~'), + 'Documents', + 'My Games', + 'StarConflict', + 'logs' + )} +if __name__ == '__main__': + coll = LogSessionCollector(os.path.join(os.path.expanduser('~'), + 'Documents', 'My Games', 'sc')) + coll.collect_unique() + for logf in coll.sessions: + logf.parse_files(['game.log', 'combat.log']) + logf.clean() + if logf.combat_log: + print(('length combat log ', len(logf.combat_log.lines))) + if logf.game_log: + print(('length game log ', len(logf.game_log.lines))) \ No newline at end of file diff --git a/src/scon/brainstorm.py b/src/scon/brainstorm.py index df6fcda..f908b89 100644 --- a/src/scon/brainstorm.py +++ b/src/scon/brainstorm.py @@ -1,85 +1,85 @@ -""" - Brainstorm File for Star Conflict Log Parsing - - Needed - - find steam/scon folder on windows - - find steam/scon folder on mac - - find steam/scon folder on linux - - what about steamless installs? - - Elaborate - - which GUI to use? wx? PyQt4? PySide? - - take over the database stuff from weltenfall.starconflict? - - Investigate - - language based log files? -""" -#from win32com.shell import shell, shellcon -import os, sys, logging -from logs.logfiles import LogFileResolver as LogFile -from logs import combat - -# for windows its kinda this: -settings = {'root_path': os.path.join(os.path.expanduser('~'), - 'Documents', - 'My Games', - 'StarConflict',), - 'logfiles': os.path.join(os.path.expanduser('~'), - 'Documents', - 'My Games', - 'StarConflict', - 'logs' - )} - -def find_log_files(logpath): - ''' returns a list of 4-tuples representing - (combat.log, game.log, chat.log, game.net.log) - for each directory in the logpath - ''' - ret = [] - for directory in os.listdir(logpath): - full_dir = os.path.join(logpath, directory) - 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')): - ret.append(( - os.path.join(full_dir, 'combat.log'), - os.path.join(full_dir, 'game.log'), - os.path.join(full_dir, 'chat.log'), - os.path.join(full_dir, 'game.net.log') - )) - return ret - -def parse_games(logfiles): - _logfiles = [] - for logpack in logfiles: - combatlog, gamelog, chatlog, gamenetlog = logpack - _logfiles.append(LogFile(combatlog)) - #_logfiles.append(LogFile(gamelog)) - #_logfiles.append(LogFile(chatlog)) - #_logfiles.append(LogFile(gamenetlog)) - return _logfiles - -if __name__ == '__main__': - logfiles = find_log_files(settings['logfiles']) - logfiles = parse_games(logfiles) - #f = open('output.txt', 'w') - rex = {} - for logf in logfiles: - logf.read() - logf.parse() - for l in logf.lines: - if isinstance(l, dict): - #print l - pass - else: - if not l.unpack(): - rex[l.__class__.__name__] = rex.get(l.__class__.__name__, 0) + 1 - if not isinstance(l, combat.UserEvent): - print l.values['log'] - #f.write(l.values['log'] + '\n') - #f.close() - #print type(l) - print rex \ No newline at end of file +""" + Brainstorm File for Star Conflict Log Parsing + + Needed + - find steam/scon folder on windows + - find steam/scon folder on mac + - find steam/scon folder on linux + - what about steamless installs? + + Elaborate + - which GUI to use? wx? PyQt4? PySide? + - take over the database stuff from weltenfall.starconflict? + + Investigate + - language based log files? +""" +#from win32com.shell import shell, shellcon +import os, sys, logging +from .logs.logfiles import LogFileResolver as LogFile +from .logs import combat + +# for windows its kinda this: +settings = {'root_path': os.path.join(os.path.expanduser('~'), + 'Documents', + 'My Games', + 'StarConflict',), + 'logfiles': os.path.join(os.path.expanduser('~'), + 'Documents', + 'My Games', + 'StarConflict', + 'logs' + )} + +def find_log_files(logpath): + ''' returns a list of 4-tuples representing + (combat.log, game.log, chat.log, game.net.log) + for each directory in the logpath + ''' + ret = [] + for directory in os.listdir(logpath): + full_dir = os.path.join(logpath, directory) + 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')): + ret.append(( + os.path.join(full_dir, 'combat.log'), + os.path.join(full_dir, 'game.log'), + os.path.join(full_dir, 'chat.log'), + os.path.join(full_dir, 'game.net.log') + )) + return ret + +def parse_games(logfiles): + _logfiles = [] + for logpack in logfiles: + combatlog, gamelog, chatlog, gamenetlog = logpack + _logfiles.append(LogFile(combatlog)) + #_logfiles.append(LogFile(gamelog)) + #_logfiles.append(LogFile(chatlog)) + #_logfiles.append(LogFile(gamenetlog)) + return _logfiles + +if __name__ == '__main__': + logfiles = find_log_files(settings['logfiles']) + logfiles = parse_games(logfiles) + #f = open('output.txt', 'w') + rex = {} + for logf in logfiles: + logf.read() + logf.parse() + for l in logf.lines: + if isinstance(l, dict): + #print l + pass + else: + if not l.unpack(): + rex[l.__class__.__name__] = rex.get(l.__class__.__name__, 0) + 1 + if not isinstance(l, combat.UserEvent): + print((l.values['log'])) + #f.write(l.values['log'] + '\n') + #f.close() + #print type(l) + print(rex) \ No newline at end of file diff --git a/src/scon/config/display_config.py b/src/scon/config/display_config.py index b26c4b4..e43f371 100644 --- a/src/scon/config/display_config.py +++ b/src/scon/config/display_config.py @@ -1,119 +1,119 @@ -""" - Simple brainstorm to display a config file. -""" -import os, logging -from settings import settings -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." -################################################################################################## - -class ConfigFile(object): - def __init__(self, config_file=None): - self.cvars = [] - if config_file: - self.config_file = config_file - elif settings: - # settings based loading. - self.config_file = os.path.join(settings.get_path(), 'user_config.xml') - - def open(self, filename = None): - # reads a config file. - filename = filename or self.config_file - self.tree = ET.parse(filename) - doc = self.tree.getroot() - if doc.tag == 'UserConfig' \ - and len(doc) == 1\ - and doc[0].tag == 'CVars'\ - and doc[0].attrib['version'] == '4': - logging.info( "Found valid config file." ) - # save my cvars - self.cvars = doc[0] - else: - logging.info( "Config File not supported." ) - return self - - def pprint(self): - # print out my cvars - for child in self.cvars: - print '%s = %s' % (child.tag, child.attrib['val']) - - def write(self, filename): - output = '\n' - doc = self.tree.getroot() - # we manually serialize it to keep it exactly the same - # like original SC to avoid problems with their software. - def append_node(node, depth=0): - # xml serializing helper function... - s = ['%s<%s' % (' '*depth*2, node.tag),] - for key, val in node.attrib.items(): - s.append(' %s="%s"' % (key, val)) - if len(node): - s.append('>\n') - # append children - for child in node: - s.extend(append_node(child, depth+1)) - s.append('%s\n' % (' '*depth*2, node.tag)) - else: - s.append(' />\n') - return s - l = append_node(doc) - output = output + ''.join( l ) - if filename is None: - # dev. - assert output[-1], '\n' - else: - try: - f = open(filename, 'w') - f.write(output) - finally: - f.close() - return output - - def debug_serializing(self): - # detects if output would result in the same data as input - input, output = None, None - try: - f = open(self.config_file, 'r') - input = f.read() - finally: - f.close() - output = self.write(None) - return output == input - - -def read_config(config_file): - tree = ET.parse(config_file) - # doc = tree.getroot() - return tree - -if __name__ == '__main__': - # Read the config - settings.autodetect() - c = ConfigFile().open() - print '#' * 80 - print "Output File would be:" - print c.write(None) - print '#' * 80 - print "Detected Settings:" - c.pprint() - print '#' * 80 - print 'Serializing Test successful: %s' % c.debug_serializing() +""" + Simple brainstorm to display a config file. +""" +import os, logging +from .settings import settings +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.") +################################################################################################## + +class ConfigFile(object): + def __init__(self, config_file=None): + self.cvars = [] + if config_file: + self.config_file = config_file + elif settings: + # settings based loading. + self.config_file = os.path.join(settings.get_path(), 'user_config.xml') + + def open(self, filename = None): + # reads a config file. + filename = filename or self.config_file + self.tree = ET.parse(filename) + doc = self.tree.getroot() + if doc.tag == 'UserConfig' \ + and len(doc) == 1\ + and doc[0].tag == 'CVars'\ + and doc[0].attrib['version'] == '4': + logging.info( "Found valid config file." ) + # save my cvars + self.cvars = doc[0] + else: + logging.info( "Config File not supported." ) + return self + + def pprint(self): + # print out my cvars + for child in self.cvars: + print(('%s = %s' % (child.tag, child.attrib['val']))) + + def write(self, filename): + output = '\n' + doc = self.tree.getroot() + # we manually serialize it to keep it exactly the same + # like original SC to avoid problems with their software. + def append_node(node, depth=0): + # xml serializing helper function... + s = ['%s<%s' % (' '*depth*2, node.tag),] + for key, val in list(node.attrib.items()): + s.append(' %s="%s"' % (key, val)) + if len(node): + s.append('>\n') + # append children + for child in node: + s.extend(append_node(child, depth+1)) + s.append('%s\n' % (' '*depth*2, node.tag)) + else: + s.append(' />\n') + return s + l = append_node(doc) + output = output + ''.join( l ) + if filename is None: + # dev. + assert output[-1], '\n' + else: + try: + f = open(filename, 'w') + f.write(output) + finally: + f.close() + return output + + def debug_serializing(self): + # detects if output would result in the same data as input + input, output = None, None + try: + f = open(self.config_file, 'r') + input = f.read() + finally: + f.close() + output = self.write(None) + return output == input + + +def read_config(config_file): + tree = ET.parse(config_file) + # doc = tree.getroot() + return tree + +if __name__ == '__main__': + # Read the config + settings.autodetect() + c = ConfigFile().open() + print(('#' * 80)) + print("Output File would be:") + print((c.write(None))) + print(('#' * 80)) + print("Detected Settings:") + c.pprint() + print(('#' * 80)) + print(('Serializing Test successful: %s' % c.debug_serializing())) \ No newline at end of file diff --git a/src/scon/config/settings.py b/src/scon/config/settings.py index 62984b2..6f1053b 100644 --- a/src/scon/config/settings.py +++ b/src/scon/config/settings.py @@ -1,31 +1,31 @@ -import os -import platform - -class Settings(dict): - def autodetect(self, path=None): - # autodetect settings. - d = path - system = platform.system() - if system == 'Windows' or system.startswith('CYGWIN_NT'): - # try to find user folder: - d = d or os.path.join(os.path.expanduser('~'), - 'Documents', - 'My Games', - 'StarConflict',) - elif system == 'Linux': - raise NotImplementedError, "Implement Linux!" - elif system == 'Darwin': - raise NotImplementedError, "Implement Mac!" - else: - raise NotImplementedError, "Unknown System! %s" % platform.system() - if not os.path.exists(d) or not os.path.isdir(d): - raise Exception, "Configuration Autodetection failed. " - self['root_path'] = d - - def get_path(self): - return self.get('root_path', None) - - def get_logs_path(self): - return os.path.join(self.get_path, 'logs') - -settings = Settings() +import os +import platform + +class Settings(dict): + def autodetect(self, path=None): + # autodetect settings. + d = path + system = platform.system() + if system == 'Windows' or system.startswith('CYGWIN_NT'): + # try to find user folder: + d = d or os.path.join(os.path.expanduser('~'), + 'Documents', + 'My Games', + 'StarConflict',) + elif system == 'Linux': + raise NotImplementedError("Implement Linux!") + elif system == 'Darwin': + raise NotImplementedError("Implement Mac!") + else: + raise NotImplementedError("Unknown System! %s" % platform.system()) + if not os.path.exists(d) or not os.path.isdir(d): + raise Exception("Configuration Autodetection failed. ") + self['root_path'] = d + + def get_path(self): + return self.get('root_path', None) + + def get_logs_path(self): + return os.path.join(self.get_path, 'logs') + +settings = Settings() diff --git a/src/scon/dejaqt/folders.py b/src/scon/dejaqt/folders.py index bbc5d7f..fd44e3e 100644 --- a/src/scon/dejaqt/folders.py +++ b/src/scon/dejaqt/folders.py @@ -1,84 +1,84 @@ - -import logging, os -try: - from django.conf import settings -except: - logging.error('Django Settings could not be loaded. Maybe Django has not been initialized?') - settings = None - -class FolderLibrary(object): - def __init__(self, folders=None): - self._folders = {} - try: - if settings: - self.folders.update( getattr(settings, 'DEJAQT_DIRS', {}) ) - except: - logging.error('DEJAQT_DIRS in django settings threw error.') - import traceback - traceback.print_exc() - if folders: - # no try here: if this fails, you got yourself a programming error. - self.folders.update(folders) - self._keys = [] - self.build_keycache() - - def get_folders(self): - return self._folders - - def set_folders(self, folders): - self._folders = folders - self.build_keycache() - folders = property(get_folders, set_folders) - - def build_keycache(self): - self._keys = self._folders.keys() - self._keys.sort(key=lambda item: (-len(item), item)) - - def add_folder(self, url, folder): - if not url: - url = '' - self._folders[url] = folder - self.build_keycache() - - def match(self, url): - # run down our keycache, first match wins. - for key in self._keys: - if url.startswith(key): - return key - - def matched_folder(self, url): - m = self.match(url) - if m is not None: - folder = self._folders[m] - #heading, rest = url[:len(m)], url[len(m):] - rest = url[len(m):] - real_folder = os.path.abspath( os.path.join(folder, rest) ) - if real_folder.startswith(os.path.abspath(folder)): - return real_folder - else: - logging.error('%s does not seem to be a subpath of %s' % (real_folder, folder)) - - def print_folders(self): - print '{' - for k in self._keys: - print "'%s': '%s'," % (k, self._folders[k]) - print '}' - - -if __name__ == "__main__": - # test this: - import os - os.environ['DJANGO_SETTINGS_MODULE'] = 'scon.dj.settings' - f = FolderLibrary({'abc/dab/': 'c:/media', - 'abc': 'd:/abc', - 'abc/dab/tmp': '/tmp', - 'uiuiui': 'x:/', - 'abc/vul/no': 'x:/2', - 'abc/vul': 'x:/3', - 'abc/vul/yes': 'x:/1', - }) - f.add_folder('abc/dub/', 'c:/dubdub') - f.print_folders() - - print f.matched_folder('abc/dab/okokok/some.png') + +import logging, os +try: + from django.conf import settings +except: + logging.error('Django Settings could not be loaded. Maybe Django has not been initialized?') + settings = None + +class FolderLibrary(object): + def __init__(self, folders=None): + self._folders = {} + try: + if settings: + self.folders.update( getattr(settings, 'DEJAQT_DIRS', {}) ) + except: + logging.error('DEJAQT_DIRS in django settings threw error.') + import traceback + traceback.print_exc() + if folders: + # no try here: if this fails, you got yourself a programming error. + self.folders.update(folders) + self._keys = [] + self.build_keycache() + + def get_folders(self): + return self._folders + + def set_folders(self, folders): + self._folders = folders + self.build_keycache() + folders = property(get_folders, set_folders) + + def build_keycache(self): + self._keys = list(self._folders.keys()) + self._keys.sort(key=lambda item: (-len(item), item)) + + def add_folder(self, url, folder): + if not url: + url = '' + self._folders[url] = folder + self.build_keycache() + + def match(self, url): + # run down our keycache, first match wins. + for key in self._keys: + if url.startswith(key): + return key + + def matched_folder(self, url): + m = self.match(url) + if m is not None: + folder = self._folders[m] + #heading, rest = url[:len(m)], url[len(m):] + rest = url[len(m):] + real_folder = os.path.abspath( os.path.join(folder, rest) ) + if real_folder.startswith(os.path.abspath(folder)): + return real_folder + else: + logging.error('%s does not seem to be a subpath of %s' % (real_folder, folder)) + + def print_folders(self): + print('{') + for k in self._keys: + print(("'%s': '%s'," % (k, self._folders[k]))) + print('}') + + +if __name__ == "__main__": + # test this: + import os + os.environ['DJANGO_SETTINGS_MODULE'] = 'scon.dj.settings' + f = FolderLibrary({'abc/dab/': 'c:/media', + 'abc': 'd:/abc', + 'abc/dab/tmp': '/tmp', + 'uiuiui': 'x:/', + 'abc/vul/no': 'x:/2', + 'abc/vul': 'x:/3', + 'abc/vul/yes': 'x:/1', + }) + f.add_folder('abc/dub/', 'c:/dubdub') + f.print_folders() + + print((f.matched_folder('abc/dab/okokok/some.png'))) \ No newline at end of file diff --git a/src/scon/dejaqt/qweb.py b/src/scon/dejaqt/qweb.py index 2d1f29d..dd93a15 100644 --- a/src/scon/dejaqt/qweb.py +++ b/src/scon/dejaqt/qweb.py @@ -1,198 +1,198 @@ -""" - Qt WebKit Browser for local access to internal Django Views. -""" -import os, logging -from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork -from django.test import Client -from folders import FolderLibrary -from django.http.request import QueryDict -from urlparse import urlparse, parse_qs -import cgi -from io import BytesIO -from django.http.multipartparser import MultiPartParser - -class DebugPage(QtWebKit.QWebPage): - def sayMyName(self): - return 'DebugPage' - -class DejaWebView(QtWebKit.QWebView): - ''' - Optional: - * folders: FolderLibrary() Instance. - * page: Initialized QWebPage instance for initial page (default DebugPage()) - ''' - def __init__(self, *args, **kwargs): - self.folders = kwargs.pop('folders', FolderLibrary()) - page = kwargs.pop('page', DebugPage()) - QtWebKit.QWebView.__init__(self, *args, **kwargs) - #self.oldManager = self.page().networkAccessManager() - self.setPage(page) - self.page().setNetworkAccessManager(DejaNetworkAccessManager(self)) - self.client = Client() - #self.client.login(username='admin', password='admin') - -class DejaNetworkAccessManager(QtNetwork.QNetworkAccessManager): - ''' - The Deja Network Access Manager provides access to two new protocols: - - page:/// tries to resolve a page internally via a django test client. - - res:/// direct access to a resource. - - USE_NETWORK delegates to other network access manager protocols, if False, it will not - allow any requests outside these two protocols - (hopefully disabling network access for your internal browser) - - Note, if page does not find the page, a res:/// attempt is made automatically. - This has to be expanded! - - Note2: not sure if cookies and sessions will work this way! - ''' - USE_NETWORK = False - def __init__(self, parent=None): - QtNetwork.QNetworkAccessManager.__init__(self, parent=parent) - if parent: - self.folders = getattr(parent, 'folders', FolderLibrary()) - - def createRequest(self, operation, request, data): - scheme = request.url().scheme() - if scheme != 'page' and scheme != 'res': - if self.USE_NETWORK: - return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data) - elif scheme == 'page': - if operation == self.GetOperation: - # Handle page:// URLs separately by creating custom - # QNetworkReply objects. - reply = PageReply(self, request.url(), self.GetOperation) - #print('here') - #print reply - return reply - elif operation == self.PostOperation: - reply = PageReply(self, request.url(), self.PostOperation, request, data) - return reply - elif scheme == 'res': - if operation == self.GetOperation: - return ResourceReply(self, request.url(), self.GetOperation) - else: - if self.USE_NETWORK: - return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data) - return NoNetworkReply(self, request.url(), self.GetOperation) - -class BasePageReply(QtNetwork.QNetworkReply): - content_type = 'text/html; charset=utf-8' - def __init__(self, parent, url, operation, request=None, data=None): - QtNetwork.QNetworkReply.__init__(self, parent) - self.data = data - self.request = request - self.content = self.initialize_content(url, operation) - self.offset = 0 - self.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, self.get_content_type()) - self.setHeader(QtNetwork.QNetworkRequest.ContentLengthHeader, len(self.content)) - QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('readyRead()')) - QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('finished()')) - self.open(self.ReadOnly | self.Unbuffered) - self.setUrl(url) - - def get_content_type(self): - return self.content_type - - def initialize_content(self, url, operation): - return ''' - - Empty Page - This is an empty page. If you see this, you need to subclass BasePageReply. - - ''' - - def abort(self): - pass - - def bytesAvailable(self): - return len(self.content) - self.offset + QtNetwork.QNetworkReply.bytesAvailable(self) - - def isSequential(self): - return True - - def readData(self, maxSize): - if self.offset < len(self.content): - end = min(self.offset + maxSize, len(self.content)) - data = self.content[self.offset:end] - self.offset = end - return data - -class ResourceReply(BasePageReply): - content_type = 'image/png' - - def determine_content_type(self, path): - return self.content_type - - def initialize_content(self, url, operation): - # determine folder: - path = unicode(url.path()).lstrip('/') - folders = getattr(self.parent(), 'folders') - if folders: - path = folders.matched_folder(path) - if path: - if os.path.exists(path): - try: - f = open(path, 'rb') - return f.read() - finally: - f.close() - else: - logging.warning('Path does not exist: %s' % path) - else: - logging.error('Containing Folder not found for %s' % path) - else: - logging.error('Configuration Error: No Folders found.') - return '' - -class PageReply(ResourceReply): - content_type = 'text/html' - - def initialize_content(self, url, operation): - try: - c = self.parent().parent().client - except: - logging.error('Internal HTTP Client not found. Creating new.') - c = Client() - logging.info( "Response for %s, method %s" % (url.path(), operation) ) - if operation == DejaNetworkAccessManager.GetOperation: - response = c.get(unicode(url.path()), follow=True ) - elif operation == DejaNetworkAccessManager.PostOperation: - ct = str(self.request.rawHeader('Content-Type')) - cl = str(self.request.rawHeader('Content-Length')) - s = str(self.data.readAll()) - if ct.startswith('multipart/form-data'): - # multipart parsing - logging.error('Multipart Parsing Try...') - b = BytesIO(s) - q, files = MultiPartParser({'CONTENT_TYPE': ct, - 'CONTENT_LENGTH': cl, - }, - b, - []).parse() - response = c.post(unicode(url.path()), q, follow=True) - else: - # assume post data. - q = QueryDict( s ) - response = c.post(unicode(url.path()), q, follow=True) - self.content_type = response.get('Content-Type', self.content_type) - # response code - #print "Response Status: %s" % response.status_code - # note: on a 404, we might need to trigger file response. - if response.status_code == 404: - return ResourceReply.initialize_content(self, url, DejaNetworkAccessManager.GetOperation) - if hasattr(response, 'streaming_content'): - return ''.join(response.streaming_content) - return response.content - -class NoNetworkReply(BasePageReply): - def initialize_content(self, url, operation): - return ''' - - No Network Access. - - Internal access to the network has been disabled. - - - ''' - +""" + Qt WebKit Browser for local access to internal Django Views. +""" +import os, logging +from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork +from django.test import Client +from .folders import FolderLibrary +from django.http.request import QueryDict +from urllib.parse import urlparse, parse_qs +import cgi +from io import BytesIO +from django.http.multipartparser import MultiPartParser + +class DebugPage(QtWebKit.QWebPage): + def sayMyName(self): + return 'DebugPage' + +class DejaWebView(QtWebKit.QWebView): + ''' + Optional: + * folders: FolderLibrary() Instance. + * page: Initialized QWebPage instance for initial page (default DebugPage()) + ''' + def __init__(self, *args, **kwargs): + self.folders = kwargs.pop('folders', FolderLibrary()) + page = kwargs.pop('page', DebugPage()) + QtWebKit.QWebView.__init__(self, *args, **kwargs) + #self.oldManager = self.page().networkAccessManager() + self.setPage(page) + self.page().setNetworkAccessManager(DejaNetworkAccessManager(self)) + self.client = Client() + #self.client.login(username='admin', password='admin') + +class DejaNetworkAccessManager(QtNetwork.QNetworkAccessManager): + ''' + The Deja Network Access Manager provides access to two new protocols: + - page:/// tries to resolve a page internally via a django test client. + - res:/// direct access to a resource. + + USE_NETWORK delegates to other network access manager protocols, if False, it will not + allow any requests outside these two protocols + (hopefully disabling network access for your internal browser) + + Note, if page does not find the page, a res:/// attempt is made automatically. + This has to be expanded! + + Note2: not sure if cookies and sessions will work this way! + ''' + USE_NETWORK = False + def __init__(self, parent=None): + QtNetwork.QNetworkAccessManager.__init__(self, parent=parent) + if parent: + self.folders = getattr(parent, 'folders', FolderLibrary()) + + def createRequest(self, operation, request, data): + scheme = request.url().scheme() + if scheme != 'page' and scheme != 'res': + if self.USE_NETWORK: + return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data) + elif scheme == 'page': + if operation == self.GetOperation: + # Handle page:// URLs separately by creating custom + # QNetworkReply objects. + reply = PageReply(self, request.url(), self.GetOperation) + #print('here') + #print reply + return reply + elif operation == self.PostOperation: + reply = PageReply(self, request.url(), self.PostOperation, request, data) + return reply + elif scheme == 'res': + if operation == self.GetOperation: + return ResourceReply(self, request.url(), self.GetOperation) + else: + if self.USE_NETWORK: + return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data) + return NoNetworkReply(self, request.url(), self.GetOperation) + +class BasePageReply(QtNetwork.QNetworkReply): + content_type = 'text/html; charset=utf-8' + def __init__(self, parent, url, operation, request=None, data=None): + QtNetwork.QNetworkReply.__init__(self, parent) + self.data = data + self.request = request + self.content = self.initialize_content(url, operation) + self.offset = 0 + self.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, self.get_content_type()) + self.setHeader(QtNetwork.QNetworkRequest.ContentLengthHeader, len(self.content)) + QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('readyRead()')) + QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('finished()')) + self.open(self.ReadOnly | self.Unbuffered) + self.setUrl(url) + + def get_content_type(self): + return self.content_type + + def initialize_content(self, url, operation): + return ''' + + Empty Page + This is an empty page. If you see this, you need to subclass BasePageReply. + + ''' + + def abort(self): + pass + + def bytesAvailable(self): + return len(self.content) - self.offset + QtNetwork.QNetworkReply.bytesAvailable(self) + + def isSequential(self): + return True + + def readData(self, maxSize): + if self.offset < len(self.content): + end = min(self.offset + maxSize, len(self.content)) + data = self.content[self.offset:end] + self.offset = end + return data + +class ResourceReply(BasePageReply): + content_type = 'image/png' + + def determine_content_type(self, path): + return self.content_type + + def initialize_content(self, url, operation): + # determine folder: + path = str(url.path()).lstrip('/') + folders = getattr(self.parent(), 'folders') + if folders: + path = folders.matched_folder(path) + if path: + if os.path.exists(path): + try: + f = open(path, 'rb') + return f.read() + finally: + f.close() + else: + logging.warning('Path does not exist: %s' % path) + else: + logging.error('Containing Folder not found for %s' % path) + else: + logging.error('Configuration Error: No Folders found.') + return '' + +class PageReply(ResourceReply): + content_type = 'text/html' + + def initialize_content(self, url, operation): + try: + c = self.parent().parent().client + except: + logging.error('Internal HTTP Client not found. Creating new.') + c = Client() + logging.info( "Response for %s, method %s" % (url.path(), operation) ) + if operation == DejaNetworkAccessManager.GetOperation: + response = c.get(str(url.path()), follow=True ) + elif operation == DejaNetworkAccessManager.PostOperation: + ct = str(self.request.rawHeader('Content-Type')) + cl = str(self.request.rawHeader('Content-Length')) + s = str(self.data.readAll()) + if ct.startswith('multipart/form-data'): + # multipart parsing + logging.error('Multipart Parsing Try...') + b = BytesIO(s) + q, files = MultiPartParser({'CONTENT_TYPE': ct, + 'CONTENT_LENGTH': cl, + }, + b, + []).parse() + response = c.post(str(url.path()), q, follow=True) + else: + # assume post data. + q = QueryDict( s ) + response = c.post(str(url.path()), q, follow=True) + self.content_type = response.get('Content-Type', self.content_type) + # response code + #print "Response Status: %s" % response.status_code + # note: on a 404, we might need to trigger file response. + if response.status_code == 404: + return ResourceReply.initialize_content(self, url, DejaNetworkAccessManager.GetOperation) + if hasattr(response, 'streaming_content'): + return ''.join(response.streaming_content) + return response.content + +class NoNetworkReply(BasePageReply): + def initialize_content(self, url, operation): + return ''' + + No Network Access. + + Internal access to the network has been disabled. + + + ''' + diff --git a/src/scon/dj/scon/admin.py b/src/scon/dj/scon/admin.py index 1c94f0c..b5cac14 100644 --- a/src/scon/dj/scon/admin.py +++ b/src/scon/dj/scon/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -import models +from . import models # Register your models here. admin.site.register(models.Crafting) admin.site.register(models.CraftingInput) diff --git a/src/scon/dj/scon/generate_fixtures.py b/src/scon/dj/scon/generate_fixtures.py index 9b5714a..efa23f2 100644 --- a/src/scon/dj/scon/generate_fixtures.py +++ b/src/scon/dj/scon/generate_fixtures.py @@ -1,592 +1,592 @@ -""" - Generate Fixtures for Crafting. - Simple generator, does not integrate well into existing stuff, so please use - only for bootstrapping. -""" -import os, json -BASE_DIR = os.path.dirname(os.path.dirname(__file__)) -DIR = os.path.join(BASE_DIR, 'scon', 'fixtures') - -def write_fixture(data): - f = open(os.path.join(DIR, 'generated.json'), 'w') - f.write(json.dumps(data)) - f.close() - -def build_pk_cache(data, models=None): - pk_cache = {} - # fill cache from existing - for d in data: - if 'pk' in d.keys(): - # has pk - pk_cache[d['model']] = max(pk_cache.get('model', 0), d['pk']) - for d in data: - m = d['model'] - if models: - if m not in models: - continue - if 'pk' in d.keys(): - #print "PK was already in there! %s" % d - pass - else: - if m not in pk_cache.keys(): - pk_cache[m] = 1 - i = 1 - else: - i = pk_cache[m] + 1 - pk_cache[m] = i - d['pk'] = i - return data - -def lookup_pk(data, name, mdl='scon.item', kwargs=None): - for d in data: - if d['model'] == mdl: - if d['fields'].get('name', '').lower() == name.lower(): - found = True - if kwargs is not None: - for key, val in kwargs.items(): - if not d['fields'].get(key, None) == val: - found = False - if found: - return d['pk'] - -def generate_fixtures(): - data = [] - - ORES = [ - {'name': 'Impure tungsten', 'sell_price': 6600, 'icon': 'resource_tungsten_ore'}, - {'name': 'Impure osmium', 'sell_price': 4500, 'icon': 'resource_osmium_ore'}, - {'name': 'Impure silicon', 'sell_price': 600, 'icon': 'resource_silicon_ore'}, - {'name': 'Vanadium', 'sell_price': 500, 'icon': 'resource_vanadium'}, - {'name': 'Crystal shard', 'sell_price': 3500, 'icon': 'resource_crystal_shard'}, - ] - MATERIALS = [ - {'name': 'Tungsten plate', - 'description': 'Durable tungsten plate', - 'sell_price': 20000, - 'icon': 'component_tungsten_plate'}, - {'name': 'Screened battery', 'sell_price': 42000, - 'icon': 'component_screened_battery'}, - {'name': 'Osmium crystals', 'sell_price': 5500, - 'icon': 'component_osmium_crystals'}, - {'name': 'Pure Silicon', 'sell_price': 2500, - 'icon': 'component_pure_silicon'}, - {'name': 'Processing block', 'sell_price': 22000, - 'icon': 'component_processing_block'}, - {'name': 'Metal blank', 'sell_price': 1600, - 'icon': 'component_metal_blank'}, - {'name': 'Alien Monocrystal', 'sell_price': 25000, - 'icon': 'component_alien_monocrystal'}, - {'name': 'Computing chip', 'sell_price': 4500, - 'icon': 'component_computing_chip'}, - ] - AMMOS = [ - {'name': 'Explosive Shells', - 'quality': 4, - 'sell_price': 1000, - 'icon': 'ammo_explosive_shells_mk4', - }, - {'name': 'Double Deflector', - 'quality': 4, - 'sell_price': 1000, - 'icon': 'ammo_double_deflector_mk4', - }, - {'name': 'Xenon Lamp', - 'quality': 4, - 'sell_price': 1000, - 'icon': 'ammo_xenon_lamp_mk4', - }, - {'name': 'Attack Drone', - 'quality': 10, - 'sell_price': 1092, - 'icon': 'ammo_attack_drone', - }, - {'name': 'Focusing Lens', - 'quality': 4, - 'sell_price': 1000, - 'icon': 'ammo_focusing_lens', - }, - {'name': 'Iridium Slugs', - 'quality': 4, - 'sell_price': 1000, - 'icon': 'ammo_iridium_slugs', - }, - {'name': 'Supercooled Charges', - 'quality': 4, - 'sell_price': 1000, - 'icon': 'ammo_supercooled_charges', - }, - {'name': 'Doomsday Missile', - 'quality': 1, - 'sell_price': 1000, - #'tech': 5, - 'icon': 'ammo_doomsday_missile', - } - ] - - ITEMS_NON_CRAFTING = [ - {'name': 'Target Tracking Coprocessor III', - 'typ': 5, # cpu - 'tech': 3, - 'sell_price': 20188, - 'description': 'Increases Critical Damage', - 'icon': 'cpu_target_tracking_coprocessor', - }, - {'name': 'Plasma Gun III', - 'typ': 7, # weap - 'quality': 4, - 'tech': 3, - 'icon': 'weapon_plasma_gun', - }, - {'name': 'Plasma Gun IV', - 'typ': 7, # weap - 'quality': 4, - 'tech': 4, - 'icon': 'weapon_plasma_gun', - }, - {'name': 'Plasma Gun V', - 'typ': 7, # weap - 'quality': 4, - 'tech': 5, - 'icon': 'weapon_plasma_gun', - }, - # assault rails: - {'name': 'Assault Railgun III', - 'typ': 7, # weap - 'quality': 4, - 'tech': 3, - 'icon': 'weapon_assault_railgun', - }, - {'name': 'Assault Railgun IV', - 'typ': 7, # weap - 'quality': 4, - 'tech': 4, - 'icon': 'weapon_assault_railgun', - }, - {'name': 'Assault Railgun V', - 'typ': 7, # weap - 'quality': 4, - 'tech': 5, - 'icon': 'weapon_assault_railgun', - }, - # beam cannon: - {'name': 'Beam Cannon III', - 'typ': 7, # weap - 'quality': 4, - 'tech': 3, - 'icon': 'weapon_beam_cannon', - }, - {'name': 'Beam Cannon IV', - 'typ': 7, # weap - 'quality': 4, - 'tech': 4, - 'icon': 'weapon_beam_cannon', - }, - {'name': 'Beam Cannon V', - 'typ': 7, # weap - 'quality': 4, - 'tech': 5, - 'icon': 'weapon_beam_cannon', - }, - ] - - ITEMS = [ - {'name': 'Duplicator', - 'typ': 0, - 'sell_price': 8000, - 'buy_price_premium': 200, - 'description': 'Revives in Invasion with Cargo once.', - 'icon': 'duplicator', - }, - {'name': 'A1MA IV', - 'quality': 1, - 'tech': 4, - 'typ': 8, # active. - 'role': 0, # multipurp. - 'sell_price': 26910, - 'icon': 'active_a1ma', - }, - {'name': 'Pirate "Orion" Targeting Complex V', - 'quality': 14, - 'tech': 5, - 'typ': 8, # active. - 'role': 3, # covops - 'icon': 'active_t5_orion_targeting_complex_pirate', - }, - {'name': 'Pirate Engine Overcharge V', - 'quality': 14, - 'tech': 5, - 'typ': 8, # active. - 'role': 6, # gunship - 'icon': 'active_t5_engine_overcharge_pirate', - }, - {'name': 'Pirate Mass Shield Generator V', - 'quality': 14, - 'tech': 5, - 'typ': 8, # active. - 'role': 7, # engi - 'icon': 'active_t5_mass_shield_generator_pirate', - }, - {'name': 'Reverse Thruster III', - 'quality': 1, - 'tech': 3, - 'typ': 8, # active. - 'role': 9, # LRF - 'icon': 'active_reverse_thruster', - }, - {'name': 'Reverse Thruster IV', - 'quality': 1, - 'tech': 4, - 'typ': 8, # active. - 'role': 9, # LRF - 'icon': 'active_reverse_thruster', - }, - {'name': 'Reverse Thruster V', - 'quality': 1, - 'tech': 5, - 'typ': 8, # active. - 'role': 9, # LRF - 'icon': 'active_reverse_thruster', - }, - {'name': 'Plasma Gun III', - 'quality': 5, - 'tech': 3, - 'typ': 7, # weap - 'icon': 'weapon_plasma_gun_mk5', - }, - {'name': 'Plasma Gun IV', - 'quality': 5, - 'tech': 4, - 'typ': 7, # weap - 'icon': 'weapon_plasma_gun_mk5', - }, - {'name': 'Plasma Gun V', - 'quality': 5, - 'tech': 5, - 'typ': 7, # weap - 'icon': 'weapon_plasma_gun_mk5', - }, - {'name': 'Assault Railgun III', - 'quality': 5, - 'tech': 3, - 'typ': 7, # weap - 'icon': 'weapon_assault_rail_mk5', - }, - {'name': 'Assault Railgun IV', - 'quality': 5, - 'tech': 4, - 'typ': 7, # weap - 'icon': 'weapon_assault_rail_mk5', - }, - {'name': 'Assault Railgun V', - 'quality': 5, - 'tech': 5, - 'typ': 7, # weap - 'icon': 'weapon_assault_rail_mk5', - }, - {'name': 'Beam Cannon III', - 'quality': 5, - 'tech': 3, - 'typ': 7, # weap - 'icon': 'weapon_beam_cannon_mk5', - }, - {'name': 'Beam Cannon IV', - 'quality': 5, - 'tech': 4, - 'typ': 7, # weap - 'icon': 'weapon_beam_cannon_mk5', - }, - {'name': 'Beam Cannon V', - 'quality': 5, - 'tech': 5, - 'typ': 7, # weap - 'icon': 'weapon_beam_cannon_mk5', - }, - ] - BLUEPRINTS = [ - {'name': 'Focusing Lens Blueprint'}, - {'name': 'Iridium Slugs Blueprint'}, - {'name': 'Supercooled Charges Blueprint'}, - {'name': 'A1MA T4 Blueprint'}, - {'name': 'Orion-2 Targeting Complex Blueprint', - 'description': 'Module works twice as long but much weaker.'}, - {'name': 'Engine Warp Overcharge Blueprint'}, - {'name': 'Mass Shield Energizer Blueprint'}, - {'name': 'Reverse Thruster T3 Blueprint'}, - {'name': 'Reverse Thruster T4 Blueprint'}, - {'name': 'Reverse Thruster T5 Blueprint'}, - {'name': 'Beam Cannon Prototype T3 Blueprint'}, - {'name': 'Beam Cannon Prototype T4 Blueprint'}, - {'name': 'Beam Cannon Prototype T5 Blueprint'}, - {'name': 'Assault Railgun Prototype T3 Blueprint'}, - {'name': 'Assault Railgun Prototype T4 Blueprint'}, - {'name': 'Assault Railgun Prototype T5 Blueprint'}, - {'name': 'Plasma Gun Prototype T3 Blueprint'}, - {'name': 'Plasma Gun Prototype T4 Blueprint'}, - {'name': 'Plasma Gun Prototype T5 Blueprint'}, - {'name': 'Doomsday Missile Blueprint'}, - ] - CRAFTING = [ - {'item': 'Duplicator', - 'recipee': [(1, 'Processing Block'), (2,'Computing chip'), (2, 'Metal blank')]}, - {'item': 'Tungsten plate', - 'recipee': [(2, 'Impure tungsten'),]}, - {'item': 'Screened battery', - 'recipee': [(1, 'Tungsten plate'), (2, 'Computing chip')]}, - {'item': 'Osmium crystals', - 'recipee': [(1, 'Impure osmium'),]}, - {'item': 'Pure Silicon', - 'recipee': [(1, 'Impure silicon'),]}, - {'item': 'Computing chip', - 'recipee': [(1, 'Crystal shard'),]}, - {'item': 'Processing block', - 'recipee': [(4, 'Pure Silicon'), (2, 'Computing chip')]}, - {'item': 'Metal blank', - 'recipee': [(2, 'Vanadium'),]}, - {'item': 'Target Tracking Coprocessor III', - 'recipee': [(1, 'Screened battery'), (7, 'Metal blank'), (5, 'Pure silicon')]}, - {'item': 'Explosive Shells', - 'amount': 2, - 'recipee': [(1, 'Metal blank'),(2, 'Pure Silicon')]}, - {'item': 'Attack drone', - 'recipee': [(1, 'Alien Monocrystal'), (1,'Computing chip')]}, - {'item': 'Double Deflector', - 'amount': 2, - 'recipee': [(1, 'Osmium crystals'),]}, - {'item': 'Xenon Lamp', - 'amount': 2, - 'recipee': [(1, 'Computing chip'), (1, 'Pure Silicon')]}, - - {'item': 'Focusing Lens', - 'amount': 2, - 'recipee': [(1, 'Focusing Lens Blueprint'), (1, 'Osmium crystals')]}, - {'item': 'Supercooled Charges', - 'amount': 2, - 'recipee': [(1, 'Supercooled Charges Blueprint'), (1, 'Computing chip')]}, - {'item': 'Iridium Slugs', - 'amount': 2, - 'recipee': [(1, 'Iridium Slugs Blueprint'), (1, 'Metal blank')]}, - {'item': 'A1MA IV', - 'recipee': [(1, 'A1MA T4 Blueprint'), (2, 'Processing block'), - (14, 'Metal blank'), (2, 'Screened battery'), - (20, 'Alien Monocrystal')]}, - {'item': 'Pirate "Orion" Targeting Complex V', - 'recipee': [(1, 'Orion-2 Targeting Complex Blueprint'), - (3, 'Tungsten plate'), - (4, 'Computing chip'), - (2, 'Processing block'), - (30, 'Alien Monocrystal') - ]}, - {'item': 'Pirate Engine Overcharge V', - 'recipee': [(1, 'Engine Warp Overcharge Blueprint'), - (3, 'Tungsten plate'), - (2, 'Osmium crystals'), - (2, 'Processing block'), - (30, 'Alien Monocrystal') - ]}, - {'item': 'Pirate Mass Shield Generator V', - 'recipee': [(1, 'Mass Shield Energizer Blueprint'), - (10, 'Metal blank'), - (3, 'Computing chip'), - (3, 'Processing block'), - (30, 'Alien Monocrystal') - ]}, - # lrf reverse blink: - {'item': 'Reverse Thruster III', - 'recipee': [(1, 'Reverse Thruster T3 Blueprint'), - (7, 'Metal blank'), - (1, 'Screened battery'), - (4, 'Computing chip'), - (15, 'Alien Monocrystal') - ]}, - {'item': 'Reverse Thruster IV', - 'recipee': [(1, 'Reverse Thruster T4 Blueprint'), - (12, 'Metal blank'), - (2, 'Screened battery'), - (5, 'Computing chip'), - (20, 'Alien Monocrystal') - ]}, - {'item': 'Reverse Thruster V', - 'recipee': [(1, 'Reverse Thruster T5 Blueprint'), - (7, 'Tungsten plate'), - (3, 'Screened battery'), - (6, 'Computing chip'), - (30, 'Alien Monocrystal') - ]}, - - # plasma - {'item': ('Plasma Gun III', {'quality': 5}), - 'recipee': [(1, 'Plasma Gun Prototype T3 Blueprint'), - (1, ('Plasma Gun III', {'quality': 4})), - (6, 'Metal blank'), - (3, 'Screened battery'), - (30, 'Alien Monocrystal') - ]}, - {'item': ('Plasma Gun IV', {'quality': 5}), - 'recipee': [(1, 'Plasma Gun Prototype T4 Blueprint'), - (1, ('Plasma Gun IV', {'quality': 4})), - (1, 'Tungsten plate'), - (4, 'Screened battery'), - (50, 'Alien Monocrystal') - ]}, - {'item': ('Plasma Gun V', {'quality': 5}), - 'recipee': [(1, 'Plasma Gun Prototype T5 Blueprint'), - (1, ('Plasma Gun V', {'quality': 4})), - (3, 'Tungsten plate'), - (5, 'Screened battery'), - (70, 'Alien Monocrystal') - ]}, - # assault - {'item': ('Assault Railgun III', {'quality': 5}), - 'recipee': [(1, 'Assault Railgun Prototype T3 Blueprint'), - (1, ('Assault Railgun III', {'quality': 4})), - (6, 'Metal blank'), - (3, 'Screened battery'), - (30, 'Alien Monocrystal') - ]}, - {'item': ('Assault Railgun IV', {'quality': 5}), - 'recipee': [(1, 'Assault Railgun Prototype T4 Blueprint'), - (1, ('Assault Railgun IV', {'quality': 4})), - (1, 'Tungsten plate'), - (4, 'Screened battery'), - (50, 'Alien Monocrystal') - ]}, - {'item': ('Assault Railgun V', {'quality': 5}), - 'recipee': [(1, 'Assault Railgun Prototype T5 Blueprint'), - (1, ('Assault Railgun V', {'quality': 4})), - (3, 'Tungsten plate'), - (5, 'Screened battery'), - (70, 'Alien Monocrystal') - ]}, - # beam - {'item': ('Beam Cannon III', {'quality': 5}), - 'recipee': [(1, 'Beam Cannon Prototype T3 Blueprint'), - (1, ('Beam Cannon III', {'quality': 4})), - (6, 'Metal blank'), - (3, 'Screened battery'), - (30, 'Alien Monocrystal') - ]}, - {'item': ('Beam Cannon IV', {'quality': 5}), - 'recipee': [(1, 'Beam Cannon Prototype T4 Blueprint'), - (1, ('Beam Cannon IV', {'quality': 4})), - (1, 'Tungsten plate'), - (4, 'Screened battery'), - (50, 'Alien Monocrystal') - ]}, - {'item': ('Beam Cannon V', {'quality': 5}), - 'recipee': [(1, 'Beam Cannon Prototype T5 Blueprint'), - (1, ('Beam Cannon V', {'quality': 4})), - (3, 'Tungsten plate'), - (5, 'Screened battery'), - (70, 'Alien Monocrystal') - ]}, - # missiles - {'item': 'Doomsday Missile', - 'recipee': [(1, 'Doomsday Missile Blueprint'), - (2, 'Osmium crystals'), - (1, 'Computing chip'), - (1, 'Metal blank'), - ]}, - ] - - for ore in ORES: - fields = {'typ': 12, - 'tech': 0, - 'craftable': True, - } - fields.update(ore) - data.append({'model': 'scon.item', 'fields': fields}) - for mat in MATERIALS: - fields = {'typ': 13, - 'tech': 0, - 'craftable': True, - } - fields.update(mat) - data.append({'model': 'scon.item', 'fields': fields}) - - for ammo in AMMOS: - fields = {'typ': 8, - 'tech': 0, - 'craftable': True, - } - fields.update(ammo) - data.append({'model': 'scon.item', 'fields': fields}) - - for item in ITEMS: - fields = { - # items define typ and tech! - 'craftable': True, - } - fields.update(item) - data.append({'model': 'scon.item', 'fields': fields}) - - for item in ITEMS_NON_CRAFTING: - fields = { - # items define typ and tech! - 'craftable': False, - } - fields.update(item) - data.append({'model': 'scon.item', 'fields': fields}) - - for bluep in BLUEPRINTS: - fields = { - 'typ': 11, # blueprint - 'tech': 0, - 'craftable': True, - 'icon': 'blueprint', - } - fields.update(bluep) - data.append({'model': 'scon.item', 'fields': fields}) - - - build_pk_cache(data) - # now to the crafting recipees: - i = 1 # counter for crafting - j = 1 # counter for input - for craft in CRAFTING: - try: - item = craft['item'] - kwargs = None - if isinstance(item, tuple) or isinstance(item, list): - kwargs = item[1] - item = item[0] - crafting = {'model': 'scon.crafting', - 'pk': i, - 'fields': { 'output': lookup_pk(data, item, kwargs=kwargs ), - 'amount': craft.get('amount', 1) }} - data.append(crafting) - primary = True - for amount, recipee in craft['recipee']: - item = recipee - kwargs = None - if isinstance(item, tuple) or isinstance(item, list): - print item - kwargs = item[1] - item = item[0] - - crafting_input = {'model': 'scon.craftinginput', - 'pk': j, - 'fields': {'crafting': i, - 'item': lookup_pk(data, item, kwargs=kwargs), - 'amount': amount, - 'primary': primary} - } - primary = False - j = j + 1 - data.append(crafting_input) - i = i + 1 - except: - raise - - build_pk_cache(data) - return data - -if __name__ == "__main__": - fixes = generate_fixtures() - from pprint import pprint - pprint( fixes ) - # check pks: - for d in fixes: - if d.get('pk', None) is None: - print "%s is fail." % d - +""" + Generate Fixtures for Crafting. + Simple generator, does not integrate well into existing stuff, so please use + only for bootstrapping. +""" +import os, json +BASE_DIR = os.path.dirname(os.path.dirname(__file__)) +DIR = os.path.join(BASE_DIR, 'scon', 'fixtures') + +def write_fixture(data): + f = open(os.path.join(DIR, 'generated.json'), 'w') + f.write(json.dumps(data)) + f.close() + +def build_pk_cache(data, models=None): + pk_cache = {} + # fill cache from existing + for d in data: + if 'pk' in list(d.keys()): + # has pk + pk_cache[d['model']] = max(pk_cache.get('model', 0), d['pk']) + for d in data: + m = d['model'] + if models: + if m not in models: + continue + if 'pk' in list(d.keys()): + #print "PK was already in there! %s" % d + pass + else: + if m not in list(pk_cache.keys()): + pk_cache[m] = 1 + i = 1 + else: + i = pk_cache[m] + 1 + pk_cache[m] = i + d['pk'] = i + return data + +def lookup_pk(data, name, mdl='scon.item', kwargs=None): + for d in data: + if d['model'] == mdl: + if d['fields'].get('name', '').lower() == name.lower(): + found = True + if kwargs is not None: + for key, val in list(kwargs.items()): + if not d['fields'].get(key, None) == val: + found = False + if found: + return d['pk'] + +def generate_fixtures(): + data = [] + + ORES = [ + {'name': 'Impure tungsten', 'sell_price': 6600, 'icon': 'resource_tungsten_ore'}, + {'name': 'Impure osmium', 'sell_price': 4500, 'icon': 'resource_osmium_ore'}, + {'name': 'Impure silicon', 'sell_price': 600, 'icon': 'resource_silicon_ore'}, + {'name': 'Vanadium', 'sell_price': 500, 'icon': 'resource_vanadium'}, + {'name': 'Crystal shard', 'sell_price': 3500, 'icon': 'resource_crystal_shard'}, + ] + MATERIALS = [ + {'name': 'Tungsten plate', + 'description': 'Durable tungsten plate', + 'sell_price': 20000, + 'icon': 'component_tungsten_plate'}, + {'name': 'Screened battery', 'sell_price': 42000, + 'icon': 'component_screened_battery'}, + {'name': 'Osmium crystals', 'sell_price': 5500, + 'icon': 'component_osmium_crystals'}, + {'name': 'Pure Silicon', 'sell_price': 2500, + 'icon': 'component_pure_silicon'}, + {'name': 'Processing block', 'sell_price': 22000, + 'icon': 'component_processing_block'}, + {'name': 'Metal blank', 'sell_price': 1600, + 'icon': 'component_metal_blank'}, + {'name': 'Alien Monocrystal', 'sell_price': 25000, + 'icon': 'component_alien_monocrystal'}, + {'name': 'Computing chip', 'sell_price': 4500, + 'icon': 'component_computing_chip'}, + ] + AMMOS = [ + {'name': 'Explosive Shells', + 'quality': 4, + 'sell_price': 1000, + 'icon': 'ammo_explosive_shells_mk4', + }, + {'name': 'Double Deflector', + 'quality': 4, + 'sell_price': 1000, + 'icon': 'ammo_double_deflector_mk4', + }, + {'name': 'Xenon Lamp', + 'quality': 4, + 'sell_price': 1000, + 'icon': 'ammo_xenon_lamp_mk4', + }, + {'name': 'Attack Drone', + 'quality': 10, + 'sell_price': 1092, + 'icon': 'ammo_attack_drone', + }, + {'name': 'Focusing Lens', + 'quality': 4, + 'sell_price': 1000, + 'icon': 'ammo_focusing_lens', + }, + {'name': 'Iridium Slugs', + 'quality': 4, + 'sell_price': 1000, + 'icon': 'ammo_iridium_slugs', + }, + {'name': 'Supercooled Charges', + 'quality': 4, + 'sell_price': 1000, + 'icon': 'ammo_supercooled_charges', + }, + {'name': 'Doomsday Missile', + 'quality': 1, + 'sell_price': 1000, + #'tech': 5, + 'icon': 'ammo_doomsday_missile', + } + ] + + ITEMS_NON_CRAFTING = [ + {'name': 'Target Tracking Coprocessor III', + 'typ': 5, # cpu + 'tech': 3, + 'sell_price': 20188, + 'description': 'Increases Critical Damage', + 'icon': 'cpu_target_tracking_coprocessor', + }, + {'name': 'Plasma Gun III', + 'typ': 7, # weap + 'quality': 4, + 'tech': 3, + 'icon': 'weapon_plasma_gun', + }, + {'name': 'Plasma Gun IV', + 'typ': 7, # weap + 'quality': 4, + 'tech': 4, + 'icon': 'weapon_plasma_gun', + }, + {'name': 'Plasma Gun V', + 'typ': 7, # weap + 'quality': 4, + 'tech': 5, + 'icon': 'weapon_plasma_gun', + }, + # assault rails: + {'name': 'Assault Railgun III', + 'typ': 7, # weap + 'quality': 4, + 'tech': 3, + 'icon': 'weapon_assault_railgun', + }, + {'name': 'Assault Railgun IV', + 'typ': 7, # weap + 'quality': 4, + 'tech': 4, + 'icon': 'weapon_assault_railgun', + }, + {'name': 'Assault Railgun V', + 'typ': 7, # weap + 'quality': 4, + 'tech': 5, + 'icon': 'weapon_assault_railgun', + }, + # beam cannon: + {'name': 'Beam Cannon III', + 'typ': 7, # weap + 'quality': 4, + 'tech': 3, + 'icon': 'weapon_beam_cannon', + }, + {'name': 'Beam Cannon IV', + 'typ': 7, # weap + 'quality': 4, + 'tech': 4, + 'icon': 'weapon_beam_cannon', + }, + {'name': 'Beam Cannon V', + 'typ': 7, # weap + 'quality': 4, + 'tech': 5, + 'icon': 'weapon_beam_cannon', + }, + ] + + ITEMS = [ + {'name': 'Duplicator', + 'typ': 0, + 'sell_price': 8000, + 'buy_price_premium': 200, + 'description': 'Revives in Invasion with Cargo once.', + 'icon': 'duplicator', + }, + {'name': 'A1MA IV', + 'quality': 1, + 'tech': 4, + 'typ': 8, # active. + 'role': 0, # multipurp. + 'sell_price': 26910, + 'icon': 'active_a1ma', + }, + {'name': 'Pirate "Orion" Targeting Complex V', + 'quality': 14, + 'tech': 5, + 'typ': 8, # active. + 'role': 3, # covops + 'icon': 'active_t5_orion_targeting_complex_pirate', + }, + {'name': 'Pirate Engine Overcharge V', + 'quality': 14, + 'tech': 5, + 'typ': 8, # active. + 'role': 6, # gunship + 'icon': 'active_t5_engine_overcharge_pirate', + }, + {'name': 'Pirate Mass Shield Generator V', + 'quality': 14, + 'tech': 5, + 'typ': 8, # active. + 'role': 7, # engi + 'icon': 'active_t5_mass_shield_generator_pirate', + }, + {'name': 'Reverse Thruster III', + 'quality': 1, + 'tech': 3, + 'typ': 8, # active. + 'role': 9, # LRF + 'icon': 'active_reverse_thruster', + }, + {'name': 'Reverse Thruster IV', + 'quality': 1, + 'tech': 4, + 'typ': 8, # active. + 'role': 9, # LRF + 'icon': 'active_reverse_thruster', + }, + {'name': 'Reverse Thruster V', + 'quality': 1, + 'tech': 5, + 'typ': 8, # active. + 'role': 9, # LRF + 'icon': 'active_reverse_thruster', + }, + {'name': 'Plasma Gun III', + 'quality': 5, + 'tech': 3, + 'typ': 7, # weap + 'icon': 'weapon_plasma_gun_mk5', + }, + {'name': 'Plasma Gun IV', + 'quality': 5, + 'tech': 4, + 'typ': 7, # weap + 'icon': 'weapon_plasma_gun_mk5', + }, + {'name': 'Plasma Gun V', + 'quality': 5, + 'tech': 5, + 'typ': 7, # weap + 'icon': 'weapon_plasma_gun_mk5', + }, + {'name': 'Assault Railgun III', + 'quality': 5, + 'tech': 3, + 'typ': 7, # weap + 'icon': 'weapon_assault_rail_mk5', + }, + {'name': 'Assault Railgun IV', + 'quality': 5, + 'tech': 4, + 'typ': 7, # weap + 'icon': 'weapon_assault_rail_mk5', + }, + {'name': 'Assault Railgun V', + 'quality': 5, + 'tech': 5, + 'typ': 7, # weap + 'icon': 'weapon_assault_rail_mk5', + }, + {'name': 'Beam Cannon III', + 'quality': 5, + 'tech': 3, + 'typ': 7, # weap + 'icon': 'weapon_beam_cannon_mk5', + }, + {'name': 'Beam Cannon IV', + 'quality': 5, + 'tech': 4, + 'typ': 7, # weap + 'icon': 'weapon_beam_cannon_mk5', + }, + {'name': 'Beam Cannon V', + 'quality': 5, + 'tech': 5, + 'typ': 7, # weap + 'icon': 'weapon_beam_cannon_mk5', + }, + ] + BLUEPRINTS = [ + {'name': 'Focusing Lens Blueprint'}, + {'name': 'Iridium Slugs Blueprint'}, + {'name': 'Supercooled Charges Blueprint'}, + {'name': 'A1MA T4 Blueprint'}, + {'name': 'Orion-2 Targeting Complex Blueprint', + 'description': 'Module works twice as long but much weaker.'}, + {'name': 'Engine Warp Overcharge Blueprint'}, + {'name': 'Mass Shield Energizer Blueprint'}, + {'name': 'Reverse Thruster T3 Blueprint'}, + {'name': 'Reverse Thruster T4 Blueprint'}, + {'name': 'Reverse Thruster T5 Blueprint'}, + {'name': 'Beam Cannon Prototype T3 Blueprint'}, + {'name': 'Beam Cannon Prototype T4 Blueprint'}, + {'name': 'Beam Cannon Prototype T5 Blueprint'}, + {'name': 'Assault Railgun Prototype T3 Blueprint'}, + {'name': 'Assault Railgun Prototype T4 Blueprint'}, + {'name': 'Assault Railgun Prototype T5 Blueprint'}, + {'name': 'Plasma Gun Prototype T3 Blueprint'}, + {'name': 'Plasma Gun Prototype T4 Blueprint'}, + {'name': 'Plasma Gun Prototype T5 Blueprint'}, + {'name': 'Doomsday Missile Blueprint'}, + ] + CRAFTING = [ + {'item': 'Duplicator', + 'recipee': [(1, 'Processing Block'), (2,'Computing chip'), (2, 'Metal blank')]}, + {'item': 'Tungsten plate', + 'recipee': [(2, 'Impure tungsten'),]}, + {'item': 'Screened battery', + 'recipee': [(1, 'Tungsten plate'), (2, 'Computing chip')]}, + {'item': 'Osmium crystals', + 'recipee': [(1, 'Impure osmium'),]}, + {'item': 'Pure Silicon', + 'recipee': [(1, 'Impure silicon'),]}, + {'item': 'Computing chip', + 'recipee': [(1, 'Crystal shard'),]}, + {'item': 'Processing block', + 'recipee': [(4, 'Pure Silicon'), (2, 'Computing chip')]}, + {'item': 'Metal blank', + 'recipee': [(2, 'Vanadium'),]}, + {'item': 'Target Tracking Coprocessor III', + 'recipee': [(1, 'Screened battery'), (7, 'Metal blank'), (5, 'Pure silicon')]}, + {'item': 'Explosive Shells', + 'amount': 2, + 'recipee': [(1, 'Metal blank'),(2, 'Pure Silicon')]}, + {'item': 'Attack drone', + 'recipee': [(1, 'Alien Monocrystal'), (1,'Computing chip')]}, + {'item': 'Double Deflector', + 'amount': 2, + 'recipee': [(1, 'Osmium crystals'),]}, + {'item': 'Xenon Lamp', + 'amount': 2, + 'recipee': [(1, 'Computing chip'), (1, 'Pure Silicon')]}, + + {'item': 'Focusing Lens', + 'amount': 2, + 'recipee': [(1, 'Focusing Lens Blueprint'), (1, 'Osmium crystals')]}, + {'item': 'Supercooled Charges', + 'amount': 2, + 'recipee': [(1, 'Supercooled Charges Blueprint'), (1, 'Computing chip')]}, + {'item': 'Iridium Slugs', + 'amount': 2, + 'recipee': [(1, 'Iridium Slugs Blueprint'), (1, 'Metal blank')]}, + {'item': 'A1MA IV', + 'recipee': [(1, 'A1MA T4 Blueprint'), (2, 'Processing block'), + (14, 'Metal blank'), (2, 'Screened battery'), + (20, 'Alien Monocrystal')]}, + {'item': 'Pirate "Orion" Targeting Complex V', + 'recipee': [(1, 'Orion-2 Targeting Complex Blueprint'), + (3, 'Tungsten plate'), + (4, 'Computing chip'), + (2, 'Processing block'), + (30, 'Alien Monocrystal') + ]}, + {'item': 'Pirate Engine Overcharge V', + 'recipee': [(1, 'Engine Warp Overcharge Blueprint'), + (3, 'Tungsten plate'), + (2, 'Osmium crystals'), + (2, 'Processing block'), + (30, 'Alien Monocrystal') + ]}, + {'item': 'Pirate Mass Shield Generator V', + 'recipee': [(1, 'Mass Shield Energizer Blueprint'), + (10, 'Metal blank'), + (3, 'Computing chip'), + (3, 'Processing block'), + (30, 'Alien Monocrystal') + ]}, + # lrf reverse blink: + {'item': 'Reverse Thruster III', + 'recipee': [(1, 'Reverse Thruster T3 Blueprint'), + (7, 'Metal blank'), + (1, 'Screened battery'), + (4, 'Computing chip'), + (15, 'Alien Monocrystal') + ]}, + {'item': 'Reverse Thruster IV', + 'recipee': [(1, 'Reverse Thruster T4 Blueprint'), + (12, 'Metal blank'), + (2, 'Screened battery'), + (5, 'Computing chip'), + (20, 'Alien Monocrystal') + ]}, + {'item': 'Reverse Thruster V', + 'recipee': [(1, 'Reverse Thruster T5 Blueprint'), + (7, 'Tungsten plate'), + (3, 'Screened battery'), + (6, 'Computing chip'), + (30, 'Alien Monocrystal') + ]}, + + # plasma + {'item': ('Plasma Gun III', {'quality': 5}), + 'recipee': [(1, 'Plasma Gun Prototype T3 Blueprint'), + (1, ('Plasma Gun III', {'quality': 4})), + (6, 'Metal blank'), + (3, 'Screened battery'), + (30, 'Alien Monocrystal') + ]}, + {'item': ('Plasma Gun IV', {'quality': 5}), + 'recipee': [(1, 'Plasma Gun Prototype T4 Blueprint'), + (1, ('Plasma Gun IV', {'quality': 4})), + (1, 'Tungsten plate'), + (4, 'Screened battery'), + (50, 'Alien Monocrystal') + ]}, + {'item': ('Plasma Gun V', {'quality': 5}), + 'recipee': [(1, 'Plasma Gun Prototype T5 Blueprint'), + (1, ('Plasma Gun V', {'quality': 4})), + (3, 'Tungsten plate'), + (5, 'Screened battery'), + (70, 'Alien Monocrystal') + ]}, + # assault + {'item': ('Assault Railgun III', {'quality': 5}), + 'recipee': [(1, 'Assault Railgun Prototype T3 Blueprint'), + (1, ('Assault Railgun III', {'quality': 4})), + (6, 'Metal blank'), + (3, 'Screened battery'), + (30, 'Alien Monocrystal') + ]}, + {'item': ('Assault Railgun IV', {'quality': 5}), + 'recipee': [(1, 'Assault Railgun Prototype T4 Blueprint'), + (1, ('Assault Railgun IV', {'quality': 4})), + (1, 'Tungsten plate'), + (4, 'Screened battery'), + (50, 'Alien Monocrystal') + ]}, + {'item': ('Assault Railgun V', {'quality': 5}), + 'recipee': [(1, 'Assault Railgun Prototype T5 Blueprint'), + (1, ('Assault Railgun V', {'quality': 4})), + (3, 'Tungsten plate'), + (5, 'Screened battery'), + (70, 'Alien Monocrystal') + ]}, + # beam + {'item': ('Beam Cannon III', {'quality': 5}), + 'recipee': [(1, 'Beam Cannon Prototype T3 Blueprint'), + (1, ('Beam Cannon III', {'quality': 4})), + (6, 'Metal blank'), + (3, 'Screened battery'), + (30, 'Alien Monocrystal') + ]}, + {'item': ('Beam Cannon IV', {'quality': 5}), + 'recipee': [(1, 'Beam Cannon Prototype T4 Blueprint'), + (1, ('Beam Cannon IV', {'quality': 4})), + (1, 'Tungsten plate'), + (4, 'Screened battery'), + (50, 'Alien Monocrystal') + ]}, + {'item': ('Beam Cannon V', {'quality': 5}), + 'recipee': [(1, 'Beam Cannon Prototype T5 Blueprint'), + (1, ('Beam Cannon V', {'quality': 4})), + (3, 'Tungsten plate'), + (5, 'Screened battery'), + (70, 'Alien Monocrystal') + ]}, + # missiles + {'item': 'Doomsday Missile', + 'recipee': [(1, 'Doomsday Missile Blueprint'), + (2, 'Osmium crystals'), + (1, 'Computing chip'), + (1, 'Metal blank'), + ]}, + ] + + for ore in ORES: + fields = {'typ': 12, + 'tech': 0, + 'craftable': True, + } + fields.update(ore) + data.append({'model': 'scon.item', 'fields': fields}) + for mat in MATERIALS: + fields = {'typ': 13, + 'tech': 0, + 'craftable': True, + } + fields.update(mat) + data.append({'model': 'scon.item', 'fields': fields}) + + for ammo in AMMOS: + fields = {'typ': 8, + 'tech': 0, + 'craftable': True, + } + fields.update(ammo) + data.append({'model': 'scon.item', 'fields': fields}) + + for item in ITEMS: + fields = { + # items define typ and tech! + 'craftable': True, + } + fields.update(item) + data.append({'model': 'scon.item', 'fields': fields}) + + for item in ITEMS_NON_CRAFTING: + fields = { + # items define typ and tech! + 'craftable': False, + } + fields.update(item) + data.append({'model': 'scon.item', 'fields': fields}) + + for bluep in BLUEPRINTS: + fields = { + 'typ': 11, # blueprint + 'tech': 0, + 'craftable': True, + 'icon': 'blueprint', + } + fields.update(bluep) + data.append({'model': 'scon.item', 'fields': fields}) + + + build_pk_cache(data) + # now to the crafting recipees: + i = 1 # counter for crafting + j = 1 # counter for input + for craft in CRAFTING: + try: + item = craft['item'] + kwargs = None + if isinstance(item, tuple) or isinstance(item, list): + kwargs = item[1] + item = item[0] + crafting = {'model': 'scon.crafting', + 'pk': i, + 'fields': { 'output': lookup_pk(data, item, kwargs=kwargs ), + 'amount': craft.get('amount', 1) }} + data.append(crafting) + primary = True + for amount, recipee in craft['recipee']: + item = recipee + kwargs = None + if isinstance(item, tuple) or isinstance(item, list): + print(item) + kwargs = item[1] + item = item[0] + + crafting_input = {'model': 'scon.craftinginput', + 'pk': j, + 'fields': {'crafting': i, + 'item': lookup_pk(data, item, kwargs=kwargs), + 'amount': amount, + 'primary': primary} + } + primary = False + j = j + 1 + data.append(crafting_input) + i = i + 1 + except: + raise + + build_pk_cache(data) + return data + +if __name__ == "__main__": + fixes = generate_fixtures() + from pprint import pprint + pprint( fixes ) + # check pks: + for d in fixes: + if d.get('pk', None) is None: + print(("%s is fail." % d)) + write_fixture(fixes) \ No newline at end of file diff --git a/src/scon/dj/scon/views.py b/src/scon/dj/scon/views.py index 022d568..f7bc75c 100644 --- a/src/scon/dj/scon/views.py +++ b/src/scon/dj/scon/views.py @@ -2,8 +2,8 @@ from django.shortcuts import render from django.http import HttpResponse from django.template import RequestContext, loader -import logic -import models +from . import logic +from . import models def config(request): t = loader.get_template('scon/config.html') diff --git a/src/scon/game/pieces.py b/src/scon/game/pieces.py index 22d47b9..880f4bb 100644 --- a/src/scon/game/pieces.py +++ b/src/scon/game/pieces.py @@ -1,100 +1,100 @@ - -def res_to_red(res): - ''' calculates reduction % of damage from base resistance - incoming damage is assumed to be 100.0 to get percentages. - ''' - if res >= 0: - fd = 100 / (1.0+res/100.0) - else: - fd = 100 / (1.0-res/100.0) - return 100.0 - fd - -def dam_res(dam, res): - ''' calculates damage modified by resistance. - ''' - if res >= 0: - fd = dam / (1.0+res/100.0) - else: - fd = dam / (1.0-res/100.0) - return fd - -class ShipInstance(object): - # just testin something. - def __init__(self, - shields=None, - hulls=None, - shield_resis=None, - hull_resis=None ): - self.shield_max = shields or 5000 - self.hull_max = hulls or 5000 - shield_resis = shield_resis or (100,100,100) - hull_resis = hull_resis or (100,100,100) - self.set_shield_res(*shield_resis) - self.set_hull_res(*hull_resis) - - - def set_shield_res(self, kn, em, th): - self.shield_res_kn = kn - self.shield_res_em = em - self.shield_res_th = th - - def set_hull_res(self, kn, em, th): - self.hull_res_kn = kn - self.hull_res_em = em - self.hull_res_th = th - - def survivability(self): - # i have no clue how they calc this. - # multiple attempts shows, they are using base pts as measure, but how exactly the calc is? - krs = (self.shield_max/100.0 * self.shield_res_kn) - ers = (self.shield_max/100.0 * self.shield_res_em) - trs = (self.shield_max/100.0 * self.shield_res_th) - print "Shield.", krs, ers, trs - - krh = (self.hull_max/100.0 * self.hull_res_kn) - erh = (self.hull_max/100.0 * self.hull_res_em) - trh = (self.hull_max/100.0 * self.hull_res_th) - print "Hull.", krh, erh, trh - - #print "?1", ((krs+ers+trs+krh+erh+trh)/6.0)+self.shield_max + self.hull_max - print "?2", ((krs+ers+trs+3*self.shield_max)/3.0)+((krh+erh+trh+3*self.hull_max)/3.0) - - - # another try: - """ - lets assume survivability is really measured through applying 1000 dps for 10 secs. - - """ - print "Assuming dps..." - shield = self.shield_max - hull = self.hull_max - r1s = shield / (1.0*dam_res(1000, self.shield_res_kn)) - r2s = shield / (1.0*dam_res(1000, self.shield_res_em)) - r3s = shield / (1.0*dam_res(1000, self.shield_res_th)) - print r1s, r2s, r3s - rXs = (r1s+r2s+r3s) / 3.0 - print "Shield survival time at 1kdps", rXs - - r1h = hull / (1.0*dam_res(1000, self.hull_res_kn)) - r2h = hull / (1.0*dam_res(1000, self.hull_res_em)) - r3h = hull / (1.0*dam_res(1000, self.hull_res_th)) - print r1h, r2h, r3h - rXh = (r1h+r2h+r3h) / 3.0 - print "Hull survival time at 1kdps", rXh - - print "Total survival time ", rXs + rXh, " sec" - print "Surv should be ", int(round((rXs+rXh) * 1000)) - - - - return ((krs+ers+trs)/3.0)+self.shield_max + self.hull_max + ((krh+erh+trh)/3.0) - - - -ship = ShipInstance() -print ship.survivability() - -print "#" * 80 -mykatanas=ShipInstance(7664, 4296, (70,61,100), (20,80,50)) -print "We know its 19736... but own calcs say..." -print mykatanas.survivability() \ No newline at end of file + +def res_to_red(res): + ''' calculates reduction % of damage from base resistance + incoming damage is assumed to be 100.0 to get percentages. + ''' + if res >= 0: + fd = 100 / (1.0+res/100.0) + else: + fd = 100 / (1.0-res/100.0) + return 100.0 - fd + +def dam_res(dam, res): + ''' calculates damage modified by resistance. + ''' + if res >= 0: + fd = dam / (1.0+res/100.0) + else: + fd = dam / (1.0-res/100.0) + return fd + +class ShipInstance(object): + # just testin something. + def __init__(self, + shields=None, + hulls=None, + shield_resis=None, + hull_resis=None ): + self.shield_max = shields or 5000 + self.hull_max = hulls or 5000 + shield_resis = shield_resis or (100,100,100) + hull_resis = hull_resis or (100,100,100) + self.set_shield_res(*shield_resis) + self.set_hull_res(*hull_resis) + + + def set_shield_res(self, kn, em, th): + self.shield_res_kn = kn + self.shield_res_em = em + self.shield_res_th = th + + def set_hull_res(self, kn, em, th): + self.hull_res_kn = kn + self.hull_res_em = em + self.hull_res_th = th + + def survivability(self): + # i have no clue how they calc this. + # multiple attempts shows, they are using base pts as measure, but how exactly the calc is? + krs = (self.shield_max/100.0 * self.shield_res_kn) + ers = (self.shield_max/100.0 * self.shield_res_em) + trs = (self.shield_max/100.0 * self.shield_res_th) + print(("Shield.", krs, ers, trs)) + + krh = (self.hull_max/100.0 * self.hull_res_kn) + erh = (self.hull_max/100.0 * self.hull_res_em) + trh = (self.hull_max/100.0 * self.hull_res_th) + print(("Hull.", krh, erh, trh)) + + #print "?1", ((krs+ers+trs+krh+erh+trh)/6.0)+self.shield_max + self.hull_max + print(("?2", ((krs+ers+trs+3*self.shield_max)/3.0)+((krh+erh+trh+3*self.hull_max)/3.0))) + + + # another try: + """ + lets assume survivability is really measured through applying 1000 dps for 10 secs. + + """ + print("Assuming dps...") + shield = self.shield_max + hull = self.hull_max + r1s = shield / (1.0*dam_res(1000, self.shield_res_kn)) + r2s = shield / (1.0*dam_res(1000, self.shield_res_em)) + r3s = shield / (1.0*dam_res(1000, self.shield_res_th)) + print((r1s, r2s, r3s)) + rXs = (r1s+r2s+r3s) / 3.0 + print(("Shield survival time at 1kdps", rXs)) + + r1h = hull / (1.0*dam_res(1000, self.hull_res_kn)) + r2h = hull / (1.0*dam_res(1000, self.hull_res_em)) + r3h = hull / (1.0*dam_res(1000, self.hull_res_th)) + print((r1h, r2h, r3h)) + rXh = (r1h+r2h+r3h) / 3.0 + print(("Hull survival time at 1kdps", rXh)) + + print(("Total survival time ", rXs + rXh, " sec")) + print(("Surv should be ", int(round((rXs+rXh) * 1000)))) + + + + return ((krs+ers+trs)/3.0)+self.shield_max + self.hull_max + ((krh+erh+trh)/3.0) + + + +ship = ShipInstance() +print((ship.survivability())) + +print(("#" * 80)) +mykatanas=ShipInstance(7664, 4296, (70,61,100), (20,80,50)) +print("We know its 19736... but own calcs say...") +print((mykatanas.survivability())) \ No newline at end of file diff --git a/src/scon/gui/treeview.py b/src/scon/gui/treeview.py index e42a9c2..6e551ce 100644 --- a/src/scon/gui/treeview.py +++ b/src/scon/gui/treeview.py @@ -1,444 +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 -# For more information see -# -# 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: -# -# -# 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) +#!/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 +# For more information see +# +# 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: +# +# +# 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) \ No newline at end of file diff --git a/src/scon/gui/viewer.py b/src/scon/gui/viewer.py index ff59fa6..1f88023 100644 --- a/src/scon/gui/viewer.py +++ b/src/scon/gui/viewer.py @@ -13,7 +13,7 @@ import sys from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork from scon.dejaqt.folders import FolderLibrary from scon.dejaqt.qweb import DejaWebView -from treeview import TreeViewModel, Node +from .treeview import TreeViewModel, Node class MenuTree(QtGui.QTreeView): def __init__(self, *args, **kwargs): diff --git a/src/scon/logs/base.py b/src/scon/logs/base.py index 8415e91..439de57 100644 --- a/src/scon/logs/base.py +++ b/src/scon/logs/base.py @@ -1,89 +1,89 @@ -import logging -""" - Base Class for a Logentry is Log. Stacktrace is an exception, which gets injected if a stacktrace - is assumed, and swallows all following unrecognized logs. - - -> It gets parsed by a Logstream, like the Logfile, but might also be used to be feeded - by live-streams of currently open log files. - - -> Logfiles is responsible to read whole packs of files, and - -> Sessions are responsible for reading whole directories. - - A Log object usually will expand itself containing "values", and is responsible to retain all dynamic data needed to describe it in unpack() - The classmethod is_handler should pre-scan a log, which is usually a dict containing the actual log in log['log'] - but it could be a string aswell. - - clean is called to make a log object independent of its source information, and delete all incoming data, so it becomes sleek. - reviewed is an internal boolean, which supposed to be saved on successful unpack, unpack should ignore already unpacked logs. - matcher is a regex object to match, or a list of them. - trash is a boolean flag to indicate, this log is possibly unknown information or unneeded, and should be removed or ignored. - - -> Note for anyone creating new subclasses for parsing: - All classes are to be __slot__-ed so they can be created more efficiently by python. - A class without __slot__ will slow down parsing exponentially in CPython. - __slots__ hinder you to add new properties on the fly in the code, but having this immutable class optimizes memory allocation. - - This is the reason, the base layout of the log object is explained here. -""" - - -L_CMBT = 'CMBT' -L_WARNING = 'WARNING' -L_NET = 'NET' # Not supported in near future. -L_CHAT = 'CHAT' - -class Log(object): - __slots__ = ['matcher', 'trash', 'reviewed'] - matcher = None - trash = False - reviewed = False - - @classmethod - def is_handler(cls, log): - return False - - def unpack(self, force=False): - ''' unpacks this log from its data and saves values ''' - pass - - def explain(self): - ''' returns a String readable by humans explaining this Log ''' - return '' - - def clean(self): - ''' tell the log to forget all non-essential data ''' - pass - - def append(self, something): - ''' returns true if this logfile wants an unrecognized log appended to it. ''' - return False - -class Stacktrace(Log): - ''' Special Log to catch error reports ''' - def __init__(self, values=None): - super(Stacktrace, self).__init__() - self.message = values or '' - if isinstance(self.message, dict): - self.message = self.message.get('log', '') - #self.trash = True - - @classmethod - def is_handler(cls, log): - # do i have a system crash report beginning here? - if isinstance(log, basestring): - l = log.strip() - elif isinstance(log, dict): - l = log.get('log', '').strip() - else: - return False - if l.startswith('Stack trace:') or l.startswith('BitStream::DbgLog'): - return True - - def clean(self): - self.message = '' - - def append(self, something): - ''' I take anything! ''' - logging.debug( "EXC: %s" % something ) - self.message = '%s\n%s' % (self.message, something) +import logging +""" + Base Class for a Logentry is Log. Stacktrace is an exception, which gets injected if a stacktrace + is assumed, and swallows all following unrecognized logs. + + -> It gets parsed by a Logstream, like the Logfile, but might also be used to be feeded + by live-streams of currently open log files. + + -> Logfiles is responsible to read whole packs of files, and + -> Sessions are responsible for reading whole directories. + + A Log object usually will expand itself containing "values", and is responsible to retain all dynamic data needed to describe it in unpack() + The classmethod is_handler should pre-scan a log, which is usually a dict containing the actual log in log['log'] + but it could be a string aswell. + + clean is called to make a log object independent of its source information, and delete all incoming data, so it becomes sleek. + reviewed is an internal boolean, which supposed to be saved on successful unpack, unpack should ignore already unpacked logs. + matcher is a regex object to match, or a list of them. + trash is a boolean flag to indicate, this log is possibly unknown information or unneeded, and should be removed or ignored. + + -> Note for anyone creating new subclasses for parsing: + All classes are to be __slot__-ed so they can be created more efficiently by python. + A class without __slot__ will slow down parsing exponentially in CPython. + __slots__ hinder you to add new properties on the fly in the code, but having this immutable class optimizes memory allocation. + + This is the reason, the base layout of the log object is explained here. +""" + + +L_CMBT = 'CMBT' +L_WARNING = 'WARNING' +L_NET = 'NET' # Not supported in near future. +L_CHAT = 'CHAT' + +class Log(object): + __slots__ = ['matcher', 'trash', 'reviewed'] + matcher = None + trash = False + reviewed = False + + @classmethod + def is_handler(cls, log): + return False + + def unpack(self, force=False): + ''' unpacks this log from its data and saves values ''' + pass + + def explain(self): + ''' returns a String readable by humans explaining this Log ''' + return '' + + def clean(self): + ''' tell the log to forget all non-essential data ''' + pass + + def append(self, something): + ''' returns true if this logfile wants an unrecognized log appended to it. ''' + return False + +class Stacktrace(Log): + ''' Special Log to catch error reports ''' + def __init__(self, values=None): + super(Stacktrace, self).__init__() + self.message = values or '' + if isinstance(self.message, dict): + self.message = self.message.get('log', '') + #self.trash = True + + @classmethod + def is_handler(cls, log): + # do i have a system crash report beginning here? + if isinstance(log, str): + l = log.strip() + elif isinstance(log, dict): + l = log.get('log', '').strip() + else: + return False + if l.startswith('Stack trace:') or l.startswith('BitStream::DbgLog'): + return True + + def clean(self): + self.message = '' + + def append(self, something): + ''' I take anything! ''' + logging.debug( "EXC: %s" % something ) + self.message = '%s\n%s' % (self.message, something) return True \ No newline at end of file diff --git a/src/scon/logs/chat.py b/src/scon/logs/chat.py index bf31589..18768fc 100644 --- a/src/scon/logs/chat.py +++ b/src/scon/logs/chat.py @@ -1,206 +1,206 @@ -# -*- coding: utf-8 -*- -from logs.base import Log, L_WARNING, Stacktrace -import re -""" - Responsible for Chat Log. - - Anything related to chat gets logged here, basicly interesting for chat related stuff mainly. - Channel leaving and joining and connection to the chat server get logged here too. - -------------------------------- -Maybe add something to create a ColorChart of usernames? -between 33-33-33 and FF-33 FF-33 FF-33 - -""" - -class ChatLog(Log): - __slots__ = ['matcher', 'trash', '_match_id', 'values'] - - @classmethod - def is_handler(cls, log): - if log.get('logtype', None) == 'CHAT': - return cls._is_handler(log) - return False - - @classmethod - def _is_handler(cls, log): - return False - - def __init__(self, values=None): - self.values = values or {} - self.reviewed = False - - def unpack(self, force=False): - if self.reviewed and not force: - return True - self._match_id = None - # unpacks the data from the values. - if hasattr(self, 'matcher') and self.matcher: - matchers = self.matcher - if not isinstance(matchers, list): - matchers = [matchers,] - for i, matcher in enumerate(matchers): - m = matcher.match(self.values.get('log', '')) - if m: - self.values.update(m.groupdict()) - self._match_id = i - self.reviewed = True - return True - # unknown? - self.trash = True - - def explain(self): - ''' returns a String readable by humans explaining this Log ''' - return self.values.get('log', 'Unknown Chat Log') - - def clean(self): - if 'log' in self.values.keys(): - del self.values['log'] - -class SystemMessage(ChatLog): - matcher = re.compile(r"^<\s+SYSTEM>\s(?P.*)") - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').lstrip().startswith('< SYSTEM>'): - return True - return False - - def explain(self): - return '[SYSTEM]: %(message)s' % self.values - - def append(self, something): - ''' System Messages accept appends ''' - if 'message' in self.values.keys(): - self.values['message'] = '%s\n%s' % (self.values['message'], something) - return True - - - -class PrivateMessageReceived(ChatLog): - matcher = re.compile(r"^<\s\s\s\sPRIVATE From>\[\s*(?P[^\]]+)\]\s(?P.*)") - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').lstrip().startswith('< PRIVATE From>'): - return True - return False - - def explain(self): - return '[From %(nickname)s]: %(message)s' % self.values - - def append(self, something): - ''' Private Messages accept appends ''' - if 'message' in self.values.keys(): - self.values['message'] = '%s\n%s' % (self.values['message'], something) - return True - -class PrivateMessageSent(ChatLog): - matcher = re.compile(r"^<\s\s\s\sPRIVATE To\s\s>\[\s*(?P[^\]]+)\]\s(?P.*)") - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').lstrip().startswith('< PRIVATE To >'): - return True - return False - - def explain(self): - return '[To %(nickname)s]: %(message)s' % self.values - - def append(self, something): - ''' Private Messages accept appends ''' - if 'message' in self.values.keys(): - self.values['message'] = '%s\n%s' % (self.values['message'], something) - return True - -class ChatMessage(ChatLog): - matcher = re.compile(r"^<\s*#(?P[^>]+)>\[\s*(?P[^\]]+)\]\s(?P.*)") - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').lstrip().startswith('<'): - return True - return False - - def explain(self): - return '[%(channel)s] <%(nickname)s>: %(message)s' % self.values - - def append(self, something): - ''' ChatMessages accept appends ''' - if not 'message' in self.values.keys(): - print "Missing message? %s" % self.values - self.values['message'] = '' - self.values['message'] = '%s\n%s' % (self.values['message'], something) - return True - -class ChatJoinChannel(ChatLog): - matcher = re.compile(r"^Join\schannel\s<\s*#(?P[^>]+)>") - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').lstrip().startswith('Join channel'): - return True - return False - - def explain(self): - return '[joined %(channel)s]' % self.values - -class ChatLeaveChannel(ChatLog): - matcher = re.compile(r"^Leave\schannel\s<\s*#(?P[^>]+)>") - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').lstrip().startswith('Leave channel'): - return True - return False - - def explain(self): - return '[left %(channel)s]' % self.values - - -class ChatServerConnect(ChatLog): - # 00:12:47.668 CHAT| Connection to chat-server established - matcher = [] - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').lstrip().startswith('Connection to'): - return True - return False - - def unpack(self, force=False): - self.reviewed = True - return True - - def explain(self): - return '[connected]' - - -class ChatServerDisconnect(ChatLog): - # 00:53:03.738 CHAT| Disconnect form chat-server (reason 0) - matcher = [] - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').lstrip().startswith('Disconnect'): - return True - return False - - def unpack(self, force=False): - self.reviewed = True - return True - - def explain(self): - return '[disconnected]' - -CHAT_LOGS = [ - SystemMessage, - PrivateMessageReceived, - PrivateMessageSent, - ChatMessage, # private messages need to be before chatmessage. - ChatServerConnect, - ChatServerDisconnect, - ChatJoinChannel, - ChatLeaveChannel, - Stacktrace, - ] +# -*- coding: utf-8 -*- +from logs.base import Log, L_WARNING, Stacktrace +import re +""" + Responsible for Chat Log. + + Anything related to chat gets logged here, basicly interesting for chat related stuff mainly. + Channel leaving and joining and connection to the chat server get logged here too. + +------------------------------- +Maybe add something to create a ColorChart of usernames? +between 33-33-33 and FF-33 FF-33 FF-33 + +""" + +class ChatLog(Log): + __slots__ = ['matcher', 'trash', '_match_id', 'values'] + + @classmethod + def is_handler(cls, log): + if log.get('logtype', None) == 'CHAT': + return cls._is_handler(log) + return False + + @classmethod + def _is_handler(cls, log): + return False + + def __init__(self, values=None): + self.values = values or {} + self.reviewed = False + + def unpack(self, force=False): + if self.reviewed and not force: + return True + self._match_id = None + # unpacks the data from the values. + if hasattr(self, 'matcher') and self.matcher: + matchers = self.matcher + if not isinstance(matchers, list): + matchers = [matchers,] + for i, matcher in enumerate(matchers): + m = matcher.match(self.values.get('log', '')) + if m: + self.values.update(m.groupdict()) + self._match_id = i + self.reviewed = True + return True + # unknown? + self.trash = True + + def explain(self): + ''' returns a String readable by humans explaining this Log ''' + return self.values.get('log', 'Unknown Chat Log') + + def clean(self): + if 'log' in list(self.values.keys()): + del self.values['log'] + +class SystemMessage(ChatLog): + matcher = re.compile(r"^<\s+SYSTEM>\s(?P.*)") + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').lstrip().startswith('< SYSTEM>'): + return True + return False + + def explain(self): + return '[SYSTEM]: %(message)s' % self.values + + def append(self, something): + ''' System Messages accept appends ''' + if 'message' in list(self.values.keys()): + self.values['message'] = '%s\n%s' % (self.values['message'], something) + return True + + + +class PrivateMessageReceived(ChatLog): + matcher = re.compile(r"^<\s\s\s\sPRIVATE From>\[\s*(?P[^\]]+)\]\s(?P.*)") + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').lstrip().startswith('< PRIVATE From>'): + return True + return False + + def explain(self): + return '[From %(nickname)s]: %(message)s' % self.values + + def append(self, something): + ''' Private Messages accept appends ''' + if 'message' in list(self.values.keys()): + self.values['message'] = '%s\n%s' % (self.values['message'], something) + return True + +class PrivateMessageSent(ChatLog): + matcher = re.compile(r"^<\s\s\s\sPRIVATE To\s\s>\[\s*(?P[^\]]+)\]\s(?P.*)") + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').lstrip().startswith('< PRIVATE To >'): + return True + return False + + def explain(self): + return '[To %(nickname)s]: %(message)s' % self.values + + def append(self, something): + ''' Private Messages accept appends ''' + if 'message' in list(self.values.keys()): + self.values['message'] = '%s\n%s' % (self.values['message'], something) + return True + +class ChatMessage(ChatLog): + matcher = re.compile(r"^<\s*#(?P[^>]+)>\[\s*(?P[^\]]+)\]\s(?P.*)") + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').lstrip().startswith('<'): + return True + return False + + def explain(self): + return '[%(channel)s] <%(nickname)s>: %(message)s' % self.values + + def append(self, something): + ''' ChatMessages accept appends ''' + if not 'message' in list(self.values.keys()): + print(("Missing message? %s" % self.values)) + self.values['message'] = '' + self.values['message'] = '%s\n%s' % (self.values['message'], something) + return True + +class ChatJoinChannel(ChatLog): + matcher = re.compile(r"^Join\schannel\s<\s*#(?P[^>]+)>") + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').lstrip().startswith('Join channel'): + return True + return False + + def explain(self): + return '[joined %(channel)s]' % self.values + +class ChatLeaveChannel(ChatLog): + matcher = re.compile(r"^Leave\schannel\s<\s*#(?P[^>]+)>") + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').lstrip().startswith('Leave channel'): + return True + return False + + def explain(self): + return '[left %(channel)s]' % self.values + + +class ChatServerConnect(ChatLog): + # 00:12:47.668 CHAT| Connection to chat-server established + matcher = [] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').lstrip().startswith('Connection to'): + return True + return False + + def unpack(self, force=False): + self.reviewed = True + return True + + def explain(self): + return '[connected]' + + +class ChatServerDisconnect(ChatLog): + # 00:53:03.738 CHAT| Disconnect form chat-server (reason 0) + matcher = [] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').lstrip().startswith('Disconnect'): + return True + return False + + def unpack(self, force=False): + self.reviewed = True + return True + + def explain(self): + return '[disconnected]' + +CHAT_LOGS = [ + SystemMessage, + PrivateMessageReceived, + PrivateMessageSent, + ChatMessage, # private messages need to be before chatmessage. + ChatServerConnect, + ChatServerDisconnect, + ChatJoinChannel, + ChatLeaveChannel, + Stacktrace, + ] diff --git a/src/scon/logs/combat.py b/src/scon/logs/combat.py index d39c450..a75fde8 100644 --- a/src/scon/logs/combat.py +++ b/src/scon/logs/combat.py @@ -1,304 +1,304 @@ -# -*- coding: utf-8 -*- -""" - Primary Packets for Combat.Log Files. - - This is the most important part for dealing with actual statistics, since every action taken - in a combat instance gets logged here. - - - ------------------------------------ - Note: - All logs start with something like - 23:53:29.137 | LOGDATA - - LOGDATA can be quite different depending on the logfile. - - other forms encountered: - 23:54:00.600 WARNING| - - combat logs: - 01:04:38.805 CMBT | -""" -import re -from base import Log, L_CMBT, Stacktrace -import logging - -class CombatLog(Log): - __slots__ = Log.__slots__ + [ '_match_id', 'values'] - @classmethod - def _log_handler(cls, log): - if log.startswith(cls.__name__): - return True - return False - - @classmethod - def is_handler(cls, log): - if log.get('logtype', None) == L_CMBT: - return cls._log_handler(log.get('log', '').strip()) - return False - - def __init__(self, values=None): - self.values = values or {} - self.reviewed = False - - def unpack(self, force=False): - if self.reviewed and not force: - return True - self._match_id = None - # unpacks the data from the values. - if hasattr(self, 'matcher') and self.matcher: - matchers = self.matcher - if not isinstance(matchers, list): - matchers = [matchers,] - for i, matcher in enumerate(matchers): - m = matcher.match(self.values.get('log', '')) - if m: - self.values.update(m.groupdict()) - self._match_id = i - self.reviewed = True - return True - # unknown? - if not isinstance(self, UserEvent): - logging.warning('Unknown Packet for %s:\n%s' % (self.__class__.__name__, - self.values.get('log', ''))) - # trash if unknown or no matcher. - self.trash = True - - def explain(self): - ''' returns a String readable by humans explaining this Log ''' - return self.values.get('log', 'Unknown Combat Log') - - def clean(self): - if 'log' in self.values.keys(): - del self.values['log'] - - -# @todo: where does this come from? -class Action(CombatLog): - __slots__ = CombatLog.__slots__ - pass - -class Gameplay(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = [ - # usual: team(reason). explained reason. - re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P\d+)\((?P\w+)\)\.\sFinish\sreason\:\s'(?P[^']+)'\.\sActual\sgame\stime\s+(?P\d+|\d+\.\d+)\ssec"), - # team, unexplained reason (unknown, Timeout) - re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P\d+).\sFinish\sreason\:\s'(?P[^']+)'\.\sActual\sgame\stime\s+(?P\d+|\d+\.\d+)\ssec"), - ] - -class Apply(CombatLog): # Apply Aura. - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^Apply\saura\s'(?P\w+)'\sid\s(?P\d+)\stype\s(?P\w+)\sto\s'(?P[^\']+)'") - -class Damage(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^Damage\s+(?P[^\s]+)\s\->\s+(?P[^\s]+)\s+(?P(?:\d+|\d+\.\d+))(?:\s(?P[^\s]+)\s|\s{2,2})(?P(?:\w|\|)+)") - -class Spawn(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P\d+)\s\((?P[^,]+),\s+(?P#\w+)\)\.\s+'(?P\w+)'") - -class Spell(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^Spell\s'(?P\w+)'\sby\s+(?P.*)(?:\((?P\w+)\)|)\stargets\((?P\d+)\)\:(?:\s(?P.+)|\s*)") - -class Reward(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = [ - # ordinary reward: - re.compile(r"^Reward\s+(?P[^\s]+)(?:\s(?P\w+)\s+|\s+)(?P\d+)\s(?P.*)\s+for\s(?P.*)"), - # openspace reward (karma): - re.compile(r"^Reward\s+(?P[^\s]+)(?:\s(?P\w+)\s+|\s+)\s+(?P[\+\-]\d+)\skarma\spoints\s+for\s(?P.*)"), - ] - -class Participant(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^\s+Participant\s+(?P[^\s]+)(?:\s+(?P\w+)|\s{30,})\s+(?:totalDamage\s(?P(?:\d+|\d+\.\d+));\s+|\s+)(?:mostDamageWith\s'(?P[^']+)';\s*(?P.*)|<(?P\w+)>)") - -""" -2017-03-29 13:25:49 - Unknown Packet for Rocket: -Rocket launch 18912, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', target 'white213mouse' (17894) -2017-03-29 13:25:49 - Unknown Packet for Rocket: -Rocket detonation 18912, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', reason 'auto_detonate', directHit 'white213mouse' -2017-03-29 13:25:49 - Unknown Packet for Rocket: -Rocket launch 18966, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', target 'white213mouse' (17894) -2017-03-29 13:25:49 - Unknown Packet for Rocket: -Rocket detonation 18966, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', reason 'auto_detonate', directHit 'white213mouse' -2017-03-29 13:25:49 - Unknown Packet for Rocket: -Rocket detonation 18892, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', reason 'ttl' -2017-03-29 13:25:49 - Unknown Packet for Rocket: -Rocket detonation 18931, owner 'optimistik', def 'Weapon_Railgun_Heavy_T5_Epic', reason 'hit' -2017-03-29 13:25:49 - Unknown Packet for Participant: - Participant white213mouse Ship_Race5_M_ATTACK_Rank15 -""" - -class Rocket(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^Rocket\s(?Plaunch|detonation)\.\sowner\s'(?P[^']+)'(?:,\s(?:def\s'(?P\w+)'|target\s'(?P[^']+)'|reason\s'(?P\w+)'|directHit\s'(?P[^']+)'))+") - -class Heal(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = [ - # heal by module - re.compile(r"^Heal\s+(?P[^\s]+)\s\->\s+(?P[^\s]+)\s+(?P(?:\d+|\d+\.\d+))\s(?P[^\s]+)"), - # direct heal by source or n/a (global buff) - re.compile(r"^Heal\s+(?:n/a|(?P\w+))\s+\->\s+(?P[^\s]+)\s+(?P(?:\d+|\d+\.\d+))"), - ] - -class Killed(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = [ - re.compile(r"^Killed\s(?P[^\s]+)\s+(?P\w+);\s+killer\s(?P[^\s]+)\s*"), - re.compile(r"^Killed\s(?P[^\(]+)\((?P\w+)\);\s+killer\s(?P[^\s]+)\s*"), - re.compile(r"^Killed\s(?P[^\;]+);\s+killer\s(?P[^\s]+)\s+.*"), - ] - -class Captured(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^Captured\s'(?P[^']+)'\(team\s(?P\d+)\)\.(?:\sAttackers\:(?P.*)|.*)") - -class AddStack(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^AddStack\saura\s'(?P\w+)'\sid\s(?P\d+)\stype\s(?P\w+)\.\snew\sstacks\scount\s(?P\d+)") - -class Cancel(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^Cancel\saura\s'(?P\w+)'\sid\s(?P\d+)\stype\s(?P\w+)\sfrom\s'(?P[^']*)'") - -class Scores(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^Scores\s+-\sTeam1\((?P(?:\d+|\d+\.\d+))\)\sTeam2\((?P(?:\d+|\d+\.\d+))\)") - -class Uncaptured(CombatLog): - """ - Variables: - - objective (which was uncaptured (most likely something like VitalPointXY)) - - team (number) - - attackers (split by space, names of the attackers, contains bots) - """ - __slots__ = CombatLog.__slots__ - matcher = re.compile(r"^Uncaptured\s'(?P[^']+)'\(team\s(?P\d+)\)\.(?:\sAttackers\:\s(?P.*)|)") - -# Special classes -class GameEvent(CombatLog): - __slots__ = CombatLog.__slots__ - matcher = [ - # game session identifier. - re.compile(r"^Connect\sto\sgame\ssession\s+(?P\d+)"), - # start gameplay identifier. - re.compile(r"^Start\sgameplay\s'(?P\w+)'\smap\s+'(?P\w+)',\slocal\sclient\steam\s(?P\d+)"), - # pve mission identifier. - re.compile(r"^Start\sPVE\smission\s'(?P\w+)'\smap\s+'(?P\w+)'"), - ] - - @classmethod - def _log_handler(cls, log): - if log.startswith('======='): - return True - return False - - def unpack(self, force=False): - if self.reviewed and not force: - return True - self._match_id = None - # unpacks the data from the values. - # small override to remove trailing "="s in the matching. - if hasattr(self, 'matcher') and self.matcher: - matchers = self.matcher - if not isinstance(matchers, list): - matchers = [matchers,] - for i, matcher in enumerate(matchers): - m = matcher.match(self.values.get('log', '').strip('=').strip()) - if m: - self.values.update(m.groupdict()) - self._match_id = i - self.reviewed = True - return True - # unknown? - self.trash = True - - def clean(self): - if 'log' in self.values.keys(): - del self.values['log'] - -class PVE_Mission(CombatLog): - """ - - mission: contains the mission id. - - message: contains the pve mission message, like starting rounds, waves, etc. - """ - __slots__ = CombatLog.__slots__ - matcher = re.compile("^PVE_Mission:\s'(?P[^']+)'.\s(?P.*)") # this is very general, but we dont care for pve now. - -class Looted(CombatLog): - """ - called on looting in openspace. - - loot contains the loot id. - - container contains the container looted from. - """ - __slots__ = CombatLog.__slots__ - matcher = re.compile("^Looted\s'(?P[^']+)'\sfrom\s'(?P[^']+)'") - -class Dropped(CombatLog): - """ - called on dropping in openspace. - - loot contains the loot id. it can be '' - """ - __slots__ = CombatLog.__slots__ - matcher = re.compile("^Dropped\sloot\s'(?P[^']+)'") - -class Set(CombatLog): - """ - called on setting "relationship" / OpenSpace - Variables in values: - - what (relationship) - - name (who do i set?) - - value (to what value?) - """ - __slots__ = CombatLog.__slots__ - matcher = re.compile("^Set\s(?P\w+)\s(?P[^\s]+)\sto\s(?P\w+)") - -class SqIdChange(CombatLog): - """ - number: player number - - name: player name - - old_sqid: sqid of player before - - sqid: new player sqid - """ - __slots__ = CombatLog.__slots__ - matcher = re.compile("^Player\s(?P\d+)\((?P[^\)]+)\)\schanged\ssqid\sfrom\s(?P\d+)\sto\s(?P\d+)") - - @classmethod - def _log_handler(cls, log): - if log.startswith('Player'): - return True - return False - -class Mailed(CombatLog): - """ has no information. only that loot has been mailed """ - __slots__ = CombatLog.__slots__ - matcher = re.compile("Mailed\sloot") - -class UserEvent(CombatLog): - """ special class for combat logs that might be associated with the playing player """ - __slots__ = CombatLog.__slots__ - @classmethod - def _log_handler(cls, log): - if log and 'earned medal' in log: - return True - elif log: - logging.debug('UserEvent saw unknown line:\n%s' % log) - return False - -# Action? -COMBAT_LOGS = [ Apply, Damage, Spawn, Spell, Reward, Participant, Rocket, Heal, - Gameplay, #? - Scores, - Killed, Captured, AddStack, Cancel, Uncaptured, - # undone openspace: - PVE_Mission, Looted, Set, Dropped, - SqIdChange, Mailed, # unknown if these are important... - # always last: - GameEvent, UserEvent, - Stacktrace, - ] - +# -*- coding: utf-8 -*- +""" + Primary Packets for Combat.Log Files. + + This is the most important part for dealing with actual statistics, since every action taken + in a combat instance gets logged here. + + + ------------------------------------ + Note: + All logs start with something like + 23:53:29.137 | LOGDATA + + LOGDATA can be quite different depending on the logfile. + + other forms encountered: + 23:54:00.600 WARNING| + + combat logs: + 01:04:38.805 CMBT | +""" +import re +from .base import Log, L_CMBT, Stacktrace +import logging + +class CombatLog(Log): + __slots__ = Log.__slots__ + [ '_match_id', 'values'] + @classmethod + def _log_handler(cls, log): + if log.startswith(cls.__name__): + return True + return False + + @classmethod + def is_handler(cls, log): + if log.get('logtype', None) == L_CMBT: + return cls._log_handler(log.get('log', '').strip()) + return False + + def __init__(self, values=None): + self.values = values or {} + self.reviewed = False + + def unpack(self, force=False): + if self.reviewed and not force: + return True + self._match_id = None + # unpacks the data from the values. + if hasattr(self, 'matcher') and self.matcher: + matchers = self.matcher + if not isinstance(matchers, list): + matchers = [matchers,] + for i, matcher in enumerate(matchers): + m = matcher.match(self.values.get('log', '')) + if m: + self.values.update(m.groupdict()) + self._match_id = i + self.reviewed = True + return True + # unknown? + if not isinstance(self, UserEvent): + logging.warning('Unknown Packet for %s:\n%s' % (self.__class__.__name__, + self.values.get('log', ''))) + # trash if unknown or no matcher. + self.trash = True + + def explain(self): + ''' returns a String readable by humans explaining this Log ''' + return self.values.get('log', 'Unknown Combat Log') + + def clean(self): + if 'log' in list(self.values.keys()): + del self.values['log'] + + +# @todo: where does this come from? +class Action(CombatLog): + __slots__ = CombatLog.__slots__ + pass + +class Gameplay(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = [ + # usual: team(reason). explained reason. + re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P\d+)\((?P\w+)\)\.\sFinish\sreason\:\s'(?P[^']+)'\.\sActual\sgame\stime\s+(?P\d+|\d+\.\d+)\ssec"), + # team, unexplained reason (unknown, Timeout) + re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P\d+).\sFinish\sreason\:\s'(?P[^']+)'\.\sActual\sgame\stime\s+(?P\d+|\d+\.\d+)\ssec"), + ] + +class Apply(CombatLog): # Apply Aura. + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Apply\saura\s'(?P\w+)'\sid\s(?P\d+)\stype\s(?P\w+)\sto\s'(?P[^\']+)'") + +class Damage(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Damage\s+(?P[^\s]+)\s\->\s+(?P[^\s]+)\s+(?P(?:\d+|\d+\.\d+))(?:\s(?P[^\s]+)\s|\s{2,2})(?P(?:\w|\|)+)") + +class Spawn(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P\d+)\s\((?P[^,]+),\s+(?P#\w+)\)\.\s+'(?P\w+)'") + +class Spell(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Spell\s'(?P\w+)'\sby\s+(?P.*)(?:\((?P\w+)\)|)\stargets\((?P\d+)\)\:(?:\s(?P.+)|\s*)") + +class Reward(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = [ + # ordinary reward: + re.compile(r"^Reward\s+(?P[^\s]+)(?:\s(?P\w+)\s+|\s+)(?P\d+)\s(?P.*)\s+for\s(?P.*)"), + # openspace reward (karma): + re.compile(r"^Reward\s+(?P[^\s]+)(?:\s(?P\w+)\s+|\s+)\s+(?P[\+\-]\d+)\skarma\spoints\s+for\s(?P.*)"), + ] + +class Participant(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^\s+Participant\s+(?P[^\s]+)(?:\s+(?P\w+)|\s{30,})\s+(?:totalDamage\s(?P(?:\d+|\d+\.\d+));\s+|\s+)(?:mostDamageWith\s'(?P[^']+)';\s*(?P.*)|<(?P\w+)>)") + +""" +2017-03-29 13:25:49 - Unknown Packet for Rocket: +Rocket launch 18912, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', target 'white213mouse' (17894) +2017-03-29 13:25:49 - Unknown Packet for Rocket: +Rocket detonation 18912, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', reason 'auto_detonate', directHit 'white213mouse' +2017-03-29 13:25:49 - Unknown Packet for Rocket: +Rocket launch 18966, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', target 'white213mouse' (17894) +2017-03-29 13:25:49 - Unknown Packet for Rocket: +Rocket detonation 18966, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', reason 'auto_detonate', directHit 'white213mouse' +2017-03-29 13:25:49 - Unknown Packet for Rocket: +Rocket detonation 18892, owner 'LOSNAR', def 'SpaceMissile_Barrage_T5_Mk3', reason 'ttl' +2017-03-29 13:25:49 - Unknown Packet for Rocket: +Rocket detonation 18931, owner 'optimistik', def 'Weapon_Railgun_Heavy_T5_Epic', reason 'hit' +2017-03-29 13:25:49 - Unknown Packet for Participant: + Participant white213mouse Ship_Race5_M_ATTACK_Rank15 +""" + +class Rocket(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Rocket\s(?Plaunch|detonation)\.\sowner\s'(?P[^']+)'(?:,\s(?:def\s'(?P\w+)'|target\s'(?P[^']+)'|reason\s'(?P\w+)'|directHit\s'(?P[^']+)'))+") + +class Heal(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = [ + # heal by module + re.compile(r"^Heal\s+(?P[^\s]+)\s\->\s+(?P[^\s]+)\s+(?P(?:\d+|\d+\.\d+))\s(?P[^\s]+)"), + # direct heal by source or n/a (global buff) + re.compile(r"^Heal\s+(?:n/a|(?P\w+))\s+\->\s+(?P[^\s]+)\s+(?P(?:\d+|\d+\.\d+))"), + ] + +class Killed(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = [ + re.compile(r"^Killed\s(?P[^\s]+)\s+(?P\w+);\s+killer\s(?P[^\s]+)\s*"), + re.compile(r"^Killed\s(?P[^\(]+)\((?P\w+)\);\s+killer\s(?P[^\s]+)\s*"), + re.compile(r"^Killed\s(?P[^\;]+);\s+killer\s(?P[^\s]+)\s+.*"), + ] + +class Captured(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Captured\s'(?P[^']+)'\(team\s(?P\d+)\)\.(?:\sAttackers\:(?P.*)|.*)") + +class AddStack(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^AddStack\saura\s'(?P\w+)'\sid\s(?P\d+)\stype\s(?P\w+)\.\snew\sstacks\scount\s(?P\d+)") + +class Cancel(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Cancel\saura\s'(?P\w+)'\sid\s(?P\d+)\stype\s(?P\w+)\sfrom\s'(?P[^']*)'") + +class Scores(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Scores\s+-\sTeam1\((?P(?:\d+|\d+\.\d+))\)\sTeam2\((?P(?:\d+|\d+\.\d+))\)") + +class Uncaptured(CombatLog): + """ + Variables: + - objective (which was uncaptured (most likely something like VitalPointXY)) + - team (number) + - attackers (split by space, names of the attackers, contains bots) + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile(r"^Uncaptured\s'(?P[^']+)'\(team\s(?P\d+)\)\.(?:\sAttackers\:\s(?P.*)|)") + +# Special classes +class GameEvent(CombatLog): + __slots__ = CombatLog.__slots__ + matcher = [ + # game session identifier. + re.compile(r"^Connect\sto\sgame\ssession\s+(?P\d+)"), + # start gameplay identifier. + re.compile(r"^Start\sgameplay\s'(?P\w+)'\smap\s+'(?P\w+)',\slocal\sclient\steam\s(?P\d+)"), + # pve mission identifier. + re.compile(r"^Start\sPVE\smission\s'(?P\w+)'\smap\s+'(?P\w+)'"), + ] + + @classmethod + def _log_handler(cls, log): + if log.startswith('======='): + return True + return False + + def unpack(self, force=False): + if self.reviewed and not force: + return True + self._match_id = None + # unpacks the data from the values. + # small override to remove trailing "="s in the matching. + if hasattr(self, 'matcher') and self.matcher: + matchers = self.matcher + if not isinstance(matchers, list): + matchers = [matchers,] + for i, matcher in enumerate(matchers): + m = matcher.match(self.values.get('log', '').strip('=').strip()) + if m: + self.values.update(m.groupdict()) + self._match_id = i + self.reviewed = True + return True + # unknown? + self.trash = True + + def clean(self): + if 'log' in list(self.values.keys()): + del self.values['log'] + +class PVE_Mission(CombatLog): + """ + - mission: contains the mission id. + - message: contains the pve mission message, like starting rounds, waves, etc. + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^PVE_Mission:\s'(?P[^']+)'.\s(?P.*)") # this is very general, but we dont care for pve now. + +class Looted(CombatLog): + """ + called on looting in openspace. + - loot contains the loot id. + - container contains the container looted from. + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^Looted\s'(?P[^']+)'\sfrom\s'(?P[^']+)'") + +class Dropped(CombatLog): + """ + called on dropping in openspace. + - loot contains the loot id. it can be '' + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^Dropped\sloot\s'(?P[^']+)'") + +class Set(CombatLog): + """ + called on setting "relationship" / OpenSpace + Variables in values: + - what (relationship) + - name (who do i set?) + - value (to what value?) + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^Set\s(?P\w+)\s(?P[^\s]+)\sto\s(?P\w+)") + +class SqIdChange(CombatLog): + """ - number: player number + - name: player name + - old_sqid: sqid of player before + - sqid: new player sqid + """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("^Player\s(?P\d+)\((?P[^\)]+)\)\schanged\ssqid\sfrom\s(?P\d+)\sto\s(?P\d+)") + + @classmethod + def _log_handler(cls, log): + if log.startswith('Player'): + return True + return False + +class Mailed(CombatLog): + """ has no information. only that loot has been mailed """ + __slots__ = CombatLog.__slots__ + matcher = re.compile("Mailed\sloot") + +class UserEvent(CombatLog): + """ special class for combat logs that might be associated with the playing player """ + __slots__ = CombatLog.__slots__ + @classmethod + def _log_handler(cls, log): + if log and 'earned medal' in log: + return True + elif log: + logging.debug('UserEvent saw unknown line:\n%s' % log) + return False + +# Action? +COMBAT_LOGS = [ Apply, Damage, Spawn, Spell, Reward, Participant, Rocket, Heal, + Gameplay, #? + Scores, + Killed, Captured, AddStack, Cancel, Uncaptured, + # undone openspace: + PVE_Mission, Looted, Set, Dropped, + SqIdChange, Mailed, # unknown if these are important... + # always last: + GameEvent, UserEvent, + Stacktrace, + ] + diff --git a/src/scon/logs/game.py b/src/scon/logs/game.py index b5052d6..cac5de9 100644 --- a/src/scon/logs/game.py +++ b/src/scon/logs/game.py @@ -1,195 +1,195 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from logs.base import Log, L_WARNING, Stacktrace -import re -""" - This deals with the Game.Log file - This file records lots of junk, but is needed to establish actions taken between combat sessions, - or retrieve more detailed information about running instances. - It is also the typical place for a Stacktrace to happen. - - --------------------------------------- -Interesting Lines: - -23:16:27.427 | Steam initialized appId 212070, userSteamID 1|1|4c5a01, userName 'G4bOrg' -23:16:36.214 | ====== starting level: 'levels/mainmenu/mainmenu' ====== -23:16:38.822 | ====== level started: 'levels/mainmenu/mainmenu' success ====== -23:16:44.251 | ====== starting level: 'levels\mainmenu\mm_empire' ====== -23:16:46.464 | ====== level started: 'levels\mainmenu\mm_empire' success ====== - ---- Date: 2014-07-18 (Fri Jul 2014) Mitteleuropäische Sommerzeit UTC+01:00 - -23:55:55.517 | MasterServerSession: connect to dedicated server, session 6777304, at addr 159.253.138.162|35005 -23:55:55.543 | client: start connecting to 159.253.138.162|35005... -23:55:55.683 | client: connected to 159.253.138.162|35005, setting up session... -23:55:55.886 | client: ADD_PLAYER 0 (OregyenDuero [OWL], 00039C86) status 6 team 1 group 1178422 -23:55:55.886 | client: ADD_PLAYER 1 (R0gue, 0012768A) status 6 team 2 group 1178451 -23:55:55.886 | client: ADD_PLAYER 2 (g4borg [OWL], 0003A848) status 1 team 1 group 1178422 -23:55:55.886 | client: ADD_PLAYER 3 (WladTepes, 001210D8) status 6 team 1 -23:55:55.886 | client: ADD_PLAYER 4 (oberus [], 000FE9B2) status 6 team 2 -23:55:55.886 | client: ADD_PLAYER 5 (TheGuns58, 00121C58) status 6 team 1 -23:55:55.886 | client: ADD_PLAYER 6 (Belleraphon, 0004C744) status 2 team 2 -23:55:55.886 | client: ADD_PLAYER 7 (TopoL, 00007E1F) status 6 team 1 -23:55:55.886 | client: ADD_PLAYER 8 (unicoimbraPT, 000C4FAC) status 6 team 2 -23:55:55.886 | client: ADD_PLAYER 9 (AeroBobik [], 00082047) status 6 team 1 -23:55:55.886 | client: ADD_PLAYER 10 (Samson4321 [], 000B93AF) status 6 team 2 -23:55:55.886 | client: ADD_PLAYER 11 (nol [], 00069165) status 6 team 1 -23:55:55.886 | client: ADD_PLAYER 12 (Pudwoppa, 000334A4) status 2 team 2 -23:55:55.886 | client: ADD_PLAYER 13 (IgorMad [], 000D2AF3) status 6 team 1 -23:55:55.886 | client: ADD_PLAYER 14 (YokaI, 000F1CC9) status 6 team 2 -23:55:55.886 | client: ADD_PLAYER 15 (MrAnyKey [], 0012246C) status 6 team 2 group 1178451 -23:55:55.886 | client: ADD_PLAYER 30 ((bot)David, 00000000) status 4 team 1 -23:55:55.886 | client: ADD_PLAYER 31 ((bot)George, 00000000) status 4 team 2 -23:55:55.886 | client: server assigned id 2 -23:55:55.886 | client: got level load message 's1340_thar_aliendebris13' -23:55:55.889 | reset d3d device -23:55:56.487 | ReplayManager: stopping activity due to map change -23:55:56.576 | ====== starting level: 'levels\area2\s1340_thar_aliendebris13' KingOfTheHill client ====== - - -""" - -class GameLog(Log): - __slots__ = ['matcher', 'trash', '_match_id', 'values'] - @classmethod - def is_handler(cls, log): - if log.get('logtype', None) == '': # we handle only logs with empty logtype. - return cls._is_handler(log) - return False - - @classmethod - def _is_handler(cls, log): - return False - - def __init__(self, values=None): - self.values = values - self.reviewed = False - - def clean(self): - if 'log' in self.values.keys(): - del self.values['log'] - - def unpack(self, force=False): - if self.reviewed and not force: - return True - self._match_id = None - # unpacks the data from the values. - if hasattr(self, 'matcher') and self.matcher: - matchers = self.matcher - if not isinstance(matchers, list): - matchers = [matchers,] - for i, matcher in enumerate(matchers): - m = matcher.match(self.values.get('log', '')) - if m: - self.values.update(m.groupdict()) - self._match_id = i - self.reviewed = True - return True - # unknown? - self.trash = True - - def explain(self): - ''' returns a String readable by humans explaining this Log ''' - return self.values.get('log', 'Unknown Game Log') - -class WarningLog(Log): - __slots__ = ['trash',] - trash = True - - @classmethod - def is_handler(cls, log): - if log.get('logtype', None) == L_WARNING: - return True - return False - - def __init__(self, values=None): - pass - -######################################################################################################## -# Individual logs. - -class SteamInitialization(GameLog): - matcher = [ - re.compile(r"^Steam\sinitialized\sappId\s(?P\d+),\suserSteamID\s(?P\d+)\|(?P\d+)\|(?P\w+),\suserName\s'(?P[^']+)'"), - ] - -class MasterServerSession(GameLog): - matcher = [ - re.compile(r"^MasterServerSession\:\sconnect\sto\sdedicated\sserver(?:,\s|session\s(?P\d+)|at addr (?P\d+\.\d+\.\d+\.\d+)\|(?P\d+))+"), - re.compile(r"^MasterServerSession:\sconnect\sto\sZoneInstance,\ssession\s(?P\d+),\sat\saddr\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+),\szoneId\s(?P\d+)"), - ] - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').startswith('MasterServerSession'): - return True - return False - - -class ClientInfo(GameLog): - # Note: clinfo holds the subtype of this packet. - matcher = [ - # connecting; addr, port - re.compile(r"^client\:\sstart\s(?Pconnecting)\sto\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+)\.\.\."), - # connected; addr, port - re.compile(r"^client\:\s(?Pconnected)\sto\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+).*"), - # ADD_PLAYER; pnr, player, clantag, player_id, status, team, group - re.compile(r"^client\:\s(?PADD_PLAYER)\s+(?P\d+)\s+\((?P[^\s\,]+)(?:\s\[(?P\w+)\],|\s\[\],|,)\s(?P\w+)\)(?:\s|status\s(?P\d+)|team\s(?P\d+)|group\s(?P\d+))+"), - # assigned; myid - re.compile(r"^client\:\sserver\s(?Passigned)\sid\s(?P\d+)"), - # level; level - re.compile(r"^client\:\sgot\s(?Plevel)\sload\smessage\s'(?P[^']+)'"), - # leave; pnr - re.compile(r"^client\:\splayer\s(?P\d+)\s(?Pleave)\sgame"), - # avgPing; avg_ping, avg_packet_loss, avg_snapshot_loss - re.compile(r"^client\:\s(?PavgPing)\s(?P[^;]+)(?:\;|\s|avgPacketLoss\s(?P[^;]+)|avgSnapshotLoss\s(?P[^;$]+))+"), - # closed; dr - re.compile(r"^client\:\sconnection\s(?Pclosed)\.(?:\s|(?P.*))+"), - # disconnect; addr, port, dr - re.compile(r"^client\:\s(?Pdisconnect)\sfrom\sserver\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+)\.(?:\s|(?P\w+))+"), - # ready; - re.compile(r"^client\:\ssend\s(?Pready)\smessage"), - # init; ping - re.compile(r"^client\:\sgot\s(?Pinit)\smessage\s+\(and\s1st\ssnapshot\)\.\sping\s(?P\d+)"), - ] - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').startswith('client:'): - return True - return False - - -class StartingLevel(GameLog): - # level, gametype, unknown_gametype - matcher = [ - re.compile(r"^======\sstarting\slevel\:\s'(?P[^']+)'(?:\s|client|(?PKingOfTheHill)|(?P[^\s]+))+======"), - ] - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').startswith('====== starting'): - return True - return False - - -class LevelStarted(GameLog): - matcher = [] - - @classmethod - def _is_handler(cls, log): - if log.get('log', '').startswith('====== level'): - return True - return False - - - -GAME_LOGS = [#SteamInitialization, - MasterServerSession, - ClientInfo, - StartingLevel, - #LevelStarted, - Stacktrace, +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from logs.base import Log, L_WARNING, Stacktrace +import re +""" + This deals with the Game.Log file + This file records lots of junk, but is needed to establish actions taken between combat sessions, + or retrieve more detailed information about running instances. + It is also the typical place for a Stacktrace to happen. + + +-------------------------------------- +Interesting Lines: + +23:16:27.427 | Steam initialized appId 212070, userSteamID 1|1|4c5a01, userName 'G4bOrg' +23:16:36.214 | ====== starting level: 'levels/mainmenu/mainmenu' ====== +23:16:38.822 | ====== level started: 'levels/mainmenu/mainmenu' success ====== +23:16:44.251 | ====== starting level: 'levels\mainmenu\mm_empire' ====== +23:16:46.464 | ====== level started: 'levels\mainmenu\mm_empire' success ====== + +--- Date: 2014-07-18 (Fri Jul 2014) Mitteleuropäische Sommerzeit UTC+01:00 + +23:55:55.517 | MasterServerSession: connect to dedicated server, session 6777304, at addr 159.253.138.162|35005 +23:55:55.543 | client: start connecting to 159.253.138.162|35005... +23:55:55.683 | client: connected to 159.253.138.162|35005, setting up session... +23:55:55.886 | client: ADD_PLAYER 0 (OregyenDuero [OWL], 00039C86) status 6 team 1 group 1178422 +23:55:55.886 | client: ADD_PLAYER 1 (R0gue, 0012768A) status 6 team 2 group 1178451 +23:55:55.886 | client: ADD_PLAYER 2 (g4borg [OWL], 0003A848) status 1 team 1 group 1178422 +23:55:55.886 | client: ADD_PLAYER 3 (WladTepes, 001210D8) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 4 (oberus [], 000FE9B2) status 6 team 2 +23:55:55.886 | client: ADD_PLAYER 5 (TheGuns58, 00121C58) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 6 (Belleraphon, 0004C744) status 2 team 2 +23:55:55.886 | client: ADD_PLAYER 7 (TopoL, 00007E1F) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 8 (unicoimbraPT, 000C4FAC) status 6 team 2 +23:55:55.886 | client: ADD_PLAYER 9 (AeroBobik [], 00082047) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 10 (Samson4321 [], 000B93AF) status 6 team 2 +23:55:55.886 | client: ADD_PLAYER 11 (nol [], 00069165) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 12 (Pudwoppa, 000334A4) status 2 team 2 +23:55:55.886 | client: ADD_PLAYER 13 (IgorMad [], 000D2AF3) status 6 team 1 +23:55:55.886 | client: ADD_PLAYER 14 (YokaI, 000F1CC9) status 6 team 2 +23:55:55.886 | client: ADD_PLAYER 15 (MrAnyKey [], 0012246C) status 6 team 2 group 1178451 +23:55:55.886 | client: ADD_PLAYER 30 ((bot)David, 00000000) status 4 team 1 +23:55:55.886 | client: ADD_PLAYER 31 ((bot)George, 00000000) status 4 team 2 +23:55:55.886 | client: server assigned id 2 +23:55:55.886 | client: got level load message 's1340_thar_aliendebris13' +23:55:55.889 | reset d3d device +23:55:56.487 | ReplayManager: stopping activity due to map change +23:55:56.576 | ====== starting level: 'levels\area2\s1340_thar_aliendebris13' KingOfTheHill client ====== + + +""" + +class GameLog(Log): + __slots__ = ['matcher', 'trash', '_match_id', 'values'] + @classmethod + def is_handler(cls, log): + if log.get('logtype', None) == '': # we handle only logs with empty logtype. + return cls._is_handler(log) + return False + + @classmethod + def _is_handler(cls, log): + return False + + def __init__(self, values=None): + self.values = values + self.reviewed = False + + def clean(self): + if 'log' in list(self.values.keys()): + del self.values['log'] + + def unpack(self, force=False): + if self.reviewed and not force: + return True + self._match_id = None + # unpacks the data from the values. + if hasattr(self, 'matcher') and self.matcher: + matchers = self.matcher + if not isinstance(matchers, list): + matchers = [matchers,] + for i, matcher in enumerate(matchers): + m = matcher.match(self.values.get('log', '')) + if m: + self.values.update(m.groupdict()) + self._match_id = i + self.reviewed = True + return True + # unknown? + self.trash = True + + def explain(self): + ''' returns a String readable by humans explaining this Log ''' + return self.values.get('log', 'Unknown Game Log') + +class WarningLog(Log): + __slots__ = ['trash',] + trash = True + + @classmethod + def is_handler(cls, log): + if log.get('logtype', None) == L_WARNING: + return True + return False + + def __init__(self, values=None): + pass + +######################################################################################################## +# Individual logs. + +class SteamInitialization(GameLog): + matcher = [ + re.compile(r"^Steam\sinitialized\sappId\s(?P\d+),\suserSteamID\s(?P\d+)\|(?P\d+)\|(?P\w+),\suserName\s'(?P[^']+)'"), + ] + +class MasterServerSession(GameLog): + matcher = [ + re.compile(r"^MasterServerSession\:\sconnect\sto\sdedicated\sserver(?:,\s|session\s(?P\d+)|at addr (?P\d+\.\d+\.\d+\.\d+)\|(?P\d+))+"), + re.compile(r"^MasterServerSession:\sconnect\sto\sZoneInstance,\ssession\s(?P\d+),\sat\saddr\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+),\szoneId\s(?P\d+)"), + ] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').startswith('MasterServerSession'): + return True + return False + + +class ClientInfo(GameLog): + # Note: clinfo holds the subtype of this packet. + matcher = [ + # connecting; addr, port + re.compile(r"^client\:\sstart\s(?Pconnecting)\sto\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+)\.\.\."), + # connected; addr, port + re.compile(r"^client\:\s(?Pconnected)\sto\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+).*"), + # ADD_PLAYER; pnr, player, clantag, player_id, status, team, group + re.compile(r"^client\:\s(?PADD_PLAYER)\s+(?P\d+)\s+\((?P[^\s\,]+)(?:\s\[(?P\w+)\],|\s\[\],|,)\s(?P\w+)\)(?:\s|status\s(?P\d+)|team\s(?P\d+)|group\s(?P\d+))+"), + # assigned; myid + re.compile(r"^client\:\sserver\s(?Passigned)\sid\s(?P\d+)"), + # level; level + re.compile(r"^client\:\sgot\s(?Plevel)\sload\smessage\s'(?P[^']+)'"), + # leave; pnr + re.compile(r"^client\:\splayer\s(?P\d+)\s(?Pleave)\sgame"), + # avgPing; avg_ping, avg_packet_loss, avg_snapshot_loss + re.compile(r"^client\:\s(?PavgPing)\s(?P[^;]+)(?:\;|\s|avgPacketLoss\s(?P[^;]+)|avgSnapshotLoss\s(?P[^;$]+))+"), + # closed; dr + re.compile(r"^client\:\sconnection\s(?Pclosed)\.(?:\s|(?P.*))+"), + # disconnect; addr, port, dr + re.compile(r"^client\:\s(?Pdisconnect)\sfrom\sserver\s(?P\d+\.\d+\.\d+\.\d+)\|(?P\d+)\.(?:\s|(?P\w+))+"), + # ready; + re.compile(r"^client\:\ssend\s(?Pready)\smessage"), + # init; ping + re.compile(r"^client\:\sgot\s(?Pinit)\smessage\s+\(and\s1st\ssnapshot\)\.\sping\s(?P\d+)"), + ] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').startswith('client:'): + return True + return False + + +class StartingLevel(GameLog): + # level, gametype, unknown_gametype + matcher = [ + re.compile(r"^======\sstarting\slevel\:\s'(?P[^']+)'(?:\s|client|(?PKingOfTheHill)|(?P[^\s]+))+======"), + ] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').startswith('====== starting'): + return True + return False + + +class LevelStarted(GameLog): + matcher = [] + + @classmethod + def _is_handler(cls, log): + if log.get('log', '').startswith('====== level'): + return True + return False + + + +GAME_LOGS = [#SteamInitialization, + MasterServerSession, + ClientInfo, + StartingLevel, + #LevelStarted, + Stacktrace, ] \ No newline at end of file diff --git a/src/scon/logs/logfiles.py b/src/scon/logs/logfiles.py index 8b578c1..08f0139 100644 --- a/src/scon/logs/logfiles.py +++ b/src/scon/logs/logfiles.py @@ -1,49 +1,49 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" - Resolves Logs. -""" - -from logfile import LogFile -from combat import COMBAT_LOGS -from game import GAME_LOGS -from chat import CHAT_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 - -class ChatLogFile(LogFile): - ''' Chat Log ''' - def resolve(self, line): - for klass in CHAT_LOGS: - if klass.is_handler(line): - return klass(line) - return line - +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Resolves Logs. +""" + +from .logfile import LogFile +from .combat import COMBAT_LOGS +from .game import GAME_LOGS +from .chat import CHAT_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 + +class ChatLogFile(LogFile): + ''' Chat Log ''' + def resolve(self, line): + for klass in CHAT_LOGS: + if klass.is_handler(line): + return klass(line) + return line + diff --git a/src/scon/logs/logstream.py b/src/scon/logs/logstream.py index bd1a314..4f2419e 100644 --- a/src/scon/logs/logstream.py +++ b/src/scon/logs/logstream.py @@ -1,144 +1,144 @@ -""" - A LogStream is supposed to: - - parse data feeded into it. - - yield new objects - - remember errors - - LogStream.Initialize: - - initialize the logstream in some way. - - LogStream.Next: - - once initialized, read your stream until you can yield a new class - the next function reads the read-stream ahead. - empty lines are omitted - it tries to match the data into a new class and yields it - if it runs into trouble, it just outputs the line for now. - - InitializeString: - - init with a data blob - - nice for trying it on files - - @TODO: look at how file streams in python are implemented and find a good generic solution - combine it with the lookup for "watching files being changed", to create a program which listens to the logs live - @see: monitor.py - @see: watchdog https://pypi.python.org/pypi/watchdog -""" -from .base import Log -import re -from logs.base import Stacktrace -import logging -RE_SCLOG = r'^(?P\d{2,2})\:(?P\d{2,2})\:(?P\d{2,2})\.(?P\d{3,3})\s(?P\s*[^\|\s]+\s*|\s+)\|\s(?P.*)' -R_SCLOG = re.compile(RE_SCLOG) - -class LogStream(object): - def __init__(self): - self.lines = [] - self._data = None - self._last_object = None - - def add_to_queue(self, line): - # adds a line to the queue - pass - - def new_packets(self, finish=False): - # yields new packets. - # processes the queue a bit. - # yields new packets, once they are done. - # watch out not to process the last packet until it has a follow up! - # finish: override and yield all packets to finish. - pass - - ##################################################################### - def has_data(self): - if self._data: - return True - - def set_data(self, data): - self._data = data - - def get_data(self): - return self._data - - def clean(self, remove_log=True): - # cleans the logs by removing all non parsed packets. - # remove_log: should i remove the raw log entry? - lines = [] - for l in self.lines: - if isinstance(l, Log): - if l.unpack(): - if not getattr(l, 'trash', False): - if remove_log: - l.clean() - lines.append(l) - else: - print type(l) - print l - self.lines = lines - self._unset_data() - - data = property(set_data, get_data) - - def _unset_data(self): - self._data = None - - def pre_parse_line(self, line): - if not isinstance(line, basestring): - return line - elif line.startswith('---'): - return None - elif line == '' or line == '\n': - if line == '\n': - logging.debug('Empty Newline detected.') - return None - else: - # get the timecode & logtype - m = R_SCLOG.match(line) - if m: - g = m.groupdict() - if 'logtype' in g.keys(): - g['logtype'] = g['logtype'].strip() - return g - else: - return line - return None - - def _parse_line(self, line): - # add the line to my lines. - if line is not None: - o = line - if isinstance(line, basestring): - # Unknown Log? - if not line: - return - # It might be a stacktrace. inject it./ - if Stacktrace.is_handler(o): - o = Stacktrace(o) - self._last_object = o - else: - #if isinstance(self._last_object, Stacktrace) and line.startswith('\t'): - # logging.debug('Workaround: %s, worked: %s' % (line, self._last_object.append(line))) - # return - if self._last_object is not None: - self._last_object.unpack() - if self._last_object.append(line): - return - logging.debug('#: %s' % line) - o = None - elif isinstance(line, dict): - # Unresolved Log. - o = self.resolve(line) - self._last_object = o - else: - self._last_object = o - if o is None: - self._last_object = None - return - self.lines.append(o) - - def parse_line(self, line): - return self._parse_line(self.pre_parse_line(line)) - - def resolve(self, gd): - # gd is a dict. - # try to find a class that is responsible for this log. - return gd +""" + A LogStream is supposed to: + - parse data feeded into it. + - yield new objects + - remember errors + + LogStream.Initialize: + - initialize the logstream in some way. + + LogStream.Next: + - once initialized, read your stream until you can yield a new class + the next function reads the read-stream ahead. + empty lines are omitted + it tries to match the data into a new class and yields it + if it runs into trouble, it just outputs the line for now. + + InitializeString: + - init with a data blob + - nice for trying it on files + + @TODO: look at how file streams in python are implemented and find a good generic solution + combine it with the lookup for "watching files being changed", to create a program which listens to the logs live + @see: monitor.py + @see: watchdog https://pypi.python.org/pypi/watchdog +""" +from .base import Log +import re +from logs.base import Stacktrace +import logging +RE_SCLOG = r'^(?P\d{2,2})\:(?P\d{2,2})\:(?P\d{2,2})\.(?P\d{3,3})\s(?P\s*[^\|\s]+\s*|\s+)\|\s(?P.*)' +R_SCLOG = re.compile(RE_SCLOG) + +class LogStream(object): + def __init__(self): + self.lines = [] + self._data = None + self._last_object = None + + def add_to_queue(self, line): + # adds a line to the queue + pass + + def new_packets(self, finish=False): + # yields new packets. + # processes the queue a bit. + # yields new packets, once they are done. + # watch out not to process the last packet until it has a follow up! + # finish: override and yield all packets to finish. + pass + + ##################################################################### + def has_data(self): + if self._data: + return True + + def set_data(self, data): + self._data = data + + def get_data(self): + return self._data + + def clean(self, remove_log=True): + # cleans the logs by removing all non parsed packets. + # remove_log: should i remove the raw log entry? + lines = [] + for l in self.lines: + if isinstance(l, Log): + if l.unpack(): + if not getattr(l, 'trash', False): + if remove_log: + l.clean() + lines.append(l) + else: + print((type(l))) + print(l) + self.lines = lines + self._unset_data() + + data = property(set_data, get_data) + + def _unset_data(self): + self._data = None + + def pre_parse_line(self, line): + if not isinstance(line, str): + return line + elif line.startswith('---'): + return None + elif line == '' or line == '\n': + if line == '\n': + logging.debug('Empty Newline detected.') + return None + else: + # get the timecode & logtype + m = R_SCLOG.match(line) + if m: + g = m.groupdict() + if 'logtype' in list(g.keys()): + g['logtype'] = g['logtype'].strip() + return g + else: + return line + return None + + def _parse_line(self, line): + # add the line to my lines. + if line is not None: + o = line + if isinstance(line, str): + # Unknown Log? + if not line: + return + # It might be a stacktrace. inject it./ + if Stacktrace.is_handler(o): + o = Stacktrace(o) + self._last_object = o + else: + #if isinstance(self._last_object, Stacktrace) and line.startswith('\t'): + # logging.debug('Workaround: %s, worked: %s' % (line, self._last_object.append(line))) + # return + if self._last_object is not None: + self._last_object.unpack() + if self._last_object.append(line): + return + logging.debug('#: %s' % line) + o = None + elif isinstance(line, dict): + # Unresolved Log. + o = self.resolve(line) + self._last_object = o + else: + self._last_object = o + if o is None: + self._last_object = None + return + self.lines.append(o) + + def parse_line(self, line): + return self._parse_line(self.pre_parse_line(line)) + + def resolve(self, gd): + # gd is a dict. + # try to find a class that is responsible for this log. + return gd diff --git a/src/scon/logs/session.py b/src/scon/logs/session.py index 3177f6a..bd6a1ca 100644 --- a/src/scon/logs/session.py +++ b/src/scon/logs/session.py @@ -1,209 +1,209 @@ -""" - Logging Session. -""" -import zipfile, logging, os -from logfiles import CombatLogFile, GameLogFile, ChatLogFile - -class LogSession(object): - """ - A basic logsession. - deal with data as it comes along, and output interpretable data for the outside world. - - """ - pass - -class LogFileSession(LogSession): - """ - The Log-File-Session is supposed to save one directory of logs. - It can parse its logs, and should become able to build up its internal - structure into Battle Instances etc. - """ - VALID_FILES = ['combat.log', 'game.log', 'chat.log' ] # extend this to other logs. - - def __init__(self, directory): - ''' if directory is a file, it will be handled as a compressed folder ''' - self.battles = [] - self.user = None - self.files_parsed = [] - - # 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 = None - self.idstr = None # id string to identify this log instance. - self._error = False - - def clean(self, remove_log=True): - if self.combat_log: - self.combat_log.clean(remove_log) - if self.game_log: - self.game_log.clean(remove_log) - if self.chat_log: - self.chat_log.clean(remove_log) - - - def validate(self, contents=False): - """ - - validates if the logfiles are within this package. - - sets the idstr of this object. - @todo: in-depth validation of logs, on contents=True. - """ - self._zip_source = os.path.isfile(self.directory) or False - v = False - try: - if self._zip_source: - v = self._unzip_validate() - if v > 0: - self.idstr = os.path.split(self.directory)[1].replace('.zip', '').lower() - else: - v = self._validate_files_exist() - if v > 0: - self.idstr = os.path.split(self.directory)[1].lower() - except: - return False - return v - - def parse_files(self, files=None): - ''' parses the logfiles ''' - # perform simple validation. - if self._zip_source is None: - self.validate(False) - if self._zip_source: - self._unzip_logs(files) - else: - if files is None: - files = self.VALID_FILES - if 'combat.log' in files and not 'combat.log' in self.files_parsed: - self.combat_log = CombatLogFile(os.path.join(self.directory, 'combat.log')) - self.combat_log.read() - self.combat_log.parse() - self.files_parsed.append('combat.log') - if 'game.log' in files and not 'game.log' in self.files_parsed: - self.game_log = GameLogFile(os.path.join(self.directory, 'game.log')) - self.game_log.read() - self.game_log.parse() - self.files_parsed.append('game.log') - if 'chat.log' in files and not 'chat.log' in self.files_parsed: - self.chat_log = ChatLogFile(os.path.join(self.directory, 'chat.log')) - self.chat_log.read() - self.chat_log.parse() - self.files_parsed.append('chat.log') - - def determine_owner(self): - ''' determines the user in the parsed gamelog ''' - pass - - def parse_battles(self): - ''' parses the battles ''' - pass - - def _unzip_logs(self, files=None): - z = zipfile.ZipFile(self.directory, "r") - try: - for filename in z.namelist(): - fn = os.path.split(filename)[1] or '' - fn = fn.lower() - if fn: - if fn == 'combat.log' and (not files or fn in files) and not 'combat.log' in self.files_parsed: - self.combat_log = CombatLogFile(fn) - self.combat_log.set_data(z.read(filename)) - self.combat_log.parse() - self.files_parsed.append('combat.log') - elif fn == 'game.log' and (not files or fn in files) and not 'game.log' in self.files_parsed: - self.game_log = GameLogFile(fn) - self.game_log.set_data(z.read(filename)) - self.game_log.parse() - self.files_parsed.append('game.log') - elif fn == 'chat.log' and (not files or fn in files) and not 'chat.log' in self.files_parsed: - self.chat_log = ChatLogFile(fn) - self.chat_log.set_data(z.read(filename)) - self.chat_log.parse() - self.files_parsed.append('chat.log') - except: - self._error = True - return - finally: - z.close() - - def _unzip_validate(self): - z = zipfile.ZipFile(self.directory, "r") - found = 0 - for filename in z.namelist(): - fn = os.path.split(filename)[1] or '' - fn = fn.lower() - if fn and fn in self.VALID_FILES: - found += 1 - z.close() - return found - - def _validate_files_exist(self): - found = 0 - for f in self.VALID_FILES: - if os.path.exists(os.path.join(self.directory, f)): - found += 1 - return found - -class LogSessionCollector(object): - """ - finds sessions in a directory, a.k.a. you load the log directories - of SC into sessions. - - - find_sessions: only find and instantiate sessions. - - collect: validates each found session and returns them as list. - - collect_unique: instead of a list, a dict is returned, where each - session can be accessed via its idstr. does not parse/validate sessions. - """ - def __init__(self, directory): - self.initial_directory = directory - self.sessions = [] - self.find_sessions() - - def find_sessions(self): - for f in os.listdir(self.initial_directory): - full_dir = os.path.join(self.initial_directory, f) - if os.path.isdir(full_dir) or full_dir.lower().endswith('.zip'): - self.sessions.append(LogFileSession(full_dir)) - - def collect(self): - sessions = [] - for session in self.sessions: - try: - if session.validate(): - sessions.append(session) - except: - continue - return sessions - - def collect_unique(self): - ''' collects all sessions into a dictionary ordered by their idstr. - sessions without idstr, or already existing (first served) are omitted - parsing is not done. - ''' - # note this function resets sessions to the working ones. - self.sessions = self.collect() - sessions_dict = {} - for session in self.sessions: - if session.idstr and not session.idstr in sessions_dict.keys(): - sessions_dict[session.idstr] = session - return sessions_dict - - def clean(self, remove_log=True): - for session in self.sessions: - session.clean(remove_log) - - - - -if __name__ == '__main__': - l_raw = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28') - l_zip = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip') - - l_zip.parse_files() - print l_zip.combat_log.lines - - collector = LogSessionCollector('D:\\Users\\g4b\\Documents\\My Games\\sc\\') - print collector.collect_unique() \ No newline at end of file +""" + Logging Session. +""" +import zipfile, logging, os +from .logfiles import CombatLogFile, GameLogFile, ChatLogFile + +class LogSession(object): + """ + A basic logsession. + deal with data as it comes along, and output interpretable data for the outside world. + + """ + pass + +class LogFileSession(LogSession): + """ + The Log-File-Session is supposed to save one directory of logs. + It can parse its logs, and should become able to build up its internal + structure into Battle Instances etc. + """ + VALID_FILES = ['combat.log', 'game.log', 'chat.log' ] # extend this to other logs. + + def __init__(self, directory): + ''' if directory is a file, it will be handled as a compressed folder ''' + self.battles = [] + self.user = None + self.files_parsed = [] + + # 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 = None + self.idstr = None # id string to identify this log instance. + self._error = False + + def clean(self, remove_log=True): + if self.combat_log: + self.combat_log.clean(remove_log) + if self.game_log: + self.game_log.clean(remove_log) + if self.chat_log: + self.chat_log.clean(remove_log) + + + def validate(self, contents=False): + """ + - validates if the logfiles are within this package. + - sets the idstr of this object. + @todo: in-depth validation of logs, on contents=True. + """ + self._zip_source = os.path.isfile(self.directory) or False + v = False + try: + if self._zip_source: + v = self._unzip_validate() + if v > 0: + self.idstr = os.path.split(self.directory)[1].replace('.zip', '').lower() + else: + v = self._validate_files_exist() + if v > 0: + self.idstr = os.path.split(self.directory)[1].lower() + except: + return False + return v + + def parse_files(self, files=None): + ''' parses the logfiles ''' + # perform simple validation. + if self._zip_source is None: + self.validate(False) + if self._zip_source: + self._unzip_logs(files) + else: + if files is None: + files = self.VALID_FILES + if 'combat.log' in files and not 'combat.log' in self.files_parsed: + self.combat_log = CombatLogFile(os.path.join(self.directory, 'combat.log')) + self.combat_log.read() + self.combat_log.parse() + self.files_parsed.append('combat.log') + if 'game.log' in files and not 'game.log' in self.files_parsed: + self.game_log = GameLogFile(os.path.join(self.directory, 'game.log')) + self.game_log.read() + self.game_log.parse() + self.files_parsed.append('game.log') + if 'chat.log' in files and not 'chat.log' in self.files_parsed: + self.chat_log = ChatLogFile(os.path.join(self.directory, 'chat.log')) + self.chat_log.read() + self.chat_log.parse() + self.files_parsed.append('chat.log') + + def determine_owner(self): + ''' determines the user in the parsed gamelog ''' + pass + + def parse_battles(self): + ''' parses the battles ''' + pass + + def _unzip_logs(self, files=None): + z = zipfile.ZipFile(self.directory, "r") + try: + for filename in z.namelist(): + fn = os.path.split(filename)[1] or '' + fn = fn.lower() + if fn: + if fn == 'combat.log' and (not files or fn in files) and not 'combat.log' in self.files_parsed: + self.combat_log = CombatLogFile(fn) + self.combat_log.set_data(z.read(filename)) + self.combat_log.parse() + self.files_parsed.append('combat.log') + elif fn == 'game.log' and (not files or fn in files) and not 'game.log' in self.files_parsed: + self.game_log = GameLogFile(fn) + self.game_log.set_data(z.read(filename)) + self.game_log.parse() + self.files_parsed.append('game.log') + elif fn == 'chat.log' and (not files or fn in files) and not 'chat.log' in self.files_parsed: + self.chat_log = ChatLogFile(fn) + self.chat_log.set_data(z.read(filename)) + self.chat_log.parse() + self.files_parsed.append('chat.log') + except: + self._error = True + return + finally: + z.close() + + def _unzip_validate(self): + z = zipfile.ZipFile(self.directory, "r") + found = 0 + for filename in z.namelist(): + fn = os.path.split(filename)[1] or '' + fn = fn.lower() + if fn and fn in self.VALID_FILES: + found += 1 + z.close() + return found + + def _validate_files_exist(self): + found = 0 + for f in self.VALID_FILES: + if os.path.exists(os.path.join(self.directory, f)): + found += 1 + return found + +class LogSessionCollector(object): + """ + finds sessions in a directory, a.k.a. you load the log directories + of SC into sessions. + + - find_sessions: only find and instantiate sessions. + - collect: validates each found session and returns them as list. + - collect_unique: instead of a list, a dict is returned, where each + session can be accessed via its idstr. does not parse/validate sessions. + """ + def __init__(self, directory): + self.initial_directory = directory + self.sessions = [] + self.find_sessions() + + def find_sessions(self): + for f in os.listdir(self.initial_directory): + full_dir = os.path.join(self.initial_directory, f) + if os.path.isdir(full_dir) or full_dir.lower().endswith('.zip'): + self.sessions.append(LogFileSession(full_dir)) + + def collect(self): + sessions = [] + for session in self.sessions: + try: + if session.validate(): + sessions.append(session) + except: + continue + return sessions + + def collect_unique(self): + ''' collects all sessions into a dictionary ordered by their idstr. + sessions without idstr, or already existing (first served) are omitted + parsing is not done. + ''' + # note this function resets sessions to the working ones. + self.sessions = self.collect() + sessions_dict = {} + for session in self.sessions: + if session.idstr and not session.idstr in list(sessions_dict.keys()): + sessions_dict[session.idstr] = session + return sessions_dict + + def clean(self, remove_log=True): + for session in self.sessions: + session.clean(remove_log) + + + + +if __name__ == '__main__': + l_raw = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28') + l_zip = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip') + + l_zip.parse_files() + print((l_zip.combat_log.lines)) + + collector = LogSessionCollector('D:\\Users\\g4b\\Documents\\My Games\\sc\\') + print((collector.collect_unique())) \ No newline at end of file diff --git a/src/scon/monitor.py b/src/scon/monitor.py index e6bc3ca..3673aca 100644 --- a/src/scon/monitor.py +++ b/src/scon/monitor.py @@ -1,161 +1,161 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" - Monitor a StarConflict Log Directory. -""" -import sys, os -import time -import logging -from watchdog.observers import Observer -from watchdog.events import LoggingEventHandler - -class SconEventHandler(LoggingEventHandler): - def __init__(self, monitor, *args, **kwargs): - self.monitor = monitor - return super(SconEventHandler, self).__init__(*args, **kwargs) - - def on_moved(self, event): - super(SconEventHandler, self).on_moved(event) - if not event.is_directory: - self.monitor.close(event.src_path) - self.monitor.notify_event('moved', {'src': event.src_path, - 'is_dir': event.is_directory, - 'dest': event.dest_path}) - - def on_created(self, event): - super(SconEventHandler, self).on_created(event) - if not event.is_directory: - self.monitor.open(event.src_path) - self.monitor.notify_event('created', {'src': event.src_path, - 'is_dir': event.is_directory}) - - def on_deleted(self, event): - super(SconEventHandler, self).on_deleted(event) - if not event.is_directory: - self.monitor.close(event.src_path) - self.monitor.notify_event('deleted', {'src': event.src_path, - 'is_dir': event.is_directory}) - - def on_modified(self, event): - super(SconEventHandler, self).on_modified(event) - self.monitor.notify_event('modified', {'src': event.src_path, - 'is_dir': event.is_directory}) - - - -class SconMonitor(object): - - def notify(self, filename, lines): - # notify somebody, filename has a few lines. - # this is basicly the function you want to overwrite. - # @see: self.run for how to integrate monitor into your main loop. - if self.notifier is not None: - self.notifier.notify(filename, lines) - - def notify_event(self, event_type, data): - if self.notifier is not None: - self.notifier.notify_event(event_type, data) - - def __init__(self, path=None, notifier=None): - # if you initialize path directly, you lose success boolean. - # albeit atm. this is always true. - if path is not None: - self.initialize(path) - self.notifier = notifier - - def initialize(self, path): - # initialize the monitor with a path to observe. - self.files = {} - self.event_handler = SconEventHandler(self) - self.observer = Observer() - self.observer.schedule(self.event_handler, - path, - recursive=True) - return True - - def open(self, filename=None): - # open a logfile and add it to the read-list... - f = open(filename, 'r') - new_file = { 'file': f, - 'cursor': f.tell() } - self.files[filename] = new_file - return new_file - - def close(self, filename): - # close a single file by key, does not do anything if not found. - if filename in self.files.keys(): - close_file = self.files.pop(filename) - close_file['file'].close() - del close_file - - def close_all(self): - """ closes all open files in the monitor """ - for key in self.files.keys(): - self.close(key) - - def read_line(self, afile): - # read a single line in a file. - f = afile.get('file', None) - if f is None: - return - afile['cursor'] = f.tell() - line = f.readline() - if not line: - f.seek(afile['cursor']) - return None - else: - return line - - def do(self): - ''' Monitor main task handler, call this in your mainloop in ~1 sec intervals ''' - # read all file changes. - for key, value in self.files.items(): - lines = [] - data = self.read_line(value) - while data is not None: - lines.append(data) - data = self.read_line(value) - if lines: - self.notify(key, lines) - # - - def initialize_loop(self): - ''' initializes the main loop for the monitor ''' - self.observer.start() - - def break_loop(self): - ''' call this if you want to break the monitors tasks ''' - self.observer.stop() - - def end_loop(self): - ''' always call this before exiting your main loop ''' - self.close_all() - self.observer.join() - - def run(self): - ''' Basic Standalone Main Loop implementation, do not call this in your app. ''' - # if you want to run this on its own. - # everytime any logfile is updated, print it. - self.initialize_loop() - try: - while True: - self.do() - time.sleep(1) - except KeyboardInterrupt: - self.break_loop() - self.end_loop() - -if __name__ == '__main__': - monitor = SconMonitor() - logging.basicConfig(level=logging.INFO, - format='%(asctime)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S') - path = os.path.join(os.path.expanduser('~'), - 'Documents', - 'My Games', - 'StarConflict', - 'logs' - ) - if monitor.initialize(path) is True: - monitor.run() +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" + Monitor a StarConflict Log Directory. +""" +import sys, os +import time +import logging +from watchdog.observers import Observer +from watchdog.events import LoggingEventHandler + +class SconEventHandler(LoggingEventHandler): + def __init__(self, monitor, *args, **kwargs): + self.monitor = monitor + return super(SconEventHandler, self).__init__(*args, **kwargs) + + def on_moved(self, event): + super(SconEventHandler, self).on_moved(event) + if not event.is_directory: + self.monitor.close(event.src_path) + self.monitor.notify_event('moved', {'src': event.src_path, + 'is_dir': event.is_directory, + 'dest': event.dest_path}) + + def on_created(self, event): + super(SconEventHandler, self).on_created(event) + if not event.is_directory: + self.monitor.open(event.src_path) + self.monitor.notify_event('created', {'src': event.src_path, + 'is_dir': event.is_directory}) + + def on_deleted(self, event): + super(SconEventHandler, self).on_deleted(event) + if not event.is_directory: + self.monitor.close(event.src_path) + self.monitor.notify_event('deleted', {'src': event.src_path, + 'is_dir': event.is_directory}) + + def on_modified(self, event): + super(SconEventHandler, self).on_modified(event) + self.monitor.notify_event('modified', {'src': event.src_path, + 'is_dir': event.is_directory}) + + + +class SconMonitor(object): + + def notify(self, filename, lines): + # notify somebody, filename has a few lines. + # this is basicly the function you want to overwrite. + # @see: self.run for how to integrate monitor into your main loop. + if self.notifier is not None: + self.notifier.notify(filename, lines) + + def notify_event(self, event_type, data): + if self.notifier is not None: + self.notifier.notify_event(event_type, data) + + def __init__(self, path=None, notifier=None): + # if you initialize path directly, you lose success boolean. + # albeit atm. this is always true. + if path is not None: + self.initialize(path) + self.notifier = notifier + + def initialize(self, path): + # initialize the monitor with a path to observe. + self.files = {} + self.event_handler = SconEventHandler(self) + self.observer = Observer() + self.observer.schedule(self.event_handler, + path, + recursive=True) + return True + + def open(self, filename=None): + # open a logfile and add it to the read-list... + f = open(filename, 'r') + new_file = { 'file': f, + 'cursor': f.tell() } + self.files[filename] = new_file + return new_file + + def close(self, filename): + # close a single file by key, does not do anything if not found. + if filename in list(self.files.keys()): + close_file = self.files.pop(filename) + close_file['file'].close() + del close_file + + def close_all(self): + """ closes all open files in the monitor """ + for key in list(self.files.keys()): + self.close(key) + + def read_line(self, afile): + # read a single line in a file. + f = afile.get('file', None) + if f is None: + return + afile['cursor'] = f.tell() + line = f.readline() + if not line: + f.seek(afile['cursor']) + return None + else: + return line + + def do(self): + ''' Monitor main task handler, call this in your mainloop in ~1 sec intervals ''' + # read all file changes. + for key, value in list(self.files.items()): + lines = [] + data = self.read_line(value) + while data is not None: + lines.append(data) + data = self.read_line(value) + if lines: + self.notify(key, lines) + # + + def initialize_loop(self): + ''' initializes the main loop for the monitor ''' + self.observer.start() + + def break_loop(self): + ''' call this if you want to break the monitors tasks ''' + self.observer.stop() + + def end_loop(self): + ''' always call this before exiting your main loop ''' + self.close_all() + self.observer.join() + + def run(self): + ''' Basic Standalone Main Loop implementation, do not call this in your app. ''' + # if you want to run this on its own. + # everytime any logfile is updated, print it. + self.initialize_loop() + try: + while True: + self.do() + time.sleep(1) + except KeyboardInterrupt: + self.break_loop() + self.end_loop() + +if __name__ == '__main__': + monitor = SconMonitor() + logging.basicConfig(level=logging.INFO, + format='%(asctime)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + path = os.path.join(os.path.expanduser('~'), + 'Documents', + 'My Games', + 'StarConflict', + 'logs' + ) + if monitor.initialize(path) is True: + monitor.run() \ No newline at end of file diff --git a/src/scon/qscon.py b/src/scon/qscon.py index f02bddb..6914074 100644 --- a/src/scon/qscon.py +++ b/src/scon/qscon.py @@ -1,103 +1,103 @@ -""" - Main Entry Point / Exe File for QScon, the main log handler / monitor / manager app for log files. - -""" -import os, sys, logging -import sys -import urllib2 -from PyQt4 import QtCore, QtGui -from monitor import SconMonitor -from PyQt4.QtCore import QObject, pyqtSignal, pyqtSlot - - -class SconMonitorThread(QtCore.QThread): - updated = pyqtSignal(str, list) - created = pyqtSignal(str, bool) - - def __init__(self, path): - QtCore.QThread.__init__(self) - self.path = path - - def notify(self, filename, lines): - self.updated.emit(filename, lines) - #self.mainwindow.notify_filelines(filename, lines) - #self.list_widget.addItem('%s\n%s' % (filename, ''.join(lines))) - - def notify_event(self, event_type, data): - if event_type == 'created': - self.created.emit(data['src'], data['is_dir']) - - def run(self): - monitor = SconMonitor(self.path, notifier=self) - #self.list_widget.addItem('Starting to monitor: %s' % self.path) - monitor.run() - -class MainWindow(QtGui.QWidget): - def __init__(self): - super(MainWindow, self).__init__() - self.tab_list = QtGui.QTabWidget() - self.tabs = {} - self.button = QtGui.QPushButton("Start") - self.button.clicked.connect(self.start_monitor) - layout = QtGui.QVBoxLayout() - layout.addWidget(self.button) - layout.addWidget(self.tab_list) - self.setLayout(layout) - - - def notify_filelines(self, filename, lines): - if filename not in self.tabs.keys(): - new_tab = QtGui.QWidget() - new_tab.list_widget = QtGui.QListWidget() - layout = QtGui.QVBoxLayout(new_tab) - layout.addWidget(new_tab.list_widget) - self.tabs[filename] = new_tab - self.tab_list.addTab(new_tab, "%s" % os.path.split(str(filename))[-1]) - self.tabs[filename].list_widget.addItem(''.join(lines)[:-1]) - - def notify_created(self, filename, is_directory): - if is_directory: - print "Created Directory %s" % filename - else: - print "Created File %s" % filename - - def start_monitor(self): - self.button.setDisabled(True) - paths = [os.path.join(os.path.expanduser('~'),'Documents','My Games','StarConflict','logs'), - ] - self.threads = [] - for path in paths: - athread = SconMonitorThread(path) - athread.updated.connect(self.notify_filelines) - athread.created.connect(self.notify_created) - self.threads.append(athread) - athread.start() - -######################################################################## - -def _main(): - app = QtGui.QApplication(sys.argv) - window = MainWindow() - window.resize(640, 480) - window.show() - return app.exec_() - -def main(): - r = _main() - try: - import psutil #@UnresolvedImport - except ImportError: - logging.warning('Cannot import PsUtil, terminating without cleaning up threads explicitly.') - sys.exit(r) - - def kill_proc_tree(pid, including_parent=True): - parent = psutil.Process(pid) - if including_parent: - parent.kill() - me = os.getpid() - kill_proc_tree(me) - sys.exit(r) - -if __name__ == "__main__": - main() +""" + Main Entry Point / Exe File for QScon, the main log handler / monitor / manager app for log files. + +""" +import os, sys, logging +import sys +import urllib.request, urllib.error, urllib.parse +from PyQt4 import QtCore, QtGui +from .monitor import SconMonitor +from PyQt4.QtCore import QObject, pyqtSignal, pyqtSlot + + +class SconMonitorThread(QtCore.QThread): + updated = pyqtSignal(str, list) + created = pyqtSignal(str, bool) + + def __init__(self, path): + QtCore.QThread.__init__(self) + self.path = path + + def notify(self, filename, lines): + self.updated.emit(filename, lines) + #self.mainwindow.notify_filelines(filename, lines) + #self.list_widget.addItem('%s\n%s' % (filename, ''.join(lines))) + + def notify_event(self, event_type, data): + if event_type == 'created': + self.created.emit(data['src'], data['is_dir']) + + def run(self): + monitor = SconMonitor(self.path, notifier=self) + #self.list_widget.addItem('Starting to monitor: %s' % self.path) + monitor.run() + +class MainWindow(QtGui.QWidget): + def __init__(self): + super(MainWindow, self).__init__() + self.tab_list = QtGui.QTabWidget() + self.tabs = {} + self.button = QtGui.QPushButton("Start") + self.button.clicked.connect(self.start_monitor) + layout = QtGui.QVBoxLayout() + layout.addWidget(self.button) + layout.addWidget(self.tab_list) + self.setLayout(layout) + + + def notify_filelines(self, filename, lines): + if filename not in list(self.tabs.keys()): + new_tab = QtGui.QWidget() + new_tab.list_widget = QtGui.QListWidget() + layout = QtGui.QVBoxLayout(new_tab) + layout.addWidget(new_tab.list_widget) + self.tabs[filename] = new_tab + self.tab_list.addTab(new_tab, "%s" % os.path.split(str(filename))[-1]) + self.tabs[filename].list_widget.addItem(''.join(lines)[:-1]) + + def notify_created(self, filename, is_directory): + if is_directory: + print(("Created Directory %s" % filename)) + else: + print(("Created File %s" % filename)) + + def start_monitor(self): + self.button.setDisabled(True) + paths = [os.path.join(os.path.expanduser('~'),'Documents','My Games','StarConflict','logs'), + ] + self.threads = [] + for path in paths: + athread = SconMonitorThread(path) + athread.updated.connect(self.notify_filelines) + athread.created.connect(self.notify_created) + self.threads.append(athread) + athread.start() + +######################################################################## + +def _main(): + app = QtGui.QApplication(sys.argv) + window = MainWindow() + window.resize(640, 480) + window.show() + return app.exec_() + +def main(): + r = _main() + try: + import psutil #@UnresolvedImport + except ImportError: + logging.warning('Cannot import PsUtil, terminating without cleaning up threads explicitly.') + sys.exit(r) + + def kill_proc_tree(pid, including_parent=True): + parent = psutil.Process(pid) + if including_parent: + parent.kill() + me = os.getpid() + kill_proc_tree(me) + sys.exit(r) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/scon/test/regexes.py b/src/scon/test/regexes.py index 1f0e104..b491198 100644 --- a/src/scon/test/regexes.py +++ b/src/scon/test/regexes.py @@ -22,4 +22,4 @@ R_SCLOG = re.compile(RE_SCLOG) for line in Lines: m = R_SCLOG.match(line) if m: - print m.groups() + print((m.groups()))