[Thread Prev][Thread Next][Thread Index]
The BBDB sync program
- To: pilotmgr@xxxxxxxxxxxxxxxxxxxxxxx
- Subject: The BBDB sync program
- From: Dinesh Dutt <ddutt@xxxxxxxxx>
- Date: Mon, 22 Mar 1999 22:28:47 -0800 (PST)
- Comments: Hyperbole mail buttons accepted, v04.023.
- Request-delivery-notification: TRUE
- Sender: owner-pilotmgr@moshpit
I had earlier announced the alpha version of a program to sync the BBDB
database in (X)Emacs with the Palm's Address DB. I am including the program
itself to the end of this email.
This is still rough code and so its advisable to back up one's data before
trying this program.
Please send me your comments, suggestions, criticisms etc. Hope this is
useful.
Thanks,
Dinesh
--
#!/usr/bin/perl
###############################################################################
# bbdbSync.pl #
# #
# This program is used to sync the database maintained by BBDB with the #
# Address book database maintained on the Palm Pilot family of devices. #
# WARNING !!! This is still a very rough version. Save your information b4 #
# using this program. #
# #
# Features: #
# o Updates both databases (Address DB & BBDB) with information contained #
# only in either one or both. So entries only in the Address DB are added #
# to BBDB on sync'ing and vice-versa. #
# o Merges common records assuming the information in Address DB is always #
# more up-to-date except for undefined fields. #
# o Deletes records from the BBDB that have been deleted on the Address DB #
# (after the initial sync.) #
# o Handles archived records in Address DB. #
# o Can capture and store information in Notes and custom fields in the #
# address book. #
# o Can correctly label the phone numbers with the label used in Address DB #
# o Ability to not sync a category or sync only a category. #
# #
# Constraints: #
# o Only tested on BBDB file version 2. #
# o Cannot handle multiple "don't sync" or "only sync" categories. #
# o Cannot handle private entries #
# o No support on BBDB side to delete or archive entries. #
# o Cannot restore archived entries back to Palm. #
# o Assumes that the only variable phone field is the "other". #
# o Does not handle renamed custom field names. #
# #
# Usage: #
# -s <category> - Sync only category specified #
# -d <category> - Default category to assign to entries in BBDB, but not in #
# Address DB. #
# -n <category> - Don't sync entries belonging to specified category. #
# -o <output file> - Name of output BBDB file (Default is ~/.bbdb.sync). #
# -f <input file> - Name of BBDB file to read from (Default is ~/.bbdb). #
# -v - Verbose mode (display some processing information. #
# #
# Requirements: #
# Perl 5 (tested with both 5.003 and 5.004) #
# Perl module PDA::Pilot (available with pilot-link program). #
# #
# Credits: #
# Borrowed GetFields, MatchString & MatchParen routines from Seth Golub #
# <seth@xxxxxxxxxxxx>. #
# #
# Licensing Information: #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; either version 1, or (at your option) #
# any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# A copy of the GNU General Public License can be obtained from this #
# program's author (send electronic mail to the above address) or from #
# Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #
# #
# Author: Dinesh G Dutt (ddutt@xxxxxxxxx) #
###############################################################################
use PDA::Pilot;
use Getopt::Std;
getopts ("vhf:d:n:o:s:");
###############################################################################
# Initializing global variables
###############################################################################
$verbose = $opt_v || 0;
$bbdbFile = $opt_f || $ENV{BBDB} || "$ENV{HOME}/.bbdb"; # database file
$DefaultCategory = $opt_d || "Unfiled";
$DontSyncCategory = $opt_n || "";
$OnlySyncCategory = $opt_s if (defined ($opt_s));
$OutputFile = $opt_o || "$ENV{HOME}/.bbdb.sync";
$DefaultPhoneLabelNo = 0;
@PhoneLabels = ("Work", "Home", "Fax", "Other", "E-mail", "Main", "Pager",
"Mobile", "nil");
$PhoneInvLabels = {
'Work' => 0,
'Home' => 1,
'Fax' => 2,
'Other' => 3,
'E-mail' => 4,
'Main' => 5,
'Pager' => 6,
'Mobile' => 7
};
$ShowPhoneUndef = 8; # Undefined value
$DefaultShowPhone = 0;
@CustomLabels = ("CUSTOM1", "CUSTOM2", "CUSTOM3", "CUSTOM4");
###############################################################################
# MAIN routine starts
###############################################################################
if ($opt_h) {
&Usage;
exit (0);
}
if (defined ($OnlySyncCategory) && ($DontSyncCategory ne "")) {
print "Cannot specify both -n and -s options\n";
&Usage;
exit (1);
}
# Open bbdb file or STDIN and build the database
if($bbdbFile eq "-")
{
$BBDB = STDIN;
} else {
open(BBDB, $bbdbFile) || die "$0: Can't open database: $bbdbFile\n";
$BBDB = BBDB;
}
&BbdbBuildDb();
# Connect with the Palm and open the relevant databases
if ($ARGV[0]) {
$port = $ARGV[0];
} else {
$port = "/dev/pilot";
}
$socket = PDA::Pilot::openPort ($port);
print "Now press the HotSync button\n";
$dlp = PDA::Pilot::accept($socket);
$db = $dlp->open("AddressDB");
&AddressDbBuildDb ($db);
# Write the Palm pilot first so that we can get the record Id for the records
# to store along with the record database.
&AddressDbWrite ();
&BbdbWrite ();
undef $db;
undef $dlp;
exit 0;
###############################################################################
# Adds to the database of records by reading the address book on the Pilot. It
# simulataneously updates matching records and marks deleted and archived
# records. It updates the BbdbData array.
###############################################################################
sub AddressDbBuildDb {
my ($db) = @_;
my ($isMatch, $alias, $key, $name);
# print "Labels are: ";
# foreach $label (@{$appblock->{"label"}}) {
# print $label;
# }
# print "\n";
# Retrieve categories
$app = $db->getAppBlock;
$k = 0;
foreach $cat (@{$app->{categoryName}}) {
$categoryList{$k} = $cat;
$categoryInvList{$cat} = $k++;
}
$i = 0;
$alias = "nil";
PALMRECORD:
foreach $id ($db->getRecordIDs()) {
undef @entry;
undef @phoneLabel;
undef $recId;
$isMatch = 0;
$r = $db->getRecordByID($id);
last if ($dlp->getStatus() < 0);
last if (!defined($r));
# Handle deleted and archived entries
if ($r->{'deleted'}) {
$key = &BbdbFindRecByRecID ($r->{'id'});
if (defined ($key)) {
$BbdbData{$key}->{DELETED} = 1;
}
next PALMRECORD;
}
if ($r->{'archived'}) {
$key = &BbdbFindRecByRecID ($r->{'id'});
if (defined ($key)) {
$BbdbData{$key}->{ARCHIVED} = 1;
}
next PALMRECORD;
}
@entry = @{$r->{"entry"}};
@phoneLabel = @{$r->{'phoneLabel'}};
$showPhone = $r->{'showPhone'};
$showPhone = 0 if (!defined ($showPhone));
$recId = $r->{'id'};
foreach $i (0 .. 18) {
$entry[$i] = "nil" if (!defined ($entry[$i]) ||
($entry[$i] eq ""));
}
# 8 is the index of "nil" in the @PhoneLabels array
if ($entry[3] eq "nil") {$entry[3] = "0"; $phoneLabel[0] = "8";}
if ($entry[4] eq "nil") {$entry[4] = "0"; $phoneLabel[1] = "8";}
if ($entry[5] eq "nil") {$entry[5] = "0"; $phoneLabel[2] = "8";}
if ($entry[6] eq "nil") {$entry[6] = "0"; $phoneLabel[3] = "8";}
if ($entry[7] eq "nil") {$phoneLabel[4] = "8";}
$address = "nil";
# If the names match both in the first & last or organization names
# match, we consider it to be the same record.
if (($entry[0] ne "nil") && defined ($BbdbData{$entry[0]})) {
if ($BbdbData{$entry[0]}->{NAME} eq $entry[1]) {
$isMatch = 1;
$key = $entry[0];
}
}
elsif (($entry[2] ne "nil") && defined ($BbdbData{$entry[2]})) {
$isMatch = 1;
$key = $entry[2];
}
elsif (($entry[7] ne "nil") && defined ($BbdbData{$entry[7]})) {
$isMatch = 1;
$key = $entry[7];
}
# Construct the address field in the format of BBDB
if (($entry[8] ne "nil") && ($entry[9] ne "nil")) {
if ($entry[10] ne "nil") {
$address = "\"$entry[9], $entry[10]\" \"$entry[8]\" \"\" \"\" \"$entry[9]\" \"$entry[10]\"";
}
else {
$address = "\"$entry[9]\" \"$entry[8]\" \"\" \"\" \"$entry[9]\" \"\"";
}
if ($entry[11] ne "nil") {
$address .= " \"$entry[11]\"";
}
}
$bbdbRecord = {
LNAME => $entry[0],
NAME => $entry[1],
ALIAS => $alias,
TITLE => $entry[13],
ORG => $entry[2],
PHONE => $entry[3], # Work phone #
PHLABEL1 => $PhoneLabels[$phoneLabel[0]],
HOME => $entry[4], # Home phone #
PHLABEL2 => $PhoneLabels[$phoneLabel[1]],
FAX => $entry[5], # Fax #
PHLABEL3 => $PhoneLabels[$phoneLabel[2]],
OTHER => $entry[6], # Other phone #
PHLABEL4 => $PhoneLabels[$phoneLabel[3]],
ADDRESS => $address,
STREET => $entry[8], # Street address
CITY => $entry[9],
STATE => $entry[10],
ZIPCODE => $entry[11],
COUNTRY => $entry[12],
EMAIL => $entry[7],
PHLABEL5 => $PhoneLabels[$phoneLabel[4]],
CUSTOM1 => $entry[14],
CUSTOM2 => $entry[15],
CUSTOM3 => $entry[16],
CUSTOM4 => $entry[17],
NOTES => $entry[18],
CATEGORY => $categoryList{$r->{"category"}},
SHOWPHONE => $showPhone,
PILOTID => $recId,
ARCHIVED => $r->{'archived'},
DELETED => 0,
INBBDB => $isMatch,
INPILOT => 1
};
# Build list of labels used for the different phones
$k = 1;
foreach $fieldName ("PHONE", "HOME", "FAX", "OTHER") {
if (defined $bbdbRecord->{$fieldName} &&
($bbdbRecord->{$fieldName} ne "nil") &&
($bbdbRecord->{$fieldName} ne "0")) {
$name = "PHLABEL".$k;
$userFields{$bbdbRecord->{$name}} = 1;
}
$k++;
}
foreach $fieldName ("CUSTOM1", "CUSTOM2", "CUSTOM3", "CUSTOM4") {
if (($bbdbRecord->{$fieldName} ne "nil") &&
($bbdbRecord->{$fieldName} ne "0")) {
$userFields{$fieldName} = 1;
}
}
# Update the record for a matching entry immediately.
if ($isMatch && (($bbdbRecord->{CATEGORY} ne $DontSyncCategory) ||
(defined ($OnlySyncCategory) &&
($OnlySyncCategory eq $bbdbRecord->{CATEGORY})))) {
foreach $fieldName ("LNAME", "NAME", "ALIAS", "TITLE", "ORG",
"PHLABEL1", "PHLABEL2", "PHLABEL3", "PHLABEL4",
"STREET",
"CITY", "STATE", "ZIPCODE", "COUNTRY", "EMAIL",
"PHLABEL5", "CUSTOM1", "CUSTOM2", "CUSTOM3",
"CUSTOM4", "NOTES", "CATEGORY", "SHOWPHONE",
"ARCHIVED", "PILOTID") {
if ($bbdbRecord->{$fieldName} ne "nil") {
$BbdbData {$key}->{$fieldName} = $bbdbRecord->{$fieldName};
}
}
foreach $fieldName ("PHONE", "HOME", "FAX", "OTHER") {
if ($bbdbRecord->{$fieldName} ne "0") {
$BbdbData {$key}->{$fieldName} = $bbdbRecord->{$fieldName};
}
}
# If the deleted field is set and we have come this far, it means
# that the record must be deleted from the palm and has been
# deleted from the desktop already.
# If the record is archived, we must delete it from the Palm as we
# have now the information to save it into the desktop file.
if ($BbdbData {$key}->{DELETED} ||
$BbdbData {$key}->{ARCHIVED}) {
$r->deleteRecord ();
}
else {
$BbdbData {$key}->{INPILOT} = 1;
&AddressDbWriteRecord ($r, $key);
}
}
elsif (($bbdbRecord->{CATEGORY} ne $DontSyncCategory) ||
(defined ($OnlySyncCategory) &&
($bbdbRecord->{CATEGORY} eq $OnlySyncCategory))) {
if ($bbdbRecord->{LNAME} ne "nil") {
$BbdbData {$bbdbRecord->{LNAME}} = $bbdbRecord;
}
elsif ($bbdbRecord->{ORG} ne "nil") {
$BbdbData {$bbdbRecord->{ORG}} = $bbdbRecord;
}
else {
$BbdbData {$bbdbRecord->{EMAIL}} = $bbdbRecord;
}
}
}
}
###############################################################################
# Builds the database of local records by reading the .bbdb file.
###############################################################################
sub BbdbBuildDb {
my ($phoneNo, $ext, $customField, $category, $field);
my ($lname, $name, $alias, $org, $phone, $address, $email, $notes);
my ($street, $city, $zipcode, $state, $country, $deleted, $archived);
my ($recID);
my ($k, $line);
my (@custom, @phones, @phoneLabel, @bbdb, @record, @fieldList);
# Grab user field list
while(<$BBDB>)
{
last if !/^;;; /;
last if /^;;; user-fields: \(.*\)/;
if (/^;;; file-version: (.*)$/) {
if ($1 ne "2") {
print "ERROR: Can currently only work with version 2 files\n";
close BBDB;
exit (1);
}
}
}
if (defined ($_)) {
($line) = (/^;;; user-fields: \((.*)\)/);
@fieldList = split (/ /, $line);
foreach $field (@fieldList) {
$userFields{$field} = 1;
}
}
# Add the record ID field to the list of user-defined fields
if (!defined ($userFields{'RecordID'})) {
$userFields{'RecordID'} = 1;
}
@bbdb = <$BBDB>; # Read in the rest of the database
@bbdb = grep(!/^;/, @bbdb); # Filter out the comments now;
for ($i=0; $i <= $#bbdb; $i++) {
$recId = 0;
foreach $k (0 .. 3) {
$phones[$k] = "0";
$phoneLabel[$k] = "8";
}
for ($k = 0; $k < 4; $k++) {
$custom[$k] = "nil";
}
$deleted = 0;
$archived = 0;
@record = &GetFields($bbdb[$i]);
($name, $lname, $alias, $org, $phone, $address, $email, $notes, undef)
= @record;
$name =~ s/\["*(.*)"*/$1/g;
$name =~ s/\"//g;
$lname =~ s/\"//g;
# Extract telephone number in xxx-xxx-xxxxX<extension> format
if ($phone ne "nil") {
($phoneNo, $ext) = &BbdbGetNumberFromPhoneField ($phone);
if (!defined ($phoneNo)) {
$phones[0] = "0";
$ext = "0";
$phoneLabel[0] = "8";
}
else {
$phones[0] = $phoneNo;
$phoneLabel = $DefaultPhoneLabelNo;
}
if (defined ($ext) && ($ext != 0)) {
$phones[0] = $phoneNo."x".$ext;
$phoneLabel[0] = $DefaultPhoneLabelNo;
}
}
else {
$phones[0] = "0";
$phoneLabel[0] = "8"; # 8 is the index of "nil" in PhoneLabels
}
# BBDB's address stores everything from street address to zipcode in
# one string. Split it up for merging with Palm Pilot's format
if ($address ne "nil") {
($street, $city, $state, $zipcode) =
BbdbGetAddressFields ($address);
$street = "nil" if (!defined ($street));
$city = "nil" if (!defined ($city));
$state = "nil" if (!defined ($state));
$zipcode = "0" if (!defined ($zipcode));
# Strip the leading & trailing "["
$address =~ s/^\[//;
$address =~ s/\]$//;
}
else {
$street = $state = $city = "nil";
$zipcode = "0";
}
# Strip leading & trailing '"'
if (defined ($email)) {
$email =~ s/^\"//;
$email =~ s/\"$//;
}
# The Notes field can consist of not just not notes, but also names
# and values for user-defined fields. Extract these.
if ($notes ne "nil") {
$userNotes = &BbdbGetNotes ($notes);
$userNotes = "nil" if (!defined ($userNotes));
# Extract user-configured fields
if ($notes =~ m/^\(/) {
foreach $userFieldKey (%userFields) {
$customField = &BbdbGetCustomField ($notes, $userFieldKey);
next if ($customField eq "nil");
if ($userFieldKey eq "Category") {
$category = $customField;
}
if (($userFieldKey eq "Home") &&
(defined ($customField))) {
($phoneNo, $ext) =
&BbdbGetNumberFromPhoneField ($customField);
if (!defined ($phoneNo)) {
$phones[1] = $customField;
}
else {
$phones[1] = $phoneNo;
if (defined ($ext) && ($ext != 0)) {
$phones[1] = $phoneNo."x".$ext;
}
}
$phoneLabel[1] = "1";
}
if (($userFieldKey eq "Fax") &&
(defined ($customField))) {
($phoneNo, $ext) =
&BbdbGetNumberFromPhoneField ($customField);
if (!defined ($phoneNo)) {
$phones[2] = $customField;
}
else {
$phones[2] = $phoneNo;
if (defined ($ext) && ($ext != 0)) {
$phones[2] = $phoneNo."x".$ext;
}
}
$phoneLabel[2] = "2";
}
if (($userFieldKey eq "Other") &&
(defined ($customField))) {
($phoneNo, $ext) =
&BbdbGetNumberFromPhoneField ($customField);
if (!defined ($phoneNo)) {
$phones[3] = $customField;
}
else {
$phones[3] = $phoneNo;
if (defined ($ext) && ($ext != 0)) {
$phones[3] = $phoneNo."x".$ext;
}
}
$phoneLabel[3] = "3";
}
if (($userFieldKey eq "Pager") &&
(defined ($customField))) {
($phoneNo, $ext) =
&BbdbGetNumberFromPhoneField ($customField);
if (!defined ($phoneNo)) {
$phones[3] = $customField;
}
else {
$phones[3] = $phoneNo;
if (defined ($ext) && ($ext != 0)) {
$phones[3] = $phoneNo."x".$ext;
}
}
$phoneLabel[3] = "6";
}
if (($userFieldKey eq "Mobile") &&
(defined ($customField))) {
($phoneNo, $ext) =
&BbdbGetNumberFromPhoneField ($customField);
if (!defined ($phoneNo)) {
$phones[3] = $customField;
}
else {
$phones[3] = $phoneNo;
if (defined ($ext) && ($ext != 0)) {
$phones[3] = $phoneNo."x".$ext;
}
}
$phoneLabel[3] = "7";
}
if (($userFieldKey eq "Main") &&
(defined ($customField))) {
($phoneNo, $ext) =
&BbdbGetNumberFromPhoneField ($customField);
if (!defined ($phoneNo)) {
$phones[3] = $customField;
}
else {
$phones[3] = $phoneNo;
if (defined ($ext) && ($ext != 0)) {
$phones[3] = $phoneNo."x".$ext;
}
}
$phoneLabel[3] = "5";
}
if (($userFieldKey eq "RecordID") &&
(defined ($customField))) {
$recId = $customField;
}
if (($userFieldKey eq "CUSTOM1") &&
(defined ($customField))) {
$custom[0] = $customField;
}
if (($userFieldKey eq "CUSTOM2") &&
(defined ($customField))) {
$custom[1] = $customField;
}
if (($userFieldKey eq "CUSTOM3") &&
(defined ($customField))) {
$custom[2] = $customField;
}
if (($userFieldKey eq "CUSTOM4") &&
(defined ($customField))) {
$custom[2] = $customField;
}
if (($userFieldKey eq "Attributes") &&
(defined ($customField))) {
$deleted = 1 if ($customField =~ m/Deleted/);
$archived = 1 if ($customField =~ m/Archived/);
}
}
if (!defined ($userNotes)) {
$userNotes = "nil";
}
if (!defined ($category)) {
$category = $DefaultCategory;
}
}
}
else {
$userNotes = "nil";
$category = $DefaultCategory;
}
$bbdbRecord = {
LNAME => $lname,
NAME => $name,
ALIAS => $alias,
TITLE => "nil",
ORG => $org,
PHONE => $phones[0], # Work phone
PHLABEL1 => $PhoneLabels[$phoneLabel[0]],
HOME => $phones[1], # Home phone #
PHLABEL2 => $PhoneLabels[$phoneLabel[1]],
FAX => $phones[2], # Fax #
PHLABEL3 => $PhoneLabels[$phoneLabel[2]],
OTHER => $phones[3],
PHLABEL4 => $PhoneLabels[$phoneLabel[3]],
ADDRESS => $address,
STREET => $street, # extracted from $address
CITY => $city, # extracted from $address
STATE => $state, # extracted from $address
ZIPCODE => $zipcode, # extracted from $address
COUNTRY => "nil",
EMAIL => $email,
PHLABEL5 => "E-mail", # This is fixed for BBDB
CUSTOM1 => $custom[0],
CUSTOM2 => $custom[1],
CUSTOM3 => $custom[2],
CUSTOM4 => $custom[3],
NOTES => $userNotes,
CATEGORY => $category,
SHOWPHONE => $ShowPhoneUndef,
PILOTID => $recId,
ARCHIVED => $archived,
DELETED => $deleted,
INBBDB => 1,
INPILOT => 0
};
if ($bbdbRecord->{LNAME} ne "nil") {
$BbdbData {$bbdbRecord->{LNAME}} = $bbdbRecord;
}
elsif ($bbdbRecord->{ORG} ne "nil") {
$BbdbData {$bbdbRecord->{ORG}} = $bbdbRecord;
}
else {
$BbdbData {$bbdbRecord->{EMAIL}} = $bbdbRecord;
}
}
}
###############################################################################
# Sorting function for the records. The sequence is :
# if lastname, sort by lastname
# elsif organization, sort by organization
# else sort by email (even if nil)
# The PalmPilot and BBDB both follow this order.
###############################################################################
sub BbdbSort {
$BbdbData{$a}->{LNAME} cmp $BbdbData{$b}->{LNAME};
}
###############################################################################
# Updates the .bbdb file with the sync'd up data. Archived records have the
# category archive and deleted records are not copied to the new file.
###############################################################################
sub BbdbWrite {
my ($firstBrace, $notes);
open (BBDBW, "> $OutputFile") ||
die "Could not open BBDB output file for updating";
print BBDBW ";;; file-version: 2\n";
print BBDBW ";;; user-fields: (";
foreach $field (sort keys %userFields) {
print BBDBW "$field ";
}
print BBDBW ")\n";
# Sort entries by last name, organization or email
foreach $key (sort BbdbSort keys %BbdbData) {
$firstBrace = 0;
next if ($BbdbData{$key}->{DELETED});
next if (($BbdbData{$key}->{CATEGORY} eq $DontSyncCategory) &&
!($BbdbData{$key}->{INBBDB}));
next if ((defined ($OnlySyncCategory) &&
($OnlySyncCategory ne $BbdbData{$key}->{CATEGORY})) &&
!($BbdbData{$key}->{INBBDB}));
print BBDBW "[";
BbdbPrintField ($BbdbData{$key}->{NAME}, "\"", "\" ");
BbdbPrintField ($BbdbData{$key}->{LNAME}, "\"", "\" ");
BbdbPrintField ($BbdbData{$key}->{ALIAS}, "(", ") ");
BbdbPrintField ($BbdbData{$key}->{ORG}, "\"", "\" ");
if ($BbdbData{$key}->{PHONE} ne "0") {
($areacode, $no1, $no2, $ext) =
split (/[-x]/, $BbdbData{$key}->{PHONE});
$ext = 0 if (!defined ($ext));
$areacode = 0 if (!defined ($areacode));
$no1 = 0 if (!defined ($no1));
$no2 = 0 if (!defined ($no2));
print BBDBW "([";
if ($BbdbData{$key}->{CITY} ne "nil") {
BbdbPrintField ("\"$BbdbData{$key}->{CITY}\" ", "", "");
}
else {
BbdbPrintField (" ", "\"", "\" ");
}
BbdbPrintField ($areacode." ".$no1." ".$no2." ".$ext, "", "]) ");
}
else {
BbdbPrintField ("nil", "", " ");
}
BbdbPrintField ($BbdbData{$key}->{ADDRESS}, "([", "]) ");
BbdbPrintField ($BbdbData{$key}->{EMAIL}, "(\"", "\") ");
# BBDB cannot handle newlines in the notes field. Replace newlines with
# spaces.
if ($BbdbData{$key}->{NOTES} ne "nil") {
$notes = $BbdbData{$key}->{NOTES};
$notes =~ s/\n/ /g;
BbdbPrintField ("\"$notes\"",
"((notes . ", ")");
$firstBrace = 1;
}
# Remaining fields are user-defined fields
if (defined ($BbdbData{$key}->{HOME}) &&
($BbdbData{$key}->{HOME} ne "0")) {
if ($BbdbData{$key}->{HOME} =~ m/^\d+/) {
($areacode, $no1, $no2, $ext) =
split (/[-x]/, $BbdbData{$key}->{HOME});
$ext = 0 if (!defined ($ext));
$areacode = 0 if (!defined ($areacode));
$no1 = 0 if (!defined ($no1));
$no2 = 0 if (!defined ($no2));
$value = $areacode." ".$no1." ".$no2." ".$ext;
}
else {
$value = $BbdbData{$key}->{HOME};
}
if ($firstBrace) {
BbdbPrintField ($value,
" ($BbdbData{$key}->{PHLABEL2} . \"", "\") ");
}
else {
BbdbPrintField ($value,
" (($BbdbData{$key}->{PHLABEL2} . \"", "\") ");
$firstBrace = 1;
}
}
if (defined ($BbdbData{$key}->{FAX}) &&
($BbdbData{$key}->{FAX} ne "0")) {
if ($BbdbData{$key}->{FAX} =~ m/^\d+/) {
($areacode, $no1, $no2, $ext) =
split (/[-x]/, $BbdbData{$key}->{FAX});
$ext = 0 if (!defined ($ext));
$areacode = 0 if (!defined ($areacode));
$no1 = 0 if (!defined ($no1));
$no2 = 0 if (!defined ($no2));
$value = $areacode." ".$no1." ".$no2." ".$ext;
}
else {
$value = $BbdbData{$key}->{FAX};
}
if ($firstBrace) {
BbdbPrintField ($value,
" ($BbdbData{$key}->{PHLABEL3} . \"", "\") ");
}
else {
BbdbPrintField ($value,
" (($BbdbData{$key}->{PHLABEL3} . \"", "\") ");
$firstBrace = 1;
}
}
if (defined ($BbdbData{$key}->{OTHER}) &&
($BbdbData{$key}->{OTHER} ne "0")) {
if ($BbdbData{$key}->{OTHER} =~ m/^\d+/) {
($areacode, $no1, $no2, $ext) =
split (/[-x]/, $BbdbData{$key}->{OTHER});
$ext = 0 if (!defined ($ext));
$areacode = 0 if (!defined ($areacode));
$no1 = 0 if (!defined ($no1));
$no2 = 0 if (!defined ($no2));
$value = $areacode." ".$no1." ".$no2." ".$ext;
}
else {
$value = $BbdbData{$key}->{OTHER};
}
if ($firstBrace) {
BbdbPrintField ($value,
" ($BbdbData{$key}->{PHLABEL4} . \"", "\") ");
}
else {
BbdbPrintField ($value,
" (($BbdbData{$key}->{PHLABEL4} . \"", "\")");
$firstBrace = 1;
}
}
# Add the category as a user-defined field
if (defined ($BbdbData{$key}->{CATEGORY}) &&
($BbdbData{$key}->{CATEGORY} ne "nil")) {
if ($firstBrace) {
BbdbPrintField ($BbdbData{$key}->{CATEGORY},
" (Category . \"", "\") ");
}
else {
BbdbPrintField ($BbdbData{$key}->{CATEGORY},
" ((Category . \"", "\")");
$firstBrace = 1;
}
}
# Add the record ID as a user-defined field
if ($firstBrace) {
BbdbPrintField ($BbdbData{$key}->{PILOTID},
" (RecordID . \"", "\") ");
}
else {
BbdbPrintField ($BbdbData{$key}->{PILOTID},
" ((RecordID . \"", "\")");
$firstBrace = 1;
}
# Add the attributes section
if ($BbdbData{$key}->{ARCHIVED}) {
BbdbPrintField ("Archived", " (Attributes . \"", "\")");
}
if ($firstBrace) {
print BBDBW ") ";
}
else {
# nil Notes field and so print nil.
print BBDBW "nil ";
}
print BBDBW "nil]\n";
}
}
sub BbdbPrintField {
my ($field, $prefix, $suffix) = @_;
if (!defined ($field) || $field eq "nil") {
print BBDBW "nil ";
}
else {
print BBDBW $prefix, $field, $suffix;
}
}
###############################################################################
# Updates the address book database on the PalmPilot
###############################################################################
sub AddressDbWrite {
my (@entry, @phoneLabel, $r, $key);
foreach $key (sort BbdbSort keys %BbdbData) {
@entry = ();
@phoneLabel = ();
$recId = 0;
next if ($BbdbData{$key}->{INPILOT});
next if ($BbdbData{$key}->{DELETED});
next if ($BbdbData{$key}->{ARCHIVED});
next if ($BbdbData{$key}->{Category} eq $DontSyncCategory);
next if (defined ($OnlySyncCategory) &&
($OnlySyncCategory ne $BbdbData{$key}->{Category}));
$r = $db->newRecord();
$r->{'id'} = 0;
$recId = &AddressDbWriteRecord ($r, $key);
$BbdbData{$key}->{PILOTID} = $recId;
}
}
###############################################################################
# Writes a single record to the address book database. Uses the BbdbData array
# to extract the fields.
# Inputs : record to be written (PalmPilot record)
# : key of the record to be written in BbdbData array
# Assumes that $db is the database handle to which the record is written to.
###############################################################################
sub AddressDbWriteRecord {
my ($r, $key) = @_;
my ($k);
$k = 0;
foreach $fieldName ("LNAME", "NAME", "ORG", "PHONE",
"HOME", "FAX", "OTHER", "EMAIL",
"STREET", "CITY", "STATE", "ZIPCODE",
"COUNTRY", "TITLE", "CUSTOM1", "CUSTOM2",
"CUSTOM3", "CUSTOM4", "NOTES") {
if (($BbdbData{$key}->{$fieldName} ne "nil") &&
($BbdbData{$key}->{$fieldName} ne "0")) {
$entry[$k++] = $BbdbData{$key}->{$fieldName};
}
else {
$entry[$k++] = "";
}
}
@{$r->{'entry'}} = @entry;
$r->{"category"} = $categoryInvList{$BbdbData{$key}->{CATEGORY}};
# Build Phone Labels
$k = 0;
foreach $label ("PHLABEL1", "PHLABEL2", "PHLABEL3", "PHLABEL4",
"PHLABEL5") {
if ($BbdbData {$key}->{$label} ne "nil") {
$phoneLabel[$k] =
$PhoneInvLabels->{$BbdbData{$key}->{$label}};
if ($BbdbData {$key}->{SHOWPHONE} == $ShowPhoneUndef) {
$BbdbData {$key}->{SHOWPHONE} = $phoneLabel[$k];
}
}
else {
$phoneLabel[$k] = $k;
}
++$k;
}
if ($BbdbData {$key}->{SHOWPHONE} == $ShowPhoneUndef) {
$BbdbData {$key}->{SHOWPHONE} = $DefaultShowPhone;
}
@{$r->{'phoneLabel'}} = @phoneLabel;
$r->{'archived'} = 0;
$r->{'deleted'} = 0;
$r->{'showPhone'} = $BbdbData {$key}->{SHOWPHONE};
print "Updating record for $key on Palm\n" if ($verbose);
$recId = $db->setRecord ($r);
$recId;
}
###############################################################################
# Searches the records for a record with the specified recordID
###############################################################################
sub BbdbFindRecByRecID {
my ($recID) = @_;
my ($entry);
foreach $key (sort BbdbSort keys %BbdbData) {
if ($BbdbData{$key}->{PILOTID} == $recID) {
return $key;
}
}
return;
}
sub GetFields {
my ($i) = 0;
my (@field);
my ($j) = 0;
$j = 0;
while ($j < length($_[0])) {
if (substr($_[0], $j, 1) eq '"') { # ;"
($j, $field[$i++]) = &MatchString($_[0], $j);
}
elsif (substr($_[0], $j, 1) eq '(') {
($j, $field[$i++]) = &MatchParent($_[0], $j);
}
elsif (substr($_[0], $j, 1) ne ' ') {
($j, $field[$i++]) = &MatchWord($_[0], $j);
}
else {
$j ++;
}
}
return @field;
}
sub MatchString {
my ($i) = $_[1];
$i++;
for (; $i < length($_[0]); $i++) {
if (substr($_[0], $i, 1) eq '"') { # ;"
$i++;
return ($i, substr($_[0], $_[1]+1, $i - $_[1] - 2));
}
}
return ($i, substr($_[0], $_[1]+1));
}
sub MatchWord {
my ($i) = $_[1];
my ($startQuote) = 0;
for (; $i < length($_[0]); $i++) {
if (substr($_[0], $i, 1) eq ' ' && !$startQuote) {
return ($i, substr($_[0], $_[1], $i - $_[1]));
}
elsif (substr($_[0], $i, 1) eq '"') {
$startQuote = !$startQuote;
}
}
return ($i, substr($_[0], $_[1]));
}
sub MatchParent {
my ($i) = $_[1];
my ($skip) ;
$stack = 1;
$i++;
for (; $i < length($_[0]); $i++) {
if (substr($_[0], $i, 1) eq '"') { # ;"
($i, $skip) = &MatchString($_[0], $i);
$i --;
}
elsif (index("([", substr($_[0], $i, 1)) >= 0) {
$stack++;
}
elsif (index("])", substr($_[0], $i, 1)) >= 0) {
$stack--;
if ($stack == 0) {
$i++;
return ($i, substr($_[0], $_[1]+1, $i - $_[1] - 2));
}
}
}
return ($i, substr($_[0], $_[1]+1));
}
###############################################################################
# The BBDB representation of phone is not the way we like it to be. Convert it
# into the format xxx-xxx-xxxx add in case of an extension, add the trailing
# x<extension #>.
###############################################################################
sub BbdbGetNumberFromPhoneField {
my ($phoneNo, $extension);
($phoneNo, $extension) =
($_[0] =~ m/[^0-9]*(\d+ \d+ \d+) (\d+).*$/);
# BBDB converts numbers such as 0400 to 400. Fix this - TBD
if (defined ($phoneNo)) {
$phoneNo =~ s/ /-/g;
}
return ($phoneNo, $extension);
}
###############################################################################
# The BBDB address is stored with the street address, city, state and zipcode
# all clumped together. Split them apart into a format similar to the way the
# PalmPilot stores it.
###############################################################################
sub BbdbGetAddressFields {
my ($address) = @_;
my ($streetAddr, $st1, $st2, $st3, $city, $state, $zipcode);
# BBDB's address format is as follows:
# ["location" "street addr 1" "street addr 2" "street addr 3" "city" "state"
# zipcode]
# Our regexp below assumes that there are no " within the individual fields
($st1, $st2, $st3, $city, $state, $zipcode) = ($address =~ m/\[\"[^"]*\" \"([^"]*)\" \"([^"]*)\" \"([^"]*)\" \"([^"]*)\" \"([^"]*)\" (\d+)/);
$st1 = "" if (!defined ($st1));
$st2 = "" if (!defined ($st2));
$st3 = "" if (!defined ($st3));
$streetAddr = $st1." ".$st2." ".$st3;
return ($streetAddr, $city, $state, $zipcode);
}
###############################################################################
# The BBDB field extraction routine clumps the notes field and the user-defined
# fields under a single variable. This routine xtracts just the notes part
# from this variable.
###############################################################################
sub BbdbGetNotes {
my ($notes) = @_;
my ($justNotes);
if ($notes =~ m/^\(/) {
if ($notes =~ m/^\(notes . /) {
(undef, $justNotes) = &MatchParent ($_[0], 0);
if (!defined ($justNotes)) {
$justNotes = "nil";
}
}
else {
$justNotes = "nil";
}
}
else {
$justNotes = $notes;
}
$justNotes =~ s/^\"//;
$justNotes =~ s/\"$//;
$justNotes =~ s/^notes . "//;
return $justNotes;
}
###############################################################################
# This routine extracts the value for a specified custom field from the generic
# notes variable created by the BBDB field extraction routine.
###############################################################################
sub BbdbGetCustomField {
my ($notes, $fieldName) = @_;
my ($customField);
if ($notes =~ m/^\(/) {
($customField) = ($notes =~ m/\($fieldName . "([^)]*)"\)/);
if (!defined ($customField)) {
$customField = "nil";
}
}
return $customField;
}
sub Usage {
print " Usage: bbdbSync [-f <Input BBDB file>] [-d <default category name>]\n";
print " [-n <don't sync category>] [-s <sync category>]\n";
print " [-o <output BBDB file>]\n\n";
print " Default Input BBDB file = ~/.bbdb\n";
print " Default Category Name = Unfiled\n";
print " Default Don't Sync Category =\n";
print " Default Only Sync Category =\n";
print " Default Output BBDB file = ~/.bbdb.sync\n\n";
print " bbdbSync is a program used to sync the Address Book on the\n";
print " PalmPilot with the BBDB database used with (X)Emacs.\n";
}
# TBD
# 1. What if there are more than 4 custom fields defined in BBDB
# 3. Cannot handle " within an address field in BBDB. Use "'" instead
# 4. Way to avoid creating an entry on the Palm Pilot or on Bbdb
# 5. Don't handle sortByCompany
# 6. Don't handle secret record
# Assumptions
# 1. Either all entries have a last name or an organization name or an email.
------------------------------------------------------------------------
***********************************************************
* This is a public mailing list! *
* Please do not publish Sun proprietary information here! *
* - - - - - - - - - - - - - - *
* http://www.moshpit.org/pilotmgr *
***********************************************************
|
|