PageRenderTime 29ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/evolution-exchange-3.5.2/server/lib/e2k-freebusy.c

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