1 /*
2  * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authors: Milan Crha <mcrha@redhat.com>
17  */
18 
19 #include "evolution-config.h"
20 
21 #include <glib.h>
22 #include <glib/gi18n-lib.h>
23 
24 #include <e-util/e-util.h>
25 #include <shell/e-shell-view.h>
26 
27 #include "e-comp-editor.h"
28 #include "e-comp-editor-event.h"
29 #include "e-comp-editor-memo.h"
30 #include "e-comp-editor-task.h"
31 #include "e-cal-dialogs.h"
32 #include "calendar-config.h"
33 #include "comp-util.h"
34 #include "itip-utils.h"
35 
36 #include "e-cal-data-model.h"
37 
38 #include "e-cal-ops.h"
39 
40 static void
cal_ops_manage_send_component(ECalModel * model,ECalClient * client,ICalComponent * icomp,ECalObjModType mod,ECalOpsSendFlags send_flags)41 cal_ops_manage_send_component (ECalModel *model,
42 			       ECalClient *client,
43 			       ICalComponent *icomp,
44 			       ECalObjModType mod,
45 			       ECalOpsSendFlags send_flags)
46 {
47 	ECalComponent *comp;
48 	ESourceRegistry *registry;
49 
50 	g_return_if_fail (E_IS_CAL_MODEL (model));
51 	g_return_if_fail (E_IS_CAL_CLIENT (client));
52 	g_return_if_fail (I_CAL_IS_COMPONENT (icomp));
53 
54 	if ((send_flags & E_CAL_OPS_SEND_FLAG_DONT_SEND) != 0)
55 		return;
56 
57 	comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
58 	if (!comp)
59 		return;
60 
61 	registry = e_cal_model_get_registry (model);
62 
63 	if (itip_organizer_is_user (registry, comp, client)) {
64 		gboolean strip_alarms = (send_flags & E_CAL_OPS_SEND_FLAG_STRIP_ALARMS) != 0;
65 		gboolean only_new_attendees = (send_flags & E_CAL_OPS_SEND_FLAG_ONLY_NEW_ATTENDEES) != 0;
66 		gboolean can_send = (send_flags & E_CAL_OPS_SEND_FLAG_SEND) != 0;
67 
68 		if (!can_send) /* E_CAL_OPS_SEND_FLAG_ASK */
69 			can_send = e_cal_dialogs_send_component (NULL, client, comp,
70 				(send_flags & E_CAL_OPS_SEND_FLAG_IS_NEW_COMPONENT) != 0,
71 				&strip_alarms, &only_new_attendees);
72 
73 		if (can_send)
74 			itip_send_component_with_model (model, I_CAL_METHOD_REQUEST, comp, client,
75 				NULL, NULL, NULL,
76 				(strip_alarms ? E_ITIP_SEND_COMPONENT_FLAG_STRIP_ALARMS : 0) |
77 				(only_new_attendees ? E_ITIP_SEND_COMPONENT_FLAG_ONLY_NEW_ATTENDEES : 0) |
78 				(mod == E_CAL_OBJ_MOD_ALL ? E_ITIP_SEND_COMPONENT_FLAG_ENSURE_MASTER_OBJECT : 0));
79 	}
80 
81 	g_clear_object (&comp);
82 }
83 
84 typedef struct {
85 	ECalModel *model;
86 	ECalClient *client;
87 	ICalComponent *icomp;
88 	ECalObjModType mod;
89 	gchar *uid;
90 	gchar *rid;
91 	gboolean check_detached_instance;
92 	ECalOpsCreateComponentFunc create_cb;
93 	ECalOpsGetDefaultComponentFunc get_default_comp_cb;
94 	gboolean all_day_default_comp;
95 	gchar *for_client_uid;
96 	gboolean is_modify;
97 	ECalOpsSendFlags send_flags;
98 	gpointer user_data;
99 	GDestroyNotify user_data_free;
100 	gboolean success;
101 } BasicOperationData;
102 
103 static BasicOperationData *
basic_operation_data_new(void)104 basic_operation_data_new (void)
105 {
106 	return g_slice_new0 (BasicOperationData);
107 }
108 
109 static void
basic_operation_data_free(gpointer ptr)110 basic_operation_data_free (gpointer ptr)
111 {
112 	BasicOperationData *bod = ptr;
113 
114 	if (bod) {
115 		if (bod->success) {
116 			if (bod->create_cb && bod->uid && bod->icomp) {
117 				bod->create_cb (bod->model, bod->client, bod->icomp, bod->uid, bod->user_data);
118 				if (bod->user_data_free)
119 					bod->user_data_free (bod->user_data);
120 			}
121 
122 			if (bod->is_modify && bod->icomp && (bod->send_flags & E_CAL_OPS_SEND_FLAG_DONT_SEND) == 0) {
123 				cal_ops_manage_send_component (bod->model, bod->client, bod->icomp, bod->mod, bod->send_flags);
124 			}
125 
126 			if (bod->get_default_comp_cb && bod->icomp) {
127 				bod->get_default_comp_cb (bod->model, bod->client, bod->icomp, bod->user_data);
128 				if (bod->user_data_free)
129 					bod->user_data_free (bod->user_data);
130 			}
131 		}
132 
133 		g_clear_object (&bod->model);
134 		g_clear_object (&bod->client);
135 		g_clear_object (&bod->icomp);
136 		g_free (bod->for_client_uid);
137 		g_free (bod->uid);
138 		g_free (bod->rid);
139 		g_slice_free (BasicOperationData, bod);
140 	}
141 }
142 
143 static void
cal_ops_create_component_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)144 cal_ops_create_component_thread (EAlertSinkThreadJobData *job_data,
145 				 gpointer user_data,
146 				 GCancellable *cancellable,
147 				 GError **error)
148 {
149 	BasicOperationData *bod = user_data;
150 
151 	g_return_if_fail (bod != NULL);
152 
153 	bod->success = e_cal_client_create_object_sync (bod->client, bod->icomp, E_CAL_OPERATION_FLAG_NONE, &bod->uid, cancellable, error);
154 }
155 
156 /**
157  * e_cal_ops_create_component:
158  * @model: an #ECalModel
159  * @client: an #ECalClient
160  * @icomp: an #ICalComponent
161  * @callback: (allow none): a callback to be called on success
162  * @user_data: user data to be passed to @callback; ignored when @callback is #NULL
163  * @user_data_free: a function to free @user_data; ignored when @callback is #NULL
164  *
165  * Creates a new @icomp in the @client. The @callback, if not #NULL,
166  * is called with a new uid of the @icomp on sucessful component save.
167  * The @callback is called in the main thread.
168  *
169  * Since: 3.16
170  **/
171 void
e_cal_ops_create_component(ECalModel * model,ECalClient * client,ICalComponent * icomp,ECalOpsCreateComponentFunc callback,gpointer user_data,GDestroyNotify user_data_free)172 e_cal_ops_create_component (ECalModel *model,
173 			    ECalClient *client,
174 			    ICalComponent *icomp,
175 			    ECalOpsCreateComponentFunc callback,
176 			    gpointer user_data,
177 			    GDestroyNotify user_data_free)
178 {
179 	ECalDataModel *data_model;
180 	ESource *source;
181 	ICalProperty *prop;
182 	const gchar *description;
183 	const gchar *alert_ident;
184 	gchar *display_name;
185 	BasicOperationData *bod;
186 	GCancellable *cancellable;
187 
188 	g_return_if_fail (E_IS_CAL_MODEL (model));
189 	g_return_if_fail (E_IS_CAL_CLIENT (client));
190 	g_return_if_fail (I_CAL_IS_COMPONENT (icomp));
191 
192 	switch (e_cal_client_get_source_type (client)) {
193 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
194 			description = _("Creating an event");
195 			alert_ident = "calendar:failed-create-event";
196 			break;
197 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
198 			description = _("Creating a memo");
199 			alert_ident = "calendar:failed-create-memo";
200 			break;
201 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
202 			description = _("Creating a task");
203 			alert_ident = "calendar:failed-create-task";
204 			break;
205 		default:
206 			g_warn_if_reached ();
207 			return;
208 	}
209 
210 	data_model = e_cal_model_get_data_model (model);
211 	source = e_client_get_source (E_CLIENT (client));
212 
213 	bod = basic_operation_data_new ();
214 	bod->model = g_object_ref (model);
215 	bod->client = g_object_ref (client);
216 	bod->icomp = i_cal_component_clone (icomp);
217 	bod->create_cb = callback;
218 	bod->user_data = user_data;
219 	bod->user_data_free = user_data_free;
220 
221 	cal_comp_util_maybe_ensure_allday_timezone_properties (client, bod->icomp,
222 		e_cal_model_get_timezone (model));
223 
224 	prop = i_cal_component_get_first_property (bod->icomp, I_CAL_CLASS_PROPERTY);
225 	if (!prop || i_cal_property_get_class (prop) == I_CAL_CLASS_NONE) {
226 		ICalProperty_Class ical_class = I_CAL_CLASS_PUBLIC;
227 		GSettings *settings;
228 
229 		settings = e_util_ref_settings ("org.gnome.evolution.calendar");
230 		if (g_settings_get_boolean (settings, "classify-private"))
231 			ical_class = I_CAL_CLASS_PRIVATE;
232 		g_object_unref (settings);
233 
234 		if (!prop) {
235 			prop = i_cal_property_new_class (ical_class);
236 			i_cal_component_add_property (bod->icomp, prop);
237 		} else
238 			i_cal_property_set_class (prop, ical_class);
239 	}
240 	g_clear_object (&prop);
241 
242 	display_name = e_util_get_source_full_name (e_cal_model_get_registry (model), source);
243 	cancellable = e_cal_data_model_submit_thread_job (data_model, description, alert_ident,
244 		display_name, cal_ops_create_component_thread,
245 		bod, basic_operation_data_free);
246 
247 	g_clear_object (&cancellable);
248 	g_free (display_name);
249 }
250 
251 static void
cal_ops_modify_component_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)252 cal_ops_modify_component_thread (EAlertSinkThreadJobData *job_data,
253 				 gpointer user_data,
254 				 GCancellable *cancellable,
255 				 GError **error)
256 {
257 	BasicOperationData *bod = user_data;
258 
259 	g_return_if_fail (bod != NULL);
260 
261 	if (bod->mod == E_CAL_OBJ_MOD_ALL) {
262 		ECalComponent *comp;
263 
264 		comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (bod->icomp));
265 		if (comp && e_cal_component_has_recurrences (comp)) {
266 			if (!comp_util_sanitize_recurrence_master_sync (comp, bod->client, cancellable, error)) {
267 				g_object_unref (comp);
268 				return;
269 			}
270 
271 			g_clear_object (&bod->icomp);
272 			bod->icomp = i_cal_component_clone (e_cal_component_get_icalcomponent (comp));
273 		}
274 
275 		g_clear_object (&comp);
276 	}
277 
278 	bod->success = e_cal_client_modify_object_sync (bod->client, bod->icomp, bod->mod, E_CAL_OPERATION_FLAG_NONE, cancellable, error);
279 }
280 
281 /**
282  * e_cal_ops_modify_component:
283  * @model: an #ECalModel
284  * @client: an #ECalClient
285  * @icomp: an #ICalComponent
286  * @mod: a mode to use for modification of the component
287  * @send_flags: what to do when the modify succeeded and the component has attendees
288  *
289  * Saves changes of the @icomp into the @client using the @mod. The @send_flags influences
290  * what to do when the @icomp has attendees and the organizer is user. Only one of
291  * #E_CAL_OPS_SEND_FLAG_ASK, #E_CAL_OPS_SEND_FLAG_SEND, #E_CAL_OPS_SEND_FLAG_DONT_SEND
292  * can be used, while the ASK flag is the default.
293  *
294  * Since: 3.16
295  **/
296 void
e_cal_ops_modify_component(ECalModel * model,ECalClient * client,ICalComponent * icomp,ECalObjModType mod,ECalOpsSendFlags send_flags)297 e_cal_ops_modify_component (ECalModel *model,
298 			    ECalClient *client,
299 			    ICalComponent *icomp,
300 			    ECalObjModType mod,
301 			    ECalOpsSendFlags send_flags)
302 {
303 	ECalDataModel *data_model;
304 	ESource *source;
305 	const gchar *description;
306 	const gchar *alert_ident;
307 	gchar *display_name;
308 	BasicOperationData *bod;
309 	GCancellable *cancellable;
310 
311 	g_return_if_fail (E_IS_CAL_MODEL (model));
312 	g_return_if_fail (E_IS_CAL_CLIENT (client));
313 	g_return_if_fail (I_CAL_IS_COMPONENT (icomp));
314 
315 	switch (e_cal_client_get_source_type (client)) {
316 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
317 			description = _("Modifying an event");
318 			alert_ident = "calendar:failed-modify-event";
319 			break;
320 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
321 			description = _("Modifying a memo");
322 			alert_ident = "calendar:failed-modify-memo";
323 			break;
324 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
325 			description = _("Modifying a task");
326 			alert_ident = "calendar:failed-modify-task";
327 			break;
328 		default:
329 			g_warn_if_reached ();
330 			return;
331 	}
332 
333 	data_model = e_cal_model_get_data_model (model);
334 	source = e_client_get_source (E_CLIENT (client));
335 
336 	bod = basic_operation_data_new ();
337 	bod->model = g_object_ref (model);
338 	bod->client = g_object_ref (client);
339 	bod->icomp = i_cal_component_clone (icomp);
340 	bod->mod = mod;
341 	bod->send_flags = send_flags;
342 	bod->is_modify = TRUE;
343 
344 	cal_comp_util_maybe_ensure_allday_timezone_properties (client, bod->icomp,
345 		e_cal_model_get_timezone (model));
346 
347 	display_name = e_util_get_source_full_name (e_cal_model_get_registry (model), source);
348 	cancellable = e_cal_data_model_submit_thread_job (data_model, description, alert_ident,
349 		display_name, cal_ops_modify_component_thread,
350 		bod, basic_operation_data_free);
351 
352 	g_clear_object (&cancellable);
353 	g_free (display_name);
354 }
355 
356 static void
cal_ops_remove_component_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)357 cal_ops_remove_component_thread (EAlertSinkThreadJobData *job_data,
358 				 gpointer user_data,
359 				 GCancellable *cancellable,
360 				 GError **error)
361 {
362 	BasicOperationData *bod = user_data;
363 
364 	g_return_if_fail (bod != NULL);
365 
366 	/* The check_detached_instance means to test whether the event is a detached instance,
367 	   then only that one is deleted, otherwise the master object is deleted */
368 	if (bod->check_detached_instance && bod->mod == E_CAL_OBJ_MOD_THIS && bod->rid && *bod->rid) {
369 		ICalComponent *icomp = NULL;
370 		GError *local_error = NULL;
371 
372 		if (!e_cal_client_get_object_sync (bod->client, bod->uid, bod->rid, &icomp, cancellable, &local_error) &&
373 		    g_error_matches (local_error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND)) {
374 			g_free (bod->rid);
375 			bod->rid = NULL;
376 			bod->mod = E_CAL_OBJ_MOD_ALL;
377 		}
378 
379 		g_clear_error (&local_error);
380 		g_clear_object (&icomp);
381 	}
382 
383 	bod->success = e_cal_client_remove_object_sync (bod->client, bod->uid, bod->rid, bod->mod, E_CAL_OPERATION_FLAG_NONE, cancellable, error);
384 }
385 
386 /**
387  * e_cal_ops_remove_component:
388  * @model: an #ECalModel
389  * @client: an #ECalClient
390  * @uid: a UID of the component to remove
391  * @rid: (allow none): a recurrence ID of the component; can be #NULL
392  * @mod: a mode to use for the component removal
393  * @check_detached_instance: whether to test whether a detached instance is to be removed
394  *
395  * Removes component identified by @uid and @rid from the @client using mode @mod.
396  * The @check_detached_instance influences behaviour when removing only one instance.
397  * If set to #TRUE, then it is checked first whether the component to be removed is
398  * a detached instance. If it is, then only that one is removed (as requested), otherwise
399  * the master objects is removed. If the @check_detached_instance is set to #FALSE, then
400  * the removal is done exactly with the given values.
401  *
402  * Since: 3.16
403  **/
404 void
e_cal_ops_remove_component(ECalModel * model,ECalClient * client,const gchar * uid,const gchar * rid,ECalObjModType mod,gboolean check_detached_instance)405 e_cal_ops_remove_component (ECalModel *model,
406 			    ECalClient *client,
407 			    const gchar *uid,
408 			    const gchar *rid,
409 			    ECalObjModType mod,
410 			    gboolean check_detached_instance)
411 {
412 	ECalDataModel *data_model;
413 	ESource *source;
414 	const gchar *description;
415 	const gchar *alert_ident;
416 	gchar *display_name;
417 	BasicOperationData *bod;
418 	GCancellable *cancellable;
419 
420 	g_return_if_fail (E_IS_CAL_MODEL (model));
421 	g_return_if_fail (E_IS_CAL_CLIENT (client));
422 	g_return_if_fail (uid != NULL);
423 
424 	switch (e_cal_client_get_source_type (client)) {
425 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
426 			description = _("Removing an event");
427 			alert_ident = "calendar:failed-remove-event";
428 			break;
429 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
430 			description = _("Removing a memo");
431 			alert_ident = "calendar:failed-remove-memo";
432 			break;
433 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
434 			description = _("Removing a task");
435 			alert_ident = "calendar:failed-remove-task";
436 			break;
437 		default:
438 			g_warn_if_reached ();
439 			return;
440 	}
441 
442 	data_model = e_cal_model_get_data_model (model);
443 	source = e_client_get_source (E_CLIENT (client));
444 
445 	bod = basic_operation_data_new ();
446 	bod->model = g_object_ref (model);
447 	bod->client = g_object_ref (client);
448 	bod->uid = g_strdup (uid);
449 	bod->rid = g_strdup (rid);
450 	bod->mod = mod;
451 	bod->check_detached_instance = check_detached_instance;
452 
453 	display_name = e_util_get_source_full_name (e_cal_model_get_registry (model), source);
454 	cancellable = e_cal_data_model_submit_thread_job (data_model, description, alert_ident,
455 		display_name, cal_ops_remove_component_thread,
456 		bod, basic_operation_data_free);
457 
458 	g_clear_object (&cancellable);
459 	g_free (display_name);
460 }
461 
462 static void
cal_ops_delete_components_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)463 cal_ops_delete_components_thread (EAlertSinkThreadJobData *job_data,
464 				  gpointer user_data,
465 				  GCancellable *cancellable,
466 				  GError **error)
467 {
468 	GSList *objects = user_data, *link;
469 
470 	for (link = objects; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next (link)) {
471 		ECalModelComponent *comp_data = (ECalModelComponent *) link->data;
472 		gchar *rid;
473 
474 		rid = e_cal_util_component_get_recurid_as_string (comp_data->icalcomp);
475 
476 		if (!e_cal_client_remove_object_sync (
477 			comp_data->client, i_cal_component_get_uid (comp_data->icalcomp),
478 			rid, E_CAL_OBJ_MOD_THIS, E_CAL_OPERATION_FLAG_NONE, cancellable, error)) {
479 			ESource *source = e_client_get_source (E_CLIENT (comp_data->client));
480 			e_alert_sink_thread_job_set_alert_arg_0 (job_data, e_source_get_display_name (source));
481 			/* Stop on the first error */
482 			g_free (rid);
483 			break;
484 		}
485 
486 		g_free (rid);
487 	}
488 }
489 
490 /**
491  * e_cal_ops_delete_ecalmodel_components:
492  * @model: an #ECalModel
493  * @objects: a #GSList of an #ECalModelComponent objects to delete
494  *
495  * Deletes all components from their sources. The @objects should
496  * be part of @model.
497  *
498  * Since: 3.16
499  **/
500 void
e_cal_ops_delete_ecalmodel_components(ECalModel * model,const GSList * objects)501 e_cal_ops_delete_ecalmodel_components (ECalModel *model,
502 				       const GSList *objects)
503 {
504 	ECalDataModel *data_model;
505 	GCancellable *cancellable;
506 	const gchar *alert_ident;
507 	gchar *description;
508 	GSList *objects_copy;
509 	gint nobjects;
510 
511 	g_return_if_fail (E_IS_CAL_MODEL (model));
512 
513 	if (!objects)
514 		return;
515 
516 	objects_copy = g_slist_copy ((GSList *) objects);
517 	g_slist_foreach (objects_copy, (GFunc) g_object_ref, NULL);
518 	nobjects = g_slist_length (objects_copy);
519 
520 	switch (e_cal_model_get_component_kind (model)) {
521 		case I_CAL_VEVENT_COMPONENT:
522 			description = g_strdup_printf (ngettext ("Deleting an event", "Deleting %d events", nobjects), nobjects);
523 			alert_ident = "calendar:failed-remove-event";
524 			break;
525 		case I_CAL_VJOURNAL_COMPONENT:
526 			description = g_strdup_printf (ngettext ("Deleting a memo", "Deleting %d memos", nobjects), nobjects);
527 			alert_ident = "calendar:failed-remove-memo";
528 			break;
529 		case I_CAL_VTODO_COMPONENT:
530 			description = g_strdup_printf (ngettext ("Deleting a task", "Deleting %d tasks", nobjects), nobjects);
531 			alert_ident = "calendar:failed-remove-task";
532 			break;
533 		default:
534 			g_warn_if_reached ();
535 			return;
536 	}
537 
538 	data_model = e_cal_model_get_data_model (model);
539 
540 	cancellable = e_cal_data_model_submit_thread_job (data_model, description, alert_ident,
541 		NULL, cal_ops_delete_components_thread, objects_copy, (GDestroyNotify) e_util_free_nullable_object_slist);
542 
543 	g_clear_object (&cancellable);
544 	g_free (description);
545 }
546 
547 static gboolean
cal_ops_create_comp_with_new_uid_sync(ECalClient * cal_client,ICalComponent * icomp,ICalTimezone * zone,GCancellable * cancellable,GError ** error)548 cal_ops_create_comp_with_new_uid_sync (ECalClient *cal_client,
549 				       ICalComponent *icomp,
550 				       ICalTimezone *zone,
551 				       GCancellable *cancellable,
552 				       GError **error)
553 {
554 	ICalComponent *clone;
555 	gchar *uid;
556 	gboolean success;
557 
558 	g_return_val_if_fail (E_IS_CAL_CLIENT (cal_client), FALSE);
559 	g_return_val_if_fail (I_CAL_IS_COMPONENT (icomp), FALSE);
560 
561 	clone = i_cal_component_clone (icomp);
562 
563 	uid = e_util_generate_uid ();
564 	i_cal_component_set_uid (clone, uid);
565 	g_free (uid);
566 
567 	cal_comp_util_maybe_ensure_allday_timezone_properties (cal_client, clone, zone);
568 
569 	success = e_cal_client_create_object_sync (cal_client, clone, E_CAL_OPERATION_FLAG_NONE, NULL, cancellable, error);
570 
571 	g_clear_object (&clone);
572 
573 	return success;
574 }
575 
576 typedef struct {
577 	ECalModel *model;
578 	ICalComponent *icomp;
579 	ICalComponentKind kind;
580 	ICalTimezone *zone;
581 	const gchar *extension_name;
582 	gboolean success;
583 } PasteComponentsData;
584 
585 static void
paste_components_data_free(gpointer ptr)586 paste_components_data_free (gpointer ptr)
587 {
588 	PasteComponentsData *pcd = ptr;
589 
590 	if (pcd) {
591 		if (pcd->model && pcd->success)
592 			g_signal_emit_by_name (pcd->model, "row-appended", 0);
593 
594 		g_clear_object (&pcd->model);
595 		g_clear_object (&pcd->icomp);
596 		g_clear_object (&pcd->zone);
597 		g_slice_free (PasteComponentsData, pcd);
598 	}
599 }
600 
601 static void
cal_ops_update_components_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)602 cal_ops_update_components_thread (EAlertSinkThreadJobData *job_data,
603 				  gpointer user_data,
604 				  GCancellable *cancellable,
605 				  GError **error)
606 {
607 	PasteComponentsData *pcd = user_data;
608 	EClient *client;
609 	EClientCache *client_cache;
610 	ECalClient *cal_client;
611 	ESourceRegistry *registry;
612 	ESource *source;
613 	const gchar *uid;
614 	gchar *display_name;
615 	gboolean success = TRUE, any_copied = FALSE;
616 	GError *local_error = NULL;
617 
618 	g_return_if_fail (pcd != NULL);
619 
620 	uid = e_cal_model_get_default_source_uid (pcd->model);
621 	g_return_if_fail (uid != NULL);
622 
623 	client_cache = e_cal_model_get_client_cache (pcd->model);
624 	registry = e_cal_model_get_registry (pcd->model);
625 
626 	source = e_source_registry_ref_source (registry, uid);
627 	if (!source) {
628 		g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
629 			_("Source with UID “%s” not found"), uid);
630 		e_alert_sink_thread_job_set_alert_arg_0 (job_data, uid);
631 		return;
632 	}
633 
634 	display_name = e_util_get_source_full_name (registry, source);
635 	e_alert_sink_thread_job_set_alert_arg_0 (job_data, display_name);
636 	g_free (display_name);
637 
638 	client = e_client_cache_get_client_sync (client_cache, source, pcd->extension_name, 30, cancellable, &local_error);
639 	g_clear_object (&source);
640 
641 	if (!client) {
642 		e_util_propagate_open_source_job_error (job_data, pcd->extension_name, local_error, error);
643 		return;
644 	}
645 
646 	cal_client = E_CAL_CLIENT (client);
647 
648 	if (i_cal_component_isa (pcd->icomp) == I_CAL_VCALENDAR_COMPONENT &&
649 	    i_cal_component_count_components (pcd->icomp, pcd->kind) > 0) {
650 		ICalComponent *subcomp;
651 
652 		for (subcomp = i_cal_component_get_first_component (pcd->icomp, I_CAL_VTIMEZONE_COMPONENT);
653 		     subcomp && !g_cancellable_is_cancelled (cancellable);
654 		     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (pcd->icomp, I_CAL_VTIMEZONE_COMPONENT)) {
655 			ICalTimezone *zone;
656 
657 			zone = i_cal_timezone_new ();
658 			i_cal_timezone_set_component (zone, subcomp);
659 			if (!e_cal_client_add_timezone_sync (cal_client, zone, cancellable, error)) {
660 				g_clear_object (&zone);
661 				success = FALSE;
662 				break;
663 			}
664 
665 			g_clear_object (&zone);
666 		}
667 
668 		g_clear_object (&subcomp);
669 
670 		for (subcomp = i_cal_component_get_first_component (pcd->icomp, pcd->kind);
671 		     subcomp && !g_cancellable_is_cancelled (cancellable) && success;
672 		     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (pcd->icomp, pcd->kind)) {
673 			if (!cal_ops_create_comp_with_new_uid_sync (cal_client, subcomp, pcd->zone, cancellable, error)) {
674 				success = FALSE;
675 				break;
676 			}
677 
678 			any_copied = TRUE;
679 		}
680 
681 		g_clear_object (&subcomp);
682 	} else if (i_cal_component_isa (pcd->icomp) == pcd->kind) {
683 		success = cal_ops_create_comp_with_new_uid_sync (cal_client, pcd->icomp, pcd->zone, cancellable, error);
684 		any_copied = success;
685 	}
686 
687 	pcd->success = success && any_copied;
688 
689 	g_object_unref (client);
690 }
691 
692 /**
693  * e_cal_ops_paste_components:
694  * @model: an #ECalModel
695  * @icompstr: a string representation of an iCalendar component
696  *
697  * Pastes components into the default source of the @model.
698  *
699  * Since: 3.16
700  **/
701 void
e_cal_ops_paste_components(ECalModel * model,const gchar * icompstr)702 e_cal_ops_paste_components (ECalModel *model,
703 			    const gchar *icompstr)
704 {
705 	ECalDataModel *data_model;
706 	ICalComponent *icomp;
707 	ICalComponentKind kind;
708 	gint ncomponents = 0;
709 	GCancellable *cancellable;
710 	const gchar *alert_ident;
711 	const gchar *extension_name;
712 	gchar *description;
713 	PasteComponentsData *pcd;
714 
715 	g_return_if_fail (E_IS_CAL_MODEL (model));
716 	g_return_if_fail (icompstr != NULL);
717 
718 	icomp = i_cal_parser_parse_string (icompstr);
719 	if (!icomp)
720 		return;
721 
722 	kind = i_cal_component_isa (icomp);
723 	if (kind != I_CAL_VCALENDAR_COMPONENT &&
724 	    kind != e_cal_model_get_component_kind (model)) {
725 		g_clear_object (&icomp);
726 		return;
727 	}
728 
729 	switch (e_cal_model_get_component_kind (model)) {
730 		case I_CAL_VEVENT_COMPONENT:
731 			if (kind == I_CAL_VCALENDAR_COMPONENT) {
732 				kind = I_CAL_VEVENT_COMPONENT;
733 				ncomponents = i_cal_component_count_components (icomp, kind);
734 			} else if (kind == I_CAL_VEVENT_COMPONENT) {
735 				ncomponents = 1;
736 			}
737 
738 			if (ncomponents == 0)
739 				break;
740 
741 			description = g_strdup_printf (ngettext ("Pasting an event", "Pasting %d events", ncomponents), ncomponents);
742 			alert_ident = "calendar:failed-create-event";
743 			extension_name = E_SOURCE_EXTENSION_CALENDAR;
744 			break;
745 		case I_CAL_VJOURNAL_COMPONENT:
746 			if (kind == I_CAL_VCALENDAR_COMPONENT) {
747 				kind = I_CAL_VJOURNAL_COMPONENT;
748 				ncomponents = i_cal_component_count_components (icomp, kind);
749 			} else if (kind == I_CAL_VJOURNAL_COMPONENT) {
750 				ncomponents = 1;
751 			}
752 
753 			if (ncomponents == 0)
754 				break;
755 
756 			description = g_strdup_printf (ngettext ("Pasting a memo", "Pasting %d memos", ncomponents), ncomponents);
757 			alert_ident = "calendar:failed-create-memo";
758 			extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
759 			break;
760 		case I_CAL_VTODO_COMPONENT:
761 			if (kind == I_CAL_VCALENDAR_COMPONENT) {
762 				kind = I_CAL_VTODO_COMPONENT;
763 				ncomponents = i_cal_component_count_components (icomp, kind);
764 			} else if (kind == I_CAL_VTODO_COMPONENT) {
765 				ncomponents = 1;
766 			}
767 
768 			if (ncomponents == 0)
769 				break;
770 
771 			description = g_strdup_printf (ngettext ("Pasting a task", "Pasting %d tasks", ncomponents), ncomponents);
772 			alert_ident = "calendar:failed-create-task";
773 			extension_name = E_SOURCE_EXTENSION_TASK_LIST;
774 			break;
775 		default:
776 			g_warn_if_reached ();
777 			break;
778 	}
779 
780 	if (ncomponents == 0) {
781 		g_object_unref (icomp);
782 		return;
783 	}
784 
785 	pcd = g_slice_new0 (PasteComponentsData);
786 	pcd->model = g_object_ref (model);
787 	pcd->icomp = icomp;
788 	pcd->kind = kind;
789 	pcd->zone = e_cal_model_get_timezone (model);
790 	pcd->extension_name = extension_name;
791 	pcd->success = FALSE;
792 
793 	if (pcd->zone)
794 		g_object_ref (pcd->zone);
795 
796 	data_model = e_cal_model_get_data_model (model);
797 
798 	cancellable = e_cal_data_model_submit_thread_job (data_model, description, alert_ident,
799 		NULL, cal_ops_update_components_thread, pcd, paste_components_data_free);
800 
801 	g_clear_object (&cancellable);
802 	g_free (description);
803 }
804 
805 typedef struct {
806 	ECalClient *client;
807 	ICalComponent *icomp;
808 } SendComponentData;
809 
810 static void
send_component_data_free(gpointer ptr)811 send_component_data_free (gpointer ptr)
812 {
813 	SendComponentData *scd = ptr;
814 
815 	if (scd) {
816 		g_clear_object (&scd->client);
817 		g_clear_object (&scd->icomp);
818 		g_slice_free (SendComponentData, scd);
819 	}
820 }
821 
822 static void
cal_ops_send_component_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)823 cal_ops_send_component_thread (EAlertSinkThreadJobData *job_data,
824 			       gpointer user_data,
825 			       GCancellable *cancellable,
826 			       GError **error)
827 {
828 	SendComponentData *scd = user_data;
829 	ICalComponent *mod_comp = NULL;
830 	GSList *users = NULL;
831 
832 	g_return_if_fail (scd != NULL);
833 
834 	e_cal_client_send_objects_sync (scd->client, scd->icomp, E_CAL_OPERATION_FLAG_NONE,
835 		&users, &mod_comp, cancellable, error);
836 
837 	g_clear_object (&mod_comp);
838 	g_slist_free_full (users, g_free);
839 }
840 
841 /**
842  * e_cal_ops_send_component:
843  * @model: an #ECalModel
844  * @client: an #ECalClient
845  * @icomp: an #ICalComponent
846  *
847  * Sends (calls e_cal_client_send_objects_sync()) on the given @client
848  * with the given @icomp in a dedicated thread.
849  *
850  * Since: 3.16
851  **/
852 void
e_cal_ops_send_component(ECalModel * model,ECalClient * client,ICalComponent * icomp)853 e_cal_ops_send_component (ECalModel *model,
854 			  ECalClient *client,
855 			  ICalComponent *icomp)
856 {
857 	ECalDataModel *data_model;
858 	ESource *source;
859 	GCancellable *cancellable;
860 	const gchar *alert_ident;
861 	const gchar *description;
862 	gchar *display_name;
863 	SendComponentData *scd;
864 
865 	g_return_if_fail (E_IS_CAL_MODEL (model));
866 	g_return_if_fail (E_IS_CAL_CLIENT (client));
867 	g_return_if_fail (I_CAL_IS_COMPONENT (icomp));
868 
869 	switch (e_cal_client_get_source_type (client)) {
870 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
871 			description = _("Updating an event");
872 			alert_ident = "calendar:failed-update-event";
873 			break;
874 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
875 			description = _("Updating a memo");
876 			alert_ident = "calendar:failed-update-memo";
877 			break;
878 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
879 			description = _("Updating a task");
880 			alert_ident = "calendar:failed-update-task";
881 			break;
882 		default:
883 			g_warn_if_reached ();
884 			return;
885 	}
886 
887 	scd = g_slice_new0 (SendComponentData);
888 	scd->client = g_object_ref (client);
889 	scd->icomp = i_cal_component_clone (icomp);
890 
891 	source = e_client_get_source (E_CLIENT (client));
892 	data_model = e_cal_model_get_data_model (model);
893 	display_name = e_util_get_source_full_name (e_cal_model_get_registry (model), source);
894 
895 	cancellable = e_cal_data_model_submit_thread_job (data_model, description, alert_ident,
896 		display_name, cal_ops_send_component_thread,
897 		scd, send_component_data_free);
898 
899 	g_clear_object (&cancellable);
900 	g_free (display_name);
901 }
902 
903 typedef struct {
904 	ECalModel *model;
905 	GList *clients;
906 	ICalComponentKind kind;
907 	time_t older_than;
908 } PurgeComponentsData;
909 
910 static void
purge_components_data_free(gpointer ptr)911 purge_components_data_free (gpointer ptr)
912 {
913 	PurgeComponentsData *pcd = ptr;
914 
915 	if (pcd) {
916 		g_clear_object (&pcd->model);
917 		g_list_free_full (pcd->clients, g_object_unref);
918 		g_slice_free (PurgeComponentsData, pcd);
919 	}
920 }
921 
922 struct purge_data {
923 	GList *clients;
924 	gboolean remove;
925 	time_t older_than;
926 };
927 
928 static gboolean
ca_ops_purge_check_instance_cb(ICalComponent * comp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)929 ca_ops_purge_check_instance_cb (ICalComponent *comp,
930 				ICalTime *instance_start,
931 				ICalTime *instance_end,
932 				gpointer user_data,
933 				GCancellable *cancellable,
934 				GError **error)
935 {
936 	struct purge_data *pd = user_data;
937 
938 	if (i_cal_time_as_timet (instance_end) >= pd->older_than)
939 		pd->remove = FALSE;
940 
941 	return pd->remove;
942 }
943 
944 static void
cal_ops_purge_components_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)945 cal_ops_purge_components_thread (EAlertSinkThreadJobData *job_data,
946 				 gpointer user_data,
947 				 GCancellable *cancellable,
948 				 GError **error)
949 {
950 	PurgeComponentsData *pcd = user_data;
951 	GList *clink;
952 	gchar *sexp, *start, *end;
953 	gboolean pushed_message = FALSE;
954 	const gchar *tzloc = NULL;
955 	ICalTimezone *zone;
956 	ICalComponentKind model_kind;
957 
958 	g_return_if_fail (pcd != NULL);
959 
960 	model_kind = e_cal_model_get_component_kind (pcd->model);
961 	zone = e_cal_model_get_timezone (pcd->model);
962 	if (zone && zone != i_cal_timezone_get_utc_timezone ()) {
963 		tzloc = i_cal_timezone_get_location (zone);
964 		if (tzloc && g_ascii_strcasecmp (tzloc, "UTC") == 0)
965 			tzloc = NULL;
966 	}
967 
968 	start = isodate_from_time_t (0);
969 	end = isodate_from_time_t (pcd->older_than);
970 	sexp = g_strdup_printf (
971 		"(occur-in-time-range? (make-time \"%s\") (make-time \"%s\") \"%s\")",
972 		start, end, tzloc ? tzloc : "");
973 	g_free (start);
974 	g_free (end);
975 
976 	for (clink = pcd->clients; clink && !g_cancellable_is_cancelled (cancellable); clink = g_list_next (clink)) {
977 		ECalClient *client = clink->data;
978 		GSList *objects, *olink;
979 		gint nobjects, ii, last_percent = 0;
980 		gchar *display_name;
981 		gboolean success = TRUE;
982 
983 		if (!client || e_client_is_readonly (E_CLIENT (client)))
984 			continue;
985 
986 		display_name = e_util_get_source_full_name (e_cal_model_get_registry (pcd->model), e_client_get_source (E_CLIENT (client)));
987 		e_alert_sink_thread_job_set_alert_arg_0 (job_data, display_name);
988 
989 		switch (model_kind) {
990 			case I_CAL_VEVENT_COMPONENT:
991 				camel_operation_push_message (cancellable,
992 					_("Getting events to purge in the calendar “%s”"), display_name);
993 				break;
994 			case I_CAL_VJOURNAL_COMPONENT:
995 				camel_operation_push_message (cancellable,
996 					_("Getting memos to purge in the memo list “%s”"), display_name);
997 				break;
998 			case I_CAL_VTODO_COMPONENT:
999 				camel_operation_push_message (cancellable,
1000 					_("Getting tasks to purge in the task list “%s”"), display_name);
1001 				break;
1002 			default:
1003 				g_warn_if_reached ();
1004 				g_free (display_name);
1005 				return;
1006 		}
1007 
1008 		pushed_message = TRUE;
1009 
1010 		if (!e_cal_client_get_object_list_sync (client, sexp, &objects, cancellable, error)) {
1011 			g_free (display_name);
1012 			break;
1013 		}
1014 
1015 		camel_operation_pop_message (cancellable);
1016 		pushed_message = FALSE;
1017 
1018 		if (!objects) {
1019 			g_free (display_name);
1020 			continue;
1021 		}
1022 
1023 		switch (model_kind) {
1024 			case I_CAL_VEVENT_COMPONENT:
1025 				camel_operation_push_message (cancellable,
1026 					_("Purging events in the calendar “%s”"), display_name);
1027 				break;
1028 			case I_CAL_VJOURNAL_COMPONENT:
1029 				camel_operation_push_message (cancellable,
1030 					_("Purging memos in the memo list “%s”"), display_name);
1031 				break;
1032 			case I_CAL_VTODO_COMPONENT:
1033 				camel_operation_push_message (cancellable,
1034 					_("Purging tasks in the task list “%s”"), display_name);
1035 				break;
1036 			default:
1037 				g_warn_if_reached ();
1038 				g_free (display_name);
1039 				return;
1040 		}
1041 
1042 		g_free (display_name);
1043 		pushed_message = TRUE;
1044 		nobjects = g_slist_length (objects);
1045 
1046 		for (olink = objects, ii = 0; olink; olink = g_slist_next (olink), ii++) {
1047 			ICalComponent *icomp = olink->data;
1048 			gboolean remove = TRUE;
1049 			gint percent = 100 * (ii + 1) / nobjects;
1050 
1051 			if (!e_cal_client_check_recurrences_no_master (client)) {
1052 				struct purge_data pd;
1053 
1054 				pd.remove = TRUE;
1055 				pd.older_than = pcd->older_than;
1056 
1057 				e_cal_client_generate_instances_for_object_sync (client, icomp,
1058 					pcd->older_than, G_MAXINT32, cancellable, ca_ops_purge_check_instance_cb, &pd);
1059 
1060 				remove = pd.remove;
1061 			}
1062 
1063 			if (remove) {
1064 				const gchar *uid = i_cal_component_get_uid (icomp);
1065 
1066 				if (e_cal_util_component_is_instance (icomp) ||
1067 				    e_cal_util_component_has_recurrences (icomp)) {
1068 					gchar *rid;
1069 
1070 					rid = e_cal_util_component_get_recurid_as_string (icomp);
1071 
1072 					success = e_cal_client_remove_object_sync (client, uid, rid, E_CAL_OBJ_MOD_ALL, E_CAL_OPERATION_FLAG_NONE, cancellable, error);
1073 
1074 					g_free (rid);
1075 				} else {
1076 					success = e_cal_client_remove_object_sync (client, uid, NULL, E_CAL_OBJ_MOD_THIS, E_CAL_OPERATION_FLAG_NONE, cancellable, error);
1077 				}
1078 
1079 				if (!success)
1080 					break;
1081 			}
1082 
1083 			if (percent != last_percent) {
1084 				camel_operation_progress (cancellable, percent);
1085 				last_percent = percent;
1086 			}
1087 		}
1088 
1089 		g_slist_free_full (objects, g_object_unref);
1090 
1091 		camel_operation_progress (cancellable, 0);
1092 		camel_operation_pop_message (cancellable);
1093 		pushed_message = FALSE;
1094 
1095 		if (!success)
1096 			break;
1097 	}
1098 
1099 	if (pushed_message)
1100 		camel_operation_pop_message (cancellable);
1101 
1102 	g_free (sexp);
1103 }
1104 
1105 /**
1106  * e_cal_ops_purge_components:
1107  * @model: an #ECalModel instance
1108  * @older_than: threshold for the purge operation
1109  *
1110  * Purges (removed) all components older than @older_than from all
1111  * currently active clients in @model.
1112  *
1113  * Since: 3.16
1114  **/
1115 void
e_cal_ops_purge_components(ECalModel * model,time_t older_than)1116 e_cal_ops_purge_components (ECalModel *model,
1117 			    time_t older_than)
1118 {
1119 	ECalDataModel *data_model;
1120 	GCancellable *cancellable;
1121 	const gchar *alert_ident;
1122 	const gchar *description;
1123 	PurgeComponentsData *pcd;
1124 
1125 	g_return_if_fail (E_IS_CAL_MODEL (model));
1126 
1127 	switch (e_cal_model_get_component_kind (model)) {
1128 		case I_CAL_VEVENT_COMPONENT:
1129 			description = _("Purging events");
1130 			alert_ident = "calendar:failed-remove-event";
1131 			break;
1132 		case I_CAL_VJOURNAL_COMPONENT:
1133 			description = _("Purging memos");
1134 			alert_ident = "calendar:failed-remove-memo";
1135 			break;
1136 		case I_CAL_VTODO_COMPONENT:
1137 			description = _("Purging tasks");
1138 			alert_ident = "calendar:failed-remove-task";
1139 			break;
1140 		default:
1141 			g_warn_if_reached ();
1142 			return;
1143 	}
1144 
1145 	data_model = e_cal_model_get_data_model (model);
1146 
1147 	pcd = g_slice_new0 (PurgeComponentsData);
1148 	pcd->model = g_object_ref (model);
1149 	pcd->clients = e_cal_data_model_get_clients (data_model);
1150 	pcd->kind = e_cal_model_get_component_kind (model);
1151 	pcd->older_than = older_than;
1152 
1153 	cancellable = e_cal_data_model_submit_thread_job (data_model, description, alert_ident,
1154 		NULL, cal_ops_purge_components_thread,
1155 		pcd, purge_components_data_free);
1156 
1157 	g_clear_object (&cancellable);
1158 }
1159 
1160 static void
clients_list_free(gpointer ptr)1161 clients_list_free (gpointer ptr)
1162 {
1163 	g_list_free_full (ptr, g_object_unref);
1164 }
1165 
1166 static void
cal_ops_delete_completed_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)1167 cal_ops_delete_completed_thread (EAlertSinkThreadJobData *job_data,
1168 				 gpointer user_data,
1169 				 GCancellable *cancellable,
1170 				 GError **error)
1171 {
1172 	GList *clients = user_data, *link;
1173 
1174 	for (link = clients; link; link = g_list_next (link)) {
1175 		ECalClient *client = link->data;
1176 		GSList *objects = NULL, *olink;
1177 
1178 		if (!client ||
1179 		    e_client_is_readonly (E_CLIENT (client)))
1180 			continue;
1181 
1182 		if (!e_cal_client_get_object_list_sync (client, "(is-completed?)", &objects, cancellable, error)) {
1183 			ESource *source = e_client_get_source (E_CLIENT (client));
1184 			e_alert_sink_thread_job_set_alert_arg_0 (job_data, e_source_get_display_name (source));
1185 			break;
1186 		}
1187 
1188 		for (olink = objects; olink != NULL; olink = g_slist_next (olink)) {
1189 			ICalComponent *icomp = olink->data;
1190 			const gchar *uid;
1191 
1192 			uid = i_cal_component_get_uid (icomp);
1193 
1194 			if (!e_cal_client_remove_object_sync (client, uid, NULL, E_CAL_OBJ_MOD_THIS, E_CAL_OPERATION_FLAG_NONE, cancellable, error)) {
1195 				ESource *source = e_client_get_source (E_CLIENT (client));
1196 				e_alert_sink_thread_job_set_alert_arg_0 (job_data, e_source_get_display_name (source));
1197 				break;
1198 			}
1199 		}
1200 
1201 		e_util_free_nullable_object_slist (objects);
1202 
1203 		/* did not process all objects => an error occurred */
1204 		if (olink != NULL)
1205 			break;
1206 	}
1207 }
1208 
1209 /**
1210  * e_cal_ops_delete_completed_tasks:
1211  * @model: an #ECalModel
1212  *
1213  * Deletes all completed tasks from all currently opened
1214  * clients in @model.
1215  *
1216  * Since: 3.16
1217  **/
1218 void
e_cal_ops_delete_completed_tasks(ECalModel * model)1219 e_cal_ops_delete_completed_tasks (ECalModel *model)
1220 {
1221 	ECalDataModel *data_model;
1222 	GCancellable *cancellable;
1223 	GList *clients;
1224 
1225 	g_return_if_fail (E_IS_CAL_MODEL (model));
1226 
1227 	data_model = e_cal_model_get_data_model (model);
1228 	clients = e_cal_data_model_get_clients (data_model);
1229 
1230 	if (!clients)
1231 		return;
1232 
1233 	if (e_cal_client_get_source_type (clients->data) != E_CAL_CLIENT_SOURCE_TYPE_TASKS) {
1234 		g_list_free_full (clients, g_object_unref);
1235 		g_warn_if_reached ();
1236 		return;
1237 	}
1238 
1239 	cancellable = e_cal_data_model_submit_thread_job (data_model, _("Expunging completed tasks"),
1240 		"calendar:failed-remove-task", NULL, cal_ops_delete_completed_thread,
1241 		clients, clients_list_free);
1242 
1243 	g_clear_object (&cancellable);
1244 }
1245 
1246 static ECalClient *
cal_ops_open_client_sync(EAlertSinkThreadJobData * job_data,EShell * shell,const gchar * client_uid,const gchar * extension_name,GCancellable * cancellable,GError ** error)1247 cal_ops_open_client_sync (EAlertSinkThreadJobData *job_data,
1248 			  EShell *shell,
1249 			  const gchar *client_uid,
1250 			  const gchar *extension_name,
1251 			  GCancellable *cancellable,
1252 			  GError **error)
1253 {
1254 	ECalClient *cal_client = NULL;
1255 	ESourceRegistry *registry;
1256 	EClientCache *client_cache;
1257 	ESource *source;
1258 	EClient *client;
1259 
1260 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
1261 	g_return_val_if_fail (client_uid != NULL, NULL);
1262 	g_return_val_if_fail (extension_name != NULL, NULL);
1263 
1264 	registry = e_shell_get_registry (shell);
1265 	client_cache = e_shell_get_client_cache (shell);
1266 
1267 	source = e_source_registry_ref_source (registry, client_uid);
1268 	if (!source) {
1269 		g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1270 			_("Source with UID “%s” not found"), client_uid);
1271 		e_alert_sink_thread_job_set_alert_arg_0 (job_data, client_uid);
1272 	} else {
1273 		client = e_client_cache_get_client_sync (client_cache, source, extension_name, 30, cancellable, error);
1274 		if (client)
1275 			cal_client = E_CAL_CLIENT (client);
1276 	}
1277 
1278 	g_clear_object (&source);
1279 
1280 	return cal_client;
1281 }
1282 
1283 static void
cal_ops_get_default_component_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)1284 cal_ops_get_default_component_thread (EAlertSinkThreadJobData *job_data,
1285 				      gpointer user_data,
1286 				      GCancellable *cancellable,
1287 				      GError **error)
1288 {
1289 	BasicOperationData *bod = user_data;
1290 
1291 	g_return_if_fail (bod != NULL);
1292 
1293 	if (!bod->for_client_uid) {
1294 		ESourceRegistry *registry;
1295 		ESource *default_source = NULL;
1296 
1297 		registry = e_cal_model_get_registry (bod->model);
1298 
1299 		switch (e_cal_model_get_component_kind (bod->model)) {
1300 			case I_CAL_VEVENT_COMPONENT:
1301 				default_source = e_source_registry_ref_default_calendar (registry);
1302 				break;
1303 			case I_CAL_VJOURNAL_COMPONENT:
1304 				default_source = e_source_registry_ref_default_memo_list (registry);
1305 				break;
1306 			case I_CAL_VTODO_COMPONENT:
1307 				default_source = e_source_registry_ref_default_task_list (registry);
1308 				break;
1309 			default:
1310 				g_warn_if_reached ();
1311 				return;
1312 		}
1313 
1314 		if (default_source)
1315 			bod->for_client_uid = g_strdup (e_source_get_uid (default_source));
1316 
1317 		g_clear_object (&default_source);
1318 	}
1319 
1320 	if (bod->for_client_uid) {
1321 		const gchar *extension_name = NULL;
1322 
1323 		switch (e_cal_model_get_component_kind (bod->model)) {
1324 			case I_CAL_VEVENT_COMPONENT:
1325 				extension_name = E_SOURCE_EXTENSION_CALENDAR;
1326 				break;
1327 			case I_CAL_VJOURNAL_COMPONENT:
1328 				extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
1329 				break;
1330 			case I_CAL_VTODO_COMPONENT:
1331 				extension_name = E_SOURCE_EXTENSION_TASK_LIST;
1332 				break;
1333 			default:
1334 				g_warn_if_reached ();
1335 				return;
1336 		}
1337 
1338 		bod->client = cal_ops_open_client_sync (job_data,
1339 			e_cal_model_get_shell (bod->model),
1340 			bod->for_client_uid,
1341 			extension_name,
1342 			cancellable,
1343 			error);
1344 	}
1345 
1346 	bod->icomp = e_cal_model_create_component_with_defaults_sync (bod->model, bod->client, bod->all_day_default_comp, cancellable, error);
1347 	bod->success = bod->icomp != NULL && !g_cancellable_is_cancelled (cancellable);
1348 }
1349 
1350 /**
1351  * e_cal_ops_get_default_component:
1352  * @model: an #ECalModel
1353  * @for_client_uid: (allow none): a client UID to use for the new component; can be #NULL
1354  * @all_day: whether the default event should be an all day event; this argument
1355  *    is ignored for other than event @model-s
1356  * @callback: a callback to be called when the operation succeeded
1357  * @user_data: user data passed to @callback
1358  * @user_data_free: (allow none): a function to free @user_data, or #NULL
1359  *
1360  * Creates a new component with default values as defined by the @client,
1361  * or by the @model, if @client is #NULL. The @callback is called on success.
1362  * The @callback is called in the main thread.
1363  *
1364  * Since: 3.16
1365  **/
1366 void
e_cal_ops_get_default_component(ECalModel * model,const gchar * for_client_uid,gboolean all_day,ECalOpsGetDefaultComponentFunc callback,gpointer user_data,GDestroyNotify user_data_free)1367 e_cal_ops_get_default_component (ECalModel *model,
1368 				 const gchar *for_client_uid,
1369 				 gboolean all_day,
1370 				 ECalOpsGetDefaultComponentFunc callback,
1371 				 gpointer user_data,
1372 				 GDestroyNotify user_data_free)
1373 {
1374 	ECalDataModel *data_model;
1375 	ESource *source = NULL;
1376 	const gchar *description;
1377 	const gchar *alert_ident;
1378 	gchar *display_name = NULL;
1379 	BasicOperationData *bod;
1380 	GCancellable *cancellable;
1381 
1382 	g_return_if_fail (E_IS_CAL_MODEL (model));
1383 	g_return_if_fail (callback != NULL);
1384 
1385 	switch (e_cal_model_get_component_kind (model)) {
1386 		case I_CAL_VEVENT_COMPONENT:
1387 			description = _("Creating an event");
1388 			alert_ident = "calendar:failed-create-event";
1389 			break;
1390 		case I_CAL_VJOURNAL_COMPONENT:
1391 			description = _("Creating a memo");
1392 			alert_ident = "calendar:failed-create-memo";
1393 			break;
1394 		case I_CAL_VTODO_COMPONENT:
1395 			description = _("Creating a task");
1396 			alert_ident = "calendar:failed-create-task";
1397 			break;
1398 		default:
1399 			g_warn_if_reached ();
1400 			return;
1401 	}
1402 
1403 	data_model = e_cal_model_get_data_model (model);
1404 	if (for_client_uid) {
1405 		ESourceRegistry *registry;
1406 
1407 		registry = e_cal_model_get_registry (model);
1408 		source = e_source_registry_ref_source (registry, for_client_uid);
1409 		if (source)
1410 			display_name = e_util_get_source_full_name (registry, source);
1411 	}
1412 
1413 	bod = basic_operation_data_new ();
1414 	bod->model = g_object_ref (model);
1415 	bod->client = NULL;
1416 	bod->icomp = NULL;
1417 	bod->for_client_uid = g_strdup (for_client_uid);
1418 	bod->all_day_default_comp = all_day;
1419 	bod->get_default_comp_cb = callback;
1420 	bod->user_data = user_data;
1421 	bod->user_data_free = user_data_free;
1422 
1423 	cancellable = e_cal_data_model_submit_thread_job (data_model, description, alert_ident,
1424 		display_name ? display_name : "", cal_ops_get_default_component_thread,
1425 		bod, basic_operation_data_free);
1426 
1427 	g_clear_object (&cancellable);
1428 	g_clear_object (&source);
1429 	g_free (display_name);
1430 }
1431 
1432 static void
cal_ops_emit_model_object_created(ECompEditor * comp_editor,ECalModel * model)1433 cal_ops_emit_model_object_created (ECompEditor *comp_editor,
1434 				   ECalModel *model)
1435 {
1436 	g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1437 	g_return_if_fail (E_IS_CAL_MODEL (model));
1438 
1439 	e_cal_model_emit_object_created (model, e_comp_editor_get_target_client (comp_editor));
1440 }
1441 
1442 typedef struct
1443 {
1444 	gboolean is_new_component;
1445 	EShell *shell;
1446 	ECalModel *model;
1447 	ECalClientSourceType source_type;
1448 	gboolean is_assigned;
1449 	gchar *extension_name;
1450 	gchar *for_client_uid;
1451 	ESource *default_source;
1452 	ECalClient *client;
1453 	ECalComponent *comp;
1454 
1455 	/* for events only */
1456 	time_t dtstart;
1457 	time_t dtend;
1458 	gboolean all_day;
1459 	gboolean use_default_reminder;
1460 	gint default_reminder_interval;
1461 	EDurationType default_reminder_units;
1462 } NewComponentData;
1463 
1464 static NewComponentData *
new_component_data_new(void)1465 new_component_data_new (void)
1466 {
1467 	return g_slice_new0 (NewComponentData);
1468 }
1469 
1470 static void
new_component_data_free(gpointer ptr)1471 new_component_data_free (gpointer ptr)
1472 {
1473 	NewComponentData *ncd = ptr;
1474 
1475 	if (ncd) {
1476 		/* successfully opened the default client */
1477 		if (ncd->client && ncd->comp) {
1478 			ECompEditor *comp_editor;
1479 			ECompEditorFlags flags = 0;
1480 
1481 			if (ncd->is_new_component) {
1482 				flags |= E_COMP_EDITOR_FLAG_IS_NEW;
1483 			} else {
1484 				if (e_cal_component_has_attendees (ncd->comp))
1485 					ncd->is_assigned = TRUE;
1486 			}
1487 
1488 			if (ncd->is_assigned) {
1489 				if (ncd->is_new_component)
1490 					flags |= E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER;
1491 
1492 				flags |= E_COMP_EDITOR_FLAG_WITH_ATTENDEES;
1493 			}
1494 
1495 			if (ncd->source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) {
1496 				if (ncd->is_new_component && ncd->dtstart > 0 && ncd->dtend > 0) {
1497 					ECalComponentDateTime *dt;
1498 					ICalTime *itt;
1499 					ICalTimezone *zone;
1500 
1501 					if (ncd->model)
1502 						zone = e_cal_model_get_timezone (ncd->model);
1503 					else
1504 						zone = calendar_config_get_icaltimezone ();
1505 
1506 					itt = i_cal_time_new_from_timet_with_zone (ncd->dtstart, FALSE, zone);
1507 					if (ncd->all_day) {
1508 						i_cal_time_set_time (itt, 0, 0, 0);
1509 						i_cal_time_set_is_date (itt, TRUE);
1510 					}
1511 
1512 					dt = e_cal_component_datetime_new_take (itt,
1513 						(ncd->all_day || !zone) ? NULL : g_strdup (i_cal_timezone_get_tzid (zone)));
1514 					e_cal_component_set_dtstart (ncd->comp, dt);
1515 					e_cal_component_datetime_free (dt);
1516 
1517 					itt = i_cal_time_new_from_timet_with_zone (ncd->dtend, FALSE, zone);
1518 					if (ncd->all_day) {
1519 						/* We round it up to the end of the day, unless it is
1520 						 * already set to midnight */
1521 						if (i_cal_time_get_hour (itt) != 0 ||
1522 						    i_cal_time_get_minute (itt) != 0 ||
1523 						    i_cal_time_get_second (itt) != 0) {
1524 							i_cal_time_adjust (itt, 1, 0, 0, 0);
1525 						}
1526 						i_cal_time_set_time (itt, 0, 0, 0);
1527 						i_cal_time_set_is_date (itt, TRUE);
1528 					}
1529 					dt = e_cal_component_datetime_new_take (itt,
1530 						(ncd->all_day || !zone) ? NULL : g_strdup (i_cal_timezone_get_tzid (zone)));
1531 					e_cal_component_set_dtend (ncd->comp, dt);
1532 					e_cal_component_datetime_free (dt);
1533 				}
1534 				e_cal_component_commit_sequence (ncd->comp);
1535 			}
1536 
1537 			comp_editor = e_comp_editor_open_for_component (NULL, ncd->shell,
1538 				ncd->client ? e_client_get_source (E_CLIENT (ncd->client)) : NULL,
1539 				e_cal_component_get_icalcomponent (ncd->comp), flags);
1540 
1541 			if (comp_editor) {
1542 				if (ncd->model) {
1543 					g_signal_connect (comp_editor, "object-created",
1544 						G_CALLBACK (cal_ops_emit_model_object_created), ncd->model);
1545 
1546 					g_object_set_data_full (G_OBJECT (comp_editor), "e-cal-ops-model", g_object_ref (ncd->model), g_object_unref);
1547 				}
1548 
1549 				gtk_window_present (GTK_WINDOW (comp_editor));
1550 			}
1551 		}
1552 
1553 		g_clear_object (&ncd->shell);
1554 		g_clear_object (&ncd->model);
1555 		g_clear_object (&ncd->default_source);
1556 		g_clear_object (&ncd->client);
1557 		g_clear_object (&ncd->comp);
1558 		g_free (ncd->extension_name);
1559 		g_free (ncd->for_client_uid);
1560 		g_slice_free (NewComponentData, ncd);
1561 	}
1562 }
1563 
1564 static void
cal_ops_new_component_editor_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)1565 cal_ops_new_component_editor_thread (EAlertSinkThreadJobData *job_data,
1566 				     gpointer user_data,
1567 				     GCancellable *cancellable,
1568 				     GError **error)
1569 {
1570 	NewComponentData *ncd = user_data;
1571 	GError *local_error = NULL;
1572 
1573 	g_return_if_fail (ncd != NULL);
1574 
1575 	if (ncd->for_client_uid) {
1576 		ncd->client = cal_ops_open_client_sync (job_data, ncd->shell, ncd->for_client_uid,
1577 			ncd->extension_name, cancellable, &local_error);
1578 	}
1579 
1580 	if (!ncd->default_source && !ncd->client && !ncd->for_client_uid) {
1581 		const gchar *message;
1582 
1583 		switch (ncd->source_type) {
1584 			case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1585 				message = _("Default calendar not found");
1586 				break;
1587 			case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1588 				message = _("Default memo list not found");
1589 				break;
1590 			case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1591 				message = _("Default task list not found");
1592 				break;
1593 			default:
1594 				g_warn_if_reached ();
1595 				return;
1596 		}
1597 
1598 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, message);
1599 		return;
1600 	}
1601 
1602 	if (!ncd->client && !ncd->for_client_uid) {
1603 		EClient *client;
1604 		EClientCache *client_cache;
1605 
1606 		client_cache = e_shell_get_client_cache (ncd->shell);
1607 
1608 		client = e_client_cache_get_client_sync (client_cache, ncd->default_source, ncd->extension_name, 30, cancellable, &local_error);
1609 		if (client)
1610 			ncd->client = E_CAL_CLIENT (client);
1611 	}
1612 
1613 	if (ncd->client) {
1614 		switch (ncd->source_type) {
1615 			case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1616 				ncd->comp = cal_comp_event_new_with_current_time_sync (ncd->client,
1617 					ncd->all_day, ncd->use_default_reminder, ncd->default_reminder_interval,
1618 					ncd->default_reminder_units, cancellable, &local_error);
1619 				break;
1620 			case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1621 				ncd->comp = cal_comp_memo_new_with_defaults_sync (ncd->client, cancellable, &local_error);
1622 				break;
1623 			case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1624 				ncd->comp = cal_comp_task_new_with_defaults_sync (ncd->client, cancellable, &local_error);
1625 				break;
1626 			default:
1627 				g_warn_if_reached ();
1628 				return;
1629 		}
1630 	}
1631 
1632 	e_util_propagate_open_source_job_error (job_data, ncd->extension_name, local_error, error);
1633 }
1634 
1635 static void
e_cal_ops_new_component_ex(EShellWindow * shell_window,ECalModel * model,ECalClientSourceType source_type,const gchar * for_client_uid,gboolean is_assigned,gboolean all_day,time_t dtstart,time_t dtend,gboolean use_default_reminder,gint default_reminder_interval,EDurationType default_reminder_units)1636 e_cal_ops_new_component_ex (EShellWindow *shell_window,
1637 			    ECalModel *model,
1638 			    ECalClientSourceType source_type,
1639 			    const gchar *for_client_uid,
1640 			    gboolean is_assigned,
1641 			    gboolean all_day,
1642 			    time_t dtstart,
1643 			    time_t dtend,
1644 			    gboolean use_default_reminder,
1645 			    gint default_reminder_interval,
1646 			    EDurationType default_reminder_units)
1647 {
1648 	ESourceRegistry *registry;
1649 	ESource *default_source, *for_client_source = NULL;
1650 	EShell *shell;
1651 	gchar *description = NULL, *alert_ident = NULL, *alert_arg_0 = NULL;
1652 	gchar *source_display_name = NULL;
1653 	const gchar *extension_name;
1654 	NewComponentData *ncd;
1655 
1656 	if (shell_window) {
1657 		g_return_if_fail (E_IS_SHELL_WINDOW (shell_window));
1658 
1659 		shell = e_shell_window_get_shell (shell_window);
1660 	} else {
1661 		g_return_if_fail (E_IS_CAL_MODEL (model));
1662 
1663 		shell = e_cal_model_get_shell (model);
1664 	}
1665 
1666 	registry = e_shell_get_registry (shell);
1667 
1668 	switch (source_type) {
1669 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1670 			extension_name = E_SOURCE_EXTENSION_CALENDAR;
1671 			default_source = e_source_registry_ref_default_calendar (registry);
1672 			break;
1673 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1674 			extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
1675 			default_source = e_source_registry_ref_default_memo_list (registry);
1676 			break;
1677 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1678 			extension_name = E_SOURCE_EXTENSION_TASK_LIST;
1679 			default_source = e_source_registry_ref_default_task_list (registry);
1680 			break;
1681 		default:
1682 			g_warn_if_reached ();
1683 			return;
1684 	}
1685 
1686 	if (for_client_uid)
1687 		for_client_source = e_source_registry_ref_source (registry, for_client_uid);
1688 
1689 	ncd = new_component_data_new ();
1690 	ncd->is_new_component = TRUE;
1691 	ncd->shell = g_object_ref (shell);
1692 	ncd->model = model ? g_object_ref (model) : NULL;
1693 	ncd->source_type = source_type;
1694 	ncd->for_client_uid = g_strdup (for_client_uid);
1695 	ncd->is_assigned = is_assigned;
1696 	ncd->extension_name = g_strdup (extension_name);
1697 	ncd->default_source = default_source ? g_object_ref (default_source) : NULL;
1698 	ncd->client = NULL;
1699 	ncd->comp = NULL;
1700 	ncd->dtstart = dtstart;
1701 	ncd->dtend = dtend;
1702 	ncd->all_day = all_day;
1703 	ncd->use_default_reminder = use_default_reminder;
1704 	ncd->default_reminder_interval = default_reminder_interval;
1705 	ncd->default_reminder_units = default_reminder_units;
1706 
1707 	if (for_client_source)
1708 		source_display_name = e_util_get_source_full_name (registry, for_client_source);
1709 	else if (default_source)
1710 		source_display_name = e_util_get_source_full_name (registry, default_source);
1711 
1712 	g_warn_if_fail (e_util_get_open_source_job_info (extension_name,
1713 		source_display_name ? source_display_name : "", &description, &alert_ident, &alert_arg_0));
1714 
1715 	if (shell_window) {
1716 		EShellView *shell_view;
1717 		EActivity *activity;
1718 
1719 		shell_view = e_shell_window_get_shell_view (shell_window,
1720 			e_shell_window_get_active_view (shell_window));
1721 
1722 		activity = e_shell_view_submit_thread_job (
1723 			shell_view, description, alert_ident, alert_arg_0,
1724 			cal_ops_new_component_editor_thread, ncd, new_component_data_free);
1725 
1726 		g_clear_object (&activity);
1727 	} else {
1728 		GCancellable *cancellable;
1729 		ECalDataModel *data_model;
1730 
1731 		data_model = e_cal_model_get_data_model (model);
1732 
1733 		cancellable = e_cal_data_model_submit_thread_job (data_model, description, alert_ident, alert_arg_0,
1734 			cal_ops_new_component_editor_thread, ncd, new_component_data_free);
1735 
1736 		g_clear_object (&cancellable);
1737 	}
1738 
1739 	g_clear_object (&default_source);
1740 	g_clear_object (&for_client_source);
1741 	g_free (source_display_name);
1742 	g_free (description);
1743 	g_free (alert_ident);
1744 	g_free (alert_arg_0);
1745 }
1746 
1747 /**
1748  * e_cal_ops_new_component_editor:
1749  * @shell_window: an #EShellWindow
1750  * @source_type: a source type of the new component
1751  * @for_client_uid: (allow none): a client UID to use for the new component; can be #NULL
1752  * @is_assigned: whether the new component should be assigned
1753  *
1754  * Creates a new component either for an #ECalClient with UID @for_client_uid, or
1755  * for a default source of the @source_type, with prefilled values as provided
1756  * by the #ECalClient. Use e_cal_ops_new_event_editor() for events with
1757  * predefined alarms.
1758  *
1759  * Since: 3.16
1760  **/
1761 void
e_cal_ops_new_component_editor(EShellWindow * shell_window,ECalClientSourceType source_type,const gchar * for_client_uid,gboolean is_assigned)1762 e_cal_ops_new_component_editor (EShellWindow *shell_window,
1763 				ECalClientSourceType source_type,
1764 				const gchar *for_client_uid,
1765 				gboolean is_assigned)
1766 {
1767 	e_cal_ops_new_component_ex (shell_window, NULL, source_type, for_client_uid, is_assigned, FALSE, 0, 0, FALSE, 0, E_DURATION_MINUTES);
1768 }
1769 
1770 /**
1771  * e_cal_ops_new_event_editor:
1772  * @shell_window: an #EShellWindow
1773  * @source_type: a source type of the new component
1774  * @for_client_uid: (allow none): a client UID to use for the new component; can be #NULL
1775  * @is_meeting: whether the new event should be a meeting
1776  * @all_day: whether the new event should be an all day event
1777  * @use_default_reminder: whether a default reminder should be added,
1778  *    if #FALSE, then the next two reminded arguments are ignored
1779  * @default_reminder_interval: reminder interval for the default reminder
1780  * @default_reminder_units: reminder uints for the default reminder
1781  * @dtstart: a time_t of DTSTART to use, or 0 to use the default value
1782  * @dtend: a time_t of DTEND to use, or 0 to use the default value
1783  *
1784  * This is a fine-grained version of e_cal_ops_new_component_editor(), suitable
1785  * for events with predefined alarms. The e_cal_ops_new_component_editor()
1786  * accepts events as well.
1787  *
1788  * The @dtend is ignored, when @dtstart is zero or a negative value.
1789  *
1790  * Since: 3.16
1791  **/
1792 void
e_cal_ops_new_event_editor(EShellWindow * shell_window,const gchar * for_client_uid,gboolean is_meeting,gboolean all_day,gboolean use_default_reminder,gint default_reminder_interval,EDurationType default_reminder_units,time_t dtstart,time_t dtend)1793 e_cal_ops_new_event_editor (EShellWindow *shell_window,
1794 			    const gchar *for_client_uid,
1795 			    gboolean is_meeting,
1796 			    gboolean all_day,
1797 			    gboolean use_default_reminder,
1798 			    gint default_reminder_interval,
1799 			    EDurationType default_reminder_units,
1800 			    time_t dtstart,
1801 			    time_t dtend)
1802 {
1803 	e_cal_ops_new_component_ex (shell_window, NULL, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, for_client_uid, is_meeting,
1804 		all_day, dtstart, dtstart <= 0 ? 0 : dtend, use_default_reminder, default_reminder_interval, default_reminder_units);
1805 }
1806 
1807 /**
1808  * e_cal_ops_new_component_editor_from_model:
1809  * @model: an #ECalModel
1810  * @for_client_uid: (allow none): a client UID to use for the new component; can be #NULL
1811  * @dtstart: a DTSTART to use, for events; less than or equal to 0 to ignore
1812  * @dtend: a DTEND to use, for events; less than or equal to 0 to ignore
1813  * @is_assigned: whether the new component should be assigned
1814  * @all_day: whether the new component should be an all day event
1815  *
1816  * Creates a new component either for an #ECalClient with UID @for_client_uid, or
1817  * for a default source of the source type as defined by @model, with prefilled
1818  * values as provided by the #ECalClient. The @all_day is used only for events
1819  * source type.
1820  *
1821  * Since: 3.16
1822  **/
1823 void
e_cal_ops_new_component_editor_from_model(ECalModel * model,const gchar * for_client_uid,time_t dtstart,time_t dtend,gboolean is_assigned,gboolean all_day)1824 e_cal_ops_new_component_editor_from_model (ECalModel *model,
1825 					   const gchar *for_client_uid,
1826 					   time_t dtstart,
1827 					   time_t dtend,
1828 					   gboolean is_assigned,
1829 					   gboolean all_day)
1830 {
1831 	ECalClientSourceType source_type;
1832 
1833 	g_return_if_fail (E_IS_CAL_MODEL (model));
1834 
1835 	switch (e_cal_model_get_component_kind (model)) {
1836 		case I_CAL_VEVENT_COMPONENT:
1837 			source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
1838 			break;
1839 		case I_CAL_VJOURNAL_COMPONENT:
1840 			source_type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS;
1841 			break;
1842 		case I_CAL_VTODO_COMPONENT:
1843 			source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
1844 			break;
1845 		default:
1846 			g_warn_if_reached ();
1847 			return;
1848 	}
1849 
1850 	if (!for_client_uid)
1851 		for_client_uid = e_cal_model_get_default_source_uid (model);
1852 
1853 	if (for_client_uid && !*for_client_uid)
1854 		for_client_uid = NULL;
1855 
1856 	e_cal_ops_new_component_ex (NULL, model, source_type, for_client_uid, is_assigned, all_day, dtstart, dtend,
1857 		e_cal_model_get_use_default_reminder (model),
1858 		e_cal_model_get_default_reminder_interval (model),
1859 		e_cal_model_get_default_reminder_units (model));
1860 }
1861 
1862 /**
1863  * e_cal_ops_open_component_in_editor_sync:
1864  * @model: (nullable): an #ECalModel instance
1865  * @client: an #ECalClient, to which the component belongs
1866  * @icomp: an #ICalComponent to open in an editor
1867  * @force_attendees: set to TRUE to force to show attendees, FALSE to auto-detect
1868  *
1869  * Opens a component @icomp, which belongs to a @client, in
1870  * a component editor. This is done synchronously.
1871  *
1872  * Since: 3.16
1873  **/
1874 void
e_cal_ops_open_component_in_editor_sync(ECalModel * model,ECalClient * client,ICalComponent * icomp,gboolean force_attendees)1875 e_cal_ops_open_component_in_editor_sync (ECalModel *model,
1876 					 ECalClient *client,
1877 					 ICalComponent *icomp,
1878 					 gboolean force_attendees)
1879 {
1880 	NewComponentData *ncd;
1881 	ECalComponent *comp;
1882 	ECompEditor *comp_editor;
1883 
1884 	if (model)
1885 		g_return_if_fail (E_IS_CAL_MODEL (model));
1886 	g_return_if_fail (E_IS_CAL_CLIENT (client));
1887 	g_return_if_fail (I_CAL_IS_COMPONENT (icomp));
1888 
1889 	comp_editor = e_comp_editor_find_existing_for (e_client_get_source (E_CLIENT (client)), icomp);
1890 	if (comp_editor) {
1891 		gtk_window_present (GTK_WINDOW (comp_editor));
1892 		return;
1893 	}
1894 
1895 	comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
1896 	g_return_if_fail (comp != NULL);
1897 
1898 	ncd = new_component_data_new ();
1899 	ncd->is_new_component = FALSE;
1900 	ncd->shell = g_object_ref (model ? e_cal_model_get_shell (model) : e_shell_get_default ());
1901 	ncd->model = model ? g_object_ref (model) : NULL;
1902 	ncd->source_type = e_cal_client_get_source_type (client);
1903 	ncd->is_assigned = force_attendees;
1904 	ncd->extension_name = NULL;
1905 	ncd->for_client_uid = NULL;
1906 	ncd->default_source = NULL;
1907 	ncd->client = g_object_ref (client);
1908 	ncd->comp = comp;
1909 
1910 	/* This opens the editor */
1911 	new_component_data_free (ncd);
1912 }
1913 
1914 typedef struct {
1915 	EShell *shell;
1916 	ECalModel *model;
1917 	ESource *destination;
1918 	ECalClient *destination_client;
1919 	ECalClientSourceType source_type;
1920 	GHashTable *icomps_by_source;
1921 	gboolean is_move;
1922 	gint nobjects;
1923 } TransferComponentsData;
1924 
1925 static void
transfer_components_free_icomps_slist(gpointer ptr)1926 transfer_components_free_icomps_slist (gpointer ptr)
1927 {
1928 	GSList *icomps = ptr;
1929 
1930 	g_slist_free_full (icomps, g_object_unref);
1931 }
1932 
1933 static void
transfer_components_data_free(gpointer ptr)1934 transfer_components_data_free (gpointer ptr)
1935 {
1936 	TransferComponentsData *tcd = ptr;
1937 
1938 	if (tcd) {
1939 		if (tcd->destination_client)
1940 			e_cal_model_emit_object_created (tcd->model, tcd->destination_client);
1941 
1942 		g_clear_object (&tcd->shell);
1943 		g_clear_object (&tcd->model);
1944 		g_clear_object (&tcd->destination);
1945 		g_clear_object (&tcd->destination_client);
1946 		g_hash_table_destroy (tcd->icomps_by_source);
1947 		g_slice_free (TransferComponentsData, tcd);
1948 	}
1949 }
1950 
1951 static void
transfer_components_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)1952 transfer_components_thread (EAlertSinkThreadJobData *job_data,
1953 			    gpointer user_data,
1954 			    GCancellable *cancellable,
1955 			    GError **error)
1956 {
1957 	TransferComponentsData *tcd = user_data;
1958 	const gchar *extension_name;
1959 	EClient *from_client = NULL, *to_client = NULL;
1960 	ECalClient *from_cal_client = NULL, *to_cal_client = NULL;
1961 	EClientCache *client_cache;
1962 	GHashTableIter iter;
1963 	gpointer key, value;
1964 	gint nobjects, ii = 0, last_percent = 0;
1965 	GSList *link;
1966 	gboolean success = TRUE;
1967 
1968 	g_return_if_fail (tcd != NULL);
1969 
1970 	switch (tcd->source_type) {
1971 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1972 			extension_name = E_SOURCE_EXTENSION_CALENDAR;
1973 			break;
1974 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1975 			extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
1976 			break;
1977 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1978 			extension_name = E_SOURCE_EXTENSION_TASK_LIST;
1979 			break;
1980 		default:
1981 			g_warn_if_reached ();
1982 			return;
1983 	}
1984 
1985 	client_cache = e_shell_get_client_cache (tcd->shell);
1986 
1987 	to_client = e_util_open_client_sync (job_data, client_cache, extension_name, tcd->destination, 30, cancellable, error);
1988 	if (!to_client)
1989 		goto out;
1990 
1991 	to_cal_client = E_CAL_CLIENT (to_client);
1992 
1993 	if (e_client_is_readonly (E_CLIENT (to_client))) {
1994 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_READ_ONLY, _("Destination is read only"));
1995 		goto out;
1996 	}
1997 
1998 	nobjects = tcd->nobjects;
1999 
2000 	g_hash_table_iter_init (&iter, tcd->icomps_by_source);
2001 	while (g_hash_table_iter_next (&iter, &key, &value)) {
2002 		ESource *source = key;
2003 		GSList *icomps = value;
2004 
2005 		from_client = e_util_open_client_sync (job_data, client_cache, extension_name, source, 30, cancellable, error);
2006 		if (!from_client) {
2007 			success = FALSE;
2008 			goto out;
2009 		}
2010 
2011 		from_cal_client = E_CAL_CLIENT (from_client);
2012 
2013 		for (link = icomps; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next (link), ii++) {
2014 			gint percent = 100 * (ii + 1) / nobjects;
2015 			ICalComponent *icomp = link->data;
2016 
2017 			if (!cal_comp_transfer_item_to_sync (from_cal_client, to_cal_client, icomp, !tcd->is_move, cancellable, error)) {
2018 				success = FALSE;
2019 				break;
2020 			}
2021 
2022 			if (percent != last_percent) {
2023 				camel_operation_progress (cancellable, percent);
2024 				last_percent = percent;
2025 			}
2026 		}
2027 
2028 		g_clear_object (&from_client);
2029 	}
2030 
2031 	if (success && ii > 0)
2032 		tcd->destination_client = E_CAL_CLIENT (g_object_ref (to_client));
2033 
2034  out:
2035 	g_clear_object (&from_client);
2036 	g_clear_object (&to_client);
2037 }
2038 
2039 /**
2040  * e_cal_ops_transfer_components:
2041  * @shell_view: an #EShellView
2042  * @model: an #ECalModel, where to notify about created objects
2043  * @source_type: a source type of the @destination and the sources
2044  * @icomps_by_source: a hash table of #ESource to #GSList of ICalComponent to transfer
2045  * @destination: a destination #ESource
2046  * @is_move: whether the transfer is move (%TRUE) or copy (%FALSE)
2047  *
2048  * Transfers (copies or moves, as set by @is_move) all @icomps_by_source from their source
2049  * to the @destination of type source type (calendar/memo list/task list).
2050  *
2051  * Since: 3.16
2052  **/
2053 void
e_cal_ops_transfer_components(EShellView * shell_view,ECalModel * model,ECalClientSourceType source_type,GHashTable * icomps_by_source,ESource * destination,gboolean is_move)2054 e_cal_ops_transfer_components (EShellView *shell_view,
2055 			       ECalModel *model,
2056 			       ECalClientSourceType source_type,
2057 			       GHashTable *icomps_by_source,
2058 			       ESource *destination,
2059 			       gboolean is_move)
2060 {
2061 	gint nobjects;
2062 	gchar *description, *display_name;
2063 	const gchar *alert_ident;
2064 	TransferComponentsData *tcd;
2065 	GHashTableIter iter;
2066 	gpointer key, value;
2067 	EActivity *activity;
2068 
2069 	g_return_if_fail (E_IS_SHELL_VIEW (shell_view));
2070 	g_return_if_fail (E_IS_CAL_MODEL (model));
2071 	g_return_if_fail (icomps_by_source != NULL);
2072 	g_return_if_fail (E_IS_SOURCE (destination));
2073 
2074 	nobjects = 0;
2075 	g_hash_table_iter_init (&iter, icomps_by_source);
2076 	while (g_hash_table_iter_next (&iter, &key, &value)) {
2077 		ESource *source = key;
2078 		GSList *icomps = value;
2079 
2080 		if (!is_move || !e_source_equal (source, destination))
2081 			nobjects += g_slist_length (icomps);
2082 	}
2083 
2084 	switch (source_type) {
2085 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
2086 			description = g_strdup_printf (is_move ?
2087 				ngettext ("Moving an event", "Moving %d events", nobjects) :
2088 				ngettext ("Copying an event", "Copying %d events", nobjects),
2089 				nobjects);
2090 			alert_ident = is_move ? "calendar:failed-move-event" : "calendar:failed-copy-event";
2091 			break;
2092 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
2093 			description = g_strdup_printf (is_move ?
2094 				ngettext ("Moving a memo", "Moving %d memos", nobjects) :
2095 				ngettext ("Copying a memo", "Copying %d memos", nobjects),
2096 				nobjects);
2097 			alert_ident = is_move ? "calendar:failed-move-memo" : "calendar:failed-copy-memo";
2098 			break;
2099 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
2100 			description = g_strdup_printf (is_move ?
2101 				ngettext ("Moving a task", "Moving %d tasks", nobjects) :
2102 				ngettext ("Copying a task", "Copying %d tasks", nobjects),
2103 				nobjects);
2104 			alert_ident = is_move ? "calendar:failed-move-task" : "calendar:failed-copy-task";
2105 			break;
2106 		default:
2107 			g_warn_if_reached ();
2108 			return;
2109 	}
2110 
2111 	tcd = g_slice_new0 (TransferComponentsData);
2112 	tcd->shell = g_object_ref (e_shell_window_get_shell (e_shell_view_get_shell_window (shell_view)));
2113 	tcd->model = g_object_ref (model);
2114 	tcd->icomps_by_source = g_hash_table_new_full ((GHashFunc) e_source_hash, (GEqualFunc) e_source_equal,
2115 		g_object_unref, transfer_components_free_icomps_slist);
2116 	tcd->destination = g_object_ref (destination);
2117 	tcd->source_type = source_type;
2118 	tcd->is_move = is_move;
2119 	tcd->nobjects = nobjects;
2120 	tcd->destination_client = NULL;
2121 
2122 	g_hash_table_iter_init (&iter, icomps_by_source);
2123 	while (g_hash_table_iter_next (&iter, &key, &value)) {
2124 		ESource *source = key;
2125 		GSList *icomps = value;
2126 
2127 		if (!is_move || !e_source_equal (source, destination)) {
2128 			GSList *link;
2129 
2130 			icomps = g_slist_copy (icomps);
2131 			for (link = icomps; link; link = g_slist_next (link)) {
2132 				link->data = i_cal_component_clone (link->data);
2133 			}
2134 
2135 			g_hash_table_insert (tcd->icomps_by_source, g_object_ref (source), icomps);
2136 		}
2137 	}
2138 
2139 	display_name = e_util_get_source_full_name (e_cal_model_get_registry (model), destination);
2140 	activity = e_shell_view_submit_thread_job (shell_view, description, alert_ident,
2141 		display_name, transfer_components_thread, tcd,
2142 		transfer_components_data_free);
2143 
2144 	g_clear_object (&activity);
2145 	g_free (display_name);
2146 	g_free (description);
2147 }
2148