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