Esempio n. 1
0
def create_calendar(client, parent, name, id=None):
    """
    Create a new calendar with display name `name` in `parent`.
    """
    path = None
    if id is None:
        id = str(uuid.uuid1())

    name = dav.DisplayName(name)
    cal = cdav.CalendarCollection()
    coll = dav.Collection() + cal
    type = dav.ResourceType() + coll

    prop = dav.Prop() + [type, name]
    set = dav.Set() + prop

    mkcol = dav.Mkcol() + set

    q = etree.tostring(mkcol.xmlelement(),
                       encoding="utf-8",
                       xml_declaration=True)
    path = url.join(parent.url.path, id)

    r = client.mkcol(path, q)
    if r.status == 201:
        path = url.make(parent.url, path)
    else:
        raise error.MkcolError(r.raw)

    return (id, path)
Esempio n. 2
0
def date_search(client, calendar, start, end=None):
    """
    Perform a time-interval search in the `calendar`.
    """
    rc = []

    # build the request
    expand = cdav.Expand(start, end)
    data = cdav.CalendarData() + expand
    prop = dav.Prop() + data

    range = cdav.TimeRange(start, end)
    vevent = cdav.CompFilter("VEVENT") + range
    vcal = cdav.CompFilter("VCALENDAR") + vevent
    filter = cdav.Filter() + vcal

    root = cdav.CalendarQuery() + [prop, filter]

    q = etree.tostring(root.xmlelement(),
                       encoding="utf-8",
                       xml_declaration=True)
    response = client.report(calendar.url.path, q, 1)
    for r in response.tree.findall(".//" + dav.Response.tag):
        status = r.find(".//" + dav.Status.tag)
        if status.text.endswith("200 OK"):
            href = r.find(dav.Href.tag).text
            data = r.find(".//" + cdav.CalendarData.tag).text
            rc.append((url.make(calendar.url, href), data))
        else:
            raise error.ReportError(r.raw)

    return rc
Esempio n. 3
0
    def events(self):
        """
        List all events from the calendar.

        Returns:
         * [Event(), ...]
        """
        all = []

        data = cdav.CalendarData()
        prop = dav.Prop() + data
        vevent = cdav.CompFilter("VEVENT")
        vcalendar = cdav.CompFilter("VCALENDAR") + vevent
        filter = cdav.Filter() + vcalendar
        root = cdav.CalendarQuery() + [prop, filter]

        response = self._query(root, 1, query_method='report')
        results = self._handle_prop_response(
            response, props=[cdav.CalendarData()])
        for r in results:
            all.append(Event(
                self.client, url=self.url.join(r),
                data=results[r][cdav.CalendarData.tag], parent=self))

        return all
Esempio n. 4
0
    def journals(self):
        """
        List all journals from the calendar.

        Returns:
         * [Journal(), ...]
        """
        # TODO: this is basically a copy of events() - can we do more
        # refactoring and consolidation here?  Maybe it's wrong to do
        # separate methods for journals, todos and events?
        all = []

        data = cdav.CalendarData()
        prop = dav.Prop() + data
        vevent = cdav.CompFilter("VJOURNAL")
        vcalendar = cdav.CompFilter("VCALENDAR") + vevent
        filter = cdav.Filter() + vcalendar
        root = cdav.CalendarQuery() + [prop, filter]

        response = self._query(root, 1, query_method='report')
        results = self._handle_prop_response(
            response, props=[cdav.CalendarData()])
        for r in results:
            all.append(Journal(
                self.client, url=self.url.join(r),
                data=results[r][cdav.CalendarData.tag], parent=self))

        return all
Esempio n. 5
0
def get_properties(client, object, props=[], depth=0):
    """
    Find the properies `props` of object `object` and its children at
    maximum `depth` levels. (0 means only `object`).
    """
    rc = {}

    body = ""
    # build the propfind request
    if len(props) > 0:
        prop = dav.Prop() + props
        root = dav.Propfind() + prop

        body = etree.tostring(root.xmlelement(),
                              encoding="utf-8",
                              xml_declaration=True)

    response = client.propfind(object.url.path, body, depth)
    # All items should be in a <D:response> element
    for r in response.tree.findall(dav.Response.tag):
        href = r.find(dav.Href.tag).text
        rc[href] = {}
        for p in props:
            t = r.find(".//" + p.tag)
            if t.text is None:
                val = t.find(".//*")
                if val is not None:
                    val = val.tag
                else:
                    val = None
            else:
                val = t.text
            rc[href][p.tag] = val

    return rc
Esempio n. 6
0
def uid_search(client, calendar, uid):
    """
    Perform a uid search in the `calendar`.
    """
    data = cdav.CalendarData()
    prop = dav.Prop() + data

    match = cdav.TextMatch(uid)
    propf = cdav.PropFilter("UID") + match
    vevent = cdav.CompFilter("VEVENT") + propf
    vcal = cdav.CompFilter("VCALENDAR") + vevent
    filter = cdav.Filter() + vcal

    root = cdav.CalendarQuery() + [prop, filter]

    q = etree.tostring(root.xmlelement(),
                       encoding="utf-8",
                       xml_declaration=True)
    response = client.report(calendar.url.path, q, 1)
    r = response.tree.find(".//" + dav.Response.tag)
    if r is not None:
        href = r.find(".//" + dav.Href.tag).text
        data = r.find(".//" + cdav.CalendarData.tag).text
        info = (url.make(calendar.url, href), data)
    else:
        raise error.NotFoundError(response.raw)

    return info
Esempio n. 7
0
    def date_search(self, start, end=None, compfilter="VEVENT", expand="maybe"):
        # type (TimeStamp, TimeStamp, str, str) -> CalendarObjectResource
        """
        Search events by date in the calendar. Recurring events are
        expanded if they are occuring during the specified time frame
        and if an end timestamp is given.

        Parameters:
         * start = datetime.today().
         * end = same as above.
         * compfilter = defaults to events only.  Set to None to fetch all
           calendar components.
         * expand - should recurrent events be expanded?  (to preserve 
           backward-compatibility the default "maybe" will be changed into True 
           unless the date_search is open-ended)

        Returns:
         * [CalendarObjectResource(), ...]

        """
        matches = []

        # build the request

        ## for backward compatibility - expand should be false
        ## in an open-ended date search, otherwise true
        if expand == 'maybe':
            expand = end

        # Some servers will raise an error if we send the expand flag
        # but don't set any end-date - expand doesn't make much sense
        # if we have one recurring event describing an indefinite
        # series of events.  I think it's appropriate to raise an error
        # in this case.
        if not end and expand:
            raise error.ReportError("an open-ended date search cannot be expanded")
        elif expand:
            data = cdav.CalendarData() + cdav.Expand(start, end)
        else:
            data = cdav.CalendarData()
        prop = dav.Prop() + data

        query = cdav.TimeRange(start, end)
        if compfilter:
            query = cdav.CompFilter(compfilter) + query
        vcalendar = cdav.CompFilter("VCALENDAR") + query
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]
        response = self._query(root, 1, 'report')
        results = self._handle_prop_response(
            response=response, props=[cdav.CalendarData()])
        for r in results:
            matches.append(
                Event(self.client, url=self.url.join(r),
                      data=results[r][cdav.CalendarData.tag], parent=self))

        return matches
Esempio n. 8
0
    def _create(self,
                name=None,
                id=None,
                supported_calendar_component_set=None):
        """
        Create a new calendar with display name `name` in `parent`.
        """
        if id is None:
            id = str(uuid.uuid1())
        self.id = id

        path = self.parent.url.join(id)
        self.url = path

        # TODO: mkcalendar seems to ignore the body on most servers?
        # at least the name doesn't get set this way.
        # zimbra gives 500 (!) if body is omitted ...

        prop = dav.Prop()
        if name:
            display_name = dav.DisplayName(name)
            prop += [
                display_name,
            ]
        if supported_calendar_component_set:
            sccs = cdav.SupportedCalendarComponentSet()
            for scc in supported_calendar_component_set:
                sccs += cdav.Comp(scc)
            prop += sccs
        set = dav.Set() + prop

        mkcol = cdav.Mkcalendar() + set

        r = self._query(root=mkcol,
                        query_method='mkcalendar',
                        url=path,
                        expected_return_value=201)

        # COMPATIBILITY ISSUE
        # name should already be set, but we've seen caldav servers failing
        # on setting the DisplayName on calendar creation
        # (DAViCal, Zimbra, ...).  Doing an attempt on explicitly setting the
        # display name using PROPPATCH.
        if name:
            try:
                self.set_properties([display_name])
            except:
                try:
                    current_display_name = self.get_properties([display_name])
                    if current_display_name != name:
                        logging.warning(
                            "caldav server not complient with RFC4791. unable to set display name on calendar.  Wanted name: \"%s\" - gotten name: \"%s\".  Ignoring."
                            % (name, current_display_name))
                except:
                    logging.warning(
                        "calendar server does not support display name on calendar?  Ignoring",
                        exc_info=True)
Esempio n. 9
0
    def _query_properties(self, props=None, depth=0):
        """
        This is an internal method for doing a propfind query.  It's a
        result of code-refactoring work, attempting to consolidate
        similar-looking code into a common method.
        """
        root = None
        # build the propfind request
        if props is not None and len(props) > 0:
            prop = dav.Prop() + props
            root = dav.Propfind() + prop

        return self._query(root, depth)
Esempio n. 10
0
    def todos(self, sort_key='due', include_completed=False):
        """
        fetches a list of todo events.

        Parameters:
         * sort_key: use this field in the VTODO for sorting (lower case string, i.e. 'priority').
         * include_completed: boolean - by default, only pending tasks are listed
        """
        ## ref https://www.ietf.org/rfc/rfc4791.txt, section 7.8.9
        matches = []

        # build the request
        data = cdav.CalendarData()
        prop = dav.Prop() + data

        if not include_completed:
            vnotcompleted = cdav.TextMatch('COMPLETED', negate=True)
            vnotcancelled = cdav.TextMatch('CANCELLED', negate=True)
            vstatus = cdav.PropFilter('STATUS') + vnotcancelled + vnotcompleted
            vnocompletedate = cdav.PropFilter('COMPLETED') + cdav.NotDefined()
            vtodo = cdav.CompFilter("VTODO") + vnocompletedate + vstatus
        else:
            vtodo = cdav.CompFilter("VTODO")
        vcalendar = cdav.CompFilter("VCALENDAR") + vtodo
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        response = self._query(root, 1, 'report')
        results = self._handle_prop_response(response=response,
                                             props=[cdav.CalendarData()])
        for r in results:
            matches.append(
                Todo(self.client,
                     url=self.url.join(r),
                     data=results[r][cdav.CalendarData.tag],
                     parent=self))

        def sort_key_func(x):
            val = getattr(x.instance.vtodo, sort_key, None)
            if not val:
                return None
            val = val.value
            if hasattr(val, 'strftime'):
                return val.strftime('%F%H%M%S')
            return val

        if sort_key:
            matches.sort(key=sort_key_func)
        return matches
Esempio n. 11
0
    def _fetch_todos(self, filters):
        # ref https://www.ietf.org/rfc/rfc4791.txt, section 7.8.9
        matches = []

        # build the request
        data = cdav.CalendarData()
        prop = dav.Prop() + data

        vcalendar = cdav.CompFilter("VCALENDAR") + filters
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        return self.search(root, comp_class=Todo)
Esempio n. 12
0
    def _query_properties(self, props=[], depth=0):
        body = ""
        # build the propfind request
        if len(props) > 0:
            prop = dav.Prop() + props
            root = dav.Propfind() + prop

            body = etree.tostring(root.xmlelement(), encoding="utf-8",
                                  xml_declaration=True)

        ret = self.client.propfind(self.url, body, depth)
        if ret.status == 404:
            raise error.NotFoundError 
        return ret
Esempio n. 13
0
def set_properties(client, object, props=[]):
    prop = dav.Prop() + props
    set = dav.Set() + prop
    root = dav.PropertyUpdate() + set

    q = etree.tostring(root.xmlelement(),
                       encoding="utf-8",
                       xml_declaration=True)
    r = client.proppatch(object.url.path, q)

    statuses = r.tree.findall(".//" + dav.Status.tag)
    for s in statuses:
        if not s.text.endswith("200 OK"):
            raise error.PropsetError(r.raw)
Esempio n. 14
0
    def date_search(self, start, end=None, compfilter="VEVENT"):
        """
        Search events by date in the calendar. Recurring events are
        expanded if they are occuring during the specified time frame
        and if an end timestamp is given.

        Parameters:
         * start = datetime.today().
         * end = same as above.
         * compfilter = defaults to events only.  Set to None to fetch all
           calendar components.

        Returns:
         * [CalendarObjectResource(), ...]

        """
        matches = []

        # build the request

        # Some servers will raise an error if we send the expand flag
        # but don't set any end-date - expand doesn't make much sense
        # if we have one recurring event describing an indefinite
        # series of events.  Hence, if the end date is not set, we
        # skip asking for expanded events.
        if end:
            data = cdav.CalendarData() + cdav.Expand(start, end)
        else:
            data = cdav.CalendarData()
        prop = dav.Prop() + data

        query = cdav.TimeRange(start, end)
        if compfilter:
            query = cdav.CompFilter(compfilter) + query
        vcalendar = cdav.CompFilter("VCALENDAR") + query
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]
        response = self._query(root, 1, 'report')
        results = self._handle_prop_response(response=response,
                                             props=[cdav.CalendarData()])
        for r in results:
            matches.append(
                Event(self.client,
                      url=self.url.join(r),
                      data=results[r][cdav.CalendarData.tag],
                      parent=self))

        return matches
Esempio n. 15
0
    def events(self):
        """
        List all events from the calendar.

        Returns:
         * [Event(), ...]
        """
        data = cdav.CalendarData()
        prop = dav.Prop() + data
        vevent = cdav.CompFilter("VEVENT")
        vcalendar = cdav.CompFilter("VCALENDAR") + vevent
        filter = cdav.Filter() + vcalendar
        root = cdav.CalendarQuery() + [prop, filter]

        return self.search(root, comp_class=Event)
Esempio n. 16
0
    def object_by_uid(self, uid, comp_filter=None):
        """
        Get one event from the calendar.

        Parameters:
         * uid: the event uid

        Returns:
         * Event() or None
        """
        data = cdav.CalendarData()
        prop = dav.Prop() + data

        query = cdav.TextMatch(uid)
        query = cdav.PropFilter("UID") + query
        if comp_filter:
            query = comp_filter + query
        vcalendar = cdav.CompFilter("VCALENDAR") + query
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        response = self._query(root, 1, 'report')

        if response.status == 404:
            raise error.NotFoundError("%s not found on server" % uid)
        elif response.status == 400:
            raise error.ReportError(errmsg(response))

        items_found = response.tree.findall(".//" + dav.Response.tag)
        for r in items_found:
            href = unquote(r.find(".//" + dav.Href.tag).text)
            data = unquote(r.find(".//" + cdav.CalendarData.tag).text)
            # Ref Lucas Verney, we've actually done a substring search, if the
            # uid given in the query is short (i.e. just "0") we're likely to
            # get false positives back from the server.
            #
            # Long uids are folded, so splice the lines together here before
            # attempting a match.
            item_uid = re.search(r'\nUID:((.|\n[ \t])*)\n', data)
            if (not item_uid or
                    re.sub(r'\n[ \t]', '', item_uid.group(1)) != uid):
                continue
            return self._calendar_comp_class_by_data(data)(
                self.client, url=URL.objectify(href), data=data, parent=self)
        raise error.NotFoundError("%s not found on server" % uid)
Esempio n. 17
0
    def journals(self):
        """
        List all journals from the calendar.

        Returns:
         * [Journal(), ...]
        """
        # TODO: this is basically a copy of events() - can we do more
        # refactoring and consolidation here?  Maybe it's wrong to do
        # separate methods for journals, todos and events?
        data = cdav.CalendarData()
        prop = dav.Prop() + data
        vevent = cdav.CompFilter("VJOURNAL")
        vcalendar = cdav.CompFilter("VCALENDAR") + vevent
        filter = cdav.Filter() + vcalendar
        root = cdav.CalendarQuery() + [prop, filter]

        return self.search(root, comp_class=Journal)
Esempio n. 18
0
    def object_by_uid(self, uid, comp_filter=None):
        """
        Get one event from the calendar.

        Parameters:
         * uid: the event uid

        Returns:
         * Event() or None
        """
        data = cdav.CalendarData()
        prop = dav.Prop() + data

        query = cdav.TextMatch(uid)
        query = cdav.PropFilter("UID") + query
        if comp_filter:
            query = comp_filter + query
        vcalendar = cdav.CompFilter("VCALENDAR") + query
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        response = self._query(root, 1, 'report')

        if response.status == 404:
            raise error.NotFoundError(errmsg(response))
        elif response.status == 400:
            raise error.ReportError(errmsg(response))

        items_found = response.tree.findall(".//" + dav.Response.tag)
        for r in items_found:
            href = unquote(r.find(".//" + dav.Href.tag).text)
            data = unquote(r.find(".//" + cdav.CalendarData.tag).text)
            # Ref Lucas Verney, we've actually done a substring search, if the
            # uid given in the query is short (i.e. just "0") we're likely to
            # get false positives back from the server.
            if not "\nUID:%s\n" % uid in data:
                # TODO: optimistic assumption, uid line is not folded.  We
                # need to unfold the content to be 100% sure that we won't
                # filter away true positives here.
                continue
            return self._calendar_comp_class_by_data(data)(
                self.client, url=URL.objectify(href), data=data, parent=self)
        raise error.NotFoundError(errmsg(response))
Esempio n. 19
0
    def date_search(self, start, end=None):
        """
        Search events by date in the calendar. Recurring events are expanded
        if they have an occurence during the specified time frame.

        Parameters:
         * start = datetime.today().
         * end = same as above.

        Returns:
         * [Event(), ...]
        """
        matches = []

        # build the request
        expand = cdav.Expand(start, end)
        data = cdav.CalendarData() + expand
        prop = dav.Prop() + data

        range = cdav.TimeRange(start, end)
        vevent = cdav.CompFilter("VEVENT") + range
        vcalendar = cdav.CompFilter("VCALENDAR") + vevent
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        q = etree.tostring(root.xmlelement(), encoding="utf-8",
                           xml_declaration=True)
        response = self.client.report(self.url, q, 1)
        for r in response.tree.findall(".//" + dav.Response.tag):
            status = r.find(".//" + dav.Status.tag)
            if status.text.endswith("200 OK"):
                href = URL.objectify(r.find(dav.Href.tag).text)
                href = self.url.join(href)
                data = r.find(".//" + cdav.CalendarData.tag).text
                e = Event(self.client, url=href, data=data, parent=self)
                matches.append(e)
            else:
                raise error.ReportError(response.raw)

        return matches
Esempio n. 20
0
    def event_by_uid(self, uid):
        """
        Get one event from the calendar.

        Parameters:
         * uid: the event uid

        Returns:
         * Event() or None
        """
        e = None

        data = cdav.CalendarData()
        prop = dav.Prop() + data

        match = cdav.TextMatch(uid)
        propf = cdav.PropFilter("UID") + match
        vevent = cdav.CompFilter("VEVENT") + propf
        vcalendar = cdav.CompFilter("VCALENDAR") + vevent
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        q = etree.tostring(root.xmlelement(), encoding="utf-8",
                           xml_declaration=True)
        response = self.client.report(self.url, q, 1)

        if response.status == 404:
            raise error.NotFoundError(response.raw)
        elif response.status == 400:
            raise error.ReportError(response.raw)
            
        r = response.tree.find(".//" + dav.Response.tag)
        if r is not None:
            href = URL.objectify(r.find(".//" + dav.Href.tag).text)
            data = r.find(".//" + cdav.CalendarData.tag).text
            e = Event(self.client, url=href, data=data, parent=self)
        else:
            raise error.NotFoundError(response.raw)

        return e
Esempio n. 21
0
    def set_properties(self, props=[]):
        """
        Set properties (PROPPATCH) for this object.

         * props = [dav.DisplayName('name'), ...]

        Returns:
         * self
        """
        prop = dav.Prop() + props
        set = dav.Set() + prop
        root = dav.PropertyUpdate() + set

        r = self._query(root, query_method='proppatch')

        statuses = r.tree.findall(".//" + dav.Status.tag)
        for s in statuses:
            if ' 200 ' not in s.text:
                raise error.PropsetError(errmsg(r))

        return self
Esempio n. 22
0
    def _fetch_todos(self, filters):
        # ref https://www.ietf.org/rfc/rfc4791.txt, section 7.8.9
        matches = []

        # build the request
        data = cdav.CalendarData()
        prop = dav.Prop() + data

        vcalendar = cdav.CompFilter("VCALENDAR") + filters
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        response = self._query(root, 1, 'report')
        results = self._handle_prop_response(
            response=response, props=[cdav.CalendarData()])
        for r in results:
            matches.append(
                Todo(self.client, url=self.url.join(r),
                     data=results[r][cdav.CalendarData.tag], parent=self))
        return matches
Esempio n. 23
0
    def calendar_multiget(self, event_urls):
        """
		get multiple events' data
        @author [email protected]
        @type events list of Event
        """
        rv = []
        prop = dav.Prop() + cdav.CalendarData()
        root = cdav.CalendarMultiGet() + prop + [
            dav.Href(value=u.path) for u in event_urls
        ]
        response = self._query(root, 1, 'report')
        results = self._handle_prop_response(response=response,
                                             props=[cdav.CalendarData()])
        for r in results:
            rv.append(
                Event(self.client,
                      url=self.url.join(r),
                      data=results[r][cdav.CalendarData.tag],
                      parent=self))

        return rv
Esempio n. 24
0
    def object_by_uid(self, uid, comp_filter=None):
        """
        Get one event from the calendar.

        Parameters:
         * uid: the event uid

        Returns:
         * Event() or None
        """
        data = cdav.CalendarData()
        prop = dav.Prop() + data

        query = cdav.TextMatch(uid)
        query = cdav.PropFilter("UID") + query
        if comp_filter:
            query = comp_filter + query
        else:
            raise Exception("Need a comp_filter")
        vcalendar = cdav.CompFilter("VCALENDAR") + query
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        response = self._query(root, 1, 'report')

        if response.status == 404:
            raise error.NotFoundError(response.raw)
        elif response.status == 400:
            raise error.ReportError(response.raw)

        r = response.tree.find(".//" + dav.Response.tag)
        if r is not None:
            href = r.find(".//" + dav.Href.tag).text
            data = r.find(".//" + cdav.CalendarData.tag).text
            return self._calendar_comp_class_by_data(data)(
                self.client, url=URL.objectify(href), data=data, parent=self)
        else:
            raise error.NotFoundError(response.raw)
Esempio n. 25
0
    def object_by_uid(self, uid, comp_filter=None):
        """
        Get one event from the calendar.

        Parameters:
         * uid: the event uid

        Returns:
         * Event() or None
        """
        data = cdav.CalendarData()
        prop = dav.Prop() + data

        query = cdav.TextMatch(uid)
        query = cdav.PropFilter("UID") + query
        if comp_filter:
            query = comp_filter + query
        vcalendar = cdav.CompFilter("VCALENDAR") + query
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        items_found = self.search(root)

        # Ref Lucas Verney, we've actually done a substring search, if the
        # uid given in the query is short (i.e. just "0") we're likely to
        # get false positives back from the server, we need to do an extra
        # check that the uid is correct
        for item in items_found:
            # Long uids are folded, so splice the lines together here before
            # attempting a match.
            item_uid = re.search(r'\nUID:((.|\n[ \t])*)\n', item.data)
            if (not item_uid
                    or re.sub(r'\n[ \t]', '', item_uid.group(1)) != uid):
                continue
            return item
        raise error.NotFoundError("%s not found on server" % uid)
Esempio n. 26
0
    def build_date_search_query(self,
                                start,
                                end=None,
                                compfilter="VEVENT",
                                expand="maybe"):
        """
        Split out from the date_search-method below.  The idea is that
        maybe the generated query can be amended, i.e. to filter out
        by category etc.  To be followed up in
        https://github.com/python-caldav/caldav/issues/16
        """
        ## for backward compatibility - expand should be false
        ## in an open-ended date search, otherwise true
        if expand == 'maybe':
            expand = end

        # Some servers will raise an error if we send the expand flag
        # but don't set any end-date - expand doesn't make much sense
        # if we have one recurring event describing an indefinite
        # series of events.  I think it's appropriate to raise an error
        # in this case.
        if not end and expand:
            raise error.ReportError(
                "an open-ended date search cannot be expanded")
        elif expand:
            data = cdav.CalendarData() + cdav.Expand(start, end)
        else:
            data = cdav.CalendarData()
        prop = dav.Prop() + data

        query = cdav.TimeRange(start, end)
        if compfilter:
            query = cdav.CompFilter(compfilter) + query
        vcalendar = cdav.CompFilter("VCALENDAR") + query
        filter = cdav.Filter() + vcalendar
        root = cdav.CalendarQuery() + [prop, filter]
        return root
Esempio n. 27
0
    def set_properties(self, props=[]):
        """
        Set properties (PROPPATCH) for this object.

        Parameters:
         * props = [dav.DisplayName('name'), ...]

        Returns:
         * self
        """
        prop = dav.Prop() + props
        set = dav.Set() + prop
        root = dav.PropertyUpdate() + set

        q = etree.tostring(root.xmlelement(), encoding="utf-8",
                           xml_declaration=True)
        r = self.client.proppatch(self.url, q)

        statuses = r.tree.findall(".//" + dav.Status.tag)
        for s in statuses:
            if not s.text.endswith("200 OK"):
                raise error.PropsetError(r.raw)

        return self
Esempio n. 28
0
    def tasks(self):
        """
        Search tasks in the calendar

        Returns:
         * [Task(), ...]
        """
        matches = []

        # build the request
        getetag = dav.GetEtag()
        data = cdav.CalendarData()
        prop = dav.Prop() + [getetag, data]

        vevent = cdav.CompFilter("VTODO")
        vcal = cdav.CompFilter("VCALENDAR") + vevent
        filter = cdav.Filter() + vcal

        root = cdav.CalendarQuery() + [prop, filter]

        q = etree.tostring(root.xmlelement(), encoding="utf-8",
                           xml_declaration=True)
        response = self.client.report(self.url.path, q, 1)
        for r in response.tree.findall(".//" + dav.Response.tag):
            status = r.find(".//" + dav.Status.tag)
            if status.text.endswith("200 OK"):
                href = urlparse.urlparse(r.find(dav.Href.tag).text)
                href = url.canonicalize(href, self)
                data = r.find(".//" + cdav.CalendarData.tag).text
                etag = r.find(".//" + dav.GetEtag.tag).text
                e = self.event_cls(self.client, url=href, data=data, parent=self, etag=etag)
                matches.append(e)
            else:
                raise error.ReportError(response.raw)

        return matches
Esempio n. 29
0
    def todos(self,
              sort_keys=('due', 'priority'),
              include_completed=False,
              sort_key=None):
        """
        fetches a list of todo events.

        Parameters:
         * sort_keys: use this field in the VTODO for sorting (iterable of
           lower case string, i.e. ('priority','due')).
         * include_completed: boolean -
           by default, only pending tasks are listed
         * sort_key: DEPRECATED, for backwards compatibility with version 0.4.
        """
        # ref https://www.ietf.org/rfc/rfc4791.txt, section 7.8.9
        matches = []

        # build the request
        data = cdav.CalendarData()
        prop = dav.Prop() + data

        if sort_key:
            sort_keys = (sort_key, )

        if not include_completed:
            vnotcompleted = cdav.TextMatch('COMPLETED', negate=True)
            vnotcancelled = cdav.TextMatch('CANCELLED', negate=True)
            vstatusNotCompleted = cdav.PropFilter(
                'STATUS') + vnotcompleted + cdav.NotDefined()
            vstatusNotCancelled = cdav.PropFilter(
                'STATUS') + vnotcancelled + cdav.NotDefined()
            vnocompletedate = cdav.PropFilter('COMPLETED') + cdav.NotDefined()
            vtodo = (cdav.CompFilter("VTODO") + vnocompletedate +
                     vstatusNotCompleted + vstatusNotCancelled)
        else:
            vtodo = cdav.CompFilter("VTODO")
        vcalendar = cdav.CompFilter("VCALENDAR") + vtodo
        filter = cdav.Filter() + vcalendar

        root = cdav.CalendarQuery() + [prop, filter]

        response = self._query(root, 1, 'report')
        results = self._handle_prop_response(response=response,
                                             props=[cdav.CalendarData()])
        for r in results:
            matches.append(
                Todo(self.client,
                     url=self.url.join(r),
                     data=results[r][cdav.CalendarData.tag],
                     parent=self))

        def sort_key_func(x):
            ret = []
            vtodo = x.instance.vtodo
            defaults = {
                'due':
                '2050-01-01',
                'dtstart':
                '1970-01-01',
                'priority':
                '0',
                # JA: why compare datetime.strftime('%F%H%M%S')
                # JA: and not simply datetime?
                'isnt_overdue':
                not (hasattr(vtodo, 'due')
                     and vtodo.due.value.strftime('%F%H%M%S') <
                     datetime.datetime.now().strftime('%F%H%M%S')),
                'hasnt_started':
                (hasattr(vtodo, 'dtstart')
                 and vtodo.dtstart.value.strftime('%F%H%M%S') >
                 datetime.datetime.now().strftime('%F%H%M%S'))
            }
            for sort_key in sort_keys:
                val = getattr(vtodo, sort_key, None)
                if val is None:
                    ret.append(defaults.get(sort_key, '0'))
                    continue
                val = val.value
                if hasattr(val, 'strftime'):
                    ret.append(val.strftime('%F%H%M%S'))
                else:
                    ret.append(val)
            return ret

        if sort_keys:
            matches.sort(key=sort_key_func)
        return matches
Esempio n. 30
0
    def _create(self, name, id=None, supported_calendar_component_set=None):
        """
        Create a new calendar with display name `name` in `parent`.
        """
        if id is None:
            id = str(uuid.uuid1())
        self.id = id

        path = self.parent.url.join(id)
        self.url = path

        # TODO: mkcalendar seems to ignore the body on most servers?
        # at least the name doesn't get set this way.
        # zimbra gives 500 (!) if body is omitted ...

        # ehm ... this element seems non-existent in the RFC?
        # Breaks with baikal, too ...
        # cal = cdav.CalendarCollection()
        # coll = dav.Collection() # + cal # also breaks on baikal,
        #                                # and probably not needed?
        # type = dav.ResourceType() ## probably not needed?

        prop = dav.Prop()  # + [type,]
        if name:
            display_name = dav.DisplayName(name)
            prop += [display_name, ]
        if supported_calendar_component_set:
            sccs = cdav.SupportedCalendarComponentSet()
            for scc in supported_calendar_component_set:
                sccs += cdav.Comp(scc)
            prop += sccs
        set = dav.Set() + prop

        mkcol = cdav.Mkcalendar() + set

        r = self._query(root=mkcol, query_method='mkcalendar', url=path,
                        expected_return_value=201)

        # COMPATIBILITY ISSUE
        # name should already be set, but we've seen caldav servers failing
        # on setting the DisplayName on calendar creation
        # (DAViCal, Zimbra, ...).  Better to be explicit.
        if name:
            try:
                self.set_properties([display_name])
            except:
                self.delete()
                raise

        # Special hack for Zimbra!  The calendar we've made exists at
        # the specified URL, and we can do operations like ls, even
        # PUT an event to the calendar.  Zimbra will enforce that the
        # event uuid matches the event url, and return either 201 or
        # 302 - but alas, try to do a GET towards the event and we
        # get 404!  But turn around and replace the calendar ID with
        # the calendar name in the URL and hey ... it works!

        # TODO: write test cases for calendars with non-trivial
        # names and calendars with names already matching existing
        # calendar urls and ensure they pass.
        zimbra_url = self.parent.url.join(name)
        try:
            ret = self.client.request(zimbra_url)
            if ret.status == 404:
                raise error.NotFoundError
            # special hack for radicale.
            # It will happily accept any calendar-URL without returning 404.
            ret = self.client.request(self.parent.url.join(
                'ANYTHINGGOESHEREthisshouldforsurereturn404'))
            if ret.status == 404:
                # insane server
                self.url = zimbra_url
        except error.NotFoundError:
            # sane server
            pass