1
###############################################################################
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
###############################################################################
9
"""@package Editra.src.ed_ipc
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}.
15
@section protocol Remote Control Protocol:
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.
22
SESSION_KEY;xml;MSGEND
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
32
@section format Message Format:
36
<file name="absolute_filepath"/>
39
<arg name="g" value="2"/>
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 $"
50
#-----------------------------------------------------------------------------#
64
#-----------------------------------------------------------------------------#
67
# Port choosing algorithm ;)
68
EDPORT = (10 * int('ed', 16) + sum(ord(x) for x in "itr") + int('a', 16)) * 10
73
EDXML_FILELIST = "filelist"
75
EDXML_ARGLIST = "arglist"
78
#-----------------------------------------------------------------------------#
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)
89
def GetCommands(self):
90
"""Returns the list of commands sent to the server
91
@return: the value of this event
96
#-----------------------------------------------------------------------------#
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.
104
AuthenticationKey;Message Data;MSGEND
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.
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.
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.
117
@todo: investigate possible security issues
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
127
super(EdIpcServer, self).__init__()
133
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
136
## Try new ports till we find one that we can use
139
self.socket.bind(('127.0.0.1', port))
146
self.socket.listen(5)
149
"""Tell the server to exit"""
151
# Wake up the server in case its waiting
152
# TODO: should add a specific exit event message
153
SendCommands(IPCCommand(), self.__key)
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
161
while not self._exit:
163
client, addr = self.socket.accept()
168
# Block for up to 2 seconds while reading
171
while time.time() < start + 2:
172
recieved += client.recv(4096)
173
if recieved.endswith(MSGEND):
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):
180
recieved = recieved.replace(self.__key, '', 1)
181
# Strip the end token
182
xmlstr = recieved.rstrip(MSGEND).strip(";")
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)
200
evt = IpcServerEvent(edEVT_COMMAND_RECV, wx.ID_ANY, exml)
201
wx.CallAfter(wx.PostEvent, self.app, evt)
203
# TODO: Better error handling
208
self.socket.shutdown(socket.SHUT_RDWR)
214
#-----------------------------------------------------------------------------#
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
223
assert isinstance(xmlobj, ed_xml.EdXml), "SendCommands expects an xml object"
225
# Build the edipc protocol msg
228
cmds.append(xmlobj.GetXml())
231
# Setup the client socket
232
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
233
client.connect(('127.0.0.1', EDPORT))
235
# Server expects commands delimited by ;
236
message = ";".join(cmds)
238
client.shutdown(socket.SHUT_RDWR)
240
except Exception, msg:
241
util.Log("[ed_ipc][err] Failed in SendCommands: %s" % msg)
246
#-----------------------------------------------------------------------------#
247
# Command Serialization
249
class IPCFile(ed_xml.EdXml):
250
"""Xml object for holding the list of files
251
@verbatim <file value="/path/to/file"/> @endverbatim
256
value = ed_xml.String(required=True)
258
class IPCArg(ed_xml.EdXml):
259
"""Xml object for holding the list of args
260
@verbatim <arg name="test" value="x"/> @endverbatim
265
name = ed_xml.String(required=True)
266
value = ed_xml.String(required=True)
268
class IPCCommand(ed_xml.EdXml):
269
"""IPC XML Command"""
272
filelist = ed_xml.List(ed_xml.Model(IPCFile))
273
arglist = ed_xml.List(ed_xml.Model(IPCArg))