client.py
153 lines
| 5.1 KiB
| text/x-python
|
PythonLexer
Bryan O'Sullivan
|
r6239 | # client.py - inotify status client | ||
# | ||||
# Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com> | ||||
# Copyright 2007, 2008 Brendan Cully <brendan@kublai.com> | ||||
Nicolas Dumazet
|
r8551 | # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com> | ||
Bryan O'Sullivan
|
r6239 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
# GNU General Public License version 2, incorporated herein by reference. | ||||
Bryan O'Sullivan
|
r6239 | |||
Martin Geisler
|
r7225 | from mercurial.i18n import _ | ||
Nicolas Dumazet
|
r8552 | import common, server | ||
import errno, os, socket, struct | ||||
class QueryFailed(Exception): pass | ||||
def start_server(function): | ||||
""" | ||||
Decorator. | ||||
Tries to call function, if it fails, try to (re)start inotify server. | ||||
Raise QueryFailed if something went wrong | ||||
""" | ||||
def decorated_function(self, *args): | ||||
result = None | ||||
try: | ||||
return function(self, *args) | ||||
except (OSError, socket.error), err: | ||||
autostart = self.ui.configbool('inotify', 'autostart', True) | ||||
if err[0] == errno.ECONNREFUSED: | ||||
self.ui.warn(_('(found dead inotify server socket; ' | ||||
'removing it)\n')) | ||||
os.unlink(self.repo.join('inotify.sock')) | ||||
if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart: | ||||
self.ui.debug(_('(starting inotify server)\n')) | ||||
try: | ||||
try: | ||||
server.start(self.ui, self.repo) | ||||
except server.AlreadyStartedException, inst: | ||||
# another process may have started its own | ||||
# inotify server while this one was starting. | ||||
self.ui.debug(str(inst)) | ||||
except Exception, inst: | ||||
self.ui.warn(_('could not start inotify server: ' | ||||
'%s\n') % inst) | ||||
else: | ||||
try: | ||||
return function(self, *args) | ||||
except socket.error, err: | ||||
self.ui.warn(_('could not talk to new inotify ' | ||||
'server: %s\n') % err[-1]) | ||||
elif err[0] in (errno.ECONNREFUSED, errno.ENOENT): | ||||
# silently ignore normal errors if autostart is False | ||||
self.ui.debug(_('(inotify server not running)\n')) | ||||
else: | ||||
self.ui.warn(_('failed to contact inotify server: %s\n') | ||||
% err[-1]) | ||||
self.ui.traceback() | ||||
raise QueryFailed('inotify query failed') | ||||
return decorated_function | ||||
Bryan O'Sullivan
|
r6239 | |||
Nicolas Dumazet
|
r8551 | class client(object): | ||
def __init__(self, ui, repo): | ||||
self.ui = ui | ||||
self.repo = repo | ||||
self.sock = socket.socket(socket.AF_UNIX) | ||||
def _connect(self): | ||||
sockpath = self.repo.join('inotify.sock') | ||||
try: | ||||
self.sock.connect(sockpath) | ||||
except socket.error, err: | ||||
if err[0] == "AF_UNIX path too long": | ||||
sockpath = os.readlink(sockpath) | ||||
self.sock.connect(sockpath) | ||||
else: | ||||
raise | ||||
Bryan O'Sullivan
|
r6239 | |||
Nicolas Dumazet
|
r8553 | def _send(self, type, data): | ||
Nicolas Dumazet
|
r8551 | """Sends protocol version number, and the data""" | ||
Nicolas Dumazet
|
r8553 | self.sock.sendall(chr(common.version) + type + data) | ||
Nicolas Dumazet
|
r8551 | |||
self.sock.shutdown(socket.SHUT_WR) | ||||
Bryan O'Sullivan
|
r6239 | |||
Nicolas Dumazet
|
r8553 | def _receive(self, type): | ||
Nicolas Dumazet
|
r8551 | """ | ||
Read data, check version number, extract headers, | ||||
and returns a tuple (data descriptor, header) | ||||
Nicolas Dumazet
|
r8552 | Raises QueryFailed on error | ||
Nicolas Dumazet
|
r8551 | """ | ||
cs = common.recvcs(self.sock) | ||||
version = ord(cs.read(1)) | ||||
if version != common.version: | ||||
self.ui.warn(_('(inotify: received response from incompatible ' | ||||
'server version %d)\n') % version) | ||||
Nicolas Dumazet
|
r8552 | raise QueryFailed('incompatible server version') | ||
Bryan O'Sullivan
|
r6239 | |||
Nicolas Dumazet
|
r8553 | readtype = cs.read(4) | ||
if readtype != type: | ||||
self.ui.warn(_('(inotify: received \'%s\' response when expecting' | ||||
' \'%s\')\n') % (readtype, type)) | ||||
raise QueryFailed('wrong response type') | ||||
Nicolas Dumazet
|
r8551 | hdrfmt = common.resphdrfmts[type] | ||
hdrsize = common.resphdrsizes[type] | ||||
try: | ||||
resphdr = struct.unpack(hdrfmt, cs.read(hdrsize)) | ||||
except struct.error: | ||||
Nicolas Dumazet
|
r8552 | raise QueryFailed('unable to retrieve query response headers') | ||
Bryan O'Sullivan
|
r6239 | |||
Nicolas Dumazet
|
r8551 | return cs, resphdr | ||
Nicolas Dumazet
|
r8553 | def query(self, type, req): | ||
Nicolas Dumazet
|
r8551 | self._connect() | ||
Bryan O'Sullivan
|
r6239 | |||
Nicolas Dumazet
|
r8553 | self._send(type, req) | ||
Nicolas Dumazet
|
r8551 | |||
Nicolas Dumazet
|
r8553 | return self._receive(type) | ||
Nicolas Dumazet
|
r8551 | |||
Nicolas Dumazet
|
r8552 | @start_server | ||
Nicolas Dumazet
|
r8551 | def statusquery(self, names, match, ignored, clean, unknown=True): | ||
Bryan O'Sullivan
|
r6239 | |||
Nicolas Dumazet
|
r8551 | def genquery(): | ||
for n in names: | ||||
yield n | ||||
states = 'almrx!' | ||||
if ignored: | ||||
raise ValueError('this is insanity') | ||||
if clean: states += 'c' | ||||
if unknown: states += '?' | ||||
yield states | ||||
req = '\0'.join(genquery()) | ||||
Bryan O'Sullivan
|
r6239 | |||
Nicolas Dumazet
|
r8553 | cs, resphdr = self.query('STAT', req) | ||
Nicolas Dumazet
|
r8551 | |||
def readnames(nbytes): | ||||
if nbytes: | ||||
names = cs.read(nbytes) | ||||
if names: | ||||
return filter(match, names.split('\0')) | ||||
return [] | ||||
Nicolas Dumazet
|
r8555 | return map(readnames, resphdr) | ||
Nicolas Dumazet
|
r8551 | |||
Nicolas Dumazet
|
r8555 | @start_server | ||
def debugquery(self): | ||||
cs, resphdr = self.query('DBUG', '') | ||||
nbytes = resphdr[0] | ||||
names = cs.read(nbytes) | ||||
return names.split('\0') | ||||