Mercurial > hg
comparison mercurial/commandserver.py @ 14647:2e9f379de0ac
serve: add --cmdserver option to communicate with hg over a pipe
author | Idan Kamara <idankk86@gmail.com> |
---|---|
date | Fri, 03 Jun 2011 17:27:41 +0300 |
parents | |
children | 5fd5dd9a610a |
comparison
equal
deleted
inserted
replaced
14646:001788ef4bbb | 14647:2e9f379de0ac |
---|---|
1 # commandserver.py - communicate with Mercurial's API over a pipe | |
2 # | |
3 # Copyright Matt Mackall <mpm@selenic.com> | |
4 # | |
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. | |
7 | |
8 from i18n import _ | |
9 import struct | |
10 import sys | |
11 import dispatch, encoding, util | |
12 | |
13 logfile = None | |
14 | |
15 def log(*args): | |
16 if not logfile: | |
17 return | |
18 | |
19 for a in args: | |
20 logfile.write(str(a)) | |
21 | |
22 logfile.flush() | |
23 | |
24 class channeledoutput(object): | |
25 """ | |
26 Write data from in_ to out in the following format: | |
27 | |
28 data length (unsigned int), | |
29 data | |
30 """ | |
31 def __init__(self, in_, out, channel): | |
32 self.in_ = in_ | |
33 self.out = out | |
34 self.channel = channel | |
35 | |
36 def write(self, data): | |
37 if not data: | |
38 return | |
39 self.out.write(struct.pack('>cI', self.channel, len(data))) | |
40 self.out.write(data) | |
41 self.out.flush() | |
42 | |
43 def __getattr__(self, attr): | |
44 if attr in ('isatty', 'fileno'): | |
45 raise AttributeError, attr | |
46 return getattr(self.in_, attr) | |
47 | |
48 class channeledinput(object): | |
49 """ | |
50 Read data from in_. | |
51 | |
52 Requests for input are written to out in the following format: | |
53 channel identifier - 'I' for plain input, 'L' line based (1 byte) | |
54 how many bytes to send at most (unsigned int), | |
55 | |
56 The client replies with: | |
57 data length (unsigned int), 0 meaning EOF | |
58 data | |
59 """ | |
60 | |
61 maxchunksize = 4 * 1024 | |
62 | |
63 def __init__(self, in_, out, channel): | |
64 self.in_ = in_ | |
65 self.out = out | |
66 self.channel = channel | |
67 | |
68 def read(self, size=-1): | |
69 if size < 0: | |
70 # if we need to consume all the clients input, ask for 4k chunks | |
71 # so the pipe doesn't fill up risking a deadlock | |
72 size = self.maxchunksize | |
73 s = self._read(size, self.channel) | |
74 buf = s | |
75 while s: | |
76 buf += s | |
77 s = self._read(size, self.channel) | |
78 | |
79 return buf | |
80 else: | |
81 return self._read(size, self.channel) | |
82 | |
83 def _read(self, size, channel): | |
84 if not size: | |
85 return '' | |
86 assert size > 0 | |
87 | |
88 # tell the client we need at most size bytes | |
89 self.out.write(struct.pack('>cI', channel, size)) | |
90 self.out.flush() | |
91 | |
92 length = self.in_.read(4) | |
93 length = struct.unpack('>I', length)[0] | |
94 if not length: | |
95 return '' | |
96 else: | |
97 return self.in_.read(length) | |
98 | |
99 def readline(self, size=-1): | |
100 if size < 0: | |
101 size = self.maxchunksize | |
102 s = self._read(size, 'L') | |
103 buf = s | |
104 # keep asking for more until there's either no more or | |
105 # we got a full line | |
106 while s and s[-1] != '\n': | |
107 buf += s | |
108 s = self._read(size, 'L') | |
109 | |
110 return buf | |
111 else: | |
112 return self._read(size, 'L') | |
113 | |
114 def __iter__(self): | |
115 return self | |
116 | |
117 def next(self): | |
118 l = self.readline() | |
119 if not l: | |
120 raise StopIteration | |
121 return l | |
122 | |
123 def __getattr__(self, attr): | |
124 if attr in ('isatty', 'fileno'): | |
125 raise AttributeError, attr | |
126 return getattr(self.in_, attr) | |
127 | |
128 class server(object): | |
129 """ | |
130 Listens for commands on stdin, runs them and writes the output on a channel | |
131 based stream to stdout. | |
132 """ | |
133 def __init__(self, ui, repo, mode): | |
134 self.ui = ui | |
135 | |
136 logpath = ui.config("cmdserver", "log", None) | |
137 if logpath: | |
138 global logfile | |
139 if logpath == '-': | |
140 # write log on a special 'd'ebug channel | |
141 logfile = channeledoutput(sys.stdout, sys.stdout, 'd') | |
142 else: | |
143 logfile = open(logpath, 'a') | |
144 | |
145 self.repo = repo | |
146 | |
147 if mode == 'pipe': | |
148 self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e') | |
149 self.cout = channeledoutput(sys.stdout, sys.stdout, 'o') | |
150 self.cin = channeledinput(sys.stdin, sys.stdout, 'I') | |
151 self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r') | |
152 | |
153 self.client = sys.stdin | |
154 else: | |
155 raise util.Abort(_('unknown mode %s') % mode) | |
156 | |
157 def _read(self, size): | |
158 data = self.client.read(size) | |
159 | |
160 # is the other end closed? | |
161 if not data: | |
162 raise EOFError() | |
163 | |
164 return data | |
165 | |
166 def runcommand(self): | |
167 """ reads a list of \0 terminated arguments, executes | |
168 and writes the return code to the result channel """ | |
169 | |
170 length = struct.unpack('>I', self._read(4))[0] | |
171 args = self._read(length).split('\0') | |
172 | |
173 # copy the ui so changes to it don't persist between requests | |
174 req = dispatch.request(args, self.ui.copy(), self.repo, self.cin, | |
175 self.cout, self.cerr) | |
176 | |
177 ret = dispatch.dispatch(req) or 0 # might return None | |
178 | |
179 self.cresult.write(struct.pack('>i', int(ret))) | |
180 | |
181 def getencoding(self): | |
182 """ writes the current encoding to the result channel """ | |
183 self.cresult.write(encoding.encoding) | |
184 | |
185 def serveone(self): | |
186 cmd = self.client.readline()[:-1] | |
187 if cmd: | |
188 handler = self.capabilities.get(cmd) | |
189 if handler: | |
190 handler(self) | |
191 else: | |
192 # clients are expected to check what commands are supported by | |
193 # looking at the servers capabilities | |
194 raise util.Abort(_('unknown command %s') % cmd) | |
195 | |
196 return cmd != '' | |
197 | |
198 capabilities = {'runcommand' : runcommand, | |
199 'getencoding' : getencoding} | |
200 | |
201 def serve(self): | |
202 self.cout.write('capabilities: %s' % ' '.join(self.capabilities.keys())) | |
203 self.cout.write('encoding: %s' % encoding.encoding) | |
204 | |
205 try: | |
206 while self.serveone(): | |
207 pass | |
208 except EOFError: | |
209 # we'll get here if the client disconnected while we were reading | |
210 # its request | |
211 return 1 | |
212 | |
213 return 0 |