~brian-sidebotham/wxwidgets-cmake/wxpython-2.9.4

« back to all changes in this revision

Viewing changes to wxPython/wx/tools/Editra/src/ed_ipc.py

  • Committer: Brian Sidebotham
  • Date: 2013-08-03 14:30:08 UTC
  • Revision ID: brian.sidebotham@gmail.com-20130803143008-c7806tkych1tp6fc
Initial import into Bazaar

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
###############################################################################
 
2
# Name: ed_ipc.py                                                             #
 
3
# Purpose: Editra IPC client/server                                           #
 
4
# Author: Cody Precord <cprecord@editra.org>                                  #
 
5
# Copyright: (c) 2008-2009 Cody Precord <staff@editra.org>                    #
 
6
# License: wxWindows License                                                  #
 
7
###############################################################################
 
8
 
 
9
"""@package Editra.src.ed_ipc
 
10
 
 
11
Classes and utilities for handling IPC between running instances of Editra. The
 
12
IPC is done through sockets using the TCP protocol. Message packets have a
 
13
specified format and authentication method that is described in L{EdIpcServer}.
 
14
 
 
15
@section protocol Remote Control Protocol:
 
16
 
 
17
This server and its relationship with the main application object allows for
 
18
some limited remote control of Editra. The server's basic message protocol
 
19
requirements are as follows.
 
20
 
 
21
@verbatim
 
22
SESSION_KEY;xml;MSGEND
 
23
@endverbatim
 
24
 
 
25
Where the SESSION_KEY is the unique authentication key created by the app that
 
26
started the server. This key is stored in the user profile and only valid for
 
27
the current running session of Editra. The MSGEND indicator is the L{MSGEND}
 
28
string defined in this file (*EDEND*). If both of these parts of the message
 
29
are found and correct the server will forward the messages that are packed in
 
30
between to the app.
 
31
 
 
32
@section format Message Format:
 
33
@verbatim
 
34
<edipc>
 
35
   <filelist>
 
36
      <file name="absolute_filepath"/>
 
37
   </filelist>
 
38
   <arglist>
 
39
      <arg name="g" value="2"/>
 
40
   </arglist>
 
41
</edipc>
 
42
@endverbatim
 
43
 
 
44
"""
 
45
 
 
46
__author__ = "Cody Precord <cprecord@editra.org>"
 
47
__svnid__ = "$Id: ed_ipc.py 71718 2012-06-12 13:25:48Z CJP $"
 
48
__revision__ = "$Revision: 71718 $"
 
49
 
 
50
#-----------------------------------------------------------------------------#
 
51
# Imports
 
52
import sys
 
53
import wx
 
54
import threading
 
55
import socket
 
56
import time
 
57
#import select
 
58
 
 
59
# Editra Libs
 
60
import util
 
61
import ed_xml
 
62
import ebmlib
 
63
 
 
64
#-----------------------------------------------------------------------------#
 
65
# Globals
 
66
 
 
67
# Port choosing algorithm ;)
 
68
EDPORT = (10 * int('ed', 16) + sum(ord(x) for x in "itr") + int('a', 16)) * 10
 
69
MSGEND = "*EDEND*"
 
70
 
 
71
# Xml Implementation
 
72
EDXML_IPC       = "edipc"
 
73
EDXML_FILELIST  = "filelist"
 
74
EDXML_FILE      = "file"
 
75
EDXML_ARGLIST   = "arglist"
 
76
EDXML_ARG       = "arg"
 
77
 
 
78
#-----------------------------------------------------------------------------#
 
79
 
 
80
edEVT_COMMAND_RECV = wx.NewEventType()
 
81
EVT_COMMAND_RECV = wx.PyEventBinder(edEVT_COMMAND_RECV, 1)
 
82
class IpcServerEvent(wx.PyCommandEvent):
 
83
    """Event to signal the server has recieved some commands"""
 
84
    def __init__(self, etype, eid, values=None):
 
85
        """Creates the event object"""
 
86
        wx.PyCommandEvent.__init__(self, etype, eid)
 
87
        self._value = values
 
88
 
 
89
    def GetCommands(self):
 
90
        """Returns the list of commands sent to the server
 
91
        @return: the value of this event
 
92
 
 
93
        """
 
94
        return self._value
 
95
 
 
96
#-----------------------------------------------------------------------------#
 
97
 
 
98
class EdIpcServer(threading.Thread):
 
99
    """Create an instance of IPC server for Editra. IPC is handled through
 
100
    a socket connection to an instance of this server listening on L{EDPORT}.
 
101
    The server will recieve commands and dispatch them to the app.
 
102
    Messages sent to the server must be in the following format.
 
103
    
 
104
      AuthenticationKey;Message Data;MSGEND
 
105
 
 
106
    The _AuthenticationKey_ is the same as the key that started the server it
 
107
    is used to validate that messages are coming from a legitimate source.
 
108
 
 
109
    _Message Data_ is a string of data where items are separated by a single
 
110
    ';' character. If you use L{SendCommands} to communicate with the server
 
111
    then this message separators are handled internally by that method.
 
112
 
 
113
    L{MSGEND} is the token to signify that the client is finished sending
 
114
    commands to the server. When using L{SendCommands} this is also 
 
115
    automatically handled.
 
116
 
 
117
    @todo: investigate possible security issues
 
118
 
 
119
    """
 
120
    def __init__(self, app, key, port=EDPORT):
 
121
        """Create the server thread
 
122
        @param app: Application object the server belongs to
 
123
        @param key: Unique user authentication key (string)
 
124
        @keyword port: TCP port to attempt to connect to
 
125
 
 
126
        """
 
127
        super(EdIpcServer, self).__init__()
 
128
 
 
129
        # Attributes
 
130
        self._exit = False
 
131
        self.__key = key
 
132
        self.app = app
 
133
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
134
 
 
135
        # Setup
 
136
        ## Try new ports till we find one that we can use
 
137
        while True:
 
138
            try:
 
139
                self.socket.bind(('127.0.0.1', port))
 
140
                break
 
141
            except:
 
142
                port += 1
 
143
 
 
144
        global EDPORT
 
145
        EDPORT = port
 
146
        self.socket.listen(5)
 
147
 
 
148
    def Shutdown(self):
 
149
        """Tell the server to exit"""
 
150
        self._exit = True
 
151
        # Wake up the server in case its waiting
 
152
        # TODO: should add a specific exit event message
 
153
        SendCommands(IPCCommand(), self.__key)
 
154
 
 
155
    def run(self):
 
156
        """Start the server. The server runs in blocking mode, this
 
157
        shouldn't be an issue as it should rarely need to respond to
 
158
        anything.
 
159
 
 
160
        """
 
161
        while not self._exit:
 
162
            try:
 
163
                client, addr = self.socket.accept()
 
164
 
 
165
                if self._exit:
 
166
                    break
 
167
 
 
168
                # Block for up to 2 seconds while reading
 
169
                start = time.time()
 
170
                recieved = ''
 
171
                while time.time() < start + 2:
 
172
                    recieved += client.recv(4096)
 
173
                    if recieved.endswith(MSGEND):
 
174
                        break
 
175
 
 
176
                # If message key is correct and the message is ended, process
 
177
                # the input and dispatch to the app.
 
178
                if recieved.startswith(self.__key) and recieved.endswith(MSGEND):
 
179
                    # Strip the key
 
180
                    recieved = recieved.replace(self.__key, '', 1)
 
181
                    # Strip the end token
 
182
                    xmlstr = recieved.rstrip(MSGEND).strip(";")
 
183
 
 
184
                    # Parse the xml
 
185
                    exml = IPCCommand()
 
186
                    try:
 
187
                        # Well formed xml must be utf-8 string not Unicode
 
188
                        if not ebmlib.IsUnicode(xmlstr):
 
189
                            xmlstr = unicode(xmlstr, sys.getfilesystemencoding())
 
190
                        xmlstr = xmlstr.encode('utf-8')
 
191
                        exml = IPCCommand.parse(xmlstr)
 
192
                    except Exception, msg:
 
193
                        # Log and ignore parsing errors
 
194
                        logmsg = "[ed_ipc][err] Parsing failed: %s\n" % msg
 
195
                        xmlstr = xmlstr.replace('\n', '').strip()
 
196
                        logmsg += "Bad xml was: %s" % repr(xmlstr)
 
197
                        util.Log(logmsg)
 
198
                        continue
 
199
 
 
200
                    evt = IpcServerEvent(edEVT_COMMAND_RECV, wx.ID_ANY, exml)
 
201
                    wx.CallAfter(wx.PostEvent, self.app, evt)
 
202
            except socket.error:
 
203
                # TODO: Better error handling
 
204
                self._exit = True
 
205
 
 
206
        # Shutdown Server
 
207
        try:
 
208
            self.socket.shutdown(socket.SHUT_RDWR)
 
209
        except:
 
210
            pass
 
211
 
 
212
        self.socket.close()
 
213
 
 
214
#-----------------------------------------------------------------------------#
 
215
 
 
216
def SendCommands(xmlobj, key):
 
217
    """Send commands to the running instance of Editra
 
218
    @param xmlobj: EditraXml Object
 
219
    @param key: Server session authentication key
 
220
    @return: bool
 
221
 
 
222
    """
 
223
    assert isinstance(xmlobj, ed_xml.EdXml), "SendCommands expects an xml object"
 
224
 
 
225
    # Build the edipc protocol msg
 
226
    cmds = list()
 
227
    cmds.insert(0, key)
 
228
    cmds.append(xmlobj.GetXml())
 
229
    cmds.append(MSGEND)
 
230
    try:
 
231
        # Setup the client socket
 
232
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
233
        client.connect(('127.0.0.1', EDPORT))
 
234
 
 
235
        # Server expects commands delimited by ;
 
236
        message = ";".join(cmds)
 
237
        client.send(message)
 
238
        client.shutdown(socket.SHUT_RDWR)
 
239
        client.close()
 
240
    except Exception, msg:
 
241
        util.Log("[ed_ipc][err] Failed in SendCommands: %s" % msg)
 
242
        return False
 
243
    else:
 
244
        return True
 
245
 
 
246
#-----------------------------------------------------------------------------#
 
247
# Command Serialization
 
248
 
 
249
class IPCFile(ed_xml.EdXml):
 
250
    """Xml object for holding the list of files
 
251
    @verbatim <file value="/path/to/file"/> @endverbatim
 
252
 
 
253
    """
 
254
    class meta:
 
255
        tagname = EDXML_FILE
 
256
    value = ed_xml.String(required=True)
 
257
 
 
258
class IPCArg(ed_xml.EdXml):
 
259
    """Xml object for holding the list of args
 
260
    @verbatim <arg name="test" value="x"/> @endverbatim
 
261
 
 
262
    """
 
263
    class meta:
 
264
        tagname = EDXML_ARG
 
265
    name = ed_xml.String(required=True)
 
266
    value = ed_xml.String(required=True)
 
267
 
 
268
class IPCCommand(ed_xml.EdXml):
 
269
    """IPC XML Command"""
 
270
    class meta:
 
271
        tagname = EDXML_IPC
 
272
    filelist = ed_xml.List(ed_xml.Model(IPCFile))
 
273
    arglist = ed_xml.List(ed_xml.Model(IPCArg))