ProPeler
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
opt
/
cpanel-ccs
/
calendarserver
/
tools
/
shell
/
Filename :
vfs.py
back
Copy
# -*- test-case-name: calendarserver.tools.shell.test.test_vfs -*- ## # Copyright (c) 2011-2017 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. """ __all__ = [ "ListEntry", "File", "Folder", "RootFolder", "UIDsFolder", "RecordFolder", "UsersFolder", "LocationsFolder", "ResourcesFolder", "GroupsFolder", "PrincipalHomeFolder", "CalendarHomeFolder", "CalendarFolder", "CalendarObject", "AddressBookHomeFolder", ] from cStringIO import StringIO from time import strftime, localtime from twisted.internet.defer import succeed from twisted.internet.defer import inlineCallbacks, returnValue from twext.python.log import Logger from txdav.common.icommondatastore import NotFoundError from twistedcaldav.ical import InvalidICalendarDataError from calendarserver.tools.tables import Table from calendarserver.tools.shell.directory import recordInfo log = Logger() class ListEntry(object): """ Information about a C{File} as returned by C{File.list()}. """ def __init__(self, parent, Class, Name, **fields): self.parent = parent # The class implementing list() self.fileClass = Class self.fileName = Name self.fields = fields fields["Name"] = Name def __str__(self): return self.toString() def __repr__(self): fields = self.fields.copy() del fields["Name"] if fields: fields = " %s" % (fields,) else: fields = "" return "<%s(%s): %r%s>" % ( self.__class__.__name__, self.fileClass.__name__, self.fileName, fields, ) def isFolder(self): return issubclass(self.fileClass, Folder) def toString(self): if self.isFolder(): return "%s/" % (self.fileName,) else: return self.fileName @property def fieldNames(self): if not hasattr(self, "_fieldNames"): if hasattr(self.parent.list, "fieldNames"): if "Name" in self.parent.list.fieldNames: self._fieldNames = tuple(self.parent.list.fieldNames) else: self._fieldNames = ("Name",) + tuple(self.parent.list.fieldNames) else: self._fieldNames = ["Name"] + sorted(n for n in self.fields if n != "Name") return self._fieldNames def toFields(self): try: return tuple(self.fields[fieldName] for fieldName in self.fieldNames) except KeyError, e: raise AssertionError( "Field %s is not in %r, defined by %s" % (e, self.fields.keys(), self.parent.__name__) ) 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 __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self) def __eq__(self, other): if isinstance(other, File): return self.path == other.path else: return NotImplemented def describe(self): return succeed("%s (%s)" % (self, self.__class__.__name__)) def list(self): return succeed(( ListEntry(self, self.__class__, self.path[-1]), )) 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 path and path[-1] == "": path.pop() 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 = {} for name in self._children: result[name] = ListEntry(self, self._children[name].__class__, name) for name in self._childClasses: if name not in result: result[name] = ListEntry(self, self._childClasses[name], name) return succeed(result.itervalues()) 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 self._childClasses["groups"] = GroupsFolder 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): results = {} # 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 list entry @inlineCallbacks def addResult(ignoredTxn, home): uid = home.uid() record = yield self.service.directory.recordWithUID(uid) if record: info = { "Record Type": record.recordType, "Short Name": record.shortNames[0], "Full Name": record.fullName, } else: info = {} results[uid] = ListEntry(self, PrincipalHomeFolder, uid, **info) yield self.service.store.withEachCalendarHomeDo(addResult) yield self.service.store.withEachAddressbookHomeDo(addResult) returnValue(results.itervalues()) class RecordFolder(Folder): def _recordForName(self, name): recordTypeAttr = "recordType_" + self.recordType recordType = getattr(self.service.directory, recordTypeAttr) return self.service.directory.recordWithShortName(recordType, name) def child(self, name): record = self._recordForName(name) if record is None: return Folder.child(self, name) return PrincipalHomeFolder( self.service, self.path + (name,), record.uid, record=record ) @inlineCallbacks def list(self): names = set() for record in (yield self.service.directory.recordsWithRecordType( self.recordType )): for shortName in record.shortNames: if shortName in names: continue names.add(shortName) yield ListEntry( self, PrincipalHomeFolder, shortName, **{ "UID": record.uid, "Full Name": record.fullName, } ) list.fieldNames = ("UID", "Full Name") 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 GroupsFolder(RecordFolder): """ Folder containing all group principals by name. """ recordType = "groups" 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 None: # FIXME: recordWithUID returns a Deferred but we cannot return or yield it in an __init__ method record = self.service.directory.recordWithUID(uid) 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() try: if ( self.record is not None and self.service.config.EnableCalDAV and self.record.hasCalendars ): create = True else: create = False # Try assuming it exists home = (yield txn.calendarHomeWithUID(self.uid, create=False)) if home is None and create: # Doesn't exist, so create it in a different # transaction, to avoid having to commit the live # transaction. txnTemp = self.service.store.newTransaction() try: home = (yield txnTemp.calendarHomeWithUID(self.uid, create=True)) (yield txnTemp.commit()) # Fetch the home again. This time we expect it to be there. home = (yield txn.calendarHomeWithUID(self.uid, create=False)) assert home finally: (yield txn.abort()) if home: self._children["calendars"] = CalendarHomeFolder( self.service, self.path + ("calendars",), home, self.record, ) if ( self.record is not None and self.service.config.EnableCardDAV and self.record.enabledForAddressBooks ): create = True else: create = False # Again, assume it exists home = (yield txn.addressbookHomeWithUID(self.uid)) if not home and create: # Repeat the above dance. txnTemp = self.service.store.newTransaction() try: home = (yield txnTemp.addressbookHomeWithUID(self.uid, create=True)) (yield txnTemp.commit()) # Fetch the home again. This time we expect it to be there. home = (yield txn.addressbookHomeWithUID(self.uid, create=False)) assert home finally: (yield txn.abort()) if home: self._children["addressbooks"] = AddressBookHomeFolder( self.service, self.path + ("addressbooks",), home, self.record, ) finally: (yield txn.abort()) self._didInitChildren = True def _needsChildren(m): # @NoSelf 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) def describe(self): return recordInfo(self.service.directory, self.record) class CalendarHomeFolder(Folder): """ Calendar home folder. """ def __init__(self, service, path, home, record): Folder.__init__(self, service, path) self.home = home self.record = record @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()) result = [] for calendar in calendars: displayName = calendar.displayName() if displayName is None: displayName = "(unset)" info = { "Display Name": displayName, "Sync Token": (yield calendar.syncToken()), } result.append(ListEntry(self, CalendarFolder, calendar.name(), **info)) returnValue(result) @inlineCallbacks def describe(self): description = ["Calendar home:\n"] # # Attributes # 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()) recordType = (yield self.record.recordType) recordShortName = (yield self.record.shortNames[0]) rows = [] rows.append(("UID", uid)) rows.append(("Owner", "(%s)%s" % (recordType, recordShortName))) rows.append(("Created", timeString(created))) rows.append(("Last modified", timeString(modified))) if quotaUsed is not None: rows.append(( "Quota", "%s of %s (%.2s%%)" % (quotaUsed, quotaAllowed, quotaUsed / quotaAllowed) )) description.append("Attributes:") description.append(tableString(rows)) # # Properties # properties = (yield self.home.properties()) if properties: description.append(tableStringForProperties(properties)) returnValue("\n".join(description)) class CalendarFolder(Folder): """ Calendar. """ def __init__(self, service, path, calendar): Folder.__init__(self, service, path) self.calendar = calendar @inlineCallbacks def _childWithObject(self, object): uid = (yield object.uid()) returnValue(CalendarObject(self.service, self.path + (uid,), object, uid)) @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) @inlineCallbacks def describe(self): description = ["Calendar:\n"] # # Attributes # ownerHome = (yield self.calendar.ownerCalendarHome()) # FIXME: Translate into human syncToken = (yield self.calendar.syncToken()) rows = [] rows.append(("Owner", ownerHome)) rows.append(("Sync Token", syncToken)) description.append("Attributes:") description.append(tableString(rows)) # # Properties # properties = (yield self.calendar.properties()) if properties: description.append(tableStringForProperties(properties)) returnValue("\n".join(description)) def delete(self, implicit=True): if implicit: # We need data store-level scheduling support to implement # this. raise NotImplementedError("Delete not implemented.") else: self.calendarObject.remove() class CalendarObject(File): """ Calendar object. """ def __init__(self, service, path, calendarObject, uid): File.__init__(self, service, path) self.object = calendarObject self.uid = uid @inlineCallbacks def lookup(self): if not hasattr(self, "component"): component = (yield self.object.component()) try: mainComponent = component.mainComponent() assert self.uid == mainComponent.propertyValue("UID") self.componentType = mainComponent.name() self.summary = mainComponent.propertyValue("SUMMARY") self.mainComponent = mainComponent except InvalidICalendarDataError, e: log.error("{path}: {ex}", path=self.path, ex=e) self.componentType = "?" self.summary = "** Invalid data **" self.mainComponent = None self.component = component @inlineCallbacks def list(self): (yield self.lookup()) returnValue((ListEntry(self, CalendarObject, self.uid, { "Component Type": self.componentType, "Summary": self.summary.replace("\n", " "), }),)) list.fieldNames = ("Component Type", "Summary") @inlineCallbacks def text(self): (yield self.lookup()) returnValue(str(self.component)) @inlineCallbacks def describe(self): (yield self.lookup()) description = ["Calendar object:\n"] # # Calendar object attributes # rows = [] rows.append(("UID", self.uid)) rows.append(("Component 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))) rows.append(("Created", timeString(self.object.created()))) rows.append(("Modified", timeString(self.object.modified()))) description.append("Attributes:") description.append(tableString(rows)) # # Attachments # attachments = (yield self.object.attachments()) for attachment in attachments: contentType = attachment.contentType() contentType = "%s/%s" % (contentType.mediaType, contentType.mediaSubtype) rows = [] rows.append(("Name", attachment.name())) rows.append(("Size", "%s bytes" % (attachment.size(),))) rows.append(("Content Type", contentType)) rows.append(("MD5 Sum", attachment.md5())) rows.append(("Created", timeString(attachment.created()))) rows.append(("Modified", timeString(attachment.modified()))) description.append("Attachment:") description.append(tableString(rows)) # # Properties # properties = (yield self.object.properties()) if properties: description.append(tableStringForProperties(properties)) returnValue("\n".join(description)) class AddressBookHomeFolder(Folder): """ Address book home folder. """ def __init__(self, service, path, home, record): Folder.__init__(self, service, path) self.home = home self.record = record # 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 tableStringForProperties(properties): return "Properties:\n%s" % (tableString(( (name.toString(), truncateAtNewline(properties[name])) for name in sorted(properties) ))) def timeString(time): if time is None: return "(unknown)" return strftime("%a, %d %b %Y %H:%M:%S %z(%Z)", localtime(time)) def truncateAtNewline(text): text = str(text) try: index = text.index("\n") except ValueError: return text return text[:index] + "..."