def __init__(self, parent, record): """ @param parent: the parent of this resource. @param record: the L{IDirectoryRecord} that this resource represents. """ super(DirectoryPrincipalResource, self).__init__() self.cacheNotifier = self.cacheNotifierFactory(self, cacheHandle="PrincipalToken") if self.isCollection(): slash = "/" else: slash = "" assert record is not None, "Principal must have a directory record" self.record = record self.parent = parent url = joinURL(parent.principalCollectionURL(), self.principalUID()) + slash self._url = url self._alternate_urls = tuple([ joinURL(parent.parent.principalCollectionURL(), record.recordType, shortName) + slash for shortName in record.shortNames ])
def calendar_query(self, calendar_uri, query, got_xml, data, no_init): if not no_init: response = yield self.send(SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authid="wsanchez")) response = IResponse(response) if response.code != responsecode.CREATED: self.fail("MKCALENDAR failed: %s" % (response.code,)) if data: for filename, icaldata in data.iteritems(): request = SimpleStoreRequest(self, "PUT", joinURL(calendar_uri, filename + ".ics"), authid="wsanchez") request.stream = MemoryStream(icaldata) yield self.send(request) else: # Add holiday events to calendar for child in FilePath(self.holidays_dir).children(): if os.path.splitext(child.basename())[1] != ".ics": continue request = SimpleStoreRequest(self, "PUT", joinURL(calendar_uri, child.basename()), authid="wsanchez") request.stream = MemoryStream(child.getContent()) yield self.send(request) request = SimpleStoreRequest(self, "REPORT", calendar_uri, authid="wsanchez") request.stream = MemoryStream(query.toxml()) response = yield self.send(request) response = IResponse(response) if response.code != responsecode.MULTI_STATUS: self.fail("REPORT failed: %s" % (response.code,)) returnValue( (yield davXMLFromStream(response.stream).addCallback(got_xml)) )
def pickNewDefaultCalendar(self, request): """ First see if "calendar" exists in the calendar home and pick that. Otherwise create "calendar" in the calendar home. """ calendarHomeURL = self.parent.url() defaultCalendarURL = joinURL(calendarHomeURL, "calendar") defaultCalendar = (yield request.locateResource(defaultCalendarURL)) if defaultCalendar is None or not defaultCalendar.exists(): getter = iter(self.parent._newStoreCalendarHome.calendars()) # FIXME: the back-end should re-provision a default calendar here. # Really, the dead property shouldn't be necessary, and this should # be entirely computed by a back-end method like 'defaultCalendar()' try: aCalendar = getter.next() except StopIteration: raise RuntimeError("No calendars at all.") defaultCalendarURL = joinURL(calendarHomeURL, aCalendar.name()) self.writeDeadProperty( caldavxml.ScheduleDefaultCalendarURL( davxml.HRef(defaultCalendarURL) ) ) returnValue(caldavxml.ScheduleDefaultCalendarURL( davxml.HRef(defaultCalendarURL)) )
def __init__(self, parent, proxyType): """ @param parent: the parent of this resource. @param proxyType: a C{str} containing the name of the resource. """ if self.isCollection(): slash = "/" else: slash = "" url = joinURL(parent.principalURL(), proxyType) + slash super(CalendarUserProxyPrincipalResource, self).__init__() DAVResourceWithChildrenMixin.__init__(self) self.parent = parent self.proxyType = proxyType self._url = url # FIXME: if this is supposed to be public, it needs a better name: self.pcollection = self.parent.parent.parent # Principal UID is parent's GUID plus the proxy type; this we can easily # map back to a principal. self.uid = "%s#%s" % (self.parent.principalUID(), proxyType) self._alternate_urls = tuple( joinURL(url, proxyType) + slash for url in parent.alternateURIs() if url.startswith("/") )
def defaultAccessControlList(self): myPrincipal = self.principalForRecord() aces = ( # Inheritable DAV:all access for the resource's associated principal. davxml.ACE( davxml.Principal(davxml.HRef(myPrincipal.principalURL())), davxml.Grant(davxml.Privilege(davxml.All())), davxml.Protected(), TwistedACLInheritable(), ), # Inheritable CALDAV:read-free-busy access for authenticated users. davxml.ACE( davxml.Principal(davxml.Authenticated()), davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())), TwistedACLInheritable(), ), ) # Give read access to config.ReadPrincipals aces += config.ReadACEs # Give all access to config.AdminPrincipals aces += config.AdminACEs if config.EnableProxyPrincipals: aces += ( # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users. davxml.ACE( davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-read/"))), davxml.Grant( davxml.Privilege(davxml.Read()), davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()), ), davxml.Protected(), TwistedACLInheritable(), ), # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users. davxml.ACE( davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write/"))), davxml.Grant( davxml.Privilege(davxml.Read()), davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()), davxml.Privilege(davxml.Write()), ), davxml.Protected(), TwistedACLInheritable(), ), ) return davxml.ACL(*aces)
def _addressBookHomeChildURL(self, name): if not hasattr(self, "addressBookHomeURL"): if not hasattr(self.record.service, "addressBookHomesCollection"): return None self.addressBookHomeURL = joinURL( self.record.service.addressBookHomesCollection.url(), uidsResourceName, self.record.uid ) url = self.addressBookHomeURL if url is None: return None else: return joinURL(url, name) if name else url
def readProperty(self, property, request): if type(property) is tuple: qname = property else: qname = property.qname() if qname == (caldav_namespace, "calendar-free-busy-set"): # Always return at least an empty list if not self.hasDeadProperty(property): top = self.parent.url() values = [] for cal in self.parent._newStoreCalendarHome.calendars(): prop = cal.properties().get(PropertyName.fromString(ScheduleCalendarTransp.sname())) if prop == ScheduleCalendarTransp(Opaque()): values.append(HRef(joinURL(top, cal.name()))) returnValue(CalendarFreeBusySet(*values)) elif qname == (caldav_namespace, "schedule-default-calendar-URL"): # Must have a valid default try: defaultCalendarProperty = self.readDeadProperty(property) except HTTPError: defaultCalendarProperty = None if defaultCalendarProperty and len(defaultCalendarProperty.children) == 1: defaultCalendar = str(defaultCalendarProperty.children[0]) cal = (yield request.locateResource(str(defaultCalendar))) if cal is not None and cal.exists() and isCalendarCollectionResource(cal): returnValue(defaultCalendarProperty) # Default is not valid - we have to try to pick one defaultCalendarProperty = (yield self.pickNewDefaultCalendar(request)) returnValue(defaultCalendarProperty) result = (yield super(ScheduleInboxResource, self).readProperty(property, request)) returnValue(result)
def queryCalendarObjectResource(resource, uri, name, calendar, timezone, query_ok=False, isowner=True): """ Run a query on the specified calendar. @param resource: the L{CalDAVResource} for the calendar. @param uri: the uri of the resource. @param name: the name of the resource. @param calendar: the L{Component} calendar read from the resource. """ # Handle private events access restrictions if not isowner: access = resource.accessMode else: access = None if query_ok or filter.match(calendar, access): # Check size of results is within limit matchcount[0] += 1 if max_number_of_results[0] is not None and matchcount[0] > max_number_of_results[0]: raise NumberOfMatchesWithinLimits(max_number_of_results[0]) if name: href = davxml.HRef.fromString(joinURL(uri, name)) else: href = davxml.HRef.fromString(uri) try: yield report_common.responseForHref(request, responses, href, resource, propertiesForResource, props, isowner, calendar=calendar, timezone=timezone) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.err("Missing resource during query: %s" % (href,))
def _acceptShare(self, request, sharetype, hostUrl, shareUID, displayname=None): # Add or update in DB oldShare = self.sharesDB().recordForShareUID(shareUID) if not oldShare: oldShare = share = SharedCollectionRecord(shareUID, sharetype, hostUrl, str(uuid4()), displayname) self.sharesDB().addOrUpdateRecord(share) # Set per-user displayname to whatever was given sharedCollection = (yield request.locateResource(hostUrl)) ownerPrincipal = (yield self.ownerPrincipal(request)) sharedCollection.setVirtualShare(ownerPrincipal, oldShare) if displayname: yield sharedCollection.writeProperty(davxml.DisplayName.fromString(displayname), request) # Calendars always start out transparent if sharedCollection.isCalendarCollection(): yield sharedCollection.writeProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Transparent()), request) # Return the URL of the shared collection returnValue(XMLResponse( code = responsecode.OK, element = customxml.SharedAs( davxml.HRef.fromString(joinURL(self.url(), oldShare.localname)) ) ))
def queryAddressBookObjectResource(resource, uri, name, vcard, query_ok=False): """ Run a query on the specified vcard. @param resource: the L{CalDAVResource} for the vcard. @param uri: the uri of the resource. @param name: the name of the resource. @param vcard: the L{Component} vcard read from the resource. """ if query_ok or filter.match(vcard): # Check size of results is within limit checkMaxResults() if name: href = davxml.HRef.fromString(joinURL(uri, name)) else: href = davxml.HRef.fromString(uri) try: yield report_common.responseForHref(request, responses, href, resource, propertiesForResource, query, vcard=vcard) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during sync: %s" % (href,))
def accessControlList(self, request, *args, **kwargs): """ Override this to give write proxies DAV:write-acl privilege so they can add attachments too. """ acl = (yield super(DropBoxHomeResource, self).accessControlList(request, *args, **kwargs)) if config.EnableProxyPrincipals: owner = (yield self.ownerPrincipal(request)) newaces = tuple(acl.children) newaces += ( # DAV:write-acl access for this principal's calendar-proxy-write users. davxml.ACE( davxml.Principal(davxml.HRef(joinURL(owner.principalURL(), "calendar-proxy-write/"))), davxml.Grant( davxml.Privilege(davxml.WriteACL()), ), davxml.Protected(), TwistedACLInheritable(), ), ) returnValue(davxml.ACL(*newaces)) else: returnValue(acl)
def queryCalendarObjectResource(resource, uri, name, calendar, timezone, query_ok=False, isowner=True): """ Run a query on the specified calendar. @param resource: the L{CalDAVResource} for the calendar. @param uri: the uri of the resource. @param name: the name of the resource. @param calendar: the L{Component} calendar read from the resource. """ # Handle private events access restrictions if not isowner: try: access = resource.readDeadProperty(TwistedCalendarAccessProperty) except HTTPError: access = None else: access = None if query_ok or filter.match(calendar, access): # Check size of results is within limit matchcount[0] += 1 if max_number_of_results[0] is not None and matchcount[0] > max_number_of_results[0]: raise NumberOfMatchesWithinLimits(max_number_of_results[0]) if name: href = davxml.HRef.fromString(joinURL(uri, name)) else: href = davxml.HRef.fromString(uri) return report_common.responseForHref(request, responses, href, resource, propertiesForResource, props, isowner, calendar=calendar, timezone=timezone) else: return succeed(None)
def readProperty(self, property, request): if type(property) is tuple: qname = property else: qname = property.qname() if qname == caldavxml.CalendarFreeBusySet.qname(): # Synthesize value for calendar transparency state top = self.parent.url() values = [] for cal in (yield self.parent._newStoreHome.calendars()): if cal.isUsedForFreeBusy(): values.append(HRef(joinURL(top, cal.name()) + "/")) returnValue(CalendarFreeBusySet(*values)) elif qname == customxml.CalendarAvailability.qname(): availability = self.parent._newStoreHome.getAvailability() returnValue(customxml.CalendarAvailability.fromString(str(availability)) if availability else None) elif qname in (caldavxml.ScheduleDefaultCalendarURL.qname(), customxml.ScheduleDefaultTasksURL.qname()): result = (yield self.readDefaultCalendarProperty(request, qname)) returnValue(result) result = (yield super(ScheduleInboxResource, self).readProperty(property, request)) returnValue(result)
def cleanup(self): """ Do some cleanup after the real request. """ # Remove created resources href = joinURL(self.sessions[0].calendarHref, "put.ics") self.sessions[0].deleteResource(URL(path=href))
def pickNewDefaultCalendar(self, request, tasks=False): """ First see if default provisioned calendar exists in the calendar home and pick that. Otherwise pick another from the calendar home. """ componentType = "VTODO" if tasks else "VEVENT" test_name = "tasks" if tasks else "calendar" prop_to_set = customxml.ScheduleDefaultTasksURL if tasks else caldavxml.ScheduleDefaultCalendarURL calendarHomeURL = self.parent.url() defaultCalendarURL = joinURL(calendarHomeURL, test_name) defaultCalendar = (yield request.locateResource(defaultCalendarURL)) if defaultCalendar is None or not defaultCalendar.exists(): # Really, the dead property shouldn't be necessary, and this should # be entirely computed by a back-end method like 'defaultCalendar()' @inlineCallbacks def _findDefault(): for calendarName in (yield self.parent._newStoreHome.listCalendars()): # These are only unshared children if calendarName == "inbox": continue calendar = (yield self.parent._newStoreHome.calendarWithName(calendarName)) if not calendar.owned(): continue if not calendar.isSupportedComponent(componentType): continue break else: calendarName = None returnValue(calendarName) foundName = yield _findDefault() if foundName is None: # Create a default and try and get its name again yield self.parent._newStoreHome.ensureDefaultCalendarsExist() foundName = yield _findDefault() if foundName is None: # Failed to even create a default - bad news... raise RuntimeError("No valid calendars to use as a default %s calendar." % (componentType,)) defaultCalendarURL = joinURL(calendarHomeURL, foundName) prop = prop_to_set(davxml.HRef(defaultCalendarURL)) self.writeDeadProperty(prop) returnValue(prop)
def cleanup(self): """ Do some cleanup after the real request. """ # Remove created resources for i in range(self.count): href = joinURL(self.sessions[0].calendarHref, "tr-query-%d.ics" % (i + 1,)) self.sessions[0].deleteResource(URL(path=href))
def addressbook_query(self, addressbook_uri, query, got_xml, data, no_init): if not no_init: ''' FIXME: clear address book, possibly by removing mkcol = """<?xml version="1.0" encoding="utf-8" ?> <D:mkcol xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav"> <D:set> <D:prop> <D:resourcetype><D:collection/><C:addressbook/></D:resourcetype> </D:prop> </D:set> </D:mkcol> """ response = yield self.send(SimpleStoreRequest(self, "MKCOL", addressbook_uri, content=mkcol, authid="wsanchez")) response = IResponse(response) if response.code != responsecode.CREATED: self.fail("MKCOL failed: %s" % (response.code,)) ''' if data: for filename, icaldata in data.iteritems(): request = SimpleStoreRequest(self, "PUT", joinURL(addressbook_uri, filename + ".vcf"), authid="wsanchez") request.stream = MemoryStream(icaldata) yield self.send(request) else: # Add vcards to addressbook for child in FilePath(self.vcards_dir).children(): if os.path.splitext(child.basename())[1] != ".vcf": continue request = SimpleStoreRequest(self, "PUT", joinURL(addressbook_uri, child.basename()), authid="wsanchez") request.stream = MemoryStream(child.getContent()) yield self.send(request) request = SimpleStoreRequest(self, "REPORT", addressbook_uri, authid="wsanchez") request.stream = MemoryStream(query.toxml()) response = yield self.send(request) response = IResponse(response) if response.code != responsecode.MULTI_STATUS: self.fail("REPORT failed: %s" % (response.code,)) returnValue( (yield davXMLFromStream(response.stream).addCallback(got_xml)) )
def doRequest(self): """ Execute the actual HTTP request. """ now = PyCalendarDateTime.getNowUTC() href = joinURL(self.sessions[0].calendarHref, "put.ics") self.sessions[0].writeData(URL(path=href), ICAL % (now.getYear() + 1,), "text/calendar")
def mkdtemp(self, prefix): """ Creates a new directory in the document root and returns its path and URI. """ path = mkdtemp(prefix=prefix + "_", dir=self.docroot) uri = joinURL("/", url_quote(os.path.basename(path))) + "/" return (os.path.abspath(path), uri)
def writeProperty(self, property, request): assert isinstance(property, davxml.WebDAVElement) # Strictly speaking CS:calendar-availability is a live property in the sense that the # server enforces what can be stored, however it need not actually # exist so we cannot list it in liveProperties on this resource, since its # its presence there means that hasProperty will always return True for it. if property.qname() == customxml.CalendarAvailability.qname(): if not property.valid(): raise HTTPError(ErrorResponse( responsecode.CONFLICT, (caldav_namespace, "valid-calendar-data"), description="Invalid property" )) yield self.parent._newStoreHome.setAvailability(property.calendar()) returnValue(None) elif property.qname() == caldavxml.CalendarFreeBusySet.qname(): # Verify that the calendars added in the PROPPATCH are valid. We do not check # whether existing items in the property are still valid - only new ones. property.children = [davxml.HRef(normalizeURL(str(href))) for href in property.children] new_calendars = set([str(href) for href in property.children]) old_calendars = set() for cal in (yield self.parent._newStoreHome.calendars()): if cal.isUsedForFreeBusy(): old_calendars.add(HRef(joinURL(self.parent.url(), cal.name()))) added_calendars = new_calendars.difference(old_calendars) for href in added_calendars: cal = (yield request.locateResource(str(href))) if cal is None or not cal.exists() or not isCalendarCollectionResource(cal): # Validate that href's point to a valid calendar. raise HTTPError(ErrorResponse( responsecode.CONFLICT, (caldav_namespace, "valid-calendar-url"), "Invalid URI", )) # Remove old ones for href in old_calendars.difference(new_calendars): cal = (yield request.locateResource(str(href))) if cal is not None and cal.exists() and isCalendarCollectionResource(cal) and cal._newStoreObject.isUsedForFreeBusy(): yield cal._newStoreObject.setUsedForFreeBusy(False) # Add new ones for href in new_calendars: cal = (yield request.locateResource(str(href))) if cal is not None and cal.exists() and isCalendarCollectionResource(cal) and not cal._newStoreObject.isUsedForFreeBusy(): yield cal._newStoreObject.setUsedForFreeBusy(True) returnValue(None) elif property.qname() in (caldavxml.ScheduleDefaultCalendarURL.qname(), customxml.ScheduleDefaultTasksURL.qname()): yield self.writeDefaultCalendarProperty(request, property) returnValue(None) yield super(ScheduleInboxResource, self).writeProperty(property, request)
def _addressBookHomeChildURL(self, name): if not hasattr(self, "addressBookHomeURL"): if not hasattr(self.record.service, "addressBookHomesCollection"): return None self.addressBookHomeURL = joinURL( self.record.service.addressBookHomesCollection.url(), uidsResourceName, self.record.uid ) + "/" # Prefix with other server if needed if not self.thisServer(): self.addressBookHomeURL = joinURL(self.serverURI(), self.addressBookHomeURL) url = self.addressBookHomeURL if url is None: return None else: return joinURL(url, name) if name else url
def getCalendarObjectForPrincipals(request, principal, uid, allow_shared=False): """ Get a copy of the event for a principal. NOTE: if more than one resource with the same UID is found, we will delete all but one of them to avoid scheduling problems. """ result = { "resource": None, "resource_name": None, "calendar_collection": None, "calendar_collection_uri": None, } if principal and principal.locallyHosted(): # Get principal's calendar-home calendar_home = yield principal.calendarHome(request) # FIXME: because of the URL->resource request mapping thing, we have to # force the request to recognize this resource. request._rememberResource(calendar_home, calendar_home.url()) # Get matching newstore objects objectResources = (yield calendar_home.getCalendarResourcesForUID(uid, allow_shared)) if len(objectResources) > 1: # Delete all but the first one log.debug("Should only have zero or one scheduling object resource with UID '%s' in calendar home: %s" % (uid, calendar_home,)) for resource in objectResources[1:]: yield resource._parentCollection.removeObjectResource(resource) objectResources = objectResources[:1] # We really want only one or zero of these if len(objectResources) == 1: result["calendar_collection_uri"] = joinURL(calendar_home.url(), objectResources[0]._parentCollection.name()) result["calendar_collection"] = (yield request.locateResource(result["calendar_collection_uri"])) result["resource_name"] = objectResources[0].name() result["resource"] = (yield request.locateResource(joinURL(result["calendar_collection_uri"], result["resource_name"]))) returnValue((result["resource"], result["resource_name"], result["calendar_collection"], result["calendar_collection_uri"],))
def prepare(self): """ Do some setup prior to the real request. """ # Add resources to create required number of changes self.start = PyCalendarDateTime.getNowUTC() self.start.setHHMMSS(12, 0, 0) self.end = self.start.duplicate() self.end.offsetHours(1) for i in range(self.count): href = joinURL(self.sessions[0].calendarHref, "tr-query-%d.ics" % (i + 1,)) self.sessions[0].writeData(URL(path=href), ICAL % (self.start.getText(), i + 1,), "text/calendar")
def __init__(self, parent): """ @param directory: an L{IDirectoryService} to provision calendars from. @param recordType: the directory record type to provision. """ DirectoryProvisioningResource.__init__( self, joinURL(parent.principalCollectionURL(), uidsResourceName) + "/", parent.directory ) self.parent = parent
def __init__(self, parent, recordType): """ @param parent: the parent L{DirectoryPrincipalProvisioningResource}. @param recordType: the directory record type to provision. """ DirectoryProvisioningResource.__init__( self, joinURL(parent.principalCollectionURL(), recordType) + "/", parent.directory ) self.recordType = recordType self.parent = parent
def doRequest(self): """ Execute the actual HTTP request. """ # Invite as user02 now = PyCalendarDateTime.getNowUTC() href = joinURL(self.sessions[1].calendarHref, "organizer.ics") attendees = "\r\n".join(["ATTENDEE:mailto:[email protected]"] + [ATTENDEE % (ctr + 3,) for ctr in range(self.count - 1)]) self.sessions[1].writeData( URL(path=href), ICAL.format(year=now.getYear() + 1, count=self.count, attendees=attendees), "text/calendar", )
def prepare(self): """ Do some setup prior to the real request. """ if not self.full: # Get current sync token results, _ignore_bad = self.sessions[0].getProperties(URL(path=self.sessions[0].calendarHref), (davxml.sync_token,)) self.synctoken = results[davxml.sync_token] # Add resources to create required number of changes now = PyCalendarDateTime.getNowUTC() for i in range(self.count): href = joinURL(self.sessions[0].calendarHref, "sync-collection-%d.ics" % (i + 1,)) self.sessions[0].writeData(URL(path=href), ICAL % (now.getYear() + 1, i + 1,), "text/calendar")
def defaultCalendar(self, request, componentType): """ Find the default calendar for the supplied iCalendar component type. If one does not exist, automatically provision it. """ # This property now comes direct from the calendar home new store object default = (yield self.parent._newStoreHome.defaultCalendar(componentType, create=False)) # Need L{DAVResource} object to return not new store object if default is not None: default = (yield request.locateResource(joinURL(self.parent.url(), default.name()))) returnValue(default)
def ensureEvents(self, session, calendarhref, n): """ Make sure the required number of events are present in the calendar. @param n: number of events @type n: C{int} """ now = PyCalendarDateTime.getNowUTC() for i in range(n - self.currentCount): index = self.currentCount + i + 1 href = joinURL(calendarhref, "%d.ics" % (index,)) session.writeData(URL(path=href), ICAL % (now.getYear() + 1, index,), "text/calendar") self.currentCount = n
def removeDirectShare(self, request, share): """ Remove a shared collection but do not send a decline back """ shareURL = joinURL(self.url(), share.localname) if self.isCalendarCollection(): # For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox principal = (yield self.resourceOwnerPrincipal(request)) inboxURL = principal.scheduleInboxURL() if inboxURL: inbox = (yield request.locateResource(inboxURL)) inbox.processFreeBusyCalendar(shareURL, False) self.sharesDB().removeRecordForShareUID(share.shareuid)