diff --git a/src/scon/analyze.py b/src/scon/analyze.py
index 43f92ed..f0438d6 100644
--- a/src/scon/analyze.py
+++ b/src/scon/analyze.py
@@ -1,90 +1,90 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-"""
-    Tool to analyze Logs in general.
-"""
-import os, sys, logging
-from logs.logfiles import LogFileResolver as LogFile
-from logs import combat, game, chat
-from logs.session import LogSessionCollector
-from logs.game import ClientInfo
-
-# for windows its kinda this:
-settings = {'root_path': os.path.join(os.path.expanduser('~'),
-                                     'Documents',
-                                     'My Games',
-                                     'StarConflict',),            
-            'logfiles': os.path.join(os.path.expanduser('~'),
-                                     'Documents',
-                                     'My Games',
-                                     'StarConflict',
-                                     'logs'
-                                     )}
-
-
-if __name__ == '__main__':
-    import logging
-    logging.basicConfig(level=logging.DEBUG,
-                        format='%(asctime)s - %(message)s',
-                        datefmt='%Y-%m-%d %H:%M:%S')
-    coll = LogSessionCollector(os.path.join(os.path.expanduser('~'),
-                        'Documents', 'My Games', 'sc'))
-    coll.collect_unique()
-    #f = open('output.txt', 'w')
-    rex_combat = {}
-    rex_game = {}
-    rex_chat = {}
-    LOG_GOOD = True # Log good packets.
-    for logf in coll.sessions:
-        logf.parse_files(['game.log', 'combat.log', 'chat.log'])
-        
-        print "----- Log %s -----" % logf.idstr
-        if logf.combat_log:
-            for l in logf.combat_log.lines:
-                if isinstance(l, dict):
-                    #print l
-                    rex_combat['dict'] = rex_combat.get('dict', 0) + 1
-                else:
-                    if not l.unpack() or LOG_GOOD:
-                        rex_combat[l.__class__.__name__] = rex_combat.get(l.__class__.__name__, 0) + 1
-                        if not isinstance(l, combat.UserEvent):
-                            if not LOG_GOOD:
-                                print l.values['log']
-        if logf.game_log:
-            for l in logf.game_log.lines:
-                if isinstance(l, dict):
-                    rex_game['dict'] = rex_game.get('dict', 0) + 1 
-                elif isinstance(l, str):
-                    print l
-                else:
-                    if l.unpack() and not LOG_GOOD:
-                        pass
-                    else:
-                        rex_game[l.__class__.__name__] = rex_game.get(l.__class__.__name__, 0) + 1
-                        if not LOG_GOOD:
-                            print l.values['log']
-        if logf.chat_log:
-            for l in logf.chat_log.lines:
-                if isinstance(l, dict):
-                    rex_chat['dict'] = rex_chat.get('dict', 0) + 1 
-                elif isinstance(l, str):
-                    print l
-                else:
-                    if l.unpack() and not LOG_GOOD:
-                        pass
-                    else:
-                        rex_chat[l.__class__.__name__] = rex_chat.get(l.__class__.__name__, 0) + 1
-                        if not LOG_GOOD:
-                            print l.values['log']
-        logf.clean(True)
-        # additional cleanup:
-        logf.chat_log.lines = []
-        logf.game_log.lines = []
-        logf.combat_log.lines = []
-    print 'Analysis complete:'
-    print '#'*20+' RexCombat ' + '#' *20
-    print rex_combat
-    print '#'*20+' RexGame ' + '#' *20
-    print rex_game
-    print '#'*20+' RexChat ' + '#' *20
-    print rex_chat
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+    Tool to analyze Logs in general.
+"""
+import os, sys, logging
+from .logs.logfiles import LogFileResolver as LogFile
+from .logs import combat, game, chat
+from .logs.session import LogSessionCollector
+from .logs.game import ClientInfo
+
+# for windows its kinda this:
+settings = {'root_path': os.path.join(os.path.expanduser('~'),
+                                     'Documents',
+                                     'My Games',
+                                     'StarConflict',),            
+            'logfiles': os.path.join(os.path.expanduser('~'),
+                                     'Documents',
+                                     'My Games',
+                                     'StarConflict',
+                                     'logs'
+                                     )}
+
+
+if __name__ == '__main__':
+    import logging
+    logging.basicConfig(level=logging.DEBUG,
+                        format='%(asctime)s - %(message)s',
+                        datefmt='%Y-%m-%d %H:%M:%S')
+    coll = LogSessionCollector(os.path.join(os.path.expanduser('~'),
+                        'Documents', 'My Games', 'sc'))
+    coll.collect_unique()
+    #f = open('output.txt', 'w')
+    rex_combat = {}
+    rex_game = {}
+    rex_chat = {}
+    LOG_GOOD = True # Log good packets.
+    for logf in coll.sessions:
+        logf.parse_files(['game.log', 'combat.log', 'chat.log'])
+        
+        print(("----- Log %s -----" % logf.idstr))
+        if logf.combat_log:
+            for l in logf.combat_log.lines:
+                if isinstance(l, dict):
+                    #print l
+                    rex_combat['dict'] = rex_combat.get('dict', 0) + 1
+                else:
+                    if not l.unpack() or LOG_GOOD:
+                        rex_combat[l.__class__.__name__] = rex_combat.get(l.__class__.__name__, 0) + 1
+                        if not isinstance(l, combat.UserEvent):
+                            if not LOG_GOOD:
+                                print((l.values['log']))
+        if logf.game_log:
+            for l in logf.game_log.lines:
+                if isinstance(l, dict):
+                    rex_game['dict'] = rex_game.get('dict', 0) + 1 
+                elif isinstance(l, str):
+                    print(l)
+                else:
+                    if l.unpack() and not LOG_GOOD:
+                        pass
+                    else:
+                        rex_game[l.__class__.__name__] = rex_game.get(l.__class__.__name__, 0) + 1
+                        if not LOG_GOOD:
+                            print((l.values['log']))
+        if logf.chat_log:
+            for l in logf.chat_log.lines:
+                if isinstance(l, dict):
+                    rex_chat['dict'] = rex_chat.get('dict', 0) + 1 
+                elif isinstance(l, str):
+                    print(l)
+                else:
+                    if l.unpack() and not LOG_GOOD:
+                        pass
+                    else:
+                        rex_chat[l.__class__.__name__] = rex_chat.get(l.__class__.__name__, 0) + 1
+                        if not LOG_GOOD:
+                            print((l.values['log']))
+        logf.clean(True)
+        # additional cleanup:
+        logf.chat_log.lines = []
+        logf.game_log.lines = []
+        logf.combat_log.lines = []
+    print('Analysis complete:')
+    print(('#'*20+' RexCombat ' + '#' *20))
+    print(rex_combat)
+    print(('#'*20+' RexGame ' + '#' *20))
+    print(rex_game)
+    print(('#'*20+' RexChat ' + '#' *20))
+    print(rex_chat)
diff --git a/src/scon/archive/localbrowser.py b/src/scon/archive/localbrowser.py
index 31a995b..ba51a03 100644
--- a/src/scon/archive/localbrowser.py
+++ b/src/scon/archive/localbrowser.py
@@ -1,142 +1,142 @@
-import os, logging
-from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork
-from treeview import TreeViewModel, Node
-from django.test import Client
-
-class DebugPage(QtWebKit.QWebPage):
-    def sayMyName(self):
-        return 'DebugPage'
-
-class LocalWebView(QtWebKit.QWebView):
-    def __init__(self, *args, **kwargs):
-        basedir = kwargs.pop('basedir', None)
-        QtWebKit.QWebView.__init__(self, *args, **kwargs)
-        oldManager = self.page().networkAccessManager()
-        self.setPage(DebugPage())
-        self.page().setNetworkAccessManager(LocalNetworkAccessManager(self, basedir))
-    
-    def set_basedir(self, basedir):
-        self.page().setNetworkAccessManager(LocalNetworkAccessManager(self, basedir))    
-
-class LocalNetworkAccessManager(QtNetwork.QNetworkAccessManager):
-    USE_NETWORK = False
-    def __init__(self, parent=None, basedir=None):
-        QtNetwork.QNetworkAccessManager.__init__(self, parent=None)
-        if not basedir:
-            # take current dir as basedir.
-            self.basedir = os.path.dirname(os.path.abspath(__file__))
-        else:
-            self.basedir = basedir
-
-    def createRequest(self, operation, request, data):
-        scheme = request.url().scheme()
-        if scheme != 'page' and scheme != 'image':
-            if self.USE_NETWORK:
-                return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data)
-        elif scheme == 'page':
-            if operation == self.GetOperation:
-                # Handle page:// URLs separately by creating custom
-                # QNetworkReply objects.
-                reply = PageReply(self, request.url(), self.GetOperation)
-                #print('here')
-                #print reply
-                return reply
-            elif operation == self.PostOperation:
-                #print data.readAll()
-                #print request
-                reply = PageReply(self, request.url(), self.PostOperation)
-                return reply
-        elif scheme == 'image':
-            if operation == self.GetOperation:
-                return ImageReply(self, request.url(), self.GetOperation, self.basedir)
-        else:
-            if self.USE_NETWORK:
-                return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data)
-        return NoNetworkReply(self, request.url(), self.GetOperation)
-
-class BasePageReply(QtNetwork.QNetworkReply):
-    content_type = 'text/html; charset=utf-8'
-    def __init__(self, parent, url, operation):
-        QtNetwork.QNetworkReply.__init__(self, parent)
-        self.content = self.initialize_content(url, operation)
-        self.offset = 0
-        self.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, self.get_content_type())
-        self.setHeader(QtNetwork.QNetworkRequest.ContentLengthHeader, len(self.content))
-        QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('readyRead()'))
-        QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('finished()'))
-        self.open(self.ReadOnly | self.Unbuffered)
-        self.setUrl(url)
-    
-    def get_content_type(self):
-        return self.content_type
-    
-    def initialize_content(self, url, operation):        
-        return '''
-            <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()
         
\ No newline at end of file
diff --git a/src/scon/backup.py b/src/scon/backup.py
index 29bb191..5ee21be 100644
--- a/src/scon/backup.py
+++ b/src/scon/backup.py
@@ -1,71 +1,71 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-"""
-    Backup Directories, Handle Files...
-"""
-
-import os, logging, zipfile
-
-def make_zipfile(output_filename, source_dir):
-    relroot = os.path.abspath(os.path.join(source_dir, os.pardir))
-    with zipfile.ZipFile(output_filename, "w", zipfile.ZIP_DEFLATED) as zip:
-        for root, dirs, files in os.walk(source_dir):
-            # add directory (needed for empty dirs)
-            zip.write(root, os.path.relpath(root, relroot))
-            for file in files:
-                filename = os.path.join(root, file)
-                if os.path.isfile(filename): # regular files only
-                    arcname = os.path.join(os.path.relpath(root, relroot), file)
-                    zip.write(filename, arcname)
-                    
-def backup_log_directory(log_directory, backup_directory, compress=True,
-                         ommit_level=2, verbose=False):
-    # @todo: raw copy
-    # ommit_level 0: overwrite.
-    # ommit_level 1: write if selected compression method not backuped yet
-    # ommit_level 2: write only if neither method contains directory.
-    nothing_found = True
-    # get all directory names in log_directory.
-    # zip them into backup_directory
-    for directory in os.listdir(log_directory):
-        full_dir = os.path.join(log_directory, directory)
-        nothing_found = False
-        if os.path.isdir(full_dir):
-            if  os.path.exists(os.path.join(full_dir, 'combat.log'))\
-            and os.path.exists(os.path.join(full_dir, 'game.log'))\
-            and os.path.exists(os.path.join(full_dir, 'chat.log'))\
-            and os.path.exists(os.path.join(full_dir, 'game.net.log')):
-                output_filename = '%s.zip' % directory
-                if os.path.exists(os.path.join(backup_directory, output_filename))\
-                    and ((ommit_level >= 1 and compress) or (ommit_level==2 and not compress)):
-                    logging.warning('Log %s exists as zip backup, ommited.' % output_filename)
-                elif os.path.exists(os.path.join(backup_directory, directory))\
-                    and ((ommit_level == 2 and compress) or (ommit_level>=1 and not compress)):
-                    logging.warning('Log %s exists as directory backup, ommited.' % directory)
-                else:
-                    # do the backup
-                    if compress:
-                        make_zipfile(os.path.join(backup_directory, output_filename),
-                                 full_dir)
-                        logging.info('Backed up %s' % directory)
-                        if verbose:
-                            print "Backed up %s" % directory
-                    else:
-                        if verbose:
-                            print "Directory Raw Backup not implemented yet."
-                        raise NotImplementedError
-        else:
-            if verbose:
-                print "%s is not a directory." % full_dir
-    if verbose and nothing_found:
-        print "Nothing to backup found in %s" % log_directory
-
-
-if __name__ == '__main__':
-    print "Performing Log Backup (Dev)"
-    log_source = os.path.join(os.path.expanduser('~'),
-                              'Documents', 'My Games', 'StarConflict', 'logs')
-    log_dest = os.path.join(os.path.expanduser('~'),
-                              'Documents', 'My Games', 'sc')
-    backup_log_directory(log_source, log_dest, verbose=True, compress=True)
-    
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+    Backup Directories, Handle Files...
+"""
+
+import os, logging, zipfile
+
+def make_zipfile(output_filename, source_dir):
+    relroot = os.path.abspath(os.path.join(source_dir, os.pardir))
+    with zipfile.ZipFile(output_filename, "w", zipfile.ZIP_DEFLATED) as zip:
+        for root, dirs, files in os.walk(source_dir):
+            # add directory (needed for empty dirs)
+            zip.write(root, os.path.relpath(root, relroot))
+            for file in files:
+                filename = os.path.join(root, file)
+                if os.path.isfile(filename): # regular files only
+                    arcname = os.path.join(os.path.relpath(root, relroot), file)
+                    zip.write(filename, arcname)
+                    
+def backup_log_directory(log_directory, backup_directory, compress=True,
+                         ommit_level=2, verbose=False):
+    # @todo: raw copy
+    # ommit_level 0: overwrite.
+    # ommit_level 1: write if selected compression method not backuped yet
+    # ommit_level 2: write only if neither method contains directory.
+    nothing_found = True
+    # get all directory names in log_directory.
+    # zip them into backup_directory
+    for directory in os.listdir(log_directory):
+        full_dir = os.path.join(log_directory, directory)
+        nothing_found = False
+        if os.path.isdir(full_dir):
+            if  os.path.exists(os.path.join(full_dir, 'combat.log'))\
+            and os.path.exists(os.path.join(full_dir, 'game.log'))\
+            and os.path.exists(os.path.join(full_dir, 'chat.log'))\
+            and os.path.exists(os.path.join(full_dir, 'game.net.log')):
+                output_filename = '%s.zip' % directory
+                if os.path.exists(os.path.join(backup_directory, output_filename))\
+                    and ((ommit_level >= 1 and compress) or (ommit_level==2 and not compress)):
+                    logging.warning('Log %s exists as zip backup, ommited.' % output_filename)
+                elif os.path.exists(os.path.join(backup_directory, directory))\
+                    and ((ommit_level == 2 and compress) or (ommit_level>=1 and not compress)):
+                    logging.warning('Log %s exists as directory backup, ommited.' % directory)
+                else:
+                    # do the backup
+                    if compress:
+                        make_zipfile(os.path.join(backup_directory, output_filename),
+                                 full_dir)
+                        logging.info('Backed up %s' % directory)
+                        if verbose:
+                            print(("Backed up %s" % directory))
+                    else:
+                        if verbose:
+                            print("Directory Raw Backup not implemented yet.")
+                        raise NotImplementedError
+        else:
+            if verbose:
+                print(("%s is not a directory." % full_dir))
+    if verbose and nothing_found:
+        print(("Nothing to backup found in %s" % log_directory))
+
+
+if __name__ == '__main__':
+    print("Performing Log Backup (Dev)")
+    log_source = os.path.join(os.path.expanduser('~'),
+                              'Documents', 'My Games', 'StarConflict', 'logs')
+    log_dest = os.path.join(os.path.expanduser('~'),
+                              'Documents', 'My Games', 'sc')
+    backup_log_directory(log_source, log_dest, verbose=True, compress=True)
+    
diff --git a/src/scon/battle.py b/src/scon/battle.py
index 8b81c05..c88f37b 100644
--- a/src/scon/battle.py
+++ b/src/scon/battle.py
@@ -1,33 +1,33 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-"""
-    Tool to analyze Logs in general.
-"""
-import os, sys, logging
-from logs.logfiles import LogFileResolver as LogFile
-from logs import combat, game, chat
-from logs.session import LogSessionCollector
-from logs.game import ClientInfo
-
-# for windows its kinda this:
-settings = {'root_path': os.path.join(os.path.expanduser('~'),
-                                     'Documents',
-                                     'My Games',
-                                     'StarConflict',),            
-            'logfiles': os.path.join(os.path.expanduser('~'),
-                                     'Documents',
-                                     'My Games',
-                                     'StarConflict',
-                                     'logs'
-                                     )}
-if __name__ == '__main__':
-    coll = LogSessionCollector(os.path.join(os.path.expanduser('~'),
-                        'Documents', 'My Games', 'sc'))
-    coll.collect_unique()
-    for logf in coll.sessions:
-        logf.parse_files(['game.log', 'combat.log'])
-        logf.clean()
-        if logf.combat_log:
-            print 'length combat log ', len(logf.combat_log.lines)
-        if logf.game_log:
-            print 'length game log ', len(logf.game_log.lines)
\ No newline at end of file
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+    Tool to analyze Logs in general.
+"""
+import os, sys, logging
+from .logs.logfiles import LogFileResolver as LogFile
+from .logs import combat, game, chat
+from .logs.session import LogSessionCollector
+from .logs.game import ClientInfo
+
+# for windows its kinda this:
+settings = {'root_path': os.path.join(os.path.expanduser('~'),
+                                     'Documents',
+                                     'My Games',
+                                     'StarConflict',),            
+            'logfiles': os.path.join(os.path.expanduser('~'),
+                                     'Documents',
+                                     'My Games',
+                                     'StarConflict',
+                                     'logs'
+                                     )}
+if __name__ == '__main__':
+    coll = LogSessionCollector(os.path.join(os.path.expanduser('~'),
+                        'Documents', 'My Games', 'sc'))
+    coll.collect_unique()
+    for logf in coll.sessions:
+        logf.parse_files(['game.log', 'combat.log'])
+        logf.clean()
+        if logf.combat_log:
+            print(('length combat log ', len(logf.combat_log.lines)))
+        if logf.game_log:
+            print(('length game log ', len(logf.game_log.lines)))
\ No newline at end of file
diff --git a/src/scon/brainstorm.py b/src/scon/brainstorm.py
index df6fcda..f908b89 100644
--- a/src/scon/brainstorm.py
+++ b/src/scon/brainstorm.py
@@ -1,85 +1,85 @@
-"""
-    Brainstorm File for Star Conflict Log Parsing
-    
-    Needed
-     - find steam/scon folder on windows
-     - find steam/scon folder on mac
-     - find steam/scon folder on linux
-     - what about steamless installs?
-    
-    Elaborate
-     - which GUI to use? wx? PyQt4? PySide?
-     - take over the database stuff from weltenfall.starconflict?
-    
-    Investigate
-     - language based log files?
-"""
-#from win32com.shell import shell, shellcon
-import os, sys, logging
-from logs.logfiles import LogFileResolver as LogFile
-from logs import combat
-
-# for windows its kinda this:
-settings = {'root_path': os.path.join(os.path.expanduser('~'),
-                                     'Documents',
-                                     'My Games',
-                                     'StarConflict',),            
-            'logfiles': os.path.join(os.path.expanduser('~'),
-                                     'Documents',
-                                     'My Games',
-                                     'StarConflict',
-                                     'logs'
-                                     )}
-
-def find_log_files(logpath):
-    ''' returns a list of 4-tuples representing
-       (combat.log, game.log, chat.log, game.net.log)
-       for each directory in the logpath
-    '''
-    ret = []
-    for directory in os.listdir(logpath):
-        full_dir = os.path.join(logpath, directory)
-        if os.path.isdir(full_dir):
-            if  os.path.exists(os.path.join(full_dir, 'combat.log'))\
-            and os.path.exists(os.path.join(full_dir, 'game.log'))\
-            and os.path.exists(os.path.join(full_dir, 'chat.log'))\
-            and os.path.exists(os.path.join(full_dir, 'game.net.log')):
-                ret.append((
-                        os.path.join(full_dir, 'combat.log'),
-                        os.path.join(full_dir, 'game.log'),
-                        os.path.join(full_dir, 'chat.log'),
-                        os.path.join(full_dir, 'game.net.log')
-                        ))
-    return ret
-
-def parse_games(logfiles):
-    _logfiles = []
-    for logpack in logfiles:
-        combatlog, gamelog, chatlog, gamenetlog = logpack
-        _logfiles.append(LogFile(combatlog))
-        #_logfiles.append(LogFile(gamelog))
-        #_logfiles.append(LogFile(chatlog))
-        #_logfiles.append(LogFile(gamenetlog))
-    return _logfiles
-
-if __name__ == '__main__':
-    logfiles = find_log_files(settings['logfiles'])
-    logfiles = parse_games(logfiles)
-    #f = open('output.txt', 'w')
-    rex = {}
-    for logf in logfiles:
-        logf.read()
-        logf.parse()
-        for l in logf.lines:
-            if isinstance(l, dict):
-                #print l
-                pass
-            else:
-                if not l.unpack():
-                    rex[l.__class__.__name__] = rex.get(l.__class__.__name__, 0) + 1
-                    if not isinstance(l, combat.UserEvent):
-                        print l.values['log']
-                #f.write(l.values['log'] + '\n')
-    #f.close()
-            #print type(l)
-    print rex
\ No newline at end of file
+"""
+    Brainstorm File for Star Conflict Log Parsing
+    
+    Needed
+     - find steam/scon folder on windows
+     - find steam/scon folder on mac
+     - find steam/scon folder on linux
+     - what about steamless installs?
+    
+    Elaborate
+     - which GUI to use? wx? PyQt4? PySide?
+     - take over the database stuff from weltenfall.starconflict?
+    
+    Investigate
+     - language based log files?
+"""
+#from win32com.shell import shell, shellcon
+import os, sys, logging
+from .logs.logfiles import LogFileResolver as LogFile
+from .logs import combat
+
+# for windows its kinda this:
+settings = {'root_path': os.path.join(os.path.expanduser('~'),
+                                     'Documents',
+                                     'My Games',
+                                     'StarConflict',),            
+            'logfiles': os.path.join(os.path.expanduser('~'),
+                                     'Documents',
+                                     'My Games',
+                                     'StarConflict',
+                                     'logs'
+                                     )}
+
+def find_log_files(logpath):
+    ''' returns a list of 4-tuples representing
+       (combat.log, game.log, chat.log, game.net.log)
+       for each directory in the logpath
+    '''
+    ret = []
+    for directory in os.listdir(logpath):
+        full_dir = os.path.join(logpath, directory)
+        if os.path.isdir(full_dir):
+            if  os.path.exists(os.path.join(full_dir, 'combat.log'))\
+            and os.path.exists(os.path.join(full_dir, 'game.log'))\
+            and os.path.exists(os.path.join(full_dir, 'chat.log'))\
+            and os.path.exists(os.path.join(full_dir, 'game.net.log')):
+                ret.append((
+                        os.path.join(full_dir, 'combat.log'),
+                        os.path.join(full_dir, 'game.log'),
+                        os.path.join(full_dir, 'chat.log'),
+                        os.path.join(full_dir, 'game.net.log')
+                        ))
+    return ret
+
+def parse_games(logfiles):
+    _logfiles = []
+    for logpack in logfiles:
+        combatlog, gamelog, chatlog, gamenetlog = logpack
+        _logfiles.append(LogFile(combatlog))
+        #_logfiles.append(LogFile(gamelog))
+        #_logfiles.append(LogFile(chatlog))
+        #_logfiles.append(LogFile(gamenetlog))
+    return _logfiles
+
+if __name__ == '__main__':
+    logfiles = find_log_files(settings['logfiles'])
+    logfiles = parse_games(logfiles)
+    #f = open('output.txt', 'w')
+    rex = {}
+    for logf in logfiles:
+        logf.read()
+        logf.parse()
+        for l in logf.lines:
+            if isinstance(l, dict):
+                #print l
+                pass
+            else:
+                if not l.unpack():
+                    rex[l.__class__.__name__] = rex.get(l.__class__.__name__, 0) + 1
+                    if not isinstance(l, combat.UserEvent):
+                        print((l.values['log']))
+                #f.write(l.values['log'] + '\n')
+    #f.close()
+            #print type(l)
+    print(rex)
\ No newline at end of file
diff --git a/src/scon/config/display_config.py b/src/scon/config/display_config.py
index b26c4b4..e43f371 100644
--- a/src/scon/config/display_config.py
+++ b/src/scon/config/display_config.py
@@ -1,119 +1,119 @@
-"""
-    Simple brainstorm to display a config file.
-"""
-import os, logging
-from settings import settings
-logging.basicConfig(level=logging.INFO)
-# import ET:
-try:
-    ET = None
-    import lxml.etree as ET
-    logging.info('Using LXML.')
-except ImportError:
-    try:
-        import cElementTree as ET
-        logging.info('Using cElementTree')
-    except ImportError:
-        try:
-            import elementtree.ElementTree as ET
-            logging.info('Using ElementTree')
-        except ImportError:
-            import xml.etree.ElementTree as ET # python 2.5
-            logging.info('Using xml.ElementTree')
-finally:
-    if not ET:
-        raise NotImplementedError, "XML Parser not found in your Python."
-##################################################################################################
-
-class ConfigFile(object):
-    def __init__(self, config_file=None):
-        self.cvars = []
-        if config_file:
-            self.config_file = config_file
-        elif settings:
-            # settings based loading.
-            self.config_file = os.path.join(settings.get_path(), 'user_config.xml')
-    
-    def open(self, filename = None):
-        # reads a config file.
-        filename = filename or self.config_file
-        self.tree = ET.parse(filename)
-        doc = self.tree.getroot()
-        if doc.tag == 'UserConfig' \
-            and len(doc) == 1\
-            and doc[0].tag == 'CVars'\
-            and doc[0].attrib['version'] == '4':
-            logging.info( "Found valid config file." )
-            # save my cvars
-            self.cvars = doc[0]
-        else:
-            logging.info( "Config File not supported." )
-        return self
-    
-    def pprint(self):
-        # print out my cvars
-        for child in self.cvars:
-            print '%s = %s' % (child.tag, child.attrib['val'])
-    
-    def write(self, filename):
-        output = '<?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()))
     
\ No newline at end of file
diff --git a/src/scon/config/settings.py b/src/scon/config/settings.py
index 62984b2..6f1053b 100644
--- a/src/scon/config/settings.py
+++ b/src/scon/config/settings.py
@@ -1,31 +1,31 @@
-import os
-import platform
-
-class Settings(dict):
-    def autodetect(self, path=None):
-        # autodetect settings.
-        d = path
-        system = platform.system()
-        if system == 'Windows' or system.startswith('CYGWIN_NT'):
-            # try to find user folder:
-            d = d or os.path.join(os.path.expanduser('~'),
-                                     'Documents',
-                                     'My Games',
-                                     'StarConflict',)
-        elif system == 'Linux':
-            raise NotImplementedError, "Implement Linux!"
-        elif system == 'Darwin':
-            raise NotImplementedError, "Implement Mac!"
-        else:
-            raise NotImplementedError, "Unknown System! %s" % platform.system()
-        if not os.path.exists(d) or not os.path.isdir(d):
-            raise Exception, "Configuration Autodetection failed. "
-        self['root_path'] = d
-        
-    def get_path(self):
-        return self.get('root_path', None)
-    
-    def get_logs_path(self):
-        return os.path.join(self.get_path, 'logs')
-
-settings = Settings()
+import os
+import platform
+
+class Settings(dict):
+    def autodetect(self, path=None):
+        # autodetect settings.
+        d = path
+        system = platform.system()
+        if system == 'Windows' or system.startswith('CYGWIN_NT'):
+            # try to find user folder:
+            d = d or os.path.join(os.path.expanduser('~'),
+                                     'Documents',
+                                     'My Games',
+                                     'StarConflict',)
+        elif system == 'Linux':
+            raise NotImplementedError("Implement Linux!")
+        elif system == 'Darwin':
+            raise NotImplementedError("Implement Mac!")
+        else:
+            raise NotImplementedError("Unknown System! %s" % platform.system())
+        if not os.path.exists(d) or not os.path.isdir(d):
+            raise Exception("Configuration Autodetection failed. ")
+        self['root_path'] = d
+        
+    def get_path(self):
+        return self.get('root_path', None)
+    
+    def get_logs_path(self):
+        return os.path.join(self.get_path, 'logs')
+
+settings = Settings()
diff --git a/src/scon/dejaqt/folders.py b/src/scon/dejaqt/folders.py
index bbc5d7f..fd44e3e 100644
--- a/src/scon/dejaqt/folders.py
+++ b/src/scon/dejaqt/folders.py
@@ -1,84 +1,84 @@
-
-import logging, os
-try:
-    from django.conf import settings
-except:
-    logging.error('Django Settings could not be loaded. Maybe Django has not been initialized?')
-    settings = None
-
-class FolderLibrary(object):
-    def __init__(self, folders=None):
-        self._folders = {}
-        try:
-            if settings:
-                self.folders.update( getattr(settings, 'DEJAQT_DIRS', {}) )
-        except:
-            logging.error('DEJAQT_DIRS in django settings threw error.')
-            import traceback
-            traceback.print_exc()
-        if folders:
-            # no try here: if this fails, you got yourself a programming error.
-            self.folders.update(folders)
-        self._keys = []
-        self.build_keycache()
-    
-    def get_folders(self):
-        return self._folders
-    
-    def set_folders(self, folders):
-        self._folders = folders
-        self.build_keycache()
-    folders = property(get_folders, set_folders)
-    
-    def build_keycache(self):
-        self._keys = self._folders.keys()
-        self._keys.sort(key=lambda item: (-len(item), item))
-    
-    def add_folder(self, url, folder):
-        if not url:
-            url = ''
-        self._folders[url] = folder
-        self.build_keycache()
-    
-    def match(self, url):
-        # run down our keycache, first match wins.
-        for key in self._keys:
-            if url.startswith(key):
-                return key
-    
-    def matched_folder(self, url):
-        m = self.match(url)
-        if m is not None:
-            folder = self._folders[m]
-            #heading, rest = url[:len(m)], url[len(m):]
-            rest = url[len(m):]
-            real_folder = os.path.abspath( os.path.join(folder, rest) )
-            if real_folder.startswith(os.path.abspath(folder)):
-                return real_folder
-            else:
-                logging.error('%s does not seem to be a subpath of %s' % (real_folder, folder))
-        
-    def print_folders(self):
-        print '{'
-        for k in self._keys:
-            print "'%s': '%s'," % (k, self._folders[k])
-        print '}'
-    
-
-if __name__ == "__main__":
-    # test this:
-    import os
-    os.environ['DJANGO_SETTINGS_MODULE'] = 'scon.dj.settings' 
-    f = FolderLibrary({'abc/dab/': 'c:/media',
-                       'abc': 'd:/abc',
-                       'abc/dab/tmp': '/tmp',
-                       'uiuiui': 'x:/',
-                       'abc/vul/no': 'x:/2',
-                       'abc/vul': 'x:/3',
-                       'abc/vul/yes': 'x:/1',
-                       })
-    f.add_folder('abc/dub/', 'c:/dubdub')
-    f.print_folders()
-    
-    print f.matched_folder('abc/dab/okokok/some.png')
+
+import logging, os
+try:
+    from django.conf import settings
+except:
+    logging.error('Django Settings could not be loaded. Maybe Django has not been initialized?')
+    settings = None
+
+class FolderLibrary(object):
+    def __init__(self, folders=None):
+        self._folders = {}
+        try:
+            if settings:
+                self.folders.update( getattr(settings, 'DEJAQT_DIRS', {}) )
+        except:
+            logging.error('DEJAQT_DIRS in django settings threw error.')
+            import traceback
+            traceback.print_exc()
+        if folders:
+            # no try here: if this fails, you got yourself a programming error.
+            self.folders.update(folders)
+        self._keys = []
+        self.build_keycache()
+    
+    def get_folders(self):
+        return self._folders
+    
+    def set_folders(self, folders):
+        self._folders = folders
+        self.build_keycache()
+    folders = property(get_folders, set_folders)
+    
+    def build_keycache(self):
+        self._keys = list(self._folders.keys())
+        self._keys.sort(key=lambda item: (-len(item), item))
+    
+    def add_folder(self, url, folder):
+        if not url:
+            url = ''
+        self._folders[url] = folder
+        self.build_keycache()
+    
+    def match(self, url):
+        # run down our keycache, first match wins.
+        for key in self._keys:
+            if url.startswith(key):
+                return key
+    
+    def matched_folder(self, url):
+        m = self.match(url)
+        if m is not None:
+            folder = self._folders[m]
+            #heading, rest = url[:len(m)], url[len(m):]
+            rest = url[len(m):]
+            real_folder = os.path.abspath( os.path.join(folder, rest) )
+            if real_folder.startswith(os.path.abspath(folder)):
+                return real_folder
+            else:
+                logging.error('%s does not seem to be a subpath of %s' % (real_folder, folder))
+        
+    def print_folders(self):
+        print('{')
+        for k in self._keys:
+            print(("'%s': '%s'," % (k, self._folders[k])))
+        print('}')
+    
+
+if __name__ == "__main__":
+    # test this:
+    import os
+    os.environ['DJANGO_SETTINGS_MODULE'] = 'scon.dj.settings' 
+    f = FolderLibrary({'abc/dab/': 'c:/media',
+                       'abc': 'd:/abc',
+                       'abc/dab/tmp': '/tmp',
+                       'uiuiui': 'x:/',
+                       'abc/vul/no': 'x:/2',
+                       'abc/vul': 'x:/3',
+                       'abc/vul/yes': 'x:/1',
+                       })
+    f.add_folder('abc/dub/', 'c:/dubdub')
+    f.print_folders()
+    
+    print((f.matched_folder('abc/dab/okokok/some.png')))
     
\ No newline at end of file
diff --git a/src/scon/dejaqt/qweb.py b/src/scon/dejaqt/qweb.py
index 2d1f29d..dd93a15 100644
--- a/src/scon/dejaqt/qweb.py
+++ b/src/scon/dejaqt/qweb.py
@@ -1,198 +1,198 @@
-"""
-    Qt WebKit Browser for local access to internal Django Views.
-"""
-import os, logging
-from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork
-from django.test import Client
-from folders import FolderLibrary
-from django.http.request import QueryDict
-from urlparse import urlparse, parse_qs
-import cgi
-from io import BytesIO
-from django.http.multipartparser import MultiPartParser
-
-class DebugPage(QtWebKit.QWebPage):
-    def sayMyName(self):
-        return 'DebugPage'
-
-class DejaWebView(QtWebKit.QWebView):
-    '''
-        Optional: 
-          * folders: FolderLibrary() Instance.
-          * page: Initialized QWebPage instance for initial page (default DebugPage())
-    '''
-    def __init__(self, *args, **kwargs):
-        self.folders = kwargs.pop('folders', FolderLibrary())
-        page = kwargs.pop('page', DebugPage())
-        QtWebKit.QWebView.__init__(self, *args, **kwargs)
-        #self.oldManager = self.page().networkAccessManager()
-        self.setPage(page)
-        self.page().setNetworkAccessManager(DejaNetworkAccessManager(self))
-        self.client = Client()
-        #self.client.login(username='admin', password='admin')
-
-class DejaNetworkAccessManager(QtNetwork.QNetworkAccessManager):
-    '''
-        The Deja Network Access Manager provides access to two new protocols:
-        - page:/// tries to resolve a page internally via a django test client.
-        - res:/// direct access to a resource.
-        
-        USE_NETWORK delegates to other network access manager protocols, if False, it will not
-        allow any requests outside these two protocols 
-        (hopefully disabling network access for your internal browser)
-        
-        Note, if page does not find the page, a res:/// attempt is made automatically.
-        This has to be expanded!
-        
-        Note2: not sure if cookies and sessions will work this way!
-    '''
-    USE_NETWORK = False
-    def __init__(self, parent=None):
-        QtNetwork.QNetworkAccessManager.__init__(self, parent=parent)
-        if parent:
-            self.folders = getattr(parent, 'folders', FolderLibrary())
-        
-    def createRequest(self, operation, request, data):
-        scheme = request.url().scheme()
-        if scheme != 'page' and scheme != 'res':
-            if self.USE_NETWORK:
-                return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data)
-        elif scheme == 'page':
-            if operation == self.GetOperation:
-                # Handle page:// URLs separately by creating custom
-                # QNetworkReply objects.
-                reply = PageReply(self, request.url(), self.GetOperation)
-                #print('here')
-                #print reply
-                return reply
-            elif operation == self.PostOperation:
-                reply = PageReply(self, request.url(), self.PostOperation, request, data)
-                return reply
-        elif scheme == 'res':
-            if operation == self.GetOperation:
-                return ResourceReply(self, request.url(), self.GetOperation)
-        else:
-            if self.USE_NETWORK:
-                return QtNetwork.QNetworkAccessManager.createRequest(self, operation, request, data)
-        return NoNetworkReply(self, request.url(), self.GetOperation)
-
-class BasePageReply(QtNetwork.QNetworkReply):
-    content_type = 'text/html; charset=utf-8'
-    def __init__(self, parent, url, operation, request=None, data=None):
-        QtNetwork.QNetworkReply.__init__(self, parent)
-        self.data = data
-        self.request = request
-        self.content = self.initialize_content(url, operation)
-        self.offset = 0
-        self.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, self.get_content_type())
-        self.setHeader(QtNetwork.QNetworkRequest.ContentLengthHeader, len(self.content))
-        QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('readyRead()'))
-        QtCore.QTimer.singleShot(0, self, QtCore.SIGNAL('finished()'))
-        self.open(self.ReadOnly | self.Unbuffered)
-        self.setUrl(url)
-    
-    def get_content_type(self):
-        return self.content_type
-    
-    def initialize_content(self, url, operation):        
-        return '''
-            <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>
+        '''
+
diff --git a/src/scon/dj/scon/admin.py b/src/scon/dj/scon/admin.py
index 1c94f0c..b5cac14 100644
--- a/src/scon/dj/scon/admin.py
+++ b/src/scon/dj/scon/admin.py
@@ -1,5 +1,5 @@
 from django.contrib import admin
-import models 
+from . import models 
 # Register your models here.
 admin.site.register(models.Crafting)
 admin.site.register(models.CraftingInput)
diff --git a/src/scon/dj/scon/generate_fixtures.py b/src/scon/dj/scon/generate_fixtures.py
index 9b5714a..efa23f2 100644
--- a/src/scon/dj/scon/generate_fixtures.py
+++ b/src/scon/dj/scon/generate_fixtures.py
@@ -1,592 +1,592 @@
-"""
-    Generate Fixtures for Crafting.
-    Simple generator, does not integrate well into existing stuff, so please use
-    only for bootstrapping.
-"""
-import os, json
-BASE_DIR = os.path.dirname(os.path.dirname(__file__))
-DIR = os.path.join(BASE_DIR, 'scon', 'fixtures')
-
-def write_fixture(data):
-    f = open(os.path.join(DIR, 'generated.json'), 'w')
-    f.write(json.dumps(data))
-    f.close()
-
-def build_pk_cache(data, models=None):
-    pk_cache = {}
-    # fill cache from existing
-    for d in data:
-        if 'pk' in d.keys():
-            # has pk
-            pk_cache[d['model']] = max(pk_cache.get('model', 0), d['pk'])
-    for d in data:
-        m = d['model']
-        if models:
-            if m not in models:
-                continue
-        if 'pk' in d.keys():
-            #print "PK was already in there! %s" % d
-            pass
-        else:
-            if m not in pk_cache.keys():
-                pk_cache[m] = 1
-                i = 1
-            else:
-                i = pk_cache[m] + 1
-                pk_cache[m] = i
-            d['pk'] = i        
-    return data
-
-def lookup_pk(data, name, mdl='scon.item', kwargs=None):
-    for d in data:
-        if d['model'] == mdl:
-            if d['fields'].get('name', '').lower() == name.lower():
-                found = True
-                if kwargs is not None:
-                    for key, val in kwargs.items():
-                        if not d['fields'].get(key, None) == val:
-                            found = False
-                if found:
-                    return d['pk']
-
-def generate_fixtures():
-    data = []
-    
-    ORES = [
-            {'name': 'Impure tungsten', 'sell_price': 6600, 'icon': 'resource_tungsten_ore'},
-            {'name': 'Impure osmium', 'sell_price': 4500, 'icon': 'resource_osmium_ore'},
-            {'name': 'Impure silicon', 'sell_price': 600, 'icon': 'resource_silicon_ore'},
-            {'name': 'Vanadium', 'sell_price': 500, 'icon': 'resource_vanadium'},
-            {'name': 'Crystal shard', 'sell_price': 3500, 'icon': 'resource_crystal_shard'},
-            ]
-    MATERIALS = [
-            {'name': 'Tungsten plate', 
-             'description': 'Durable tungsten plate', 
-             'sell_price': 20000,
-             'icon': 'component_tungsten_plate'},
-            {'name': 'Screened battery', 'sell_price': 42000, 
-             'icon': 'component_screened_battery'},
-            {'name': 'Osmium crystals', 'sell_price': 5500, 
-             'icon': 'component_osmium_crystals'},
-            {'name': 'Pure Silicon', 'sell_price': 2500, 
-             'icon': 'component_pure_silicon'},
-            {'name': 'Processing block', 'sell_price': 22000, 
-             'icon': 'component_processing_block'},
-            {'name': 'Metal blank', 'sell_price': 1600, 
-             'icon': 'component_metal_blank'},
-            {'name': 'Alien Monocrystal', 'sell_price': 25000, 
-             'icon': 'component_alien_monocrystal'},
-            {'name': 'Computing chip', 'sell_price': 4500, 
-             'icon': 'component_computing_chip'},
-            ]
-    AMMOS = [
-            {'name': 'Explosive Shells',
-             'quality': 4,
-             'sell_price': 1000,
-             'icon': 'ammo_explosive_shells_mk4',
-             },
-            {'name': 'Double Deflector',
-             'quality': 4,
-             'sell_price': 1000,
-             'icon': 'ammo_double_deflector_mk4',
-             },
-            {'name': 'Xenon Lamp',
-             'quality': 4,
-             'sell_price': 1000,
-             'icon': 'ammo_xenon_lamp_mk4',
-             },
-            {'name': 'Attack Drone',
-             'quality': 10,
-             'sell_price': 1092,
-             'icon': 'ammo_attack_drone',
-             },
-             {'name': 'Focusing Lens',
-              'quality': 4,
-              'sell_price': 1000,
-              'icon': 'ammo_focusing_lens',
-              },
-             {'name': 'Iridium Slugs',
-              'quality': 4,
-              'sell_price': 1000,
-              'icon': 'ammo_iridium_slugs',
-              },
-             {'name': 'Supercooled Charges',
-              'quality': 4,
-              'sell_price': 1000,
-              'icon': 'ammo_supercooled_charges',
-              },
-             {'name': 'Doomsday Missile',
-              'quality': 1,
-              'sell_price': 1000,
-              #'tech': 5,
-              'icon': 'ammo_doomsday_missile',
-              }
-             ]
-    
-    ITEMS_NON_CRAFTING = [
-             {'name': 'Target Tracking Coprocessor III',
-              'typ': 5, # cpu
-              'tech': 3,
-              'sell_price': 20188,
-              'description': 'Increases Critical Damage',
-              'icon': 'cpu_target_tracking_coprocessor',
-              },
-            {'name': 'Plasma Gun III',
-              'typ': 7, # weap
-              'quality': 4,
-              'tech': 3,
-              'icon': 'weapon_plasma_gun',
-              },
-            {'name': 'Plasma Gun IV',
-              'typ': 7, # weap
-              'quality': 4,
-              'tech': 4,
-              'icon': 'weapon_plasma_gun',
-              },
-            {'name': 'Plasma Gun V',
-              'typ': 7, # weap
-              'quality': 4,
-              'tech': 5,
-              'icon': 'weapon_plasma_gun',
-              },
-            # assault rails:
-            {'name': 'Assault Railgun III',
-              'typ': 7, # weap
-              'quality': 4,
-              'tech': 3,
-              'icon': 'weapon_assault_railgun',
-              },
-            {'name': 'Assault Railgun IV',
-              'typ': 7, # weap
-              'quality': 4,
-              'tech': 4,
-              'icon': 'weapon_assault_railgun',
-              },
-            {'name': 'Assault Railgun V',
-              'typ': 7, # weap
-              'quality': 4,
-              'tech': 5,
-              'icon': 'weapon_assault_railgun',
-              },
-            # beam cannon:
-            {'name': 'Beam Cannon III',
-              'typ': 7, # weap
-              'quality': 4,
-              'tech': 3,
-              'icon': 'weapon_beam_cannon',
-              },
-            {'name': 'Beam Cannon IV',
-              'typ': 7, # weap
-              'quality': 4,
-              'tech': 4,
-              'icon': 'weapon_beam_cannon',
-              },
-            {'name': 'Beam Cannon V',
-              'typ': 7, # weap
-              'quality': 4,
-              'tech': 5,
-              'icon': 'weapon_beam_cannon',
-              },
-            ]
-    
-    ITEMS = [
-             {'name': 'Duplicator',
-              'typ': 0,
-              'sell_price': 8000,
-              'buy_price_premium': 200,
-              'description': 'Revives in Invasion with Cargo once.',
-              'icon': 'duplicator',
-              },
-             {'name': 'A1MA IV',
-              'quality': 1,
-              'tech': 4,
-              'typ': 8, # active.
-              'role': 0, # multipurp.
-              'sell_price': 26910,
-              'icon': 'active_a1ma',
-              },
-             {'name': 'Pirate "Orion" Targeting Complex V',
-              'quality': 14,
-              'tech': 5,
-              'typ': 8, # active.
-              'role': 3, # covops
-              'icon': 'active_t5_orion_targeting_complex_pirate', 
-              },
-             {'name': 'Pirate Engine Overcharge V',
-              'quality': 14,
-              'tech': 5,
-              'typ': 8, # active.
-              'role': 6, # gunship
-              'icon': 'active_t5_engine_overcharge_pirate', 
-              },
-             {'name': 'Pirate Mass Shield Generator V',
-              'quality': 14,
-              'tech': 5,
-              'typ': 8, # active.
-              'role': 7, # engi 
-              'icon': 'active_t5_mass_shield_generator_pirate',
-              },
-             {'name': 'Reverse Thruster III',
-              'quality': 1,
-              'tech': 3,
-              'typ': 8, # active.
-              'role': 9, # LRF 
-              'icon': 'active_reverse_thruster',
-              },
-             {'name': 'Reverse Thruster IV',
-              'quality': 1,
-              'tech': 4,
-              'typ': 8, # active.
-              'role': 9, # LRF
-              'icon': 'active_reverse_thruster', 
-              },
-             {'name': 'Reverse Thruster V',
-              'quality': 1,
-              'tech': 5,
-              'typ': 8, # active.
-              'role': 9, # LRF 
-              'icon': 'active_reverse_thruster',
-              },
-             {'name': 'Plasma Gun III',
-              'quality': 5,
-              'tech': 3,
-              'typ': 7, # weap 
-              'icon': 'weapon_plasma_gun_mk5',
-              },
-             {'name': 'Plasma Gun IV',
-              'quality': 5,
-              'tech': 4,
-              'typ': 7, # weap
-              'icon': 'weapon_plasma_gun_mk5', 
-              },
-             {'name': 'Plasma Gun V',
-              'quality': 5,
-              'tech': 5,
-              'typ': 7, # weap 
-              'icon': 'weapon_plasma_gun_mk5',
-              },
-             {'name': 'Assault Railgun III',
-              'quality': 5,
-              'tech': 3,
-              'typ': 7, # weap 
-              'icon': 'weapon_assault_rail_mk5',
-              },
-             {'name': 'Assault Railgun IV',
-              'quality': 5,
-              'tech': 4,
-              'typ': 7, # weap
-              'icon': 'weapon_assault_rail_mk5', 
-              },
-             {'name': 'Assault Railgun V',
-              'quality': 5,
-              'tech': 5,
-              'typ': 7, # weap 
-              'icon': 'weapon_assault_rail_mk5',
-              },
-             {'name': 'Beam Cannon III',
-              'quality': 5,
-              'tech': 3,
-              'typ': 7, # weap 
-              'icon': 'weapon_beam_cannon_mk5',
-              },
-             {'name': 'Beam Cannon IV',
-              'quality': 5,
-              'tech': 4,
-              'typ': 7, # weap
-              'icon': 'weapon_beam_cannon_mk5', 
-              },
-             {'name': 'Beam Cannon V',
-              'quality': 5,
-              'tech': 5,
-              'typ': 7, # weap 
-              'icon': 'weapon_beam_cannon_mk5',
-              },
-             ]
-    BLUEPRINTS = [
-            {'name': 'Focusing Lens Blueprint'},
-            {'name': 'Iridium Slugs Blueprint'},
-            {'name': 'Supercooled Charges Blueprint'},
-            {'name': 'A1MA T4 Blueprint'},
-            {'name': 'Orion-2 Targeting Complex Blueprint',
-             'description': 'Module works twice as long but much weaker.'},
-            {'name': 'Engine Warp Overcharge Blueprint'},
-            {'name': 'Mass Shield Energizer Blueprint'},
-            {'name': 'Reverse Thruster T3 Blueprint'},
-            {'name': 'Reverse Thruster T4 Blueprint'},
-            {'name': 'Reverse Thruster T5 Blueprint'},
-            {'name': 'Beam Cannon Prototype T3 Blueprint'},
-            {'name': 'Beam Cannon Prototype T4 Blueprint'},
-            {'name': 'Beam Cannon Prototype T5 Blueprint'},
-            {'name': 'Assault Railgun Prototype T3 Blueprint'},
-            {'name': 'Assault Railgun Prototype T4 Blueprint'},
-            {'name': 'Assault Railgun Prototype T5 Blueprint'},
-            {'name': 'Plasma Gun Prototype T3 Blueprint'},
-            {'name': 'Plasma Gun Prototype T4 Blueprint'},
-            {'name': 'Plasma Gun Prototype T5 Blueprint'},
-            {'name': 'Doomsday Missile Blueprint'},
-            ]
-    CRAFTING = [
-            {'item': 'Duplicator',
-             'recipee': [(1, 'Processing Block'), (2,'Computing chip'), (2, 'Metal blank')]},
-            {'item': 'Tungsten plate',
-             'recipee': [(2, 'Impure tungsten'),]},
-            {'item': 'Screened battery',
-             'recipee': [(1, 'Tungsten plate'), (2, 'Computing chip')]},
-            {'item': 'Osmium crystals',
-             'recipee': [(1, 'Impure osmium'),]},
-            {'item': 'Pure Silicon',
-             'recipee': [(1, 'Impure silicon'),]},
-            {'item': 'Computing chip',
-             'recipee': [(1, 'Crystal shard'),]},
-            {'item': 'Processing block',
-             'recipee': [(4, 'Pure Silicon'), (2, 'Computing chip')]},
-            {'item': 'Metal blank',
-             'recipee': [(2, 'Vanadium'),]},
-            {'item': 'Target Tracking Coprocessor III',
-             'recipee': [(1, 'Screened battery'), (7, 'Metal blank'), (5, 'Pure silicon')]},
-            {'item': 'Explosive Shells',
-             'amount': 2,
-             'recipee': [(1, 'Metal blank'),(2, 'Pure Silicon')]},
-            {'item': 'Attack drone',
-             'recipee': [(1, 'Alien Monocrystal'), (1,'Computing chip')]},
-            {'item': 'Double Deflector',
-             'amount': 2,
-             'recipee': [(1, 'Osmium crystals'),]},
-            {'item': 'Xenon Lamp',
-             'amount': 2,
-             'recipee': [(1, 'Computing chip'), (1, 'Pure Silicon')]},
-            
-            {'item': 'Focusing Lens',
-             'amount': 2,
-             'recipee': [(1, 'Focusing Lens Blueprint'), (1, 'Osmium crystals')]},
-            {'item': 'Supercooled Charges',
-             'amount': 2,
-             'recipee': [(1, 'Supercooled Charges Blueprint'), (1, 'Computing chip')]},
-            {'item': 'Iridium Slugs',
-             'amount': 2,
-             'recipee': [(1, 'Iridium Slugs Blueprint'), (1, 'Metal blank')]},
-            {'item': 'A1MA IV',
-             'recipee': [(1, 'A1MA T4 Blueprint'), (2, 'Processing block'),
-                         (14, 'Metal blank'), (2, 'Screened battery'),
-                         (20, 'Alien Monocrystal')]},
-            {'item': 'Pirate "Orion" Targeting Complex V',
-             'recipee': [(1, 'Orion-2 Targeting Complex Blueprint'), 
-                         (3, 'Tungsten plate'),
-                         (4, 'Computing chip'), 
-                         (2, 'Processing block'),
-                         (30, 'Alien Monocrystal')
-                         ]},
-            {'item': 'Pirate Engine Overcharge V',
-             'recipee': [(1, 'Engine Warp Overcharge Blueprint'), 
-                         (3, 'Tungsten plate'),
-                         (2, 'Osmium crystals'), 
-                         (2, 'Processing block'),
-                         (30, 'Alien Monocrystal')
-                         ]},
-            {'item': 'Pirate Mass Shield Generator V',
-             'recipee': [(1, 'Mass Shield Energizer Blueprint'), 
-                         (10, 'Metal blank'),
-                         (3, 'Computing chip'), 
-                         (3, 'Processing block'),
-                         (30, 'Alien Monocrystal')
-                         ]},
-            # lrf reverse blink:
-            {'item': 'Reverse Thruster III',
-             'recipee': [(1, 'Reverse Thruster T3 Blueprint'), 
-                         (7, 'Metal blank'),
-                         (1, 'Screened battery'),
-                         (4, 'Computing chip'), 
-                         (15, 'Alien Monocrystal')
-                         ]},
-            {'item': 'Reverse Thruster IV',
-             'recipee': [(1, 'Reverse Thruster T4 Blueprint'), 
-                         (12, 'Metal blank'),
-                         (2, 'Screened battery'),
-                         (5, 'Computing chip'), 
-                         (20, 'Alien Monocrystal')
-                         ]},
-            {'item': 'Reverse Thruster V',
-             'recipee': [(1, 'Reverse Thruster T5 Blueprint'), 
-                         (7, 'Tungsten plate'),
-                         (3, 'Screened battery'),
-                         (6, 'Computing chip'), 
-                         (30, 'Alien Monocrystal')
-                         ]},
-            
-            # plasma
-            {'item': ('Plasma Gun III', {'quality': 5}),
-             'recipee': [(1, 'Plasma Gun Prototype T3 Blueprint'), 
-                         (1, ('Plasma Gun III', {'quality': 4})),
-                         (6, 'Metal blank'),
-                         (3, 'Screened battery'),
-                         (30, 'Alien Monocrystal')
-                         ]},
-            {'item': ('Plasma Gun IV', {'quality': 5}),
-             'recipee': [(1, 'Plasma Gun Prototype T4 Blueprint'), 
-                         (1, ('Plasma Gun IV', {'quality': 4})),
-                         (1, 'Tungsten plate'),
-                         (4, 'Screened battery'),
-                         (50, 'Alien Monocrystal')
-                         ]},
-            {'item': ('Plasma Gun V', {'quality': 5}),
-             'recipee': [(1, 'Plasma Gun Prototype T5 Blueprint'), 
-                         (1, ('Plasma Gun V', {'quality': 4})),
-                         (3, 'Tungsten plate'),
-                         (5, 'Screened battery'),
-                         (70, 'Alien Monocrystal')
-                         ]},
-            # assault
-            {'item': ('Assault Railgun III', {'quality': 5}),
-             'recipee': [(1, 'Assault Railgun Prototype T3 Blueprint'), 
-                         (1, ('Assault Railgun III', {'quality': 4})),
-                         (6, 'Metal blank'),
-                         (3, 'Screened battery'),
-                         (30, 'Alien Monocrystal')
-                         ]},
-            {'item': ('Assault Railgun IV', {'quality': 5}),
-             'recipee': [(1, 'Assault Railgun Prototype T4 Blueprint'), 
-                         (1, ('Assault Railgun IV', {'quality': 4})),
-                         (1, 'Tungsten plate'),
-                         (4, 'Screened battery'),
-                         (50, 'Alien Monocrystal')
-                         ]},
-            {'item': ('Assault Railgun V', {'quality': 5}),
-             'recipee': [(1, 'Assault Railgun Prototype T5 Blueprint'), 
-                         (1, ('Assault Railgun V', {'quality': 4})),
-                         (3, 'Tungsten plate'),
-                         (5, 'Screened battery'),
-                         (70, 'Alien Monocrystal')
-                         ]},
-            # beam
-             {'item': ('Beam Cannon III', {'quality': 5}),
-             'recipee': [(1, 'Beam Cannon Prototype T3 Blueprint'), 
-                         (1, ('Beam Cannon III', {'quality': 4})),
-                         (6, 'Metal blank'),
-                         (3, 'Screened battery'),
-                         (30, 'Alien Monocrystal')
-                         ]},
-            {'item': ('Beam Cannon IV', {'quality': 5}),
-             'recipee': [(1, 'Beam Cannon Prototype T4 Blueprint'), 
-                         (1, ('Beam Cannon IV', {'quality': 4})),
-                         (1, 'Tungsten plate'),
-                         (4, 'Screened battery'),
-                         (50, 'Alien Monocrystal')
-                         ]},
-            {'item': ('Beam Cannon V', {'quality': 5}),
-             'recipee': [(1, 'Beam Cannon Prototype T5 Blueprint'), 
-                         (1, ('Beam Cannon V', {'quality': 4})),
-                         (3, 'Tungsten plate'),
-                         (5, 'Screened battery'),
-                         (70, 'Alien Monocrystal')
-                         ]},
-            # missiles
-            {'item': 'Doomsday Missile',
-             'recipee': [(1, 'Doomsday Missile Blueprint'), 
-                         (2, 'Osmium crystals'),
-                         (1, 'Computing chip'), 
-                         (1, 'Metal blank'),
-                         ]},
-            ]
-    
-    for ore in ORES:
-        fields = {'typ': 12,
-                  'tech': 0,
-                  'craftable': True,
-                     }
-        fields.update(ore)
-        data.append({'model': 'scon.item', 'fields': fields})
-    for mat in MATERIALS:
-        fields = {'typ': 13,
-                  'tech': 0,
-                  'craftable': True,
-                     }
-        fields.update(mat)
-        data.append({'model': 'scon.item', 'fields': fields})
-    
-    for ammo in AMMOS:
-        fields = {'typ': 8,
-                  'tech': 0,
-                  'craftable': True,
-                     }
-        fields.update(ammo)
-        data.append({'model': 'scon.item', 'fields': fields})
-    
-    for item in ITEMS:
-        fields = {
-                  # items define typ and tech!
-                  'craftable': True,
-                     }
-        fields.update(item)
-        data.append({'model': 'scon.item', 'fields': fields})
-    
-    for item in ITEMS_NON_CRAFTING:
-        fields = {
-                  # items define typ and tech!
-                  'craftable': False,
-                     }
-        fields.update(item)
-        data.append({'model': 'scon.item', 'fields': fields})
-        
-    for bluep in BLUEPRINTS:
-        fields = {
-                  'typ': 11, # blueprint
-                  'tech': 0,
-                  'craftable': True,
-                  'icon': 'blueprint',
-                     }
-        fields.update(bluep)
-        data.append({'model': 'scon.item', 'fields': fields})
-        
-    
-    build_pk_cache(data)
-    # now to the crafting recipees:
-    i = 1 # counter for crafting
-    j = 1 # counter for input
-    for craft in CRAFTING:
-        try:
-            item = craft['item']
-            kwargs = None
-            if isinstance(item, tuple) or isinstance(item, list):
-                kwargs = item[1]
-                item = item[0]
-            crafting = {'model': 'scon.crafting',
-                        'pk': i,
-                        'fields': { 'output': lookup_pk(data, item, kwargs=kwargs ),
-                                    'amount': craft.get('amount', 1) }}
-            data.append(crafting)
-            primary = True
-            for amount, recipee in craft['recipee']:
-                item = recipee
-                kwargs = None
-                if isinstance(item, tuple) or isinstance(item, list):
-                    print item
-                    kwargs = item[1]
-                    item = item[0]
-                    
-                crafting_input = {'model': 'scon.craftinginput',
-                                  'pk': j,
-                                  'fields': {'crafting': i,
-                                             'item': lookup_pk(data, item, kwargs=kwargs),
-                                             'amount': amount,
-                                             'primary': primary}
-                                  }
-                primary = False
-                j = j + 1
-                data.append(crafting_input)
-            i = i + 1
-        except:
-            raise
-    
-    build_pk_cache(data)
-    return data
-    
-if __name__ == "__main__":
-    fixes = generate_fixtures()
-    from pprint import pprint
-    pprint( fixes )
-    # check pks:
-    for d in fixes:
-        if d.get('pk', None) is None:
-            print "%s is fail." % d
-    
+"""
+    Generate Fixtures for Crafting.
+    Simple generator, does not integrate well into existing stuff, so please use
+    only for bootstrapping.
+"""
+import os, json
+BASE_DIR = os.path.dirname(os.path.dirname(__file__))
+DIR = os.path.join(BASE_DIR, 'scon', 'fixtures')
+
+def write_fixture(data):
+    f = open(os.path.join(DIR, 'generated.json'), 'w')
+    f.write(json.dumps(data))
+    f.close()
+
+def build_pk_cache(data, models=None):
+    pk_cache = {}
+    # fill cache from existing
+    for d in data:
+        if 'pk' in list(d.keys()):
+            # has pk
+            pk_cache[d['model']] = max(pk_cache.get('model', 0), d['pk'])
+    for d in data:
+        m = d['model']
+        if models:
+            if m not in models:
+                continue
+        if 'pk' in list(d.keys()):
+            #print "PK was already in there! %s" % d
+            pass
+        else:
+            if m not in list(pk_cache.keys()):
+                pk_cache[m] = 1
+                i = 1
+            else:
+                i = pk_cache[m] + 1
+                pk_cache[m] = i
+            d['pk'] = i        
+    return data
+
+def lookup_pk(data, name, mdl='scon.item', kwargs=None):
+    for d in data:
+        if d['model'] == mdl:
+            if d['fields'].get('name', '').lower() == name.lower():
+                found = True
+                if kwargs is not None:
+                    for key, val in list(kwargs.items()):
+                        if not d['fields'].get(key, None) == val:
+                            found = False
+                if found:
+                    return d['pk']
+
+def generate_fixtures():
+    data = []
+    
+    ORES = [
+            {'name': 'Impure tungsten', 'sell_price': 6600, 'icon': 'resource_tungsten_ore'},
+            {'name': 'Impure osmium', 'sell_price': 4500, 'icon': 'resource_osmium_ore'},
+            {'name': 'Impure silicon', 'sell_price': 600, 'icon': 'resource_silicon_ore'},
+            {'name': 'Vanadium', 'sell_price': 500, 'icon': 'resource_vanadium'},
+            {'name': 'Crystal shard', 'sell_price': 3500, 'icon': 'resource_crystal_shard'},
+            ]
+    MATERIALS = [
+            {'name': 'Tungsten plate', 
+             'description': 'Durable tungsten plate', 
+             'sell_price': 20000,
+             'icon': 'component_tungsten_plate'},
+            {'name': 'Screened battery', 'sell_price': 42000, 
+             'icon': 'component_screened_battery'},
+            {'name': 'Osmium crystals', 'sell_price': 5500, 
+             'icon': 'component_osmium_crystals'},
+            {'name': 'Pure Silicon', 'sell_price': 2500, 
+             'icon': 'component_pure_silicon'},
+            {'name': 'Processing block', 'sell_price': 22000, 
+             'icon': 'component_processing_block'},
+            {'name': 'Metal blank', 'sell_price': 1600, 
+             'icon': 'component_metal_blank'},
+            {'name': 'Alien Monocrystal', 'sell_price': 25000, 
+             'icon': 'component_alien_monocrystal'},
+            {'name': 'Computing chip', 'sell_price': 4500, 
+             'icon': 'component_computing_chip'},
+            ]
+    AMMOS = [
+            {'name': 'Explosive Shells',
+             'quality': 4,
+             'sell_price': 1000,
+             'icon': 'ammo_explosive_shells_mk4',
+             },
+            {'name': 'Double Deflector',
+             'quality': 4,
+             'sell_price': 1000,
+             'icon': 'ammo_double_deflector_mk4',
+             },
+            {'name': 'Xenon Lamp',
+             'quality': 4,
+             'sell_price': 1000,
+             'icon': 'ammo_xenon_lamp_mk4',
+             },
+            {'name': 'Attack Drone',
+             'quality': 10,
+             'sell_price': 1092,
+             'icon': 'ammo_attack_drone',
+             },
+             {'name': 'Focusing Lens',
+              'quality': 4,
+              'sell_price': 1000,
+              'icon': 'ammo_focusing_lens',
+              },
+             {'name': 'Iridium Slugs',
+              'quality': 4,
+              'sell_price': 1000,
+              'icon': 'ammo_iridium_slugs',
+              },
+             {'name': 'Supercooled Charges',
+              'quality': 4,
+              'sell_price': 1000,
+              'icon': 'ammo_supercooled_charges',
+              },
+             {'name': 'Doomsday Missile',
+              'quality': 1,
+              'sell_price': 1000,
+              #'tech': 5,
+              'icon': 'ammo_doomsday_missile',
+              }
+             ]
+    
+    ITEMS_NON_CRAFTING = [
+             {'name': 'Target Tracking Coprocessor III',
+              'typ': 5, # cpu
+              'tech': 3,
+              'sell_price': 20188,
+              'description': 'Increases Critical Damage',
+              'icon': 'cpu_target_tracking_coprocessor',
+              },
+            {'name': 'Plasma Gun III',
+              'typ': 7, # weap
+              'quality': 4,
+              'tech': 3,
+              'icon': 'weapon_plasma_gun',
+              },
+            {'name': 'Plasma Gun IV',
+              'typ': 7, # weap
+              'quality': 4,
+              'tech': 4,
+              'icon': 'weapon_plasma_gun',
+              },
+            {'name': 'Plasma Gun V',
+              'typ': 7, # weap
+              'quality': 4,
+              'tech': 5,
+              'icon': 'weapon_plasma_gun',
+              },
+            # assault rails:
+            {'name': 'Assault Railgun III',
+              'typ': 7, # weap
+              'quality': 4,
+              'tech': 3,
+              'icon': 'weapon_assault_railgun',
+              },
+            {'name': 'Assault Railgun IV',
+              'typ': 7, # weap
+              'quality': 4,
+              'tech': 4,
+              'icon': 'weapon_assault_railgun',
+              },
+            {'name': 'Assault Railgun V',
+              'typ': 7, # weap
+              'quality': 4,
+              'tech': 5,
+              'icon': 'weapon_assault_railgun',
+              },
+            # beam cannon:
+            {'name': 'Beam Cannon III',
+              'typ': 7, # weap
+              'quality': 4,
+              'tech': 3,
+              'icon': 'weapon_beam_cannon',
+              },
+            {'name': 'Beam Cannon IV',
+              'typ': 7, # weap
+              'quality': 4,
+              'tech': 4,
+              'icon': 'weapon_beam_cannon',
+              },
+            {'name': 'Beam Cannon V',
+              'typ': 7, # weap
+              'quality': 4,
+              'tech': 5,
+              'icon': 'weapon_beam_cannon',
+              },
+            ]
+    
+    ITEMS = [
+             {'name': 'Duplicator',
+              'typ': 0,
+              'sell_price': 8000,
+              'buy_price_premium': 200,
+              'description': 'Revives in Invasion with Cargo once.',
+              'icon': 'duplicator',
+              },
+             {'name': 'A1MA IV',
+              'quality': 1,
+              'tech': 4,
+              'typ': 8, # active.
+              'role': 0, # multipurp.
+              'sell_price': 26910,
+              'icon': 'active_a1ma',
+              },
+             {'name': 'Pirate "Orion" Targeting Complex V',
+              'quality': 14,
+              'tech': 5,
+              'typ': 8, # active.
+              'role': 3, # covops
+              'icon': 'active_t5_orion_targeting_complex_pirate', 
+              },
+             {'name': 'Pirate Engine Overcharge V',
+              'quality': 14,
+              'tech': 5,
+              'typ': 8, # active.
+              'role': 6, # gunship
+              'icon': 'active_t5_engine_overcharge_pirate', 
+              },
+             {'name': 'Pirate Mass Shield Generator V',
+              'quality': 14,
+              'tech': 5,
+              'typ': 8, # active.
+              'role': 7, # engi 
+              'icon': 'active_t5_mass_shield_generator_pirate',
+              },
+             {'name': 'Reverse Thruster III',
+              'quality': 1,
+              'tech': 3,
+              'typ': 8, # active.
+              'role': 9, # LRF 
+              'icon': 'active_reverse_thruster',
+              },
+             {'name': 'Reverse Thruster IV',
+              'quality': 1,
+              'tech': 4,
+              'typ': 8, # active.
+              'role': 9, # LRF
+              'icon': 'active_reverse_thruster', 
+              },
+             {'name': 'Reverse Thruster V',
+              'quality': 1,
+              'tech': 5,
+              'typ': 8, # active.
+              'role': 9, # LRF 
+              'icon': 'active_reverse_thruster',
+              },
+             {'name': 'Plasma Gun III',
+              'quality': 5,
+              'tech': 3,
+              'typ': 7, # weap 
+              'icon': 'weapon_plasma_gun_mk5',
+              },
+             {'name': 'Plasma Gun IV',
+              'quality': 5,
+              'tech': 4,
+              'typ': 7, # weap
+              'icon': 'weapon_plasma_gun_mk5', 
+              },
+             {'name': 'Plasma Gun V',
+              'quality': 5,
+              'tech': 5,
+              'typ': 7, # weap 
+              'icon': 'weapon_plasma_gun_mk5',
+              },
+             {'name': 'Assault Railgun III',
+              'quality': 5,
+              'tech': 3,
+              'typ': 7, # weap 
+              'icon': 'weapon_assault_rail_mk5',
+              },
+             {'name': 'Assault Railgun IV',
+              'quality': 5,
+              'tech': 4,
+              'typ': 7, # weap
+              'icon': 'weapon_assault_rail_mk5', 
+              },
+             {'name': 'Assault Railgun V',
+              'quality': 5,
+              'tech': 5,
+              'typ': 7, # weap 
+              'icon': 'weapon_assault_rail_mk5',
+              },
+             {'name': 'Beam Cannon III',
+              'quality': 5,
+              'tech': 3,
+              'typ': 7, # weap 
+              'icon': 'weapon_beam_cannon_mk5',
+              },
+             {'name': 'Beam Cannon IV',
+              'quality': 5,
+              'tech': 4,
+              'typ': 7, # weap
+              'icon': 'weapon_beam_cannon_mk5', 
+              },
+             {'name': 'Beam Cannon V',
+              'quality': 5,
+              'tech': 5,
+              'typ': 7, # weap 
+              'icon': 'weapon_beam_cannon_mk5',
+              },
+             ]
+    BLUEPRINTS = [
+            {'name': 'Focusing Lens Blueprint'},
+            {'name': 'Iridium Slugs Blueprint'},
+            {'name': 'Supercooled Charges Blueprint'},
+            {'name': 'A1MA T4 Blueprint'},
+            {'name': 'Orion-2 Targeting Complex Blueprint',
+             'description': 'Module works twice as long but much weaker.'},
+            {'name': 'Engine Warp Overcharge Blueprint'},
+            {'name': 'Mass Shield Energizer Blueprint'},
+            {'name': 'Reverse Thruster T3 Blueprint'},
+            {'name': 'Reverse Thruster T4 Blueprint'},
+            {'name': 'Reverse Thruster T5 Blueprint'},
+            {'name': 'Beam Cannon Prototype T3 Blueprint'},
+            {'name': 'Beam Cannon Prototype T4 Blueprint'},
+            {'name': 'Beam Cannon Prototype T5 Blueprint'},
+            {'name': 'Assault Railgun Prototype T3 Blueprint'},
+            {'name': 'Assault Railgun Prototype T4 Blueprint'},
+            {'name': 'Assault Railgun Prototype T5 Blueprint'},
+            {'name': 'Plasma Gun Prototype T3 Blueprint'},
+            {'name': 'Plasma Gun Prototype T4 Blueprint'},
+            {'name': 'Plasma Gun Prototype T5 Blueprint'},
+            {'name': 'Doomsday Missile Blueprint'},
+            ]
+    CRAFTING = [
+            {'item': 'Duplicator',
+             'recipee': [(1, 'Processing Block'), (2,'Computing chip'), (2, 'Metal blank')]},
+            {'item': 'Tungsten plate',
+             'recipee': [(2, 'Impure tungsten'),]},
+            {'item': 'Screened battery',
+             'recipee': [(1, 'Tungsten plate'), (2, 'Computing chip')]},
+            {'item': 'Osmium crystals',
+             'recipee': [(1, 'Impure osmium'),]},
+            {'item': 'Pure Silicon',
+             'recipee': [(1, 'Impure silicon'),]},
+            {'item': 'Computing chip',
+             'recipee': [(1, 'Crystal shard'),]},
+            {'item': 'Processing block',
+             'recipee': [(4, 'Pure Silicon'), (2, 'Computing chip')]},
+            {'item': 'Metal blank',
+             'recipee': [(2, 'Vanadium'),]},
+            {'item': 'Target Tracking Coprocessor III',
+             'recipee': [(1, 'Screened battery'), (7, 'Metal blank'), (5, 'Pure silicon')]},
+            {'item': 'Explosive Shells',
+             'amount': 2,
+             'recipee': [(1, 'Metal blank'),(2, 'Pure Silicon')]},
+            {'item': 'Attack drone',
+             'recipee': [(1, 'Alien Monocrystal'), (1,'Computing chip')]},
+            {'item': 'Double Deflector',
+             'amount': 2,
+             'recipee': [(1, 'Osmium crystals'),]},
+            {'item': 'Xenon Lamp',
+             'amount': 2,
+             'recipee': [(1, 'Computing chip'), (1, 'Pure Silicon')]},
+            
+            {'item': 'Focusing Lens',
+             'amount': 2,
+             'recipee': [(1, 'Focusing Lens Blueprint'), (1, 'Osmium crystals')]},
+            {'item': 'Supercooled Charges',
+             'amount': 2,
+             'recipee': [(1, 'Supercooled Charges Blueprint'), (1, 'Computing chip')]},
+            {'item': 'Iridium Slugs',
+             'amount': 2,
+             'recipee': [(1, 'Iridium Slugs Blueprint'), (1, 'Metal blank')]},
+            {'item': 'A1MA IV',
+             'recipee': [(1, 'A1MA T4 Blueprint'), (2, 'Processing block'),
+                         (14, 'Metal blank'), (2, 'Screened battery'),
+                         (20, 'Alien Monocrystal')]},
+            {'item': 'Pirate "Orion" Targeting Complex V',
+             'recipee': [(1, 'Orion-2 Targeting Complex Blueprint'), 
+                         (3, 'Tungsten plate'),
+                         (4, 'Computing chip'), 
+                         (2, 'Processing block'),
+                         (30, 'Alien Monocrystal')
+                         ]},
+            {'item': 'Pirate Engine Overcharge V',
+             'recipee': [(1, 'Engine Warp Overcharge Blueprint'), 
+                         (3, 'Tungsten plate'),
+                         (2, 'Osmium crystals'), 
+                         (2, 'Processing block'),
+                         (30, 'Alien Monocrystal')
+                         ]},
+            {'item': 'Pirate Mass Shield Generator V',
+             'recipee': [(1, 'Mass Shield Energizer Blueprint'), 
+                         (10, 'Metal blank'),
+                         (3, 'Computing chip'), 
+                         (3, 'Processing block'),
+                         (30, 'Alien Monocrystal')
+                         ]},
+            # lrf reverse blink:
+            {'item': 'Reverse Thruster III',
+             'recipee': [(1, 'Reverse Thruster T3 Blueprint'), 
+                         (7, 'Metal blank'),
+                         (1, 'Screened battery'),
+                         (4, 'Computing chip'), 
+                         (15, 'Alien Monocrystal')
+                         ]},
+            {'item': 'Reverse Thruster IV',
+             'recipee': [(1, 'Reverse Thruster T4 Blueprint'), 
+                         (12, 'Metal blank'),
+                         (2, 'Screened battery'),
+                         (5, 'Computing chip'), 
+                         (20, 'Alien Monocrystal')
+                         ]},
+            {'item': 'Reverse Thruster V',
+             'recipee': [(1, 'Reverse Thruster T5 Blueprint'), 
+                         (7, 'Tungsten plate'),
+                         (3, 'Screened battery'),
+                         (6, 'Computing chip'), 
+                         (30, 'Alien Monocrystal')
+                         ]},
+            
+            # plasma
+            {'item': ('Plasma Gun III', {'quality': 5}),
+             'recipee': [(1, 'Plasma Gun Prototype T3 Blueprint'), 
+                         (1, ('Plasma Gun III', {'quality': 4})),
+                         (6, 'Metal blank'),
+                         (3, 'Screened battery'),
+                         (30, 'Alien Monocrystal')
+                         ]},
+            {'item': ('Plasma Gun IV', {'quality': 5}),
+             'recipee': [(1, 'Plasma Gun Prototype T4 Blueprint'), 
+                         (1, ('Plasma Gun IV', {'quality': 4})),
+                         (1, 'Tungsten plate'),
+                         (4, 'Screened battery'),
+                         (50, 'Alien Monocrystal')
+                         ]},
+            {'item': ('Plasma Gun V', {'quality': 5}),
+             'recipee': [(1, 'Plasma Gun Prototype T5 Blueprint'), 
+                         (1, ('Plasma Gun V', {'quality': 4})),
+                         (3, 'Tungsten plate'),
+                         (5, 'Screened battery'),
+                         (70, 'Alien Monocrystal')
+                         ]},
+            # assault
+            {'item': ('Assault Railgun III', {'quality': 5}),
+             'recipee': [(1, 'Assault Railgun Prototype T3 Blueprint'), 
+                         (1, ('Assault Railgun III', {'quality': 4})),
+                         (6, 'Metal blank'),
+                         (3, 'Screened battery'),
+                         (30, 'Alien Monocrystal')
+                         ]},
+            {'item': ('Assault Railgun IV', {'quality': 5}),
+             'recipee': [(1, 'Assault Railgun Prototype T4 Blueprint'), 
+                         (1, ('Assault Railgun IV', {'quality': 4})),
+                         (1, 'Tungsten plate'),
+                         (4, 'Screened battery'),
+                         (50, 'Alien Monocrystal')
+                         ]},
+            {'item': ('Assault Railgun V', {'quality': 5}),
+             'recipee': [(1, 'Assault Railgun Prototype T5 Blueprint'), 
+                         (1, ('Assault Railgun V', {'quality': 4})),
+                         (3, 'Tungsten plate'),
+                         (5, 'Screened battery'),
+                         (70, 'Alien Monocrystal')
+                         ]},
+            # beam
+             {'item': ('Beam Cannon III', {'quality': 5}),
+             'recipee': [(1, 'Beam Cannon Prototype T3 Blueprint'), 
+                         (1, ('Beam Cannon III', {'quality': 4})),
+                         (6, 'Metal blank'),
+                         (3, 'Screened battery'),
+                         (30, 'Alien Monocrystal')
+                         ]},
+            {'item': ('Beam Cannon IV', {'quality': 5}),
+             'recipee': [(1, 'Beam Cannon Prototype T4 Blueprint'), 
+                         (1, ('Beam Cannon IV', {'quality': 4})),
+                         (1, 'Tungsten plate'),
+                         (4, 'Screened battery'),
+                         (50, 'Alien Monocrystal')
+                         ]},
+            {'item': ('Beam Cannon V', {'quality': 5}),
+             'recipee': [(1, 'Beam Cannon Prototype T5 Blueprint'), 
+                         (1, ('Beam Cannon V', {'quality': 4})),
+                         (3, 'Tungsten plate'),
+                         (5, 'Screened battery'),
+                         (70, 'Alien Monocrystal')
+                         ]},
+            # missiles
+            {'item': 'Doomsday Missile',
+             'recipee': [(1, 'Doomsday Missile Blueprint'), 
+                         (2, 'Osmium crystals'),
+                         (1, 'Computing chip'), 
+                         (1, 'Metal blank'),
+                         ]},
+            ]
+    
+    for ore in ORES:
+        fields = {'typ': 12,
+                  'tech': 0,
+                  'craftable': True,
+                     }
+        fields.update(ore)
+        data.append({'model': 'scon.item', 'fields': fields})
+    for mat in MATERIALS:
+        fields = {'typ': 13,
+                  'tech': 0,
+                  'craftable': True,
+                     }
+        fields.update(mat)
+        data.append({'model': 'scon.item', 'fields': fields})
+    
+    for ammo in AMMOS:
+        fields = {'typ': 8,
+                  'tech': 0,
+                  'craftable': True,
+                     }
+        fields.update(ammo)
+        data.append({'model': 'scon.item', 'fields': fields})
+    
+    for item in ITEMS:
+        fields = {
+                  # items define typ and tech!
+                  'craftable': True,
+                     }
+        fields.update(item)
+        data.append({'model': 'scon.item', 'fields': fields})
+    
+    for item in ITEMS_NON_CRAFTING:
+        fields = {
+                  # items define typ and tech!
+                  'craftable': False,
+                     }
+        fields.update(item)
+        data.append({'model': 'scon.item', 'fields': fields})
+        
+    for bluep in BLUEPRINTS:
+        fields = {
+                  'typ': 11, # blueprint
+                  'tech': 0,
+                  'craftable': True,
+                  'icon': 'blueprint',
+                     }
+        fields.update(bluep)
+        data.append({'model': 'scon.item', 'fields': fields})
+        
+    
+    build_pk_cache(data)
+    # now to the crafting recipees:
+    i = 1 # counter for crafting
+    j = 1 # counter for input
+    for craft in CRAFTING:
+        try:
+            item = craft['item']
+            kwargs = None
+            if isinstance(item, tuple) or isinstance(item, list):
+                kwargs = item[1]
+                item = item[0]
+            crafting = {'model': 'scon.crafting',
+                        'pk': i,
+                        'fields': { 'output': lookup_pk(data, item, kwargs=kwargs ),
+                                    'amount': craft.get('amount', 1) }}
+            data.append(crafting)
+            primary = True
+            for amount, recipee in craft['recipee']:
+                item = recipee
+                kwargs = None
+                if isinstance(item, tuple) or isinstance(item, list):
+                    print(item)
+                    kwargs = item[1]
+                    item = item[0]
+                    
+                crafting_input = {'model': 'scon.craftinginput',
+                                  'pk': j,
+                                  'fields': {'crafting': i,
+                                             'item': lookup_pk(data, item, kwargs=kwargs),
+                                             'amount': amount,
+                                             'primary': primary}
+                                  }
+                primary = False
+                j = j + 1
+                data.append(crafting_input)
+            i = i + 1
+        except:
+            raise
+    
+    build_pk_cache(data)
+    return data
+    
+if __name__ == "__main__":
+    fixes = generate_fixtures()
+    from pprint import pprint
+    pprint( fixes )
+    # check pks:
+    for d in fixes:
+        if d.get('pk', None) is None:
+            print(("%s is fail." % d))
+    
     write_fixture(fixes)
\ No newline at end of file
diff --git a/src/scon/dj/scon/views.py b/src/scon/dj/scon/views.py
index 022d568..f7bc75c 100644
--- a/src/scon/dj/scon/views.py
+++ b/src/scon/dj/scon/views.py
@@ -2,8 +2,8 @@
 from django.shortcuts import render
 from django.http import HttpResponse
 from django.template import RequestContext, loader
-import logic
-import models
+from . import logic
+from . import models
 
 def config(request):
     t = loader.get_template('scon/config.html')
diff --git a/src/scon/game/pieces.py b/src/scon/game/pieces.py
index 22d47b9..880f4bb 100644
--- a/src/scon/game/pieces.py
+++ b/src/scon/game/pieces.py
@@ -1,100 +1,100 @@
-
-def res_to_red(res):
-    ''' calculates reduction % of damage from base resistance
-        incoming damage is assumed to be 100.0 to get percentages.
-    '''
-    if res >= 0:
-        fd = 100 / (1.0+res/100.0)
-    else:
-        fd = 100 / (1.0-res/100.0)
-    return 100.0 - fd
-
-def dam_res(dam, res):
-    ''' calculates damage modified by resistance.
-    '''
-    if res >= 0:
-        fd = dam / (1.0+res/100.0)
-    else:
-        fd = dam / (1.0-res/100.0)
-    return fd
-
-class ShipInstance(object):
-    # just testin something.
-    def __init__(self,
-                    shields=None,
-                    hulls=None, 
-                    shield_resis=None, 
-                    hull_resis=None ):
-        self.shield_max = shields or 5000
-        self.hull_max = hulls or 5000
-        shield_resis = shield_resis or (100,100,100)
-        hull_resis = hull_resis or (100,100,100)
-        self.set_shield_res(*shield_resis)
-        self.set_hull_res(*hull_resis)
-        
-    
-    def set_shield_res(self, kn, em, th):
-        self.shield_res_kn = kn
-        self.shield_res_em = em
-        self.shield_res_th = th
-    
-    def set_hull_res(self, kn, em, th):
-        self.hull_res_kn = kn
-        self.hull_res_em = em
-        self.hull_res_th = th
-        
-    def survivability(self):
-        # i have no clue how they calc this.
-        # multiple attempts shows, they are using base pts as measure, but how exactly the calc is?
-        krs = (self.shield_max/100.0 * self.shield_res_kn)
-        ers = (self.shield_max/100.0 * self.shield_res_em)
-        trs = (self.shield_max/100.0 * self.shield_res_th)
-        print "Shield.", krs, ers, trs
-        
-        krh = (self.hull_max/100.0 * self.hull_res_kn)
-        erh = (self.hull_max/100.0 * self.hull_res_em)
-        trh = (self.hull_max/100.0 * self.hull_res_th)
-        print "Hull.", krh, erh, trh
-        
-        #print "?1", ((krs+ers+trs+krh+erh+trh)/6.0)+self.shield_max + self.hull_max
-        print "?2", ((krs+ers+trs+3*self.shield_max)/3.0)+((krh+erh+trh+3*self.hull_max)/3.0)
-        
-        
-        # another try:
-        """
-            lets assume survivability is really measured through applying 1000 dps for 10 secs.
-            
-        """
-        print "Assuming dps..."
-        shield = self.shield_max
-        hull = self.hull_max
-        r1s = shield / (1.0*dam_res(1000, self.shield_res_kn))
-        r2s = shield / (1.0*dam_res(1000, self.shield_res_em))
-        r3s = shield / (1.0*dam_res(1000, self.shield_res_th))
-        print r1s, r2s, r3s
-        rXs = (r1s+r2s+r3s) / 3.0
-        print "Shield survival time at 1kdps", rXs
-
-        r1h = hull / (1.0*dam_res(1000, self.hull_res_kn))
-        r2h = hull / (1.0*dam_res(1000, self.hull_res_em))
-        r3h = hull / (1.0*dam_res(1000, self.hull_res_th))
-        print r1h, r2h, r3h
-        rXh = (r1h+r2h+r3h) / 3.0
-        print "Hull survival time at 1kdps", rXh
-        
-        print "Total survival time ", rXs + rXh, " sec"
-        print "Surv should be ", int(round((rXs+rXh) * 1000))
-        
-        
-        
-        return ((krs+ers+trs)/3.0)+self.shield_max + self.hull_max + ((krh+erh+trh)/3.0)
-    
-    
-
-ship = ShipInstance()
-print ship.survivability()
-
-print "#" * 80
-mykatanas=ShipInstance(7664, 4296, (70,61,100), (20,80,50))
-print "We know its 19736... but own calcs say..."
-print mykatanas.survivability()
\ No newline at end of file
+
+def res_to_red(res):
+    ''' calculates reduction % of damage from base resistance
+        incoming damage is assumed to be 100.0 to get percentages.
+    '''
+    if res >= 0:
+        fd = 100 / (1.0+res/100.0)
+    else:
+        fd = 100 / (1.0-res/100.0)
+    return 100.0 - fd
+
+def dam_res(dam, res):
+    ''' calculates damage modified by resistance.
+    '''
+    if res >= 0:
+        fd = dam / (1.0+res/100.0)
+    else:
+        fd = dam / (1.0-res/100.0)
+    return fd
+
+class ShipInstance(object):
+    # just testin something.
+    def __init__(self,
+                    shields=None,
+                    hulls=None, 
+                    shield_resis=None, 
+                    hull_resis=None ):
+        self.shield_max = shields or 5000
+        self.hull_max = hulls or 5000
+        shield_resis = shield_resis or (100,100,100)
+        hull_resis = hull_resis or (100,100,100)
+        self.set_shield_res(*shield_resis)
+        self.set_hull_res(*hull_resis)
+        
+    
+    def set_shield_res(self, kn, em, th):
+        self.shield_res_kn = kn
+        self.shield_res_em = em
+        self.shield_res_th = th
+    
+    def set_hull_res(self, kn, em, th):
+        self.hull_res_kn = kn
+        self.hull_res_em = em
+        self.hull_res_th = th
+        
+    def survivability(self):
+        # i have no clue how they calc this.
+        # multiple attempts shows, they are using base pts as measure, but how exactly the calc is?
+        krs = (self.shield_max/100.0 * self.shield_res_kn)
+        ers = (self.shield_max/100.0 * self.shield_res_em)
+        trs = (self.shield_max/100.0 * self.shield_res_th)
+        print(("Shield.", krs, ers, trs))
+        
+        krh = (self.hull_max/100.0 * self.hull_res_kn)
+        erh = (self.hull_max/100.0 * self.hull_res_em)
+        trh = (self.hull_max/100.0 * self.hull_res_th)
+        print(("Hull.", krh, erh, trh))
+        
+        #print "?1", ((krs+ers+trs+krh+erh+trh)/6.0)+self.shield_max + self.hull_max
+        print(("?2", ((krs+ers+trs+3*self.shield_max)/3.0)+((krh+erh+trh+3*self.hull_max)/3.0)))
+        
+        
+        # another try:
+        """
+            lets assume survivability is really measured through applying 1000 dps for 10 secs.
+            
+        """
+        print("Assuming dps...")
+        shield = self.shield_max
+        hull = self.hull_max
+        r1s = shield / (1.0*dam_res(1000, self.shield_res_kn))
+        r2s = shield / (1.0*dam_res(1000, self.shield_res_em))
+        r3s = shield / (1.0*dam_res(1000, self.shield_res_th))
+        print((r1s, r2s, r3s))
+        rXs = (r1s+r2s+r3s) / 3.0
+        print(("Shield survival time at 1kdps", rXs))
+
+        r1h = hull / (1.0*dam_res(1000, self.hull_res_kn))
+        r2h = hull / (1.0*dam_res(1000, self.hull_res_em))
+        r3h = hull / (1.0*dam_res(1000, self.hull_res_th))
+        print((r1h, r2h, r3h))
+        rXh = (r1h+r2h+r3h) / 3.0
+        print(("Hull survival time at 1kdps", rXh))
+        
+        print(("Total survival time ", rXs + rXh, " sec"))
+        print(("Surv should be ", int(round((rXs+rXh) * 1000))))
+        
+        
+        
+        return ((krs+ers+trs)/3.0)+self.shield_max + self.hull_max + ((krh+erh+trh)/3.0)
+    
+    
+
+ship = ShipInstance()
+print((ship.survivability()))
+
+print(("#" * 80))
+mykatanas=ShipInstance(7664, 4296, (70,61,100), (20,80,50))
+print("We know its 19736... but own calcs say...")
+print((mykatanas.survivability()))
\ No newline at end of file
diff --git a/src/scon/gui/treeview.py b/src/scon/gui/treeview.py
index e42a9c2..6e551ce 100644
--- a/src/scon/gui/treeview.py
+++ b/src/scon/gui/treeview.py
@@ -1,444 +1,444 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-## FROM:
-# https://github.com/cloudformdesign/cloudtb/blob/master/extra/PyQt/treeview.py
-# ****** The Cloud Toolbox v0.1.2******
-# This is the cloud toolbox -- a single module used in several packages
-# found at <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)
     
\ No newline at end of file
diff --git a/src/scon/gui/viewer.py b/src/scon/gui/viewer.py
index ff59fa6..1f88023 100644
--- a/src/scon/gui/viewer.py
+++ b/src/scon/gui/viewer.py
@@ -13,7 +13,7 @@ import sys
 from PyQt4 import QtCore, QtGui, QtWebKit, QtNetwork
 from scon.dejaqt.folders import FolderLibrary
 from scon.dejaqt.qweb import DejaWebView
-from treeview import TreeViewModel, Node
+from .treeview import TreeViewModel, Node
 
 class MenuTree(QtGui.QTreeView):
     def __init__(self, *args, **kwargs):
diff --git a/src/scon/logs/base.py b/src/scon/logs/base.py
index 8415e91..439de57 100644
--- a/src/scon/logs/base.py
+++ b/src/scon/logs/base.py
@@ -1,89 +1,89 @@
-import logging
-"""
-    Base Class for a Logentry is Log. Stacktrace is an exception, which gets injected if a stacktrace 
-    is assumed, and swallows all following unrecognized logs.
-    
-    -> It gets parsed by a Logstream, like the Logfile, but might also be used to be feeded 
-       by live-streams of currently open log files.
-    
-    -> Logfiles is responsible to read whole packs of files, and 
-    -> Sessions are responsible for reading whole directories.
-    
-    A Log object usually will expand itself containing "values", and is responsible to retain all dynamic data needed to describe it in unpack()
-    The classmethod is_handler should pre-scan a log, which is usually a dict containing the actual log in log['log']
-    but it could be a string aswell.
-    
-    clean is called to make a log object independent of its source information, and delete all incoming data, so it becomes sleek.
-    reviewed is an internal boolean, which supposed to be saved on successful unpack, unpack should ignore already unpacked logs.
-    matcher is a regex object to match, or a list of them.
-    trash is a boolean flag to indicate, this log is possibly unknown information or unneeded, and should be removed or ignored.
-    
-    -> Note for anyone creating new subclasses for parsing: 
-        All classes are to be __slot__-ed so they can be created more efficiently by python. 
-        A class without __slot__ will slow down parsing exponentially in CPython. 
-        __slots__ hinder you to add new properties on the fly in the code, but having this immutable class optimizes memory allocation.
-        
-        This is the reason, the base layout of the log object is explained here.
-"""
-
-
-L_CMBT = 'CMBT'
-L_WARNING = 'WARNING'
-L_NET = 'NET' # Not supported in near future.
-L_CHAT = 'CHAT'
-
-class Log(object):
-    __slots__ = ['matcher', 'trash', 'reviewed']
-    matcher = None
-    trash = False
-    reviewed = False
-    
-    @classmethod
-    def is_handler(cls, log):
-        return False
-    
-    def unpack(self, force=False):
-        ''' unpacks this log from its data and saves values '''
-        pass
-    
-    def explain(self):
-        ''' returns a String readable by humans explaining this Log '''
-        return ''
-    
-    def clean(self):
-        ''' tell the log to forget all non-essential data '''
-        pass
-    
-    def append(self, something):
-        ''' returns true if this logfile wants an unrecognized log appended to it. '''
-        return False
-
-class Stacktrace(Log):
-    ''' Special Log to catch error reports '''
-    def __init__(self, values=None):
-        super(Stacktrace, self).__init__()
-        self.message = values or ''
-        if isinstance(self.message, dict):
-            self.message = self.message.get('log', '')
-        #self.trash = True
-    
-    @classmethod
-    def is_handler(cls, log):
-        # do i have a system crash report beginning here?
-        if isinstance(log, basestring):
-            l = log.strip()
-        elif isinstance(log, dict):
-            l = log.get('log', '').strip()
-        else:
-            return False
-        if l.startswith('Stack trace:') or l.startswith('BitStream::DbgLog'):
-            return True
-    
-    def clean(self):
-        self.message = ''
-    
-    def append(self, something):
-        ''' I take anything! '''
-        logging.debug( "EXC: %s" % something )
-        self.message = '%s\n%s' % (self.message, something)
+import logging
+"""
+    Base Class for a Logentry is Log. Stacktrace is an exception, which gets injected if a stacktrace 
+    is assumed, and swallows all following unrecognized logs.
+    
+    -> It gets parsed by a Logstream, like the Logfile, but might also be used to be feeded 
+       by live-streams of currently open log files.
+    
+    -> Logfiles is responsible to read whole packs of files, and 
+    -> Sessions are responsible for reading whole directories.
+    
+    A Log object usually will expand itself containing "values", and is responsible to retain all dynamic data needed to describe it in unpack()
+    The classmethod is_handler should pre-scan a log, which is usually a dict containing the actual log in log['log']
+    but it could be a string aswell.
+    
+    clean is called to make a log object independent of its source information, and delete all incoming data, so it becomes sleek.
+    reviewed is an internal boolean, which supposed to be saved on successful unpack, unpack should ignore already unpacked logs.
+    matcher is a regex object to match, or a list of them.
+    trash is a boolean flag to indicate, this log is possibly unknown information or unneeded, and should be removed or ignored.
+    
+    -> Note for anyone creating new subclasses for parsing: 
+        All classes are to be __slot__-ed so they can be created more efficiently by python. 
+        A class without __slot__ will slow down parsing exponentially in CPython. 
+        __slots__ hinder you to add new properties on the fly in the code, but having this immutable class optimizes memory allocation.
+        
+        This is the reason, the base layout of the log object is explained here.
+"""
+
+
+L_CMBT = 'CMBT'
+L_WARNING = 'WARNING'
+L_NET = 'NET' # Not supported in near future.
+L_CHAT = 'CHAT'
+
+class Log(object):
+    __slots__ = ['matcher', 'trash', 'reviewed']
+    matcher = None
+    trash = False
+    reviewed = False
+    
+    @classmethod
+    def is_handler(cls, log):
+        return False
+    
+    def unpack(self, force=False):
+        ''' unpacks this log from its data and saves values '''
+        pass
+    
+    def explain(self):
+        ''' returns a String readable by humans explaining this Log '''
+        return ''
+    
+    def clean(self):
+        ''' tell the log to forget all non-essential data '''
+        pass
+    
+    def append(self, something):
+        ''' returns true if this logfile wants an unrecognized log appended to it. '''
+        return False
+
+class Stacktrace(Log):
+    ''' Special Log to catch error reports '''
+    def __init__(self, values=None):
+        super(Stacktrace, self).__init__()
+        self.message = values or ''
+        if isinstance(self.message, dict):
+            self.message = self.message.get('log', '')
+        #self.trash = True
+    
+    @classmethod
+    def is_handler(cls, log):
+        # do i have a system crash report beginning here?
+        if isinstance(log, str):
+            l = log.strip()
+        elif isinstance(log, dict):
+            l = log.get('log', '').strip()
+        else:
+            return False
+        if l.startswith('Stack trace:') or l.startswith('BitStream::DbgLog'):
+            return True
+    
+    def clean(self):
+        self.message = ''
+    
+    def append(self, something):
+        ''' I take anything! '''
+        logging.debug( "EXC: %s" % something )
+        self.message = '%s\n%s' % (self.message, something)
         return True
\ No newline at end of file
diff --git a/src/scon/logs/chat.py b/src/scon/logs/chat.py
index bf31589..18768fc 100644
--- a/src/scon/logs/chat.py
+++ b/src/scon/logs/chat.py
@@ -1,206 +1,206 @@
-# -*- coding: utf-8 -*-
-from logs.base import Log, L_WARNING, Stacktrace
-import re
-"""
-    Responsible for Chat Log.
-    
-    Anything related to chat gets logged here, basicly interesting for chat related stuff mainly.
-    Channel leaving and joining and connection to the chat server get logged here too.
-
--------------------------------
-Maybe add something to create a ColorChart of usernames?
-between 33-33-33 and FF-33 FF-33 FF-33
-
-"""
-
-class ChatLog(Log):
-    __slots__ = ['matcher', 'trash', '_match_id', 'values']
-    
-    @classmethod
-    def is_handler(cls, log):
-        if log.get('logtype', None) == 'CHAT':
-            return cls._is_handler(log)
-        return False
-    
-    @classmethod
-    def _is_handler(cls, log):
-        return False
-    
-    def __init__(self, values=None):
-        self.values = values or {}
-        self.reviewed = False
-    
-    def unpack(self, force=False):
-        if self.reviewed and not force:
-            return True
-        self._match_id = None
-        # unpacks the data from the values.
-        if hasattr(self, 'matcher') and self.matcher:
-            matchers = self.matcher
-            if not isinstance(matchers, list):
-                matchers = [matchers,]
-            for i, matcher in enumerate(matchers):
-                m = matcher.match(self.values.get('log', ''))
-                if m:
-                    self.values.update(m.groupdict())
-                    self._match_id = i
-                    self.reviewed = True
-                    return True
-        # unknown?
-        self.trash = True
-    
-    def explain(self):
-        ''' returns a String readable by humans explaining this Log '''
-        return self.values.get('log', 'Unknown Chat Log')
-    
-    def clean(self):
-        if 'log' in self.values.keys():
-            del self.values['log']
-
-class SystemMessage(ChatLog):
-    matcher = re.compile(r"^<\s+SYSTEM>\s(?P<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,
+             ]
diff --git a/src/scon/logs/combat.py b/src/scon/logs/combat.py
index d39c450..a75fde8 100644
--- a/src/scon/logs/combat.py
+++ b/src/scon/logs/combat.py
@@ -1,304 +1,304 @@
-# -*- coding: utf-8 -*-
-"""
-    Primary Packets for Combat.Log Files.
-    
-    This is the most important part for dealing with actual statistics, since every action taken
-    in a combat instance gets logged here.
-    
-    
-    ------------------------------------
-    Note:
-    All logs start with something like
-    23:53:29.137        | LOGDATA 
-    
-    LOGDATA can be quite different depending on the logfile.
-    
-    other forms encountered:
-    23:54:00.600 WARNING| 
-    
-    combat logs:
-    01:04:38.805 CMBT   | 
-"""
-import re
-from base import Log, L_CMBT, Stacktrace
-import logging
-
-class CombatLog(Log):
-    __slots__ = Log.__slots__ + [ '_match_id', 'values']
-    @classmethod
-    def _log_handler(cls, log):
-        if log.startswith(cls.__name__):
-            return True
-        return False
-    
-    @classmethod
-    def is_handler(cls, log):
-        if log.get('logtype', None) == L_CMBT:
-            return cls._log_handler(log.get('log', '').strip())
-        return False
-    
-    def __init__(self, values=None):
-        self.values = values or {}
-        self.reviewed = False
-    
-    def unpack(self, force=False):
-        if self.reviewed and not force:
-            return True
-        self._match_id = None
-        # unpacks the data from the values.
-        if hasattr(self, 'matcher') and self.matcher:
-            matchers = self.matcher
-            if not isinstance(matchers, list):
-                matchers = [matchers,]
-            for i, matcher in enumerate(matchers):
-                m = matcher.match(self.values.get('log', ''))
-                if m:
-                    self.values.update(m.groupdict())
-                    self._match_id = i
-                    self.reviewed = True
-                    return True
-        # unknown?
-        if not isinstance(self, UserEvent):
-            logging.warning('Unknown Packet for %s:\n%s' % (self.__class__.__name__, 
-                                                             self.values.get('log', '')))
-        # trash if unknown or no matcher.
-        self.trash = True
-    
-    def explain(self):
-        ''' returns a String readable by humans explaining this Log '''
-        return self.values.get('log', 'Unknown Combat Log')
-    
-    def clean(self):
-        if 'log' in self.values.keys():
-            del self.values['log']
-
-                
-# @todo: where does this come from?
-class Action(CombatLog):
-    __slots__ = CombatLog.__slots__
-    pass
-
-class Gameplay(CombatLog):
-    __slots__ = CombatLog.__slots__
-    matcher = [
-        # usual: team(reason). explained reason.
-        re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<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,
+               ]
+
diff --git a/src/scon/logs/game.py b/src/scon/logs/game.py
index b5052d6..cac5de9 100644
--- a/src/scon/logs/game.py
+++ b/src/scon/logs/game.py
@@ -1,195 +1,195 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-from logs.base import Log, L_WARNING, Stacktrace
-import re
-"""
-    This deals with the Game.Log file
-    This file records lots of junk, but is needed to establish actions taken  between combat sessions,
-    or retrieve more detailed information about running instances.
-    It is also the typical place for a Stacktrace to happen.
-
-
---------------------------------------
-Interesting Lines:
-
-23:16:27.427        | Steam initialized appId 212070, userSteamID 1|1|4c5a01, userName 'G4bOrg'
-23:16:36.214        | ====== starting level: 'levels/mainmenu/mainmenu'  ======
-23:16:38.822        | ====== level started:  'levels/mainmenu/mainmenu' success ======
-23:16:44.251        | ====== starting level: 'levels\mainmenu\mm_empire'  ======
-23:16:46.464        | ====== level started:  'levels\mainmenu\mm_empire' success ======
-
---- Date: 2014-07-18 (Fri Jul 2014) Mitteleuropäische Sommerzeit UTC+01:00
-
-23:55:55.517        | MasterServerSession: connect to dedicated server, session 6777304, at addr 159.253.138.162|35005
-23:55:55.543        | client: start connecting to 159.253.138.162|35005...
-23:55:55.683        | client: connected to 159.253.138.162|35005, setting up session...
-23:55:55.886        | client: ADD_PLAYER 0 (OregyenDuero [OWL], 00039C86) status 6 team 1 group 1178422
-23:55:55.886        | client: ADD_PLAYER 1 (R0gue, 0012768A) status 6 team 2 group 1178451
-23:55:55.886        | client: ADD_PLAYER 2 (g4borg [OWL], 0003A848) status 1 team 1 group 1178422
-23:55:55.886        | client: ADD_PLAYER 3 (WladTepes, 001210D8) status 6 team 1
-23:55:55.886        | client: ADD_PLAYER 4 (oberus [], 000FE9B2) status 6 team 2
-23:55:55.886        | client: ADD_PLAYER 5 (TheGuns58, 00121C58) status 6 team 1
-23:55:55.886        | client: ADD_PLAYER 6 (Belleraphon, 0004C744) status 2 team 2
-23:55:55.886        | client: ADD_PLAYER 7 (TopoL, 00007E1F) status 6 team 1
-23:55:55.886        | client: ADD_PLAYER 8 (unicoimbraPT, 000C4FAC) status 6 team 2
-23:55:55.886        | client: ADD_PLAYER 9 (AeroBobik [], 00082047) status 6 team 1
-23:55:55.886        | client: ADD_PLAYER 10 (Samson4321 [], 000B93AF) status 6 team 2
-23:55:55.886        | client: ADD_PLAYER 11 (nol [], 00069165) status 6 team 1
-23:55:55.886        | client: ADD_PLAYER 12 (Pudwoppa, 000334A4) status 2 team 2
-23:55:55.886        | client: ADD_PLAYER 13 (IgorMad [], 000D2AF3) status 6 team 1
-23:55:55.886        | client: ADD_PLAYER 14 (YokaI, 000F1CC9) status 6 team 2
-23:55:55.886        | client: ADD_PLAYER 15 (MrAnyKey [], 0012246C) status 6 team 2 group 1178451
-23:55:55.886        | client: ADD_PLAYER 30 ((bot)David, 00000000) status 4 team 1
-23:55:55.886        | client: ADD_PLAYER 31 ((bot)George, 00000000) status 4 team 2
-23:55:55.886        | client: server assigned id 2
-23:55:55.886        | client: got level load message 's1340_thar_aliendebris13'
-23:55:55.889        | reset d3d device
-23:55:56.487        | ReplayManager: stopping activity due to map change
-23:55:56.576        | ====== starting level: 'levels\area2\s1340_thar_aliendebris13' KingOfTheHill client ======
-
-
-"""
-
-class GameLog(Log):
-    __slots__ = ['matcher', 'trash', '_match_id', 'values']
-    @classmethod
-    def is_handler(cls, log):
-        if log.get('logtype', None) == '': # we handle only logs with empty logtype.
-            return cls._is_handler(log)
-        return False
-    
-    @classmethod
-    def _is_handler(cls, log):
-        return False
-    
-    def __init__(self, values=None):
-        self.values = values
-        self.reviewed = False
-    
-    def clean(self):
-        if 'log' in self.values.keys():
-            del self.values['log']
-    
-    def unpack(self, force=False):
-        if self.reviewed and not force:
-            return True
-        self._match_id = None
-        # unpacks the data from the values.
-        if hasattr(self, 'matcher') and self.matcher:
-            matchers = self.matcher
-            if not isinstance(matchers, list):
-                matchers = [matchers,]
-            for i, matcher in enumerate(matchers):
-                m = matcher.match(self.values.get('log', ''))
-                if m:
-                    self.values.update(m.groupdict())
-                    self._match_id = i
-                    self.reviewed = True
-                    return True
-        # unknown?
-        self.trash = True
-    
-    def explain(self):
-        ''' returns a String readable by humans explaining this Log '''
-        return self.values.get('log', 'Unknown Game Log')
-
-class WarningLog(Log):
-    __slots__ = ['trash',]
-    trash = True
-    
-    @classmethod
-    def is_handler(cls, log):
-        if log.get('logtype', None) == L_WARNING:
-            return True
-        return False
-    
-    def __init__(self, values=None):
-        pass
-
-########################################################################################################
-# Individual logs.
-
-class SteamInitialization(GameLog):
-    matcher = [
-        re.compile(r"^Steam\sinitialized\sappId\s(?P<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,
              ]
\ No newline at end of file
diff --git a/src/scon/logs/logfiles.py b/src/scon/logs/logfiles.py
index 8b578c1..08f0139 100644
--- a/src/scon/logs/logfiles.py
+++ b/src/scon/logs/logfiles.py
@@ -1,49 +1,49 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-"""
-    Resolves Logs.
-"""
-
-from logfile import LogFile
-from combat import COMBAT_LOGS
-from game import GAME_LOGS
-from chat import CHAT_LOGS
-
-class LogFileResolver(LogFile):
-    ''' dynamic logfile resolver '''
-    resolution_classes = COMBAT_LOGS
-    
-    def __init__(self, *args, **kwargs):
-        super(LogFileResolver, self).__init__(*args, **kwargs)
-        self.resolution_classes = self.resolution_classes or []
-    
-    def resolve(self, line):
-        for klass in self.resolution_classes:
-            if klass.is_handler(line):
-                return klass(line)
-        return line
-
-class CombatLogFile(LogFile):
-    ''' Combat Log '''
-    def resolve(self, line):
-        for klass in COMBAT_LOGS:
-            if klass.is_handler(line):
-                return klass(line)
-        return line
-
-class GameLogFile(LogFile):
-    ''' Game Log '''
-    def resolve(self, line):
-        for klass in GAME_LOGS:
-            if klass.is_handler(line):
-                return klass(line)
-        return line
-
-class ChatLogFile(LogFile):
-    ''' Chat Log '''
-    def resolve(self, line):
-        for klass in CHAT_LOGS:
-            if klass.is_handler(line):
-                return klass(line)
-        return line
-
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+    Resolves Logs.
+"""
+
+from .logfile import LogFile
+from .combat import COMBAT_LOGS
+from .game import GAME_LOGS
+from .chat import CHAT_LOGS
+
+class LogFileResolver(LogFile):
+    ''' dynamic logfile resolver '''
+    resolution_classes = COMBAT_LOGS
+    
+    def __init__(self, *args, **kwargs):
+        super(LogFileResolver, self).__init__(*args, **kwargs)
+        self.resolution_classes = self.resolution_classes or []
+    
+    def resolve(self, line):
+        for klass in self.resolution_classes:
+            if klass.is_handler(line):
+                return klass(line)
+        return line
+
+class CombatLogFile(LogFile):
+    ''' Combat Log '''
+    def resolve(self, line):
+        for klass in COMBAT_LOGS:
+            if klass.is_handler(line):
+                return klass(line)
+        return line
+
+class GameLogFile(LogFile):
+    ''' Game Log '''
+    def resolve(self, line):
+        for klass in GAME_LOGS:
+            if klass.is_handler(line):
+                return klass(line)
+        return line
+
+class ChatLogFile(LogFile):
+    ''' Chat Log '''
+    def resolve(self, line):
+        for klass in CHAT_LOGS:
+            if klass.is_handler(line):
+                return klass(line)
+        return line
+
diff --git a/src/scon/logs/logstream.py b/src/scon/logs/logstream.py
index bd1a314..4f2419e 100644
--- a/src/scon/logs/logstream.py
+++ b/src/scon/logs/logstream.py
@@ -1,144 +1,144 @@
-"""
-    A LogStream is supposed to:
-     - parse data feeded into it.
-     - yield new objects
-     - remember errors
-    
-    LogStream.Initialize:
-     - initialize the logstream in some way.
-    
-    LogStream.Next:
-     - once initialized, read your stream until you can yield a new class
-     the next function reads the read-stream ahead.
-     empty lines are omitted
-     it tries to match the data into a new class and yields it
-     if it runs into trouble, it just outputs the line for now.
-    
-    InitializeString:
-     - init with a data blob
-     - nice for trying it on files
-    
-    @TODO: look at how file streams in python are implemented and find a good generic solution
-    combine it with the lookup for "watching files being changed", to create a program which listens to the logs live
-    @see: monitor.py
-    @see: watchdog https://pypi.python.org/pypi/watchdog
-"""
-from .base import Log
-import re
-from logs.base import Stacktrace
-import logging
-RE_SCLOG = r'^(?P<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
diff --git a/src/scon/logs/session.py b/src/scon/logs/session.py
index 3177f6a..bd6a1ca 100644
--- a/src/scon/logs/session.py
+++ b/src/scon/logs/session.py
@@ -1,209 +1,209 @@
-"""
-    Logging Session.
-"""
-import zipfile, logging, os
-from logfiles import CombatLogFile, GameLogFile, ChatLogFile
-
-class LogSession(object):
-    """
-        A basic logsession.
-        deal with data as it comes along, and output interpretable data for the outside world.
-        
-    """
-    pass
-
-class LogFileSession(LogSession):
-    """
-        The Log-File-Session is supposed to save one directory of logs.
-        It can parse its logs, and should become able to build up its internal 
-        structure into Battle Instances etc.
-    """
-    VALID_FILES = ['combat.log', 'game.log', 'chat.log' ] # extend this to other logs.
-    
-    def __init__(self, directory):
-        ''' if directory is a file, it will be handled as a compressed folder '''
-        self.battles = []
-        self.user = None
-        self.files_parsed = []
-        
-        # various logfiles used.
-        self.combat_log = None
-        self.game_log = None
-        self.chat_log = None
-        # self.net_log = None
-        
-        self.directory = directory
-        self._zip_source = None
-        self.idstr = None # id string to identify this log instance.
-        self._error = False
-    
-    def clean(self, remove_log=True):
-        if self.combat_log:
-            self.combat_log.clean(remove_log)
-        if self.game_log:
-            self.game_log.clean(remove_log)
-        if self.chat_log:
-            self.chat_log.clean(remove_log)
-        
-    
-    def validate(self, contents=False):
-        """ 
-          - validates if the logfiles are within this package.
-          - sets the idstr of this object.
-          @todo: in-depth validation of logs, on contents=True.
-        """
-        self._zip_source = os.path.isfile(self.directory) or False
-        v = False
-        try:
-            if self._zip_source:
-                v = self._unzip_validate()
-                if v > 0:
-                    self.idstr = os.path.split(self.directory)[1].replace('.zip', '').lower()
-            else:
-                v = self._validate_files_exist()                
-                if v > 0:
-                    self.idstr = os.path.split(self.directory)[1].lower()
-        except:
-            return False
-        return v            
-    
-    def parse_files(self, files=None):
-        ''' parses the logfiles '''
-        # perform simple validation.
-        if self._zip_source is None:
-            self.validate(False)
-        if self._zip_source:
-            self._unzip_logs(files)
-        else:
-            if files is None:
-                files = self.VALID_FILES
-            if 'combat.log' in files and not 'combat.log' in self.files_parsed:
-                self.combat_log = CombatLogFile(os.path.join(self.directory, 'combat.log'))
-                self.combat_log.read()
-                self.combat_log.parse()
-                self.files_parsed.append('combat.log')
-            if 'game.log' in files and not 'game.log' in self.files_parsed:
-                self.game_log = GameLogFile(os.path.join(self.directory, 'game.log'))
-                self.game_log.read()
-                self.game_log.parse()
-                self.files_parsed.append('game.log')
-            if 'chat.log' in files and not 'chat.log' in self.files_parsed:
-                self.chat_log = ChatLogFile(os.path.join(self.directory, 'chat.log'))
-                self.chat_log.read()
-                self.chat_log.parse()
-                self.files_parsed.append('chat.log')
-    
-    def determine_owner(self):
-        ''' determines the user in the parsed gamelog '''
-        pass
-    
-    def parse_battles(self):
-        ''' parses the battles '''
-        pass
-    
-    def _unzip_logs(self, files=None):
-        z = zipfile.ZipFile(self.directory, "r")
-        try:
-            for filename in z.namelist():
-                fn = os.path.split(filename)[1] or ''
-                fn = fn.lower()
-                if fn:
-                    if fn == 'combat.log' and (not files or fn in files) and not 'combat.log' in self.files_parsed:
-                        self.combat_log = CombatLogFile(fn)
-                        self.combat_log.set_data(z.read(filename))
-                        self.combat_log.parse()
-                        self.files_parsed.append('combat.log')
-                    elif fn == 'game.log' and (not files or fn in files) and not 'game.log' in self.files_parsed:
-                        self.game_log = GameLogFile(fn)
-                        self.game_log.set_data(z.read(filename))
-                        self.game_log.parse()
-                        self.files_parsed.append('game.log')
-                    elif fn == 'chat.log' and (not files or fn in files) and not 'chat.log' in self.files_parsed:
-                        self.chat_log = ChatLogFile(fn)
-                        self.chat_log.set_data(z.read(filename))
-                        self.chat_log.parse()
-                        self.files_parsed.append('chat.log')
-        except:
-            self._error = True
-            return
-        finally:
-            z.close()
-    
-    def _unzip_validate(self):
-        z = zipfile.ZipFile(self.directory, "r")
-        found = 0
-        for filename in z.namelist():
-            fn = os.path.split(filename)[1] or ''
-            fn = fn.lower()
-            if fn and fn in self.VALID_FILES:
-                found += 1
-        z.close()
-        return found
-    
-    def _validate_files_exist(self):
-        found = 0
-        for f in self.VALID_FILES:
-            if os.path.exists(os.path.join(self.directory, f)):
-                found += 1
-        return found
-
-class LogSessionCollector(object):
-    """
-        finds sessions in a directory, a.k.a. you load the log directories
-        of SC into sessions.
-        
-        - find_sessions: only find and instantiate sessions.
-        - collect: validates each found session and returns them as list.
-        - collect_unique: instead of a list, a dict is returned, where each
-        session can be accessed via its idstr. does not parse/validate sessions.
-    """
-    def __init__(self, directory):
-        self.initial_directory = directory
-        self.sessions = []
-        self.find_sessions()
-    
-    def find_sessions(self):
-        for f in os.listdir(self.initial_directory):
-            full_dir = os.path.join(self.initial_directory, f)
-            if os.path.isdir(full_dir) or full_dir.lower().endswith('.zip'):
-                self.sessions.append(LogFileSession(full_dir))
-    
-    def collect(self):
-        sessions = []
-        for session in self.sessions:
-            try:
-                if session.validate():
-                    sessions.append(session)       
-            except:
-                continue
-        return sessions
-    
-    def collect_unique(self):
-        ''' collects all sessions into a dictionary ordered by their idstr.
-            sessions without idstr, or already existing (first served) are omitted
-            parsing is not done.
-        '''
-        # note this function resets sessions to the working ones.
-        self.sessions = self.collect()
-        sessions_dict = {}
-        for session in self.sessions:
-            if session.idstr and not session.idstr in sessions_dict.keys():
-                sessions_dict[session.idstr] = session
-        return sessions_dict
-    
-    def clean(self, remove_log=True):
-        for session in self.sessions:
-            session.clean(remove_log)
-        
-        
-
-
-if __name__ == '__main__':
-    l_raw = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28')
-    l_zip = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip')
-    
-    l_zip.parse_files()
-    print l_zip.combat_log.lines
-    
-    collector = LogSessionCollector('D:\\Users\\g4b\\Documents\\My Games\\sc\\')
-    print collector.collect_unique()
\ No newline at end of file
+"""
+    Logging Session.
+"""
+import zipfile, logging, os
+from .logfiles import CombatLogFile, GameLogFile, ChatLogFile
+
+class LogSession(object):
+    """
+        A basic logsession.
+        deal with data as it comes along, and output interpretable data for the outside world.
+        
+    """
+    pass
+
+class LogFileSession(LogSession):
+    """
+        The Log-File-Session is supposed to save one directory of logs.
+        It can parse its logs, and should become able to build up its internal 
+        structure into Battle Instances etc.
+    """
+    VALID_FILES = ['combat.log', 'game.log', 'chat.log' ] # extend this to other logs.
+    
+    def __init__(self, directory):
+        ''' if directory is a file, it will be handled as a compressed folder '''
+        self.battles = []
+        self.user = None
+        self.files_parsed = []
+        
+        # various logfiles used.
+        self.combat_log = None
+        self.game_log = None
+        self.chat_log = None
+        # self.net_log = None
+        
+        self.directory = directory
+        self._zip_source = None
+        self.idstr = None # id string to identify this log instance.
+        self._error = False
+    
+    def clean(self, remove_log=True):
+        if self.combat_log:
+            self.combat_log.clean(remove_log)
+        if self.game_log:
+            self.game_log.clean(remove_log)
+        if self.chat_log:
+            self.chat_log.clean(remove_log)
+        
+    
+    def validate(self, contents=False):
+        """ 
+          - validates if the logfiles are within this package.
+          - sets the idstr of this object.
+          @todo: in-depth validation of logs, on contents=True.
+        """
+        self._zip_source = os.path.isfile(self.directory) or False
+        v = False
+        try:
+            if self._zip_source:
+                v = self._unzip_validate()
+                if v > 0:
+                    self.idstr = os.path.split(self.directory)[1].replace('.zip', '').lower()
+            else:
+                v = self._validate_files_exist()                
+                if v > 0:
+                    self.idstr = os.path.split(self.directory)[1].lower()
+        except:
+            return False
+        return v            
+    
+    def parse_files(self, files=None):
+        ''' parses the logfiles '''
+        # perform simple validation.
+        if self._zip_source is None:
+            self.validate(False)
+        if self._zip_source:
+            self._unzip_logs(files)
+        else:
+            if files is None:
+                files = self.VALID_FILES
+            if 'combat.log' in files and not 'combat.log' in self.files_parsed:
+                self.combat_log = CombatLogFile(os.path.join(self.directory, 'combat.log'))
+                self.combat_log.read()
+                self.combat_log.parse()
+                self.files_parsed.append('combat.log')
+            if 'game.log' in files and not 'game.log' in self.files_parsed:
+                self.game_log = GameLogFile(os.path.join(self.directory, 'game.log'))
+                self.game_log.read()
+                self.game_log.parse()
+                self.files_parsed.append('game.log')
+            if 'chat.log' in files and not 'chat.log' in self.files_parsed:
+                self.chat_log = ChatLogFile(os.path.join(self.directory, 'chat.log'))
+                self.chat_log.read()
+                self.chat_log.parse()
+                self.files_parsed.append('chat.log')
+    
+    def determine_owner(self):
+        ''' determines the user in the parsed gamelog '''
+        pass
+    
+    def parse_battles(self):
+        ''' parses the battles '''
+        pass
+    
+    def _unzip_logs(self, files=None):
+        z = zipfile.ZipFile(self.directory, "r")
+        try:
+            for filename in z.namelist():
+                fn = os.path.split(filename)[1] or ''
+                fn = fn.lower()
+                if fn:
+                    if fn == 'combat.log' and (not files or fn in files) and not 'combat.log' in self.files_parsed:
+                        self.combat_log = CombatLogFile(fn)
+                        self.combat_log.set_data(z.read(filename))
+                        self.combat_log.parse()
+                        self.files_parsed.append('combat.log')
+                    elif fn == 'game.log' and (not files or fn in files) and not 'game.log' in self.files_parsed:
+                        self.game_log = GameLogFile(fn)
+                        self.game_log.set_data(z.read(filename))
+                        self.game_log.parse()
+                        self.files_parsed.append('game.log')
+                    elif fn == 'chat.log' and (not files or fn in files) and not 'chat.log' in self.files_parsed:
+                        self.chat_log = ChatLogFile(fn)
+                        self.chat_log.set_data(z.read(filename))
+                        self.chat_log.parse()
+                        self.files_parsed.append('chat.log')
+        except:
+            self._error = True
+            return
+        finally:
+            z.close()
+    
+    def _unzip_validate(self):
+        z = zipfile.ZipFile(self.directory, "r")
+        found = 0
+        for filename in z.namelist():
+            fn = os.path.split(filename)[1] or ''
+            fn = fn.lower()
+            if fn and fn in self.VALID_FILES:
+                found += 1
+        z.close()
+        return found
+    
+    def _validate_files_exist(self):
+        found = 0
+        for f in self.VALID_FILES:
+            if os.path.exists(os.path.join(self.directory, f)):
+                found += 1
+        return found
+
+class LogSessionCollector(object):
+    """
+        finds sessions in a directory, a.k.a. you load the log directories
+        of SC into sessions.
+        
+        - find_sessions: only find and instantiate sessions.
+        - collect: validates each found session and returns them as list.
+        - collect_unique: instead of a list, a dict is returned, where each
+        session can be accessed via its idstr. does not parse/validate sessions.
+    """
+    def __init__(self, directory):
+        self.initial_directory = directory
+        self.sessions = []
+        self.find_sessions()
+    
+    def find_sessions(self):
+        for f in os.listdir(self.initial_directory):
+            full_dir = os.path.join(self.initial_directory, f)
+            if os.path.isdir(full_dir) or full_dir.lower().endswith('.zip'):
+                self.sessions.append(LogFileSession(full_dir))
+    
+    def collect(self):
+        sessions = []
+        for session in self.sessions:
+            try:
+                if session.validate():
+                    sessions.append(session)       
+            except:
+                continue
+        return sessions
+    
+    def collect_unique(self):
+        ''' collects all sessions into a dictionary ordered by their idstr.
+            sessions without idstr, or already existing (first served) are omitted
+            parsing is not done.
+        '''
+        # note this function resets sessions to the working ones.
+        self.sessions = self.collect()
+        sessions_dict = {}
+        for session in self.sessions:
+            if session.idstr and not session.idstr in list(sessions_dict.keys()):
+                sessions_dict[session.idstr] = session
+        return sessions_dict
+    
+    def clean(self, remove_log=True):
+        for session in self.sessions:
+            session.clean(remove_log)
+        
+        
+
+
+if __name__ == '__main__':
+    l_raw = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28')
+    l_zip = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip')
+    
+    l_zip.parse_files()
+    print((l_zip.combat_log.lines))
+    
+    collector = LogSessionCollector('D:\\Users\\g4b\\Documents\\My Games\\sc\\')
+    print((collector.collect_unique()))
\ No newline at end of file
diff --git a/src/scon/monitor.py b/src/scon/monitor.py
index e6bc3ca..3673aca 100644
--- a/src/scon/monitor.py
+++ b/src/scon/monitor.py
@@ -1,161 +1,161 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-"""
-    Monitor a StarConflict Log Directory.
-"""
-import sys, os
-import time
-import logging
-from watchdog.observers import Observer
-from watchdog.events import LoggingEventHandler
-
-class SconEventHandler(LoggingEventHandler):
-    def __init__(self, monitor, *args, **kwargs):
-        self.monitor = monitor
-        return super(SconEventHandler, self).__init__(*args, **kwargs)
-    
-    def on_moved(self, event):
-        super(SconEventHandler, self).on_moved(event)
-        if not event.is_directory:
-            self.monitor.close(event.src_path)
-        self.monitor.notify_event('moved', {'src': event.src_path, 
-                                            'is_dir': event.is_directory, 
-                                            'dest': event.dest_path})
-
-    def on_created(self, event):
-        super(SconEventHandler, self).on_created(event)
-        if not event.is_directory:
-            self.monitor.open(event.src_path)
-        self.monitor.notify_event('created', {'src': event.src_path, 
-                                              'is_dir': event.is_directory})
-
-    def on_deleted(self, event):
-        super(SconEventHandler, self).on_deleted(event)
-        if not event.is_directory:
-            self.monitor.close(event.src_path)
-        self.monitor.notify_event('deleted', {'src': event.src_path, 
-                                              'is_dir': event.is_directory})
-
-    def on_modified(self, event):
-        super(SconEventHandler, self).on_modified(event)
-        self.monitor.notify_event('modified', {'src': event.src_path, 
-                                              'is_dir': event.is_directory})
-        
-        
-
-class SconMonitor(object):
-        
-    def notify(self, filename, lines):
-        # notify somebody, filename has a few lines.
-        # this is basicly the function you want to overwrite.
-        # @see: self.run for how to integrate monitor into your main loop.
-        if self.notifier is not None:
-            self.notifier.notify(filename, lines)
-    
-    def notify_event(self, event_type, data):
-        if self.notifier is not None:
-            self.notifier.notify_event(event_type, data)
-    
-    def __init__(self, path=None, notifier=None):
-        # if you initialize path directly, you lose success boolean.
-        # albeit atm. this is always true.
-        if path is not None:
-            self.initialize(path)
-        self.notifier = notifier
-    
-    def initialize(self, path):
-        # initialize the monitor with a path to observe.
-        self.files = {}
-        self.event_handler = SconEventHandler(self)
-        self.observer = Observer()
-        self.observer.schedule(self.event_handler, 
-                               path, 
-                               recursive=True)
-        return True
-    
-    def open(self, filename=None):
-        # open a logfile and add it to the read-list...
-        f = open(filename, 'r')
-        new_file = { 'file': f,
-                     'cursor': f.tell() }
-        self.files[filename] = new_file
-        return new_file
-    
-    def close(self, filename):
-        # close a single file by key, does not do anything if not found.
-        if filename in self.files.keys():
-            close_file = self.files.pop(filename)
-            close_file['file'].close()
-            del close_file
-    
-    def close_all(self):
-        """ closes all open files in the monitor """
-        for key in self.files.keys():
-            self.close(key)
-    
-    def read_line(self, afile):
-        # read a single line in a file.
-        f = afile.get('file', None)
-        if f is None:
-            return
-        afile['cursor'] = f.tell()
-        line = f.readline()
-        if not line:
-            f.seek(afile['cursor'])
-            return None
-        else:
-            return line
-    
-    def do(self):
-        ''' Monitor main task handler, call this in your mainloop in ~1 sec intervals '''
-        # read all file changes.
-        for key, value in self.files.items():
-            lines = []
-            data = self.read_line(value)
-            while data is not None:
-                lines.append(data)
-                data = self.read_line(value)
-            if lines:
-                self.notify(key, lines)
-            # 
-    
-    def initialize_loop(self):
-        ''' initializes the main loop for the monitor '''
-        self.observer.start()
-    
-    def break_loop(self):
-        ''' call this if you want to break the monitors tasks '''
-        self.observer.stop()
-    
-    def end_loop(self):
-        ''' always call this before exiting your main loop '''
-        self.close_all()
-        self.observer.join()
-        
-    def run(self):
-        ''' Basic Standalone Main Loop implementation, do not call this in your app. '''
-        # if you want to run this on its own.
-        # everytime any logfile is updated, print it.
-        self.initialize_loop()
-        try:
-            while True:
-                self.do()
-                time.sleep(1)
-        except KeyboardInterrupt:
-            self.break_loop()
-        self.end_loop()
-
-if __name__ == '__main__':
-    monitor = SconMonitor()
-    logging.basicConfig(level=logging.INFO,
-                        format='%(asctime)s - %(message)s',
-                        datefmt='%Y-%m-%d %H:%M:%S')
-    path = os.path.join(os.path.expanduser('~'),
-                                     'Documents',
-                                     'My Games',
-                                     'StarConflict',
-                                     'logs'
-                                     )
-    if monitor.initialize(path) is True:
-        monitor.run()
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+    Monitor a StarConflict Log Directory.
+"""
+import sys, os
+import time
+import logging
+from watchdog.observers import Observer
+from watchdog.events import LoggingEventHandler
+
+class SconEventHandler(LoggingEventHandler):
+    def __init__(self, monitor, *args, **kwargs):
+        self.monitor = monitor
+        return super(SconEventHandler, self).__init__(*args, **kwargs)
+    
+    def on_moved(self, event):
+        super(SconEventHandler, self).on_moved(event)
+        if not event.is_directory:
+            self.monitor.close(event.src_path)
+        self.monitor.notify_event('moved', {'src': event.src_path, 
+                                            'is_dir': event.is_directory, 
+                                            'dest': event.dest_path})
+
+    def on_created(self, event):
+        super(SconEventHandler, self).on_created(event)
+        if not event.is_directory:
+            self.monitor.open(event.src_path)
+        self.monitor.notify_event('created', {'src': event.src_path, 
+                                              'is_dir': event.is_directory})
+
+    def on_deleted(self, event):
+        super(SconEventHandler, self).on_deleted(event)
+        if not event.is_directory:
+            self.monitor.close(event.src_path)
+        self.monitor.notify_event('deleted', {'src': event.src_path, 
+                                              'is_dir': event.is_directory})
+
+    def on_modified(self, event):
+        super(SconEventHandler, self).on_modified(event)
+        self.monitor.notify_event('modified', {'src': event.src_path, 
+                                              'is_dir': event.is_directory})
+        
+        
+
+class SconMonitor(object):
+        
+    def notify(self, filename, lines):
+        # notify somebody, filename has a few lines.
+        # this is basicly the function you want to overwrite.
+        # @see: self.run for how to integrate monitor into your main loop.
+        if self.notifier is not None:
+            self.notifier.notify(filename, lines)
+    
+    def notify_event(self, event_type, data):
+        if self.notifier is not None:
+            self.notifier.notify_event(event_type, data)
+    
+    def __init__(self, path=None, notifier=None):
+        # if you initialize path directly, you lose success boolean.
+        # albeit atm. this is always true.
+        if path is not None:
+            self.initialize(path)
+        self.notifier = notifier
+    
+    def initialize(self, path):
+        # initialize the monitor with a path to observe.
+        self.files = {}
+        self.event_handler = SconEventHandler(self)
+        self.observer = Observer()
+        self.observer.schedule(self.event_handler, 
+                               path, 
+                               recursive=True)
+        return True
+    
+    def open(self, filename=None):
+        # open a logfile and add it to the read-list...
+        f = open(filename, 'r')
+        new_file = { 'file': f,
+                     'cursor': f.tell() }
+        self.files[filename] = new_file
+        return new_file
+    
+    def close(self, filename):
+        # close a single file by key, does not do anything if not found.
+        if filename in list(self.files.keys()):
+            close_file = self.files.pop(filename)
+            close_file['file'].close()
+            del close_file
+    
+    def close_all(self):
+        """ closes all open files in the monitor """
+        for key in list(self.files.keys()):
+            self.close(key)
+    
+    def read_line(self, afile):
+        # read a single line in a file.
+        f = afile.get('file', None)
+        if f is None:
+            return
+        afile['cursor'] = f.tell()
+        line = f.readline()
+        if not line:
+            f.seek(afile['cursor'])
+            return None
+        else:
+            return line
+    
+    def do(self):
+        ''' Monitor main task handler, call this in your mainloop in ~1 sec intervals '''
+        # read all file changes.
+        for key, value in list(self.files.items()):
+            lines = []
+            data = self.read_line(value)
+            while data is not None:
+                lines.append(data)
+                data = self.read_line(value)
+            if lines:
+                self.notify(key, lines)
+            # 
+    
+    def initialize_loop(self):
+        ''' initializes the main loop for the monitor '''
+        self.observer.start()
+    
+    def break_loop(self):
+        ''' call this if you want to break the monitors tasks '''
+        self.observer.stop()
+    
+    def end_loop(self):
+        ''' always call this before exiting your main loop '''
+        self.close_all()
+        self.observer.join()
+        
+    def run(self):
+        ''' Basic Standalone Main Loop implementation, do not call this in your app. '''
+        # if you want to run this on its own.
+        # everytime any logfile is updated, print it.
+        self.initialize_loop()
+        try:
+            while True:
+                self.do()
+                time.sleep(1)
+        except KeyboardInterrupt:
+            self.break_loop()
+        self.end_loop()
+
+if __name__ == '__main__':
+    monitor = SconMonitor()
+    logging.basicConfig(level=logging.INFO,
+                        format='%(asctime)s - %(message)s',
+                        datefmt='%Y-%m-%d %H:%M:%S')
+    path = os.path.join(os.path.expanduser('~'),
+                                     'Documents',
+                                     'My Games',
+                                     'StarConflict',
+                                     'logs'
+                                     )
+    if monitor.initialize(path) is True:
+        monitor.run()
     
\ No newline at end of file
diff --git a/src/scon/qscon.py b/src/scon/qscon.py
index f02bddb..6914074 100644
--- a/src/scon/qscon.py
+++ b/src/scon/qscon.py
@@ -1,103 +1,103 @@
-"""
-    Main Entry Point / Exe File for QScon, the main log handler / monitor / manager app for log files.
-    
-"""
-import os, sys, logging
-import sys
-import urllib2
-from PyQt4 import QtCore, QtGui
-from monitor import SconMonitor
-from PyQt4.QtCore import QObject, pyqtSignal, pyqtSlot
-
-
-class SconMonitorThread(QtCore.QThread):
-    updated = pyqtSignal(str, list)
-    created = pyqtSignal(str, bool)
-    
-    def __init__(self, path):
-        QtCore.QThread.__init__(self)
-        self.path = path
-    
-    def notify(self, filename, lines):
-        self.updated.emit(filename, lines)
-        #self.mainwindow.notify_filelines(filename, lines)
-        #self.list_widget.addItem('%s\n%s' % (filename, ''.join(lines)))
-    
-    def notify_event(self, event_type, data):
-        if event_type == 'created':
-            self.created.emit(data['src'], data['is_dir'])
-
-    def run(self):
-        monitor = SconMonitor(self.path, notifier=self)
-        #self.list_widget.addItem('Starting to monitor: %s' % self.path)
-        monitor.run()
-
-class MainWindow(QtGui.QWidget):
-    def __init__(self):
-        super(MainWindow, self).__init__()        
-        self.tab_list = QtGui.QTabWidget()
-        self.tabs = {}
-        self.button = QtGui.QPushButton("Start")
-        self.button.clicked.connect(self.start_monitor)
-        layout = QtGui.QVBoxLayout()
-        layout.addWidget(self.button)
-        layout.addWidget(self.tab_list)
-        self.setLayout(layout)
-    
-    
-    def notify_filelines(self, filename, lines):
-        if filename not in self.tabs.keys():
-            new_tab = QtGui.QWidget()
-            new_tab.list_widget = QtGui.QListWidget()
-            layout = QtGui.QVBoxLayout(new_tab)
-            layout.addWidget(new_tab.list_widget)
-            self.tabs[filename] = new_tab
-            self.tab_list.addTab(new_tab, "%s" % os.path.split(str(filename))[-1])
-        self.tabs[filename].list_widget.addItem(''.join(lines)[:-1])
-    
-    def notify_created(self, filename, is_directory):
-        if is_directory:
-            print "Created Directory %s" % filename
-        else:
-            print "Created File %s" % filename
-
-    def start_monitor(self):
-        self.button.setDisabled(True)
-        paths = [os.path.join(os.path.expanduser('~'),'Documents','My Games','StarConflict','logs'),
-                 ]
-        self.threads = []
-        for path in paths:
-            athread = SconMonitorThread(path)
-            athread.updated.connect(self.notify_filelines)
-            athread.created.connect(self.notify_created)
-            self.threads.append(athread)
-            athread.start()
-
-########################################################################
-
-def _main():
-    app = QtGui.QApplication(sys.argv)
-    window = MainWindow()
-    window.resize(640, 480)
-    window.show()
-    return app.exec_()
-
-def main():
-    r = _main()
-    try:
-        import psutil #@UnresolvedImport
-    except ImportError:
-        logging.warning('Cannot import PsUtil, terminating without cleaning up threads explicitly.')
-        sys.exit(r)
-        
-    def kill_proc_tree(pid, including_parent=True):    
-        parent = psutil.Process(pid)
-        if including_parent:
-            parent.kill()
-    me = os.getpid()
-    kill_proc_tree(me)
-    sys.exit(r)
-
-if __name__ == "__main__":
-    main()
+"""
+    Main Entry Point / Exe File for QScon, the main log handler / monitor / manager app for log files.
+    
+"""
+import os, sys, logging
+import sys
+import urllib.request, urllib.error, urllib.parse
+from PyQt4 import QtCore, QtGui
+from .monitor import SconMonitor
+from PyQt4.QtCore import QObject, pyqtSignal, pyqtSlot
+
+
+class SconMonitorThread(QtCore.QThread):
+    updated = pyqtSignal(str, list)
+    created = pyqtSignal(str, bool)
+    
+    def __init__(self, path):
+        QtCore.QThread.__init__(self)
+        self.path = path
+    
+    def notify(self, filename, lines):
+        self.updated.emit(filename, lines)
+        #self.mainwindow.notify_filelines(filename, lines)
+        #self.list_widget.addItem('%s\n%s' % (filename, ''.join(lines)))
+    
+    def notify_event(self, event_type, data):
+        if event_type == 'created':
+            self.created.emit(data['src'], data['is_dir'])
+
+    def run(self):
+        monitor = SconMonitor(self.path, notifier=self)
+        #self.list_widget.addItem('Starting to monitor: %s' % self.path)
+        monitor.run()
+
+class MainWindow(QtGui.QWidget):
+    def __init__(self):
+        super(MainWindow, self).__init__()        
+        self.tab_list = QtGui.QTabWidget()
+        self.tabs = {}
+        self.button = QtGui.QPushButton("Start")
+        self.button.clicked.connect(self.start_monitor)
+        layout = QtGui.QVBoxLayout()
+        layout.addWidget(self.button)
+        layout.addWidget(self.tab_list)
+        self.setLayout(layout)
+    
+    
+    def notify_filelines(self, filename, lines):
+        if filename not in list(self.tabs.keys()):
+            new_tab = QtGui.QWidget()
+            new_tab.list_widget = QtGui.QListWidget()
+            layout = QtGui.QVBoxLayout(new_tab)
+            layout.addWidget(new_tab.list_widget)
+            self.tabs[filename] = new_tab
+            self.tab_list.addTab(new_tab, "%s" % os.path.split(str(filename))[-1])
+        self.tabs[filename].list_widget.addItem(''.join(lines)[:-1])
+    
+    def notify_created(self, filename, is_directory):
+        if is_directory:
+            print(("Created Directory %s" % filename))
+        else:
+            print(("Created File %s" % filename))
+
+    def start_monitor(self):
+        self.button.setDisabled(True)
+        paths = [os.path.join(os.path.expanduser('~'),'Documents','My Games','StarConflict','logs'),
+                 ]
+        self.threads = []
+        for path in paths:
+            athread = SconMonitorThread(path)
+            athread.updated.connect(self.notify_filelines)
+            athread.created.connect(self.notify_created)
+            self.threads.append(athread)
+            athread.start()
+
+########################################################################
+
+def _main():
+    app = QtGui.QApplication(sys.argv)
+    window = MainWindow()
+    window.resize(640, 480)
+    window.show()
+    return app.exec_()
+
+def main():
+    r = _main()
+    try:
+        import psutil #@UnresolvedImport
+    except ImportError:
+        logging.warning('Cannot import PsUtil, terminating without cleaning up threads explicitly.')
+        sys.exit(r)
+        
+    def kill_proc_tree(pid, including_parent=True):    
+        parent = psutil.Process(pid)
+        if including_parent:
+            parent.kill()
+    me = os.getpid()
+    kill_proc_tree(me)
+    sys.exit(r)
+
+if __name__ == "__main__":
+    main()
     
\ No newline at end of file
diff --git a/src/scon/test/regexes.py b/src/scon/test/regexes.py
index 1f0e104..b491198 100644
--- a/src/scon/test/regexes.py
+++ b/src/scon/test/regexes.py
@@ -22,4 +22,4 @@ R_SCLOG = re.compile(RE_SCLOG)
 for line in Lines:
     m = R_SCLOG.match(line)
     if m:
-        print m.groups()
+        print((m.groups()))