##// END OF EJS Templates
cmdserver: make server streams switchable...
Yuya Nishihara -
r22990:a0e81aa9 default
parent child Browse files
Show More
@@ -1,268 +1,268 b''
1 1 # commandserver.py - communicate with Mercurial's API over a pipe
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import struct
10 10 import sys, os
11 11 import dispatch, encoding, util
12 12
13 13 logfile = None
14 14
15 15 def log(*args):
16 16 if not logfile:
17 17 return
18 18
19 19 for a in args:
20 20 logfile.write(str(a))
21 21
22 22 logfile.flush()
23 23
24 24 class channeledoutput(object):
25 25 """
26 26 Write data to out in the following format:
27 27
28 28 data length (unsigned int),
29 29 data
30 30 """
31 31 def __init__(self, out, channel):
32 32 self.out = out
33 33 self.channel = channel
34 34
35 35 def write(self, data):
36 36 if not data:
37 37 return
38 38 self.out.write(struct.pack('>cI', self.channel, len(data)))
39 39 self.out.write(data)
40 40 self.out.flush()
41 41
42 42 def __getattr__(self, attr):
43 43 if attr in ('isatty', 'fileno'):
44 44 raise AttributeError(attr)
45 45 return getattr(self.out, attr)
46 46
47 47 class channeledinput(object):
48 48 """
49 49 Read data from in_.
50 50
51 51 Requests for input are written to out in the following format:
52 52 channel identifier - 'I' for plain input, 'L' line based (1 byte)
53 53 how many bytes to send at most (unsigned int),
54 54
55 55 The client replies with:
56 56 data length (unsigned int), 0 meaning EOF
57 57 data
58 58 """
59 59
60 60 maxchunksize = 4 * 1024
61 61
62 62 def __init__(self, in_, out, channel):
63 63 self.in_ = in_
64 64 self.out = out
65 65 self.channel = channel
66 66
67 67 def read(self, size=-1):
68 68 if size < 0:
69 69 # if we need to consume all the clients input, ask for 4k chunks
70 70 # so the pipe doesn't fill up risking a deadlock
71 71 size = self.maxchunksize
72 72 s = self._read(size, self.channel)
73 73 buf = s
74 74 while s:
75 75 s = self._read(size, self.channel)
76 76 buf += s
77 77
78 78 return buf
79 79 else:
80 80 return self._read(size, self.channel)
81 81
82 82 def _read(self, size, channel):
83 83 if not size:
84 84 return ''
85 85 assert size > 0
86 86
87 87 # tell the client we need at most size bytes
88 88 self.out.write(struct.pack('>cI', channel, size))
89 89 self.out.flush()
90 90
91 91 length = self.in_.read(4)
92 92 length = struct.unpack('>I', length)[0]
93 93 if not length:
94 94 return ''
95 95 else:
96 96 return self.in_.read(length)
97 97
98 98 def readline(self, size=-1):
99 99 if size < 0:
100 100 size = self.maxchunksize
101 101 s = self._read(size, 'L')
102 102 buf = s
103 103 # keep asking for more until there's either no more or
104 104 # we got a full line
105 105 while s and s[-1] != '\n':
106 106 s = self._read(size, 'L')
107 107 buf += s
108 108
109 109 return buf
110 110 else:
111 111 return self._read(size, 'L')
112 112
113 113 def __iter__(self):
114 114 return self
115 115
116 116 def next(self):
117 117 l = self.readline()
118 118 if not l:
119 119 raise StopIteration
120 120 return l
121 121
122 122 def __getattr__(self, attr):
123 123 if attr in ('isatty', 'fileno'):
124 124 raise AttributeError(attr)
125 125 return getattr(self.in_, attr)
126 126
127 127 class server(object):
128 128 """
129 Listens for commands on stdin, runs them and writes the output on a channel
130 based stream to stdout.
129 Listens for commands on fin, runs them and writes the output on a channel
130 based stream to fout.
131 131 """
132 def __init__(self, ui, repo):
132 def __init__(self, ui, repo, fin, fout):
133 133 self.cwd = os.getcwd()
134 134
135 135 logpath = ui.config("cmdserver", "log", None)
136 136 if logpath:
137 137 global logfile
138 138 if logpath == '-':
139 139 # write log on a special 'd' (debug) channel
140 logfile = channeledoutput(sys.stdout, 'd')
140 logfile = channeledoutput(fout, 'd')
141 141 else:
142 142 logfile = open(logpath, 'a')
143 143
144 144 if repo:
145 145 # the ui here is really the repo ui so take its baseui so we don't
146 146 # end up with its local configuration
147 147 self.ui = repo.baseui
148 148 self.repo = repo
149 149 self.repoui = repo.ui
150 150 else:
151 151 self.ui = ui
152 152 self.repo = self.repoui = None
153 153
154 self.cerr = channeledoutput(sys.stdout, 'e')
155 self.cout = channeledoutput(sys.stdout, 'o')
156 self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
157 self.cresult = channeledoutput(sys.stdout, 'r')
154 self.cerr = channeledoutput(fout, 'e')
155 self.cout = channeledoutput(fout, 'o')
156 self.cin = channeledinput(fin, fout, 'I')
157 self.cresult = channeledoutput(fout, 'r')
158 158
159 self.client = sys.stdin
159 self.client = fin
160 160
161 161 def _read(self, size):
162 162 if not size:
163 163 return ''
164 164
165 165 data = self.client.read(size)
166 166
167 167 # is the other end closed?
168 168 if not data:
169 169 raise EOFError
170 170
171 171 return data
172 172
173 173 def runcommand(self):
174 174 """ reads a list of \0 terminated arguments, executes
175 175 and writes the return code to the result channel """
176 176
177 177 length = struct.unpack('>I', self._read(4))[0]
178 178 if not length:
179 179 args = []
180 180 else:
181 181 args = self._read(length).split('\0')
182 182
183 183 # copy the uis so changes (e.g. --config or --verbose) don't
184 184 # persist between requests
185 185 copiedui = self.ui.copy()
186 186 uis = [copiedui]
187 187 if self.repo:
188 188 self.repo.baseui = copiedui
189 189 # clone ui without using ui.copy because this is protected
190 190 repoui = self.repoui.__class__(self.repoui)
191 191 repoui.copy = copiedui.copy # redo copy protection
192 192 uis.append(repoui)
193 193 self.repo.ui = self.repo.dirstate._ui = repoui
194 194 self.repo.invalidateall()
195 195
196 196 for ui in uis:
197 197 # any kind of interaction must use server channels
198 198 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
199 199
200 200 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
201 201 self.cout, self.cerr)
202 202
203 203 ret = (dispatch.dispatch(req) or 0) & 255 # might return None
204 204
205 205 # restore old cwd
206 206 if '--cwd' in args:
207 207 os.chdir(self.cwd)
208 208
209 209 self.cresult.write(struct.pack('>i', int(ret)))
210 210
211 211 def getencoding(self):
212 212 """ writes the current encoding to the result channel """
213 213 self.cresult.write(encoding.encoding)
214 214
215 215 def serveone(self):
216 216 cmd = self.client.readline()[:-1]
217 217 if cmd:
218 218 handler = self.capabilities.get(cmd)
219 219 if handler:
220 220 handler(self)
221 221 else:
222 222 # clients are expected to check what commands are supported by
223 223 # looking at the servers capabilities
224 224 raise util.Abort(_('unknown command %s') % cmd)
225 225
226 226 return cmd != ''
227 227
228 228 capabilities = {'runcommand' : runcommand,
229 229 'getencoding' : getencoding}
230 230
231 231 def serve(self):
232 232 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
233 233 hellomsg += '\n'
234 234 hellomsg += 'encoding: ' + encoding.encoding
235 235
236 236 # write the hello msg in -one- chunk
237 237 self.cout.write(hellomsg)
238 238
239 239 try:
240 240 while self.serveone():
241 241 pass
242 242 except EOFError:
243 243 # we'll get here if the client disconnected while we were reading
244 244 # its request
245 245 return 1
246 246
247 247 return 0
248 248
249 249 class pipeservice(object):
250 250 def __init__(self, ui, repo, opts):
251 self.server = server(ui, repo)
251 self.server = server(ui, repo, sys.stdin, sys.stdout)
252 252
253 253 def init(self):
254 254 pass
255 255
256 256 def run(self):
257 257 return self.server.serve()
258 258
259 259 _servicemap = {
260 260 'pipe': pipeservice,
261 261 }
262 262
263 263 def createservice(ui, repo, opts):
264 264 mode = opts['cmdserver']
265 265 try:
266 266 return _servicemap[mode](ui, repo, opts)
267 267 except KeyError:
268 268 raise util.Abort(_('unknown mode %s') % mode)
General Comments 0
You need to be logged in to leave comments. Login now