static icalcomponent *busytime_query_local(struct transaction_t *txn,
					   struct propfind_ctx *fctx,
					   char mailboxname[],
					   icalproperty_method method,
					   const char *uid,
					   const char *organizer,
					   const char *attendee)
{
    struct calquery_filter *calfilter =
	(struct calquery_filter *) fctx->filter_crit;
    struct freebusy_array *freebusy = &calfilter->freebusy;
    struct vavailability_array *vavail = &calfilter->vavail;
    icalcomponent *ical = NULL;
    icalcomponent *fbcomp;
    icalproperty *prop;
    unsigned n;

    fctx->open_db = (db_open_proc_t) &my_caldav_open;
    fctx->close_db = (db_close_proc_t) &my_caldav_close;
    fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource;
    fctx->release_resource = (db_release_proc_t) &caldav_data_fini;
    fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach;
    fctx->proc_by_resource = &busytime_by_resource;

    /* Gather up all of the busytime and VAVAILABILITY periods */
    if (fctx->depth > 0) {
	/* Calendar collection(s) */

	if (txn->req_tgt.collection) {
	    /* Get busytime for target calendar collection */
	    busytime_by_collection(mailboxname, 0, 0, fctx);
	}
	else {
	    /* Get busytime for all contained calendar collections */
	    strlcat(mailboxname, ".%", MAX_MAILBOX_PATH);
	    mboxlist_findall(NULL,  /* internal namespace */
			     mailboxname, 1, httpd_userid, 
			     httpd_authstate, busytime_by_collection, fctx);
	}

	if (fctx->davdb) my_caldav_close(fctx->davdb);
    }

    if (*fctx->ret) return NULL;

    if (calfilter->flags & CHECK_USER_AVAIL) {
	/* Check for CALDAV:calendar-availability on user's Inbox */
	struct buf attrib = BUF_INITIALIZER;
	const char *prop_annot =
	    ANNOT_NS "<" XML_NS_CALDAV ">calendar-availability";
	const char *userid = mboxname_to_userid(mailboxname);
	const char *mboxname = caldav_mboxname(userid, SCHED_INBOX);
	if (!annotatemore_lookup(mboxname, prop_annot,
				 /* shared */ "", &attrib) && attrib.len) {
	    add_vavailability(vavail, icalparser_parse_string(buf_cstring(&attrib)));
	}
    }

    /* Combine VAVAILABILITY components into busytime */
    if (vavail->len) combine_vavailability(calfilter);

    /* Sort busytime periods by type and start/end times for coalescing */
    qsort(freebusy->fb, freebusy->len,
	  sizeof(struct freebusy), compare_freebusy_with_type);

    /* Coalesce busytime periods of same type into one */
    for (n = 0; n + 1 < freebusy->len; n++) {
	struct freebusy *fb, *next_fb;
	icaltimetype end, next_end;
	int isdur, next_isdur;

	fb = &freebusy->fb[n];
	next_fb = &freebusy->fb[n+1];

	/* Ignore overridden instances */
	if (fb->type == ICAL_FBTYPE_NONE) continue;

	isdur = !icaldurationtype_is_null_duration(fb->per.duration);
	end = !isdur ? fb->per.end :
	    icaltime_add(fb->per.start, fb->per.duration);
	    
	/* Skip periods of different type or that don't overlap */
	if ((fb->type != next_fb->type) ||
	    icaltime_compare(end, next_fb->per.start) < 0) continue;

	/* Coalesce into next busytime */
	next_isdur = !icaldurationtype_is_null_duration(next_fb->per.duration);
	next_end = !next_isdur ? next_fb->per.end :
	    icaltime_add(next_fb->per.start, next_fb->per.duration);
	    
	if (icaltime_compare(end, next_end) >= 0) {
	    /* Current period subsumes next */
	    next_fb->per.end = fb->per.end;
	    next_fb->per.duration = fb->per.duration;
	}
	else if (isdur && next_isdur) {
	    /* Both periods are durations */
	    struct icaldurationtype overlap =
		icaltime_subtract(end, next_fb->per.start);

	    next_fb->per.duration.days += fb->per.duration.days - overlap.days;
	}
	else {
	    /* Need to use explicit period */
	    next_fb->per.end = next_end;
	    next_fb->per.duration = icaldurationtype_null_duration();
	}

	next_fb->per.start = fb->per.start;

	/* "Remove" the instance
	   by setting fbtype to NONE (we ignore these later) */
	fb->type = ICAL_FBTYPE_NONE;
    }

    /* Sort busytime periods by start/end times for addition to VFREEBUSY */
    qsort(freebusy->fb, freebusy->len,
	  sizeof(struct freebusy), compare_freebusy);

    /* Construct iCalendar object with VFREEBUSY component */
    ical = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
			       icalproperty_new_version("2.0"),
			       icalproperty_new_prodid(ical_prodid),
			       0);

    if (method) icalcomponent_set_method(ical, method);

    fbcomp = icalcomponent_vanew(ICAL_VFREEBUSY_COMPONENT,
				 icalproperty_new_dtstamp(
				     icaltime_from_timet_with_zone(
					 time(0),
					 0,
					 icaltimezone_get_utc_timezone())),
				 icalproperty_new_dtstart(calfilter->start),
				 icalproperty_new_dtend(calfilter->end),
				 0);

    icalcomponent_add_component(ical, fbcomp);

    if (uid) icalcomponent_set_uid(fbcomp, uid);
    if (organizer) {
	prop = icalproperty_new_organizer(organizer);
	icalcomponent_add_property(fbcomp, prop);
    }
    if (attendee) {
	prop = icalproperty_new_attendee(attendee);
	icalcomponent_add_property(fbcomp, prop);
    }

    /* Add busytime periods to VFREEBUSY component */
    for (n = 0; n < freebusy->len; n++) {
	struct freebusy *fb = &freebusy->fb[n];
	icalproperty *busy;

	/* Ignore overridden instances */
	if (fb->type == ICAL_FBTYPE_NONE) continue;

	/* Create new FREEBUSY property with FBTYPE and add to component */
	busy = icalproperty_new_freebusy(fb->per);
	icalproperty_add_parameter(busy, icalparameter_new_fbtype(fb->type));
	icalcomponent_add_property(fbcomp, busy);
    }

    return ical;
}