<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Cyrus CalDAV Scheduling Flowchart</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body> <h1>Cyrus CalDAV Scheduling Flowchart</h1> <h3 id="caldav_put">caldav_put() - create/modify via HTTP PUT on a resource or POST (add-member) on a calendar</h3> <ol> <li>Check if the new resource is a scheduling resource (contains ORGANIZER property). If not, skip to step 4.</li> <li>Check for (and load) any existing resource.</li> <li>Check if the authenticated user matches ORGANIZER: <ul> <li>If yes: <ul> <li>If only voter (VPOLL) responses changed, goto <a href="#sched_pollstatus">sched_pollstatus()</a>.</li> <li>Otherwise, goto <a href="#sched_request">sched_request()</a>.</li> </ul> <li>Otherwise, goto <a href="#sched_reply">sched_reply()</a>.</li> </ul> <li>Store the new/modified resource.</li> </ol> <h3 id="caldav_delete_sched">caldav_delete_sched() - remove via HTTP DELETE on a resource</h3> <ol> <li>Check if the existing resource is a scheduling resource (has Schedule-Tag). If not, we are done.</li> <li>Load the existing resource.</li> <li>Check if the authenticated user matches ORGANIZER. If yes, goto <a href="#sched_request">sched_request()</a>, otherwise goto <a href="#sched_reply">sched_reply()</a>.</li> </ol> <h3 id="caldav_post">caldav_post() - busytime query via HTTP POST on Scheduling Outbox</h3> <ol> <li>Check the ACL on the owner's Scheduling Outbox. If the authenticated user doesn't have the DACL_SCHEDFB right, fail.</li> <li><a href="#sched_busytime_query">sched_busytime_query()</a>.</li> </ol> <hr> <h3 id="sched_pollstatus">sched_pollstatus - perform a voter response update</h3> <ol> <li></li> </ol> <hr> <h3 id="sched_request">sched_request() - perform an organizer request / attendee status update</h3> <ol> <li>Check the ACL on the owner's Scheduling Outbox. If the authenticated user doesn't have the DACL_INVITE right, fail.</li> <li>If the request includes a resource, then set METHOD:REQUEST, otherwise set METHOD:CANCEL.</li> <li>Create an iTIP message template, copying over any CALSCALE property and VTIMEZONE components.</li> <li>If not an attendee status update and the existing resource is a scheduling resource: Foreach component in the existing resource, add it and its SEQUENCE to our hash table keyed by RECURRENCE-ID (for comparison against new/modified resource).</li> <li>Create a hash table of attendees. This will hold attendee-specific iTIP messages.</li> <li>Foreach component in the new/modified resource:</li> <ol type=a> <li>Lookup (by RECURRENCE-ID) and remove the component from the hash table of existing components.</li> <li>If the component exists compare all of DTSTART, DTEND, DURATION, RRULE, RDATE, EXDATE to those of the new component.</li> <li>If the component is new or changed, then <a href="#process_attendees">process_attendees()</a>.</li> </ol> <li>Foreach remaining component in the hash table of existing components do <a href="#sched_cancel">sched_cancel()</a>.</li> <li>Foreach iTIP message in our hash table of ATTENDEES, <a href="#sched_deliver">sched_deliver()</a> the iTIP message.</li> <li>Foreach component in the new/modified resource update the SCHEDULE-STATUS of each ATTENDEE.</li> </ol> <h3 id="process_attendees">process_attendees() - create a suitable iTIP request message for each attendee</h3> <ol> <li>Foreach ATTENDEE in the component, remove the SCHEDULE-STATUS parameter, and set PROPSTAT=NEEDS-ACTION if required.</li> <li>Make a copy of the component and <a href="#clean_component">clean_component()</a>.</li> <li>Foreach ATTENDEE in the cleaned component:</li> <ol type=a> <li>Check the CalDAV Scheduling parameters. If SCHEDULE-AGENT != SERVER, skip to the next attendee.</li> <li>Lookup attendee in our hash table.</li> <li>If it doesn't exist, create a clone of our iTIP template and insert it into our hash table of attendees.</li> <li>Add the component to the attendee's iTIP message.</li> <li>Add the component “number” to our mask of new components appearing in the attendee's iTIP message.</li> </ol> <li>If the component is not the "master", foreach attendee do <a href="#sched_exclude">sched_exclude()</a>.</li> </ol> <h3 id="sched_exclude">sched_exclude() - exclude an attendee from a recurrence instance</h3> <ol> <li>If the component did not appear in the attendee's iTIP message, add an EXDATE property (based on the RECURRENCE-ID of the component) to the master component of the attendee's iTIP message.</li> </ol> <h3 id="sched_cancel">sched_cancel() - cancel an organizer event/task</h3> <ol> <li>Set STATUS:CANCELLED on the component.</li> <li><a href="#process_attendees">process_attendees()</a>.</li> </ol> <hr> <h3 id="sched_reply">sched_reply() - perform an attendee reply</h3> <ol> <li>Check the CalDAV Scheduling parameters on ORGANIZER. If SCHEDULE-AGENT != SERVER, we are done.</li> <li>Check the ACL on the owner's Scheduling Outbox. If the authenticated user doesn't have the DACL_REPLY right, fail.</li> <li>Create a new iTIP (METHOD:REPLY) message, copying over any CALSCALE property and VTIMEZONE components.</li> <li>Foreach component in the existing resource:</li> <ol type=a> <li><a href="#trim_attendees">trim_attendees()</a>.</li> <li>Add the trimmed component and the attendee's PARTSTAT to our hash table keyed by RECURRENCE-ID (for comparison against new/modified resource).</li> </ol> <li>Foreach component in the new/modified resource:</li> <ol type=a> <li><a href="#trim_attendees">trim_attendees()</a>.</li> <li>Lookup (by RECURRENCE-ID) and remove the component from the hash table of existing components.</li> <li>If the component exists:</li> <ol type=i> <li>If component is VPOLL, add voter responses to REPLY via <a href="#sched_vpoll_reply">sched_vpoll_reply().</a></li> <li>Otherwise, compare the PARTSTAT of the ATTENDEE to that of the new component.</li> </ol> <li>If the component is new or the PARTSTAT has changed:</li> <ol type=i> <li><a href="#clean_component">clean_component()</a>.</li> <li>Add the component to our iTIP message.</li> <li>Add the component “number” to our mask of new components appearing in our iTIP message.</li> </ol> </ol> <li>Foreach remaining component in the hash table of existing components do <a href="#sched_decline">sched_decline()</a>.</li> <li><a href="#sched_deliver">sched_deliver()</a> our iTIP message.</li> <li>Foreach component in the new/modified resource that appeared in our iTIP message, update the SCHEDULE-STATUS of the ORGANIZER.</li> </ol> <h3 id="trim_attendees">trim_attendees() - remove all attendees other than the one replying</h3> <ol> <li>Clone the component and remove all ATTENDEE properties other than the one corresponding to the owner of the calendar.</li> <li>Return the ATTENDEE property of owner, his/her PARTSTAT parameter, and the RECURRENCE-ID of the component.</li> </ol> <h3 id="sched_vpoll_reply">sched_vpoll_reply() - add voter responses to VPOLL reply</h3> <ol> <li></li> </ol> <h3 id="sched_decline">sched_decline() - decline a recurrence instance for an attendee</h3> <ol> <li>Set PARTSTAT of ATTENDEE to DECLINED.</li> <li><a href="#clean_component">clean_component()</a>.</li> <li>Add the component to our iTIP message.</li> </ol> <hr> <h3 id="clean_component">clean_component() - sanitize a component for use in an iTIP message</h3> <ol> <li>Update DTSTAMP.</li> <li>Remove any VALARM components.</li> <li>For a reply/decline only, remove scheduling parameters from ORGANIZER.</li> </ol> <h3 id="sched_deliver">sched_deliver() - deliver an iTIP message to a recipient</h3> <ol> <li>Lookup the recipient.</li> <li>If local to our server goto <a href="#sched_deliver_local">sched_deliver_local()</a>, otherwise goto <a href="#sched_deliver_remote">sched_deliver_remote()</a>.</li> </ol> <hr> <h3 id="sched_deliver_local">sched_deliver_local() - deliver an iTIP message to a local user</h3> <ol> <li>Check the ACL on the owner's Scheduling Inbox. If the sender doesn't have the proper right (DACL_INVITE for request/cancel, DACL_REPLY for reply), fail.</li> <li>Search the recipient's calendars for a resource having the specified UID.</li> <li>If the resource doesn't exist:</li> <ol type=a> <li>If the iTIP method is REPLY, fail (we are done).</li> <li>If the iTIP method is CANCEL, ignore it (we are done).</li> <li>Otherwise, create a new (empty) attendee object and target the recipient's Default calendar.</li> </ol> <li>Otherwise, load the existing resource.</li> <li>Update the new/existing resource:</li> <ol type=a> <li>If the iTIP method is CANCEL, set STATUS:CANCELLED on all existing components.</li> <li>If the iTIP method is REPLY, do <a href="#deliver_merge_reply">deliver_merge_reply()</a>.</li> <li>If the iTIP method is REQUEST, do <a href="#deliver_merge_request">deliver_merge_request()</a>.</li> <li>If the iTIP method is POLLSTATUS, do <a href="#deliver_merge_pollstatus">deliver_merge_pollstatus()</a>.</li> </ol> <li>Store the new/updated resource in the recipient's target calendar.</li> <li>Record the delivery status (SCHEDULE-STATUS).</li> <li>If the iTIP message is something other than just a PARTSTAT update from an attendee, store the iTIP message as a new resource in the recipient's Inbox.</li> <li>If the iTIP method is REPLY, send an update other attendees via <a href="#sched_pollstatus">sched_pollstatus()</a> (VPOLL only) or <a href="#sched_request">sched_request()</a>.</li> </ol> <h3 id="deliver_merge_reply">deliver_merge_reply() - update an organizer resource with an attendee reply</h3> <ol> <li>Foreach component in the existing resource, add it to our hash table keyed by RECURRENCE-ID (for comparison against iTIP message).</li> <li>Foreach component in the iTIP message:</li> <ol type=a> <li>Lookup (by RECURRENCE-ID) the component from the hash table of existing components.</li> <li>If the component doesn't exist (new recurrence overridden by ATTENDEE) create a new recurring component:</li> <ol type=i> <li>Clone the existing master component.</li> <li>Remove the RRULE property.</li> <li>Add the RECURRENCE-ID from the iTIP message.</li> <li>Replace the DTSTART, DTEND, SEQUENCE properties with those from the iTIP message.</li> <li>Add the new component to our existing resource.</li> </ol> <li>Get the sending ATTENDEE from the iTIP message.</li> <li>Find the matching ATTENDEE in the existing component.</li> <li>If not found (ATTENDEE added themselves to this recurrence), add new ATTENDEE to the component.</li> <li>Set the ATTENDEE PARTSTAT, RSVP, and SCHEDULE-STATUS parameters in the existing component.</li> <li>If the component is VPOLL, update the voter responses in the existing component via <a href="#deliver_merge_vpoll_reply">deliver_merge_vpoll_reply()</a>.</li> </ol> <li>Return the sending ATTENDEE.</li> </ol> <h3 id="deliver_merge_vpoll_reply">deliver_merge_vpoll_reply() - update an organizer resource with voter responses</h3> <ol> <li>Foreach sub-component in the existing resource, replace any voter response(s) with those from the reply.</li> </ol> <h3 id="deliver_merge_request">deliver_merge_request() - create/update an attendee resource with an organizer request</h3> <ol> <li>Foreach VTIMEZONE component in the existing resource, add it to our hash table keyed by TZID (for comparison against iTIP message).</li> <li>Foreach VTIMEZONE component in the iTIP message:</li> <ol type=a> <li>Lookup (by TZID) the VTIMEZONE component from the hash table of existing components.</li> <li>If the component exists, remove it from the existing object.</li> <li>Add the VTIMEZONE from the iTIP message to our existing object.</li> </ol> <li>Foreach component in the existing resource, add it to our hash table keyed by RECURRENCE-ID (for comparison against iTIP message).</li> <li>Foreach component in the iTIP message:</li> <ol type=a> <li>Clone a new component from the iTIP component.</li> <li>Lookup (by RECURRENCE-ID) the component from the hash table of existing components.</li> <li>If the component exists:</li> <ol type=i> <li>Compare the SEQUENCE of the new component to the existing component to see if it has changed.</li> <li>Copy any COMPLETED, PERCENT-COMPLETE, or TRANSP properties from the existing component to the new component.</li> <li>Copy any ORGANIZER SCHEDULE-STATUS parameter from the existing component to the new component.</li> <li>Remove the existing component from the existing object.</li> </ol> <li>Add the new component to the existing object.</li> </ol> </ol> <h3 id="deliver_merge_pollstatus">deliver_merge_pollstatus() - update voter responses on a voter resource</h3> <ol> <li>Foreach sub-component in the existing resource, add it to our hash table keyed by POLL-ITEM-ID (for comparison against iTIP message). The sub-component entry includes a hash table of VOTERs.</li> <li>Foreach sub-component in the iTIP message:</li> <ol type=a> <li>Lookup (by POLL-ITEM-ID) the sub-component from the hash table of existing sub-components.</li> <li>If the component exists, foreach VOTER in the sub-component in the iTIP message: <ol type=i> <li>Lookup VOTER in the hash table of existing sub-component.</li> <li>Add/update VOTER response.</li> </ol> </li> </ol> </ol> <hr> <h3 id="sched_deliver_remote">sched_deliver_remote() - deliver an iTIP message to a remote user</h3> <ol> <li>If the recipient is local to our Murder, goto <a href="#isched_send">isched_send()</a>, otherwise goto <a href="#imip_send">imip_send()</a>.</li> <li>Retrieve status of iTIP message delivery.</li> </ol> <h3 id="isched_send">isched_send() - deliver an iTIP message to a remote user via iSchedule (HTTP)</h3> <ol> </ol> <h3 id="imip_send">imip_send() - deliver an iTIP message to a remote user via iMIP (SMTP)</h3> <ol> </ol> <hr> <h3 id="sched_busytime_query">sched_busytime_query() - perform a busytime query</h3> <ol> </ol> <h3 id="busytime_query_local">busytime_query_local() - perform a busytime query on a local user</h3> <ol> </ol> <h3 id="busytime_query_remote">busytime_query_remote() - perform a busytime query on a remote user</h3> <ol> </ol> </body> </html>