Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/commandserver.py @ 40636: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
40635:9683dfb6f13a | 40636: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 |