Logo Search packages:      
Sourcecode: gaby version File versions  Download package

gaby.c

/*  Gaby
 *  Copyright (C) 1998-1999 Frederic Peters
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <format_plugin.h>
#include <math.h>

#undef USE_HELPER_BINARY
#define FORMAT_REVISION       0

/* TODO (post-2.0): maximum record size is 10000
 * this should definitely be moved to something dynamic,
 * 
 * Jari Eskelinen <jari.eskelinen@mbnet.fi> had lots of problems because of
 * this stupid thing !!!
 *
 * delayed since nobody experienced problems with max_size == 10000
 */
#define           RECORD_MAX_SIZE         10000

/* Thoughts about this file format
 *   pros:
 *    - text file, can be edited without gaby
 *    - simple
 *    - portable
 *   cons:
 *    - ssslllooowww :)
 *
 * The question is now : 'what do you want ?'
 *                      fast but unreadable or slow but popular ?
 *
 * I'd like to have something faster _while keeping_ the advantages.
 * How to achieve this ? (this a draft, written while I think about it)
 *  
 *  have a new file, binary, aside as the current text file;
 *  when loading I'd stat(2) the files and if they've not the same time
 *  I'll switch back to the current behaviour.
 *  This might slow down the saving process but people want Gaby to start
 *  quick, they rarely bother about leaving Gaby quick.
 *
 *  That said; how would that work ?
 *   - the binary file could be named ("%s.bin", textfile)
 *   - it would contain :
 *      - gaby version number (so the file could be skipped if the format
 *        changed)
 *      - number of records
 *      - and, for each record :
 *         - its id (so atoi(3) won't be called)
 *         - offset to record beginning
 *         - offsets and lengths of every fields
 *         - flags to tell if the fields use backslash sequences (one per field)
 */

/* those were thougths, here is the implementation :)
 * you can use it with #define USE_HELPER_BINARY
 * quick tests showed no significant gain
 * */

struct file_header {
      int revision;           /* in case the format changes */
      time_t last_change;     /* so we don't use this file if the data were
                           modified by hand (or with another tool) */
      int nb_records;
      long file_length;
};

struct file_record {
      char use_backslashes;
      int id;
      long offset_start;
      /* plus <nb_fields> * (long) offset_field_end */
};

static gboolean record_add_str(table *t, char *str, struct location *loc);
static gboolean record_add_simple_str(table *t, char *str,struct location *loc);

#ifdef USE_HELPER_BINARY
static void record_add_str_for_helper( struct location *loc, gchar *wholefile,
                               struct file_record *rec, long offsets[]);
#endif

static void record_get_str(table *t, record r, char *str, 
                              char *use_backslashes, long *offsets);
static GDate* create_date_field(char *str);

gboolean gaby_load_file (struct location *loc)
{
      table *t = loc->table;
      FILE *f;
      char s[RECORD_MAX_SIZE];
#ifdef SUPERFLUOUS
      char *twirling = "-/|\\-/|\\-";
      static int twirlingpos=0;
#endif
#ifdef USE_HELPER_BINARY
      struct file_header head;
      struct file_record rec;
      FILE *fxx;
      char helperfile[PATH_MAX];
      struct stat buf;
      int i;
      char *wholefile;
      long *offsets;
#endif
      
#ifdef DEBUG_GABY
      debug_print("Loading %s\n", loc->filename);
#endif

      f = fopen(loc->filename, "r" );
      if ( f == NULL ) {
            gaby_errno = FILE_READ_ERROR;
            if ( app != NULL ) {
                  gaby_message = g_strdup(loc->filename);
                  gaby_perror_in_a_box();
            }
            return FALSE;
      }

#ifdef USE_HELPER_BINARY
      sprintf(helperfile, "%s.xx", loc->filename);
      fxx = fopen(helperfile, "r");
      if ( fxx != NULL ) {
            fread(&head, sizeof(struct file_header), 1, fxx);
            stat(helperfile, &buf);
            if ( head.revision != FORMAT_REVISION || 
                        head.last_change != buf.st_mtime ) {
                  /* helper file no longer valid */
                  fclose(fxx);
                  fxx = NULL;
            } else {
                  wholefile = g_malloc( head.file_length+2 );
                  fread(wholefile, head.file_length, 1, f);
                  offsets = g_new0(long, t->nb_fields);
                  for ( i=0; i < head.nb_records; i++ ) {
                        fread(&rec, sizeof(struct file_record), 1, fxx);
                        fread(offsets, sizeof(long),t->nb_fields, fxx);
                        record_add_str_for_helper(loc, wholefile, 
                                    &rec, offsets );
                  }
                  g_free(wholefile);
                  g_free(offsets);
                  fclose(f);
                  fclose(fxx);
                  return TRUE;
            }
                  
      }
#endif /* USE_HELPER_BINARY */

      fgets(s, RECORD_MAX_SIZE, f);
#ifdef DEBUG_GABY
      debug_print("the first id will be %d\n", loc->offset);
#endif
      while ( ! feof(f) ) {
#ifdef SUPERFLUOUS
            putchar(twirling[(twirlingpos++)%9]); putchar('\b');
            fflush(stdout);
#endif
            if ( strchr(s, '\\') ) {
                  record_add_str(t, s, loc);
            } else {
                  record_add_simple_str(t, s, loc);
            }
            fgets(s, RECORD_MAX_SIZE, f);
      }

      fclose(f);
      
      return TRUE;
}

gboolean gaby_save_file (struct location *loc)
{
      table *t = loc->table;
      FILE *f;
      int i;
      char str[RECORD_MAX_SIZE];
#ifdef USE_HELPER_BINARY
      struct file_header head = { FORMAT_REVISION, 0, 0, 0 };
      struct file_record rec;
      FILE *fxx;
      char helperfile[PATH_MAX];
      struct stat buf;
#endif
      char use_backslashes = 0;
      long *offsets = NULL;
      
#ifdef DEBUG_GABY
      debug_print("Saving %s\n", loc->filename);
#endif

      f = fopen(loc->filename, "w" );
      if ( f == NULL ) {
            gaby_errno = FILE_WRITE_ERROR;
            gaby_message = g_strdup(loc->filename);
            gaby_perror_in_a_box();
            return FALSE;
      }

#ifdef USE_HELPER_BINARY
      offsets = g_new0(long, t->nb_fields+1);
      sprintf(helperfile, "%s.xx", loc->filename);
      fxx = fopen(helperfile, "w");
      if ( fxx != NULL ) {
            fwrite(&head, sizeof(struct file_header), 1, fxx);
#ifdef DEBUG_GABY
            debug_print("Writing %d bytes\n", sizeof(struct file_header));
#endif
      }
#endif
      
      for ( i=0; i<t->max_records;i++ ) {
            if ( t->records[i] == NULL || t->records[i]->id == 0 )
                  continue;
            if ( loc->type != NULL && t->records[i]->file_loc != loc )
                  continue;
#ifdef DEBUG_GABY
            debug_print("[gaby_fmt:ras] record %d\n", i );
#endif
#ifdef USE_HELPER_BINARY
            offsets[0] = ftell(f) - 1;
            rec.id = t->records[i]->id;
#endif
            record_get_str(t, *(t->records[i]), str, 
                                    &use_backslashes, offsets+1 );
#ifdef USE_HELPER_BINARY
            head.nb_records++;
            rec.use_backslashes = use_backslashes;
            rec.offset_start = offsets[0]+1;
            if ( fxx != NULL ) {
                  fwrite(&rec, sizeof(struct file_record), 1, fxx);
                  fwrite(offsets+1, sizeof(long), t->nb_fields, fxx);
#ifdef DEBUG_GABY
            debug_print("Writing %d + %d bytes\n", 
                        sizeof(struct file_record), 
                        sizeof(long) * t->nb_fields );
#endif
            }
#endif
            fputs(str, f);
      }
      fputs("\n", f);
      
#ifdef USE_HELPER_BINARY
      if ( fxx != NULL ) {
            head.file_length = ftell(f);
            rewind(fxx);
            stat(loc->filename, &buf);
            head.last_change = buf.st_mtime;
            fwrite(&head, sizeof(struct file_header), 1, fxx);
            fclose(fxx);
      }
      g_free(offsets);
#endif
      fclose(f);
      
      return TRUE;
}

static gboolean record_add_str(table *t, char *str, struct location *loc)
{
      record *r;
      char *s2, *s1, *s3;
      int i=0, j;
      int ta;
      char *src="n;\\";
      char *dst="\n;\\";
      /* TODO (post-2.0): check g_strescape and g_strcompress function
       * (problem will be for ';'s)
       */
      
#ifdef DEBUG_GABY
      /* g_print("Adding : %s\n", str);*/
#endif

      if ( str[0] == '#' || str[0] == '\n' || str[0] == 0 )
            return FALSE;
      
      r = g_malloc(sizeof(record));
      r->id = atoi(str) + loc->offset - 1;
      r->file_loc = loc;
      /* TODO (post-2.0) : check id
       * delayed: integrity not a goal */

      s1 = strchr(str, ';')+1;
      
      r->cont = g_new0(union data, t->nb_fields);

      do {
            s2 = s1;
            do {
                  s2 = strchr(s2, ';');
                  if ( s2 == NULL ) { s2 = s1+strlen(s1); break;}
                  s2++;
            } while ( s2[-2] == '\\' );
            ta = s2-s1-1;
            s3 = g_malloc(ta+1);
            strncpy(s3, s1, ta);
            s3[ta] = 0;
            for ( j=0; j<strlen(src); j++ ) {
                  s2 = s3;
                  while ( strchr(s2, src[j]) != NULL ) {
                        s2 = strchr(s2, src[j]);
                        if ( s2[-1] != '\\' ) {
                              s2++;
                              continue;
                        }
                        s2[-1] = dst[j];
                        strcpy(s2, s2+1);
                  }
            }
#ifdef DEBUG_GABY
/*          g_print("[format:gaby:record_add_str] type : %d\n", t->fields[i].type);*/
#endif
            switch ( t->fields[i].type ) {
                  case T_DATE:
                  {
                        r->cont[i].date = create_date_field(s3);
                  } break;
                  case T_INTEGER:
                  case T_RECORD:
                  {
                        r->cont[i].i = atoi(s3);
                  } break;
                  case T_DECIMAL:
                  {
                        r->cont[i].i = (int)(rint(atof(s3)*100));
                  } break;
                  case T_REAL:
                  {
                        r->cont[i].d = atof(s3);
                  } break;
                  case T_STRING:
                  case T_STRINGS:
                  case T_MULTIMEDIA:
                  case T_FILE:
                  default:
                  {
                        r->cont[i].str = g_string_new(s3);
                  } break;
                  case T_BOOLEAN:
                  {
                        r->cont[i].b = FALSE;
                        if ( strcmp(s3, "yes") == 0 ) {
                              r->cont[i].b = TRUE;
                        }
                  } break;
            }
            
            g_free(s3);
            s1 += ta+1;
            i++;
      } while ( s1[-1] != '\n');

      record_add(t, r, FALSE, TRUE);

      return TRUE;
}

static gboolean record_add_simple_str(table *t, char *str, struct location *loc)
{
      record *r;
      int i;
      char *s, *s2;
      
      if ( str[0] == '#' || str[0] == '\n' || str[0] == 0 )
            return FALSE;
      if ( str[strlen(str)-1] == '\n' ) str[strlen(str)-1] = 0;
      
      r = g_new0(record, 1);
      r->id = atoi(str) + loc->offset - 1;
      r->file_loc = loc;
      r->cont = g_new0(union data, t->nb_fields);

      s = strchr(str, ';')+1;
      
      for (i=0; i<t->nb_fields; i++ ) {
            s2 = strchr(s, ';');
            if ( s2 == NULL && i < t->nb_fields-1 ) {
#ifdef DEBUG_GABY
                  debug_print("%s failed with i = %d\n", str, i);
#endif
                  g_free(r->cont);
                  g_free(r);
                  return FALSE;
            }
            if ( s2 != NULL ) *s2 = 0;
#if 0
            switch ( t->fields[i].type ) {
                  case T_DATE:
                  {
                        r->cont[i].date = create_date_field(s);
                  } break;
                  case T_INTEGER:
                  case T_RECORD:
                  {
                        r->cont[i].i = atoi(s);
                  } break;
                  case T_REAL:
                  {
                        r->cont[i].d = atof(s);
                  } break;
                  case T_STRING:
                  case T_STRINGS:
                  case T_MULTIMEDIA:
                  default:
                  {
                        r->cont[i].str = g_string_new(s);
                  } break;
                  case T_BOOLEAN:
                  {
                        r->cont[i].b = FALSE;
                        if ( strcmp(s, "yes") == 0 ) {
                              r->cont[i].b = TRUE;
                        }
                  } break;
            }
#else
            if ( t->fields[i].type == T_DATE ) {
                  r->cont[i].date = create_date_field(s);
            } else {
                  set_table_stringed_field(t, r, i, s);
            }
            s = s2+1;
#endif
      }
      
      record_add(t, r, FALSE, TRUE);

      return TRUE;
}

#ifdef USE_HELPER_BINARY
static void record_add_str_for_helper( struct location *loc, gchar *wholefile,
                               struct file_record *rec, long offsets[])
{
      table *t = loc->table;
      record *r;
      int i;
      char *cur_pos;
      int field_length;
      
      r = g_new0(record, 1);
      r->id = rec->id;
      r->file_loc = loc;
      r->cont = g_new0(union data, t->nb_fields);

      cur_pos = wholefile + rec->offset_start;
      field_length = offsets[0] - rec->offset_start;
      for ( i=0; i < t->nb_fields; i++ ) {
            cur_pos [ field_length ] = 0;
            if ( t->fields[i].type == T_DATE ) {
                  r->cont[i].date = create_date_field(cur_pos);
            } else {
                  if ( rec->use_backslashes == 0 || ( 
                              t->fields[i].type != T_STRING && 
                              t->fields[i].type != T_STRINGS && 
                              t->fields[i].type != T_MULTIMEDIA ) ) {
                        set_table_stringed_field(t, r, i, cur_pos);
                  } else {
                        char *src="n;\\";
                        char *dst="\n;\\";
                        char *s2, *s3;
                        int j;

                        s2 = s3 = cur_pos;
                        
                        for ( j=0; j<strlen(src); j++ ) {
                              s2 = s3;
                              while ( strchr(s2, src[j]) != NULL ) {
                                    s2 = strchr(s2, src[j]);
                                    if ( s2[-1] != '\\' ) {
                                          s2++;
                                          continue;
                                    }
                                    s2[-1] = dst[j];
                                    strcpy(s2, s2+1);
                              }
                        }
                        r->cont[i].str = g_string_new(s2);
                  }
            }
            cur_pos += field_length + 1;
            field_length = offsets[i+1] - offsets[i] - 1;
      }
      record_add(t, r, FALSE, TRUE);
}

#endif

static void record_get_str(table *t, record r, char *str, 
                              char *use_backslashes, long *offsets)
{
      int i;
      char *fix_str = str;
      char *semi_fix_str;
      char *src="\\\n;"; /* those will be replaced */
      char *dst="\\n;";  /* by \ + these ones */
      int j;
      
#ifdef USE_HELPER_BINARY
      *use_backslashes = 0; /* default to 'no' */
#endif
      
      sprintf(str, "%d;", (r.id % (r.file_loc->offset+ (1<<16) ) ));
#ifdef USE_HELPER_BINARY
      offsets[-1] += strlen(str);
#endif
      str += strlen(str);
      semi_fix_str = str;

      for ( i=0; i<t->nb_fields; i++ ) {
#ifdef DEBUG_GABY
/*          g_print("[gaby:rgs] field %d (type : %d)\n", i, 
                        t->fields[i].type );*/
#endif
            switch ( t->fields[i].type ) {
                  case T_DATE:
                  {
                        if ( r.cont[i].date != NULL )
                              sprintf(str, "%d %d %d", 
                                    g_date_year(r.cont[i].date),
                                    g_date_month(r.cont[i].date),
                                    g_date_day(r.cont[i].date) );
                  } break;
                  case T_INTEGER:
                  case T_RECORD:
                  {
                        sprintf(str, "%d", r.cont[i].i);
                  } break;
                  case T_REAL:
                  {
                        sprintf(str,"%f", r.cont[i].d);
                  } break;
                  case T_DECIMAL:
                  {
                        sprintf(str, "%f", (float)r.cont[i].i / 100.0 );
                  } break;
                  case T_BOOLEAN:
                  {
                        if ( r.cont[i].b == TRUE ) {
                              sprintf(str, "yes");
                        } else {
                              sprintf(str, "false");
                        }
                  } break;
                  case T_STRING:
                  case T_STRINGS:
                  case T_MULTIMEDIA:
                  case T_FILE:
                  default:
                  {
#ifdef DEBUG_GABY
/*                      g_print("[gaby:rgs] |= %p\n", r.cont[i].str);*/
#endif
                        if ( r.cont[i].str != NULL ) {
                              sprintf(str, "%s", r.cont[i].str->str);
                        }
                  } break;
            }
            for ( j=0; j<strlen(src); j++ ) {
                  str = semi_fix_str;
                  while ( /* str = */ strchr(str, src[j]) != NULL ) {
#ifdef USE_HELPER_BINARY
                        *use_backslashes = 1;
#endif
                        str = strchr(str, src[j]); /* cleaner */
                        memmove(str+2, str+1, strlen(str+1)+1);
                        str[0] = '\\'; str[1] = dst[j]; str+=2;
                  }
            }
            str = fix_str;
            str += strlen(str);
            str[0] = ';'; str[1] = 0; str++;
#ifdef USE_HELPER_BINARY
            offsets[i] = offsets[i-1] + strlen(semi_fix_str);
#endif
            semi_fix_str = str;
      }
      str = fix_str;
      str[strlen(str)-1] = '\n'; /* we don't want a trailing ';' */
      
}

static GDate* create_date_field(char *str)
{
      /*
       * dates are stored in file in the 'yyyy mm dd' format and then it is
       * glib (and strftime) job to provide a stringed date in a format
       * suitable to the user.
       */
      GDate *date;
      int year, month, day;

#ifdef DEBUG_GABY
      debug_print("[gaby:fmt:cdf] date : %s\n", str);
#endif

      /* if the string is empty, we return NULL and I hope that
       *  get_subtable_stringed_field will handle this :)
       */
      if ( strlen(str) == 0 )
            return NULL;

#if 1 /* GDate way */
      
      if ( strchr(str, '/') != NULL ) {
#ifdef DEBUG_GABY
            debug_print("Sorry but a new date format wiped your datas.\n");
#endif
            return NULL;      /* snif :( */
      }
      
      sscanf(str, "%d %d %d", &year, &month, &day);
#ifdef DEBUG_GABY
      debug_print("[gaby:fmt:cdf] date (before) : %d/%d/%d\n", day, month, year);
#endif
      date = g_date_new_dmy(day, month, year);
#ifdef DEBUG_GABY
      debug_print("[gaby:fmt:cdf] date (after) : %d/%d/%d\n", g_date_day(date), 
                  g_date_month(date), g_date_year(date) );
#endif
      return date;
      
#else /* classic way */
      if ( strlen(str) == 0 ) return;
      if ( ! strchr(str,'/') ) return;

      strchr(str,'/')[0] = 0;
      date->tm_mday = atoi(str);
      if ( date->tm_mday == 0 ) date->tm_mday++;
      str += strlen(str)+1;
      
      if ( ! strchr(str,'/') ) return;

      strchr(str,'/')[0] = 0;
      date->tm_mon = atoi(str)-1; /* tm_mon is 0..11, not 1..12 */
      str += strlen(str)+1;
      
      date->tm_year = atoi(str);
      
      if ( date->tm_year < 100 ) {
            /* we try to be smarter than you : 2 digit years are 19xx
             * there's no way to force a date < 1/1/100, but I believe
             * it's enough for your friends/family
             */
            date->tm_year += 1900;
      }
#endif

}


Generated by  Doxygen 1.6.0   Back to index