evolution-3.6.4/calendar/alarm-notify/alarm-queue.c

Location Tool Test ID Function Issue
alarm-queue.c:1144:19 clang-analyzer Access to field 'cqa' results in a dereference of a null pointer (loaded from variable 'tray_data')
   1 /*
   2  * Alarm queueing engine
   3  *
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2 of the License, or (at your option) version 3.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  16  *
  17  *
  18  * Authors:
  19  *		Federico Mena-Quintero <federico@ximian.com>
  20  *
  21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  22  *
  23  */
  24 
  25 #ifdef HAVE_CONFIG_H
  26 #include <config.h>
  27 #endif
  28 
  29 #include <string.h>
  30 #include <gtk/gtk.h>
  31 #include <glib/gi18n.h>
  32 
  33 #ifdef HAVE_CANBERRA
  34 #include <canberra-gtk.h>
  35 #endif
  36 
  37 #ifdef HAVE_LIBNOTIFY
  38 #include <libnotify/notify.h>
  39 #endif
  40 
  41 #include "alarm.h"
  42 #include "alarm-notify-dialog.h"
  43 #include "alarm-queue.h"
  44 #include "alarm-notify.h"
  45 #include "config-data.h"
  46 #include "util.h"
  47 
  48 #include "calendar/gui/print.h"
  49 
  50 /* The dialog with alarm nofications */
  51 static AlarmNotificationsDialog *alarm_notifications_dialog = NULL;
  52 
  53 /* Whether the queueing system has been initialized */
  54 static gboolean alarm_queue_inited = FALSE;
  55 
  56 /* Clients we are monitoring for alarms */
  57 static GHashTable *client_alarms_hash = NULL;
  58 
  59 /* List of tray icons being displayed */
  60 static GList *tray_icons_list = NULL;
  61 
  62 /* Top Tray Image */
  63 static GtkStatusIcon *tray_icon = NULL;
  64 static gint tray_blink_id = -1;
  65 static gint tray_blink_countdown = 0;
  66 static AlarmNotify *an;
  67 
  68 /* Structure that stores a client we are monitoring */
  69 typedef struct {
  70 	/* Monitored client */
  71 	ECalClient *cal_client;
  72 
  73 	/* The live view to the calendar */
  74 	ECalClientView *view;
  75 
  76 	/* Hash table of component UID -> CompQueuedAlarms.  If an element is
  77 	 * present here, then it means its cqa->queued_alarms contains at least
  78 	 * one queued alarm.  When all the alarms for a component have been
  79 	 * dequeued, the CompQueuedAlarms structure is removed from the hash
  80 	 * table.  Thus a CQA exists <=> it has queued alarms.
  81 	 */
  82 	GHashTable *uid_alarms_hash;
  83 } ClientAlarms;
  84 
  85 /* Pair of a ECalComponentAlarms and the mapping from queued alarm IDs to the
  86  * actual alarm instance structures.
  87  */
  88 typedef struct {
  89 	/* The parent client alarms structure */
  90 	ClientAlarms *parent_client;
  91 
  92 	/* The component's UID */
  93 	ECalComponentId *id;
  94 
  95 	/* The actual component and its alarm instances */
  96 	ECalComponentAlarms *alarms;
  97 
  98 	/* List of QueuedAlarm structures */
  99 	GSList *queued_alarms;
 100 
 101 	/* Flags */
 102 	gboolean expecting_update;
 103 } CompQueuedAlarms;
 104 
 105 /* Pair of a queued alarm ID and the alarm trigger instance it refers to */
 106 typedef struct {
 107 	/* Alarm ID from alarm.h */
 108 	gpointer alarm_id;
 109 
 110 	/* Instance from our parent CompQueuedAlarms->alarms->alarms list */
 111 	ECalComponentAlarmInstance *instance;
 112 
 113 	/* original trigger of the instance from component */
 114 	time_t orig_trigger;
 115 
 116 	/* Whether this is a snoozed queued alarm or a normal one */
 117 	guint snooze : 1;
 118 } QueuedAlarm;
 119 
 120 /* Alarm ID for the midnight refresh function */
 121 static gpointer midnight_refresh_id = NULL;
 122 static time_t midnight = 0;
 123 
 124 static void	remove_client_alarms		(ClientAlarms *ca);
 125 static void	display_notification		(time_t trigger,
 126 						 CompQueuedAlarms *cqa,
 127 						 gpointer alarm_id,
 128 						 gboolean use_description);
 129 static void	audio_notification		(time_t trigger,
 130 						 CompQueuedAlarms *cqa,
 131 						 gpointer alarm_id);
 132 static void	mail_notification		(time_t trigger,
 133 						 CompQueuedAlarms *cqa,
 134 						 gpointer alarm_id);
 135 static void	procedure_notification		(time_t trigger,
 136 						 CompQueuedAlarms *cqa,
 137 						 gpointer alarm_id);
 138 #ifdef HAVE_LIBNOTIFY
 139 static void	popup_notification		(time_t trigger,
 140 						 CompQueuedAlarms *cqa,
 141 						 gpointer alarm_id,
 142 						 gboolean use_description);
 143 #endif
 144 static void	query_objects_modified_cb	(ECalClientView *view,
 145 						 const GSList *objects,
 146 						 gpointer data);
 147 static void	query_objects_removed_cb	(ECalClientView *view,
 148 						 const GSList *uids,
 149 						 gpointer data);
 150 
 151 static void	update_cqa			(CompQueuedAlarms *cqa,
 152 						 ECalComponent *comp);
 153 static void	update_qa			(ECalComponentAlarms *alarms,
 154 						 QueuedAlarm *qa);
 155 static void	tray_list_remove_cqa		(CompQueuedAlarms *cqa);
 156 static void	on_dialog_objs_removed_cb	(ECalClientView *view,
 157 						 const GSList *uids,
 158 						 gpointer data);
 159 
 160 /* Alarm queue engine */
 161 
 162 static void	load_alarms_for_today		(ClientAlarms *ca);
 163 static void	midnight_refresh_cb		(gpointer alarm_id,
 164 						 time_t trigger,
 165 						 gpointer data);
 166 
 167 /* Simple asynchronous message dispatcher */
 168 
 169 typedef struct _Message Message;
 170 typedef void (*MessageFunc) (Message *msg);
 171 
 172 struct _Message {
 173 	MessageFunc func;
 174 };
 175 
 176 /*
 177 static void
 178 message_proxy (Message *msg)
 179 {
 180 	g_return_if_fail (msg->func != NULL);
 181  *
 182 	msg->func (msg);
 183 }
 184  *
 185 static gpointer
 186 create_thread_pool (void)
 187 {
 188 	return g_thread_pool_new ((GFunc) message_proxy, NULL, 1, FALSE, NULL);
 189 }*/
 190 
 191 static void
 192 message_push (Message *msg)
 193 {
 194 	/* This used be pushed through the thread pool. This fix is made to
 195 	 * work-around the crashers in dbus due to threading. The threading
 196 	 * is not completely removed as its better to have alarm daemon
 197 	 * running in a thread rather than blocking main thread.  This is
 198 	 * the reason the creation of thread pool is commented out. */
 199 	msg->func (msg);
 200 }
 201 
 202 /*
 203  * use a static ring-buffer so we can call this twice
 204  * in a printf without getting nonsense results.
 205  */
 206 static const gchar *
 207 e_ctime (const time_t *timep)
 208 {
 209 	static gchar *buffer[4] = { 0, };
 210 	static gint next = 0;
 211 	const gchar *ret;
 212 
 213 	g_free (buffer[next]);
 214 	ret = buffer[next++] = g_strdup (ctime (timep));
 215 	if (buffer[next - 1] && *buffer[next - 1]) {
 216 		gint len = strlen (buffer[next - 1]);
 217 		while (len > 0 && (buffer[next - 1][len - 1] == '\n' ||
 218 			buffer[next - 1][len - 1] == '\r' ||
 219 			g_ascii_isspace (buffer[next - 1][len - 1])))
 220 			len--;
 221 
 222 		buffer[next - 1][len - 1] = 0;
 223 	}
 224 
 225 	if (next >= G_N_ELEMENTS (buffer))
 226 		next = 0;
 227 
 228 	return ret;
 229 }
 230 
 231 /* Queues an alarm trigger for midnight so that we can load the next
 232  * day's worth of alarms. */
 233 static void
 234 queue_midnight_refresh (void)
 235 {
 236 	icaltimezone *zone;
 237 
 238 	if (midnight_refresh_id != NULL) {
 239 		alarm_remove (midnight_refresh_id);
 240 		midnight_refresh_id = NULL;
 241 	}
 242 
 243 	zone = config_data_get_timezone ();
 244 	midnight = time_day_end_with_zone (time (NULL), zone);
 245 
 246 	debug (("Refresh at %s", e_ctime (&midnight)));
 247 
 248 	midnight_refresh_id = alarm_add (
 249 		midnight, midnight_refresh_cb, NULL, NULL);
 250 	if (!midnight_refresh_id) {
 251 		debug (("Could not setup the midnight refresh alarm"));
 252 		/* FIXME: what to do? */
 253 	}
 254 }
 255 
 256 /* Loads a client's alarms; called from g_hash_table_foreach() */
 257 static void
 258 add_client_alarms_cb (gpointer key,
 259                       gpointer value,
 260                       gpointer data)
 261 {
 262 	ClientAlarms *ca = (ClientAlarms *) value;
 263 
 264 	debug (("Adding %p", ca));
 265 
 266 	load_alarms_for_today (ca);
 267 }
 268 
 269 struct _midnight_refresh_msg {
 270 	Message header;
 271 	gboolean remove;
 272 };
 273 
 274 /* Loads the alarms for the new day every midnight */
 275 static void
 276 midnight_refresh_async (struct _midnight_refresh_msg *msg)
 277 {
 278 	debug (("..."));
 279 
 280 	/* Re-load the alarms for all clients */
 281 	g_hash_table_foreach (client_alarms_hash, add_client_alarms_cb, NULL);
 282 
 283 	/* Re-schedule the midnight update */
 284 	if (msg->remove && midnight_refresh_id != NULL) {
 285 		debug (("Reschedule the midnight update"));
 286 		alarm_remove (midnight_refresh_id);
 287 		midnight_refresh_id = NULL;
 288 	}
 289 
 290 	queue_midnight_refresh ();
 291 
 292 	g_slice_free (struct _midnight_refresh_msg, msg);
 293 }
 294 
 295 static void
 296 midnight_refresh_cb (gpointer alarm_id,
 297                      time_t trigger,
 298                      gpointer data)
 299 {
 300 	struct _midnight_refresh_msg *msg;
 301 
 302 	msg = g_slice_new0 (struct _midnight_refresh_msg);
 303 	msg->header.func = (MessageFunc) midnight_refresh_async;
 304 	msg->remove = TRUE;
 305 
 306 	message_push ((Message *) msg);
 307 }
 308 
 309 /* Looks up a client in the client alarms hash table */
 310 static ClientAlarms *
 311 lookup_client (ECalClient *cal_client)
 312 {
 313 	return g_hash_table_lookup (client_alarms_hash, cal_client);
 314 }
 315 
 316 /* Looks up a queued alarm based on its alarm ID */
 317 static QueuedAlarm *
 318 lookup_queued_alarm (CompQueuedAlarms *cqa,
 319                      gpointer alarm_id)
 320 {
 321 	GSList *l;
 322 	QueuedAlarm *qa;
 323 
 324 	qa = NULL;
 325 
 326 	for (l = cqa->queued_alarms; l; l = l->next) {
 327 		qa = l->data;
 328 		if (qa->alarm_id == alarm_id)
 329 			return qa;
 330 	}
 331 
 332 	/* not found, might have been updated/removed */
 333 	return NULL;
 334 }
 335 
 336 /* Removes an alarm from the list of alarms of a component.  If the alarm was
 337  * the last one listed for the component, it removes the component itself.
 338  */
 339 static gboolean
 340 remove_queued_alarm (CompQueuedAlarms *cqa,
 341                      gpointer alarm_id,
 342                      gboolean free_object,
 343                      gboolean remove_alarm)
 344 {
 345 	QueuedAlarm *qa = NULL;
 346 	GSList *l;
 347 
 348 	debug (("..."));
 349 
 350 	for (l = cqa->queued_alarms; l; l = l->next) {
 351 		qa = l->data;
 352 		if (qa->alarm_id == alarm_id)
 353 			break;
 354 	}
 355 
 356 	if (!l)
 357 		return FALSE;
 358 
 359 	cqa->queued_alarms = g_slist_delete_link (cqa->queued_alarms, l);
 360 
 361 	if (remove_alarm) {
 362 		GError *error = NULL;
 363 		ECalComponentId *id;
 364 
 365 		id = e_cal_component_get_id (cqa->alarms->comp);
 366 		if (id) {
 367 			cqa->expecting_update = TRUE;
 368 			e_cal_client_discard_alarm_sync (
 369 				cqa->parent_client->cal_client, id->uid,
 370 				id->rid, qa->instance->auid, NULL, &error);
 371 			cqa->expecting_update = FALSE;
 372 
 373 			if (error) {
 374 				if (!g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED))
 375 					g_warning (
 376 						"%s: Failed to discard alarm: %s",
 377 						G_STRFUNC, error->message);
 378 				g_error_free (error);
 379 			}
 380 			e_cal_component_free_id (id);
 381 		}
 382 	}
 383 
 384 	g_free (qa);
 385 
 386 	/* If this was the last queued alarm for this component, remove the
 387 	 * component itself.
 388 	 */
 389 
 390 	if (cqa->queued_alarms != NULL)
 391 		return FALSE;
 392 
 393 	debug (("Last Component. Removing CQA- Free=%d", free_object));
 394 	if (free_object) {
 395 		cqa->id = NULL;
 396 		cqa->parent_client = NULL;
 397 		e_cal_component_alarms_free (cqa->alarms);
 398 		g_free (cqa);
 399 	} else {
 400 		e_cal_component_alarms_free (cqa->alarms);
 401 		cqa->alarms = NULL;
 402 	}
 403 
 404 	return TRUE;
 405 }
 406 
 407 /**
 408  * has_known_notification:
 409  * Test for notification method and returns if it knows it or not.
 410  * @comp: Component with an alarm.
 411  * @alarm_uid: ID of the alarm in the comp to test.
 412  *
 413  * Returns: %TRUE when we know the notification type, %FALSE otherwise.
 414  */
 415 static gboolean
 416 has_known_notification (ECalComponent *comp,
 417                         const gchar *alarm_uid)
 418 {
 419 	ECalComponentAlarm *alarm;
 420 	ECalComponentAlarmAction action;
 421 
 422 	g_return_val_if_fail (comp != NULL, FALSE);
 423 	g_return_val_if_fail (alarm_uid != NULL, FALSE);
 424 
 425 	alarm = e_cal_component_get_alarm (comp, alarm_uid);
 426 	if (!alarm)
 427 		 return FALSE;
 428 
 429 	e_cal_component_alarm_get_action (alarm, &action);
 430 	e_cal_component_alarm_free (alarm);
 431 
 432 	switch (action) {
 433 		case E_CAL_COMPONENT_ALARM_AUDIO:
 434 		case E_CAL_COMPONENT_ALARM_DISPLAY:
 435 		case E_CAL_COMPONENT_ALARM_EMAIL:
 436 		case E_CAL_COMPONENT_ALARM_PROCEDURE:
 437 			return TRUE;
 438 		default:
 439 			break;
 440 	}
 441 
 442 	return FALSE;
 443 }
 444 
 445 /* Callback used when an alarm triggers */
 446 static void
 447 alarm_trigger_cb (gpointer alarm_id,
 448                   time_t trigger,
 449                   gpointer data)
 450 {
 451 	CompQueuedAlarms *cqa;
 452 	ECalComponent *comp;
 453 	QueuedAlarm *qa;
 454 	ECalComponentAlarm *alarm;
 455 	ECalComponentAlarmAction action;
 456 
 457 	cqa = data;
 458 	comp = cqa->alarms->comp;
 459 
 460 	config_data_set_last_notification_time (
 461 		cqa->parent_client->cal_client, trigger);
 462 	debug (("Setting Last notification time to %s", e_ctime (&trigger)));
 463 
 464 	qa = lookup_queued_alarm (cqa, alarm_id);
 465 	if (!qa)
 466 		return;
 467 
 468 	/* Decide what to do based on the alarm action.  We use the trigger that
 469 	 * is passed to us instead of the one from the instance structure
 470 	 * because this may be a snoozed alarm instead of an original
 471 	 * occurrence.
 472 	 */
 473 
 474 	alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
 475 	if (!alarm)
 476 		 return;
 477 
 478 	e_cal_component_alarm_get_action (alarm, &action);
 479 	e_cal_component_alarm_free (alarm);
 480 
 481 	switch (action) {
 482 	case E_CAL_COMPONENT_ALARM_AUDIO:
 483 		audio_notification (trigger, cqa, alarm_id);
 484 		break;
 485 
 486 	case E_CAL_COMPONENT_ALARM_DISPLAY:
 487 #ifdef HAVE_LIBNOTIFY
 488 		popup_notification (trigger, cqa, alarm_id, TRUE);
 489 #endif
 490 		display_notification (trigger, cqa, alarm_id, TRUE);
 491 		break;
 492 
 493 	case E_CAL_COMPONENT_ALARM_EMAIL:
 494 		mail_notification (trigger, cqa, alarm_id);
 495 		break;
 496 
 497 	case E_CAL_COMPONENT_ALARM_PROCEDURE:
 498 		procedure_notification (trigger, cqa, alarm_id);
 499 		break;
 500 
 501 	default:
 502 		g_return_if_reached ();
 503 	}
 504 	debug (("Notification sent: %d", action));
 505 }
 506 
 507 /* Adds the alarms in a ECalComponentAlarms structure to the alarms queued for a
 508  * particular client.  Also puts the triggers in the alarm timer queue.
 509  */
 510 static void
 511 add_component_alarms (ClientAlarms *ca,
 512                       ECalComponentAlarms *alarms)
 513 {
 514 	ECalComponentId *id;
 515 	CompQueuedAlarms *cqa;
 516 	GSList *l;
 517 
 518 	/* No alarms? */
 519 	if (alarms == NULL || alarms->alarms == NULL) {
 520 		debug (("No alarms to add"));
 521 		if (alarms)
 522 			e_cal_component_alarms_free (alarms);
 523 		return;
 524 	}
 525 
 526 	cqa = g_new (CompQueuedAlarms, 1);
 527 	cqa->parent_client = ca;
 528 	cqa->alarms = alarms;
 529 	cqa->expecting_update = FALSE;
 530 
 531 	cqa->queued_alarms = NULL;
 532 	debug (("Creating CQA %p", cqa));
 533 
 534 	for (l = alarms->alarms; l; l = l->next) {
 535 		ECalComponentAlarmInstance *instance;
 536 		gpointer alarm_id;
 537 		QueuedAlarm *qa;
 538 
 539 		instance = l->data;
 540 
 541 		if (!has_known_notification (cqa->alarms->comp, instance->auid))
 542 			continue;
 543 
 544 		alarm_id = alarm_add (
 545 			instance->trigger, alarm_trigger_cb, cqa, NULL);
 546 		if (!alarm_id)
 547 			continue;
 548 
 549 		qa = g_new (QueuedAlarm, 1);
 550 		qa->alarm_id = alarm_id;
 551 		qa->instance = instance;
 552 		qa->orig_trigger = instance->trigger;
 553 		qa->snooze = FALSE;
 554 
 555 		cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
 556 	}
 557 
 558 	id = e_cal_component_get_id (alarms->comp);
 559 
 560 	/* If we failed to add all the alarms, then we should get rid of the cqa */
 561 	if (cqa->queued_alarms == NULL) {
 562 		e_cal_component_alarms_free (cqa->alarms);
 563 		cqa->alarms = NULL;
 564 		debug (("Failed to add all : %p", cqa));
 565 		g_free (cqa);
 566 		return;
 567 	}
 568 
 569 	cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
 570 	cqa->id = id;
 571 	debug (("Alarm added for %s", id->uid));
 572 	g_hash_table_insert (ca->uid_alarms_hash, cqa->id, cqa);
 573 }
 574 
 575 /* Loads the alarms of a client for a given range of time */
 576 static void
 577 load_alarms (ClientAlarms *ca,
 578              time_t start,
 579              time_t end)
 580 {
 581 	gchar *str_query, *iso_start, *iso_end;
 582 	GError *error = NULL;
 583 
 584 	debug (("..."));
 585 
 586 	iso_start = isodate_from_time_t (start);
 587 	if (!iso_start)
 588 		return;
 589 
 590 	iso_end = isodate_from_time_t (end);
 591 	if (!iso_end) {
 592 		g_free (iso_start);
 593 		return;
 594 	}
 595 
 596 	str_query = g_strdup_printf (
 597 		"(has-alarms-in-range? (make-time \"%s\") "
 598 		"(make-time \"%s\"))", iso_start, iso_end);
 599 	g_free (iso_start);
 600 	g_free (iso_end);
 601 
 602 	/* create the live query */
 603 	if (ca->view) {
 604 		debug (("Disconnecting old queries"));
 605 		g_signal_handlers_disconnect_matched (
 606 			ca->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, ca);
 607 		g_object_unref (ca->view);
 608 		ca->view = NULL;
 609 	}
 610 
 611 	e_cal_client_get_view_sync (
 612 		ca->cal_client, str_query, &ca->view, NULL, &error);
 613 
 614 	if (error != NULL) {
 615 		g_warning (
 616 			"%s: Could not get query for client: %s",
 617 			G_STRFUNC, error->message);
 618 		g_error_free (error);
 619 	} else {
 620 		debug (("Setting Call backs"));
 621 
 622 		g_signal_connect (
 623 			ca->view, "objects-added",
 624 			G_CALLBACK (query_objects_modified_cb), ca);
 625 		g_signal_connect (
 626 			ca->view, "objects-modified",
 627 			G_CALLBACK (query_objects_modified_cb), ca);
 628 		g_signal_connect (
 629 			ca->view, "objects-removed",
 630 			G_CALLBACK (query_objects_removed_cb), ca);
 631 
 632 		e_cal_client_view_start (ca->view, &error);
 633 
 634 		if (error != NULL) {
 635 			g_warning (
 636 				"%s: Failed to start view: %s",
 637 				G_STRFUNC, error->message);
 638 			g_error_free (error);
 639 		}
 640 	}
 641 
 642 	g_free (str_query);
 643 }
 644 
 645 /* Loads today's remaining alarms for a client */
 646 static void
 647 load_alarms_for_today (ClientAlarms *ca)
 648 {
 649 	time_t now, from, day_end, day_start;
 650 	icaltimezone *zone;
 651 
 652 	now = time (NULL);
 653 	zone = config_data_get_timezone ();
 654 	day_start = time_day_begin_with_zone (now, zone);
 655 
 656 	/* Make sure we don't miss some events from the last notification.
 657 	 * We add 1 to the saved notification time to make the time ranges
 658 	 * half-open; we do not want to display the "last" displayed alarm
 659 	 * twice, once when it occurs and once when the alarm daemon restarts.
 660 	 */
 661 	from = config_data_get_last_notification_time (ca->cal_client) + 1;
 662 	if (from <= 0)
 663 		from = MAX (from, day_start);
 664 
 665 	/* Add one hour after midnight, just to cover the delay in 30 minutes
 666 	 * midnight checking. */
 667 	day_end = time_day_end_with_zone (now, zone) + (60 * 60);
 668 	debug (("From %s to %s", e_ctime (&from), e_ctime (&day_end)));
 669 	load_alarms (ca, from, day_end);
 670 }
 671 
 672 /* Looks up a component's queued alarm structure in a client alarms structure */
 673 static CompQueuedAlarms *
 674 lookup_comp_queued_alarms (ClientAlarms *ca,
 675                            const ECalComponentId *id)
 676 {
 677 	return g_hash_table_lookup (ca->uid_alarms_hash, id);
 678 }
 679 
 680 static void
 681 remove_alarms (CompQueuedAlarms *cqa,
 682                gboolean free_object)
 683 {
 684 	GSList *l;
 685 
 686 	debug (("Removing for %p", cqa));
 687 	for (l = cqa->queued_alarms; l;) {
 688 		QueuedAlarm *qa;
 689 
 690 		qa = l->data;
 691 
 692 		/* Get the next element here because the list element will go
 693 		 * away in remove_queued_alarm().  The qa will be freed there as
 694 		 * well.
 695 		 */
 696 		l = l->next;
 697 
 698 		alarm_remove (qa->alarm_id);
 699 		remove_queued_alarm (cqa, qa->alarm_id, free_object, FALSE);
 700 	}
 701 }
 702 
 703 /* Removes a component an its alarms */
 704 static void
 705 remove_comp (ClientAlarms *ca,
 706              ECalComponentId *id)
 707 {
 708 	CompQueuedAlarms *cqa;
 709 
 710 	debug (("Removing uid %s", id->uid));
 711 
 712 	if (id->rid && !(*(id->rid))) {
 713 		g_free (id->rid);
 714 		id->rid = NULL;
 715 	}
 716 
 717 	cqa = lookup_comp_queued_alarms (ca, id);
 718 	if (!cqa)
 719 		return;
 720 
 721 	/* If a component is present, then it means we must have alarms queued
 722 	 * for it.
 723 	 */
 724 	g_return_if_fail (cqa->queued_alarms != NULL);
 725 
 726 	debug (("Removing CQA %p", cqa));
 727 	remove_alarms (cqa, TRUE);
 728 }
 729 
 730 /* Called when a calendar component changes; we must reload its corresponding
 731  * alarms.
 732  */
 733 struct _query_msg {
 734 	Message header;
 735 	GSList *objects;
 736 	gpointer data;
 737 };
 738 
 739 static GSList *
 740 duplicate_ical (const GSList *in_list)
 741 {
 742 	const GSList *l;
 743 	GSList *out_list = NULL;
 744 	for (l = in_list; l; l = l->next) {
 745 		out_list = g_slist_prepend (
 746 			out_list, icalcomponent_new_clone (l->data));
 747 	}
 748 
 749 	return g_slist_reverse (out_list);
 750 }
 751 
 752 static GSList *
 753 duplicate_ecal (const GSList *in_list)
 754 {
 755 	const GSList *l;
 756 	GSList *out_list = NULL;
 757 	for (l = in_list; l; l = l->next) {
 758 		ECalComponentId *id, *old;
 759 		old = l->data;
 760 		id = g_new0 (ECalComponentId, 1);
 761 		id->uid = g_strdup (old->uid);
 762 		id->rid = g_strdup (old->rid);
 763 		out_list = g_slist_prepend (out_list, id);
 764 	}
 765 
 766 	return g_slist_reverse (out_list);
 767 }
 768 
 769 static gboolean
 770 get_alarms_for_object (ECalClient *cal_client,
 771                        const ECalComponentId *id,
 772                        time_t start,
 773                        time_t end,
 774                        ECalComponentAlarms **alarms)
 775 {
 776 	icalcomponent *icalcomp;
 777 	ECalComponent *comp;
 778 	ECalComponentAlarmAction omit[] = {-1};
 779 
 780 	g_return_val_if_fail (cal_client != NULL, FALSE);
 781 	g_return_val_if_fail (id != NULL, FALSE);
 782 	g_return_val_if_fail (alarms != NULL, FALSE);
 783 	g_return_val_if_fail (start >= 0 && end >= 0, FALSE);
 784 	g_return_val_if_fail (start <= end, FALSE);
 785 
 786 	if (!e_cal_client_get_object_sync (
 787 		cal_client, id->uid, id->rid, &icalcomp, NULL, NULL))
 788 		return FALSE;
 789 
 790 	if (!icalcomp)
 791 		return FALSE;
 792 
 793 	comp = e_cal_component_new ();
 794 	if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
 795 		icalcomponent_free (icalcomp);
 796 		g_object_unref (comp);
 797 		return FALSE;
 798 	}
 799 
 800 	*alarms = e_cal_util_generate_alarms_for_comp (
 801 		comp, start, end, omit, e_cal_client_resolve_tzid_cb,
 802 		cal_client, e_cal_client_get_default_timezone (cal_client));
 803 
 804 	g_object_unref (comp);
 805 
 806 	return TRUE;
 807 }
 808 
 809 static void
 810 query_objects_changed_async (struct _query_msg *msg)
 811 {
 812 	ClientAlarms *ca;
 813 	time_t from, day_end;
 814 	ECalComponentAlarms *alarms;
 815 	gboolean found;
 816 	icaltimezone *zone;
 817 	CompQueuedAlarms *cqa;
 818 	GSList *l;
 819 	GSList *objects;
 820 
 821 	ca = msg->data;
 822 	objects = msg->objects;
 823 
 824 	from = config_data_get_last_notification_time (ca->cal_client);
 825 	if (from == -1)
 826 		from = time (NULL);
 827 	else
 828 		from += 1; /* we add 1 to make sure the alarm is not displayed twice */
 829 
 830 	zone = config_data_get_timezone ();
 831 
 832 	day_end = time_day_end_with_zone (time (NULL), zone);
 833 
 834 	for (l = objects; l != NULL; l = l->next) {
 835 		ECalComponentId *id;
 836 		GSList *sl;
 837 		ECalComponent *comp = e_cal_component_new ();
 838 
 839 		e_cal_component_set_icalcomponent (comp, l->data);
 840 
 841 		id = e_cal_component_get_id (comp);
 842 		found = get_alarms_for_object (ca->cal_client, id, from, day_end, &alarms);
 843 
 844 		if (!found) {
 845 			debug (("No Alarm found for client %p", ca->cal_client));
 846 			tray_list_remove_cqa (lookup_comp_queued_alarms (ca, id));
 847 			remove_comp (ca, id);
 848 			g_hash_table_remove (ca->uid_alarms_hash, id);
 849 			e_cal_component_free_id (id);
 850 			g_object_unref (comp);
 851 			comp = NULL;
 852 			continue;
 853 		}
 854 
 855 		cqa = lookup_comp_queued_alarms (ca, id);
 856 		if (!cqa) {
 857 			debug (("No currently queued alarms for %s", id->uid));
 858 			add_component_alarms (ca, alarms);
 859 			g_object_unref (comp);
 860 			comp = NULL;
 861 			continue;
 862 		}
 863 
 864 		debug (("Alarm Already Exist for %s", id->uid));
 865 		/* If the alarms or the alarms list is empty,
 866 		 * remove it after updating the cqa structure. */
 867 		if (alarms == NULL || alarms->alarms == NULL) {
 868 
 869 			/* Update the cqa and its queued alarms
 870 			 * for changes in summary and alarm_uid.  */
 871 			update_cqa (cqa, comp);
 872 
 873 			if (alarms)
 874 				e_cal_component_alarms_free (alarms);
 875 			continue;
 876 		}
 877 
 878 		/* if already in the list, just update it */
 879 		remove_alarms (cqa, FALSE);
 880 		cqa->alarms = alarms;
 881 		cqa->queued_alarms = NULL;
 882 
 883 		/* add the new alarms */
 884 		for (sl = cqa->alarms->alarms; sl; sl = sl->next) {
 885 			ECalComponentAlarmInstance *instance;
 886 			gpointer alarm_id;
 887 			QueuedAlarm *qa;
 888 
 889 			instance = sl->data;
 890 
 891 			if (!has_known_notification (cqa->alarms->comp, instance->auid))
 892 				continue;
 893 
 894 			alarm_id = alarm_add (instance->trigger, alarm_trigger_cb, cqa, NULL);
 895 			if (!alarm_id)
 896 				continue;
 897 
 898 			qa = g_new (QueuedAlarm, 1);
 899 			qa->alarm_id = alarm_id;
 900 			qa->instance = instance;
 901 			qa->snooze = FALSE;
 902 			qa->orig_trigger = instance->trigger;
 903 			cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
 904 			debug (("Adding %p to queue", qa));
 905 		}
 906 
 907 		cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
 908 		g_object_unref (comp);
 909 		comp = NULL;
 910 	}
 911 	g_slist_free (objects);
 912 
 913 	g_slice_free (struct _query_msg, msg);
 914 }
 915 
 916 static void
 917 query_objects_modified_cb (ECalClientView *view,
 918                            const GSList *objects,
 919                            gpointer data)
 920 {
 921 	struct _query_msg *msg;
 922 
 923 	msg = g_slice_new0 (struct _query_msg);
 924 	msg->header.func = (MessageFunc) query_objects_changed_async;
 925 	msg->objects = duplicate_ical (objects);
 926 	msg->data = data;
 927 
 928 	message_push ((Message *) msg);
 929 }
 930 
 931 /* Called when a calendar component is removed; we must delete its corresponding
 932  * alarms.
 933  */
 934 static void
 935 query_objects_removed_async (struct _query_msg *msg)
 936 {
 937 	ClientAlarms *ca;
 938 	GSList *l;
 939 	GSList *objects;
 940 
 941 	ca = msg->data;
 942 	objects = msg->objects;
 943 
 944 	debug (("Removing %d objects", g_slist_length (objects)));
 945 
 946 	for (l = objects; l != NULL; l = l->next) {
 947 		/* If the alarm is already triggered remove it. */
 948 		tray_list_remove_cqa (lookup_comp_queued_alarms (ca, l->data));
 949 		remove_comp (ca, l->data);
 950 		g_hash_table_remove (ca->uid_alarms_hash, l->data);
 951 		e_cal_component_free_id (l->data);
 952 	}
 953 
 954 	g_slist_free (objects);
 955 
 956 	g_slice_free (struct _query_msg, msg);
 957 }
 958 
 959 static void
 960 query_objects_removed_cb (ECalClientView *view,
 961                           const GSList *uids,
 962                           gpointer data)
 963 {
 964 	struct _query_msg *msg;
 965 
 966 	msg = g_slice_new0 (struct _query_msg);
 967 	msg->header.func = (MessageFunc) query_objects_removed_async;
 968 	msg->objects = duplicate_ecal (uids);
 969 	msg->data = data;
 970 
 971 	message_push ((Message *) msg);
 972 }
 973 
 974 /* Notification functions */
 975 
 976 /* Creates a snooze alarm based on an existing one.  The snooze offset is
 977  * compued with respect to the current time.
 978  */
 979 static void
 980 create_snooze (CompQueuedAlarms *cqa,
 981                gpointer alarm_id,
 982                gint snooze_mins)
 983 {
 984 	QueuedAlarm *orig_qa;
 985 	time_t t;
 986 	gpointer new_id;
 987 
 988 	orig_qa = lookup_queued_alarm (cqa, alarm_id);
 989 	if (!orig_qa)
 990 		return;
 991 
 992 	t = time (NULL);
 993 	t += snooze_mins * 60;
 994 
 995 	new_id = alarm_add (t, alarm_trigger_cb, cqa, NULL);
 996 	if (!new_id) {
 997 		debug (("Unable to schedule trigger for %s", e_ctime (&t)));
 998 		return;
 999 	}
1000 
1001 	orig_qa->instance->trigger = t;
1002 	orig_qa->alarm_id = new_id;
1003 	orig_qa->snooze = TRUE;
1004 	debug (("Adding a alarm at %s", e_ctime (&t)));
1005 }
1006 
1007 /* Launches a component editor for a component */
1008 static void
1009 edit_component (ECalClient *cal_client,
1010                 ECalComponent *comp)
1011 {
1012 	ESource *source;
1013 	gchar *command_line;
1014 	const gchar *scheme;
1015 	const gchar *comp_uid;
1016 	const gchar *source_uid;
1017 	GError *error = NULL;
1018 
1019 	/* XXX Don't we have a function to construct these URIs?
1020 	 *     How are other apps expected to know this stuff? */
1021 
1022 	source = e_client_get_source (E_CLIENT (cal_client));
1023 	source_uid = e_source_get_uid (source);
1024 
1025 	e_cal_component_get_uid (comp, &comp_uid);
1026 
1027 	switch (e_cal_client_get_source_type (cal_client)) {
1028 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1029 			scheme = "calendar:";
1030 			break;
1031 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1032 			scheme = "task:";
1033 			break;
1034 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1035 			scheme = "memo:";
1036 			break;
1037 		default:
1038 			g_return_if_reached ();
1039 	}
1040 
1041 	command_line = g_strdup_printf (
1042 		"%s %s///?source-uid=%s&comp-uid=%s",
1043 		PACKAGE, scheme, source_uid, comp_uid);
1044 
1045 	if (!g_spawn_command_line_async (command_line, &error)) {
1046 		g_critical ("%s", error->message);
1047 		g_error_free (error);
1048 	}
1049 
1050 	g_free (command_line);
1051 }
1052 
1053 static void
1054 print_component (ECalClient *cal_client,
1055                  ECalComponent *comp)
1056 {
1057 	print_comp (
1058 		comp,
1059 		cal_client,
1060 		config_data_get_timezone (),
1061 		config_data_get_24_hour_format (),
1062 		GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG);
1063 }
1064 
1065 typedef struct {
1066 	gchar *summary;
1067 	gchar *description;
1068 	gchar *location;
1069 	gboolean blink_state;
1070 	gboolean snooze_set;
1071 	gint blink_id;
1072 	time_t trigger;
1073 	CompQueuedAlarms *cqa;
1074 	gpointer alarm_id;
1075 	ECalComponent *comp;
1076 	ECalClient *cal_client;
1077 	ECalClientView *view;
1078 	GdkPixbuf *image;
1079 	GtkTreeIter iter;
1080 } TrayIconData;
1081 
1082 static void
1083 free_tray_icon_data (TrayIconData *tray_data)
1084 {
1085 	g_return_if_fail (tray_data != NULL);
1086 
1087 	if (tray_data->summary) {
1088 		g_free (tray_data->summary);
1089 		tray_data->summary = NULL;
1090 	}
1091 
1092 	if (tray_data->description) {
1093 		g_free (tray_data->description);
1094 		tray_data->description = NULL;
1095 	}
1096 
1097 	if (tray_data->location) {
1098 		g_free (tray_data->location);
1099 		tray_data->location = NULL;
1100 	}
1101 
1102 	g_object_unref (tray_data->cal_client);
1103 	tray_data->cal_client = NULL;
1104 
1105 	g_signal_handlers_disconnect_matched (
1106 		tray_data->view, G_SIGNAL_MATCH_FUNC,
1107 		0, 0, NULL, on_dialog_objs_removed_cb, NULL);
1108 	g_object_unref (tray_data->view);
1109 	tray_data->view = NULL;
1110 
1111 	g_object_unref (tray_data->comp);
1112 	tray_data->comp = NULL;
1113 
1114 	tray_data->cqa = NULL;
1115 	tray_data->alarm_id = NULL;
1116 	tray_data->image = NULL;
1117 
1118 	g_free (tray_data);
1119 }
1120 
1121 static void
1122 on_dialog_objs_removed_async (struct _query_msg *msg)
1123 {
1124 	TrayIconData *tray_data;
1125 	GSList *l, *objects;
1126 	ECalComponentId *our_id;
1127 
1128 	debug (("..."));
1129 
1130 	tray_data = msg->data;
1131 	objects = msg->objects;
1132 
1133 	our_id = e_cal_component_get_id (tray_data->comp);
1134 	g_return_if_fail (our_id);
1135 
1136 	for (l = objects; l != NULL; l = l->next) {
1137 		ECalComponentId *id = l->data;
1138 
1139 		if (!id)
1140 			continue;
1141 
1142 		if (g_strcmp0 (id->uid, our_id->uid) == 0 &&
1143 		    g_strcmp0 (id->rid, our_id->rid) == 0) {
1144 			tray_data->cqa = NULL;
Access to field 'cqa' results in a dereference of a null pointer (loaded from variable 'tray_data')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

1145 tray_data->alarm_id = NULL; 1146 tray_icons_list = g_list_remove ( 1147 tray_icons_list, tray_data); 1148 tray_data = NULL; 1149 } 1150 1151 e_cal_component_free_id (id); 1152 } 1153 1154 e_cal_component_free_id (our_id); 1155 g_slist_free (objects); 1156 g_slice_free (struct _query_msg, msg); 1157 } 1158 1159 static void 1160 on_dialog_objs_removed_cb (ECalClientView *view, 1161 const GSList *uids, 1162 gpointer data) 1163 { 1164 struct _query_msg *msg; 1165 1166 msg = g_slice_new0 (struct _query_msg); 1167 msg->header.func = (MessageFunc) on_dialog_objs_removed_async; 1168 msg->objects = duplicate_ecal (uids); 1169 msg->data = data; 1170 1171 message_push ((Message *) msg); 1172 } 1173 1174 struct _tray_cqa_msg { 1175 Message header; 1176 CompQueuedAlarms *cqa; 1177 }; 1178 1179 static void 1180 tray_list_remove_cqa_async (struct _tray_cqa_msg *msg) 1181 { 1182 CompQueuedAlarms *cqa = msg->cqa; 1183 GList *list = tray_icons_list; 1184 1185 debug (("Removing CQA %p from tray list", cqa)); 1186 1187 while (list) { 1188 TrayIconData *tray_data = list->data; 1189 GList *tmp = list; 1190 GtkTreeModel *model; 1191 1192 list = list->next; 1193 if (tray_data->cqa == cqa) { 1194 debug (("Found")); 1195 tray_icons_list = g_list_delete_link (tray_icons_list, tmp); 1196 if (alarm_notifications_dialog) { 1197 model = gtk_tree_view_get_model ( 1198 GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); 1199 gtk_list_store_remove (GTK_LIST_STORE (model), &(tray_data->iter)); 1200 } 1201 free_tray_icon_data (tray_data); 1202 } 1203 } 1204 1205 debug (("%d alarms left", g_list_length (tray_icons_list))); 1206 1207 if (alarm_notifications_dialog) { 1208 if (!g_list_length (tray_icons_list)) { 1209 gtk_widget_destroy (alarm_notifications_dialog->dialog); 1210 g_free (alarm_notifications_dialog); 1211 alarm_notifications_dialog = NULL; 1212 } else { 1213 GtkTreeIter iter; 1214 GtkTreeModel *model; 1215 GtkTreeSelection *sel; 1216 1217 model = gtk_tree_view_get_model ( 1218 GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); 1219 gtk_tree_model_get_iter_first (model, &iter); 1220 sel = gtk_tree_view_get_selection ( 1221 GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); 1222 gtk_tree_selection_select_iter (sel, &iter); 1223 } 1224 } 1225 1226 g_slice_free (struct _tray_cqa_msg, msg); 1227 } 1228 1229 static void 1230 tray_list_remove_cqa (CompQueuedAlarms *cqa) 1231 { 1232 struct _tray_cqa_msg *msg; 1233 1234 msg = g_slice_new0 (struct _tray_cqa_msg); 1235 msg->header.func = (MessageFunc) tray_list_remove_cqa_async; 1236 msg->cqa = cqa; 1237 1238 message_push ((Message *) msg); 1239 } 1240 1241 /* Callback used from the alarm notify dialog */ 1242 static void 1243 tray_list_remove_async (Message *msg) 1244 { 1245 GList *list = tray_icons_list; 1246 1247 debug (("Removing %d alarms", g_list_length (list))); 1248 while (list != NULL) { 1249 1250 TrayIconData *tray_data = list->data; 1251 1252 if (!tray_data->snooze_set) { 1253 GList *temp = list->next; 1254 gboolean status; 1255 1256 tray_icons_list = g_list_remove_link (tray_icons_list, list); 1257 status = remove_queued_alarm ( 1258 tray_data->cqa, 1259 tray_data->alarm_id, FALSE, TRUE); 1260 if (status) { 1261 g_hash_table_remove ( 1262 tray_data->cqa->parent_client->uid_alarms_hash, 1263 tray_data->cqa->id); 1264 e_cal_component_free_id (tray_data->cqa->id); 1265 g_free (tray_data->cqa); 1266 } 1267 free_tray_icon_data (tray_data); 1268 tray_data = NULL; 1269 g_list_free_1 (list); 1270 if (tray_icons_list != list) /* List head is modified */ 1271 list = tray_icons_list; 1272 else 1273 list = temp; 1274 } else 1275 list = list->next; 1276 } 1277 1278 g_slice_free (Message, msg); 1279 } 1280 1281 static void 1282 tray_list_remove_icons (void) 1283 { 1284 Message *msg; 1285 1286 msg = g_slice_new0 (Message); 1287 msg->func = tray_list_remove_async; 1288 1289 message_push (msg); 1290 } 1291 1292 struct _tray_msg { 1293 Message header; 1294 TrayIconData *data; 1295 }; 1296 1297 static void 1298 tray_list_remove_data_async (struct _tray_msg *msg) 1299 { 1300 TrayIconData *tray_data = msg->data; 1301 1302 debug (("Removing %p from tray list", tray_data)); 1303 1304 tray_icons_list = g_list_remove_all (tray_icons_list, tray_data); 1305 free_tray_icon_data (tray_data); 1306 tray_data = NULL; 1307 1308 g_slice_free (struct _tray_msg, msg); 1309 } 1310 1311 static void 1312 tray_list_remove_data (TrayIconData *data) 1313 { 1314 struct _tray_msg *msg; 1315 1316 msg = g_slice_new0 (struct _tray_msg); 1317 msg->header.func = (MessageFunc) tray_list_remove_data_async; 1318 msg->data = data; 1319 1320 message_push ((Message *) msg); 1321 } 1322 1323 static void 1324 notify_dialog_cb (AlarmNotifyResult result, 1325 gint snooze_mins, 1326 gpointer data) 1327 { 1328 TrayIconData *tray_data = data; 1329 1330 debug (("Received from dialog")); 1331 1332 g_signal_handlers_disconnect_matched ( 1333 tray_data->view, G_SIGNAL_MATCH_FUNC, 1334 0, 0, NULL, on_dialog_objs_removed_cb, NULL); 1335 1336 switch (result) { 1337 case ALARM_NOTIFY_SNOOZE: 1338 debug (("Creating a snooze")); 1339 create_snooze (tray_data->cqa, tray_data->alarm_id, snooze_mins); 1340 tray_data->snooze_set = TRUE; 1341 tray_list_remove_data (tray_data); 1342 if (alarm_notifications_dialog) { 1343 GtkTreeSelection *selection = 1344 gtk_tree_view_get_selection ( 1345 GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); 1346 GtkTreeIter iter; 1347 GtkTreeModel *model = NULL; 1348 1349 /* We can also use tray_data->iter */ 1350 if (gtk_tree_selection_get_selected (selection, &model, &iter)) { 1351 gtk_list_store_remove (GTK_LIST_STORE (model), &iter); 1352 if (!gtk_tree_model_get_iter_first (model, &iter)) { 1353 /* We removed the last one */ 1354 gtk_widget_destroy (alarm_notifications_dialog->dialog); 1355 g_free (alarm_notifications_dialog); 1356 alarm_notifications_dialog = NULL; 1357 } else { 1358 /* Select the first */ 1359 gtk_tree_selection_select_iter (selection, &iter); 1360 } 1361 } 1362 1363 } 1364 1365 break; 1366 1367 case ALARM_NOTIFY_EDIT: 1368 edit_component (tray_data->cal_client, tray_data->comp); 1369 1370 break; 1371 1372 case ALARM_NOTIFY_PRINT: 1373 print_component (tray_data->cal_client, tray_data->comp); 1374 1375 break; 1376 1377 case ALARM_NOTIFY_DISMISS: 1378 if (alarm_notifications_dialog) { 1379 GtkTreeModel *model; 1380 1381 model = gtk_tree_view_get_model ( 1382 GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); 1383 gtk_list_store_remove (GTK_LIST_STORE (model), &tray_data->iter); 1384 } 1385 break; 1386 1387 case ALARM_NOTIFY_CLOSE: 1388 debug (("Dialog close")); 1389 if (alarm_notifications_dialog) { 1390 GtkTreeIter iter; 1391 GtkTreeModel *model = 1392 gtk_tree_view_get_model ( 1393 GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); 1394 gboolean valid = gtk_tree_model_get_iter_first (model, &iter); 1395 1396 /* Maybe we should warn about this first? */ 1397 while (valid) { 1398 valid = gtk_list_store_remove (GTK_LIST_STORE (model), &iter); 1399 } 1400 1401 gtk_widget_destroy (alarm_notifications_dialog->dialog); 1402 g_free (alarm_notifications_dialog); 1403 alarm_notifications_dialog = NULL; 1404 1405 /* Task to remove the tray icons */ 1406 tray_list_remove_icons (); 1407 } 1408 1409 break; 1410 1411 default: 1412 g_return_if_reached (); 1413 } 1414 1415 return; 1416 } 1417 1418 static void 1419 remove_tray_icon (void) 1420 { 1421 if (tray_blink_id > -1) 1422 g_source_remove (tray_blink_id); 1423 tray_blink_id = -1; 1424 1425 if (tray_icon) { 1426 gtk_status_icon_set_visible (tray_icon, FALSE); 1427 g_object_unref (tray_icon); 1428 tray_icon = NULL; 1429 } 1430 } 1431 1432 /* Callbacks. */ 1433 static gboolean 1434 open_alarm_dialog (TrayIconData *tray_data) 1435 { 1436 QueuedAlarm *qa; 1437 1438 debug (("...")); 1439 qa = lookup_queued_alarm (tray_data->cqa, tray_data->alarm_id); 1440 if (qa) { 1441 remove_tray_icon (); 1442 1443 if (!alarm_notifications_dialog) 1444 alarm_notifications_dialog = notified_alarms_dialog_new (); 1445 1446 if (alarm_notifications_dialog) { 1447 1448 GtkTreeSelection *selection = NULL; 1449 1450 selection = gtk_tree_view_get_selection ( 1451 GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); 1452 1453 tray_data->iter = add_alarm_to_notified_alarms_dialog ( 1454 alarm_notifications_dialog, 1455 tray_data->trigger, 1456 qa->instance->occur_start, 1457 qa->instance->occur_end, 1458 e_cal_component_get_vtype (tray_data->comp), 1459 tray_data->summary, 1460 tray_data->description, 1461 tray_data->location, 1462 notify_dialog_cb, tray_data); 1463 1464 gtk_tree_selection_select_iter ( 1465 selection, &tray_data->iter); 1466 1467 gtk_window_present (GTK_WINDOW (alarm_notifications_dialog->dialog)); 1468 } 1469 1470 } else { 1471 remove_tray_icon (); 1472 } 1473 1474 return TRUE; 1475 } 1476 1477 static gint 1478 tray_icon_clicked_cb (GtkWidget *widget, 1479 GdkEventButton *event, 1480 gpointer user_data) 1481 { 1482 if (event->type == GDK_BUTTON_PRESS) { 1483 debug (("left click and %d alarms", g_list_length (tray_icons_list))); 1484 if (event->button == 1 && g_list_length (tray_icons_list) > 0) { 1485 GList *tmp; 1486 for (tmp = tray_icons_list; tmp; tmp = tmp->next) { 1487 open_alarm_dialog (tmp->data); 1488 } 1489 1490 return TRUE; 1491 } else if (event->button == 3) { 1492 debug (("right click")); 1493 1494 remove_tray_icon (); 1495 return TRUE; 1496 } 1497 } 1498 1499 return FALSE; 1500 } 1501 1502 static void 1503 icon_activated (GtkStatusIcon *icon) 1504 { 1505 GdkEventButton event; 1506 1507 event.type = GDK_BUTTON_PRESS; 1508 event.button = 1; 1509 event.time = gtk_get_current_event_time (); 1510 1511 tray_icon_clicked_cb (NULL, &event, NULL); 1512 } 1513 1514 static void 1515 popup_menu (GtkStatusIcon *icon, 1516 guint button, 1517 guint activate_time) 1518 { 1519 if (button == 3) { 1520 /* right click */ 1521 GdkEventButton event; 1522 1523 event.type = GDK_BUTTON_PRESS; 1524 event.button = 3; 1525 event.time = gtk_get_current_event_time (); 1526 1527 tray_icon_clicked_cb (NULL, &event, NULL); 1528 } 1529 } 1530 1531 static gboolean 1532 tray_icon_blink_cb (gpointer data) 1533 { 1534 static gboolean tray_blink_state = FALSE; 1535 const gchar *icon_name; 1536 1537 tray_blink_countdown--; 1538 tray_blink_state = !tray_blink_state; 1539 1540 if (tray_blink_state || tray_blink_countdown <= 0) 1541 icon_name = "appointment-missed"; 1542 else 1543 icon_name = "appointment-soon"; 1544 1545 if (tray_icon) 1546 gtk_status_icon_set_from_icon_name (tray_icon, icon_name); 1547 1548 if (tray_blink_countdown <= 0) 1549 tray_blink_id = -1; 1550 1551 return tray_blink_countdown > 0; 1552 } 1553 1554 /* Add a new data to tray list */ 1555 1556 static void 1557 tray_list_add_async (struct _tray_msg *msg) 1558 { 1559 tray_icons_list = g_list_prepend (tray_icons_list, msg->data); 1560 1561 g_slice_free (struct _tray_msg, msg); 1562 } 1563 1564 static void 1565 tray_list_add_new (TrayIconData *data) 1566 { 1567 struct _tray_msg *msg; 1568 1569 msg = g_slice_new0 (struct _tray_msg); 1570 msg->header.func = (MessageFunc) tray_list_add_async; 1571 msg->data = data; 1572 1573 message_push ((Message *) msg); 1574 } 1575 1576 /* Performs notification of a display alarm */ 1577 static void 1578 display_notification (time_t trigger, 1579 CompQueuedAlarms *cqa, 1580 gpointer alarm_id, 1581 gboolean use_description) 1582 { 1583 QueuedAlarm *qa; 1584 ECalComponent *comp; 1585 const gchar *summary, *description, *location; 1586 TrayIconData *tray_data; 1587 ECalComponentText text; 1588 GSList *text_list; 1589 gchar *str, *start_str, *end_str, *alarm_str, *time_str; 1590 icaltimezone *current_zone; 1591 ECalComponentOrganizer organiser; 1592 1593 debug (("...")); 1594 1595 comp = cqa->alarms->comp; 1596 qa = lookup_queued_alarm (cqa, alarm_id); 1597 if (!qa) 1598 return; 1599 1600 /* get a sensible description for the event */ 1601 e_cal_component_get_summary (comp, &text); 1602 e_cal_component_get_organizer (comp, &organiser); 1603 1604 if (text.value) 1605 summary = text.value; 1606 else 1607 summary = _("No summary available."); 1608 1609 e_cal_component_get_description_list (comp, &text_list); 1610 1611 if (text_list) { 1612 text = *((ECalComponentText *) text_list->data); 1613 if (text.value) 1614 description = text.value; 1615 else 1616 description = _("No description available."); 1617 } else { 1618 description = _("No description available."); 1619 } 1620 1621 e_cal_component_free_text_list (text_list); 1622 1623 e_cal_component_get_location (comp, &location); 1624 1625 if (!location) 1626 location = _("No location information available."); 1627 1628 /* create the tray icon */ 1629 if (tray_icon == NULL) { 1630 tray_icon = gtk_status_icon_new (); 1631 gtk_status_icon_set_from_icon_name ( 1632 tray_icon, "appointment-soon"); 1633 g_signal_connect ( 1634 tray_icon, "activate", 1635 G_CALLBACK (icon_activated), NULL); 1636 g_signal_connect ( 1637 tray_icon, "popup-menu", 1638 G_CALLBACK (popup_menu), NULL); 1639 } 1640 1641 current_zone = config_data_get_timezone (); 1642 alarm_str = timet_to_str_with_zone (trigger, current_zone); 1643 start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone); 1644 end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone); 1645 time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end); 1646 1647 str = g_strdup_printf ( 1648 "%s\n%s %s", 1649 summary, start_str, time_str); 1650 1651 /* create the private structure */ 1652 tray_data = g_new0 (TrayIconData, 1); 1653 tray_data->summary = g_strdup (summary); 1654 tray_data->description = g_strdup (description); 1655 tray_data->location = g_strdup (location); 1656 tray_data->trigger = trigger; 1657 tray_data->cqa = cqa; 1658 tray_data->alarm_id = alarm_id; 1659 tray_data->comp = g_object_ref (e_cal_component_clone (comp)); 1660 tray_data->cal_client = cqa->parent_client->cal_client; 1661 tray_data->view = g_object_ref (cqa->parent_client->view); 1662 tray_data->blink_state = FALSE; 1663 tray_data->snooze_set = FALSE; 1664 g_object_ref (tray_data->cal_client); 1665 1666 /* Task to add tray_data to the global tray_icon_list */ 1667 tray_list_add_new (tray_data); 1668 1669 if (g_list_length (tray_icons_list) > 1) { 1670 gchar *tip; 1671 1672 tip = g_strdup_printf (ngettext ( 1673 "You have %d reminder", "You have %d reminders", 1674 g_list_length (tray_icons_list)), 1675 g_list_length (tray_icons_list)); 1676 gtk_status_icon_set_tooltip_text (tray_icon, tip); 1677 } 1678 else { 1679 gtk_status_icon_set_tooltip_text (tray_icon, str); 1680 } 1681 1682 g_free (start_str); 1683 g_free (end_str); 1684 g_free (alarm_str); 1685 g_free (time_str); 1686 g_free (str); 1687 1688 g_signal_connect ( 1689 tray_data->view, "objects_removed", 1690 G_CALLBACK (on_dialog_objs_removed_cb), tray_data); 1691 1692 /* FIXME: We should remove this check */ 1693 if (!config_data_get_notify_with_tray ()) { 1694 tray_blink_id = -1; 1695 open_alarm_dialog (tray_data); 1696 if (alarm_notifications_dialog) 1697 gtk_window_stick (GTK_WINDOW ( 1698 alarm_notifications_dialog->dialog)); 1699 } else { 1700 if (tray_blink_id == -1) { 1701 tray_blink_countdown = 30; 1702 tray_blink_id = g_timeout_add (500, tray_icon_blink_cb, tray_data); 1703 } 1704 } 1705 } 1706 1707 #ifdef HAVE_LIBNOTIFY 1708 static void 1709 popup_notification (time_t trigger, 1710 CompQueuedAlarms *cqa, 1711 gpointer alarm_id, 1712 gboolean use_description) 1713 { 1714 QueuedAlarm *qa; 1715 ECalComponent *comp; 1716 const gchar *summary, *location; 1717 ECalComponentText text; 1718 gchar *str, *start_str, *end_str, *alarm_str, *time_str; 1719 icaltimezone *current_zone; 1720 ECalComponentOrganizer organiser; 1721 NotifyNotification *n; 1722 gchar *body; 1723 1724 debug (("...")); 1725 1726 comp = cqa->alarms->comp; 1727 qa = lookup_queued_alarm (cqa, alarm_id); 1728 if (!qa) 1729 return; 1730 if (!notify_is_initted ()) 1731 notify_init ("Evolution Alarm Notify"); 1732 1733 /* get a sensible description for the event */ 1734 e_cal_component_get_summary (comp, &text); 1735 e_cal_component_get_organizer (comp, &organiser); 1736 1737 if (text.value) 1738 summary = text.value; 1739 else 1740 summary = _("No summary available."); 1741 1742 e_cal_component_get_location (comp, &location); 1743 1744 /* create the tray icon */ 1745 1746 current_zone = config_data_get_timezone (); 1747 alarm_str = timet_to_str_with_zone (trigger, current_zone); 1748 start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone); 1749 end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone); 1750 time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end); 1751 1752 str = g_strdup_printf ( 1753 "%s %s", 1754 start_str, time_str); 1755 1756 if (organiser.cn) { 1757 if (location) 1758 body = g_strdup_printf ( 1759 "<b>%s</b>\n%s %s\n%s %s", 1760 organiser.cn, _("Location:"), 1761 location, start_str, time_str); 1762 else 1763 body = g_strdup_printf ( 1764 "<b>%s</b>\n%s %s", 1765 organiser.cn, start_str, time_str); 1766 } 1767 else { 1768 if (location) 1769 body = g_strdup_printf ( 1770 "%s %s\n%s %s", _("Location:"), 1771 location, start_str, time_str); 1772 else 1773 body = g_strdup_printf ( 1774 "%s %s", start_str, time_str); 1775 } 1776 1777 n = notify_notification_new (summary, body, "appointment-soon"); 1778 if (!notify_notification_show (n, NULL)) 1779 g_warning ("Could not send notification to daemon\n"); 1780 1781 /* create the private structure */ 1782 g_free (start_str); 1783 g_free (end_str); 1784 g_free (alarm_str); 1785 g_free (time_str); 1786 g_free (str); 1787 1788 } 1789 #endif 1790 1791 /* Performs notification of an audio alarm */ 1792 static void 1793 audio_notification (time_t trigger, 1794 CompQueuedAlarms *cqa, 1795 gpointer alarm_id) 1796 { 1797 QueuedAlarm *qa; 1798 ECalComponent *comp; 1799 ECalComponentAlarm *alarm; 1800 icalattach *attach; 1801 gint flag = 0; 1802 1803 debug (("...")); 1804 1805 comp = cqa->alarms->comp; 1806 qa = lookup_queued_alarm (cqa, alarm_id); 1807 if (!qa) 1808 return; 1809 1810 alarm = e_cal_component_get_alarm (comp, qa->instance->auid); 1811 g_return_if_fail (alarm != NULL); 1812 1813 e_cal_component_alarm_get_attach (alarm, &attach); 1814 e_cal_component_alarm_free (alarm); 1815 1816 if (attach && icalattach_get_is_url (attach)) { 1817 const gchar *url; 1818 1819 url = icalattach_get_url (attach); 1820 if (url && *url) { 1821 gchar *filename; 1822 GError *error = NULL; 1823 1824 filename = g_filename_from_uri (url, NULL, &error); 1825 1826 if (error != NULL) { 1827 g_warning ("%s: %s", G_STRFUNC, error->message); 1828 g_error_free (error); 1829 } else if (filename && g_file_test (filename, G_FILE_TEST_EXISTS)) { 1830 #ifdef HAVE_CANBERRA 1831 flag = 1; 1832 ca_context_play ( 1833 ca_gtk_context_get (), 0, 1834 CA_PROP_MEDIA_FILENAME, filename, NULL); 1835 #endif 1836 } 1837 1838 g_free (filename); 1839 } 1840 } 1841 1842 if (!flag) 1843 gdk_beep (); 1844 1845 if (attach) 1846 icalattach_unref (attach); 1847 1848 } 1849 1850 /* Performs notification of a mail alarm */ 1851 static void 1852 mail_notification (time_t trigger, 1853 CompQueuedAlarms *cqa, 1854 gpointer alarm_id) 1855 { 1856 GtkWidget *container; 1857 GtkWidget *dialog; 1858 GtkWidget *label; 1859 1860 /* FIXME */ 1861 1862 debug (("...")); 1863 1864 if (!e_client_check_capability ( 1865 E_CLIENT (cqa->parent_client->cal_client), 1866 CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS)) 1867 return; 1868 1869 dialog = gtk_dialog_new_with_buttons ( 1870 _("Warning"), NULL, 0, 1871 GTK_STOCK_OK, GTK_RESPONSE_CANCEL, 1872 NULL); 1873 label = gtk_label_new ( 1874 _("Evolution does not support calendar reminders with\n" 1875 "email notifications yet, but this reminder was\n" 1876 "configured to send an email. Evolution will display\n" 1877 "a normal reminder dialog box instead.")); 1878 gtk_widget_show (label); 1879 1880 container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); 1881 gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4); 1882 1883 gtk_dialog_run (GTK_DIALOG (dialog)); 1884 gtk_widget_destroy (dialog); 1885 } 1886 1887 /* Performs notification of a procedure alarm */ 1888 static gboolean 1889 procedure_notification_dialog (const gchar *cmd, 1890 const gchar *url) 1891 { 1892 GtkWidget *container; 1893 GtkWidget *dialog; 1894 GtkWidget *label; 1895 GtkWidget *checkbox; 1896 gchar *str; 1897 gint btn; 1898 1899 debug (("...")); 1900 1901 if (config_data_is_blessed_program (url)) 1902 return TRUE; 1903 1904 dialog = gtk_dialog_new_with_buttons ( 1905 _("Warning"), NULL, 0, 1906 GTK_STOCK_NO, GTK_RESPONSE_CANCEL, 1907 GTK_STOCK_YES, GTK_RESPONSE_OK, 1908 NULL); 1909 1910 str = g_strdup_printf ( 1911 _("An Evolution Calendar reminder is about to trigger. " 1912 "This reminder is configured to run the following program:\n\n" 1913 " %s\n\n" 1914 "Are you sure you want to run this program?"), 1915 cmd); 1916 label = gtk_label_new (str); 1917 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); 1918 gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); 1919 gtk_widget_show (label); 1920 1921 container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); 1922 gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4); 1923 g_free (str); 1924 1925 checkbox = gtk_check_button_new_with_label 1926 (_("Do not ask me about this program again.")); 1927 gtk_widget_show (checkbox); 1928 gtk_box_pack_start (GTK_BOX (container), checkbox, TRUE, TRUE, 4); 1929 1930 /* Run the dialog */ 1931 btn = gtk_dialog_run (GTK_DIALOG (dialog)); 1932 if (btn == GTK_RESPONSE_OK && 1933 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbox))) 1934 config_data_save_blessed_program (url); 1935 gtk_widget_destroy (dialog); 1936 1937 return (btn == GTK_RESPONSE_OK); 1938 } 1939 1940 static void 1941 procedure_notification (time_t trigger, 1942 CompQueuedAlarms *cqa, 1943 gpointer alarm_id) 1944 { 1945 QueuedAlarm *qa; 1946 ECalComponent *comp; 1947 ECalComponentAlarm *alarm; 1948 ECalComponentText description; 1949 icalattach *attach; 1950 const gchar *url; 1951 gchar *cmd; 1952 gboolean result = TRUE; 1953 1954 debug (("...")); 1955 1956 comp = cqa->alarms->comp; 1957 qa = lookup_queued_alarm (cqa, alarm_id); 1958 if (!qa) 1959 return; 1960 1961 alarm = e_cal_component_get_alarm (comp, qa->instance->auid); 1962 g_return_if_fail (alarm != NULL); 1963 1964 e_cal_component_alarm_get_attach (alarm, &attach); 1965 e_cal_component_alarm_get_description (alarm, &description); 1966 e_cal_component_alarm_free (alarm); 1967 1968 /* If the alarm has no attachment, simply display a notification dialog. */ 1969 if (!attach) 1970 goto fallback; 1971 1972 if (!icalattach_get_is_url (attach)) { 1973 icalattach_unref (attach); 1974 goto fallback; 1975 } 1976 1977 url = icalattach_get_url (attach); 1978 g_return_if_fail (url != NULL); 1979 1980 /* Ask for confirmation before executing the stuff */ 1981 if (description.value) 1982 cmd = g_strconcat (url, " ", description.value, NULL); 1983 else 1984 cmd = (gchar *) url; 1985 1986 if (procedure_notification_dialog (cmd, url)) 1987 result = g_spawn_command_line_async (cmd, NULL); 1988 1989 if (cmd != (gchar *) url) 1990 g_free (cmd); 1991 1992 icalattach_unref (attach); 1993 1994 /* Fall back to display notification if we got an error */ 1995 if (result == FALSE) 1996 goto fallback; 1997 1998 return; 1999 2000 fallback: 2001 2002 display_notification (trigger, cqa, alarm_id, FALSE); 2003 } 2004 2005 static gboolean 2006 check_midnight_refresh (gpointer user_data) 2007 { 2008 time_t new_midnight; 2009 icaltimezone *zone; 2010 2011 debug (("...")); 2012 2013 zone = config_data_get_timezone (); 2014 new_midnight = time_day_end_with_zone (time (NULL), zone); 2015 2016 if (new_midnight > midnight) { 2017 struct _midnight_refresh_msg *msg; 2018 2019 msg = g_slice_new0 (struct _midnight_refresh_msg); 2020 msg->header.func = (MessageFunc) midnight_refresh_async; 2021 msg->remove = FALSE; 2022 2023 message_push ((Message *) msg); 2024 } 2025 2026 return TRUE; 2027 } 2028 2029 static gboolean 2030 check_wall_clock_time_changed (gpointer user_data) 2031 { 2032 static gint64 expected_wall_clock_time = 0; 2033 gint64 wall_clock_time; 2034 2035 #define ADD_SECONDS(to, secs) ((to) + ((secs) * 1000000)) 2036 2037 wall_clock_time = g_get_real_time (); 2038 2039 /* use one second margin */ 2040 if (wall_clock_time > ADD_SECONDS (expected_wall_clock_time, 1) || 2041 wall_clock_time < ADD_SECONDS (expected_wall_clock_time, -1)) { 2042 debug (("Current wall-clock time differs from expected, rescheduling alarms")); 2043 check_midnight_refresh (NULL); 2044 alarm_reschedule_timeout (); 2045 } 2046 2047 expected_wall_clock_time = ADD_SECONDS (wall_clock_time, 60); 2048 2049 #undef ADD_SECONDS 2050 2051 return TRUE; 2052 } 2053 2054 /** 2055 * alarm_queue_init: 2056 * 2057 * Initializes the alarm queueing system. This should be called near the 2058 * beginning of the program. 2059 **/ 2060 void 2061 alarm_queue_init (gpointer data) 2062 { 2063 an = data; 2064 g_return_if_fail (alarm_queue_inited == FALSE); 2065 2066 debug (("...")); 2067 2068 client_alarms_hash = g_hash_table_new (g_direct_hash, g_direct_equal); 2069 queue_midnight_refresh (); 2070 2071 if (config_data_get_last_notification_time (NULL) == -1) { 2072 time_t tmval = time_day_begin (time (NULL)); 2073 debug (("Setting last notification time to %s", e_ctime (&tmval))); 2074 config_data_set_last_notification_time (NULL, tmval); 2075 } 2076 2077 /* install timeout handler (every 30 mins) for not missing the midnight refresh */ 2078 g_timeout_add_seconds (1800, check_midnight_refresh, NULL); 2079 2080 /* monotonic time doesn't change during hibernation, while the wall clock time does, 2081 * thus check for wall clock time changes and reschedule alarms when it changes */ 2082 g_timeout_add_seconds (60, check_wall_clock_time_changed, NULL); 2083 2084 #ifdef HAVE_LIBNOTIFY 2085 notify_init ("Evolution Alarms"); 2086 #endif 2087 2088 alarm_queue_inited = TRUE; 2089 } 2090 2091 static gboolean 2092 free_client_alarms_cb (gpointer key, 2093 gpointer value, 2094 gpointer user_data) 2095 { 2096 ClientAlarms *ca = value; 2097 2098 debug (("ca=%p", ca)); 2099 2100 if (ca) { 2101 remove_client_alarms (ca); 2102 if (ca->cal_client) { 2103 debug (("Disconnecting Client")); 2104 2105 g_signal_handlers_disconnect_matched ( 2106 ca->cal_client, G_SIGNAL_MATCH_DATA, 2107 0, 0, NULL, NULL, ca); 2108 g_object_unref (ca->cal_client); 2109 } 2110 2111 if (ca->view) { 2112 debug (("Disconnecting Query")); 2113 2114 g_signal_handlers_disconnect_matched ( 2115 ca->view, G_SIGNAL_MATCH_DATA, 2116 0, 0, NULL, NULL, ca); 2117 g_object_unref (ca->view); 2118 } 2119 2120 g_hash_table_destroy (ca->uid_alarms_hash); 2121 2122 g_free (ca); 2123 return TRUE; 2124 } 2125 2126 return FALSE; 2127 } 2128 2129 /** 2130 * alarm_queue_done: 2131 * 2132 * Shuts down the alarm queueing system. This should be called near the end 2133 * of the program. All the monitored calendar clients should already have been 2134 * unregistered with alarm_queue_remove_client(). 2135 **/ 2136 void 2137 alarm_queue_done (void) 2138 { 2139 g_return_if_fail (alarm_queue_inited); 2140 2141 /* All clients must be unregistered by now */ 2142 g_return_if_fail (g_hash_table_size (client_alarms_hash) == 0); 2143 2144 debug (("...")); 2145 2146 g_hash_table_foreach_remove ( 2147 client_alarms_hash, (GHRFunc) free_client_alarms_cb, NULL); 2148 g_hash_table_destroy (client_alarms_hash); 2149 client_alarms_hash = NULL; 2150 2151 if (midnight_refresh_id != NULL) { 2152 alarm_remove (midnight_refresh_id); 2153 midnight_refresh_id = NULL; 2154 } 2155 2156 alarm_queue_inited = FALSE; 2157 } 2158 2159 static gboolean 2160 compare_ids (gpointer a, 2161 gpointer b) 2162 { 2163 ECalComponentId *id, *id1; 2164 2165 id = a; 2166 id1 = b; 2167 if (id->uid != NULL && id1->uid != NULL) { 2168 if (g_str_equal (id->uid, id1->uid)) { 2169 2170 if (id->rid && id1->rid) 2171 return g_str_equal (id->rid, id1->rid); 2172 else if (!(id->rid && id1->rid)) 2173 return TRUE; 2174 } 2175 } 2176 return FALSE; 2177 } 2178 2179 static guint 2180 hash_ids (gpointer a) 2181 { 2182 ECalComponentId *id =a; 2183 2184 return g_str_hash (id->uid); 2185 } 2186 2187 struct _alarm_client_msg { 2188 Message header; 2189 ECalClient *cal_client; 2190 }; 2191 2192 static void 2193 alarm_queue_add_async (struct _alarm_client_msg *msg) 2194 { 2195 ClientAlarms *ca; 2196 ECalClient *cal_client = msg->cal_client; 2197 2198 g_return_if_fail (alarm_queue_inited); 2199 g_return_if_fail (cal_client != NULL); 2200 g_return_if_fail (E_IS_CAL_CLIENT (cal_client)); 2201 2202 ca = lookup_client (cal_client); 2203 if (ca) { 2204 /* We already have it. Unref the passed one*/ 2205 g_object_unref (cal_client); 2206 return; 2207 } 2208 2209 debug (("client=%p", cal_client)); 2210 2211 ca = g_new (ClientAlarms, 1); 2212 2213 ca->cal_client = cal_client; 2214 ca->view = NULL; 2215 2216 g_hash_table_insert (client_alarms_hash, cal_client, ca); 2217 2218 ca->uid_alarms_hash = g_hash_table_new ( 2219 (GHashFunc) hash_ids, (GEqualFunc) compare_ids); 2220 2221 load_alarms_for_today (ca); 2222 2223 g_slice_free (struct _alarm_client_msg, msg); 2224 } 2225 2226 /** 2227 * alarm_queue_add_client: 2228 * @cal_client: A calendar client. 2229 * 2230 * Adds a calendar client to the alarm queueing system. Alarm trigger 2231 * notifications will be presented at the appropriate times. The client should 2232 * be removed with alarm_queue_remove_client() when receiving notifications 2233 * from it is no longer desired. 2234 * 2235 * A client can be added any number of times to the alarm queueing system, 2236 * but any single alarm trigger will only be presented once for a particular 2237 * client. The client must still be removed the same number of times from the 2238 * queueing system when it is no longer wanted. 2239 **/ 2240 void 2241 alarm_queue_add_client (ECalClient *cal_client) 2242 { 2243 struct _alarm_client_msg *msg; 2244 2245 msg = g_slice_new0 (struct _alarm_client_msg); 2246 msg->header.func = (MessageFunc) alarm_queue_add_async; 2247 msg->cal_client = g_object_ref (cal_client); 2248 2249 message_push ((Message *) msg); 2250 } 2251 2252 /* Removes a component an its alarms */ 2253 static void 2254 remove_cqa (ClientAlarms *ca, 2255 ECalComponentId *id, 2256 CompQueuedAlarms *cqa) 2257 { 2258 2259 /* If a component is present, then it means we must have alarms queued 2260 * for it. 2261 */ 2262 g_return_if_fail (cqa->queued_alarms != NULL); 2263 2264 debug (("removing %d alarms", g_slist_length (cqa->queued_alarms))); 2265 remove_alarms (cqa, TRUE); 2266 } 2267 2268 static gboolean 2269 remove_comp_by_id (gpointer key, 2270 gpointer value, 2271 gpointer userdata) 2272 { 2273 2274 ClientAlarms *ca = (ClientAlarms *) userdata; 2275 2276 debug (("...")); 2277 2278 /* if (!g_hash_table_size (ca->uid_alarms_hash)) */ 2279 /* return; */ 2280 2281 remove_cqa (ca, (ECalComponentId *) key, (CompQueuedAlarms *) value); 2282 2283 return TRUE; 2284 } 2285 2286 /* Removes all the alarms queued for a particular calendar client */ 2287 static void 2288 remove_client_alarms (ClientAlarms *ca) 2289 { 2290 debug (("size %d", g_hash_table_size (ca->uid_alarms_hash))); 2291 2292 g_hash_table_foreach_remove ( 2293 ca->uid_alarms_hash, (GHRFunc) remove_comp_by_id, ca); 2294 2295 /* The hash table should be empty now */ 2296 g_return_if_fail (g_hash_table_size (ca->uid_alarms_hash) == 0); 2297 } 2298 2299 /** 2300 * alarm_queue_remove_client: 2301 * @client: A calendar client. 2302 * 2303 * Removes a calendar client from the alarm queueing system. 2304 **/ 2305 static void 2306 alarm_queue_remove_async (struct _alarm_client_msg *msg) 2307 { 2308 ClientAlarms *ca; 2309 ECalClient *cal_client = msg->cal_client; 2310 2311 g_return_if_fail (alarm_queue_inited); 2312 g_return_if_fail (cal_client != NULL); 2313 g_return_if_fail (E_IS_CAL_CLIENT (cal_client)); 2314 2315 ca = lookup_client (cal_client); 2316 g_return_if_fail (ca != NULL); 2317 2318 debug (("...")); 2319 remove_client_alarms (ca); 2320 2321 /* Clean up */ 2322 if (ca->cal_client) { 2323 debug (("Disconnecting Client")); 2324 2325 g_signal_handlers_disconnect_matched ( 2326 ca->cal_client, G_SIGNAL_MATCH_DATA, 2327 0, 0, NULL, NULL, ca); 2328 g_object_unref (ca->cal_client); 2329 ca->cal_client = NULL; 2330 } 2331 2332 if (ca->view) { 2333 debug (("Disconnecting Query")); 2334 2335 g_signal_handlers_disconnect_matched ( 2336 ca->view, G_SIGNAL_MATCH_DATA, 2337 0, 0, NULL, NULL, ca); 2338 g_object_unref (ca->view); 2339 ca->view = NULL; 2340 } 2341 2342 g_hash_table_destroy (ca->uid_alarms_hash); 2343 ca->uid_alarms_hash = NULL; 2344 2345 g_free (ca); 2346 2347 g_hash_table_remove (client_alarms_hash, cal_client); 2348 2349 g_slice_free (struct _alarm_client_msg, msg); 2350 } 2351 2352 /** alarm_queue_remove_client 2353 * 2354 * asynchronously remove client from alarm queue. 2355 * @cal_client: Client to remove. 2356 * @immediately: Indicates whether use thread or do it right now. 2357 */ 2358 2359 void 2360 alarm_queue_remove_client (ECalClient *cal_client, 2361 gboolean immediately) 2362 { 2363 struct _alarm_client_msg *msg; 2364 2365 msg = g_slice_new0 (struct _alarm_client_msg); 2366 msg->header.func = (MessageFunc) alarm_queue_remove_async; 2367 msg->cal_client = cal_client; 2368 2369 if (immediately) { 2370 alarm_queue_remove_async (msg); 2371 } else 2372 message_push ((Message *) msg); 2373 } 2374 2375 /* Update non-time related variables for various structures on modification 2376 * of an existing component to be called only from query_objects_changed_cb */ 2377 static void 2378 update_cqa (CompQueuedAlarms *cqa, 2379 ECalComponent *newcomp) 2380 { 2381 ECalComponent *oldcomp; 2382 ECalComponentAlarms *alarms = NULL; 2383 GSList *qa_list; /* List of current QueuedAlarms corresponding to cqa */ 2384 time_t from, to; 2385 icaltimezone *zone; 2386 ECalComponentAlarmAction omit[] = {-1}; 2387 2388 oldcomp = cqa->alarms->comp; 2389 2390 zone = config_data_get_timezone (); 2391 from = time_day_begin_with_zone (time (NULL), zone); 2392 to = time_day_end_with_zone (time (NULL), zone); 2393 2394 debug (("Generating alarms between %s and %s", e_ctime (&from), e_ctime (&to))); 2395 alarms = e_cal_util_generate_alarms_for_comp ( 2396 newcomp, from, to, omit, e_cal_client_resolve_tzid_cb, 2397 cqa->parent_client->cal_client, zone); 2398 2399 /* Update auids in Queued Alarms*/ 2400 for (qa_list = cqa->queued_alarms; qa_list; qa_list = qa_list->next) { 2401 QueuedAlarm *qa = qa_list->data; 2402 gchar *check_auid = (gchar *) qa->instance->auid; 2403 ECalComponentAlarm *alarm; 2404 2405 alarm = e_cal_component_get_alarm (newcomp, check_auid); 2406 if (alarm) { 2407 e_cal_component_alarm_free (alarm); 2408 continue; 2409 } else { 2410 alarm = e_cal_component_get_alarm (oldcomp, check_auid); 2411 if (alarm) { /* Need to update QueuedAlarms */ 2412 e_cal_component_alarm_free (alarm); 2413 if (alarms == NULL) { 2414 debug (("No alarms found in the modified component")); 2415 break; 2416 } 2417 update_qa (alarms, qa); 2418 } 2419 else 2420 g_warning ("Failed in auid lookup for old component also\n"); 2421 } 2422 } 2423 2424 /* Update the actual component stored in CompQueuedAlarms structure */ 2425 g_object_unref (cqa->alarms->comp); 2426 cqa->alarms->comp = newcomp; 2427 if (alarms != NULL) 2428 e_cal_component_alarms_free (alarms); 2429 } 2430 2431 static void 2432 update_qa (ECalComponentAlarms *alarms, 2433 QueuedAlarm *qa) 2434 { 2435 ECalComponentAlarmInstance *al_inst; 2436 GSList *instance_list; 2437 2438 debug (("...")); 2439 for (instance_list = alarms->alarms; 2440 instance_list; 2441 instance_list = instance_list->next) { 2442 al_inst = instance_list->data; 2443 /* FIXME If two or more alarm instances (audio, note) 2444 * for same component have same trigger... */ 2445 if (al_inst->trigger == qa->orig_trigger) { 2446 g_free ((gchar *) qa->instance->auid); 2447 qa->instance->auid = g_strdup (al_inst->auid); 2448 break; 2449 } 2450 } 2451 }