Nagyon szeret(t)em a Palm-omat. Nekem egy m515-ös jutott. Anno direkt olyan PDA-t kerestem, amit (közel) kézírással lehet vezérelni (részletesebb értékelés). Használtan vettem. Sajnos "El hulla! Virág! El iram! Lik az élet..." és 10+ év alatt az aksija erősen elpunnyadt és az okostelefonok világában már felesleges még egy kütyü. Bár még bírja pár 1-2 órát világítás nélkül, de nem az igazi. Infra porton keresztül szinkronizálom és ehhez egy olyan laptop-ot kell üzemben tartanom, ami szintén a szétesés határán van. Egy remek Win98 fut rajta (mondjuk ezzel a ATMEL mikrokontrollereket is jó programozni, mert még van párhuzamos és soros port is rajt).
A feladat (és a poszt tulajdonképpeni témája) az lenne, hogyan tudjuk lementeni a kütyüre felhalmozott adatokat és hogyan tudjuk valami olvasható formátumra konvertálni.
Első körben kell csinálni egy szinkronizációt. Ezzel az adatok átkerülnek a laptopra. Keressük meg, hogy hol is vannak? Hopp, ide pakolja: C:\Palm\<user>\Backup. Az Archive alá pedig a már letörölt app-ok és adatbázisai kerülnek. Az alap appok, úgymint address, datebook, memopad, todo pedig ezekbe a könyvtárakba kerülnek. Két speciális app-pot használok. Az egyik a Cryptopad. Ez lényegében megegyezik a memopad-dal, csak a tartalmakat jelszóval lehet titkosítani. Itt tárolom az összes nem publikus infót. A másik a Pilot-DB. Ez egy egyszerű adatbázis felület. Hasznos kis táblázatokat dobhatunk össze, illetve írtam egy perl szkriptet, ami több XLS táblázatot alakít át Pilot-DB formátumba és így pl. a könyveimet, CD-imet, filmjeimet tudom beinjektálni a szisztémába.
Első nekifutásra a beépített adatbázisok konverziójával próbálkoztam. Némi borzolás után találtam majdnem teljesen jó leírást az adatbázisokról (általános infó, address, datebook, memopad, todo). Ahhoz, hogy a Cryptopad-ot is teljesen le tudjam menteni, leszedtem a védett doksikról a jelszóvédelmet. Erre egyből megjelentek, mint sima memopad bejegyzésekként.
Sajnos nem teljesen komplett, illetve egyértelmű a leírás. Leltem pár példa programot, ami segített az első lépésekben, de ezek mindegyike speciálisan egy típusú adatbázisra volt kihegyezve. Pedig sok a közös az egyes adatbázisok felépítésében. Megírtam hát saját dekódoló programomat perl-ben. És ezt most be is pakolom...
Jöjjön hát a szokásos perl header:
#!/usr/bin/perl
#...
use strict;
use warnings;
use POSIX;
Az adatbázis lényegében egymás után dobált adatmezőkből áll. Van pár függvény, ami az adatokat beolvassa.
sub CString(); # Read a CString
sub Short(); # Read a short (2 bytes)
sub Long(); # Read a long (4 bytes)
sub Char($); # Read n bytes
sub Dt($); # Convert unix time to date
A Short és Long egyszerűen beolvas 2, vagy 4 byte-ot. A CString egy Pascal szerű string, aminek az első egy, vagy első 3 byte-ja írja le a hosszát. A Char pár byte-ot olvas be. A Dt pedig csak beformáz egy unix időt.
Most jön az egyes adatbázisok leírása
my @field_types = ("None", "Integer", "Float", "Date", "Alpha",
"CString", "Boolean", "Bitflag", "RepeatEvent");
# '_' common fields, '#' date (Date or Integer)
my %field_names = (
ADDRESS => [ "_Record ID", "_Status", "_Position",
"Name", "First", "Title", "Company",
"Phone 1 Label ID", "Phone 1",
"Phone 2 Label ID", "Phone 2",
"Phone 3 Label ID", "Phone 3",
"Phone 4 Label ID", "Phone 4",
"Phone 5 Label ID", "Phone 5",
"Address", "City", "State", "Zip", "Country",
"Note", "_Private", "_Category",
"Custom 1", "Custom 2", "Custom 3", "Custom 4",
"Display Phone" ],
DATEBOOK => [ "_Record ID", "_Status", "_Position",
"#Start Time", "#End Time", "Description", "Duration",
"Note", "Untimed", "_Private", "_Category", "Alarm Set",
"Alarm Adv Units", "Alarm Adv Type", "Repeat Event" ],
MEMOPAD => [ "_Record ID", "_Status", "_Position",
"Memo Text", "_Private", "_Category" ],
TODO => [ "_Record ID", "_Status", "_Position",
"Description", "#Due Date", "Completed", "Priority",
"_Private", "_Category", "Note" ]
);
A '_' jellel a közös adatmezőket jelölöm (csak, hogy lássam). A '#'-kal pedig a dátumokat jelölő mezőket. Sajnos nem minden dátum mező dátum formátumú (nem értem, hogy miért).
Most pedig definiáljunk pár parancssori argumentumot. A '-a' address, '-d' datebook, '-m' memopad, '-t' todo adatbázis. Alapban ilyenkor a file neve és alapértelmezett elérési útja is adott. Persze megadhatunk közvetlenül egy adatbázis file-t. Ilyenkor azt nyitja meg.if ($#ARGV == -1) { die "No argument"; }
my $input_file;
if ($ARGV[0] =~ /^-(.)/) {
my $name;
if ($1 eq "a") { $name = "address";
} elsif ($1 eq "d") { $name = "datebook";
} elsif ($1 eq "m") { $name = "memopad";
} elsif ($1 eq "t") { $name = "todo";
} else { die "Bad argument ($1)";
}
$input_file = "$name/$name.dat";
} else { $input_file = $ARGV[0];
}
Végre megnyitható a file. Az első 4 byte tartalmazza a file típusát. open(CF, "<$input_file") or die "Can't open $input_file: $!\n";
binmode (CF);
my $byte = 0; # Keep track of data offset. Just in case.
my $TYPE;
my $tag = Char(4);
if ($tag eq "\x00\x01BA") { $TYPE = "ADDRESS";
} elsif ($tag eq "\x00\x01BD") { $TYPE = "DATEBOOK";
} elsif ($tag eq "\x00\x01PM") { $TYPE = "MEMOPAD";
} elsif ($tag eq "\x00\x01DT") { $TYPE = "TODO";
} else { die "Bad file tag info ($tag)";
}
if (!exists $field_names{$TYPE}) { die "Field names not filled in"; }
Akkor először dekódolni kell a fejlécet.
#
# Header
#
my $x;
print "=" x 30, "\n";
$x = CString; printf "* Filename: %s\n", $x;
$x = CString; printf "* Custom names: %s\n", $x;
$x = Long; printf "* Next Free Category ID: %d\n", $x;
my $count = Long; printf "* Number of category entries: %d\n", $count;
print "* Category (count: $count)\n";
my %category_names;
for (my $i = 0; $i < $count; ++$i) {
my ($seq, $id, $flag, $long, $short) =
(Long, Long, Long, CString, CString);
printf " #%2d, ID: %2d, Flag: %d, lName: %s, sName: %s\n",
$seq, $id, $flag, $long, $short;
if (exists $category_names{$id}) { die "Duplicated category name"; }
$category_names{$seq} = $long;
}
$x = Long; printf "* Schema Resource ID: %d\n", $x;
$x = Long; printf "* Schema Fields per row: %d\n", $x;
$x = Long; printf "* Schema Record ID Position: %d\n", $x;
$x = Long; printf "* Schema Record Status Position: %d\n", $x;
$x = Long; printf "* Schema Placement Position: %d\n", $x;
my $field_count = Short;
printf "* Schema Field Count: %d\n", $field_count;
printf "* Schema field entries\n";
my @schema_entries;
for (my $i = 0; $i < $field_count; ++$i) { push @schema_entries, Short; }
print " ", join(",", @schema_entries), "\n";
my $all_field_count = Long; printf "* Entries count: %d\n", $all_field_count;
if ($all_field_count % $field_count != 0) { die "Bad field count"; }
my $rec_count = $all_field_count / $field_count;
printf "[ Record count: %d ]\n", $rec_count;
És a fejléc után az adat rekordokat is.
#
# Processing Records
#
print "=" x 30, "\n";
for (my $j = 0; $j < $rec_count; $j++) {
printf "\n* Record: %d / $rec_count\n", $j + 1;
for (my $i = 0; $i < $field_count; ++$i) {
my $type = $schema_entries[$i];
if ($type != Long) { die "Bad type ($i, $type)"; }
my $v;
# Int, Date, Boolean
if ($type == 1 || $type == 3 || $type == 6) { $v = Long;
} elsif ($type == 5) { # CString
if (Long != 0) { die "Not zero string padding"; }
$v = CString;
} elsif ($type == 8) { # RepeatEvent
$v = "See record above";
printf " Repeat Event (%d:%s)\n", $type,
$field_types[$type];
my $count = Short; printf " Date exception: %d\n",
$count;
my $x;
for (my $k = 0; $k < $count; ++$k) {
$x = Long;
printf " Exception entry: %s\n", Dt $x;
}
$x = Short;
if ($x != 0) {
if ($x == 0xFFFF) {
printf " CLASS-ENTRY\n", $x;
$x = Short; printf " 1: %d\n", $x;
my $len = Short;
printf " Length: %d\n", $len;
$x = Char($len);
printf " Class name: %s\n", $x;
} else {
printf " Repeat Event Flag: %d\n",
$x & 0x7FFF;
}
my $brand = Long;
printf " Brand: %d (%s)\n", $brand,
("Daily", "Weekly", "Monthly-by-Day",
"Monthly-by-Date",
"Yearly-by-Date", "Yearly-by-Day")[$brand];
$x = Long; printf " Interval: %d\n", $x;
$x = Long; printf " End Date: %s\n", Dt $x;
$x = Long; printf " First Day of Week: %d\n", $x;
if ($brand < 0 || $brand > 6) {
die "Bad brand ($brand)";
}
printf " Brand data\n";
if ($brand >= 1 && $brand <= 3) {
$x = Long; printf " Day Index: %d\n", $x;
}
if ($brand == 2) {
$x = Char(1);
printf " Days Mask: %d\n", unpack "c", $x;
}
if ($brand == 3) {
$x = Long; printf " Week Index: %d\n", $x;
}
if ($brand == 4 || $brand == 5) {
$x = Long; printf " Day Number: %d\n", $x;
}
if ($brand == 5) {
$x = Long; printf " Month Index: %d\n", $x;
}
}
} else { die "Not handled value";
}
my $fld_name = $field_names{$TYPE}[$i];
if ($fld_name eq "_Category") {
if (!exists $category_names{$v}) { $v .= " (N/A)";
} else { $v .= " (".$category_names{$v}.")";
}
} elsif ($fld_name eq "_Status") {
my @status;
if ($v & 0x01) { push @status, "Add"; }
if ($v & 0x02) { push @status, "Update"; }
if ($v & 0x04) { push @status, "Delete"; }
if ($v & 0x08) { push @status, "Pending"; }
if ($v & 0x80) { push @status, "Pending"; }
$v .= " (".join(",", @status).")";
} elsif ($fld_name eq "Alarm Adv Type") {
$v = $v." (".("Minutes", "Hours", "Days")[$v].")";
}
if (substr($fld_name, 0, 1) eq '#') { $v = Dt $v; }
printf " %s (%d:%s): %s\n", $fld_name, $type,
$field_types[$type], $v;
}
}
exit;
És végül a beolvasó segéd függvények.
sub CString() {
my $pad = shift;
read (CF, $_, 1) or die "Unable to get CString length: $!";
++$byte;
my $x = unpack "C", $_;
if ($x == 0) { return ""; }
if ($x == 0xFF) {
read (CF, $_, 2) or
die "Unable to get CString long length: $!";
$byte += 2;
$x = unpack "s", $_;
};
read (CF, $_, $x) or
die "Unable to get CString: $!" unless ($x == 0);
$byte += $x;
return $_;
} # End of CString
sub Short() {
read (CF, $_, 2) or die "Unable to get Short: $!\n";
$byte += 2;
#return unpack "s", $_; # signed
return unpack "S", $_; # unsigned
} # End of Short
sub Long() {
read (CF, $_, 4) or die "Unable to get Long: $!\n";
$byte += 4;
#return unpack "l", $_; # signed
return unpack "L", $_; # unsigned
} # End of Long
sub Char($) {
my $n = shift;
read (CF, $_, $n) or die "unable to get Char: $!";
$byte += $n;
return $_;
}
sub Dt($) { my $t = shift; return $t.strftime(" (%Y-%m-%d %T)", localtime $t);
A második eset a Pilot-DB adatbázisok konverziója. Ennek mikéntjét egy másik posztban fogom a világ szeme elé tárni.
Tárogassunk minden nap!
+jegyzések