aboutsummaryrefslogblamecommitdiffstats
path: root/src/io.c
blob: 2067dd04bc46eddd2f2bcca612b84548b538c8ef (plain) (tree)
1
2
3
4
5
6
7
8
9

                                  
  
                                                                        
                       
  


                                                                     
  



















                                                                        
  
                                                        
                                           


   

                   
                     
                      
                   


                   
                  
 
                     
                 
 
              


                     

                 












                              
                         

                  


                                  








                                                                            
                                                                  

                                                
 



                          



                                                                            
                      
                         

                     

                     
    
                            



                     

                             


                                 


                                                               
                             
                            



                           
                                                                              


                                             
                                                                              


                                              

                                                                        





                                                
                                                      




                                                 
                             


                 

 
                                                 
             
                                         
 
               
             
                           

                                                                            
                                                                      
                                                            
                                                            

                
                                             
                                       
                                                                           
      

                                                                    





                                                             

                                 
                      
         

                                        

                                                
                              
         
     
                         
 
                
 
 
















                                                         
 

                                                                     
 






                                                             
  
                                                                   


                                                                       
                                                                           

    
                                    
 




                              
                      
     
                     








                                                                   


      




                             







                                                              

                        
                                                              


          

                                                    










                                                                        
                                            
                                               







                                                     
                                                   



                                                                         
                                                                 



                        
                                            
                                               


                        
                                               
         
     

 

                                                          
 
        
 
                                             
                               
     
                                                      
              


                           

 






                                   
 

                                         
                       

                               
                       
                
                   



                                                                 


                                              




                                     
                  

           


                
                                            




                           





                                                 
                           
                               
 




                                               








                                
                  
           
 


                
                                            

             
                              
     
                                                
                            










                                
 


                
                                            
             
 







                                
                                       
 


                                                                         
               
 


                
                                      
 
               
                                                            
                           
                 

                                                                   
 
               
                                                        
                      
                                
 
               
                                                        
                       
                                
 
               
                                                        
                       
                                
 

                                                        
                       
                                
 
                                               
                                                 
                                         

                                        
                          
     

                                        

 

                                                              

                                                                        


                  
 


                                                
              



                        
                                     





                                     

                                                                    

          
                        


                                                   
              
                            

                                                         
                          
         
                                             
                                                                      
                                                  
 





                                                               
                           
                        
                     
          
                                                   


                                            
         
                                                                       



                                                              
         
                        
         



                                                                              
         
          
         

                                                               
         




                                              
         
                           

                                                              



                                                            
                                

                                                    
             
                                                       
             
                                                                     

                                                               


                                   
                                        
                                                   

                                       
                                
                 
                                                        
                                        
                 

                                                            

                            
                                                                  
                                    
                                               




                                   

                                                                   

             
          
                              



                                                    
         
                                      
                       
         
          

                       
                                
         





                                                               



                               
                                     

                                                    
             
                            
             
                         

                                                    
             

                                                        









                                                                        
                        










                                                           
          
         

                                                               
         
     
                                       


                        
    
                   
 
                  
                

                 
                                                          

                                     

                                                             



                           
              
                        
                                                       



                                                                              
         
          

                 
                                
         


                                               
                                    
          



                                
                                       

                                              


                                   
                                                


                                  
                                       
                       

 
           


















                                                                         
  









                                                                              


                                                                      

                                                                     

    
                          
 
                                       

                   
                      

                               

               
 
                                                                
 
                              
     

                                                   



                                                         

                                                        

                              




                                                
                   
 


                               
                                






                                                                        
                                            


                                     
                                          
                 
 
                                                   




                                                                  
                                 
                                       
                                                                   






                                                                  





                                              
                                            

                                                       
                     
 
                                                   



                                           

                                                                          

                                                        
                  
                 
                           
 

                                                               



                                                    
                                                               




                                                                              
                 
                                       

              




                           

         

                                     

                  
                            
                                                                           
 





                                                                      

                                     
                                                                          

 

                                      
 


                



                             
         

                                                                            

                                       

      



                     
 
 

                          
 
           
 


             
                                       

             
              
 





                                        


                
            
                            
     
               
 

                     
                                           
         

                                                                             

                                       
                                    
     
 
 

                                                   





                                             
                            


                            

                          
 
                            
 
                             
            

                                      



                                           





                                     
                 


                             
    
                                    
 
                                                     
 






                                                                            

 

                           
                                      
 
               

                                                                 
 
                                                         


                                    
                  
     
                    

                      
                   
                                        

            
                                    


                      
                     

           



                                  
 
                                                  

                                   
                          
     
 





















                                                         
                   
 
 

                                         
 

                    

                                                                        
                                                                      
                                                            



                                    
                                  


















                                                             


  



                                                                                
   

                                                         
 
                                                                         





                                                    
 
                                                                            
                  
     
                    



                                                                   
            
                   


                                        
                                                 

                      



                     
                                   
 
                       
                  

                          
                                          

             
 



                                                                     
                      
                                      
 






                                                                            
 


                                         
                                                  


                                      



                                                                     
                               
                          
     
                                 

                                        

                                                                  
     
 


                                                                           
                                     

                        
                                                                                   
 
                                                 


                    
 
                

                  
                         
                
                      
 
                                                                   
                                                
                                
                                                                             
                                             
                            
                                                                           
                                                                       

                                   
                      









                                                                               
                                                             

                     
                                                  


    
                                                                  
 

          
                                                        

                            
                                  


                          

                                                   
 

                                                         
         


      
                              
        
         

                                    
             
                                                      
             
         
                                       
                               
     


    
                                 






                                                                            
 






                                            

            
                             
                                          
 

          
                               
                                         




                                                     
                            
 
                                                            





                               
                 



                                      
 



                                                                          
  




                                                                              




                  














                                                                      
 
                   
     





                                                                         
                          


      


                                                          
     
 
 
  

                                                                              


                        
 

            
 







                                             
 
           
 














                                                                    
                                       




                                   
              


             










                                       
                                                                 










                      
            
 
































                                                                   
/*
 * Calcurse - text-based organizer
 *
 * Copyright (c) 2004-2012 calcurse Development Team <misc@calcurse.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the
 *        following disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the
 *        following disclaimer in the documentation and/or other
 *        materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Send your feedback or comments to : misc@calcurse.org
 * Calcurse home page : http://calcurse.org
 *
 */

#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <time.h>
#include <math.h>
#include <unistd.h>
#include <errno.h>

#include "calcurse.h"
#include "sha1.h"

typedef enum {
  PROGRESS_BAR_SAVE,
  PROGRESS_BAR_LOAD,
  PROGRESS_BAR_EXPORT
} progress_bar_t;

enum {
  PROGRESS_BAR_CONF,
  PROGRESS_BAR_TODO,
  PROGRESS_BAR_APTS,
  PROGRESS_BAR_KEYS
};

enum {
  PROGRESS_BAR_EXPORT_EVENTS,
  PROGRESS_BAR_EXPORT_APOINTS,
  PROGRESS_BAR_EXPORT_TODO
};

struct ht_keybindings_s {
  char     *label;
  enum key  key;
  HTABLE_ENTRY (ht_keybindings_s);
};

static void load_keys_ht_getkey (struct ht_keybindings_s *, char **, int *);
static int load_keys_ht_compare (struct ht_keybindings_s *,
                                 struct ht_keybindings_s *);

#define HSIZE 256
HTABLE_HEAD (ht_keybindings, HSIZE, ht_keybindings_s);
HTABLE_GENERATE (ht_keybindings, ht_keybindings_s, load_keys_ht_getkey,
                 load_keys_ht_compare)

/* Draw a progress bar while saving, loading or exporting data. */
static void
progress_bar (progress_bar_t type, int progress)
{
#define NBFILES		4
#define NBEXPORTED      3
#define LABELENGTH      15
  int i, step, steps;
  const char *mesg_sav = _("Saving...");
  const char *mesg_load = _("Loading...");
  const char *mesg_export = _("Exporting...");
  const char *error_msg = _("Internal error while displaying progress bar");
  char *barchar = "|";
  char *file[NBFILES] = {
    "[    conf    ]",
    "[    todo    ]",
    "[    apts    ]",
    "[    keys    ]"
  };
  char *data[NBEXPORTED] = {
    "[   events   ]",
    "[appointments]",
    "[    todo    ]"
  };
  int ipos =  LABELENGTH + 2;
  int epos[NBFILES];

  /* progress bar length init. */
  ipos = LABELENGTH + 2;
  steps = (type == PROGRESS_BAR_EXPORT) ? NBEXPORTED : NBFILES;
  step = floor (col / (steps + 1));
  for (i = 0; i < steps - 1; i++)
    epos[i] = (i + 2) * step;
  epos[steps - 1] = col - 2;

  switch (type)
    {
    case PROGRESS_BAR_SAVE:
      EXIT_IF (progress < 0 || progress > PROGRESS_BAR_KEYS, "%s", error_msg);
      status_mesg (mesg_sav, file[progress]);
      break;
    case PROGRESS_BAR_LOAD:
      EXIT_IF (progress < 0 || progress > PROGRESS_BAR_KEYS, "%s", error_msg);
      status_mesg (mesg_load, file[progress]);
      break;
    case PROGRESS_BAR_EXPORT:
      EXIT_IF (progress < 0
               || progress > PROGRESS_BAR_EXPORT_TODO, "%s", error_msg);
      status_mesg (mesg_export, data[progress]);
      break;
    }

  /* Draw the progress bar. */
  mvwprintw (win[STA].p, 1, ipos, barchar);
  mvwprintw (win[STA].p, 1, epos[steps - 1], barchar);
  custom_apply_attr (win[STA].p, ATTR_HIGHEST);
  for (i = ipos + 1; i < epos[progress]; i++)
    mvwaddch (win[STA].p, 1, i, ' ' | A_REVERSE);
  custom_remove_attr (win[STA].p, ATTR_HIGHEST);
  wmove (win[STA].p, 0, 0);
  wins_wrefresh (win[STA].p);
#undef NBFILES
#undef NBEXPORTED
#undef LABELENGTH
}

/* Ask user for a file name to export data to. */
static FILE *
get_export_stream (enum export_type type)
{
  FILE *stream;
  int cancel;
  char *home, *stream_name;
  const char *question = _("Choose the file used to export calcurse data:");
  const char *wrong_name =
    _("The file cannot be accessed, please enter another file name.");
  const char *press_enter = _("Press [ENTER] to continue.");
  const char *file_ext[IO_EXPORT_NBTYPES] = {"ical", "txt"};

  stream = NULL;
  stream_name = (char *) mem_malloc (BUFSIZ);
  if ((home = getenv ("HOME")) != NULL)
    snprintf (stream_name, BUFSIZ, "%s/calcurse.%s", home, file_ext[type]);
  else
    snprintf (stream_name, BUFSIZ, "%s/calcurse.%s", get_tempdir (),
              file_ext[type]);

  while (stream == NULL)
    {
      status_mesg (question, "");
      cancel = updatestring (win[STA].p, &stream_name, 0, 1);
      if (cancel)
        {
          mem_free (stream_name);
          return NULL;
        }
      stream = fopen (stream_name, "w");
      if (stream == NULL)
        {
          status_mesg (wrong_name, press_enter);
          wgetch (win[STA].p);
        }
    }
  mem_free (stream_name);

  return stream;
}

/* Append a line to a file. */
unsigned
io_fprintln (const char *fname, const char *fmt, ...)
{
  FILE *fp;
  va_list ap;
  char buf[BUFSIZ];
  int ret;

  fp = fopen (fname, "a");
  RETVAL_IF (!fp, 0, _("Failed to open \"%s\", - %s\n"),
             fname, strerror (errno));

  va_start (ap, fmt);
  ret = vsnprintf (buf, sizeof buf, fmt, ap);
  RETVAL_IF (ret < 0, 0, _("Failed to build message\n"));
  va_end (ap);

  ret = fprintf (fp, "%s", buf);
  RETVAL_IF (ret < 0, 0, _("Failed to print message \"%s\"\n"), buf);

  ret = fclose (fp);
  RETVAL_IF (ret != 0, 0, _("Failed to close \"%s\" - %s\n"),
             fname, strerror (errno));

  return 1;
}

/*
 * Initialization of data paths. The cfile argument is the variable
 * which contains the calendar file. If none is given, then the default
 * one (~/.calcurse/apts) is taken. If the one given does not exist, it
 * is created.
 * The datadir argument can be use to specify an alternative data root dir.
 */
void
io_init (char *cfile, char *datadir)
{
  FILE *data_file;
  char *home;
  char apts_file[BUFSIZ] = "";
  int ch;

  if (datadir != NULL)
    {
      home = datadir;
      snprintf (path_dir, BUFSIZ, "%s", home);
      snprintf (path_todo, BUFSIZ, "%s/" TODO_PATH_NAME, home);
      snprintf (path_conf, BUFSIZ, "%s/" CONF_PATH_NAME, home);
      snprintf (path_notes, BUFSIZ, "%s/" NOTES_DIR_NAME, home);
      snprintf (path_apts, BUFSIZ, "%s/" APTS_PATH_NAME, home);
      snprintf (path_keys, BUFSIZ, "%s/" KEYS_PATH_NAME, home);
      snprintf (path_cpid, BUFSIZ, "%s/" CPID_PATH_NAME, home);
      snprintf (path_dpid, BUFSIZ, "%s/" DPID_PATH_NAME, home);
      snprintf (path_dmon_log, BUFSIZ, "%s/" DLOG_PATH_NAME, home);
    }
  else
    {
      home = getenv ("HOME");
      if (home == NULL)
        {
          home = ".";
        }
      snprintf (path_dir, BUFSIZ, "%s/" DIR_NAME, home);
      snprintf (path_todo, BUFSIZ, "%s/" TODO_PATH, home);
      snprintf (path_conf, BUFSIZ, "%s/" CONF_PATH, home);
      snprintf (path_keys, BUFSIZ, "%s/" KEYS_PATH, home);
      snprintf (path_cpid, BUFSIZ, "%s/" CPID_PATH, home);
      snprintf (path_dpid, BUFSIZ, "%s/" DPID_PATH, home);
      snprintf (path_dmon_log, BUFSIZ, "%s/" DLOG_PATH, home);
      snprintf (path_notes, BUFSIZ, "%s/" NOTES_DIR, home);
      if (cfile == NULL)
        {
          snprintf (path_apts, BUFSIZ, "%s/" APTS_PATH, home);
        }
      else
        {
          snprintf (apts_file, BUFSIZ, "%s", cfile);
          strncpy (path_apts, apts_file, BUFSIZ);
          /* check if the file exists, otherwise create it */
          data_file = fopen (path_apts, "r");
          if (data_file == NULL)
            {
              printf (_("%s does not exist, create it now [y or n] ? "),
                      path_apts);
              ch = getchar ();
              switch (ch)
                {
                case 'N':
                case 'n':
                  puts (_("aborting...\n"));
                  exit_calcurse (EXIT_FAILURE);
                  break;

                case 'Y':
                case 'y':
                  data_file = fopen (path_apts, "w");
                  if (data_file == NULL)
                    {
                      perror (path_apts);
                      exit_calcurse (EXIT_FAILURE);
                    }
                  else
                    {
                      printf (_("%s successfully created\n"), path_apts);
                      puts (_("starting interactive mode...\n"));
                    }
                  break;

                default:
                  puts (_("aborting...\n"));
                  exit_calcurse (EXIT_FAILURE);
                  break;
                }
            }
          file_close (data_file, __FILE_POS__);
        }
    }
}

void
io_extract_data (char *dst_data, const char *org, int len)
{
  int i;

  for (; *org == ' ' || *org == '\t'; org++);
  for (i = 0; i < len - 1; i++)
    {
      if (*org == '\n' || *org == '\0' || *org == '#')
        break;
      *dst_data++ = *org++;
    }
  *dst_data = '\0';
}

void
display_mark (void)
{
  const int DISPLAY_TIME = 1;
  WINDOW *mwin;

  mwin = newwin (1, 2, 1, col - 3);

  custom_apply_attr (mwin, ATTR_HIGHEST);
  mvwprintw (mwin, 0, 0, "**");
  wins_wrefresh (mwin);
  sleep (DISPLAY_TIME);
  mvwprintw (mwin, 0, 0, "  ");
  wins_wrefresh (mwin);
  delwin (mwin);
  wins_doupdate ();
}

static pthread_mutex_t io_save_mutex = PTHREAD_MUTEX_INITIALIZER;

/*
 * Save the apts data file, which contains the
 * appointments first, and then the events.
 * Recursive items are written first.
 */
unsigned
io_save_apts (void)
{
  llist_item_t *i;
  FILE *fp;

  if (read_only)
    return 1;

  if ((fp = fopen (path_apts, "w")) == NULL)
    return 0;

  recur_save_data (fp);

  if (ui_mode == UI_CURSES)
    LLIST_TS_LOCK (&alist_p);
  LLIST_TS_FOREACH (&alist_p, i)
    {
      struct apoint *apt = LLIST_TS_GET_DATA (i);
      apoint_write (apt, fp);
    }
  if (ui_mode == UI_CURSES)
    LLIST_TS_UNLOCK (&alist_p);

  LLIST_FOREACH (&eventlist, i)
    {
      struct event *ev = LLIST_TS_GET_DATA (i);
      event_write (ev, fp);
    }
  file_close (fp, __FILE_POS__);

  return 1;
}

/* Save the todo data file. */
unsigned
io_save_todo (void)
{
  llist_item_t *i;
  FILE *fp;

  if (read_only)
    return 1;

  if ((fp = fopen (path_todo, "w")) == NULL)
    return 0;

  LLIST_FOREACH (&todolist, i)
    {
      struct todo *todo = LLIST_TS_GET_DATA (i);
      todo_write (todo, fp);
    }
  file_close (fp, __FILE_POS__);

  return 1;
}

/* Save user-defined keys */
unsigned
io_save_keys (void)
{
  FILE *fp;

  if (read_only)
    return 1;

  if ((fp = fopen (path_keys, "w")) == NULL)
    return 0;

  keys_save_bindings (fp);
  file_close (fp, __FILE_POS__);

  return 1;
}

/* Save the calendar data */
void
io_save_cal (enum save_display display)
{
  const char *access_pb = _("Problems accessing data file ...");
  const char *save_success = _("The data files were successfully saved");
  const char *enter = _("Press [ENTER] to continue");
  int show_bar;

  if (read_only)
    return;

  pthread_mutex_lock (&io_save_mutex);

  show_bar = 0;
  if (ui_mode == UI_CURSES && display == IO_SAVE_DISPLAY_BAR
      && conf.progress_bar)
    show_bar = 1;
  else if (ui_mode == UI_CURSES && display == IO_SAVE_DISPLAY_MARK)
    display_mark ();

  if (show_bar)
    progress_bar (PROGRESS_BAR_SAVE, PROGRESS_BAR_CONF);
  if (!config_save ())
    ERROR_MSG ("%s", access_pb);

  if (show_bar)
    progress_bar (PROGRESS_BAR_SAVE, PROGRESS_BAR_TODO);
  if (!io_save_todo ())
    ERROR_MSG ("%s", access_pb);

  if (show_bar)
    progress_bar (PROGRESS_BAR_SAVE, PROGRESS_BAR_APTS);
  if (!io_save_apts ())
    ERROR_MSG ("%s", access_pb);

  if (show_bar)
    progress_bar (PROGRESS_BAR_SAVE, PROGRESS_BAR_KEYS);
  if (!io_save_keys ())
    ERROR_MSG ("%s", access_pb);

  /* Print a message telling data were saved */
  if (ui_mode == UI_CURSES && conf.system_dialogs
      && display != IO_SAVE_DISPLAY_MARK)
    {
      status_mesg (save_success, enter);
      wgetch (win[STA].p);
    }

  pthread_mutex_unlock (&io_save_mutex);
}

/*
 * Check what type of data is written in the appointment file,
 * and then load either: a new appointment, a new event, or a new
 * recursive item (which can also be either an event or an appointment).
 */
void
io_load_app (void)
{
  FILE *data_file;
  int c, is_appointment, is_event, is_recursive;
  struct tm start, end, until, *lt;
  llist_t exc;
  time_t t;
  int id = 0;
  int freq;
  char type, state = 0L;
  char note[MAX_NOTESIZ + 1], *notep;

  t = time (NULL);
  lt = localtime (&t);
  start = end = until = *lt;

  data_file = fopen (path_apts, "r");
  EXIT_IF (data_file == NULL, _("failed to open appointment file"));

  for (;;)
    {
      LLIST_INIT (&exc);
      is_appointment = is_event = is_recursive = 0;
      c = getc (data_file);
      if (c == EOF)
        break;
      ungetc (c, data_file);

      /* Read the date first: it is common to both events
       * and appointments.
       */
      if (fscanf (data_file, "%d / %d / %d ",
                  &start.tm_mon, &start.tm_mday, &start.tm_year) != 3)
        EXIT (_("syntax error in the item date"));

      /* Read the next character : if it is an '@' then we have
       * an appointment, else if it is an '[' we have en event.
       */
      c = getc (data_file);

      if (c == '@')
        is_appointment = 1;
      else if (c == '[')
        is_event = 1;
      else
        EXIT (_("no event nor appointment found"));

      /* Read the remaining informations. */
      if (is_appointment)
        {
          if (fscanf (data_file, " %d : %d -> %d / %d / %d @ %d : %d ",
                      &start.tm_hour, &start.tm_min,
                      &end.tm_mon, &end.tm_mday, &end.tm_year,
                      &end.tm_hour, &end.tm_min) != 7)
            EXIT (_("syntax error in item time or duration"));
        }
      else if (is_event)
        {
          if (fscanf (data_file, " %d ", &id) != 1 || getc (data_file) != ']')
            EXIT (_("syntax error in item identifier"));
          while ((c = getc (data_file)) == ' ');
          ungetc (c, data_file);
        }
      else
        {
          EXIT (_("wrong format in the appointment or event"));
          /* NOTREACHED */
        }

      /* Check if we have a recursive item. */
      c = getc (data_file);

      if (c == '{')
        {
          is_recursive = 1;
          if (fscanf (data_file, " %d%c ", &freq, &type) != 2)
            EXIT (_("syntax error in item repetition"));

          c = getc (data_file);
          if (c == '}')
            {			/* endless recurrent item */
              until.tm_year = 0;
              while ((c = getc (data_file)) == ' ');
              ungetc (c, data_file);
            }
          else if (c == '-' && getc (data_file) == '>')
            {
              if (fscanf (data_file, " %d / %d / %d ", &until.tm_mon,
                          &until.tm_mday, &until.tm_year) != 3)
                EXIT (_("syntax error in item repetition"));
              c = getc (data_file);
              if (c == '!')
                {
                  ungetc (c, data_file);
                  recur_exc_scan (&exc, data_file);
                  c = getc (data_file);
                }
              else if (c == '}')
                {
                  while ((c = getc (data_file)) == ' ');
                  ungetc (c, data_file);
                }
              else
                EXIT (_("syntax error in item repetition"));
            }
          else if (c == '!')
            {			/* endless item with exceptions */
              ungetc (c, data_file);
              recur_exc_scan (&exc, data_file);
              c = getc (data_file);
              until.tm_year = 0;
            }
          else
            {
              EXIT (_("wrong format in the appointment or event"));
              /* NOTREACHED */
            }
        }
      else
        ungetc (c, data_file);

      /* Check if a note is attached to the item. */
      c = getc (data_file);
      if (c == '>')
        {
          note_read (note, data_file);
          notep = note;
        }
      else
        {
          notep = NULL;
          ungetc (c, data_file);
        }

      /*
       * Last: read the item description and load it into its
       * corresponding linked list, depending on the item type.
       */
      if (is_appointment)
        {
          c = getc (data_file);
          if (c == '!')
            {
              state |= APOINT_NOTIFY;
              while ((c = getc (data_file)) == ' ');
              ungetc (c, data_file);
            }
          else if (c == '|')
            {
              state = 0L;
              while ((c = getc (data_file)) == ' ');
              ungetc (c, data_file);
            }
          else
            EXIT (_("syntax error in item repetition"));
          if (is_recursive)
            {
              recur_apoint_scan (data_file, start, end,
                                 type, freq, until, notep, &exc, state);
            }
          else
            {
              apoint_scan (data_file, start, end, state, notep);
            }
        }
      else if (is_event)
        {
          if (is_recursive)
            {
              recur_event_scan (data_file, start, id, type,
                                freq, until, notep, &exc);
            }
          else
            {
              event_scan (data_file, start, id, notep);
            }
        }
      else
        {
          EXIT (_("wrong format in the appointment or event"));
          /* NOTREACHED */
        }
    }
  file_close (data_file, __FILE_POS__);
}

/* Load the todo data */
void
io_load_todo (void)
{
  FILE *data_file;
  char *newline;
  int nb_tod = 0;
  int c, id;
  char buf[BUFSIZ], e_todo[BUFSIZ], note[MAX_NOTESIZ + 1];

  data_file = fopen (path_todo, "r");
  EXIT_IF (data_file == NULL, _("failed to open todo file"));

  for (;;)
    {
      c = getc (data_file);
      if (c == EOF)
        break;
      else if (c == '[')
        {			/* new style with id */
          if (fscanf (data_file, " %d ", &id) != 1 || getc (data_file) != ']')
            EXIT (_("syntax error in item identifier"));
          while ((c = getc (data_file)) == ' ');
          ungetc (c, data_file);
        }
      else
        {
          id = 9;
          ungetc (c, data_file);
        }
      /* Now read the attached note, if any. */
      c = getc (data_file);
      if (c == '>')
        note_read (note, data_file);
      else
        {
          note[0] = '\0';
          ungetc (c, data_file);
        }
      /* Then read todo description. */
      if (!fgets (buf, sizeof buf, data_file))
        buf[0] = '\0';
      newline = strchr (buf, '\n');
      if (newline)
        *newline = '\0';
      io_extract_data (e_todo, buf, sizeof buf);
      todo_add (e_todo, id, note);
      ++nb_tod;
    }
  file_close (data_file, __FILE_POS__);
  todo_set_nb (nb_tod);
}

static void
load_keys_ht_getkey (struct ht_keybindings_s *data, char **key, int *len)
{
  *key = data->label;
  *len = strlen (data->label);
}

static int
load_keys_ht_compare (struct ht_keybindings_s *data1,
                      struct ht_keybindings_s *data2)
{
  const int KEYLEN = strlen (data1->label);

  if (strlen (data2->label) == KEYLEN
      && !memcmp (data1->label, data2->label, KEYLEN))
    return 0;
  else
    return 1;
}

/*
 * isblank(3) is protected by the __BSD_VISIBLE macro and this fails to be
 * visible in some specific cases. Thus replace it by the following is_blank()
 * function.
 */
static int is_blank (int c)
{
  return c == ' ' || c == '\t';
}

/*
 * Load user-definable keys from file.
 * A hash table is used to speed up loading process in avoiding string
 * comparisons.
 * A log file is also built in case some errors were found in the key
 * configuration file.
 */
void
io_load_keys (char *pager)
{
  struct ht_keybindings_s keys[NBKEYS];
  FILE *keyfp;
  char buf[BUFSIZ];
  struct io_file *log;
  int i, skipped, loaded, line;
  const int MAX_ERRORS = 5;

  keys_init ();

  struct ht_keybindings ht_keys = HTABLE_INITIALIZER (&ht_keys);

  for (i = 0; i < NBKEYS; i++)
    {
      keys[i].key = (enum key)i;
      keys[i].label = keys_get_label ((enum key)i);
      HTABLE_INSERT (ht_keybindings, &ht_keys, &keys[i]);
    }

  keyfp = fopen (path_keys, "r");
  EXIT_IF (keyfp == NULL, _("failed to open key file"));

  log = io_log_init ();
  skipped = loaded = line = 0;
  while (fgets (buf, BUFSIZ, keyfp) != NULL)
    {
      char key_label[BUFSIZ], *p;
      struct ht_keybindings_s *ht_elm, ht_entry;
      const int AWAITED = 1;
      int assigned;

      line++;
      if (skipped > MAX_ERRORS)
        {
          const char *too_many =
            _("\nToo many errors while reading configuration file!\n"
              "Please backup your keys file, remove it from directory, "
              "and launch calcurse again.\n");

          io_log_print (log, line, too_many);
          break;
        }
      for (p = buf; is_blank ((int)*p); p++)
        ;
      if (p != buf)
        memmove (buf, p, strlen (p));
      if (buf[0] == '#' || buf[0] == '\n')
        continue;

      if (sscanf (buf, "%s", key_label) != AWAITED)
        {
          skipped++;
          io_log_print (log, line, _("Could not read key label"));
          continue;
        }
      ht_entry.label = key_label;
      p = buf + strlen (key_label) + 1;
      ht_elm = HTABLE_LOOKUP (ht_keybindings, &ht_keys, &ht_entry);
      if (!ht_elm)
        {
          skipped++;
          io_log_print (log, line, _("Key label not recognized"));
          continue;
        }
      assigned = 0;
      for (;;)
        {
          char key_ch[BUFSIZ], tmpbuf[BUFSIZ];

          while (*p == ' ')
            p++;
          (void)strncpy (tmpbuf, p, BUFSIZ);
          if (sscanf (tmpbuf, "%s", key_ch) == AWAITED)
            {
              int ch;

              if ((ch = keys_str2int (key_ch)) < 0)
                {
                  char unknown_key[BUFSIZ];

                  skipped++;
                  (void)snprintf (unknown_key, BUFSIZ,
                                  _("Error reading key: \"%s\""), key_ch);
                  io_log_print (log, line, unknown_key);
                }
              else
                {
                  int used;

                  used = keys_assign_binding (ch, ht_elm->key);
                  if (used)
                    {
                      char already_assigned[BUFSIZ];

                      skipped++;
                      (void)snprintf (already_assigned, BUFSIZ,
                                _("\"%s\" assigned multiple times!"), key_ch);
                      io_log_print (log, line, already_assigned);
                    }
                  else
                    assigned++;
                }
              p += strlen (key_ch) + 1;
            }
          else
            {
              if (assigned)
                loaded++;
              break;
            }
        }
    }
  file_close (keyfp, __FILE_POS__);
  file_close (log->fd, __FILE_POS__);
  if (skipped > 0)
    {
      const char *view_log =
        _("There were some errors when loading keys file, see log file ?");

      io_log_display (log, view_log, pager);
    }
  io_log_free (log);
  EXIT_IF (skipped > MAX_ERRORS,
           _("Too many errors while reading keys file, aborting..."));
  if (loaded < NBKEYS)
    keys_fill_missing ();
  if (keys_check_missing_bindings ())
    WARN_MSG (_("Some actions do not have any associated key bindings!"));
}

void
io_check_dir (char *dir, int *missing)
{
  if (read_only)
    return;

  errno = 0;
  if (mkdir (dir, 0700) != 0)
    {
      if (errno != EEXIST)
        {
          fprintf (stderr, _("FATAL ERROR: could not create %s: %s\n"), dir,
                   strerror (errno));
          exit_calcurse (EXIT_FAILURE);
        }
    }
  else
    {
      if (missing)
        (*missing)++;
    }
}

unsigned
io_file_exist (char *file)
{
  FILE *fd;

  if (!file)
    return 0;

  if ((fd = fopen (file, "r")) == NULL)
    return 0;

  fclose (fd);

  return 1;
}

void
io_check_file (char *file, int *missing)
{
  if (read_only)
    return;

  errno = 0;
  if (!io_file_exist (file))
    {
      FILE *fd;

      if (missing)
        (*missing)++;
      if ((fd = fopen (file, "w")) == NULL)
        {
          fprintf (stderr, _("FATAL ERROR: could not create %s: %s\n"), file,
                   strerror (errno));
          exit_calcurse (EXIT_FAILURE);
        }
      file_close (fd, __FILE_POS__);
    }
}

/*
 * Checks if data files exist. If not, create them.
 * The following structure has to be created:
 *
 *	$HOME/.calcurse/
 *                 |
 *                 +--- notes/
 *                 |___ conf
 *                 |___ keys
 *                 |___ apts
 *                 |___ todo
 */
int
io_check_data_files (void)
{
  int missing, missing_keys;

  missing = missing_keys = 0;
  errno = 0;
  io_check_dir (path_dir, &missing);
  io_check_dir (path_notes, &missing);
  io_check_file (path_todo, &missing);
  io_check_file (path_apts, &missing);
  io_check_file (path_conf, &missing);
  io_check_file (path_keys, &missing_keys);
  if (missing_keys)
    {
      missing++;
      keys_dump_defaults (path_keys);
    }

  return missing;
}

/* Draw the startup screen */
void
io_startup_screen (int no_data_file)
{
  const char *enter = _("Press [ENTER] to continue");

  if (no_data_file)
    status_mesg (_("Data files found. Data will be loaded now."), enter);
  else
    status_mesg (_("Welcome to Calcurse. Missing data files were created."),
                 enter);

  wgetch (win[STA].p);
}

/* Export calcurse data. */
void
io_export_data (enum export_type type)
{
  FILE *stream;
  const char *success = _("The data were successfully exported");
  const char *enter = _("Press [ENTER] to continue");

  if (type < IO_EXPORT_ICAL || type >= IO_EXPORT_NBTYPES)
    EXIT (_("unknown export type"));

  stream = 0;
  switch (ui_mode)
    {
    case UI_CMDLINE:
      stream = stdout;
      break;
    case UI_CURSES:
      stream = get_export_stream (type);
      break;
    default:
      EXIT (_("wrong export mode"));
      /* NOTREACHED */
    }

  if (stream == NULL)
    return;

  if (type == IO_EXPORT_ICAL)
    ical_export_data (stream);
  else if (type == IO_EXPORT_PCAL)
    pcal_export_data (stream);

  if (conf.system_dialogs && ui_mode == UI_CURSES)
    {
      status_mesg (success, enter);
      wgetch (win[STA].p);
    }
}

/* Draws the export format selection bar */
void
io_export_bar (void)
{
  int smlspc, spc;

  smlspc = 2;
  spc = 15;

  custom_apply_attr (win[STA].p, ATTR_HIGHEST);
  mvwprintw (win[STA].p, 0, 2, "Q");
  mvwprintw (win[STA].p, 1, 2, "I");
  mvwprintw (win[STA].p, 0, 2 + spc, "P");
  custom_remove_attr (win[STA].p, ATTR_HIGHEST);

  mvwprintw (win[STA].p, 0, 2 + smlspc, _("Exit"));
  mvwprintw (win[STA].p, 1, 2 + smlspc, _("Ical"));
  mvwprintw (win[STA].p, 0, 2 + spc + smlspc, _("Pcal"));

  wnoutrefresh (win[STA].p);
  wmove (win[STA].p, 0, 0);
  wins_doupdate ();
}

static FILE *
get_import_stream (enum export_type type)
{
  FILE *stream;
  char *stream_name;
  const char *ask_fname = _("Enter the file name to import data from:");
  const char *wrong_file =
    _("The file cannot be accessed, please enter another file name.");
  const char *press_enter = _("Press [ENTER] to continue.");
  int cancel;

  stream = NULL;
  stream_name = mem_malloc (BUFSIZ);
  memset (stream_name, 0, BUFSIZ);
  while (stream == NULL)
    {
      status_mesg (ask_fname, "");
      cancel = updatestring (win[STA].p, &stream_name, 0, 1);
      if (cancel)
        {
          mem_free (stream_name);
          return NULL;
        }
      stream = fopen (stream_name, "r");
      if (stream == NULL)
        {
          status_mesg (wrong_file, press_enter);
          wgetch (win[STA].p);
        }
    }
  mem_free (stream_name);

  return stream;
}

/*
 * Import data from a given stream (either stdin in non-interactive mode, or the
 * user given file in interactive mode).
 * A temporary log file is created in /tmp to store the import process report,
 * and is cleared at the end.
 */
void
io_import_data (enum import_type type, char *stream_name)
{
  const char *proc_report = _("Import process report: %04d lines read ");
  char stats_str[4][BUFSIZ];
  FILE *stream = NULL;
  struct io_file *log;
  struct {
    unsigned events, apoints, todos, lines, skipped;
  } stats;

  EXIT_IF (type < 0 || type >= IO_IMPORT_NBTYPES, _("unknown import type"));
  switch (ui_mode)
    {
    case UI_CMDLINE:
      stream = fopen (stream_name, "r");
      EXIT_IF (stream == NULL,
               _("FATAL ERROR: the input file cannot be accessed, "
                 "Aborting..."));
      break;
    case UI_CURSES:
      stream = get_import_stream (type);
      break;
    default:
      EXIT (_("FATAL ERROR: wrong import mode"));
      /* NOTREACHED */
    }

  if (stream == NULL)
    return;

  memset (&stats, 0, sizeof stats);

  log = io_log_init ();
  if (log == NULL)
    {
      if (stream != stdin)
        file_close (stream, __FILE_POS__);
      return;
    }

  if (type == IO_IMPORT_ICAL)
    ical_import_data (stream, log->fd, &stats.events, &stats.apoints,
                      &stats.todos, &stats.lines, &stats.skipped);

  if (stream != stdin)
    file_close (stream, __FILE_POS__);

  snprintf (stats_str[0], BUFSIZ,
            ngettext ("%d app", "%d apps", stats.apoints), stats.apoints);
  snprintf (stats_str[1], BUFSIZ,
            ngettext ("%d event", "%d events", stats.events), stats.events);
  snprintf (stats_str[2], BUFSIZ,
            ngettext ("%d todo", "%d todos", stats.todos), stats.todos);
  snprintf (stats_str[3], BUFSIZ, _("%d skipped"), stats.skipped);

  /* Update the number of todo items. */
  todo_set_nb (todo_nb () + stats.todos);

  if (ui_mode == UI_CURSES && conf.system_dialogs)
    {
      char read[BUFSIZ], stat[BUFSIZ];

      snprintf (read, BUFSIZ, proc_report, stats.lines);
      snprintf (stat, BUFSIZ, "%s / %s / %s / %s (%s)", stats_str[0],
                stats_str[1], stats_str[2], stats_str[3],
                _("Press [ENTER] to continue"));
      status_mesg (read, stat);
      wgetch (win[STA].p);
    }
  else if (ui_mode == UI_CMDLINE)
    {
      printf (proc_report, stats.lines);
      printf ("\n%s / %s / %s / %s\n", stats_str[0], stats_str[1],
              stats_str[2], stats_str[3]);
    }

  /* User has the choice to look at the log file if some items could not be
     imported.
  */
  file_close (log->fd, __FILE_POS__);
  if (stats.skipped > 0)
    {
      const char *view_log = _("Some items could not be imported, see log file ?");

      io_log_display (log, view_log, conf.pager);
    }
  io_log_free (log);
}

struct io_file *
io_log_init (void)
{
  char logprefix[BUFSIZ];
  char *logname;
  struct io_file *log;

  snprintf (logprefix, BUFSIZ, "%s/calcurse_log.", get_tempdir ());
  logname = new_tempfile (logprefix, TMPEXTSIZ);
  RETVAL_IF (logname == NULL, 0,
             _("Warning: could not create temporary log file, Aborting..."));
  log = mem_malloc (sizeof (struct io_file));
  RETVAL_IF (log == NULL, 0,
             _("Warning: could not open temporary log file, Aborting..."));
  snprintf (log->name, sizeof (log->name), "%s%s", logprefix, logname);
  mem_free (logname);
  log->fd = fopen (log->name, "w");
  if (log->fd == NULL)
    {
      ERROR_MSG (_("Warning: could not open temporary log file, Aborting..."));
      mem_free (log);
      return 0;
    }

  return log;
}

void
io_log_print (struct io_file *log, int line, const char *msg)
{
  if (log && log->fd)
    fprintf (log->fd, "line %d: %s\n", line, msg);
}

void
io_log_display (struct io_file *log, const char *msg, char *pager)
{
  int ans;

  RETURN_IF (log == NULL, _("No log file to display!"));
  if (ui_mode == UI_CMDLINE)
    {
      printf ("\n%s [y/n] ", msg);
      ans = fgetc (stdin);
      if (ans == 'y')
        {
          char *arg[] = { pager, log->name, NULL };
          int pid;

          if ((pid = fork_exec (NULL, NULL, pager, arg)))
            child_wait (NULL, NULL, pid);
        }
    }
  else
    {
      status_mesg_yesno (msg);
      do
        {
          ans = wgetch (win[STA].p);
          if (ans == 'y')
            {
              wins_launch_external (log->name, pager);
            }
        }
      while (ans != 'y' && ans != 'n');
      wins_erase_status_bar ();
    }
}

void
io_log_free (struct io_file *log)
{
  if (!log)
    return;
  EXIT_IF (unlink (log->name) != 0,
           _("Warning: could not erase temporary log file %s, Aborting..."),
           log->name);
  mem_free (log);
}

static pthread_t io_t_psave;

/* Thread used to periodically save data. */
static void *
io_psave_thread (void *arg)
{
  int delay;

  delay = conf.periodic_save;
  EXIT_IF (delay < 0, _("Invalid delay"));

  for (;;)
    {
      sleep (delay * MININSEC);
      io_save_cal (IO_SAVE_DISPLAY_MARK);
    }
}

/* Launch the thread which handles periodic saves. */
void
io_start_psave_thread (void)
{
  pthread_create (&io_t_psave, NULL, io_psave_thread, NULL);
}

/* Stop periodic data saves. */
void
io_stop_psave_thread (void)
{
  if (io_t_psave)
    {
      pthread_cancel (io_t_psave);
      pthread_join (io_t_psave, NULL);
    }
}

/*
 * This sets a lock file to prevent from having two different instances of
 * calcurse running.
 *
 * If the lock cannot be obtained, then warn the user and exit calcurse. Else,
 * create a .calcurse.pid file in the user defined directory, which will be
 * removed when calcurse exits.
 *
 * Note: When creating the lock file, the interactive mode is not initialized
 * yet.
 */
void
io_set_lock (void)
{
  FILE *lock = fopen (path_cpid, "r");
  int pid;

  if (lock != NULL)
    {
      /* If there is a lock file, check whether the process exists. */
      if (fscanf(lock, "%d", &pid) == 1)
        {
          fclose(lock);
          if (kill(pid, 0) != 0 && errno == ESRCH)
            lock = NULL;
        }
      else
        fclose(lock);
    }

  if (lock != NULL)
    {
      fprintf (stderr,
               _("\nWARNING: it seems that another calcurse instance is "
                 "already running.\n"
                 "If this is not the case, please remove the following "
                 "lock file: \n\"%s\"\n"
                 "and restart calcurse.\n"), path_cpid);
      exit (EXIT_FAILURE);
    }
  else
    {
      if (!io_dump_pid (path_cpid))
        EXIT (_("FATAL ERROR: could not create %s: %s\n"),
                         path_cpid, strerror (errno));
    }
}

/*
 * Create a new file and write the process pid inside (used to create a simple
 * lock for example). Overwrite already existing files.
 */
unsigned
io_dump_pid (char *file)
{
  pid_t pid;
  FILE *fp;

  if (!file)
    return 0;

  pid = getpid ();
  if (!(fp = fopen (file, "w"))
      || fprintf (fp, "%ld\n", (long)pid) < 0
      || fclose (fp) != 0)
    return 0;

  return 1;
}

/*
 * Return the pid number contained in a file previously created with
 * io_dump_pid ().
 * If no file was found, return 0.
 */
unsigned
io_get_pid (char *file)
{
  FILE *fp;
  unsigned pid;

  if (!file)
    return 0;

  if ((fp = fopen (file, "r")) == NULL)
    return 0;

  if (fscanf (fp, "%u", &pid) != 1)
    return 0;

  fclose (fp);

  return pid;
}

/*
 * Check whether a file is empty.
 */
int
io_file_is_empty (char *file)
{
  FILE *fp;

  if (file && (fp = fopen (file, "r")))
    {
      if ((fgetc (fp) == '\n' && fgetc (fp) == EOF) || feof (fp))
        {
          fclose (fp);
          return 1;
        }
      else
        {
          fclose (fp);
          return 0;
        }
    }

  return -1;
}

/*
 * Copy an existing file to a new location.
 */
int
io_file_cp (const char *src, const char *dst)
{
  FILE *fp_src, *fp_dst;
  char *buffer[BUFSIZ];
  unsigned int bytes_read;

  if (!(fp_src = fopen (src, "rb")))
    return 0;
  if (!(fp_dst = fopen (dst, "wb")))
    return 0;

  while (!feof (fp_src))
    {
      bytes_read = fread (buffer, 1, BUFSIZ, fp_src);
      if (bytes_read > 0)
        {
          if (fwrite (buffer, 1, bytes_read, fp_dst) != bytes_read)
            return 0;
        }
      else
        return 0;
    }

  fclose (fp_dst);
  fclose (fp_src);

  return 1;
}