2to3 conversion tool
actual converted files.
This commit is contained in:
parent
288065d066
commit
149fc122d0
@ -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)
|
||||
|
@ -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 '''
|
||||
<html>
|
||||
<head><title>Test</title></head>
|
||||
<body><form method="POST" action=".">
|
||||
<img src="image:///scon/conflict-logo.png">
|
||||
<input type="text" name="a"></input>
|
||||
<input type="text" name="b"></input>
|
||||
<button class="submit">Submit</button></form></body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
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 '''
|
||||
<html>
|
||||
<head><title>No Network Access.</title></head>
|
||||
<body>
|
||||
Internal access to the network has been disabled.
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
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 '''
|
||||
<html>
|
||||
<head><title>Test</title></head>
|
||||
<body><form method="POST" action=".">
|
||||
<img src="image:///scon/conflict-logo.png">
|
||||
<input type="text" name="a"></input>
|
||||
<input type="text" name="b"></input>
|
||||
<button class="submit">Submit</button></form></body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
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 '''
|
||||
<html>
|
||||
<head><title>No Network Access.</title></head>
|
||||
<body>
|
||||
Internal access to the network has been disabled.
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
#!/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)))
|
@ -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
|
||||
"""
|
||||
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)
|
@ -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 = '<?xml version="1.0"?>\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</%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 = '<?xml version="1.0"?>\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</%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()))
|
||||
|
@ -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()
|
||||
|
@ -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')))
|
||||
|
@ -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 '''
|
||||
<html>
|
||||
<head><title>Empty Page</title></head>
|
||||
<body>This is an empty page. If you see this, you need to subclass BasePageReply.</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
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 '''
|
||||
<html>
|
||||
<head><title>No Network Access.</title></head>
|
||||
<body>
|
||||
Internal access to the network has been disabled.
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
"""
|
||||
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 '''
|
||||
<html>
|
||||
<head><title>Empty Page</title></head>
|
||||
<body>This is an empty page. If you see this, you need to subclass BasePageReply.</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
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 '''
|
||||
<html>
|
||||
<head><title>No Network Access.</title></head>
|
||||
<body>
|
||||
Internal access to the network has been disabled.
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
|
@ -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)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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')
|
||||
|
@ -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()
|
||||
|
||||
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()))
|
@ -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 <https://github.com/cloudformdesign>
|
||||
# For more information see <cloudformdesign.com>
|
||||
#
|
||||
# This module may be a part of a python package, and may be out of date.
|
||||
# This behavior is intentional, do NOT update it.
|
||||
#
|
||||
# You are encouraged to use this pacakge, or any code snippets in it, in
|
||||
# your own projects. Hopefully they will be helpful to you!
|
||||
#
|
||||
# This project is Licenced under The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2013 Garrett Berg cloudformdesign.com
|
||||
# An updated version of this file can be found at:
|
||||
# <https://github.com/cloudformdesign/cloudtb>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the "Software"),
|
||||
# to deal in the Software without restriction, including without limitation
|
||||
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
# and/or sell copies of the Software, and to permit persons to whom the
|
||||
# Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# http://opensource.org/licenses/MIT
|
||||
|
||||
# import pdb
|
||||
import os
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import sys
|
||||
#import icons_rc
|
||||
|
||||
|
||||
# from cloudtb import dbe
|
||||
|
||||
class Node(object):
|
||||
'''A general node stucture to be used in treeview
|
||||
the attrib_dict can store any information your overall treeview
|
||||
needs it to store.
|
||||
'''
|
||||
def __init__(self, name, parent=None, icon = None,
|
||||
attrib_dict = None):
|
||||
|
||||
self._name = name
|
||||
self._attrib = attrib_dict
|
||||
self._children = []
|
||||
self._parent = parent
|
||||
self.icon = icon
|
||||
|
||||
if parent is not None:
|
||||
parent.addChild(self)
|
||||
|
||||
def addChild(self, child):
|
||||
self._children.append(child)
|
||||
|
||||
def insertChild(self, position, child):
|
||||
|
||||
if position < 0 or position > len(self._children):
|
||||
return False
|
||||
|
||||
self._children.insert(position, child)
|
||||
child._parent = self
|
||||
return True
|
||||
|
||||
def removeChild(self, position):
|
||||
|
||||
if position < 0 or position > len(self._children):
|
||||
return False
|
||||
child = self._children.pop(position)
|
||||
child._parent = None
|
||||
|
||||
return True
|
||||
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
def setName(self, name):
|
||||
self._name = name
|
||||
|
||||
def child(self, row):
|
||||
return self._children[row]
|
||||
|
||||
def childCount(self):
|
||||
return len(self._children)
|
||||
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
def row(self):
|
||||
if self._parent is not None:
|
||||
return self._parent._children.index(self)
|
||||
|
||||
|
||||
def log(self, tabLevel=-1):
|
||||
|
||||
output = ""
|
||||
tabLevel += 1
|
||||
|
||||
for i in range(tabLevel):
|
||||
output += " "
|
||||
|
||||
output += "|-" + self._name + "\n"
|
||||
|
||||
for child in self._children:
|
||||
output += child.log(tabLevel)
|
||||
|
||||
tabLevel -= 1
|
||||
# output += "\n"
|
||||
|
||||
return output
|
||||
|
||||
def __repr__(self):
|
||||
return self.log()
|
||||
|
||||
class TreeViewModel(QtCore.QAbstractItemModel):
|
||||
"""INPUTS: Node, QObject"""
|
||||
def __init__(self, root, parent=None, header_title = None):
|
||||
super(TreeViewModel, self).__init__(parent)
|
||||
self.is_editable = False
|
||||
self.is_selectable = True
|
||||
self.is_enabled = True
|
||||
self.set_flags()
|
||||
self._rootNode = root
|
||||
self.header_title = header_title
|
||||
|
||||
# The following are created functions called in "data" -- which is a
|
||||
# Qt defined funciton. This way of doing things is FAR more pythonic
|
||||
# and allows classes to inherit this one and not have to rewrite the
|
||||
# entire data method
|
||||
# all of them recieve an index and a node
|
||||
|
||||
def role_display(self, index, node):
|
||||
if index.column() == 0:
|
||||
return node.name()
|
||||
|
||||
def role_edit(self, index, node):
|
||||
return self.role_display(index, node)
|
||||
|
||||
def role_tool_tip(self, index, node):
|
||||
return
|
||||
|
||||
def role_check_state(self, index, node):
|
||||
return
|
||||
|
||||
def role_decoration(self, index, node):
|
||||
if index.column() == 0:
|
||||
icon = node.icon
|
||||
if icon == None:
|
||||
return False
|
||||
else:
|
||||
return icon
|
||||
|
||||
def role_flags(self, index, node):
|
||||
'''While not technically a "role" it behaves in much the same way.
|
||||
This method is called by the "flags" method for all indexes with
|
||||
a node'''
|
||||
return self.BASE_FLAGS
|
||||
|
||||
def set_flags(self, is_editable = None, is_selectable = None,
|
||||
is_enabled = None):
|
||||
''' Sets new flags to the BASE_FLAGS variable'''
|
||||
if is_editable != None:
|
||||
self.is_editable = is_editable
|
||||
if is_selectable != None:
|
||||
self.is_selectable = is_selectable
|
||||
if is_enabled != None:
|
||||
self.is_enabled = is_enabled
|
||||
self.BASE_FLAGS = QtCore.Qt.ItemFlags(
|
||||
(QtCore.Qt.ItemIsEnabled * bool(self.is_enabled))
|
||||
| (QtCore.Qt.ItemIsSelectable * bool(self.is_selectable))
|
||||
| (QtCore.Qt.ItemIsEditable * bool(self.is_editable))
|
||||
)
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: int"""
|
||||
def rowCount(self, parent):
|
||||
if not parent.isValid():
|
||||
parentNode = self._rootNode
|
||||
else:
|
||||
parentNode = parent.internalPointer()
|
||||
|
||||
return parentNode.childCount()
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: int"""
|
||||
def columnCount(self, parent):
|
||||
return 1
|
||||
|
||||
"""INPUTS: QModelIndex, int"""
|
||||
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
|
||||
def data(self, index, role):
|
||||
'''index is an object that contains a pointer to the item inside
|
||||
internPointer(). Note that this was set during the insertRows
|
||||
method call, so you don't need to track them!
|
||||
'''
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
node = index.internalPointer()
|
||||
|
||||
if role == QtCore.Qt.EditRole:
|
||||
return self.role_edit(index, node)
|
||||
|
||||
if role == QtCore.Qt.ToolTipRole:
|
||||
return self.role_tool_tip(index, node)
|
||||
|
||||
if role == QtCore.Qt.CheckStateRole:
|
||||
return self.role_check_state(index, node)
|
||||
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
return self.role_display(index, node)
|
||||
|
||||
if role == QtCore.Qt.DecorationRole:
|
||||
return self.role_decoration(index, node)
|
||||
|
||||
"""INPUTS: QModelIndex, QVariant, int (flag)"""
|
||||
def setData(self, index, value, role=QtCore.Qt.EditRole):
|
||||
if index.isValid():
|
||||
if role == QtCore.Qt.EditRole:
|
||||
node = index.internalPointer()
|
||||
node.setName(value)
|
||||
return True
|
||||
return False
|
||||
|
||||
"""INPUTS: int, Qt::Orientation, int"""
|
||||
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
|
||||
def headerData(self, section, orientation, role):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if self.header_title != None:
|
||||
return self.header_title
|
||||
if section == 0:
|
||||
return "Scenegraph"
|
||||
else:
|
||||
return "Typeinfo"
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: int (flag)"""
|
||||
# def flags(self, index):
|
||||
# return (QtCore.Qt.ItemIsEnabled |
|
||||
# QtCore.Qt.ItemIsSelectable #|
|
||||
## QtCore.Qt.ItemIsEditable
|
||||
# )
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return self.BASE_FLAGS
|
||||
|
||||
node = index.internalPointer()
|
||||
return self.role_flags(index, node)
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: QModelIndex"""
|
||||
"""Should return the parent of the node with the given QModelIndex"""
|
||||
def parent(self, index):
|
||||
node = self.getNode(index)
|
||||
parentNode = node.parent()
|
||||
|
||||
if parentNode == self._rootNode:
|
||||
return QtCore.QModelIndex()
|
||||
return self.createIndex(parentNode.row(), 0, parentNode)
|
||||
|
||||
"""INPUTS: int, int, QModelIndex"""
|
||||
"""OUTPUT: QModelIndex"""
|
||||
"""Should return a QModelIndex that corresponds to the given row,
|
||||
column and parent node"""
|
||||
def index(self, row, column, parent):
|
||||
# This is how Qt creates the nested (tree) list. It knows how many
|
||||
# rows it has because of insertRows, and it uses index and
|
||||
# createIndex to build the tree.
|
||||
# print 'Index called', row, column
|
||||
parentNode = self.getNode(parent)
|
||||
childItem = parentNode.child(row)
|
||||
if childItem:
|
||||
return self.createIndex(row, column, childItem)
|
||||
else:
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
"""CUSTOM"""
|
||||
"""INPUTS: QModelIndex"""
|
||||
def getNode(self, index):
|
||||
if index.isValid():
|
||||
node = index.internalPointer()
|
||||
if node:
|
||||
return node
|
||||
|
||||
return self._rootNode
|
||||
|
||||
|
||||
"""INPUTS: int, List of Nodes, QModelIndex"""
|
||||
def insertRows(self, position, rows, parent=QtCore.QModelIndex()):
|
||||
parentNode = self.getNode(parent)
|
||||
|
||||
self.beginInsertRows(parent, position, position + len(rows) - 1)
|
||||
|
||||
for i, row in enumerate(rows):
|
||||
# childCount = parentNode.childCount()
|
||||
childNode = row
|
||||
success = parentNode.insertChild(position + i, childNode)
|
||||
|
||||
self.endInsertRows()
|
||||
|
||||
return success
|
||||
|
||||
"""INPUTS: int, int, QModelIndex"""
|
||||
def removeRows(self, position, rows, parent=QtCore.QModelIndex()):
|
||||
if rows == 0:
|
||||
return
|
||||
|
||||
parentNode = self.getNode(parent)
|
||||
self.beginRemoveRows(parent, position, position + rows - 1)
|
||||
|
||||
for row in range(rows):
|
||||
success = parentNode.removeChild(position)
|
||||
# TODO: break if not success?
|
||||
|
||||
self.endRemoveRows()
|
||||
|
||||
return success
|
||||
|
||||
def clear_rows(self):
|
||||
return self.removeRows(0, self._rootNode.childCount())
|
||||
|
||||
# TODO: doesn't work. Not sure how to get icons
|
||||
ICON_FOLDER = QtGui.QIcon.fromTheme('folder')
|
||||
|
||||
def _node_compare(a, b):
|
||||
return b.isdir - a.isdir
|
||||
|
||||
def get_file_folder_node(fdata, parent):
|
||||
'''return the node structure of the data.
|
||||
[[(dir_name, path),
|
||||
[dir_name, path),
|
||||
[(file, path),
|
||||
(file, path)]]
|
||||
]
|
||||
]
|
||||
'''
|
||||
# TODO: set icons correctly
|
||||
|
||||
nodes = []
|
||||
for fobj in fdata:
|
||||
path = fobj[0]
|
||||
name = os.path.split(path)[1]
|
||||
|
||||
if len(fobj) == 1:
|
||||
fileobj = Node(name, parent = parent, icon = None)
|
||||
fileobj.full_path = path
|
||||
fileobj.isdir = False
|
||||
nodes.append(fileobj)
|
||||
continue
|
||||
folderobj = Node(name, parent = parent, icon = ICON_FOLDER,
|
||||
)
|
||||
folderobj.full_path = path
|
||||
folderobj.isdir = True
|
||||
|
||||
get_file_folder_node(fobj[1], parent = folderobj)
|
||||
nodes.append(folderobj)
|
||||
nodes.sort(cmp = _node_compare)
|
||||
return nodes
|
||||
import itertools
|
||||
|
||||
def _get_filelist_nodes(iter_file_list, dir_path = ''):
|
||||
'''Takes a sorted file list iterator and returns the files in a
|
||||
format that can be converted'''
|
||||
files = []
|
||||
dir_path = os.path.join(dir_path, '') # Put into directory syntax
|
||||
len_dp = len(dir_path)
|
||||
while True:
|
||||
try:
|
||||
fpath = next(iter_file_list)
|
||||
except StopIteration:
|
||||
break
|
||||
if dir_path != fpath[:len_dp]:
|
||||
iter_file_list = itertools.chain((fpath,), iter_file_list)
|
||||
break
|
||||
|
||||
if os.path.isdir(fpath):
|
||||
iter_file_list, new_files = _get_filelist_nodes(iter_file_list,
|
||||
dir_path = fpath)
|
||||
files.append((fpath, new_files))
|
||||
else:
|
||||
files.append((fpath,))
|
||||
return iter_file_list, files
|
||||
|
||||
def get_filelist_nodes(file_list, parent = None):
|
||||
file_list = sorted(file_list)
|
||||
file_tuples = _get_filelist_nodes(iter(file_list))[1]
|
||||
return get_file_folder_node(file_tuples, parent)
|
||||
|
||||
def dev_show_file_list(file_objects):
|
||||
'''For developemnet'''
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
rootNode = Node("Rootdir")
|
||||
model = TreeViewModel(rootNode)
|
||||
|
||||
treeView = QtGui.QTreeView()
|
||||
treeView.show()
|
||||
|
||||
treeView.setModel(model)
|
||||
model.insertRows(0, file_objects, QtCore.QModelIndex())
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from pprint import pprint
|
||||
|
||||
files = '''/home/user/Projects/Learning/LearningQt/LearningQt.pro.user
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt.pro
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.h
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.cpp
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.pri
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt64.png
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt_harmattan.desktop
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt.svg
|
||||
/home/user/Projects/Learning/LearningQt/main.cpp
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt.desktop
|
||||
/home/user/Projects/Learning/LearningQt/qml/LearningQt/main.qml
|
||||
/home/user/Projects/Learning/LearningQt/qml/LearningQt
|
||||
/home/user/Projects/Learning/LearningQt/qml
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt80.png'''
|
||||
nodes = get_filelist_nodes(files.split('\n'))
|
||||
for n in nodes:
|
||||
print n
|
||||
dev_show_file_list(nodes)
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
## FROM:
|
||||
# https://github.com/cloudformdesign/cloudtb/blob/master/extra/PyQt/treeview.py
|
||||
# ****** The Cloud Toolbox v0.1.2******
|
||||
# This is the cloud toolbox -- a single module used in several packages
|
||||
# found at <https://github.com/cloudformdesign>
|
||||
# For more information see <cloudformdesign.com>
|
||||
#
|
||||
# This module may be a part of a python package, and may be out of date.
|
||||
# This behavior is intentional, do NOT update it.
|
||||
#
|
||||
# You are encouraged to use this pacakge, or any code snippets in it, in
|
||||
# your own projects. Hopefully they will be helpful to you!
|
||||
#
|
||||
# This project is Licenced under The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2013 Garrett Berg cloudformdesign.com
|
||||
# An updated version of this file can be found at:
|
||||
# <https://github.com/cloudformdesign/cloudtb>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the "Software"),
|
||||
# to deal in the Software without restriction, including without limitation
|
||||
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
# and/or sell copies of the Software, and to permit persons to whom the
|
||||
# Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# http://opensource.org/licenses/MIT
|
||||
|
||||
# import pdb
|
||||
import os
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import sys
|
||||
#import icons_rc
|
||||
|
||||
|
||||
# from cloudtb import dbe
|
||||
|
||||
class Node(object):
|
||||
'''A general node stucture to be used in treeview
|
||||
the attrib_dict can store any information your overall treeview
|
||||
needs it to store.
|
||||
'''
|
||||
def __init__(self, name, parent=None, icon = None,
|
||||
attrib_dict = None):
|
||||
|
||||
self._name = name
|
||||
self._attrib = attrib_dict
|
||||
self._children = []
|
||||
self._parent = parent
|
||||
self.icon = icon
|
||||
|
||||
if parent is not None:
|
||||
parent.addChild(self)
|
||||
|
||||
def addChild(self, child):
|
||||
self._children.append(child)
|
||||
|
||||
def insertChild(self, position, child):
|
||||
|
||||
if position < 0 or position > len(self._children):
|
||||
return False
|
||||
|
||||
self._children.insert(position, child)
|
||||
child._parent = self
|
||||
return True
|
||||
|
||||
def removeChild(self, position):
|
||||
|
||||
if position < 0 or position > len(self._children):
|
||||
return False
|
||||
child = self._children.pop(position)
|
||||
child._parent = None
|
||||
|
||||
return True
|
||||
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
def setName(self, name):
|
||||
self._name = name
|
||||
|
||||
def child(self, row):
|
||||
return self._children[row]
|
||||
|
||||
def childCount(self):
|
||||
return len(self._children)
|
||||
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
def row(self):
|
||||
if self._parent is not None:
|
||||
return self._parent._children.index(self)
|
||||
|
||||
|
||||
def log(self, tabLevel=-1):
|
||||
|
||||
output = ""
|
||||
tabLevel += 1
|
||||
|
||||
for i in range(tabLevel):
|
||||
output += " "
|
||||
|
||||
output += "|-" + self._name + "\n"
|
||||
|
||||
for child in self._children:
|
||||
output += child.log(tabLevel)
|
||||
|
||||
tabLevel -= 1
|
||||
# output += "\n"
|
||||
|
||||
return output
|
||||
|
||||
def __repr__(self):
|
||||
return self.log()
|
||||
|
||||
class TreeViewModel(QtCore.QAbstractItemModel):
|
||||
"""INPUTS: Node, QObject"""
|
||||
def __init__(self, root, parent=None, header_title = None):
|
||||
super(TreeViewModel, self).__init__(parent)
|
||||
self.is_editable = False
|
||||
self.is_selectable = True
|
||||
self.is_enabled = True
|
||||
self.set_flags()
|
||||
self._rootNode = root
|
||||
self.header_title = header_title
|
||||
|
||||
# The following are created functions called in "data" -- which is a
|
||||
# Qt defined funciton. This way of doing things is FAR more pythonic
|
||||
# and allows classes to inherit this one and not have to rewrite the
|
||||
# entire data method
|
||||
# all of them recieve an index and a node
|
||||
|
||||
def role_display(self, index, node):
|
||||
if index.column() == 0:
|
||||
return node.name()
|
||||
|
||||
def role_edit(self, index, node):
|
||||
return self.role_display(index, node)
|
||||
|
||||
def role_tool_tip(self, index, node):
|
||||
return
|
||||
|
||||
def role_check_state(self, index, node):
|
||||
return
|
||||
|
||||
def role_decoration(self, index, node):
|
||||
if index.column() == 0:
|
||||
icon = node.icon
|
||||
if icon == None:
|
||||
return False
|
||||
else:
|
||||
return icon
|
||||
|
||||
def role_flags(self, index, node):
|
||||
'''While not technically a "role" it behaves in much the same way.
|
||||
This method is called by the "flags" method for all indexes with
|
||||
a node'''
|
||||
return self.BASE_FLAGS
|
||||
|
||||
def set_flags(self, is_editable = None, is_selectable = None,
|
||||
is_enabled = None):
|
||||
''' Sets new flags to the BASE_FLAGS variable'''
|
||||
if is_editable != None:
|
||||
self.is_editable = is_editable
|
||||
if is_selectable != None:
|
||||
self.is_selectable = is_selectable
|
||||
if is_enabled != None:
|
||||
self.is_enabled = is_enabled
|
||||
self.BASE_FLAGS = QtCore.Qt.ItemFlags(
|
||||
(QtCore.Qt.ItemIsEnabled * bool(self.is_enabled))
|
||||
| (QtCore.Qt.ItemIsSelectable * bool(self.is_selectable))
|
||||
| (QtCore.Qt.ItemIsEditable * bool(self.is_editable))
|
||||
)
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: int"""
|
||||
def rowCount(self, parent):
|
||||
if not parent.isValid():
|
||||
parentNode = self._rootNode
|
||||
else:
|
||||
parentNode = parent.internalPointer()
|
||||
|
||||
return parentNode.childCount()
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: int"""
|
||||
def columnCount(self, parent):
|
||||
return 1
|
||||
|
||||
"""INPUTS: QModelIndex, int"""
|
||||
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
|
||||
def data(self, index, role):
|
||||
'''index is an object that contains a pointer to the item inside
|
||||
internPointer(). Note that this was set during the insertRows
|
||||
method call, so you don't need to track them!
|
||||
'''
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
node = index.internalPointer()
|
||||
|
||||
if role == QtCore.Qt.EditRole:
|
||||
return self.role_edit(index, node)
|
||||
|
||||
if role == QtCore.Qt.ToolTipRole:
|
||||
return self.role_tool_tip(index, node)
|
||||
|
||||
if role == QtCore.Qt.CheckStateRole:
|
||||
return self.role_check_state(index, node)
|
||||
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
return self.role_display(index, node)
|
||||
|
||||
if role == QtCore.Qt.DecorationRole:
|
||||
return self.role_decoration(index, node)
|
||||
|
||||
"""INPUTS: QModelIndex, QVariant, int (flag)"""
|
||||
def setData(self, index, value, role=QtCore.Qt.EditRole):
|
||||
if index.isValid():
|
||||
if role == QtCore.Qt.EditRole:
|
||||
node = index.internalPointer()
|
||||
node.setName(value)
|
||||
return True
|
||||
return False
|
||||
|
||||
"""INPUTS: int, Qt::Orientation, int"""
|
||||
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
|
||||
def headerData(self, section, orientation, role):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if self.header_title != None:
|
||||
return self.header_title
|
||||
if section == 0:
|
||||
return "Scenegraph"
|
||||
else:
|
||||
return "Typeinfo"
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: int (flag)"""
|
||||
# def flags(self, index):
|
||||
# return (QtCore.Qt.ItemIsEnabled |
|
||||
# QtCore.Qt.ItemIsSelectable #|
|
||||
## QtCore.Qt.ItemIsEditable
|
||||
# )
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return self.BASE_FLAGS
|
||||
|
||||
node = index.internalPointer()
|
||||
return self.role_flags(index, node)
|
||||
|
||||
"""INPUTS: QModelIndex"""
|
||||
"""OUTPUT: QModelIndex"""
|
||||
"""Should return the parent of the node with the given QModelIndex"""
|
||||
def parent(self, index):
|
||||
node = self.getNode(index)
|
||||
parentNode = node.parent()
|
||||
|
||||
if parentNode == self._rootNode:
|
||||
return QtCore.QModelIndex()
|
||||
return self.createIndex(parentNode.row(), 0, parentNode)
|
||||
|
||||
"""INPUTS: int, int, QModelIndex"""
|
||||
"""OUTPUT: QModelIndex"""
|
||||
"""Should return a QModelIndex that corresponds to the given row,
|
||||
column and parent node"""
|
||||
def index(self, row, column, parent):
|
||||
# This is how Qt creates the nested (tree) list. It knows how many
|
||||
# rows it has because of insertRows, and it uses index and
|
||||
# createIndex to build the tree.
|
||||
# print 'Index called', row, column
|
||||
parentNode = self.getNode(parent)
|
||||
childItem = parentNode.child(row)
|
||||
if childItem:
|
||||
return self.createIndex(row, column, childItem)
|
||||
else:
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
"""CUSTOM"""
|
||||
"""INPUTS: QModelIndex"""
|
||||
def getNode(self, index):
|
||||
if index.isValid():
|
||||
node = index.internalPointer()
|
||||
if node:
|
||||
return node
|
||||
|
||||
return self._rootNode
|
||||
|
||||
|
||||
"""INPUTS: int, List of Nodes, QModelIndex"""
|
||||
def insertRows(self, position, rows, parent=QtCore.QModelIndex()):
|
||||
parentNode = self.getNode(parent)
|
||||
|
||||
self.beginInsertRows(parent, position, position + len(rows) - 1)
|
||||
|
||||
for i, row in enumerate(rows):
|
||||
# childCount = parentNode.childCount()
|
||||
childNode = row
|
||||
success = parentNode.insertChild(position + i, childNode)
|
||||
|
||||
self.endInsertRows()
|
||||
|
||||
return success
|
||||
|
||||
"""INPUTS: int, int, QModelIndex"""
|
||||
def removeRows(self, position, rows, parent=QtCore.QModelIndex()):
|
||||
if rows == 0:
|
||||
return
|
||||
|
||||
parentNode = self.getNode(parent)
|
||||
self.beginRemoveRows(parent, position, position + rows - 1)
|
||||
|
||||
for row in range(rows):
|
||||
success = parentNode.removeChild(position)
|
||||
# TODO: break if not success?
|
||||
|
||||
self.endRemoveRows()
|
||||
|
||||
return success
|
||||
|
||||
def clear_rows(self):
|
||||
return self.removeRows(0, self._rootNode.childCount())
|
||||
|
||||
# TODO: doesn't work. Not sure how to get icons
|
||||
ICON_FOLDER = QtGui.QIcon.fromTheme('folder')
|
||||
|
||||
def _node_compare(a, b):
|
||||
return b.isdir - a.isdir
|
||||
|
||||
def get_file_folder_node(fdata, parent):
|
||||
'''return the node structure of the data.
|
||||
[[(dir_name, path),
|
||||
[dir_name, path),
|
||||
[(file, path),
|
||||
(file, path)]]
|
||||
]
|
||||
]
|
||||
'''
|
||||
# TODO: set icons correctly
|
||||
|
||||
nodes = []
|
||||
for fobj in fdata:
|
||||
path = fobj[0]
|
||||
name = os.path.split(path)[1]
|
||||
|
||||
if len(fobj) == 1:
|
||||
fileobj = Node(name, parent = parent, icon = None)
|
||||
fileobj.full_path = path
|
||||
fileobj.isdir = False
|
||||
nodes.append(fileobj)
|
||||
continue
|
||||
folderobj = Node(name, parent = parent, icon = ICON_FOLDER,
|
||||
)
|
||||
folderobj.full_path = path
|
||||
folderobj.isdir = True
|
||||
|
||||
get_file_folder_node(fobj[1], parent = folderobj)
|
||||
nodes.append(folderobj)
|
||||
nodes.sort(cmp = _node_compare)
|
||||
return nodes
|
||||
import itertools
|
||||
|
||||
def _get_filelist_nodes(iter_file_list, dir_path = ''):
|
||||
'''Takes a sorted file list iterator and returns the files in a
|
||||
format that can be converted'''
|
||||
files = []
|
||||
dir_path = os.path.join(dir_path, '') # Put into directory syntax
|
||||
len_dp = len(dir_path)
|
||||
while True:
|
||||
try:
|
||||
fpath = next(iter_file_list)
|
||||
except StopIteration:
|
||||
break
|
||||
if dir_path != fpath[:len_dp]:
|
||||
iter_file_list = itertools.chain((fpath,), iter_file_list)
|
||||
break
|
||||
|
||||
if os.path.isdir(fpath):
|
||||
iter_file_list, new_files = _get_filelist_nodes(iter_file_list,
|
||||
dir_path = fpath)
|
||||
files.append((fpath, new_files))
|
||||
else:
|
||||
files.append((fpath,))
|
||||
return iter_file_list, files
|
||||
|
||||
def get_filelist_nodes(file_list, parent = None):
|
||||
file_list = sorted(file_list)
|
||||
file_tuples = _get_filelist_nodes(iter(file_list))[1]
|
||||
return get_file_folder_node(file_tuples, parent)
|
||||
|
||||
def dev_show_file_list(file_objects):
|
||||
'''For developemnet'''
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
rootNode = Node("Rootdir")
|
||||
model = TreeViewModel(rootNode)
|
||||
|
||||
treeView = QtGui.QTreeView()
|
||||
treeView.show()
|
||||
|
||||
treeView.setModel(model)
|
||||
model.insertRows(0, file_objects, QtCore.QModelIndex())
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from pprint import pprint
|
||||
|
||||
files = '''/home/user/Projects/Learning/LearningQt/LearningQt.pro.user
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt.pro
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.h
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.cpp
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.pri
|
||||
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt64.png
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt_harmattan.desktop
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt.svg
|
||||
/home/user/Projects/Learning/LearningQt/main.cpp
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt.desktop
|
||||
/home/user/Projects/Learning/LearningQt/qml/LearningQt/main.qml
|
||||
/home/user/Projects/Learning/LearningQt/qml/LearningQt
|
||||
/home/user/Projects/Learning/LearningQt/qml
|
||||
/home/user/Projects/Learning/LearningQt/LearningQt80.png'''
|
||||
nodes = get_filelist_nodes(files.split('\n'))
|
||||
for n in nodes:
|
||||
print(n)
|
||||
dev_show_file_list(nodes)
|
||||
|
@ -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):
|
||||
|
@ -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
|
@ -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<message>.*)")
|
||||
|
||||
@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<nickname>[^\]]+)\]\s(?P<message>.*)")
|
||||
|
||||
@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<nickname>[^\]]+)\]\s(?P<message>.*)")
|
||||
|
||||
@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<channel>[^>]+)>\[\s*(?P<nickname>[^\]]+)\]\s(?P<message>.*)")
|
||||
|
||||
@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<channel>[^>]+)>")
|
||||
|
||||
@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<channel>[^>]+)>")
|
||||
|
||||
@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<message>.*)")
|
||||
|
||||
@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<nickname>[^\]]+)\]\s(?P<message>.*)")
|
||||
|
||||
@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<nickname>[^\]]+)\]\s(?P<message>.*)")
|
||||
|
||||
@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<channel>[^>]+)>\[\s*(?P<nickname>[^\]]+)\]\s(?P<message>.*)")
|
||||
|
||||
@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<channel>[^>]+)>")
|
||||
|
||||
@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<channel>[^>]+)>")
|
||||
|
||||
@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,
|
||||
]
|
||||
|
@ -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<winner_team>\d+)\((?P<winner_reason>\w+)\)\.\sFinish\sreason\:\s'(?P<reason_verbose>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
|
||||
# team, unexplained reason (unknown, Timeout)
|
||||
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+).\sFinish\sreason\:\s'(?P<winner_reason>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
|
||||
]
|
||||
|
||||
class Apply(CombatLog): # Apply Aura.
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Apply\saura\s'(?P<aura_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<aura_type>\w+)\sto\s'(?P<target_name>[^\']+)'")
|
||||
|
||||
class Damage(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Damage\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))(?:\s(?P<module_class>[^\s]+)\s|\s{2,2})(?P<flags>(?:\w|\|)+)")
|
||||
|
||||
class Spawn(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P<player>\d+)\s\((?P<name>[^,]+),\s+(?P<hash>#\w+)\)\.\s+'(?P<ship_class>\w+)'")
|
||||
|
||||
class Spell(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Spell\s'(?P<spell_name>\w+)'\sby\s+(?P<source_name>.*)(?:\((?P<module_name>\w+)\)|)\stargets\((?P<target_num>\d+)\)\:(?:\s(?P<targets>.+)|\s*)")
|
||||
|
||||
class Reward(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = [
|
||||
# ordinary reward:
|
||||
re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)(?P<amount>\d+)\s(?P<reward_type>.*)\s+for\s(?P<reward_reason>.*)"),
|
||||
# openspace reward (karma):
|
||||
re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)\s+(?P<karma>[\+\-]\d+)\skarma\spoints\s+for\s(?P<reward_reason>.*)"),
|
||||
]
|
||||
|
||||
class Participant(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^\s+Participant\s+(?P<source_name>[^\s]+)(?:\s+(?P<ship_class>\w+)|\s{30,})\s+(?:totalDamage\s(?P<total_damage>(?:\d+|\d+\.\d+));\s+|\s+)(?:mostDamageWith\s'(?P<module_class>[^']+)';\s*(?P<additional>.*)|<(?P<other>\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(?P<event>launch|detonation)\.\sowner\s'(?P<name>[^']+)'(?:,\s(?:def\s'(?P<missile_type>\w+)'|target\s'(?P<target>[^']+)'|reason\s'(?P<reason>\w+)'|directHit\s'(?P<direct_hit>[^']+)'))+")
|
||||
|
||||
class Heal(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = [
|
||||
# heal by module
|
||||
re.compile(r"^Heal\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))\s(?P<module_class>[^\s]+)"),
|
||||
# direct heal by source or n/a (global buff)
|
||||
re.compile(r"^Heal\s+(?:n/a|(?P<source_name>\w+))\s+\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))"),
|
||||
]
|
||||
|
||||
class Killed(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = [
|
||||
re.compile(r"^Killed\s(?P<target_name>[^\s]+)\s+(?P<ship_class>\w+);\s+killer\s(?P<source_name>[^\s]+)\s*"),
|
||||
re.compile(r"^Killed\s(?P<object>[^\(]+)\((?P<target_name>\w+)\);\s+killer\s(?P<source_name>[^\s]+)\s*"),
|
||||
re.compile(r"^Killed\s(?P<object>[^\;]+);\s+killer\s(?P<source_name>[^\s]+)\s+.*"),
|
||||
]
|
||||
|
||||
class Captured(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Captured\s'(?P<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:(?P<attackers>.*)|.*)")
|
||||
|
||||
class AddStack(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^AddStack\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\.\snew\sstacks\scount\s(?P<stack_count>\d+)")
|
||||
|
||||
class Cancel(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Cancel\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\sfrom\s'(?P<source_name>[^']*)'")
|
||||
|
||||
class Scores(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Scores\s+-\sTeam1\((?P<team1_score>(?:\d+|\d+\.\d+))\)\sTeam2\((?P<team2_score>(?:\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<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:\s(?P<attackers>.*)|)")
|
||||
|
||||
# Special classes
|
||||
class GameEvent(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = [
|
||||
# game session identifier.
|
||||
re.compile(r"^Connect\sto\sgame\ssession\s+(?P<game_session>\d+)"),
|
||||
# start gameplay identifier.
|
||||
re.compile(r"^Start\sgameplay\s'(?P<gameplay_name>\w+)'\smap\s+'(?P<map_id>\w+)',\slocal\sclient\steam\s(?P<local_team>\d+)"),
|
||||
# pve mission identifier.
|
||||
re.compile(r"^Start\sPVE\smission\s'(?P<pve_name>\w+)'\smap\s+'(?P<map_id>\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<mission>[^']+)'.\s(?P<message>.*)") # 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<loot>[^']+)'\sfrom\s'(?P<container>[^']+)'")
|
||||
|
||||
class Dropped(CombatLog):
|
||||
"""
|
||||
called on dropping in openspace.
|
||||
- loot contains the loot id. it can be '<all>'
|
||||
"""
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile("^Dropped\sloot\s'(?P<loot>[^']+)'")
|
||||
|
||||
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<what>\w+)\s(?P<name>[^\s]+)\sto\s(?P<value>\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<number>\d+)\((?P<name>[^\)]+)\)\schanged\ssqid\sfrom\s(?P<old_sqid>\d+)\sto\s(?P<sqid>\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<winner_team>\d+)\((?P<winner_reason>\w+)\)\.\sFinish\sreason\:\s'(?P<reason_verbose>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
|
||||
# team, unexplained reason (unknown, Timeout)
|
||||
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+).\sFinish\sreason\:\s'(?P<winner_reason>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
|
||||
]
|
||||
|
||||
class Apply(CombatLog): # Apply Aura.
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Apply\saura\s'(?P<aura_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<aura_type>\w+)\sto\s'(?P<target_name>[^\']+)'")
|
||||
|
||||
class Damage(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Damage\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))(?:\s(?P<module_class>[^\s]+)\s|\s{2,2})(?P<flags>(?:\w|\|)+)")
|
||||
|
||||
class Spawn(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P<player>\d+)\s\((?P<name>[^,]+),\s+(?P<hash>#\w+)\)\.\s+'(?P<ship_class>\w+)'")
|
||||
|
||||
class Spell(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Spell\s'(?P<spell_name>\w+)'\sby\s+(?P<source_name>.*)(?:\((?P<module_name>\w+)\)|)\stargets\((?P<target_num>\d+)\)\:(?:\s(?P<targets>.+)|\s*)")
|
||||
|
||||
class Reward(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = [
|
||||
# ordinary reward:
|
||||
re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)(?P<amount>\d+)\s(?P<reward_type>.*)\s+for\s(?P<reward_reason>.*)"),
|
||||
# openspace reward (karma):
|
||||
re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)\s+(?P<karma>[\+\-]\d+)\skarma\spoints\s+for\s(?P<reward_reason>.*)"),
|
||||
]
|
||||
|
||||
class Participant(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^\s+Participant\s+(?P<source_name>[^\s]+)(?:\s+(?P<ship_class>\w+)|\s{30,})\s+(?:totalDamage\s(?P<total_damage>(?:\d+|\d+\.\d+));\s+|\s+)(?:mostDamageWith\s'(?P<module_class>[^']+)';\s*(?P<additional>.*)|<(?P<other>\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(?P<event>launch|detonation)\.\sowner\s'(?P<name>[^']+)'(?:,\s(?:def\s'(?P<missile_type>\w+)'|target\s'(?P<target>[^']+)'|reason\s'(?P<reason>\w+)'|directHit\s'(?P<direct_hit>[^']+)'))+")
|
||||
|
||||
class Heal(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = [
|
||||
# heal by module
|
||||
re.compile(r"^Heal\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))\s(?P<module_class>[^\s]+)"),
|
||||
# direct heal by source or n/a (global buff)
|
||||
re.compile(r"^Heal\s+(?:n/a|(?P<source_name>\w+))\s+\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))"),
|
||||
]
|
||||
|
||||
class Killed(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = [
|
||||
re.compile(r"^Killed\s(?P<target_name>[^\s]+)\s+(?P<ship_class>\w+);\s+killer\s(?P<source_name>[^\s]+)\s*"),
|
||||
re.compile(r"^Killed\s(?P<object>[^\(]+)\((?P<target_name>\w+)\);\s+killer\s(?P<source_name>[^\s]+)\s*"),
|
||||
re.compile(r"^Killed\s(?P<object>[^\;]+);\s+killer\s(?P<source_name>[^\s]+)\s+.*"),
|
||||
]
|
||||
|
||||
class Captured(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Captured\s'(?P<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:(?P<attackers>.*)|.*)")
|
||||
|
||||
class AddStack(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^AddStack\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\.\snew\sstacks\scount\s(?P<stack_count>\d+)")
|
||||
|
||||
class Cancel(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Cancel\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\sfrom\s'(?P<source_name>[^']*)'")
|
||||
|
||||
class Scores(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile(r"^Scores\s+-\sTeam1\((?P<team1_score>(?:\d+|\d+\.\d+))\)\sTeam2\((?P<team2_score>(?:\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<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:\s(?P<attackers>.*)|)")
|
||||
|
||||
# Special classes
|
||||
class GameEvent(CombatLog):
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = [
|
||||
# game session identifier.
|
||||
re.compile(r"^Connect\sto\sgame\ssession\s+(?P<game_session>\d+)"),
|
||||
# start gameplay identifier.
|
||||
re.compile(r"^Start\sgameplay\s'(?P<gameplay_name>\w+)'\smap\s+'(?P<map_id>\w+)',\slocal\sclient\steam\s(?P<local_team>\d+)"),
|
||||
# pve mission identifier.
|
||||
re.compile(r"^Start\sPVE\smission\s'(?P<pve_name>\w+)'\smap\s+'(?P<map_id>\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<mission>[^']+)'.\s(?P<message>.*)") # 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<loot>[^']+)'\sfrom\s'(?P<container>[^']+)'")
|
||||
|
||||
class Dropped(CombatLog):
|
||||
"""
|
||||
called on dropping in openspace.
|
||||
- loot contains the loot id. it can be '<all>'
|
||||
"""
|
||||
__slots__ = CombatLog.__slots__
|
||||
matcher = re.compile("^Dropped\sloot\s'(?P<loot>[^']+)'")
|
||||
|
||||
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<what>\w+)\s(?P<name>[^\s]+)\sto\s(?P<value>\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<number>\d+)\((?P<name>[^\)]+)\)\schanged\ssqid\sfrom\s(?P<old_sqid>\d+)\sto\s(?P<sqid>\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,
|
||||
]
|
||||
|
||||
|
@ -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<steam_app_id>\d+),\suserSteamID\s(?P<steam_id_universe>\d+)\|(?P<steam_id_type>\d+)\|(?P<steam_id_account_hex>\w+),\suserName\s'(?P<steam_username>[^']+)'"),
|
||||
]
|
||||
|
||||
class MasterServerSession(GameLog):
|
||||
matcher = [
|
||||
re.compile(r"^MasterServerSession\:\sconnect\sto\sdedicated\sserver(?:,\s|session\s(?P<session_id>\d+)|at addr (?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+))+"),
|
||||
re.compile(r"^MasterServerSession:\sconnect\sto\sZoneInstance,\ssession\s(?P<session_id>\d+),\sat\saddr\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+),\szoneId\s(?P<zone_id>\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(?P<clinfo>connecting)\sto\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+)\.\.\."),
|
||||
# connected; addr, port
|
||||
re.compile(r"^client\:\s(?P<clinfo>connected)\sto\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+).*"),
|
||||
# ADD_PLAYER; pnr, player, clantag, player_id, status, team, group
|
||||
re.compile(r"^client\:\s(?P<clinfo>ADD_PLAYER)\s+(?P<pnr>\d+)\s+\((?P<player>[^\s\,]+)(?:\s\[(?P<clantag>\w+)\],|\s\[\],|,)\s(?P<player_id>\w+)\)(?:\s|status\s(?P<status>\d+)|team\s(?P<team>\d+)|group\s(?P<group>\d+))+"),
|
||||
# assigned; myid
|
||||
re.compile(r"^client\:\sserver\s(?P<clinfo>assigned)\sid\s(?P<myid>\d+)"),
|
||||
# level; level
|
||||
re.compile(r"^client\:\sgot\s(?P<clinfo>level)\sload\smessage\s'(?P<level>[^']+)'"),
|
||||
# leave; pnr
|
||||
re.compile(r"^client\:\splayer\s(?P<pnr>\d+)\s(?P<clinfo>leave)\sgame"),
|
||||
# avgPing; avg_ping, avg_packet_loss, avg_snapshot_loss
|
||||
re.compile(r"^client\:\s(?P<clinfo>avgPing)\s(?P<avg_ping>[^;]+)(?:\;|\s|avgPacketLoss\s(?P<avg_packet_loss>[^;]+)|avgSnapshotLoss\s(?P<avg_snapshot_loss>[^;$]+))+"),
|
||||
# closed; dr
|
||||
re.compile(r"^client\:\sconnection\s(?P<clinfo>closed)\.(?:\s|(?P<dr>.*))+"),
|
||||
# disconnect; addr, port, dr
|
||||
re.compile(r"^client\:\s(?P<clinfo>disconnect)\sfrom\sserver\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+)\.(?:\s|(?P<dr>\w+))+"),
|
||||
# ready;
|
||||
re.compile(r"^client\:\ssend\s(?P<clinfo>ready)\smessage"),
|
||||
# init; ping
|
||||
re.compile(r"^client\:\sgot\s(?P<clinfo>init)\smessage\s+\(and\s1st\ssnapshot\)\.\sping\s(?P<ping>\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<level>[^']+)'(?:\s|client|(?P<gametype>KingOfTheHill)|(?P<unknown_gametype>[^\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<steam_app_id>\d+),\suserSteamID\s(?P<steam_id_universe>\d+)\|(?P<steam_id_type>\d+)\|(?P<steam_id_account_hex>\w+),\suserName\s'(?P<steam_username>[^']+)'"),
|
||||
]
|
||||
|
||||
class MasterServerSession(GameLog):
|
||||
matcher = [
|
||||
re.compile(r"^MasterServerSession\:\sconnect\sto\sdedicated\sserver(?:,\s|session\s(?P<session_id>\d+)|at addr (?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+))+"),
|
||||
re.compile(r"^MasterServerSession:\sconnect\sto\sZoneInstance,\ssession\s(?P<session_id>\d+),\sat\saddr\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+),\szoneId\s(?P<zone_id>\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(?P<clinfo>connecting)\sto\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+)\.\.\."),
|
||||
# connected; addr, port
|
||||
re.compile(r"^client\:\s(?P<clinfo>connected)\sto\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+).*"),
|
||||
# ADD_PLAYER; pnr, player, clantag, player_id, status, team, group
|
||||
re.compile(r"^client\:\s(?P<clinfo>ADD_PLAYER)\s+(?P<pnr>\d+)\s+\((?P<player>[^\s\,]+)(?:\s\[(?P<clantag>\w+)\],|\s\[\],|,)\s(?P<player_id>\w+)\)(?:\s|status\s(?P<status>\d+)|team\s(?P<team>\d+)|group\s(?P<group>\d+))+"),
|
||||
# assigned; myid
|
||||
re.compile(r"^client\:\sserver\s(?P<clinfo>assigned)\sid\s(?P<myid>\d+)"),
|
||||
# level; level
|
||||
re.compile(r"^client\:\sgot\s(?P<clinfo>level)\sload\smessage\s'(?P<level>[^']+)'"),
|
||||
# leave; pnr
|
||||
re.compile(r"^client\:\splayer\s(?P<pnr>\d+)\s(?P<clinfo>leave)\sgame"),
|
||||
# avgPing; avg_ping, avg_packet_loss, avg_snapshot_loss
|
||||
re.compile(r"^client\:\s(?P<clinfo>avgPing)\s(?P<avg_ping>[^;]+)(?:\;|\s|avgPacketLoss\s(?P<avg_packet_loss>[^;]+)|avgSnapshotLoss\s(?P<avg_snapshot_loss>[^;$]+))+"),
|
||||
# closed; dr
|
||||
re.compile(r"^client\:\sconnection\s(?P<clinfo>closed)\.(?:\s|(?P<dr>.*))+"),
|
||||
# disconnect; addr, port, dr
|
||||
re.compile(r"^client\:\s(?P<clinfo>disconnect)\sfrom\sserver\s(?P<addr>\d+\.\d+\.\d+\.\d+)\|(?P<port>\d+)\.(?:\s|(?P<dr>\w+))+"),
|
||||
# ready;
|
||||
re.compile(r"^client\:\ssend\s(?P<clinfo>ready)\smessage"),
|
||||
# init; ping
|
||||
re.compile(r"^client\:\sgot\s(?P<clinfo>init)\smessage\s+\(and\s1st\ssnapshot\)\.\sping\s(?P<ping>\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<level>[^']+)'(?:\s|client|(?P<gametype>KingOfTheHill)|(?P<unknown_gametype>[^\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,
|
||||
]
|
@ -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
|
||||
|
||||
|
@ -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<hh>\d{2,2})\:(?P<mm>\d{2,2})\:(?P<ss>\d{2,2})\.(?P<ns>\d{3,3})\s(?P<logtype>\s*[^\|\s]+\s*|\s+)\|\s(?P<log>.*)'
|
||||
R_SCLOG = re.compile(RE_SCLOG)
|
||||
|
||||
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<hh>\d{2,2})\:(?P<mm>\d{2,2})\:(?P<ss>\d{2,2})\.(?P<ns>\d{3,3})\s(?P<logtype>\s*[^\|\s]+\s*|\s+)\|\s(?P<log>.*)'
|
||||
R_SCLOG = re.compile(RE_SCLOG)
|
||||
|
||||
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
|
||||
|
@ -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()
|
||||
"""
|
||||
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()))
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()))
|
||||
|
Loading…
Reference in New Issue
Block a user