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