def test_query_extended(self): """ Basic query test with time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange(**{ "start": "20060605T160000Z", }) ], **{"name": ("VEVENT")}), caldavxml.ComponentFilter(**{"name": ("VTODO")}), ], **{ "name": "VCALENDAR", "test": "anyof" })) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) j = filter.serialize() self.assertEqual(j["type"], "Filter") f = FilterBase.deserialize(j) self.assertTrue(isinstance(f, Filter)) self.assertEqual(len(f.child.filters), 2) self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
def test_query(self): """ Basic query test - no time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) expression = buildExpression(filter, self._queryFields) sql = CalDAVSQLQueryGenerator(expression, self, 1234) select, args, usedtimerange = sql.generate() self.assertEqual( select.toSQL(), SQLFragment( "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE in (?, ?, ?)", [1234, Parameter('arg1', 3)])) self.assertEqual(args, {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) self.assertEqual(usedtimerange, False)
def test_query_not_extended(self): """ Query test - two terms not anyof """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter(**{"name": ("VEVENT")}), caldavxml.ComponentFilter(**{"name": ("VTODO")}), ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) expression = buildExpression(filter, self._queryFields) sql = CalDAVSQLQueryGenerator(expression, self, 1234) select, args, usedtimerange = sql.generate() self.assertEqual( select.toSQL(), SQLFragment( "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE = ? and ICALENDAR_TYPE = ?", [1234, "VEVENT", "VTODO"])) self.assertEqual(args, {}) self.assertEqual(usedtimerange, False)
def test_search(self): """ Test that action=resourcenameforuid works. """ yield self.createShare("user01", "puser01") calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar") yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)) yield self.commitTransaction(0) filter = caldavxml.Filter( caldavxml.ComponentFilter( *[caldavxml.ComponentFilter( **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")} )], **{"name": "VCALENDAR"} ) ) filter = Filter(filter) calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar") names = [item[0] for item in (yield calendar1.search(filter))] self.assertEqual(names, ["1.ics", ]) yield self.commitTransaction(0) shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar") names = [item[0] for item in (yield shared.search(filter))] self.assertEqual(names, ["1.ics", ]) yield self.commitTransaction(1)
def test_calendar_query_bogus_timezone_id(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZoneID.fromString("bogus"), ) result = yield self.calendar_query( query, got_xml=None, expected_code=responsecode.FORBIDDEN) self.assertTrue("valid-timezone" in result)
def eventsInTimeRange(self, calendar, uid, timerange): # Create fake filter element to match time-range filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( timerange, name=("VEVENT", ), ), name="VCALENDAR", )) filter = Filter(filter) filter.settimezone(None) matches = yield calendar.search(filter, useruid=uid, fbtype=False) if matches is None: returnValue(None) for name, _ignore_uid, _ignore_type in matches: event = yield calendar.calendarObjectWithName(name) ical_data = yield event.componentForUser() ical_data.stripStandardTimezones() table = tables.Table() table.addRow(( "Calendar:", calendar.name(), )) table.addRow(("Resource Name:", name)) table.addRow(("Resource ID:", event._resourceID)) table.addRow(("Created", event.created())) table.addRow(("Modified", event.modified())) print("\n") table.printTable() print(ical_data.getTextWithoutTimezones())
def test_timerange_query(self): """ Basic query test with time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange( **{ "start": "20060605T160000Z", "end": "20060605T170000Z" }) ], **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) j = filter.serialize() self.assertEqual(j["type"], "Filter") f = FilterBase.deserialize(j) self.assertTrue(isinstance(f, Filter)) self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange)) self.assertTrue( isinstance(f.child.filters[0].qualifier.tzinfo, Timezone)) self.assertEqual(f.child.filters[0].qualifier.tzinfo.getTimezoneID(), "America/New_York")
def test_query_text(self): """ Basic query test with time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( caldavxml.PropertyFilter( caldavxml.TextMatch.fromString("1234", False), name="UID", ), **{"name": ("VEVENT")}), ], **{ "name": "VCALENDAR", "test": "anyof" })) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) j = filter.serialize() self.assertEqual(j["type"], "Filter") f = FilterBase.deserialize(j) self.assertTrue(isinstance(f, Filter)) self.assertTrue( isinstance(f.child.filters[0].filters[0], PropertyFilter)) self.assertTrue( isinstance(f.child.filters[0].filters[0].qualifier, TextMatch)) self.assertEqual(f.child.filters[0].filters[0].qualifier.text, "1234")
def test_vlarm_undefined(self): filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter(caldavxml.IsNotDefined(), **{"name": "VALARM"}) ], **{"name": "VEVENT"}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) self.assertFalse( filter.match( Component.fromString("""BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//Example Inc.//Example Calendar//EN VERSION:2.0 BEGIN:VTIMEZONE LAST-MODIFIED:20040110T032845Z TZID:US/Eastern BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20051222T210412Z CREATED:20060102T150000Z DTSTART;TZID=US/Eastern:20130102T100000 DURATION:PT1H RRULE:FREQ=DAILY;COUNT=5 SUMMARY:event 5 UID:[email protected] CATEGORIES:cool,hot CATEGORIES:warm BEGIN:VALARM ACTION:AUDIO TRIGGER;RELATED=START:-PT10M END:VALARM END:VEVENT END:VCALENDAR """)))
def test_query(self): """ Basic query test - no time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) j = filter.serialize() self.assertEqual(j["type"], "Filter") f = FilterBase.deserialize(j) self.assertTrue(isinstance(f, Filter))
def test_sqllite_query(self): """ Basic query test - single term. Only UID can be queried via sql. """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) sql, args = sqlcalendarquery(filter, 1234) self.assertTrue(sql.find("RESOURCE") != -1) self.assertTrue(sql.find("TIMESPAN") == -1) self.assertTrue(sql.find("PERUSER") == -1) self.assertTrue("VEVENT" in args)
def test_calendar_query_timezone(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) tzid1 = "Etc/GMT+1" tz1 = Component(None, pycalendar=readVTZ(tzid1)) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZone.fromCalendar(tz1), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) return self.calendar_query(query, got_xml)
def test_query_freebusy(self): """ Basic query test - with time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange( **{ "start": "20060605T160000Z", "end": "20060605T170000Z" }) ], **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) expression = buildExpression(filter, self._queryFields) sql = CalDAVSQLQueryGenerator(expression, self, 1234, "user01", True) select, args, usedtimerange = sql.generate() self.assertEqual( select.toSQL(), SQLFragment( "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE, ORGANIZER, FLOATING, coalesce(ADJUSTED_START_DATE, START_DATE), coalesce(ADJUSTED_END_DATE, END_DATE), FBTYPE, TIME_RANGE.TRANSPARENT, PERUSER.TRANSPARENT from CALENDAR_OBJECT, TIME_RANGE left outer join PERUSER on INSTANCE_ID = TIME_RANGE_INSTANCE_ID and USER_ID = ? where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and coalesce(ADJUSTED_START_DATE, START_DATE) < ? and coalesce(ADJUSTED_END_DATE, END_DATE) > ? or FLOATING = ? and coalesce(ADJUSTED_START_DATE, START_DATE) < ? and coalesce(ADJUSTED_END_DATE, END_DATE) > ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?", [ 'user01', Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0), datetime.datetime(2006, 6, 5, 16, 0), True, datetime.datetime(2006, 6, 5, 13, 0), datetime.datetime(2006, 6, 5, 12, 0), 1234 ])) self.assertEqual(args, {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) self.assertEqual(usedtimerange, True)
def test_query_timerange(self): """ Basic query test - with time range """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange( **{ "start": "20060605T160000Z", "end": "20060605T170000Z" }) ], **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) ], **{"name": "VCALENDAR"})) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) expression = buildExpression(filter, self._queryFields) sql = CalDAVSQLQueryGenerator(expression, self, 1234) select, args, usedtimerange = sql.generate() self.assertEqual( select.toSQL(), SQLFragment( "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE < ? and END_DATE > ? or FLOATING = ? and START_DATE < ? and END_DATE > ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?", [ Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0), datetime.datetime(2006, 6, 5, 16, 0), True, datetime.datetime(2006, 6, 5, 13, 0), datetime.datetime(2006, 6, 5, 12, 0), 1234 ])) self.assertEqual(args, {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}) self.assertEqual(usedtimerange, True)
def test_calendar_query_wrong_timezone_elements(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) tzid1 = "Etc/GMT+1" tz1 = Component(None, pycalendar=readVTZ(tzid1)) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZone.fromCalendar(tz1), ) query.children += (caldavxml.TimeZoneID.fromString(tzid1), ) result = yield self.calendar_query( query, got_xml=None, expected_code=responsecode.BAD_REQUEST) self.assertTrue("Only one of" in result)
def test_query_extended(self): """ Extended query test - two terms with anyof """ filter = caldavxml.Filter( caldavxml.ComponentFilter( *[ caldavxml.ComponentFilter( *[ caldavxml.TimeRange(**{ "start": "20060605T160000Z", }) ], **{"name": ("VEVENT")}), caldavxml.ComponentFilter(**{"name": ("VTODO")}), ], **{ "name": "VCALENDAR", "test": "anyof" })) filter = Filter(filter) filter.child.settzinfo(Timezone(tzid="America/New_York")) expression = buildExpression(filter, self._queryFields) sql = CalDAVSQLQueryGenerator(expression, self, 1234) select, args, usedtimerange = sql.generate() self.assertEqual( select.toSQL(), SQLFragment( "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where (ICALENDAR_TYPE = ? and (FLOATING = ? and END_DATE > ? or FLOATING = ? and END_DATE > ?) or ICALENDAR_TYPE = ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?", [ 'VEVENT', False, datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 'VTODO', 1234 ])) self.assertEqual(args, {}) self.assertEqual(usedtimerange, True)
def test_index_timespan(self): data = ( ( "#1.1 Simple component - busy", "1.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """, "20080601T000000Z", "20080602T000000Z", "mailto:[email protected]", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ), ), ( "#1.2 Simple component - transparent", "1.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.2 DTSTART:20080602T120000Z DTEND:20080602T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """, "20080602T000000Z", "20080603T000000Z", "mailto:[email protected]", (('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'T'), ), ), ( "#1.3 Simple component - canceled", "1.3", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.3 DTSTART:20080603T120000Z DTEND:20080603T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] STATUS:CANCELLED END:VEVENT END:VCALENDAR """, "20080603T000000Z", "20080604T000000Z", "mailto:[email protected]", (('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'F', 'F'), ), ), ( "#1.4 Simple component - tentative", "1.4", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.4 DTSTART:20080604T120000Z DTEND:20080604T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] STATUS:TENTATIVE END:VEVENT END:VCALENDAR """, "20080604T000000Z", "20080605T000000Z", "mailto:[email protected]", (('N', "2008-06-04 12:00:00", "2008-06-04 13:00:00", 'T', 'F'), ), ), ( "#2.1 Recurring component - busy", "2.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.1 DTSTART:20080605T120000Z DTEND:20080605T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=2 END:VEVENT END:VCALENDAR """, "20080605T000000Z", "20080607T000000Z", "mailto:[email protected]", ( ('N', "2008-06-05 12:00:00", "2008-06-05 13:00:00", 'B', 'F'), ('N', "2008-06-06 12:00:00", "2008-06-06 13:00:00", 'B', 'F'), ), ), ( "#2.2 Recurring component - busy", "2.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.2 DTSTART:20080607T120000Z DTEND:20080607T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=2 END:VEVENT BEGIN:VEVENT UID:12345-67890-2.2 RECURRENCE-ID:20080608T120000Z DTSTART:20080608T140000Z DTEND:20080608T150000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """, "20080607T000000Z", "20080609T000000Z", "mailto:[email protected]", ( ('N', "2008-06-07 12:00:00", "2008-06-07 13:00:00", 'B', 'F'), ('N', "2008-06-08 14:00:00", "2008-06-08 15:00:00", 'B', 'T'), ), ), ) for description, name, calendar_txt, trstart, trend, organizer, instances in data: calendar = Component.fromString(calendar_txt) with open(os.path.join(self.indexDirPath.path, name), "w") as f: f.write(calendar_txt) self.db.addResource(name, calendar) self.assertTrue(self.db.resourceExists(name), msg=description) # Create fake filter element to match time-range filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( TimeRange( start=trstart, end=trend, ), name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"), ), name="VCALENDAR", )) filter = Filter(filter) resources = yield self.db.indexedSearch(filter, fbtype=True) index_results = set() for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources: self.assertEqual(test_organizer, organizer, msg=description) index_results.add(( float, start, end, fbtype, transp, )) self.assertEqual(set(instances), index_results, msg=description)
def _internalGenerateFreeBusyInfo( calresource, fbinfo, timerange, matchtotal, excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False, servertoserver=False, event_details=None, logItems=None, accountingItems=None, ): """ Run a free busy report on the specified calendar collection accumulating the free busy info for later processing. @param calresource: the L{Calendar} for a calendar collection. @param fbinfo: the array of busy periods to update. @param timerange: the L{TimeRange} for the query. @param matchtotal: the running total for the number of matches. @param excludeuid: a C{str} containing a UID value to exclude any components with that UID from contributing to free-busy. @param organizer: a C{str} containing the value of the ORGANIZER property in the VFREEBUSY request. This is used in conjunction with the UID value to process exclusions. @param same_calendar_user: a C{bool} indicating whether the calendar user requesting the free-busy information is the same as the calendar user being targeted. @param servertoserver: a C{bool} indicating whether we are doing a local or remote lookup request. @param event_details: a C{list} into which to store extended VEVENT details if not C{None} @param logItems: a C{dict} to store logging info to @param accountingItems: a C{dict} to store accounting info to """ # First check the privilege on this collection # TODO: for server-to-server we bypass this right now as we have no way to authorize external users. # TODO: actually we by pass altogether by assuming anyone can check anyone else's freebusy # May need organizer principal organizer_record = (yield calresource.directoryService( ).recordWithCalendarUserAddress(organizer)) if organizer else None organizer_uid = organizer_record.uid if organizer_record else "" # Free busy is per-user attendee_uid = calresource.viewerHome().uid() attendee_record = yield calresource.directoryService().recordWithUID( attendee_uid.decode("utf-8")) # Get the timezone property from the collection. tz = calresource.getTimezone() # Look for possible extended free busy information rich_options = { "organizer": False, "delegate": False, "resource": False, } do_event_details = False if event_details is not None and organizer_record is not None and attendee_record is not None: # Get the principal of the authorized user which may be different from the organizer if a delegate of # the organizer is making the request authz_uid = organizer_uid authz_record = organizer_record if calresource._txn._authz_uid is not None and calresource._txn._authz_uid != organizer_uid: authz_uid = calresource._txn._authz_uid authz_record = yield calresource.directoryService().recordWithUID( authz_uid.decode("utf-8")) # Check if attendee is also the organizer or the delegate doing the request if attendee_uid in (organizer_uid, authz_uid): do_event_details = True rich_options["organizer"] = True # Check if authorized user is a delegate of attendee proxy = (yield authz_record.isProxyFor(attendee_record)) if config.Scheduling.Options.DelegeteRichFreeBusy and proxy: do_event_details = True rich_options["delegate"] = True # Check if attendee is room or resource if config.Scheduling.Options.RoomResourceRichFreeBusy and attendee_record.getCUType( ) in ( "RESOURCE", "ROOM", ): do_event_details = True rich_options["resource"] = True # Try cache resources = (yield FBCacheEntry.getCacheEntry( calresource, attendee_uid, timerange)) if config.EnableFreeBusyCache else None if resources is None: if accountingItems is not None: accountingItems["fb-uncached"] = accountingItems.get( "fb-uncached", 0) + 1 caching = False if config.EnableFreeBusyCache: # Log extended item if logItems is not None: logItems["fb-uncached"] = logItems.get("fb-uncached", 0) + 1 # We want to cache a large range of time based on the current date cache_start = normalizeToUTC(DateTime.getToday() + Duration( days=0 - config.FreeBusyCacheDaysBack)) cache_end = normalizeToUTC(DateTime.getToday() + Duration( days=config.FreeBusyCacheDaysForward)) # If the requested time range would fit in our allowed cache range, trigger the cache creation if compareDateTime(timerange.start, cache_start) >= 0 and compareDateTime( timerange.end, cache_end) <= 0: cache_timerange = TimeRange(start=cache_start.getText(), end=cache_end.getText()) caching = True # # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range. # We then take those results and merge them into one VFREEBUSY component # with appropriate FREEBUSY properties, and return that single item as iCal data. # # Create fake filter element to match time-range filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( cache_timerange if caching else timerange, name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"), ), name="VCALENDAR", )) filter = Filter(filter) tzinfo = filter.settimezone(tz) if accountingItems is not None: tr = cache_timerange if caching else timerange accountingItems["fb-query-timerange"] = ( str(tr.start), str(tr.end), ) try: resources = yield calresource.search(filter, useruid=attendee_uid, fbtype=True) if caching: yield FBCacheEntry.makeCacheEntry(calresource, attendee_uid, cache_timerange, resources) except IndexedSearchException: raise InternalDataStoreError("Invalid indexedSearch query") else: if accountingItems is not None: accountingItems["fb-cached"] = accountingItems.get("fb-cached", 0) + 1 # Log extended item if logItems is not None: logItems["fb-cached"] = logItems.get("fb-cached", 0) + 1 # Determine appropriate timezone (UTC is the default) tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True) # We care about separate instances for VEVENTs only aggregated_resources = {} for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources: if transp == 'T' and fbtype != '?': fbtype = 'F' aggregated_resources.setdefault(( name, uid, type, test_organizer, ), []).append(( float, start, end, fbtype, )) if accountingItems is not None: accountingItems["fb-resources"] = {} for k, v in aggregated_resources.items(): name, uid, type, test_organizer = k accountingItems["fb-resources"][uid] = [] for float, start, end, fbtype in v: fbstart = parseSQLTimestampToPyCalendar(start) if float == 'Y': fbstart.setTimezone(tzinfo) else: fbstart.setTimezone(Timezone(utc=True)) fbend = parseSQLTimestampToPyCalendar(end) if float == 'Y': fbend.setTimezone(tzinfo) else: fbend.setTimezone(Timezone(utc=True)) accountingItems["fb-resources"][uid].append(( float, str(fbstart), str(fbend), fbtype, )) # Cache directory record lookup outside this loop as it is expensive and will likely # always end up being called with the same organizer address. recordUIDCache = {} for key in aggregated_resources.iterkeys(): name, uid, type, test_organizer = key # Short-cut - if an fbtype exists we can use that if type == "VEVENT" and aggregated_resources[key][0][3] != '?': matchedResource = False # Look at each instance for float, start, end, fbtype in aggregated_resources[key]: # Ignore free time or unknown if fbtype in ('F', '?'): continue # Ignore ones of this UID if excludeuid: # See if we have a UID match if (excludeuid == uid): if test_organizer: test_uid = recordUIDCache.get(test_organizer) if test_uid is None: test_record = (yield calresource.directoryService( ).recordWithCalendarUserAddress( test_organizer)) test_uid = test_record.uid if test_record else "" recordUIDCache[test_organizer] = test_uid else: test_uid = "" # Check that ORGANIZER's match (security requirement) if (organizer is None) or (organizer_uid == test_uid): continue # Check for no ORGANIZER and check by same calendar user elif (test_uid == "") and same_calendar_user: continue # Apply a timezone to any floating times fbstart = parseSQLTimestampToPyCalendar(start) if float == 'Y': fbstart.setTimezone(tzinfo) else: fbstart.setTimezone(Timezone(utc=True)) fbend = parseSQLTimestampToPyCalendar(end) if float == 'Y': fbend.setTimezone(tzinfo) else: fbend.setTimezone(Timezone(utc=True)) # Clip instance to time range clipped = clipPeriod(Period(fbstart, duration=fbend - fbstart), Period(timerange.start, timerange.end)) # Double check for overlap if clipped: matchedResource = True fbinfo[fbtype_index_mapper.get(fbtype, 0)].append(clipped) if matchedResource: # Check size of results is within limit matchtotal += 1 if matchtotal > config.MaxQueryWithDataResults: raise QueryMaxResources(config.MaxQueryWithDataResults, matchtotal) # Add extended details if do_event_details: child = (yield calresource.calendarObjectWithName(name)) # Only add fully public events if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC: calendar = (yield child.componentForUser()) _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo) else: child = (yield calresource.calendarObjectWithName(name)) calendar = (yield child.componentForUser()) # The calendar may come back as None if the resource is being changed, or was deleted # between our initial index query and getting here. For now we will ignore this error, but in # the longer term we need to implement some form of locking, perhaps. if calendar is None: log.error( "Calendar %s is missing from calendar collection %r" % (name, calresource)) continue # Ignore ones of this UID if excludeuid: # See if we have a UID match if (excludeuid == uid): test_organizer = calendar.getOrganizer() if test_organizer: test_uid = recordUIDCache.get(test_organizer) if test_uid is None: test_record = (yield calresource.directoryService( ).recordWithCalendarUserAddress(test_organizer)) test_uid = test_record.uid if test_record else "" recordUIDCache[test_organizer] = test_uid else: test_uid = "" # Check that ORGANIZER's match (security requirement) if (organizer is None) or (organizer_uid == test_uid): continue # Check for no ORGANIZER and check by same calendar user elif (test_organizer is None) and same_calendar_user: continue if accountingItems is not None: accountingItems.setdefault("fb-filter-match", []).append(uid) if filter.match(calendar, None): if accountingItems is not None: accountingItems.setdefault("fb-filter-matched", []).append(uid) # Check size of results is within limit matchtotal += 1 if matchtotal > config.MaxQueryWithDataResults: raise QueryMaxResources(config.MaxQueryWithDataResults, matchtotal) if calendar.mainType() == "VEVENT": processEventFreeBusy(calendar, fbinfo, timerange, tzinfo) elif calendar.mainType() == "VFREEBUSY": processFreeBusyFreeBusy(calendar, fbinfo, timerange) elif calendar.mainType() == "VAVAILABILITY": processAvailabilityFreeBusy(calendar, fbinfo, timerange) else: assert "Free-busy query returned unwanted component: %s in %r", ( name, calresource, ) # Add extended details if calendar.mainType() == "VEVENT" and do_event_details: child = (yield calresource.calendarObjectWithName(name)) # Only add fully public events if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC: calendar = (yield child.componentForUser()) _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo) returnValue(matchtotal)
def generateFreeBusyInfo( request, calresource, fbinfo, timerange, matchtotal, excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False, servertoserver=False, event_details=None, ): """ Run a free busy report on the specified calendar collection accumulating the free busy info for later processing. @param request: the L{IRequest} for the current request. @param calresource: the L{CalDAVResource} for a calendar collection. @param fbinfo: the array of busy periods to update. @param timerange: the L{TimeRange} for the query. @param matchtotal: the running total for the number of matches. @param excludeuid: a C{str} containing a UID value to exclude any components with that UID from contributing to free-busy. @param organizer: a C{str} containing the value of the ORGANIZER property in the VFREEBUSY request. This is used in conjunction with the UID value to process exclusions. @param same_calendar_user: a C{bool} indicating whether the calendar user requesting the free-busy information is the same as the calendar user being targeted. @param servertoserver: a C{bool} indicating whether we are doing a local or remote lookup request. @param event_details: a C{list} into which to store extended VEVENT details if not C{None} """ # First check the privilege on this collection # TODO: for server-to-server we bypass this right now as we have no way to authorize external users. if not servertoserver: try: yield calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(), ), principal=organizerPrincipal) except AccessDeniedError: returnValue(matchtotal) # May need organizer principal organizer_principal = (yield calresource.principalForCalendarUserAddress( organizer)) if organizer else None organizer_uid = organizer_principal.principalUID( ) if organizer_principal else "" # Free busy is per-user userPrincipal = (yield calresource.resourceOwnerPrincipal(request)) if userPrincipal: useruid = userPrincipal.principalUID() else: useruid = "" # Get the timezone property from the collection. has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request)) if has_prop: tz = (yield calresource.readProperty(CalendarTimeZone(), request)) else: tz = None # Look for possible extended free busy information rich_options = { "organizer": False, "delegate": False, "resource": False, } do_event_details = False if event_details is not None and organizer_principal is not None and userPrincipal is not None: # Check if organizer is attendee if organizer_principal == userPrincipal: do_event_details = True rich_options["organizer"] = True # Check if organizer is a delegate of attendee proxy = (yield organizer_principal.isProxyFor(userPrincipal)) if config.Scheduling.Options.DelegeteRichFreeBusy and proxy: do_event_details = True rich_options["delegate"] = True # Check if attendee is room or resource if config.Scheduling.Options.RoomResourceRichFreeBusy and userPrincipal.getCUType( ) in ( "RESOURCE", "ROOM", ): do_event_details = True rich_options["resource"] = True # Try cache resources = (yield FBCacheEntry.getCacheEntry( calresource, useruid, timerange)) if config.EnableFreeBusyCache else None if resources is None: caching = False if config.EnableFreeBusyCache: # Log extended item if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems[ "fb-uncached"] = request.extendedLogItems.get( "fb-uncached", 0) + 1 # We want to cache a large range of time based on the current date cache_start = normalizeToUTC(DateTime.getToday() + Duration( days=0 - config.FreeBusyCacheDaysBack)) cache_end = normalizeToUTC(DateTime.getToday() + Duration( days=config.FreeBusyCacheDaysForward)) # If the requested timerange would fit in our allowed cache range, trigger the cache creation if compareDateTime(timerange.start, cache_start) >= 0 and compareDateTime( timerange.end, cache_end) <= 0: cache_timerange = TimeRange(start=cache_start.getText(), end=cache_end.getText()) caching = True # # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range. # We then take those results and merge them into one VFREEBUSY component # with appropriate FREEBUSY properties, and return that single item as iCal data. # # Create fake filter element to match time-range filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( cache_timerange if caching else timerange, name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"), ), name="VCALENDAR", )) filter = Filter(filter) tzinfo = filter.settimezone(tz) try: resources = yield calresource.search(filter, useruid=useruid, fbtype=True) if caching: yield FBCacheEntry.makeCacheEntry(calresource, useruid, cache_timerange, resources) except IndexedSearchException: raise HTTPError( StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "Failed freebusy query")) else: # Log extended item if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems["fb-cached"] = request.extendedLogItems.get( "fb-cached", 0) + 1 # Determine appropriate timezone (UTC is the default) tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True) # We care about separate instances for VEVENTs only aggregated_resources = {} for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources: if transp == 'T' and fbtype != '?': fbtype = 'F' aggregated_resources.setdefault(( name, uid, type, test_organizer, ), []).append(( float, start, end, fbtype, )) for key in aggregated_resources.iterkeys(): name, uid, type, test_organizer = key # Short-cut - if an fbtype exists we can use that if type == "VEVENT" and aggregated_resources[key][0][3] != '?': matchedResource = False # Look at each instance for float, start, end, fbtype in aggregated_resources[key]: # Ignore free time or unknown if fbtype in ('F', '?'): continue # Ignore ones of this UID if excludeuid: # See if we have a UID match if (excludeuid == uid): test_principal = ( yield calresource.principalForCalendarUserAddress( test_organizer)) if test_organizer else None test_uid = test_principal.principalUID( ) if test_principal else "" # Check that ORGANIZER's match (security requirement) if (organizer is None) or (organizer_uid == test_uid): continue # Check for no ORGANIZER and check by same calendar user elif (test_uid == "") and same_calendar_user: continue # Apply a timezone to any floating times fbstart = parseSQLTimestampToPyCalendar(start) if float == 'Y': fbstart.setTimezone(tzinfo) else: fbstart.setTimezone(Timezone(utc=True)) fbend = parseSQLTimestampToPyCalendar(end) if float == 'Y': fbend.setTimezone(tzinfo) else: fbend.setTimezone(Timezone(utc=True)) # Clip instance to time range clipped = clipPeriod(Period(fbstart, duration=fbend - fbstart), Period(timerange.start, timerange.end)) # Double check for overlap if clipped: matchedResource = True fbinfo[fbtype_index_mapper.get(fbtype, 0)].append(clipped) if matchedResource: # Check size of results is within limit matchtotal += 1 if matchtotal > max_number_of_matches: raise NumberOfMatchesWithinLimits(max_number_of_matches) # Add extended details if do_event_details: child = (yield request.locateChildResource(calresource, name)) calendar = (yield child.iCalendarForUser(request)) _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo) else: child = (yield request.locateChildResource(calresource, name)) calendar = (yield child.iCalendarForUser(request)) # The calendar may come back as None if the resource is being changed, or was deleted # between our initial index query and getting here. For now we will ignore this error, but in # the longer term we need to implement some form of locking, perhaps. if calendar is None: log.error( "Calendar %s is missing from calendar collection %r" % (name, calresource)) continue # Ignore ones of this UID if excludeuid: # See if we have a UID match if (excludeuid == uid): test_organizer = calendar.getOrganizer() test_principal = ( yield calresource.principalForCalendarUserAddress( test_organizer)) if test_organizer else None test_uid = test_principal.principalUID( ) if test_principal else "" # Check that ORGANIZER's match (security requirement) if (organizer is None) or (organizer_uid == test_uid): continue # Check for no ORGANIZER and check by same calendar user elif (test_organizer is None) and same_calendar_user: continue if filter.match(calendar, None): # Check size of results is within limit matchtotal += 1 if matchtotal > max_number_of_matches: raise NumberOfMatchesWithinLimits(max_number_of_matches) if calendar.mainType() == "VEVENT": processEventFreeBusy(calendar, fbinfo, timerange, tzinfo) elif calendar.mainType() == "VFREEBUSY": processFreeBusyFreeBusy(calendar, fbinfo, timerange) elif calendar.mainType() == "VAVAILABILITY": processAvailabilityFreeBusy(calendar, fbinfo, timerange) else: assert "Free-busy query returned unwanted component: %s in %r", ( name, calresource, ) # Add extended details if calendar.mainType() == "VEVENT" and do_event_details: child = (yield request.locateChildResource(calresource, name)) calendar = (yield child.iCalendarForUser(request)) _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo) returnValue(matchtotal)
def test_calendar_query_time_range(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData( caldavxml.CalendarComponent( caldavxml.AllProperties(), caldavxml.CalendarComponent( caldavxml.Property(name="X-ABC-GUID"), caldavxml.Property(name="UID"), caldavxml.Property(name="DTSTART"), caldavxml.Property(name="DTEND"), caldavxml.Property(name="DURATION"), caldavxml.Property(name="EXDATE"), caldavxml.Property(name="EXRULE"), caldavxml.Property(name="RDATE"), caldavxml.Property(name="RRULE"), caldavxml.Property(name="LOCATION"), caldavxml.Property(name="SUMMARY"), name="VEVENT", ), caldavxml.CalendarComponent( caldavxml.AllProperties(), caldavxml.AllComponents(), name="VTIMEZONE", ), name="VCALENDAR", ), ), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) for response in doc.root_element.childrenOfType( davxml.PropertyStatusResponse): properties_to_find = [p.qname() for p in calendar_properties] for propstat in response.childrenOfType(davxml.PropertyStatus): status = propstat.childOfType(davxml.Status) properties = propstat.childOfType( davxml.PropertyContainer).children if status.code != responsecode.OK: self.fail( "REPORT failed (status %s) to locate properties: %r" % (status.code, properties)) for property in properties: qname = property.qname() if qname in properties_to_find: properties_to_find.remove(qname) else: self.fail( "REPORT found property we didn't ask for: %r" % (property, )) if isinstance(property, caldavxml.CalendarData): cal = property.calendar() instances = cal.expandTimeRanges( query_timerange.end) vevents = [ x for x in cal.subcomponents() if x.name() == "VEVENT" ] if not TimeRange(query_timerange).matchinstance( vevents[0], instances): self.fail( "REPORT property %r returned calendar %s outside of request time range %r" % (property, property.calendar, query_timerange)) return self.calendar_query(query, got_xml)
def simple_event_query(self, event_filter, uids, withData=True): props = (davxml.GETETag(), ) if withData: props += (caldavxml.CalendarData(), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*props), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( event_filter, name="VEVENT", ), name="VCALENDAR", ), ), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) for response in doc.root_element.childrenOfType( davxml.PropertyStatusResponse): for propstat in response.childrenOfType(davxml.PropertyStatus): status = propstat.childOfType(davxml.Status) if status.code != responsecode.OK: self.fail( "REPORT failed (status %s) to locate properties: %r" % (status.code, propstat)) properties = propstat.childOfType( davxml.PropertyContainer).children for property in properties: qname = property.qname() if qname == (davxml.dav_namespace, "getetag"): continue if qname != (caldavxml.caldav_namespace, "calendar-data"): self.fail( "Response included unexpected property %r" % (property, )) result_calendar = property.calendar() if result_calendar is None: self.fail( "Invalid response CalDAV:calendar-data: %r" % (property, )) uid = result_calendar.resourceUID() if uid in uids: uids.remove(uid) else: self.fail("Got calendar for unexpected UID %r" % (uid, )) original_filename = file( os.path.join(self.holidays_dir, uid + ".ics")) original_calendar = ical.Component.fromStream( original_filename) self.assertEqual(result_calendar, original_calendar) return self.calendar_query(query, got_xml)
def _cancelEvents(self, txn, uid, cuas): # Anything in the past is left alone whenString = self.when.getText() query_filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( caldavxml.TimeRange(start=whenString,), name=("VEVENT",), ), name="VCALENDAR", ) ) query_filter = Filter(query_filter) count = 0 txn = self.store.newTransaction() storeCalHome = yield txn.calendarHomeWithUID(uid) calendarNames = yield storeCalHome.listCalendars() yield txn.commit() for calendarName in calendarNames: txn = self.store.newTransaction(authz_uid=uid) storeCalHome = yield txn.calendarHomeWithUID(uid) calendar = yield storeCalHome.calendarWithName(calendarName) allChildNames = [] futureChildNames = set() # Only purge owned calendars if calendar.owned(): # all events for childName in (yield calendar.listCalendarObjects()): allChildNames.append(childName) # events matching filter for childName, _ignore_childUid, _ignore_childType in (yield calendar.search(query_filter)): futureChildNames.add(childName) yield txn.commit() for childName in allChildNames: txn = self.store.newTransaction(authz_uid=uid) storeCalHome = yield txn.calendarHomeWithUID(uid) calendar = yield storeCalHome.calendarWithName(calendarName) doScheduling = childName in futureChildNames try: childResource = yield calendar.calendarObjectWithName(childName) uri = "/calendars/__uids__/%s/%s/%s" % (storeCalHome.uid(), calendar.name(), childName) incrementCount = self.dryrun if self.verbose: if self.dryrun: print("Would delete%s: %s" % (" with scheduling" if doScheduling else "", uri,)) else: print("Deleting%s: %s" % (" with scheduling" if doScheduling else "", uri,)) if not self.dryrun: retry = False try: yield childResource.purge(implicitly=doScheduling) incrementCount = True except Exception, e: print("Exception deleting %s: %s" % (uri, str(e))) retry = True if retry and doScheduling: # Try again with implicit scheduling off print("Retrying deletion of %s with scheduling turned off" % (uri,)) try: yield childResource.purge(implicitly=False) incrementCount = True except Exception, e: print("Still couldn't delete %s even with scheduling turned off: %s" % (uri, str(e))) if incrementCount: count += 1 # Commit yield txn.commit() except Exception, e: # Abort yield txn.abort() raise e
def test_index_timespan_per_user(self): data = ( ( "#1.1 Single per-user non-recurring component", "1.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.1 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080602T000000Z", "mailto:[email protected]", ( ( "user01", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ), ), ( "user02", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ), ), ), ), ( "#1.2 Two per-user non-recurring component", "1.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.2 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user02 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080602T000000Z", "mailto:[email protected]", ( ( "user01", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ), ), ( "user02", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ), ), ( "user03", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ), ), ), ), ( "#2.1 Single per-user simple recurring component", "2.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=10 END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.1 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080603T000000Z", "mailto:[email protected]", ( ( "user01", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'T'), ), ), ( "user02", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'F'), ), ), ), ), ( "#2.2 Two per-user simple recurring component", "2.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.2 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=10 END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user02 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080603T000000Z", "mailto:[email protected]", ( ( "user01", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'T'), ), ), ( "user02", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'F'), ), ), ( "user03", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'F'), ), ), ), ), ( "#3.1 Single per-user complex recurring component", "3.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=10 END:VEVENT BEGIN:VEVENT UID:12345-67890-1.1 RECURRENCE-ID:20080602T120000Z DTSTART:20080602T130000Z DTEND:20080602T140000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.1 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE BEGIN:X-CALENDARSERVER-PERINSTANCE RECURRENCE-ID:20080602T120000Z TRANSP:OPAQUE END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080604T000000Z", "mailto:[email protected]", ( ( "user01", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'T'), ), ), ( "user02", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'F'), ), ), ), ), ( "#3.2 Two per-user complex recurring component", "3.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.2 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=10 END:VEVENT BEGIN:VEVENT UID:12345-67890-1.2 RECURRENCE-ID:20080602T120000Z DTSTART:20080602T130000Z DTEND:20080602T140000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE BEGIN:X-CALENDARSERVER-PERINSTANCE RECURRENCE-ID:20080602T120000Z TRANSP:OPAQUE END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user02 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM END:X-CALENDARSERVER-PERINSTANCE BEGIN:X-CALENDARSERVER-PERINSTANCE RECURRENCE-ID:20080603T120000Z TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080604T000000Z", "mailto:[email protected]", ( ( "user01", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'T'), ), ), ( "user02", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'T'), ), ), ( "user03", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'F'), ), ), ), ), ) for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data: calendar = Component.fromString(calendar_txt) with open(os.path.join(self.indexDirPath.path, name), "w") as f: f.write(calendar_txt) self.db.addResource(name, calendar) self.assertTrue(self.db.resourceExists(name), msg=description) # Create fake filter element to match time-range filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( TimeRange( start=trstart, end=trend, ), name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"), ), name="VCALENDAR", )) filter = Filter(filter) for useruid, instances in peruserinstances: resources = yield self.db.indexedSearch(filter, useruid=useruid, fbtype=True) index_results = set() for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources: self.assertEqual(test_organizer, organizer, msg=description) index_results.add(( str(float), str(start), str(end), str(fbtype), str(transp), )) self.assertEqual(set(instances), index_results, msg="%s, user:%s" % ( description, useruid, )) self.db.deleteResource(name)
def _matchCalendarResources(self, calresource): # Get the timezone property from the collection. tz = calresource.getTimezone() # Try cache aggregated_resources = (yield FBCacheEntry.getCacheEntry(calresource, self.attendee_uid, self.timerange)) if config.EnableFreeBusyCache else None if aggregated_resources is None: if self.accountingItems is not None: self.accountingItems["fb-uncached"] = self.accountingItems.get("fb-uncached", 0) + 1 caching = False if config.EnableFreeBusyCache: # Log extended item if self.logItems is not None: self.logItems["fb-uncached"] = self.logItems.get("fb-uncached", 0) + 1 # We want to cache a large range of time based on the current date cache_start = normalizeToUTC(DateTime.getToday() + Duration(days=0 - config.FreeBusyCacheDaysBack)) cache_end = normalizeToUTC(DateTime.getToday() + Duration(days=config.FreeBusyCacheDaysForward)) # If the requested time range would fit in our allowed cache range, trigger the cache creation if compareDateTime(self.timerange.getStart(), cache_start) >= 0 and compareDateTime(self.timerange.getEnd(), cache_end) <= 0: cache_timerange = Period(cache_start, cache_end) caching = True # # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range. # We then take those results and merge them into one VFREEBUSY component # with appropriate FREEBUSY properties, and return that single item as iCal data. # # Create fake filter element to match time-range tr = TimeRange( start=(cache_timerange if caching else self.timerange).getStart().getText(), end=(cache_timerange if caching else self.timerange).getEnd().getText(), ) filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( tr, name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"), ), name="VCALENDAR", ) ) filter = Filter(filter) tzinfo = filter.settimezone(tz) if self.accountingItems is not None: self.accountingItems["fb-query-timerange"] = (str(tr.start), str(tr.end),) try: resources = yield calresource.search(filter, useruid=self.attendee_uid, fbtype=True) aggregated_resources = {} for name, uid, comptype, test_organizer, float, start, end, fbtype, transp in resources: if transp == 'T' and fbtype != '?': fbtype = 'F' aggregated_resources.setdefault((name, uid, comptype, test_organizer,), []).append(( float, tupleFromDateTime(parseSQLTimestampToPyCalendar(start)), tupleFromDateTime(parseSQLTimestampToPyCalendar(end)), fbtype, )) if caching: yield FBCacheEntry.makeCacheEntry(calresource, self.attendee_uid, cache_timerange, aggregated_resources) except IndexedSearchException: raise InternalDataStoreError("Invalid indexedSearch query") else: if self.accountingItems is not None: self.accountingItems["fb-cached"] = self.accountingItems.get("fb-cached", 0) + 1 # Log extended item if self.logItems is not None: self.logItems["fb-cached"] = self.logItems.get("fb-cached", 0) + 1 # Determine appropriate timezone (UTC is the default) tzinfo = tz.gettimezone() if tz is not None else Timezone.UTCTimezone filter = None returnValue((aggregated_resources, tzinfo, filter,))
def test_index_timerange(self): """ A plain (not freebusy) time range test. """ data = ( ( "#1.1 Simple component - busy", "1.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """, "20080601T000000Z", "20080602T000000Z", ), ( "#1.2 Simple component - transparent", "1.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.2 DTSTART:20080602T120000Z DTEND:20080602T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """, "20080602T000000Z", "20080603T000000Z", ), ( "#1.3 Simple component - canceled", "1.3", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.3 DTSTART:20080603T120000Z DTEND:20080603T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] STATUS:CANCELLED END:VEVENT END:VCALENDAR """, "20080603T000000Z", "20080604T000000Z", ), ( "#1.4 Simple component - tentative", "1.4", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.4 DTSTART:20080604T120000Z DTEND:20080604T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] STATUS:TENTATIVE END:VEVENT END:VCALENDAR """, "20080604T000000Z", "20080605T000000Z", ), ( "#2.1 Recurring component - busy", "2.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.1 DTSTART:20080605T120000Z DTEND:20080605T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=2 END:VEVENT END:VCALENDAR """, "20080605T000000Z", "20080607T000000Z", ), ( "#2.2 Recurring component - busy", "2.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.2 DTSTART:20080607T120000Z DTEND:20080607T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=2 END:VEVENT BEGIN:VEVENT UID:12345-67890-2.2 RECURRENCE-ID:20080608T120000Z DTSTART:20080608T140000Z DTEND:20080608T150000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """, "20080607T000000Z", "20080609T000000Z", ), ) for description, name, calendar_txt, trstart, trend in data: calendar = Component.fromString(calendar_txt) f = open(os.path.join(self.indexDirPath.path, name), "w") f.write(calendar_txt) del f self.db.addResource(name, calendar) self.assertTrue(self.db.resourceExists(name), msg=description) # Create fake filter element to match time-range filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( TimeRange( start=trstart, end=trend, ), name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"), ), name="VCALENDAR", )) filter = Filter(filter) resources = yield self.db.indexedSearch(filter) index_results = set() for found_name, _ignore_uid, _ignore_type in resources: index_results.add(found_name) self.assertEqual(set((name, )), index_results, msg=description)