aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/alarm-notify/alarm.c
blob: 2797d44b0a37ace999365e0800f05ba2d558278b (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                                                       
  
                                                                


                                                               


                                                                  



                                                                    
                                                                             






                                                            
  
   
 
                    
                   

      
                   
                 


                     
                    
                  
                        
 

                        

                                
                            
 
                              
                



                                             

              
                                 
 
                                                                            
           
                
 


                        



                                                                  



                          
                                                
 
                    

 
                                     
               
                              
 
                   
 




                                                                      
                       
 
                          
 
                                    
                        
                                            
                                    
 
                                  
 

                                      
 
                                                                        

                               

                              
 


                                                           
 
                                                                    
 
                                          
                                                                        
         
 





                                                                        

                     

 


                                                                              
           
                    
 


                              




                                                  
 
                          
 




                                             
 



                                                               
 
                              
                                                                
                                                              

                                              
                                                                        
 

 

                                                  
           

                                       
 

                                   
                    

                                           


                                                    
                                                      

                             
 
                        
 

                                                                          
 
                                                                       
                                    
                                                                          
 


                                                                                 
 

                                                       

 

             


                                              
                                                     
  

                                                                                
  




                                                                                 



                                                
 
                        
 


                                                      





                                                  
                         

                  

 


                                     
  



                                         
 
                                    
                            


                              

                                         



                                     
                                                                                 
                       
         


                                

                       


                              



                                                           
                
                                                        

         
                               



                                             
 
                                                    
 
                                  
                                                                
 



              
  







                                                                             
                              

                                                                          


                       

                                     
 




                                                                             












                                                                 
 
/*
 * Evolution calendar - Low-level alarm timer mechanism
 *
 * This program is free software; you can redistribute it and/or
 * modify it 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) version 3.
 *
 * This program is distributed in the hope that it 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 the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *      Miguel de Icaza <miguel@ximian.com>
 *      Federico Mena-Quintero <federico@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <gdk/gdk.h>
#include "alarm.h"
#include "config-data.h"

/* Our glib timeout */
static guint timeout_id;

/* The list of pending alarms */
static GList *alarms = NULL;

/* A queued alarm structure */
typedef struct {
    time_t             trigger;
    AlarmFunction      alarm_fn;
    gpointer           data;
    AlarmDestroyNotify destroy_notify_fn;
} AlarmRecord;

static void setup_timeout (void);

/* Removes the head alarm from the queue.  Does not touch the timeout_id. */
static void
pop_alarm (void)
{
    AlarmRecord *ar;
    GList *l;

    if (!alarms) {
        g_warning ("Nothing to pop from the alarm queue");
        return;
    }

    ar = alarms->data;

    l = alarms;
    alarms = g_list_delete_link (alarms, l);

    g_free (ar);
}

/* Callback from the alarm timeout */
static gboolean
alarm_ready_cb (gpointer data)
{
    time_t now;

    if (!alarms) {
        g_warning ("Alarm triggered, but no alarm present\n");
        return FALSE;
    }

    timeout_id = 0;

    now = time (NULL);

    debug (("Alarm callback!"));
    while (alarms) {
        AlarmRecord *notify_id, *ar;
        AlarmRecord ar_copy;

        ar = alarms->data;

        if (ar->trigger > now)
            break;

        debug (("Process alarm with trigger %lu", ar->trigger));
        notify_id = ar;

        ar_copy = *ar;
        ar = &ar_copy;

        /* This will free the original AlarmRecord;
         * that's why we copy it. */
        pop_alarm ();

        (* ar->alarm_fn) (notify_id, ar->trigger, ar->data);

        if (ar->destroy_notify_fn)
            (* ar->destroy_notify_fn) (notify_id, ar->data);
    }

    /* We need this check because one of the alarm_fn above may have
     * re-entered and added an alarm of its own, so the timer will
     * already be set up.
     */
    if (alarms)
        setup_timeout ();

    return FALSE;
}

/* Sets up a timeout for the next minute.  We do not need to be concerned with
 * timezones here, as this is just a periodic check on the alarm queue.
 */
static void
setup_timeout (void)
{
    const AlarmRecord *ar;
    guint diff;
    time_t now;

    if (!alarms) {
        g_warning ("No alarm to setup\n");
        return;
    }

    ar = alarms->data;

    /* Remove the existing time out */
    if (timeout_id != 0) {
        g_source_remove (timeout_id);
        timeout_id = 0;
    }

    /* Ensure that if the trigger managed to get behind the
     * current time we timeout immediately */
    diff = MAX (0, ar->trigger - time (NULL));
    now = time (NULL);

    /* Add the time out */
    debug (("Setting timeout for %d.%2d (from now) %lu %lu",
             diff / 60, diff % 60, ar->trigger, now));
    debug ((" %s", ctime (&ar->trigger)));
    debug ((" %s", ctime (&now)));
    timeout_id = g_timeout_add_seconds (diff, alarm_ready_cb, NULL);

}

/* Used from g_list_insert_sorted(); compares the
 * trigger times of two AlarmRecord structures. */
static gint
compare_alarm_by_time (gconstpointer a,
                       gconstpointer b)
{
    const AlarmRecord *ara = a;
    const AlarmRecord *arb = b;
    time_t diff;

    diff = ara->trigger - arb->trigger;
    return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}

/* Adds an alarm to the queue and sets up the timer */
static void
queue_alarm (AlarmRecord *ar)
{
    GList *old_head;

    /* Track the current head of the list in case there are changes */
    old_head = alarms;

    /* Insert the new alarm in order if the alarm's trigger time is
     * after the current time */
    alarms = g_list_insert_sorted (alarms, ar, compare_alarm_by_time);

    /* If there first item on the list didn't change, the time out is fine */
    if (old_head == alarms)
        return;

    /* Set the timer for removal upon activation */
    setup_timeout ();
}

/**
 * alarm_add:
 * @trigger: Time at which alarm will trigger.
 * @alarm_fn: Callback for trigger.
 * @data: Closure data for callback.
 * @destroy_notify_fn: destroy notification callback.
 *
 * Adds an alarm to trigger at the specified time.  The @alarm_fn will be called
 * with the provided data and the alarm will be removed from the trigger list.
 *
 * Return value: An identifier for this alarm; it can be used to remove the
 * alarm later with alarm_remove().  If the trigger time occurs in the past, then
 * the alarm will not be queued and the function will return NULL.
 **/
gpointer
alarm_add (time_t trigger,
           AlarmFunction alarm_fn,
           gpointer data,
           AlarmDestroyNotify destroy_notify_fn)
{
    AlarmRecord *ar;

    g_return_val_if_fail (trigger != -1, NULL);
    g_return_val_if_fail (alarm_fn != NULL, NULL);

    ar = g_new (AlarmRecord, 1);
    ar->trigger = trigger;
    ar->alarm_fn = alarm_fn;
    ar->data = data;
    ar->destroy_notify_fn = destroy_notify_fn;

    queue_alarm (ar);

    return ar;
}

/**
 * alarm_remove:
 * @alarm: A queued alarm identifier.
 *
 * Removes an alarm from the alarm queue.
 **/
void
alarm_remove (gpointer alarm)
{
    AlarmRecord *notify_id, *ar;
    AlarmRecord ar_copy;
    AlarmRecord *old_head;
    GList *l;

    g_return_if_fail (alarm != NULL);

    ar = alarm;

    l = g_list_find (alarms, ar);
    if (!l) {
        g_warning (G_STRLOC ": Requested removal of nonexistent alarm!");
        return;
    }

    old_head = alarms->data;

    notify_id = ar;

    if (old_head == ar) {
        ar_copy = *ar;
        ar = &ar_copy;

        /* This will free the original AlarmRecord;
         * that's why we copy it. */
        pop_alarm ();
    } else {
        alarms = g_list_delete_link (alarms, l);
    }

    /* Reset the timeout */
    if (!alarms) {
        g_source_remove (timeout_id);
        timeout_id = 0;
    }

    /* Notify about destructiono of the alarm */

    if (ar->destroy_notify_fn)
        (* ar->destroy_notify_fn) (notify_id, ar->data);

}

/**
 * alarm_done:
 *
 * Terminates the alarm timer mechanism.  This should be called at the end of
 * the program.
 **/
void
alarm_done (void)
{
    GList *l;

    if (timeout_id == 0) {
        if (alarms)
            g_warning ("No timeout, but queue is not NULL\n");
        return;
    }

    g_source_remove (timeout_id);
    timeout_id = 0;

    if (!alarms) {
        g_warning ("timeout present, freed, but no alarms active\n");
        return;
    }

    for (l = alarms; l; l = l->next) {
        AlarmRecord *ar;

        ar = l->data;

        if (ar->destroy_notify_fn)
            (* ar->destroy_notify_fn) (ar, ar->data);

        g_free (ar);
    }

    g_list_free (alarms);
    alarms = NULL;
}