1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		JP Rosevear  <jpr@ximian.com>
17  *	    Mike Kestner  <mkestner@ximian.com>
18  *
19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20  *
21  */
22 
23 #include "evolution-config.h"
24 
25 #include <gio/gio.h>
26 #include <glib/gi18n.h>
27 #include <libsoup/soup.h>
28 
29 #include <libecal/libecal.h>
30 #include <libebackend/libebackend.h>
31 
32 #include <shell/e-shell.h>
33 #include <e-util/e-util-enumtypes.h>
34 
35 #include "itip-utils.h"
36 #include "e-meeting-utils.h"
37 #include "e-meeting-attendee.h"
38 #include "e-meeting-store.h"
39 
40 #define ROW_VALID(store, row) \
41 	(row >= 0 && row < store->priv->attendees->len)
42 
43 #define E_MEETING_STORE_GET_PRIVATE(obj) \
44 	(G_TYPE_INSTANCE_GET_PRIVATE \
45 	((obj), E_TYPE_MEETING_STORE, EMeetingStorePrivate))
46 
47 struct _EMeetingStorePrivate {
48 	GPtrArray *attendees;
49 	gint stamp;
50 
51 	ECalClient *client;
52 	ICalTimezone *zone;
53 
54 	gint default_reminder_interval;
55 	EDurationType default_reminder_units;
56 
57 	gchar *fb_uri;
58 
59 	GPtrArray *refresh_queue;
60 	GHashTable *refresh_data;
61 	GMutex mutex;
62 	guint refresh_idle_id;
63 
64 	guint num_threads;
65 	guint num_queries;
66 
67 	gboolean show_address;
68 };
69 
70 #define BUF_SIZE 1024
71 
72 typedef struct _EMeetingStoreQueueData EMeetingStoreQueueData;
73 struct _EMeetingStoreQueueData {
74 	EMeetingStore *store;
75 	EMeetingAttendee *attendee;
76 
77 	gboolean refreshing;
78 
79 	EMeetingTime start;
80 	EMeetingTime end;
81 
82 	gchar buffer[BUF_SIZE];
83 	GString *string;
84 
85 	GPtrArray *call_backs;
86 	GPtrArray *data;
87 };
88 
89 enum {
90 	PROP_0,
91 	PROP_CLIENT,
92 	PROP_DEFAULT_REMINDER_INTERVAL,
93 	PROP_DEFAULT_REMINDER_UNITS,
94 	PROP_FREE_BUSY_TEMPLATE,
95 	PROP_SHOW_ADDRESS,
96 	PROP_TIMEZONE
97 };
98 
99 /* Forward Declarations */
100 static void ems_tree_model_init (GtkTreeModelIface *iface);
101 
G_DEFINE_TYPE_WITH_CODE(EMeetingStore,e_meeting_store,GTK_TYPE_LIST_STORE,G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE,NULL)G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,ems_tree_model_init))102 G_DEFINE_TYPE_WITH_CODE (
103 	EMeetingStore, e_meeting_store, GTK_TYPE_LIST_STORE,
104 	G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)
105 	G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, ems_tree_model_init))
106 
107 static ICalParameterCutype
108 text_to_type (const gchar *type)
109 {
110 	if (!e_util_utf8_strcasecmp (type, _("Individual")))
111 		return I_CAL_CUTYPE_INDIVIDUAL;
112 	else if (!e_util_utf8_strcasecmp (type, _("Group")))
113 		return I_CAL_CUTYPE_GROUP;
114 	else if (!e_util_utf8_strcasecmp (type, _("Resource")))
115 		return I_CAL_CUTYPE_RESOURCE;
116 	else if (!e_util_utf8_strcasecmp (type, _("Room")))
117 		return I_CAL_CUTYPE_ROOM;
118 	else
119 		return I_CAL_CUTYPE_NONE;
120 }
121 
122 static gchar *
type_to_text(ICalParameterCutype type)123 type_to_text (ICalParameterCutype type)
124 {
125 	switch (type) {
126 	case I_CAL_CUTYPE_INDIVIDUAL:
127 		return _("Individual");
128 	case I_CAL_CUTYPE_GROUP:
129 		return _("Group");
130 	case I_CAL_CUTYPE_RESOURCE:
131 		return _("Resource");
132 	case I_CAL_CUTYPE_ROOM:
133 		return _("Room");
134 	default:
135 		return _("Unknown");
136 	}
137 
138 	return NULL;
139 
140 }
141 
142 static ICalParameterRole
text_to_role(const gchar * role)143 text_to_role (const gchar *role)
144 {
145 	if (!e_util_utf8_strcasecmp (role, _("Chair")))
146 		return I_CAL_ROLE_CHAIR;
147 	else if (!e_util_utf8_strcasecmp (role, _("Required Participant")))
148 		return I_CAL_ROLE_REQPARTICIPANT;
149 	else if (!e_util_utf8_strcasecmp (role, _("Optional Participant")))
150 		return I_CAL_ROLE_OPTPARTICIPANT;
151 	else if (!e_util_utf8_strcasecmp (role, _("Non-Participant")))
152 		return I_CAL_ROLE_NONPARTICIPANT;
153 	else
154 		return I_CAL_ROLE_NONE;
155 }
156 
157 static gchar *
role_to_text(ICalParameterRole role)158 role_to_text (ICalParameterRole role)
159 {
160 	switch (role) {
161 	case I_CAL_ROLE_CHAIR:
162 		return _("Chair");
163 	case I_CAL_ROLE_REQPARTICIPANT:
164 		return _("Required Participant");
165 	case I_CAL_ROLE_OPTPARTICIPANT:
166 		return _("Optional Participant");
167 	case I_CAL_ROLE_NONPARTICIPANT:
168 		return _("Non-Participant");
169 	default:
170 		return _("Unknown");
171 	}
172 }
173 
174 static ICalParameterPartstat
text_to_partstat(const gchar * partstat)175 text_to_partstat (const gchar *partstat)
176 {
177 	if (!e_util_utf8_strcasecmp (partstat, _("Needs Action")))
178 		return I_CAL_PARTSTAT_NEEDSACTION;
179 	else if (!e_util_utf8_strcasecmp (partstat, _("Accepted")))
180 		return I_CAL_PARTSTAT_ACCEPTED;
181 	else if (!e_util_utf8_strcasecmp (partstat, _("Declined")))
182 		return I_CAL_PARTSTAT_DECLINED;
183 	else if (!e_util_utf8_strcasecmp (partstat, _("Tentative")))
184 		return I_CAL_PARTSTAT_TENTATIVE;
185 	else if (!e_util_utf8_strcasecmp (partstat, _("Delegated")))
186 		return I_CAL_PARTSTAT_DELEGATED;
187 	else if (!e_util_utf8_strcasecmp (partstat, _("Completed")))
188 		return I_CAL_PARTSTAT_COMPLETED;
189 	else if (!e_util_utf8_strcasecmp (partstat, _("In Process")))
190 		return I_CAL_PARTSTAT_INPROCESS;
191 	else
192 		return I_CAL_PARTSTAT_NONE;
193 }
194 
195 static gchar *
partstat_to_text(ICalParameterPartstat partstat)196 partstat_to_text (ICalParameterPartstat partstat)
197 {
198 	switch (partstat) {
199 	case I_CAL_PARTSTAT_NEEDSACTION:
200 		return _("Needs Action");
201 	case I_CAL_PARTSTAT_ACCEPTED:
202 		return _("Accepted");
203 	case I_CAL_PARTSTAT_DECLINED:
204 		return _("Declined");
205 	case I_CAL_PARTSTAT_TENTATIVE:
206 		return _("Tentative");
207 	case I_CAL_PARTSTAT_DELEGATED:
208 		return _("Delegated");
209 	case I_CAL_PARTSTAT_COMPLETED:
210 		return _("Completed");
211 	case I_CAL_PARTSTAT_INPROCESS:
212 		return _("In Process");
213 	case I_CAL_PARTSTAT_NONE:
214 	default:
215 		return _("Unknown");
216 	}
217 }
218 
219 static GtkTreeModelFlags
get_flags(GtkTreeModel * model)220 get_flags (GtkTreeModel *model)
221 {
222 	g_return_val_if_fail (E_IS_MEETING_STORE (model), 0);
223 
224 	return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
225 }
226 
227 static gint
get_n_columns(GtkTreeModel * model)228 get_n_columns (GtkTreeModel *model)
229 {
230 	g_return_val_if_fail (E_IS_MEETING_STORE (model), 0);
231 
232 	return E_MEETING_STORE_COLUMN_COUNT;
233 }
234 
235 static GType
get_column_type(GtkTreeModel * model,gint col)236 get_column_type (GtkTreeModel *model,
237                  gint col)
238 {
239 	g_return_val_if_fail (E_IS_MEETING_STORE (model), G_TYPE_INVALID);
240 
241 	switch (col) {
242 	case E_MEETING_STORE_ADDRESS_COL:
243 	case E_MEETING_STORE_MEMBER_COL:
244 	case E_MEETING_STORE_TYPE_COL:
245 	case E_MEETING_STORE_ROLE_COL:
246 	case E_MEETING_STORE_DELTO_COL:
247 	case E_MEETING_STORE_DELFROM_COL:
248 	case E_MEETING_STORE_STATUS_COL:
249 	case E_MEETING_STORE_CN_COL:
250 	case E_MEETING_STORE_LANGUAGE_COL:
251 	case E_MEETING_STORE_ATTENDEE_COL:
252 		return G_TYPE_STRING;
253 	case E_MEETING_STORE_RSVP_COL:
254 		return G_TYPE_BOOLEAN;
255 	case E_MEETING_STORE_ATTENDEE_UNDERLINE_COL:
256 		return PANGO_TYPE_UNDERLINE;
257 	default:
258 		return G_TYPE_INVALID;
259 	}
260 }
261 
262 static gboolean
get_iter(GtkTreeModel * model,GtkTreeIter * iter,GtkTreePath * path)263 get_iter (GtkTreeModel *model,
264           GtkTreeIter *iter,
265           GtkTreePath *path)
266 {
267 	gint row;
268 
269 	g_return_val_if_fail (E_IS_MEETING_STORE (model), FALSE);
270 	g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
271 
272 	row = gtk_tree_path_get_indices (path)[0];
273 
274 	if (!ROW_VALID (E_MEETING_STORE (model), row))
275 	       return FALSE;
276 
277 	iter->stamp = E_MEETING_STORE (model)->priv->stamp;
278 	iter->user_data = GINT_TO_POINTER (row);
279 
280 	return TRUE;
281 }
282 
283 static GtkTreePath *
get_path(GtkTreeModel * model,GtkTreeIter * iter)284 get_path (GtkTreeModel *model,
285           GtkTreeIter *iter)
286 {
287 	gint row;
288 	GtkTreePath *result;
289 
290 	g_return_val_if_fail (E_IS_MEETING_STORE (model), NULL);
291 	g_return_val_if_fail (iter->stamp == E_MEETING_STORE (model)->priv->stamp, NULL);
292 
293 	row = GPOINTER_TO_INT (iter->user_data);
294 
295 	g_return_val_if_fail (ROW_VALID (E_MEETING_STORE (model), row), NULL);
296 
297 	result = gtk_tree_path_new ();
298 	gtk_tree_path_append_index (result, row);
299 	return result;
300 }
301 
302 static void
get_value(GtkTreeModel * model,GtkTreeIter * iter,gint col,GValue * value)303 get_value (GtkTreeModel *model,
304            GtkTreeIter *iter,
305            gint col,
306            GValue *value)
307 {
308 	EMeetingStore *store;
309 	EMeetingAttendee *attendee;
310 	const gchar *cn;
311 	gint row;
312 
313 	g_return_if_fail (E_IS_MEETING_STORE (model));
314 	g_return_if_fail (col >= 0 && col < E_MEETING_STORE_COLUMN_COUNT);
315 
316 	row = GPOINTER_TO_INT (iter->user_data);
317 	store = E_MEETING_STORE (model);
318 
319 	g_return_if_fail (iter->stamp == store->priv->stamp);
320 	g_return_if_fail (ROW_VALID (E_MEETING_STORE (model), row));
321 
322 	attendee = g_ptr_array_index (store->priv->attendees, row);
323 
324 	switch (col) {
325 	case E_MEETING_STORE_ADDRESS_COL:
326 		g_value_init (value, G_TYPE_STRING);
327 		g_value_set_string (
328 			value, itip_strip_mailto (
329 			e_meeting_attendee_get_address (attendee)));
330 		break;
331 	case E_MEETING_STORE_MEMBER_COL:
332 		g_value_init (value, G_TYPE_STRING);
333 		g_value_set_string (
334 			value, e_meeting_attendee_get_member (attendee));
335 		break;
336 	case E_MEETING_STORE_TYPE_COL:
337 		g_value_init (value, G_TYPE_STRING);
338 		g_value_set_string (
339 			value, type_to_text (
340 			e_meeting_attendee_get_cutype (attendee)));
341 		break;
342 	case E_MEETING_STORE_ROLE_COL:
343 		g_value_init (value, G_TYPE_STRING);
344 		g_value_set_string (
345 			value, role_to_text (
346 			e_meeting_attendee_get_role (attendee)));
347 		break;
348 	case E_MEETING_STORE_RSVP_COL:
349 		g_value_init (value, G_TYPE_BOOLEAN);
350 		g_value_set_boolean (
351 			value, e_meeting_attendee_get_rsvp (attendee));
352 		break;
353 	case E_MEETING_STORE_DELTO_COL:
354 		g_value_init (value, G_TYPE_STRING);
355 		g_value_set_string (
356 			value, itip_strip_mailto (
357 			e_meeting_attendee_get_delto (attendee)));
358 		break;
359 	case E_MEETING_STORE_DELFROM_COL:
360 		g_value_init (value, G_TYPE_STRING);
361 		g_value_set_string (
362 			value, itip_strip_mailto (
363 			e_meeting_attendee_get_delfrom (attendee)));
364 		break;
365 	case E_MEETING_STORE_STATUS_COL:
366 		g_value_init (value, G_TYPE_STRING);
367 		g_value_set_string (
368 			value, partstat_to_text (
369 			e_meeting_attendee_get_partstat (attendee)));
370 		break;
371 	case E_MEETING_STORE_CN_COL:
372 		g_value_init (value, G_TYPE_STRING);
373 		g_value_set_string (
374 			value, e_meeting_attendee_get_cn (attendee));
375 		break;
376 	case E_MEETING_STORE_LANGUAGE_COL:
377 		g_value_init (value, G_TYPE_STRING);
378 		g_value_set_string (
379 			value, e_meeting_attendee_get_language (attendee));
380 		break;
381 	case E_MEETING_STORE_ATTENDEE_COL:
382 		g_value_init (value, G_TYPE_STRING);
383 		cn = e_meeting_attendee_get_cn (attendee);
384 		if (cn && *cn) {
385 			if (e_meeting_store_get_show_address (store) ||
386 			    e_meeting_attendee_get_show_address (attendee)) {
387 				const gchar *email = itip_strip_mailto (e_meeting_attendee_get_address (attendee));
388 
389 				if (email && *email) {
390 					g_value_take_string (value, camel_internet_address_format_address (cn, email));
391 				} else {
392 					g_value_set_string (value, cn);
393 				}
394 			} else {
395 				g_value_set_string (value, cn);
396 			}
397 		} else {
398 			g_value_set_string (
399 				value, itip_strip_mailto (
400 				e_meeting_attendee_get_address (attendee)));
401 		}
402 		break;
403 	case E_MEETING_STORE_ATTENDEE_UNDERLINE_COL:
404 		cn = e_meeting_attendee_get_cn (attendee);
405 		g_value_init (value, PANGO_TYPE_UNDERLINE);
406 		g_value_set_enum (
407 			value, (!cn || !*cn) ?
408 			PANGO_UNDERLINE_NONE : PANGO_UNDERLINE_SINGLE);
409 	}
410 }
411 
412 static gboolean
iter_next(GtkTreeModel * model,GtkTreeIter * iter)413 iter_next (GtkTreeModel *model,
414            GtkTreeIter *iter)
415 {
416 	gint row;
417 
418 	g_return_val_if_fail (E_IS_MEETING_STORE (model), FALSE);
419 	g_return_val_if_fail (iter->stamp == E_MEETING_STORE (model)->priv->stamp, FALSE);
420 
421 	row = GPOINTER_TO_INT (iter->user_data) + 1;
422 	iter->user_data = GINT_TO_POINTER (row);
423 
424 	return ROW_VALID (E_MEETING_STORE (model), row);
425 }
426 
427 static gboolean
iter_children(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent)428 iter_children (GtkTreeModel *model,
429                GtkTreeIter *iter,
430                GtkTreeIter *parent)
431 {
432 	EMeetingStore *store;
433 
434 	g_return_val_if_fail (E_IS_MEETING_STORE (model), FALSE);
435 
436 	store = E_MEETING_STORE (model);
437 
438 	if (parent || store->priv->attendees->len <= 0)
439 		return FALSE;
440 
441 	iter->stamp = store->priv->stamp;
442 	iter->user_data = GINT_TO_POINTER (0);
443 
444 	return TRUE;
445 }
446 
447 static gboolean
iter_has_child(GtkTreeModel * model,GtkTreeIter * iter)448 iter_has_child (GtkTreeModel *model,
449                 GtkTreeIter *iter)
450 {
451 	return FALSE;
452 }
453 
454 static gint
iter_n_children(GtkTreeModel * model,GtkTreeIter * iter)455 iter_n_children (GtkTreeModel *model,
456                  GtkTreeIter *iter)
457 {
458 	g_return_val_if_fail (E_IS_MEETING_STORE (model), -1);
459 
460 	if (!iter)
461 		return E_MEETING_STORE (model)->priv->attendees->len;
462 
463 	g_return_val_if_fail (iter->stamp == E_MEETING_STORE (model)->priv->stamp, -1);
464 
465 	return 0;
466 }
467 
468 static gboolean
iter_nth_child(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent,gint n)469 iter_nth_child (GtkTreeModel *model,
470                 GtkTreeIter *iter,
471                 GtkTreeIter *parent,
472                 gint n)
473 {
474 	EMeetingStore *store;
475 
476 	g_return_val_if_fail (E_IS_MEETING_STORE (model), FALSE);
477 
478 	store = E_MEETING_STORE (model);
479 
480 	if (parent || !ROW_VALID (store, n))
481 		return FALSE;
482 
483 	iter->stamp = store->priv->stamp;
484 	iter->user_data = GINT_TO_POINTER (n);
485 
486 	return TRUE;
487 }
488 
489 static gboolean
iter_parent(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * child)490 iter_parent (GtkTreeModel *model,
491              GtkTreeIter *iter,
492              GtkTreeIter *child)
493 {
494 	return FALSE;
495 }
496 
497 static void
ems_tree_model_init(GtkTreeModelIface * iface)498 ems_tree_model_init (GtkTreeModelIface *iface)
499 {
500 	iface->get_flags = get_flags;
501 	iface->get_n_columns = get_n_columns;
502 	iface->get_column_type = get_column_type;
503 	iface->get_iter = get_iter;
504 	iface->get_path = get_path;
505 	iface->get_value = get_value;
506 	iface->iter_next = iter_next;
507 	iface->iter_children = iter_children;
508 	iface->iter_has_child = iter_has_child;
509 	iface->iter_n_children = iter_n_children;
510 	iface->iter_nth_child = iter_nth_child;
511 	iface->iter_parent = iter_parent;
512 }
513 
514 void
e_meeting_store_set_value(EMeetingStore * store,gint row,gint col,const gchar * val)515 e_meeting_store_set_value (EMeetingStore *store,
516                            gint row,
517                            gint col,
518                            const gchar *val)
519 {
520 	ICalParameterCutype cutype;
521 	EMeetingAttendee *attendee = g_ptr_array_index (store->priv->attendees, row);
522 
523 	switch (col) {
524 	case E_MEETING_STORE_ADDRESS_COL:
525 		if (val != NULL && *((gchar *) val)) {
526 			gchar *mailto;
527 
528 			mailto = g_strdup_printf ("mailto:%s", (const gchar *) val);
529 			e_meeting_attendee_set_address (attendee, mailto);
530 			g_free (mailto);
531 		}
532 		break;
533 	case E_MEETING_STORE_MEMBER_COL:
534 		e_meeting_attendee_set_member (attendee, val);
535 		break;
536 	case E_MEETING_STORE_TYPE_COL:
537 		cutype = text_to_type (val);
538 		e_meeting_attendee_set_cutype (attendee, cutype);
539 		if (cutype == I_CAL_CUTYPE_RESOURCE) {
540 			e_meeting_attendee_set_role (attendee, I_CAL_ROLE_NONPARTICIPANT);
541 		}
542 		break;
543 	case E_MEETING_STORE_ROLE_COL:
544 		e_meeting_attendee_set_role (attendee, text_to_role (val));
545 		break;
546 	case E_MEETING_STORE_RSVP_COL:
547 		e_meeting_attendee_set_rsvp (attendee, val != NULL);
548 		break;
549 	case E_MEETING_STORE_DELTO_COL:
550 		e_meeting_attendee_set_delto (attendee, val);
551 		break;
552 	case E_MEETING_STORE_DELFROM_COL:
553 		e_meeting_attendee_set_delfrom (attendee, val);
554 		break;
555 	case E_MEETING_STORE_STATUS_COL:
556 		e_meeting_attendee_set_partstat (attendee, text_to_partstat (val));
557 		break;
558 	case E_MEETING_STORE_CN_COL:
559 		e_meeting_attendee_set_cn (attendee, val);
560 		break;
561 	case E_MEETING_STORE_LANGUAGE_COL:
562 		e_meeting_attendee_set_language (attendee, val);
563 		break;
564 	}
565 }
566 
567 struct FindAttendeeData
568 {
569 	EMeetingAttendee *find;
570 	EMeetingStoreQueueData *qdata;
571 };
572 
573 static void
find_attendee_cb(gpointer key,gpointer value,gpointer user_data)574 find_attendee_cb (gpointer key,
575                   gpointer value,
576                   gpointer user_data)
577 {
578 	EMeetingStoreQueueData *qdata = value;
579 	struct FindAttendeeData *fad = user_data;
580 
581 	g_return_if_fail (qdata != NULL);
582 	g_return_if_fail (fad != NULL);
583 
584 	if (qdata->attendee == fad->find)
585 		fad->qdata = qdata;
586 }
587 
588 static void
refresh_queue_remove(EMeetingStore * store,EMeetingAttendee * attendee)589 refresh_queue_remove (EMeetingStore *store,
590                       EMeetingAttendee *attendee)
591 {
592 	EMeetingStorePrivate *priv;
593 	EMeetingStoreQueueData *qdata;
594 
595 	priv = store->priv;
596 
597 	/* Free the queue data */
598 	qdata = g_hash_table_lookup (
599 		priv->refresh_data, itip_strip_mailto (
600 		e_meeting_attendee_get_address (attendee)));
601 	if (!qdata) {
602 		struct FindAttendeeData fad = { 0 };
603 
604 		fad.find = attendee;
605 		fad.qdata = NULL;
606 
607 		g_hash_table_foreach (priv->refresh_data, find_attendee_cb, &fad);
608 
609 		qdata = fad.qdata;
610 	}
611 
612 	if (qdata) {
613 		g_mutex_lock (&priv->mutex);
614 		g_hash_table_remove (
615 			priv->refresh_data, itip_strip_mailto (
616 			e_meeting_attendee_get_address (attendee)));
617 		g_mutex_unlock (&priv->mutex);
618 		g_ptr_array_free (qdata->call_backs, TRUE);
619 		g_ptr_array_free (qdata->data, TRUE);
620 		g_string_free (qdata->string, TRUE);
621 		g_free (qdata);
622 	}
623 
624 	/* Unref the attendee */
625 	g_ptr_array_remove (priv->refresh_queue, attendee);
626 	g_object_unref (attendee);
627 }
628 
629 static void
meeting_store_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)630 meeting_store_set_property (GObject *object,
631                             guint property_id,
632                             const GValue *value,
633                             GParamSpec *pspec)
634 {
635 	switch (property_id) {
636 		case PROP_CLIENT:
637 			e_meeting_store_set_client (
638 				E_MEETING_STORE (object),
639 				g_value_get_object (value));
640 			return;
641 
642 		case PROP_DEFAULT_REMINDER_INTERVAL:
643 			e_meeting_store_set_default_reminder_interval (
644 				E_MEETING_STORE (object),
645 				g_value_get_int (value));
646 			return;
647 
648 		case PROP_DEFAULT_REMINDER_UNITS:
649 			e_meeting_store_set_default_reminder_units (
650 				E_MEETING_STORE (object),
651 				g_value_get_enum (value));
652 			return;
653 
654 		case PROP_FREE_BUSY_TEMPLATE:
655 			e_meeting_store_set_free_busy_template (
656 				E_MEETING_STORE (object),
657 				g_value_get_string (value));
658 			return;
659 
660 		case PROP_SHOW_ADDRESS:
661 			e_meeting_store_set_show_address (
662 				E_MEETING_STORE (object),
663 				g_value_get_boolean (value));
664 			return;
665 
666 		case PROP_TIMEZONE:
667 			e_meeting_store_set_timezone (
668 				E_MEETING_STORE (object),
669 				g_value_get_object (value));
670 			return;
671 	}
672 
673 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
674 }
675 
676 static void
meeting_store_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)677 meeting_store_get_property (GObject *object,
678                             guint property_id,
679                             GValue *value,
680                             GParamSpec *pspec)
681 {
682 	switch (property_id) {
683 		case PROP_CLIENT:
684 			g_value_set_object (
685 				value,
686 				e_meeting_store_get_client (
687 				E_MEETING_STORE (object)));
688 			return;
689 
690 		case PROP_DEFAULT_REMINDER_INTERVAL:
691 			g_value_set_int (
692 				value,
693 				e_meeting_store_get_default_reminder_interval (
694 				E_MEETING_STORE (object)));
695 			return;
696 
697 		case PROP_DEFAULT_REMINDER_UNITS:
698 			g_value_set_enum (
699 				value,
700 				e_meeting_store_get_default_reminder_units (
701 				E_MEETING_STORE (object)));
702 			return;
703 
704 		case PROP_FREE_BUSY_TEMPLATE:
705 			g_value_set_string (
706 				value,
707 				e_meeting_store_get_free_busy_template (
708 				E_MEETING_STORE (object)));
709 			return;
710 
711 		case PROP_SHOW_ADDRESS:
712 			g_value_set_boolean (
713 				value,
714 				e_meeting_store_get_show_address (
715 				E_MEETING_STORE (object)));
716 			return;
717 
718 		case PROP_TIMEZONE:
719 			g_value_set_object (
720 				value,
721 				e_meeting_store_get_timezone (
722 				E_MEETING_STORE (object)));
723 			return;
724 	}
725 
726 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
727 }
728 
729 static void
meeting_store_constructed(GObject * object)730 meeting_store_constructed (GObject *object)
731 {
732 	/* Chain up to parent's method. */
733 	G_OBJECT_CLASS (e_meeting_store_parent_class)->constructed (object);
734 
735 	e_extensible_load_extensions (E_EXTENSIBLE (object));
736 }
737 
738 static void
meeting_store_finalize(GObject * object)739 meeting_store_finalize (GObject *object)
740 {
741 	EMeetingStorePrivate *priv;
742 	gint i;
743 
744 	priv = E_MEETING_STORE_GET_PRIVATE (object);
745 
746 	for (i = 0; i < priv->attendees->len; i++)
747 		g_object_unref (g_ptr_array_index (priv->attendees, i));
748 	g_ptr_array_free (priv->attendees, TRUE);
749 
750 	if (priv->client != NULL)
751 		g_object_unref (priv->client);
752 
753 	while (priv->refresh_queue->len > 0)
754 		refresh_queue_remove (
755 			E_MEETING_STORE (object),
756 			g_ptr_array_index (priv->refresh_queue, 0));
757 	g_ptr_array_free (priv->refresh_queue, TRUE);
758 	g_hash_table_destroy (priv->refresh_data);
759 
760 	if (priv->refresh_idle_id)
761 		g_source_remove (priv->refresh_idle_id);
762 
763 	g_free (priv->fb_uri);
764 
765 	g_clear_object (&priv->zone);
766 
767 	g_mutex_clear (&priv->mutex);
768 
769 	/* Chain up to parent's finalize() method. */
770 	G_OBJECT_CLASS (e_meeting_store_parent_class)->finalize (object);
771 }
772 
773 static void
e_meeting_store_class_init(EMeetingStoreClass * class)774 e_meeting_store_class_init (EMeetingStoreClass *class)
775 {
776 	GObjectClass *object_class;
777 
778 	g_type_class_add_private (class, sizeof (EMeetingStorePrivate));
779 
780 	object_class = G_OBJECT_CLASS (class);
781 	object_class->set_property = meeting_store_set_property;
782 	object_class->get_property = meeting_store_get_property;
783 	object_class->constructed = meeting_store_constructed;
784 	object_class->finalize = meeting_store_finalize;
785 
786 	g_object_class_install_property (
787 		object_class,
788 		PROP_CLIENT,
789 		g_param_spec_object (
790 			"client",
791 			"ECalClient",
792 			NULL,
793 			E_TYPE_CAL_CLIENT,
794 			G_PARAM_READWRITE));
795 
796 	g_object_class_install_property (
797 		object_class,
798 		PROP_DEFAULT_REMINDER_INTERVAL,
799 		g_param_spec_int (
800 			"default-reminder-interval",
801 			"Default Reminder Interval",
802 			NULL,
803 			G_MININT,
804 			G_MAXINT,
805 			0,
806 			G_PARAM_READWRITE));
807 
808 	g_object_class_install_property (
809 		object_class,
810 		PROP_DEFAULT_REMINDER_UNITS,
811 		g_param_spec_enum (
812 			"default-reminder-units",
813 			"Default Reminder Units",
814 			NULL,
815 			E_TYPE_DURATION_TYPE,
816 			E_DURATION_MINUTES,
817 			G_PARAM_READWRITE));
818 
819 	g_object_class_install_property (
820 		object_class,
821 		PROP_FREE_BUSY_TEMPLATE,
822 		g_param_spec_string (
823 			"free-busy-template",
824 			"Free/Busy Template",
825 			NULL,
826 			NULL,
827 			G_PARAM_READWRITE));
828 
829 	g_object_class_install_property (
830 		object_class,
831 		PROP_SHOW_ADDRESS,
832 		g_param_spec_boolean (
833 			"show-address",
834 			"Show email addresses",
835 			NULL,
836 			FALSE,
837 			G_PARAM_READWRITE |
838 			G_PARAM_CONSTRUCT));
839 
840 	g_object_class_install_property (
841 		object_class,
842 		PROP_TIMEZONE,
843 		g_param_spec_object (
844 			"timezone",
845 			"Timezone",
846 			NULL,
847 			I_CAL_TYPE_TIMEZONE,
848 			G_PARAM_READWRITE));
849 }
850 
851 static void
e_meeting_store_init(EMeetingStore * store)852 e_meeting_store_init (EMeetingStore *store)
853 {
854 	store->priv = E_MEETING_STORE_GET_PRIVATE (store);
855 
856 	store->priv->attendees = g_ptr_array_new ();
857 	store->priv->refresh_queue = g_ptr_array_new ();
858 	store->priv->refresh_data = g_hash_table_new_full (
859 		g_str_hash, g_str_equal, g_free, NULL);
860 
861 	g_mutex_init (&store->priv->mutex);
862 
863 	store->priv->num_queries = 0;
864 }
865 
866 GObject *
e_meeting_store_new(void)867 e_meeting_store_new (void)
868 {
869 	return g_object_new (E_TYPE_MEETING_STORE, NULL);
870 }
871 
872 ECalClient *
e_meeting_store_get_client(EMeetingStore * store)873 e_meeting_store_get_client (EMeetingStore *store)
874 {
875 	g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);
876 
877 	return store->priv->client;
878 }
879 
880 void
e_meeting_store_set_client(EMeetingStore * store,ECalClient * client)881 e_meeting_store_set_client (EMeetingStore *store,
882                             ECalClient *client)
883 {
884 	g_return_if_fail (E_IS_MEETING_STORE (store));
885 
886 	if (store->priv->client == client)
887 		return;
888 
889 	if (client != NULL) {
890 		g_return_if_fail (E_IS_CAL_CLIENT (client));
891 		g_object_ref (client);
892 	}
893 
894 	if (store->priv->client != NULL)
895 		g_object_unref (store->priv->client);
896 
897 	store->priv->client = client;
898 
899 	g_object_notify (G_OBJECT (store), "client");
900 }
901 
902 gint
e_meeting_store_get_default_reminder_interval(EMeetingStore * store)903 e_meeting_store_get_default_reminder_interval (EMeetingStore *store)
904 {
905 	g_return_val_if_fail (E_IS_MEETING_STORE (store), 0);
906 
907 	return store->priv->default_reminder_interval;
908 }
909 
910 void
e_meeting_store_set_default_reminder_interval(EMeetingStore * store,gint default_reminder_interval)911 e_meeting_store_set_default_reminder_interval (EMeetingStore *store,
912                                                gint default_reminder_interval)
913 {
914 	g_return_if_fail (E_IS_MEETING_STORE (store));
915 
916 	if (store->priv->default_reminder_interval == default_reminder_interval)
917 		return;
918 
919 	store->priv->default_reminder_interval = default_reminder_interval;
920 
921 	g_object_notify (G_OBJECT (store), "default-reminder-interval");
922 }
923 
924 EDurationType
e_meeting_store_get_default_reminder_units(EMeetingStore * store)925 e_meeting_store_get_default_reminder_units (EMeetingStore *store)
926 {
927 	g_return_val_if_fail (E_IS_MEETING_STORE (store), 0);
928 
929 	return store->priv->default_reminder_units;
930 }
931 
932 void
e_meeting_store_set_default_reminder_units(EMeetingStore * store,EDurationType default_reminder_units)933 e_meeting_store_set_default_reminder_units (EMeetingStore *store,
934                                             EDurationType default_reminder_units)
935 {
936 	g_return_if_fail (E_IS_MEETING_STORE (store));
937 
938 	if (store->priv->default_reminder_units == default_reminder_units)
939 		return;
940 
941 	store->priv->default_reminder_units = default_reminder_units;
942 
943 	g_object_notify (G_OBJECT (store), "default-reminder-units");
944 }
945 
946 const gchar *
e_meeting_store_get_free_busy_template(EMeetingStore * store)947 e_meeting_store_get_free_busy_template (EMeetingStore *store)
948 {
949 	g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);
950 
951 	return store->priv->fb_uri;
952 }
953 
954 void
e_meeting_store_set_free_busy_template(EMeetingStore * store,const gchar * free_busy_template)955 e_meeting_store_set_free_busy_template (EMeetingStore *store,
956                                         const gchar *free_busy_template)
957 {
958 	g_return_if_fail (E_IS_MEETING_STORE (store));
959 
960 	if (g_strcmp0 (store->priv->fb_uri, free_busy_template) == 0)
961 		return;
962 
963 	g_free (store->priv->fb_uri);
964 	store->priv->fb_uri = g_strdup (free_busy_template);
965 
966 	g_object_notify (G_OBJECT (store), "free-busy-template");
967 }
968 
969 ICalTimezone *
e_meeting_store_get_timezone(EMeetingStore * store)970 e_meeting_store_get_timezone (EMeetingStore *store)
971 {
972 	g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);
973 
974 	return store->priv->zone;
975 }
976 
977 void
e_meeting_store_set_timezone(EMeetingStore * store,const ICalTimezone * timezone)978 e_meeting_store_set_timezone (EMeetingStore *store,
979 			      const ICalTimezone *timezone)
980 {
981 	g_return_if_fail (E_IS_MEETING_STORE (store));
982 
983 	if (store->priv->zone == timezone)
984 		return;
985 
986 	g_clear_object (&store->priv->zone);
987 	store->priv->zone = e_cal_util_copy_timezone (timezone);
988 
989 	g_object_notify (G_OBJECT (store), "timezone");
990 }
991 
992 gboolean
e_meeting_store_get_show_address(EMeetingStore * store)993 e_meeting_store_get_show_address (EMeetingStore *store)
994 {
995 	g_return_val_if_fail (E_IS_MEETING_STORE (store), FALSE);
996 
997 	return store->priv->show_address;
998 }
999 
1000 void
e_meeting_store_set_show_address(EMeetingStore * store,gboolean show_address)1001 e_meeting_store_set_show_address (EMeetingStore *store,
1002 				  gboolean show_address)
1003 {
1004 	g_return_if_fail (E_IS_MEETING_STORE (store));
1005 
1006 	if ((store->priv->show_address ? 1 : 0) == (show_address ? 1 : 0))
1007 		return;
1008 
1009 	store->priv->show_address = show_address;
1010 
1011 	g_object_notify (G_OBJECT (store), "show-address");
1012 }
1013 
1014 static void
attendee_changed_cb(EMeetingAttendee * attendee,gpointer data)1015 attendee_changed_cb (EMeetingAttendee *attendee,
1016                      gpointer data)
1017 {
1018 	EMeetingStore *store = E_MEETING_STORE (data);
1019 	GtkTreePath *path;
1020 	GtkTreeIter iter;
1021 	gint row = -1, i;
1022 
1023 	for (i = 0; i < store->priv->attendees->len; i++) {
1024 		if (attendee == g_ptr_array_index (store->priv->attendees, i)) {
1025 			row = i;
1026 			break;
1027 		}
1028 	}
1029 
1030 	if (row == -1)
1031 		return;
1032 
1033 	path = gtk_tree_path_new ();
1034 	gtk_tree_path_append_index (path, row);
1035 	get_iter (GTK_TREE_MODEL (store), &iter, path);
1036 	gtk_tree_model_row_changed (GTK_TREE_MODEL (store), path, &iter);
1037 	gtk_tree_path_free (path);
1038 }
1039 
1040 void
e_meeting_store_add_attendee(EMeetingStore * store,EMeetingAttendee * attendee)1041 e_meeting_store_add_attendee (EMeetingStore *store,
1042                               EMeetingAttendee *attendee)
1043 {
1044 	GtkTreePath *path;
1045 	GtkTreeIter iter;
1046 
1047 	g_return_if_fail (E_IS_MEETING_STORE (store));
1048 
1049 	g_object_ref (attendee);
1050 	g_ptr_array_add (store->priv->attendees, attendee);
1051 
1052 	g_signal_connect (
1053 		attendee, "changed",
1054 		G_CALLBACK (attendee_changed_cb), store);
1055 
1056 	path = gtk_tree_path_new ();
1057 	gtk_tree_path_append_index (path, store->priv->attendees->len - 1);
1058 	get_iter (GTK_TREE_MODEL (store), &iter, path);
1059 	gtk_tree_model_row_inserted (GTK_TREE_MODEL (store), path, &iter);
1060 	gtk_tree_path_free (path);
1061 }
1062 
1063 EMeetingAttendee *
e_meeting_store_add_attendee_with_defaults(EMeetingStore * store)1064 e_meeting_store_add_attendee_with_defaults (EMeetingStore *store)
1065 {
1066 	EMeetingAttendee *attendee;
1067 
1068 	attendee = E_MEETING_ATTENDEE (e_meeting_attendee_new ());
1069 
1070 	e_meeting_attendee_set_address (attendee, "");
1071 	e_meeting_attendee_set_member (attendee, "");
1072 
1073 	e_meeting_attendee_set_cutype (attendee, text_to_type (_("Individual")));
1074 	e_meeting_attendee_set_role (attendee, text_to_role (_("Required Participant")));
1075 	e_meeting_attendee_set_rsvp (attendee, TRUE);
1076 
1077 	e_meeting_attendee_set_delto (attendee, "");
1078 	e_meeting_attendee_set_delfrom (attendee, "");
1079 
1080 	e_meeting_attendee_set_partstat (attendee, text_to_partstat (_("Needs Action")));
1081 
1082 	e_meeting_attendee_set_cn (attendee, "");
1083 	e_meeting_attendee_set_language (attendee, "");
1084 
1085 	e_meeting_store_add_attendee (store, attendee);
1086 
1087 	return attendee;
1088 }
1089 
1090 void
e_meeting_store_remove_attendee(EMeetingStore * store,EMeetingAttendee * attendee)1091 e_meeting_store_remove_attendee (EMeetingStore *store,
1092                                  EMeetingAttendee *attendee)
1093 {
1094 	gint i, row = -1;
1095 	GtkTreePath *path;
1096 
1097 	for (i = 0; i < store->priv->attendees->len; i++) {
1098 		if (attendee == g_ptr_array_index (store->priv->attendees, i)) {
1099 			row = i;
1100 			break;
1101 		}
1102 	}
1103 
1104 	if (row != -1) {
1105 		g_ptr_array_remove_index (store->priv->attendees, row);
1106 
1107 		path = gtk_tree_path_new ();
1108 		gtk_tree_path_append_index (path, row);
1109 		gtk_tree_model_row_deleted (GTK_TREE_MODEL (store), path);
1110 		gtk_tree_path_free (path);
1111 
1112 		g_object_unref (attendee);
1113 	}
1114 }
1115 
1116 void
e_meeting_store_remove_all_attendees(EMeetingStore * store)1117 e_meeting_store_remove_all_attendees (EMeetingStore *store)
1118 {
1119 	gint i, j, k;
1120 
1121 	for (i = 0, j = e_meeting_store_count_actual_attendees (store), k = 0;
1122 	     i < j; i++) {
1123 		/* Always try to remove the attendee at index 0 since
1124 		 * it is the only one that will continue to exist until
1125 		 * all attendees are removed. */
1126 		EMeetingAttendee *attendee;
1127 		GtkTreePath *path;
1128 
1129 		attendee = g_ptr_array_index (store->priv->attendees, k);
1130 		g_ptr_array_remove_index (store->priv->attendees, k);
1131 
1132 		path = gtk_tree_path_new ();
1133 		gtk_tree_path_append_index (path, k);
1134 		gtk_tree_model_row_deleted (GTK_TREE_MODEL (store), path);
1135 		gtk_tree_path_free (path);
1136 
1137 		g_object_unref (attendee);
1138 	}
1139 }
1140 
1141 /**
1142  * e_meeting_store_find_self:
1143  * @store: an #EMeetingStore
1144  * @row: return location for the matching row number, or %NULL
1145  *
1146  * Looks for the user in @store by comparing attendee email addresses to
1147  * registered mail identities.  If a matching email address is found and
1148  * @row is not %NULL, @row will be set to the #EMeetingStore row number
1149  * with the matching email address.
1150  *
1151  * Returns: an #EMeetingAttendee, or %NULL
1152  **/
1153 EMeetingAttendee *
e_meeting_store_find_self(EMeetingStore * store,gint * row)1154 e_meeting_store_find_self (EMeetingStore *store,
1155                            gint *row)
1156 {
1157 	EMeetingAttendee *attendee = NULL;
1158 	ESourceRegistry *registry;
1159 	EShell *shell;
1160 	GList *list, *iter;
1161 	const gchar *extension_name;
1162 
1163 	g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);
1164 
1165 	/* FIXME Refactor this so we don't need e_shell_get_default(). */
1166 	shell = e_shell_get_default ();
1167 	registry = e_shell_get_registry (shell);
1168 	extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
1169 
1170 	list = e_source_registry_list_sources (registry, extension_name);
1171 
1172 	for (iter = list; iter != NULL; iter = g_list_next (iter)) {
1173 		ESource *source = E_SOURCE (iter->data);
1174 		ESourceMailIdentity *extension;
1175 		GHashTable *aliases;
1176 		const gchar *address;
1177 
1178 		extension = e_source_get_extension (source, extension_name);
1179 		address = e_source_mail_identity_get_address (extension);
1180 
1181 		if (address != NULL)
1182 			attendee = e_meeting_store_find_attendee (
1183 				store, address, row);
1184 
1185 		if (attendee != NULL)
1186 			break;
1187 
1188 		aliases = e_source_mail_identity_get_aliases_as_hash_table (extension);
1189 		if (aliases) {
1190 			GHashTableIter iter;
1191 			gpointer key = NULL;
1192 
1193 			g_hash_table_iter_init (&iter, aliases);
1194 			while (!attendee && g_hash_table_iter_next (&iter, &key, NULL)) {
1195 				const gchar *email = key;
1196 
1197 				if (email && *email)
1198 					attendee = e_meeting_store_find_attendee (store, email, row);
1199 			}
1200 
1201 			g_hash_table_destroy (aliases);
1202 		}
1203 
1204 		if (attendee)
1205 			break;
1206 	}
1207 
1208 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
1209 
1210 	return attendee;
1211 }
1212 
1213 EMeetingAttendee *
e_meeting_store_find_attendee(EMeetingStore * store,const gchar * address,gint * row)1214 e_meeting_store_find_attendee (EMeetingStore *store,
1215                                const gchar *address,
1216                                gint *row)
1217 {
1218 	EMeetingAttendee *attendee;
1219 	gint i;
1220 
1221 	if (address == NULL)
1222 		return NULL;
1223 
1224 	for (i = 0; i < store->priv->attendees->len; i++) {
1225 		const gchar *attendee_address;
1226 
1227 		attendee = g_ptr_array_index (store->priv->attendees, i);
1228 
1229 		attendee_address = e_meeting_attendee_get_address (attendee);
1230 		if (attendee_address && !g_ascii_strcasecmp (
1231 			itip_strip_mailto (attendee_address),
1232 			itip_strip_mailto (address))) {
1233 			if (row != NULL)
1234 				*row = i;
1235 
1236 			return attendee;
1237 		}
1238 	}
1239 
1240 	return NULL;
1241 }
1242 
1243 EMeetingAttendee *
e_meeting_store_find_attendee_at_row(EMeetingStore * store,gint row)1244 e_meeting_store_find_attendee_at_row (EMeetingStore *store,
1245                                       gint row)
1246 {
1247 	g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);
1248 	g_return_val_if_fail (ROW_VALID (store, row), NULL);
1249 
1250 	return g_ptr_array_index (store->priv->attendees, row);
1251 }
1252 
1253 GtkTreePath *
e_meeting_store_find_attendee_path(EMeetingStore * store,EMeetingAttendee * attendee)1254 e_meeting_store_find_attendee_path (EMeetingStore *store,
1255                                     EMeetingAttendee *attendee)
1256 {
1257 	GtkTreePath *path;
1258 	gint row = -1, i;
1259 
1260 	for (i = 0; i < store->priv->attendees->len; i++) {
1261 		if (attendee == g_ptr_array_index (store->priv->attendees, i)) {
1262 			row = i;
1263 			break;
1264 		}
1265 	}
1266 
1267 	if (row == -1)
1268 		return NULL;
1269 
1270 	path = gtk_tree_path_new ();
1271 	gtk_tree_path_append_index (path, row);
1272 
1273 	return path;
1274 }
1275 
1276 gint
e_meeting_store_count_actual_attendees(EMeetingStore * store)1277 e_meeting_store_count_actual_attendees (EMeetingStore *store)
1278 {
1279 	g_return_val_if_fail (E_IS_MEETING_STORE (store), 0);
1280 
1281 	return store->priv->attendees->len;
1282 }
1283 
1284 const GPtrArray *
e_meeting_store_get_attendees(EMeetingStore * store)1285 e_meeting_store_get_attendees (EMeetingStore *store)
1286 {
1287 	g_return_val_if_fail (E_IS_MEETING_STORE (store), NULL);
1288 
1289 	return store->priv->attendees;
1290 }
1291 
1292 static ICalTimezone *
find_zone(ICalProperty * in_prop,ICalComponent * tz_top_level)1293 find_zone (ICalProperty *in_prop,
1294 	   ICalComponent *tz_top_level)
1295 {
1296 	ICalParameter *param;
1297 	ICalComponent *subcomp;
1298 	const gchar *tzid;
1299 	ICalCompIter *iter;
1300 
1301 	if (tz_top_level == NULL)
1302 		return NULL;
1303 
1304 	param = i_cal_property_get_first_parameter (in_prop, I_CAL_TZID_PARAMETER);
1305 	if (param == NULL)
1306 		return NULL;
1307 	tzid = i_cal_parameter_get_tzid (param);
1308 
1309 	iter = i_cal_component_begin_component (tz_top_level, I_CAL_VTIMEZONE_COMPONENT);
1310 	subcomp = i_cal_comp_iter_deref (iter);
1311 	while (subcomp) {
1312 		ICalComponent *next_subcomp;
1313 		ICalProperty *prop;
1314 
1315 		next_subcomp = i_cal_comp_iter_next (iter);
1316 
1317 		prop = i_cal_component_get_first_property (subcomp, I_CAL_TZID_PROPERTY);
1318 		if (prop && !g_strcmp0 (tzid, i_cal_property_get_tzid (prop))) {
1319 			ICalComponent *clone;
1320 			ICalTimezone *zone;
1321 
1322 			zone = i_cal_timezone_new ();
1323 			clone = i_cal_component_clone (subcomp);
1324 			i_cal_timezone_set_component (zone, clone);
1325 
1326 			g_clear_object (&next_subcomp);
1327 			g_clear_object (&subcomp);
1328 			g_clear_object (&param);
1329 			g_clear_object (&prop);
1330 			g_clear_object (&iter);
1331 
1332 			return zone;
1333 		}
1334 
1335 		g_clear_object (&prop);
1336 		g_object_unref (subcomp);
1337 		subcomp = next_subcomp;
1338 	}
1339 
1340 	g_clear_object (&param);
1341 	g_clear_object (&iter);
1342 
1343 	return NULL;
1344 }
1345 
1346 static void
process_callbacks(EMeetingStoreQueueData * qdata)1347 process_callbacks (EMeetingStoreQueueData *qdata)
1348 {
1349 	EMeetingStore *store;
1350 	gint i;
1351 
1352 	store = qdata->store;
1353 
1354 	for (i = 0; i < qdata->call_backs->len; i++) {
1355 		EMeetingStoreRefreshCallback call_back;
1356 		gpointer *data = NULL;
1357 
1358 		call_back = g_ptr_array_index (qdata->call_backs, i);
1359 		data = g_ptr_array_index (qdata->data, i);
1360 
1361 		g_idle_add ((GSourceFunc) call_back, data);
1362 	}
1363 
1364 	g_mutex_lock (&store->priv->mutex);
1365 	store->priv->num_threads--;
1366 	g_mutex_unlock (&store->priv->mutex);
1367 
1368 	refresh_queue_remove (qdata->store, qdata->attendee);
1369 	g_object_unref (store);
1370 }
1371 
1372 static void
process_free_busy_comp_get_xfb(ICalProperty * ip,gchar ** summary,gchar ** location)1373 process_free_busy_comp_get_xfb (ICalProperty *ip,
1374                                 gchar **summary,
1375                                 gchar **location)
1376 {
1377 	gchar *tmp;
1378 
1379 	g_return_if_fail (ip != NULL);
1380 	g_return_if_fail (summary != NULL && *summary == NULL);
1381 	g_return_if_fail (location != NULL && *location == NULL);
1382 
1383 	/* We extract extended free/busy information from the ICalProperty
1384 	 * here (X-SUMMARY and X-LOCATION). If the property carries such,
1385 	 * it will be displayed as a tooltip for the busy period. Otherwise,
1386 	 * nothing will happen (*summary and/or *location will be NULL)
1387 	 */
1388 
1389 	tmp = i_cal_property_get_parameter_as_string (ip, E_MEETING_FREE_BUSY_XPROP_SUMMARY);
1390 	*summary = e_meeting_xfb_utf8_string_new_from_ical (tmp, E_MEETING_FREE_BUSY_XPROP_MAXLEN);
1391 	g_free (tmp);
1392 
1393 	tmp = i_cal_property_get_parameter_as_string (ip, E_MEETING_FREE_BUSY_XPROP_LOCATION);
1394 	*location = e_meeting_xfb_utf8_string_new_from_ical (tmp, E_MEETING_FREE_BUSY_XPROP_MAXLEN);
1395 	g_free (tmp);
1396 }
1397 
1398 static void
process_free_busy_comp(EMeetingAttendee * attendee,ICalComponent * fb_comp,ICalTimezone * zone,ICalComponent * tz_top_level)1399 process_free_busy_comp (EMeetingAttendee *attendee,
1400 			ICalComponent *fb_comp,
1401 			ICalTimezone *zone,
1402 			ICalComponent *tz_top_level)
1403 {
1404 	ICalProperty *ip;
1405 
1406 	ip = i_cal_component_get_first_property (fb_comp, I_CAL_DTSTART_PROPERTY);
1407 	if (ip != NULL) {
1408 		ICalTime *dtstart;
1409 		ICalTimezone *ds_zone;
1410 
1411 		dtstart = i_cal_property_get_dtstart (ip);
1412 		if (dtstart) {
1413 			if (!i_cal_time_is_utc (dtstart))
1414 				ds_zone = find_zone (ip, tz_top_level);
1415 			else
1416 				ds_zone = g_object_ref (i_cal_timezone_get_utc_timezone ());
1417 			i_cal_time_convert_timezone (dtstart, ds_zone, zone);
1418 
1419 			e_meeting_attendee_set_start_busy_range (
1420 				attendee,
1421 				i_cal_time_get_year (dtstart),
1422 				i_cal_time_get_month (dtstart),
1423 				i_cal_time_get_day (dtstart),
1424 				i_cal_time_get_hour (dtstart),
1425 				i_cal_time_get_minute (dtstart));
1426 
1427 			g_clear_object (&ds_zone);
1428 			g_clear_object (&dtstart);
1429 		}
1430 	}
1431 	g_clear_object (&ip);
1432 
1433 	ip = i_cal_component_get_first_property (fb_comp, I_CAL_DTEND_PROPERTY);
1434 	if (ip != NULL) {
1435 		ICalTime *dtend;
1436 		ICalTimezone *de_zone;
1437 
1438 		dtend = i_cal_property_get_dtend (ip);
1439 		if (dtend) {
1440 			if (!i_cal_time_is_utc (dtend))
1441 				de_zone = find_zone (ip, tz_top_level);
1442 			else
1443 				de_zone = g_object_ref (i_cal_timezone_get_utc_timezone ());
1444 			i_cal_time_convert_timezone (dtend, de_zone, zone);
1445 
1446 			e_meeting_attendee_set_end_busy_range (
1447 				attendee,
1448 				i_cal_time_get_year (dtend),
1449 				i_cal_time_get_month (dtend),
1450 				i_cal_time_get_day (dtend),
1451 				i_cal_time_get_hour (dtend),
1452 				i_cal_time_get_minute (dtend));
1453 
1454 			g_clear_object (&de_zone);
1455 			g_clear_object (&dtend);
1456 		}
1457 	}
1458 	g_clear_object (&ip);
1459 
1460 	for (ip = i_cal_component_get_first_property (fb_comp, I_CAL_FREEBUSY_PROPERTY);
1461 	     ip;
1462 	     g_object_unref (ip), ip = i_cal_component_get_next_property (fb_comp, I_CAL_FREEBUSY_PROPERTY)) {
1463 		ICalParameter *param;
1464 		ICalPeriod *fb;
1465 		EMeetingFreeBusyType busy_type = E_MEETING_FREE_BUSY_LAST;
1466 		ICalParameterFbtype fbtype = I_CAL_FBTYPE_BUSY;
1467 
1468 		fb = i_cal_property_get_freebusy (ip);
1469 		param = i_cal_property_get_first_parameter (ip, I_CAL_FBTYPE_PARAMETER);
1470 		if (param) {
1471 			fbtype = i_cal_parameter_get_fbtype (param);
1472 			g_clear_object (&param);
1473 		}
1474 
1475 		switch (fbtype) {
1476 		case I_CAL_FBTYPE_BUSY:
1477 			busy_type = E_MEETING_FREE_BUSY_BUSY;
1478 			break;
1479 
1480 		case I_CAL_FBTYPE_BUSYUNAVAILABLE:
1481 			busy_type = E_MEETING_FREE_BUSY_OUT_OF_OFFICE;
1482 			break;
1483 
1484 		case I_CAL_FBTYPE_BUSYTENTATIVE:
1485 			busy_type = E_MEETING_FREE_BUSY_TENTATIVE;
1486 			break;
1487 
1488 		case I_CAL_FBTYPE_FREE:
1489 			busy_type = E_MEETING_FREE_BUSY_FREE;
1490 			break;
1491 
1492 		default:
1493 			break;
1494 		}
1495 
1496 		if (busy_type != E_MEETING_FREE_BUSY_LAST) {
1497 			ICalTimezone *utc_zone = i_cal_timezone_get_utc_timezone ();
1498 			ICalTime *fbstart, *fbend;
1499 			gchar *summary = NULL;
1500 			gchar *location = NULL;
1501 
1502 			fbstart = i_cal_period_get_start (fb);
1503 			fbend = i_cal_period_get_end (fb);
1504 
1505 			i_cal_time_convert_timezone (fbstart, utc_zone, zone);
1506 			i_cal_time_convert_timezone (fbend, utc_zone, zone);
1507 
1508 			/* Extract extended free/busy (XFB) information from
1509 			 * the ICalProperty, if it carries such.
1510 			 * See the comment for the EMeetingXfbData structure
1511 			 * for a reference.
1512 			 */
1513 			process_free_busy_comp_get_xfb (ip, &summary, &location);
1514 
1515 			e_meeting_attendee_add_busy_period (
1516 				attendee,
1517 				i_cal_time_get_year (fbstart),
1518 				i_cal_time_get_month (fbstart),
1519 				i_cal_time_get_day (fbstart),
1520 				i_cal_time_get_hour (fbstart),
1521 				i_cal_time_get_minute (fbstart),
1522 				i_cal_time_get_year (fbend),
1523 				i_cal_time_get_month (fbend),
1524 				i_cal_time_get_day (fbend),
1525 				i_cal_time_get_hour (fbend),
1526 				i_cal_time_get_minute (fbend),
1527 				busy_type,
1528 				summary,
1529 				location);
1530 
1531 			g_clear_object (&fbstart);
1532 			g_clear_object (&fbend);
1533 			g_free (summary);
1534 			g_free (location);
1535 		}
1536 
1537 		g_clear_object (&fb);
1538 	}
1539 }
1540 
1541 static void
process_free_busy(EMeetingStoreQueueData * qdata,const gchar * text)1542 process_free_busy (EMeetingStoreQueueData *qdata,
1543 		   const gchar *text)
1544 {
1545 	EMeetingStore *store = qdata->store;
1546 	EMeetingStorePrivate *priv;
1547 	EMeetingAttendee *attendee = qdata->attendee;
1548 	ICalComponent *main_comp;
1549 	ICalComponentKind kind = I_CAL_NO_COMPONENT;
1550 
1551 	priv = store->priv;
1552 
1553 	main_comp = i_cal_parser_parse_string (text);
1554 	if (main_comp == NULL) {
1555 		process_callbacks (qdata);
1556 		return;
1557 	}
1558 
1559 	kind = i_cal_component_isa (main_comp);
1560 	if (kind == I_CAL_VCALENDAR_COMPONENT) {
1561 		ICalCompIter *iter;
1562 		ICalComponent *tz_top_level, *subcomp;
1563 
1564 		tz_top_level = e_cal_util_new_top_level ();
1565 
1566 		iter = i_cal_component_begin_component (main_comp, I_CAL_VTIMEZONE_COMPONENT);
1567 		subcomp = i_cal_comp_iter_deref (iter);
1568 		while (subcomp) {
1569 			ICalComponent *next_subcomp;
1570 
1571 			next_subcomp = i_cal_comp_iter_next (iter);
1572 
1573 			i_cal_component_take_component (tz_top_level,
1574 				i_cal_component_clone (subcomp));
1575 
1576 			g_object_unref (subcomp);
1577 			subcomp = next_subcomp;
1578 		}
1579 
1580 		g_clear_object (&iter);
1581 
1582 		iter = i_cal_component_begin_component (main_comp, I_CAL_VFREEBUSY_COMPONENT);
1583 		subcomp = i_cal_comp_iter_deref (iter);
1584 		while (subcomp) {
1585 			ICalComponent *next_subcomp;
1586 
1587 			next_subcomp = i_cal_comp_iter_next (iter);
1588 
1589 			process_free_busy_comp (attendee, subcomp, priv->zone, tz_top_level);
1590 
1591 			g_object_unref (subcomp);
1592 			subcomp = next_subcomp;
1593 		}
1594 
1595 		g_clear_object (&iter);
1596 		g_clear_object (&tz_top_level);
1597 	} else if (kind == I_CAL_VFREEBUSY_COMPONENT) {
1598 		process_free_busy_comp (attendee, main_comp, priv->zone, NULL);
1599 	}
1600 
1601 	g_clear_object (&main_comp);
1602 
1603 	process_callbacks (qdata);
1604 }
1605 
1606 /*
1607  * Replace all instances of from_value in string with to_value
1608  * In the returned newly allocated string.
1609 */
1610 static gchar *
replace_string(gchar * string,const gchar * from_value,gchar * to_value)1611 replace_string (gchar *string,
1612                 const gchar *from_value,
1613                 gchar *to_value)
1614 {
1615 	gchar *replaced;
1616 	gchar **split_uri;
1617 
1618 	split_uri = g_strsplit (string, from_value, 0);
1619 	replaced = g_strjoinv (to_value, split_uri);
1620 	g_strfreev (split_uri);
1621 
1622 	return replaced;
1623 }
1624 
1625 static void start_async_read (const gchar *uri, gpointer data);
1626 
1627 typedef struct {
1628 	ECalClient *client;
1629 	time_t startt;
1630 	time_t endt;
1631 	GSList *users;
1632 	GSList *fb_data;
1633 	gchar *fb_uri;
1634 	gchar *email;
1635 	EMeetingAttendee *attendee;
1636 	EMeetingStoreQueueData *qdata;
1637 	EMeetingStore *store;
1638 } FreeBusyAsyncData;
1639 
1640 static void
free_busy_data_free(FreeBusyAsyncData * fbd)1641 free_busy_data_free (FreeBusyAsyncData *fbd)
1642 {
1643 	if (fbd) {
1644 		g_slist_free_full (fbd->users, g_free);
1645 		g_free (fbd->email);
1646 		g_slice_free (FreeBusyAsyncData, fbd);
1647 	}
1648 }
1649 
1650 #define USER_SUB   "%u"
1651 #define DOMAIN_SUB "%d"
1652 
1653 static gpointer
freebusy_async_thread(gpointer data)1654 freebusy_async_thread (gpointer data)
1655 {
1656 	FreeBusyAsyncData *fbd = data;
1657 	EMeetingAttendee *attendee = fbd->attendee;
1658 	gchar *default_fb_uri = NULL;
1659 	gchar *fburi = NULL;
1660 	static GMutex mutex;
1661 	EMeetingStorePrivate *priv = fbd->store->priv;
1662 
1663 	if (fbd->client) {
1664 		/* FIXME This a workaround for getting all the free busy
1665 		 *       information for the users.  We should be able to
1666 		 *       get free busy asynchronously. */
1667 		g_mutex_lock (&mutex);
1668 		priv->num_queries++;
1669 		e_cal_client_get_free_busy_sync (
1670 			fbd->client, fbd->startt,
1671 			fbd->endt, fbd->users, &fbd->fb_data, NULL, NULL);
1672 		priv->num_queries--;
1673 		g_mutex_unlock (&mutex);
1674 
1675 		if (fbd->fb_data != NULL) {
1676 			ECalComponent *comp = fbd->fb_data->data;
1677 			gchar *comp_str;
1678 
1679 			comp_str = e_cal_component_get_as_string (comp);
1680 			process_free_busy (fbd->qdata, comp_str);
1681 			g_free (comp_str);
1682 
1683 			free_busy_data_free (fbd);
1684 			return NULL;
1685 		}
1686 	}
1687 
1688 	/* Look for fburl's of attendee with no free busy info on server */
1689 	if (!e_meeting_attendee_is_set_address (attendee)) {
1690 		process_callbacks (fbd->qdata);
1691 		free_busy_data_free (fbd);
1692 		return NULL;
1693 	}
1694 
1695 	/* Check for free busy info on the default server */
1696 	default_fb_uri = g_strdup (fbd->fb_uri);
1697 	fburi = g_strdup (e_meeting_attendee_get_fburi (attendee));
1698 
1699 	if (fburi && !*fburi) {
1700 		g_free (fburi);
1701 		fburi = NULL;
1702 	}
1703 
1704 	if (fburi) {
1705 		priv->num_queries++;
1706 		start_async_read (fburi, fbd->qdata);
1707 		g_free (fburi);
1708 	} else if (default_fb_uri != NULL && !g_str_equal (default_fb_uri, "")) {
1709 		gchar *tmp_fb_uri;
1710 		gchar **split_email;
1711 
1712 		split_email = g_strsplit (fbd->email, "@", 2);
1713 
1714 		tmp_fb_uri = replace_string (default_fb_uri, USER_SUB, split_email[0]);
1715 		g_free (default_fb_uri);
1716 		default_fb_uri = replace_string (tmp_fb_uri, DOMAIN_SUB, split_email[1]);
1717 
1718 		priv->num_queries++;
1719 		start_async_read (default_fb_uri, fbd->qdata);
1720 		g_free (tmp_fb_uri);
1721 		g_strfreev (split_email);
1722 		g_free (default_fb_uri);
1723 	} else {
1724 		process_callbacks (fbd->qdata);
1725 	}
1726 
1727 	free_busy_data_free (fbd);
1728 
1729 	return NULL;
1730 }
1731 
1732 #undef USER_SUB
1733 #undef DOMAIN_SUB
1734 
1735 static gboolean
refresh_busy_periods(gpointer data)1736 refresh_busy_periods (gpointer data)
1737 {
1738 	EMeetingStore *store = E_MEETING_STORE (data);
1739 	EMeetingStorePrivate *priv;
1740 	EMeetingAttendee *attendee = NULL;
1741 	EMeetingStoreQueueData *qdata = NULL;
1742 	gint i;
1743 	GThread *thread;
1744 	GError *error = NULL;
1745 	FreeBusyAsyncData *fbd;
1746 
1747 	priv = store->priv;
1748 
1749 	/* Check to see if there are any remaining attendees in the queue */
1750 	for (i = 0; i < priv->refresh_queue->len; i++) {
1751 		attendee = g_ptr_array_index (priv->refresh_queue, i);
1752 		g_return_val_if_fail (attendee != NULL, FALSE);
1753 
1754 		qdata = g_hash_table_lookup (
1755 			priv->refresh_data, itip_strip_mailto (
1756 			e_meeting_attendee_get_address (attendee)));
1757 		if (!qdata)
1758 			continue;
1759 
1760 		if (!qdata->refreshing)
1761 			break;
1762 	}
1763 
1764 	/* The everything in the queue is being refreshed */
1765 	if (i >= priv->refresh_queue->len) {
1766 		priv->refresh_idle_id = 0;
1767 		return FALSE;
1768 	}
1769 
1770 	/* Indicate we are trying to refresh it */
1771 	qdata->refreshing = TRUE;
1772 
1773 	/* We take a ref in case we get destroyed in the gui during a callback */
1774 	g_object_ref (qdata->store);
1775 
1776 	fbd = g_slice_new0 (FreeBusyAsyncData);
1777 	fbd->client = priv->client;
1778 	fbd->attendee = attendee;
1779 	fbd->users = NULL;
1780 	fbd->fb_data = NULL;
1781 	fbd->qdata = qdata;
1782 	fbd->fb_uri = priv->fb_uri;
1783 	fbd->store = store;
1784 	fbd->email = g_strdup (itip_strip_mailto (
1785 		e_meeting_attendee_get_address (attendee)));
1786 
1787 	/* Check the server for free busy data */
1788 	if (priv->client) {
1789 		ICalTime *itt;
1790 
1791 		itt = i_cal_time_new_null_time ();
1792 		i_cal_time_set_date (itt,
1793 			g_date_get_year (&qdata->start.date),
1794 			g_date_get_month (&qdata->start.date),
1795 			g_date_get_day (&qdata->start.date));
1796 		i_cal_time_set_time (itt,
1797 			qdata->start.hour,
1798 			qdata->start.minute,
1799 			0);
1800 		fbd->startt = i_cal_time_as_timet_with_zone (itt, priv->zone);
1801 		g_clear_object (&itt);
1802 
1803 		itt = i_cal_time_new_null_time ();
1804 		i_cal_time_set_date (itt,
1805 			g_date_get_year (&qdata->end.date),
1806 			g_date_get_month (&qdata->end.date),
1807 			g_date_get_day (&qdata->end.date));
1808 		i_cal_time_set_time (itt,
1809 			qdata->end.hour,
1810 			qdata->end.minute,
1811 			0);
1812 		fbd->endt = i_cal_time_as_timet_with_zone (itt, priv->zone);
1813 		g_clear_object (&itt);
1814 
1815 		fbd->qdata = qdata;
1816 		fbd->users = g_slist_append (fbd->users, g_strdup (fbd->email));
1817 
1818 	}
1819 
1820 	g_mutex_lock (&store->priv->mutex);
1821 	store->priv->num_threads++;
1822 	g_mutex_unlock (&store->priv->mutex);
1823 
1824 	thread = g_thread_try_new (NULL, freebusy_async_thread, fbd, &error);
1825 	if (!thread) {
1826 		free_busy_data_free (fbd);
1827 
1828 		priv->refresh_idle_id = 0;
1829 
1830 		g_mutex_lock (&store->priv->mutex);
1831 		store->priv->num_threads--;
1832 		g_mutex_unlock (&store->priv->mutex);
1833 
1834 		g_object_unref (store);
1835 
1836 		return FALSE;
1837 	}
1838 
1839 	g_thread_unref (thread);
1840 
1841 	return TRUE;
1842 }
1843 
1844 static void
refresh_queue_add(EMeetingStore * store,gint row,EMeetingTime * start,EMeetingTime * end,EMeetingStoreRefreshCallback call_back,gpointer data)1845 refresh_queue_add (EMeetingStore *store,
1846                    gint row,
1847                    EMeetingTime *start,
1848                    EMeetingTime *end,
1849                    EMeetingStoreRefreshCallback call_back,
1850                    gpointer data)
1851 {
1852 	EMeetingStorePrivate *priv;
1853 	EMeetingAttendee *attendee;
1854 	EMeetingStoreQueueData *qdata;
1855 	gint i;
1856 
1857 	priv = store->priv;
1858 
1859 	attendee = g_ptr_array_index (priv->attendees, row);
1860 	if ((attendee == NULL) || !strcmp (itip_strip_mailto (
1861 		e_meeting_attendee_get_address (attendee)), ""))
1862 		return;
1863 
1864 	/* check the queue if the attendee is already in there*/
1865 	for (i = 0; i < priv->refresh_queue->len; i++) {
1866 		if (attendee == g_ptr_array_index (priv->refresh_queue, i))
1867 			return;
1868 
1869 		if (!strcmp (e_meeting_attendee_get_address (attendee),
1870 			e_meeting_attendee_get_address (
1871 			g_ptr_array_index (priv->refresh_queue, i))))
1872 			return;
1873 	}
1874 
1875 	g_mutex_lock (&priv->mutex);
1876 	qdata = g_hash_table_lookup (
1877 		priv->refresh_data, itip_strip_mailto (
1878 		e_meeting_attendee_get_address (attendee)));
1879 
1880 	if (qdata == NULL) {
1881 		qdata = g_new0 (EMeetingStoreQueueData, 1);
1882 
1883 		qdata->store = store;
1884 		qdata->attendee = attendee;
1885 		e_meeting_attendee_clear_busy_periods (attendee);
1886 		e_meeting_attendee_set_has_calendar_info (attendee, FALSE);
1887 
1888 		qdata->start = *start;
1889 		qdata->end = *end;
1890 		qdata->string = g_string_new (NULL);
1891 		qdata->call_backs = g_ptr_array_new ();
1892 		qdata->data = g_ptr_array_new ();
1893 		g_ptr_array_add (qdata->call_backs, call_back);
1894 		g_ptr_array_add (qdata->data, data);
1895 
1896 		g_hash_table_insert (
1897 			priv->refresh_data, g_strdup (itip_strip_mailto (
1898 			e_meeting_attendee_get_address (attendee))), qdata);
1899 	} else {
1900 		if (e_meeting_time_compare_times (start, &qdata->start) == -1)
1901 			qdata->start = *start;
1902 		if (e_meeting_time_compare_times (end, &qdata->end) == -1)
1903 			qdata->end = *end;
1904 		g_ptr_array_add (qdata->call_backs, call_back);
1905 		g_ptr_array_add (qdata->data, data);
1906 	}
1907 	g_mutex_unlock (&priv->mutex);
1908 
1909 	g_object_ref (attendee);
1910 	g_ptr_array_add (priv->refresh_queue, attendee);
1911 
1912 	if (priv->refresh_idle_id == 0)
1913 		priv->refresh_idle_id = g_idle_add (refresh_busy_periods, store);
1914 }
1915 
1916 static void
async_read(GObject * source_object,GAsyncResult * result,gpointer data)1917 async_read (GObject *source_object,
1918             GAsyncResult *result,
1919             gpointer data)
1920 {
1921 	EMeetingStoreQueueData *qdata = data;
1922 	GError *error = NULL;
1923 	GInputStream *istream;
1924 	gssize read;
1925 
1926 	g_return_if_fail (source_object != NULL);
1927 	g_return_if_fail (G_IS_INPUT_STREAM (source_object));
1928 
1929 	istream = G_INPUT_STREAM (source_object);
1930 
1931 	read = g_input_stream_read_finish (istream, result, &error);
1932 
1933 	if (error != NULL) {
1934 		g_warning (
1935 			"Read finish failed: %s", error->message);
1936 		g_error_free (error);
1937 
1938 		g_input_stream_close (istream, NULL, NULL);
1939 		g_object_unref (istream);
1940 		process_free_busy (qdata, qdata->string->str);
1941 		return;
1942 	}
1943 
1944 	g_return_if_fail (read >= 0);
1945 
1946 	if (read == 0) {
1947 		g_input_stream_close (istream, NULL, NULL);
1948 		g_object_unref (istream);
1949 		process_free_busy (qdata, qdata->string->str);
1950 	} else {
1951 		qdata->buffer[read] = '\0';
1952 		g_string_append (qdata->string, qdata->buffer);
1953 
1954 		g_input_stream_read_async (
1955 			istream, qdata->buffer, BUF_SIZE - 1,
1956 			G_PRIORITY_DEFAULT, NULL, async_read, qdata);
1957 	}
1958 }
1959 
1960 static void
soup_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer data)1961 soup_authenticate (SoupSession *session,
1962                    SoupMessage *msg,
1963                    SoupAuth *auth,
1964                    gboolean retrying,
1965                    gpointer data)
1966 {
1967 	SoupURI *suri;
1968 	const gchar *orig_uri;
1969 	gboolean tried = FALSE;
1970 
1971 	g_return_if_fail (msg != NULL);
1972 	g_return_if_fail (auth != NULL);
1973 
1974 	orig_uri = g_object_get_data (G_OBJECT (msg), "orig-uri");
1975 	g_return_if_fail (orig_uri != NULL);
1976 
1977 	suri = soup_uri_new (orig_uri);
1978 	if (!suri)
1979 		return;
1980 
1981 	if (!suri->user || !*suri->user) {
1982 		soup_uri_free (suri);
1983 		return;
1984 	}
1985 
1986 	if (!retrying) {
1987 		if (suri->password) {
1988 			soup_auth_authenticate (auth, suri->user, suri->password);
1989 			tried = TRUE;
1990 		} else {
1991 			gchar *password;
1992 
1993 			password = e_passwords_get_password (orig_uri);
1994 			if (password) {
1995 				soup_auth_authenticate (auth, suri->user, password);
1996 				tried = TRUE;
1997 
1998 				memset (password, 0, strlen (password));
1999 				g_free (password);
2000 			}
2001 		}
2002 	}
2003 
2004 	if (!tried) {
2005 		gboolean remember = FALSE;
2006 		gchar *password, *bold_host, *bold_user;
2007 		GString *description;
2008 
2009 		bold_host = g_strconcat ("<b>", suri->host, "</b>", NULL);
2010 		bold_user = g_strconcat ("<b>", suri->user, "</b>", NULL);
2011 
2012 		description = g_string_new ("");
2013 
2014 		g_string_append_printf (
2015 			description, _("Enter password to access "
2016 			"free/busy information on server %s as user %s"),
2017 			bold_host, bold_user);
2018 
2019 		g_free (bold_host);
2020 		g_free (bold_user);
2021 
2022 		if (retrying && msg->reason_phrase && *msg->reason_phrase) {
2023 			g_string_append_c (description, '\n');
2024 			g_string_append_printf (
2025 				description, _("Failure reason: %s"),
2026 				msg->reason_phrase);
2027 		}
2028 
2029 		password = e_passwords_ask_password (
2030 			_("Enter password"), orig_uri,
2031 			description->str, E_PASSWORDS_REMEMBER_FOREVER |
2032 			E_PASSWORDS_SECRET | E_PASSWORDS_ONLINE |
2033 			(retrying ? E_PASSWORDS_REPROMPT : 0),
2034 			&remember, NULL);
2035 
2036 		g_string_free (description, TRUE);
2037 
2038 		if (password) {
2039 			soup_auth_authenticate (auth, suri->user, password);
2040 
2041 			memset (password, 0, strlen (password));
2042 			g_free (password);
2043 		}
2044 	}
2045 
2046 	soup_uri_free (suri);
2047 }
2048 
2049 static void
redirect_handler(SoupMessage * msg,gpointer user_data)2050 redirect_handler (SoupMessage *msg,
2051                   gpointer user_data)
2052 {
2053 	if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
2054 		SoupSession *soup_session = user_data;
2055 		SoupURI *new_uri;
2056 		const gchar *new_loc;
2057 
2058 		new_loc = soup_message_headers_get_list (msg->response_headers, "Location");
2059 		if (!new_loc)
2060 			return;
2061 
2062 		new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
2063 		if (!new_uri) {
2064 			soup_message_set_status_full (
2065 				msg,
2066 				SOUP_STATUS_MALFORMED,
2067 				"Invalid Redirect URL");
2068 			return;
2069 		}
2070 
2071 		soup_message_set_uri (msg, new_uri);
2072 		soup_session_requeue_message (soup_session, msg);
2073 
2074 		soup_uri_free (new_uri);
2075 	}
2076 }
2077 
2078 static void
soup_msg_ready_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)2079 soup_msg_ready_cb (SoupSession *session,
2080                    SoupMessage *msg,
2081                    gpointer user_data)
2082 {
2083 	EMeetingStoreQueueData *qdata = user_data;
2084 
2085 	g_return_if_fail (session != NULL);
2086 	g_return_if_fail (msg != NULL);
2087 	g_return_if_fail (qdata != NULL);
2088 
2089 	if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
2090 		qdata->string = g_string_new_len (
2091 			msg->response_body->data,
2092 			msg->response_body->length);
2093 		process_free_busy (qdata, qdata->string->str);
2094 	} else {
2095 		g_warning (
2096 			"Unable to access free/busy url: %s",
2097 			msg->reason_phrase && *msg->reason_phrase ?
2098 			msg->reason_phrase : (soup_status_get_phrase (
2099 			msg->status_code) ? soup_status_get_phrase (
2100 			msg->status_code) : "Unknown error"));
2101 		process_callbacks (qdata);
2102 	}
2103 }
2104 
2105 static void
download_with_libsoup(const gchar * uri,EMeetingStoreQueueData * qdata)2106 download_with_libsoup (const gchar *uri,
2107                        EMeetingStoreQueueData *qdata)
2108 {
2109 	SoupSession *session;
2110 	SoupMessage *msg;
2111 
2112 	g_return_if_fail (uri != NULL);
2113 	g_return_if_fail (qdata != NULL);
2114 
2115 	msg = soup_message_new (SOUP_METHOD_GET, uri);
2116 	if (!msg) {
2117 		g_warning ("Unable to access free/busy url '%s'; malformed?", uri);
2118 		process_callbacks (qdata);
2119 		return;
2120 	}
2121 
2122 	g_object_set_data_full (G_OBJECT (msg), "orig-uri", g_strdup (uri), g_free);
2123 
2124 	session = soup_session_new ();
2125 	g_object_set (session, SOUP_SESSION_TIMEOUT, 90, NULL);
2126 	g_signal_connect (
2127 		session, "authenticate",
2128 		G_CALLBACK (soup_authenticate), NULL);
2129 
2130 	soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
2131 	soup_message_add_header_handler (
2132 		msg, "got_body", "Location",
2133 		G_CALLBACK (redirect_handler), session);
2134 	soup_message_headers_append (msg->request_headers, "Connection", "close");
2135 	soup_session_queue_message (session, msg, soup_msg_ready_cb, qdata);
2136 }
2137 
2138 static void
start_async_read(const gchar * uri,gpointer data)2139 start_async_read (const gchar *uri,
2140                   gpointer data)
2141 {
2142 	EMeetingStoreQueueData *qdata = data;
2143 	GError *error = NULL;
2144 	GFile *file;
2145 	GInputStream *istream;
2146 
2147 	g_return_if_fail (uri != NULL);
2148 	g_return_if_fail (data != NULL);
2149 
2150 	qdata->store->priv->num_queries--;
2151 	file = g_file_new_for_uri (uri);
2152 
2153 	g_return_if_fail (file != NULL);
2154 
2155 	istream = G_INPUT_STREAM (g_file_read (file, NULL, &error));
2156 
2157 	if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
2158 	    g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) {
2159 		download_with_libsoup (uri, qdata);
2160 		g_object_unref (file);
2161 		g_error_free (error);
2162 		return;
2163 	}
2164 
2165 	if (error != NULL) {
2166 		g_warning (
2167 			"Unable to access free/busy url: %s",
2168 			error->message);
2169 		g_error_free (error);
2170 		process_callbacks (qdata);
2171 		g_object_unref (file);
2172 		return;
2173 	}
2174 
2175 	if (!istream) {
2176 		process_callbacks (qdata);
2177 		g_object_unref (file);
2178 	} else {
2179 		g_input_stream_read_async (
2180 			istream, qdata->buffer, BUF_SIZE - 1,
2181 			G_PRIORITY_DEFAULT, NULL, async_read, qdata);
2182 	}
2183 }
2184 
2185 void
e_meeting_store_refresh_all_busy_periods(EMeetingStore * store,EMeetingTime * start,EMeetingTime * end,EMeetingStoreRefreshCallback call_back,gpointer data)2186 e_meeting_store_refresh_all_busy_periods (EMeetingStore *store,
2187                                           EMeetingTime *start,
2188                                           EMeetingTime *end,
2189                                           EMeetingStoreRefreshCallback call_back,
2190                                           gpointer data)
2191 {
2192 	gint i;
2193 
2194 	g_return_if_fail (E_IS_MEETING_STORE (store));
2195 
2196 	for (i = 0; i < store->priv->attendees->len; i++)
2197 		refresh_queue_add (store, i, start, end, call_back, data);
2198 }
2199 
2200 void
e_meeting_store_refresh_busy_periods(EMeetingStore * store,gint row,EMeetingTime * start,EMeetingTime * end,EMeetingStoreRefreshCallback call_back,gpointer data)2201 e_meeting_store_refresh_busy_periods (EMeetingStore *store,
2202                                       gint row,
2203                                       EMeetingTime *start,
2204                                       EMeetingTime *end,
2205                                       EMeetingStoreRefreshCallback call_back,
2206                                       gpointer data)
2207 {
2208 	g_return_if_fail (E_IS_MEETING_STORE (store));
2209 
2210 	refresh_queue_add (store, row, start, end, call_back, data);
2211 }
2212 
2213 guint
e_meeting_store_get_num_queries(EMeetingStore * store)2214 e_meeting_store_get_num_queries (EMeetingStore *store)
2215 {
2216 	g_return_val_if_fail (E_IS_MEETING_STORE (store), 0);
2217 
2218 	return store->priv->num_queries;
2219 }
2220