client.py
174 lines
| 5.9 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 | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Bryan O'Sullivan
|
r6239 | |||
Martin Geisler
|
r7225 | from mercurial.i18n import _ | ||
Nicolas Dumazet
|
r8552 | import common, server | ||
import errno, os, socket, struct | ||||
Matt Mackall
|
r10282 | class QueryFailed(Exception): | ||
pass | ||||
Nicolas Dumazet
|
r8552 | |||
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: | ||||
Nicolas Dumazet
|
r9900 | self.ui.warn(_('inotify-client: found dead inotify server ' | ||
'socket; removing it\n')) | ||||
Nicolas Dumazet
|
r9351 | os.unlink(os.path.join(self.root, '.hg', 'inotify.sock')) | ||
Nicolas Dumazet
|
r8552 | if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart: | ||
Martin Geisler
|
r9467 | self.ui.debug('(starting inotify server)\n') | ||
Nicolas Dumazet
|
r8552 | try: | ||
try: | ||||
Nicolas Dumazet
|
r9514 | server.start(self.ui, self.dirstate, self.root, | ||
dict(daemon=True, daemon_pipefds='')) | ||||
Nicolas Dumazet
|
r8552 | 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: | ||||
Nicolas Dumazet
|
r9900 | self.ui.warn(_('inotify-client: could not start inotify ' | ||
'server: %s\n') % inst) | ||||
Nicolas Dumazet
|
r8552 | else: | ||
try: | ||||
return function(self, *args) | ||||
except socket.error, err: | ||||
Nicolas Dumazet
|
r9900 | self.ui.warn(_('inotify-client: could not talk to new ' | ||
'inotify server: %s\n') % err[-1]) | ||||
Nicolas Dumazet
|
r8552 | elif err[0] in (errno.ECONNREFUSED, errno.ENOENT): | ||
# silently ignore normal errors if autostart is False | ||||
Martin Geisler
|
r9467 | self.ui.debug('(inotify server not running)\n') | ||
Nicolas Dumazet
|
r8552 | else: | ||
Nicolas Dumazet
|
r9900 | self.ui.warn(_('inotify-client: failed to contact inotify ' | ||
'server: %s\n') % err[-1]) | ||||
Nicolas Dumazet
|
r8552 | |||
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 | ||||
Nicolas Dumazet
|
r9351 | self.dirstate = repo.dirstate | ||
self.root = repo.root | ||||
Nicolas Dumazet
|
r8551 | self.sock = socket.socket(socket.AF_UNIX) | ||
def _connect(self): | ||||
Nicolas Dumazet
|
r9351 | sockpath = os.path.join(self.root, '.hg', 'inotify.sock') | ||
Nicolas Dumazet
|
r8551 | 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) | ||||
Nicolas Dumazet
|
r8788 | try: | ||
version = ord(cs.read(1)) | ||||
except TypeError: | ||||
# empty answer, assume the server crashed | ||||
Nicolas Dumazet
|
r9900 | self.ui.warn(_('inotify-client: received empty answer from inotify ' | ||
'server')) | ||||
Nicolas Dumazet
|
r8788 | raise QueryFailed('server crashed') | ||
Nicolas Dumazet
|
r8551 | 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') | ||||
Matt Mackall
|
r10282 | if clean: | ||
states += 'c' | ||||
if unknown: | ||||
states += '?' | ||||
Nicolas Dumazet
|
r8551 | 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 [] | ||||
Greg Ward
|
r11628 | results = tuple(map(readnames, resphdr[:-1])) | ||
Nicolas Dumazet
|
r9854 | |||
if names: | ||||
nbytes = resphdr[-1] | ||||
vdirs = cs.read(nbytes) | ||||
if vdirs: | ||||
for vdir in vdirs.split('\0'): | ||||
match.dir(vdir) | ||||
return results | ||||
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') | ||||