[CalendarServer-changes] [8264] CalendarServer/trunk/calendarserver/tools/shell.py

source_changes at macosforge.org source_changes at macosforge.org
Tue Nov 8 15:21:13 PST 2011


Revision: 8264
          http://trac.macosforge.org/projects/calendarserver/changeset/8264
Author:   wsanchez at apple.com
Date:     2011-11-08 15:21:12 -0800 (Tue, 08 Nov 2011)
Log Message:
-----------
Terminal handling now works properly

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/shell.py

Modified: CalendarServer/trunk/calendarserver/tools/shell.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/shell.py	2011-11-08 22:36:03 UTC (rev 8263)
+++ CalendarServer/trunk/calendarserver/tools/shell.py	2011-11-08 23:21:12 UTC (rev 8264)
@@ -22,13 +22,18 @@
 import os
 import sys
 import traceback
+import tty
+import termios
 from shlex import shlex
 
+from twisted.python.log import startLogging
 from twisted.python.text import wordWrap
 from twisted.python.usage import Options, UsageError
-from twisted.internet.defer import succeed, maybeDeferred
+from twisted.internet.defer import succeed, fail, maybeDeferred, Deferred
+from twisted.internet.stdio import StandardIO
 from twisted.conch.stdio import runWithProtocol as shellWithProtocol
-from twisted.conch.recvline import RecvLine as ReceiveLineProtocol
+from twisted.conch.recvline import HistoricRecvLine as ReceiveLineProtocol
+from twisted.conch.insults.insults import ServerProtocol
 from twisted.application.service import Service
 
 from txdav.common.icommondatastore import NotFoundError
@@ -76,23 +81,38 @@
 class ShellService(Service, object):
     def __init__(self, store, options, reactor, config):
         super(ShellService, self).__init__()
-        self.store   = store
-        self.options = options
-        self.reactor = reactor
-        self.config = config
+        self.store      = store
+        self.options    = options
+        self.reactor    = reactor
+        self.config     = config
+        self.terminalFD = None
+        self.protocol   = None
 
     def startService(self):
         """
         Start the service.
         """
+        # For debugging
+        #f = open("/tmp/shell.log", "w")
+        #startLogging(f)
+
         super(ShellService, self).startService()
-        shellWithProtocol(lambda: ShellProtocol(self.store))
-        self.reactor.stop()
 
+        # Set up the terminal for interactive action
+        self.terminalFD = sys.__stdin__.fileno()
+        self._oldTerminalSettings = termios.tcgetattr(self.terminalFD)
+        tty.setraw(self.terminalFD)
+
+        self.protocol = ServerProtocol(lambda: ShellProtocol(self))
+        StandardIO(self.protocol)
+
     def stopService(self):
         """
         Stop the service.
         """
+        # Restore terminal settings
+        termios.tcsetattr(self.terminalFD, termios.TCSANOW, self._oldTerminalSettings)
+        os.write(self.terminalFD, "\r\x1bc\r")
 
 
 class UnknownArguments (Exception):
@@ -115,11 +135,67 @@
 
     ps = ("ds% ", "... ")
 
-    def __init__(self, store):
+    def __init__(self, service):
         ReceiveLineProtocol.__init__(self)
-        self.wd = RootDirectory(store)
+        self.service = service
+        self.wd = RootDirectory(service.store)
+        self.inputLines = []
+        self.activeCommand = None
 
+    def connectionMade(self):
+        ReceiveLineProtocol.connectionMade(self)
+
+        CTRL_C = '\x03'
+        CTRL_D = '\x04'
+        CTRL_BACKSLASH = '\x1c'
+        CTRL_L = '\x0c'
+
+        self.keyHandlers[CTRL_C] = self.handle_INT
+        self.keyHandlers[CTRL_D] = self.handle_EOF
+        self.keyHandlers[CTRL_L] = self.handle_FF
+        self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
+
+    def handle_INT(self):
+        """
+        Handle ^C as an interrupt keystroke by resetting the current input
+        variables to their initial state.
+        """
+        self.pn = 0
+        self.lineBuffer = []
+        self.lineBufferIndex = 0
+
+        self.terminal.nextLine()
+        self.terminal.write("KeyboardInterrupt")
+        self.terminal.nextLine()
+        self.exit()
+
+    def handle_EOF(self):
+        if self.lineBuffer:
+            self.terminal.write('\a')
+        else:
+            self.handle_QUIT()
+
+    def handle_FF(self):
+        """
+        Handle a 'form feed' byte - generally used to request a screen
+        refresh/redraw.
+        """
+        self.terminal.eraseDisplay()
+        self.terminal.cursorHome()
+        self.drawInputLine()
+
+    def handle_QUIT(self):
+        self.exit()
+
+    def exit(self):
+        self.terminal.loseConnection()
+        self.service.reactor.stop()
+
     def lineReceived(self, line):
+        if self.activeCommand is not None:
+            self.inputLines.append(line)
+            return
+
         lexer = shlex(line)
         lexer.whitespace_split = True
 
@@ -136,21 +212,40 @@
 
             m = getattr(self, "cmd_%s" % (cmd,), None)
             if m:
-                try:
-                    m(tokens)
-                except UnknownArguments, e:
-                    self.terminal.write("%s\n" % (e,))
-                except Exception, e:
-                    print "Error: %s" % (e,)
-                    print "-"*80
-                    f.printTraceback()
-                    print "-"*80
+                def handleUnknownArguments(f):
+                    f.trap(UnknownArguments)
+                    self.terminal.write("%s\n" % (f.value,))
 
+                def handleException(f):
+                    self.terminal.write("Error: %s\n" % (e,))
+                    self.terminal.write("-"*80 + "\n")
+                    self.terminal.write(f.getTraceback())
+                    self.terminal.write("-"*80 + "\n")
+
+                def next(_):
+                    self.activeCommand = None
+                    self.drawInputLine()
+                    if self.inputLines:
+                        line = self.inputLines.pop(0)
+                        self.lineReceived(line)
+
+                d = Deferred()
+                self.activeCommand = d
+                d.addCallback(lambda _: m(tokens))
+                if True:
+                    d.callback(None)
+                else:
+                    # Add time to test callbacks
+                    self.service.reactor.callLater(4, d.callback, None)
+                d.addErrback(handleUnknownArguments)
+                d.addErrback(handleException)
+                d.addBoth(next)
             else:
                 self.terminal.write("Unknown command: %s\n" % (cmd,))
+                self.drawInputLine()
+        else:
+            self.drawInputLine()
 
-        self.drawInputLine()
-
     def cmd_pwd(self, tokens):
         """
         Print working directory.
@@ -214,8 +309,7 @@
         """
         Exit the shell.
         """
-        self.terminal.loseConnection()
-        # FIXME: This is insufficient.
+        self.exit()
 
     def cmd_python(self, tokens):
         """
@@ -270,9 +364,9 @@
         if name == ".":
             return succeed(self)
         if name == "..":
-            return RootDirectory(self.store).locate(self.path[:-1])
+            return succeed(RootDirectory(self.store).locate(self.path[:-1]))
 
-        raise NotFoundError("Directory %r has no subdirectory %r" % (str(self), name))
+        return fail(NotFoundError("Directory %r has no subdirectory %r" % (str(self), name)))
 
     def list(self):
         return ()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20111108/48c091a5/attachment-0001.html>


More information about the calendarserver-changes mailing list