/* * CDE - Common Desktop Environment * * Copyright (c) 1993-2012, The Open Group. All rights reserved. * * These libraries and programs are free software; you can * redistribute them and/or modify them under the terms of the GNU * Lesser General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * These libraries and programs are distributed in the hope that * they will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with these libraries and programs; if not, write * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth * Floor, Boston, MA 02110-1301 USA */ /* $XConsortium: reminder.c /main/4 1995/11/09 12:48:20 rswiston $ */ /* * (c) Copyright 1993, 1994 Hewlett-Packard Company * (c) Copyright 1993, 1994 International Business Machines Corp. * (c) Copyright 1993, 1994 Novell, Inc. * (c) Copyright 1993, 1994 Sun Microsystems, Inc. */ #include #include #include #include #include #include "cmscalendar.h" #include "reminder.h" #include "appt4.h" #include "repeat.h" #include "v4ops.h" #include "v5ops.h" #include "rerule.h" #include "reutil.h" #include "iso8601.h" /****************************************************************************** * forward declaration of static functions used within the file ******************************************************************************/ static _DtCmsRemInfo *_BuildReminder4Entry( cms_entry *entry, int aindex, List_node *lnode, time_t cutoff, _DtCmsRemInfo **active); static void _InsertReminder( _DtCmsRemInfo **head, _DtCmsRemInfo *rem); static CSA_return_code _GetNextReminders( _DtCmsRemQueue *remq, time_t tick, cms_reminder_ref **rems); static CSA_return_code _GetNextRemindersFromQ( _DtCmsRemQueue *remq, int qindex, time_t tick, cms_reminder_ref **rems); static cms_reminder_ref *_GetReminderRefFromInfo( _DtCmsRemInfo *rem, time_t starttime, time_t runtime); static void _RemoveReminderFromQ( _DtCmsRemQueue *remq, int qindex, cms_entry *entry, List_node *lnode, time_t starttime, boolean_t delfwd); static _DtCmsRemInfo *_RemoveReminderFromList( _DtCmsRemInfo **remq, cms_entry *entry, time_t starttime, boolean_t delfwd); static long _GetNextActiveTick( cms_entry *entry, time_t target, time_t lasttick, RepeatEvent *re); static void _UpdateReminderQ(_DtCmsRemQueue *remq, int qindex); static CSA_return_code _GetNextRemindersFromList( _DtCmsRemInfo *rlist, time_t giventime, cms_reminder_ref **rf_r); static void _DtCmsAddReminder4EntryToQ( _DtCmsRemQueue *remq, cms_entry *entry, int aindex, List_node *lnode); /***************************************************************************** * extern functions used in the library *****************************************************************************/ extern void _DtCmsAddReminderV4(Rm_que **qhead, Rm_que *p_reminder) { Rm_que *p_prev; Rm_que *p_node; if (p_reminder == NULL) return; p_prev = NULL; p_node = *qhead; while (p_node != NULL) { if (p_reminder->remind_at < p_node->remind_at) break; p_prev = p_node; p_node = p_node->next; } if (p_prev == NULL) { p_reminder->next = *qhead; *qhead = p_reminder; } else { p_reminder->next = p_prev->next; p_prev->next = p_reminder; } } extern Rm_que * _DtCmsRemoveReminderV4(Rm_que **qhead, Rm_que *p_prev, Rm_que *p_curr) { if (p_prev == NULL) *qhead = p_curr->next; else p_prev->next = p_curr->next; return (p_curr->next); } extern Rm_que * build_reminder( time_t current_time, Appt_4 *p_appt, Attr_4 p_attr, time_t start_tick, u_int start_ord) { int ntimes; Period_4 period; Rm_que *p_reminder = NULL; /* Ignore the expired or unqualified reminder. */ p_reminder = NULL; if (is_appointment(p_appt)) { /* The event is not expired yet, build the reminder */ if (start_tick >= current_time) { if ((p_reminder = (Rm_que *)calloc(1, sizeof(Rm_que))) == NULL) return (NULL); p_reminder->remind_ord = 0; } } else { period = p_appt->period; ntimes = _DtCms_get_ninstance_v4(p_appt); while (start_ord <= ntimes) { /* Event is not expired */ if (start_tick >= current_time) { /* Event is not cancelled */ if (!_DtCms_marked_4_cancellation (p_appt, start_ord)) { if ((p_reminder = (Rm_que *)calloc(1, sizeof(Rm_que))) == NULL) return (NULL); p_reminder->remind_ord = start_ord; break; } } /* Event is expired, advance to next event */ start_tick = _DtCms_next_tick_v4 (start_tick, period); start_ord++; } } if (p_reminder != NULL) { p_reminder->remind_at = start_tick; p_reminder->appt = p_appt; p_reminder->attr = p_attr; } return (p_reminder); } extern Reminder_4 * _DtCmsGetReminderInfoV4(Rm_que *original) { Reminder_4 *copy; if (original == NULL) return (NULL); if ((copy = calloc(1, sizeof(Reminder_4))) != NULL) { copy->tick = original->remind_at; copy->next = NULL; if ((copy->attr.attr = strdup(original->attr->attr)) == NULL) { free(copy); return (NULL); } if ((copy->attr.value = strdup(original->attr->value)) == NULL) { _DtCm_free_reminder4(copy); return (NULL); } if ((copy->attr.clientdata = strdup(original->attr->clientdata)) == NULL) { _DtCm_free_reminder4(copy); return (NULL); } copy->attr.next = NULL; copy->appt_id.tick = copy->tick + atol (copy->attr.value); copy->appt_id.key = original->appt->appt_id.key; } return (copy); } extern void _DtCmsPrintReminderListV4(Rm_que *qhead) { Rm_que *p_node = qhead; char *temp=NULL; if (qhead == NULL) return; fprintf (stderr, "--- Active Reminder Queue ---\n"); while (p_node != NULL) { if (temp = strchr(p_node->appt->what, '\n')) *temp = '\0'; fprintf(stderr, "%s (%d) %s: %s\n", ctime(&p_node->remind_at), p_node->remind_ord, p_node->attr->attr, p_node->appt->what); if (temp) *temp = '\n'; p_node = p_node->next; } } /* * Obsolete all reminders (iff ord == 0) whose parent appointment matches a * given appointment. If ord != 0, then obsolete all active reminders whose * serving ordinal matches ord in additional to the matching of its parent * appointment. The reminder of the next available instance will be put on the * reminder queue. */ extern void _DtCmsObsoleteReminderV4( Rm_que **qhead, Appt_4 *p_appt, int ord, boolean_t delforward) { Rm_que *p_prev; Rm_que *p_next; Rm_que *p_node; Rm_que *p_hdr = NULL; p_prev = NULL; p_node = *qhead; while (p_node != NULL) { if ((p_node->appt != p_appt) || ((ord != 0) && (ord != p_node->remind_ord)) || ((ord != 0) && delforward && p_node->remind_ord < ord)) { p_next = p_node->next; } else { /* Found the obsolete reminder. */ p_next = _DtCmsRemoveReminderV4 (qhead,p_prev,p_node); if (ord == 0) free (p_node); else { /* Chain the obsolete reminders together to * re-calculate the new reminders. */ p_node->next = p_hdr; p_hdr = p_node; } p_node = p_prev; } p_prev = p_node; p_node = p_next; } /* Build the reminders of the next instance from obsoleted reminders. * Note, we can't put this code in the above 'while'-loop because it * may confuse the loop. */ while (p_hdr != NULL) { p_next = p_hdr->next; p_node = build_reminder(p_hdr->remind_at+1, p_hdr->appt, p_hdr->attr, p_hdr->remind_at, ord); _DtCmsAddReminderV4 (qhead, p_node); free (p_hdr); p_hdr = p_next; } } #define _DtCms_NUM_REMINDERS 4 extern void _DtCmsAddReminders4Entry( _DtCmsRemQueue **remq, cms_entry *entry, List_node *lnode) { _DtCmsRemQueue *queue; int i; if (*remq == NULL) { if ((queue = (_DtCmsRemQueue *)calloc(1, sizeof(_DtCmsRemQueue))) == NULL) return; /* initialize queues for the cde defined reminders */ if ((queue->aindex = (int *)malloc(sizeof(int) * _DtCms_NUM_REMINDERS)) == NULL) { free(queue); return; } if ((queue->names = (char **)malloc(sizeof(char *) * _DtCms_NUM_REMINDERS)) == NULL) { free(queue->aindex); free(queue); return; } if ((queue->active = (_DtCmsRemInfo **)calloc(1, sizeof(_DtCmsRemInfo) * _DtCms_NUM_REMINDERS)) == NULL) { free(queue->names); free(queue->aindex); free(queue); return; } if ((queue->oldhead = (_DtCmsRemInfo **)calloc(1, sizeof(_DtCmsRemInfo) * _DtCms_NUM_REMINDERS)) == NULL) { free(queue->active); free(queue->names); free(queue->aindex); free(queue); return; } queue->num_queues = _DtCms_NUM_REMINDERS; queue->aindex[0] = CSA_ENTRY_ATTR_AUDIO_REMINDER_I; queue->names[0] = CSA_ENTRY_ATTR_AUDIO_REMINDER; queue->aindex[1] = CSA_ENTRY_ATTR_FLASHING_REMINDER_I; queue->names[1] = CSA_ENTRY_ATTR_FLASHING_REMINDER; queue->aindex[2] = CSA_ENTRY_ATTR_MAIL_REMINDER_I; queue->names[2] = CSA_ENTRY_ATTR_MAIL_REMINDER; queue->aindex[3] = CSA_ENTRY_ATTR_POPUP_REMINDER_I; queue->names[3] = CSA_ENTRY_ATTR_POPUP_REMINDER; /* set cutoff to be half an hour earlier than now * to compensate time difference between machines */ queue->cutoff = time(0) - 60 * 30; *remq = queue; } /* Add the qualified reminder attrs to the reminder queue */ for (i = 1; i <= entry->num_attrs; i++) { if (entry->attrs[i].value == NULL || entry->attrs[i].value->type != CSA_VALUE_REMINDER) continue; _DtCmsAddReminder4EntryToQ(*remq, entry, i, lnode); } } extern void _DtCmsObsoleteReminder4Entry( _DtCmsRemQueue *remq, cms_entry *entry, List_node *lnode, time_t starttime, boolean_t delfwd) { int i; for (i = 0; i < remq->num_queues; i++) { if (entry->attrs[remq->aindex[i]].value) _RemoveReminderFromQ(remq, i, entry, lnode, starttime, delfwd); } } extern CSA_return_code _DtCmsLookupReminder( _DtCmsRemQueue *remq, time_t tick, uint num_names, cms_attr_name *names, cms_reminder_ref **rems) { int i, j; CSA_return_code stat; *rems = NULL; if (remq == NULL) return (CSA_SUCCESS); if (num_names == 0) return (_GetNextReminders(remq, tick, rems)); for (i = 0; i < num_names; i++) { for (j = 0; j < remq->num_queues; j++) { if (strcmp(names[i].name, remq->names[j]) == 0) { names[i].num = remq->aindex[j]; if ((stat = _GetNextRemindersFromQ(remq, i, tick, rems)) != CSA_SUCCESS) { if (*rems) _DtCmsFreeReminderRef(*rems); return (stat); } } } } return (CSA_SUCCESS); } extern void _DtCmsFreeReminderRef(cms_reminder_ref *rems) { cms_reminder_ref *next; while (rems != NULL) { next = rems->next; if (rems->reminder_name) free(rems->reminder_name); if (rems->entryid) free(rems->entryid); free(rems); rems = next; } } extern void _DtCmsUpdateReminders(_DtCmsRemQueue *remq) { int i; remq->cutoff = time(0) - 60*30; for (i = 0; i < remq->num_queues; i++) _UpdateReminderQ(remq, i); } /***************************************************************************** * static functions used within the file *****************************************************************************/ static _DtCmsRemInfo * _BuildReminder4Entry( cms_entry *entry, int aindex, List_node *lnode, time_t cutoff, _DtCmsRemInfo **active) { _DtCmsRemInfo *rptr, *rptr2; time_t lead; time_t tick; RepeatEventState *restate; if (active) *active = NULL; if ((rptr = (_DtCmsRemInfo *)calloc(1, sizeof(_DtCmsRemInfo))) == NULL) return (NULL); rptr->lnode = lnode; rptr->isentry = B_TRUE; rptr->data.e = entry; rptr->rem.i = entry->attrs[aindex].name.num; _csa_iso8601_to_duration(entry->attrs[aindex].value->item.\ reminder_value->lead_time, &lead); if (lnode == NULL) { rptr->starttime = entry->key.time; rptr->runtime = entry->key.time - lead; } else { if (lnode->lasttick == 0) { lnode->lasttick = LastTick(entry->key.time, lnode->re); lnode->duration = _DtCmsGetDuration(entry); } rptr->lasttick = lnode->lasttick; /* calculate first tick */ tick = _GetNextActiveTick(entry, entry->key.time, lnode->lasttick, lnode->re); rptr->starttime = tick; rptr->runtime = tick - lead; /* need to calculate the active tick if * 1. runtime of first tick is before cutoff time, * 2. runtime of last tick is after cutoff time, and * 3. there's reminders for instances after the cutoff time */ if (active && rptr->runtime < cutoff && (lnode->lasttick - lead >= cutoff) && (tick = _GetNextActiveTick(entry, cutoff + lead, lnode->lasttick, lnode->re)) > 0) { if ((rptr2 = (_DtCmsRemInfo *)calloc(1, sizeof(_DtCmsRemInfo))) == NULL) { free(rptr); return (NULL); } rptr2->lnode = lnode; rptr2->isentry = B_TRUE; rptr2->data.e = entry; rptr2->rem.i = entry->attrs[aindex].name.num; rptr2->lasttick = lnode->lasttick; rptr2->starttime = tick; rptr2->runtime = tick - lead; *active = rptr2; } } return (rptr); } static void _InsertReminder( _DtCmsRemInfo **head, _DtCmsRemInfo *rem) { _DtCmsRemInfo *rptr, *prev; for (rptr = *head, prev = NULL; rptr != NULL; prev = rptr, rptr = rptr->next) { if (rem->runtime < rptr->runtime) break; } if (rptr == NULL) { if (*head == NULL) { *head = rem; } else prev->next = rem; } else { rem->next = rptr; if (prev == NULL) *head = rem; else prev->next = rem; } } static CSA_return_code _GetNextReminders(_DtCmsRemQueue *remq, time_t tick, cms_reminder_ref **rems) { CSA_return_code stat; cms_reminder_ref *rptr, *head, *tail; int i; for (i = 0, head = NULL; i < remq->num_queues; i++) { rptr = NULL; if ((stat = _GetNextRemindersFromQ(remq, i, tick, &rptr)) != CSA_SUCCESS) { if (head) _DtCmsFreeReminderRef(head); return (stat); } if (rptr == NULL) continue; if (head == NULL) head = tail = rptr; else if (rptr->runtime == head->runtime) { /* combine the list */ for (; tail->next != NULL; tail = tail->next); tail->next = rptr; tail = rptr; } else if (rptr->runtime < head->runtime) { _DtCmsFreeReminderRef(head); head = tail = rptr; } else _DtCmsFreeReminderRef(rptr); } if (head) { *rems = head; } return (CSA_SUCCESS); } static CSA_return_code _GetNextRemindersFromQ( _DtCmsRemQueue *remq, int qindex, time_t tick, cms_reminder_ref **rems) { CSA_return_code stat; cms_reminder_ref *rem = *rems; if (tick >= remq->cutoff) return (_GetNextRemindersFromList(remq->active[qindex], tick, rems)); else { if ((stat = _GetNextRemindersFromList(remq->oldhead[qindex], tick, rems)) == CSA_SUCCESS && rem == *rems) return (_GetNextRemindersFromList(remq->active[qindex], tick, rems)); else return (stat); } } static cms_reminder_ref * _GetReminderRefFromInfo(_DtCmsRemInfo *rem, time_t starttime, time_t runtime) { cms_reminder_ref *rptr; cms_entry *entry = rem->data.e; int size; if ((rptr = (cms_reminder_ref *)calloc(1, sizeof(cms_reminder_ref))) == NULL) return (NULL); if ((rptr->reminder_name = strdup(entry->attrs[rem->rem.i].name.name)) == NULL) { free(rptr); return (NULL); } size = entry->attrs[CSA_ENTRY_ATTR_REFERENCE_IDENTIFIER_I].value->item.\ opaque_data_value->size; if ((rptr->entryid = malloc(size)) == NULL) { free(rptr->reminder_name); free(rptr); return (NULL); } else strncpy(rptr->entryid, (char *)entry->attrs\ [CSA_ENTRY_ATTR_REFERENCE_IDENTIFIER_I].value->item.\ opaque_data_value->data, size); rptr->key.id = entry->key.id; rptr->key.time = starttime ? starttime : rem->starttime; rptr->runtime = runtime ? runtime : rem->runtime; return (rptr); } static void _RemoveReminderFromQ( _DtCmsRemQueue *remq, int qindex, cms_entry *entry, List_node *lnode, /* zero for one time entries */ time_t starttime, boolean_t delfwd) { _DtCmsRemInfo *rptr, *rptr1 = NULL, *rptr2 = NULL; time_t lead; time_t tick; boolean_t do_old, do_new; RepeatEventState *restate; _csa_iso8601_to_duration(entry->attrs[remq->aindex[qindex]].value->\ item.reminder_value->lead_time, &lead); if (lnode == 0) { if (entry->key.time - lead < remq->cutoff) _RemoveReminderFromList(&remq->oldhead[qindex], entry, starttime, delfwd); else _RemoveReminderFromList(&remq->active[qindex], entry, starttime, delfwd); } else { tick = ClosestTick(entry->key.time, entry->key.time, lnode->re, &restate); free(restate); if (do_old = (tick - lead < remq->cutoff)) rptr1 = _RemoveReminderFromList(&remq->oldhead[qindex], entry, starttime, delfwd); if (do_new = (lnode->lasttick - lead >= remq->cutoff)) rptr2 = _RemoveReminderFromList(&remq->active[qindex], entry, starttime, delfwd); if (rptr = rptr1 ? rptr1 : rptr2) { if (do_old && do_new) { /* need to clean up the other queue * since add reminder will add to both * queue */ if (rptr1 == NULL) _RemoveReminderFromList( &remq->oldhead[qindex], entry, 0, B_FALSE); else _RemoveReminderFromList( &remq->active[qindex], entry, 0, B_FALSE); } _DtCmsAddReminder4EntryToQ(remq, entry, remq->aindex[qindex], rptr->lnode); if (rptr1) free(rptr1); if (rptr2) free(rptr2); } } } static _DtCmsRemInfo * _RemoveReminderFromList( _DtCmsRemInfo **qhead, cms_entry *entry, time_t starttime, boolean_t delfwd) { _DtCmsRemInfo *rptr, *prev; /* find reminder in list */ for (rptr = *qhead, prev = NULL; rptr != NULL; prev = rptr, rptr = rptr->next) { if (rptr->data.e != entry || (starttime > 0 && !delfwd && rptr->starttime != starttime)|| (starttime > 0 && delfwd && rptr->starttime < starttime)) { continue; } if (prev == NULL) *qhead = rptr->next; else prev->next = rptr->next; if (starttime == 0 || delfwd) { free(rptr); return (NULL); } else return (rptr); } return (NULL); } /* * move all reminders in active queue whose runtime < cutoff * to old queue */ static void _UpdateReminderQ(_DtCmsRemQueue *remq, int qindex) { _DtCmsRemInfo *rptr, *nptr; cms_entry *entry; time_t lead, tick; RepeatEventState *restate = NULL; for (; (rptr = remq->active[qindex]) != NULL && rptr->runtime < remq->cutoff; ) { remq->active[qindex] = rptr->next; rptr->next = NULL; if (rptr->lnode == NULL) _InsertReminder(&remq->oldhead[qindex], rptr); else { lead = rptr->starttime - rptr->runtime; entry = rptr->data.e; tick = ClosestTick(entry->key.time, entry->key.time, rptr->lnode->re, &restate); free(restate); if (tick == rptr->starttime) { /* add this to old queue */ _InsertReminder(&remq->oldhead[qindex], rptr); /* make copy of rptr */ nptr = (_DtCmsRemInfo *)calloc(1, sizeof(_DtCmsRemInfo)); *nptr = *rptr; rptr = nptr; } if ((rptr->lasttick - lead < remq->cutoff) || (tick = _GetNextActiveTick(entry, remq->cutoff + lead, rptr->lasttick, rptr->lnode->re)) <= 0) { free(rptr); } else { rptr->starttime = tick; rptr->runtime = tick - lead; _InsertReminder(&remq->active[qindex], rptr); } } } } static long _GetNextActiveTick( cms_entry *entry, time_t target, time_t lasttick, RepeatEvent *re) { RepeatEventState *restate = NULL; time_t tick; for (tick = ClosestTick(target, entry->key.time, re, &restate); tick <= lasttick; tick = NextTick(tick, entry->key.time, re, restate)) { if (tick <= 0 || !_DtCmsInExceptionList(entry, tick)) break; } free(restate); return (tick); } /* * The reminders found will be linked with the list * contained in rf_f */ static CSA_return_code _GetNextRemindersFromList( _DtCmsRemInfo *rlist, time_t giventime, cms_reminder_ref **rf_r) { cms_reminder_ref *rptr, *head = NULL, *tail; time_t tick; int lead; /* get from active queue */ for (; rlist != NULL; rlist = rlist->next) { if (giventime < rlist->runtime) break; else if (rlist->lnode) { /* check the next active tick */ lead = rlist->starttime - rlist->runtime; tick = _GetNextActiveTick(rlist->data.e, giventime + lead + 1, rlist->lasttick, rlist->lnode->re); if (tick > 0 && (head == NULL || (tick-lead <= head->runtime))) { if ((rptr = _GetReminderRefFromInfo(rlist, tick, tick-lead)) == NULL) { if (head) _DtCmsFreeReminderRef(head); return (CSA_E_INSUFFICIENT_MEMORY); } if (head == NULL) head = rptr; else if (head->runtime = rptr->runtime) { rptr->next = head; head = rptr; } else { _DtCmsFreeReminderRef(head); head = rptr; } } } } if (rlist) { if (head) { if (head->runtime > rlist->runtime) { _DtCmsFreeReminderRef(head); head = NULL; } else if (head->runtime < rlist->runtime) goto _done; } /* now do lookup in the remaining list */ while (rlist != NULL) { if (rptr = _GetReminderRefFromInfo(rlist, 0, 0)) { rptr->next = head; head = rptr; } else { if (head) _DtCmsFreeReminderRef(head); return (CSA_E_INSUFFICIENT_MEMORY); } if (rlist->next && rlist->next->runtime == rlist->runtime) rlist = rlist->next; else break; } } _done: /* find tail */ if (head) { for (tail = head; tail->next != NULL; tail = tail->next) ; tail->next = *rf_r; *rf_r = head; } return (CSA_SUCCESS); } static void _DtCmsAddReminder4EntryToQ( _DtCmsRemQueue *remq, cms_entry *entry, int aindex, List_node *lnode) { _DtCmsRemInfo *rptr, *rptr2; int i; if ((rptr = _BuildReminder4Entry(entry, aindex, lnode, remq->cutoff, &rptr2)) == NULL) return; for (i = 0; i < remq->num_queues; i++) { if (remq->aindex[i] == aindex) { if (rptr->runtime >= remq->cutoff) _InsertReminder(&remq->active[i], rptr); else _InsertReminder(&remq->oldhead[i], rptr); if (rptr2) _InsertReminder(&remq->active[i], rptr2); break; } } if (i == remq->num_queues) { /* expand the queue */ } }