SSH and telnet library
[monitor.git] / pyssh / nbpipe.py
diff --git a/pyssh/nbpipe.py b/pyssh/nbpipe.py
new file mode 100644 (file)
index 0000000..08b4f97
--- /dev/null
@@ -0,0 +1,111 @@
+"""Implements a non-blocking pipe class."""\r
+\r
+# Since it uses thread rather than select, it is portable to at least\r
+# posix and windows environments.\r
+\r
+# Author: Rasjid Wilcox, copyright (c) 2002\r
+# Ideas taken from the Python 2.2 telnetlib.py library.\r
+#\r
+# Last modified: 3 August 2002\r
+# Licence: Python 2.2 Style License.  See license.txt.\r
+\r
+# TO DO:\r
+#     * Handle excpetions better, particularly Keyboard Interupts.\r
+#     * Possibly do a threadless version for posix environments\r
+#       where we can use select (is probably more efficient).\r
+#     * A test function.\r
+\r
+import Queue\r
+import thread\r
+import os\r
+import time\r
+import types\r
+\r
+#INT_TYPE = type(1)\r
+MIN_TIMEOUT = 0.01\r
+\r
+class nbpipe:\r
+    def __init__(self, readfile, pipesize=0, blocksize=1024):\r
+        """Initialise a non-blocking pipe object, given a real file or file-descriptor.\r
+        pipesize = the size (in blocks) of the queue used to buffer the blocks read\r
+        blocksize = the maximum block size for a raw read."""\r
+        if type(readfile) == types.IntType:\r
+            self.fd = readfile\r
+        else:\r
+            self.fd = readfile.fileno()\r
+        self.pipesize = pipesize\r
+        self.blocksize = blocksize\r
+        self.eof = 0\r
+        self._q = Queue.Queue(self.pipesize)\r
+        self.data = ''\r
+        thread.start_new_thread(self._readtoq, ())\r
+    def _readtoq(self):\r
+        finish = 0\r
+        while (1):\r
+            try:\r
+                item = os.read(self.fd, self.blocksize)\r
+            except (IOError, OSError):\r
+                finish = 1\r
+            if (item == '') or finish:\r
+                # Wait until everything has been read from the queue before\r
+                # setting eof = 1 and exiting.\r
+                while not self._q.empty():\r
+                    time.sleep(MIN_TIMEOUT)\r
+                self.eof = 1\r
+                thread.exit()\r
+            else:\r
+                self._q.put(item)\r
+    def has_data(self):\r
+        return self.data\r
+    def eof(self):\r
+        return self.eof\r
+    def read_lazy(self):\r
+        """Process and return data that's already in the queues (lazy).\r
+\r
+        Return '' if no data available. Don't block.\r
+\r
+        """\r
+        while not self._q.empty():\r
+            self.data += self._q.get()\r
+        data = self.data\r
+        self.data = ''\r
+        return data\r
+    def read_some(self, until_eof=False):\r
+        """Read at least one byte of cooked data unless EOF is hit.\r
+\r
+        Return '' if EOF is hit.  Block if no data is immediately\r
+        available.\r
+\r
+        """\r
+        data = ''\r
+        while (until_eof or not data) and not self.eof:\r
+            data += self.read_lazy()\r
+            time.sleep(MIN_TIMEOUT)\r
+        return data\r
+    def read_until(self, match, timeout=None):\r
+        """Read until a given string is encountered or until timeout.\r
+\r
+        If no match is found or EOF is hit, return whatever is\r
+        available instead, possibly the empty string.\r
+        """\r
+        if timeout is not None:\r
+            timeout = timeout / MIN_TIMEOUT\r
+        else:\r
+            timeout = 1\r
+        n = len(match)\r
+        data = self.read_lazy()\r
+        i = 0\r
+        while timeout >= 0 and not self.eof:\r
+            i = data.find(match, i)\r
+            if i >= 0:\r
+                i += n\r
+                self.data = data[i:]\r
+                return data[:i]\r
+            time.sleep(MIN_TIMEOUT)\r
+            timeout -= 1\r
+            i = max(0, len(data) - n)\r
+            data += self.read_lazy()\r
+        return data\r
+    def read_all(self):\r
+        """Read until the EOF. May block."""\r
+        return read_some(until_eof=True)\r