Main program and conversion for standard info

Code and documentation copyright 1998 by Norman Ramsey. All rights reserved.

To compile this, you'll need Lua.

Here's the source layout.

<*>=
#include <string.h>
#include <stdio.h>
#include <stdlib.h> 
#include <assert.h>

#include "pdbconv.h"
#include "pdbrep.h"
#include "pdblua.h"

<macros>

<functions>

These macros help pack and unpack Lua.

<macros>= (<-U)
#define UNFLAG(X)   fprintf(out, "  %s = %s,\n",  #X, db->info.X ? "1" : "nil");
#define UNINT(X)    fprintf(out, "  %s = %d,\n",  #X, db->info.X);
#define UNLINT(X)   fprintf(out, "  %s = %ld,\n", #X, db->info.X);
#define UNSTRING(X) fprintf(out, "  %s = %s,\n",  #X, QUOTE(db->info.X));
#define UNDATE(X)   (fprintf(out, "    %s = ", #X), \
                     unpack_date(out, db->info.X ## Date), \
                     fprintf(out, ",\n"));
#define PFLAG(X)     info->X = bitval("Database." #X);
#define PINT(X)      info->X = intval("Database." #X);
#define PINTOPT(X,N) info->X = intopt("Database." #X, N);
#define PLINT(X)     PINT(X);
#define PLINTOPT(X, N) PINTOPT(X,N);
#define PSTRING(X)   setstring(info->X, "Database." #X, sizeof(info->X)-1);
                        /* note size doesn't include terminating null */
#define PDATE(X) info->X ## Date = pack_date(objectval("Database.dates." #X));
Defines PDATE, PFLAG, PINT, PINTOPT, PLINT, PLINTOPT, PSTRING, UNDATE, UNFLAG, UNINT, UNLINT, UNSTRING (links are to index).

Writing the Lua version of a database

<functions>= (<-U) [D->]
static PDBConverter get_converter(struct pdb *db); /* uses only type, creator */
static void dbLua(FILE *out, struct pdb *db);
static void unpack(FILE *in, FILE *out) {
  struct pdb db = read_db(in);
  dbLua(out, &db);
}

static void dbLua(FILE *out, struct pdb *db) {
  PDBConverter convert = get_converter(db);
  int i;


  lua_beginblock();
  fprintf(out, "-- This %s created by Norman Ramsey's pdb converter\n",
          convert->application ? convert->application :
          "generic view of a PDB file");
  fprintf(out, "-- To use the output, get http://www.tecgraf.puc-rio.br/lua\n");
  fprintf(out, "-- For more information, see http://www.cs.tufts.edu/~nr/pilot\n");
  fprintf(out, "\n\n");
  fprintf(out, "-- The value `Database' represents the database\n");
  fprintf(out, "Database = {\n");
  fprintf(out, "  -- the first four fields are required\n");
  UNSTRING(name)
  UNSTRING(type) UNSTRING(creator)
  UNINT(version)
  fprintf(out, "\n  -- no flags are required (default to nil)\n");
  UNFLAG(resource)  UNFLAG(readonly)  UNFLAG(dirtyAppInfo)  
  UNFLAG(backup) UNFLAG(installOver) UNFLAG(reset)
  { static char *docs [] = { 
      "Date format:", "  unixtime is seconds since 1/1/1970.",
      "  If unixtime is present, all other fields are ignored.",
      "  Otherwise, as many fields as are present are used to construct",
      "  a date, with missing fields defaulting to minimum values.",
      "  The value `today' (without quotes!) may be used instead",
      "  of a table; it is bound to a table with suitable unixtime",
      0 };
    putc('\n', out);
    write_docs(out, 4, docs);
  }
  fprintf(out, "  dates = {\n");
  UNDATE(create) UNDATE(modify) UNDATE(backup)
  fprintf(out, "  },\n\n");
  fprintf(out, "  -- these next three fields default to 0 if omitted\n");
  UNLINT(uidSeed)
  fprintf(out, "        -- nextRecordListID should always be 0\n");
  UNLINT(nextRecordListID) 
  fprintf(out, "        -- modnum is incremented at every database change\n");
  UNLINT(modnum)
  fprintf(out, "  -- the following value exists just so we can do exact copies;\n");
  fprintf(out, "  -- it's fine to leave it out, and it defaults to zero\n");
  fprintf(out, "  filler = %d,\n", db->filler);

  fprintf(out, "\n\n  -- Everything from here down is application-dependent\n");

  if (db->info.appInfo.chars && convert->appInfo.docs)
    write_docs(out, 4, convert->appInfo.docs);

  fprintf(out, "  appInfo = ");
  if (db->info.appInfo.chars)
    convert->appInfo.unpack(out, db, db->info.appInfo);
  else
    fprintf(out, "nil");
  fprintf(out, ",\n");     

  if (db->info.sortInfo.chars && convert->sortInfo.docs)
    write_docs(out, 2, convert->sortInfo.docs);
    
  fprintf(out, "  sortInfo = ");
  if (db->info.sortInfo.chars)
    convert->sortInfo.unpack(out, db, db->info.sortInfo);
  else
    fprintf(out, "nil");
  fprintf(out, ",\n");     

  { static char *docs [] = { 
      "Records:", "  records must be an array, and each item a table.",
      "  All fields except data can be omitted and default to 0 (or nil).",
      "    delete = deleted          busy  = locked by an app",
      "    secret = need passwd      dirty = set at modify (cleared when?)",
      0 };
     write_docs(out, 2, docs);
     if (convert->record.docs) {
       fprintf(out, "  --  The interpretation of the data field is as follows:\n");
       write_docs(out, 6, convert->record.docs);
     }
  }

#define UNRFLAG(X)   fprintf(out, "%s = %s, ", #X, db->records[i].X ? "1" : "nil");
  fprintf(out, "  records = {\n");
  for(i=0; i<db->info.numrecs; i++) {
    lua_beginblock();
    fprintf(out, "    { category = %d, uid = %d,\n      ",
            db->records[i].category, db->records[i].uid);
    UNRFLAG(secret) UNRFLAG(busy) UNRFLAG(dirty) UNRFLAG(delete)
    fprintf(out, "\n      data = ");
    convert->record.unpack(out, db, i);
    fprintf(out, "\n    }%s\n", comma(i + 1 < db->info.numrecs));
    lua_endblock();
  }
  fprintf(out, "  }\n");
  fprintf(out, "}\n\n");
  lua_endblock();
  if (convert->trailers) {
    char **p;
    for (p = convert->trailers; *p; p++)
      fprintf(out, "%s\n", *p);
  }
}
Defines dbLua, unpack, UNRFLAG (links are to index).

Packing a Lua database into PDB format

<functions>+= (<-U) [<-D->]
static void pack(char *filename, FILE *output) {
  int i;
  struct pdb db;
  struct DBInfo *info = &db.info;
  long startpos = ftell(output);
  memset(&db, 0, sizeof(db));   /* trailing 0s for strings */

  <set up the Lua data space and read in the database>
  lua_beginblock();
  <pack the Lua database into data structures, then write it>
  lua_endblock();
}
Defines pack (links are to index).

We set up a date of today.

<set up the Lua data space and read in the database>= (<-U) [D->]
{ static char buf[80];
  sprintf(buf, "today = { unixtime = '%lu' }", time(NULL));
  if (lua_dostring(buf)) assert(0);
}

We need this function to be able to count the number of records.

<bind listlength and istable into the Lua space>= (U->)
if(lua_dostring(
  "$debug\n"
  "function listlength(list, file, line)\n"
  "  local i, n, v\n"
  "  if type(list) ~= 'table' then \n"
  "    error(file .. ':' .. tostring(line) .. ' -- value of type '"
  "          .. type(list) .. ' is not a list') \n"
  "  end\n"
  "  i = nil\n"
  "  n = 0\n"
  "  repeat\n"
  "    i, v = next(list, i)\n"
  "    if (i ~= nil) then\n"
  "      n = n + 1\n"
  "    end\n"
  "  until (i == nil)\n"
  "  return n\n"
  "end\n"
  "\n"
  "function istable(x)\n"
  "  return type(x) == 'table'\n"
  "end\n")
) assert(0);

Read the database.

<set up the Lua data space and read in the database>+= (<-U) [<-D]
if (lua_dofile(filename)) {
  fprintf(stderr, "Could not read %s\n", filename ? filename : "<stdin>");
  exit(1);
}

First, the obvious fields.

<pack the Lua database into data structures, then write it>= (<-U) [D->]
PSTRING(name) PSTRING(type) PSTRING(creator)
PINT(version)
PFLAG(resource)  PFLAG(readonly)  PFLAG(dirtyAppInfo)  
PFLAG(backup) PFLAG(installOver) PFLAG(reset)
PDATE(create) PDATE(modify) PDATE(backup)
PLINTOPT(uidSeed, 0) PLINTOPT(nextRecordListID, 0) PLINTOPT(modnum, 0)
info->numrecs = intval("listlength(Database.records)");
db.filler = intopt("Database.filler", 0);
Defines backup, create, name, resource, version (links are to index).

Next, compute the sizes of the applicatino info and sort areas, and advance the offset to the start of those areas.

<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
{ int offset = 78;              /* size of database header */
  PDBConverter convert = get_converter(&db);
  int appsize = convert->appInfo.pack(NULL, 1);
  int sortsize = convert->sortInfo.pack(NULL, 0);
  int appoff, sortoff;
  offset += info->numrecs * 8;  /* sizeof record list */
  offset += 2;                  /* size of filler */

Now we can computer the offsets.

<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
  if (appsize) {
    appoff = offset;
    offset += appsize;
  } else
    appoff = 0;
  if (sortsize) {
    sortoff = offset;
    offset += sortsize;
  } else
    sortoff = 0;

Given those offsets, write the database header part.

<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
  if (write_dbinfo(output, info, appoff, sortoff) != 78)
    assert(((void)"header length botch", 0));

Next, write the record list. offset now points to the beginning of the first record.

<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
  db.records = malloc(info->numrecs * sizeof(*db.records));
  assert(db.records);
  if (info->numrecs < 1) {
    fprintf(stderr, "Cannot write database with %d records\n", info->numrecs);
    exit(1);
  }
  for(i=0; i < info->numrecs; i++) {
    lua_beginblock(); 
    lua_pushnumber((double)(i+1)); lua_setglobal("i");
    db.records[i].category = intopt("Database.records[i].category", 0);
    db.records[i].uid = intopt("Database.records[i].uid", 0);
#define PRFLAG(F) db.records[i].F = bitval("Database.records[i]." #F);
    PRFLAG(secret) PRFLAG(busy) PRFLAG(dirty) PRFLAG(delete)
    write_record_header(output, &db.records[i], offset);
    db.records[i].data = text(NULL, offset); /* flagrant misuse */
    offset += convert->record.pack(NULL, i);
    lua_endblock();
  }      
Defines PRFLAG (links are to index).

Now we have written the header and record list, and it's time to write the filler.

<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
  put_short(output, intopt("Database.filler", 0));

Next, write the app info and sort area.

<pack the Lua database into data structures, then write it>+= (<-U) [<-D->]
  if (appoff) {
    assert(ftell(output) - startpos == appoff);
    convert->appInfo.pack(output, 1);
  }
  if (sortoff) {
    assert(ftell(output) - startpos == sortoff);
    convert->sortInfo.pack(output, 1);
  }

At this point, we should be ready to write the records, and then we are done.

<pack the Lua database into data structures, then write it>+= (<-U) [<-D]
  for (i = 0; i < info->numrecs; i++) {
    assert(ftell(output) - startpos == db.records[i].data.len);
    lua_beginblock();
    convert->record.pack(output, i);
    lua_endblock();
  }
}

Main program, command line, etc.

<functions>+= (<-U) [<-D->]
void usage(char *prog) {
  fprintf(stderr, "Usage: %s [-generic] pack   [input [output.pdb]]  --  convert ASCII to .pdb\n", prog);
  fprintf(stderr, "       %s [-generic] unpack [input.pdb [output]]  --  convert .pdb to ASCII\n", prog);
  fprintf(stderr, "  Omitted files default to stdin/stdout\n");
  exit(1);
}
Defines usage (links are to index).

This is an error-checking fopen.

<functions>+= (<-U) [<-D->]
static FILE *openfile(char *name, char *mode) {
  FILE *fp = fopen(name, mode);
  if (fp) return fp;
  fprintf(stderr, "Unable to open file %s in mode \"%s\"\n", name, mode);
  exit(1);
  return NULL; /* prevent warning */
}
Defines openfile (links are to index).

<functions>+= (<-U) [<-D->]
extern struct PDBConv handyShopper, JFile, mobileDB, ToDo, expense;
static int generic = 0;
static PDBConverter converters[] = {
  &handyShopper,
  &JFile,
  &mobileDB,
  &ToDo,
  &expense,
  0
};
static PDBConverter get_converter(struct pdb *db) {
  PDBConverter *convert;
  for (convert = converters; !generic && *convert; convert++)
    if (!strcmp((*convert)->type,    db->info.type)
    &&  !strcmp((*convert)->creator, db->info.creator)
    ) 
      return *convert;
  return generic_convert;
}
Defines converters, generic, get_converter (links are to index).

<functions>+= (<-U) [<-D]
int main(int argc, char *argv[]) {
  char *in, *out;
  extern void strlib_open(void);
  extern void iolib_open(void);
  generic = argc > 1 && !strcmp(argv[1], "-generic");
  atexit(showall);
  strlib_open();
  iolib_open();
  <bind listlength and istable into the Lua space>
  if (generic) { argv[1] = argv[0]; argv++; argc--; }
  if (argc < 2 || argc > 4) usage(argv[0]);
  in  = argc < 3 ? NULL : strcmp(argv[2], "-") ? argv[2] : NULL;
  out = argc < 4 ? NULL : strcmp(argv[3], "-") ? argv[3] : NULL;
  if (!strcmp(argv[1], "pack")) 
    pack(in, out ? openfile(out, "wb") : stdout);
  else if (!strcmp(argv[1], "unpack")) 
    unpack(in ? openfile(in, "rb") : stdin, out ? openfile(out, "w") : stdout);
  else
    usage(argv[0]);
  return 0;
}
Defines main (links are to index).