[CalendarServer-changes] [8542] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Mon Jan 16 13:47:38 PST 2012
Revision: 8542
http://trac.macosforge.org/projects/calendarserver/changeset/8542
Author: wsanchez at apple.com
Date: 2012-01-16 13:47:36 -0800 (Mon, 16 Jan 2012)
Log Message:
-----------
Reorganize shell tool into three modules:
vfs: virtual file system
cmd: commands
terminal: tty support
Modified Paths:
--------------
CalendarServer/trunk/bin/calendarserver_shell
Added Paths:
-----------
CalendarServer/trunk/calendarserver/tools/shell/
CalendarServer/trunk/calendarserver/tools/shell/cmd.py
CalendarServer/trunk/calendarserver/tools/shell/terminal.py
CalendarServer/trunk/calendarserver/tools/shell/vfs.py
Removed Paths:
-------------
CalendarServer/trunk/calendarserver/tools/shell.py
Modified: CalendarServer/trunk/bin/calendarserver_shell
===================================================================
--- CalendarServer/trunk/bin/calendarserver_shell 2012-01-16 21:15:37 UTC (rev 8541)
+++ CalendarServer/trunk/bin/calendarserver_shell 2012-01-16 21:47:36 UTC (rev 8542)
@@ -29,5 +29,5 @@
except ImportError:
sys.exc_clear()
- from calendarserver.tools.shell import main
+ from calendarserver.tools.shell.terminal import main
main()
Copied: CalendarServer/trunk/calendarserver/tools/shell/cmd.py (from rev 8540, CalendarServer/trunk/calendarserver/tools/shell.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tools/shell/cmd.py (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/shell/cmd.py 2012-01-16 21:47:36 UTC (rev 8542)
@@ -0,0 +1,352 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2011-2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Data store commands.
+"""
+
+from twisted.python import log
+from twisted.internet.defer import succeed
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from txdav.common.icommondatastore import NotFoundError
+
+from calendarserver.tools.tables import Table
+from calendarserver.tools.shell.vfs import Folder
+
+
+class UsageError (Exception):
+ """
+ Usage error.
+ """
+
+
+class UnknownArguments (UsageError):
+ """
+ Unknown arguments.
+ """
+ def __init__(self, arguments):
+ Exception.__init__(self, "Unknown arguments: %s" % (arguments,))
+ self.arguments = arguments
+
+
+class Commands (object):
+ """
+ Data store commands.
+ """
+
+ #
+ # Utilities
+ #
+
+ def _getTarget(self, tokens):
+ if tokens:
+ return self.wd.locate(tokens.pop(0).split("/"))
+ else:
+ return succeed(self.wd)
+
+ @inlineCallbacks
+ def _getTargets(self, tokens):
+ if tokens:
+ result = []
+ for token in tokens:
+ result.append((yield self.wd.locate(token.split("/"))))
+ returnValue(result)
+ else:
+ returnValue((self.wd,))
+
+ def commands(self):
+ for attr in dir(self):
+ if attr.startswith("cmd_"):
+ m = getattr(self, attr)
+ if not hasattr(m, "hidden"):
+ yield (attr[4:], m)
+
+ @staticmethod
+ def _complete(word, items):
+ for item in items:
+ if item.startswith(word):
+ yield item[len(word):]
+
+ def _complete_commands(self, word):
+ return self._complete(word, (name for name, method in self.commands()))
+
+ @inlineCallbacks
+ def _complete_files(self, tokens, filter=None):
+ if filter is None:
+ filter = lambda items: True
+
+ files = (
+ self._listEntryToString(item)
+ for item in (yield self.wd.list())
+ if filter(item)
+ )
+
+ if len(tokens) == 0:
+ returnValue(files)
+ elif len(tokens) == 1:
+ returnValue(self._complete(tokens[0], files))
+ else:
+ returnValue(())
+
+ @staticmethod
+ def _listEntryToString(entry):
+ klass = entry[0]
+ name = entry[1]
+
+ if issubclass(klass, Folder):
+ return "%s/" % (name,)
+ else:
+ return name
+
+ #
+ # Commands
+ #
+
+ def cmd_help(self, tokens):
+ """
+ Show help.
+
+ usage: help [command]
+ """
+ if tokens:
+ command = tokens.pop(0)
+ else:
+ command = None
+
+ if tokens:
+ raise UnknownArguments(tokens)
+
+ if command:
+ m = getattr(self, "cmd_%s" % (command,), None)
+ if m:
+ doc = m.__doc__.split("\n")
+
+ # Throw out first and last line if it's empty
+ if doc:
+ if not doc[0].strip():
+ doc.pop(0)
+ if not doc[-1].strip():
+ doc.pop()
+
+ if doc:
+ # Get length of indentation
+ i = len(doc[0]) - len(doc[0].lstrip())
+
+ for line in doc:
+ self.terminal.write(line[i:])
+ self.terminal.nextLine()
+
+ else:
+ self.terminal.write("(No documentation available for %s)\n" % (command,))
+ else:
+ raise NotFoundError("Unknown command: %s" % (command,))
+ else:
+ self.terminal.write("Available commands:\n")
+
+ result = []
+ max_len = 0
+
+ for name, m in self.commands():
+ for line in m.__doc__.split("\n"):
+ line = line.strip()
+ if line:
+ doc = line
+ break
+ else:
+ doc = "(no info available)"
+
+ if len(name) > max_len:
+ max_len = len(name)
+
+ result.append((name, doc))
+
+ format = " %%%ds - %%s\n" % (max_len,)
+
+ for info in sorted(result):
+ self.terminal.write(format % (info))
+
+ def complete_help(self, tokens):
+ if len(tokens) == 0:
+ return (name for name, method in self.commands())
+ elif len(tokens) == 1:
+ return self._complete_commands(tokens[0])
+ else:
+ return ()
+
+ def cmd_emulate(self, tokens):
+ """
+ Emulate editor behavior.
+ The only correct argument is: emacs
+ Other choices include: none
+
+ usage: emulate editor
+ """
+ if not tokens:
+ if self.emulate:
+ self.terminal.write("Emulating %s.\n" % (self.emulate,))
+ else:
+ self.terminal.write("Emulation disabled.\n")
+ return
+
+ editor = tokens.pop(0).lower()
+
+ if tokens:
+ raise UnknownArguments(tokens)
+
+ if editor == "none":
+ self.terminal.write("Disabling emulation.\n")
+ editor = None
+ elif editor in self.emulation_modes:
+ self.terminal.write("Emulating %s.\n" % (editor,))
+ else:
+ raise UsageError("Unknown editor: %s" % (editor,))
+
+ self.emulate = editor
+
+ # FIXME: Need to update key registrations
+
+ cmd_emulate.hidden = "Incomplete"
+
+ def complete_emulate(self, tokens):
+ if len(tokens) == 0:
+ return self.emulation_modes
+ elif len(tokens) == 1:
+ return self._complete(tokens[0], self.emulation_modes)
+ else:
+ return ()
+
+ def cmd_pwd(self, tokens):
+ """
+ Print working folder.
+
+ usage: pwd
+ """
+ if tokens:
+ raise UnknownArguments(tokens)
+
+ self.terminal.write("%s\n" % (self.wd,))
+
+ @inlineCallbacks
+ def cmd_cd(self, tokens):
+ """
+ Change working folder.
+
+ usage: cd [folder]
+ """
+ if not tokens:
+ return
+
+ dirname = tokens.pop(0)
+
+ if tokens:
+ raise UnknownArguments(tokens)
+
+ wd = (yield self.wd.locate(dirname.split("/")))
+
+ if not isinstance(wd, Folder):
+ raise NotFoundError("Not a folder: %s" % (wd,))
+
+ log.msg("wd -> %s" % (wd,))
+ self.wd = wd
+
+ @inlineCallbacks
+ def complete_cd(self, tokens):
+ returnValue((yield self._complete_files(
+ tokens,
+ filter = lambda item: issubclass(item[0], Folder)
+ )))
+
+ @inlineCallbacks
+ def cmd_ls(self, tokens):
+ """
+ List folder contents.
+
+ usage: ls [folder]
+ """
+ targets = (yield self._getTargets(tokens))
+ multiple = len(targets) > 0
+
+ for target in targets:
+ rows = (yield target.list())
+ #
+ # FIXME: this can be ugly if, for example, there are zillions
+ # of entries to output. Paging would be good.
+ #
+ table = Table()
+ for row in rows:
+ table.addRow((self._listEntryToString(row),) + tuple(row[2:]))
+
+ if multiple:
+ self.terminal.write("%s:\n" % (target,))
+ if table.rows:
+ table.printTable(self.terminal)
+ self.terminal.nextLine()
+
+ complete_ls = _complete_files
+
+ @inlineCallbacks
+ def cmd_info(self, tokens):
+ """
+ Print information about a folder.
+
+ usage: info [folder]
+ """
+ target = (yield self._getTarget(tokens))
+
+ if tokens:
+ raise UnknownArguments(tokens)
+
+ description = (yield target.describe())
+ self.terminal.write(description)
+ self.terminal.nextLine()
+
+ complete_ls = _complete_files
+
+ @inlineCallbacks
+ def cmd_cat(self, tokens):
+ """
+ Show contents of target.
+
+ usage: cat target [target ...]
+ """
+ for target in (yield self._getTargets(tokens)):
+ if hasattr(target, "text"):
+ text = (yield target.text())
+ self.terminal.write(text)
+
+ complete_ls = _complete_files
+
+ def cmd_exit(self, tokens):
+ """
+ Exit the shell.
+
+ usage: exit
+ """
+ self.exit()
+
+ def cmd_python(self, tokens):
+ """
+ Switch to a python prompt.
+
+ usage: python
+ """
+ # Crazy idea #19568: switch to an interactive python prompt
+ # with self exposed in globals.
+ raise NotImplementedError()
+
+ cmd_python.hidden = "Not implemented"
Copied: CalendarServer/trunk/calendarserver/tools/shell/terminal.py (from rev 8540, CalendarServer/trunk/calendarserver/tools/shell.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tools/shell/terminal.py (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/shell/terminal.py 2012-01-16 21:47:36 UTC (rev 8542)
@@ -0,0 +1,343 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2011-2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Interactive shell for terminals.
+"""
+
+import string
+import os
+import sys
+import tty
+import termios
+from shlex import shlex
+
+from twisted.python import log
+from twisted.python.text import wordWrap
+from twisted.python.usage import Options, UsageError
+from twisted.internet.defer import Deferred
+from twisted.internet.defer import inlineCallbacks
+from twisted.internet.stdio import StandardIO
+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
+
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+
+from calendarserver.tools.cmdline import utilityMain
+from calendarserver.tools.util import getDirectory
+from calendarserver.tools.shell.cmd import Commands, UsageError as CommandUsageError
+from calendarserver.tools.shell.vfs import Folder, RootFolder
+
+
+def usage(e=None):
+ if e:
+ print e
+ print ""
+ try:
+ ShellOptions().opt_help()
+ except SystemExit:
+ pass
+ if e:
+ sys.exit(64)
+ else:
+ sys.exit(0)
+
+
+class ShellOptions(Options):
+ """
+ Command line options for "calendarserver_shell".
+ """
+ synopsis = "\n".join(
+ wordWrap(
+ """
+ Usage: calendarserver_shell [options]\n
+ """ + __doc__,
+ int(os.environ.get("COLUMNS", "80"))
+ )
+ )
+
+ optParameters = [
+ ["config", "f", DEFAULT_CONFIG_FILE, "Specify caldavd.plist configuration path."],
+ ]
+
+ def __init__(self):
+ super(ShellOptions, self).__init__()
+
+
+class ShellService(Service, object):
+ def __init__(self, store, directory, options, reactor, config):
+ super(ShellService, self).__init__()
+ self.store = store
+ self.directory = directory
+ self.options = options
+ self.reactor = reactor
+ self.config = config
+ self.terminalFD = None
+ self.protocol = None
+
+ def startService(self):
+ """
+ Start the service.
+ """
+ # For debugging
+ if True:
+ from twisted.python.log import startLogging
+ f = open("/tmp/shell.log", "w")
+ startLogging(f)
+
+ super(ShellService, self).startService()
+
+ # 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 ShellProtocol(ReceiveLineProtocol, Commands):
+ """
+ Data store shell protocol.
+ """
+
+ # FIXME:
+ # * Received lines are being echoed; find out why and stop it.
+ # * Backspace transposes characters in the terminal.
+
+ ps = ("ds% ", "... ")
+
+ emulation_modes = ("emacs", "none")
+
+ def __init__(self, service):
+ ReceiveLineProtocol.__init__(self)
+ self.service = service
+ self.wd = RootFolder(service)
+ self.inputLines = []
+ self.activeCommand = None
+ self.emulate = "emacs"
+
+ #
+ # Input handling
+ #
+
+ def connectionMade(self):
+ ReceiveLineProtocol.connectionMade(self)
+
+ self.keyHandlers['\x03'] = self.handle_INT # Control-C
+ self.keyHandlers['\x04'] = self.handle_EOF # Control-D
+ self.keyHandlers['\x1c'] = self.handle_QUIT # Control-\
+ self.keyHandlers['\x0c'] = self.handle_FF # Control-L
+ #self.keyHandlers['\t' ] = self.handle_TAB # Tab
+
+ if self.emulate == "emacs":
+ # EMACS key bindinds
+ self.keyHandlers['\x10'] = self.handle_UP # Control-P
+ self.keyHandlers['\x0e'] = self.handle_DOWN # Control-N
+ self.keyHandlers['\x02'] = self.handle_LEFT # Control-B
+ self.keyHandlers['\x06'] = self.handle_RIGHT # Control-F
+ self.keyHandlers['\x01'] = self.handle_HOME # Control-A
+ self.keyHandlers['\x05'] = self.handle_END # Control-E
+
+ 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:
+ if self.emulate == "emacs":
+ self.handle_DELETE()
+ else:
+ 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()
+
+ @inlineCallbacks
+ def handle_TAB(self):
+ # Tokenize the text before the cursor
+ tokens = self.tokenize("".join(self.lineBuffer[:self.lineBufferIndex]))
+
+ if tokens:
+ if len(tokens) == 1 and self.lineBuffer[-1] in string.whitespace:
+ word = ""
+ else:
+ word = tokens[-1]
+ cmd = tokens.pop(0)
+ else:
+ word = cmd = ""
+
+ if cmd and (tokens or word == ""):
+ # Completing arguments
+
+ m = getattr(self, "complete_%s" % (cmd,), None)
+ if not m:
+ return
+ completions = tuple((yield m(tokens)))
+
+ log.msg("COMPLETIONS: %r" % (completions,))
+ else:
+ # Completing command name
+ completions = tuple(self._complete_commands(cmd))
+
+ if len(completions) == 1:
+ for completion in completions:
+ break
+ for c in completion:
+ self.characterReceived(c, True)
+ self.characterReceived(" ", False)
+ else:
+ self.terminal.nextLine()
+ for completion in completions:
+ # FIXME Emitting these in columns would be swell
+ self.terminal.write("%s%s\n" % (word, completion))
+ self.drawInputLine()
+
+ #
+ # Utilities
+ #
+
+ def exit(self):
+ self.terminal.loseConnection()
+ self.service.reactor.stop()
+
+ @staticmethod
+ def _listEntryToString(entry):
+ klass = entry[0]
+ name = entry[1]
+
+ if issubclass(klass, Folder):
+ return "%s/" % (name,)
+ else:
+ return name
+
+ #
+ # Command dispatch
+ #
+
+ def lineReceived(self, line):
+ if self.activeCommand is not None:
+ self.inputLines.append(line)
+ return
+
+ tokens = self.tokenize(line)
+
+ if tokens:
+ cmd = tokens.pop(0)
+ #print "Arguments: %r" % (tokens,)
+
+ m = getattr(self, "cmd_%s" % (cmd,), None)
+ if m:
+ def handleUsageError(f):
+ f.trap(CommandUsageError)
+ self.terminal.write("%s\n" % (f.value,))
+
+ def handleException(f):
+ self.terminal.write("Error: %s\n" % (f.value,))
+ if not f.check(NotImplementedError, NotFoundError):
+ log.msg("-"*80 + "\n")
+ log.msg(f.getTraceback())
+ log.msg("-"*80 + "\n")
+
+ def next(_):
+ self.activeCommand = None
+ self.drawInputLine()
+ if self.inputLines:
+ line = self.inputLines.pop(0)
+ self.lineReceived(line)
+
+ d = self.activeCommand = Deferred()
+ 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(handleUsageError)
+ d.addErrback(handleException)
+ d.addCallback(next)
+ else:
+ self.terminal.write("Unknown command: %s\n" % (cmd,))
+ self.drawInputLine()
+ else:
+ self.drawInputLine()
+
+ @staticmethod
+ def tokenize(line):
+ lexer = shlex(line)
+ lexer.whitespace_split = True
+
+ tokens = []
+ while True:
+ token = lexer.get_token()
+ if not token:
+ break
+ tokens.append(token)
+
+ return tokens
+
+
+def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
+ if reactor is None:
+ from twisted.internet import reactor
+
+ options = ShellOptions()
+ try:
+ options.parseOptions(argv[1:])
+ except UsageError, e:
+ usage(e)
+
+ def makeService(store):
+ from twistedcaldav.config import config
+ directory = getDirectory()
+ return ShellService(store, directory, options, reactor, config)
+
+ print "Initializing shell..."
+
+ utilityMain(options["config"], makeService, reactor)
Copied: CalendarServer/trunk/calendarserver/tools/shell/vfs.py (from rev 8540, CalendarServer/trunk/calendarserver/tools/shell.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tools/shell/vfs.py (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/shell/vfs.py 2012-01-16 21:47:36 UTC (rev 8542)
@@ -0,0 +1,477 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2011-2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Virtual file system for data store objects.
+"""
+
+from cStringIO import StringIO
+
+from twisted.python import log
+from twisted.internet.defer import succeed
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from txdav.common.icommondatastore import NotFoundError
+
+from calendarserver.tools.tables import Table
+
+
+class File(object):
+ """
+ Object in virtual data hierarchy.
+ """
+ def __init__(self, service, path):
+ assert type(path) is tuple
+
+ self.service = service
+ self.path = path
+
+ def __str__(self):
+ return "/" + "/".join(self.path)
+
+ def describe(self):
+ return succeed("%s (%s)" % (self, self.__class__.__name__))
+
+ def list(self):
+ return succeed((File, str(self)))
+
+
+class Folder(File):
+ """
+ Location in virtual data hierarchy.
+ """
+ def __init__(self, service, path):
+ File.__init__(self, service, path)
+
+ self._children = {}
+ self._childClasses = {}
+
+ def __str__(self):
+ if self.path:
+ return "/" + "/".join(self.path) + "/"
+ else:
+ return "/"
+
+ @inlineCallbacks
+ def locate(self, path):
+ if not path:
+ returnValue(RootFolder(self.service))
+
+ name = path[0]
+ if name:
+ target = (yield self.child(name))
+ if len(path) > 1:
+ target = (yield target.locate(path[1:]))
+ else:
+ target = (yield RootFolder(self.service).locate(path[1:]))
+
+ returnValue(target)
+
+ @inlineCallbacks
+ def child(self, name):
+ # FIXME: Move this logic to locate()
+ #if not name:
+ # return succeed(self)
+ #if name == ".":
+ # return succeed(self)
+ #if name == "..":
+ # path = self.path[:-1]
+ # if not path:
+ # path = "/"
+ # return RootFolder(self.service).locate(path)
+
+ if name in self._children:
+ returnValue(self._children[name])
+
+ if name in self._childClasses:
+ child = (yield self._childClasses[name](self.service, self.path + (name,)))
+ self._children[name] = child
+ returnValue(child)
+
+ raise NotFoundError("Folder %r has no child %r" % (str(self), name))
+
+ def list(self):
+ result = set()
+ for name in self._children:
+ result.add((self._children[name].__class__, name))
+ for name in self._childClasses:
+ result.add((self._childClasses[name], name))
+ return succeed(result)
+
+
+class RootFolder(Folder):
+ """
+ Root of virtual data hierarchy.
+
+ Hierarchy:
+ / RootFolder
+ uids/ UIDsFolder
+ <uid>/ PrincipalHomeFolder
+ calendars/ CalendarHomeFolder
+ <name>/ CalendarFolder
+ <uid> CalendarObject
+ addressbooks/ AddressBookHomeFolder
+ <name>/ AddressBookFolder
+ <uid> AddressBookObject
+ users/ UsersFolder
+ <name>/ PrincipalHomeFolder
+ ...
+ locations/ LocationsFolder
+ <name>/ PrincipalHomeFolder
+ ...
+ resources/ ResourcesFolder
+ <name>/ PrincipalHomeFolder
+ ...
+ """
+ def __init__(self, service):
+ Folder.__init__(self, service, ())
+
+ self._childClasses["uids" ] = UIDsFolder
+ self._childClasses["users" ] = UsersFolder
+ self._childClasses["locations"] = LocationsFolder
+ self._childClasses["resources"] = ResourcesFolder
+
+
+class UIDsFolder(Folder):
+ """
+ Folder containing all principals by UID.
+ """
+ def child(self, name):
+ return PrincipalHomeFolder(self.service, self.path + (name,), name)
+
+ @inlineCallbacks
+ def list(self):
+ result = set()
+
+ # FIXME: This should be the merged total of calendar homes and address book homes.
+ # FIXME: Merge in directory UIDs also?
+ # FIXME: Add directory info (eg. name) to listing
+
+ for txn, home in (yield self.service.store.eachCalendarHome()):
+ result.add((PrincipalHomeFolder, home.uid()))
+
+ returnValue(result)
+
+
+
+class RecordFolder(Folder):
+ def _recordForName(self, name):
+ recordTypeAttr = "recordType_" + self.recordType
+ recordType = getattr(self.service.directory, recordTypeAttr)
+
+ log.msg("Record type = %s" % (recordType,))
+
+ return self.service.directory.recordWithShortName(recordType, name)
+
+ def child(self, name):
+ record = self._recordForName(name)
+ log.msg("Record = %s" % (record,))
+ return PrincipalHomeFolder(
+ self.service,
+ self.path + (record.uid,),
+ record.uid,
+ record=record
+ )
+
+ @inlineCallbacks
+ def list(self):
+ result = set()
+
+ # FIXME ...?
+
+ returnValue(result)
+
+
+class UsersFolder(RecordFolder):
+ """
+ Folder containing all user principals by name.
+ """
+ recordType = "users"
+
+
+class LocationsFolder(RecordFolder):
+ """
+ Folder containing all location principals by name.
+ """
+ recordType = "locations"
+
+
+class ResourcesFolder(RecordFolder):
+ """
+ Folder containing all resource principals by name.
+ """
+ recordType = "resources"
+
+
+class PrincipalHomeFolder(Folder):
+ """
+ Folder containing everything related to a given principal.
+ """
+ def __init__(self, service, path, uid, record=None):
+ Folder.__init__(self, service, path)
+
+ if record is not None:
+ assert uid == record.uid
+
+ self.uid = uid
+ self.record = record
+
+ @inlineCallbacks
+ def _initChildren(self):
+ if not hasattr(self, "_didInitChildren"):
+ txn = self.service.store.newTransaction()
+
+ if (
+ self.record is not None and
+ self.service.config.EnableCalDAV and
+ self.record.enabledForCalendaring
+ ):
+ create = True
+ else:
+ create = False
+
+ home = (yield txn.calendarHomeWithUID(self.uid, create=create))
+ if home:
+ self._children["calendars"] = CalendarHomeFolder(
+ self.service,
+ self.path + ("calendars",),
+ home,
+ )
+
+ if (
+ self.record is not None and
+ self.service.config.EnableCardDAV and
+ self.record.enabledForAddressBooks
+ ):
+ create = True
+ else:
+ create = False
+
+ home = (yield txn.addressbookHomeWithUID(self.uid))
+ if home:
+ self._children["addressbooks"] = AddressBookHomeFolder(
+ self.service,
+ self.path + ("addressbooks",),
+ home,
+ )
+
+ self._didInitChildren = True
+
+ def _needsChildren(m):
+ def decorate(self, *args, **kwargs):
+ d = self._initChildren()
+ d.addCallback(lambda _: m(self, *args, **kwargs))
+ return d
+ return decorate
+
+ @_needsChildren
+ def child(self, name):
+ return Folder.child(self, name)
+
+ @_needsChildren
+ def list(self):
+ return Folder.list(self)
+
+
+class CalendarHomeFolder(Folder):
+ """
+ Calendar home folder.
+ """
+ def __init__(self, service, path, home):
+ Folder.__init__(self, service, path)
+
+ self.home = home
+
+ @inlineCallbacks
+ def child(self, name):
+ calendar = (yield self.home.calendarWithName(name))
+ if calendar:
+ returnValue(CalendarFolder(self.service, self.path + (name,), calendar))
+ else:
+ raise NotFoundError("Calendar home %r has no calendar %r" % (self, name))
+
+ @inlineCallbacks
+ def list(self):
+ calendars = (yield self.home.calendars())
+ returnValue(((CalendarFolder, c.name()) for c in calendars))
+
+ @inlineCallbacks
+ def describe(self):
+ # created() -> int
+ # modified() -> int
+ # properties -> IPropertyStore
+
+ uid = (yield self.home.uid())
+ created = (yield self.home.created())
+ modified = (yield self.home.modified())
+ quotaUsed = (yield self.home.quotaUsedBytes())
+ quotaAllowed = (yield self.home.quotaAllowedBytes())
+ properties = (yield self.home.properties())
+
+ result = []
+ result.append("Calendar home for UID: %s" % (uid,))
+
+ #
+ # Attributes
+ #
+ rows = []
+ if created is not None:
+ # FIXME: convert to formatted string
+ rows.append(("Created", str(created)))
+ if modified is not None:
+ # FIXME: convert to formatted string
+ rows.append(("Last modified", str(modified)))
+ if quotaUsed is not None:
+ rows.append((
+ "Quota",
+ "%s of %s (%.2s%%)"
+ % (quotaUsed, quotaAllowed, quotaUsed / quotaAllowed)
+ ))
+
+ if len(rows):
+ result.append("\nAttributes:")
+ result.append(tableString(rows, header=("Name", "Value")))
+
+ #
+ # Properties
+ #
+ if properties:
+ result.append("\Properties:")
+ result.append(tableString(
+ ((name, properties[name]) for name in sorted(properties)),
+ header=("Name", "Value")
+ ))
+
+ returnValue("\n".join(result))
+
+
+class CalendarFolder(Folder):
+ """
+ Calendar.
+ """
+ def __init__(self, service, path, calendar):
+ Folder.__init__(self, service, path)
+
+ self.calendar = calendar
+
+ @inlineCallbacks
+ def _childWithObject(self, object):
+ name = (yield object.uid())
+ returnValue(CalendarObject(self.service, self.path + (name,), object))
+
+ @inlineCallbacks
+ def child(self, name):
+ object = (yield self.calendar.calendarObjectWithUID(name))
+
+ if not object:
+ raise NotFoundError("Calendar %r has no object %r" % (str(self), name))
+
+ child = (yield self._childWithObject(object))
+ returnValue(child)
+
+ @inlineCallbacks
+ def list(self):
+ result = []
+
+ for object in (yield self.calendar.calendarObjects()):
+ object = (yield self._childWithObject(object))
+ items = (yield object.list())
+ result.append(items[0])
+
+ returnValue(result)
+
+
+class CalendarObject(File):
+ """
+ Calendar object.
+ """
+ def __init__(self, service, path, calendarObject):
+ File.__init__(self, service, path)
+
+ self.object = calendarObject
+
+ @inlineCallbacks
+ def lookup(self):
+ if not hasattr(self, "component"):
+ component = (yield self.object.component())
+ mainComponent = component.mainComponent()
+
+ self.componentType = mainComponent.name()
+ self.uid = mainComponent.propertyValue("UID")
+ self.summary = mainComponent.propertyValue("SUMMARY")
+ self.mainComponent = mainComponent
+ self.component = component
+
+ @inlineCallbacks
+ def list(self):
+ (yield self.lookup())
+ returnValue(((CalendarObject, self.uid, self.componentType, self.summary),))
+
+ @inlineCallbacks
+ def text(self):
+ (yield self.lookup())
+ returnValue(str(self.component))
+
+ @inlineCallbacks
+ def describe(self):
+ (yield self.lookup())
+
+ rows = []
+
+ rows.append(("UID", self.uid))
+ rows.append(("Type", self.componentType))
+ rows.append(("Summary", self.summary))
+
+ organizer = self.mainComponent.getProperty("ORGANIZER")
+ if organizer:
+ organizerName = organizer.parameterValue("CN")
+ organizerEmail = organizer.parameterValue("EMAIL")
+
+ name = " (%s)" % (organizerName ,) if organizerName else ""
+ email = " <%s>" % (organizerEmail,) if organizerEmail else ""
+
+ rows.append(("Organizer", "%s%s%s" % (organizer.value(), name, email)))
+
+ #
+ # Attachments
+ #
+# attachments = (yield self.object.attachments())
+# log.msg("%r" % (attachments,))
+# for attachment in attachments:
+# log.msg("%r" % (attachment,))
+# # FIXME: Not getting any results here
+
+
+ returnValue("Calendar object:\n%s" % tableString(rows))
+
+class AddressBookHomeFolder(Folder):
+ """
+ Address book home folder.
+ """
+ # FIXME
+
+
+def tableString(rows, header=None):
+ table = Table()
+ if header:
+ table.addHeader(header)
+ for row in rows:
+ table.addRow(row)
+
+ output = StringIO()
+ table.printTable(os=output)
+ return output.getvalue()
Deleted: CalendarServer/trunk/calendarserver/tools/shell.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/shell.py 2012-01-16 21:15:37 UTC (rev 8541)
+++ CalendarServer/trunk/calendarserver/tools/shell.py 2012-01-16 21:47:36 UTC (rev 8542)
@@ -1,1091 +0,0 @@
-#!/usr/bin/env python
-##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-Interactive shell for navigating the data store.
-"""
-
-import string
-import os
-import sys
-import tty
-import termios
-from shlex import shlex
-from cStringIO import StringIO
-
-from twisted.python import log
-from twisted.python.text import wordWrap
-from twisted.python.usage import Options, UsageError
-from twisted.internet.defer import succeed, Deferred
-from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.internet.stdio import StandardIO
-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
-
-from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
-
-from calendarserver.tools.cmdline import utilityMain
-from calendarserver.tools.util import getDirectory
-from calendarserver.tools.tables import Table
-
-
-def usage(e=None):
- if e:
- print e
- print ""
- try:
- ShellOptions().opt_help()
- except SystemExit:
- pass
- if e:
- sys.exit(64)
- else:
- sys.exit(0)
-
-
-class ShellOptions(Options):
- """
- Command line options for "calendarserver_shell".
- """
- synopsis = "\n".join(
- wordWrap(
- """
- Usage: calendarserver_shell [options]\n
- """ + __doc__,
- int(os.environ.get("COLUMNS", "80"))
- )
- )
-
- optParameters = [
- ["config", "f", DEFAULT_CONFIG_FILE, "Specify caldavd.plist configuration path."],
- ]
-
- def __init__(self):
- super(ShellOptions, self).__init__()
-
-
-class ShellService(Service, object):
- def __init__(self, store, directory, options, reactor, config):
- super(ShellService, self).__init__()
- self.store = store
- self.directory = directory
- self.options = options
- self.reactor = reactor
- self.config = config
- self.terminalFD = None
- self.protocol = None
-
- def startService(self):
- """
- Start the service.
- """
- # For debugging
- #from twisted.python.log import startLogging
- #f = open("/tmp/shell.log", "w")
- #startLogging(f)
-
- super(ShellService, self).startService()
-
- # 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 UsageError (Exception):
- """
- Usage error.
- """
-
-class UnknownArguments (UsageError):
- """
- Unknown arguments.
- """
- def __init__(self, arguments):
- Exception.__init__(self, "Unknown arguments: %s" % (arguments,))
- self.arguments = arguments
-
-
-class ShellProtocol(ReceiveLineProtocol):
- """
- Data store shell protocol.
- """
-
- # FIXME:
- # * Received lines are being echoed; find out why and stop it.
- # * Backspace transposes characters in the terminal.
-
- ps = ("ds% ", "... ")
-
- emulation_modes = ("emacs", "none")
-
- def __init__(self, service):
- ReceiveLineProtocol.__init__(self)
- self.service = service
- self.wd = RootFolder(service)
- self.inputLines = []
- self.activeCommand = None
- self.emulate = "emacs"
-
- #
- # Input handling
- #
-
- def connectionMade(self):
- ReceiveLineProtocol.connectionMade(self)
-
- self.keyHandlers['\x03'] = self.handle_INT # Control-C
- self.keyHandlers['\x04'] = self.handle_EOF # Control-D
- self.keyHandlers['\x1c'] = self.handle_QUIT # Control-\
- self.keyHandlers['\x0c'] = self.handle_FF # Control-L
- #self.keyHandlers['\t' ] = self.handle_TAB # Tab
-
- if self.emulate == "emacs":
- # EMACS key bindinds
- self.keyHandlers['\x10'] = self.handle_UP # Control-P
- self.keyHandlers['\x0e'] = self.handle_DOWN # Control-N
- self.keyHandlers['\x02'] = self.handle_LEFT # Control-B
- self.keyHandlers['\x06'] = self.handle_RIGHT # Control-F
- self.keyHandlers['\x01'] = self.handle_HOME # Control-A
- self.keyHandlers['\x05'] = self.handle_END # Control-E
-
- 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:
- if self.emulate == "emacs":
- self.handle_DELETE()
- else:
- 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()
-
- @inlineCallbacks
- def handle_TAB(self):
- # Tokenize the text before the cursor
- tokens = self.tokenize("".join(self.lineBuffer[:self.lineBufferIndex]))
-
- if tokens:
- if len(tokens) == 1 and self.lineBuffer[-1] in string.whitespace:
- word = ""
- else:
- word = tokens[-1]
- cmd = tokens.pop(0)
- else:
- word = cmd = ""
-
- if cmd and (tokens or word == ""):
- # Completing arguments
-
- m = getattr(self, "complete_%s" % (cmd,), None)
- if not m:
- return
- completions = tuple((yield m(tokens)))
-
- log.msg("COMPLETIONS: %r" % (completions,))
- else:
- # Completing command name
- completions = tuple(self._complete_commands(cmd))
-
- if len(completions) == 1:
- for completion in completions:
- break
- for c in completion:
- self.characterReceived(c, True)
- self.characterReceived(" ", False)
- else:
- self.terminal.nextLine()
- for completion in completions:
- # FIXME Emitting these in columns would be swell
- self.terminal.write("%s%s\n" % (word, completion))
- self.drawInputLine()
-
- #
- # Utilities
- #
-
- def exit(self):
- self.terminal.loseConnection()
- self.service.reactor.stop()
-
- @staticmethod
- def _listEntryToString(entry):
- klass = entry[0]
- name = entry[1]
-
- if issubclass(klass, Folder):
- return "%s/" % (name,)
- else:
- return name
-
- #
- # Command dispatch
- #
-
- def lineReceived(self, line):
- if self.activeCommand is not None:
- self.inputLines.append(line)
- return
-
- tokens = self.tokenize(line)
-
- if tokens:
- cmd = tokens.pop(0)
- #print "Arguments: %r" % (tokens,)
-
- m = getattr(self, "cmd_%s" % (cmd,), None)
- if m:
- def handleUsageError(f):
- f.trap(UsageError)
- self.terminal.write("%s\n" % (f.value,))
-
- def handleException(f):
- self.terminal.write("Error: %s\n" % (f.value,))
- if not f.check(NotImplementedError, NotFoundError):
- log.msg("-"*80 + "\n")
- log.msg(f.getTraceback())
- log.msg("-"*80 + "\n")
-
- def next(_):
- self.activeCommand = None
- self.drawInputLine()
- if self.inputLines:
- line = self.inputLines.pop(0)
- self.lineReceived(line)
-
- d = self.activeCommand = Deferred()
- 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(handleUsageError)
- d.addErrback(handleException)
- d.addCallback(next)
- else:
- self.terminal.write("Unknown command: %s\n" % (cmd,))
- self.drawInputLine()
- else:
- self.drawInputLine()
-
- @staticmethod
- def tokenize(line):
- lexer = shlex(line)
- lexer.whitespace_split = True
-
- tokens = []
- while True:
- token = lexer.get_token()
- if not token:
- break
- tokens.append(token)
-
- return tokens
-
- def _getTarget(self, tokens):
- if tokens:
- return self.wd.locate(tokens.pop(0).split("/"))
- else:
- return succeed(self.wd)
-
- @inlineCallbacks
- def _getTargets(self, tokens):
- if tokens:
- result = []
- for token in tokens:
- result.append((yield self.wd.locate(token.split("/"))))
- returnValue(result)
- else:
- returnValue((self.wd,))
-
- def commands(self):
- for attr in dir(self):
- if attr.startswith("cmd_"):
- m = getattr(self, attr)
- if not hasattr(m, "hidden"):
- yield (attr[4:], m)
-
- @staticmethod
- def _complete(word, items):
- for item in items:
- if item.startswith(word):
- yield item[len(word):]
-
- def _complete_commands(self, word):
- return self._complete(word, (name for name, method in self.commands()))
-
- @inlineCallbacks
- def _complete_files(self, tokens, filter=None):
- if filter is None:
- filter = lambda items: True
-
- files = (
- self._listEntryToString(item)
- for item in (yield self.wd.list())
- if filter(item)
- )
-
- if len(tokens) == 0:
- returnValue(files)
- elif len(tokens) == 1:
- returnValue(self._complete(tokens[0], files))
- else:
- returnValue(())
-
- #
- # Commands
- #
-
- def cmd_help(self, tokens):
- """
- Show help.
-
- usage: help [command]
- """
- if tokens:
- command = tokens.pop(0)
- else:
- command = None
-
- if tokens:
- raise UnknownArguments(tokens)
-
- if command:
- m = getattr(self, "cmd_%s" % (command,), None)
- if m:
- doc = m.__doc__.split("\n")
-
- # Throw out first and last line if it's empty
- if doc:
- if not doc[0].strip():
- doc.pop(0)
- if not doc[-1].strip():
- doc.pop()
-
- if doc:
- # Get length of indentation
- i = len(doc[0]) - len(doc[0].lstrip())
-
- for line in doc:
- self.terminal.write(line[i:])
- self.terminal.nextLine()
-
- else:
- self.terminal.write("(No documentation available for %s)\n" % (command,))
- else:
- raise NotFoundError("Unknown command: %s" % (command,))
- else:
- self.terminal.write("Available commands:\n")
-
- result = []
- max_len = 0
-
- for name, m in self.commands():
- for line in m.__doc__.split("\n"):
- line = line.strip()
- if line:
- doc = line
- break
- else:
- doc = "(no info available)"
-
- if len(name) > max_len:
- max_len = len(name)
-
- result.append((name, doc))
-
- format = " %%%ds - %%s\n" % (max_len,)
-
- for info in sorted(result):
- self.terminal.write(format % (info))
-
- def complete_help(self, tokens):
- if len(tokens) == 0:
- return (name for name, method in self.commands())
- elif len(tokens) == 1:
- return self._complete_commands(tokens[0])
- else:
- return ()
-
- def cmd_emulate(self, tokens):
- """
- Emulate editor behavior.
- The only correct argument is: emacs
- Other choices include: none
-
- usage: emulate editor
- """
- if not tokens:
- if self.emulate:
- self.terminal.write("Emulating %s.\n" % (self.emulate,))
- else:
- self.terminal.write("Emulation disabled.\n")
- return
-
- editor = tokens.pop(0).lower()
-
- if tokens:
- raise UnknownArguments(tokens)
-
- if editor == "none":
- self.terminal.write("Disabling emulation.\n")
- editor = None
- elif editor in self.emulation_modes:
- self.terminal.write("Emulating %s.\n" % (editor,))
- else:
- raise UsageError("Unknown editor: %s" % (editor,))
-
- self.emulate = editor
-
- # FIXME: Need to update key registrations
-
- cmd_emulate.hidden = "Incomplete"
-
- def complete_emulate(self, tokens):
- if len(tokens) == 0:
- return self.emulation_modes
- elif len(tokens) == 1:
- return self._complete(tokens[0], self.emulation_modes)
- else:
- return ()
-
- def cmd_pwd(self, tokens):
- """
- Print working folder.
-
- usage: pwd
- """
- if tokens:
- raise UnknownArguments(tokens)
-
- self.terminal.write("%s\n" % (self.wd,))
-
- @inlineCallbacks
- def cmd_cd(self, tokens):
- """
- Change working folder.
-
- usage: cd [folder]
- """
- if not tokens:
- return
-
- dirname = tokens.pop(0)
-
- if tokens:
- raise UnknownArguments(tokens)
-
- wd = (yield self.wd.locate(dirname.split("/")))
-
- if not isinstance(wd, Folder):
- raise NotFoundError("Not a folder: %s" % (wd,))
-
- log.msg("wd -> %s" % (wd,))
- self.wd = wd
-
- @inlineCallbacks
- def complete_cd(self, tokens):
- returnValue((yield self._complete_files(
- tokens,
- filter = lambda item: issubclass(item[0], Folder)
- )))
-
- @inlineCallbacks
- def cmd_ls(self, tokens):
- """
- List folder contents.
-
- usage: ls [folder]
- """
- targets = (yield self._getTargets(tokens))
- multiple = len(targets) > 0
-
- for target in targets:
- rows = (yield target.list())
- #
- # FIXME: this can be ugly if, for example, there are zillions
- # of entries to output. Paging would be good.
- #
- table = Table()
- for row in rows:
- table.addRow((self._listEntryToString(row),) + tuple(row[2:]))
-
- if multiple:
- self.terminal.write("%s:\n" % (target,))
- if table.rows:
- table.printTable(self.terminal)
- self.terminal.nextLine()
-
- complete_ls = _complete_files
-
- @inlineCallbacks
- def cmd_info(self, tokens):
- """
- Print information about a folder.
-
- usage: info [folder]
- """
- target = (yield self._getTarget(tokens))
-
- if tokens:
- raise UnknownArguments(tokens)
-
- description = (yield target.describe())
- self.terminal.write(description)
- self.terminal.nextLine()
-
- complete_ls = _complete_files
-
- @inlineCallbacks
- def cmd_cat(self, tokens):
- """
- Show contents of target.
-
- usage: cat target [target ...]
- """
- for target in (yield self._getTargets(tokens)):
- if hasattr(target, "text"):
- text = (yield target.text())
- self.terminal.write(text)
-
- complete_ls = _complete_files
-
- def cmd_exit(self, tokens):
- """
- Exit the shell.
-
- usage: exit
- """
- self.exit()
-
- def cmd_python(self, tokens):
- """
- Switch to a python prompt.
-
- usage: python
- """
- # Crazy idea #19568: switch to an interactive python prompt
- # with self exposed in globals.
- raise NotImplementedError()
-
- cmd_python.hidden = "Not implemented"
-
-
-class File(object):
- """
- Object in virtual data hierarchy.
- """
- def __init__(self, service, path):
- assert type(path) is tuple
-
- self.service = service
- self.path = path
-
- def __str__(self):
- return "/" + "/".join(self.path)
-
- def describe(self):
- return succeed("%s (%s)" % (self, self.__class__.__name__))
-
- def list(self):
- return succeed((File, str(self)))
-
-
-class Folder(File):
- """
- Location in virtual data hierarchy.
- """
- def __init__(self, service, path):
- File.__init__(self, service, path)
-
- self._children = {}
- self._childClasses = {}
-
- def __str__(self):
- if self.path:
- return "/" + "/".join(self.path) + "/"
- else:
- return "/"
-
- @inlineCallbacks
- def locate(self, path):
- if not path:
- returnValue(RootFolder(self.service))
-
- name = path[0]
- if name:
- target = (yield self.child(name))
- if len(path) > 1:
- target = (yield target.locate(path[1:]))
- else:
- target = (yield RootFolder(self.service).locate(path[1:]))
-
- returnValue(target)
-
- @inlineCallbacks
- def child(self, name):
- # FIXME: Move this logic to locate()
- #if not name:
- # return succeed(self)
- #if name == ".":
- # return succeed(self)
- #if name == "..":
- # path = self.path[:-1]
- # if not path:
- # path = "/"
- # return RootFolder(self.service).locate(path)
-
- if name in self._children:
- returnValue(self._children[name])
-
- if name in self._childClasses:
- child = (yield self._childClasses[name](self.service, self.path + (name,)))
- self._children[name] = child
- returnValue(child)
-
- raise NotFoundError("Folder %r has no child %r" % (str(self), name))
-
- def list(self):
- result = set()
- for name in self._children:
- result.add((self._children[name].__class__, name))
- for name in self._childClasses:
- result.add((self._childClasses[name], name))
- return succeed(result)
-
-
-class RootFolder(Folder):
- """
- Root of virtual data hierarchy.
-
- Hierarchy:
- / RootFolder
- uids/ UIDsFolder
- <uid>/ PrincipalHomeFolder
- calendars/ CalendarHomeFolder
- <name>/ CalendarFolder
- <uid> CalendarObject
- addressbooks/ AddressBookHomeFolder
- <name>/ AddressBookFolder
- <uid> AddressBookObject
- users/ UsersFolder
- <name>/ PrincipalHomeFolder
- ...
- locations/ LocationsFolder
- <name>/ PrincipalHomeFolder
- ...
- resources/ ResourcesFolder
- <name>/ PrincipalHomeFolder
- ...
- """
- def __init__(self, service):
- Folder.__init__(self, service, ())
-
- self._childClasses["uids" ] = UIDsFolder
- self._childClasses["users" ] = UsersFolder
- self._childClasses["locations"] = LocationsFolder
- self._childClasses["resources"] = ResourcesFolder
-
-
-class UIDsFolder(Folder):
- """
- Folder containing all principals by UID.
- """
- def child(self, name):
- return PrincipalHomeFolder(self.service, self.path + (name,), name)
-
- @inlineCallbacks
- def list(self):
- result = set()
-
- # FIXME: This should be the merged total of calendar homes and address book homes.
- # FIXME: Merge in directory UIDs also?
- # FIXME: Add directory info (eg. name) to listing
-
- for txn, home in (yield self.service.store.eachCalendarHome()):
- result.add((PrincipalHomeFolder, home.uid()))
-
- returnValue(result)
-
-
-
-class RecordFolder(Folder):
- def _recordForName(self, name):
- recordTypeAttr = "recordType_" + self.recordType
- recordType = getattr(self.service.directory, recordTypeAttr)
-
- log.msg("Record type = %s" % (recordType,))
-
- return self.service.directory.recordWithShortName(recordType, name)
-
- def child(self, name):
- record = self._recordForName(name)
- log.msg("Record = %s" % (record,))
- return PrincipalHomeFolder(
- self.service,
- self.path + (record.uid,),
- record.uid,
- record=record
- )
-
- @inlineCallbacks
- def list(self):
- result = set()
-
- # FIXME ...?
-
- returnValue(result)
-
-
-class UsersFolder(RecordFolder):
- """
- Folder containing all user principals by name.
- """
- recordType = "users"
-
-
-class LocationsFolder(RecordFolder):
- """
- Folder containing all location principals by name.
- """
- recordType = "locations"
-
-
-class ResourcesFolder(RecordFolder):
- """
- Folder containing all resource principals by name.
- """
- recordType = "resources"
-
-
-class PrincipalHomeFolder(Folder):
- """
- Folder containing everything related to a given principal.
- """
- def __init__(self, service, path, uid, record=None):
- Folder.__init__(self, service, path)
-
- if record is not None:
- assert uid == record.uid
-
- self.uid = uid
- self.record = record
-
- @inlineCallbacks
- def _initChildren(self):
- if not hasattr(self, "_didInitChildren"):
- txn = self.service.store.newTransaction()
-
- if (
- self.record is not None and
- self.service.config.EnableCalDAV and
- self.record.enabledForCalendaring
- ):
- create = True
- else:
- create = False
-
- home = (yield txn.calendarHomeWithUID(self.uid, create=create))
- if home:
- self._children["calendars"] = CalendarHomeFolder(
- self.service,
- self.path + ("calendars",),
- home,
- )
-
- if (
- self.record is not None and
- self.service.config.EnableCardDAV and
- self.record.enabledForAddressBooks
- ):
- create = True
- else:
- create = False
-
- home = (yield txn.addressbookHomeWithUID(self.uid))
- if home:
- self._children["addressbooks"] = AddressBookHomeFolder(
- self.service,
- self.path + ("addressbooks",),
- home,
- )
-
- self._didInitChildren = True
-
- def _needsChildren(m):
- def decorate(self, *args, **kwargs):
- d = self._initChildren()
- d.addCallback(lambda _: m(self, *args, **kwargs))
- return d
- return decorate
-
- @_needsChildren
- def child(self, name):
- return Folder.child(self, name)
-
- @_needsChildren
- def list(self):
- return Folder.list(self)
-
-
-class CalendarHomeFolder(Folder):
- """
- Calendar home folder.
- """
- def __init__(self, service, path, home):
- Folder.__init__(self, service, path)
-
- self.home = home
-
- @inlineCallbacks
- def child(self, name):
- calendar = (yield self.home.calendarWithName(name))
- if calendar:
- returnValue(CalendarFolder(self.service, self.path + (name,), calendar))
- else:
- raise NotFoundError("Calendar home %r has no calendar %r" % (self, name))
-
- @inlineCallbacks
- def list(self):
- calendars = (yield self.home.calendars())
- returnValue(((CalendarFolder, c.name()) for c in calendars))
-
- @inlineCallbacks
- def describe(self):
- # created() -> int
- # modified() -> int
- # properties -> IPropertyStore
-
- uid = (yield self.home.uid())
- created = (yield self.home.created())
- modified = (yield self.home.modified())
- quotaUsed = (yield self.home.quotaUsedBytes())
- quotaAllowed = (yield self.home.quotaAllowedBytes())
- properties = (yield self.home.properties())
-
- result = []
- result.append("Calendar home for UID: %s" % (uid,))
-
- #
- # Attributes
- #
- rows = []
- if created is not None:
- # FIXME: convert to formatted string
- rows.append(("Created", str(created)))
- if modified is not None:
- # FIXME: convert to formatted string
- rows.append(("Last modified", str(modified)))
- if quotaUsed is not None:
- rows.append((
- "Quota",
- "%s of %s (%.2s%%)"
- % (quotaUsed, quotaAllowed, quotaUsed / quotaAllowed)
- ))
-
- if len(rows):
- result.append("\nAttributes:")
- result.append(tableString(rows, header=("Name", "Value")))
-
- #
- # Properties
- #
- if properties:
- result.append("\Properties:")
- result.append(tableString(
- ((name, properties[name]) for name in sorted(properties)),
- header=("Name", "Value")
- ))
-
- returnValue("\n".join(result))
-
-
-class CalendarFolder(Folder):
- """
- Calendar.
- """
- def __init__(self, service, path, calendar):
- Folder.__init__(self, service, path)
-
- self.calendar = calendar
-
- @inlineCallbacks
- def _childWithObject(self, object):
- name = (yield object.uid())
- returnValue(CalendarObject(self.service, self.path + (name,), object))
-
- @inlineCallbacks
- def child(self, name):
- object = (yield self.calendar.calendarObjectWithUID(name))
-
- if not object:
- raise NotFoundError("Calendar %r has no object %r" % (str(self), name))
-
- child = (yield self._childWithObject(object))
- returnValue(child)
-
- @inlineCallbacks
- def list(self):
- result = []
-
- for object in (yield self.calendar.calendarObjects()):
- object = (yield self._childWithObject(object))
- items = (yield object.list())
- result.append(items[0])
-
- returnValue(result)
-
-
-class CalendarObject(File):
- """
- Calendar object.
- """
- def __init__(self, service, path, calendarObject):
- File.__init__(self, service, path)
-
- self.object = calendarObject
-
- @inlineCallbacks
- def lookup(self):
- if not hasattr(self, "component"):
- component = (yield self.object.component())
- mainComponent = component.mainComponent()
-
- self.componentType = mainComponent.name()
- self.uid = mainComponent.propertyValue("UID")
- self.summary = mainComponent.propertyValue("SUMMARY")
- self.mainComponent = mainComponent
- self.component = component
-
- @inlineCallbacks
- def list(self):
- (yield self.lookup())
- returnValue(((CalendarObject, self.uid, self.componentType, self.summary),))
-
- @inlineCallbacks
- def text(self):
- (yield self.lookup())
- returnValue(str(self.component))
-
- @inlineCallbacks
- def describe(self):
- (yield self.lookup())
-
- rows = []
-
- rows.append(("UID", self.uid))
- rows.append(("Type", self.componentType))
- rows.append(("Summary", self.summary))
-
- organizer = self.mainComponent.getProperty("ORGANIZER")
- if organizer:
- organizerName = organizer.parameterValue("CN")
- organizerEmail = organizer.parameterValue("EMAIL")
-
- name = " (%s)" % (organizerName ,) if organizerName else ""
- email = " <%s>" % (organizerEmail,) if organizerEmail else ""
-
- rows.append(("Organizer", "%s%s%s" % (organizer.value(), name, email)))
-
- #
- # Attachments
- #
-# attachments = (yield self.object.attachments())
-# log.msg("%r" % (attachments,))
-# for attachment in attachments:
-# log.msg("%r" % (attachment,))
-# # FIXME: Not getting any results here
-
-
- returnValue("Calendar object:\n%s" % tableString(rows))
-
-class AddressBookHomeFolder(Folder):
- """
- Address book home folder.
- """
- # FIXME
-
-
-def tableString(rows, header=None):
- table = Table()
- if header:
- table.addHeader(header)
- for row in rows:
- table.addRow(row)
-
- output = StringIO()
- table.printTable(os=output)
- return output.getvalue()
-
-
-def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
- if reactor is None:
- from twisted.internet import reactor
-
- options = ShellOptions()
- try:
- options.parseOptions(argv[1:])
- except UsageError, e:
- usage(e)
-
- def makeService(store):
- from twistedcaldav.config import config
- directory = getDirectory()
- return ShellService(store, directory, options, reactor, config)
-
- print "Initializing shell..."
-
- utilityMain(options["config"], makeService, reactor)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120116/029ac1d1/attachment-0001.html>
More information about the calendarserver-changes
mailing list