Exemplo n.º 1
0
def getTimerangeArguments(timerange):
    """
    Get start/end and floating start/end (adjusted for timezone offset) values from the
    supplied time-range test.

    @param timerange: the L{TimeRange} used in the query.
    @return: C{tuple} of C{str} for start, end, startfloat, endfloat
    """

    # Start/end in UTC
    start = timerange.start
    end = timerange.end

    # Get timezone
    tzinfo = timerange.tzinfo

    # Now force to floating UTC
    startfloat = floatoffset(start, tzinfo) if start else None
    endfloat = floatoffset(end, tzinfo) if end else None

    return (
        pyCalendarTodatetime(start) if start else None,
        pyCalendarTodatetime(end) if end else None,
        pyCalendarTodatetime(startfloat) if startfloat else None,
        pyCalendarTodatetime(endfloat) if endfloat else None,
    )
def getTimerangeArguments(timerange):
    """
    Get start/end and floating start/end (adjusted for timezone offset) values from the
    supplied time-range test.
    
    @param timerange: the L{TimeRange} used in the query.
    @return: C{tuple} of C{str} for start, end, startfloat, endfloat
    """
    
    # Start/end in UTC
    start = timerange.start
    end = timerange.end
    
    # Get timezone
    tzinfo = timerange.tzinfo

    # Now force to floating UTC
    startfloat = floatoffset(start, tzinfo) if start else None
    endfloat = floatoffset(end, tzinfo) if end else None

    return (
        pyCalendarTodatetime(start) if start else None,
        pyCalendarTodatetime(end) if end else None,
        pyCalendarTodatetime(startfloat) if startfloat else None,
        pyCalendarTodatetime(endfloat) if endfloat else None,
    )
Exemplo n.º 3
0
 def notExpandedWithin(self, minDate, maxDate):
     """
     Gives all resources which have not been expanded beyond a given date
     in the database.  (Unused; see above L{postgresqlgenerator}.
     """
     returnValue([row[0] for row in (
         yield self._notExpandedWithinQuery.on(
             self._txn,
             minDate=pyCalendarTodatetime(normalizeForIndex(minDate)) if minDate is not None else None,
             maxDate=pyCalendarTodatetime(normalizeForIndex(maxDate)),
             resourceID=self.calendar._resourceID))]
     )
Exemplo n.º 4
0
 def notExpandedBeyond(self, minDate):
     """
     Gives all resources which have not been expanded beyond a given date
     in the index
     """
     return self._db_values_for_sql(
         "select NAME from RESOURCE where RECURRANCE_MAX < :1",
         pyCalendarTodatetime(minDate))
Exemplo n.º 5
0
    def test_pyCalendarTodatetime(self):
        """
        dateops.pyCalendarTodatetime
        """
        tests = (
            (PyCalendarDateTime(2012, 4, 4, 12, 34, 56), datetime.datetime(2012, 4, 4, 12, 34, 56, tzinfo=dateutil.tz.tzutc())),
            (PyCalendarDateTime(2012, 12, 31), datetime.date(2012, 12, 31)),
        )

        for pycal, result in tests:
            self.assertEqual(pyCalendarTodatetime(pycal), result)
Exemplo n.º 6
0
    def test_pyCalendarTodatetime(self):
        """
        dateops.pyCalendarTodatetime
        """
        tests = (
            (DateTime(2012, 4, 4, 12, 34, 56), datetime.datetime(2012, 4, 4, 12, 34, 56, tzinfo=dateutil.tz.tzutc())),
            (DateTime(2012, 12, 31), datetime.date(2012, 12, 31)),
        )

        for pycal, result in tests:
            self.assertEqual(pyCalendarTodatetime(pycal), result)
Exemplo n.º 7
0
 def notExpandedBeyond(self, minDate):
     """
     Gives all resources which have not been expanded beyond a given date
     in the index
     """
     return self._db_values_for_sql("select NAME from RESOURCE where RECURRANCE_MAX < :1", pyCalendarTodatetime(minDate))
Exemplo n.º 8
0
class CalendarIndex(AbstractCalendarIndex):
    """
    Calendar index - abstract class for indexer that indexes calendar objects in a collection.
    """
    def __init__(self, resource):
        """
        @param resource: the L{CalDAVResource} resource to
            index.
        """
        super(CalendarIndex, self).__init__(resource)

    def _db_init_data_tables_base(self, q, uidunique):
        """
        Initialise the underlying database tables.
        @param q:           a database cursor to use.
        """
        #
        # RESOURCE table is the primary index table
        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
        #   UID: iCalendar UID (may or may not be unique)
        #   TYPE: iCalendar component type
        #   RECURRANCE_MAX: Highest date of recurrence expansion
        #   ORGANIZER: cu-address of the Organizer of the event
        #
        q.execute("""
            create table RESOURCE (
                RESOURCEID     integer primary key autoincrement,
                NAME           text unique,
                UID            text%s,
                TYPE           text,
                RECURRANCE_MAX date,
                ORGANIZER      text
            )
            """ % (" unique" if uidunique else "", ))

        #
        # TIMESPAN table tracks (expanded) time spans for resources
        #   NAME: Related resource (RESOURCE foreign key)
        #   FLOAT: 'Y' if start/end are floating, 'N' otherwise
        #   START: Start date
        #   END: End date
        #   FBTYPE: FBTYPE value:
        #     '?' - unknown
        #     'F' - free
        #     'B' - busy
        #     'U' - busy-unavailable
        #     'T' - busy-tentative
        #   TRANSPARENT: Y if transparent, N if opaque (default non-per-user value)
        #
        q.execute("""
            create table TIMESPAN (
                INSTANCEID   integer primary key autoincrement,
                RESOURCEID   integer,
                FLOAT        text(1),
                START        date,
                END          date,
                FBTYPE       text(1),
                TRANSPARENT  text(1)
            )
            """)
        q.execute("""
            create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)
            """)

        #
        # PERUSER table tracks per-user ids
        #   PERUSERID: autoincrement primary key
        #   UID: User ID used in calendar data
        #
        q.execute("""
            create table PERUSER (
                PERUSERID       integer primary key autoincrement,
                USERUID         text
            )
            """)
        q.execute("""
            create index PERUSER_UID on PERUSER (USERUID)
            """)

        #
        # TRANSPARENCY table tracks per-user per-instance transparency
        #   PERUSERID: user id key
        #   INSTANCEID: instance id key
        #   TRANSPARENT: Y if transparent, N if opaque
        #
        q.execute("""
            create table TRANSPARENCY (
                PERUSERID       integer,
                INSTANCEID      integer,
                TRANSPARENT     text(1)
            )
            """)

        #
        # REVISIONS table tracks changes
        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
        #   REVISION: revision number
        #   WASDELETED: Y if revision deleted, N if added or changed
        #
        q.execute("""
            create table REVISION_SEQUENCE (
                REVISION        integer
            )
            """)
        q.execute("""
            insert into REVISION_SEQUENCE (REVISION) values (0)
            """)
        q.execute("""
            create table REVISIONS (
                NAME            text unique,
                REVISION        integer,
                DELETED         text(1)
            )
            """)
        q.execute("""
            create index REVISION on REVISIONS (REVISION)
            """)

        if uidunique:
            #
            # RESERVED table tracks reserved UIDs
            #   UID: The UID being reserved
            #   TIME: When the reservation was made
            #
            q.execute("""
                create table RESERVED (
                    UID  text unique,
                    TIME date
                )
                """)

        # Cascading triggers to help on delete
        q.execute("""
            create trigger resourceDelete after delete on RESOURCE
            for each row
            begin
                delete from TIMESPAN where TIMESPAN.RESOURCEID = OLD.RESOURCEID;
            end
            """)
        q.execute("""
            create trigger timespanDelete after delete on TIMESPAN
            for each row
            begin
                delete from TRANSPARENCY where INSTANCEID = OLD.INSTANCEID;
            end
            """)

    def _db_can_upgrade(self, old_version):
        """
        Can we do an in-place upgrade
        """

        # v10 is a big change - no upgrade possible
        return False

    def _db_upgrade_data_tables(self, q, old_version):
        """
        Upgrade the data from an older version of the DB.
        """

        # v10 is a big change - no upgrade possible
        pass

    def notExpandedBeyond(self, minDate):
        """
        Gives all resources which have not been expanded beyond a given date
        in the index
        """
        return self._db_values_for_sql(
            "select NAME from RESOURCE where RECURRANCE_MAX < :1",
            pyCalendarTodatetime(minDate))

    def reExpandResource(self, name, expand_until):
        """
        Given a resource name, remove it from the database and re-add it
        with a longer expansion.
        """
        calendar = self.resource.getChild(name).iCalendar()
        self._add_to_db(name,
                        calendar,
                        expand_until=expand_until,
                        reCreate=True)
        self._db_commit()

    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

        # Now coerce indexing to off if needed
        if not doInstanceIndexing:
            instances = None
            recurrenceLimit = DateTime(1900,
                                       1,
                                       1,
                                       0,
                                       0,
                                       0,
                                       tzid=Timezone(utc=True))

        self._delete_from_db(name, uid, False)

        # Add RESOURCE item
        self._db_execute(
            """
            insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
            values (:1, :2, :3, :4, :5)
            """, name, uid, calendar.resourceType(),
            pyCalendarTodatetime(recurrenceLimit) if recurrenceLimit else None,
            organizer)
        resourceid = self.lastrowid

        # Get a set of all referenced per-user UIDs and map those to entries already
        # in the DB and add new ones as needed
        useruids = calendar.allPerUserUIDs()
        useruids.add("")
        useruidmap = {}
        for useruid in useruids:
            peruserid = self._db_value_for_sql(
                "select PERUSERID from PERUSER where USERUID = :1", useruid)
            if peruserid is None:
                self._db_execute(
                    """
                    insert into PERUSER (USERUID)
                    values (:1)
                    """, useruid)
                peruserid = self.lastrowid
            useruidmap[useruid] = peruserid

        if doInstanceIndexing:
            for key in instances:
                instance = instances[key]
                start = instance.start
                end = instance.end
                float = 'Y' if instance.start.floating() else 'N'
                transp = 'T' if instance.component.propertyValue(
                    "TRANSP") == "TRANSPARENT" else 'F'
                self._db_execute(
                    """
                    insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
                    values (:1, :2, :3, :4, :5, :6)
                    """, resourceid, float, pyCalendarTodatetime(start),
                    pyCalendarTodatetime(end),
                    icalfbtype_to_indexfbtype.get(
                        instance.component.getFBType(), 'F'), transp)
                instanceid = self.lastrowid
                peruserdata = calendar.perUserData(instance.rid)
                for useruid, (transp, _ignore_adjusted_start,
                              _ignore_adjusted_end) in peruserdata:
                    peruserid = useruidmap[useruid]
                    self._db_execute(
                        """
                        insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
                        values (:1, :2, :3)
                        """, peruserid, instanceid, 'T' if transp else 'F')

            # Special - for unbounded recurrence we insert a value for "infinity"
            # that will allow an open-ended time-range to always match it.
            if calendar.isRecurringUnbounded():
                start = DateTime(2100, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
                end = DateTime(2100, 1, 1, 1, 0, 0, tzid=Timezone(utc=True))
                float = 'N'
                self._db_execute(
                    """
                    insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
                    values (:1, :2, :3, :4, :5, :6)
                    """, resourceid, float, pyCalendarTodatetime(start),
                    pyCalendarTodatetime(end), '?', '?')
                instanceid = self.lastrowid
                peruserdata = calendar.perUserData(None)
                for useruid, (transp, _ignore_adjusted_start,
                              _ignore_adjusted_end) in peruserdata:
                    peruserid = useruidmap[useruid]
                    self._db_execute(
                        """
                        insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
                        values (:1, :2, :3)
                        """, peruserid, instanceid, 'T' if transp else 'F')

        self._db_execute(
            """
            insert or replace into REVISIONS (NAME, REVISION, DELETED)
            values (:1, :2, :3)
            """,
            name,
            self.bumpRevision(fast=True),
            'N',
        )