hgext/inotify/client.py
branchstable
changeset 21160 564f55b25122
parent 21028 a0f437e2f5a9
parent 21159 024f38f6d5f6
child 21161 ef59019f4771
equal deleted inserted replaced
21028:a0f437e2f5a9 21160:564f55b25122
     1 # client.py - inotify status client
       
     2 #
       
     3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
       
     4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
       
     5 # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com>
       
     6 #
       
     7 # This software may be used and distributed according to the terms of the
       
     8 # GNU General Public License version 2 or any later version.
       
     9 
       
    10 from mercurial.i18n import _
       
    11 import common, server
       
    12 import errno, os, socket, struct
       
    13 
       
    14 class QueryFailed(Exception):
       
    15     pass
       
    16 
       
    17 def start_server(function):
       
    18     """
       
    19     Decorator.
       
    20     Tries to call function, if it fails, try to (re)start inotify server.
       
    21     Raise QueryFailed if something went wrong
       
    22     """
       
    23     def decorated_function(self, *args):
       
    24         try:
       
    25             return function(self, *args)
       
    26         except (OSError, socket.error), err:
       
    27             autostart = self.ui.configbool('inotify', 'autostart', True)
       
    28 
       
    29             if err.args[0] == errno.ECONNREFUSED:
       
    30                 self.ui.warn(_('inotify-client: found dead inotify server '
       
    31                                'socket; removing it\n'))
       
    32                 os.unlink(os.path.join(self.root, '.hg', 'inotify.sock'))
       
    33             if err.args[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart:
       
    34                 try:
       
    35                     try:
       
    36                         server.start(self.ui, self.dirstate, self.root,
       
    37                                      dict(daemon=True, daemon_pipefds=''))
       
    38                     except server.AlreadyStartedException, inst:
       
    39                         # another process may have started its own
       
    40                         # inotify server while this one was starting.
       
    41                         self.ui.debug(str(inst))
       
    42                 except Exception, inst:
       
    43                     self.ui.warn(_('inotify-client: could not start inotify '
       
    44                                    'server: %s\n') % inst)
       
    45                 else:
       
    46                     try:
       
    47                         return function(self, *args)
       
    48                     except socket.error, err:
       
    49                         self.ui.warn(_('inotify-client: could not talk to new '
       
    50                                        'inotify server: %s\n') % err.args[-1])
       
    51             elif err.args[0] in (errno.ECONNREFUSED, errno.ENOENT):
       
    52                 # silently ignore normal errors if autostart is False
       
    53                 self.ui.debug('(inotify server not running)\n')
       
    54             else:
       
    55                 self.ui.warn(_('inotify-client: failed to contact inotify '
       
    56                                'server: %s\n') % err.args[-1])
       
    57 
       
    58         self.ui.traceback()
       
    59         raise QueryFailed('inotify query failed')
       
    60 
       
    61     return decorated_function
       
    62 
       
    63 
       
    64 class client(object):
       
    65     def __init__(self, ui, repo):
       
    66         self.ui = ui
       
    67         self.dirstate = repo.dirstate
       
    68         self.root = repo.root
       
    69         self.sock = socket.socket(socket.AF_UNIX)
       
    70 
       
    71     def _connect(self):
       
    72         sockpath = os.path.join(self.root, '.hg', 'inotify.sock')
       
    73         try:
       
    74             self.sock.connect(sockpath)
       
    75         except socket.error, err:
       
    76             if err.args[0] == "AF_UNIX path too long":
       
    77                 sockpath = os.readlink(sockpath)
       
    78                 self.sock.connect(sockpath)
       
    79             else:
       
    80                 raise
       
    81 
       
    82     def _send(self, type, data):
       
    83         """Sends protocol version number, and the data"""
       
    84         self.sock.sendall(chr(common.version) + type + data)
       
    85 
       
    86         self.sock.shutdown(socket.SHUT_WR)
       
    87 
       
    88     def _receive(self, type):
       
    89         """
       
    90         Read data, check version number, extract headers,
       
    91         and returns a tuple (data descriptor, header)
       
    92         Raises QueryFailed on error
       
    93         """
       
    94         cs = common.recvcs(self.sock)
       
    95         try:
       
    96             version = ord(cs.read(1))
       
    97         except TypeError:
       
    98             # empty answer, assume the server crashed
       
    99             self.ui.warn(_('inotify-client: received empty answer from inotify '
       
   100                            'server'))
       
   101             raise QueryFailed('server crashed')
       
   102 
       
   103         if version != common.version:
       
   104             self.ui.warn(_('(inotify: received response from incompatible '
       
   105                       'server version %d)\n') % version)
       
   106             raise QueryFailed('incompatible server version')
       
   107 
       
   108         readtype = cs.read(4)
       
   109         if readtype != type:
       
   110             self.ui.warn(_('(inotify: received \'%s\' response when expecting'
       
   111                        ' \'%s\')\n') % (readtype, type))
       
   112             raise QueryFailed('wrong response type')
       
   113 
       
   114         hdrfmt = common.resphdrfmts[type]
       
   115         hdrsize = common.resphdrsizes[type]
       
   116         try:
       
   117             resphdr = struct.unpack(hdrfmt, cs.read(hdrsize))
       
   118         except struct.error:
       
   119             raise QueryFailed('unable to retrieve query response headers')
       
   120 
       
   121         return cs, resphdr
       
   122 
       
   123     def query(self, type, req):
       
   124         self._connect()
       
   125 
       
   126         self._send(type, req)
       
   127 
       
   128         return self._receive(type)
       
   129 
       
   130     @start_server
       
   131     def statusquery(self, names, match, ignored, clean, unknown=True):
       
   132 
       
   133         def genquery():
       
   134             for n in names:
       
   135                 yield n
       
   136             states = 'almrx!'
       
   137             if ignored:
       
   138                 raise ValueError('this is insanity')
       
   139             if clean:
       
   140                 states += 'c'
       
   141             if unknown:
       
   142                 states += '?'
       
   143             yield states
       
   144 
       
   145         req = '\0'.join(genquery())
       
   146 
       
   147         cs, resphdr = self.query('STAT', req)
       
   148 
       
   149         def readnames(nbytes):
       
   150             if nbytes:
       
   151                 names = cs.read(nbytes)
       
   152                 if names:
       
   153                     return filter(match, names.split('\0'))
       
   154             return []
       
   155         results = tuple(map(readnames, resphdr[:-1]))
       
   156 
       
   157         if names:
       
   158             nbytes = resphdr[-1]
       
   159             vdirs = cs.read(nbytes)
       
   160             if vdirs:
       
   161                 for vdir in vdirs.split('\0'):
       
   162                     if match.explicitdir:
       
   163                         match.explicitdir(vdir)
       
   164 
       
   165         return results
       
   166 
       
   167     @start_server
       
   168     def debugquery(self):
       
   169         cs, resphdr = self.query('DBUG', '')
       
   170 
       
   171         nbytes = resphdr[0]
       
   172         names = cs.read(nbytes)
       
   173         return names.split('\0')