Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / lib / e2k-freebusy.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2002-2004 Novell, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /* e2k-freebusy.c: routines for manipulating Exchange free/busy data */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "e2k-freebusy.h"
27 #include "e2k-propnames.h"
28 #include "e2k-restriction.h"
29 #include "e2k-uri.h"
30 #include "e2k-utils.h"
31
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <libedataserver/e-time-utils.h>
36
37 /**
38  * e2k_freebusy_destroy:
39  * @fb: the #E2kFreebusy
40  *
41  * Frees @fb and all associated data.
42  **/
43 void
44 e2k_freebusy_destroy (E2kFreebusy *fb)
45 {
46         int i;
47
48         g_object_unref (fb->ctx);
49         for (i = 0; i < E2K_BUSYSTATUS_MAX; i++)
50                 g_array_free (fb->events[i], TRUE);
51         g_free (fb->uri);
52         g_free (fb->dn);
53         g_free (fb);
54 }
55
56 static char *
57 fb_uri_for_dn (const char *public_uri, const char *dn)
58 {
59         char *uri, *div, *org;
60         GString *str;
61
62         for (div = strchr (dn, '/'); div; div = strchr (div + 1, '/')) {
63                 if (!g_ascii_strncasecmp (div, "/cn=", 4))
64                         break;
65         }
66         g_return_val_if_fail (div, NULL);
67
68         org = g_strndup (dn, div - dn);
69
70         str = g_string_new (public_uri);
71         g_string_append (str, "/NON_IPM_SUBTREE/SCHEDULE%2B%20FREE%20BUSY/EX:");
72         e2k_uri_append_encoded (str, org, TRUE, NULL);
73         g_string_append (str, "/USER-");
74         e2k_uri_append_encoded (str, div, TRUE, NULL);
75         g_string_append (str, ".EML");
76
77         uri = str->str;
78         g_string_free (str, FALSE);
79         g_free (org);
80
81         return uri;
82 }
83
84 static void
85 merge_events (GArray *events)
86 {
87         E2kFreebusyEvent evt, evt2;
88         int i;
89
90         if (events->len < 2)
91                 return;
92
93         evt = g_array_index (events, E2kFreebusyEvent, 0);
94         for (i = 1; i < events->len; i++) {
95                 evt2 = g_array_index (events, E2kFreebusyEvent, i);
96                 if (evt.end >= evt2.start) {
97                         if (evt2.end > evt.end)
98                                 evt.end = evt2.end;
99                         g_array_remove_index (events, i--);
100                 } else
101                         evt = evt2;
102         }
103 }
104
105 static void
106 add_data_for_status (E2kFreebusy *fb, GPtrArray *monthyears, GPtrArray *fbdatas, GArray *events)
107 {
108         E2kFreebusyEvent evt;
109         int i, monthyear;
110         GByteArray *fbdata;
111         unsigned char *p;
112         struct tm tm;
113
114         if (!monthyears || !fbdatas)
115                 return;
116
117         memset (&tm, 0, sizeof (tm));
118         for (i = 0; i < monthyears->len && i < fbdatas->len; i++) {
119                 monthyear = atoi (monthyears->pdata[i]);
120                 fbdata = fbdatas->pdata[i];
121
122                 tm.tm_year = (monthyear >> 4) - 1900;
123                 tm.tm_mon = (monthyear & 0xF) - 1;
124
125                 for (p = fbdata->data; p + 3 < fbdata->data + fbdata->len; p += 4) {
126                         tm.tm_mday = 1;
127                         tm.tm_hour = 0;
128                         tm.tm_min = p[0] + p[1] * 256;
129                         evt.start = e_mktime_utc (&tm);
130
131                         tm.tm_mday = 1;
132                         tm.tm_hour = 0;
133                         tm.tm_min = p[2] + p[3] * 256;
134                         evt.end = e_mktime_utc (&tm);
135
136                         g_array_append_val (events, evt);
137                 }
138         }
139         merge_events (events);
140 }
141
142 static const char *public_freebusy_props[] = {
143         PR_FREEBUSY_START_RANGE,
144         PR_FREEBUSY_END_RANGE,
145         PR_FREEBUSY_ALL_MONTHS,
146         PR_FREEBUSY_ALL_EVENTS,
147         PR_FREEBUSY_TENTATIVE_MONTHS,
148         PR_FREEBUSY_TENTATIVE_EVENTS,
149         PR_FREEBUSY_BUSY_MONTHS,
150         PR_FREEBUSY_BUSY_EVENTS,
151         PR_FREEBUSY_OOF_MONTHS,
152         PR_FREEBUSY_OOF_EVENTS
153 };
154 static const int n_public_freebusy_props = sizeof (public_freebusy_props) / sizeof (public_freebusy_props[0]);
155
156 /**
157  * e2k_freebusy_new:
158  * @ctx: an #E2kContext
159  * @public_uri: the URI of the MAPI public folder tree
160  * @dn: the legacy Exchange DN of a user
161  *
162  * Creates a new #E2kFreebusy, filled in with information from the
163  * indicated user's published free/busy information. This uses the
164  * public free/busy folder; the caller does not need permission to
165  * access the @dn's Calendar.
166  *
167  * Note that currently, this will fail and return %NULL if the user
168  * does not already have free/busy information stored on the server.
169  *
170  * Return value: the freebusy information
171  **/
172 E2kFreebusy *
173 e2k_freebusy_new (E2kContext *ctx, const char *public_uri, const char *dn)
174 {
175         E2kFreebusy *fb;
176         char *uri, *time;
177         GPtrArray *monthyears, *fbdatas;
178         E2kHTTPStatus status;
179         E2kResult *results;
180         int nresults = 0, i;
181
182         uri = fb_uri_for_dn (public_uri, dn);
183         g_return_val_if_fail (uri, NULL);
184
185         status = e2k_context_propfind (ctx, NULL, uri,
186                                        public_freebusy_props,
187                                        n_public_freebusy_props,
188                                        &results, &nresults);
189         if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status) || nresults == 0) {
190                 /* FIXME: create it */
191                 g_free (uri);
192                 return NULL;
193         }
194
195         fb = g_new0 (E2kFreebusy, 1);
196         fb->uri = uri;
197         fb->dn = g_strdup (dn);
198         fb->ctx = ctx;
199         g_object_ref (ctx);
200
201         for (i = 0; i < E2K_BUSYSTATUS_MAX; i++)
202                 fb->events[i] = g_array_new (FALSE, FALSE, sizeof (E2kFreebusyEvent));
203
204         time = e2k_properties_get_prop (
205                 results[0].props, PR_FREEBUSY_START_RANGE);
206         fb->start = time ? e2k_systime_to_time_t (strtol (time, NULL, 10)) : 0;
207         time = e2k_properties_get_prop (
208                 results[0].props, PR_FREEBUSY_END_RANGE);
209         fb->end = time ? e2k_systime_to_time_t (strtol (time, NULL, 10)) : 0;
210
211         monthyears = e2k_properties_get_prop (
212                 results[0].props, PR_FREEBUSY_ALL_MONTHS);
213         fbdatas = e2k_properties_get_prop (
214                 results[0].props, PR_FREEBUSY_ALL_EVENTS);
215         add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_ALL]);
216
217         monthyears = e2k_properties_get_prop (
218                 results[0].props, PR_FREEBUSY_TENTATIVE_MONTHS);
219         fbdatas = e2k_properties_get_prop (
220                 results[0].props, PR_FREEBUSY_TENTATIVE_EVENTS);
221         add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_TENTATIVE]);
222
223         monthyears = e2k_properties_get_prop (
224                 results[0].props, PR_FREEBUSY_BUSY_MONTHS);
225         fbdatas = e2k_properties_get_prop (
226                 results[0].props, PR_FREEBUSY_BUSY_EVENTS);
227         add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_BUSY]);
228
229         monthyears = e2k_properties_get_prop (
230                 results[0].props, PR_FREEBUSY_OOF_MONTHS);
231         fbdatas = e2k_properties_get_prop (
232                 results[0].props, PR_FREEBUSY_OOF_EVENTS);
233         add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_OOF]);
234
235         e2k_results_free (results, nresults);
236         return fb;
237 }
238
239 /**
240  * e2k_freebusy_reset:
241  * @fb: an #E2kFreebusy
242  * @nmonths: the number of months of info @fb will store
243  *
244  * Clears all existing data in @fb and resets the start and end times
245  * to a span of @nmonths around the current date.
246  **/
247 void
248 e2k_freebusy_reset (E2kFreebusy *fb, int nmonths)
249 {
250         time_t now;
251         struct tm tm;
252         int i;
253
254         /* Remove all existing events */
255         for (i = 0; i < E2K_BUSYSTATUS_MAX; i++)
256                 g_array_set_size (fb->events[i], 0);
257
258         /* Set the start and end times appropriately: from the beginning
259          * of the current month until nmonths later.
260          * FIXME: Use default timezone, not local time.
261          */
262         now = time (NULL);
263         tm = *gmtime (&now);
264         tm.tm_mday = 1;
265         tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
266
267         tm.tm_isdst = -1;
268         fb->start = mktime (&tm);
269
270         tm.tm_mon += nmonths;
271         tm.tm_isdst = -1;
272         fb->end = mktime (&tm);
273 }
274
275 /**
276  * e2k_freebusy_add_interval:
277  * @fb: an #E2kFreebusy
278  * @busystatus: the busy status of the interval
279  * @start: the start of the interval
280  * @end: the end of the interval
281  *
282  * This adds an interval of type @busystatus to @fb.
283  **/
284 void
285 e2k_freebusy_add_interval (E2kFreebusy *fb, E2kBusyStatus busystatus,
286                            time_t start, time_t end)
287 {
288         E2kFreebusyEvent evt, *events;
289         int i;
290
291         if (busystatus == E2K_BUSYSTATUS_FREE)
292                 return;
293
294         /* Clip to the fb's range */
295         if (start < fb->start)
296                 start = fb->start;
297         if (end > fb->end)
298                 end = fb->end;
299         if (end <= start)
300                 return;
301
302         events = (E2kFreebusyEvent *)(fb->events[busystatus]->data);
303
304         for (i = 0; i < fb->events[busystatus]->len; i++) {
305                 if (events[i].end >= start)
306                         break;
307         }
308
309         evt.start = start;
310         evt.end = end;
311
312         if (i == fb->events[busystatus]->len)
313                 g_array_append_val (fb->events[busystatus], evt);
314         else {
315                 /* events[i] is the first event that is not completely
316                  * before evt, meaning it is either completely after it,
317                  * or they overlap/abut.
318                  */
319                 if (events[i].start > end) {
320                         /* No overlap. Insert evt before events[i]. */
321                         g_array_insert_val (fb->events[busystatus], i, evt);
322                 } else {
323                         /* They overlap or abut. Merge them. */
324                         events[i].start = MIN (events[i].start, start);
325                         events[i].end   = MAX (events[i].end, end);
326                 }
327         }
328 }
329
330 /**
331  * e2k_freebusy_clear_interval:
332  * @fb: an #E2kFreebusy
333  * @start: the start of the interval
334  * @end: the end of the interval
335  *
336  * This removes any events between @start and @end in @fb.
337  **/
338 void
339 e2k_freebusy_clear_interval (E2kFreebusy *fb, time_t start, time_t end)
340 {
341         E2kFreebusyEvent *evt;
342         int busystatus, i;
343
344         for (busystatus = 0; busystatus < E2K_BUSYSTATUS_MAX; busystatus++) {
345                 for (i = 0; i < fb->events[busystatus]->len; i++) {
346                         evt = &g_array_index (fb->events[busystatus], E2kFreebusyEvent, i);
347                         if (evt->end < start || evt->start > end)
348                                 continue;
349
350                         /* evt overlaps the interval. Truncate or
351                          * remove it.
352                          */
353
354                         if (evt->start > start /* && evt->start <= end */)
355                                 evt->start = end;
356                         if (evt->end < end /* && evt->end >= start */)
357                                 evt->end = start;
358
359                         if (evt->start >= evt->end)
360                                 g_array_remove_index (fb->events[busystatus], i--);
361                 }
362         }
363 }
364
365 static const char *freebusy_props[] = {
366         E2K_PR_CALENDAR_DTSTART,
367         E2K_PR_CALENDAR_DTEND,
368         E2K_PR_CALENDAR_BUSY_STATUS
369 };
370 static const int n_freebusy_props = sizeof (freebusy_props) / sizeof (freebusy_props[0]);
371
372 /**
373  * e2k_freebusy_add_from_calendar_uri:
374  * @fb: an #E2kFreebusy
375  * @uri: the URI of a calendar folder
376  * @start_tt: start of the range to add
377  * @end_tt: end of the range to add
378  *
379  * This queries the server for events between @start_tt and @end_tt in
380  * the calendar at @uri (which the caller must have permission to
381  * read) and adds them @fb. Any previously-existing events during that
382  * range are removed.
383  *
384  * Return value: an HTTP status code.
385  **/
386 E2kHTTPStatus
387 e2k_freebusy_add_from_calendar_uri (E2kFreebusy *fb, const char *uri,
388                                     time_t start_tt, time_t end_tt)
389 {
390         char *start, *end, *busystatus;
391         E2kBusyStatus busy;
392         E2kRestriction *rn;
393         E2kResultIter *iter;
394         E2kResult *result;
395
396         e2k_freebusy_clear_interval (fb, start_tt, end_tt);
397
398         start = e2k_make_timestamp (start_tt);
399         end = e2k_make_timestamp (end_tt);
400
401         rn = e2k_restriction_andv (
402                 e2k_restriction_prop_string (E2K_PR_DAV_CONTENT_CLASS,
403                                              E2K_RELOP_EQ,
404                                              "urn:content-classes:appointment"),
405                 e2k_restriction_prop_date (E2K_PR_CALENDAR_DTEND,
406                                            E2K_RELOP_GT, start),
407                 e2k_restriction_prop_date (E2K_PR_CALENDAR_DTSTART,
408                                            E2K_RELOP_LT, end),
409                 e2k_restriction_prop_string (E2K_PR_CALENDAR_BUSY_STATUS,
410                                              E2K_RELOP_NE, "FREE"),
411                 NULL);
412
413         iter = e2k_context_search_start (fb->ctx, NULL, uri,
414                                          freebusy_props, n_freebusy_props,
415                                          rn, NULL, TRUE);
416         e2k_restriction_unref (rn);
417         g_free (start);
418         g_free (end);
419
420         while ((result = e2k_result_iter_next (iter))) {
421                 start = e2k_properties_get_prop (result->props,
422                                                  E2K_PR_CALENDAR_DTSTART);
423                 end = e2k_properties_get_prop (result->props,
424                                                E2K_PR_CALENDAR_DTEND);
425                 busystatus = e2k_properties_get_prop (result->props,
426                                                       E2K_PR_CALENDAR_BUSY_STATUS);
427                 if (!start || !end || !busystatus)
428                         continue;
429
430                 if (!strcmp (busystatus, "TENTATIVE"))
431                         busy = E2K_BUSYSTATUS_TENTATIVE;
432                 else if (!strcmp (busystatus, "OUTOFOFFICE"))
433                         busy = E2K_BUSYSTATUS_OOF;
434                 else
435                         busy = E2K_BUSYSTATUS_BUSY;
436
437                 e2k_freebusy_add_interval (fb, busy,
438                                            e2k_parse_timestamp (start),
439                                            e2k_parse_timestamp (end));
440                               
441         }
442
443         return e2k_result_iter_free (iter);
444 }
445
446 static void
447 add_events (GArray *events_array, E2kProperties *props,
448             const char *month_list_prop, const char *data_list_prop)
449 {
450         E2kFreebusyEvent *events = (E2kFreebusyEvent *)events_array->data;
451         int i, evt_start, evt_end, monthyear;
452         struct tm start_tm, end_tm;
453         time_t start, end;
454         GPtrArray *monthyears, *datas;
455         GByteArray *data;
456         char startend[4];
457
458         if (!events_array->len) {
459                 e2k_properties_remove (props, month_list_prop);
460                 e2k_properties_remove (props, data_list_prop);
461                 return;
462         }
463
464         monthyears = g_ptr_array_new ();
465         start_tm = *gmtime (&events[0].start);
466         end_tm = *gmtime (&events[events_array->len - 1].end);
467         while (start_tm.tm_year <= end_tm.tm_year ||
468                start_tm.tm_mon <= end_tm.tm_mon) {
469                 monthyear = ((start_tm.tm_year + 1900) * 16) +
470                         (start_tm.tm_mon + 1);
471                 g_ptr_array_add (monthyears, g_strdup_printf ("%d", monthyear));
472
473                 start_tm.tm_mon++;
474                 if (start_tm.tm_mon == 12) {
475                         start_tm.tm_year++;
476                         start_tm.tm_mon = 0;
477                 }
478         }            
479         e2k_properties_set_int_array (props, month_list_prop, monthyears);
480
481         datas = g_ptr_array_new ();
482         start = events[0].start;
483         i = 0;
484         while (i < events_array->len) {
485                 start_tm = *gmtime (&start);
486                 start_tm.tm_mon++;
487                 end = e_mktime_utc (&start_tm);
488
489                 data = g_byte_array_new ();
490                 while (i << events_array->len &&
491                        events[i].end > start && events[i].start < end) {
492                         if (events[i].start < start)
493                                 evt_start = 0;
494                         else
495                                 evt_start = (events[i].start - start) / 60;
496                         if (events[i].end > end)
497                                 evt_end = (end - start) / 60;
498                         else
499                                 evt_end = (events[i].end - start) / 60;
500
501                         startend[0] = evt_start & 0xFF;
502                         startend[1] = evt_start >> 8;
503                         startend[2] = evt_end & 0xFF;
504                         startend[3] = evt_end >> 8;
505                         g_byte_array_append (data, startend, 4);
506                         i++;
507                 }
508
509                 g_ptr_array_add (datas, data);
510                 start = end;
511         }
512         e2k_properties_set_binary_array (props, data_list_prop, datas);
513 }
514
515 /**
516  * e2k_freebusy_save:
517  * @fb: an #E2kFreebusy
518  *
519  * Saves the data in @fb back to the server.
520  *
521  * Return value: a libsoup or HTTP status code
522  **/
523 E2kHTTPStatus
524 e2k_freebusy_save (E2kFreebusy *fb)
525 {
526         E2kProperties *props;
527         char *timestamp;
528         E2kHTTPStatus status;
529
530         props = e2k_properties_new ();
531         e2k_properties_set_string (props, E2K_PR_EXCHANGE_MESSAGE_CLASS,
532                                    g_strdup ("IPM.Post"));
533         e2k_properties_set_int (props, PR_FREEBUSY_START_RANGE, fb->start);
534         e2k_properties_set_int (props, PR_FREEBUSY_END_RANGE, fb->end);
535         e2k_properties_set_string (props, PR_FREEBUSY_EMAIL_ADDRESS,
536                                    g_strdup (fb->dn));
537
538         add_events (fb->events[E2K_BUSYSTATUS_ALL], props,
539                     PR_FREEBUSY_ALL_MONTHS, PR_FREEBUSY_ALL_EVENTS);
540         add_events (fb->events[E2K_BUSYSTATUS_TENTATIVE], props,
541                     PR_FREEBUSY_TENTATIVE_MONTHS, PR_FREEBUSY_TENTATIVE_EVENTS);
542         add_events (fb->events[E2K_BUSYSTATUS_BUSY], props,
543                     PR_FREEBUSY_BUSY_MONTHS, PR_FREEBUSY_BUSY_EVENTS);
544         add_events (fb->events[E2K_BUSYSTATUS_OOF], props,
545                     PR_FREEBUSY_OOF_MONTHS, PR_FREEBUSY_OOF_EVENTS);
546
547         timestamp = e2k_make_timestamp (e2k_context_get_last_timestamp (fb->ctx));
548         e2k_properties_set_date (props, PR_FREEBUSY_LAST_MODIFIED, timestamp);
549
550         status = e2k_context_proppatch (fb->ctx, NULL, fb->uri, props,
551                                         TRUE, NULL);
552         e2k_properties_free (props);
553
554         return status;
555 }