p5-Palm-1.012/0000755000076500000240000000000011341055221011700 5ustar brianstaffp5-Palm-1.012/Changes0000644000076500000240000005036111341055215013203 0ustar brianstaff1.011 - 2009-08-17 Tim Adye * Palm/Datebook.pm: add support for the newer-format Palm Calendar databases. 1.12 - Tue Feb 23 16:44:10 2010 Promote to a full release from the latest development cycle. 1.11_01 - Sat Jan 2 12:16:38 2010 Tim Adye : Palm/Datebook.pm: add support for time zone entries. 1.009 - 2007-07-30 brian d foy * cleaned up the pod coverage test. * add License and Author stuff to the distro 1.006 - 2007-01-15 brian d foy * chromatic sent me some changes living in another distro (p5-Palm-1.004_000). It catches up 1.003_000 for all of the changes since November 2002 listed in this file. 1.005 - 2007-01-13 brian d foy * Cleansed the dist to make it installable by CPAN tools. It just needs a version without a _ in it. (http://rt.cpan.org/Ticket/Display.html?id=12837) 2004-04-15 21:21 christophe * Palm/PDB.pm, util/pdbdump: Added some logic to detect broken databases during loading: - database names _must_ contain the terminating NUL within the first 32 characters of the header. This catches obviously broken situations like text files renamed to .pdb files (which, oddly enough, happens a lot when you're trying to process text files into Doc files). - position offsets mustn't go beyond the end of the database file ($pdb->{_size}). This indicates either truncation of the db or straight corruption. 2004-04-08 22:55 christophe * Palm/PDB.pm: make it impossible to create a nameless database. It can't be uploaded to the PDA, that's for sure. 2003-11-09 20:40 azummo * Palm/PDB.pm: Handle uniqueID and uniqueIDseed in the Palm OS way. 2003-11-05 15:19 azummo * Palm/DateTime.pm: Fixed ISO8601 format in palmtime_to_iso8601 Minor stylistical changes 2003-10-10 19:19 azummo * Palm/Datebook.pm: Fixed alarm documentation (Christophe Beauregard) 2003-10-10 07:01 azummo * Palm/PDB.pm: Implemented remove_Record() method. Fixed dirty flags handling. (Christophe Beauregard) 2003-10-10 06:51 azummo * Palm/PDB.pm: Added is_Dirty method (Christophe Beauregard). 2003-09-16 18:59 azummo * MANIFEST, README: Updated in preparation for the new release. 2003-09-16 18:56 azummo * Makefile.PL: Bumped version number. 2003-09-16 18:53 azummo * Palm/ZirePhoto.pm: Removed unused methods. The superclass will take care. 2003-08-03 13:06 arensb * README, Palm/PDB.pm: Added missing section header in POD. Patch contributed by Kurt Starsinic 2003-06-26 15:21 azummo * Palm/Mail.pm: Some headers were left undefined (Christophe Beauregard) 2003-06-24 10:28 azummo * Palm/ZirePhoto.pm: Added. 2002-12-17 19:09 azummo * MANIFEST: Added DateTime.pm to manifest. 2002-11-07 10:50 arensb * Makefile.PL: Updated version number. 2002-11-07 09:27 arensb * Palm/StdAppInfo.pm: (API) Now supports databases where the last byte of the AppInfo block is data instead of padding. Backwards-compatible. 2002-11-07 09:11 arensb * Palm/: Address.pm, Datebook.pm, Mail.pm, Memo.pm, ToDo.pm: Rearranged @ISA, so that it'll get the right &ParseAppInfoBlock. 2002-11-03 11:43 azummo * Palm/: Address.pm, DateTime.pm, Datebook.pm, Mail.pm, Memo.pm, PDB.pm, PQA.pm, Raw.pm, StdAppInfo.pm, ToDo.pm: Changed $VERSION parser. 2002-09-03 13:48 azummo * Palm/Address.pm: Patch from Brian Johnson (it just adds an else to the if statement to nullify non-existing entities). 2002-06-22 09:16 azummo * Palm/PDB.pm: now now takes an optional hash with default values. 2002-06-16 09:37 azummo * Palm/PDB.pm: Added some compatibility code. Reflected (part of) changes in the POD. 2002-06-16 09:26 azummo * Palm/PDB.pm: Make some more use of the new attribute names. 2002-06-16 08:34 azummo * Palm/PDB.pm: Added "official" names to record attributes. 2002-06-16 08:19 azummo * Palm/PDB.pm: Corrected an attribute flag (open) Added new attributes using "official" names 2002-05-09 11:08 arensb * Palm/Address.pm: Again, more sanity-checking and a little less code duplication. 2002-05-09 10:33 arensb * Palm/Address.pm: More sanity checks, and a somewhat more elegant way of doing things (iterate over field names, rather than duplicating code a bunch of times). 2002-05-09 10:24 arensb * Palm/StdAppInfo.pm: (bug fix) Used to do some sanity-checking, then ignored it. We now pay attention to our own sanity checks. 2002-04-09 20:59 arensb * README: Added Keni as a contributor. 2002-04-09 20:58 arensb * util/pdbdump: Applied Keni's patch: array-handling for parsed records (e.g., Datebook exceptions). 2002-04-05 14:59 azummo * Palm/DateTime.pm: Hopefully fixed mktime() invocation. (?) 2002-03-23 08:38 arensb * Palm/Mail.pm: Added extra sanity checks. 2002-03-12 15:23 azummo * Palm/DateTime.pm: Added Palm/DateTime.pm 2002-02-15 09:40 arensb * Palm/PDB.pm: (bug fix) Strip everything after the first NUL, not just trailing NULs. Patch submitted by Don Park 2002-02-08 09:20 arensb * Makefile.PL: Bumped up version number. 2002-02-08 08:56 arensb * MANIFEST: Added add-memo to distribution. 2002-02-08 08:55 arensb * util/add-memo: Added first draft of add-memo, to add a memo to an existing MemoDB.pdb. 2002-02-01 09:09 arensb * Palm/PDB.pm: Comment used to refer to a doc file in ColdSync that no longer exists. Fixed. 2002-01-26 17:25 arensb * Palm/Address.pm, Palm/Datebook.pm, Palm/Mail.pm, Palm/Memo.pm, Palm/PDB.pm, Palm/PQA.pm, Palm/Raw.pm, Palm/StdAppInfo.pm, Palm/ToDo.pm, util/pdbdump: CPAN-friendly $VERSION. 2002-01-26 17:22 arensb * TODO: Added some items. 2002-01-26 17:22 arensb * Makefile.PL: Rewrote version number in CPAN-friendly format. 2001-06-05 09:04 arensb * Palm/StdAppInfo.pm: Made addCategory, deleteCategory, renameCategory behave a bit more like the Palm, even when it's brain-damaged. 2001-06-05 08:52 arensb * TODO: Removed an item. 2001-06-02 15:17 arensb * Palm/: Datebook.pm, StdAppInfo.pm: Fixed typo in POD. 2001-04-04 23:54 arensb * Palm/StdAppInfo.pm: Fixed typo. 2001-03-30 01:23 arensb * Palm/Datebook.pm: Clarified whether date numbers start at 0 or 1. (bug fix): Fixed year conversion. (bug fix): Fixed use of undefined value. 2001-02-24 18:35 arensb * Palm/PDB.pm: In &new, initialize 'baktime' to -$EPOCH_1904, that being the Palm's epoch. 2001-02-24 17:59 arensb * FAQ: Added pointers to other sources of information about HotSync .dat files. 2001-02-24 11:49 arensb * Palm/Datebook.pm: Clarified documentation. 2001-02-20 06:17 arensb * MANIFEST: Added FAQ to distribution. 2001-02-20 06:17 arensb * Makefile.PL: Incremented version number. 2001-02-20 06:17 arensb * FAQ: (Added). 2001-02-20 06:12 arensb * README: Added contributor. 2001-02-20 06:12 arensb * util/pdbdump: (bug fix): the two NULs are optional. Fixed typo. 2001-02-20 06:11 arensb * Palm/StdAppInfo.pm: Added a "to do" comment. 2000-11-09 10:32 arensb * Palm/PDB.pm: Applied John-David Smith's patch: add the "archive" attribute. Better check for non-NULLness of AppInfo block. 2000-09-25 01:06 arensb * util/pdbdump: Added "launchable" attribute, for PQAs. Print database attributes more clearly. 2000-09-25 01:05 arensb * Palm/PQA.pm: First draft of PQA parser. 2000-09-25 01:05 arensb * Palm/PDB.pm: Added "launchable" attribute, for PQAs. 2000-09-24 12:37 arensb * util/pdbdump: Now handles resource databases as well as record databases. 2000-09-24 12:25 arensb * Palm/: Address.pm, Datebook.pm, Mail.pm, Memo.pm, PDB.pm, Raw.pm, StdAppInfo.pm, ToDo.pm: $VERSION is now a floating-point number, for comparison. 2000-09-24 12:24 arensb * TODO: Added an item. 2000-09-20 10:16 arensb * Palm/StdAppInfo.pm: Removed a "to do" comment. 2000-09-16 19:58 arensb * Makefile.PL: Bumped up patchlevel number. 2000-09-16 19:58 arensb * Palm/PDB.pm: (bug fix): The two useless NULs are allowed, but not mandated, by the spec. By assuming that they will always be there, this module failed to parse some well-formed PDBs. 2000-09-16 19:57 arensb * util/pdbdump: Oops! Forgot to fix resources. Minor cleaning. 2000-09-16 19:53 arensb * util/pdbdump: (bug fix): The two useless NULs are allowed, but not mandated, by the spec. By assuming that they will always be there, 'pdbdump' failed to parse some well-formed PDBs. 2000-09-09 00:52 arensb * Makefile.PL: Bumped up version number. 2000-09-09 00:50 arensb * Palm/StdAppInfo.pm: Added $error variable. (bug fix): A mis-written test would cause &pack_StdAppInfo to omit unnamed categories. Hence, the category list had fewer than 16 categories, when written. Fixed typo. (feature): Added &addCategory, &deleteCategory, &renameCategory. Removed BUGS section. 2000-09-08 22:48 arensb * Palm/StdAppInfo.pm: Stop Perl from complaining if there are unnamed categories. 2000-09-08 22:47 arensb * README: Added author. 2000-08-28 23:20 arensb * Makefile.PL: Bumped up the version number. 2000-08-28 23:19 arensb * Palm/StdAppInfo.pm: Changed API: the categories are new arranged as a 16-element array of category structs, the way they logically belong, rather than as disparate arrays, the way they're represented in the file. Added a lot to the POD. 2000-08-28 23:16 arensb * TODO: Removed an item. 2000-08-28 23:16 arensb * TODO: Added an item. 2000-08-24 00:57 arensb * Palm/PDB.pm: Clarified pod somewhat. 2000-08-24 00:29 arensb * util/pdbdump: (feature): Expands arrays of hash references recursively. 2000-08-13 18:08 arensb * Palm/Datebook.pm: Incorporated changes suggested by John Jannotti , to avoid warnings under Perl 5.6, when various fields are undefined. 2000-08-13 17:59 arensb * Palm/: Address.pm, Datebook.pm, Mail.pm, Memo.pm, ToDo.pm: Updated for new &parse_StdAppInfo behavior. 2000-08-13 17:59 arensb * Palm/StdAppInfo.pm: (bug fix): &parse_StdAppInfo now puts the non-category part of the AppInfo block in $appinfo->{other}. This is so that a script that reads a random .pdb file as a StdAppInfo (and nothing else) can copy it without loss of data. &pack_StdAppInfo uses $appinfo->{other}. Clarified documentation somewhat. 2000-08-04 00:12 arensb * Palm/PDB.pm: Fixed expression that made 'perl5.6 -w' print a warning. 2000-08-01 23:20 arensb * Makefile.PL: Bumped up version number. 2000-08-01 23:19 arensb * util/pdbdump: (bug fix): Read files in binary mode under Windows, MS-DOS, and other OSes that make that distinction. 2000-08-01 23:19 arensb * Palm/PDB.pm: (bug fix): Read and write files in binary mode under Windows, MS-DOS, and other OSes that make that distinction. 2000-08-01 23:17 arensb * Palm/Datebook.pm: Delete raw data field at the end of &ParseRecord. 2000-08-01 23:16 arensb * TODO: Added, removed some items. 2000-07-20 00:17 arensb * Makefile.PL: Bumped up version number. 2000-07-20 00:17 arensb * Palm/Datebook.pm: (Bug fix): Test for undefined record fields, to avoid warnings with databases built from scratch. Patch supplied by . 2000-07-19 00:00 arensb * Makefile.PL: Bumped up version number. 2000-07-19 00:00 arensb * TODO: Removed an item. 2000-07-18 23:58 arensb * Palm/: Address.pm, Datebook.pm, Mail.pm, Memo.pm, PDB.pm, ToDo.pm: Clarified the documentation for &new_Record. 2000-07-18 23:57 arensb * TODO: Added an item. 2000-07-08 20:25 arensb * Palm/PDB.pm: PDB::new now initializes more fields. PDB::Write prints a warning if writing a zero-length record. PDB::new_Resource: type now defaults to all NULs (instead of four spaces). 2000-07-08 20:11 arensb * Palm/Datebook.pm: Changed a comment. 2000-06-30 11:41 arensb * Makefile.PL: Bumped up version number. 2000-06-30 11:41 arensb * TODO: Added an item. 2000-06-30 11:41 arensb * Palm/Datebook.pm: Made AppInfo block initialization the same as other modules, even though Datebook doesn't really use categories and such. 2000-06-30 11:40 arensb * Palm/StdAppInfo.pm: (bug fix): Use of anonymous array instead of a real array caused first category (and first category ID) to be a reference rather than a "real" scalar. 2000-05-23 02:56 arensb * Makefile.PL: Incremented patchlevel number. 2000-05-23 02:55 arensb * Palm/: Address.pm, Memo.pm: (bug fix): Don't run StdAppInfo::import(). This prevents StdAppInfo from registering itself as the default handler in generic tools. 2000-05-13 16:30 arensb * Makefile.PL: Bumped up version number. 2000-05-13 16:29 arensb * README: Added contributor. 2000-05-13 16:29 arensb * Palm/Mail.pm: Fixed inconsistency in record member names ("replyTo" was sometimes "reply_to", and "sentTo" was sometimes "sent_to"). Patch supplied by Sumant S.R. Oemrawsingh 2000-05-13 01:16 arensb * Makefile.PL: Incremented patch level. Added "pdbdump" to the distribution. 2000-05-13 01:15 arensb * MANIFEST: Ship "pdbdump" instead of "dumpdb". 2000-05-13 01:15 arensb * util/dumpdb: Some obsolete refinements. 2000-05-13 01:14 arensb * Palm/StdAppInfo.pm: Added an import() function, so you can say that a function is a Palm::StdAppInfo. 2000-05-13 01:12 arensb * Palm/Mail.pm: (bug fix): Palm::Mail::new_Record() didn't return a value. Don't import StdAppInfo, since don't want new PDBs to default to StdAppInfo. 2000-05-08 05:48 arensb * util/pdbdump: Rewritten from scratch. This is basically just Palm/PDB.pm with trace statements. 2000-05-08 05:48 arensb * util/pdbdump: intermediate 2000-05-07 02:33 arensb * Palm/: StdAppInfo.pm, ToDo.pm: Removed unnecessary quoting in class members. 2000-05-07 02:33 arensb * Palm/Raw.pm: Removed unnecessary quoting in class members. Added `use strict'. Rearranged some variables at the top. 2000-05-07 02:30 arensb * Palm/: Datebook.pm, Mail.pm, Memo.pm, PDB.pm: Removed unnecessary quoting in class members. 2000-05-06 17:48 arensb * Palm/Address.pm: Removed unnecessary quoting in class members. 2000-05-06 17:20 arensb * Palm/ToDo.pm: Converted to use StdAppInfo. Fixed man page section in "see also" section. 2000-05-06 17:19 arensb * Palm/StdAppInfo.pm: Added a "to do" comment. 2000-05-06 17:18 arensb * Palm/: PDB.pm, Raw.pm: Fixed man page sections for "see also" section. 2000-04-29 17:53 arensb * Makefile.PL: Bumped up patch level. 2000-04-29 17:49 arensb * README: Added Robert Norris's patch for empty PDBs. 2000-04-29 16:52 arensb * Palm/PDB.pm: Applied Robert Norris 's patch: _load_appinfo_block used to die if the database contained no records. 2000-04-24 06:02 arensb * util/pdbdump: (added) This will eventually become the successor to dumpdb. 2000-04-24 06:01 arensb * util/dumpdb: More AppInfo block processing. 2000-04-24 06:01 arensb * Palm/StdAppInfo.pm: (added) Class for dealing with the standard part of AppInfo blocks (category support). 2000-04-24 06:00 arensb * Palm/PDB.pm: Uses `use strict'. Changed the way $VERSION is generated from RCS tag. Fixed the POD a bit. 2000-04-24 05:59 arensb * Palm/: Mail.pm, Memo.pm: Use StdAppInfo. 2000-04-24 05:56 arensb * Palm/Address.pm: Fixed manual section numbers. 2000-04-24 05:56 arensb * Palm/: Address.pm, Datebook.pm: Use StdAppInfo. 2000-04-24 05:55 arensb * TODO: Removed some stuff. 2000-04-24 05:55 arensb * Makefile.PL: Incremented version number. 2000-04-24 05:54 arensb * MANIFEST: Added "TODO", "StdAppInfo.pm" 2000-04-20 01:42 arensb * Palm/Mail.pm: Forgot to mention the subject in the documentation. 2000-02-01 23:45 arensb * TODO: (added): Things that need to be done. 2000-02-01 23:45 arensb * README: (added): README file. 2000-02-01 23:40 arensb * Makefile.PL: Incremented version number. 2000-02-01 23:21 arensb * Palm/Raw.pm: Rearranged the POD. Inherit &new, &new_Record methods. 2000-02-01 23:19 arensb * Palm/: Datebook.pm, Mail.pm, Memo.pm, ToDo.pm: Rearranged the POD. Added &new, &new_Record methods. 2000-02-01 23:18 arensb * Palm/Address.pm: Added placeholder for $pdb->{appinfo}{lastUniqueID}. 2000-02-01 07:31 arensb * Palm/: Raw.pm, ToDo.pm: Added $VERSION variable, per CPAN. 2000-02-01 07:31 arensb * Palm/PDB.pm: Renamed &deleteRecord to &delete_Record, for consistency. &append_Record, &append_Resource, &delete_Record update PDB's mtime. 2000-02-01 07:20 arensb * Palm/: Mail.pm, Memo.pm: Added $VERSION variable, per CPAN. 2000-02-01 07:19 arensb * Palm/Address.pm: Added $VERSION to all .pm files, per CPAN. Moved pod documentation to be adjacent to the code, instead of at the end. Fixed &new so there are always 16 categories, one of which is named "Unfiled" with code 0. 2000-01-23 04:18 arensb * Palm/PDB.pm: The various _load_*() functions are no longer methods, since they shouldn't be overridden anyway. Added documentation for new_Record(), append_Record(), new_Resource(), append_resource(), findRecordByID(), deleteRecord(). 2000-01-23 01:33 arensb * util/dumpdb: Bug fix: deal with PDBs without records, and PRCs without resources. Memo: Dump() now prints the AppInfo and sort blocks, if they exist. Address: Dump() now prints the field labels in the AppInfo block. Address: print the record attributes. 2000-01-23 01:28 arensb * Palm/PDB.pm: Updated copyright. Fixed function cross-references in the documentation. new() method now initializes the common fields. Fiddled with quotes in the documentation some more. Write() method now supplies the two NULs if they're missing. Added methods: new_Record(), append_Record(), new_Resource(), append_Resource(), findRecordByID(), deleteRecord(). 2000-01-23 01:24 arensb * Palm/Address.pm: Added new() method, to specifically create a new Address PDB. Added new_Record() method, to create a new Address record. Bug fix: PackAppInfoBlock() only packed whichever categories were defined. It now makes sure that it packs 16 categories. 2000-01-23 01:20 arensb * Makefile.PL: Incremented version. 1999-12-05 23:11 arensb * Palm/Address.pm: Fixed documentation to reflect reality. 1999-11-18 13:20 arensb * Palm/PDB.pm: Added magic $VERSION, to make CPAN happy. 1999-11-18 03:29 arensb * util/dumpdb: Took out Bitmaps and DopeWars, since those aren't part of the distribution (yet?). 1999-11-18 03:28 arensb * MANIFEST: Added README. 1999-11-18 03:24 arensb * Palm/Raw.pm: Clarified the use of "use Palm::Raw;" vs. "use Palm::Raw();" in the documentation. 1999-11-18 01:20 arensb * Palm/: Address.pm, Datebook.pm, Mail.pm, Memo.pm, PDB.pm, Raw.pm, ToDo.pm: Changed Palm::PDB:: to Palm:: where required. 1999-11-18 01:19 arensb * Makefile.PL: Changed distribution name to p5-Palm, for clarity. Explicitly set installation method. 1999-11-18 00:51 arensb * Makefile.PL: Fixed NAME and VERSION. 1999-11-18 00:51 arensb * MANIFEST: Removed Palm/Makefile.PL 1999-11-18 00:48 arensb * Palm/Makefile.PL: Deleted. It's not necessary. 1999-11-18 00:26 arensb * MANIFEST: Fixed after directory shuffle. 1999-11-18 00:24 arensb * test.pl: Generated by MakeMaker. Not used. 1999-11-18 00:23 arensb * util/dumpdb: Added RCS comment. Updated "use" statements to reflect the fact that the Palm stuff is in Palm::, not Palm::PDB::. 1999-11-18 00:21 arensb * util/copydb: Added RCS header. Took out the "use lib" line, for now. Changed "use" statements: the Palm stuff is no longer Palm::PDB:, but rather Palm::. 1999-11-18 00:17 arensb * util/dumpdb: Test utility to dump the contents of a PDB. 1999-11-18 00:17 arensb * util/copydb: Test utility to copy a PDB. 1999-11-18 00:16 arensb * Palm/ToDo.pm: Module for dealing with ToDo databases. 1999-11-18 00:16 arensb * Palm/Raw.pm: Module for dealing with "Raw" databases. 1999-11-18 00:16 arensb * Palm/Memo.pm: Module for dealing with Memo databases. 1999-11-18 00:16 arensb * Palm/Mail.pm: Module for dealing with Mail databases. 1999-11-18 00:16 arensb * Palm/Datebook.pm: Module for dealing with DateBook databases. 1999-11-18 00:15 arensb * Palm/Address.pm: Module for dealing with AddressBook databases. 1999-11-18 00:15 arensb * Palm/PDB.pm: Base module for dealing with PDBs. 1999-11-18 00:14 arensb * Palm/Makefile.PL: MakeMaker-generated Makefile.PL. 1999-11-18 00:10 arensb * Makefile.PL: MakeMaker-generated Makefile.PL. 1999-11-18 00:10 arensb * MANIFEST: First draft of manifest. p5-Palm-1.012/examples/0000755000076500000240000000000011341055220013515 5ustar brianstaffp5-Palm-1.012/examples/add-memo0000644000076500000240000000436311341055215015135 0ustar brianstaff#!/usr/bin/perl # $Id: add-memo,v 3.1 2002/02/08 13:55:59 arensb Exp $ use strict; use Palm::Memo; use vars qw( $category $fname $pdb $cat_id $memo_text ); # Set default values $category = undef; # Default category to add to: "Unfiled" $cat_id = 0; # Default category index is 0 $fname = "$ENV{HOME}/.palm/backup/MemoDB.pdb"; # Default file to modify # Parse command-line arguments # XXX - Ought to read ~/.add-memo.args or some such: this should # contain the default command-line arguments. read it, split into # arguments, and prepend to @ARGV. Then continue as below. while ($ARGV[0] =~ /^-./) { my $arg = shift; if ($arg eq "-h" or $arg eq "-help" or $arg eq "--help") { &usage; exit 0; } if ($arg eq "-c") # Set category { $category = shift; if (!defined($category)) { print STDERR "Error: -c argument requires an argument.\n"; &usage; exit 1; } next; } if ($arg eq "-f") # Specify PDB file { $fname = shift; if (!defined($fname)) { print STDERR "Error: -f argument requires an argument.\n"; &usage; exit 1; } next; } } $pdb = new Palm::PDB; $pdb->Load($fname); # Given a category name, set $cat_id to its index. if ($category eq "") { # No category specified. Default to "Unfiled" $cat_id = 0; } else { # Find the named category my $i; for ($i = 0; $i <= 16; $i++) { if ($pdb->{appinfo}{categories}[$i]{name} eq $category) { $cat_id = $i; last; } } if ($i >= 16) { # No such category print STDERR "Warning: can't find category \"$category\".\n", "Defaulting to \"Unfiled\".\n"; } } $memo_text = ""; # This loop might iterate over several files while (<>) { # XXX - If input file is STDIN, perhaps ought to print a # message saying to hit at EOF. Then again, that's # not the Unix way. $memo_text .= $_; if (eof(ARGV)) { # End of current file. Append the current record to # the PDB, and reset $memo_text for the next file. my $record; $record = $pdb->append_Record; $record->{data} = $memo_text; $record->{category} = $cat_id; $memo_text = ""; } } $pdb->Write($fname); # usage # Print a usage message sub usage { print <Load($ARGV[0]); $pdb->Write("foo.pdb"); p5-Palm-1.012/examples/pdbdump0000644000076500000240000004621011341055215015102 0ustar brianstaff#!/usr/bin/perl # # Dump a Palm PDB or PRC database. # # $Id: pdbdump,v 3.5 2002/04/10 00:58:28 arensb Exp $ use strict; use Palm::PDB; use Palm::Raw; # Load handlers for the built-in apps by default use Palm::Memo; use Palm::Address; use Palm::Datebook; use Palm::Mail; use Palm::ToDo; use vars qw( $VERSION %PDBHandlers %PRCHandlers $hexdump ); $VERSION = sprintf "%d.%03d_%03d_%03d", '$Revision: 3.5 $ ' =~ m{(\d+)(?:\.(\d+))}; *PDBHandlers = *Palm::PDB::PDBHandlers; *PRCHandlers = *Palm::PDB::PRCHandlers; &Palm::PDB::RegisterPRCHandlers("Palm::Raw", [ "", "" ] ); $hexdump = 1; # By default, print hex dumps of everything # Parse command-line arguments my $arg; while (($arg = $ARGV[0]) =~ /^-/) { $arg = shift; if (($arg eq "-h") || ($arg eq "-help") || ($arg eq "--help")) { print < Load (e.g., -MPalm::Address) EOT #' exit 0; } elsif ($arg =~ /^-M/) { eval "use $';"; } elsif ($arg eq "-nohex") { $hexdump = 0; } else { die "Unrecognized option: $arg\n"; } } my $fname = shift; die "No such file: $fname\n" if ! -f $fname; my $EPOCH_1904 = 2082844800; # Difference between Palm's # epoch (Jan. 1, 1904) and # Unix's epoch (Jan. 1, 1970), # in seconds. my $HeaderLen = 32+2+2+(9*4); # Size of database header my $RecIndexHeaderLen = 6; # Size of record index header my $IndexRecLen = 8; # Length of record index entry my $IndexRsrcLen = 10; # Length of resource index entry #%PDBHandlers = (); # Record handler map #%PRCHandlers = (); # Resource handler map #&Palm::PDB::rawread($fname); &rawread($fname); #package Palm::PDB; # XXX - Gross hack! sub rawread { my $self = new Palm::Raw; my $fname = shift; # Filename to read from my $buf; # Buffer into which to read stuff # Open database file open PDB, "< $fname" or die "Can't open \"$fname\": $!\n"; binmode PDB; # Parse as binary file under MS-DOS # Get the size of the file. It'll be useful later seek PDB, 0, 2; # 2 == SEEK_END. Seek to the end. $self->{_size} = tell PDB; print "File size: $self->{_size}\n"; seek PDB, 0, 0; # 0 == SEEK_START. Rewind to the beginning. # Read header my $name; my $attributes; my $version; my $ctime; my $mtime; my $baktime; my $modnum; my $appinfo_offset; my $sort_offset; my $type; my $creator; my $uniqueIDseed; read PDB, $buf, $HeaderLen; # Read the PDB header print "Database header:\n"; if ($hexdump) { &hexdump(" ", $buf); print "\n"; } # Split header into its component fields ($name, $attributes, $version, $ctime, $mtime, $baktime, $modnum, $appinfo_offset, $sort_offset, $type, $creator, $uniqueIDseed) = unpack "a32 n n N N N N N N a4 a4 N", $buf; ($self->{name} = $name) =~ s/\0*$//; $self->{attributes}{resource} = 1 if $attributes & 0x0001; $self->{attributes}{"read-only"} = 1 if $attributes & 0x0002; $self->{attributes}{"AppInfo dirty"} = 1 if $attributes & 0x0004; $self->{attributes}{backup} = 1 if $attributes & 0x0008; $self->{attributes}{"OK newer"} = 1 if $attributes & 0x0010; $self->{attributes}{reset} = 1 if $attributes & 0x0020; $self->{attributes}{open} = 1 if $attributes & 0x0040; $self->{attributes}{launchable} = 1 if $attributes & 0x0200; $self->{version} = $version; $self->{ctime} = $ctime - $EPOCH_1904; $self->{mtime} = $mtime - $EPOCH_1904; $self->{baktime} = $baktime - $EPOCH_1904; $self->{modnum} = $modnum; # _appinfo_offset and _sort_offset are private fields $self->{_appinfo_offset} = $appinfo_offset; $self->{_sort_offset} = $sort_offset; $self->{type} = $type; $self->{creator} = $creator; $self->{uniqueIDseed} = $uniqueIDseed; print <{attributes}{launchable}; print " OPEN" if $self->{attributes}{open}; print " RESET" if $self->{attributes}{reset}; print " OKNEWER" if $self->{attributes}{"OK newer"}; print " BACKUP" if $self->{attributes}{backup}; print " APPINFO-DIRTY" if $self->{attributes}{"AppInfo dirty"}; print " READ-ONLY" if $self->{attributes}{"read-only"}; print " RESOURCE" if $self->{attributes}{resource}; print "\n"; print <{attributes}{resource}) { # Look among resource handlers $handler = $PRCHandlers{$self->{creator}}{$self->{type}} || $PRCHandlers{undef}{$self->{type}} || $PRCHandlers{$self->{creator}}{""} || $PRCHandlers{""}{""}; } else { # Look among record handlers $handler = $PDBHandlers{$self->{creator}}{$self->{type}} || $PDBHandlers{""}{$self->{type}} || $PDBHandlers{$self->{creator}}{""} || $PDBHandlers{""}{""}; } if (defined($handler)) { bless $self, $handler; } else { # XXX - This should probably return 'undef' or something, # rather than die. die "No handler defined for creator \"$creator\", type \"$type\"\n"; } ## Read record/resource index # Read index header read PDB, $buf, $RecIndexHeaderLen; print "Record/resource index header:\n"; if ($hexdump) { &hexdump(" ", $buf); print "\n"; } my $next_index; my $numrecs; ($next_index, $numrecs) = unpack "N n", $buf; $self->{_numrecs} = $numrecs; print <{attributes}{resource}) { &_load_rsrc_index($self, \*PDB); } else { &_load_rec_index($self, \*PDB); } # Ignore the two NUL bytes that are usually here. We'll seek() # around them later. # Read AppInfo block, if it exists if ($self->{_appinfo_offset} != 0) { &_load_appinfo_block($self, \*PDB); } # Read sort block, if it exists if ($self->{_sort_offset} != 0) { &_load_sort_block($self, \*PDB); } # Read record/resource list if ($self->{attributes}{resource}) { &_load_resources($self, \*PDB); } else { &_load_records($self, \*PDB); } # These keys were needed for parsing the file, but are not # needed any longer. Delete them. delete $self->{_index}; delete $self->{_numrecs}; delete $self->{_appinfo_offset}; delete $self->{_sort_offset}; delete $self->{_size}; close PDB; } # _load_rec_index # Private function. Read the record index, for a record database sub _load_rec_index { my $pdb = shift; my $fh = shift; # Input file handle my $i; my $lastoffset = 0; print "Record index:\n"; # Read each record index entry in turn for ($i = 0; $i < $pdb->{_numrecs}; $i++) { my $buf; # Input buffer print " Record index entry $i\n"; # Read the next record index entry my $offset; my $attributes; my @id; # Raw ID my $id; # Numerical ID my $entry = {}; # Parsed index entry read $fh, $buf, $IndexRecLen; if ($hexdump) { &hexdump(" ", $buf); print "\n"; } # The ID field is a bit weird: it's represented as 3 # bytes, but it's really a double word (long) value. ($offset, $attributes, @id) = unpack "N C C3", $buf; if ($offset == $lastoffset) { print STDERR "Record $i has same offset as previous one: $offset\n"; } $lastoffset = $offset; $entry->{offset} = $offset; $entry->{attributes}{expunged} = 1 if $attributes & 0x80; $entry->{attributes}{dirty} = 1 if $attributes & 0x40; $entry->{attributes}{deleted} = 1 if $attributes & 0x20; $entry->{attributes}{private} = 1 if $attributes & 0x10; $entry->{id} = ($id[0] << 16) | ($id[1] << 8) | $id[2]; # The lower 4 bits of the attributes field are # overloaded: If the record has been deleted and/or # expunged, then bit 0x08 indicates whether the record # should be archived. Otherwise (if it's an ordinary, # non-deleted record), the lower 4 bits specify the # category that the record belongs in. if (($attributes & 0xa0) == 0) { $entry->{category} = $attributes & 0x0f; } else { $entry->{attributes}{archive} = 1 if $attributes & 0x08; } print <{attributes}}]} Category: $entry->{category} ID: @{[sprintf("0x%02x%02x%02x", @id)]} EOT # Put this information on a temporary array push @{$pdb->{_index}}, $entry; } } # XXX - Make this print out debugging information # _load_rsrc_index # Private function. Read the resource index, for a resource database sub _load_rsrc_index { my $pdb = shift; my $fh = shift; # Input file handle my $i; print "Resource index:\n"; # Read each resource index entry in turn for ($i = 0; $i < $pdb->{_numrecs}; $i++) { my $buf; # Input buffer print " Resource index entry $i\n"; # Read the next resource index entry my $type; my $id; my $offset; my $entry = {}; # Parsed index entry read $fh, $buf, $IndexRsrcLen; if ($hexdump) { &hexdump(" ", $buf); print "\n"; } ($type, $id, $offset) = unpack "a4 n N", $buf; $entry->{type} = $type; $entry->{id} = $id; $entry->{offset} = $offset; print <{_index}}, $entry; } } # _load_appinfo_block # Private function. Read the AppInfo block sub _load_appinfo_block { my $pdb = shift; my $fh = shift; # Input file handle my $len; # Length of AppInfo block my $buf; # Input buffer print "AppInfo block:\n"; # Sanity check: make sure we're positioned at the beginning of # the AppInfo block if (tell($fh) > $pdb->{_appinfo_offset}) { die "Bad AppInfo offset: expected ", sprintf("0x%08x", $pdb->{_appinfo_offset}), ", but I'm at ", tell($fh), "\n"; } # Seek to the right place, if necessary if (tell($fh) != $pdb->{_appinfo_offset}) { seek PDB, $pdb->{_appinfo_offset}, 0; } # There's nothing that explicitly gives the size of the # AppInfo block. Rather, it has to be inferred from the offset # of the AppInfo block (previously recorded in # $pdb->{_appinfo_offset}) and whatever's next in the file. # That's either the sort block, the first data record, or the # end of the file. if ($pdb->{_sort_offset}) { # The next thing in the file is the sort block $len = $pdb->{_sort_offset} - $pdb->{_appinfo_offset}; } elsif ((defined $pdb->{_index}) && @{$pdb->{_index}}) { # There's no sort block; the next thing in the file is # the first data record $len = $pdb->{_index}[0]{offset} - $pdb->{_appinfo_offset}; } else { # There's no sort block and there are no records. The # AppInfo block goes to the end of the file. $len = $pdb->{_size} - $pdb->{_appinfo_offset}; } # Read the AppInfo block read $fh, $buf, $len; if ($hexdump) { &hexdump(" ", $buf); print "\n"; } # Tell the real class to parse the AppInfo block $pdb->{appinfo} = $pdb->ParseAppInfoBlock($buf); # Print out the parsed values if (ref($pdb->{appinfo}) ne "") { &dumphash($pdb->{appinfo}, "\t"); print "\n"; } } # _load_sort_block # Private function. Read the sort block. sub _load_sort_block { my $pdb = shift; my $fh = shift; # Input file handle my $len; # Length of sort block my $buf; # Input buffer print "Sort block:\n"; # Sanity check: make sure we're positioned at the beginning of # the sort block if (tell($fh) > $pdb->{_sort_offset}) { die "Bad sort block offset: expected ", sprintf("0x%08x", $pdb->{_sort_offset}), ", but I'm at ", tell($fh), "\n"; } # Seek to the right place, if necessary if (tell($fh) != $pdb->{_sort_offset}) { seek PDB, $pdb->{_sort_offset}, 0; } # There's nothing that explicitly gives the size of the sort # block. Rather, it has to be inferred from the offset of the # sort block (previously recorded in $pdb->{_sort_offset}) # and whatever's next in the file. That's either the first # data record, or the end of the file. if (defined($pdb->{_index})) { # The next thing in the file is the first data record $len = $pdb->{_index}[0]{offset} - $pdb->{_sort_offset}; } else { # There are no records. The sort block goes to the end # of the file. $len = $pdb->{_size} - $pdb->{_sort_offset}; } # Read the AppInfo block read $fh, $buf, $len; if ($hexdump) { &hexdump(" ", $buf); print "\n"; } # XXX - Check to see if the sort block has some predefined # structure. If so, it might be a good idea to parse the sort # block here. # Tell the real class to parse the sort block $pdb->{sort} = $pdb->ParseSortBlock($buf); } # _load_records # Private function. Load the actual data records, for a record database # (PDB) sub _load_records { my $pdb = shift; my $fh = shift; # Input file handle my $i; print "Records:\n"; # Read each record in turn for ($i = 0; $i < $pdb->{_numrecs}; $i++) { my $len; # Length of record my $buf; # Input buffer print " Record $i\n"; # Sanity check: make sure we're where we think we # should be. if (tell($fh) > $pdb->{_index}[$i]{offset}) { # XXX - The two NULs are really optional. # die "Bad offset for record $i: expected ", # sprintf("0x%08x", # $pdb->{_index}[$i]{offset}), # " but it's at ", # sprintf("[0x%08x]", tell($fh)), "\n"; } # Seek to the right place, if necessary if (tell($fh) != $pdb->{_index}[$i]{offset}) { seek PDB, $pdb->{_index}[$i]{offset}, 0; } # Compute the length of the record: the last record # extends to the end of the file. The others extend to # the beginning of the next record. if ($i == $pdb->{_numrecs} - 1) { # This is the last record $len = $pdb->{_size} - $pdb->{_index}[$i]{offset}; } else { # This is not the last record $len = $pdb->{_index}[$i+1]{offset} - $pdb->{_index}[$i]{offset}; } # Read the record read $fh, $buf, $len; if ($hexdump) { &hexdump(" ", $buf); print "\n"; } # Tell the real class to parse the record data. Pass # &ParseRecord all of the information from the index, # plus a "data" field with the raw record data. my $record; $record = $pdb->ParseRecord( %{$pdb->{_index}[$i]}, "data" => $buf, ); push @{$pdb->{records}}, $record; # Print out the parsed values &dumphash($record, "\t"); print "\n"; } } # _load_resources # Private function. Load the actual data resources, for a resource database # (PRC) sub _load_resources { my $pdb = shift; my $fh = shift; # Input file handle my $i; print "Resources:\n"; # Read each resource in turn for ($i = 0; $i < $pdb->{_numrecs}; $i++) { my $len; # Length of record my $buf; # Input buffer print " Resource $i\n"; # Sanity check: make sure we're where we think we # should be. if (tell($fh) > $pdb->{_index}[$i]{offset}) { die "Bad offset for resource $i: expected ", sprintf("0x%08x", $pdb->{_index}[$i]{offset}), " but it's at ", sprintf("0x%08x", tell($fh)), "\n"; } # Seek to the right place, if necessary if (tell($fh) != $pdb->{_index}[$i]{offset}) { seek PDB, $pdb->{_index}[$i]{offset}, 0; } # Compute the length of the resource: the last # resource extends to the end of the file. The others # extend to the beginning of the next resource. if ($i == $pdb->{_numrecs} - 1) { # This is the last resource $len = $pdb->{_size} - $pdb->{_index}[$i]{offset}; } else { # This is not the last resource $len = $pdb->{_index}[$i+1]{offset} - $pdb->{_index}[$i]{offset}; } # Read the resource read $fh, $buf, $len; if ($hexdump) { &hexdump(" ", $buf); print "\n"; } # Tell the real class to parse the resource data. Pass # &ParseResource all of the information from the # index, plus a "data" field with the raw resource # data. my $resource; $resource = $pdb->ParseResource( %{$pdb->{_index}[$i]}, "data" => $buf, ); push @{$pdb->{resources}}, $resource; # Print out the parsed values &dumphash($resource, "\t"); print "\n"; } } sub hexdump { my $prefix = shift; # What to print in front of each line my $data = shift; # The data to dump my $maxlines = shift; # Max # of lines to dump my $offset; # Offset of current chunk for ($offset = 0; $offset < length($data); $offset += 16) { my $hex; # Hex values of the data my $ascii; # ASCII values of the data my $chunk; # Current chunk of data last if defined($maxlines) && ($offset >= ($maxlines * 16)); $chunk = substr($data, $offset, 16); ($hex = $chunk) =~ s/./sprintf "%02x ", ord($&)/ges; ($ascii = $chunk) =~ y/\040-\176/./c; printf "%s %-48s|%-16s|\n", $prefix, $hex, $ascii; } } # XXX - Ought to have a &dumparray as well. The two can call each other # recursively. sub dumphash { my $hash = shift; my $indent = shift; my $key; my $value; while (($key, $value) = each %{$hash}) { if (ref($value) eq "HASH") { print $indent, $key, ":\n"; &dumphash($value, $indent . "\t"); } elsif (ref($value) eq "ARRAY") { my($i,$j); print $indent, $key, ":\n"; for ($i = 0; $i <= $#{$value}; $i++) { if (ref($value->[$i]) eq "HASH") { print $indent, " $i:\n"; &dumphash($value->[$i], $indent . "\t"); } elsif (ref($value->[$i]) eq "ARRAY") { my @v2 = @{$value->[$i]}; for ($j = 0; $j <= $#v2; $j++) { print $indent, "\t$i-$j: [$v2[$j]]\n"; } }else { print $indent, "\t$i: [$value->[$i]]\n"; } } } else { print $indent, $key, " -> [", $value, "]\n"; } } } __END__ =head1 NAME pdbdump - Print the contents of a Palm PDB file =head1 SYNOPSIS C I<[options]> F =head1 DESCRIPTION C reads a PalmOS F<.pdb> file, parses it, and prints its contents. This includes both a hex dump of the raw data of each piece, and a human-readable list of the various values, insofar as possible. The aim of C is to allow one to verify whether a particular file is a well-formed PalmOS database file and if not, where the error lies. If the database is of a known type, C parses the AppInfo block and records. Otherwise, it simply prints out a hex dump of their contents. C includes, by default, support for most of the built-in applications. Other helper modules may be loaded with the C<-M> option. =head1 OPTIONS =over 4 =item -h -help --help Print a usage message and exit. =item -nohex Don't print the hex dump of the various parts. =item -MI C the named module. This can be useful for loading additional helper modules. =back =head1 BUGS C only recognizes record databases (C<.pdb> files), not resource databases (C<.prc> files). =head1 SEE ALSO Palm::PDB(3) =head1 AUTHOR Andrew Arensburger p5-Palm-1.012/FAQ0000644000076500000240000000115411341055215012236 0ustar brianstaffQ: I'm trying to read the "datebook.dat", "address.dat", "todo.dat" or "memopad.dat" files created by the HotSync desktop tool, but it doesn't work. A: Those files aren't PDB files, so these modules can't read them. It looks as if the HotSync conduits don't store a backup PDB file of the database. In any case, I don't know what format those files are in. http://www.geocities.com/Heartland/Acres/3216/palmrecs.htm appears to have information about the file formats used by HotSync. The Pilot:: Perl package by John Wiegley might also prove useful. See: http://www.cpan.org/authors/id/JWIEGLEY/ p5-Palm-1.012/lib/0000755000076500000240000000000011341055220012445 5ustar brianstaffp5-Palm-1.012/lib/Address.pm0000644000076500000240000004503311341055215014401 0ustar brianstaff# Palm::Address.pm # # Perl class for dealing with Palm AddressBook databases. # # Copyright (C) 1999, 2000, Andrew Arensburger. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. use strict; package Palm::Address; use Palm::Raw(); use Palm::StdAppInfo(); use vars qw( $VERSION @ISA $numFieldLabels $addrLabelLength @phoneLabels @countries %fieldMapBits ); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; @ISA = qw( Palm::StdAppInfo Palm::Raw ); # AddressDB records are quite flexible and customizable, and therefore # a pain in the ass to deal with correctly. =head1 NAME Palm::Address - Handler for Palm AddressBook databases =head1 SYNOPSIS use Palm::Address; =head1 DESCRIPTION The Address PDB handler is a helper class for the Palm::PDB package. It parses AddressBook databases. =head2 AppInfo block The AppInfo block begins with standard category support. See L for details. Other fields include: $pdb->{appinfo}{lastUniqueID} $pdb->{appinfo}{dirtyFields} I don't know what these are. $pdb->{appinfo}{fieldLabels}{name} $pdb->{appinfo}{fieldLabels}{firstName} $pdb->{appinfo}{fieldLabels}{company} $pdb->{appinfo}{fieldLabels}{phone1} $pdb->{appinfo}{fieldLabels}{phone2} $pdb->{appinfo}{fieldLabels}{phone3} $pdb->{appinfo}{fieldLabels}{phone4} $pdb->{appinfo}{fieldLabels}{phone5} $pdb->{appinfo}{fieldLabels}{phone6} $pdb->{appinfo}{fieldLabels}{phone7} $pdb->{appinfo}{fieldLabels}{phone8} $pdb->{appinfo}{fieldLabels}{address} $pdb->{appinfo}{fieldLabels}{city} $pdb->{appinfo}{fieldLabels}{state} $pdb->{appinfo}{fieldLabels}{zipCode} $pdb->{appinfo}{fieldLabels}{country} $pdb->{appinfo}{fieldLabels}{title} $pdb->{appinfo}{fieldLabels}{custom1} $pdb->{appinfo}{fieldLabels}{custom2} $pdb->{appinfo}{fieldLabels}{custom3} $pdb->{appinfo}{fieldLabels}{custom4} $pdb->{appinfo}{fieldLabels}{note} These are the names of the various fields in the address record. $pdb->{appinfo}{country} An integer: the code for the country for which these labels were designed. The country name is available as $Palm::Address::countries[$pdb->{appinfo}{country}]; $pdb->{appinfo}{misc} An integer. The least-significant bit is a flag that indicates whether the database should be sorted by company. The other bits are reserved. =head2 Sort block $pdb->{sort} This is a scalar, the raw data of the sort block. =head2 Records $record = $pdb->{records}[N]; $record->{fields}{name} $record->{fields}{firstName} $record->{fields}{company} $record->{fields}{phone1} $record->{fields}{phone2} $record->{fields}{phone3} $record->{fields}{phone4} $record->{fields}{phone5} $record->{fields}{address} $record->{fields}{city} $record->{fields}{state} $record->{fields}{zipCode} $record->{fields}{country} $record->{fields}{title} $record->{fields}{custom1} $record->{fields}{custom2} $record->{fields}{custom3} $record->{fields}{custom4} $record->{fields}{note} These are scalars, the values of the various address book fields. $record->{phoneLabel}{phone1} $record->{phoneLabel}{phone2} $record->{phoneLabel}{phone3} $record->{phoneLabel}{phone4} $record->{phoneLabel}{phone5} Most fields in an AddressBook record are straightforward: the "name" field always gives the person's last name. The "phoneI" fields, on the other hand, can mean different things in different records. There are five such fields in each record, each of which can take on one of eight different values: "Work", "Home", "Fax", "Other", "E-mail", "Main", "Pager" and "Mobile". The $record->{phoneLabel}{phone*} fields are integers. Each one is an index into @Palm::Address::phoneLabels, and indicates which particular type of phone number each of the $record->{phone*} fields represents. $record->{phoneLabel}{display} Like the phone* fields above, this is an index into @Palm::Address::phoneLabels. It indicates which of the phone* fields to display in the list view. $record->{phoneLabel}{reserved} I don't know what this is. =head1 METHODS =cut #' $addrLabelLength = 16; $numFieldLabels = 22; @phoneLabels = ( "Work", "Home", "Fax", "Other", "E-mail", "Main", "Pager", "Mobile", ); @countries = ( "Australia", "Austria", "Belgium", "Brazil", "Canada", "Denmark", "Finland", "France", "Germany", "Hong Kong", "Iceland", "Ireland", "Italy", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Norway", "Spain", "Sweden", "Switzerland", "United Kingdom", "United States", ); # fieldMapBits # Each Address record contains a flag record ($fieldMap, in # &PackRecord) that indicates which fields exist in the record. This # hash defines these flags' values. %fieldMapBits = ( name => 0x0001, firstName => 0x0002, company => 0x0004, phone1 => 0x0008, phone2 => 0x0010, phone3 => 0x0020, phone4 => 0x0040, phone5 => 0x0080, address => 0x0100, city => 0x0200, state => 0x0400, zipCode => 0x0800, country => 0x1000, title => 0x2000, custom1 => 0x4000, custom2 => 0x8000, custom3 => 0x10000, custom4 => 0x20000, note => 0x40000, ); sub import { &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "addr", "DATA" ], ); } =head2 new $pdb = new Palm::Address; Create a new PDB, initialized with the various Palm::Address fields and an empty record list. Use this method if you're creating an Address PDB from scratch. =cut #' # new # Create a new Palm::Address database, and return it sub new { my $classname = shift; my $self = $classname->SUPER::new(@_); # Create a generic PDB. No need to rebless it, # though. $self->{name} = "AddressDB"; # Default $self->{creator} = "addr"; $self->{type} = "DATA"; $self->{attributes}{resource} = 0; # The PDB is not a resource database by # default, but it's worth emphasizing, # since AddressDB is explicitly not a PRC. # Initialize the AppInfo block $self->{appinfo} = { fieldLabels => { # Displayed labels for the various fields in # each address record. # XXX - These are American English defaults. It'd # be way keen to allow i18n. name => "Name", firstName => "First name", company => "Company", phone1 => "Work", phone2 => "Home", phone3 => "Fax", phone4 => "Other", phone5 => "E-mail", phone6 => "Main", phone7 => "Pager", phone8 => "Mobile", address => "Address", city => "City", state => "State", zipCode => "Zip Code", country => "Country", title => "Title", custom1 => "Custom 1", custom2 => "Custom 2", custom3 => "Custom 3", custom4 => "Custom 4", note => "Note", }, # XXX - The country code corresponds to "United # States". Again, it'd be keen to allow the user's # # country-specific defaults. country => 22, misc => 0, }; # Add the standard AppInfo block stuff &Palm::StdAppInfo::seed_StdAppInfo($self->{appinfo}); # Give the PDB a blank sort block $self->{sort} = undef; # Give the PDB an empty list of records $self->{records} = []; return $self; } =head2 new_Record $record = $pdb->new_Record; Creates a new Address record, with blank values for all of the fields. The AppInfo block will contain only an "Unfiled" category, with ID 0. C does B add the new record to C<$pdb>. For that, you want C<$pdb-Eappend_Record>. =cut # new_Record # Create a new, initialized record. sub new_Record { my $classname = shift; my $retval = $classname->SUPER::new_Record(@_); # Initialize the fields. This isn't particularly enlightening, # but every AddressDB record has these. $retval->{fields} = { name => undef, firstName => undef, company => undef, phone1 => undef, phone2 => undef, phone3 => undef, phone4 => undef, phone5 => undef, address => undef, city => undef, state => undef, zipCode => undef, country => undef, title => undef, custom1 => undef, custom2 => undef, custom3 => undef, custom4 => undef, note => undef, }; # Initialize the phone labels $retval->{phoneLabel} = { phone1 => 0, # Work phone2 => 1, # Home phone3 => 2, # Fax phone4 => 3, # Other phone5 => 4, # E-mail display => 0, # Display work phone by default reserved => undef # ??? }; return $retval; } # ParseAppInfoBlock # Parse the AppInfo block for Address databases. # # The AppInfo block has the following overall structure: # 1: Categories (see StdAppInfo.pm) # 2: reserved word # 3: dirty field labels # 4: field labels # 5: country # 6: misc # 3: I think this is similar to the first part of the standard AppInfo # blocka, a bit field of which field labels have changed (i.e., # which fields have been renamed). # 4: An array of field labels (16-character strings, NUL-terminated). # 5: The code for the country for which the labels were designed. # 6: 7 reserved bits followed by one flag that's set if the database # should be sorted by company. sub ParseAppInfoBlock { my $self = shift; my $data = shift; my $dirtyFields; my @fieldLabels; my $country; my $misc; my $i; my $appinfo = {}; my $std_len; # Get the standard parts of the AppInfo block $std_len = &Palm::StdAppInfo::parse_StdAppInfo($appinfo, $data); $data = $appinfo->{other}; # Look at the non-standard part # Get the rest of the AppInfo block my $unpackstr = # Argument to unpack() "x2" . # Reserved "N" . # Dirty flags "a$addrLabelLength" x $numFieldLabels . # Address labels "C" . # Country "C"; # Misc ($dirtyFields, @fieldLabels[0..($numFieldLabels-1)], $country, $misc) = unpack $unpackstr, $data; for (@fieldLabels) { s/\0.*$//; # Trim everything after the first NUL # (when renaming custom fields, might # have something like "Foo\0om 1" } $appinfo->{dirtyFields} = $dirtyFields; $appinfo->{fieldLabels} = { name => $fieldLabels[0], firstName => $fieldLabels[1], company => $fieldLabels[2], phone1 => $fieldLabels[3], phone2 => $fieldLabels[4], phone3 => $fieldLabels[5], phone4 => $fieldLabels[6], phone5 => $fieldLabels[7], address => $fieldLabels[8], city => $fieldLabels[9], state => $fieldLabels[10], zipCode => $fieldLabels[11], country => $fieldLabels[12], title => $fieldLabels[13], custom1 => $fieldLabels[14], custom2 => $fieldLabels[15], custom3 => $fieldLabels[16], custom4 => $fieldLabels[17], note => $fieldLabels[18], phone6 => $fieldLabels[19], phone7 => $fieldLabels[20], phone8 => $fieldLabels[21], }; $appinfo->{country} = $country; $appinfo->{misc} = $misc; # XXX - Parse the "misc" field further return $appinfo; } sub PackAppInfoBlock { my $self = shift; my $retval; my $i; my $other; # Non-standard AppInfo stuff # Pack the application-specific part of the AppInfo block $other = pack("x2 N", $self->{appinfo}{dirtyFields}); $other .= pack("a$addrLabelLength" x $numFieldLabels, $self->{appinfo}{fieldLabels}{name}, $self->{appinfo}{fieldLabels}{firstName}, $self->{appinfo}{fieldLabels}{company}, $self->{appinfo}{fieldLabels}{phone1}, $self->{appinfo}{fieldLabels}{phone2}, $self->{appinfo}{fieldLabels}{phone3}, $self->{appinfo}{fieldLabels}{phone4}, $self->{appinfo}{fieldLabels}{phone5}, $self->{appinfo}{fieldLabels}{address}, $self->{appinfo}{fieldLabels}{city}, $self->{appinfo}{fieldLabels}{state}, $self->{appinfo}{fieldLabels}{zipCode}, $self->{appinfo}{fieldLabels}{country}, $self->{appinfo}{fieldLabels}{title}, $self->{appinfo}{fieldLabels}{custom1}, $self->{appinfo}{fieldLabels}{custom2}, $self->{appinfo}{fieldLabels}{custom3}, $self->{appinfo}{fieldLabels}{custom4}, $self->{appinfo}{fieldLabels}{note}, $self->{appinfo}{fieldLabels}{phone6}, $self->{appinfo}{fieldLabels}{phone7}, $self->{appinfo}{fieldLabels}{phone8}); $other .= pack("C C x2", $self->{appinfo}{country}, $self->{appinfo}{misc}); $self->{appinfo}{other} = $other; # Pack the standard part of the AppInfo block $retval = &Palm::StdAppInfo::pack_StdAppInfo($self->{appinfo}); return $retval; } # ParseRecord # Parse an Address Book record. # Address book records have the following overall structure: # 1: phone labels # 2: field map # 3: fields # Each record can contain a number of fields, such as "name", # "address", "city", "company", and so forth. Each field has an # internal name ("zipCode"), a printable name ("Zip Code"), and a # value ("90210"). # # For most fields, there is a hard mapping between internal and # printed names: "name" always corresponds to "Last Name". The fields # "phone1" through "phone5" are different: each of these can be mapped # to one of several printed names: "Work", "Home", "Fax", "Other", # "E-Mail", "Main", "Pager" or "Mobile". Multiple internal names can # map to the same printed name (a person might have several e-mail # addresses), and the mapping is part of the record (i.e., each record # has its own mapping). # # Part (3) is simply a series of NUL-terminated strings, giving the # values of the various fields in the record, in a certain order. If a # record does not have a given field, there is no string corresponding # to it in this part. # # Part (2) is a bit field that specifies which fields the record # contains. # # Part (1) determines the phone mapping described above. This is # implemented as an unsigned long, but what we're interested in are # the six least-significant nybbles. They are: # disp phone5 phone4 phone3 phone2 phone1 # ("phone1" is the least-significant nybble). Each nybble holds a # value in the range 0-15 which in turn specifies the printed name for # that particular internal name. sub ParseRecord { my $self = shift; my %record = @_; delete $record{offset}; # This is useless my $phoneFlags; my @phoneTypes; my $dispPhone; # Which phone to display in the phone list my $reserved; # Not sure what this is. It's the 8 high bits # of the "phone types" field. my $fieldMap; my $companyFieldOff; # Company field offset: offset into the # raw "fields" string of the beginning of # the company name, plus 1. Presumably this # is to allow the address book app to quickly # display by company name. It is 0 in entries # that don't have a "Company" field. # This can be ignored when reading, and # must be computed when writing. my $fields; my @fields; ($phoneFlags, $fieldMap, $companyFieldOff, $fields) = unpack("N N C a*", $record{data}); @fields = split /\0/, $fields; # Parse the phone flags $phoneTypes[0] = $phoneFlags & 0x0f; $phoneTypes[1] = ($phoneFlags >> 4) & 0x0f; $phoneTypes[2] = ($phoneFlags >> 8) & 0x0f; $phoneTypes[3] = ($phoneFlags >> 12) & 0x0f; $phoneTypes[4] = ($phoneFlags >> 16) & 0x0f; $dispPhone = ($phoneFlags >> 20) & 0x0f; $reserved = ($phoneFlags >> 24) & 0xff; $record{phoneLabel}{phone1} = $phoneTypes[0]; $record{phoneLabel}{phone2} = $phoneTypes[1]; $record{phoneLabel}{phone3} = $phoneTypes[2]; $record{phoneLabel}{phone4} = $phoneTypes[3]; $record{phoneLabel}{phone5} = $phoneTypes[4]; $record{phoneLabel}{display} = $dispPhone; $record{phoneLabel}{reserved} = $reserved; # Get the relevant fields $fieldMap & 0x0001 and $record{fields}{name} = shift @fields; $fieldMap & 0x0002 and $record{fields}{firstName} = shift @fields; $fieldMap & 0x0004 and $record{fields}{company} = shift @fields; $fieldMap & 0x0008 and $record{fields}{phone1} = shift @fields; $fieldMap & 0x0010 and $record{fields}{phone2} = shift @fields; $fieldMap & 0x0020 and $record{fields}{phone3} = shift @fields; $fieldMap & 0x0040 and $record{fields}{phone4} = shift @fields; $fieldMap & 0x0080 and $record{fields}{phone5} = shift @fields; $fieldMap & 0x0100 and $record{fields}{address} = shift @fields; $fieldMap & 0x0200 and $record{fields}{city} = shift @fields; $fieldMap & 0x0400 and $record{fields}{state} = shift @fields; $fieldMap & 0x0800 and $record{fields}{zipCode} = shift @fields; $fieldMap & 0x1000 and $record{fields}{country} = shift @fields; $fieldMap & 0x2000 and $record{fields}{title} = shift @fields; $fieldMap & 0x4000 and $record{fields}{custom1} = shift @fields; $fieldMap & 0x8000 and $record{fields}{custom2} = shift @fields; $fieldMap & 0x10000 and $record{fields}{custom3} = shift @fields; $fieldMap & 0x20000 and $record{fields}{custom4} = shift @fields; $fieldMap & 0x40000 and $record{fields}{note} = shift @fields; delete $record{data}; return \%record; } sub PackRecord { my $self = shift; my $record = shift; my $retval; $retval = pack("N", ($record->{phoneLabel}{phone1} & 0x0f) | (($record->{phoneLabel}{phone2} & 0x0f) << 4) | (($record->{phoneLabel}{phone3} & 0x0f) << 8) | (($record->{phoneLabel}{phone4} & 0x0f) << 12) | (($record->{phoneLabel}{phone5} & 0x0f) << 16) | (($record->{phoneLabel}{display} & 0x0f) << 20) | (($record->{phoneLabel}{reserved} & 0xff) << 24)); # Set the flag bits that indicate which fields exist in this # record. my $fieldMap = 0; foreach my $fieldname (qw(name firstName company phone1 phone2 phone3 phone4 phone5 address city state zipCode country title custom1 custom2 custom3 custom4 note)) { if (defined($record->{fields}{$fieldname}) && ($record->{fields}{$fieldname} ne "")) { $fieldMap |= $fieldMapBits{$fieldname}; } else { $record->{fields}{$fieldname} = ""; } } $retval .= pack("N", $fieldMap); my $fields = ''; my $companyFieldOff = 0; $fields .= $record->{fields}{name} . "\0" unless $record->{fields}{name} eq ""; $fields .= $record->{fields}{firstName} . "\0" unless $record->{fields}{firstName} eq ""; if ($record->{fields}{company} ne "") { $companyFieldOff = length($fields) + 1; $fields .= $record->{fields}{company} . "\0" } # Append each nonempty field in turn to $fields. foreach my $fieldname (qw(phone1 phone2 phone3 phone4 phone5 address city state zipCode country title custom1 custom2 custom3 custom4 note)) { # Skip empty fields (either blank or undefined). next if !defined($record->{fields}{$fieldname}); next if $record->{fields}{$fieldname} eq ""; # Append the field (with a terminating NUL) $fields .= $record->{fields}{$fieldname} . "\0"; } $retval .= pack("C", $companyFieldOff); $retval .= $fields; return $retval; } 1; __END__ =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::PDB(3) Palm::StdAppInfo(3) =head1 BUGS The new() method initializes the AppInfo block with English labels and "United States" as the country. =cut p5-Palm-1.012/lib/Datebook.pm0000644000076500000240000004736611341055215014557 0ustar brianstaff# Palm::Datebook.pm # # Perl class for dealing with Palm DateBook and Calendar databases. # # Copyright (C) 1999-2001, Andrew Arensburger. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. use strict; package Palm::Datebook; use Palm::Raw(); use Palm::StdAppInfo(); use vars qw( $VERSION @ISA ); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; @ISA = qw( Palm::StdAppInfo Palm::Raw ); =head1 NAME Palm::Datebook - Handler for Palm DateBook and Calendar databases. =head1 SYNOPSIS use Palm::Datebook; =head1 DESCRIPTION The Datebook PDB handler is a helper class for the Palm::PDB package. It parses DateBook and Calendar databases. =head2 AppInfo block The AppInfo block begins with standard category support. See L for details. =head2 Sort block $pdb->{sort} This is a scalar, the raw data of the sort block. =head2 Records $record = $pdb->{records}[N] $record->{day} $record->{month} $record->{year} The day, month and year of the event. The day and month start at 1 (I, for January, C<< $record->{month} >> is set to 1). The year is a four-digit number (for dates in 2001, C<< $record->{year} >> is "2001"). For repeating events, these fields specify the first date at which the event occurs. $record->{start_hour} $record->{start_minute} $record->{end_hour} $record->{end_minute} The start and end times of the event. For untimed events, all of these are 0xff. $record->{when_changed} This is defined and true iff the "when info" for the record has changed. I don't know what this means. $record->{alarm}{advance} $record->{alarm}{unit} If the record has an alarm associated with it, the %{$record->{alarm}} hash exists. The "unit" subfield is an integer: 0 for minutes, 1 for hours, 2 for days. The "advance" subfield specifies how many units before the event the alarm should ring. I, if "unit" is 1 and "advance" is 5, then the alarm will sound 5 hours before the event. If C is -1, then there is no alarm associated with this event. New records created via C have a 10 minute alarm set by default. C<< undef $record->{alarm} >> to remove this alarm before writing. An alarm icon will still show up in the Datebook if the C field exists, even with C set to -1. %{$record->{repeat}} This exists iff this is a repeating event. $record->{repeat}{type} An integer which specifies the type of repeat: =over 4 =item 0 no repeat. =item 1 a daily event, one that occurs every day. =item 2 a weekly event, one that occurs every week on the same dayZ<>(s). An event may occur on several days every week, I, every Monday, Wednesday and Friday. For weekly events, the following fields are defined: @{$record->{repeat}{repeat_days}} This is an array of 7 elements; each element is true iff the event occurs on the corresponding day. Element 0 is Sunday, element 1 is Monday, and so forth. $record->{repeat}{start_of_week} Day the week starts on (0 for Sunday, 1 for Monday). This affects the phase of events that repeat every 2nd (or more) Sunday. =item 3 a "monthly by day" event, I, one that occurs on the second Friday of every month. For "monthly by day" events, the following fields are defined: $record->{repeat}{weeknum} The number of the week on which the event occurs. 0 means the first week of the month, 1 means the second week of the month, and so forth. A value of 5 means that the event occurs on the last week of the month. $record->{repeat}{daynum} An integer, the day of the week on which the event occurs. 0 means Sunday, 1 means Monday, and so forth. =item 4 a "monthly by date" event, I, one that occurs on the 12th of every month. =item 5 a yearly event, I, one that occurs every year on December 25th. $record->{repeat}{frequency} Specifies the frequency of the repeat. For instance, if the event is a daily one, and $record->{repeat}{frequency} is 3, then the event occurs every 3 days. =back $record->{repeat}{unknown} I don't know what this is. $record->{repeat}{end_day} $record->{repeat}{end_month} $record->{repeat}{end_year} The last day, month and year on which the event occurs. @{$record->{exceptions}} $day = $record->{exceptions}[N][0] $month = $record->{exceptions}[N][1] $year = $record->{exceptions}[N][2] If there are any exceptions to a repeating event, I a weekly meeting that was cancelled one time, then the @{$record->{exceptions}} array is defined. Each element in this array is a reference to an anonymous array with three elements: the day, month, and year of the exception. $record->{description} A text string, the description of the event. $record->{location} A text string, the location (if any) of the event (Calendar database only). $record->{note} A text string, the note (if any) attached to the event. %{$record->{timezone}} This exists iff a time zone has been set for the event. $record->{timezone}{name} The time zone name, I, "London" or "San Francisco". $record->{timezone}{country} The country the time zone is in. This is an integer defined in Core/System/PalmLocale.h. $record->{timezone}{offset} This gives the offset from UTC, in minutes, of the time zone. $record->{timezone}{dst_adjustment} This gives the additional offset while daylight savings time is in effect. The offset from UTC is $record->{timezone}{offset} + $record->{timezone}{dst_adjustment} (actually only 0 or 60 are used). $record->{timezone}{custom} Should indicate whether this location was created by the user, though this always seems to be true. $record->{timezone}{flags} Reserved flags. $record->{timezone}{start_hour} $record->{timezone}{start_daynum} $record->{timezone}{start_weeknum} $record->{timezone}{start_month} $record->{timezone}{end_hour} $record->{timezone}{end_daynum} $record->{timezone}{end_weeknum} $record->{timezone}{end_month} These define the period during which daylight savings time is in effect if $record->{timezone}{dst_adjustment} is non-zero. daynum specifies the day of week (0=Sunday, 6=Saturday) and weeknum specifies the week of month (0=1st, 3=4th, 4=last), analagously to the "monthly by day" repeating event. I, The "London" time zone has DST starting on the last Sunday of March, which is indicated with start_daynum=0 (Sunday), start_weeknum=4 (last week of the month), and start_month=3 (March). =head1 METHODS =cut #' sub import { &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "date", "DATA" ], [ "PDat", "DATA" ], ); } =head2 new $pdb = new Palm::Datebook; $pdb = new Palm::Datebook({app => 'Calendar'}); Create a new PDB, initialized with the various Palm::Datebook fields and an empty record list. Creates a Datebook PDB by default. Specify the app parameter as 'Calendar' to create a Calendar PDB. =cut #' # new # Create a new Palm::Datebook database, and return it sub new { my $classname = shift; my $params = $_[0] || {}; my $self = $classname->SUPER::new(@_); # Create a generic PDB. No need to rebless it, # though. if ($params->{app} eq 'Calendar' || $self->{creator} eq 'PDat') { $self->{name} ||= "CalendarDB-PDat"; # Default $self->{creator} = "PDat" if $self->{creator} eq "\0\0\0\0"; } else { $self->{name} ||= "DatebookDB"; # Default $self->{creator} = "date" if $self->{creator} eq "\0\0\0\0"; } $self->{type} = "DATA" if $self->{type} eq "\0\0\0\0"; $self->{attributes}{resource} = 0; # The PDB is not a resource database by # default, but it's worth emphasizing, # since DatebookDB is explicitly not a PRC. $self->{appinfo} = { start_of_week => 0, # XXX - This is bogus }; &Palm::StdAppInfo::seed_StdAppInfo($self->{appinfo}); $self->{sort} = undef; # Empty sort block $self->{records} = []; # Empty list of records return $self; } =head2 new_Record $record = $pdb->new_Record; Creates a new Datebook record, with blank values for all of the fields. C does B add the new record to C<$pdb>. For that, you want C<< $pdb->append_Record >>. =cut sub new_Record { my $classname = shift; my $retval = $classname->SUPER::new_Record(@_); # By default, the new record is an untimed event that occurs # today. my @now = localtime(time); $retval->{day} = $now[3]; $retval->{month} = $now[4] + 1; $retval->{year} = $now[5] + 1900; $retval->{start_hour} = $retval->{start_minute} = $retval->{end_hour} = $retval->{end_minute} = 0xff; # Set the alarm. Defaults to 10 minutes before the event. $retval->{alarm}{advance} = 10; $retval->{alarm}{unit} = 0; # Minutes $retval->{repeat} = {}; # No repeat $retval->{exceptions} = []; # No exceptions $retval->{description} = ""; $retval->{note} = undef; $retval->{location} = undef; return $retval; } # ParseAppInfoBlock # Parse the AppInfo block for Datebook databases. # There appears to be one byte of padding at the end. sub ParseAppInfoBlock { my $self = shift; my $data = shift; my $startOfWeek; my $i; my $appinfo = {}; my $std_len; # Get the standard parts of the AppInfo block $std_len = &Palm::StdAppInfo::parse_StdAppInfo($appinfo, $data); $data = $appinfo->{other}; # Look at non-category part # Get the rest of the AppInfo block my $unpackstr = # Argument to unpack(), since it's hairy "x2" . # Padding "C"; # Start of week # XXX - This is actually "sortOrder". Dunno what that is, # though. ($startOfWeek) = unpack $unpackstr, $data; $appinfo->{start_of_week} = $startOfWeek; return $appinfo; } sub PackAppInfoBlock { my $self = shift; my $retval; # Pack the non-category part of the AppInfo block $self->{appinfo}{other} = pack("x2 C x", $self->{appinfo}{start_of_week}); # Pack the standard part of the AppInfo block $retval = &Palm::StdAppInfo::pack_StdAppInfo($self->{appinfo}); return $retval; } sub ParseRecord { my $self = shift; my %record = @_; my $data; my $iscal = ($self->{creator} eq 'PDat'); delete $record{offset}; # This is useless # Untimed events have 0xff for $startHour, $startMinute, # $endHour and $endMinute. my $startHour; # In 24-hour format my $startMinute; my $endHour; # In 24-hour format my $endMinute; my $rawDate; my $flags; my $unpackstr = # Argument to unpack(). "C C" . # Start hour, minute "C C" . # End hour, minute "n" . # Raw date "n"; # Flags $data = $record{data}; ($startHour, $startMinute, $endHour, $endMinute, $rawDate, $flags) = unpack $unpackstr, $data; $data = substr $data, 8; # Chop off the part we've just parsed my $year; my $month; my $day; $day = $rawDate & 0x001f; # 5 bits $month = ($rawDate >> 5) & 0x000f; # 4 bits $year = ($rawDate >> 9) & 0x007f; # 7 bits (years since 1904) $year += 1904; $record{start_hour} = $startHour; $record{start_minute} = $startMinute; $record{end_hour} = $endHour; $record{end_minute} = $endMinute; $record{day} = $day; $record{month} = $month; $record{year} = $year; # Flags my $when_changed = ($flags & 0x8000 ? 1 : 0); my $have_alarm = ($flags & 0x4000 ? 1 : 0); my $have_repeat = ($flags & 0x2000 ? 1 : 0); my $have_note = ($flags & 0x1000 ? 1 : 0); my $have_exceptions = ($flags & 0x0800 ? 1 : 0); my $have_description = ($flags & 0x0400 ? 1 : 0); my $have_location = (($iscal && ($flags & 0x0200)) ? 1 : 0); $record{other_flags} = $flags & ($iscal ? 0x01ff : 0x03ff); if ($when_changed) { $record{when_changed} = 1; } if ($have_alarm) { my $advance; my $adv_unit; ($advance, $adv_unit) = unpack "cC", $data; $data = substr $data, 2; # Chop off alarm data $record{alarm}{advance} = $advance; $record{alarm}{unit} = $adv_unit; } if ($have_repeat) { my $type; my $endDate; my $frequency; my $repeatOn; my $repeatStartOfWeek; my $unknown; ($type, $endDate, $frequency, $repeatOn, $repeatStartOfWeek, $unknown) = unpack "Cx n C C C C", $data; $data = substr $data, 8; # Chop off repeat part $record{repeat}{type} = $type; $record{repeat}{unknown} = $unknown; if ($endDate != 0xffff) { my $endYear; my $endMonth; my $endDay; $endDay = $endDate & 0x001f; # 5 bits $endMonth = ($endDate >> 5) & 0x000f; # 4 bits $endYear = ($endDate >> 9) & 0x007f; # 7 bits (years $endYear += 1904; # since 1904) $record{repeat}{end_day} = $endDay; $record{repeat}{end_month} = $endMonth; $record{repeat}{end_year} = $endYear; } $record{repeat}{frequency} = $frequency; if ($type == 2) { # "Weekly" repeat my $i; my @days; # Build an array of 7 elements (one for each # day of the week). Each element is set iff # the appointment repeats on that day. for ($i = 0; $i < 7; $i++) { if ($repeatOn & (1 << $i)) { $days[$i] = 1; } else { $days[$i] = 0; } } $record{repeat}{repeat_days} = [ @days ]; $record{repeat}{start_of_week} = $repeatStartOfWeek; # I don't know what this is, # but the Datebook app appears # to perform some hairy # calculations involving this. } elsif ($type == 3) { # "Monthly by day" repeat # If "weeknum" is 5, it means the last week of # the month $record{repeat}{weeknum} = int($repeatOn / 7); $record{repeat}{daynum} = $repeatOn % 7; } } if ($have_exceptions) { my $numExceptions; my @exceptions; $numExceptions = unpack "n", $data; $data = substr $data, 2; @exceptions = unpack "n" x $numExceptions, $data; $data = substr $data, 2 * $numExceptions; my $exception; foreach $exception (@exceptions) { my $year; my $month; my $day; $day = $exception & 0x001f; $month = ($exception >> 5) & 0x000f; $year = ($exception >> 9) & 0x007f; $year += 1904; push @{$record{exceptions}}, [ $day, $month, $year ]; } } my @fields = split /\0/, $data, -1; if ($have_description) { my $description; $description = shift @fields; $record{description} = $description; } if ($have_note) { my $note; $note = shift @fields; $record{note} = $note; } if ($have_location) { my $location; $location = shift @fields; $record{location} = $location; } my $other_data= join ("\0", @fields); if ($iscal && length ($other_data) >= 21 && substr ($other_data, 0, 4) eq 'Bd00') { my $len= unpack ('n', substr ($other_data, 4, 2)); if ($len+6 <= length ($other_data)) { my $tzdata= substr ($other_data, 6, $len); $other_data= substr ($other_data, $len+6); @{$record{timezone}}{qw(offset start_hour start_daynum start_weeknum start_month end_hour end_daynum end_weeknum end_month dst_adjustment country flags name)}= unpack ('n C8 n C2 a*', $tzdata); $record{timezone}{name} =~ s/\0$//; $record{timezone}{offset}= $record{timezone}{offset} -65536 if $record{timezone}{offset} > 32767; # signed short $record{timezone}{dst_adjustment}= $record{timezone}{dst_adjustment}-65536 if $record{timezone}{dst_adjustment} > 32767; # signed short $record{timezone}{custom}= ($record{timezone}{flags} & 0x80) ? 1 : 0; $record{timezone}{flags} &= 0x7f; $record{timezone}{data}= $tzdata; } } $record{other_data}= $other_data if $other_data ne ''; delete $record{data}; return \%record; } sub PackRecord { my $self = shift; my $record = shift; my $retval; my $rawDate; my $flags; my $iscal = ($self->{creator} eq 'PDat'); $rawDate = ($record->{day} & 0x001f) | (($record->{month} & 0x000f) << 5) | ((($record->{year} - 1904) & 0x007f) << 9); # XXX - Better to collect data first, then build flags. $flags = $record->{other_flags}; # $flags |= 0x8000 if $record->{when_changed}; # $flags |= 0x4000 if keys %{$record->{alarm}} ne (); # $flags |= 0x2000 if keys %{$record->{repeat}} ne (); # $flags |= 0x1000 if $record->{note} ne ""; # $flags |= 0x0800 if $#{$record->{exceptions}} >= 0; # $flags |= 0x0400 if $record->{description} ne ""; # $flags |= 0x0200 if $iscal && $record->{location} ne ""; # $retval = pack "C C C C n n", # $record->{start_hour}, # $record->{start_minute}, # $record->{end_hour}, # $record->{end_minute}, # $rawDate, # $flags; if ($record->{when_changed}) { $flags |= 0x8000; } my $alarm = undef; if (defined($record->{alarm}) && %{$record->{alarm}}) { $flags |= 0x4000; $alarm = pack "c C", $record->{alarm}{advance}, $record->{alarm}{unit}; } my $repeat = undef; if (defined($record->{repeat}) && %{$record->{repeat}}) { my $type; # Repeat type my $endDate = 0xffff; # No end date defined by default my $repeatOn = 0; my $repeatStartOfWeek = 0; $flags |= 0x2000; if (defined($record->{repeat}{end_day})) { # End date defined $endDate = ($record->{repeat}{end_day} & 0x001f) | (($record->{repeat}{end_month} & 0x000f) << 5) | ((($record->{repeat}{end_year} - 1904) & 0x007f) << 9); } if ($record->{repeat}{type} == 2) { # Weekly repeat my $i; $repeatOn = 0; for ($i = 0; $i < 7; $i++) { if ($record->{repeat}{repeat_days}[$i]) { $repeatOn |= (1 << $i); } } $repeatStartOfWeek = $record->{repeat}{start_of_week}; } elsif ($record->{repeat}{type} == 3) { # "Monthly by day" repeat my $weeknum = $record->{repeat}{weeknum}; if ($weeknum > 5) { $weeknum = 5; } $repeatOn = ($record->{repeat}{weeknum} * 7) + ($record->{repeat}{daynum} % 7); } $repeat = pack "Cx n C C C C", $record->{repeat}{type}, $endDate, $record->{repeat}{frequency}, $repeatOn, $repeatStartOfWeek, $record->{repeat}{unknown}; } my $exceptions = undef; if (defined($record->{exceptions}) && @{$record->{exceptions}}) { my $numExceptions = $#{$record->{exceptions}} + 1; my $exception; $flags |= 0x0800; $exceptions = pack("n", $numExceptions); foreach $exception (@{$record->{exceptions}}) { my $day = $exception->[0]; my $month = $exception->[1]; my $year = $exception->[2]; $exceptions .= pack("n", ($day & 0x001f) | (($month & 0x000f) << 5) | ((($year - 1904) & 0x007f) << 9)); } } my $description = undef; if (defined($record->{description}) && ($record->{description} ne "")) { $flags |= 0x0400; $description = $record->{description} . "\0"; } my $note = undef; if (defined($record->{note}) && ($record->{note} ne "")) { $flags |= 0x1000; $note = $record->{note} . "\0"; } my $location = undef; if ($iscal && defined($record->{location}) && ($record->{location} ne "")) { $flags |= 0x0200; $location = $record->{location} . "\0"; } $retval = pack "C C C C n n", $record->{start_hour}, $record->{start_minute}, $record->{end_hour}, $record->{end_minute}, $rawDate, $flags; $retval .= $alarm if defined($alarm); $retval .= $repeat if defined($repeat); $retval .= $exceptions if defined($exceptions); $retval .= $description if defined($description); $retval .= $note if defined($note); $retval .= $location if defined($location); if ($iscal && $record->{timezone}) { my $tzflags= $record->{timezone}{flags}; $tzflags |= 0x80 if $record->{timezone}{custom}; my $tzdata = pack ('n C8 n C2', @{$record->{timezone}}{qw(offset start_hour start_daynum start_weeknum start_month end_hour end_daynum end_weeknum end_month dst_adjustment country)}, $tzflags); $tzdata .= "$record->{timezone}{name}\0"; $retval .= 'Bd00'; $retval .= pack ('n', length ($tzdata)); $retval .= $tzdata; } $retval .= $record->{other_data} if exists $record->{other_data}; return $retval; } 1; __END__ =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::PDB(3) Palm::StdAppInfo(3) =cut p5-Palm-1.012/lib/DateTime.pm0000644000076500000240000000551511341055215014511 0ustar brianstaff# Palm::DateTime.pm # # Package to deal with various (palm) date/time formats.. # # Copyright (C) 2001-2002, Alessandro Zummo # You may distribute this file under the terms of the Artistic # License, as specified in the README file. # # Data types: # # secs - Seconds since whatever time the system considers to be The Epoch # dlptime - PalmOS DlpDateTimeType (raw) # datetime - PalmOS DateTimeType (raw) # palmtime - Decoded date/time package Palm::DateTime; use strict; use Exporter; use POSIX; use vars qw($VERSION); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; @Palm::DateTime::ISA = qw( Exporter ); @Palm::DateTime::EXPORT = qw( datetime_to_palmtime dlptime_to_palmtime palmtime_to_dlptime secs_to_dlptime dlptime_to_secs palmtime_to_secs secs_to_palmtime palmtime_to_ascii palmtime_to_iso8601 ); sub datetime_to_palmtime { my ($datetime) = @_; my $palmtime = {}; @$palmtime { 'second', 'minute', 'hour', 'day', 'month', 'year', 'wday', } = unpack("nnnnnnn", $datetime); return $palmtime; } sub dlptime_to_palmtime { my ($dlptime) = @_; my $palmtime = {}; @$palmtime { 'year', 'month', 'day', 'hour', 'minute', 'second', } = unpack("nCCCCCx", $dlptime); return $palmtime; } # This one takes a palmtime structure, which must be completely filled in. # A future version might allow to specify only some of the fields. sub palmtime_to_dlptime { my ($palmtime) = @_; return pack("nCCCCCx", @$palmtime { 'year', 'month', 'day', 'hour', 'minute', 'second', }); } sub secs_to_dlptime { my ($secs) = @_; return palmtime_to_dlptime(secs_to_palmtime($secs)); } sub dlptime_to_secs { my ($dlptime) = @_; return palmtime_to_secs(dlptime_to_palmtime($dlptime)); } sub palmtime_to_secs { my ($palmtime) = @_; return POSIX::mktime( $palmtime->{'second'}, $palmtime->{'minute'}, $palmtime->{'hour'}, $palmtime->{'day'}, $palmtime->{'month'} - 1, # Palm used 1-12, mktime needs 0-11 $palmtime->{'year'} - 1900, 0, 0, -1); } sub secs_to_palmtime { my ($secs) = @_; my $palmtime = {}; @$palmtime { 'second', 'minute', 'hour', 'day', 'month', 'year' } = localtime($secs); # Fix values $palmtime->{'year'} += 1900; $palmtime->{'month'} += 1; return $palmtime; } # This one gives out something like 20011116200051 sub palmtime_to_ascii { my ($palmtime) = @_; return sprintf("%4d%02d%02d%02d%02d%02d", @$palmtime { 'year', 'month', 'day', 'hour', 'minute', 'second', }); } # IS8601 compliant: 2001-11-16T20:00:51Z # GMT timezone ("Z") is assumed. XXX ? sub palmtime_to_iso8601 { my ($palmtime) = @_; return sprintf("%4d-%02d-%02dT%02d:%02d:%02dZ", @$palmtime { 'year', 'month', 'day', 'hour', 'minute', 'second', }); } 1; p5-Palm-1.012/lib/Mail.pm0000644000076500000240000002335511341055215013701 0ustar brianstaff# Palm::Mail.pm # # Perl class for dealing with Palm Mail databases. # # Copyright (C) 1999, 2000, Andrew Arensburger. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. use strict; package Palm::Mail; use Palm::Raw(); use Palm::StdAppInfo(); use vars qw( $VERSION @ISA ); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; @ISA = qw( Palm::StdAppInfo Palm::Raw ); =head1 NAME Palm::Mail - Handler for Palm Mail databases. =head1 SYNOPSIS use Palm::Mail; =head1 DESCRIPTION The Mail PDB handler is a helper class for the Palm::PDB package. It parses Mail databases. =head2 AppInfo block The AppInfo block begins with standard category support. See L for details. Other fields include: $pdb->{appinfo}{sortOrder} $pdb->{appinfo}{unsent} $pdb->{appinfo}{sigOffset} I don't know what these are. =head2 Sort block $pdb->{sort} This is a scalar, the raw data of the sort block. =head2 Records $record = $pdb->{records}[N] $record->{year} $record->{month} $record->{day} $record->{hour} $record->{minute} The message's timestamp. $record->{is_read} This is defined and true iff the message has been read. $record->{has_signature} For outgoing messages, this is defined and true iff the message should have a signature attached. The signature itself is stored in the "Saved Preferences.prc" database, and is of type "mail" with ID 2. $record->{confirm_read} If this is defined and true, then the sender requests notification when the message has been read. $record->{confirm_delivery} If this is defined and true, then the sender requests notification when the message has been delivered. $record->{priority} An integer in the range 0-2, for high, normal, or low priority, respectively. $record->{addressing} An integer in the range 0-2, indicating the addressing type: To, Cc, or Bcc respectively. I don't know what this means. $record->{subject} $record->{from} $record->{to} $record->{cc} $record->{bcc} $record->{replyTo} $record->{sentTo} Strings, the various header fields. $record->{body} A string, the body of the message. =head1 METHODS =cut #' sub import { &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "mail", "DATA" ], ); } =head2 new $pdb = new Palm::Mail; Create a new PDB, initialized with the various Palm::Mail fields and an empty record list. Use this method if you're creating a Mail PDB from scratch. =cut #' sub new { my $classname = shift; my $self = $classname->SUPER::new(@_); # Create a generic PDB. No need to rebless it, # though. $self->{name} = "MailDB"; # Default $self->{creator} = "mail"; $self->{type} = "DATA"; $self->{attributes}{resource} = 0; # The PDB is not a resource database by # default, but it's worth emphasizing, # since MailDB is explicitly not a PRC. # Initialize the AppInfo block $self->{appinfo} = { sortOrder => undef, # XXX - ? unsent => undef, # XXX - ? sigOffset => 0, # XXX - ? }; # Add the standard AppInfo block stuff &Palm::StdAppInfo::seed_StdAppInfo($self->{appinfo}); $self->{sort} = undef; # Empty sort block $self->{records} = []; # Empty list of records return $self; } =head2 new_Record $record = $pdb->new_Record; Creates a new Mail record, with blank values for all of the fields. C does B add the new record to C<$pdb>. For that, you want C<$pdb-Eappend_Record>. Note: the time given by the C, C, C, C, and C fields in the new record are initialized to the time when the record was created. They should be reset to the time when the message was sent. =cut sub new_Record { my $classname = shift; my $retval = $classname->SUPER::new_Record(@_); # Set the date and time on this message to today and now. This # is arguably bogus, since the Date: header on a message ought # to represent the time when the message was sent, rather than # the time when the user started composing it, but this is # better than nothing. ($retval->{year}, $retval->{month}, $retval->{day}, $retval->{hour}, $retval->{minute}) = (localtime(time))[5,4,3,2,1]; $retval->{is_read} = 0; # Message hasn't been read yet. # No delivery service notification (DSN) by default. $retval->{confirm_read} = 0; $retval->{confirm_delivery} = 0; $retval->{priority} = 1; # Normal priority $retval->{addressing} = 0; # XXX - ? # All header fields empty by default. $retval->{from} = undef; $retval->{to} = undef; $retval->{cc} = undef; $retval->{bcc} = undef; $retval->{replyTo} = undef; $retval->{sentTo} = undef; $retval->{body} = ""; return $retval; } # ParseAppInfoBlock # Parse the AppInfo block for Mail databases. sub ParseAppInfoBlock { my $self = shift; my $data = shift; my $dirtyAppInfo; my $sortOrder; my $unsent; my $sigOffset; # XXX - Offset of signature? my $appinfo = {}; my $std_len; # Get the standard parts of the AppInfo block $std_len = &Palm::StdAppInfo::parse_StdAppInfo($appinfo, $data); $data = $appinfo->{other}; # Look at the non-category part # Get the rest of the AppInfo block my $unpackstr = # Argument to unpack() "x2" . # Padding "n" . # Dirty AppInfo (what is this?) "Cx" . # Sort order "N" . # Unique ID of unsent message (what is this?) "n"; # Signature offset ($dirtyAppInfo, $sortOrder, $unsent, $sigOffset) = unpack $unpackstr, $data; $appinfo->{dirty_AppInfo} = $dirtyAppInfo; $appinfo->{sort_order} = $sortOrder; $appinfo->{unsent} = $unsent; $appinfo->{sig_offset} = $sigOffset; return $appinfo; } sub PackAppInfoBlock { my $self = shift; my $retval; # Pack the non-category part of the AppInfo block $self->{appinfo}{other} = pack "x2 n Cx N n", $self->{appinfo}{dirty_AppInfo}, $self->{appinfo}{sort_order}, $self->{appinfo}{unsent}, $self->{appinfo}{sig_offset}; # Pack the AppInfo block $retval = &Palm::StdAppInfo::pack_StdAppInfo($self->{appinfo}); return $retval; } sub ParseRecord { my $self = shift; my %record = @_; my $data = $record{data}; delete $record{offset}; # This is useless delete $record{data}; my $date; my $hour; my $minute; my $flags; my $subject; my $from; my $to; my $cc; my $bcc; my $replyTo; my $sentTo; my $body; my $extra; # Extra field after body. I don't know what # it is. my $unpackstr = "n" . # Date "C" . # Hour "C" . # Minute "n"; # Flags ($date, $hour, $minute, $flags) = unpack $unpackstr, $data; my $year; my $month; my $day; if ($date != 0) { $day = $date & 0x001f; # 5 bits $month = ($date >> 5) & 0x000f; # 4 bits $year = ($date >> 9) & 0x007f; # 7 bits (years since 1904) $year += 1904; $record{year} = $year; $record{month} = $month; $record{day} = $day; $record{hour} = $hour; $record{minute} = $minute; } my $is_read = ($flags & 0x8000); my $has_signature = ($flags & 0x4000); my $confirm_read = ($flags & 0x2000); my $confirm_delivery = ($flags & 0x1000); my $priority = ($flags >> 10) & 0x03; my $addressing = ($flags >> 8) & 0x03; # The signature is problematic: it's not stored in # "MailDB.pdb": it's actually in "Saved Preferences.pdb". Work # around this somehow; either read it from "Saved # Preferences.pdb" or, more simply, just read ~/.signature if # it exists. $record{is_read} = 1 if $is_read; $record{has_signature} = 1 if $has_signature; $record{confirm_read} = 1 if $confirm_read; $record{confirm_delivery} = 1 if $confirm_delivery; $record{priority} = $priority; $record{addressing} = $addressing; my $fields = substr $data, 6; my @fields = split /\0/, $fields; ($subject, $from, $to, $cc, $bcc, $replyTo, $sentTo, $body, $extra) = @fields; # Clean things up a bit # Multi-line values are bad in these headers. Replace newlines # with commas. Ideally, we'd use arrays for multiple # recipients, but that would involve parsing addresses, which # is non-trivial. Besides, most likely we'll just wind up # sending these strings as they are to 'sendmail', which is # better equipped to parse them. $to =~ s/\s*\n\s*(?!$)/, /gs if defined($to); $cc =~ s/\s*\n\s*(?!$)/, /gs if defined($cc); $bcc =~ s/\s*\n\s*(?!$)/, /gs if defined($bcc); $replyTo =~ s/\s*\n\s*(?!$)/, /gs if defined($replyTo); $sentTo =~ s/\s*\n\s*(?!$)/, /gs if defined($sentTo); $record{subject} = $subject; $record{from} = $from; $record{to} = $to; $record{cc} = $cc; $record{bcc} = $bcc; $record{replyTo} = $replyTo; $record{sentTo} = $sentTo; $record{body} = $body; $record{extra} = $extra; return \%record; } sub PackRecord { my $self = shift; my $record = shift; my $retval; my $rawDate; my $flags; $rawDate = ($record->{day} & 0x001f) | (($record->{month} & 0x000f) << 5) | ((($record->{year} - 1904) & 0x07f) << 9); $flags = 0; $flags |= 0x8000 if $record->{is_read}; $flags |= 0x4000 if $record->{has_signature}; $flags |= 0x2000 if $record->{confirm_read}; $flags |= 0x1000 if $record->{confirm_delivery}; $flags |= (($record->{priority} & 0x03) << 10); $flags |= (($record->{addressing} & 0x03) << 8); $retval = pack "n C C n", $rawDate, $record->{hour}, $record->{minute}, $flags; # can't leave any of these undef or join() complains foreach (qw(subject from to cc bcc replyTo sentTo body) ) { $record->{$_} ||= ""; } $retval .= join "\0", $record->{subject}, $record->{from}, $record->{to}, $record->{cc}, $record->{bcc}, $record->{replyTo}, $record->{sentTo}, $record->{body}; $retval .= "\0"; return $retval; } 1; __END__ =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::PDB(3) Palm::StdAppInfo(3) =cut p5-Palm-1.012/lib/Memo.pm0000644000076500000240000000754511341055215013717 0ustar brianstaff# Palm::Memo.pm # # Perl class for dealing with Palm Memo databases. # # Copyright (C) 1999, 2000, Andrew Arensburger. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. use strict; package Palm::Memo; use Palm::Raw(); use Palm::StdAppInfo(); use vars qw( $VERSION @ISA ); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; @ISA = qw( Palm::StdAppInfo Palm::Raw ); =head1 NAME Palm::Memo - Handler for Palm Memo databases. =head1 SYNOPSIS use Palm::Memo; =head1 DESCRIPTION The Memo PDB handler is a helper class for the Palm::PDB package. It parses Memo databases. =head2 AppInfo block The AppInfo block begins with standard category support. See L for details. Other fields include: $pdb->{appinfo}{sortOrder} I don't know what this is. =head2 Sort block $pdb->{sort} This is a scalar, the raw data of the sort block. =head2 Records $record = $pdb->{records}[N] $record->{data} A string, the text of the memo. =cut #' sub import { &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "memo", "DATA" ], ); } =head2 new $pdb = new Palm::Memo; Create a new PDB, initialized with the various Palm::Memo fields and an empty record list. Use this method if you're creating a Memo PDB from scratch. =cut #' sub new { my $classname = shift; my $self = $classname->SUPER::new(@_); # Create a generic PDB. No need to rebless it, # though. $self->{name} = "MemoDB"; # Default $self->{creator} = "memo"; $self->{type} = "DATA"; $self->{attributes}{resource} = 0; # The PDB is not a resource database by # default, but it's worth emphasizing, # since MemoDB is explicitly not a PRC. # Initialize the AppInfo block $self->{appinfo} = { sortOrder => undef, # XXX - ? }; # Add the standard AppInfo block stuff &Palm::StdAppInfo::seed_StdAppInfo($self->{appinfo}); # Give the PDB a blank sort block $self->{sort} = undef; # Give the PDB an empty list of records $self->{records} = []; return $self; } =head2 new_Record $record = $pdb->new_Record; Creates a new Memo record, with blank values for all of the fields. C does B add the new record to C<$pdb>. For that, you want C<$pdb-Eappend_Record>. =cut sub new_Record { my $classname = shift; my $retval = $classname->SUPER::new_Record(@_); $retval->{data} = ""; return $retval; } # ParseAppInfoBlock # Parse the AppInfo block for Memo databases. sub ParseAppInfoBlock { my $self = shift; my $data = shift; my $sortOrder; my $i; my $appinfo = {}; my $std_len; # Get the standard parts of the AppInfo block $std_len = &Palm::StdAppInfo::parse_StdAppInfo($appinfo, $data); $data = $appinfo->{other}; # Look at the non-category part # Get the rest of the AppInfo block my $unpackstr = # Argument to unpack() "x4" . # Padding "C"; # Sort order ($sortOrder) = unpack $unpackstr, $data; $appinfo->{sortOrder} = $sortOrder; return $appinfo; } sub PackAppInfoBlock { my $self = shift; my $retval; my $i; # Pack the non-category part of the AppInfo block $self->{appinfo}{other} = pack("x4 C x1", $self->{appinfo}{sortOrder}); # Pack the AppInfo block $retval = &Palm::StdAppInfo::pack_StdAppInfo($self->{appinfo}); return $retval; } sub PackSortBlock { # XXX return undef; } sub ParseRecord { my $self = shift; my %record = @_; delete $record{offset}; # This is useless $record{data} =~ s/\0$//; # Trim trailing NUL return \%record; } sub PackRecord { my $self = shift; my $record = shift; return $record->{data} . "\0"; # Add the trailing NUL } 1; __END__ =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::PDB(3) Palm::StdAppInfo(3) =cut p5-Palm-1.012/lib/Palm.pm0000644000076500000240000000343211341055215013702 0ustar brianstaff# Palm.pm # # Perl module for reading and writing Palm databases (both PDB and PRC). # # Copyright (C) 1999, 2000, Andrew Arensburger. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. use strict; use warnings; package Palm; use vars qw( $VERSION ); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; =head1 NAME Palm - Palm OS utility functions =head1 SYNOPSIS =head1 DESCRIPTION =head1 FUNCTIONS =cut my $EPOCH_1904 = 2082844800; # Difference between Palm's # epoch (Jan. 1, 1904) and # Unix's epoch (Jan. 1, 1970), # in seconds. =head2 palm2epoch my @parts = localtime( palm2epoch($palmtime) ); Converts a PalmOS timestamp to a Unix Epoch time. Note, however, that PalmOS time is in the timezone of the Palm itself while Epoch is defined to be in the GMT timezone. Further conversion may be necessary. =cut sub palm2epoch { return $_[0] - $EPOCH_1904; } =head2 epoch2palm my $palmtime = epoch2palm( time() ); Converts Unix epoch time to Palm OS time. =cut sub epoch2palm { return $_[0] + $EPOCH_1904; } =head2 mkpdbname $PDB->Write( mkpdbname($PDB->{name}) ); Convert a PalmOS database name to a 7-bit ASCII representation. Native Palm database names can be found in ISO-8859-1 encoding. This encoding isn't going to generate the most portable of filenames and, in particular, ColdSync databases use this representation. =cut sub mkpdbname { my $name = shift; $name =~ s![%/\x00-\x19\x7f-\xff]!sprintf("%%%02X",ord($&))!ge; return $name; } =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::PDB(3) p5-Palm-1.012/lib/PDB.pm0000644000076500000240000013476311341055215013432 0ustar brianstaff# PDB.pm # # Perl module for reading and writing Palm databases (both PDB and PRC). # # Copyright (C) 1999, 2000, Andrew Arensburger. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. # A Palm database file (either .pdb or .prc) has the following overall # structure: # Header # Index header # Record/resource index # Two NUL(?) bytes # Optional AppInfo block # Optional sort block # Records/resources # See http://www.palmos.com/dev/tech/docs/fileformats.zip # for details. use strict; package Palm::PDB; use vars qw( $VERSION %PDBHandlers %PRCHandlers ); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; =head1 NAME Palm::PDB - Parse Palm database files. =head1 SYNOPSIS use Palm::PDB; use SomeHelperClass; $pdb = new Palm::PDB; $pdb->Load("myfile.pdb"); # Manipulate records in $pdb $pdb->Write("myotherfile.pdb"); (Note: yes, you do want to use C, even if you're dealing with some other type of database. $pdb will be reblessed to the appropriate type by C<$pdb-ELoad>.) =head1 DESCRIPTION The Palm::PDB module provides a framework for reading and writing database files for use on PalmOS devices such as the PalmPilot. It can read and write both Palm Database (C<.pdb>) and Palm Resource (C<.prc>) files. By itself, the PDB module is not terribly useful; it is intended to be used in conjunction with supplemental modules for specific types of databases, such as Palm::Raw or Palm::Memo. The Palm::PDB module encapsulates the common work of parsing the structure of a Palm database. The L function reads the file, then passes the individual chunks (header, records, etc.) to application-specific functions for processing. Similarly, the L function calls application-specific functions to get the individual chunks, then writes them to a file. =head1 METHODS =cut use constant dmRecordIDReservedRange => 1; # The range of upper bits in the database's # uniqueIDSeed from 0 to this number are # reserved and not randomly picked when a #database is created. my $EPOCH_1904 = 2082844800; # Difference between Palm's # epoch (Jan. 1, 1904) and # Unix's epoch (Jan. 1, 1970), # in seconds. my $HeaderLen = 32+2+2+(9*4); # Size of database header my $RecIndexHeaderLen = 6; # Size of record index header my $IndexRecLen = 8; # Length of record index entry my $IndexRsrcLen = 10; # Length of resource index entry # XXX Should we 'use constant' for the above vars? %PDBHandlers = (); # Record handler map %PRCHandlers = (); # Resource handler map =head2 new $new = new Palm::PDB(); Creates a new PDB. $new is a reference to an anonymous hash. Some of its elements have special significance. See L. =cut sub new { my $class = shift; my $params = shift; my $self = {}; # Initialize the PDB. These values are just defaults, of course. $self->{'name'} = $params->{'name'} || ""; $self->{'attributes'} = $params->{'attributes'} || {}; $self->{'version'} = $params->{'version'} || 0; my $now = time; $self->{'ctime'} = $params->{'ctime'} || $now; $self->{'mtime'} = $params->{'mtime'} || $now; $self->{'baktime'} = $params->{'baktime'} || -$EPOCH_1904; $self->{'modnum'} = $params->{'modnum'} || 0; $self->{'type'} = $params->{'type'} || "\0\0\0\0"; $self->{'creator'} = $params->{'creator'} || "\0\0\0\0"; $self->{'uniqueIDseed'} = $params->{'uniqueIDseed'} || 0; $self->{"2NULs"} = "\0\0"; # This will be set when any elements of the object are changed $self->{'dirty'} = 0; # Calculate a proper uniqueIDseed if the user has not provided # a correct one. if ($self->{'uniqueIDseed'} <= ((dmRecordIDReservedRange + 1) << 12)) { my $uniqueIDseed = 0; do { $uniqueIDseed = int(rand(0x0FFF)); } while ($uniqueIDseed <= dmRecordIDReservedRange); $self->{'uniqueIDseed'} = $uniqueIDseed << 12; $self->{'uniqueIDseed'} &= 0x00FFF000; # Isolate the upper 12 seed bits. } bless $self, $class; return $self; } =head2 RegisterPDBHandlers &Palm::PDB::RegisterPDBHandlers("classname", typespec...); Typically: &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "FooB", "DATA" ], ); The $pdb->L method acts as a virtual constructor. When it reads the header of a C<.pdb> file, it looks up the file's creator and type in a set of tables, and reblesses $pdb into a class capable of parsing the application-specific parts of the file (AppInfo block, records, etc.) RegisterPDBHandlers() adds entries to these tables; it says that any file whose creator and/or type match any of the Is (there may be several) should be reblessed into the class I. Note that RegisterPDBHandlers() applies only to record databases (C<.pdb> files). For resource databases, see L. RegisterPDBHandlers() is typically called in the import() function of a helper class. In this case, the class is registering itself, and it is simplest just to use C<__PACKAGE__> for the package name: package PalmFoo; use Palm::PDB; sub import { &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "FooZ", "DATA" ] ); } A I can be either a string, or an anonymous array with two elements. If it is an anonymous array, then the first element is the file's creator; the second element is its type. If a I is a string, it is equivalent to specifying that string as the database's creator, and a wildcard as its type. The creator and type should be either four-character strings, or the empty string. An empty string represents a wildcard. Thus: &Palm::PDB::RegisterPDBHandlers("MyClass", [ "fOOf", "DATA" ], [ "BarB", "" ], [ "", "BazQ" ], "Fred" ); Class MyClass will handle: =over 4 =item Z<> Databases whose creator is C and whose type is C. =item Z<> Databases whose creator is C, of any type. =item Z<> Databases with any creator whose type is C. =item Z<> Databases whose creator is C, of any type. =back =for html =cut #' <-- For Emacs. sub RegisterPDBHandlers { my $handler = shift; # Name of class that'll handle # these databases my @types = @_; my $item; foreach $item (@types) { if (ref($item) eq "ARRAY") { $PDBHandlers{$item->[0]}{$item->[1]} = $handler; } else { $PDBHandlers{$item}{""} = $handler; } } } =head2 RegisterPRCHandlers &Palm::PDB::RegisterPRCHandlers("classname", typespec...); Typically: &Palm::PDB::RegisterPRCHandlers(__PACKAGE__, [ "FooZ", "CODE" ], ); RegisterPRCHandlers() is similar to L, but specifies a class to handle resource database (C<.prc>) files. A class for parsing applications should begin with: package PalmApps; use Palm::PDB; sub import { &Palm::PDB::RegisterPRCHandlers(__PACKAGE__, [ "", "appl" ] ); } =cut sub RegisterPRCHandlers { my $handler = shift; # Name of class that'll handle # these databases my @types = @_; my $item; foreach $item (@types) { if (ref($item) eq "ARRAY") { $PRCHandlers{$item->[0]}{$item->[1]} = $handler; } else { $PRCHandlers{$item}{""} = $handler; } } } =head2 Load $pdb->Load($filename); Reads the file C<$filename>, parses it, reblesses $pdb to the appropriate class, and invokes appropriate methods to parse the application-specific parts of the database (see L). C<$filename> may also be an open file handle (as long as it's seekable). This allows for manipulating databases in memory structures. Load() uses the Is given to RegisterPDBHandlers() and RegisterPRCHandlers() when deciding how to rebless $pdb. For record databases, it uses the Is passed to RegisterPDBHandlers(), and for resource databases, it uses the Is passed to RegisterPRCHandlers(). Load() looks for matching Is in the following order, from most to least specific: =over 4 =item 1 A I that specifies both the database's creator and its type exactly. =item 2 A I that specifies the database's type and has a wildcard for the creator (this is rarely used). =item 3 A I that specifies the database's creator and has a wildcard for the type. =item 4 A I that has wildcards for both the creator and type. =back =for html Thus, if the database has creator "FooZ" and type "DATA", Load() will first look for "FooZ"/"DATA", then ""/"DATA", then "FooZ"/"", and finally will fall back on ""/"" (the universal default). After Load() returns, $pdb may contain the following fields: =over =item $pdb-E{Z<>"name"Z<>} The name of the database. =item $pdb-E{Z<>"attributes"Z<>}{Z<>"ResDB"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"ReadOnly"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"AppInfoDirty"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"Backup"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"OKToInstallNewer"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"ResetAfterInstall"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"CopyPrevention"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"Stream"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"Hidden"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"LaunchableData"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"Recyclable"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"Bundle"Z<>} =item $pdb-E{Z<>"attributes"Z<>}{Z<>"Open"Z<>} These are the attribute flags from the database header. Each is true iff the corresponding flag is set. The "LaunchableData" attribute is set on PQAs. =item $pdb-E{Z<>"version"Z<>} The database's version number. An integer. =item $pdb-E{Z<>"ctime"Z<>} =item $pdb-E{Z<>"mtime"Z<>} =item $pdb-E{Z<>"baktime"Z<>} The database's creation time, last modification time, and time of last backup, in Unix C format (seconds since Jan. 1, 1970). =item $pdb-E{Z<>"modnum"Z<>} The database's modification number. An integer. =item $pdb-E{Z<>"type"Z<>} The database's type. A four-character string. =item $pdb-E{Z<>"creator"Z<>} The database's creator. A four-character string. =item $pdb-E{Z<>"uniqueIDseed"Z<>} The database's unique ID seed. An integer. =item $pdb-E{Z<>"2NULs"Z<>} The two NUL bytes that appear after the record index and the AppInfo block. Included here because every once in a long while, they are not NULs, for some reason. =item $pdb-E{Z<>"appinfo"Z<>} The AppInfo block, as returned by the $pdb->ParseAppInfoBlock() helper method. =item $pdb-E{Z<>"sort"Z<>} The sort block, as returned by the $pdb->ParseSortBlock() helper method. =item @{$pdb-E{Z<>"records"Z<>}} The list of records in the database, as returned by the $pdb->ParseRecord() helper method. Resource databases do not have this. =item @{$pdb-E{Z<>"resources"Z<>}} The list of resources in the database, as returned by the $pdb->ParseResource() helper method. Record databases do not have this. =back All of these fields may be set by hand, but should conform to the format given above. =for html =cut #' # _open sub _open { my($self, $mode, $fname) = @_; my $handle; if (ref($fname)) { # Already a filehandle if (ref($fname) eq 'GLOB' or UNIVERSAL::isa($fname,"IO::Seekable")) { $handle = $fname; } # Probably a reference to a SCALAR else { unless (eval 'open $handle, $mode, $fname') { if ($@ ne '') { die "Open of \"$fname\" unsupported: $@\n"; } else { die "Can't open \"$fname\": $!\n"; } } } } else { # Before 5.6.0 "autovivified file handles" don't exist eval 'use IO::File; $handle = new IO::File' if $] < 5.006; open $handle, "$mode $fname" or die "Can't open \"$fname\": $!\n"; } return $handle; } # Load sub Load { my $self = shift; my $fname = shift; # Filename to read from my $buf; # Buffer into which to read stuff my $handle = $self->_open('<', $fname); return undef unless defined $handle; binmode $handle; # Read as binary file under MS-DOS # Get the size of the file. It'll be useful later seek $handle, 0, 2; # 2 == SEEK_END. Seek to the end. $self->{_size} = tell $handle; seek $handle, 0, 0; # 0 == SEEK_START. Rewind to the beginning. # Read header my $name; my $attributes; my $version; my $ctime; my $mtime; my $baktime; my $modnum; my $appinfo_offset; my $sort_offset; my $type; my $creator; my $uniqueIDseed; read $handle, $buf, $HeaderLen; # Read the PDB header # Split header into its component fields ($name, $attributes, $version, $ctime, $mtime, $baktime, $modnum, $appinfo_offset, $sort_offset, $type, $creator, $uniqueIDseed) = unpack "a32 n n N N N N N N a4 a4 N", $buf; # database names must include a terminating NUL. die "bogus database name! is this really a PalmOS file?" unless $name =~ /.+\0/; ($self->{name} = $name) =~ s/\0.*$//; $self->{attributes}{resource} = 1 if $attributes & 0x0001; $self->{attributes}{"read-only"} = 1 if $attributes & 0x0002; $self->{attributes}{"AppInfo dirty"} = 1 if $attributes & 0x0004; $self->{attributes}{backup} = 1 if $attributes & 0x0008; $self->{attributes}{"OK newer"} = 1 if $attributes & 0x0010; $self->{attributes}{reset} = 1 if $attributes & 0x0020; $self->{attributes}{open} = 1 if $attributes & 0x8000; $self->{attributes}{launchable} = 1 if $attributes & 0x0200; # Attribute names as of PalmOS 5.0 ( see /Core/System/DataMgr.h ) $self->{'attributes'}{'ResDB'} = 1 if $attributes & 0x0001; $self->{'attributes'}{'ReadOnly'} = 1 if $attributes & 0x0002; $self->{'attributes'}{'AppInfoDirty'} = 1 if $attributes & 0x0004; $self->{'attributes'}{'Backup'} = 1 if $attributes & 0x0008; $self->{'attributes'}{'OKToInstallNewer'} = 1 if $attributes & 0x0010; $self->{'attributes'}{'ResetAfterInstall'} = 1 if $attributes & 0x0020; $self->{'attributes'}{'CopyPrevention'} = 1 if $attributes & 0x0040; $self->{'attributes'}{'Stream'} = 1 if $attributes & 0x0080; $self->{'attributes'}{'Hidden'} = 1 if $attributes & 0x0100; $self->{'attributes'}{'LaunchableData'} = 1 if $attributes & 0x0200; $self->{'attributes'}{'Recyclable'} = 1 if $attributes & 0x0400; $self->{'attributes'}{'Bundle'} = 1 if $attributes & 0x0800; $self->{'attributes'}{'Open'} = 1 if $attributes & 0x8000; $self->{version} = $version; $self->{ctime} = $ctime - $EPOCH_1904; $self->{mtime} = $mtime - $EPOCH_1904; $self->{baktime} = $baktime - $EPOCH_1904; $self->{modnum} = $modnum; # _appinfo_offset and _sort_offset are private fields $self->{_appinfo_offset} = $appinfo_offset; $self->{_sort_offset} = $sort_offset; $self->{type} = $type; $self->{creator} = $creator; $self->{uniqueIDseed} = $uniqueIDseed; # XXX strictly speaking, ctime/mtime/baktime values before 1990 are quite # unlikely. Palm was founded in 1992, so even allowing for some prototypes. # This is another way one could detect bogus databases. if( $self->{_appinfo_offset} > $self->{_size} ) { die "AppInfo block offset beyond end of file!"; } if( $self->{_sort_offset} > $self->{_size} ) { die "Sort block offset beyond end of file!"; } # Rebless this PDB object, depending on its type and/or # creator. This allows us to magically invoke the proper # &Parse*() function on the various parts of the database. # Look for most specific handlers first, least specific ones # last. That is, first look for a handler that deals # specifically with this database's creator and type, then for # one that deals with this database's creator and any type, # and finally for one that deals with anything. my $handler; if ($self->{attributes}{resource} || $self->{'attributes'}{'ResDB'}) { # Look among resource handlers $handler = $PRCHandlers{$self->{creator}}{$self->{type}} || $PRCHandlers{undef}{$self->{type}} || $PRCHandlers{$self->{creator}}{""} || $PRCHandlers{""}{""}; } else { # Look among record handlers $handler = $PDBHandlers{$self->{creator}}{$self->{type}} || $PDBHandlers{""}{$self->{type}} || $PDBHandlers{$self->{creator}}{""} || $PDBHandlers{""}{""}; } if (defined($handler)) { bless $self, $handler; } else { # XXX - This should probably return 'undef' or something, # rather than die. die "No handler defined for creator \"$creator\", type \"$type\"\n"; } ## Read record/resource index # Read index header read $handle, $buf, $RecIndexHeaderLen; my $next_index; my $numrecs; ($next_index, $numrecs) = unpack "N n", $buf; $self->{_numrecs} = $numrecs; # Read the index itself if ($self->{attributes}{resource} || $self->{'attributes'}{'ResDB'}) { &_load_rsrc_index($self, $handle); } else { &_load_rec_index($self, $handle); } # Read the two NUL bytes # XXX - Actually, these are bogus. They don't appear in the # spec. The Right Thing to do is to ignore them, and use the # specified or calculated offsets, if they're sane. Sane == # appears later than the current position. # read $handle, $buf, 2; # $self->{"2NULs"} = $buf; # Read AppInfo block, if it exists if ($self->{_appinfo_offset} != 0) { &_load_appinfo_block($self, $handle); } # Read sort block, if it exists if ($self->{_sort_offset} != 0) { &_load_sort_block($self, $handle); } # Read record/resource list if ($self->{attributes}{resource} || $self->{'attributes'}{'ResDB'}) { &_load_resources($self, $handle); } else { &_load_records($self, $handle); } # These keys were needed for parsing the file, but are not # needed any longer. Delete them. delete $self->{_index}; delete $self->{_numrecs}; delete $self->{_appinfo_offset}; delete $self->{_sort_offset}; delete $self->{_size}; $self->{'dirty'} = 0; return $self; } # _load_rec_index # Private function. Read the record index, for a record database sub _load_rec_index { my $pdb = shift; my $fh = shift; # Input file handle my $i; my $lastoffset = 0; # Read each record index entry in turn for ($i = 0; $i < $pdb->{_numrecs}; $i++) { my $buf; # Input buffer # Read the next record index entry my $offset; my $attributes; my @id; # Raw ID my $id; # Numerical ID my $entry = {}; # Parsed index entry read $fh, $buf, $IndexRecLen; # The ID field is a bit weird: it's represented as 3 # bytes, but it's really a double word (long) value. ($offset, $attributes, @id) = unpack "N C C3", $buf; if ($offset == $lastoffset) { print STDERR "Record $i has same offset as previous one: $offset\n"; } $lastoffset = $offset; $entry->{offset} = $offset; $entry->{attributes}{expunged} = 1 if $attributes & 0x80; $entry->{attributes}{dirty} = 1 if $attributes & 0x40; $entry->{attributes}{deleted} = 1 if $attributes & 0x20; $entry->{attributes}{private} = 1 if $attributes & 0x10; # Attribute names as of PalmOS 5.0 ( see /Core/System/DataMgr.h ) $entry->{'attributes'}{'Delete'} = 1 if $attributes & 0x80; $entry->{'attributes'}{'Dirty'} = 1 if $attributes & 0x40; $entry->{'attributes'}{'Busy'} = 1 if $attributes & 0x20; $entry->{'attributes'}{'Secret'} = 1 if $attributes & 0x10; $entry->{id} = ($id[0] << 16) | ($id[1] << 8) | $id[2]; # The lower 4 bits of the attributes field are # overloaded: If the record has been deleted and/or # expunged, then bit 0x08 indicates whether the record # should be archived. Otherwise (if it's an ordinary, # non-deleted record), the lower 4 bits specify the # category that the record belongs in. if (($attributes & 0xa0) == 0) { $entry->{category} = $attributes & 0x0f; } else { $entry->{attributes}{archive} = 1 if $attributes & 0x08; } # Put this information on a temporary array push @{$pdb->{_index}}, $entry; } } # _load_rsrc_index # Private function. Read the resource index, for a resource database sub _load_rsrc_index { my $pdb = shift; my $fh = shift; # Input file handle my $i; # Read each resource index entry in turn for ($i = 0; $i < $pdb->{_numrecs}; $i++) { my $buf; # Input buffer # Read the next resource index entry my $type; my $id; my $offset; my $entry = {}; # Parsed index entry read $fh, $buf, $IndexRsrcLen; ($type, $id, $offset) = unpack "a4 n N", $buf; $entry->{type} = $type; $entry->{id} = $id; $entry->{offset} = $offset; push @{$pdb->{_index}}, $entry; } } # _load_appinfo_block # Private function. Read the AppInfo block sub _load_appinfo_block { my $pdb = shift; my $fh = shift; # Input file handle my $len; # Length of AppInfo block my $buf; # Input buffer # Sanity check: make sure we're positioned at the beginning of # the AppInfo block if (tell($fh) > $pdb->{_appinfo_offset}) { die "Bad AppInfo offset: expected ", sprintf("0x%08x", $pdb->{_appinfo_offset}), ", but I'm at ", tell($fh), "\n"; } # Seek to the right place, if necessary if (tell($fh) != $pdb->{_appinfo_offset}) { seek $fh, $pdb->{_appinfo_offset}, 0; } # There's nothing that explicitly gives the size of the # AppInfo block. Rather, it has to be inferred from the offset # of the AppInfo block (previously recorded in # $pdb->{_appinfo_offset}) and whatever's next in the file. # That's either the sort block, the first data record, or the # end of the file. if ($pdb->{_sort_offset}) { # The next thing in the file is the sort block $len = $pdb->{_sort_offset} - $pdb->{_appinfo_offset}; } elsif ((defined $pdb->{_index}) && @{$pdb->{_index}}) { # There's no sort block; the next thing in the file is # the first data record $len = $pdb->{_index}[0]{offset} - $pdb->{_appinfo_offset}; } else { # There's no sort block and there are no records. The # AppInfo block goes to the end of the file. $len = $pdb->{_size} - $pdb->{_appinfo_offset}; } # Read the AppInfo block read $fh, $buf, $len; # Tell the real class to parse the AppInfo block $pdb->{appinfo} = $pdb->ParseAppInfoBlock($buf); } # _load_sort_block # Private function. Read the sort block. sub _load_sort_block { my $pdb = shift; my $fh = shift; # Input file handle my $len; # Length of sort block my $buf; # Input buffer # Sanity check: make sure we're positioned at the beginning of # the sort block if (tell($fh) > $pdb->{_sort_offset}) { die "Bad sort block offset: expected ", sprintf("0x%08x", $pdb->{_sort_offset}), ", but I'm at ", tell($fh), "\n"; } # Seek to the right place, if necessary if (tell($fh) != $pdb->{_sort_offset}) { seek $fh, $pdb->{_sort_offset}, 0; } # There's nothing that explicitly gives the size of the sort # block. Rather, it has to be inferred from the offset of the # sort block (previously recorded in $pdb->{_sort_offset}) # and whatever's next in the file. That's either the first # data record, or the end of the file. if (defined($pdb->{_index})) { # The next thing in the file is the first data record $len = $pdb->{_index}[0]{offset} - $pdb->{_sort_offset}; } else { # There are no records. The sort block goes to the end # of the file. $len = $pdb->{_size} - $pdb->{_sort_offset}; } # Read the AppInfo block read $fh, $buf, $len; # XXX - Check to see if the sort block has some predefined # structure. If so, it might be a good idea to parse the sort # block here. # Tell the real class to parse the sort block $pdb->{sort} = $pdb->ParseSortBlock($buf); } # _load_records # Private function. Load the actual data records, for a record database # (PDB) sub _load_records { my $pdb = shift; my $fh = shift; # Input file handle my $i; # Read each record in turn for ($i = 0; $i < $pdb->{_numrecs}; $i++) { my $len; # Length of record my $buf; # Input buffer # Sanity check: make sure we're where we think we # should be. if (tell($fh) > $pdb->{_index}[$i]{offset}) { die "Bad offset for record $i: expected ", sprintf("0x%08x", $pdb->{_index}[$i]{offset}), " but it's at ", sprintf("[0x%08x]", tell($fh)), "\n"; } if( $pdb->{_index}[$i]{offset} > $pdb->{_size} ) { die "corruption: Record $i beyond end of database!"; } # Seek to the right place, if necessary if (tell($fh) != $pdb->{_index}[$i]{offset}) { seek $fh, $pdb->{_index}[$i]{offset}, 0; } # Compute the length of the record: the last record # extends to the end of the file. The others extend to # the beginning of the next record. if ($i == $pdb->{_numrecs} - 1) { # This is the last record $len = $pdb->{_size} - $pdb->{_index}[$i]{offset}; } else { # This is not the last record $len = $pdb->{_index}[$i+1]{offset} - $pdb->{_index}[$i]{offset}; } # Read the record read $fh, $buf, $len; # Tell the real class to parse the record data. Pass # &ParseRecord all of the information from the index, # plus a "data" field with the raw record data. my $record; $record = $pdb->ParseRecord( %{$pdb->{_index}[$i]}, "data" => $buf, ); push @{$pdb->{records}}, $record; } } # _load_resources # Private function. Load the actual data resources, for a resource database # (PRC) sub _load_resources { my $pdb = shift; my $fh = shift; # Input file handle my $i; # Read each resource in turn for ($i = 0; $i < $pdb->{_numrecs}; $i++) { my $len; # Length of record my $buf; # Input buffer # Sanity check: make sure we're where we think we # should be. if (tell($fh) > $pdb->{_index}[$i]{offset}) { die "Bad offset for resource $i: expected ", sprintf("0x%08x", $pdb->{_index}[$i]{offset}), " but it's at ", sprintf("0x%08x", tell($fh)), "\n"; } if( $pdb->{_index}[$i]{offset} > $pdb->{_size} ) { die "corruption: Resource $i beyond end of database!"; } # Seek to the right place, if necessary if (tell($fh) != $pdb->{_index}[$i]{offset}) { seek $fh, $pdb->{_index}[$i]{offset}, 0; } # Compute the length of the resource: the last # resource extends to the end of the file. The others # extend to the beginning of the next resource. if ($i == $pdb->{_numrecs} - 1) { # This is the last resource $len = $pdb->{_size} - $pdb->{_index}[$i]{offset}; } else { # This is not the last resource $len = $pdb->{_index}[$i+1]{offset} - $pdb->{_index}[$i]{offset}; } # Read the resource read $fh, $buf, $len; # Tell the real class to parse the resource data. Pass # &ParseResource all of the information from the # index, plus a "data" field with the raw resource # data. my $resource; $resource = $pdb->ParseResource( %{$pdb->{_index}[$i]}, "data" => $buf, ); push @{$pdb->{resources}}, $resource; } } =head2 Write $pdb->Write($filename); Invokes methods in helper classes to get the application-specific parts of the database, then writes the database to the file C<$filename>. C<$filename> may also be an open file handle (as long as it's seekable). This allows for manipulating databases in memory structures. Write() uses the following helper methods: =over =item Z<> PackAppInfoBlock() =item Z<> PackSortBlock() =item Z<> PackResource() or PackRecord() =back =for html See also L. =cut #' <-- For Emacs sub Write { my $self = shift; my $fname = shift; # Output file name my @record_data; die "Can't write a database with no name\n" unless $self->{name} ne ""; my $handle = $self->_open('>', $fname); return undef unless defined $handle; # Open file binmode $handle; # Write as binary file under MS-DOS # Get AppInfo block my $appinfo_block = $self->PackAppInfoBlock; # Get sort block my $sort_block = $self->PackSortBlock; my $index_len; # Get records or resources if ($self->{attributes}{resource} || $self->{'attributes'}{'ResDB'}) { # Resource database my $resource; foreach $resource (@{$self->{resources}}) { my $type; my $id; my $data; # Get all the stuff that goes in the index, as # well as the resource data. $type = $resource->{type}; $id = $resource->{id}; $data = $self->PackResource($resource); push @record_data, [ $type, $id, $data ]; } # Figure out size of index $index_len = $RecIndexHeaderLen + ($#record_data + 1) * $IndexRsrcLen; } else { my $record; foreach $record (@{$self->{records}}) { my $attributes; my $id; my $data; # XXX - Should probably check the length of this # record and not add it to the record if it's 0. # Get all the stuff that goes in the index, as # well as the record data. $attributes = 0; if ($record->{attributes}{expunged} || $record->{attributes}{deleted}) { $attributes |= 0x08 if $record->{attributes}{archive}; } else { $attributes = ($record->{category} & 0x0f); } $attributes |= 0x80 if $record->{attributes}{expunged}; $attributes |= 0x40 if $record->{attributes}{dirty}; $attributes |= 0x20 if $record->{attributes}{deleted}; $attributes |= 0x10 if $record->{attributes}{private}; $attributes |= 0x80 if $record->{'attributes'}{'Delete'}; $attributes |= 0x40 if $record->{'attributes'}{'Dirty'}; $attributes |= 0x20 if $record->{'attributes'}{'Busy'}; $attributes |= 0x10 if $record->{'attributes'}{'Secret'}; $id = $record->{id}; $data = $self->PackRecord($record); push @record_data, [ $attributes, $id, $data ]; } # Figure out size of index $index_len = $RecIndexHeaderLen + ($#record_data + 1) * $IndexRecLen; } my $header; my $attributes = 0x0000; my $appinfo_offset; my $sort_offset; # Build attributes field $attributes = ($self->{attributes}{resource} ? 0x0001 : 0) | ($self->{attributes}{"read-only"} ? 0x0002 : 0) | ($self->{attributes}{"AppInfo dirty"} ? 0x0004 : 0) | ($self->{attributes}{backup} ? 0x0008 : 0) | ($self->{attributes}{"OK newer"} ? 0x0010 : 0) | ($self->{attributes}{reset} ? 0x0020 : 0) | ($self->{attributes}{open} ? 0x8000 : 0); $attributes |= 0x0001 if $self->{'attributes'}{'ResDB'}; $attributes |= 0x0002 if $self->{'attributes'}{'ReadOnly'}; $attributes |= 0x0004 if $self->{'attributes'}{'AppInfoDirty'}; $attributes |= 0x0008 if $self->{'attributes'}{'Backup'}; $attributes |= 0x0010 if $self->{'attributes'}{'OKToInstallNewer'}; $attributes |= 0x0020 if $self->{'attributes'}{'ResetAfterInstall'}; $attributes |= 0x0040 if $self->{'attributes'}{'CopyPrevention'}; $attributes |= 0x0080 if $self->{'attributes'}{'Stream'}; $attributes |= 0x0100 if $self->{'attributes'}{'Hidden'}; $attributes |= 0x0200 if $self->{'attributes'}{'LaunchableData'}; $attributes |= 0x0400 if $self->{'attributes'}{'Recyclable'}; $attributes |= 0x0800 if $self->{'attributes'}{'Bundle'}; $attributes |= 0x8000 if $self->{'attributes'}{'Open'}; # Calculate AppInfo block offset if ((!defined($appinfo_block)) || ($appinfo_block eq "")) { # There's no AppInfo block $appinfo_offset = 0; } else { # Offset of AppInfo block from start of file $appinfo_offset = $HeaderLen + $index_len + 2; } # Calculate sort block offset if ((!defined($sort_block)) || ($sort_block eq "")) { # There's no sort block $sort_offset = 0; } else { # Offset of sort block... if ($appinfo_offset == 0) { # ...from start of file $sort_offset = $HeaderLen + $index_len + 2; } else { # ...or just from start of AppInfo block $sort_offset = $appinfo_offset + length($appinfo_block); } } # Write header $header = pack "a32 n n N N N N N N a4 a4 N", $self->{name}, $attributes, $self->{version}, $self->{ctime} + $EPOCH_1904, $self->{mtime} + $EPOCH_1904, $self->{baktime} + $EPOCH_1904, $self->{modnum}, $appinfo_offset, $sort_offset, $self->{type}, $self->{creator}, $self->{uniqueIDseed}; ; print $handle "$header"; # Write index header my $index_header; $index_header = pack "N n", 0, ($#record_data+1); print $handle "$index_header"; # Write index my $rec_offset; # Offset of next record/resource # Calculate offset of first record/resource if ($sort_offset != 0) { $rec_offset = $sort_offset + length($sort_block); } elsif ($appinfo_offset != 0) { $rec_offset = $appinfo_offset + length($appinfo_block); } else { $rec_offset = $HeaderLen + $index_len + 2; } if ($self->{attributes}{resource} || $self->{'attributes'}{'ResDB'}) { # Resource database # Record database my $rsrc_data; foreach $rsrc_data (@record_data) { my $type; my $id; my $data; my $index_data; ($type, $id, $data) = @{$rsrc_data}; $index_data = pack "a4 n N", $type, $id, $rec_offset; print $handle "$index_data"; $rec_offset += length($data); } } else { # Record database my $rec_data; foreach $rec_data (@record_data) { my $attributes; my $data; my $id; my $index_data; # XXX - Probably shouldn't write this record if # length($data) == 0 ($attributes, $id, $data) = @{$rec_data}; if (length($data) == 0) { warn printf("Write: Warning: record 0x%08x has length 0\n", $id) } $index_data = pack "N C C3", $rec_offset, $attributes, ($id >> 16) & 0xff, ($id >> 8) & 0xff, $id & 0xff; print $handle "$index_data"; $rec_offset += length($data); } } # Write the two NULs if (length($self->{"2NULs"}) == 2) { print $handle $self->{"2NULs"}; } else { print $handle "\0\0"; } # Write AppInfo block print $handle $appinfo_block unless $appinfo_offset == 0; # Write sort block print $handle $sort_block unless $sort_offset == 0; # Write record/resource list my $record; foreach $record (@record_data) { my $data; if ($self->{attributes}{resource} || $self->{'attributes'}{'ResDB'}) { # Resource database my $type; my $id; ($type, $id, $data) = @{$record}; } else { my $attributes; my $id; ($attributes, $id, $data) = @{$record}; } print $handle $data; } return $self; } =head2 new_Record $record = Palm::PDB->new_Record(); $record = new_Record Palm::PDB; Creates a new record, with the bare minimum needed: $record->{'category'} $record->{'attributes'}{'Dirty'} $record->{'id'} The ``Dirty'' attribute is originally set, since this function will usually be called to create records to be added to a database. C does B add the new record to a PDB. For that, you want C. =cut # PDB::new_Record() # Create a new, initialized record, and return a reference to it. # The record is initially marked as being dirty, since that's usually # the Right Thing. sub new_Record { my $classname = shift; my $retval = {}; # Initialize the record $retval->{'category'} = 0; # Unfiled, by convention $retval->{'attributes'} = { # expunged => 0, dirty => 1, # Note: originally dirty 'Dirty' => 1, # deleted => 0, # private => 0, # archive => 0, }; $retval->{'id'} = 0; # Initially, no record ID return $retval; } =head2 is_Dirty $pdb->Write( $fname ) if $pdb->is_Dirty(); Returns non-zero if any of the in-memory elements of the database have been changed. This includes changes via function calls (any call that changes the C<$pdb>'s "last modification" time) as well as testing the "dirty" status of attributes where possible (i.e. AppInfo, records, but not resource entries). =cut #' sub is_Dirty { my $self = shift; # try the quick and easy tests first return 1 if $self->{'dirty'}; return 1 if $self->{'attributes'}{'AppInfoDirty'}; return 1 if $self->{'attributes'}{'AppInfo dirty'}; # okay, check the records. Note that resource entries appear to # have no dirty flags for us to use. if (!$self->{attributes}{resource} and !$self->{'attributes'}{'ResDB'}) { my $record; foreach $record (@{$self->{records}}) { return 1 if $record->{'attributes'}{'Dirty'}; return 1 if $record->{'attributes'}{'dirty'}; } } return 0; } =head2 append_Record $record = $pdb->append_Record; $record2 = $pdb->append_Record($record1); If called without any arguments, creates a new record with L, and appends it to $pdb. If given a reference to a record, appends that record to @{$pdb->{records}}. Returns a reference to the newly-appended record. This method updates $pdb's "last modification" time. =cut #' # append_Record # Append the given records to the database's list of records. If no # records are given, create one, append it, and return a reference to # it. sub append_Record { my $self = shift; if ($#_ < 0) { # No arguments given. Create a new record. my $record = $self->new_Record; # Validate the unique ID. $self->_setUniqueID($record) if $record->{'id'} eq 0; push @{$self->{records}}, $record; # Update the "last modification time". $self->{mtime} = time; $self->{dirty} = 1; return $record; } # Validate the unique IDs. foreach my $record (@_) { $self->_setUniqueID($record) if $record->{'id'} eq 0; } # At least one argument was given. Append all of the arguments # to the list of records, and return the first one. push @{$self->{records}}, @_; # Update the "last modification time". $self->{mtime} = time; $self->{'dirty'} = 1; return $_[0]; } sub _setUniqueID { my($self, $record) = @_; # Bump the seed to prevent a uniqueIDseed of 0 which represents # an unassigned uniqueID. # XXX IMHO this just couldn't happen given the way the seed it's # generated. But if Palm OS goes this way maybe it's better to do # the same. $self->{'uniqueIDseed'}++; # Check for wrap around. Remember that an uniqueID is made of only 24 bits. $self->{'uniqueIDseed'} = (dmRecordIDReservedRange + 1) << 12 if ($self->{'uniqueIDseed'} & 0xFF000000); # Copy the seed into the new record. $record->{'id'} = $self->{'uniqueIDseed'}; } =head2 new_Resource $resource = Palm::PDB->new_Resource(); $resource = new_Resource Palm::PDB; Creates a new resource and initializes $resource->{type} $resource->{id} =cut # new_Resource # Create a new, initialized resource, and return a reference to it. sub new_Resource { my $classname = shift; my $retval = {}; # Initialize the resource $retval->{type} = "\0\0\0\0"; $retval->{id} = 0; return $retval; } =head2 append_Resource $resource = $pdb->append_Resource; $resource2 = $pdb->append_Resource($resource1); If called without any arguments, creates a new resource with L, and appends it to $pdb. If given a reference to a resource, appends that resource to @{$pdb->{resources}}. Returns a reference to the newly-appended resource. This method updates $pdb's "last modification" time. =cut #' # append_Resource # Append the given resources to the database's list of resources. If no # resources are given, create one, append it, and return a reference to # it. sub append_Resource { my $self = shift; if ($#_ < 0) { # No arguments given. Create a new resource my $resource = $self->new_Resource; push @{$self->{resources}}, $resource; # Update the "last modification time". $self->{mtime} = time; $self->{'dirty'} = 1; return $resource; } # At least one argument was given. Append all of the arguments # to the list of resources, and return the first one. push @{$self->{resources}}, @_; # Update the "last modification time". $self->{mtime} = time; $self->{'dirty'} = 1; return $_[0]; } =head2 findRecordByID $record = $pdb->findRecordByID($id); Looks through the list of records in $pdb, and returns a reference to the record with ID $id, or the undefined value if no such record was found. =cut # findRecordByID # Returns a reference to the record with the given ID, or 'undef' if # it doesn't exist. sub findRecordByID { my $self = shift; my $id = shift; return undef if $id eq ""; for (@{$self->{records}}) { next unless $_->{id} == $id; return $_; # Found it } return undef; # Not found } =head2 delete_Record $pdb->delete_Record($record, $expunge); Marks $record for deletion, so that it will be deleted from the database at the next sync. If $expunge is false or omitted, the record will be marked for deletion with archival. If $expunge is true, the record will be marked for deletion without archival. This method updates $pdb's "last modification" time. =cut #' # delete_Record # $pdb->delete_Record($record ?, $expunge?) # # Mark the given record for deletion. If $expunge is true, mark the # record for deletion without an archive. sub delete_Record { my $self = shift; my $record = shift; my $expunge = shift; $record->{attributes}{deleted} = 1; if ($expunge) { $record->{attributes}{expunged} = 1; $record->{attributes}{archive} = 0; } else { $record->{attributes}{expunged} = 0; $record->{attributes}{archive} = 1; } # Update the "last modification time". $self->{mtime} = time; $self->{'dirty'} = 1; } =head2 remove_Record for (@{$pdb->{'records'}}) { $pdb->remove_Record( $_ ) if $_->{attributes}{deleted}; } Removes C<$record> from the database. This differs from C in that it's an actual deletion rather than just setting a flag. This method updates $pdb's "last modification" time. =cut #' sub remove_Record($$) { my $self = shift; my $record = shift; for (my $i = 0; $i <= $#{$self->{records}}; $i ++) { if ($self->{records}->[$i] == $record) { # make a copy of the records array. This is really necessary # because there's frequently something using the records reference # for iteration purposes (like the doc example) and we can't # just start splicing that apart (tried, failed). # So we have to make a new copy. This does, unfortunately, # make remove_Record() more expensive that you'd expect. $self->{records} = [ @{$self->{records}} ]; # remove the record index. splice @{$self->{records}}, $i, 1; $self->{mtime} = time; $self->{'dirty'} = 1; last; } } } 1; __END__ =head1 HELPER CLASSES $pdb->Load() reblesses $pdb into a new class. This helper class is expected to convert raw data from the database into parsed representations of it, and vice-versa. A helper class must have all of the methods listed below. The Palm::Raw class is useful if you don't want to define all of the required methods. =head2 ParseAppInfoBlock $appinfo = $pdb->ParseAppInfoBlock($buf); $buf is a string of raw data. ParseAppInfoBlock() should parse this data and return it, typically in the form of a reference to an object or to an anonymous hash. This method will not be called if the database does not have an AppInfo block. The return value from ParseAppInfoBlock() will be accessible as $pdb->{appinfo}. =head2 PackAppInfoBlock $buf = $pdb->PackAppInfoBlock(); This is the converse of ParseAppInfoBlock(). It takes $pdb's AppInfo block, $pdb->{appinfo}, and returns a string of binary data that can be written to the database file. =head2 ParseSortBlock $sort = $pdb->ParseSortBlock($buf); $buf is a string of raw data. ParseSortBlock() should parse this data and return it, typically in the form of a reference to an object or to an anonymous hash. This method will not be called if the database does not have a sort block. The return value from ParseSortBlock() will be accessible as $pdb->{sort}. =head2 PackSortBlock $buf = $pdb->PackSortBlock(); This is the converse of ParseSortBlock(). It takes $pdb's sort block, $pdb->{sort}, and returns a string of raw data that can be written to the database file. =head2 ParseRecord $record = $pdb->ParseRecord( offset => $offset, # Record's offset in file attributes => # Record attributes { expunged => bool, # True iff expunged dirty => bool, # True iff dirty deleted => bool, # True iff deleted private => bool, # True iff private archive => bool, # True iff to be archived }, category => $category, # Record's category number id => $id, # Record's unique ID data => $buf, # Raw record data ); ParseRecord() takes the arguments listed above and returns a parsed representation of the record, typically as a reference to a record object or anonymous hash. The output from ParseRecord() will be appended to @{$pdb->{records}}. The records appear in this list in the same order as they appear in the file. $offset argument is not normally useful, but is included for completeness. The fields in %$attributes are boolean values. They are true iff the record has the corresponding flag set. $category is an integer in the range 0-15, which indicates which category the record belongs to. This is normally an index into a table given at the beginning of the AppInfo block. A typical ParseRecord() method has this general form: sub ParseRecord { my $self = shift my %record = @_; # Parse $self->{data} and put the fields into new fields in # $self. delete $record{data}; # No longer useful return \%record; } =head2 PackRecord $buf = $pdb->PackRecord($record); The converse of ParseRecord(). PackRecord() takes a record as returned by ParseRecord() and returns a string of raw data that can be written to the database file. PackRecord() is never called when writing a resource database. =head2 ParseResource $record = $pdb->ParseResource( type => $type, # Resource type id => $id, # Resource ID offset => $offset, # Resource's offset in file data => $buf, # Raw resource data ); ParseResource() takes the arguments listed above and returns a parsed representation of the resource, typically as a reference to a resource object or anonymous hash. The output from ParseResource() will be appended to @{$pdb->{resources}}. The resources appear in this list in the same order as they appear in the file. $type is a four-character string giving the resource's type. $id is an integer that uniquely identifies the resource amongst others of its type. $offset is not normally useful, but is included for completeness. =head2 PackResource $buf = $pdb->PackResource($resource); The converse of ParseResource(). PackResource() takes a resource as returned by PackResource() and returns a string of raw data that can be written to the database file. PackResource() is never called when writing a record database. =head1 BUGS These functions die too easily. They should return an error code. Database manipulation is still an arcane art. It may be possible to parse sort blocks further. =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::Raw(3) Palm::Address(3) Palm::Datebook(3) Palm::Mail(3) Palm::Memo(3) Palm::ToDo(3) F, in the ColdSync distribution. The Virtual Constructor (aka Factory Method) pattern is described in F, by Erich Gamma I, Addison-Wesley. p5-Palm-1.012/lib/Raw.pm0000644000076500000240000000712511341055215013545 0ustar brianstaff# Palm::Raw.pm # # Perl class for dealing with "raw" PDB databases. A "raw" database is # one where the AppInfo and sort blocks, and all of the # records/resources, are just strings of bytes. # This is useful as a default PDB handler, for cases where you want to # be able to handle any kind of database in a generic fashion. # You may also find it useful to subclass this class, for cases where # you don't care about every type of thing in a database. # # Copyright (C) 1999, 2000, Andrew Arensburger. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. use strict; package Palm::Raw; use Palm::PDB; use vars qw( $VERSION @ISA ); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; @ISA = qw( Palm::PDB ); =head1 NAME Palm::Raw - Handler for "raw" Palm databases. =head1 SYNOPSIS use Palm::Raw; For standalone programs. use Palm::Raw(); @ISA = qw( Palm::Raw ); For Palm::PDB helper modules. =head1 DESCRIPTION The Raw PDB handler is a helper class for the Palm::PDB package. It is intended as a generic handler for any database, or as a fallback default handler. If you have a standalone program and want it to be able to parse any type of database, use use Palm::Raw; If you are using Palm::Raw as a parent class for your own database handler, use use Palm::Raw(); If you omit the parentheses, Palm::Raw will register itself as the default handler for all databases, which is probably not what you want. The Raw handler does no processing on the database whatsoever. The AppInfo block, sort block, records and resources are simply strings, raw data from the database. By default, the Raw handler only handles record databases (.pdb files). If you want it to handle resource databases (.prc files) as well, you need to call &Palm::PDB::RegisterPRCHandlers("Palm::Raw", ""); in your script. =head2 AppInfo block $pdb->{appinfo} This is a scalar, the raw data of the AppInfo block. =head2 Sort block $pdb->{sort} This is a scalar, the raw data of the sort block. =head2 Records @{$pdb->{records}}; Each element in the "records" array is a reference-to-hash. In addition to the standard keys ("attributes", "category", and "id"), this hash contains the key "data"; its value is a string with the raw record data. =head2 Resources @{$pdb->{resources}}; Each element in the "resources" array is a reference-to-hash. In addition to the standard keys ("type" and "id"), it contains the key "data"; its value is a string with the raw resource data. =cut #' sub import { # This package handles any PDB. &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "", "" ] ); } # sub new # sub new_Record # These are just inherited. sub ParseAppInfoBlock { my $self = shift; my $data = shift; return $data; } sub ParseSortBlock { my $self = shift; my $data = shift; return $data; } sub ParseRecord { my $self = shift; my %record = @_; return \%record; } sub ParseResource { my $self = shift; my %resource = @_; return \%resource; } sub PackAppInfoBlock { my $self = shift; return $self->{appinfo}; } sub PackSortBlock { my $self = shift; return $self->{sort}; } sub PackRecord { my $self = shift; my $record = shift; return $record->{data}; } sub PackResource { my $self = shift; my $resource = shift; return $resource->{data}; } 1; __END__ =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::PDB(3) =cut p5-Palm-1.012/lib/StdAppInfo.pm0000644000076500000240000003712011341055215015021 0ustar brianstaff# Palm::StdAppInfo.pm # # Class for dealing with standard AppInfo blocks in PDBs. # # Copyright (C) 1999, 2000, Andrew Arensburger. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. use strict; package Palm::StdAppInfo; use Palm::Raw(); # Don't harass me about these variables use vars qw( $VERSION @ISA $error ); # $error acts like $! in that it reports the error that occurred # One liner, to allow MakeMaker to work. $VERSION = '1.012'; @ISA = qw( Palm::Raw ); =head1 NAME Palm::StdAppInfo - Handles standard AppInfo block (categories) =head1 SYNOPSIS Usually: package MyPDBHandler; use Palm::StdAppInfo(); # Note the parentheses @ISA = qw( Palm::StdAppInfo ); use constant APPINFO_PADDING = 1; sub ParseAppInfoBlock { my $self = shift; my $data = shift; my $appinfo = {}; &Palm::StdAppInfo::parse_StdAppInfo($appinfo, $data); $app_specific_data = $appinfo->{other}; } sub PackAppInfoBlock { my $self = shift; my $retval; $self->{appinfo}{other} = ; $retval = &Palm::StdAppInfo::pack_StdAppInfo($self->{appinfo}); return $retval; } Or as a standalone C helper class: use Palm::StdAppInfo; =head1 DESCRIPTION Many Palm applications use a common format for keeping track of categories. The C class deals with this common format: $pdb = new Palm::PDB; $pdb->Load("myfile.pdb"); @categories = @{$pdb->{appinfo}{categories}}; $lastUniqueID = $pdb->{appinfo}{lastUniqueID}; $other = $pdb->{appinfo}{other}; where: C<@categories> is an array of references-to-hash: =over 4 =item C<$cat = $categories[0];> =item C<$cat-E{name}> The name of the category, a string of at most 16 characters. =item C<$cat-E{id}> The category ID, an integer in the range 0-255. Each category has a unique ID. By convention, 0 is reserved for the "Unfiled" category; IDs assigned by the Palm are in the range 1-127, and IDs assigned by the desktop are in the range 128-255. =item C<$cat-E{renamed}> A boolean. This field is true iff the category has been renamed since the last sync. =back C<$lastUniqueID> is (I think) the last category ID that was assigned. C<$other> is any data that follows the category list in the AppInfo block. If you're writing a helper class for a PDB that includes a category list, you should parse this field to get any data that follows the category list; you should also make sure that this field is initialized before you call C<&Palm::StdAppInfo::pack_AppInfo>. =head2 APPINFO_PADDING Normally, the AppInfo block includes a byte of padding at the end, to bring its length to an even number. However, some databases use this byte for data. If your database uses the padding byte for data, then your C<&ParseAppInfoBlock> method (see L<"SYNOPSIS">) should call C<&parse_StdAppInfo> with a true $nopadding argument. If, for whatever reason, you wish to inherit C<&StdAppInfo::ParseAppInfoBlock>, then add use constant APPINFO_PADDING => 0; to your handler package, to tell it that the padding byte is really data. =head1 FUNCTIONS =cut #' use constant APPINFO_PADDING => 1; # Whether to add the padding byte at # the end of the AppInfo block. # Note that this might be considered a hack: # this relies on the fact that 'use constant' # defines a function with no arguments; that # therefore this can be called as an instance # method, with full inheritance. That is, if # the handler class doesn't define it, Perl # will find the constant in the parent. If # this ever changes, the code below that uses # $self->APPINFO_PADDING will need to be # changed. use constant numCategories => 16; # Number of categories in AppInfo block use constant categoryLength => 16; # Length of category names use constant stdAppInfoSize => # Length of a standard AppInfo block 2 + (categoryLength * numCategories) + numCategories + 1 + 1; # The padding byte at the end may # be omitted sub import { &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "", "" ], ); } =head2 seed_StdAppInfo &Palm::StdAppInfo::seed_StdAppInfo(\%appinfo); Creates the standard fields in an existing AppInfo hash. Usually used to ensure that a newly-created AppInfo block contains an initialized category array: my $appinfo = {}; &Palm::StdAppInfo::seed_StdAppInfo($appinfo); Note: this is not a method. =cut # seed_StdAppInfo # *** THIS IS NOT A METHOD *** # Given a reference to an appinfo hash, creates all of the fields for # a new AppInfo block. sub seed_StdAppInfo { my $appinfo = shift; my $i; $appinfo->{categories} = []; # Create array of categories # Initialize the categories # Note that all of the IDs are initialized to $i. There's no # real good reason for doing it this way, except that that's # what the Palm appears to do with new category lists. for ($i = 0; $i < numCategories; $i++) { $appinfo->{categories}[$i] = {}; $appinfo->{categories}[$i]{renamed} = 0; $appinfo->{categories}[$i]{name} = undef; $appinfo->{categories}[$i]{id} = $i; } # The only fixed category is "Unfiled". Initialize it now $appinfo->{categories}[0]{name} = "Unfiled"; $appinfo->{categories}[0]{id} = 0; # I'm not sure what this is, but let's initialize it. # The Palm appears to initialize this to numCategories - 1. $appinfo->{lastUniqueID} = numCategories - 1; } =head2 newStdAppInfo $appinfo = Palm::StdAppInfo->newStdAppInfo; Like C, but creates an AppInfo hash and returns a reference to it. =cut sub newStdAppInfo { my $class = shift; my $retval = {}; &seed_StdAppInfo($retval); return $retval; } =head2 new $pdb = new Palm::StdAppInfo; Create a new PDB, initialized with nothing but a standard AppInfo block. There are very few reasons to use this, and even fewer good ones. If you're writing a helper class to parse some PDB format that contains a category list, then you should make that helper class a subclass of C. =cut #' sub new { my $classname = shift; my $self = $classname->SUPER::new(@_); # Create a generic PDB. No need to rebless it, # though. # Initialize the AppInfo block $self->{appinfo} = &newStdAppInfo(); return $self; } =head2 parse_StdAppInfo $len = &Palm::StdAppInfo::parse_StdAppInfo(\%appinfo, $data, $nopadding); This function (this is not a method) is intended to be called from within a PDB helper class's C method. C parses a standard AppInfo block from the raw data C<$data> and fills in the fields in C<%appinfo>. It returns the number of bytes parsed. C<$nopadding> is optional, and defaults to false. Normally, the AppInfo block includes a padding byte at the end. If C<$nopadding> is true, then C<&parse_StdAppInfo> assumes that the padding byte is application data, and includes it in C<$appinfo{'other'}>, so that the caller can parse it. =cut #' # parse_StdAppInfo # *** THIS IS NOT A METHOD *** # # Reads the raw data from $data, parses it as a standard AppInfo # block, and fills in the corresponding fields in %$appinfo. Returns # the number of bytes parsed. sub parse_StdAppInfo { my $appinfo = shift; # A reference to hash, to fill in my $data = shift; # Raw data to read my $nopadding = shift; # Optional: no padding byte at end my $unpackstr; # First argument to unpack() my $renamed; # Bitmap of renamed categories my @labels; # Array of category labels my @uniqueIDs; # Array of category IDs my $lastUniqueID; # Not sure what this is return undef if length $data < 4+(categoryLength*numCategories)+numCategories; if (!defined($nopadding)) { $nopadding = 0; } # Make sure $appinfo contains all of the requisite fields &seed_StdAppInfo($appinfo); # The argument to unpack() isn't hard to understand, it's just # hard to write in a readable fashion. $unpackstr = # Argument to unpack(), since it's hairy "n" . # Renamed categories ("a" . categoryLength) x numCategories . # Category labels "C" x numCategories . # Category IDs "C" . # Last unique ID "x"; # Unpack the data ($renamed, @labels[0..(numCategories-1)], @uniqueIDs[0..(numCategories-1)], $lastUniqueID) = unpack $unpackstr, $data; # Clean this stuff up a bit for (@labels) { s/\0.*$//; # Trim at NUL } # Now put the data into $appinfo my $i; for ($i = 0; $i < numCategories; $i++) { $appinfo->{categories}[$i]{renamed} = ($renamed & (1 << $i) ? 1 : 0); $appinfo->{categories}[$i]{name} = $labels[$i]; $appinfo->{categories}[$i]{id} = $uniqueIDs[$i]; } $appinfo->{lastUniqueID} = $lastUniqueID; # There might be other stuff in the AppInfo block other than # the standard categories. Put everything else in # $appinfo->{other}. $appinfo->{other} = substr($data, stdAppInfoSize - ($nopadding ? 1 : 0)); return ($nopadding ? stdAppInfoSize - 1 : stdAppInfoSize); } =head2 ParseAppInfoBlock $pdb = new Palm::StdAppInfo; $pdb->ParseAppInfoBlock($data); If your application's AppInfo block contains standard category support and nothing else, you may choose to just inherit this method instead of writing your own C method. Otherwise, see the example in L<"SYNOPSIS">. =cut #' sub ParseAppInfoBlock { my $self = shift; my $data = shift; my $appinfo = {}; &parse_StdAppInfo($appinfo, $data, $self->APPINFO_PADDING); return $appinfo; } =head2 pack_StdAppInfo $data = &Palm::StdAppInfo::pack_StdAppInfo(\%appinfo); This function (this is not a method) is intended to be called from within a PDB helper class's C method. C takes an AppInfo hash and packs it as a string of raw data that can be written to a PDB. Note that if you're using this inside a helper class's C method, you should make sure that C<$appinfo{other}> is properly initialized before you call C<&Palm::StdAppInfo::pack_StdAppInfo>. C<$nopadding> is optional, and defaults to false. Normally, the AppInfo block includes a byte of padding at the end. If C<$nopadding> is true, then C<&pack_StdAppInfo> doesn't include this byte of padding, so that the application can use it. =cut #' # pack_StdAppInfo # *** THIS IS NOT A METHOD *** # # Given a reference to a hash containing an AppInfo block (such as # that initialized by parse_StdAppInfo()), returns a packed string # that can be written to the PDB file. sub pack_StdAppInfo { my $appinfo = shift; my $nopadding = shift; my $retval; my $i; $nopadding = 0 if !defined($nopadding); # Create the bitfield of renamed categories my $renamed; $renamed = 0; for ($i = 0; $i < numCategories; $i++) { if ($appinfo->{categories}[$i]{renamed}) { $renamed |= (1 << $i); } } $retval = pack("n", $renamed); # There have to be exactly 16 categories in the AppInfo block, # even though $appinfo->{categories} may have been mangled # by a naive (or clever) user or broken program. for ($i = 0; $i < numCategories; $i++) { my $name; # Category name # This is mainly to stop Perl 5.6 from complaining if # the category name is undefined. if ((!defined($appinfo->{categories}[$i]{name})) || $appinfo->{categories}[$i]{name} eq "") { $name = ""; } else { $name = $appinfo->{categories}[$i]{name}; } $retval .= pack("a" . categoryLength, $name); } # Ditto for category IDs for ($i = 0; $i < numCategories; $i++) { $retval .= pack("C", $appinfo->{categories}[$i]{id}); } # Last unique ID, and alignment padding $retval .= pack("Cx", $appinfo->{lastUniqueID}); $retval .= $appinfo->{other} if defined($appinfo->{other}); return $retval; } =head2 PackAppInfoBlock $pdb = new Palm::StdAppInfo; $data = $pdb->PackAppInfoBlock(); If your application's AppInfo block contains standard category support and nothing else, you may choose to just inherit this method instead of writing your own C method. Otherwise, see the example in L<"SYNOPSIS">. =cut #' sub PackAppInfoBlock { my $self = shift; return &pack_StdAppInfo($self->{appinfo}, $self->{APPINFO_PADDING}); } =head2 addCategory $pdb->addCategory($name [, $id [, $renamed]]); Adds a category to $pdb. The $name argument specifies the new category's name. The optional $id argument specifies the new category's numeric ID; if omitted or undefined, &addCategory will pick one. The optional $renamed argument is a boolean value indicating whether the new category should be marked as having been modified. This defaults to true since, conceptually, &addCategory doesn't really add a category: it finds one whose name happens to be empty, and renames it. Returns a true value if successful, false otherwise. In case of failure, &addCategory sets $Palm::StdAppInfo::error to an error message. =cut #' # XXX - When choosing a new category ID, should pick them from the # range 128-255. sub addCategory { my $self = shift; # PDB my $name = shift; # Category name my $id = shift; # Category ID (optional) my $renamed = $#_ >= 0 ? $_[0] : 1; # Flag: was the category renamed (optional) # This initialization may look weird, # but it's this way so that it'll # default to true if omitted. my $categories = $self->{appinfo}{categories}; my $i; my %used; # Category IDs in use # Collect all the IDs in the current list for (@{$categories}) { next if !defined($_->{name}) || $_->{name} eq ""; $used{$_->{id}} = 1; } if (defined($id)) { # Sanity check: make sure this ID isn't already in use if (defined($used{$id})) { $error = "Category ID already in use"; return undef; } } else { # Find an unused category number, if none was specified for ($id = 128; $id < 256; $id++) { last if !defined($used{$id}); } } # Go through the list of categories, looking for an unused slot for ($i = 0; $i < numCategories; $i++) { # Ignore named categories next unless !defined($categories->[$i]{name}) or $categories->[$i]{name} eq ""; # Found an empty slot $categories->[$i]{name} = $name; $categories->[$i]{id} = $id; $categories->[$i]{renamed} = $renamed; return 1; } # If we get this far, there are no empty category slots $error = "No unused categories"; return undef; } =head2 deleteCategory $pdb->deleteCategory($name); Deletes the category with name $name. Actually, though, it doesn't delete the category: it just changes its name to the empty string, and marks the category as renamed. =cut #' sub deleteCategory { my $self = shift; my $name = shift; # Category name for (@{$self->{appinfo}{categories}}) { # Find the category named $name next if $_->{name} ne $name; # Erase this category $_->{name} = ""; # You'd think it would make sense to set the "renamed" # field here, but the Palm doesn't do that. } } =head2 renameCategory $pdb->renameCategory($oldname, $newname); Renames the category named $oldname to $newname. If successful, returns a true value. If there is no category named $oldname, returns a false value and sets $Palm::StdAppInfo::error to an error message. =cut #' # XXX - This doesn't behave the same way as the Palm: the Palm also # picks a new category ID. sub renameCategory { my $self = shift; my $oldname = shift; my $newname = shift; for (@{$self->{appinfo}{categories}}) { # Look for a category named $oldname next if !defined($_->{name}) || $_->{name} ne $oldname; # Found it. Rename it and mark it as renamed. $_->{name} = $newname; $_->{renamed} = 1; return 1; } $error = "No such category"; return undef; } 1; __END__ =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::PDB(3) =cut p5-Palm-1.012/lib/ToDo.pm0000644000076500000240000001412511341055215013657 0ustar brianstaff# Palm::ToDo.pm # # Perl class for dealing with Palm ToDo databases. # # Copyright (C) 1999, 2000, Andrew Arensburger. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. # XXX - Bug: apparently, the first ToDo item shows up with a category # of "unfiled" use strict; package Palm::ToDo; use Palm::Raw(); use Palm::StdAppInfo(); use vars qw( $VERSION @ISA ); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; @ISA = qw( Palm::StdAppInfo Palm::Raw ); =head1 NAME Palm::ToDo - Handler for Palm ToDo databases. =head1 SYNOPSIS use Palm::ToDo; =head1 DESCRIPTION The ToDo PDB handler is a helper class for the Palm::PDB package. It parses ToDo databases. =head2 AppInfo block The AppInfo block begins with standard category support. See L for details. Other fields include: $pdb->{appinfo}{dirty_appinfo} $pdb->{appinfo}{sortOrder} I don't know what these are. =head2 Sort block $pdb->{sort} This is a scalar, the raw data of the sort block. =head2 Records $record = $pdb->{records}[N] $record->{due_day} $record->{due_month} $record->{due_year} The due date of the ToDo item. If the item has no due date, these are undefined. $record->{completed} This is defined and true iff the item has been completed. $record->{priority} An integer. The priority of the item. $record->{description} A text string. The description of the item. $record->{note} A text string. The note attached to the item. Undefined if the item has no note. =cut #' sub import { &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "todo", "DATA" ], ); } =head2 new $pdb = new Palm::ToDo; Create a new PDB, initialized with the various Palm::ToDo fields and an empty record list. Use this method if you're creating a ToDo PDB from scratch. =cut #' # new # Create a new Palm::ToDo database, and return it sub new { my $classname = shift; my $self = $classname->SUPER::new(@_); # Create a generic PDB. No need to rebless it, # though. $self->{name} = "ToDoDB"; # Default $self->{creator} = "todo"; $self->{type} = "DATA"; $self->{attributes}{resource} = 0; # The PDB is not a resource database by # default, but it's worth emphasizing, # since ToDoDB is explicitly not a PRC. # Initialize the AppInfo block $self->{appinfo} = { dirty_appinfo => undef, # ? sortOrder => undef, # ? }; # Add the standard AppInfo block stuff &Palm::StdAppInfo::seed_StdAppInfo($self->{appinfo}); # Give the PDB a blank sort block $self->{sort} = undef; # Give the PDB an empty list of records $self->{records} = []; return $self; } =head2 new_Record $record = $pdb->new_Record; Creates a new ToDo record, with blank values for all of the fields. C does B add the new record to C<$pdb>. For that, you want C<$pdb-Eappend_Record>. =cut # new_Record # Create a new, initialized record. sub new_Record { my $classname = shift; my $retval = $classname->SUPER::new_Record(@_); # Item has no due date by default. $retval->{due_day} = undef; $retval->{due_month} = undef; $retval->{due_year} = undef; $retval->{completed} = 0; # Not completed $retval->{priority} = 1; # Empty description, no note. $retval->{description} = ""; $retval->{note} = undef; return $retval; } # ParseAppInfoBlock # Parse the AppInfo block for ToDo databases. sub ParseAppInfoBlock { my $self = shift; my $data = shift; my $dirtyAppInfo; my $sortOrder; my $appinfo = {}; my $std_len; # Get the standard parts of the AppInfo block $std_len = &Palm::StdAppInfo::parse_StdAppInfo($appinfo, $data); $data = $appinfo->{other}; # Look at the non-category part # Get the rest of the AppInfo block my $unpackstr = # Argument to unpack() "x2" . # Reserved "n" . # XXX - Dirty AppInfo (what is this?) "Cx"; # Sort order ($dirtyAppInfo, $sortOrder) = unpack $unpackstr, $data; $appinfo->{dirty_appinfo} = $dirtyAppInfo; $appinfo->{sort_order} = $sortOrder; return $appinfo; } sub PackAppInfoBlock { my $self = shift; my $retval; # Pack the application-specific part of the AppInfo block $self->{appinfo}{other} = pack("x2 n Cx", $self->{appinfo}{dirty_appinfo}, $self->{appinfo}{sort_order}); # Pack the AppInfo block $retval = &Palm::StdAppInfo::pack_StdAppInfo($self->{appinfo}); return $retval; } sub ParseRecord { my $self = shift; my %record = @_; my $data = $record{data}; delete $record{offset}; # This is useless delete $record{data}; # No longer necessary my $date; my $priority; ($date, $priority) = unpack "n C", $data; $data = substr $data, 3; # Remove the stuff we've already seen if ($date != 0xffff) { my $day; my $month; my $year; $day = $date & 0x001f; # 5 bits $month = ($date >> 5) & 0x000f; # 4 bits $year = ($date >> 9) & 0x007f; # 7 bits (years since 1904) $year += 1904; $record{due_day} = $day; $record{due_month} = $month; $record{due_year} = $year; } my $completed; # Boolean $completed = $priority & 0x80; $priority &= 0x7f; # Strip high bit $record{completed} = 1 if $completed; $record{priority} = $priority; my $description; my $note; ($description, $note) = split /\0/, $data; $record{description} = $description; $record{note} = $note unless $note eq ""; return \%record; } sub PackRecord { my $self = shift; my $record = shift; my $retval; my $rawDate; my $priority; if (defined($record->{due_day})) { $rawDate = ($record->{due_day} & 0x001f) | (($record->{due_month} & 0x000f) << 5) | ((($record->{due_year} - 1904) & 0x007f) << 9); } else { $rawDate = 0xffff; } $priority = $record->{priority} & 0x7f; $priority |= 0x80 if $record->{completed}; $retval = pack "n C", $rawDate, $priority; $retval .= $record->{description} . "\0"; $retval .= $record->{note} . "\0"; return $retval; } 1; __END__ =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::PDB(3) Palm::StdAppInfo(3) =cut p5-Palm-1.012/lib/ZirePhoto.pm0000644000076500000240000001342011341055215014732 0ustar brianstaff# Palm::ZirePhoto.pm # # Perl class for dealing with Zire71 photo database. # # Copyright (C) 2003, Alessandro Zummo. # You may distribute this file under the terms of the Artistic # License, as specified in the README file. use strict; package Palm::ZirePhoto; use Palm::Raw(); use Palm::StdAppInfo(); use vars qw( $VERSION @ISA ); # One liner, to allow MakeMaker to work. $VERSION = '1.012'; @ISA = qw( Palm::StdAppInfo Palm::Raw ); =head1 NAME Palm::ZirePhoto - Handler for Palm Zire71 Photo thumbnail databases. =head1 SYNOPSIS use Palm::ZirePhoto; =head1 DESCRIPTION The Zire71 PDB handler is a helper class for the L package. It parses Zire71 Photo thumbnail databases (and, hopefully, Tungsten Photo databases). Actual photos are separate databases and must be processed separately. This database is currently only capable of reading. =head2 AppInfo block The AppInfo block begins with standard category support. See L for details. =head2 Records Records may contain no data fields. This occurs when the record has been marked deleted on the Palm, presumably in order to save space (Photo has no provision for archiving when deleting and the separate database storage for the actual images would make it pointless anyways). $record = $pdb->{records}[N] $record->{'width'} $record->{'height'} $record->{'size'} The actual JPEG images dimensions and (compressed) file size. $record->{'thumb'} The thumbnail is a very small (max size approx 84x84) JPEG format image. $record->{'name'} Image name. Appending C<.jpg> to this will give the database name of the actual image data. $record->{'time1'} $record->{'time2'} Unix epoch time of when the image was last modified (C) and when it was created (C). =head2 Photo Databases Actual photos are stored in separate databases. Each record is preceeded by an 8 byte header that describes it a) as a data block (B) and b) the size of the block. Records are generally 4k, except for the last. To convert a Photo database to a JPEG image, one would do something like: use Palm::Raw; my $pdb = new Palm::PDB; $pdb->Load( "image.jpg.pdb" ); open F, ">image.jpg"; for( @{$pdb->{records}} ) { print F substr($_->{'data'}, 8); } close F; Notes are stored at the end of the JPEG image. Use C to get it. =cut #' sub import { &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "Foto", "Foto" ], ); } sub new { my $classname = shift; my $self = $classname->SUPER::new(@_); # Create a generic PDB. No need to rebless it, # though. $self->{name} = "PhotosDB-Foto"; # Default $self->{creator} = "Foto"; $self->{type} = "Foto"; $self->{attributes}{resource} = 0; # The PDB is not a resource database by # default, but it's worth emphasizing. # Give the PDB an empty list of records $self->{records} = []; return $self; } sub ParseRecord { my $self = shift; my %record = @_; my $data = $record{'data'}; delete $record{offset}; # This is useless delete $record{data}; # No longer necessary # when Photo thumbnail records are deleted/archived/whatever, the data section is # actually set to zero length. Presumably this is so that thumbnails take up # minimum space until a sync purges the records. return \%record unless length $data > 36; @record{ 'width', 'height', 'time1_secs', 'size', 'nameSize', 'time2_secs', 'thumb', 'name' } = unpack "xxxx n n N N x5 n x5 N x4 N/a a*", $data; $record{'thumbSize'} = length($record{'thumb'}); $record{'time1'} = $record{'time1_secs'} - 2082844800; $record{'time2'} = $record{'time2_secs'} - 2082844800; $record{'name'} = substr($record{'name'}, 0, $record{'nameSize'}); return \%record; } =head1 METHODS Handling Palm photos can be a bit complicated. Some helper methods are exported to make some special cases a bit easier. =head2 ParseNote my $photo = read_jpeg_file( "image.jpg" ); my $note = Palm::ZirePhoto::ParseNote($photo); print "Note: $note" if defined $note; The Palm photo application stores user notes at the end of the JPEG file itself. This method will extract that note and return it. C is returned if the note is unavailable. =cut sub ParseNote { return ($_[0] =~ /NOTE.{8}([^\0]+)\0*ARCPHOTOBASE.{8}$/so) ? $1 : undef; } =head2 ParseAlbum my $album = slurp("/DCIM/Unfiled/Album.db"); my @records = Palm::ZirePhoto::ParseAlbum( $album ); print $_->{name},"\n" for( @records ); Photos on memory cards are stored in subdirectories of C. The meta-data for these images are stored in C files under each category directory. This method will parse out the meta-data into an array of records similar to those returned by C. Thumbnail information, however, is not available. =cut sub ParseAlbum { my $album = shift; # make sure it's an expected record format. return undef unless $album =~ /^DBFH/o; my @records; # skip .db file's initial 16 byte header, then grab 292 byte records for( my $pos = 16; $pos < length($album); $pos += 292 ) { my $buf = substr( $album, $pos, 292 ); last if length($buf) < 292; my %record; @record{ 'name', 'time1_secs', 'time2_secs', 'size', 'width', 'height' } = unpack('a256 x4 N N N x8 n n x8', $buf); $record{name} =~ s/\0+$//o; $record{time1} = $record{time1_secs} - 2082844800; $record{time2} = $record{time2_secs} - 2082844800; $record{'thumbSize'} = 0; $record{'thumb'} = ''; $record{'nameSize'} = length $record{name}; push @records, \%record; } return @records; } 1; __END__ =head1 SOURCE CONTROL The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master =head1 AUTHOR Alessandro Zummo, C<< >> Currently maintained by brian d foy, C<< >> =head1 SEE ALSO Palm::PDB(3) Palm::StdAppInfo(3) =cut p5-Palm-1.012/LICENSE0000644000076500000240000000007111341055215012706 0ustar brianstaffYou can use p5-Palm under the same terms as Perl itself. p5-Palm-1.012/Makefile.PL0000644000076500000240000000133611341055215013660 0ustar brianstaffuse ExtUtils::MakeMaker; require 5.006; eval "use Test::Manifest 1.21"; my @classes = qw( Address Datebook DateTime Mail Memo PDB Raw StdAppInfo ToDo ZirePhoto ); WriteMakefile( 'NAME' => 'Palm', 'ABSTRACT' => 'Module for handling PalmOS databases', 'VERSION_FROM' => 'lib/Palm.pm', 'LICENSE' => 'perl', 'DISTNAME' => 'p5-Palm', # More descriptive than just "Palm" 'AUTHOR' => 'brian d foy ', 'PREREQ_PM' => { 'Test::More' => '0', }, 'PM' => { 'lib/Palm.pm' => '$(INST_LIBDIR)/Palm.pm', map { ( "lib/$_.pm", "\$(INST_LIBDIR)/Palm/$_.pm" ) } @classes }, 'EXE_FILES' => [ 'examples/pdbdump' ], clean => { FILES => q|*.bak p5-*| }, ); p5-Palm-1.012/MANIFEST0000644000076500000240000000110311341055221013024 0ustar brianstaffChanges examples/add-memo examples/copydb examples/pdbdump FAQ lib/Address.pm lib/Datebook.pm lib/DateTime.pm lib/Mail.pm lib/Memo.pm lib/Palm.pm lib/PDB.pm lib/Raw.pm lib/StdAppInfo.pm lib/ToDo.pm lib/ZirePhoto.pm LICENSE Makefile.PL MANIFEST README t/address.t t/AddressDB.pdb t/bogus1DB.pdb t/bogus2DB.pdb t/bogusdb.t t/datebook.t t/DatebookDB.pdb t/load.t t/loadwrite.t t/mail.t t/MailDB.pdb t/memo.t t/MemoDB.pdb t/mkpdbname.t t/pod.t t/pod_coverage.t t/test_manifest t/todo.t t/ToDoDB.pdb TODO META.yml Module meta-data (added by MakeMaker) p5-Palm-1.012/META.yml0000664000076500000240000000104011341055221013146 0ustar brianstaff--- #YAML:1.0 name: p5-Palm version: 1.012 abstract: Module for handling PalmOS databases author: - brian d foy license: perl distribution_type: module configure_requires: ExtUtils::MakeMaker: 0 build_requires: ExtUtils::MakeMaker: 0 requires: Test::More: 0 no_index: directory: - t - inc generated_by: ExtUtils::MakeMaker version 6.50 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 p5-Palm-1.012/README0000644000076500000240000000406511341055215012570 0ustar brianstaff This is p5-Palm, a set of Perl 5 modules for reading, manipulating, and writing the .pdb and .prc database files used by PalmOS devices such as the PalmPilot and its successors. Copyright (C) 1999-2000 by Andrew Arensburger This program is free software; you can redistribute it and/or modify it under the terms of the Artistic License, a copy of which can be found with the Perl distribution. This code 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 Artistic License for more details. * INSTALLING: This is pretty standard: - Run "perl Makefile.PL" to create the Makefile. - Run "make" to build everything. - Run "make install" to install the package. Easy, huh? * DOCUMENTATION: All documentation is in the form of PODs embedded in the module files themselves. Use pod2text Palm/PDB.pm to read the documentation for Palm::PDB. You'll want to start with Palm::PDB, then Palm::Raw, then the others. * FEEDBACK: If you have any comments, suggestions, bug reports, patches, etc., please send mail to the maintainer, brian d foy, at . * CONTRIBUTING The source is in Github: http://github.com/briandfoy/p5-Palm/tree/master If you have a change, fork the repo, make your patch, and send me a pull request. Don't be scared; it's easy! * AUTHORS AND CONTRIBUTORS: Andrew Arensburger : principal perpetrator Robert Norris : patch for empty PDBs Sumant S.R. Oemrawsingh : patch for inconsistent record fields in Mail.pm John Jannotti : patches to make Perl 5.6 shut up. John-David Smith : added "archive" attribute. Kenneth Lorber : fixed "pdbdump" to handle arrays in parsed records (e.g., Datebook exceptions). Alessandro Zummo : DateTime.pm, ZirePhoto.pm Kurt Starsinic : documentation patch. Seb Wills : documentation. brian d foy p5-Palm-1.012/t/0000755000076500000240000000000011341055220012142 5ustar brianstaffp5-Palm-1.012/t/address.t0000755000076500000240000000162511341055215013767 0ustar brianstaff# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) BEGIN { $| = 1; print "1..3\n"; } END {print "not ok 1\n" unless $loaded;} use Palm::PDB; $loaded = 1; print "ok 1\n"; use Palm::Address; ######################### End of black magic. # Insert your test code below (better if it prints "ok 13" # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): my $pdb = new Palm::PDB; eval { $pdb->Load( 't/AddressDB.pdb' ); }; unless( $@ ) { print "ok 2\n"; } else { print "not ok 2\n"; } my $recs = 0; for( @{$pdb->{'records'}} ) { $recs ++; } print (($recs == 2) ? "ok 3\n" : "not ok 3\n"); 1; p5-Palm-1.012/t/AddressDB.pdb0000644000076500000240000000251211341055215014430 0ustar brianstaffAddressDB:i/vvvv`DATAaddrUnfiledBusinessPersonalQuickList ?Last nameFirst nameCompanyWorkHomeFaxOtherE-mailAddressCityStateZip CodeCountryTitleCustom 1Custom 2Custom 3Custom 4NoteMainPagerMobile5U AccessoriesPalm, Inc.800-881-7256 (US)800-891-6342 (Canada)Int'l: See Worldwide Customer Support Information CardWebsite: www.palm.comTo find accessories and useful add-on software, please visit our web site at www.palm.com.2UTechnical SupportPalm, Inc.847-262-PALM (7256) (US)Int'l: See Worldwide Customer Support Information CardWebsite: www.palm.comsupport@palm.comFor technical support outside of the US, please use the numbers listed on the Worldwide Customer Support Information card. For the latest information on products and upgrades, check our web site regularly at www.palm.com.p5-Palm-1.012/t/bogus1DB.pdb0000644000076500000240000000250611341055215014246 0ustar brianstaffBogus1..........................:i/vvvv`DATAaddrUnfiledBusinessPersonalQuickList ?Last nameFirst nameCompanyWorkHomeFaxOtherE-mailAddressCityStateZip CodeCountryTitleCustom 1Custom 2Custom 3Custom 4NoteMainPagerMobile5U AccessoriesPalm, Inc.800-881-7256 (US)800-891-6342 (Canada)Int'l: See Worldwide Customer Support Information CardWebsite: www.palm.comTo find accessories and useful add-on software, please visit our web site at www.palm.com.2UTechnical SupportPalm, Inc.847-262-PALM (7256) (US)Int'l: See Worldwide Customer Support Information CardWebsite: www.palm.comsupport@palm.comFor technical support outside of the US, please use the numbers listed on the Worldwide Customer Support Information card. For the latest information on products and upgrades, check our web site regularly at www.palm.com.p5-Palm-1.012/t/bogus2DB.pdb0000644000076500000240000000011011341055215014234 0ustar brianstaffbogus2DB:i/vvvv`DATAaddrp5-Palm-1.012/t/bogusdb.t0000755000076500000240000000214511341055215013765 0ustar brianstaff# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) BEGIN { $| = 1; print "1..4\n"; } END {print "not ok 1\n" unless $loaded;} use Palm::PDB; $loaded = 1; print "ok 1\n"; use Palm::Raw; ######################### End of black magic. # Insert your test code below (better if it prints "ok 13" # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): eval { my $pdb = new Palm::PDB; $pdb->Load( 't/bogusdb.t' ); }; unless( $@ ) { print "not ok 2\n"; } else { print "ok 2\n"; } eval { # name doesn't have a NUL my $pdb = new Palm::PDB; $pdb->Load( 't/bogus1DB.pdb' ); }; unless( $@ ) { print "not ok 3\n"; } else { print "ok 3\n"; } eval { # truncated after header my $pdb = new Palm::PDB; $pdb->Load( 't/bogus2DB.pdb' ); }; unless( $@ ) { print "not ok 4\n"; } else { print "ok 4\n"; } 1; p5-Palm-1.012/t/datebook.t0000755000076500000240000000162711341055215014134 0ustar brianstaff# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) BEGIN { $| = 1; print "1..3\n"; } END {print "not ok 1\n" unless $loaded;} use Palm::PDB; $loaded = 1; print "ok 1\n"; use Palm::Datebook; ######################### End of black magic. # Insert your test code below (better if it prints "ok 13" # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): my $pdb = new Palm::PDB; eval { $pdb->Load( 't/DatebookDB.pdb' ); }; unless( $@ ) { print "ok 2\n"; } else { print "not ok 2\n"; } my $recs = 0; for( @{$pdb->{'records'}} ) { $recs ++; } print (($recs == 7) ? "ok 3\n" : "not ok 3\n"); 1; p5-Palm-1.012/t/DatebookDB.pdb0000644000076500000240000000122011341055215014566 0ustar brianstaffDatebookDB/vDATAdatet@[P@[P@[P)@[P?@[PO@[Po@[P ȏ$11 to 12, repeat daily ȏ$!12 to 1:30, repeat weekly until 2007ȏ$all day event, yearly repeatȏuntimed event ȏ:8 tro 9 ȏD9 to 10, 15 min alarm ȏD10 to 11, 3 hour alarmp5-Palm-1.012/t/load.t0000644000076500000240000000034411341055215013253 0ustar brianstaffuse vars qw(@classes); BEGIN { @classes = map { "Palm::$_" } qw( Address Datebook Mail Memo PDB Raw StdAppInfo ToDo ); } use Test::More tests => scalar @classes; foreach my $class ( @classes ) { use_ok( $class ); } p5-Palm-1.012/t/loadwrite.t0000755000076500000240000000120511341055215014326 0ustar brianstaffuse strict; use Test::More qw(no_plan); BEGIN { use_ok('Palm::PDB') } BEGIN { use_ok('Palm::Raw') } BEGIN { use_ok('IO::File') } my $name = "t/Test.pdb"; my $pdb = new Palm::Raw; ok( defined $pdb ); $pdb->{name} = "Test"; for( qw(tic tac toe) ) { print "storing '$_'\n"; my $rec = $pdb->append_Record(); ok( defined $rec ); $rec->{data} = $_; } $pdb->Write( $name ); pass( "Write" ); undef $pdb; my $fh = new IO::File $name, "r+"; ok( defined $fh ); $pdb = new Palm::PDB; ok( defined $pdb ); $pdb->Load( $fh ); pass( "Load" ); for( @{$pdb->{records}} ) { print "got '$_->{data}'\n"; ok( defined $_ and length $_->{data} > 0 ); } 1; p5-Palm-1.012/t/mail.t0000755000076500000240000000161711341055215013265 0ustar brianstaff# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) BEGIN { $| = 1; print "1..3\n"; } END {print "not ok 1\n" unless $loaded;} use Palm::PDB; $loaded = 1; print "ok 1\n"; use Palm::Mail; ######################### End of black magic. # Insert your test code below (better if it prints "ok 13" # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): my $pdb = new Palm::PDB; eval { $pdb->Load( 't/MailDB.pdb' ); }; unless( $@ ) { print "ok 2\n"; } else { print "not ok 2\n"; } my $recs = 0; for( @{$pdb->{'records'}} ) { $recs ++; } print (($recs == 2) ? "ok 3\n" : "not ok 3\n"); 1; p5-Palm-1.012/t/MailDB.pdb0000644000076500000240000000076411341055215013734 0ustar brianstaffMailDB=k`DATAmailtARARInboxOutboxDeletedFiledDraft  Dtestchrissomeone elsethis is a test e-mail@test2 chris this is a high priority mailingp5-Palm-1.012/t/memo.t0000755000076500000240000000161711341055215013300 0ustar brianstaff# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) BEGIN { $| = 1; print "1..3\n"; } END {print "not ok 1\n" unless $loaded;} use Palm::PDB; $loaded = 1; print "ok 1\n"; use Palm::Memo; ######################### End of black magic. # Insert your test code below (better if it prints "ok 13" # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): my $pdb = new Palm::PDB; eval { $pdb->Load( 't/MemoDB.pdb' ); }; unless( $@ ) { print "ok 2\n"; } else { print "not ok 2\n"; } my $recs = 0; for( @{$pdb->{'records'}} ) { $recs ++; } print (($recs == 4) ? "ok 3\n" : "not ok 3\n"); 1; p5-Palm-1.012/t/MemoDB.pdb0000644000076500000240000000660611341055215013750 0ustar brianstaffMemoDBuvvvvpDATAmemo 8UnfiledBusinessPersonal Handheld Basics Press any application button to turn on your handheld computer and start a main application. Tap New to create a new record. Tap a record to edit it. Tap Details to access more options or delete the record. Tap the pick list in the upper-right corner to categorize records or create a new category. (Not in Date Book.) Tap the information ("i") icon in dialog boxes for tips. Press the HotSync button on the cable to exchange and update data between your computer and your handheld.4 Ways to Enter Text 1. Type into your Desktop software, attach your handheld to the cable, and press the HotSync button to transfer the typed data. 2. Use the onscreen keyboard. In the Graffiti writing area below, tap the dot below "abc" for the alphabetic keyboard or the dot below "123" for the numeric keyboard. 3. Use the stylus to write Graffiti characters in the writing area. Characters are mainly uppercase, single-stroke letters. Write letters on the left, numbers on the right. Note: To call up a Graffiti reference screen, drag the stylus from the bottom of the writing area to the top of the screen. Or see the Graffiti Reference Card. 4. Connect a portable keyboard to your handheld and type the data directly into your handheld.Download Free Applications You can easily download lots of freeware, shareware, and commercial applications from the Internet. Just follow these 4 steps: 1. Select and download applications from any Palm related web site (e.g., www.palm.com). 2. If the downloaded application is a Zip file, unzip its contents to the add-on folder in the Desktop Software directory (e.g., c:\palm\add-on). 3. Open the Palm Desktop software and click Install (or run the Install Tool from the Palm Desktop software directory). Then click Add. Select the applications you want to install. Click Open, then click Done. 4. Perform a HotSync operation to transfer your new applications to your handheld.Power Tips To improve your Graffiti accuracy, write letters BIG. (Having trouble with "V"? Try writing it backwards.) In Date Book Day View, press the scroll button on your handheld to move backward and forward one day at a time. To move an event to another day or time, tap Details and change the day or time there. In Note Pad, To Do List, and Memo you don't have to tap New to create a new record; just start writing. In Note Pad, this creates a title for a new note or drawing. In Date Book, this creates a new UNTIMED event. To create a new TIMED event, just write the time in the Graffiti number area. To set up your business card for beaming, go to Address Book and select your own name & address. Tap Menu. Then tap Select Business Card. To quickly find an application icon in the Launcher, switch to the All category and use Graffiti writing to write the first letter of the application's title. The icons with titles starting with that letter spring to view. Want to change your alarm sounds? In Date Book, Note Pad, or Clock, choose Preferences from the Options menu. p5-Palm-1.012/t/mkpdbname.t0000644000076500000240000000034311341055215014271 0ustar brianstaffuse Test::More tests => 5; BEGIN { use_ok('Palm') } for( "Test.pdb", "Test\x04.pdb", "Test\x80.prc", "Test\xfe.pdb" ) { print "Trying $_..."; my $n = Palm::mkpdbname( $_ ); print $n,"\n"; ok( $n =~ m/[\x21-\x73]/ ); } 1; p5-Palm-1.012/t/pod.t0000644000076500000240000000020111341055215013106 0ustar brianstaffuse Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); p5-Palm-1.012/t/pod_coverage.t0000644000076500000240000000103311341055215014765 0ustar brianstaffuse vars qw(@classes %ignore); %ignore = ( Palm::PDB => [ qw( new_Resource ) ], ); BEGIN { @classes = map { "Palm::$_" } qw( Address Datebook Mail Memo PDB Raw StdAppInfo ToDo ); } use Test::More; eval "use Test::Pod::Coverage 1.00"; plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD coverage" if $@; plan tests => scalar @classes; foreach my $class ( @classes ) { pod_coverage_ok( $class, { also_private => [ qr/^Pa(ck|rse)/, @{ $ignore{$class} || [] } ], }, "$class pod coverage", ); } p5-Palm-1.012/t/test_manifest0000644000076500000240000000014711341055215014740 0ustar brianstaffload.t pod.t pod_coverage.t loadwrite.t address.t bogusdb.t datebook.t mail.t memo.t mkpdbname.t todo.tp5-Palm-1.012/t/todo.t0000755000076500000240000000161711341055215013310 0ustar brianstaff# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) BEGIN { $| = 1; print "1..3\n"; } END {print "not ok 1\n" unless $loaded;} use Palm::PDB; $loaded = 1; print "ok 1\n"; use Palm::ToDo; ######################### End of black magic. # Insert your test code below (better if it prints "ok 13" # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): my $pdb = new Palm::PDB; eval { $pdb->Load( 't/ToDoDB.pdb' ); }; unless( $@ ) { print "ok 2\n"; } else { print "not ok 2\n"; } my $recs = 0; for( @{$pdb->{'records'}} ) { $recs ++; } print (($recs == 1) ? "ok 3\n" : "not ok 3\n"); 1; p5-Palm-1.012/t/ToDoDB.pdb0000644000076500000240000000145611341055215013716 0ustar brianstaffToDoDB0ZvwvwXDATAtodorUnfiledBusinessPersonal Don't forget to register!To register your Palm handheld electronically, you must have an Internet connection or a modem connected to your computer. Or if you prefer, you can fill out and send the mail-in registration card included in your Palm package. Special rewards only for registered users: -FREE software programs * -Upgrade information -Important updates * For a complete list of free software, go to www.palm.com/eregdownload p5-Palm-1.012/TODO0000644000076500000240000000120611341055215012372 0ustar brianstaff- POD for add-memo. - Add support for general-purpose parsers: read the database type (PDB or PRC), creator, and type, and look them up in a database. This should return the name of a Perl module to use to parse that particular database type. The best way of doing this appears to be sub load_module { eval "require $_[0]"; die if $@; $_[0]->import($_[1..$#_]); } See "Perl Cookbook," 12.3. - Add PRC support. Unlike PDBs, the thing to do here is to have registered handlers for various resource types. - Add PQA parser. - Make $VERSION be a floating-point number. See mail from Andreas J. Koenig