| # client.py - inotify status client |
| # |
| # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com> |
| # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com> |
| # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com> |
| # |
| # This software may be used and distributed according to the terms of the |
| # GNU General Public License version 2 or any later version. |
| |
| from mercurial.i18n import _ |
| 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): |
| try: |
| return function(self, *args) |
| except (OSError, socket.error), err: |
| autostart = self.ui.configbool('inotify', 'autostart', True) |
| |
| if err.args[0] == errno.ECONNREFUSED: |
| self.ui.warn(_('inotify-client: found dead inotify server ' |
| 'socket; removing it\n')) |
| os.unlink(os.path.join(self.root, '.hg', 'inotify.sock')) |
| if err.args[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart: |
| try: |
| try: |
| server.start(self.ui, self.dirstate, self.root, |
| dict(daemon=True, daemon_pipefds='')) |
| 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(_('inotify-client: could not start inotify ' |
| 'server: %s\n') % inst) |
| else: |
| try: |
| return function(self, *args) |
| except socket.error, err: |
| self.ui.warn(_('inotify-client: could not talk to new ' |
| 'inotify server: %s\n') % err.args[-1]) |
| elif err.args[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(_('inotify-client: failed to contact inotify ' |
| 'server: %s\n') % err.args[-1]) |
| |
| self.ui.traceback() |
| raise QueryFailed('inotify query failed') |
| |
| return decorated_function |
| |
| |
| class client(object): |
| def __init__(self, ui, repo): |
| self.ui = ui |
| self.dirstate = repo.dirstate |
| self.root = repo.root |
| self.sock = socket.socket(socket.AF_UNIX) |
| |
| def _connect(self): |
| sockpath = os.path.join(self.root, '.hg', 'inotify.sock') |
| try: |
| self.sock.connect(sockpath) |
| except socket.error, err: |
| if err.args[0] == "AF_UNIX path too long": |
| sockpath = os.readlink(sockpath) |
| self.sock.connect(sockpath) |
| else: |
| raise |
| |
| def _send(self, type, data): |
| """Sends protocol version number, and the data""" |
| self.sock.sendall(chr(common.version) + type + data) |
| |
| self.sock.shutdown(socket.SHUT_WR) |
| |
| def _receive(self, type): |
| """ |
| Read data, check version number, extract headers, |
| and returns a tuple (data descriptor, header) |
| Raises QueryFailed on error |
| """ |
| cs = common.recvcs(self.sock) |
| try: |
| version = ord(cs.read(1)) |
| except TypeError: |
| # empty answer, assume the server crashed |
| self.ui.warn(_('inotify-client: received empty answer from inotify ' |
| 'server')) |
| raise QueryFailed('server crashed') |
| |
| if version != common.version: |
| self.ui.warn(_('(inotify: received response from incompatible ' |
| 'server version %d)\n') % version) |
| raise QueryFailed('incompatible server version') |
| |
| 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') |
| |
| hdrfmt = common.resphdrfmts[type] |
| hdrsize = common.resphdrsizes[type] |
| try: |
| resphdr = struct.unpack(hdrfmt, cs.read(hdrsize)) |
| except struct.error: |
| raise QueryFailed('unable to retrieve query response headers') |
| |
| return cs, resphdr |
| |
| def query(self, type, req): |
| self._connect() |
| |
| self._send(type, req) |
| |
| return self._receive(type) |
| |
| @start_server |
| def statusquery(self, names, match, ignored, clean, unknown=True): |
| |
| 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()) |
| |
| cs, resphdr = self.query('STAT', req) |
| |
| def readnames(nbytes): |
| if nbytes: |
| names = cs.read(nbytes) |
| if names: |
| return filter(match, names.split('\0')) |
| return [] |
| results = tuple(map(readnames, resphdr[:-1])) |
| |
| if names: |
| nbytes = resphdr[-1] |
| vdirs = cs.read(nbytes) |
| if vdirs: |
| for vdir in vdirs.split('\0'): |
| match.dir(vdir) |
| |
| return results |
| |
| @start_server |
| def debugquery(self): |
| cs, resphdr = self.query('DBUG', '') |
| |
| nbytes = resphdr[0] |
| names = cs.read(nbytes) |
| return names.split('\0') |