PDB conversion for MobileDB

MobileDB uses the same structure for every record in the database. The type of data in each record is defined by the PalmOS category for that record. The following is a list of the categories and the type of data in that record.

  1. Field names record
  2. Data Records
  3. Filtered out records from filter function
  4. Database preferences record (values are 00 or 01)
  5. Field types record - MobileDB v1.0 currently utilizes 'str' only
  6. Column widths record as a string (default is '80')
Every record contains this basic structure but the data depends on the PalmOS category of the record (above).
HeaderDataEnd
ff ff ff 01 ff 0000 00 data for field 1 00 01 data for field 2 ...00 nn data for field n00 ff
Notes:

<*>=
#include <stdio.h>
#include <assert.h>
#include <ctype.h>

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

<functions>

<functions>= (<-U) [D->]
static void fail(char *msg, int n) {
  fprintf(stdout, "Malformed MobileDB database: %s %d\n", msg, n);
  exit(1);
}
Defines fail (links are to index).

<functions>+= (<-U) [<-D->]
struct rheader {
  unsigned char reserved[3];
  unsigned char version;
  unsigned char reserved2;
  unsigned char encryption;
};
static char *categories[] = { 0, "Field names", "Data record", "Filtered out record",
                                "Preferences", "Field Types", "Column Widths", 0 };

static void dumpr(FILE *out, struct pdb *db, int recnum) {
  Record *r = db->records + recnum;
  struct rheader *h = (struct rheader *)r->data.chars;
  Text t = r->data;
  int i;
  char *p;
  assert(h);
  <check for valid header h>
  fprintf(out, "\n        {");
  fprintf(out, " version = %d, encryption = %d,\n", h->version, h->encryption);
  <check category>
  fprintf(out, "          type = %s,\n", QUOTE(categories[r->category]));
  fprintf(out, "          fields = { ");
  
  p = t.chars + sizeof (*h);
  for (i = 0; p < t.chars + t.len; i++) {
    if (*p++) fail("bad field zero", i);
    if (*(unsigned char *)p == 0xff) break;
    if (*(unsigned char *)p++ != i) fail("bad field number", i);
    if (i > 0) fprintf(out, ", ");
    switch (r->category) {
      case 4:
        fprintf(out, "%d", *p++);
        break;
      case 6:
        { char *s;
          for (s = p; *s && (isspace(*s) || isdigit(*s)); s++);
          if (*s) fail("bad column width in field", i);
          fprintf(out, "%s", p);
          p = s;
        }
        break;
      default:
        fprintf(out, "%s", QUOTE(p));
        p += strlen(p);
        break;
    }
  }
  if (p + 1 != t.chars + t.len)
    fprintf(stderr, "Warning: %d garbage characters at end of record %d (uid %d)\n",
            t.len - (p + 1 - t.chars), recnum, r->uid);
  fprintf(out, " }\n");
  fprintf(out, "        }");
}
Defines categories, dumpr (links are to index).

<check for valid header h>= (<-U)
if (h->reserved[0] != 0xff || h->reserved[1] != 0xff || h->reserved[2] != 0xff 
||  h->reserved2 != 0xff)
  fail("bad reserved bits in header", 99);
<check category>= (<-U)
if (1 <= r->category && r->category <= 6) { }
else fail("bad category", r->category);

<functions>+= (<-U) [<-D->]
static int packr(FILE *pdb, int i) {
  struct rheader hdr;
  int cat;
  int j, n;
  lua_pushnumber((double)(i+1)); lua_setglobal("i");
  if (lua_dostring(
        "$debug\n"
        "thisrecord = Database.records[i].data"))
    assert(0);
  dbcheck("istable(thisrecord)", "record data is not a table");
  hdr.version = intopt("thisrecord.version", 1);
  hdr.encryption = intopt("thisrecord.encryption", 0);
  hdr.reserved[0] = hdr.reserved[1] = hdr.reserved[2] = hdr.reserved2 = 0xff;
  if (pdb) put_chars(pdb, &hdr, sizeof(hdr));
  n = sizeof(hdr);

  cat = intval("Database.records[i].category");
  for (j = 0; ; j++) {
    lua_pushnumber((double)(j+1)); lua_setglobal("j");
    if (lua_isnil(objectval("thisrecord.fields[j]"))) break;
    if (pdb) {putc(0, pdb); putc(j, pdb);}
    n += 2;   
    switch (cat) {
      case 4:
        if (pdb) putc(intopt("thisrecord.fields[j]", 0) & 0xff, pdb);
        n++;
        break;
      default:
        { char *s = stringval("thisrecord.fields[j]");
          if (pdb) put_chars(pdb, s, strlen(s)); /* no trailing null */
          n += strlen(s);
        }
        break;
    }
  }
  if (pdb) { putc(0, pdb); fputc(0xff, pdb); }
  n += 2;
  return n;
}
Defines packr (links are to index).

<functions>+= (<-U) [<-D->]
typedef struct {
  struct PDBCategoryAppInfo categories;

  unsigned char reserved1;
  unsigned short reserved2;
} MobileAppInfoType;

static void dumpi(FILE *out, struct pdb *db, Text txt) {
  Text *t = &txt;
  MobileAppInfoType info;
  info.categories = catread(t);

  fprintf(out, "\n    {");
  fprintf(out, "\n      categories = ");
  unpack_categories(out, &info.categories, 1);
  info.reserved1 = get_byte(t);
  info.reserved2 = get_short(t);
  fprintf(out, ",\n      reserved = { %d, %d }", info.reserved1, info.reserved2);
  fprintf(out, "\n    }");
  if (chars_remaining(t) > 0) 
    fprintf(stderr, "Warning: %d unused bytes in application info for MobileDB",
            chars_remaining(t));
}
static int packi(FILE *out, int appinfo) {
  int n;
  MobileAppInfoType info;
  info.categories = pack_categories();
  n = catwrite(out, &info.categories);
  if (out) {
    info.reserved1 = intopt("istable(Database.appInfo.reserved) and "
                            "Database.appInfo.reserved[1]", 0);
    info.reserved2 = intopt("istable(Database.appInfo.reserved) and "
                            "Database.appInfo.reserved[2]", 0);
    put_byte(out, info.reserved1);
    put_short(out, info.reserved2);
  }
  return n+3;
}
Defines dumpi, MobileAppInfoType, packi (links are to index).

<functions>+= (<-U) [<-D]
struct PDBConv mobileDB = {
  "MobileDB database",
  "Mdb1", "Mdb1",
  { 0, dumpi, packi },
  { 0, generic_unpackInfo, generic_packInfo },
  { 0, dumpr, packr }, 
  0
};

Defines mobileDB (links are to index).