def test_toxml(self): """ L{WebDAVDocument.toxml} returns a C{str} giving the XML representation of the L{WebDAVDocument} instance. """ document = WebDAVDocument(self.element) self.assertEquals(document, WebDAVDocument.fromString(document.toxml()))
def test_fromString(self): """ The XML representation of L{WebDAVDocument} can be parsed into a L{WebDAVDocument} instance using L{WebDAVDocument.fromString}. """ doc = WebDAVDocument.fromString(self.serialized) self.assertEquals(doc, WebDAVDocument(self.element))
def moveCalendarTimezoneProperties(sqlStore): """ Need to move all the CalDAV:calendar-timezone properties in the RESOURCE_PROPERTY table to the new CALENDAR_BIND table columns, extracting the new value from the XML property. """ cb = schema.CALENDAR_BIND rp = schema.RESOURCE_PROPERTY try: calendars_for_id = {} while True: sqlTxn = sqlStore.newTransaction() rows = (yield rowsForProperty(sqlTxn, caldavxml.CalendarTimeZone, with_uid=True, batch=BATCH_SIZE)) if len(rows) == 0: yield sqlTxn.commit() break delete_ids = [] for calendar_rid, value, viewer in rows: delete_ids.append(calendar_rid) if calendar_rid not in calendars_for_id: ids = yield Select( [cb.CALENDAR_HOME_RESOURCE_ID, cb.BIND_MODE, ], From=cb, Where=cb.CALENDAR_RESOURCE_ID == calendar_rid, ).on(sqlTxn) calendars_for_id[calendar_rid] = ids if viewer: calendarHome = (yield sqlTxn.calendarHomeWithUID(viewer)) else: calendarHome = None for row in calendars_for_id[calendar_rid]: home_id, bind_mode = row if bind_mode == _BIND_MODE_OWN: calendarHome = (yield sqlTxn.calendarHomeWithResourceID(home_id)) break if calendarHome is not None: prop = WebDAVDocument.fromString(value).root_element calendar = (yield calendarHome.childWithID(calendar_rid)) if calendar is not None: yield calendar.setTimezone(prop.calendar()) # Always delete the rows so that batch processing works correctly yield Delete( From=rp, Where=(rp.RESOURCE_ID.In(Parameter("ids", len(delete_ids)))).And (rp.NAME == PropertyName.fromElement(caldavxml.CalendarTimeZone).toString()), ).on(sqlTxn, ids=delete_ids) yield sqlTxn.commit() yield cleanPropertyStore() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()
def moveSupportedComponentSetProperties(sqlStore): """ Need to move all the CalDAV:supported-component-set properties in the RESOURCE_PROPERTY table to the new CALENDAR_METADATA table column, extracting the new format value from the XML property. """ sqlTxn = sqlStore.newTransaction() try: rows = (yield rowsForProperty(sqlTxn, caldavxml.SupportedCalendarComponentSet)) for calendar_rid, value in rows: prop = WebDAVDocument.fromString(value).root_element supported_components = ",".join(sorted([comp.attributes["name"].upper() for comp in prop.children])) meta = schema.CALENDAR_METADATA yield Update( { meta.SUPPORTED_COMPONENTS : supported_components }, Where=(meta.RESOURCE_ID == calendar_rid) ).on(sqlTxn) yield removeProperty(sqlTxn, caldavxml.SupportedCalendarComponentSet) yield sqlTxn.commit() except RuntimeError: yield sqlTxn.abort() raise
def parse(xml): try: doc = WebDAVDocument.fromString(xml) doc.root_element.validate() return doc except ValueError: log.error("Bad XML:\n%s" % (xml, )) raise
def parse(xml): try: doc = WebDAVDocument.fromString(xml) doc.root_element.validate() return doc except ValueError: log.err("Bad XML:\n%s" % (xml,)) raise
def _getitem_uid(self, key, uid): validKey(key) try: value = self._cached[(key.toString(), uid)] except KeyError: raise KeyError(key) return WebDAVDocument.fromString(value).root_element
def test_extractCalendarServerPrincipalSearchData(self): """ Exercise the parser for calendarserver-principal-search documents """ data = """<B:calendarserver-principal-search xmlns:A="DAV:" xmlns:B="http://calendarserver.org/ns/" context="attendee"> <B:search-token>morgen</B:search-token> <A:prop> <A:principal-URL/> <A:displayname/> </A:prop> </B:calendarserver-principal-search> """ doc = WebDAVDocument.fromString(data) tokens, context, applyTo, clientLimit, _ignore_propElement = extractCalendarServerPrincipalSearchData( doc.root_element) self.assertEquals(tokens, ["morgen"]) self.assertEquals(context, "attendee") self.assertFalse(applyTo) self.assertEquals(clientLimit, None) data = """<B:calendarserver-principal-search xmlns:A="DAV:" xmlns:B="http://calendarserver.org/ns/"> <B:search-token>morgen</B:search-token> <B:search-token>sagen</B:search-token> <B:limit> <B:nresults>42</B:nresults> </B:limit> <A:prop> <A:principal-URL/> <A:displayname/> </A:prop> <A:apply-to-principal-collection-set/> </B:calendarserver-principal-search> """ doc = WebDAVDocument.fromString(data) tokens, context, applyTo, clientLimit, _ignore_propElement = extractCalendarServerPrincipalSearchData( doc.root_element) self.assertEquals(tokens, ["morgen", "sagen"]) self.assertEquals(context, None) self.assertTrue(applyTo) self.assertEquals(clientLimit, 42)
def test_extractCalendarServerPrincipalSearchData(self): """ Exercise the parser for calendarserver-principal-search documents """ data = """<B:calendarserver-principal-search xmlns:A="DAV:" xmlns:B="http://calendarserver.org/ns/" context="attendee"> <B:search-token>morgen</B:search-token> <A:prop> <A:principal-URL/> <A:displayname/> </A:prop> </B:calendarserver-principal-search> """ doc = WebDAVDocument.fromString(data) tokens, context, applyTo, clientLimit, propElement = extractCalendarServerPrincipalSearchData(doc.root_element) self.assertEquals(tokens, ["morgen"]) self.assertEquals(context, "attendee") self.assertFalse(applyTo) self.assertEquals(clientLimit, None) data = """<B:calendarserver-principal-search xmlns:A="DAV:" xmlns:B="http://calendarserver.org/ns/"> <B:search-token>morgen</B:search-token> <B:search-token>sagen</B:search-token> <B:limit> <B:nresults>42</B:nresults> </B:limit> <A:prop> <A:principal-URL/> <A:displayname/> </A:prop> <A:apply-to-principal-collection-set/> </B:calendarserver-principal-search> """ doc = WebDAVDocument.fromString(data) tokens, context, applyTo, clientLimit, propElement = extractCalendarServerPrincipalSearchData(doc.root_element) self.assertEquals(tokens, ["morgen", "sagen"]) self.assertEquals(context, None) self.assertTrue(applyTo) self.assertEquals(clientLimit, 42)
def moveCalendarAvailabilityProperties(sqlStore): """ Need to move all the CS:calendar-availability properties in the RESOURCE_PROPERTY table to the new CALENDAR_BIND table columns, extracting the new value from the XML property. """ cb = schema.CALENDAR_BIND rp = schema.RESOURCE_PROPERTY try: while True: sqlTxn = sqlStore.newTransaction() rows = (yield rowsForProperty(sqlTxn, customxml.CalendarAvailability, batch=BATCH_SIZE)) if len(rows) == 0: yield sqlTxn.commit() break # Map each calendar to a home id using a single query for efficiency calendar_ids = [row[0] for row in rows] home_map = yield Select( [cb.CALENDAR_RESOURCE_ID, cb.CALENDAR_HOME_RESOURCE_ID, ], From=cb, Where=(cb.CALENDAR_RESOURCE_ID.In(Parameter("ids", len(calendar_ids)))).And(cb.BIND_MODE == _BIND_MODE_OWN), ).on(sqlTxn, ids=calendar_ids) calendar_to_home = dict(home_map) # Move property to each home for calendar_rid, value in rows: if calendar_rid in calendar_to_home: calendarHome = (yield sqlTxn.calendarHomeWithResourceID(calendar_to_home[calendar_rid])) if calendarHome is not None: prop = WebDAVDocument.fromString(value).root_element yield calendarHome.setAvailability(prop.calendar()) # Always delete the rows so that batch processing works correctly yield Delete( From=rp, Where=(rp.RESOURCE_ID.In(Parameter("ids", len(calendar_ids)))).And (rp.NAME == PropertyName.fromElement(customxml.CalendarAvailability).toString()), ).on(sqlTxn, ids=calendar_ids) yield sqlTxn.commit() yield cleanPropertyStore() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()
def _doPOSTSharerAccept(self, body, resultcode=responsecode.OK): request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/user02/", content=body, authid="user02") request.headers.setHeader("content-type", MimeType("text", "xml")) response = yield self.send(request) response = IResponse(response) self.assertEqual(response.code, resultcode) if response.stream: xmldata = yield allDataFromStream(response.stream) doc = WebDAVDocument.fromString(xmldata) returnValue(doc) else: returnValue(None)
def _doPOSTSharerAccept(self, body, resultcode=responsecode.OK, sharer="user02"): authPrincipal = yield self.actualRoot.findPrincipalForAuthID(sharer) request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/{}/".format(sharer), content=body, authPrincipal=authPrincipal) request.headers.setHeader("content-type", MimeType("text", "xml")) response = yield self.send(request) response = IResponse(response) self.assertEqual(response.code, resultcode) if response.stream: xmldata = yield allDataFromStream(response.stream) doc = WebDAVDocument.fromString(xmldata) returnValue(doc) else: returnValue(None)
def moveSupportedComponentSetProperties(sqlStore): """ Need to move all the CalDAV:supported-component-set properties in the RESOURCE_PROPERTY table to the new CALENDAR_METADATA table column, extracting the new format value from the XML property. """ logUpgradeStatus("Starting Move supported-component-set") sqlTxn = sqlStore.newTransaction( label="calendar_upgrade_from_1_to_2.moveSupportedComponentSetProperties" ) try: # Do not move the properties if migrating, as migration will do a split and set supported-components, # however we still need to remove the old properties. if not sqlStore._migrating: calendar_rid = None rows = (yield rowsForProperty(sqlTxn, caldavxml.SupportedCalendarComponentSet)) total = len(rows) count = 0 for calendar_rid, value in rows: prop = WebDAVDocument.fromString(value).root_element supported_components = ",".join( sorted([ comp.attributes["name"].upper() for comp in prop.children ])) meta = schema.CALENDAR_METADATA yield Update( { meta.SUPPORTED_COMPONENTS: supported_components }, Where=(meta.RESOURCE_ID == calendar_rid)).on(sqlTxn) count += 1 logUpgradeStatus("Move supported-component-set", count, total) yield removeProperty(sqlTxn, caldavxml.SupportedCalendarComponentSet) yield sqlTxn.commit() logUpgradeStatus("End Move supported-component-set") except RuntimeError: yield sqlTxn.abort() logUpgradeError("Move supported-component-set", "Last calendar: {}".format(calendar_rid)) raise
def moveSupportedComponentSetProperties(sqlStore): """ Need to move all the CalDAV:supported-component-set properties in the RESOURCE_PROPERTY table to the new CALENDAR_METADATA table column, extracting the new format value from the XML property. """ logUpgradeStatus("Starting Move supported-component-set") sqlTxn = sqlStore.newTransaction() try: # Do not move the properties if migrating, as migration will do a split and set supported-components, # however we still need to remove the old properties. if not sqlStore._migrating: calendar_rid = None rows = (yield rowsForProperty(sqlTxn, caldavxml.SupportedCalendarComponentSet)) total = len(rows) count = 0 for calendar_rid, value in rows: prop = WebDAVDocument.fromString(value).root_element supported_components = ",".join(sorted([comp.attributes["name"].upper() for comp in prop.children])) meta = schema.CALENDAR_METADATA yield Update( { meta.SUPPORTED_COMPONENTS : supported_components }, Where=(meta.RESOURCE_ID == calendar_rid) ).on(sqlTxn) count += 1 logUpgradeStatus("Move supported-component-set", count, total) yield removeProperty(sqlTxn, caldavxml.SupportedCalendarComponentSet) yield sqlTxn.commit() logUpgradeStatus("End Move supported-component-set") except RuntimeError: yield sqlTxn.abort() logUpgradeError( "Move supported-component-set", "Last calendar: {}".format(calendar_rid) ) raise
def normalize(x): """ Normalize some XML by parsing it, collapsing whitespace, and pretty-printing. """ return WebDAVDocument.fromString(x).toxml()
class xattrPropertyStore(object): """ This implementation uses Bob Ippolito's xattr package, available from:: http://undefined.org/python/#xattr Note that the Bob's xattr package is specific to Linux and Darwin, at least presently. """ # # Dead properties are stored as extended attributes on disk. In order to # avoid conflicts with other attributes, prefix dead property names. # deadPropertyXattrPrefix = "WebDAV:" # Linux seems to require that attribute names use a "user." prefix. # FIXME: Is is a system-wide thing, or a per-filesystem thing? # If the latter, how to we detect the file system? if sys.platform == "linux2": deadPropertyXattrPrefix = "user." def _encode(clazz, name, uid=None): result = urllib.quote(encodeXMLName(*name), safe='{}:') if uid: result = uid + result r = clazz.deadPropertyXattrPrefix + result return r def _decode(clazz, name): name = urllib.unquote(name[len(clazz.deadPropertyXattrPrefix):]) index1 = name.find("{") index2 = name.find("}") if (index1 is -1 or index2 is -1 or not len(name) > index2): raise ValueError("Invalid encoded name: %r" % (name, )) if index1 == 0: uid = None else: uid = name[:index1] propnamespace = name[index1 + 1:index2] propname = name[index2 + 1:] return (propnamespace, propname, uid) _encode = classmethod(_encode) _decode = classmethod(_decode) def __init__(self, resource): self.resource = resource self.attrs = xattr.xattr(self.resource.fp.path) def get(self, qname, uid=None): """ Retrieve the value of a property stored as an extended attribute on the wrapped path. @param qname: The property to retrieve as a two-tuple of namespace URI and local name. @param uid: The per-user identifier for per user properties. @raise HTTPError: If there is no value associated with the given property. @return: A L{WebDAVDocument} representing the value associated with the given property. """ try: data = self.attrs.get(self._encode(qname, uid)) except KeyError: raise HTTPError( StatusResponse( responsecode.NOT_FOUND, "No such property: %s" % (encodeXMLName(*qname), ))) except IOError, e: if e.errno in _ATTR_MISSING or e.errno == errno.ENOENT: raise HTTPError( StatusResponse( responsecode.NOT_FOUND, "No such property: %s" % (encodeXMLName(*qname), ))) else: raise HTTPError( StatusResponse( statusForFailure(Failure()), "Unable to read property: %s" % (encodeXMLName(*qname), ))) # # Unserialize XML data from an xattr. The storage format has changed # over time: # # 1- Started with XML # 2- Started compressing the XML due to limits on xattr size # 3- Switched to pickle which is faster, still compressing # 4- Back to compressed XML for interoperability, size # # We only write the current format, but we also read the old # ones for compatibility. # legacy = False try: data = decompress(data) except zlib.error: legacy = True try: doc = WebDAVDocument.fromString(data) except ValueError: try: doc = unpickle(data) except UnpicklingError: format = "Invalid property value stored on server: %s %s" msg = format % (encodeXMLName(*qname), data) err(None, msg) raise HTTPError( StatusResponse(responsecode.INTERNAL_SERVER_ERROR, msg)) else: legacy = True if legacy: self.set(doc.root_element) return doc.root_element
def _processDefaultCalendarProperty(sqlStore, propname, colname): """ Move the specified property value to the matching CALENDAR_HOME_METADATA table column. Since the number of calendar homes may well be large, we need to do this in batches. """ cb = schema.CALENDAR_BIND rp = schema.RESOURCE_PROPERTY try: while True: sqlTxn = sqlStore.newTransaction() rows = (yield rowsForProperty(sqlTxn, propname, batch=BATCH_SIZE)) if len(rows) == 0: yield sqlTxn.commit() break delete_ids = [] for inbox_rid, value in rows: delete_ids.append(inbox_rid) ids = yield Select( [cb.CALENDAR_HOME_RESOURCE_ID, ], From=cb, Where=cb.CALENDAR_RESOURCE_ID == inbox_rid, ).on(sqlTxn) if len(ids) > 0: calendarHome = (yield sqlTxn.calendarHomeWithResourceID(ids[0][0])) if calendarHome is not None: prop = WebDAVDocument.fromString(value).root_element defaultCalendar = str(prop.children[0]) parts = defaultCalendar.split("/") if len(parts) == 5: calendarName = parts[-1] calendarHomeUID = parts[-2] expectedHome = (yield sqlTxn.calendarHomeWithUID(calendarHomeUID)) if expectedHome is not None and expectedHome.id() == calendarHome.id(): calendar = (yield calendarHome.calendarWithName(calendarName)) if calendar is not None: yield calendarHome.setDefaultCalendar( calendar, tasks=(propname == customxml.ScheduleDefaultTasksURL) ) # Always delete the rows so that batch processing works correctly yield Delete( From=rp, Where=(rp.RESOURCE_ID.In(Parameter("ids", len(delete_ids)))).And (rp.NAME == PropertyName.fromElement(propname).toString()), ).on(sqlTxn, ids=delete_ids) yield sqlTxn.commit() yield cleanPropertyStore() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()
def _processDefaultAlarmProperty(sqlStore, propname, vevent, timed): """ Move the specified property value to the matching CALENDAR_HOME_METADATA or CALENDAR_BIND table column. Since the number of properties may well be large, we need to do this in batches. """ hm = schema.CALENDAR_HOME_METADATA cb = schema.CALENDAR_BIND rp = schema.RESOURCE_PROPERTY try: calendars_for_id = {} while True: sqlTxn = sqlStore.newTransaction() rows = (yield rowsForProperty(sqlTxn, propname, with_uid=True, batch=BATCH_SIZE)) if len(rows) == 0: yield sqlTxn.commit() break delete_ids = [] for rid, value, viewer in rows: delete_ids.append(rid) prop = WebDAVDocument.fromString(value).root_element alarm = str(prop.children[0]) if prop.children else None # First check if the rid is a home - this is the most common case ids = yield Select( [hm.RESOURCE_ID, ], From=hm, Where=hm.RESOURCE_ID == rid, ).on(sqlTxn) if len(ids) > 0: # Home object calendarHome = (yield sqlTxn.calendarHomeWithResourceID(ids[0][0])) if calendarHome is not None: yield calendarHome.setDefaultAlarm(alarm, vevent, timed) else: # rid is a calendar - we need to find the per-user calendar for the resource viewer if rid not in calendars_for_id: ids = yield Select( [cb.CALENDAR_HOME_RESOURCE_ID, cb.BIND_MODE, ], From=cb, Where=cb.CALENDAR_RESOURCE_ID == rid, ).on(sqlTxn) calendars_for_id[rid] = ids if viewer: calendarHome = (yield sqlTxn.calendarHomeWithUID(viewer)) else: calendarHome = None for row in calendars_for_id[rid]: home_id, bind_mode = row if bind_mode == _BIND_MODE_OWN: calendarHome = (yield sqlTxn.calendarHomeWithResourceID(home_id)) break if calendarHome is not None: calendar = yield calendarHome.childWithID(rid) if calendar is not None: yield calendar.setDefaultAlarm(alarm, vevent, timed) # Always delete the rows so that batch processing works correctly yield Delete( From=rp, Where=(rp.RESOURCE_ID.In(Parameter("ids", len(delete_ids)))).And (rp.NAME == PropertyName.fromElement(propname).toString()), ).on(sqlTxn, ids=delete_ids) yield sqlTxn.commit() yield cleanPropertyStore() except RuntimeError: f = Failure() yield sqlTxn.abort() f.raiseException()
# 2- Started compressing the XML due to limits on xattr size # 3- Switched to pickle which is faster, still compressing # 4- Back to compressed XML for interoperability, size # # We only write the current format, but we also read the old # ones for compatibility. # legacy = False try: data = decompress(data) except ZlibError: legacy = True try: doc = WebDAVDocument.fromString(data) except ValueError: try: doc = unpickle(data) except UnpicklingError: msg = "Invalid property value stored on server: %s %s" % ( key.toString(), data ) self.log.error(msg) raise PropertyStoreError(msg) else: legacy = True if legacy: # XXX untested: CDT catches this though. self._setitem_uid(key, doc.root_element, uid)
def updateNotification(txn, notification): """ For this notification home, update the associated child resources. """ # Convert the type value to JSON xmltype = WebDAVDocument.fromString( notification.notificationType()).root_element shared_type = "calendar" if xmltype.children[0].qname() == customxml.InviteNotification.qname(): jsontype = {"notification-type": "invite-notification"} if "shared-type" in xmltype.children[0].attributes: shared_type = xmltype.children[0].attributes["shared-type"] jsontype["shared-type"] = shared_type elif xmltype.children[0].qname() == customxml.InviteReply.qname(): jsontype = {"notification-type": "invite-reply"} # Convert the data value to JSON xmldata = (yield notification.notificationData()) xmldata = WebDAVDocument.fromString(xmldata).root_element def _extract_UID(uri): if uri.startswith("urn:uuid:"): return uri[len("urn:uuid:"):] elif uri[0] == "/": return uri.rstrip("/").split("/")[-1] elif uri.startswith("mailto:"): return uri[7:].split("@")[0] else: return "" if xmldata.childOfType(customxml.InviteNotification) is not None: ntype = xmldata.childOfType(customxml.InviteNotification) dtstamp = str(xmldata.childOfType(customxml.DTStamp)) owner = _extract_UID( str( ntype.childOfType(customxml.Organizer).childOfType( element.HRef))) sharee = _extract_UID(str(ntype.childOfType(element.HRef))) uid = str(ntype.childOfType(customxml.UID)) for xml in invitationBindStatusFromXMLMap.keys(): if ntype.childOfType(xml) is not None: state = invitationBindStatusFromXMLMap[xml] break else: state = _BIND_STATUS_INVITED mode = invitationBindModeFromXMLMap[type( ntype.childOfType(customxml.InviteAccess).children[0])] name = str( ntype.childOfType(customxml.HostURL).childOfType( element.HRef)).rstrip("/").split("/")[-1] summary = str(ntype.childOfType(customxml.InviteSummary)) jsondata = { "notification-type": "invite-notification", "shared-type": shared_type, "dtstamp": dtstamp, "owner": owner, "sharee": sharee, "uid": uid, "status": state, "access": mode, "name": name, "summary": summary, } if ntype.childOfType(caldavxml.SupportedCalendarComponentSet): comps = [ child.attributes["name"] for child in ntype.childOfType( caldavxml.SupportedCalendarComponentSet).children ] jsondata["supported-components"] = ",".join(comps) elif xmldata.childOfType(customxml.InviteReply) is not None: ntype = xmldata.childOfType(customxml.InviteReply) dtstamp = str(xmldata.childOfType(customxml.DTStamp)) sharee = _extract_UID(str(ntype.childOfType(element.HRef))) for xml in invitationBindStatusFromXMLMap.keys(): if ntype.childOfType(xml) is not None: state = invitationBindStatusFromXMLMap[xml] break else: state = _BIND_STATUS_INVITED name = str( ntype.childOfType(customxml.HostURL).childOfType( element.HRef)).rstrip("/").split("/")[-1] inreplyto = str(ntype.childOfType(customxml.InReplyTo)) summary = str(ntype.childOfType( customxml.InviteSummary)) if ntype.childOfType( customxml.InviteSummary) is not None else "" owner = str( ntype.childOfType(customxml.HostURL).childOfType( element.HRef)).rstrip("/").split("/")[-2] jsondata = { "notification-type": "invite-reply", "shared-type": shared_type, "dtstamp": dtstamp, "owner": owner, "sharee": sharee, "status": state, "name": name, "in-reply-to": inreplyto, "summary": summary, } yield notification.setData(notification.uid(), jsontype, jsondata)
def updateNotification(txn, notification): """ For this notification home, update the associated child resources. """ # Convert the type value to JSON xmltype = WebDAVDocument.fromString(notification.notificationType()).root_element shared_type = "calendar" if xmltype.children[0].qname() == customxml.InviteNotification.qname(): jsontype = {"notification-type": "invite-notification"} if "shared-type" in xmltype.children[0].attributes: shared_type = xmltype.children[0].attributes["shared-type"] jsontype["shared-type"] = shared_type elif xmltype.children[0].qname() == customxml.InviteReply.qname(): jsontype = {"notification-type": "invite-reply"} # Convert the data value to JSON xmldata = (yield notification.notificationData()) xmldata = WebDAVDocument.fromString(xmldata).root_element def _extract_UID(uri): if uri.startswith("urn:uuid:"): return uri[len("urn:uuid:"):] elif uri[0] == "/": return uri.rstrip("/").split("/")[-1] elif uri.startswith("mailto:"): return uri[7:].split("@")[0] else: return "" if xmldata.childOfType(customxml.InviteNotification) is not None: ntype = xmldata.childOfType(customxml.InviteNotification) dtstamp = str(xmldata.childOfType(customxml.DTStamp)) owner = _extract_UID(str(ntype.childOfType(customxml.Organizer).childOfType(element.HRef))) sharee = _extract_UID(str(ntype.childOfType(element.HRef))) uid = str(ntype.childOfType(customxml.UID)) for xml in invitationBindStatusFromXMLMap.keys(): if ntype.childOfType(xml) is not None: state = invitationBindStatusFromXMLMap[xml] break else: state = _BIND_STATUS_INVITED mode = invitationBindModeFromXMLMap[type(ntype.childOfType(customxml.InviteAccess).children[0])] name = str(ntype.childOfType(customxml.HostURL).childOfType(element.HRef)).rstrip("/").split("/")[-1] summary = str(ntype.childOfType(customxml.InviteSummary)) jsondata = { "notification-type": "invite-notification", "shared-type": shared_type, "dtstamp": dtstamp, "owner": owner, "sharee": sharee, "uid": uid, "status": state, "access": mode, "name": name, "summary": summary, } if ntype.childOfType(caldavxml.SupportedCalendarComponentSet): comps = [child.attributes["name"] for child in ntype.childOfType(caldavxml.SupportedCalendarComponentSet).children] jsondata["supported-components"] = ",".join(comps) elif xmldata.childOfType(customxml.InviteReply) is not None: ntype = xmldata.childOfType(customxml.InviteReply) dtstamp = str(xmldata.childOfType(customxml.DTStamp)) sharee = _extract_UID(str(ntype.childOfType(element.HRef))) for xml in invitationBindStatusFromXMLMap.keys(): if ntype.childOfType(xml) is not None: state = invitationBindStatusFromXMLMap[xml] break else: state = _BIND_STATUS_INVITED name = str(ntype.childOfType(customxml.HostURL).childOfType(element.HRef)).rstrip("/").split("/")[-1] inreplyto = str(ntype.childOfType(customxml.InReplyTo)) summary = str(ntype.childOfType(customxml.InviteSummary)) if ntype.childOfType(customxml.InviteSummary) is not None else "" owner = str(ntype.childOfType(customxml.HostURL).childOfType(element.HRef)).rstrip("/").split("/")[-2] jsondata = { "notification-type": "invite-reply", "shared-type": shared_type, "dtstamp": dtstamp, "owner": owner, "sharee": sharee, "status": state, "name": name, "in-reply-to": inreplyto, "summary": summary, } yield notification.setData(notification.uid(), jsontype, jsondata)