comparison mercurial/commandserver.py @ 40589:054d0fcba2c4

commandserver: add experimental option to use separate message channel This is loosely based on the idea of the TortoiseHg's pipeui extension, which attaches ui.label to message text so the command-server client can capture prompt text, for example. https://bitbucket.org/tortoisehg/thg/src/4.7.2/tortoisehg/util/pipeui.py I was thinking that this functionality could be generalized to templating, but changed mind as doing template stuff would be unnecessarily complex. It's merely a status message, a simple serialization option should suffice. Since this slightly changes the command-server protocol, it's gated by a config knob. If the config is enabled, and if it's supported by the server, "message-encoding: <name>" is advertised so the client can stop parsing 'o'/'e' channel data and read encoded messages from the 'm' channel. As we might add new message encodings in future releases, client can specify a list of encoding names in preferred order. This patch includes 'cbor' encoding as example. Perhaps, 'json' should be supported as well.
author Yuya Nishihara <yuya@tcha.org>
date Sun, 18 Jan 2015 18:49:59 +0900
parents 9683dfb6f13a
children 83dd8c63a0c6
comparison
equal deleted inserted replaced
40588:9683dfb6f13a 40589:054d0fcba2c4
24 24
25 from .i18n import _ 25 from .i18n import _
26 from . import ( 26 from . import (
27 encoding, 27 encoding,
28 error, 28 error,
29 pycompat,
29 util, 30 util,
30 ) 31 )
31 from .utils import ( 32 from .utils import (
33 cborutil,
32 procutil, 34 procutil,
33 ) 35 )
34 36
35 logfile = None 37 logfile = None
36 38
67 69
68 def __getattr__(self, attr): 70 def __getattr__(self, attr):
69 if attr in (r'isatty', r'fileno', r'tell', r'seek'): 71 if attr in (r'isatty', r'fileno', r'tell', r'seek'):
70 raise AttributeError(attr) 72 raise AttributeError(attr)
71 return getattr(self.out, attr) 73 return getattr(self.out, attr)
74
75 class channeledmessage(object):
76 """
77 Write encoded message and metadata to out in the following format:
78
79 data length (unsigned int),
80 encoded message and metadata, as a flat key-value dict.
81 """
82
83 # teach ui that write() can take **opts
84 structured = True
85
86 def __init__(self, out, channel, encodename, encodefn):
87 self._cout = channeledoutput(out, channel)
88 self.encoding = encodename
89 self._encodefn = encodefn
90
91 def write(self, data, **opts):
92 opts = pycompat.byteskwargs(opts)
93 opts[b'data'] = data
94 self._cout.write(self._encodefn(opts))
95
96 def __getattr__(self, attr):
97 return getattr(self._cout, attr)
72 98
73 class channeledinput(object): 99 class channeledinput(object):
74 """ 100 """
75 Read data from in_. 101 Read data from in_.
76 102
154 def __getattr__(self, attr): 180 def __getattr__(self, attr):
155 if attr in (r'isatty', r'fileno', r'tell', r'seek'): 181 if attr in (r'isatty', r'fileno', r'tell', r'seek'):
156 raise AttributeError(attr) 182 raise AttributeError(attr)
157 return getattr(self.in_, attr) 183 return getattr(self.in_, attr)
158 184
185 _messageencoders = {
186 b'cbor': lambda v: b''.join(cborutil.streamencode(v)),
187 }
188
189 def _selectmessageencoder(ui):
190 # experimental config: cmdserver.message-encodings
191 encnames = ui.configlist(b'cmdserver', b'message-encodings')
192 for n in encnames:
193 f = _messageencoders.get(n)
194 if f:
195 return n, f
196 raise error.Abort(b'no supported message encodings: %s'
197 % b' '.join(encnames))
198
159 class server(object): 199 class server(object):
160 """ 200 """
161 Listens for commands on fin, runs them and writes the output on a channel 201 Listens for commands on fin, runs them and writes the output on a channel
162 based stream to fout. 202 based stream to fout.
163 """ 203 """
186 226
187 self.cerr = channeledoutput(fout, 'e') 227 self.cerr = channeledoutput(fout, 'e')
188 self.cout = channeledoutput(fout, 'o') 228 self.cout = channeledoutput(fout, 'o')
189 self.cin = channeledinput(fin, fout, 'I') 229 self.cin = channeledinput(fin, fout, 'I')
190 self.cresult = channeledoutput(fout, 'r') 230 self.cresult = channeledoutput(fout, 'r')
231
232 # TODO: add this to help/config.txt when stabilized
233 # ``channel``
234 # Use separate channel for structured output. (Command-server only)
235 self.cmsg = None
236 if ui.config(b'ui', b'message-output') == b'channel':
237 encname, encfn = _selectmessageencoder(ui)
238 self.cmsg = channeledmessage(fout, b'm', encname, encfn)
191 239
192 self.client = fin 240 self.client = fin
193 241
194 def cleanup(self): 242 def cleanup(self):
195 """release and restore resources taken during server session""" 243 """release and restore resources taken during server session"""
252 # enforced only if cin is a channel. 300 # enforced only if cin is a channel.
253 if not util.safehasattr(self.cin, 'fileno'): 301 if not util.safehasattr(self.cin, 'fileno'):
254 ui.setconfig('ui', 'nontty', 'true', 'commandserver') 302 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
255 303
256 req = dispatch.request(args[:], copiedui, self.repo, self.cin, 304 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
257 self.cout, self.cerr) 305 self.cout, self.cerr, self.cmsg)
258 306
259 try: 307 try:
260 ret = dispatch.dispatch(req) & 255 308 ret = dispatch.dispatch(req) & 255
261 self.cresult.write(struct.pack('>i', int(ret))) 309 self.cresult.write(struct.pack('>i', int(ret)))
262 finally: 310 finally:
287 def serve(self): 335 def serve(self):
288 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities)) 336 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
289 hellomsg += '\n' 337 hellomsg += '\n'
290 hellomsg += 'encoding: ' + encoding.encoding 338 hellomsg += 'encoding: ' + encoding.encoding
291 hellomsg += '\n' 339 hellomsg += '\n'
340 if self.cmsg:
341 hellomsg += 'message-encoding: %s\n' % self.cmsg.encoding
292 hellomsg += 'pid: %d' % procutil.getpid() 342 hellomsg += 'pid: %d' % procutil.getpid()
293 if util.safehasattr(os, 'getpgid'): 343 if util.safehasattr(os, 'getpgid'):
294 hellomsg += '\n' 344 hellomsg += '\n'
295 hellomsg += 'pgid: %d' % os.getpgid(0) 345 hellomsg += 'pgid: %d' % os.getpgid(0)
296 346