]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/hgext/inotify/client.py
hgwebfs: write headers individually, so they are not limited by webfs iounit (thanks...
[plan9front.git] / sys / lib / python / hgext / inotify / client.py
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, incorporated herein by reference.
9
10 from mercurial.i18n import _
11 import common, server
12 import errno, os, socket, struct
13
14 class QueryFailed(Exception): pass
15
16 def start_server(function):
17     """
18     Decorator.
19     Tries to call function, if it fails, try to (re)start inotify server.
20     Raise QueryFailed if something went wrong
21     """
22     def decorated_function(self, *args):
23         result = None
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[0] == errno.ECONNREFUSED:
30                 self.ui.warn(_('(found dead inotify server socket; '
31                                'removing it)\n'))
32                 os.unlink(os.path.join(self.root, '.hg', 'inotify.sock'))
33             if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart:
34                 self.ui.debug(_('(starting inotify server)\n'))
35                 try:
36                     try:
37                         server.start(self.ui, self.dirstate, self.root)
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(_('could not start inotify server: '
44                                    '%s\n') % inst)
45                 else:
46                     try:
47                         return function(self, *args)
48                     except socket.error, err:
49                         self.ui.warn(_('could not talk to new inotify '
50                                        'server: %s\n') % err[-1])
51             elif err[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(_('failed to contact inotify server: %s\n')
56                          % err[-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[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(_('received empty answer from inotify server'))
100             raise QueryFailed('server crashed')
101
102         if version != common.version:
103             self.ui.warn(_('(inotify: received response from incompatible '
104                       'server version %d)\n') % version)
105             raise QueryFailed('incompatible server version')
106
107         readtype = cs.read(4)
108         if readtype != type:
109             self.ui.warn(_('(inotify: received \'%s\' response when expecting'
110                        ' \'%s\')\n') % (readtype, type))
111             raise QueryFailed('wrong response type')
112
113         hdrfmt = common.resphdrfmts[type]
114         hdrsize = common.resphdrsizes[type]
115         try:
116             resphdr = struct.unpack(hdrfmt, cs.read(hdrsize))
117         except struct.error:
118             raise QueryFailed('unable to retrieve query response headers')
119
120         return cs, resphdr
121
122     def query(self, type, req):
123         self._connect()
124
125         self._send(type, req)
126
127         return self._receive(type)
128
129     @start_server
130     def statusquery(self, names, match, ignored, clean, unknown=True):
131
132         def genquery():
133             for n in names:
134                 yield n
135             states = 'almrx!'
136             if ignored:
137                 raise ValueError('this is insanity')
138             if clean: states += 'c'
139             if unknown: states += '?'
140             yield states
141
142         req = '\0'.join(genquery())
143
144         cs, resphdr = self.query('STAT', req)
145
146         def readnames(nbytes):
147             if nbytes:
148                 names = cs.read(nbytes)
149                 if names:
150                     return filter(match, names.split('\0'))
151             return []
152         return map(readnames, resphdr)
153
154     @start_server
155     def debugquery(self):
156         cs, resphdr = self.query('DBUG', '')
157
158         nbytes = resphdr[0]
159         names = cs.read(nbytes)
160         return names.split('\0')