changeset 8551:7089d9727867

inotify: modular architecture for inotify clients Put the socket init, query generation and response analysis in a more generic client class.
author Nicolas Dumazet <nicdumz.commits@gmail.com>
date Tue, 07 Apr 2009 18:39:34 +0900
parents cbd3a104637f
children 06561793778e
files hgext/inotify/__init__.py hgext/inotify/client.py
diffstat 2 files changed, 81 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/inotify/__init__.py	Sat May 23 13:43:11 2009 +0200
+++ b/hgext/inotify/__init__.py	Tue Apr 07 18:39:34 2009 +0900
@@ -13,8 +13,9 @@
 
 from mercurial.i18n import _
 from mercurial import cmdutil, util
-import client, errno, os, server, socket
+import errno, os, server, socket
 from weakref import proxy
+from client import client
 
 def serve(ui, repo, **opts):
     '''start an inotify server for this repository'''
@@ -54,10 +55,11 @@
             files = match.files()
             if '.' in files:
                 files = []
+            cli = client(ui, repo)
             try:
                 if not ignored and not self.inotifyserver:
-                    result = client.query(ui, repo, files, match, False,
-                                          clean, unknown)
+                    result = cli.statusquery(files, match, False,
+                                             clean, unknown)
                     if result and ui.config('inotify', 'debug'):
                         r2 = super(inotifydirstate, self).status(
                             match, False, clean, unknown)
@@ -94,8 +96,8 @@
                     else:
                         # server is started, send query again
                         try:
-                            return client.query(ui, repo, files, match,
-                                         ignored, clean, unknown)
+                            return cli.statusquery(files, match, ignored,
+                                                   clean, unknown)
                         except socket.error, err:
                             ui.warn(_('could not talk to new inotify '
                                            'server: %s\n') % err[-1])
--- a/hgext/inotify/client.py	Sat May 23 13:43:11 2009 +0200
+++ b/hgext/inotify/client.py	Tue Apr 07 18:39:34 2009 +0900
@@ -2,6 +2,7 @@
 #
 # 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, incorporated herein by reference.
@@ -10,56 +11,84 @@
 import common
 import os, socket, struct
 
-def query(ui, repo, names, match, ignored, clean, unknown=True):
-    sock = socket.socket(socket.AF_UNIX)
-    sockpath = repo.join('inotify.sock')
-    try:
-        sock.connect(sockpath)
-    except socket.error, err:
-        if err[0] == "AF_UNIX path too long":
-            sockpath = os.readlink(sockpath)
-            sock.connect(sockpath)
-        else:
-            raise
+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
 
-    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
+    def _send(self, data):
+        """Sends protocol version number, and the data"""
+        self.sock.sendall(chr(common.version) + data)
+
+        self.sock.shutdown(socket.SHUT_WR)
 
-    req = '\0'.join(genquery())
+    def _receive(self):
+        """
+        Read data, check version number, extract headers,
+        and returns a tuple (data descriptor, header)
+        Returns (None, None) on error
+        """
+        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)
+            return None, None
 
-    sock.sendall(chr(common.version))
-    sock.sendall(req)
-    sock.shutdown(socket.SHUT_WR)
+        # only one type of request is supported for now
+        type = 'STAT'
+        hdrfmt = common.resphdrfmts[type]
+        hdrsize = common.resphdrsizes[type]
+        try:
+            resphdr = struct.unpack(hdrfmt, cs.read(hdrsize))
+        except struct.error:
+            return None, None
 
-    cs = common.recvcs(sock)
-    version = ord(cs.read(1))
+        return cs, resphdr
+
+    def query(self, req):
+        self._connect()
 
-    if version != common.version:
-        ui.warn(_('(inotify: received response from incompatible server '
-                  'version %d)\n') % version)
-        return None
+        self._send(req)
+
+        return self._receive()
+
+    def statusquery(self, names, match, ignored, clean, unknown=True):
 
-    # only one type of request is supported for now
-    type = 'STAT'
-    hdrfmt = common.resphdrfmts[type]
-    hdrsize = common.resphdrsizes[type]
-    try:
-        resphdr = struct.unpack(hdrfmt, cs.read(hdrsize))
-    except struct.error:
-        return None
+        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())
 
-    def readnames(nbytes):
-        if nbytes:
-            names = cs.read(nbytes)
-            if names:
-                return filter(match, names.split('\0'))
-        return []
+        cs, resphdr = self.query(req)
+
+        if not cs:
+            return None
 
-    return map(readnames, resphdr)
+        def readnames(nbytes):
+            if nbytes:
+                names = cs.read(nbytes)
+                if names:
+                    return filter(match, names.split('\0'))
+            return []
+
+        return map(readnames, resphdr)