update to xmlrpc tracker, work in progress
authorsmbaker <smbaker@fc8clean.lan>
Wed, 21 Sep 2011 00:54:08 +0000 (17:54 -0700)
committersmbaker <smbaker@fc8clean.lan>
Wed, 21 Sep 2011 00:54:08 +0000 (17:54 -0700)
sface/mainwindow.py
sface/sfiprocess.py
sface/xmlrpcwindow.py

index a8e7d33..719fd07 100644 (file)
@@ -11,6 +11,7 @@ from sface.config import config
 from sface.logwindow import LogWindow
 from sface.rspecwindow import RSpecWindow
 from sface.screens.sfascreen import SfaScreen
 from sface.logwindow import LogWindow
 from sface.rspecwindow import RSpecWindow
 from sface.screens.sfascreen import SfaScreen
+from sface.xmlrpcwindow import get_tracker, init_tracker
 
 # depending on the platform..
 # could probably use Qt's resource system but looks overkill for just one file...
 
 # depending on the platform..
 # could probably use Qt's resource system but looks overkill for just one file...
@@ -126,6 +127,7 @@ class MainWindow(QWidget):
         # to our parent. Otherwise, getting a segfault on exit in Ubuntu.
         self.logWindow = LogWindow(parent)
         self.rspecWindow = RSpecWindow(parent)
         # to our parent. Otherwise, getting a segfault on exit in Ubuntu.
         self.logWindow = LogWindow(parent)
         self.rspecWindow = RSpecWindow(parent)
+        self.trackerWindow = init_tracker(parent)
 
         self.pix = QLabel(self)
 
 
         self.pix = QLabel(self)
 
@@ -153,12 +155,14 @@ class MainWindow(QWidget):
             self.nav.setTitle(self.screenWidgets[0].getTitleText())
 
         self.status = Status(self)
             self.nav.setTitle(self.screenWidgets[0].getTitleText())
 
         self.status = Status(self)
+        self.tracker = QLabel("<a href='showtracker'>Show Xmlrpc</a>", self)
         self.log = QLabel("<a href='showlog'>Show Log</a>", self)
         self.rspec = QLabel("<a href='showlog'>Show RSpec</a>", self)
 
         hlayout = QHBoxLayout()
         hlayout.addWidget(self.status)
         hlayout.addStretch()
         self.log = QLabel("<a href='showlog'>Show Log</a>", self)
         self.rspec = QLabel("<a href='showlog'>Show RSpec</a>", self)
 
         hlayout = QHBoxLayout()
         hlayout.addWidget(self.status)
         hlayout.addStretch()
+        hlayout.addWidget(self.tracker)
         hlayout.addWidget(self.rspec)
         hlayout.addWidget(self.log)
 
         hlayout.addWidget(self.rspec)
         hlayout.addWidget(self.log)
 
@@ -173,6 +177,8 @@ class MainWindow(QWidget):
             self.connect(link, SIGNAL('linkActivated(QString)'),
                          self.animateToScreen)
 
             self.connect(link, SIGNAL('linkActivated(QString)'),
                          self.animateToScreen)
 
+        self.connect(self.tracker, SIGNAL('linkActivated(QString)'),
+                     self.showTrackerWindow)
         self.connect(self.log, SIGNAL('linkActivated(QString)'),
                      self.showLogWindow)
         self.connect(self.rspec, SIGNAL('linkActivated(QString)'),
         self.connect(self.log, SIGNAL('linkActivated(QString)'),
                      self.showLogWindow)
         self.connect(self.rspec, SIGNAL('linkActivated(QString)'),
@@ -181,6 +187,13 @@ class MainWindow(QWidget):
     def redirectOutputToLog(self):
         self.logWindow.redirectOutput()
 
     def redirectOutputToLog(self):
         self.logWindow.redirectOutput()
 
+    def showTrackerWindow(self):
+        tracker = get_tracker()
+        tracker.show()
+        tracker.resize(500, 640)
+        tracker.raise_()
+        tracker.activateWindow()
+
     def showLogWindow(self, link):
         self.logWindow.show()
         self.logWindow.resize(800, 200)
     def showLogWindow(self, link):
         self.logWindow.show()
         self.logWindow.resize(800, 200)
@@ -193,7 +206,6 @@ class MainWindow(QWidget):
         self.rspecWindow.raise_()
         self.rspecWindow.activateWindow()
 
         self.rspecWindow.raise_()
         self.rspecWindow.activateWindow()
 
-
     def animatePixmap(self, y):
         self.pix.move(0, y)
 
     def animatePixmap(self, y):
         self.pix.move(0, y)
 
@@ -222,7 +234,6 @@ class MainWindow(QWidget):
         self.connect(timeLine, SIGNAL('finished()'), self.toNextScreen)
         timeLine.start()
 
         self.connect(timeLine, SIGNAL('finished()'), self.toNextScreen)
         timeLine.start()
 
-
     def toNextScreen(self):
         self.screens.setCurrentWidget(self.next_screen)
         self.nav.setTitle(self.next_screen.getTitleText())
     def toNextScreen(self):
         self.screens.setCurrentWidget(self.next_screen)
         self.nav.setTitle(self.next_screen.getTitleText())
index 108530c..6950554 100644 (file)
@@ -5,7 +5,7 @@ import time
 
 from PyQt4.QtCore import *
 from sface.config import config
 
 from PyQt4.QtCore import *
 from sface.config import config
-from sface.xmlrpcwindow import XmlrpcTracker, XmlrpcReader
+from sface.xmlrpcwindow import get_tracker, XmlrpcReader
 
 def find_executable(exec_name):
     """find the given executable in $PATH"""
 
 def find_executable(exec_name):
     """find the given executable in $PATH"""
@@ -29,7 +29,6 @@ class SfiProcess(QObject):
                      self.processFinished)
 
         self.xmlrpcreader = XmlrpcReader() # this one is for parsing XMLRPC responses
                      self.processFinished)
 
         self.xmlrpcreader = XmlrpcReader() # this one is for parsing XMLRPC responses
-        self.xmlrpctracker = XmlrpcTracker() # this one is for the debug window
 
         # holds aggregate output from processStandardOutput(); used by xmlrpc
         # tracker.
 
         # holds aggregate output from processStandardOutput(); used by xmlrpc
         # tracker.
@@ -212,5 +211,5 @@ class SfiProcess(QObject):
 #            command = "%s %s" % (self.exe, self.args.join(" "))
             print time.strftime('%H:%M:%S'),"Done [%.3f s]"%(time.time()-self._trace)
         if config.debug:
 #            command = "%s %s" % (self.exe, self.args.join(" "))
             print time.strftime('%H:%M:%S'),"Done [%.3f s]"%(time.time()-self._trace)
         if config.debug:
-            self.xmlrpctracker.getAndPrint(self.output)
+            get_tracker().getAndPrint(self.output)
 
 
index 81007c0..72b0f68 100644 (file)
@@ -1,24 +1,83 @@
 import re
 from lxml import etree
 from PyQt4.QtXml import QDomDocument
 import re
 from lxml import etree
 from PyQt4.QtXml import QDomDocument
-from sface.xmlwidget import XmlWindow, DomModel
+from sface.xmlwidget import XmlWindow, DomModel, XmlView, XmlDelegate, ElemNodeDelegate, TextNodeDelegate, CommentNodeDelegate
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
 
 class XmlrpcReader():
     def __init__(self):
         self.rawOutput = None
         self.responses = []
 
 class XmlrpcReader():
     def __init__(self):
         self.rawOutput = None
         self.responses = []
-
-    def getAndPrint(self, rawOutput):
-        self.store(rawOutput)
-        self.extractXml()
-        self.xmlrpcWindow.setData(self.xml)
-        if self.xml != "<debug></debug>":
-            # only popup the window if we have something to show
-            self.showXmlrpc()
+        self.calls = []
 
     def store(self, rawOutput):
         self.rawOutput = rawOutput
 
 
     def store(self, rawOutput):
         self.rawOutput = rawOutput
 
+    def paramString(self, item):
+        """
+           Convert an xmlrpc <PARAM> into a human-readable string
+           Is there an easier way?
+        """
+        if item.tag == "string":
+            return item.text
+        elif item.tag == "array":
+            arrayValues = item.xpath("data/value")
+            if arrayValues:
+                children = list(arrayValues[0])
+                if children:
+                    return self.paramString(children[0]) + ", ..."
+                else:
+                    return "[]"
+            else:
+                return "[]"
+        elif item.tag == "struct":
+            dict = {}
+            members = item.xpath("member")
+            for member in members:
+                names = member.xpath("name")
+                values = member.xpath("value")
+                if (names) and (values):
+                    name = names[0]
+                    value = values[0]
+                    if len(list(value))>0:
+                        data = list(value)[0]
+                        dict[name.text] = self.paramString(list(value)[0])
+            return str(dict)
+        else:
+            return "unknown"
+
+    def parseMethodCall(self, mc):
+        mc = str(mc)
+
+        request = {}
+
+        try:
+            tree = etree.fromstring(mc)
+        except etree.XMLSyntaxError, e:
+            return request
+
+        if tree.tag != "methodCall" or (len(list(tree))==0):
+            return request
+
+        methodNames = tree.xpath("//methodCall/methodName")
+
+        if len(methodNames) == 0:
+            return request
+
+        request["args"] = []
+
+        params = tree.xpath("//methodCall/params/param/value")
+        for param in params:
+            children = list(param)
+            if children:
+                request["args"].append(self.paramString(children[0]))
+
+        request["methodName"] = methodNames[0].text
+
+        return request
+
     def parseMethodResponse(self, mr):
         mr = str(mr) # PyQT supplies a QByteArray; make it a string
 
     def parseMethodResponse(self, mr):
         mr = str(mr) # PyQT supplies a QByteArray; make it a string
 
@@ -58,31 +117,45 @@ class XmlrpcReader():
             return response
 
         # whatever didn't fault must have succeeded?
             return response
 
         # whatever didn't fault must have succeeded?
-        response["kind"] = "success"
+        # "success" really isn't quite the right word. The best we can say is
+        # that the call was executed. The actual op may or may not have
+        # succeeded.
+        response["kind"] = "executed"
 
         return response
 
     def extractXml(self):
         pttrnAsk = '<methodCall>.*?</methodCall>'
         pttrnAns = '<methodResponse>.*?</methodResponse>'
 
         return response
 
     def extractXml(self):
         pttrnAsk = '<methodCall>.*?</methodCall>'
         pttrnAns = '<methodResponse>.*?</methodResponse>'
-        answers = re.compile(pttrnAsk, re.DOTALL).findall(self.rawOutput)
+        requests = re.compile(pttrnAsk, re.DOTALL).findall(self.rawOutput)
         replies = re.compile(pttrnAns, re.DOTALL).findall(self.rawOutput)
         # cleaning
         replies = re.compile(pttrnAns, re.DOTALL).findall(self.rawOutput)
         # cleaning
-        answers = [ x.replace('\\n','\n') for x in answers ]
+        requests = [ x.replace('\\n','\n') for x in requests ]
         replies = [ x.replace('\\n','\n').replace("'\nbody: '", '').replace("\"\nbody: '", '') for x in replies ]
         replies = [ x.replace('\\n','\n').replace("'\nbody: '", '').replace("\"\nbody: '", '') for x in replies ]
-        replies.reverse() # so that I use pop() as popleft
         # A well-formed XML document must have one, and only one, top-level element
 
         self.responses = []
 
         self.xml = ""
         # A well-formed XML document must have one, and only one, top-level element
 
         self.responses = []
 
         self.xml = ""
-        for ans in answers:
-            self.xml += ans
+        for requestXml in requests:
+            self.xml += requestXml
+            callDict = {"request": requestXml}
             # we could have less responses than calls, so guard the pop
             if replies:
             # we could have less responses than calls, so guard the pop
             if replies:
-                replyXml = replies.pop()
+                replyXml = replies.pop(0)
                 self.xml += replyXml
                 self.xml += replyXml
-                self.responses.append(self.parseMethodResponse(replyXml))
+
+                # parse the response to extract fault information
+                responseDict = self.parseMethodResponse(replyXml)
+                self.responses.append(responseDict)
+
+                requestDict = self.parseMethodCall(requestXml)
+
+                callDict["reply"] = replyXml
+                callDict.update(responseDict)
+                callDict.update(requestDict)
+
+            self.calls.append(callDict)
 
         # just in case somehow we ended up with more responses than calls
         while replies:
 
         # just in case somehow we ended up with more responses than calls
         while replies:
@@ -96,39 +169,140 @@ class XmlrpcReader():
         # statistics: round-trip time, size of the com
         pass
 
         # statistics: round-trip time, size of the com
         pass
 
-class XmlrpcTracker(XmlrpcReader):
-    def __init__(self):
-        XmlrpcReader.__init__(self)
-        self.xmlrpcWindow = XmlrpcWindow()
+class XmlrpcTracker(QWidget):
+    def __init__(self, parent=None):
+        QWidget.__init__(self, parent=parent)
+
+        self.reader = XmlrpcReader()
+
+        labelCalls = QLabel("Calls: (from most recent to least recent)")
+
+        self.callTable = QTableWidget(self)
+        self.callTable.setSelectionBehavior(QAbstractItemView.SelectRows)
+        self.callTable.setSelectionMode(QAbstractItemView.SingleSelection)
+        self.callTable.setMaximumHeight(140)
+
+        labelArgs = QLabel("Params:")
+
+        self.argsTable = QTableWidget(self)
+        self.argsTable.setMaximumHeight(140)
+
+        labelXml = QLabel("XMLRPC Contents")
+        self.xmlView = XmlView(self)
+
+        self.delegate = XmlDelegate(self)
+        self.delegate.insertNodeDelegate('element', ElemNodeDelegate())
+        self.delegate.insertNodeDelegate('text', TextNodeDelegate())
+        self.delegate.insertNodeDelegate('comment', CommentNodeDelegate())
+        self.xmlView.setItemDelegate(self.delegate)
+
+        self.layout = QVBoxLayout()
+        self.layout.addWidget(labelCalls)
+        self.layout.addWidget(self.callTable)
+        self.layout.addWidget(labelArgs)
+        self.layout.addWidget(self.argsTable)
+        self.layout.addWidget(labelXml)
+        self.layout.addWidget(self.xmlView)
+
+        self.setLayout(self.layout)
+
+        self.connect(self.callTable, SIGNAL("itemSelectionChanged ()"), self.onCallSelect)
 
     def getAndPrint(self, rawOutput):
 
     def getAndPrint(self, rawOutput):
-        self.store(rawOutput)
-        self.extractXml()
-        self.xmlrpcWindow.setData(self.xml)
-        if self.xml != "<debug></debug>":
-            # only popup the window if we have something to show
-            self.showXmlrpc()
-
-    def showXmlrpc(self):
-        self.xmlrpcWindow.show()
-        self.xmlrpcWindow.resize(500, 640)
-        self.xmlrpcWindow.raise_()
-        self.xmlrpcWindow.activateWindow()
+        self.reader.store(rawOutput)
+        self.reader.extractXml()
+        self.updateCallTable()
 
 
-    def extractXml(self):
-        self.xml = "<debug>" + XmlrpcReader.extractXml(self) + "</debug>"
+    def updateCallTable(self):
+        self.callTable.clear()
+        self.callTable.setColumnCount(2)
+        self.callTable.setHorizontalHeaderLabels(["name", "status"])
 
 
-class XmlrpcWindow(XmlWindow):
-    def __init__(self, parent=None):
-        # super __init__() calls updateView,
-        # which assumes you have some data
-        self.data = '<debug/>'
-        XmlWindow.__init__(self, parent, 'XMLRPC window')
+        calls = self.reader.calls
+        self.callTable.setRowCount(len(calls))
+
+        row = 0
+        reverse_calls = list(enumerate(calls))
+        reverse_calls.reverse()
+        for (index,call) in reverse_calls:
+            item = QTableWidgetItem(call.get("methodName", "unknown"))
+            item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
+            item.setData(Qt.UserRole, index)
+            self.callTable.setItem(row, 0, item)
+
+            item = QTableWidgetItem(call.get("kind", "unknown"))
+            item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
+            item.setData(Qt.UserRole, index)
+            self.callTable.setItem(row, 1, item)
+
+            row = row + 1
+
+        self.callTable.resizeColumnsToContents()
+
+    def updateArgsTable(self, args):
+        self.argsTable.clear()
+        self.argsTable.setColumnCount(1)
+        self.argsTable.setHorizontalHeaderLabels(["param"])
+        self.argsTable.horizontalHeader().hide()
+        self.argsTable.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
+
+        self.argsTable.setRowCount(len(args))
+
+        row = 0
+        for arg in args:
+            if len(arg)>255:
+                arg = arg[:255] + "..."
+
+            item = QTableWidgetItem(arg)
+            item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
+            self.argsTable.setItem(row, 0, item)
+
+            row = row + 1
+
+        self.argsTable.resizeColumnsToContents()
+
+    def onCallSelect(self):
+        selItems = self.callTable.selectedItems()
+        if (len(selItems) <= 0):
+            return
+
+        row = selItems[0].data(Qt.UserRole).toInt()[0]
+
+        calls = self.reader.calls
+        if len(calls)<=row:
+            return
+
+        call = calls[row]
+
+        xml = "<debug>" + call.get("request","") + call.get("reply", "") + "</debug>"
+        #xml = call.get("request","") + call.get("reply", "")
+
+        self.displayXml(xml)
+
+        self.updateArgsTable(call.get("args",[]))
+
+    def displayXml(self, xml):
+        self.document = QDomDocument("XMLRPC Tracker")
+        self.document.setContent(xml)
+        self.model = DomModel(self.document, self)
+        self.xmlView.setModel(self.model)
+
+
+# There's one global tracker for XMLRPCs. Use init_tracker() to create it, and
+# get_tracker() to get it.
+
+glo_tracker = None
+
+def init_tracker(parent):
+    global glo_tracker
+
+    glo_tracker = XmlrpcTracker(parent)
+
+    return glo_tracker
+
+def get_tracker():
+    global glo_tracker
 
 
-    def setData(self, XmlrpcCom):
-        self.data = XmlrpcCom
+    return glo_tracker
 
 
-    def updateView(self):
-        XmlWindow.updateView(self)
 
 
-        self.document.setContent(self.data)