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
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
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)
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))
def _handle_prop_response(self, response, props=[], type=None, what='text'): """ Internal method to massage an XML response into a dict. (This method is a result of some code refactoring work, attempting to consolidate similar-looking code) """ properties = {} # All items should be in a <D:response> element for r in response.tree.findall('.//' + dav.Response.tag): status = r.find('.//' + dav.Status.tag) ## TODO: status should never be None, this needs more research. ## added here as it solves real-world issues, ref ## https://github.com/python-caldav/caldav/pull/56 if status is not None: if (' 200 ' not in status.text and ' 207 ' not in status.text and ' 404 ' not in status.text): raise error.ReportError(errmsg(response)) # TODO: may be wrong error class href = unquote(r.find('.//' + dav.Href.tag).text) properties[href] = {} for p in props: t = r.find(".//" + p.tag) if t is None: val = None elif t is not None and list(t): if type is not None: val = t.find(".//" + type) else: val = t.find(".//*") if val is not None: val = getattr(val, what) else: val = None else: val = t.text properties[href][p.tag] = val return properties
def _handle_prop_response(self, response, props=[], type=None, what='text'): """ Internal method to massage an XML response into a dict. (This method is a result of some code refactoring work, attempting to consolidate similar-looking code) """ properties = {} # All items should be in a <D:response> element for r in response.tree.findall('.//' + dav.Response.tag): status = r.find('.//' + dav.Status.tag) if not '200 ' in status.text and not '404 ' in status.text: raise error.ReportError( response.raw) ## TODO: may be wrong error class href = r.find('.//' + dav.Href.tag).text properties[href] = {} for p in props: t = r.find(".//" + p.tag) if t is not None: if len(list(t)) > 0: if type is not None: val = t.find(".//" + type) else: val = t.find(".//*") if val is not None: val = getattr(val, what) else: val = None else: val = t.text else: val = None properties[href][p.tag] = val return properties
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
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
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)
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
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