* All OS-level includes are now included from webcit.h instead of from
[citadel.git] / webcit / availability.c
1 /*
2  * $Id$
3  *
4  * Check attendee availability for scheduling a meeting.
5  *
6  */
7
8 #include "webcit.h"
9 #include "webserver.h"
10
11
12 #ifdef WEBCIT_WITH_CALENDAR_SERVICE
13
14
15
16 /*
17  * Utility function to fetch a VFREEBUSY type of thing for
18  * any specified user.
19  */
20 icalcomponent *get_freebusy_for_user(char *who) {
21         char buf[SIZ];
22         char *serialized_fb = NULL;
23         icalcomponent *fb = NULL;
24
25         serv_printf("ICAL freebusy|%s", who);
26         serv_getln(buf, sizeof buf);
27         if (buf[0] == '1') {
28                 serialized_fb = read_server_text();
29         }
30
31         if (serialized_fb == NULL) {
32                 return NULL;
33         }
34         
35         fb = icalcomponent_new_from_string(serialized_fb);
36         free(serialized_fb);
37         if (fb == NULL) {
38                 return NULL;
39         }
40
41         return(fb);
42 }
43
44
45
46
47 /*
48  * Check to see if two events overlap.  Returns nonzero if they do.
49  * (This function is used in both Citadel and WebCit.  If you change it in
50  * one place, change it in the other.  Better yet, put it in a library.)
51  */
52 int ical_ctdl_is_overlap(
53                         struct icaltimetype t1start,
54                         struct icaltimetype t1end,
55                         struct icaltimetype t2start,
56                         struct icaltimetype t2end
57 ) {
58
59         if (icaltime_is_null_time(t1start)) return(0);
60         if (icaltime_is_null_time(t2start)) return(0);
61
62         /* First, check for all-day events */
63         if (t1start.is_date) {
64                 if (!icaltime_compare_date_only(t1start, t2start)) {
65                         return(1);
66                 }
67                 if (!icaltime_is_null_time(t2end)) {
68                         if (!icaltime_compare_date_only(t1start, t2end)) {
69                                 return(1);
70                         }
71                 }
72         }
73
74         if (t2start.is_date) {
75                 if (!icaltime_compare_date_only(t2start, t1start)) {
76                         return(1);
77                 }
78                 if (!icaltime_is_null_time(t1end)) {
79                         if (!icaltime_compare_date_only(t2start, t1end)) {
80                                 return(1);
81                         }
82                 }
83         }
84
85         /* Now check for overlaps using date *and* time. */
86
87         /* First, bail out if either event 1 or event 2 is missing end time. */
88         if (icaltime_is_null_time(t1end)) return(0);
89         if (icaltime_is_null_time(t2end)) return(0);
90
91         /* If event 1 ends before event 2 starts, we're in the clear. */
92         if (icaltime_compare(t1end, t2start) <= 0) return(0);
93
94         /* If event 2 ends before event 1 starts, we're also ok. */
95         if (icaltime_compare(t2end, t1start) <= 0) return(0);
96
97         /* Otherwise, they overlap. */
98         return(1);
99 }
100
101
102
103 /*
104  * Back end function for check_attendee_availability()
105  * This one checks an individual attendee against a supplied
106  * event start and end time.  All these fields have already been
107  * broken out.  The result is placed in 'annotation'.
108  */
109 void check_individual_attendee(char *attendee_string,
110                                 struct icaltimetype event_start,
111                                 struct icaltimetype event_end,
112                                 char *annotation) {
113
114         icalcomponent *fbc = NULL;
115         icalcomponent *fb = NULL;
116         icalproperty *thisfb = NULL;
117         struct icalperiodtype period;
118
119         /* Set to 'unknown' right from the beginning.  Unless we learn
120          * something else, that's what we'll go with.
121          */
122         strcpy(annotation, "availability unknown");
123
124         fbc = get_freebusy_for_user(attendee_string);
125         if (fbc == NULL) {
126                 return;
127         }
128
129         /* Make sure we're looking at a VFREEBUSY by itself.  What we're probably
130          * looking at initially is a VFREEBUSY encapsulated in a VCALENDAR.
131          */
132         if (icalcomponent_isa(fbc) == ICAL_VCALENDAR_COMPONENT) {
133                 fb = icalcomponent_get_first_component(fbc, ICAL_VFREEBUSY_COMPONENT);
134         }
135         else if (icalcomponent_isa(fbc) == ICAL_VFREEBUSY_COMPONENT) {
136                 fb = fbc;
137         }
138
139         /* Iterate through all FREEBUSY's looking for conflicts. */
140         if (fb != NULL) {
141
142                 strcpy(annotation, "free");
143
144                 for (thisfb = icalcomponent_get_first_property(fb, ICAL_FREEBUSY_PROPERTY);
145                     thisfb != NULL;
146                     thisfb = icalcomponent_get_next_property(fb, ICAL_FREEBUSY_PROPERTY) ) {
147
148                         /* Do the check */
149                         period = icalproperty_get_freebusy(thisfb);
150                         if (ical_ctdl_is_overlap(period.start, period.end,
151                            event_start, event_end)) {
152                                 strcpy(annotation, "BUSY");
153                         }
154
155                 }
156         }
157
158         icalcomponent_free(fbc);
159 }
160
161
162
163
164 /*
165  * Check the availability of all attendees for an event (when possible)
166  * and annotate accordingly.
167  */
168 void check_attendee_availability(icalcomponent *vevent) {
169         icalproperty *attendee = NULL;
170         icalproperty *dtstart_p = NULL;
171         icalproperty *dtend_p = NULL;
172         struct icaltimetype dtstart_t;
173         struct icaltimetype dtend_t;
174         char attendee_string[SIZ];
175         char annotated_attendee_string[SIZ];
176         char annotation[SIZ];
177
178         if (vevent == NULL) {
179                 return;
180         }
181
182         /* If we're looking at a fully encapsulated VCALENDAR
183          * rather than a VEVENT component, attempt to use the first
184          * relevant VEVENT subcomponent.  If there is none, the
185          * NULL returned by icalcomponent_get_first_component() will
186          * tell the next iteration of this function to create a
187          * new one.
188          */
189         if (icalcomponent_isa(vevent) == ICAL_VCALENDAR_COMPONENT) {
190                 check_attendee_availability(
191                         icalcomponent_get_first_component(
192                                 vevent, ICAL_VEVENT_COMPONENT
193                         )
194                 );
195                 return;
196         }
197
198         ical_dezonify(vevent);          /* Convert everything to UTC */
199
200         /*
201          * Learn the start and end times.
202          */
203         dtstart_p = icalcomponent_get_first_property(vevent, ICAL_DTSTART_PROPERTY);
204         if (dtstart_p != NULL) dtstart_t = icalproperty_get_dtstart(dtstart_p);
205
206         dtend_p = icalcomponent_get_first_property(vevent, ICAL_DTEND_PROPERTY);
207         if (dtend_p != NULL) dtend_t = icalproperty_get_dtend(dtend_p);
208
209         /*
210          * Iterate through attendees.
211          */
212         for (attendee = icalcomponent_get_first_property(vevent, ICAL_ATTENDEE_PROPERTY);
213             attendee != NULL;
214             attendee = icalcomponent_get_next_property(vevent, ICAL_ATTENDEE_PROPERTY)) {
215
216                 strcpy(attendee_string, icalproperty_get_attendee(attendee));
217                 if (!strncasecmp(attendee_string, "MAILTO:", 7)) {
218
219                         /* screen name or email address */
220                         strcpy(attendee_string, &attendee_string[7]);
221                         striplt(attendee_string);
222
223                         check_individual_attendee(attendee_string,
224                                                 dtstart_t, dtend_t,
225                                                 annotation);
226
227                         /* Replace the attendee name with an annotated one. */
228                         snprintf(annotated_attendee_string, sizeof annotated_attendee_string,
229                                 "MAILTO:%s (%s)", attendee_string, annotation);
230                         icalproperty_set_attendee(attendee, annotated_attendee_string);
231
232                 }
233         }
234
235 }
236
237
238 #endif /* WEBCIT_WITH_CALENDAR_SERVICE */