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 _doRefresh(tzpath, xmlfile, tzdb, tzvers):
    """
    Refresh data from IANA.
    """

    print("Downloading latest data from IANA")
    if tzvers:
        path = "https://www.iana.org/time-zones/repository/releases/tzdata%s.tar.gz" % (tzvers,)
    else:
        path = "https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz"
    data = urllib.urlretrieve(path)
    print("Extract data at: %s" % (data[0]))
    rootdir = tempfile.mkdtemp()
    zonedir = os.path.join(rootdir, "tzdata")
    os.mkdir(zonedir)
    with tarfile.open(data[0], "r:gz") as t:
        t.extractall(zonedir)

    # Get the version from the Makefile
    try:
        makefile = open(os.path.join(zonedir, "Makefile")).read()
        lines = makefile.splitlines()
        for line in lines:
            if line.startswith("VERSION="):
                tzvers = line[8:].strip()
                break
    except IOError:
        pass

    if not tzvers:
        tzvers = DateTime.getToday().getText()
    print("Converting data (version: %s) at: %s" % (tzvers, zonedir,))
    startYear = 1800
    endYear = DateTime.getToday().getYear() + 10
    Calendar.sProdID = "-//calendarserver.org//Zonal//EN"
    zonefiles = "northamerica", "southamerica", "europe", "africa", "asia", "australasia", "antarctica", "etcetera", "backward"
    parser = tzconvert()
    for file in zonefiles:
        parser.parse(os.path.join(zonedir, file))

    parser.generateZoneinfoFiles(os.path.join(rootdir, "zoneinfo"), startYear, endYear, filterzones=())
    print("Copy new zoneinfo to destination: %s" % (tzpath,))
    z = FilePath(os.path.join(rootdir, "zoneinfo"))
    tz = FilePath(tzpath)
    z.copyTo(tz)
    print("Updating XML file at: %s" % (xmlfile,))
    tzdb.readDatabase()
    tzdb.updateDatabase()
    print("Current total: %d" % (len(tzdb.timezones),))
    print("Total Changed: %d" % (tzdb.changeCount,))
    if tzdb.changeCount:
        print("Changed:")
        for k in sorted(tzdb.changed):
            print("  %s" % (k,))

    versfile = os.path.join(os.path.dirname(xmlfile), "version.txt")
    print("Updating version file at: %s" % (versfile,))
    with open(versfile, "w") as f:
        f.write(TimezoneCache.IANA_VERSION_PREFIX + tzvers)
def setUpCalendarStore(test):
    test.root = FilePath(test.mktemp())
    test.root.createDirectory()

    storeRootPath = test.storeRootPath = test.root.child("store")
    calendarPath = storeRootPath.child("calendars").child("__uids__")
    calendarPath.parent().makedirs()
    storePath.copyTo(calendarPath)

    # Set year values to current year
    nowYear = DateTime.getToday().getYear()
    for home in calendarPath.child("ho").child("me").children():
        if not home.basename().startswith("."):
            for calendar in home.children():
                if not calendar.basename().startswith("."):
                    for resource in calendar.children():
                        if resource.basename().endswith(".ics"):
                            resource.setContent(resource.getContent() % {"now": nowYear})

    testID = test.id()
    test.calendarStore = CalendarStore(
        storeRootPath,
        {"push": test.notifierFactory} if test.notifierFactory else {},
        buildDirectory(),
        quota=deriveQuota(test),
    )
    test.txn = test.calendarStore.newTransaction(testID + "(old)")
    assert test.calendarStore is not None, "No calendar store?"
Example #4
0
def updateToCurrentYear(data):
    """
    Update the supplied iCalendar data so that all dates are updated to the current year.
    """

    nowYear = DateTime.getToday().getYear()
    return data % {"now": nowYear}
    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)
Example #6
0
def updateToCurrentYear(data):
    """
    Update the supplied iCalendar data so that all dates are updated to the
    current year.
    """

    subs = {}
    nowYear = DateTime.getToday().getYear()
    subs["now"] = nowYear
    for i in range(1, 10):
        subs["now-{}".format(i)] = nowYear - 1
        subs["now+{}".format(i)] = nowYear + 1
    return data % subs
    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)
Example #8
0
    def command_purgeOldEvents(self, command):
        """
        Convert RetainDays from the command dictionary into a date, then purge
        events older than that date.

        @param command: the dictionary parsed from the plist read from stdin
        @type command: C{dict}
        """
        retainDays = command.get("RetainDays", DEFAULT_RETAIN_DAYS)
        cutoff = DateTime.getToday()
        cutoff.setDateOnly(False)
        cutoff.offsetDay(-retainDays)
        eventCount = (yield PurgeOldEventsService.purgeOldEvents(self.store, cutoff, DEFAULT_BATCH_SIZE))
        self.respond(command, {'EventsRemoved': eventCount, "RetainDays": retainDays})
Example #9
0
    def purgeAttachments(cls, store, uuid, days, limit, dryrun, verbose):

        service = cls(store)
        service.uuid = uuid
        if days > 0:
            cutoff = DateTime.getToday()
            cutoff.setDateOnly(False)
            cutoff.offsetDay(-days)
            service.cutoff = cutoff
        else:
            service.cutoff = None
        service.batchSize = limit
        service.dryrun = dryrun
        service.verbose = verbose
        result = (yield service.doWork())
        returnValue(result)
Example #10
0
def componentUpdate(data):
    """
    Update the supplied iCalendar data so that all dates are updated to the current year.
    """

    if len(relativeDateSubstitutions) == 0:
        now = DateTime.getToday()

        relativeDateSubstitutions["now"] = now

        for i in range(30):
            attrname = "now_back%s" % (i + 1,)
            dt = now.duplicate()
            dt.offsetDay(-(i + 1))
            relativeDateSubstitutions[attrname] = dt

        for i in range(30):
            attrname = "now_fwd%s" % (i + 1,)
            dt = now.duplicate()
            dt.offsetDay(i + 1)
            relativeDateSubstitutions[attrname] = dt

    return Component.fromString(data.format(**relativeDateSubstitutions))
Example #11
0
def setUpCalendarStore(test):
    test.root = FilePath(test.mktemp())
    test.root.createDirectory()

    storeRootPath = test.storeRootPath = test.root.child("store")
    calendarPath = storeRootPath.child("calendars").child("__uids__")
    calendarPath.parent().makedirs()
    storePath.copyTo(calendarPath)

    # Set year values to current year
    subs = {}
    nowYear = DateTime.getToday().getYear()
    subs["now"] = nowYear
    for i in range(1, 10):
        subs["now-{}".format(i)] = nowYear - 1
        subs["now+{}".format(i)] = nowYear + 1
    for home in calendarPath.child("ho").child("me").children():
        if not home.basename().startswith("."):
            for calendar in home.children():
                if not calendar.basename().startswith("."):
                    for resource in calendar.children():
                        if resource.basename().endswith(".ics"):
                            resource.setContent(resource.getContent() % subs)

    testID = test.id()
    test.counter = 0
    test.notifierFactory = StubNotifierFactory()
    test.calendarStore = CalendarStore(
        storeRootPath,
        {"push": test.notifierFactory} if test.notifierFactory else {},
        None,  # must create directory later
        quota=deriveQuota(test),
    )
    test.directory = buildTestDirectory(test.calendarStore, test.mktemp())
    test.txn = test.calendarStore.newTransaction(testID + "(old)")
    assert test.calendarStore is not None, "No calendar store?"
    def test_purgeOldEvents_old_cutoff(self):

        # Dry run
        cutoff = DateTime.getToday()
        cutoff.setDateOnly(False)
        cutoff.offsetDay(-400)

        total = (yield PurgeOldEventsService.purgeOldEvents(
            self._sqlCalendarStore,
            "ho",
            cutoff,
            2,
            dryrun=True,
            debug=True
        ))
        self.assertEquals(total, 12)

        # Actually remove
        total = (yield PurgeOldEventsService.purgeOldEvents(
            self._sqlCalendarStore,
            None,
            cutoff,
            2,
            debug=True
        ))
        self.assertEquals(total, 12)

        total = (yield PurgeOldEventsService.purgeOldEvents(
            self._sqlCalendarStore,
            "ho",
            cutoff,
            2,
            dryrun=True,
            debug=True
        ))
        self.assertEquals(total, 0)
class TestConduitAPI(MultiStoreConduitTest):
    """
    Test that the conduit api works.
    """

    nowYear = {"now": DateTime.getToday().getYear()}

    caldata1 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid1
DTSTART:{now:04d}0102T140000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:instance
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**nowYear)

    caldata1_changed = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid1
DTSTART:{now:04d}0102T140000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:instance changed
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**nowYear)

    caldata1_failed = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid1-failed
DTSTART:{now:04d}0102T140000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:instance changed
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**nowYear)

    caldata2 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid2
DTSTART:{now:04d}0102T160000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:instance
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**nowYear)

    @inlineCallbacks
    def _remoteHome(self, txn, uid):
        """
        Create a synthetic external home object that maps to the actual remote home.

        @param ownerUID: directory uid of the user's home
        @type ownerUID: L{str}
        """

        from txdav.caldav.datastore.sql_external import CalendarHomeExternal
        recipient = yield txn.store().directoryService().recordWithUID(uid)
        resourceID = yield txn.store().conduit.send_home_resource_id(txn, recipient)
        home = CalendarHomeExternal.makeSyntheticExternalHome(txn, recipient.uid, resourceID) if resourceID is not None else None
        if home:
            home._childClass = home._childClass._externalClass
        returnValue(home)

    @inlineCallbacks
    def test_remote_home(self):
        """
        Test that a remote home can be accessed.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        self.assertEqual(home.id(), home01.id())
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_homechild_listobjects(self):
        """
        Test that a remote home L{listChildren} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        children01 = yield home01.listChildren()
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        self.assertEqual(home.id(), home01.id())
        children = yield home.listChildren()
        self.assertEqual(set(children), set(children01))
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_homechild_loadallobjects(self):
        """
        Test that a remote home L{loadChildren} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        children01 = yield home01.loadChildren()
        names01 = [child.name() for child in children01]
        ids01 = [child.id() for child in children01]
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        self.assertEqual(home.id(), home01.id())
        children = yield home.loadChildren()
        names = [child.name() for child in children]
        ids = [child.id() for child in children]
        self.assertEqual(set(names), set(names01))
        self.assertEqual(set(ids), set(ids01))
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_homechild_objectwith(self):
        """
        Test that a remote home L{loadChildren} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        self.assertEqual(calendar.id(), calendar01.id())
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_loadallobjects(self):
        """
        Test that a remote home child L{objectResources} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        objects = yield calendar.objectResources()
        self.assertEqual(len(objects), 1)
        self.assertEqual(objects[0].name(), "1.ics")
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_loadallobjectswithnames(self):
        """
        Test that a remote home child L{objectResourcesWithNames} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        objects = yield calendar.objectResourcesWithNames(("2.ics",))
        self.assertEqual(len(objects), 1)
        self.assertEqual(objects[0].name(), "2.ics")
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_listobjects(self):
        """
        Test that a remote home child L{listObjectResources} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        names = yield calendar.listObjectResources()
        self.assertEqual(set(names), set(("1.ics", "2.ics",)))
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_countobjects(self):
        """
        Test that a remote home child L{countObjectResources} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        count = yield calendar.countObjectResources()
        self.assertEqual(count, 2)
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_objectwith(self):
        """
        Test that a remote home child L{objectResourceWithName} and L{objectResourceWithUID} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        resource01 = yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")

        resource = yield calendar.objectResourceWithName("2.ics")
        self.assertEqual(resource.name(), "2.ics")

        resource = yield calendar.objectResourceWithName("foo.ics")
        self.assertEqual(resource, None)

        resource = yield calendar.objectResourceWithUID("uid1")
        self.assertEqual(resource.name(), "1.ics")

        resource = yield calendar.objectResourceWithUID("foo")
        self.assertEqual(resource, None)

        resource = yield calendar.objectResourceWithID(resource01.id())
        self.assertEqual(resource.name(), "1.ics")

        resource = yield calendar.objectResourceWithID(12345)
        self.assertEqual(resource, None)

        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_resourcenameforuid(self):
        """
        Test that a remote home child L{resourceNameForUID} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")

        name = yield calendar.resourceNameForUID("uid1")
        self.assertEqual(name, "1.ics")

        name = yield calendar.resourceNameForUID("uid2")
        self.assertEqual(name, "2.ics")

        name = yield calendar.resourceNameForUID("foo")
        self.assertEqual(name, None)

        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_resourceuidforname(self):
        """
        Test that a remote home child L{resourceUIDForName} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")

        uid = yield calendar.resourceUIDForName("1.ics")
        self.assertEqual(uid, "uid1")

        uid = yield calendar.resourceUIDForName("2.ics")
        self.assertEqual(uid, "uid2")

        uid = yield calendar.resourceUIDForName("foo.ics")
        self.assertEqual(uid, None)

        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_create(self):
        """
        Test that a remote object resource L{create} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        yield home01.childWithName("calendar")
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        resource = yield calendar.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield self.commitTransaction(1)

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        resource01 = yield calendar01.objectResourceWithName("1.ics")
        self.assertEqual(resource01.id(), resource.id())
        caldata = yield resource01.component()
        self.assertEqual(str(caldata), self.caldata1)
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        resource = yield calendar.objectResourceWithName("1.ics")
        caldata = yield resource.component()
        self.assertEqual(str(caldata), self.caldata1)
        yield self.commitTransaction(1)

        # Recreate fails
        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        self.assertFailure(
            calendar.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)),
            ObjectResourceNameAlreadyExistsError,
        )
        yield self.abortTransaction(1)

    @inlineCallbacks
    def test_objectresource_setcomponent(self):
        """
        Test that a remote object resource L{setComponent} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        resource = yield calendar.objectResourceWithName("1.ics")
        changed = yield resource.setComponent(Component.fromString(self.caldata1_changed))
        self.assertFalse(changed)
        caldata = yield resource.component()
        self.assertEqual(normalize_iCalStr(str(caldata)), normalize_iCalStr(self.caldata1_changed))
        yield self.commitTransaction(1)

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        resource01 = yield calendar01.objectResourceWithName("1.ics")
        caldata = yield resource01.component()
        self.assertEqual(normalize_iCalStr(str(caldata)), normalize_iCalStr(self.caldata1_changed))
        yield self.commitTransaction(0)

        # Fail to set with different UID
        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        resource = yield calendar.objectResourceWithName("1.ics")
        self.assertFailure(
            resource.setComponent(Component.fromString(self.caldata1_failed)),
            InvalidUIDError,
        )
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_component(self):
        """
        Test that a remote object resource L{component} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")

        resource = yield calendar.objectResourceWithName("1.ics")
        caldata = yield resource.component()
        self.assertEqual(str(caldata), self.caldata1)

        resource = yield calendar.objectResourceWithName("2.ics")
        caldata = yield resource.component()
        self.assertEqual(str(caldata), self.caldata2)

        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectresource_remove(self):
        """
        Test that a remote object resource L{component} works.
        """

        home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
        self.assertTrue(home01 is not None)
        calendar01 = yield home01.childWithName("calendar")
        yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        resource = yield calendar.objectResourceWithName("1.ics")
        yield resource.remove()
        yield self.commitTransaction(1)

        resource01 = yield self.calendarObjectUnderTest(
            txn=self.theTransactionUnderTest(0),
            home="user01",
            calendar_name="calendar",
            name="1.ics",
        )
        self.assertTrue(resource01 is None)
        yield self.commitTransaction(0)

        home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01")
        self.assertTrue(home is not None)
        calendar = yield home.childWithName("calendar")
        resource = yield calendar.objectResourceWithName("1.ics")
        self.assertTrue(resource is None)
        yield self.commitTransaction(1)
Example #14
0
    def _add_to_db(self,
                   name,
                   calendar,
                   cursor=None,
                   expand_until=None,
                   reCreate=False):
        """
        Records the given calendar resource in the index with the given name.
        Resource names and UIDs must both be unique; only one resource name may
        be associated with any given UID and vice versa.
        NB This method does not commit the changes to the db - the caller
        MUST take care of that
        @param name: the name of the resource to add.
        @param calendar: a L{Calendar} object representing the resource
            contents.
        """
        uid = calendar.resourceUID()
        organizer = calendar.getOrganizer()
        if not organizer:
            organizer = ""

        # Decide how far to expand based on the component
        doInstanceIndexing = False
        master = calendar.masterComponent()
        if master is None or not calendar.isRecurring():
            # When there is no master we have a set of overridden components - index them all.
            # When there is one instance - index it.
            expand = DateTime(2100, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
            doInstanceIndexing = True
        else:
            # If migrating or re-creating or config option for delayed indexing is off, always index
            if reCreate or not config.FreeBusyIndexDelayedExpand:
                doInstanceIndexing = True

            # Duration into the future through which recurrences are expanded in the index
            # by default.  This is a caching parameter which affects the size of the index;
            # it does not affect search results beyond this period, but it may affect
            # performance of such a search.
            expand = (DateTime.getToday() +
                      Duration(days=config.FreeBusyIndexExpandAheadDays))

            if expand_until and expand_until > expand:
                expand = expand_until

            # Maximum duration into the future through which recurrences are expanded in the
            # index.  This is a caching parameter which affects the size of the index; it
            # does not affect search results beyond this period, but it may affect
            # performance of such a search.
            #
            # When a search is performed on a time span that goes beyond that which is
            # expanded in the index, we have to open each resource which may have data in
            # that time period.  In order to avoid doing that multiple times, we want to
            # cache those results.  However, we don't necessarily want to cache all
            # occurrences into some obscenely far-in-the-future date, so we cap the caching
            # period.  Searches beyond this period will always be relatively expensive for
            # resources with occurrences beyond this period.
            if expand > (DateTime.getToday() +
                         Duration(days=config.FreeBusyIndexExpandMaxDays)):
                raise IndexedSearchException()

        # Always do recurrence expansion even if we do not intend to index - we need this to double-check the
        # validity of the iCalendar recurrence data.
        try:
            instances = calendar.expandTimeRanges(
                expand, ignoreInvalidInstances=reCreate)
            recurrenceLimit = instances.limit
        except InvalidOverriddenInstanceError, e:
            log.error("Invalid instance %s when indexing %s in %s" % (
                e.rid,
                name,
                self.resource,
            ))
            raise
Example #15
0
            elif opt in ("-n", "--dry-run"):
                dryrun = True

            elif opt in ("-f", "--config"):
                configFileName = arg

            else:
                raise NotImplementedError(opt)

        if args:
            cls.usage("Too many arguments: %s" % (args,))

        if dryrun:
            verbose = True

        cutoff = DateTime.getToday()
        cutoff.setDateOnly(False)
        cutoff.offsetDay(-days)
        cls.cutoff = cutoff
        cls.batchSize = batchSize
        cls.dryrun = dryrun
        cls.verbose = verbose

        utilityMain(
            configFileName,
            cls,
            verbose=debug,
        )


    @classmethod
Example #16
0
    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)
Example #17
0
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 doExpand(self, request):
        """
        Expand a timezone within specified start/end dates.
        """

        tzids = request.args.get("tzid", ())
        if len(tzids) != 1:
            raise HTTPError(
                JSONResponse(
                    responsecode.BAD_REQUEST, {"error": "invalid-tzid", "description": "Invalid tzid query parameter"}
                )
            )

        try:
            start = request.args.get("start", ())
            if len(start) > 1:
                raise ValueError()
            elif len(start) == 1:
                start = DateTime.parseText("{}0101".format(int(start[0])))
            else:
                start = DateTime.getToday()
                start.setDay(1)
                start.setMonth(1)
        except ValueError:
            raise HTTPError(
                JSONResponse(
                    responsecode.BAD_REQUEST, {"error": "invalid-start", "description": "Invalid start query parameter"}
                )
            )

        try:
            end = request.args.get("end", ())
            if len(end) > 1:
                raise ValueError()
            elif len(end) == 1:
                end = DateTime.parseText("{}0101".format(int(end[0])))
            else:
                end = DateTime.getToday()
                end.setDay(1)
                end.setMonth(1)
                end.offsetYear(10)
            if end <= start:
                raise ValueError()
        except ValueError:
            raise HTTPError(
                JSONResponse(
                    responsecode.BAD_REQUEST, {"error": "invalid-end", "description": "Invalid end query parameter"}
                )
            )

        tzid = tzids[0]
        tzdata = self.timezones.getTimezone(tzid)
        if tzdata is None:
            raise HTTPError(
                JSONResponse(
                    responsecode.NOT_FOUND, {"error": "tzid-not-found", "description": "Tzid could not be found"}
                )
            )

        # Now do the expansion (but use a cache to avoid re-calculating TZs)
        observances = self.expandcache.get((tzid, start, end), None)
        if observances is None:
            observances = tzexpandlocal(tzdata, start, end)
            self.expandcache[(tzid, start, end)] = observances

        # Turn into JSON
        result = {
            "dtstamp": self.timezones.dtstamp,
            "observances": [
                {
                    "name": name,
                    "onset": onset.getXMLText(),
                    "utc-offset-from": utc_offset_from,
                    "utc-offset-to": utc_offset_to,
                }
                for onset, utc_offset_from, utc_offset_to, name in observances
            ],
        }
        return JSONResponse(responsecode.OK, result)
Example #19
0
def _doRefresh(tzpath, xmlfile, tzdb, tzvers):
    """
    Refresh data from IANA.
    """

    print("Downloading latest data from IANA")
    if tzvers:
        path = "https://www.iana.org/time-zones/repository/releases/tzdata%s.tar.gz" % (
            tzvers, )
    else:
        path = "https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz"
    data = urllib.urlretrieve(path)
    print("Extract data at: %s" % (data[0]))
    rootdir = tempfile.mkdtemp()
    zonedir = os.path.join(rootdir, "tzdata")
    os.mkdir(zonedir)
    with tarfile.open(data[0], "r:gz") as t:
        t.extractall(zonedir)

    # Get the version from the Makefile
    try:
        makefile = open(os.path.join(zonedir, "Makefile")).read()
        lines = makefile.splitlines()
        for line in lines:
            if line.startswith("VERSION="):
                tzvers = line[8:].strip()
                break
    except IOError:
        pass

    if not tzvers:
        tzvers = DateTime.getToday().getText()
    print("Converting data (version: %s) at: %s" % (
        tzvers,
        zonedir,
    ))
    startYear = 1800
    endYear = DateTime.getToday().getYear() + 10
    Calendar.sProdID = "-//calendarserver.org//Zonal//EN"
    zonefiles = "northamerica", "southamerica", "europe", "africa", "asia", "australasia", "antarctica", "etcetera", "backward"
    parser = tzconvert()
    for file in zonefiles:
        parser.parse(os.path.join(zonedir, file))

    # Check for windows aliases
    print("Downloading latest data from unicode.org")
    path = "http://unicode.org/repos/cldr/tags/latest/common/supplemental/windowsZones.xml"
    data = urllib.urlretrieve(path)
    wpath = data[0]

    # Generate the iCalendar data
    print("Generating iCalendar data")
    parser.generateZoneinfoFiles(os.path.join(rootdir, "zoneinfo"),
                                 startYear,
                                 endYear,
                                 windowsAliases=wpath,
                                 filterzones=())

    print("Copy new zoneinfo to destination: %s" % (tzpath, ))
    z = FilePath(os.path.join(rootdir, "zoneinfo"))
    tz = FilePath(tzpath)
    z.copyTo(tz)
    print("Updating XML file at: %s" % (xmlfile, ))
    tzdb.readDatabase()
    tzdb.updateDatabase()
    print("Current total: %d" % (len(tzdb.timezones), ))
    print("Total Changed: %d" % (tzdb.changeCount, ))
    if tzdb.changeCount:
        print("Changed:")
        for k in sorted(tzdb.changed):
            print("  %s" % (k, ))

    versfile = os.path.join(os.path.dirname(xmlfile), "version.txt")
    print("Updating version file at: %s" % (versfile, ))
    with open(versfile, "w") as f:
        f.write(TimezoneCache.IANA_VERSION_PREFIX + tzvers)
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)
Example #21
0
    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)
Example #22
0
    def _add_to_db(self, name, calendar, cursor=None, expand_until=None, reCreate=False):
        """
        Records the given calendar resource in the index with the given name.
        Resource names and UIDs must both be unique; only one resource name may
        be associated with any given UID and vice versa.
        NB This method does not commit the changes to the db - the caller
        MUST take care of that
        @param name: the name of the resource to add.
        @param calendar: a L{Calendar} object representing the resource
            contents.
        """
        uid = calendar.resourceUID()
        organizer = calendar.getOrganizer()
        if not organizer:
            organizer = ""

        # Decide how far to expand based on the component
        doInstanceIndexing = False
        master = calendar.masterComponent()
        if master is None or not calendar.isRecurring():
            # When there is no master we have a set of overridden components - index them all.
            # When there is one instance - index it.
            expand = DateTime(2100, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
            doInstanceIndexing = True
        else:
            # If migrating or re-creating or config option for delayed indexing is off, always index
            if reCreate or not config.FreeBusyIndexDelayedExpand:
                doInstanceIndexing = True

            # Duration into the future through which recurrences are expanded in the index
            # by default.  This is a caching parameter which affects the size of the index;
            # it does not affect search results beyond this period, but it may affect
            # performance of such a search.
            expand = (DateTime.getToday() +
                      Duration(days=config.FreeBusyIndexExpandAheadDays))

            if expand_until and expand_until > expand:
                expand = expand_until

            # Maximum duration into the future through which recurrences are expanded in the
            # index.  This is a caching parameter which affects the size of the index; it
            # does not affect search results beyond this period, but it may affect
            # performance of such a search.
            #
            # When a search is performed on a time span that goes beyond that which is
            # expanded in the index, we have to open each resource which may have data in
            # that time period.  In order to avoid doing that multiple times, we want to
            # cache those results.  However, we don't necessarily want to cache all
            # occurrences into some obscenely far-in-the-future date, so we cap the caching
            # period.  Searches beyond this period will always be relatively expensive for
            # resources with occurrences beyond this period.
            if expand > (DateTime.getToday() +
                         Duration(days=config.FreeBusyIndexExpandMaxDays)):
                raise IndexedSearchException()

        # Always do recurrence expansion even if we do not intend to index - we need this to double-check the
        # validity of the iCalendar recurrence data.
        try:
            instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
            recurrenceLimit = instances.limit
        except InvalidOverriddenInstanceError, e:
            log.error("Invalid instance %s when indexing %s in %s" % (e.rid, name, self.resource,))
            raise
Example #23
0
    def doWork(self):

        # Delete all other work items for this event
        yield Delete(
            From=self.table,
            Where=self.group,
        ).on(self.transaction)

        # get db object
        calendarObject = yield CalendarStoreFeatures(
            self.transaction._store).calendarObjectWithID(
                self.transaction, self.resourceID)
        component = yield calendarObject.componentForUser()

        # Change a copy of the original, as we need the original cached on the resource
        # so we can do a diff to test implicit scheduling changes
        component = component.duplicate()

        # sync group attendees
        if (yield calendarObject.reconcileGroupAttendees(component)):

            # group attendees in event have changed
            if (component.masterComponent() is None
                    or not component.isRecurring()):

                # skip non-recurring old events, no instances
                if (yield calendarObject.removeOldEventGroupLink(
                        component,
                        instances=None,
                        inserting=False,
                        txn=self.transaction)):
                    returnValue(None)
            else:
                # skip recurring old events
                expand = (DateTime.getToday() +
                          Duration(days=config.FreeBusyIndexExpandAheadDays))

                if config.FreeBusyIndexLowerLimitDays:
                    truncateLowerLimit = DateTime.getToday()
                    truncateLowerLimit.offsetDay(
                        -config.FreeBusyIndexLowerLimitDays)
                else:
                    truncateLowerLimit = None

                instances = component.expandTimeRanges(
                    expand,
                    lowerLimit=truncateLowerLimit,
                    ignoreInvalidInstances=True)
                if (yield calendarObject.removeOldEventGroupLink(
                        component,
                        instances=instances,
                        inserting=False,
                        txn=self.transaction)):
                    returnValue(None)

                # split spanning events and only update present-future split result
                splitter = iCalSplitter(0, 1)
                break_point = DateTime.getToday() - Duration(
                    seconds=config.GroupAttendees.UpdateOldEventLimitSeconds)
                rid = splitter.whereSplit(component, break_point=break_point)
                if rid is not None:
                    yield calendarObject.split(onlyThis=True, rid=rid)

                    # remove group link to ensure update (update to unknown hash would work too)
                    # FIXME: its possible that more than one group id gets updated during this single work item, so we
                    # need to make sure that ALL the group_id's are removed by this query.
                    ga = schema.GROUP_ATTENDEE
                    yield Delete(From=ga,
                                 Where=(ga.RESOURCE_ID == self.resourceID).And(
                                     ga.GROUP_ID == self.groupID)).on(
                                         self.transaction)

                    # update group attendee in remaining component
                    component = yield calendarObject.componentForUser()
                    component = component.duplicate()
                    change = yield calendarObject.reconcileGroupAttendees(
                        component)
                    assert change
                    yield calendarObject._setComponentInternal(
                        component, False, ComponentUpdateState.SPLIT_OWNER)
                    returnValue(None)

            yield calendarObject.setComponent(component)
Example #24
0
class TestConduitAPI(MultiStoreConduitTest):
    """
    Test that the conduit api works.
    """

    nowYear = {"now": DateTime.getToday().getYear()}

    caldata1 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid1
DTSTART:{now:04d}0102T140000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:instance
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**nowYear)

    caldata1_changed = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid1
DTSTART:{now:04d}0102T150000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:instance changed
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**nowYear)

    caldata2 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid2
DTSTART:{now:04d}0102T160000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:instance
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**nowYear)

    caldata3 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid3
DTSTART:{now:04d}0102T160000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:instance
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**nowYear)

    @inlineCallbacks
    def test_basic_share(self):
        """
        Test that basic invite/uninvite works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        shared = yield calendar1.shareeView("puser01")
        self.assertEqual(shared.shareStatus(), _BIND_STATUS_ACCEPTED)
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        self.assertTrue(shared is not None)
        self.assertTrue(shared.external())
        yield self.commitTransaction(1)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        yield calendar1.uninviteUIDFromShare("puser01")
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        self.assertTrue(shared is None)
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_countobjects(self):
        """
        Test that action=countobjects works.
        """

        yield self.createShare("user01", "puser01")

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        count = yield shared.countObjectResources()
        self.assertEqual(count, 0)
        yield self.commitTransaction(1)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        count = yield calendar1.countObjectResources()
        self.assertEqual(count, 1)
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        count = yield shared.countObjectResources()
        self.assertEqual(count, 1)
        yield self.commitTransaction(1)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        yield object1.remove()
        count = yield calendar1.countObjectResources()
        self.assertEqual(count, 0)
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        count = yield shared.countObjectResources()
        self.assertEqual(count, 0)
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_listobjects(self):
        """
        Test that action=listobjects works.
        """

        yield self.createShare("user01", "puser01")

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        objects = yield shared.listObjectResources()
        self.assertEqual(set(objects), set())
        yield self.commitTransaction(1)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield calendar1.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        objects = yield calendar1.listObjectResources()
        self.assertEqual(set(objects), set(("1.ics", "2.ics",)))
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        objects = yield shared.listObjectResources()
        self.assertEqual(set(objects), set(("1.ics", "2.ics",)))
        yield self.commitTransaction(1)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        yield object1.remove()
        objects = yield calendar1.listObjectResources()
        self.assertEqual(set(objects), set(("2.ics",)))
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        objects = yield shared.listObjectResources()
        self.assertEqual(set(objects), set(("2.ics",)))
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_synctoken(self):
        """
        Test that action=synctoken works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        token1_1 = yield calendar1.syncTokenRevision()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        token2_1 = yield shared.syncTokenRevision()
        yield self.commitTransaction(1)

        self.assertEqual(token1_1, token2_1)

        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)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        token1_2 = yield calendar1.syncTokenRevision()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        token2_2 = yield shared.syncTokenRevision()
        yield self.commitTransaction(1)

        self.assertNotEqual(token1_1, token1_2)
        self.assertEqual(token1_2, token2_2)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        yield object1.remove()
        count = yield calendar1.countObjectResources()
        self.assertEqual(count, 0)
        yield self.commitTransaction(0)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        token1_3 = yield calendar1.syncTokenRevision()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        token2_3 = yield shared.syncTokenRevision()
        yield self.commitTransaction(1)

        self.assertNotEqual(token1_1, token1_3)
        self.assertNotEqual(token1_2, token1_3)
        self.assertEqual(token1_3, token2_3)

    @inlineCallbacks
    def test_resourcenamessincerevision(self):
        """
        Test that action=synctoken works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        token1_1 = yield calendar1.syncToken()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        token2_1 = yield shared.syncToken()
        yield self.commitTransaction(1)

        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)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        token1_2 = yield calendar1.syncToken()
        names1 = yield calendar1.resourceNamesSinceToken(token1_1)
        self.assertEqual(names1, ([u"1.ics"], [], [],))
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        token2_2 = yield shared.syncToken()
        names2 = yield shared.resourceNamesSinceToken(token2_1)
        self.assertEqual(names2, ([u"1.ics"], [], [],))
        yield self.commitTransaction(1)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        yield object1.remove()
        count = yield calendar1.countObjectResources()
        self.assertEqual(count, 0)
        yield self.commitTransaction(0)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        token1_3 = yield calendar1.syncToken()
        names1 = yield calendar1.resourceNamesSinceToken(token1_2)
        self.assertEqual(names1, ([], [u"1.ics"], [],))
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        token2_3 = yield shared.syncToken()
        names2 = yield shared.resourceNamesSinceToken(token2_2)
        self.assertEqual(names2, ([], [u"1.ics"], [],))
        yield self.commitTransaction(1)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        names1 = yield calendar1.resourceNamesSinceToken(token1_3)
        self.assertEqual(names1, ([], [], [],))
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        names2 = yield shared.resourceNamesSinceToken(token2_3)
        self.assertEqual(names2, ([], [], [],))
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_resourceuidforname(self):
        """
        Test that action=resourceuidforname 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)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        uid = yield calendar1.resourceUIDForName("1.ics")
        self.assertEqual(uid, "uid1")
        uid = yield calendar1.resourceUIDForName("2.ics")
        self.assertTrue(uid is None)
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        uid = yield shared.resourceUIDForName("1.ics")
        self.assertEqual(uid, "uid1")
        uid = yield shared.resourceUIDForName("2.ics")
        self.assertTrue(uid is None)
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_resourcenameforuid(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)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        name = yield calendar1.resourceNameForUID("uid1")
        self.assertEqual(name, "1.ics")
        name = yield calendar1.resourceNameForUID("uid2")
        self.assertTrue(name is None)
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        name = yield shared.resourceNameForUID("uid1")
        self.assertEqual(name, "1.ics")
        name = yield shared.resourceNameForUID("uid2")
        self.assertTrue(name is None)
        yield self.commitTransaction(1)

    @inlineCallbacks
    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)

    @inlineCallbacks
    def test_loadallobjects(self):
        """
        Test that action=loadallobjects works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        resource1 = yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        resource_id1 = resource1.id()
        resource2 = yield calendar1.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        resource_id2 = resource2.id()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resources = yield shared.objectResources()
        byname = dict([(obj.name(), obj) for obj in resources])
        byuid = dict([(obj.uid(), obj) for obj in resources])
        self.assertEqual(len(resources), 2)
        self.assertEqual(set([obj.name() for obj in resources]), set(("1.ics", "2.ics",)))
        self.assertEqual(set([obj.uid() for obj in resources]), set(("uid1", "uid2",)))
        self.assertEqual(set([obj.id() for obj in resources]), set((resource_id1, resource_id2,)))
        resource = yield shared.objectResourceWithName("1.ics")
        self.assertTrue(resource is byname["1.ics"])
        resource = yield shared.objectResourceWithName("2.ics")
        self.assertTrue(resource is byname["2.ics"])
        resource = yield shared.objectResourceWithName("Missing.ics")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithUID("uid1")
        self.assertTrue(resource is byuid["uid1"])
        resource = yield shared.objectResourceWithUID("uid2")
        self.assertTrue(resource is byuid["uid2"])
        resource = yield shared.objectResourceWithUID("uid-missing")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithID(resource_id1)
        self.assertTrue(resource is byname["1.ics"])
        resource = yield shared.objectResourceWithID(resource_id2)
        self.assertTrue(resource is byname["2.ics"])
        resource = yield shared.objectResourceWithID(0)
        self.assertTrue(resource is None)
        yield self.commitTransaction(1)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        yield object1.remove()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resources = yield shared.objectResources()
        byname = dict([(obj.name(), obj) for obj in resources])
        byuid = dict([(obj.uid(), obj) for obj in resources])
        self.assertEqual(len(resources), 1)
        self.assertEqual(set([obj.name() for obj in resources]), set(("2.ics",)))
        self.assertEqual(set([obj.uid() for obj in resources]), set(("uid2",)))
        self.assertEqual(set([obj.id() for obj in resources]), set((resource_id2,)))
        resource = yield shared.objectResourceWithName("1.ics")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithName("2.ics")
        self.assertTrue(resource is byname["2.ics"])
        resource = yield shared.objectResourceWithName("Missing.ics")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithUID("uid1")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithUID("uid2")
        self.assertTrue(resource is byuid["uid2"])
        resource = yield shared.objectResourceWithUID("uid-missing")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithID(resource_id1)
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithID(resource_id2)
        self.assertTrue(resource is byname["2.ics"])
        resource = yield shared.objectResourceWithID(0)
        self.assertTrue(resource is None)
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_loadallobjectswithnames(self):
        """
        Test that action=loadallobjectswithnames works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        resource1 = yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        resource_id1 = resource1.id()
        yield calendar1.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
        resource3 = yield calendar1.createCalendarObjectWithName("3.ics", Component.fromString(self.caldata3))
        resource_id3 = resource3.id()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resources = yield shared.objectResources()
        self.assertEqual(len(resources), 3)
        yield self.commitTransaction(1)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resources = yield shared.objectResourcesWithNames(("1.ics", "3.ics",))
        byname = dict([(obj.name(), obj) for obj in resources])
        byuid = dict([(obj.uid(), obj) for obj in resources])
        self.assertEqual(len(resources), 2)
        self.assertEqual(set([obj.name() for obj in resources]), set(("1.ics", "3.ics",)))
        self.assertEqual(set([obj.uid() for obj in resources]), set(("uid1", "uid3",)))
        self.assertEqual(set([obj.id() for obj in resources]), set((resource_id1, resource_id3,)))
        resource = yield shared.objectResourceWithName("1.ics")
        self.assertTrue(resource is byname["1.ics"])
        resource = yield shared.objectResourceWithName("3.ics")
        self.assertTrue(resource is byname["3.ics"])
        resource = yield shared.objectResourceWithName("Missing.ics")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithUID("uid1")
        self.assertTrue(resource is byuid["uid1"])
        resource = yield shared.objectResourceWithUID("uid3")
        self.assertTrue(resource is byuid["uid3"])
        resource = yield shared.objectResourceWithUID("uid-missing")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithID(resource_id1)
        self.assertTrue(resource is byname["1.ics"])
        resource = yield shared.objectResourceWithID(resource_id3)
        self.assertTrue(resource is byname["3.ics"])
        resource = yield shared.objectResourceWithID(0)
        self.assertTrue(resource is None)
        yield self.commitTransaction(1)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        yield object1.remove()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resources = yield shared.objectResourcesWithNames(("1.ics", "3.ics",))
        byname = dict([(obj.name(), obj) for obj in resources])
        byuid = dict([(obj.uid(), obj) for obj in resources])
        self.assertEqual(len(resources), 1)
        self.assertEqual(set([obj.name() for obj in resources]), set(("3.ics",)))
        self.assertEqual(set([obj.uid() for obj in resources]), set(("uid3",)))
        self.assertEqual(set([obj.id() for obj in resources]), set((resource_id3,)))
        resource = yield shared.objectResourceWithName("1.ics")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithName("3.ics")
        self.assertTrue(resource is byname["3.ics"])
        resource = yield shared.objectResourceWithName("Missing.ics")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithUID("uid1")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithUID("uid3")
        self.assertTrue(resource is byuid["uid3"])
        resource = yield shared.objectResourceWithUID("uid-missing")
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithID(resource_id1)
        self.assertTrue(resource is None)
        resource = yield shared.objectResourceWithID(resource_id3)
        self.assertTrue(resource is byname["3.ics"])
        resource = yield shared.objectResourceWithID(0)
        self.assertTrue(resource is None)
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_objectwith(self):
        """
        Test that action=objectwith works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        resource = yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        resource_id = resource.id()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resource = yield shared.objectResourceWithName("1.ics")
        self.assertTrue(resource is not None)
        self.assertEqual(resource.name(), "1.ics")
        self.assertEqual(resource.uid(), "uid1")

        resource = yield shared.objectResourceWithName("2.ics")
        self.assertTrue(resource is None)

        yield self.commitTransaction(1)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resource = yield shared.objectResourceWithUID("uid1")
        self.assertTrue(resource is not None)
        self.assertEqual(resource.name(), "1.ics")
        self.assertEqual(resource.uid(), "uid1")

        resource = yield shared.objectResourceWithUID("uid2")
        self.assertTrue(resource is None)

        yield self.commitTransaction(1)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resource = yield shared.objectResourceWithID(resource_id)
        self.assertTrue(resource is not None)
        self.assertEqual(resource.name(), "1.ics")
        self.assertEqual(resource.uid(), "uid1")

        resource = yield shared.objectResourceWithID(0)
        self.assertTrue(resource is None)

        yield self.commitTransaction(1)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        yield object1.remove()
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resource = yield shared.objectResourceWithName("1.ics")
        self.assertTrue(resource is None)
        yield self.commitTransaction(1)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resource = yield shared.objectResourceWithUID("uid1")
        self.assertTrue(resource is None)
        yield self.commitTransaction(1)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resource = yield shared.objectResourceWithID(resource_id)
        self.assertTrue(resource is None)
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_create(self):
        """
        Test that action=create works.
        """

        yield self.createShare("user01", "puser01")

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resource = yield shared.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        resource_id = resource.id()
        self.assertTrue(resource is not None)
        self.assertEqual(resource.name(), "1.ics")
        self.assertEqual(resource.uid(), "uid1")
        self.assertFalse(resource._componentChanged)
        yield self.commitTransaction(1)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        resource = yield shared.objectResourceWithUID("uid1")
        self.assertTrue(resource is not None)
        self.assertEqual(resource.name(), "1.ics")
        self.assertEqual(resource.uid(), "uid1")
        self.assertEqual(resource.id(), resource_id)
        yield self.commitTransaction(1)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        self.assertTrue(object1 is not None)
        self.assertEqual(object1.name(), "1.ics")
        self.assertEqual(object1.uid(), "uid1")
        self.assertEqual(object1.id(), resource_id)
        yield self.commitTransaction(0)

    @inlineCallbacks
    def test_create_exception(self):
        """
        Test that action=create fails when a duplicate name is used.
        """

        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)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        yield self.failUnlessFailure(shared.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)), ObjectResourceNameAlreadyExistsError)
        yield self.abortTransaction(1)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        yield self.failUnlessFailure(shared.createCalendarObjectWithName(".2.ics", Component.fromString(self.caldata2)), ObjectResourceNameNotAllowedError)
        yield self.abortTransaction(1)

    @inlineCallbacks
    def test_setcomponent(self):
        """
        Test that action=setcomponent 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)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        ical = yield shared_object.component()
        self.assertTrue(isinstance(ical, Component))
        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
        yield self.commitTransaction(1)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        changed = yield shared_object.setComponent(Component.fromString(self.caldata1_changed))
        self.assertFalse(changed)
        ical = yield shared_object.component()
        self.assertTrue(isinstance(ical, Component))
        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
        yield self.commitTransaction(1)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        ical = yield object1.component()
        self.assertTrue(isinstance(ical, Component))
        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
        yield self.commitTransaction(0)

    @inlineCallbacks
    def test_component(self):
        """
        Test that action=component 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)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        ical = yield shared_object.component()
        self.assertTrue(isinstance(ical, Component))
        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_remove(self):
        """
        Test that action=remove 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)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        yield shared_object.remove()
        yield self.commitTransaction(1)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        self.assertTrue(shared_object is None)
        yield self.commitTransaction(1)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        self.assertTrue(object1 is None)
        yield self.commitTransaction(0)

    @inlineCallbacks
    def test_freebusy(self):
        """
        Test that action=component 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)

        fbstart = "{now:04d}0102T000000Z".format(**self.nowYear)
        fbend = "{now:04d}0103T000000Z".format(**self.nowYear)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")

        fbinfo = FreebusyQuery.FBInfo([], [], [])
        timerange = Period(DateTime.parseText(fbstart), DateTime.parseText(fbend))
        organizer = recipient = (yield calendarUserFromCalendarUserAddress("mailto:[email protected]", self.theTransactionUnderTest(1)))

        freebusy = FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange)
        matchtotal = (yield freebusy.generateFreeBusyInfo([shared, ], fbinfo))

        self.assertEqual(matchtotal, 1)
        self.assertEqual(fbinfo[0], [Period.parseText("{now:04d}0102T140000Z/PT1H".format(**self.nowYear)), ])
        self.assertEqual(len(fbinfo[1]), 0)
        self.assertEqual(len(fbinfo[2]), 0)
        yield self.commitTransaction(1)

    def attachmentToString(self, attachment):
        """
        Convenience to convert an L{IAttachment} to a string.

        @param attachment: an L{IAttachment} provider to convert into a string.

        @return: a L{Deferred} that fires with the contents of the attachment.

        @rtype: L{Deferred} firing C{bytes}
        """
        capture = CaptureProtocol()
        attachment.retrieve(capture)
        return capture.deferred

    @inlineCallbacks
    def test_add_attachment(self):
        """
        Test that action=add-attachment works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        object1 = yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        resourceID = object1.id()
        yield self.commitTransaction(0)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        data = "Here is some text."
        attachment, location = yield shared_object.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream(data))
        managedID = attachment.managedID()
        from txdav.caldav.datastore.sql_external import ManagedAttachmentExternal
        self.assertTrue(isinstance(attachment, ManagedAttachmentExternal))
        self.assertEqual(attachment.size(), len(data))
        self.assertTrue("user01/dropbox/" in location)
        yield self.commitTransaction(1)

        cobjs = yield ManagedAttachment.referencesTo(self.theTransactionUnderTest(0), managedID)
        self.assertEqual(cobjs, set((resourceID,)))
        attachment = yield ManagedAttachment.load(self.theTransactionUnderTest(0), resourceID, managedID)
        self.assertEqual(attachment.name(), "test.txt")
        data = yield self.attachmentToString(attachment)
        self.assertEqual(data, "Here is some text.")
        yield self.commitTransaction(0)

    @inlineCallbacks
    def test_update_attachment(self):
        """
        Test that action=update-attachment 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)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        resourceID = object1.id()
        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        managedID = attachment.managedID()
        yield self.commitTransaction(0)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        data = "Here is some more text."
        attachment, location = yield shared_object.updateAttachment(managedID, MimeType.fromString("text/plain"), "test.txt", MemoryStream(data))
        managedID = attachment.managedID()
        from txdav.caldav.datastore.sql_external import ManagedAttachmentExternal
        self.assertTrue(isinstance(attachment, ManagedAttachmentExternal))
        self.assertEqual(attachment.size(), len(data))
        self.assertTrue("user01/dropbox/" in location)
        yield self.commitTransaction(1)

        cobjs = yield ManagedAttachment.referencesTo(self.theTransactionUnderTest(0), managedID)
        self.assertEqual(cobjs, set((resourceID,)))
        attachment = yield ManagedAttachment.load(self.transactionUnderTest(), resourceID, managedID)
        self.assertEqual(attachment.name(), "test.txt")
        data = yield self.attachmentToString(attachment)
        self.assertEqual(data, "Here is some more text.")
        yield self.commitTransaction(0)

    @inlineCallbacks
    def test_remove_attachment(self):
        """
        Test that action=remove-attachment 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)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        resourceID = object1.id()
        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        managedID = attachment.managedID()
        yield self.commitTransaction(0)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        yield shared_object.removeAttachment(None, managedID)
        yield self.commitTransaction(1)

        cobjs = yield ManagedAttachment.referencesTo(self.theTransactionUnderTest(0), managedID)
        self.assertEqual(cobjs, set())
        attachment = yield ManagedAttachment.load(self.theTransactionUnderTest(0), resourceID, managedID)
        self.assertTrue(attachment is None)
        yield self.commitTransaction(0)

    @inlineCallbacks
    def test_get_all_attachments(self):
        """
        Test that action=get-all-attachments 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)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        yield self.commitTransaction(0)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        attachments = yield shared_object.ownerHome().getAllAttachments()
        self.assertEqual(len(attachments), 1)
        self.assertTrue(isinstance(attachments[0], ManagedAttachment))
        self.assertEqual(attachments[0].contentType(), MimeType.fromString("text/plain"))
        self.assertEqual(attachments[0].name(), "test.txt")
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_get_attachment_data(self):
        """
        Test that action=get-all-attachments 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)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        remote_id = attachment.id()
        yield self.commitTransaction(0)

        home1 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1), name="puser01")
        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        attachment = yield ManagedAttachment._create(self.theTransactionUnderTest(1), None, home1.id())
        attachment._contentType = MimeType.fromString("text/plain")
        attachment._name = "test.txt"
        yield shared_object.ownerHome().readAttachmentData(remote_id, attachment)
        yield self.commitTransaction(1)

    @inlineCallbacks
    def test_get_attachment_links(self):
        """
        Test that action=get-attachment-links works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        cobj1 = yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        calobjID = cobj1.id()
        yield self.commitTransaction(0)

        object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics")
        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString("text/plain"), "test.txt", MemoryStream("Here is some text."))
        attID = attachment.id()
        managedID = attachment.managedID()
        yield self.commitTransaction(0)

        shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics")
        links = yield shared_object.ownerHome().getAttachmentLinks()
        self.assertEqual(len(links), 1)
        self.assertTrue(isinstance(links[0], AttachmentLink))
        self.assertEqual(links[0]._attachmentID, attID)
        self.assertEqual(links[0]._managedID, managedID)
        self.assertEqual(links[0]._calendarObjectID, calobjID)
        yield self.commitTransaction(1)
Example #25
0
def getMonthTable(month, year, weekstart, table, today_index):
    from pycalendar.datetime import DateTime

    # Get today
    today = DateTime.getToday(None)
    today_index = [-1, -1]

    # Start with empty table
    table = []

    # Determine first weekday in month
    temp = DateTime(year, month, 1, 0)
    row = -1
    initial_col = temp.getDayOfWeek() - weekstart
    if initial_col < 0:
        initial_col += 7
    col = initial_col

    # Counters
    max_day = daysInMonth(month, year)

    # Fill up each row
    for day in range(1, max_day + 1):
        # Insert new row if we are at the start of a row
        if (col == 0) or (day == 1):
            table.extend([0] * 7)
            row += 1

        # Set the table item to the current day
        table[row][col] = packDate(temp.getYear(), temp.getMonth(), day)

        # Check on today
        if (temp.getYear() == today.getYear()) and (
                temp.getMonth() == today.getMonth()) and (day
                                                          == today.getDay()):
            today_index = [row, col]

        # Bump column (modulo 7)
        col += 1
        if (col > 6):
            col = 0

    # Add next month to remainder
    temp.offsetMonth(1)
    if col != 0:
        day = 1
        while col < 7:
            table[row][col] = packDate(temp.getYear(), temp.getMonth(), -day)

            # Check on today
            if (temp.getYear() == today.getYear()) and (
                    temp.getMonth()
                    == today.getMonth()) and (day == today.getDay()):
                today_index = [row, col]

            day += 1
            col += 1

    # Add previous month to start
    temp.offsetMonth(-2)
    if (initial_col != 0):
        day = daysInMonth(temp.getMonth(), temp.getYear())
        back_col = initial_col - 1
        while (back_col >= 0):
            table[row][back_col] = packDate(temp.getYear(), temp.getMonth(),
                                            -day)

            # Check on today
            if (temp.getYear() == today.getYear()) and (
                    temp.getMonth()
                    == today.getMonth()) and (day == today.getDay()):
                today_index = [0, back_col]

            back_col -= 1
            day -= 1

    return table, today_index
Example #26
0
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,
                ))

    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_record = (yield calresource.directoryService(
                        ).recordWithCalendarUserAddress(test_organizer)
                                       ) if test_organizer else None
                        test_uid = test_record.uid if test_record 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 > 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()
                    test_record = (
                        yield calresource.principalForCalendarUserAddress(
                            test_organizer)) if test_organizer else None
                    test_uid = test_record.principalUID(
                    ) if test_record 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 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)
Example #27
0
def getMonthTable(month, year, weekstart, table, today_index):
    from pycalendar.datetime import DateTime

    # Get today
    today = DateTime.getToday(None)
    today_index = [-1, -1]

    # Start with empty table
    table = []

    # Determine first weekday in month
    temp = DateTime(year, month, 1, 0)
    row = -1
    initial_col = temp.getDayOfWeek() - weekstart
    if initial_col < 0:
        initial_col += 7
    col = initial_col

    # Counters
    max_day = daysInMonth(month, year)

    # Fill up each row
    for day in range(1, max_day + 1):
        # Insert new row if we are at the start of a row
        if (col == 0) or (day == 1):
            table.extend([0] * 7)
            row += 1

        # Set the table item to the current day
        table[row][col] = packDate(temp.getYear(), temp.getMonth(), day)

        # Check on today
        if (temp.getYear() == today.getYear()) and (temp.getMonth() == today.getMonth()) and (day == today.getDay()):
            today_index = [row, col]

        # Bump column (modulo 7)
        col += 1
        if (col > 6):
            col = 0

    # Add next month to remainder
    temp.offsetMonth(1)
    if col != 0:
        day = 1
        while col < 7:
            table[row][col] = packDate(temp.getYear(), temp.getMonth(), -day)

            # Check on today
            if (temp.getYear() == today.getYear()) and (temp.getMonth() == today.getMonth()) and (day == today.getDay()):
                today_index = [row, col]

            day += 1
            col += 1

    # Add previous month to start
    temp.offsetMonth(-2)
    if (initial_col != 0):
        day = daysInMonth(temp.getMonth(), temp.getYear())
        back_col = initial_col - 1
        while(back_col >= 0):
            table[row][back_col] = packDate(temp.getYear(), temp.getMonth(), -day)

            # Check on today
            if (temp.getYear() == today.getYear()) and (temp.getMonth() == today.getMonth()) and (day == today.getDay()):
                today_index = [0, back_col]

            back_col -= 1
            day -= 1

    return table, today_index
Example #28
0
    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,))
Example #29
0
    def doWork(self):

        # Delete all other work items for this event
        yield Delete(
            From=self.table,
            Where=self.group,
        ).on(self.transaction)

        # get db object
        calendarObject = yield CalendarStoreFeatures(
            self.transaction._store
        ).calendarObjectWithID(
            self.transaction, self.resourceID
        )
        component = yield calendarObject.componentForUser()

        # Change a copy of the original, as we need the original cached on the resource
        # so we can do a diff to test implicit scheduling changes
        component = component.duplicate()

        # sync group attendees
        if (yield calendarObject.reconcileGroupAttendees(component)):

            # group attendees in event have changed
            if (component.masterComponent() is None or not component.isRecurring()):

                # skip non-recurring old events, no instances
                if (
                    yield calendarObject.removeOldEventGroupLink(
                        component,
                        instances=None,
                        inserting=False,
                        txn=self.transaction
                    )
                ):
                    returnValue(None)
            else:
                # skip recurring old events
                expand = (DateTime.getToday() +
                          Duration(days=config.FreeBusyIndexExpandAheadDays))

                if config.FreeBusyIndexLowerLimitDays:
                    truncateLowerLimit = DateTime.getToday()
                    truncateLowerLimit.offsetDay(-config.FreeBusyIndexLowerLimitDays)
                else:
                    truncateLowerLimit = None

                instances = component.expandTimeRanges(
                    expand,
                    lowerLimit=truncateLowerLimit,
                    ignoreInvalidInstances=True
                )
                if (
                    yield calendarObject.removeOldEventGroupLink(
                        component,
                        instances=instances,
                        inserting=False,
                        txn=self.transaction
                    )
                ):
                    returnValue(None)

                # split spanning events and only update present-future split result
                splitter = iCalSplitter(0, 1)
                break_point = DateTime.getToday() - Duration(seconds=config.GroupAttendees.UpdateOldEventLimitSeconds)
                rid = splitter.whereSplit(component, break_point=break_point)
                if rid is not None:
                    yield calendarObject.split(onlyThis=True, rid=rid)

                    # remove group link to ensure update (update to unknown hash would work too)
                    # FIXME: its possible that more than one group id gets updated during this single work item, so we
                    # need to make sure that ALL the group_id's are removed by this query.
                    ga = schema.GROUP_ATTENDEE
                    yield Delete(
                        From=ga,
                        Where=(ga.RESOURCE_ID == self.resourceID).And(
                            ga.GROUP_ID == self.groupID
                        )
                    ).on(self.transaction)

                    # update group attendee in remaining component
                    component = yield calendarObject.componentForUser()
                    component = component.duplicate()
                    change = yield calendarObject.reconcileGroupAttendees(component)
                    assert change
                    yield calendarObject._setComponentInternal(component, False, ComponentUpdateState.SPLIT_OWNER)
                    returnValue(None)

            yield calendarObject.setComponent(component)
Example #30
0
    def checkAttendeeAutoReply(self, calendar, automode):
        """
        Check whether a reply to the given iTIP message is needed and if so make the
        appropriate changes to the calendar data. Changes are only made for the case
        where the PARTSTAT of the attendee is NEEDS-ACTION - i.e., any existing state
        is left unchanged. This allows, e.g., proxies to decline events that would
        otherwise have been auto-accepted and those stay declined as non-schedule-change
        updates are received.

        @param calendar: the iTIP message to process
        @type calendar: L{Component}
        @param automode: the auto-schedule mode for the recipient
        @type automode: L{txdav.who.idirectory.AutoScheduleMode}

        @return: C{tuple} of C{bool}, C{bool}, C{str} indicating whether changes were made, whether the inbox item
            should be added, and the new PARTSTAT.
        """
        if accountingEnabled("AutoScheduling", self.recipient.record):
            accounting = {
                "when": DateTime.getNowUTC().getText(),
                "automode": automode.name,
                "changed": False,
            }
        else:
            accounting = None

        # First ignore the none mode
        if automode == AutoScheduleMode.none:
            returnValue((False, True, "", accounting,))
        elif not automode:
            automode = {
                "none": AutoScheduleMode.none,
                "accept-always": AutoScheduleMode.accept,
                "decline-always": AutoScheduleMode.decline,
                "accept-if-free": AutoScheduleMode.acceptIfFree,
                "decline-if-busy": AutoScheduleMode.declineIfBusy,
                "automatic": AutoScheduleMode.acceptIfFreeDeclineIfBusy,
            }.get(
                config.Scheduling.Options.AutoSchedule.DefaultMode,
                AutoScheduleMode.acceptIfFreeDeclineIfBusy
            )

        log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s" % (self.recipient.cuaddr, self.uid, automode.name,))

        cuas = self.recipient.record.calendarUserAddresses

        # First expand current one to get instances (only go 1 year into the future)
        default_future_expansion_duration = Duration(days=config.Scheduling.Options.AutoSchedule.FutureFreeBusyDays)
        expand_max = DateTime.getToday() + default_future_expansion_duration
        instances = calendar.expandTimeRanges(expand_max, ignoreInvalidInstances=True)

        if accounting is not None:
            accounting["expand-max"] = expand_max.getText()
            accounting["instances"] = len(instances.instances)

        # We are going to ignore auto-accept processing for anything more than a day old (actually use -2 days
        # to add some slop to account for possible timezone offsets)
        min_date = DateTime.getToday()
        min_date.offsetDay(-2)
        allOld = True

        # Cache the current attendee partstat on the instance object for later use, and
        # also mark whether the instance time slot would be free
        for instance in instances.instances.itervalues():
            attendee = instance.component.getAttendeeProperty(cuas)
            instance.partstat = attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") if attendee else None
            instance.free = True
            instance.active = (instance.end > min_date)
            if instance.active:
                allOld = False

        instances = sorted(instances.instances.values(), key=lambda x: x.rid)

        # If every instance is in the past we punt right here so we don't waste time on freebusy lookups etc.
        # There will be no auto-accept and no inbox item stored (so as not to waste storage on items that will
        # never be processed).
        if allOld:
            if accounting is not None:
                accounting["status"] = "all instances are old"
            returnValue((False, False, "", accounting,))

        # Extract UID from primary component as we want to ignore this one if we match it
        # in any calendars.
        uid = calendar.resourceUID()

        # Now compare each instance time-range with the index and see if there is an overlap
        fbset = (yield self.recipient.inbox.ownerHome().loadCalendars())
        fbset = [fbcalendar for fbcalendar in fbset if fbcalendar.isUsedForFreeBusy()]
        if accounting is not None:
            accounting["fbset"] = [testcal.name() for testcal in fbset]
            accounting["tr"] = []

        for testcal in fbset:

            # Get the timezone property from the collection, and store in the query filter
            # for use during the query itself.
            tz = testcal.getTimezone()
            tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True)

            # Now do search for overlapping time-range and set instance.free based
            # on whether there is an overlap or not.
            # NB Do this in reverse order so that the date farthest in the future is tested first - that will
            # ensure that freebusy that far into the future is determined and will trigger time-range caching
            # and indexing out that far - and that will happen only once through this loop.
            for instance in reversed(instances):
                if instance.partstat == "NEEDS-ACTION" and instance.free and instance.active:
                    try:
                        # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
                        fbinfo = ([], [], [])

                        def makeTimedUTC(dt):
                            dt = dt.duplicate()
                            if dt.isDateOnly():
                                dt.setDateOnly(False)
                                dt.setHHMMSS(0, 0, 0)
                            if dt.floating():
                                dt.setTimezone(tzinfo)
                                dt.adjustToUTC()
                            return dt

                        tr = caldavxml.TimeRange(
                            start=str(makeTimedUTC(instance.start)),
                            end=str(makeTimedUTC(instance.end)),
                        )

                        yield generateFreeBusyInfo(testcal, fbinfo, tr, 0, uid, servertoserver=True, accountingItems=accounting if len(instances) == 1 else None)

                        # If any fbinfo entries exist we have an overlap
                        if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):
                            instance.free = False
                        if accounting is not None:
                            accounting["tr"].insert(0, (tr.attributes["start"], tr.attributes["end"], instance.free,))
                    except QueryMaxResources:
                        instance.free[instance] = False
                        log.info("Exceeded number of matches whilst trying to find free-time.")
                        if accounting is not None:
                            accounting["problem"] = "Exceeded number of matches"

            # If everything is declined we can exit now
            if not any([instance.free for instance in instances]):
                break

        if accounting is not None:
            accounting["tr"] = accounting["tr"][:30]

        # Now adjust the instance.partstat currently set to "NEEDS-ACTION" to the
        # value determined by auto-accept logic based on instance.free state. However,
        # ignore any instance in the past - leave them as NEEDS-ACTION.
        partstat_counts = collections.defaultdict(int)
        for instance in instances:
            if instance.partstat == "NEEDS-ACTION" and instance.active:
                if automode == AutoScheduleMode.accept:
                    freePartstat = busyPartstat = "ACCEPTED"
                elif automode == AutoScheduleMode.decline:
                    freePartstat = busyPartstat = "DECLINED"
                else:
                    freePartstat = "ACCEPTED" if automode in (
                        AutoScheduleMode.acceptIfFree,
                        AutoScheduleMode.acceptIfFreeDeclineIfBusy,
                    ) else "NEEDS-ACTION"
                    busyPartstat = "DECLINED" if automode in (
                        AutoScheduleMode.declineIfBusy,
                        AutoScheduleMode.acceptIfFreeDeclineIfBusy,
                    ) else "NEEDS-ACTION"
                instance.partstat = freePartstat if instance.free else busyPartstat
            partstat_counts[instance.partstat] += 1

        if len(partstat_counts) == 0:
            # Nothing to do
            if accounting is not None:
                accounting["status"] = "no partstat changes"
            returnValue((False, False, "", accounting,))

        elif len(partstat_counts) == 1:
            # Do the simple case of all PARTSTATs the same separately
            # Extract the ATTENDEE property matching current recipient from the calendar data
            attendeeProps = calendar.getAttendeeProperties(cuas)
            if not attendeeProps:
                if accounting is not None:
                    accounting["status"] = "no attendee to change"
                returnValue((False, False, "", accounting,))

            made_changes = False
            partstat = partstat_counts.keys()[0]
            for component in calendar.subcomponents():
                made_changes |= self.resetAttendeePartstat(component, cuas, partstat)
            store_inbox = partstat == "NEEDS-ACTION"

            if accounting is not None:
                accounting["status"] = "setting all partstats to {}".format(partstat) if made_changes else "all partstats correct"
                accounting["changed"] = made_changes

        else:
            # Hard case: some accepted, some declined, some needs-action
            # What we will do is mark any master instance as accepted, then mark each existing
            # overridden instance as accepted or declined, and generate new overridden instances for
            # any other declines.

            made_changes = False
            store_inbox = False
            partstat = "MIXED RESPONSE"

            # Default state is whichever of free or busy has most instances
            defaultPartStat = max(sorted(partstat_counts.items()), key=lambda x: x[1])[0]

            # See if there is a master component first
            hadMasterRsvp = False
            master = calendar.masterComponent()
            if master:
                attendee = master.getAttendeeProperty(cuas)
                if attendee:
                    hadMasterRsvp = attendee.parameterValue("RSVP", "FALSE") == "TRUE"
                    if defaultPartStat == "NEEDS-ACTION":
                        store_inbox = True
                    made_changes |= self.resetAttendeePartstat(master, cuas, defaultPartStat)

            # Look at expanded instances and change partstat accordingly
            for instance in instances:

                overridden = calendar.overriddenComponent(instance.rid)
                if not overridden and instance.partstat == defaultPartStat:
                    # Nothing to do as state matches the master
                    continue

                if overridden:
                    # Change ATTENDEE property to match new state
                    if instance.partstat == "NEEDS-ACTION" and instance.active:
                        store_inbox = True
                    made_changes |= self.resetAttendeePartstat(overridden, cuas, instance.partstat)
                else:
                    # Derive a new overridden component and change partstat. We also need to make sure we restore any RSVP
                    # value that may have been overwritten by any change to the master itself.
                    derived = calendar.deriveInstance(instance.rid)
                    if derived is not None:
                        attendee = derived.getAttendeeProperty(cuas)
                        if attendee:
                            if instance.partstat == "NEEDS-ACTION" and instance.active:
                                store_inbox = True
                            self.resetAttendeePartstat(derived, cuas, instance.partstat, hadMasterRsvp)
                            made_changes = True
                            calendar.addComponent(derived)

            if accounting is not None:
                accounting["status"] = "mixed partstat changes" if made_changes else "mixed partstats correct"
                accounting["changed"] = made_changes

        # Fake a SCHEDULE-STATUS on the ORGANIZER property
        if made_changes:
            calendar.setParameterToValueForPropertyWithValue("SCHEDULE-STATUS", iTIPRequestStatus.MESSAGE_DELIVERED_CODE, "ORGANIZER", None)

        returnValue((made_changes, store_inbox, partstat, accounting,))
Example #31
0
"""
Tests for txdav.caldav.datastore.utils
"""

from pycalendar.datetime import DateTime

from twisted.internet.defer import inlineCallbacks
from twisted.trial import unittest

from txdav.caldav.datastore.scheduling.utils import getCalendarObjectForRecord, \
    extractEmailDomain, uidFromCalendarUserAddress
from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests


now = DateTime.getToday().getYear()

ORGANIZER_ICS = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.1//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20100303T181216Z
UID:685BC3A1-195A-49B3-926D-388DDACA78A6
TRANSP:OPAQUE
SUMMARY:Ancient event
DTSTART:%(year)s0307T111500Z
DURATION:PT1H
DTSTAMP:20100303T181220Z
ORGANIZER:urn:uuid:user01
ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:user01
Example #32
0
                uuid = arg

            else:
                raise NotImplementedError(opt)

        if args:
            cls.usage("Too many arguments: %s" % (args,))

        if uuid is None:
            cls.usage("uuid must be specified")
        cls.uuid = uuid

        if dryrun:
            verbose = True

        cutoff = DateTime.getToday()
        cutoff.setDateOnly(False)
        cutoff.offsetDay(-days)
        cls.cutoff = cutoff
        cls.batchSize = batchSize
        cls.dryrun = dryrun
        cls.debug = debug

        utilityMain(
            configFileName,
            cls,
            verbose=verbose,
        )

    @classmethod
    @inlineCallbacks
Example #33
0
"""
Tests for txdav.caldav.datastore.utils
"""

from pycalendar.datetime import DateTime

from twisted.internet.defer import inlineCallbacks
from twisted.trial import unittest

from txdav.caldav.datastore.scheduling.utils import getCalendarObjectForRecord, \
    extractEmailDomain, uidFromCalendarUserAddress
from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests


now = DateTime.getToday().getYear()

ORGANIZER_ICS = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.1//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
CREATED:20100303T181216Z
UID:685BC3A1-195A-49B3-926D-388DDACA78A6
TRANSP:OPAQUE
SUMMARY:Ancient event
DTSTART:%(year)s0307T111500Z
DURATION:PT1H
DTSTAMP:20100303T181220Z
ORGANIZER:urn:uuid:user01
ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:user01
Example #34
0
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,
                ))

    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_record = (yield calresource.directoryService().recordWithCalendarUserAddress(test_organizer)) if test_organizer else None
                        test_uid = test_record.uid if test_record 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 > 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()
                    test_record = (yield calresource.principalForCalendarUserAddress(test_organizer)) if test_organizer else None
                    test_uid = test_record.principalUID() if test_record 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 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)
Example #35
0
    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,
        ))
Example #36
0
class TestCompleteMigrationCycle(MultiStoreConduitTest):
    """
    Test that a full migration cycle using L{CrossPodHomeSync} works.
    """
    def __init__(self, methodName='runTest'):
        super(TestCompleteMigrationCycle, self).__init__(methodName)
        self.stash = {}

    @inlineCallbacks
    def setUp(self):
        @inlineCallbacks
        def _fakeSubmitRequest(iself, ssl, host, port, request):
            pod = (port - 8008) / 100
            inbox = IScheduleInboxResource(self.site.resource,
                                           self.theStoreUnderTest(pod),
                                           podding=True)
            response = yield inbox.http_POST(
                SimpleRequest(
                    self.site,
                    "POST",
                    "http://{host}:{port}/podding".format(host=host,
                                                          port=port),
                    request.headers,
                    request.stream.mem,
                ))
            returnValue(response)

        self.patch(IScheduleRequest, "_submitRequest", _fakeSubmitRequest)
        self.accounts = FilePath(__file__).sibling("accounts").child(
            "groupAccounts.xml")
        self.augments = FilePath(__file__).sibling("accounts").child(
            "augments.xml")
        yield super(TestCompleteMigrationCycle, self).setUp()
        yield self.populate()

        # Speed up work
        self.patch(MigrationCleanupWork, "notBeforeDelay", 1)
        self.patch(HomeCleanupWork, "notBeforeDelay", 1)
        self.patch(MigratedHomeCleanupWork, "notBeforeDelay", 1)

    def configure(self):
        super(TestCompleteMigrationCycle, self).configure()
        config.GroupAttendees.Enabled = True
        config.GroupAttendees.ReconciliationDelaySeconds = 0
        config.GroupAttendees.AutoUpdateSecondsFromNow = 0
        config.AccountingCategories.migration = True
        config.AccountingPrincipals = ["*"]

    @inlineCallbacks
    def populate(self):
        yield populateCalendarsFrom(self.requirements0,
                                    self.theStoreUnderTest(0))
        yield populateCalendarsFrom(self.requirements1,
                                    self.theStoreUnderTest(1))

    requirements0 = {
        "user01": None,
        "user02": None,
        "user03": None,
        "user04": None,
        "user05": None,
        "user06": None,
        "user07": None,
        "user08": None,
        "user09": None,
        "user10": None,
    }

    requirements1 = {
        "puser01": None,
        "puser02": None,
        "puser03": None,
        "puser04": None,
        "puser05": None,
        "puser06": None,
        "puser07": None,
        "puser08": None,
        "puser09": None,
        "puser10": None,
    }

    @inlineCallbacks
    def _createShare(self, shareFrom, shareTo, accept=True):
        # Invite
        txnindex = 1 if shareFrom[0] == "p" else 0
        home = yield self.homeUnderTest(
            txn=self.theTransactionUnderTest(txnindex),
            name=shareFrom,
            create=True)
        calendar = yield home.childWithName("calendar")
        shareeView = yield calendar.inviteUIDToShare(shareTo, _BIND_MODE_READ,
                                                     "summary")
        yield self.commitTransaction(txnindex)

        # Accept
        if accept:
            inviteUID = shareeView.shareUID()
            txnindex = 1 if shareTo[0] == "p" else 0
            shareeHome = yield self.homeUnderTest(
                txn=self.theTransactionUnderTest(txnindex), name=shareTo)
            shareeView = yield shareeHome.acceptShare(inviteUID)
            sharedName = shareeView.name()
            yield self.commitTransaction(txnindex)
        else:
            sharedName = None

        returnValue(sharedName)

    def attachmentToString(self, attachment):
        """
        Convenience to convert an L{IAttachment} to a string.

        @param attachment: an L{IAttachment} provider to convert into a string.

        @return: a L{Deferred} that fires with the contents of the attachment.

        @rtype: L{Deferred} firing C{bytes}
        """
        capture = CaptureProtocol()
        attachment.retrieve(capture)
        return capture.deferred

    now = {
        "now": DateTime.getToday().getYear(),
        "now1": DateTime.getToday().getYear() + 1,
    }

    data01_1 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_data01_1
DTSTART:{now1:04d}0102T140000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:data01_1
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    data01_1_changed = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_data01_1
DTSTART:{now1:04d}0102T140000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:data01_1_changed
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    data01_2 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_data01_2
DTSTART:{now1:04d}0102T160000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
SUMMARY:data01_2
ORGANIZER:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    data01_3 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_data01_3
DTSTART:{now1:04d}0102T180000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
SUMMARY:data01_3
ORGANIZER:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    data02_1 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_data02_1
DTSTART:{now1:04d}0103T140000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:data02_1
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    data02_2 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_data02_2
DTSTART:{now1:04d}0103T160000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
SUMMARY:data02_2
ORGANIZER:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    data02_3 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_data02_3
DTSTART:{now1:04d}0103T180000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
SUMMARY:data02_3
ORGANIZER:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    datap02_1 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_datap02_1
DTSTART:{now1:04d}0103T140000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
RRULE:FREQ=WEEKLY
SUMMARY:datap02_1
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    datap02_2 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_datap02_2
DTSTART:{now1:04d}0103T160000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
SUMMARY:datap02_2
ORGANIZER:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    datap02_3 = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:uid_datap02_3
DTSTART:{now1:04d}0103T180000Z
DURATION:PT1H
CREATED:20060102T190000Z
DTSTAMP:20051222T210507Z
SUMMARY:datap02_3
ORGANIZER:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n").format(**now)

    @inlineCallbacks
    def preCheck(self):
        """
        Checks prior to starting any tests
        """

        for i in range(self.numberOfStores):
            txn = self.theTransactionUnderTest(i)
            record = yield txn.directoryService().recordWithUID(u"user01")
            self.assertEqual(record.serviceNodeUID, "A")
            self.assertEqual(record.thisServer(), i == 0)
            record = yield txn.directoryService().recordWithUID(u"user02")
            self.assertEqual(record.serviceNodeUID, "A")
            self.assertEqual(record.thisServer(), i == 0)
            record = yield txn.directoryService().recordWithUID(u"puser02")
            self.assertEqual(record.serviceNodeUID, "B")
            self.assertEqual(record.thisServer(), i == 1)
            yield self.commitTransaction(i)

    @inlineCallbacks
    def initialState(self):
        """
        Setup the server with an initial set of data

        user01 - migrating user
        user02 - has a calendar shared with user01
        user03 - shared to by user01

        puser01 - user on other pod
        puser02 - has a calendar shared with user01
        puser03 - shared to by user01
        """

        # Data for user01
        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0),
                                        name="user01",
                                        create=True)
        self.stash["user01_pod0_home_id"] = home.id()
        calendar = yield home.childWithName("calendar")
        yield calendar.createCalendarObjectWithName(
            "01_1.ics", Component.fromString(self.data01_1))
        yield calendar.createCalendarObjectWithName(
            "01_2.ics", Component.fromString(self.data01_2))
        obj3 = yield calendar.createCalendarObjectWithName(
            "01_3.ics", Component.fromString(self.data01_3))
        attachment, _ignore_location = yield obj3.addAttachment(
            None, MimeType.fromString("text/plain"), "test.txt",
            MemoryStream("Here is some text #1."))
        self.stash["user01_attachment_id"] = attachment.id()
        self.stash["user01_attachment_md5"] = attachment.md5()
        self.stash["user01_attachment_mid"] = attachment.managedID()
        yield self.commitTransaction(0)

        # Data for user02
        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0),
                                        name="user02",
                                        create=True)
        calendar = yield home.childWithName("calendar")
        yield calendar.createCalendarObjectWithName(
            "02_1.ics", Component.fromString(self.data02_1))
        yield calendar.createCalendarObjectWithName(
            "02_2.ics", Component.fromString(self.data02_2))
        yield calendar.createCalendarObjectWithName(
            "02_3.ics", Component.fromString(self.data02_3))
        yield self.commitTransaction(0)

        # Data for puser02
        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1),
                                        name="puser02",
                                        create=True)
        calendar = yield home.childWithName("calendar")
        yield calendar.createCalendarObjectWithName(
            "p02_1.ics", Component.fromString(self.datap02_1))
        yield calendar.createCalendarObjectWithName(
            "p02_2.ics", Component.fromString(self.datap02_2))
        yield calendar.createCalendarObjectWithName(
            "p02_3.ics", Component.fromString(self.datap02_3))
        yield self.commitTransaction(1)

        # Share calendars
        self.stash["sharename_user01_to_user03"] = yield self._createShare(
            "user01", "user03")
        self.stash["sharename_user01_to_puser03"] = yield self._createShare(
            "user01", "puser03")
        self.stash["sharename_user02_to_user01"] = yield self._createShare(
            "user02", "user01")
        self.stash["sharename_puser02_to_user01"] = yield self._createShare(
            "puser02", "user01")

        # Add some delegates
        txn = self.theTransactionUnderTest(0)
        record01 = yield txn.directoryService().recordWithUID(u"user01")
        record02 = yield txn.directoryService().recordWithUID(u"user02")
        record03 = yield txn.directoryService().recordWithUID(u"user03")
        precord01 = yield txn.directoryService().recordWithUID(u"puser01")

        group02 = yield txn.directoryService().recordWithUID(u"group02")
        group03 = yield txn.directoryService().recordWithUID(u"group03")

        # Add user02 and user03 as individual delegates
        yield Delegates.addDelegate(txn, record01, record02, True)
        yield Delegates.addDelegate(txn, record01, record03, False)
        yield Delegates.addDelegate(txn, record01, precord01, False)

        # Add group delegates
        yield Delegates.addDelegate(txn, record01, group02, True)
        yield Delegates.addDelegate(txn, record01, group03, False)

        # Add external delegates
        yield txn.assignExternalDelegates(u"user01", None, None, u"external1",
                                          u"external2")

        yield self.commitTransaction(0)

        yield self.waitAllEmpty()

    @inlineCallbacks
    def secondState(self):
        """
        Setup the server with data changes appearing after the first sync
        """
        txn = self.theTransactionUnderTest(0)
        obj = yield self.calendarObjectUnderTest(txn,
                                                 name="01_1.ics",
                                                 calendar_name="calendar",
                                                 home="user01")
        yield obj.setComponent(self.data01_1_changed)

        obj = yield self.calendarObjectUnderTest(txn,
                                                 name="02_2.ics",
                                                 calendar_name="calendar",
                                                 home="user02")
        attachment, _ignore_location = yield obj.addAttachment(
            None, MimeType.fromString("text/plain"), "test_02.txt",
            MemoryStream("Here is some text #02."))
        self.stash["user02_attachment_id"] = attachment.id()
        self.stash["user02_attachment_md5"] = attachment.md5()
        self.stash["user02_attachment_mid"] = attachment.managedID()

        yield self.commitTransaction(0)

        yield self.waitAllEmpty()

    @inlineCallbacks
    def finalState(self):
        """
        Setup the server with data changes appearing before the final sync
        """
        txn = self.theTransactionUnderTest(1)
        obj = yield self.calendarObjectUnderTest(txn,
                                                 name="p02_2.ics",
                                                 calendar_name="calendar",
                                                 home="puser02")
        attachment, _ignore_location = yield obj.addAttachment(
            None, MimeType.fromString("text/plain"), "test_p02.txt",
            MemoryStream("Here is some text #p02."))
        self.stash["puser02_attachment_id"] = attachment.id()
        self.stash["puser02_attachment_mid"] = attachment.managedID()
        self.stash["puser02_attachment_md5"] = attachment.md5()

        yield self.commitTransaction(1)

        yield self.waitAllEmpty()

    @inlineCallbacks
    def switchAccounts(self):
        """
        Switch the migrated user accounts to point to the new pod
        """

        for i in range(self.numberOfStores):
            txn = self.theTransactionUnderTest(i)
            record = yield txn.directoryService().recordWithUID(u"user01")
            yield self.changeRecord(
                record,
                txn.directoryService().fieldName.serviceNodeUID,
                u"B",
                directory=txn.directoryService())
            yield self.commitTransaction(i)

        for i in range(self.numberOfStores):
            txn = self.theTransactionUnderTest(i)
            record = yield txn.directoryService().recordWithUID(u"user01")
            self.assertEqual(record.serviceNodeUID, "B")
            self.assertEqual(record.thisServer(), i == 1)
            record = yield txn.directoryService().recordWithUID(u"user02")
            self.assertEqual(record.serviceNodeUID, "A")
            self.assertEqual(record.thisServer(), i == 0)
            record = yield txn.directoryService().recordWithUID(u"puser02")
            self.assertEqual(record.serviceNodeUID, "B")
            self.assertEqual(record.thisServer(), i == 1)
            yield self.commitTransaction(i)

    @inlineCallbacks
    def postCheck(self):
        """
        Checks after migration is done
        """

        # Check that the home has been moved
        home = yield self.homeUnderTest(self.theTransactionUnderTest(0),
                                        name="user01")
        self.assertTrue(home.external())
        home = yield self.homeUnderTest(self.theTransactionUnderTest(0),
                                        name="user01",
                                        status=_HOME_STATUS_NORMAL)
        self.assertTrue(home is None)
        home = yield self.homeUnderTest(self.theTransactionUnderTest(0),
                                        name="user01",
                                        status=_HOME_STATUS_EXTERNAL)
        self.assertTrue(home is not None)
        home = yield self.homeUnderTest(self.theTransactionUnderTest(0),
                                        name="user01",
                                        status=_HOME_STATUS_DISABLED)
        self.assertTrue(home is not None)
        home = yield self.homeUnderTest(self.theTransactionUnderTest(0),
                                        name="user01",
                                        status=_HOME_STATUS_MIGRATING)
        self.assertTrue(home is None)
        yield self.commitTransaction(0)

        home = yield self.homeUnderTest(self.theTransactionUnderTest(1),
                                        name="user01")
        self.assertTrue(home.normal())
        home = yield self.homeUnderTest(self.theTransactionUnderTest(1),
                                        name="user01",
                                        status=_HOME_STATUS_NORMAL)
        self.assertTrue(home is not None)
        home = yield self.homeUnderTest(self.theTransactionUnderTest(1),
                                        name="user01",
                                        status=_HOME_STATUS_EXTERNAL)
        self.assertTrue(home is None)
        home = yield self.homeUnderTest(self.theTransactionUnderTest(1),
                                        name="user01",
                                        status=_HOME_STATUS_DISABLED)
        self.assertTrue(home is not None)
        home = yield self.homeUnderTest(self.theTransactionUnderTest(1),
                                        name="user01",
                                        status=_HOME_STATUS_MIGRATING)
        self.assertTrue(home is None)
        yield self.commitTransaction(1)

        # Check that the notifications have been moved
        notifications = yield self.notificationCollectionUnderTest(
            self.theTransactionUnderTest(0),
            name="user01",
            status=_HOME_STATUS_NORMAL)
        self.assertTrue(notifications is None)
        notifications = yield self.notificationCollectionUnderTest(
            self.theTransactionUnderTest(0),
            name="user01",
            status=_HOME_STATUS_EXTERNAL)
        self.assertTrue(notifications is None)
        notifications = yield self.notificationCollectionUnderTest(
            self.theTransactionUnderTest(0),
            name="user01",
            status=_HOME_STATUS_DISABLED)
        self.assertTrue(notifications is not None)
        yield self.commitTransaction(0)

        notifications = yield self.notificationCollectionUnderTest(
            self.theTransactionUnderTest(1),
            name="user01",
            status=_HOME_STATUS_NORMAL)
        self.assertTrue(notifications is not None)
        notifications = yield self.notificationCollectionUnderTest(
            self.theTransactionUnderTest(1),
            name="user01",
            status=_HOME_STATUS_EXTERNAL)
        self.assertTrue(notifications is None)
        notifications = yield self.notificationCollectionUnderTest(
            self.theTransactionUnderTest(1),
            name="user01",
            status=_HOME_STATUS_DISABLED)
        self.assertTrue(notifications is not None)
        yield self.commitTransaction(1)

        # New pod data
        homes = {}
        homes["user01"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(1), name="user01")
        homes["user02"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(1), name="user02")
        self.assertTrue(homes["user02"].external())
        homes["user03"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(1), name="user03")
        self.assertTrue(homes["user03"].external())
        homes["puser01"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(1), name="puser01")
        self.assertTrue(homes["puser01"].normal())
        homes["puser02"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(1), name="puser02")
        self.assertTrue(homes["puser02"].normal())
        homes["puser03"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(1), name="puser03")
        self.assertTrue(homes["puser03"].normal())

        # Check calendar data on new pod
        calendars = yield homes["user01"].loadChildren()
        calnames = dict([(calendar.name(), calendar)
                         for calendar in calendars])
        self.assertEqual(
            set(calnames.keys()),
            set((
                "calendar",
                "tasks",
                "inbox",
                self.stash["sharename_user02_to_user01"],
                self.stash["sharename_puser02_to_user01"],
            )))

        # Check shared-by user01 on new pod
        shared = calnames["calendar"]
        invitations = yield shared.sharingInvites()
        by_sharee = dict([(invitation.shareeUID, invitation)
                          for invitation in invitations])
        self.assertEqual(len(invitations), 2)
        self.assertEqual(set(by_sharee.keys()), set((
            "user03",
            "puser03",
        )))
        self.assertEqual(by_sharee["user03"].shareeHomeID,
                         homes["user03"].id())
        self.assertEqual(by_sharee["puser03"].shareeHomeID,
                         homes["puser03"].id())

        # Check shared-to user01 on new pod
        shared = calnames[self.stash["sharename_user02_to_user01"]]
        self.assertEqual(shared.ownerHome().uid(), "user02")
        self.assertEqual(shared.ownerHome().id(), homes["user02"].id())

        shared = calnames[self.stash["sharename_puser02_to_user01"]]
        self.assertEqual(shared.ownerHome().uid(), "puser02")
        self.assertEqual(shared.ownerHome().id(), homes["puser02"].id())

        shared = yield homes["puser02"].calendarWithName("calendar")
        invitations = yield shared.sharingInvites()
        self.assertEqual(len(invitations), 1)
        self.assertEqual(invitations[0].shareeHomeID, homes["user01"].id())

        yield self.commitTransaction(1)

        # Old pod data
        homes = {}
        homes["user01"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(0), name="user01")
        homes["user02"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(0), name="user02")
        self.assertTrue(homes["user02"].normal())
        homes["user03"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(0), name="user03")
        self.assertTrue(homes["user03"].normal())
        homes["puser01"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(0), name="puser01")
        self.assertTrue(homes["puser01"] is None)
        homes["puser02"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(0), name="puser02")
        self.assertTrue(homes["puser02"].external())
        homes["puser03"] = yield self.homeUnderTest(
            self.theTransactionUnderTest(0), name="puser03")
        self.assertTrue(homes["puser03"].external())

        # Check shared-by user01 on old pod
        shared = yield homes["user03"].calendarWithName(
            self.stash["sharename_user01_to_user03"])
        self.assertEqual(shared.ownerHome().uid(), "user01")
        self.assertEqual(shared.ownerHome().id(), homes["user01"].id())

        # Check shared-to user01 on old pod
        shared = yield homes["user02"].calendarWithName("calendar")
        invitations = yield shared.sharingInvites()
        self.assertEqual(len(invitations), 1)
        self.assertEqual(invitations[0].shareeHomeID, homes["user01"].id())

        yield self.commitTransaction(0)

        # Delegates on each pod
        for pod in range(self.numberOfStores):
            txn = self.theTransactionUnderTest(pod)
            records = {}
            for ctr in range(10):
                uid = u"user{:02d}".format(ctr + 1)
                records[uid] = yield txn.directoryService().recordWithUID(uid)
            for ctr in range(10):
                uid = u"puser{:02d}".format(ctr + 1)
                records[uid] = yield txn.directoryService().recordWithUID(uid)
            for ctr in range(10):
                uid = u"group{:02d}".format(ctr + 1)
                records[uid] = yield txn.directoryService().recordWithUID(uid)

            delegates = yield Delegates.delegatesOf(txn, records["user01"],
                                                    True, False)
            self.assertTrue(records["user02"] in delegates)
            self.assertTrue(records["group02"] in delegates)
            delegates = yield Delegates.delegatesOf(txn, records["user01"],
                                                    True, True)
            self.assertTrue(records["user02"] in delegates)
            self.assertTrue(records["user06"] in delegates)
            self.assertTrue(records["user07"] in delegates)
            self.assertTrue(records["user08"] in delegates)

            delegates = yield Delegates.delegatesOf(txn, records["user01"],
                                                    False, False)
            self.assertTrue(records["user03"] in delegates)
            self.assertTrue(records["group03"] in delegates)
            self.assertTrue(records["puser01"] in delegates)
            delegates = yield Delegates.delegatesOf(txn, records["user01"],
                                                    False, True)
            self.assertTrue(records["user03"] in delegates)
            self.assertTrue(records["user07"] in delegates)
            self.assertTrue(records["user08"] in delegates)
            self.assertTrue(records["user09"] in delegates)
            self.assertTrue(records["puser01"] in delegates)

        # Attachments
        obj = yield self.calendarObjectUnderTest(
            txn=self.theTransactionUnderTest(1),
            name="01_3.ics",
            calendar_name="calendar",
            home="user01")
        attachment = yield obj.attachmentWithManagedID(
            self.stash["user01_attachment_mid"])
        self.assertTrue(attachment is not None)
        self.assertEqual(attachment.md5(), self.stash["user01_attachment_md5"])
        data = yield self.attachmentToString(attachment)
        self.assertEqual(data, "Here is some text #1.")

        # Check removal of data from new pod

        # Make sure all jobs are done
        yield JobItem.waitEmpty(
            self.theStoreUnderTest(1).newTransaction, reactor, 60)

        # No migration state data left
        txn = self.theTransactionUnderTest(1)
        for migrationType in (
                CalendarMigrationRecord,
                CalendarObjectMigrationRecord,
                AttachmentMigrationRecord,
        ):
            records = yield migrationType.all(txn)
            self.assertEqual(len(records), 0, msg=migrationType.__name__)
        yield self.commitTransaction(1)

        # No homes
        txn = self.theTransactionUnderTest(1)
        oldhome = yield txn.calendarHomeWithUID("user01",
                                                status=_HOME_STATUS_DISABLED)
        self.assertTrue(oldhome is None)
        oldhome = yield txn.notificationsWithUID("user01",
                                                 status=_HOME_STATUS_DISABLED)
        self.assertTrue(oldhome is None)

        # Check removal of data from old pod

        # Make sure all jobs are done
        yield JobItem.waitEmpty(
            self.theStoreUnderTest(0).newTransaction, reactor, 60)

        # No homes
        txn = self.theTransactionUnderTest(0)
        oldhome = yield txn.calendarHomeWithUID("user01",
                                                status=_HOME_STATUS_DISABLED)
        self.assertTrue(oldhome is None)
        oldhome = yield txn.notificationsWithUID("user01",
                                                 status=_HOME_STATUS_DISABLED)
        self.assertTrue(oldhome is None)

        # No delegates
        for delegateType in (DelegateRecord, DelegateGroupsRecord,
                             ExternalDelegateGroupsRecord):
            records = yield delegateType.query(
                txn, delegateType.delegator == "user01")
            self.assertEqual(len(records), 0, msg=delegateType.__name__)

        # No work items
        for workType in allScheduleWork:
            records = yield workType.query(
                txn,
                workType.homeResourceID == self.stash["user01_pod0_home_id"])
            self.assertEqual(len(records), 0, msg=workType.__name__)

    @inlineCallbacks
    def test_migration(self):
        """
        Full migration cycle.
        """

        yield self.preCheck()

        # Step 1. Live full sync
        yield self.initialState()
        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), "user01")
        yield syncer.sync()

        # Step 2. Live incremental sync
        yield self.secondState()
        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), "user01")
        yield syncer.sync()

        # Step 3. Disable home after final changes
        yield self.finalState()
        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), "user01")
        yield syncer.disableRemoteHome()

        # Step 4. Final incremental sync
        syncer = CrossPodHomeSync(self.theStoreUnderTest(1),
                                  "user01",
                                  final=True)
        yield syncer.sync()

        # Step 5. Final reconcile sync
        syncer = CrossPodHomeSync(self.theStoreUnderTest(1),
                                  "user01",
                                  final=True)
        yield syncer.finalSync()

        # Step 6. Enable new home
        syncer = CrossPodHomeSync(self.theStoreUnderTest(1),
                                  "user01",
                                  final=True)
        yield syncer.enableLocalHome()

        # Step 7. Remove old home
        syncer = CrossPodHomeSync(self.theStoreUnderTest(1),
                                  "user01",
                                  final=True)
        yield syncer.removeRemoteHome()

        yield self.switchAccounts()

        yield self.postCheck()