1 /* Evolution calendar - iCalendar file backend
2  *
3  * Copyright (C) 1993 Free Software Foundation, Inc.
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * This library is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors: Federico Mena-Quintero <federico@ximian.com>
19  *          Rodrigo Moya <rodrigo@ximian.com>
20  *          Jan Brittenson <bson@gnu.ai.mit.edu>
21  */
22 
23 #include "evolution-data-server-config.h"
24 
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <glib/gstdio.h>
31 #include <glib/gi18n-lib.h>
32 
33 #include <libedataserver/libedataserver.h>
34 
35 #include "e-cal-backend-file-events.h"
36 
37 #ifndef O_BINARY
38 #define O_BINARY 0
39 #endif
40 
41 #define EC_ERROR(_code) e_client_error_create (_code, NULL)
42 #define EC_ERROR_EX(_code, _msg) e_client_error_create (_code, _msg)
43 #define EC_ERROR_NO_URI() e_client_error_create (E_CLIENT_ERROR_OTHER_ERROR, _("Cannot get URI"))
44 #define ECC_ERROR(_code) e_cal_client_error_create (_code, NULL)
45 
46 #define ECAL_REVISION_X_PROP  "X-EVOLUTION-DATA-REVISION"
47 
48 /* Placeholder for each component and its recurrences */
49 typedef struct {
50 	ECalComponent *full_object;
51 	GHashTable *recurrences;
52 	GList *recurrences_list;
53 } ECalBackendFileObject;
54 
55 /* Private part of the ECalBackendFile structure */
56 struct _ECalBackendFilePrivate {
57 	/* path where the calendar data is stored */
58 	gchar *path;
59 
60 	/* Filename in the dir */
61 	gchar *file_name;
62 	gboolean is_dirty;
63 	guint dirty_idle_id;
64 
65 	/* locked in high-level functions to ensure data is consistent
66 	 * in idle and CORBA thread(s?); because high-level functions
67 	 * may call other high-level functions the mutex must allow
68 	 * recursive locking
69 	 */
70 	GRecMutex idle_save_rmutex;
71 
72 	/* Toplevel VCALENDAR component */
73 	ICalComponent *vcalendar;
74 
75 	/* All the objects in the calendar, hashed by UID.  The
76 	 * hash key *is* the uid returned by cal_component_get_uid(); it is not
77 	 * copied, so don't free it when you remove an object from the hash
78 	 * table. Each item in the hash table is a ECalBackendFileObject.
79 	 */
80 	GHashTable *comp_uid_hash;
81 
82 	EIntervalTree *interval_tree;
83 
84 	GList *comp;
85 
86 	/* guards refresh members */
87 	GMutex refresh_lock;
88 	/* set to TRUE to indicate thread should stop */
89 	gboolean refresh_thread_stop;
90 	/* set to TRUE when the refresh thread is running;
91 	   it can happen that the refresh_cond is set, but the thread is gone */
92 	gboolean refresh_thread_running;
93 	/* condition for refreshing, not NULL when thread exists */
94 	GCond *refresh_cond;
95 	/* cond to know the refresh thread gone */
96 	GCond *refresh_gone_cond;
97 	/* increased when backend saves the file */
98 	guint refresh_skip;
99 
100 	GFileMonitor *refresh_monitor;
101 
102 	/* Just an incremental number to ensure uniqueness across revisions */
103 	guint revision_counter;
104 
105 	/* Only for ETimezoneCache::get_timezone() call */
106 	GHashTable *cached_timezones; /* gchar *tzid ~> ICalTimezone * */
107 };
108 
109 #define d(x)
110 
111 static void free_refresh_data (ECalBackendFile *cbfile);
112 
113 static void bump_revision (ECalBackendFile *cbfile);
114 
115 static void	e_cal_backend_file_timezone_cache_init
116 					(ETimezoneCacheInterface *iface);
117 
118 static ETimezoneCacheInterface *parent_timezone_cache_interface;
119 
G_DEFINE_TYPE_WITH_CODE(ECalBackendFile,e_cal_backend_file,E_TYPE_CAL_BACKEND_SYNC,G_ADD_PRIVATE (ECalBackendFile)G_IMPLEMENT_INTERFACE (E_TYPE_TIMEZONE_CACHE,e_cal_backend_file_timezone_cache_init))120 G_DEFINE_TYPE_WITH_CODE (
121 	ECalBackendFile,
122 	e_cal_backend_file,
123 	E_TYPE_CAL_BACKEND_SYNC,
124 	G_ADD_PRIVATE (ECalBackendFile)
125 	G_IMPLEMENT_INTERFACE (
126 		E_TYPE_TIMEZONE_CACHE,
127 		e_cal_backend_file_timezone_cache_init))
128 
129 /* g_hash_table_foreach() callback to destroy a ECalBackendFileObject */
130 static void
131 free_object_data (gpointer data)
132 {
133 	ECalBackendFileObject *obj_data = data;
134 
135 	if (obj_data->full_object)
136 		g_object_unref (obj_data->full_object);
137 	g_hash_table_destroy (obj_data->recurrences);
138 	g_list_free (obj_data->recurrences_list);
139 
140 	g_free (obj_data);
141 }
142 
143 /* Saves the calendar data */
144 static gboolean
save_file_when_idle(gpointer user_data)145 save_file_when_idle (gpointer user_data)
146 {
147 	ECalBackendFilePrivate *priv;
148 	GError *e = NULL;
149 	GFile *file, *backup_file;
150 	GFileOutputStream *stream;
151 	gboolean succeeded;
152 	gchar *tmp, *backup_uristr;
153 	gchar *buf;
154 	ECalBackendFile *cbfile = user_data;
155 	gboolean writable;
156 
157 	priv = cbfile->priv;
158 	g_return_val_if_fail (priv->path != NULL, FALSE);
159 	g_return_val_if_fail (priv->vcalendar != NULL, FALSE);
160 
161 	writable = e_cal_backend_get_writable (E_CAL_BACKEND (cbfile));
162 
163 	g_rec_mutex_lock (&priv->idle_save_rmutex);
164 	if (!priv->is_dirty || !writable) {
165 		priv->dirty_idle_id = 0;
166 		priv->is_dirty = FALSE;
167 		g_rec_mutex_unlock (&priv->idle_save_rmutex);
168 		return FALSE;
169 	}
170 
171 	file = g_file_new_for_path (priv->path);
172 	if (!file)
173 		goto error_malformed_uri;
174 
175 	/* save calendar to backup file */
176 	tmp = g_file_get_uri (file);
177 	if (!tmp) {
178 		g_object_unref (file);
179 		goto error_malformed_uri;
180 	}
181 
182 	backup_uristr = g_strconcat (tmp, "~", NULL);
183 	backup_file = g_file_new_for_uri (backup_uristr);
184 
185 	g_free (tmp);
186 	g_free (backup_uristr);
187 
188 	if (!backup_file) {
189 		g_object_unref (file);
190 		goto error_malformed_uri;
191 	}
192 
193 	priv->refresh_skip++;
194 	stream = g_file_replace (backup_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &e);
195 	if (!stream || e) {
196 		if (stream)
197 			g_object_unref (stream);
198 
199 		g_object_unref (file);
200 		g_object_unref (backup_file);
201 		priv->refresh_skip--;
202 		goto error;
203 	}
204 
205 	buf = i_cal_component_as_ical_string (priv->vcalendar);
206 	succeeded = g_output_stream_write_all (G_OUTPUT_STREAM (stream), buf, strlen (buf) * sizeof (gchar), NULL, NULL, &e);
207 	g_free (buf);
208 
209 	if (!succeeded || e) {
210 		g_object_unref (stream);
211 		g_object_unref (file);
212 		g_object_unref (backup_file);
213 		goto error;
214 	}
215 
216 	succeeded = g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &e);
217 	g_object_unref (stream);
218 
219 	if (!succeeded || e) {
220 		g_object_unref (file);
221 		g_object_unref (backup_file);
222 		goto error;
223 	}
224 
225 	/* now copy the temporary file to the real file */
226 	g_file_move (backup_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &e);
227 
228 	g_object_unref (file);
229 	g_object_unref (backup_file);
230 	if (e)
231 		goto error;
232 
233 	priv->is_dirty = FALSE;
234 	priv->dirty_idle_id = 0;
235 
236 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
237 
238 	return FALSE;
239 
240  error_malformed_uri:
241 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
242 	e_cal_backend_notify_error (E_CAL_BACKEND (cbfile),
243 				  _("Cannot save calendar data: Malformed URI."));
244 	return FALSE;
245 
246  error:
247 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
248 
249 	if (e) {
250 		gchar *msg = g_strdup_printf ("%s: %s", _("Cannot save calendar data"), e->message);
251 
252 		e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), msg);
253 		g_free (msg);
254 		g_error_free (e);
255 	} else
256 		e_cal_backend_notify_error (E_CAL_BACKEND (cbfile), _("Cannot save calendar data"));
257 
258 	return FALSE;
259 }
260 
261 static void
save(ECalBackendFile * cbfile,gboolean do_bump_revision)262 save (ECalBackendFile *cbfile,
263       gboolean do_bump_revision)
264 {
265 	ECalBackendFilePrivate *priv;
266 
267 	if (do_bump_revision)
268 		bump_revision (cbfile);
269 
270 	priv = cbfile->priv;
271 
272 	g_rec_mutex_lock (&priv->idle_save_rmutex);
273 	priv->is_dirty = TRUE;
274 
275 	if (!priv->dirty_idle_id)
276 		priv->dirty_idle_id = g_idle_add ((GSourceFunc) save_file_when_idle, cbfile);
277 
278 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
279 }
280 
281 static void
free_calendar_components(GHashTable * comp_uid_hash,ICalComponent * top_icomp)282 free_calendar_components (GHashTable *comp_uid_hash,
283 			  ICalComponent *top_icomp)
284 {
285 	if (comp_uid_hash)
286 		g_hash_table_destroy (comp_uid_hash);
287 
288 	if (top_icomp)
289 		g_object_unref (top_icomp);
290 }
291 
292 static void
free_calendar_data(ECalBackendFile * cbfile)293 free_calendar_data (ECalBackendFile *cbfile)
294 {
295 	ECalBackendFilePrivate *priv;
296 
297 	priv = cbfile->priv;
298 
299 	g_rec_mutex_lock (&priv->idle_save_rmutex);
300 
301 	if (priv->interval_tree)
302 		e_intervaltree_destroy (priv->interval_tree);
303 	priv->interval_tree = NULL;
304 
305 	free_calendar_components (priv->comp_uid_hash, priv->vcalendar);
306 	priv->comp_uid_hash = NULL;
307 	priv->vcalendar = NULL;
308 
309 	g_list_free (priv->comp);
310 	priv->comp = NULL;
311 
312 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
313 }
314 
315 /* Dispose handler for the file backend */
316 static void
e_cal_backend_file_dispose(GObject * object)317 e_cal_backend_file_dispose (GObject *object)
318 {
319 	ECalBackendFile *cbfile;
320 	ECalBackendFilePrivate *priv;
321 	ESource *source;
322 
323 	cbfile = E_CAL_BACKEND_FILE (object);
324 	priv = cbfile->priv;
325 
326 	free_refresh_data (E_CAL_BACKEND_FILE (object));
327 
328 	/* Save if necessary */
329 	if (priv->is_dirty)
330 		save_file_when_idle (cbfile);
331 
332 	free_calendar_data (cbfile);
333 
334 	source = e_backend_get_source (E_BACKEND (cbfile));
335 	if (source)
336 		g_signal_handlers_disconnect_matched (source, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, cbfile);
337 
338 	/* Chain up to parent's dispose() method. */
339 	G_OBJECT_CLASS (e_cal_backend_file_parent_class)->dispose (object);
340 }
341 
342 /* Finalize handler for the file backend */
343 static void
e_cal_backend_file_finalize(GObject * object)344 e_cal_backend_file_finalize (GObject *object)
345 {
346 	ECalBackendFilePrivate *priv;
347 
348 	priv = E_CAL_BACKEND_FILE (object)->priv;
349 
350 	/* Clean up */
351 
352 	if (priv->dirty_idle_id)
353 		g_source_remove (priv->dirty_idle_id);
354 
355 	g_mutex_clear (&priv->refresh_lock);
356 
357 	g_rec_mutex_clear (&priv->idle_save_rmutex);
358 	g_hash_table_destroy (priv->cached_timezones);
359 
360 	g_free (priv->path);
361 	g_free (priv->file_name);
362 
363 	/* Chain up to parent's finalize() method. */
364 	G_OBJECT_CLASS (e_cal_backend_file_parent_class)->finalize (object);
365 }
366 
367 /* Looks up a component by its UID on the backend's component hash table
368  * and returns TRUE if any event (regardless whether it is the master or a child)
369  * with that UID exists */
370 static gboolean
uid_in_use(ECalBackendFile * cbfile,const gchar * uid)371 uid_in_use (ECalBackendFile *cbfile,
372             const gchar *uid)
373 {
374 	ECalBackendFilePrivate *priv;
375 	ECalBackendFileObject *obj_data;
376 
377 	priv = cbfile->priv;
378 
379 	obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
380 	return obj_data != NULL;
381 }
382 
383 static ICalProperty *
get_revision_property(ECalBackendFile * cbfile)384 get_revision_property (ECalBackendFile *cbfile)
385 {
386 	if (!cbfile->priv->vcalendar)
387 		return NULL;
388 
389 	return e_cal_util_component_find_x_property (cbfile->priv->vcalendar, ECAL_REVISION_X_PROP);
390 }
391 
392 static gchar *
make_revision_string(ECalBackendFile * cbfile)393 make_revision_string (ECalBackendFile *cbfile)
394 {
395 	GTimeVal timeval;
396 	gchar   *datestr;
397 	gchar   *revision;
398 
399 	g_get_current_time (&timeval);
400 
401 	datestr = g_time_val_to_iso8601 (&timeval);
402 	revision = g_strdup_printf ("%s(%d)", datestr, cbfile->priv->revision_counter++);
403 
404 	g_free (datestr);
405 	return revision;
406 }
407 
408 static ICalProperty *
ensure_revision(ECalBackendFile * cbfile)409 ensure_revision (ECalBackendFile *cbfile)
410 {
411 	ICalProperty *prop;
412 
413 	if (cbfile->priv->vcalendar == NULL)
414 		return NULL;
415 
416 	prop = get_revision_property (cbfile);
417 
418 	if (!prop) {
419 		gchar *revision = make_revision_string (cbfile);
420 
421 		e_cal_util_component_set_x_property (cbfile->priv->vcalendar, ECAL_REVISION_X_PROP, revision);
422 
423 		g_free (revision);
424 
425 		prop = get_revision_property (cbfile);
426 		g_warn_if_fail (prop != NULL);
427 	}
428 
429 	return prop;
430 }
431 
432 static void
bump_revision(ECalBackendFile * cbfile)433 bump_revision (ECalBackendFile *cbfile)
434 {
435 	/* Update the revision string */
436 	ICalProperty *prop = ensure_revision (cbfile);
437 	gchar *revision = make_revision_string (cbfile);
438 
439 	i_cal_property_set_x (prop, revision);
440 
441 	e_cal_backend_notify_property_changed (E_CAL_BACKEND (cbfile),
442 					      E_CAL_BACKEND_PROPERTY_REVISION,
443 					      revision);
444 
445 	g_object_unref (prop);
446 	g_free (revision);
447 }
448 
449 /* Calendar backend methods */
450 
451 /* Get_email_address handler for the file backend */
452 static gchar *
e_cal_backend_file_get_backend_property(ECalBackend * backend,const gchar * prop_name)453 e_cal_backend_file_get_backend_property (ECalBackend *backend,
454                                          const gchar *prop_name)
455 {
456 	g_return_val_if_fail (prop_name != NULL, FALSE);
457 
458 	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
459 		return g_strjoin (
460 			",",
461 			E_CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS,
462 			E_CAL_STATIC_CAPABILITY_NO_THISANDPRIOR,
463 			E_CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED,
464 			E_CAL_STATIC_CAPABILITY_REMOVE_ONLY_THIS,
465 			E_CAL_STATIC_CAPABILITY_BULK_ADDS,
466 			E_CAL_STATIC_CAPABILITY_BULK_MODIFIES,
467 			E_CAL_STATIC_CAPABILITY_BULK_REMOVES,
468 			E_CAL_STATIC_CAPABILITY_ALARM_DESCRIPTION,
469 			E_CAL_STATIC_CAPABILITY_TASK_CAN_RECUR,
470 			E_CAL_STATIC_CAPABILITY_COMPONENT_COLOR,
471 			NULL);
472 
473 	} else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
474 		   g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
475 		ESourceLocal *local_extension;
476 
477 		local_extension = e_source_get_extension (e_backend_get_source (E_BACKEND (backend)), E_SOURCE_EXTENSION_LOCAL_BACKEND);
478 
479 		return e_source_local_dup_email_address (local_extension);
480 
481 	} else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
482 		ECalComponent *comp;
483 		gchar *prop_value;
484 
485 		comp = e_cal_component_new ();
486 
487 		switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
488 		case I_CAL_VEVENT_COMPONENT:
489 			e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
490 			break;
491 		case I_CAL_VTODO_COMPONENT:
492 			e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
493 			break;
494 		case I_CAL_VJOURNAL_COMPONENT:
495 			e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
496 			break;
497 		default:
498 			g_object_unref (comp);
499 			return NULL;
500 		}
501 
502 		prop_value = e_cal_component_get_as_string (comp);
503 
504 		g_object_unref (comp);
505 
506 		return prop_value;
507 
508 	} else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_REVISION)) {
509 		ICalProperty *prop;
510 		gchar *revision = NULL;
511 
512 		/* This returns NULL if backend lacks a vcalendar. */
513 		prop = ensure_revision (E_CAL_BACKEND_FILE (backend));
514 		if (prop) {
515 			revision = g_strdup (i_cal_property_get_x (prop));
516 			g_object_unref (prop);
517 		}
518 
519 		return revision;
520 	}
521 
522 	/* Chain up to parent's method. */
523 	return E_CAL_BACKEND_CLASS (e_cal_backend_file_parent_class)->impl_get_backend_property (backend, prop_name);
524 }
525 
526 typedef struct _ResolveTzidData {
527 	ICalComponent *vcalendar;
528 	GHashTable *zones; /* gchar *tzid ~> ICalTimezone * */
529 } ResolveTzidData;
530 
531 static void
resolve_tzid_data_init(ResolveTzidData * rtd,ICalComponent * vcalendar)532 resolve_tzid_data_init (ResolveTzidData *rtd,
533 			ICalComponent *vcalendar)
534 {
535 	if (rtd) {
536 		rtd->vcalendar = vcalendar;
537 		rtd->zones = NULL;
538 	}
539 }
540 
541 /* Clears the content, not the structure */
542 static void
resolve_tzid_data_clear(ResolveTzidData * rtd)543 resolve_tzid_data_clear (ResolveTzidData *rtd)
544 {
545 	if (rtd && rtd->zones)
546 		g_hash_table_destroy (rtd->zones);
547 }
548 
549 /* function to resolve timezones */
550 static ICalTimezone *
resolve_tzid_cb(const gchar * tzid,gpointer user_data,GCancellable * cancellable,GError ** error)551 resolve_tzid_cb (const gchar *tzid,
552 		 gpointer user_data,
553 		 GCancellable *cancellable,
554 		 GError **error)
555 {
556 	ResolveTzidData *rtd = user_data;
557 	ICalTimezone *zone;
558 
559 	if (!tzid || !tzid[0])
560 		return NULL;
561 	else if (!strcmp (tzid, "UTC"))
562 		return i_cal_timezone_get_utc_timezone ();
563 
564 	if (rtd->zones) {
565 		zone = g_hash_table_lookup (rtd->zones, tzid);
566 		if (zone)
567 			return zone;
568 	}
569 
570 	zone = i_cal_timezone_get_builtin_timezone_from_tzid (tzid);
571 	if (zone)
572 		g_object_ref (zone);
573 	else if (rtd->vcalendar)
574 		zone = i_cal_component_get_timezone (rtd->vcalendar, tzid);
575 
576 	if (zone) {
577 		if (!rtd->zones)
578 			rtd->zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
579 
580 		g_hash_table_insert (rtd->zones, g_strdup (tzid), zone);
581 	}
582 
583 	return zone;
584 }
585 
586 /* Checks if the specified component has a duplicated UID and if so changes it.
587  * UIDs may be shared between components if there is at most one component
588  * without RECURRENCE-ID (master) and all others have different RECURRENCE-ID
589  * values.
590  */
591 static void
check_dup_uid(ECalBackendFile * cbfile,ECalComponent * comp)592 check_dup_uid (ECalBackendFile *cbfile,
593                ECalComponent *comp)
594 {
595 	ECalBackendFilePrivate *priv;
596 	ECalBackendFileObject *obj_data;
597 	const gchar *uid;
598 	gchar *new_uid = NULL;
599 	gchar *rid = NULL;
600 
601 	priv = cbfile->priv;
602 
603 	uid = e_cal_component_get_uid (comp);
604 
605 	if (!uid) {
606 		g_warning ("Checking for duplicate uid, the component does not have a valid UID skipping it\n");
607 		return;
608 	}
609 
610 	obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
611 	if (!obj_data)
612 		return; /* Everything is fine */
613 
614 	rid = e_cal_component_get_recurid_as_string (comp);
615 	if (rid && *rid) {
616 		/* new component has rid, must not be the same as in other detached recurrence */
617 		if (!g_hash_table_lookup (obj_data->recurrences, rid))
618 			goto done;
619 	} else {
620 		/* new component has no rid, must not clash with existing master */
621 		if (!obj_data->full_object)
622 			goto done;
623 	}
624 
625 	d (
626 		g_message (G_STRLOC ": Got object with duplicated UID `%s' and rid `%s', changing it...",
627 		uid,
628 		rid ? rid : ""));
629 
630 	new_uid = e_util_generate_uid ();
631 	e_cal_component_set_uid (comp, new_uid);
632 
633 	/* FIXME: I think we need to reset the SEQUENCE property and reset the
634 	 * CREATED/DTSTAMP/LAST-MODIFIED.
635 	 */
636 
637 	save (cbfile, FALSE);
638 
639  done:
640 	g_free (rid);
641 	g_free (new_uid);
642 }
643 
644 static time_t
get_rid_as_time_t(ECalComponent * comp)645 get_rid_as_time_t (ECalComponent *comp)
646 {
647 	ECalComponentRange *range;
648 	ECalComponentDateTime *dt;
649 	time_t tmt = (time_t) -1;
650 
651 	range = e_cal_component_get_recurid (comp);
652 	if (!range)
653 		return tmt;
654 
655 	dt = e_cal_component_range_get_datetime (range);
656 	if (!dt) {
657 		e_cal_component_range_free (range);
658 		return tmt;
659 	}
660 
661 	tmt = i_cal_time_as_timet (e_cal_component_datetime_get_value (dt));
662 
663 	e_cal_component_range_free (range);
664 
665 	return tmt;
666 }
667 
668 /* Adds component to the interval tree
669  */
670 static void
add_component_to_intervaltree(ECalBackendFile * cbfile,ECalComponent * comp)671 add_component_to_intervaltree (ECalBackendFile *cbfile,
672                                ECalComponent *comp)
673 {
674 	time_t time_start = -1, time_end = -1;
675 	ECalBackendFilePrivate *priv;
676 	ResolveTzidData rtd;
677 
678 	g_return_if_fail (cbfile != NULL);
679 	g_return_if_fail (comp != NULL);
680 
681 	priv = cbfile->priv;
682 
683 	resolve_tzid_data_init (&rtd, cbfile->priv->vcalendar);
684 
685 	e_cal_util_get_component_occur_times (
686 		comp, &time_start, &time_end,
687 		resolve_tzid_cb, &rtd, i_cal_timezone_get_utc_timezone (),
688 		e_cal_backend_get_kind (E_CAL_BACKEND (cbfile)));
689 
690 	resolve_tzid_data_clear (&rtd);
691 
692 	if (time_end != -1 && time_start > time_end) {
693 		gchar *str = e_cal_component_get_as_string (comp);
694 		g_print ("Bogus component %s\n", str);
695 		g_free (str);
696 	} else {
697 		g_rec_mutex_lock (&priv->idle_save_rmutex);
698 		e_intervaltree_insert (priv->interval_tree, time_start, time_end, comp);
699 		g_rec_mutex_unlock (&priv->idle_save_rmutex);
700 	}
701 }
702 
703 static gboolean
remove_component_from_intervaltree(ECalBackendFile * cbfile,ECalComponent * comp)704 remove_component_from_intervaltree (ECalBackendFile *cbfile,
705                                     ECalComponent *comp)
706 {
707 	const gchar *uid;
708 	gchar *rid;
709 	gboolean res;
710 	ECalBackendFilePrivate *priv;
711 
712 	g_return_val_if_fail (cbfile != NULL, FALSE);
713 	g_return_val_if_fail (comp != NULL, FALSE);
714 
715 	priv = cbfile->priv;
716 
717 	uid = e_cal_component_get_uid (comp);
718 	rid = e_cal_component_get_recurid_as_string (comp);
719 
720 	g_rec_mutex_lock (&priv->idle_save_rmutex);
721 	res = e_intervaltree_remove (priv->interval_tree, uid, rid);
722 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
723 
724 	g_free (rid);
725 
726 	return res;
727 }
728 
729 /* Tries to add an ICalComponent to the file backend.  We only store the objects
730  * of the types we support; all others just remain in the toplevel component so
731  * that we don't lose them.
732  *
733  * The caller is responsible for ensuring that the component has a UID and that
734  * the UID is not in use already.
735  */
736 static void
add_component(ECalBackendFile * cbfile,ECalComponent * comp,gboolean add_to_toplevel)737 add_component (ECalBackendFile *cbfile,
738                ECalComponent *comp,
739                gboolean add_to_toplevel)
740 {
741 	ECalBackendFilePrivate *priv;
742 	ECalBackendFileObject *obj_data;
743 	const gchar *uid;
744 
745 	priv = cbfile->priv;
746 
747 	uid = e_cal_component_get_uid (comp);
748 
749 	if (!uid) {
750 		g_warning ("The component does not have a valid UID skipping it\n");
751 		return;
752 	}
753 
754 	obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
755 	if (e_cal_component_is_instance (comp)) {
756 		gchar *rid;
757 
758 		rid = e_cal_component_get_recurid_as_string (comp);
759 		if (obj_data) {
760 			if (g_hash_table_lookup (obj_data->recurrences, rid)) {
761 				g_warning (G_STRLOC ": Tried to add an already existing recurrence");
762 				g_free (rid);
763 				return;
764 			}
765 		} else {
766 			obj_data = g_new0 (ECalBackendFileObject, 1);
767 			obj_data->full_object = NULL;
768 			obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
769 			g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
770 		}
771 
772 		g_hash_table_insert (obj_data->recurrences, rid, comp);
773 		obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
774 	} else {
775 		if (obj_data) {
776 			if (obj_data->full_object) {
777 				g_warning (G_STRLOC ": Tried to add an already existing object");
778 				return;
779 			}
780 
781 			obj_data->full_object = comp;
782 		} else {
783 			obj_data = g_new0 (ECalBackendFileObject, 1);
784 			obj_data->full_object = comp;
785 			obj_data->recurrences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
786 
787 			g_hash_table_insert (priv->comp_uid_hash, g_strdup (uid), obj_data);
788 		}
789 	}
790 
791 	add_component_to_intervaltree (cbfile, comp);
792 
793 	priv->comp = g_list_prepend (priv->comp, comp);
794 
795 	/* Put the object in the toplevel component if required */
796 
797 	if (add_to_toplevel) {
798 		ICalComponent *icomp;
799 
800 		icomp = e_cal_component_get_icalcomponent (comp);
801 		g_return_if_fail (icomp != NULL);
802 
803 		i_cal_component_add_component (priv->vcalendar, icomp);
804 	}
805 }
806 
807 /* g_hash_table_foreach_remove() callback to remove recurrences from the calendar */
808 static gboolean
remove_recurrence_cb(gpointer key,gpointer value,gpointer data)809 remove_recurrence_cb (gpointer key,
810                       gpointer value,
811                       gpointer data)
812 {
813 	ICalComponent *icomp;
814 	ECalBackendFilePrivate *priv;
815 	ECalComponent *comp = value;
816 	ECalBackendFile *cbfile = data;
817 
818 	priv = cbfile->priv;
819 
820 	/* remove the recurrence from the top-level calendar */
821 	icomp = e_cal_component_get_icalcomponent (comp);
822 	g_return_val_if_fail (icomp != NULL, FALSE);
823 
824 	icomp = g_object_ref (icomp);
825 
826 	if (!remove_component_from_intervaltree (cbfile, comp)) {
827 		g_message (G_STRLOC " Could not remove component from interval tree!");
828 	}
829 	i_cal_component_remove_component (priv->vcalendar, icomp);
830 
831 	g_object_unref (icomp);
832 
833 	/* remove it from our mapping */
834 	priv->comp = g_list_remove (priv->comp, comp);
835 
836 	return TRUE;
837 }
838 
839 /* Removes a component from the backend's hash and lists.  Does not perform
840  * notification on the clients.  Also removes the component from the toplevel
841  * ICalComponent.
842  */
843 static void
remove_component(ECalBackendFile * cbfile,const gchar * uid,ECalBackendFileObject * obj_data)844 remove_component (ECalBackendFile *cbfile,
845                   const gchar *uid,
846                   ECalBackendFileObject *obj_data)
847 {
848 	ECalBackendFilePrivate *priv;
849 	ICalComponent *icomp;
850 	GList *l;
851 
852 	priv = cbfile->priv;
853 
854 	/* Remove the ICalComponent from the toplevel */
855 	if (obj_data->full_object) {
856 		icomp = e_cal_component_get_icalcomponent (obj_data->full_object);
857 		g_return_if_fail (icomp != NULL);
858 
859 		i_cal_component_remove_component (priv->vcalendar, icomp);
860 
861 		/* Remove it from our mapping */
862 		l = g_list_find (priv->comp, obj_data->full_object);
863 		g_return_if_fail (l != NULL);
864 		priv->comp = g_list_delete_link (priv->comp, l);
865 
866 		if (!remove_component_from_intervaltree (cbfile, obj_data->full_object)) {
867 			g_message (G_STRLOC " Could not remove component from interval tree!");
868 		}
869 	}
870 
871 	/* remove the recurrences also */
872 	g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_recurrence_cb, cbfile);
873 
874 	g_hash_table_remove (priv->comp_uid_hash, uid);
875 
876 	save (cbfile, TRUE);
877 }
878 
879 /* Scans the toplevel VCALENDAR component and stores the objects it finds */
880 static void
scan_vcalendar(ECalBackendFile * cbfile)881 scan_vcalendar (ECalBackendFile *cbfile)
882 {
883 	ECalBackendFilePrivate *priv;
884 	ICalCompIter *iter;
885 	ICalComponent *icomp;
886 
887 	priv = cbfile->priv;
888 	g_return_if_fail (priv->vcalendar != NULL);
889 	g_return_if_fail (priv->comp_uid_hash != NULL);
890 
891 	iter = i_cal_component_begin_component (priv->vcalendar, I_CAL_ANY_COMPONENT);
892 	icomp = iter ? i_cal_comp_iter_deref (iter) : NULL;
893 	while (icomp) {
894 		ICalComponentKind kind;
895 		ECalComponent *comp;
896 
897 		kind = i_cal_component_isa (icomp);
898 
899 		if (kind == I_CAL_VEVENT_COMPONENT ||
900 		    kind == I_CAL_VTODO_COMPONENT ||
901 		    kind == I_CAL_VJOURNAL_COMPONENT) {
902 			comp = e_cal_component_new ();
903 
904 			if (e_cal_component_set_icalcomponent (comp, icomp)) {
905 				/* Thus it's not freed while being used in the 'comp' */
906 				g_object_ref (icomp);
907 
908 				check_dup_uid (cbfile, comp);
909 
910 				add_component (cbfile, comp, FALSE);
911 			} else {
912 				g_object_unref (comp);
913 			}
914 		}
915 
916 		g_object_unref (icomp);
917 		icomp = i_cal_comp_iter_next (iter);
918 	}
919 
920 	g_clear_object (&iter);
921 }
922 
923 static gchar *
uri_to_path(ECalBackend * backend)924 uri_to_path (ECalBackend *backend)
925 {
926 	ECalBackendFile *cbfile;
927 	ECalBackendFilePrivate *priv;
928 	ESource *source;
929 	ESourceLocal *local_extension;
930 	GFile *custom_file;
931 	const gchar *extension_name;
932 	const gchar *cache_dir;
933 	gchar *filename = NULL;
934 
935 	cbfile = E_CAL_BACKEND_FILE (backend);
936 	priv = cbfile->priv;
937 
938 	cache_dir = e_cal_backend_get_cache_dir (backend);
939 
940 	source = e_backend_get_source (E_BACKEND (backend));
941 
942 	extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
943 	local_extension = e_source_get_extension (source, extension_name);
944 
945 	custom_file = e_source_local_dup_custom_file (local_extension);
946 	if (custom_file != NULL) {
947 		filename = g_file_get_path (custom_file);
948 		g_object_unref (custom_file);
949 	}
950 
951 	if (filename == NULL)
952 		filename = g_build_filename (cache_dir, priv->file_name, NULL);
953 
954 	if (filename != NULL && *filename == '\0') {
955 		g_free (filename);
956 		filename = NULL;
957 	}
958 
959 	return filename;
960 }
961 
962 static gpointer
refresh_thread_func(gpointer data)963 refresh_thread_func (gpointer data)
964 {
965 	ECalBackendFile *cbfile = data;
966 	ECalBackendFilePrivate *priv;
967 	ESource *source;
968 	ESourceLocal *extension;
969 	GFileInfo *info;
970 	GFile *file;
971 	const gchar *extension_name;
972 	guint64 last_modified, modified;
973 
974 	g_return_val_if_fail (cbfile != NULL, NULL);
975 	g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
976 
977 	priv = cbfile->priv;
978 
979 	extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
980 	source = e_backend_get_source (E_BACKEND (cbfile));
981 	extension = e_source_get_extension (source, extension_name);
982 
983 	/* This returns a newly-created GFile. */
984 	file = e_source_local_dup_custom_file (extension);
985 	if (!file) {
986 		g_mutex_lock (&priv->refresh_lock);
987 		priv->refresh_thread_running = FALSE;
988 		g_cond_signal (priv->refresh_gone_cond);
989 		g_mutex_unlock (&priv->refresh_lock);
990 
991 		return NULL;
992 	}
993 
994 	info = g_file_query_info (
995 		file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
996 		G_FILE_QUERY_INFO_NONE, NULL, NULL);
997 	if (info) {
998 		last_modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
999 		g_object_unref (info);
1000 	} else {
1001 		last_modified = 0;
1002 	}
1003 
1004 	g_mutex_lock (&priv->refresh_lock);
1005 	while (!priv->refresh_thread_stop) {
1006 		g_cond_wait (priv->refresh_cond, &priv->refresh_lock);
1007 
1008 		g_rec_mutex_lock (&priv->idle_save_rmutex);
1009 
1010 		if (priv->refresh_skip > 0) {
1011 			priv->refresh_skip--;
1012 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
1013 			continue;
1014 		}
1015 
1016 		if (priv->is_dirty) {
1017 			/* save before reload, if dirty */
1018 			if (priv->dirty_idle_id) {
1019 				g_source_remove (priv->dirty_idle_id);
1020 				priv->dirty_idle_id = 0;
1021 			}
1022 			save_file_when_idle (cbfile);
1023 			priv->refresh_skip = 0;
1024 		}
1025 
1026 		g_rec_mutex_unlock (&priv->idle_save_rmutex);
1027 
1028 		info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL);
1029 		if (!info)
1030 			break;
1031 
1032 		modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1033 		g_object_unref (info);
1034 
1035 		if (modified != last_modified) {
1036 			last_modified = modified;
1037 			e_cal_backend_file_reload (cbfile, NULL);
1038 		}
1039 	}
1040 
1041 	g_object_unref (file);
1042 	priv->refresh_thread_running = FALSE;
1043 	g_cond_signal (priv->refresh_gone_cond);
1044 	g_mutex_unlock (&priv->refresh_lock);
1045 
1046 	return NULL;
1047 }
1048 
1049 static void
custom_file_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,ECalBackendFilePrivate * priv)1050 custom_file_changed (GFileMonitor *monitor,
1051                      GFile *file,
1052                      GFile *other_file,
1053                      GFileMonitorEvent event_type,
1054                      ECalBackendFilePrivate *priv)
1055 {
1056 	if (priv->refresh_cond)
1057 		g_cond_signal (priv->refresh_cond);
1058 }
1059 
1060 static void
prepare_refresh_data(ECalBackendFile * cbfile)1061 prepare_refresh_data (ECalBackendFile *cbfile)
1062 {
1063 	ECalBackendFilePrivate *priv;
1064 	ESource *source;
1065 	ESourceLocal *local_extension;
1066 	GFile *custom_file;
1067 	const gchar *extension_name;
1068 
1069 	g_return_if_fail (cbfile != NULL);
1070 
1071 	priv = cbfile->priv;
1072 
1073 	g_mutex_lock (&priv->refresh_lock);
1074 
1075 	priv->refresh_thread_stop = FALSE;
1076 	priv->refresh_skip = 0;
1077 
1078 	source = e_backend_get_source (E_BACKEND (cbfile));
1079 
1080 	extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
1081 	local_extension = e_source_get_extension (source, extension_name);
1082 
1083 	custom_file = e_source_local_dup_custom_file (local_extension);
1084 
1085 	if (custom_file != NULL) {
1086 		GError *error = NULL;
1087 
1088 		priv->refresh_monitor = g_file_monitor_file (
1089 			custom_file, G_FILE_MONITOR_WATCH_MOUNTS, NULL, &error);
1090 
1091 		if (error == NULL) {
1092 			g_signal_connect (
1093 				priv->refresh_monitor, "changed",
1094 				G_CALLBACK (custom_file_changed), priv);
1095 		} else {
1096 			g_warning ("%s", error->message);
1097 			g_error_free (error);
1098 		}
1099 
1100 		g_object_unref (custom_file);
1101 	}
1102 
1103 	if (priv->refresh_monitor) {
1104 		GThread *thread;
1105 
1106 		priv->refresh_cond = g_new0 (GCond, 1);
1107 		priv->refresh_gone_cond = g_new0 (GCond, 1);
1108 		priv->refresh_thread_running = TRUE;
1109 
1110 		thread = g_thread_new (NULL, refresh_thread_func, cbfile);
1111 		g_thread_unref (thread);
1112 	}
1113 
1114 	g_mutex_unlock (&priv->refresh_lock);
1115 }
1116 
1117 static void
free_refresh_data(ECalBackendFile * cbfile)1118 free_refresh_data (ECalBackendFile *cbfile)
1119 {
1120 	ECalBackendFilePrivate *priv;
1121 
1122 	g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
1123 
1124 	priv = cbfile->priv;
1125 
1126 	g_mutex_lock (&priv->refresh_lock);
1127 
1128 	g_clear_object (&priv->refresh_monitor);
1129 
1130 	if (priv->refresh_cond) {
1131 		priv->refresh_thread_stop = TRUE;
1132 		g_cond_signal (priv->refresh_cond);
1133 
1134 		while (priv->refresh_thread_running) {
1135 			g_cond_wait (priv->refresh_gone_cond, &priv->refresh_lock);
1136 		}
1137 
1138 		g_cond_clear (priv->refresh_cond);
1139 		g_free (priv->refresh_cond);
1140 		priv->refresh_cond = NULL;
1141 		g_cond_clear (priv->refresh_gone_cond);
1142 		g_free (priv->refresh_gone_cond);
1143 		priv->refresh_gone_cond = NULL;
1144 	}
1145 
1146 	priv->refresh_skip = 0;
1147 
1148 	g_mutex_unlock (&priv->refresh_lock);
1149 }
1150 
1151 static void
cal_backend_file_take_icomp(ECalBackendFile * cbfile,ICalComponent * icomp)1152 cal_backend_file_take_icomp (ECalBackendFile *cbfile,
1153 			     ICalComponent *icomp)
1154 {
1155 	ICalProperty *prop;
1156 
1157 	g_warn_if_fail (cbfile->priv->vcalendar == NULL);
1158 	cbfile->priv->vcalendar = icomp;
1159 
1160 	prop = ensure_revision (cbfile);
1161 
1162 	e_cal_backend_notify_property_changed (
1163 		E_CAL_BACKEND (cbfile),
1164 		E_CAL_BACKEND_PROPERTY_REVISION,
1165 		i_cal_property_get_x (prop));
1166 
1167 	g_clear_object (&prop);
1168 }
1169 
1170 /* Parses an open iCalendar file and loads it into the backend */
1171 static void
open_cal(ECalBackendFile * cbfile,const gchar * uristr,GError ** perror)1172 open_cal (ECalBackendFile *cbfile,
1173           const gchar *uristr,
1174           GError **perror)
1175 {
1176 	ECalBackendFilePrivate *priv;
1177 	ICalComponent *icomp;
1178 
1179 	priv = cbfile->priv;
1180 
1181 	free_refresh_data (cbfile);
1182 
1183 	icomp = e_cal_util_parse_ics_file (uristr);
1184 	if (!icomp) {
1185 		g_propagate_error (perror, e_client_error_create_fmt (E_CLIENT_ERROR_OTHER_ERROR, _("Cannot parse ISC file “%s”"), uristr));
1186 		return;
1187 	}
1188 
1189 	/* FIXME: should we try to demangle XROOT components and
1190 	 * individual components as well?
1191 	 */
1192 
1193 	if (i_cal_component_isa (icomp) != I_CAL_VCALENDAR_COMPONENT) {
1194 		g_object_unref (icomp);
1195 
1196 		g_propagate_error (perror, e_client_error_create_fmt (E_CLIENT_ERROR_OTHER_ERROR, _("File “%s” is not a VCALENDAR component"), uristr));
1197 		return;
1198 	}
1199 
1200 	g_rec_mutex_lock (&priv->idle_save_rmutex);
1201 
1202 	cal_backend_file_take_icomp (cbfile, icomp);
1203 	priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1204 
1205 	priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1206 	priv->interval_tree = e_intervaltree_new ();
1207 	scan_vcalendar (cbfile);
1208 
1209 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
1210 
1211 	prepare_refresh_data (cbfile);
1212 }
1213 
1214 typedef struct
1215 {
1216 	ECalBackend *backend;
1217 	GHashTable *old_uid_hash;
1218 	GHashTable *new_uid_hash;
1219 }
1220 BackendDeltaContext;
1221 
1222 static void
notify_removals_cb(gpointer key,gpointer value,gpointer data)1223 notify_removals_cb (gpointer key,
1224                     gpointer value,
1225                     gpointer data)
1226 {
1227 	BackendDeltaContext *context = data;
1228 	const gchar *uid = key;
1229 	ECalBackendFileObject *old_obj_data = value;
1230 
1231 	if (!g_hash_table_lookup (context->new_uid_hash, uid)) {
1232 		ECalComponentId *id;
1233 
1234 		/* Object was removed */
1235 
1236 		if (!old_obj_data->full_object)
1237 			return;
1238 
1239 		id = e_cal_component_get_id (old_obj_data->full_object);
1240 
1241 		e_cal_backend_notify_component_removed (context->backend, id, old_obj_data->full_object, NULL);
1242 
1243 		e_cal_component_id_free (id);
1244 	}
1245 }
1246 
1247 static void
notify_adds_modifies_cb(gpointer key,gpointer value,gpointer data)1248 notify_adds_modifies_cb (gpointer key,
1249                          gpointer value,
1250                          gpointer data)
1251 {
1252 	BackendDeltaContext *context = data;
1253 	const gchar *uid = key;
1254 	ECalBackendFileObject *new_obj_data = value;
1255 	ECalBackendFileObject *old_obj_data;
1256 
1257 	old_obj_data = g_hash_table_lookup (context->old_uid_hash, uid);
1258 
1259 	if (!old_obj_data) {
1260 		/* Object was added */
1261 		if (!new_obj_data->full_object)
1262 			return;
1263 
1264 		e_cal_backend_notify_component_created (context->backend, new_obj_data->full_object);
1265 	} else {
1266 		gchar *old_obj_str, *new_obj_str;
1267 
1268 		if (!old_obj_data->full_object || !new_obj_data->full_object)
1269 			return;
1270 
1271 		/* There should be better ways to compare an ICalComponent
1272 		 * than serializing and comparing the strings...
1273 		 */
1274 		old_obj_str = e_cal_component_get_as_string (old_obj_data->full_object);
1275 		new_obj_str = e_cal_component_get_as_string (new_obj_data->full_object);
1276 		if (old_obj_str && new_obj_str && strcmp (old_obj_str, new_obj_str) != 0) {
1277 			/* Object was modified */
1278 			e_cal_backend_notify_component_modified (context->backend, old_obj_data->full_object, new_obj_data->full_object);
1279 		}
1280 
1281 		g_free (old_obj_str);
1282 		g_free (new_obj_str);
1283 	}
1284 }
1285 
1286 static void
notify_changes(ECalBackendFile * cbfile,GHashTable * old_uid_hash,GHashTable * new_uid_hash)1287 notify_changes (ECalBackendFile *cbfile,
1288                 GHashTable *old_uid_hash,
1289                 GHashTable *new_uid_hash)
1290 {
1291 	BackendDeltaContext context;
1292 
1293 	context.backend = E_CAL_BACKEND (cbfile);
1294 	context.old_uid_hash = old_uid_hash;
1295 	context.new_uid_hash = new_uid_hash;
1296 
1297 	g_hash_table_foreach (old_uid_hash, (GHFunc) notify_removals_cb, &context);
1298 	g_hash_table_foreach (new_uid_hash, (GHFunc) notify_adds_modifies_cb, &context);
1299 }
1300 
1301 static void
reload_cal(ECalBackendFile * cbfile,const gchar * uristr,GError ** perror)1302 reload_cal (ECalBackendFile *cbfile,
1303             const gchar *uristr,
1304             GError **perror)
1305 {
1306 	ECalBackendFilePrivate *priv;
1307 	ICalComponent *icomp, *icomp_old;
1308 	GHashTable *comp_uid_hash_old;
1309 
1310 	priv = cbfile->priv;
1311 
1312 	icomp = e_cal_util_parse_ics_file (uristr);
1313 	if (!icomp) {
1314 		g_propagate_error (perror, e_client_error_create_fmt (E_CLIENT_ERROR_OTHER_ERROR, _("Cannot parse ISC file “%s”"), uristr));
1315 		return;
1316 	}
1317 
1318 	/* FIXME: should we try to demangle XROOT components and
1319 	 * individual components as well?
1320 	 */
1321 
1322 	if (i_cal_component_isa (icomp) != I_CAL_VCALENDAR_COMPONENT) {
1323 		g_object_unref (icomp);
1324 
1325 		g_propagate_error (perror, e_client_error_create_fmt (E_CLIENT_ERROR_OTHER_ERROR, _("File “%s” is not a VCALENDAR component"), uristr));
1326 		return;
1327 	}
1328 
1329 	/* Keep old data for comparison - free later */
1330 
1331 	g_rec_mutex_lock (&priv->idle_save_rmutex);
1332 
1333 	icomp_old = priv->vcalendar;
1334 	priv->vcalendar = NULL;
1335 
1336 	comp_uid_hash_old = priv->comp_uid_hash;
1337 	priv->comp_uid_hash = NULL;
1338 
1339 	/* Load new calendar */
1340 
1341 	free_calendar_data (cbfile);
1342 
1343 	cal_backend_file_take_icomp (cbfile, icomp);
1344 
1345 	priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1346 	priv->interval_tree = e_intervaltree_new ();
1347 	scan_vcalendar (cbfile);
1348 
1349 	priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1350 
1351 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
1352 
1353 	/* Compare old and new versions of calendar */
1354 
1355 	notify_changes (cbfile, comp_uid_hash_old, priv->comp_uid_hash);
1356 
1357 	/* Free old data */
1358 
1359 	free_calendar_components (comp_uid_hash_old, icomp_old);
1360 }
1361 
1362 static void
create_cal(ECalBackendFile * cbfile,const gchar * uristr,GError ** perror)1363 create_cal (ECalBackendFile *cbfile,
1364             const gchar *uristr,
1365             GError **perror)
1366 {
1367 	gchar *dirname;
1368 	ECalBackendFilePrivate *priv;
1369 	ICalComponent *icomp;
1370 
1371 	free_refresh_data (cbfile);
1372 
1373 	priv = cbfile->priv;
1374 
1375 	/* Create the directory to contain the file */
1376 	dirname = g_path_get_dirname (uristr);
1377 	if (g_mkdir_with_parents (dirname, 0700) != 0) {
1378 		g_free (dirname);
1379 		g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
1380 		return;
1381 	}
1382 
1383 	g_free (dirname);
1384 
1385 	g_rec_mutex_lock (&priv->idle_save_rmutex);
1386 
1387 	/* Create the new calendar information */
1388 	icomp = e_cal_util_new_top_level ();
1389 	cal_backend_file_take_icomp (cbfile, icomp);
1390 
1391 	/* Create our internal data */
1392 	priv->comp_uid_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_object_data);
1393 	priv->interval_tree = e_intervaltree_new ();
1394 
1395 	priv->path = uri_to_path (E_CAL_BACKEND (cbfile));
1396 
1397 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
1398 
1399 	save (cbfile, TRUE);
1400 
1401 	prepare_refresh_data (cbfile);
1402 }
1403 
1404 static gchar *
get_uri_string(ECalBackend * backend)1405 get_uri_string (ECalBackend *backend)
1406 {
1407 	gchar *str_uri, *full_uri;
1408 
1409 	str_uri = uri_to_path (backend);
1410 	full_uri = g_uri_unescape_string (str_uri, "");
1411 	g_free (str_uri);
1412 
1413 	return full_uri;
1414 }
1415 
1416 static gboolean
get_source_writable(EBackend * backend)1417 get_source_writable (EBackend *backend)
1418 {
1419 	ESource *source;
1420 	ESourceLocal *extension;
1421 
1422 	source = e_backend_get_source (backend);
1423 
1424 	if (!e_source_get_writable (source))
1425 		return FALSE;
1426 
1427 	if (!e_source_has_extension (source, E_SOURCE_EXTENSION_LOCAL_BACKEND))
1428 		return TRUE;
1429 
1430 	extension = e_source_get_extension (source, E_SOURCE_EXTENSION_LOCAL_BACKEND);
1431 
1432 	return !e_source_local_get_custom_file (extension) ||
1433 		e_source_local_get_writable (extension);
1434 }
1435 
1436 static void
source_changed_cb(ESource * source,ECalBackend * backend)1437 source_changed_cb (ESource *source,
1438                    ECalBackend *backend)
1439 {
1440 	ESourceLocal *extension;
1441 	const gchar *extension_name;
1442 	gboolean backend_writable;
1443 	gboolean source_writable;
1444 
1445 	g_return_if_fail (source != NULL);
1446 	g_return_if_fail (E_IS_CAL_BACKEND (backend));
1447 
1448 	extension_name = E_SOURCE_EXTENSION_LOCAL_BACKEND;
1449 	extension = e_source_get_extension (source, extension_name);
1450 
1451 	if (e_source_local_get_custom_file (extension) == NULL)
1452 		return;
1453 
1454 	source_writable = get_source_writable (E_BACKEND (backend));
1455 	backend_writable = e_cal_backend_get_writable (backend);
1456 
1457 	if (source_writable != backend_writable) {
1458 		backend_writable = source_writable;
1459 
1460 		if (source_writable) {
1461 			gchar *str_uri = get_uri_string (backend);
1462 
1463 			g_return_if_fail (str_uri != NULL);
1464 
1465 			backend_writable = (g_access (str_uri, W_OK) == 0);
1466 
1467 			g_free (str_uri);
1468 		}
1469 
1470 		e_cal_backend_set_writable (backend, backend_writable);
1471 	}
1472 }
1473 
1474 /* Open handler for the file backend */
1475 static void
e_cal_backend_file_open(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,GError ** perror)1476 e_cal_backend_file_open (ECalBackendSync *backend,
1477                          EDataCal *cal,
1478                          GCancellable *cancellable,
1479                          GError **perror)
1480 {
1481 	ECalBackendFile *cbfile;
1482 	ECalBackendFilePrivate *priv;
1483 	gchar *str_uri;
1484 	gboolean writable = FALSE;
1485 	GError *err = NULL;
1486 
1487 	cbfile = E_CAL_BACKEND_FILE (backend);
1488 	priv = cbfile->priv;
1489 	g_rec_mutex_lock (&priv->idle_save_rmutex);
1490 
1491 	/* Local source is always connected. */
1492 	e_source_set_connection_status (e_backend_get_source (E_BACKEND (backend)),
1493 		E_SOURCE_CONNECTION_STATUS_CONNECTED);
1494 
1495 	/* Claim a succesful open if we are already open */
1496 	if (priv->path && priv->comp_uid_hash) {
1497 		/* Success */
1498 		goto done;
1499 	}
1500 
1501 	str_uri = get_uri_string (E_CAL_BACKEND (backend));
1502 	if (!str_uri) {
1503 		err = EC_ERROR_NO_URI ();
1504 		goto done;
1505 	}
1506 
1507 	writable = TRUE;
1508 	if (g_access (str_uri, R_OK) == 0) {
1509 		open_cal (cbfile, str_uri, &err);
1510 		if (g_access (str_uri, W_OK) != 0)
1511 			writable = FALSE;
1512 	} else {
1513 		create_cal (cbfile, str_uri, &err);
1514 	}
1515 
1516 	if (!err) {
1517 		if (writable) {
1518 			ESource *source;
1519 
1520 			source = e_backend_get_source (E_BACKEND (backend));
1521 
1522 			g_signal_connect (
1523 				source, "changed",
1524 				G_CALLBACK (source_changed_cb), backend);
1525 
1526 			if (!get_source_writable (E_BACKEND (backend)))
1527 				writable = FALSE;
1528 		}
1529 	}
1530 
1531 	g_free (str_uri);
1532 
1533   done:
1534 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
1535 	e_cal_backend_set_writable (E_CAL_BACKEND (backend), writable);
1536 	e_backend_set_online (E_BACKEND (backend), TRUE);
1537 
1538 	if (err)
1539 		g_propagate_error (perror, g_error_copy (err));
1540 }
1541 
1542 static void
add_detached_recur_to_vcalendar(gpointer key,gpointer value,gpointer user_data)1543 add_detached_recur_to_vcalendar (gpointer key,
1544                                  gpointer value,
1545                                  gpointer user_data)
1546 {
1547 	ECalComponent *recurrence = value;
1548 	ICalComponent *vcalendar = user_data;
1549 
1550 	i_cal_component_take_component (
1551 		vcalendar,
1552 		i_cal_component_clone (e_cal_component_get_icalcomponent (recurrence)));
1553 }
1554 
1555 /* Get_object_component handler for the file backend */
1556 static void
e_cal_backend_file_get_object(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * uid,const gchar * rid,gchar ** object,GError ** error)1557 e_cal_backend_file_get_object (ECalBackendSync *backend,
1558                                EDataCal *cal,
1559                                GCancellable *cancellable,
1560                                const gchar *uid,
1561                                const gchar *rid,
1562                                gchar **object,
1563                                GError **error)
1564 {
1565 	ECalBackendFile *cbfile;
1566 	ECalBackendFilePrivate *priv;
1567 	ECalBackendFileObject *obj_data;
1568 
1569 	cbfile = E_CAL_BACKEND_FILE (backend);
1570 	priv = cbfile->priv;
1571 
1572 	if (priv->vcalendar == NULL) {
1573 		g_set_error_literal (
1574 			error, E_CAL_CLIENT_ERROR,
1575 			E_CAL_CLIENT_ERROR_INVALID_OBJECT,
1576 			e_cal_client_error_to_string (
1577 			E_CAL_CLIENT_ERROR_INVALID_OBJECT));
1578 		return;
1579 	}
1580 
1581 	g_return_if_fail (uid != NULL);
1582 	g_return_if_fail (priv->comp_uid_hash != NULL);
1583 
1584 	g_rec_mutex_lock (&priv->idle_save_rmutex);
1585 
1586 	obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1587 	if (!obj_data) {
1588 		g_rec_mutex_unlock (&priv->idle_save_rmutex);
1589 		g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1590 		return;
1591 	}
1592 
1593 	if (rid && *rid) {
1594 		ECalComponent *comp;
1595 
1596 		comp = g_hash_table_lookup (obj_data->recurrences, rid);
1597 		if (comp) {
1598 			*object = e_cal_component_get_as_string (comp);
1599 		} else {
1600 			ICalComponent *icomp;
1601 			ICalTime *itt;
1602 
1603 			if (!obj_data->full_object) {
1604 				g_rec_mutex_unlock (&priv->idle_save_rmutex);
1605 				g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1606 				return;
1607 			}
1608 
1609 			itt = i_cal_time_new_from_string (rid);
1610 			icomp = e_cal_util_construct_instance (
1611 				e_cal_component_get_icalcomponent (obj_data->full_object),
1612 				itt);
1613 			g_object_unref (itt);
1614 
1615 			if (!icomp) {
1616 				g_rec_mutex_unlock (&priv->idle_save_rmutex);
1617 				g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1618 				return;
1619 			}
1620 
1621 			*object = i_cal_component_as_ical_string (icomp);
1622 
1623 			g_object_unref (icomp);
1624 		}
1625 	} else {
1626 		if (g_hash_table_size (obj_data->recurrences) > 0) {
1627 			ICalComponent *icomp;
1628 
1629 			/* if we have detached recurrences, return a VCALENDAR */
1630 			icomp = e_cal_util_new_top_level ();
1631 
1632 			/* detached recurrences don't have full_object */
1633 			if (obj_data->full_object)
1634 				i_cal_component_take_component (
1635 					icomp,
1636 					i_cal_component_clone (e_cal_component_get_icalcomponent (obj_data->full_object)));
1637 
1638 			/* add all detached recurrences */
1639 			g_hash_table_foreach (obj_data->recurrences, (GHFunc) add_detached_recur_to_vcalendar, icomp);
1640 
1641 			*object = i_cal_component_as_ical_string (icomp);
1642 
1643 			g_object_unref (icomp);
1644 		} else if (obj_data->full_object)
1645 			*object = e_cal_component_get_as_string (obj_data->full_object);
1646 	}
1647 
1648 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
1649 }
1650 
1651 /* Add_timezone handler for the file backend */
1652 static void
e_cal_backend_file_add_timezone(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * tzobj,GError ** error)1653 e_cal_backend_file_add_timezone (ECalBackendSync *backend,
1654                                  EDataCal *cal,
1655                                  GCancellable *cancellable,
1656                                  const gchar *tzobj,
1657                                  GError **error)
1658 {
1659 	ETimezoneCache *timezone_cache;
1660 	ICalComponent *tz_comp;
1661 
1662 	timezone_cache = E_TIMEZONE_CACHE (backend);
1663 
1664 	tz_comp = i_cal_parser_parse_string (tzobj);
1665 	if (!tz_comp) {
1666 		g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
1667 		return;
1668 	}
1669 
1670 	if (i_cal_component_isa (tz_comp) == I_CAL_VTIMEZONE_COMPONENT) {
1671 		ICalTimezone *zone;
1672 
1673 		zone = i_cal_timezone_new ();
1674 		if (i_cal_timezone_set_component (zone, tz_comp))
1675 			e_timezone_cache_add_timezone (timezone_cache, zone);
1676 		g_object_unref (zone);
1677 	}
1678 
1679 	g_object_unref (tz_comp);
1680 }
1681 
1682 typedef struct {
1683 	GSList *comps_list;
1684 	gboolean search_needed;
1685 	const gchar *query;
1686 	ECalBackendSExp *obj_sexp;
1687 	ECalBackend *backend;
1688 	EDataCalView *view;
1689 	gboolean as_string;
1690 } MatchObjectData;
1691 
1692 static void
match_object_sexp_to_component(gpointer value,gpointer data)1693 match_object_sexp_to_component (gpointer value,
1694                                 gpointer data)
1695 {
1696 	ECalComponent *comp = value;
1697 	MatchObjectData *match_data = data;
1698 	ETimezoneCache *timezone_cache;
1699 
1700 	g_return_if_fail (comp != NULL);
1701 	g_return_if_fail (match_data->backend != NULL);
1702 
1703 	timezone_cache = E_TIMEZONE_CACHE (match_data->backend);
1704 
1705 	if ((!match_data->search_needed) ||
1706 	    (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, timezone_cache))) {
1707 		if (match_data->as_string)
1708 			match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (comp));
1709 		else
1710 			match_data->comps_list = g_slist_prepend (match_data->comps_list, comp);
1711 	}
1712 }
1713 
1714 static void
match_recurrence_sexp(gpointer key,gpointer value,gpointer data)1715 match_recurrence_sexp (gpointer key,
1716                        gpointer value,
1717                        gpointer data)
1718 {
1719 	ECalComponent *comp = value;
1720 	MatchObjectData *match_data = data;
1721 	ETimezoneCache *timezone_cache;
1722 
1723 	timezone_cache = E_TIMEZONE_CACHE (match_data->backend);
1724 
1725 	if ((!match_data->search_needed) ||
1726 	    (e_cal_backend_sexp_match_comp (match_data->obj_sexp, comp, timezone_cache))) {
1727 		if (match_data->as_string)
1728 			match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (comp));
1729 		else
1730 			match_data->comps_list = g_slist_prepend (match_data->comps_list, comp);
1731 	}
1732 }
1733 
1734 static void
match_object_sexp(gpointer key,gpointer value,gpointer data)1735 match_object_sexp (gpointer key,
1736                    gpointer value,
1737                    gpointer data)
1738 {
1739 	ECalBackendFileObject *obj_data = value;
1740 	MatchObjectData *match_data = data;
1741 	ETimezoneCache *timezone_cache;
1742 
1743 	timezone_cache = E_TIMEZONE_CACHE (match_data->backend);
1744 
1745 	if (obj_data->full_object) {
1746 		if ((!match_data->search_needed) ||
1747 		    (e_cal_backend_sexp_match_comp (match_data->obj_sexp,
1748 						    obj_data->full_object,
1749 						    timezone_cache))) {
1750 			if (match_data->as_string)
1751 				match_data->comps_list = g_slist_prepend (match_data->comps_list, e_cal_component_get_as_string (obj_data->full_object));
1752 			else
1753 				match_data->comps_list = g_slist_prepend (match_data->comps_list, obj_data->full_object);
1754 		}
1755 	}
1756 
1757 	/* match also recurrences */
1758 	g_hash_table_foreach (obj_data->recurrences,
1759 			      (GHFunc) match_recurrence_sexp,
1760 			      match_data);
1761 }
1762 
1763 /* Get_objects_in_range handler for the file backend */
1764 static void
e_cal_backend_file_get_object_list(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * sexp,GSList ** objects,GError ** perror)1765 e_cal_backend_file_get_object_list (ECalBackendSync *backend,
1766                                     EDataCal *cal,
1767                                     GCancellable *cancellable,
1768                                     const gchar *sexp,
1769                                     GSList **objects,
1770                                     GError **perror)
1771 {
1772 	ECalBackendFile *cbfile;
1773 	ECalBackendFilePrivate *priv;
1774 	MatchObjectData match_data = { 0, };
1775 	time_t occur_start = -1, occur_end = -1;
1776 	gboolean prunning_by_time;
1777 	GList * objs_occuring_in_tw;
1778 	cbfile = E_CAL_BACKEND_FILE (backend);
1779 	priv = cbfile->priv;
1780 
1781 	d (g_message (G_STRLOC ": Getting object list (%s)", sexp));
1782 
1783 	match_data.search_needed = TRUE;
1784 	match_data.query = sexp;
1785 	match_data.comps_list = NULL;
1786 	match_data.as_string = TRUE;
1787 	match_data.backend = E_CAL_BACKEND (backend);
1788 
1789 	if (sexp && !strcmp (sexp, "#t"))
1790 		match_data.search_needed = FALSE;
1791 
1792 	match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
1793 	if (!match_data.obj_sexp) {
1794 		g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_INVALID_QUERY));
1795 		return;
1796 	}
1797 
1798 	g_rec_mutex_lock (&priv->idle_save_rmutex);
1799 
1800 	prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (
1801 		match_data.obj_sexp,
1802 		&occur_start,
1803 		&occur_end);
1804 
1805 	objs_occuring_in_tw = NULL;
1806 
1807 	if (!prunning_by_time) {
1808 		g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
1809 				      &match_data);
1810 	} else {
1811 		objs_occuring_in_tw = e_intervaltree_search (
1812 			priv->interval_tree,
1813 			occur_start, occur_end);
1814 
1815 		g_list_foreach (objs_occuring_in_tw, (GFunc) match_object_sexp_to_component,
1816 			       &match_data);
1817 	}
1818 
1819 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
1820 
1821 	*objects = g_slist_reverse (match_data.comps_list);
1822 
1823 	if (objs_occuring_in_tw) {
1824 		g_list_foreach (objs_occuring_in_tw, (GFunc) g_object_unref, NULL);
1825 		g_list_free (objs_occuring_in_tw);
1826 	}
1827 
1828 	g_object_unref (match_data.obj_sexp);
1829 }
1830 
1831 static void
add_attach_uris(GSList ** attachment_uris,ICalComponent * icomp)1832 add_attach_uris (GSList **attachment_uris,
1833 		 ICalComponent *icomp)
1834 {
1835 	ICalProperty *prop;
1836 
1837 	g_return_if_fail (attachment_uris != NULL);
1838 	g_return_if_fail (icomp != NULL);
1839 
1840 	for (prop = i_cal_component_get_first_property (icomp, I_CAL_ATTACH_PROPERTY);
1841 	     prop;
1842 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ATTACH_PROPERTY)) {
1843 		ICalAttach *attach = i_cal_property_get_attach (prop);
1844 
1845 		if (attach && i_cal_attach_get_is_url (attach)) {
1846 			const gchar *url;
1847 
1848 			url = i_cal_attach_get_url (attach);
1849 			if (url) {
1850 				gchar *buf;
1851 
1852 				buf = i_cal_value_decode_ical_string (url);
1853 
1854 				*attachment_uris = g_slist_prepend (*attachment_uris, g_strdup (buf));
1855 
1856 				g_free (buf);
1857 			}
1858 		}
1859 
1860 		g_clear_object (&attach);
1861 	}
1862 }
1863 
1864 static void
add_detached_recur_attach_uris(gpointer key,gpointer value,gpointer user_data)1865 add_detached_recur_attach_uris (gpointer key,
1866                                 gpointer value,
1867                                 gpointer user_data)
1868 {
1869 	ECalComponent *recurrence = value;
1870 	GSList **attachment_uris = user_data;
1871 
1872 	add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (recurrence));
1873 }
1874 
1875 /* Gets the list of attachments */
1876 static void
e_cal_backend_file_get_attachment_uris(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * uid,const gchar * rid,GSList ** attachment_uris,GError ** error)1877 e_cal_backend_file_get_attachment_uris (ECalBackendSync *backend,
1878                                         EDataCal *cal,
1879                                         GCancellable *cancellable,
1880                                         const gchar *uid,
1881                                         const gchar *rid,
1882                                         GSList **attachment_uris,
1883                                         GError **error)
1884 {
1885 	ECalBackendFile *cbfile;
1886 	ECalBackendFilePrivate *priv;
1887 	ECalBackendFileObject *obj_data;
1888 
1889 	cbfile = E_CAL_BACKEND_FILE (backend);
1890 	priv = cbfile->priv;
1891 
1892 	g_return_if_fail (priv->comp_uid_hash != NULL);
1893 
1894 	g_rec_mutex_lock (&priv->idle_save_rmutex);
1895 
1896 	obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
1897 	if (!obj_data) {
1898 		g_rec_mutex_unlock (&priv->idle_save_rmutex);
1899 		g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1900 		return;
1901 	}
1902 
1903 	if (rid && *rid) {
1904 		ECalComponent *comp;
1905 
1906 		comp = g_hash_table_lookup (obj_data->recurrences, rid);
1907 		if (comp) {
1908 			add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (comp));
1909 		} else {
1910 			ICalComponent *icomp;
1911 			ICalTime *itt;
1912 
1913 			if (!obj_data->full_object) {
1914 				g_rec_mutex_unlock (&priv->idle_save_rmutex);
1915 				g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1916 				return;
1917 			}
1918 
1919 			itt = i_cal_time_new_from_string (rid);
1920 			icomp = e_cal_util_construct_instance (
1921 				e_cal_component_get_icalcomponent (obj_data->full_object),
1922 				itt);
1923 			g_object_unref (itt);
1924 			if (!icomp) {
1925 				g_rec_mutex_unlock (&priv->idle_save_rmutex);
1926 				g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
1927 				return;
1928 			}
1929 
1930 			add_attach_uris (attachment_uris, icomp);
1931 
1932 			g_object_unref (icomp);
1933 		}
1934 	} else {
1935 		if (g_hash_table_size (obj_data->recurrences) > 0) {
1936 			/* detached recurrences don't have full_object */
1937 			if (obj_data->full_object)
1938 				add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (obj_data->full_object));
1939 
1940 			/* add all detached recurrences */
1941 			g_hash_table_foreach (obj_data->recurrences, add_detached_recur_attach_uris, attachment_uris);
1942 		} else if (obj_data->full_object)
1943 			add_attach_uris (attachment_uris, e_cal_component_get_icalcomponent (obj_data->full_object));
1944 	}
1945 
1946 	*attachment_uris = g_slist_reverse (*attachment_uris);
1947 
1948 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
1949 }
1950 
1951 /* get_query handler for the file backend */
1952 static void
e_cal_backend_file_start_view(ECalBackend * backend,EDataCalView * query)1953 e_cal_backend_file_start_view (ECalBackend *backend,
1954                                EDataCalView *query)
1955 {
1956 	ECalBackendFile *cbfile;
1957 	ECalBackendFilePrivate *priv;
1958 	ECalBackendSExp *sexp;
1959 	MatchObjectData match_data = { 0, };
1960 	time_t occur_start = -1, occur_end = -1;
1961 	gboolean prunning_by_time;
1962 	GList * objs_occuring_in_tw;
1963 	cbfile = E_CAL_BACKEND_FILE (backend);
1964 	priv = cbfile->priv;
1965 
1966 	sexp = e_data_cal_view_get_sexp (query);
1967 
1968 	d (g_message (G_STRLOC ": Starting query (%s)", e_cal_backend_sexp_text (sexp)));
1969 
1970 	/* try to match all currently existing objects */
1971 	match_data.search_needed = TRUE;
1972 	match_data.query = e_cal_backend_sexp_text (sexp);
1973 	match_data.comps_list = NULL;
1974 	match_data.as_string = FALSE;
1975 	match_data.backend = backend;
1976 	match_data.obj_sexp = e_data_cal_view_get_sexp (query);
1977 	match_data.view = query;
1978 
1979 	if (match_data.query && !strcmp (match_data.query, "#t"))
1980 		match_data.search_needed = FALSE;
1981 
1982 	if (!match_data.obj_sexp) {
1983 		GError *error = EC_ERROR (E_CLIENT_ERROR_INVALID_QUERY);
1984 		e_data_cal_view_notify_complete (query, error);
1985 		g_error_free (error);
1986 		return;
1987 	}
1988 	prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (
1989 		match_data.obj_sexp,
1990 		&occur_start,
1991 		&occur_end);
1992 
1993 	objs_occuring_in_tw = NULL;
1994 
1995 	g_rec_mutex_lock (&priv->idle_save_rmutex);
1996 
1997 	if (!prunning_by_time) {
1998 		/* full scan */
1999 		g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
2000 				      &match_data);
2001 
2002 		e_debug_log (
2003 			FALSE, E_DEBUG_LOG_DOMAIN_CAL_QUERIES,  "---;%p;QUERY-ITEMS;%s;%s;%d", query,
2004 			e_cal_backend_sexp_text (sexp), G_OBJECT_TYPE_NAME (backend),
2005 			g_hash_table_size (priv->comp_uid_hash));
2006 	} else {
2007 		/* matches objects in new "interval tree" way */
2008 		/* events occuring in time window */
2009 		objs_occuring_in_tw = e_intervaltree_search (priv->interval_tree, occur_start, occur_end);
2010 
2011 		g_list_foreach (objs_occuring_in_tw, (GFunc) match_object_sexp_to_component,
2012 			       &match_data);
2013 
2014 		e_debug_log (
2015 			FALSE, E_DEBUG_LOG_DOMAIN_CAL_QUERIES,  "---;%p;QUERY-ITEMS;%s;%s;%d", query,
2016 			e_cal_backend_sexp_text (sexp), G_OBJECT_TYPE_NAME (backend),
2017 			g_list_length (objs_occuring_in_tw));
2018 	}
2019 
2020 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
2021 
2022 	/* notify listeners of all objects */
2023 	if (match_data.comps_list) {
2024 		match_data.comps_list = g_slist_reverse (match_data.comps_list);
2025 
2026 		e_data_cal_view_notify_components_added (query, match_data.comps_list);
2027 
2028 		/* free memory */
2029 		g_slist_free (match_data.comps_list);
2030 	}
2031 
2032 	if (objs_occuring_in_tw) {
2033 		g_list_foreach (objs_occuring_in_tw, (GFunc) g_object_unref, NULL);
2034 		g_list_free (objs_occuring_in_tw);
2035 	}
2036 
2037 	e_data_cal_view_notify_complete (query, NULL /* Success */);
2038 }
2039 
2040 static gboolean
free_busy_instance(ICalComponent * icomp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)2041 free_busy_instance (ICalComponent *icomp,
2042 		    ICalTime *instance_start,
2043 		    ICalTime *instance_end,
2044 		    gpointer user_data,
2045 		    GCancellable *cancellable,
2046 		    GError **error)
2047 {
2048 	ICalComponent *vfb = user_data;
2049 	ICalProperty *prop;
2050 	ICalParameter *param;
2051 	ICalPeriod *ipt;
2052 	const gchar *summary, *location;
2053 
2054 	if (!i_cal_time_is_date (instance_start))
2055 		i_cal_time_convert_to_zone_inplace (instance_start, i_cal_timezone_get_utc_timezone ());
2056 
2057 	if (!i_cal_time_is_date (instance_end))
2058 		i_cal_time_convert_to_zone_inplace (instance_end, i_cal_timezone_get_utc_timezone ());
2059 
2060 	ipt = i_cal_period_new_null_period ();
2061 	i_cal_period_set_start (ipt, instance_start);
2062 	i_cal_period_set_end (ipt, instance_end);
2063 
2064         /* add busy information to the vfb component */
2065 	prop = i_cal_property_new (I_CAL_FREEBUSY_PROPERTY);
2066 	i_cal_property_set_freebusy (prop, ipt);
2067 	g_object_unref (ipt);
2068 
2069 	param = i_cal_parameter_new_fbtype (I_CAL_FBTYPE_BUSY);
2070 	i_cal_property_take_parameter (prop, param);
2071 
2072 	summary = i_cal_component_get_summary (icomp);
2073 	if (summary && *summary)
2074 		i_cal_property_set_parameter_from_string (prop, "X-SUMMARY", summary);
2075 	location = i_cal_component_get_location (icomp);
2076 	if (location && *location)
2077 		i_cal_property_set_parameter_from_string (prop, "X-LOCATION", location);
2078 
2079 	i_cal_component_take_property (vfb, prop);
2080 
2081 	return TRUE;
2082 }
2083 
2084 static ICalComponent *
create_user_free_busy(ECalBackendFile * cbfile,const gchar * address,const gchar * cn,time_t start,time_t end,GCancellable * cancellable)2085 create_user_free_busy (ECalBackendFile *cbfile,
2086                        const gchar *address,
2087                        const gchar *cn,
2088                        time_t start,
2089                        time_t end,
2090 		       GCancellable *cancellable)
2091 {
2092 	ECalBackendFilePrivate *priv;
2093 	GList *l;
2094 	ICalComponent *vfb;
2095 	ICalTimezone *utc_zone;
2096 	ICalTime *starttt, *endtt;
2097 	ECalBackendSExp *obj_sexp;
2098 	gchar *query, *iso_start, *iso_end;
2099 
2100 	priv = cbfile->priv;
2101 
2102 	/* create the (unique) VFREEBUSY object that we'll return */
2103 	vfb = i_cal_component_new_vfreebusy ();
2104 	if (address != NULL) {
2105 		ICalProperty *prop;
2106 		ICalParameter *param;
2107 
2108 		prop = i_cal_property_new_organizer (address);
2109 		if (prop != NULL && cn != NULL) {
2110 			param = i_cal_parameter_new_cn (cn);
2111 			i_cal_property_take_parameter (prop, param);
2112 		}
2113 		if (prop != NULL)
2114 			i_cal_component_take_property (vfb, prop);
2115 	}
2116 	utc_zone = i_cal_timezone_get_utc_timezone ();
2117 
2118 	starttt = i_cal_time_new_from_timet_with_zone (start, FALSE, utc_zone);
2119 	i_cal_component_set_dtstart (vfb, starttt);
2120 
2121 	endtt = i_cal_time_new_from_timet_with_zone (end, FALSE, utc_zone);
2122 	i_cal_component_set_dtend (vfb, endtt);
2123 
2124 	/* add all objects in the given interval */
2125 	iso_start = isodate_from_time_t (start);
2126 	iso_end = isodate_from_time_t (end);
2127 	query = g_strdup_printf (
2128 		"occur-in-time-range? (make-time \"%s\") (make-time \"%s\")",
2129 		iso_start, iso_end);
2130 	obj_sexp = e_cal_backend_sexp_new (query);
2131 	g_free (query);
2132 	g_free (iso_start);
2133 	g_free (iso_end);
2134 
2135 	if (!obj_sexp) {
2136 		g_clear_object (&starttt);
2137 		g_clear_object (&endtt);
2138 		return vfb;
2139 	}
2140 
2141 	for (l = priv->comp; l; l = l->next) {
2142 		ECalComponent *comp = l->data;
2143 		ICalComponent *icomp, *vcalendar_comp;
2144 		ICalProperty *prop;
2145 		ResolveTzidData rtd;
2146 
2147 		icomp = e_cal_component_get_icalcomponent (comp);
2148 		if (!icomp)
2149 			continue;
2150 
2151 		/* If the event is TRANSPARENT, skip it. */
2152 		prop = i_cal_component_get_first_property (icomp, I_CAL_TRANSP_PROPERTY);
2153 		if (prop) {
2154 			ICalPropertyTransp transp_val = i_cal_property_get_transp (prop);
2155 
2156 			g_object_unref (prop);
2157 
2158 			if (transp_val == I_CAL_TRANSP_TRANSPARENT ||
2159 			    transp_val == I_CAL_TRANSP_TRANSPARENTNOCONFLICT)
2160 				continue;
2161 		}
2162 
2163 		if (!e_cal_backend_sexp_match_comp (obj_sexp, comp, E_TIMEZONE_CACHE (cbfile)))
2164 			continue;
2165 
2166 		vcalendar_comp = i_cal_component_get_parent (icomp);
2167 
2168 		resolve_tzid_data_init (&rtd, vcalendar_comp);
2169 
2170 		e_cal_recur_generate_instances_sync (
2171 			e_cal_component_get_icalcomponent (comp), starttt, endtt,
2172 			free_busy_instance,
2173 			vfb,
2174 			resolve_tzid_cb,
2175 			&rtd,
2176 			i_cal_timezone_get_utc_timezone (),
2177 			cancellable, NULL);
2178 
2179 		resolve_tzid_data_clear (&rtd);
2180 		g_clear_object (&vcalendar_comp);
2181 	}
2182 
2183 	g_clear_object (&starttt);
2184 	g_clear_object (&endtt);
2185 	g_object_unref (obj_sexp);
2186 
2187 	return vfb;
2188 }
2189 
2190 /* Get_free_busy handler for the file backend */
2191 static void
e_cal_backend_file_get_free_busy(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const GSList * users,time_t start,time_t end,GSList ** freebusy,GError ** error)2192 e_cal_backend_file_get_free_busy (ECalBackendSync *backend,
2193                                   EDataCal *cal,
2194                                   GCancellable *cancellable,
2195                                   const GSList *users,
2196                                   time_t start,
2197                                   time_t end,
2198                                   GSList **freebusy,
2199                                   GError **error)
2200 {
2201 	ESourceRegistry *registry;
2202 	ECalBackendFile *cbfile;
2203 	ECalBackendFilePrivate *priv;
2204 	gchar *address, *name;
2205 	ICalComponent *vfb;
2206 	gchar *calobj;
2207 	const GSList *l;
2208 
2209 	cbfile = E_CAL_BACKEND_FILE (backend);
2210 	priv = cbfile->priv;
2211 
2212 	if (priv->vcalendar == NULL) {
2213 		g_set_error_literal (
2214 			error, E_CAL_CLIENT_ERROR,
2215 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
2216 			e_cal_client_error_to_string (
2217 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
2218 		return;
2219 	}
2220 
2221 	g_rec_mutex_lock (&priv->idle_save_rmutex);
2222 
2223 	*freebusy = NULL;
2224 
2225 	registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
2226 
2227 	if (users == NULL) {
2228 		if (e_cal_backend_mail_account_get_default (registry, &address, &name)) {
2229 			vfb = create_user_free_busy (cbfile, address, name, start, end, cancellable);
2230 			calobj = i_cal_component_as_ical_string (vfb);
2231 			*freebusy = g_slist_append (*freebusy, calobj);
2232 			g_object_unref (vfb);
2233 			g_free (address);
2234 			g_free (name);
2235 		}
2236 	} else {
2237 		for (l = users; l != NULL; l = l->next ) {
2238 			address = l->data;
2239 			if (e_cal_backend_mail_account_is_valid (registry, address, &name)) {
2240 				vfb = create_user_free_busy (cbfile, address, name, start, end, cancellable);
2241 				calobj = i_cal_component_as_ical_string (vfb);
2242 				*freebusy = g_slist_append (*freebusy, calobj);
2243 				g_object_unref (vfb);
2244 				g_free (name);
2245 			}
2246 		}
2247 	}
2248 
2249 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
2250 }
2251 
2252 static void
sanitize_component(ECalBackendFile * cbfile,ECalComponent * comp)2253 sanitize_component (ECalBackendFile *cbfile,
2254                     ECalComponent *comp)
2255 {
2256 	ECalComponentDateTime *dt;
2257 	ICalTimezone *zone;
2258 
2259 	/* Check dtstart, dtend and due's timezone, and convert it to local
2260 	 * default timezone if the timezone is not in our builtin timezone
2261 	 * list */
2262 	dt = e_cal_component_get_dtstart (comp);
2263 	if (dt && e_cal_component_datetime_get_value (dt) && e_cal_component_datetime_get_tzid (dt)) {
2264 		zone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cbfile), e_cal_component_datetime_get_tzid (dt));
2265 		if (!zone) {
2266 			e_cal_component_datetime_set_tzid (dt, "UTC");
2267 			e_cal_component_set_dtstart (comp, dt);
2268 		}
2269 	}
2270 	e_cal_component_datetime_free (dt);
2271 
2272 	dt = e_cal_component_get_dtend (comp);
2273 	if (dt && e_cal_component_datetime_get_value (dt) && e_cal_component_datetime_get_tzid (dt)) {
2274 		zone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cbfile), e_cal_component_datetime_get_tzid (dt));
2275 		if (!zone) {
2276 			e_cal_component_datetime_set_tzid (dt, "UTC");
2277 			e_cal_component_set_dtend (comp, dt);
2278 		}
2279 	}
2280 	e_cal_component_datetime_free (dt);
2281 
2282 	dt = e_cal_component_get_due (comp);
2283 	if (dt && e_cal_component_datetime_get_value (dt) && e_cal_component_datetime_get_tzid (dt)) {
2284 		zone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cbfile), e_cal_component_datetime_get_tzid (dt));
2285 		if (!zone) {
2286 			e_cal_component_datetime_set_tzid (dt, "UTC");
2287 			e_cal_component_set_due (comp, dt);
2288 		}
2289 	}
2290 	e_cal_component_datetime_free (dt);
2291 
2292 	e_cal_component_abort_sequence (comp);
2293 }
2294 
2295 static void
e_cal_backend_file_create_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const GSList * in_calobjs,ECalOperationFlags opflags,GSList ** uids,GSList ** new_components,GError ** error)2296 e_cal_backend_file_create_objects (ECalBackendSync *backend,
2297                                    EDataCal *cal,
2298                                    GCancellable *cancellable,
2299                                    const GSList *in_calobjs,
2300 				   ECalOperationFlags opflags,
2301                                    GSList **uids,
2302                                    GSList **new_components,
2303                                    GError **error)
2304 {
2305 	ECalBackendFile *cbfile;
2306 	ECalBackendFilePrivate *priv;
2307 	GSList *icomps = NULL;
2308 	const GSList *l;
2309 
2310 	cbfile = E_CAL_BACKEND_FILE (backend);
2311 	priv = cbfile->priv;
2312 
2313 	if (priv->vcalendar == NULL) {
2314 		g_set_error_literal (
2315 			error, E_CAL_CLIENT_ERROR,
2316 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
2317 			e_cal_client_error_to_string (
2318 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
2319 		return;
2320 	}
2321 
2322 	if (uids)
2323 		*uids = NULL;
2324 
2325 	g_rec_mutex_lock (&priv->idle_save_rmutex);
2326 
2327 	/* First step, parse input strings and do uid verification: may fail */
2328 	for (l = in_calobjs; l; l = l->next) {
2329 		ICalComponent *icomp;
2330 		const gchar *comp_uid;
2331 
2332 		/* Parse the icalendar text */
2333 		icomp = i_cal_parser_parse_string ((gchar *) l->data);
2334 		if (!icomp) {
2335 			g_slist_free_full (icomps, g_object_unref);
2336 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
2337 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2338 			return;
2339 		}
2340 
2341 		/* Append icalcomponent to icalcomps */
2342 		icomps = g_slist_prepend (icomps, icomp);
2343 
2344 		/* Check kind with the parent */
2345 		if (i_cal_component_isa (icomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2346 			g_slist_free_full (icomps, g_object_unref);
2347 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
2348 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2349 			return;
2350 		}
2351 
2352 		/* Get the UID */
2353 		comp_uid = i_cal_component_get_uid (icomp);
2354 		if (!comp_uid) {
2355 			gchar *new_uid;
2356 
2357 			new_uid = e_util_generate_uid ();
2358 			if (!new_uid) {
2359 				g_slist_free_full (icomps, g_object_unref);
2360 				g_rec_mutex_unlock (&priv->idle_save_rmutex);
2361 				g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2362 				return;
2363 			}
2364 
2365 			i_cal_component_set_uid (icomp, new_uid);
2366 			comp_uid = i_cal_component_get_uid (icomp);
2367 
2368 			g_free (new_uid);
2369 		}
2370 
2371 		/* check that the object is not in our cache */
2372 		if (uid_in_use (cbfile, comp_uid)) {
2373 			g_slist_free_full (icomps, g_object_unref);
2374 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
2375 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_ID_ALREADY_EXISTS));
2376 			return;
2377 		}
2378 	}
2379 
2380 	icomps = g_slist_reverse (icomps);
2381 
2382 	/* Second step, add the objects */
2383 	for (l = icomps; l; l = l->next) {
2384 		ECalComponent *comp;
2385 		ICalTime *current;
2386 		ICalComponent *icomp = l->data;
2387 
2388 		/* Create the cal component */
2389 		comp = e_cal_component_new_from_icalcomponent (icomp);
2390 		if (!comp)
2391 			continue;
2392 
2393 		/* Set the created and last modified times on the component, if not there already */
2394 		current = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
2395 
2396 		if (!e_cal_util_component_has_property (icomp, I_CAL_CREATED_PROPERTY)) {
2397 			/* Update both when CREATED is missing, to make sure the LAST-MODIFIED
2398 			   is not before CREATED */
2399 			e_cal_component_set_created (comp, current);
2400 			e_cal_component_set_last_modified (comp, current);
2401 		} else if (!e_cal_util_component_has_property (icomp, I_CAL_LASTMODIFIED_PROPERTY)) {
2402 			e_cal_component_set_last_modified (comp, current);
2403 		}
2404 
2405 		g_object_unref (current);
2406 
2407 		/* sanitize the component*/
2408 		sanitize_component (cbfile, comp);
2409 
2410 		/* Add the object */
2411 		add_component (cbfile, comp, TRUE);
2412 
2413 		/* Keep the UID and the modified component to return them later */
2414 		if (uids)
2415 			*uids = g_slist_prepend (*uids, g_strdup (i_cal_component_get_uid (icomp)));
2416 
2417 		*new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
2418 	}
2419 
2420 	g_slist_free (icomps);
2421 
2422 	/* Save the file */
2423 	save (cbfile, TRUE);
2424 
2425 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
2426 
2427 	if (uids)
2428 		*uids = g_slist_reverse (*uids);
2429 
2430 	*new_components = g_slist_reverse (*new_components);
2431 }
2432 
2433 typedef struct {
2434 	ECalBackendFile *cbfile;
2435 	ECalBackendFileObject *obj_data;
2436 	const gchar *rid;
2437 	ECalObjModType mod;
2438 } RemoveRecurrenceData;
2439 
2440 static gboolean
remove_object_instance_cb(gpointer key,gpointer value,gpointer user_data)2441 remove_object_instance_cb (gpointer key,
2442                            gpointer value,
2443                            gpointer user_data)
2444 {
2445 	time_t fromtt, instancett;
2446 	ICalTime *itt;
2447 	ECalComponent *instance = value;
2448 	RemoveRecurrenceData *rrdata = user_data;
2449 
2450 	itt = i_cal_time_new_from_string (rrdata->rid);
2451 	fromtt = i_cal_time_as_timet (itt);
2452 	g_object_unref (itt);
2453 
2454 	instancett = get_rid_as_time_t (instance);
2455 
2456 	if (fromtt > 0 && instancett > 0) {
2457 		if ((rrdata->mod == E_CAL_OBJ_MOD_THIS_AND_PRIOR && instancett <= fromtt) ||
2458 		    (rrdata->mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE && instancett >= fromtt)) {
2459 			/* remove the component from our data */
2460 			i_cal_component_remove_component (
2461 				rrdata->cbfile->priv->vcalendar,
2462 				e_cal_component_get_icalcomponent (instance));
2463 			rrdata->cbfile->priv->comp = g_list_remove (rrdata->cbfile->priv->comp, instance);
2464 
2465 			rrdata->obj_data->recurrences_list = g_list_remove (rrdata->obj_data->recurrences_list, instance);
2466 
2467 			return TRUE;
2468 		}
2469 	}
2470 
2471 	return FALSE;
2472 }
2473 
2474 static void
e_cal_backend_file_modify_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const GSList * calobjs,ECalObjModType mod,ECalOperationFlags opflags,GSList ** old_components,GSList ** new_components,GError ** error)2475 e_cal_backend_file_modify_objects (ECalBackendSync *backend,
2476                                    EDataCal *cal,
2477                                    GCancellable *cancellable,
2478                                    const GSList *calobjs,
2479                                    ECalObjModType mod,
2480 				   ECalOperationFlags opflags,
2481                                    GSList **old_components,
2482                                    GSList **new_components,
2483                                    GError **error)
2484 {
2485 	ECalBackendFile *cbfile;
2486 	ECalBackendFilePrivate *priv;
2487 	GSList *icomps = NULL;
2488 	const GSList *l;
2489 	ResolveTzidData rtd;
2490 
2491 	cbfile = E_CAL_BACKEND_FILE (backend);
2492 	priv = cbfile->priv;
2493 
2494 	if (priv->vcalendar == NULL) {
2495 		g_set_error_literal (
2496 			error, E_CAL_CLIENT_ERROR,
2497 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
2498 			e_cal_client_error_to_string (
2499 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
2500 		return;
2501 	}
2502 
2503 	resolve_tzid_data_init (&rtd, priv->vcalendar);
2504 
2505 	switch (mod) {
2506 	case E_CAL_OBJ_MOD_THIS:
2507 	case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
2508 	case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
2509 	case E_CAL_OBJ_MOD_ALL:
2510 		break;
2511 	default:
2512 		g_propagate_error (error, EC_ERROR (E_CLIENT_ERROR_NOT_SUPPORTED));
2513 		return;
2514 	}
2515 
2516 	if (old_components)
2517 		*old_components = NULL;
2518 	if (new_components)
2519 		*new_components = NULL;
2520 
2521 	g_rec_mutex_lock (&priv->idle_save_rmutex);
2522 
2523 	/* First step, parse input strings and do uid verification: may fail */
2524 	for (l = calobjs; l; l = l->next) {
2525 		const gchar *comp_uid;
2526 		ICalComponent *icomp;
2527 
2528 		/* Parse the iCalendar text */
2529 		icomp = i_cal_parser_parse_string (l->data);
2530 		if (!icomp) {
2531 			g_slist_free_full (icomps, g_object_unref);
2532 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
2533 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2534 			return;
2535 		}
2536 
2537 		icomps = g_slist_prepend (icomps, icomp);
2538 
2539 		/* Check kind with the parent */
2540 		if (i_cal_component_isa (icomp) != e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2541 			g_slist_free_full (icomps, g_object_unref);
2542 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
2543 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
2544 			return;
2545 		}
2546 
2547 		/* Get the uid */
2548 		comp_uid = i_cal_component_get_uid (icomp);
2549 
2550 		/* Get the object from our cache */
2551 		if (!g_hash_table_lookup (priv->comp_uid_hash, comp_uid)) {
2552 			g_slist_free_full (icomps, g_object_unref);
2553 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
2554 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2555 			return;
2556 		}
2557 	}
2558 
2559 	icomps = g_slist_reverse (icomps);
2560 
2561 	/* Second step, update the objects */
2562 	for (l = icomps; l; l = l->next) {
2563 		ICalTime *current;
2564 		RemoveRecurrenceData rrdata;
2565 		GList *detached = NULL;
2566 		gchar *rid = NULL;
2567 		const gchar *comp_uid;
2568 		ICalComponent * icomp = l->data, *split_icomp = NULL;
2569 		ECalComponent *comp, *recurrence;
2570 		ECalBackendFileObject *obj_data;
2571 		gpointer value;
2572 
2573 		/* Create the cal component */
2574 		comp = e_cal_component_new_from_icalcomponent (icomp);
2575 		if (!comp)
2576 			continue;
2577 
2578 		comp_uid = i_cal_component_get_uid (icomp);
2579 		obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid);
2580 
2581 		/* Set the last modified time on the component */
2582 		current = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
2583 		e_cal_component_set_last_modified (comp, current);
2584 		g_object_unref (current);
2585 
2586 		/* sanitize the component*/
2587 		sanitize_component (cbfile, comp);
2588 		rid = e_cal_component_get_recurid_as_string (comp);
2589 
2590 		/* handle mod_type */
2591 		switch (mod) {
2592 		case E_CAL_OBJ_MOD_THIS:
2593 			if (!rid || !*rid) {
2594 				if (old_components)
2595 					*old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2596 
2597 				/* replace only the full object */
2598 				if (obj_data->full_object) {
2599 					i_cal_component_remove_component (
2600 						priv->vcalendar,
2601 						e_cal_component_get_icalcomponent (obj_data->full_object));
2602 					priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2603 
2604 					g_object_unref (obj_data->full_object);
2605 				}
2606 
2607 				/* add the new object */
2608 				obj_data->full_object = comp;
2609 
2610 				e_cal_recur_ensure_end_dates (comp, TRUE, resolve_tzid_cb, &rtd, cancellable, NULL);
2611 
2612 				if (!remove_component_from_intervaltree (cbfile, comp)) {
2613 					g_message (G_STRLOC " Could not remove component from interval tree!");
2614 				}
2615 
2616 				add_component_to_intervaltree (cbfile, comp);
2617 
2618 				i_cal_component_add_component (
2619 					priv->vcalendar,
2620 					e_cal_component_get_icalcomponent (obj_data->full_object));
2621 				priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2622 				break;
2623 			}
2624 
2625 			if (g_hash_table_lookup_extended (obj_data->recurrences, rid, NULL, &value)) {
2626 				recurrence = value;
2627 
2628 				if (old_components)
2629 					*old_components = g_slist_prepend (*old_components, e_cal_component_clone (recurrence));
2630 
2631 				/* remove the component from our data */
2632 				i_cal_component_remove_component (
2633 					priv->vcalendar,
2634 					e_cal_component_get_icalcomponent (recurrence));
2635 				priv->comp = g_list_remove (priv->comp, recurrence);
2636 				obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2637 				g_hash_table_remove (obj_data->recurrences, rid);
2638 			} else {
2639 				if (old_components)
2640 					*old_components = g_slist_prepend (*old_components, NULL);
2641 			}
2642 
2643 			/* add the detached instance */
2644 			g_hash_table_insert (
2645 				obj_data->recurrences,
2646 				g_strdup (rid),
2647 				comp);
2648 			i_cal_component_add_component (
2649 				priv->vcalendar,
2650 				e_cal_component_get_icalcomponent (comp));
2651 			priv->comp = g_list_append (priv->comp, comp);
2652 			obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, comp);
2653 			break;
2654 		case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
2655 		case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
2656 			if (!rid || !*rid)
2657 				goto like_mod_all;
2658 
2659 			/* remove the component from our data, temporarily */
2660 			if (obj_data->full_object) {
2661 				if (mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE) {
2662 					ICalTime *itt = i_cal_component_get_recurrenceid (icomp);
2663 
2664 					if (e_cal_util_is_first_instance (obj_data->full_object, itt, resolve_tzid_cb, &rtd)) {
2665 						ICalProperty *prop = i_cal_component_get_first_property (icomp, I_CAL_RECURRENCEID_PROPERTY);
2666 
2667 						g_clear_object (&itt);
2668 
2669 						if (prop) {
2670 							i_cal_component_remove_property (icomp, prop);
2671 							g_object_unref (prop);
2672 						}
2673 
2674 						goto like_mod_all;
2675 					}
2676 
2677 					g_clear_object (&itt);
2678 				}
2679 
2680 				i_cal_component_remove_component (
2681 					priv->vcalendar,
2682 					e_cal_component_get_icalcomponent (obj_data->full_object));
2683 				priv->comp = g_list_remove (priv->comp, obj_data->full_object);
2684 			}
2685 
2686 			/* now deal with the detached recurrence */
2687 			if (g_hash_table_lookup_extended (obj_data->recurrences, rid, NULL, &value)) {
2688 				recurrence = value;
2689 
2690 				if (old_components)
2691 					*old_components = g_slist_prepend (*old_components, e_cal_component_clone (recurrence));
2692 
2693 				/* remove the component from our data */
2694 				i_cal_component_remove_component (
2695 					priv->vcalendar,
2696 					e_cal_component_get_icalcomponent (recurrence));
2697 				priv->comp = g_list_remove (priv->comp, recurrence);
2698 				obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, recurrence);
2699 				g_hash_table_remove (obj_data->recurrences, rid);
2700 			} else {
2701 				if (*old_components)
2702 					*old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2703 			}
2704 
2705 			rrdata.cbfile = cbfile;
2706 			rrdata.obj_data = obj_data;
2707 			rrdata.rid = rid;
2708 			rrdata.mod = mod;
2709 			g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
2710 
2711 			/* add the modified object to the beginning of the list,
2712 			 * so that it's always before any detached instance we
2713 			 * might have */
2714 			if (obj_data->full_object) {
2715 				ICalTime *rid_struct = i_cal_component_get_recurrenceid (icomp), *master_dtstart;
2716 				ICalComponent *master_icomp = e_cal_component_get_icalcomponent (obj_data->full_object);
2717 				ICalProperty *prop = i_cal_component_get_first_property (icomp, I_CAL_RECURRENCEID_PROPERTY);
2718 
2719 				if (prop) {
2720 					i_cal_component_remove_property (icomp, prop);
2721 					g_object_unref (prop);
2722 				}
2723 
2724 				master_dtstart = i_cal_component_get_dtstart (master_icomp);
2725 				if (master_dtstart && i_cal_time_get_timezone (master_dtstart) &&
2726 				    i_cal_time_get_timezone (master_dtstart) != i_cal_time_get_timezone (rid_struct)) {
2727 					i_cal_time_convert_to_zone_inplace (rid_struct, i_cal_time_get_timezone (master_dtstart));
2728 				}
2729 
2730 				split_icomp = e_cal_util_split_at_instance_ex (icomp, rid_struct, master_dtstart, resolve_tzid_cb, &rtd);
2731 				if (split_icomp) {
2732 					ECalComponent *prev_comp;
2733 
2734 					prev_comp = e_cal_component_clone (obj_data->full_object);
2735 
2736 					e_cal_util_remove_instances_ex (e_cal_component_get_icalcomponent (obj_data->full_object), rid_struct, mod, resolve_tzid_cb, &rtd);
2737 					e_cal_recur_ensure_end_dates (obj_data->full_object, TRUE, resolve_tzid_cb, &rtd, cancellable, NULL);
2738 
2739 					e_cal_backend_notify_component_modified (E_CAL_BACKEND (backend), prev_comp, obj_data->full_object);
2740 
2741 					g_clear_object (&prev_comp);
2742 				}
2743 
2744 				i_cal_component_add_component (
2745 					priv->vcalendar,
2746 					e_cal_component_get_icalcomponent (obj_data->full_object));
2747 				priv->comp = g_list_prepend (priv->comp, obj_data->full_object);
2748 
2749 				g_clear_object (&rid_struct);
2750 				g_clear_object (&master_dtstart);
2751 			} else {
2752 				ICalTime *rid_struct = i_cal_component_get_recurrenceid (icomp);
2753 
2754 				split_icomp = e_cal_util_split_at_instance_ex (icomp, rid_struct, NULL, resolve_tzid_cb, &rtd);
2755 
2756 				g_object_unref (rid_struct);
2757 			}
2758 
2759 			if (split_icomp) {
2760 				gchar *new_uid;
2761 
2762 				new_uid = e_util_generate_uid ();
2763 				i_cal_component_set_uid (split_icomp, new_uid);
2764 				g_free (new_uid);
2765 
2766 				g_warn_if_fail (e_cal_component_set_icalcomponent (comp, split_icomp));
2767 				e_cal_recur_ensure_end_dates (comp, TRUE, resolve_tzid_cb, &rtd, cancellable, NULL);
2768 
2769 				/* sanitize the component */
2770 				sanitize_component (cbfile, comp);
2771 
2772 				/* Add the object */
2773 				add_component (cbfile, comp, TRUE);
2774 			}
2775 			break;
2776 		case E_CAL_OBJ_MOD_ALL :
2777  like_mod_all:
2778 			/* Remove the old version */
2779 			if (old_components)
2780 				*old_components = g_slist_prepend (*old_components, obj_data->full_object ? e_cal_component_clone (obj_data->full_object) : NULL);
2781 
2782 			if (obj_data->recurrences_list) {
2783 				/* has detached components, preserve them */
2784 				GList *ll;
2785 
2786 				for (ll = obj_data->recurrences_list; ll; ll = ll->next) {
2787 					detached = g_list_prepend (detached, g_object_ref (ll->data));
2788 				}
2789 			}
2790 
2791 			remove_component (cbfile, comp_uid, obj_data);
2792 
2793 			e_cal_recur_ensure_end_dates (comp, TRUE, resolve_tzid_cb, &rtd, cancellable, NULL);
2794 
2795 			/* Add the new object */
2796 			add_component (cbfile, comp, TRUE);
2797 
2798 			if (detached) {
2799 				/* it had some detached components, place them back */
2800 				comp_uid = i_cal_component_get_uid (e_cal_component_get_icalcomponent (comp));
2801 
2802 				if ((obj_data = g_hash_table_lookup (priv->comp_uid_hash, comp_uid)) != NULL) {
2803 					GList *ll;
2804 
2805 					for (ll = detached; ll; ll = ll->next) {
2806 						ECalComponent *c = ll->data;
2807 
2808 						g_hash_table_insert (obj_data->recurrences, e_cal_component_get_recurid_as_string (c), c);
2809 						i_cal_component_add_component (priv->vcalendar, e_cal_component_get_icalcomponent (c));
2810 						priv->comp = g_list_append (priv->comp, c);
2811 						obj_data->recurrences_list = g_list_append (obj_data->recurrences_list, c);
2812 					}
2813 				}
2814 
2815 				g_list_free (detached);
2816 			}
2817 			break;
2818 		/* coverity[dead_error_begin] */
2819 		case E_CAL_OBJ_MOD_ONLY_THIS:
2820 			/* not reached, keep compiler happy */
2821 			g_warn_if_reached ();
2822 			break;
2823 		}
2824 
2825 		g_free (rid);
2826 
2827 		if (new_components) {
2828 			*new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
2829 		}
2830 	}
2831 
2832 	resolve_tzid_data_clear (&rtd);
2833 
2834 	g_slist_free (icomps);
2835 
2836 	/* All the components were updated, now we save the file */
2837 	save (cbfile, TRUE);
2838 
2839 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
2840 
2841 	if (old_components)
2842 		*old_components = g_slist_reverse (*old_components);
2843 
2844 	if (new_components)
2845 		*new_components = g_slist_reverse (*new_components);
2846 }
2847 
2848 static void
e_cal_backend_file_discard_alarm_sync(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * uid,const gchar * rid,const gchar * auid,ECalOperationFlags opflags,GError ** error)2849 e_cal_backend_file_discard_alarm_sync (ECalBackendSync *backend,
2850 				       EDataCal *cal,
2851 				       GCancellable *cancellable,
2852 				       const gchar *uid,
2853 				       const gchar *rid,
2854 				       const gchar *auid,
2855 				       ECalOperationFlags opflags,
2856 				       GError **error)
2857 {
2858 	ECalBackendFile *cbfile;
2859 	ECalBackendFilePrivate *priv;
2860 	ECalBackendFileObject *obj_data;
2861 	ECalComponent *comp = NULL;
2862 
2863 	cbfile = E_CAL_BACKEND_FILE (backend);
2864 	priv = cbfile->priv;
2865 
2866 	if (priv->vcalendar == NULL) {
2867 		g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2868 		return;
2869 	}
2870 
2871 	g_return_if_fail (uid != NULL);
2872 	g_return_if_fail (priv->comp_uid_hash != NULL);
2873 
2874 	g_rec_mutex_lock (&priv->idle_save_rmutex);
2875 
2876 	obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
2877 	if (!obj_data) {
2878 		g_rec_mutex_unlock (&priv->idle_save_rmutex);
2879 		g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2880 		return;
2881 	}
2882 
2883 	if (rid && *rid) {
2884 		comp = g_hash_table_lookup (obj_data->recurrences, rid);
2885 
2886 		if (comp) {
2887 			g_object_ref (comp);
2888 		} else if (obj_data->full_object) {
2889 			ICalComponent *icomp;
2890 			ICalTime *itt;
2891 
2892 			itt = i_cal_time_new_from_string (rid);
2893 			icomp = e_cal_util_construct_instance (
2894 				e_cal_component_get_icalcomponent (obj_data->full_object),
2895 				itt);
2896 			g_object_unref (itt);
2897 
2898 			if (icomp)
2899 				comp = e_cal_component_new_from_icalcomponent (icomp);
2900 		}
2901 	} else if (obj_data->full_object) {
2902 		comp = g_object_ref (obj_data->full_object);
2903 	}
2904 
2905 	if (comp) {
2906 		if (e_cal_util_set_alarm_acknowledged (comp, auid, 0)) {
2907 			GSList *calobjs;
2908 
2909 			calobjs = g_slist_prepend (NULL, e_cal_component_get_as_string (comp));
2910 
2911 			e_cal_backend_file_modify_objects (backend, cal, cancellable, calobjs,
2912 				(rid && *rid) ? E_CAL_OBJ_MOD_THIS : E_CAL_OBJ_MOD_ALL,
2913 				opflags, NULL, NULL, error);
2914 
2915 			g_slist_free_full (calobjs, g_free);
2916 		} else {
2917 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2918 		}
2919 
2920 		g_object_unref (comp);
2921 	} else {
2922 		g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2923 	}
2924 
2925 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
2926 }
2927 
2928 /**
2929  * Remove one and only one instance. The object may be empty
2930  * afterwards, in which case it will be removed completely.
2931  *
2932  * @mod    E_CAL_OBJ_MOD_THIS or E_CAL_OBJ_MOD_ONLY_THIS: the later only
2933  *         removes the instance, the former also adds an EXDATE if rid is set
2934  *         TODO: E_CAL_OBJ_MOD_ONLY_THIS
2935  * @uid    pointer to UID which must remain valid even if the object gets
2936  *         removed
2937  * @rid    NULL, "", or non-empty string when manipulating a specific recurrence;
2938  *         also must remain valid
2939  * @error  may be NULL if caller is not interested in errors
2940  * @return modified object or NULL if it got removed
2941  */
2942 static ECalBackendFileObject *
remove_instance(ECalBackendFile * cbfile,ECalBackendFileObject * obj_data,const gchar * uid,const gchar * rid,ECalObjModType mod,ECalComponent ** old_comp,ECalComponent ** new_comp,GError ** error)2943 remove_instance (ECalBackendFile *cbfile,
2944                  ECalBackendFileObject *obj_data,
2945                  const gchar *uid,
2946                  const gchar *rid,
2947                  ECalObjModType mod,
2948                  ECalComponent **old_comp,
2949                  ECalComponent **new_comp,
2950                  GError **error)
2951 {
2952 	ECalComponent *comp;
2953 	ICalTime *current;
2954 
2955 	/* only check for non-NULL below, empty string is detected here */
2956 	if (rid && !*rid)
2957 		rid = NULL;
2958 
2959 	if (rid) {
2960 		ICalTime *rid_struct;
2961 		ResolveTzidData rtd;
2962 		gpointer value;
2963 
2964 		/* remove recurrence */
2965 		if (g_hash_table_lookup_extended (obj_data->recurrences, rid, NULL, &value)) {
2966 			comp = value;
2967 
2968 			/* Removing without parent or not modifying parent?
2969 			 * Report removal to caller. */
2970 			if (old_comp &&
2971 			    (!obj_data->full_object || mod == E_CAL_OBJ_MOD_ONLY_THIS)) {
2972 				*old_comp = e_cal_component_clone (comp);
2973 			}
2974 
2975 			/* Reporting parent modification to caller?
2976 			 * Report directly instead of going via caller. */
2977 			if (obj_data->full_object &&
2978 			    mod != E_CAL_OBJ_MOD_ONLY_THIS) {
2979 				/* old object string not provided,
2980 				 * instead rely on the view detecting
2981 				 * whether it contains the id */
2982 				ECalComponentId *id;
2983 
2984 				id = e_cal_component_id_new (uid, rid);
2985 				e_cal_backend_notify_component_removed (E_CAL_BACKEND (cbfile), id, NULL, NULL);
2986 				e_cal_component_id_free (id);
2987 			}
2988 
2989 			/* remove the component from our data */
2990 			i_cal_component_remove_component (
2991 				cbfile->priv->vcalendar,
2992 				e_cal_component_get_icalcomponent (comp));
2993 			cbfile->priv->comp = g_list_remove (cbfile->priv->comp, comp);
2994 			obj_data->recurrences_list = g_list_remove (obj_data->recurrences_list, comp);
2995 			g_hash_table_remove (obj_data->recurrences, rid);
2996 		} else if (mod == E_CAL_OBJ_MOD_ONLY_THIS) {
2997 			if (error)
2998 				g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
2999 			return obj_data;
3000 		} else {
3001 			/* not an error, only add EXDATE */
3002 		}
3003 		/* component empty? */
3004 		if (!obj_data->full_object) {
3005 			if (!obj_data->recurrences_list) {
3006 				/* empty now, remove it */
3007 				remove_component (cbfile, uid, obj_data);
3008 				return NULL;
3009 			} else {
3010 				return obj_data;
3011 			}
3012 		}
3013 
3014 		/* avoid modifying parent? */
3015 		if (mod == E_CAL_OBJ_MOD_ONLY_THIS)
3016 			return obj_data;
3017 
3018 		/* remove the main component from our data before modifying it */
3019 		i_cal_component_remove_component (
3020 			cbfile->priv->vcalendar,
3021 			e_cal_component_get_icalcomponent (obj_data->full_object));
3022 		cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
3023 
3024 		/* add EXDATE or EXRULE to parent, report as update */
3025 		if (old_comp) {
3026 			*old_comp = e_cal_component_clone (obj_data->full_object);
3027 		}
3028 
3029 		rid_struct = i_cal_time_new_from_string (rid);
3030 		if (!i_cal_time_get_timezone (rid_struct)) {
3031 			ICalTime *master_dtstart = i_cal_component_get_dtstart (e_cal_component_get_icalcomponent (obj_data->full_object));
3032 
3033 			if (master_dtstart && i_cal_time_get_timezone (master_dtstart)) {
3034 				i_cal_time_convert_to_zone_inplace (rid_struct, i_cal_time_get_timezone (master_dtstart));
3035 			}
3036 		}
3037 
3038 		resolve_tzid_data_init (&rtd, cbfile->priv->vcalendar);
3039 
3040 		e_cal_util_remove_instances_ex (
3041 			e_cal_component_get_icalcomponent (obj_data->full_object),
3042 			rid_struct, mod, resolve_tzid_cb, &rtd);
3043 
3044 		resolve_tzid_data_clear (&rtd);
3045 		g_clear_object (&rid_struct);
3046 
3047 		/* Since we are only removing one instance of recurrence
3048 		 * event, update the last modified time on the component */
3049 		current = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
3050 		e_cal_component_set_last_modified (obj_data->full_object, current);
3051 		g_object_unref (current);
3052 
3053 		/* report update */
3054 		if (new_comp) {
3055 			*new_comp = e_cal_component_clone (obj_data->full_object);
3056 		}
3057 
3058 		/* add the modified object to the beginning of the list,
3059 		 * so that it's always before any detached instance we
3060 		 * might have */
3061 		i_cal_component_add_component (
3062 			cbfile->priv->vcalendar,
3063 			e_cal_component_get_icalcomponent (obj_data->full_object));
3064 		cbfile->priv->comp = g_list_prepend (cbfile->priv->comp, obj_data->full_object);
3065 	} else {
3066 		if (!obj_data->full_object) {
3067 			/* Nothing to do, parent doesn't exist. Tell
3068 			 * caller about this? Not an error with
3069 			 * E_CAL_OBJ_MOD_THIS. */
3070 			if (mod == E_CAL_OBJ_MOD_ONLY_THIS && error)
3071 				g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
3072 			return obj_data;
3073 		}
3074 
3075 		/* remove the main component from our data before deleting it */
3076 		if (!remove_component_from_intervaltree (cbfile, obj_data->full_object)) {
3077 			/* return without changing anything */
3078 			g_message (G_STRLOC " Could not remove component from interval tree!");
3079 			return obj_data;
3080 		}
3081 		i_cal_component_remove_component (
3082 			cbfile->priv->vcalendar,
3083 			e_cal_component_get_icalcomponent (obj_data->full_object));
3084 		cbfile->priv->comp = g_list_remove (cbfile->priv->comp, obj_data->full_object);
3085 
3086 		/* remove parent, report as removal */
3087 		if (old_comp) {
3088 			*old_comp = g_object_ref (obj_data->full_object);
3089 		}
3090 		g_object_unref (obj_data->full_object);
3091 		obj_data->full_object = NULL;
3092 
3093 		/* component may be empty now, check that */
3094 		if (!obj_data->recurrences_list) {
3095 			remove_component (cbfile, uid, obj_data);
3096 			return NULL;
3097 		}
3098 	}
3099 
3100 	/* component still exists in a modified form */
3101 	return obj_data;
3102 }
3103 
3104 static ECalComponent *
clone_ecalcomp_from_fileobject(ECalBackendFileObject * obj_data,const gchar * rid)3105 clone_ecalcomp_from_fileobject (ECalBackendFileObject *obj_data,
3106                                 const gchar *rid)
3107 {
3108 	ECalComponent *comp = obj_data->full_object;
3109 
3110 	if (!comp)
3111 		return NULL;
3112 
3113 	if (rid) {
3114 		gpointer value;
3115 
3116 		if (g_hash_table_lookup_extended (obj_data->recurrences, rid, NULL, &value)) {
3117 			comp = value;
3118 		} else {
3119 			/* FIXME remove this once we delete an instance from master object through
3120 			 * modify request by setting exception */
3121 			comp = obj_data->full_object;
3122 		}
3123 	}
3124 
3125 	return comp ? e_cal_component_clone (comp) : NULL;
3126 }
3127 
3128 static void
notify_comp_removed_cb(gpointer pecalcomp,gpointer pbackend)3129 notify_comp_removed_cb (gpointer pecalcomp,
3130                         gpointer pbackend)
3131 {
3132 	ECalComponent *comp = pecalcomp;
3133 	ECalBackend *backend = pbackend;
3134 	ECalComponentId *id;
3135 
3136 	g_return_if_fail (comp != NULL);
3137 	g_return_if_fail (backend != NULL);
3138 
3139 	id = e_cal_component_get_id (comp);
3140 	g_return_if_fail (id != NULL);
3141 
3142 	e_cal_backend_notify_component_removed (backend, id, comp, NULL);
3143 
3144 	e_cal_component_id_free (id);
3145 }
3146 
3147 /* Remove_object handler for the file backend */
3148 static void
e_cal_backend_file_remove_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const GSList * ids,ECalObjModType mod,ECalOperationFlags opflags,GSList ** old_components,GSList ** new_components,GError ** error)3149 e_cal_backend_file_remove_objects (ECalBackendSync *backend,
3150                                    EDataCal *cal,
3151                                    GCancellable *cancellable,
3152                                    const GSList *ids,
3153                                    ECalObjModType mod,
3154 				   ECalOperationFlags opflags,
3155                                    GSList **old_components,
3156                                    GSList **new_components,
3157                                    GError **error)
3158 {
3159 	ECalBackendFile *cbfile;
3160 	ECalBackendFilePrivate *priv;
3161 	const GSList *l;
3162 
3163 	cbfile = E_CAL_BACKEND_FILE (backend);
3164 	priv = cbfile->priv;
3165 
3166 	if (priv->vcalendar == NULL) {
3167 		g_set_error_literal (
3168 			error, E_CAL_CLIENT_ERROR,
3169 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
3170 			e_cal_client_error_to_string (
3171 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
3172 		return;
3173 	}
3174 
3175 	switch (mod) {
3176 	case E_CAL_OBJ_MOD_THIS:
3177 	case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
3178 	case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
3179 	case E_CAL_OBJ_MOD_ONLY_THIS:
3180 	case E_CAL_OBJ_MOD_ALL:
3181 		break;
3182 	default:
3183 		g_propagate_error (error, EC_ERROR (E_CLIENT_ERROR_NOT_SUPPORTED));
3184 		return;
3185 	}
3186 
3187 	*old_components = *new_components = NULL;
3188 
3189 	g_rec_mutex_lock (&priv->idle_save_rmutex);
3190 
3191 	/* First step, validate the input */
3192 	for (l = ids; l; l = l->next) {
3193 		ECalComponentId *id = l->data;
3194 		/* Make the ID contains a uid */
3195 		if (!id || !e_cal_component_id_get_uid (id)) {
3196 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
3197 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
3198 			return;
3199 		}
3200 				/* Check that it has a recurrence id if mod is E_CAL_OBJ_MOD_THIS_AND_PRIOR
3201 					 or E_CAL_OBJ_MOD_THIS_AND_FUTURE */
3202 		if ((mod == E_CAL_OBJ_MOD_THIS_AND_PRIOR || mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE) &&
3203 		    !e_cal_component_id_get_rid (id)) {
3204 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
3205 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
3206 			return;
3207 		}
3208 				/* Make sure the uid exists in the local hash table */
3209 		if (!g_hash_table_lookup (priv->comp_uid_hash, e_cal_component_id_get_uid (id))) {
3210 			g_rec_mutex_unlock (&priv->idle_save_rmutex);
3211 			g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
3212 			return;
3213 		}
3214 	}
3215 
3216 	/* Second step, remove objects from the calendar */
3217 	for (l = ids; l; l = l->next) {
3218 		const gchar *recur_id = NULL;
3219 		ECalComponent *comp;
3220 		RemoveRecurrenceData rrdata;
3221 		ECalBackendFileObject *obj_data;
3222 		ECalComponentId *id = l->data;
3223 
3224 		obj_data = g_hash_table_lookup (priv->comp_uid_hash, e_cal_component_id_get_uid (id));
3225 		recur_id = e_cal_component_id_get_rid (id);
3226 
3227 		switch (mod) {
3228 		case E_CAL_OBJ_MOD_ALL :
3229 			*old_components = g_slist_prepend (*old_components, clone_ecalcomp_from_fileobject (obj_data, recur_id));
3230 			*new_components = g_slist_prepend (*new_components, NULL);
3231 
3232 			if (obj_data->recurrences_list)
3233 				g_list_foreach (obj_data->recurrences_list, notify_comp_removed_cb, cbfile);
3234 			remove_component (cbfile, e_cal_component_id_get_uid (id), obj_data);
3235 			break;
3236 		case E_CAL_OBJ_MOD_ONLY_THIS:
3237 		case E_CAL_OBJ_MOD_THIS: {
3238 			ECalComponent *old_component = NULL;
3239 			ECalComponent *new_component = NULL;
3240 
3241 			remove_instance (
3242 				cbfile, obj_data, e_cal_component_id_get_uid (id), recur_id, mod,
3243 				&old_component, &new_component, error);
3244 
3245 			*old_components = g_slist_prepend (*old_components, old_component);
3246 			*new_components = g_slist_prepend (*new_components, new_component);
3247 			break;
3248 		}
3249 		case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
3250 		case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
3251 			comp = obj_data->full_object;
3252 
3253 			if (comp) {
3254 				ICalTime *rid_struct;
3255 				ResolveTzidData rtd;
3256 
3257 				*old_components = g_slist_prepend (*old_components, e_cal_component_clone (comp));
3258 
3259 				/* remove the component from our data, temporarily */
3260 				i_cal_component_remove_component (
3261 					priv->vcalendar,
3262 					e_cal_component_get_icalcomponent (comp));
3263 				priv->comp = g_list_remove (priv->comp, comp);
3264 
3265 				rid_struct = i_cal_time_new_from_string (recur_id);
3266 				if (!i_cal_time_get_timezone (rid_struct)) {
3267 					ICalTime *master_dtstart = i_cal_component_get_dtstart (e_cal_component_get_icalcomponent (comp));
3268 
3269 					if (master_dtstart && i_cal_time_get_timezone (master_dtstart)) {
3270 						i_cal_time_convert_to_zone_inplace (rid_struct, i_cal_time_get_timezone (master_dtstart));
3271 					}
3272 
3273 					i_cal_time_convert_to_zone_inplace (rid_struct, i_cal_timezone_get_utc_timezone ());
3274 				}
3275 
3276 				resolve_tzid_data_init (&rtd, priv->vcalendar);
3277 
3278 				e_cal_util_remove_instances_ex (
3279 					e_cal_component_get_icalcomponent (comp),
3280 					rid_struct, mod, resolve_tzid_cb, &rtd);
3281 
3282 				resolve_tzid_data_clear (&rtd);
3283 				g_object_unref (rid_struct);
3284 			} else {
3285 				*old_components = g_slist_prepend (*old_components, NULL);
3286 			}
3287 
3288 			/* now remove all detached instances */
3289 			rrdata.cbfile = cbfile;
3290 			rrdata.obj_data = obj_data;
3291 			rrdata.rid = recur_id;
3292 			rrdata.mod = mod;
3293 			g_hash_table_foreach_remove (obj_data->recurrences, (GHRFunc) remove_object_instance_cb, &rrdata);
3294 
3295 			/* add the modified object to the beginning of the list,
3296 			 * so that it's always before any detached instance we
3297 			 * might have */
3298 			if (comp)
3299 				priv->comp = g_list_prepend (priv->comp, comp);
3300 
3301 			if (obj_data->full_object) {
3302 				*new_components = g_slist_prepend (*new_components, e_cal_component_clone (obj_data->full_object));
3303 			} else {
3304 				*new_components = g_slist_prepend (*new_components, NULL);
3305 			}
3306 			break;
3307 		}
3308 	}
3309 
3310 	save (cbfile, TRUE);
3311 
3312 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
3313 
3314 	*old_components = g_slist_reverse (*old_components);
3315 	*new_components = g_slist_reverse (*new_components);
3316 }
3317 
3318 static gboolean
cancel_received_object(ECalBackendFile * cbfile,ECalComponent * comp,ECalObjModType mod,ECalComponent ** old_comp,ECalComponent ** new_comp)3319 cancel_received_object (ECalBackendFile *cbfile,
3320                         ECalComponent *comp,
3321 			ECalObjModType mod,
3322                         ECalComponent **old_comp,
3323                         ECalComponent **new_comp)
3324 {
3325 	ECalBackendFileObject *obj_data;
3326 	ECalBackendFilePrivate *priv;
3327 	gchar *rid;
3328 	const gchar *uid;
3329 
3330 	priv = cbfile->priv;
3331 
3332 	*old_comp = NULL;
3333 	*new_comp = NULL;
3334 
3335 	uid = e_cal_component_get_uid (comp);
3336 
3337 	/* Find the old version of the component. */
3338 	obj_data = uid ? g_hash_table_lookup (priv->comp_uid_hash, uid) : NULL;
3339 	if (!obj_data)
3340 		return FALSE;
3341 
3342 	/* And remove it */
3343 	rid = e_cal_component_get_recurid_as_string (comp);
3344 	if (rid && *rid) {
3345 		obj_data = remove_instance (
3346 			cbfile, obj_data, uid, rid, mod,
3347 			old_comp, new_comp, NULL);
3348 		if (obj_data && obj_data->full_object && !*new_comp) {
3349 			*new_comp = e_cal_component_clone (obj_data->full_object);
3350 		}
3351 	} else {
3352 		/* report as removal by keeping *new_component NULL */
3353 		if (obj_data->full_object) {
3354 			*old_comp = e_cal_component_clone (obj_data->full_object);
3355 		}
3356 		remove_component (cbfile, uid, obj_data);
3357 	}
3358 
3359 	g_free (rid);
3360 
3361 	return TRUE;
3362 }
3363 
3364 typedef struct {
3365 	GHashTable *zones;
3366 
3367 	gboolean found;
3368 } ECalBackendFileTzidData;
3369 
3370 static void
check_tzids(ICalParameter * param,gpointer data)3371 check_tzids (ICalParameter *param,
3372              gpointer data)
3373 {
3374 	ECalBackendFileTzidData *tzdata = data;
3375 	const gchar *tzid;
3376 
3377 	tzid = i_cal_parameter_get_tzid (param);
3378 	if (!tzid || g_hash_table_lookup (tzdata->zones, tzid))
3379 		tzdata->found = FALSE;
3380 }
3381 
3382 /* This function is largely duplicated in
3383  * ../groupwise/e-cal-backend-groupwise.c
3384  */
3385 static void
fetch_attachments(ECalBackendSync * backend,ECalComponent * comp)3386 fetch_attachments (ECalBackendSync *backend,
3387                    ECalComponent *comp)
3388 {
3389 	GSList *attach_list;
3390 	GSList *l;
3391 	gchar *dest_url, *dest_file;
3392 	gint fd, fileindex;
3393 	const gchar *uid;
3394 
3395 	attach_list = e_cal_component_get_attachments (comp);
3396 	uid = e_cal_component_get_uid (comp);
3397 
3398 	for (l = attach_list, fileindex = 0; l; l = l->next, fileindex++) {
3399 		ICalAttach *attach = l->data;
3400 		gchar *sfname;
3401 		gchar *filename;
3402 		GMappedFile *mapped_file;
3403 		GError *error = NULL;
3404 
3405 		if (!attach || !i_cal_attach_get_is_url (attach))
3406 			continue;
3407 
3408 		sfname = g_filename_from_uri (i_cal_attach_get_url (attach), NULL, NULL);
3409 		if (!sfname)
3410 			continue;
3411 
3412 		mapped_file = g_mapped_file_new (sfname, FALSE, &error);
3413 		if (!mapped_file) {
3414 			g_message (
3415 				"DEBUG: could not map %s: %s\n",
3416 				sfname, error ? error->message : "???");
3417 			g_error_free (error);
3418 			g_free (sfname);
3419 			continue;
3420 		}
3421 		filename = g_path_get_basename (sfname);
3422 		dest_file = e_cal_backend_create_cache_filename (E_CAL_BACKEND (backend), uid, filename, fileindex);
3423 		g_free (filename);
3424 		fd = g_open (dest_file, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600);
3425 		if (fd == -1) {
3426 			/* TODO handle error conditions */
3427 			g_message (
3428 				"DEBUG: could not open %s for writing\n",
3429 				dest_file);
3430 		} else if (write (fd, g_mapped_file_get_contents (mapped_file),
3431 				  g_mapped_file_get_length (mapped_file)) == -1) {
3432 			/* TODO handle error condition */
3433 			g_message ("DEBUG: attachment write failed.\n");
3434 		}
3435 
3436 		g_mapped_file_unref (mapped_file);
3437 
3438 		if (fd != -1)
3439 			close (fd);
3440 		dest_url = g_filename_to_uri (dest_file, NULL, NULL);
3441 		g_free (dest_file);
3442 
3443 		g_object_unref (attach);
3444 		l->data = i_cal_attach_new_from_url (dest_url);
3445 
3446 		g_free (dest_url);
3447 		g_free (sfname);
3448 	}
3449 
3450 	e_cal_component_set_attachments (comp, attach_list);
3451 
3452 	g_slist_free_full (attach_list, g_object_unref);
3453 }
3454 
3455 static gint
masters_first_cmp(gconstpointer ptr1,gconstpointer ptr2)3456 masters_first_cmp (gconstpointer ptr1,
3457 		   gconstpointer ptr2)
3458 {
3459 	ICalComponent *icomp1 = (ICalComponent *) ptr1;
3460 	ICalComponent *icomp2 = (ICalComponent *) ptr2;
3461 	gboolean has_rid1, has_rid2;
3462 
3463 	has_rid1 = (icomp1 && e_cal_util_component_has_property (icomp1, I_CAL_RECURRENCEID_PROPERTY)) ? 1 : 0;
3464 	has_rid2 = (icomp2 && e_cal_util_component_has_property (icomp2, I_CAL_RECURRENCEID_PROPERTY)) ? 1 : 0;
3465 
3466 	if (has_rid1 == has_rid2)
3467 		return g_strcmp0 (icomp1 ? i_cal_component_get_uid (icomp1) : NULL,
3468 				  icomp2 ? i_cal_component_get_uid (icomp2) : NULL);
3469 
3470 	if (has_rid1)
3471 		return 1;
3472 
3473 	return -1;
3474 }
3475 
3476 /* Update_objects handler for the file backend. */
3477 static void
e_cal_backend_file_receive_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * calobj,ECalOperationFlags opflags,GError ** error)3478 e_cal_backend_file_receive_objects (ECalBackendSync *backend,
3479                                     EDataCal *cal,
3480                                     GCancellable *cancellable,
3481                                     const gchar *calobj,
3482 				    ECalOperationFlags opflags,
3483                                     GError **error)
3484 {
3485 	ESourceRegistry *registry;
3486 	ECalBackendFile *cbfile;
3487 	ECalBackendFilePrivate *priv;
3488 	ECalClientTzlookupICalCompData *lookup_data = NULL;
3489 	ICalComponent *toplevel_comp, *icomp = NULL;
3490 	ICalComponentKind kind;
3491 	ICalPropertyMethod toplevel_method, method;
3492 	ICalComponent *subcomp;
3493 	GSList *comps = NULL, *del_comps = NULL, *link;
3494 	ECalComponent *comp;
3495 	ECalBackendFileTzidData tzdata;
3496 	GError *err = NULL;
3497 
3498 	cbfile = E_CAL_BACKEND_FILE (backend);
3499 	priv = cbfile->priv;
3500 
3501 	if (priv->vcalendar == NULL) {
3502 		g_set_error_literal (
3503 			error, E_CAL_CLIENT_ERROR,
3504 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR,
3505 			e_cal_client_error_to_string (
3506 			E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR));
3507 		return;
3508 	}
3509 
3510 	/* Pull the component from the string and ensure that it is sane */
3511 	toplevel_comp = i_cal_parser_parse_string (calobj);
3512 	if (!toplevel_comp) {
3513 		g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
3514 		return;
3515 	}
3516 
3517 	g_rec_mutex_lock (&priv->idle_save_rmutex);
3518 
3519 	registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
3520 
3521 	kind = i_cal_component_isa (toplevel_comp);
3522 	if (kind != I_CAL_VCALENDAR_COMPONENT) {
3523 		/* If it is not a VCALENDAR, make it one to simplify below */
3524 		icomp = toplevel_comp;
3525 		toplevel_comp = e_cal_util_new_top_level ();
3526 		if (i_cal_component_get_method (icomp) == I_CAL_METHOD_CANCEL)
3527 			i_cal_component_set_method (toplevel_comp, I_CAL_METHOD_CANCEL);
3528 		else
3529 			i_cal_component_set_method (toplevel_comp, I_CAL_METHOD_PUBLISH);
3530 		i_cal_component_take_component (toplevel_comp, icomp);
3531 	} else {
3532 		if (!e_cal_util_component_has_property (toplevel_comp, I_CAL_METHOD_PROPERTY))
3533 			i_cal_component_set_method (toplevel_comp, I_CAL_METHOD_PUBLISH);
3534 	}
3535 
3536 	toplevel_method = i_cal_component_get_method (toplevel_comp);
3537 
3538 	/* Build a list of timezones so we can make sure all the objects have valid info */
3539 	tzdata.zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
3540 
3541 	for (subcomp = i_cal_component_get_first_component (toplevel_comp, I_CAL_VTIMEZONE_COMPONENT);
3542 	     subcomp;
3543 	     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (toplevel_comp, I_CAL_VTIMEZONE_COMPONENT)) {
3544 		ICalTimezone *zone;
3545 
3546 		zone = i_cal_timezone_new ();
3547 		if (i_cal_timezone_set_component (zone, subcomp))
3548 			g_hash_table_insert (tzdata.zones, g_strdup (i_cal_timezone_get_tzid (zone)), NULL);
3549 		g_object_unref (zone);
3550 	}
3551 
3552 	/* First we make sure all the components are usuable */
3553 	kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
3554 
3555 	for (subcomp = i_cal_component_get_first_component (toplevel_comp, I_CAL_ANY_COMPONENT);
3556 	     subcomp;
3557 	     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (toplevel_comp, I_CAL_ANY_COMPONENT)) {
3558 		ICalComponentKind child_kind = i_cal_component_isa (subcomp);
3559 
3560 		if (child_kind != kind) {
3561 			/* remove the component from the toplevel VCALENDAR */
3562 			if (child_kind != I_CAL_VTIMEZONE_COMPONENT)
3563 				del_comps = g_slist_prepend (del_comps, g_object_ref (subcomp));
3564 			continue;
3565 		}
3566 
3567 		tzdata.found = TRUE;
3568 		i_cal_component_foreach_tzid (subcomp, check_tzids, &tzdata);
3569 
3570 		if (!tzdata.found) {
3571 			err = ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT);
3572 			g_object_unref (subcomp);
3573 			goto error;
3574 		}
3575 
3576 		if (!i_cal_component_get_uid (subcomp)) {
3577 			if (toplevel_method == I_CAL_METHOD_PUBLISH) {
3578 				gchar *new_uid = NULL;
3579 
3580 				new_uid = e_util_generate_uid ();
3581 				i_cal_component_set_uid (subcomp, new_uid);
3582 				g_free (new_uid);
3583 			} else {
3584 				err = ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT);
3585 				g_object_unref (subcomp);
3586 				goto error;
3587 			}
3588 
3589 		}
3590 
3591 		comps = g_slist_prepend (comps, g_object_ref (subcomp));
3592 	}
3593 
3594 	/* Now we remove the components we don't care about */
3595 	for (link = del_comps; link; link = g_slist_next (link)) {
3596 		subcomp = link->data;
3597 
3598 		i_cal_component_remove_component (toplevel_comp, subcomp);
3599 	}
3600 
3601 	g_slist_free_full (del_comps, g_object_unref);
3602 	del_comps = NULL;
3603 
3604 	lookup_data = e_cal_client_tzlookup_icalcomp_data_new (priv->vcalendar);
3605 
3606         /* check and patch timezones */
3607 	if (!e_cal_client_check_timezones_sync (toplevel_comp,
3608 			       NULL,
3609 			       e_cal_client_tzlookup_icalcomp_cb,
3610 			       lookup_data,
3611 			       NULL,
3612 			       &err)) {
3613 		/*
3614 		 * This makes assumptions about what kind of
3615 		 * errors can occur inside e_cal_check_timezones().
3616 		 * We control it, so that should be safe, but
3617 		 * is the code really identical with the calendar
3618 		 * status?
3619 		 */
3620 		goto error;
3621 	}
3622 
3623 	/* Merge the iCalendar components with our existing VCALENDAR,
3624 	 * resolving any conflicting TZIDs. It also frees the toplevel_comp. */
3625 	i_cal_component_merge_component (priv->vcalendar, toplevel_comp);
3626 	g_clear_object (&toplevel_comp);
3627 
3628 	/* Now we manipulate the components we care about */
3629 	comps = g_slist_sort (comps, masters_first_cmp);
3630 
3631 	for (link = comps; link; link = g_slist_next (link)) {
3632 		ECalComponent *old_component = NULL;
3633 		ECalComponent *new_component = NULL;
3634 		ECalObjModType mod = E_CAL_OBJ_MOD_THIS;
3635 		ICalTime *current;
3636 		const gchar *uid;
3637 		gchar *rid;
3638 		ECalBackendFileObject *obj_data;
3639 		gboolean is_declined;
3640 
3641 		subcomp = link->data;
3642 
3643 		/* Create the cal component */
3644 		comp = e_cal_component_new_from_icalcomponent (g_object_ref (subcomp));
3645 		if (!comp)
3646 			continue;
3647 
3648 		/* Set the created and last modified times on the component, if not there already */
3649 		current = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ());
3650 
3651 		if (!e_cal_util_component_has_property (subcomp, I_CAL_CREATED_PROPERTY)) {
3652 			/* Update both when CREATED is missing, to make sure the LAST-MODIFIED
3653 			   is not before CREATED */
3654 			e_cal_component_set_created (comp, current);
3655 			e_cal_component_set_last_modified (comp, current);
3656 		} else if (!e_cal_util_component_has_property (subcomp, I_CAL_LASTMODIFIED_PROPERTY)) {
3657 			e_cal_component_set_last_modified (comp, current);
3658 		}
3659 
3660 		g_clear_object (&current);
3661 
3662 		uid = e_cal_component_get_uid (comp);
3663 		rid = e_cal_component_get_recurid_as_string (comp);
3664 
3665 		if (rid) {
3666 			ECalComponentRange *range;
3667 
3668 			range = e_cal_component_get_recurid (comp);
3669 
3670 			if (range && e_cal_component_range_get_kind (range) == E_CAL_COMPONENT_RANGE_THISFUTURE)
3671 				mod = E_CAL_OBJ_MOD_THIS_AND_FUTURE;
3672 
3673 			e_cal_component_range_free (range);
3674 		}
3675 
3676 		if (e_cal_util_component_has_property (subcomp, I_CAL_METHOD_PROPERTY))
3677 			method = i_cal_component_get_method (subcomp);
3678 		else
3679 			method = toplevel_method;
3680 
3681 		switch (method) {
3682 		case I_CAL_METHOD_PUBLISH:
3683 		case I_CAL_METHOD_REQUEST:
3684 		case I_CAL_METHOD_REPLY:
3685 			is_declined = e_cal_backend_user_declined (registry, subcomp);
3686 
3687 			/* handle attachments */
3688 			if (!is_declined && e_cal_component_has_attachments (comp))
3689 				fetch_attachments (backend, comp);
3690 			obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
3691 			if (obj_data) {
3692 
3693 				if (rid) {
3694 					ECalComponent *ignore_comp = NULL;
3695 
3696 					remove_instance (
3697 						cbfile, obj_data, uid, rid, mod,
3698 						&old_component, &ignore_comp, NULL);
3699 
3700 					if (ignore_comp)
3701 						g_object_unref (ignore_comp);
3702 				} else {
3703 					if (obj_data->full_object) {
3704 						old_component = e_cal_component_clone (obj_data->full_object);
3705 					}
3706 					remove_component (cbfile, uid, obj_data);
3707 				}
3708 
3709 				if (!is_declined)
3710 					add_component (cbfile, comp, FALSE);
3711 
3712 				if (!is_declined)
3713 					e_cal_backend_notify_component_modified (E_CAL_BACKEND (backend),
3714 										 old_component, comp);
3715 				else {
3716 					ECalComponentId *id = e_cal_component_get_id (comp);
3717 
3718 					e_cal_backend_notify_component_removed (E_CAL_BACKEND (backend),
3719 										id, old_component,
3720 										rid ? comp : NULL);
3721 
3722 					e_cal_component_id_free (id);
3723 					g_object_unref (comp);
3724 				}
3725 
3726 				if (old_component)
3727 					g_object_unref (old_component);
3728 
3729 			} else if (!is_declined) {
3730 				add_component (cbfile, comp, FALSE);
3731 
3732 				e_cal_backend_notify_component_created (E_CAL_BACKEND (backend), comp);
3733 			} else {
3734 				g_object_unref (comp);
3735 			}
3736 			g_free (rid);
3737 			break;
3738 		case I_CAL_METHOD_ADD:
3739 			/* FIXME This should be doable once all the recurid stuff is done */
3740 			err = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Unsupported method"));
3741 			g_object_unref (comp);
3742 			g_free (rid);
3743 			goto error;
3744 			break;
3745 		case I_CAL_METHOD_COUNTER:
3746 			err = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Unsupported method"));
3747 			g_object_unref (comp);
3748 			g_free (rid);
3749 			goto error;
3750 			break;
3751 		case I_CAL_METHOD_DECLINECOUNTER:
3752 			err = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Unsupported method"));
3753 			g_object_unref (comp);
3754 			g_free (rid);
3755 			goto error;
3756 			break;
3757 		case I_CAL_METHOD_CANCEL:
3758 			if (cancel_received_object (cbfile, comp, mod, &old_component, &new_component)) {
3759 				ECalComponentId *id;
3760 
3761 				id = e_cal_component_get_id (comp);
3762 
3763 				e_cal_backend_notify_component_removed (E_CAL_BACKEND (backend),
3764 									id, old_component, new_component);
3765 
3766 				/* remove the component from the toplevel VCALENDAR */
3767 				i_cal_component_remove_component (priv->vcalendar, subcomp);
3768 				e_cal_component_id_free (id);
3769 
3770 				if (new_component)
3771 					g_object_unref (new_component);
3772 				if (old_component)
3773 					g_object_unref (old_component);
3774 			}
3775 			g_object_unref (comp);
3776 			g_free (rid);
3777 			break;
3778 		default:
3779 			err = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Unsupported method"));
3780 			g_object_unref (comp);
3781 			g_free (rid);
3782 			goto error;
3783 		}
3784 	}
3785 
3786 	save (cbfile, TRUE);
3787 
3788  error:
3789 	g_slist_free_full (del_comps, g_object_unref);
3790 	g_slist_free_full (comps, g_object_unref);
3791 
3792 	g_hash_table_destroy (tzdata.zones);
3793 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
3794 	e_cal_client_tzlookup_icalcomp_data_free (lookup_data);
3795 
3796 	if (err)
3797 		g_propagate_error (error, err);
3798 }
3799 
3800 static void
e_cal_backend_file_send_objects(ECalBackendSync * backend,EDataCal * cal,GCancellable * cancellable,const gchar * calobj,ECalOperationFlags opflags,GSList ** users,gchar ** modified_calobj,GError ** perror)3801 e_cal_backend_file_send_objects (ECalBackendSync *backend,
3802                                  EDataCal *cal,
3803                                  GCancellable *cancellable,
3804                                  const gchar *calobj,
3805 				 ECalOperationFlags opflags,
3806                                  GSList **users,
3807                                  gchar **modified_calobj,
3808                                  GError **perror)
3809 {
3810 	*users = NULL;
3811 	*modified_calobj = g_strdup (calobj);
3812 }
3813 
3814 static void
cal_backend_file_email_address_changed_cb(GObject * object,GParamSpec * param,gpointer user_data)3815 cal_backend_file_email_address_changed_cb (GObject *object,
3816 					   GParamSpec *param,
3817 					   gpointer user_data)
3818 {
3819 	ECalBackend *cal_backend = user_data;
3820 	gchar *email_address;
3821 
3822 	g_return_if_fail (E_IS_SOURCE_LOCAL (object));
3823 	g_return_if_fail (E_IS_CAL_BACKEND (cal_backend));
3824 
3825 	email_address = e_source_local_dup_email_address (E_SOURCE_LOCAL (object));
3826 
3827 	e_cal_backend_notify_property_changed (cal_backend, E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, email_address);
3828 	e_cal_backend_notify_property_changed (cal_backend, E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS, email_address);
3829 }
3830 
3831 static void
cal_backend_file_constructed(GObject * object)3832 cal_backend_file_constructed (GObject *object)
3833 {
3834 	ECalBackend *backend;
3835 	ESourceRegistry *registry;
3836 	ESource *builtin_source;
3837 	ESource *source;
3838 	ESourceLocal *local_extension;
3839 	ICalComponentKind kind;
3840 	const gchar *user_data_dir;
3841 	const gchar *component_type;
3842 	const gchar *uid;
3843 	gchar *filename;
3844 
3845 	user_data_dir = e_get_user_data_dir ();
3846 
3847 	/* Chain up to parent's constructed() method. */
3848 	G_OBJECT_CLASS (e_cal_backend_file_parent_class)->constructed (object);
3849 
3850 	/* Override the cache directory that the parent class just set. */
3851 
3852 	backend = E_CAL_BACKEND (object);
3853 	kind = e_cal_backend_get_kind (backend);
3854 	source = e_backend_get_source (E_BACKEND (backend));
3855 	registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
3856 
3857 	uid = e_source_get_uid (source);
3858 	g_return_if_fail (uid != NULL);
3859 
3860 	switch (kind) {
3861 		case I_CAL_VEVENT_COMPONENT:
3862 			component_type = "calendar";
3863 			builtin_source = e_source_registry_ref_builtin_calendar (registry);
3864 			break;
3865 		case I_CAL_VTODO_COMPONENT:
3866 			component_type = "tasks";
3867 			builtin_source = e_source_registry_ref_builtin_task_list (registry);
3868 			break;
3869 		case I_CAL_VJOURNAL_COMPONENT:
3870 			component_type = "memos";
3871 			builtin_source = e_source_registry_ref_builtin_memo_list (registry);
3872 			break;
3873 		default:
3874 			g_warn_if_reached ();
3875 			component_type = "calendar";
3876 			builtin_source = e_source_registry_ref_builtin_calendar (registry);
3877 			break;
3878 	}
3879 
3880 	/* XXX Backward-compatibility hack:
3881 	 *
3882 	 * The special built-in "Personal" data source UIDs are now named
3883 	 * "system-$COMPONENT" but since the data directories are already
3884 	 * split out by component, we'll continue to use the old "system"
3885 	 * directories for these particular data sources. */
3886 	if (e_source_equal (source, builtin_source))
3887 		uid = "system";
3888 
3889 	filename = g_build_filename (user_data_dir, component_type, uid, NULL);
3890 	e_cal_backend_set_cache_dir (backend, filename);
3891 	g_free (filename);
3892 
3893 	g_object_unref (builtin_source);
3894 
3895 	local_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_LOCAL_BACKEND);
3896 
3897 	g_signal_connect_object (local_extension, "notify::email-address",
3898 		G_CALLBACK (cal_backend_file_email_address_changed_cb), backend, 0);
3899 }
3900 
3901 static void
cal_backend_file_add_cached_timezone(ETimezoneCache * cache,ICalTimezone * zone)3902 cal_backend_file_add_cached_timezone (ETimezoneCache *cache,
3903                                       ICalTimezone *zone)
3904 {
3905 	ECalBackendFilePrivate *priv;
3906 	const gchar *tzid;
3907 	gboolean timezone_added = FALSE;
3908 
3909 	priv = E_CAL_BACKEND_FILE (cache)->priv;
3910 
3911 	g_rec_mutex_lock (&priv->idle_save_rmutex);
3912 
3913 	tzid = i_cal_timezone_get_tzid (zone);
3914 	if (!i_cal_component_get_timezone (priv->vcalendar, tzid)) {
3915 		ICalComponent *tz_comp;
3916 
3917 		tz_comp = i_cal_timezone_get_component (zone);
3918 
3919 		i_cal_component_take_component (priv->vcalendar, i_cal_component_clone (tz_comp));
3920 
3921 		g_clear_object (&tz_comp);
3922 
3923 		timezone_added = TRUE;
3924 		save (E_CAL_BACKEND_FILE (cache), TRUE);
3925 	}
3926 
3927 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
3928 
3929 	/* Emit the signal outside of the mutex. */
3930 	if (timezone_added)
3931 		g_signal_emit_by_name (cache, "timezone-added", zone);
3932 }
3933 
3934 static ICalTimezone *
cal_backend_file_get_cached_timezone(ETimezoneCache * cache,const gchar * tzid)3935 cal_backend_file_get_cached_timezone (ETimezoneCache *cache,
3936                                       const gchar *tzid)
3937 {
3938 	ECalBackendFilePrivate *priv;
3939 	ICalTimezone *zone;
3940 
3941 	priv = E_CAL_BACKEND_FILE (cache)->priv;
3942 
3943 	g_rec_mutex_lock (&priv->idle_save_rmutex);
3944 	zone = g_hash_table_lookup (priv->cached_timezones, tzid);
3945 	if (!zone) {
3946 		zone = i_cal_component_get_timezone (priv->vcalendar, tzid);
3947 		if (zone)
3948 			g_hash_table_insert (priv->cached_timezones, g_strdup (tzid), zone);
3949 	}
3950 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
3951 
3952 	if (zone != NULL)
3953 		return zone;
3954 
3955 	/* Chain up and let ECalBackend try to match
3956 	 * the TZID against a built-in ICalTimezone. */
3957 	return parent_timezone_cache_interface->tzcache_get_timezone (cache, tzid);
3958 }
3959 
3960 static GList *
cal_backend_file_list_cached_timezones(ETimezoneCache * cache)3961 cal_backend_file_list_cached_timezones (ETimezoneCache *cache)
3962 {
3963 	/* XXX As of 3.7, the only e_timezone_cache_list_timezones()
3964 	 *     call comes from ECalBackendStore, which this backend
3965 	 *     does not use.  So we should never get here.  Emit a
3966 	 *     runtime warning so we know if this changes. */
3967 
3968 	g_return_val_if_reached (NULL);
3969 }
3970 
3971 static void
e_cal_backend_file_class_init(ECalBackendFileClass * class)3972 e_cal_backend_file_class_init (ECalBackendFileClass *class)
3973 {
3974 	GObjectClass *object_class;
3975 	ECalBackendClass *backend_class;
3976 	ECalBackendSyncClass *sync_class;
3977 
3978 	object_class = (GObjectClass *) class;
3979 	backend_class = (ECalBackendClass *) class;
3980 	sync_class = (ECalBackendSyncClass *) class;
3981 
3982 	object_class->dispose = e_cal_backend_file_dispose;
3983 	object_class->finalize = e_cal_backend_file_finalize;
3984 	object_class->constructed = cal_backend_file_constructed;
3985 
3986 	backend_class->impl_get_backend_property = e_cal_backend_file_get_backend_property;
3987 	backend_class->impl_start_view = e_cal_backend_file_start_view;
3988 
3989 	sync_class->open_sync = e_cal_backend_file_open;
3990 	sync_class->create_objects_sync = e_cal_backend_file_create_objects;
3991 	sync_class->modify_objects_sync = e_cal_backend_file_modify_objects;
3992 	sync_class->remove_objects_sync = e_cal_backend_file_remove_objects;
3993 	sync_class->receive_objects_sync = e_cal_backend_file_receive_objects;
3994 	sync_class->send_objects_sync = e_cal_backend_file_send_objects;
3995 	sync_class->get_object_sync = e_cal_backend_file_get_object;
3996 	sync_class->get_object_list_sync = e_cal_backend_file_get_object_list;
3997 	sync_class->get_attachment_uris_sync = e_cal_backend_file_get_attachment_uris;
3998 	sync_class->add_timezone_sync = e_cal_backend_file_add_timezone;
3999 	sync_class->get_free_busy_sync = e_cal_backend_file_get_free_busy;
4000 	sync_class->discard_alarm_sync = e_cal_backend_file_discard_alarm_sync;
4001 
4002 	/* Register our ESource extension. */
4003 	E_TYPE_SOURCE_LOCAL;
4004 }
4005 
4006 static void
e_cal_backend_file_timezone_cache_init(ETimezoneCacheInterface * iface)4007 e_cal_backend_file_timezone_cache_init (ETimezoneCacheInterface *iface)
4008 {
4009 	parent_timezone_cache_interface = g_type_interface_peek_parent (iface);
4010 
4011 	iface->tzcache_add_timezone = cal_backend_file_add_cached_timezone;
4012 	iface->tzcache_get_timezone = cal_backend_file_get_cached_timezone;
4013 	iface->tzcache_list_timezones = cal_backend_file_list_cached_timezones;
4014 }
4015 
4016 static void
e_cal_backend_file_init(ECalBackendFile * cbfile)4017 e_cal_backend_file_init (ECalBackendFile *cbfile)
4018 {
4019 	cbfile->priv = e_cal_backend_file_get_instance_private (cbfile);
4020 
4021 	cbfile->priv->file_name = g_strdup ("calendar.ics");
4022 
4023 	g_rec_mutex_init (&cbfile->priv->idle_save_rmutex);
4024 
4025 	g_mutex_init (&cbfile->priv->refresh_lock);
4026 
4027 	cbfile->priv->cached_timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
4028 }
4029 
4030 void
e_cal_backend_file_set_file_name(ECalBackendFile * cbfile,const gchar * file_name)4031 e_cal_backend_file_set_file_name (ECalBackendFile *cbfile,
4032                                   const gchar *file_name)
4033 {
4034 	ECalBackendFilePrivate *priv;
4035 
4036 	g_return_if_fail (cbfile != NULL);
4037 	g_return_if_fail (E_IS_CAL_BACKEND_FILE (cbfile));
4038 	g_return_if_fail (file_name != NULL);
4039 
4040 	priv = cbfile->priv;
4041 	g_rec_mutex_lock (&priv->idle_save_rmutex);
4042 
4043 	g_free (priv->file_name);
4044 	priv->file_name = g_strdup (file_name);
4045 
4046 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
4047 }
4048 
4049 const gchar *
e_cal_backend_file_get_file_name(ECalBackendFile * cbfile)4050 e_cal_backend_file_get_file_name (ECalBackendFile *cbfile)
4051 {
4052 	ECalBackendFilePrivate *priv;
4053 
4054 	g_return_val_if_fail (cbfile != NULL, NULL);
4055 	g_return_val_if_fail (E_IS_CAL_BACKEND_FILE (cbfile), NULL);
4056 
4057 	priv = cbfile->priv;
4058 
4059 	return priv->file_name;
4060 }
4061 
4062 void
e_cal_backend_file_reload(ECalBackendFile * cbfile,GError ** perror)4063 e_cal_backend_file_reload (ECalBackendFile *cbfile,
4064                            GError **perror)
4065 {
4066 	ECalBackendFilePrivate *priv;
4067 	gchar *str_uri;
4068 	gboolean writable = FALSE;
4069 	GError *err = NULL;
4070 
4071 	priv = cbfile->priv;
4072 	g_rec_mutex_lock (&priv->idle_save_rmutex);
4073 
4074 	str_uri = get_uri_string (E_CAL_BACKEND (cbfile));
4075 	if (!str_uri) {
4076 		err = EC_ERROR_NO_URI ();
4077 		goto done;
4078 	}
4079 
4080 	writable = e_cal_backend_get_writable (E_CAL_BACKEND (cbfile));
4081 
4082 	if (g_access (str_uri, R_OK) == 0) {
4083 		reload_cal (cbfile, str_uri, &err);
4084 		if (g_access (str_uri, W_OK) != 0)
4085 			writable = FALSE;
4086 	} else {
4087 		err = ECC_ERROR (E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR);
4088 	}
4089 
4090 	g_free (str_uri);
4091 
4092 	if (!err && writable) {
4093 		if (!get_source_writable (E_BACKEND (cbfile)))
4094 			writable = FALSE;
4095 	}
4096   done:
4097 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
4098 	e_cal_backend_set_writable (E_CAL_BACKEND (cbfile), writable);
4099 
4100 	if (err)
4101 		g_propagate_error (perror, err);
4102 }
4103 
4104 #ifdef TEST_QUERY_RESULT
4105 
4106 static void
test_query_by_scanning_all_objects(ECalBackendFile * cbfile,const gchar * sexp,GSList ** objects)4107 test_query_by_scanning_all_objects (ECalBackendFile *cbfile,
4108                                     const gchar *sexp,
4109                                     GSList **objects)
4110 {
4111 	MatchObjectData match_data;
4112 	ECalBackendFilePrivate *priv;
4113 
4114 	priv = cbfile->priv;
4115 
4116 	match_data.search_needed = TRUE;
4117 	match_data.query = sexp;
4118 	match_data.comps_list = NULL;
4119 	match_data.as_string = TRUE;
4120 	match_data.backend = E_CAL_BACKEND (cbfile);
4121 
4122 	if (sexp && !strcmp (sexp, "#t"))
4123 		match_data.search_needed = FALSE;
4124 
4125 	match_data.obj_sexp = e_cal_backend_sexp_new (sexp);
4126 	if (!match_data.obj_sexp)
4127 		return;
4128 
4129 	g_rec_mutex_lock (&priv->idle_save_rmutex);
4130 
4131 	if (!match_data.obj_sexp)
4132 	{
4133 		g_message (G_STRLOC ": Getting object list (%s)", sexp);
4134 		exit (-1);
4135 	}
4136 
4137 	g_hash_table_foreach (priv->comp_uid_hash, (GHFunc) match_object_sexp,
4138 			&match_data);
4139 
4140 	g_rec_mutex_unlock (&priv->idle_save_rmutex);
4141 
4142 	*objects = g_slist_reverse (match_data.comps_list);
4143 
4144 	g_object_unref (match_data.obj_sexp);
4145 }
4146 
4147 static void
write_list(GSList * list)4148 write_list (GSList *list)
4149 {
4150 	GSList *l;
4151 
4152 	for (l = list; l; l = l->next)
4153 	{
4154 		const gchar *str = l->data;
4155 		ECalComponent *comp = e_cal_component_new_from_string (str);
4156 		const gchar *uid;
4157 		uid = e_cal_component_get_uid (comp);
4158 		g_print ("%s\n", uid);
4159 	}
4160 }
4161 
4162 static void
get_difference_of_lists(ECalBackendFile * cbfile,GSList * smaller,GSList * bigger)4163 get_difference_of_lists (ECalBackendFile *cbfile,
4164                          GSList *smaller,
4165                          GSList *bigger)
4166 {
4167 	GSList *l, *lsmaller;
4168 
4169 	for (l = bigger; l; l = l->next) {
4170 		gchar *str = l->data;
4171 		const gchar *uid;
4172 		ECalComponent *comp = e_cal_component_new_from_string (str);
4173 		gboolean found = FALSE;
4174 		uid = e_cal_component_get_uid (comp);
4175 
4176 		for (lsmaller = smaller; lsmaller && !found; lsmaller = lsmaller->next)
4177 		{
4178 			gchar *strsmaller = lsmaller->data;
4179 			const gchar *uidsmaller;
4180 			ECalComponent *compsmaller = e_cal_component_new_from_string (strsmaller);
4181 			uidsmaller = e_cal_component_get_uid (compsmaller);
4182 
4183 			found = strcmp (uid, uidsmaller) == 0;
4184 
4185 			g_object_unref (compsmaller);
4186 		}
4187 
4188 		if (!found)
4189 		{
4190 			time_t time_start, time_end;
4191 			ResolveTzidData rtd;
4192 			printf ("%s IS MISSING\n", uid);
4193 
4194 			resolve_tzid_data_init (&rtd, cbfile->priv->vcalendar);
4195 
4196 			e_cal_util_get_component_occur_times (
4197 				comp, &time_start, &time_end,
4198 				resolve_tzid_cb, &rtd,
4199 				i_cal_timezone_get_utc_timezone (),
4200 				e_cal_backend_get_kind (E_CAL_BACKEND (cbfile)));
4201 
4202 			resolve_tzid_data_clear (&rtd);
4203 
4204 			d (printf ("start %s\n", asctime (gmtime (&time_start))));
4205 			d (printf ("end %s\n", asctime (gmtime (&time_end))));
4206 		}
4207 
4208 		g_object_unref (comp);
4209 	}
4210 }
4211 
4212 static void
test_query(ECalBackendFile * cbfile,const gchar * query)4213 test_query (ECalBackendFile *cbfile,
4214             const gchar *query)
4215 {
4216 	GSList *objects = NULL, *all_objects = NULL;
4217 
4218 	g_return_if_fail (query != NULL);
4219 
4220 	d (g_print ("Query %s\n", query));
4221 
4222 	test_query_by_scanning_all_objects (cbfile, query, &all_objects);
4223 	e_cal_backend_file_get_object_list (E_CAL_BACKEND_SYNC (cbfile), NULL, NULL, query, &objects, NULL);
4224 	if (objects == NULL)
4225 	{
4226 		g_message (G_STRLOC " failed to get objects\n");
4227 		exit (0);
4228 	}
4229 
4230 	if (g_slist_length (objects) < g_slist_length (all_objects) )
4231 	{
4232 		g_print ("ERROR\n");
4233 		get_difference_of_lists (cbfile, objects, all_objects);
4234 		exit (-1);
4235 	}
4236 	else if (g_slist_length (objects) > g_slist_length (all_objects) )
4237 	{
4238 		g_print ("ERROR\n");
4239 		write_list (all_objects);
4240 		get_difference_of_lists (cbfile, all_objects, objects);
4241 		exit (-1);
4242 	}
4243 
4244 	g_slist_foreach (objects, (GFunc) g_free, NULL);
4245 	g_slist_free (objects);
4246 	g_slist_foreach (all_objects, (GFunc) g_free, NULL);
4247 	g_slist_free (all_objects);
4248 }
4249 
4250 static void
execute_query(ECalBackendFile * cbfile,const gchar * query)4251 execute_query (ECalBackendFile *cbfile,
4252                const gchar *query)
4253 {
4254 	GSList *objects = NULL;
4255 
4256 	g_return_if_fail (query != NULL);
4257 
4258 	d (g_print ("Query %s\n", query));
4259 	e_cal_backend_file_get_object_list (E_CAL_BACKEND_SYNC (cbfile), NULL, NULL, query, &objects, NULL);
4260 	if (objects == NULL)
4261 	{
4262 		g_message (G_STRLOC " failed to get objects\n");
4263 		exit (0);
4264 	}
4265 
4266 	g_slist_foreach (objects, (GFunc) g_free, NULL);
4267 	g_slist_free (objects);
4268 }
4269 
4270 static gchar *fname = NULL;
4271 static gboolean only_execute = FALSE;
4272 static gchar *calendar_fname = NULL;
4273 
4274 static GOptionEntry entries[] =
4275 {
4276   { "test-file", 't', 0, G_OPTION_ARG_STRING, &fname, "File with prepared queries", NULL },
4277   { "only-execute", 'e', 0, G_OPTION_ARG_NONE, &only_execute, "Only execute, do not test query", NULL },
4278   { "calendar-file", 'c', 0, G_OPTION_ARG_STRING, &calendar_fname, "Path to the calendar.ics file", NULL },
4279   { NULL }
4280 };
4281 
4282 /* Always add at least this many bytes when extending the buffer.  */
4283 #define MIN_CHUNK 64
4284 
4285 static gint
private_getline(gchar ** lineptr,gsize * n,FILE * stream)4286 private_getline (gchar **lineptr,
4287                  gsize *n,
4288                  FILE *stream)
4289 {
4290 	gint nchars_avail;
4291 	gchar *read_pos;
4292 
4293 	if (!lineptr || !n || !stream)
4294 		return -1;
4295 
4296 	if (!*lineptr) {
4297 		*n = MIN_CHUNK;
4298 		*lineptr = (char *)malloc (*n);
4299 		if (!*lineptr)
4300 			return -1;
4301 	}
4302 
4303 	nchars_avail = (gint) *n;
4304 	read_pos = *lineptr;
4305 
4306 	for (;;) {
4307 		gint c = getc (stream);
4308 
4309 		if (nchars_avail < 2) {
4310 			if (*n > MIN_CHUNK)
4311 				*n *= 2;
4312 			else
4313 				*n += MIN_CHUNK;
4314 
4315 			nchars_avail = (gint)(*n + *lineptr - read_pos);
4316 			*lineptr = (char *)realloc (*lineptr, *n);
4317 			if (!*lineptr)
4318 				return -1;
4319 			read_pos = *n - nchars_avail + *lineptr;
4320 		}
4321 
4322 		if (ferror (stream) || c == EOF) {
4323 			if (read_pos == *lineptr)
4324 				return -1;
4325 			else
4326 				break;
4327 		}
4328 
4329 		*read_pos++ = c;
4330 		nchars_avail--;
4331 
4332 		if (c == '\n')
4333 			/* Return the line.  */
4334 			break;
4335 	}
4336 
4337 	*read_pos = '\0';
4338 
4339 	return (gint)(read_pos - (*lineptr));
4340 }
4341 
4342 gint
main(gint argc,gchar ** argv)4343 main (gint argc,
4344       gchar **argv)
4345 {
4346 	gchar * line = NULL;
4347 	gsize len = 0;
4348 	ECalBackendFile * cbfile;
4349 	gint num = 0;
4350 	GError *error = NULL;
4351 	GOptionContext *context;
4352 	FILE * fin = NULL;
4353 
4354 	context = g_option_context_new ("- test utility for e-d-s file backend");
4355 	g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
4356 	if (!g_option_context_parse (context, &argc, &argv, &error))
4357 	{
4358 		g_print ("option parsing failed: %s\n", error->message);
4359 		exit (1);
4360 	}
4361 
4362 	calendar_fname = g_strdup ("calendar.ics");
4363 
4364 	if (!calendar_fname)
4365 	{
4366 		g_message (G_STRLOC " Please, use -c parameter");
4367 		exit (-1);
4368 	}
4369 
4370 	cbfile = g_object_new (E_TYPE_CAL_BACKEND_FILE, NULL);
4371 	open_cal (cbfile, calendar_fname, &error);
4372 	if (error != NULL) {
4373 		g_message (G_STRLOC " Could not open calendar %s: %s", calendar_fname, error->message);
4374 		exit (-1);
4375 	}
4376 
4377 	if (fname)
4378 	{
4379 		fin = fopen (fname, "r");
4380 
4381 		if (!fin)
4382 		{
4383 			g_message (G_STRLOC " Could not open file %s", fname);
4384 			goto err0;
4385 		}
4386 	}
4387 	else
4388 	{
4389 		g_message (G_STRLOC " Reading from stdin");
4390 		fin = stdin;
4391 	}
4392 
4393 	while (private_getline (&line, &len, fin) != -1) {
4394 		g_print ("Query %d: %s", num++, line);
4395 
4396 		if (only_execute)
4397 			execute_query (cbfile, line);
4398 		else
4399 			test_query (cbfile, line);
4400 	}
4401 
4402 	if (line)
4403 		free (line);
4404 
4405 	if (fname)
4406 		fclose (fin);
4407 
4408 err0:
4409 	g_object_unref (cbfile);
4410 
4411 	return 0;
4412 }
4413 #endif
4414