7 Reads the events from a Palm Desktop DateBook.dat
10 This file reads a Palm Desktop DateBook file (datebook.dat) and prints
11 all the non-expired entries (can return all, see below) to STDOUT. It prints a pipe
12 separated value list by default.
15 The script is set up to be used with the webcalendar and doesn't need to be
16 altered. If you want to use it some other way, then change the variables in
17 the config section to suit your needs and edit the main program (bottom of script).
18 You can uncomment the $outfile config to print to a file. The default program
19 will not include expired events and takes 2 arguments:
21 1. $DateBookFileName - The name/location of the datebook.dat
22 2. $exc_private - If a 1 is passed private records will be skipped
24 The following data is available in $Entry:
26 $Entry->{RecordID} = Record ID in the Palm
27 $Entry->{Status} = Identifies new and deleted records (status in datebook)
28 $Entry->{Position} = Position in list?
29 $Entry->{StartTime} = In seconds since 1970
30 $Entry->{EndTime} = In seconds since 1970
31 $Entry->{Description} = Description of event (string)
32 $Entry->{Duration} = How long the event lasts (in minutes)
33 $Entry->{Note} = Note (string)
34 $Entry->{Untimed} = 1 = true 0 = false
35 $Entry->{Private} = 1 = true 0 = false
36 $Entry->{Category} = useless for Palm
37 $Entry->{AlarmSet} = 1 = true 0 = false
38 $Entry->{AlarmAdvanceAmount} = How many units in AlarmAdvanceType (-1 means not set)
39 $Entry->{AlarmAdvanceType} = Units: (0=minutes, 1=hours, 2=days)
40 $Entry->{Repeat} = Array containing repeat information (if repeat)
41 $Entry->{Repeat}->{Interval} = 1=daily,2=weekly,3=MonthlyByDay,4=MonthlyByDate,5=Yearly
42 $Entry->{Repeat}->{Frequency} = How often event occurs. (1=every, 2=every other,etc.)
43 $Entry->{Repeat}->{EndTime} = When the repeat ends (In seconds since 1970)
44 $Entry->{Repeat}->{Exceptions} = An exception to the repeat (In seconds since 1970)
45 $Entry->{Repeat}->{RepeatDays} = For Weekly: What days to repeat on (7 characters...y or n for each day)
46 $Entry->{Repeat}->{DayNum} = For MonthlyByDay: Day of week (1=sun,2=mon,3=tue,4=wed,5=thu,6=fri,7=sat)
47 $Entry->{Repeat}->{WeekNum} = For MonthlyByDay: Week number (1=first,2=second,3=third,4=fourth,5=last)
50 Eduard Martinescu - Provided patch to parse category list.
54 use CGI qw (:standard);
56 my ($Year, $Month, $Day);
59 # ----------------------- Config if necessary ----------------------------
60 my $DateBookFileName = $ARGV[0]; # The name of the file
61 my $exc_private = $ARGV[1]; # Do we want private entries? (1 = skip private)
62 my $inc_expired = 0; # Do we want expired entries? (1 = include expired)
63 my $sep = "|"; # what to separate the output with
64 #my $outfile = "/tmp/datebook_dump.txt"; # uncomment to print to file
65 #---------------------------------------------------------------------------
70 # ReadDateBook opens the file we passed (datebook.dat) and reads the entries.
72 my ($FileName, $Filter) = @_;
73 my (@Fields, @Entries, $FieldCount, $NumberOfEntries);
74 my ($Entry, $i, $Header, $Tag);
75 my ($NextFree, $CategoryCount, $Category, @Categories, $ResourceID);
76 my ($FieldsPerRow, $RecordIdPos, $RecordStatusPos, $RecordPlacementPos);
78 open DATEBOOK, "<".$FileName;
86 # First, check the initial 4 byte "tag" field.
87 $Tag = ReadByteString(4);
89 # Next, read the header information.
90 $FileName = ReadPilotString();
91 $Header = ReadPilotString();
92 $NextFree = ReadLong();
94 # Read the category information
95 $CategoryCount = ReadLong();
96 for ($i=0; $i<$CategoryCount; $i++) {
97 $Category = ReadCategory();
98 push (@Categories,$Category) if ($Category ne 0);
101 $ResourceID = ReadLong();
102 $FieldsPerRow = ReadLong();
103 $RecordIdPos = ReadLong();
104 $RecordStatusPos = ReadLong();
105 $RecordPlacementPos = ReadLong();
107 # Read the field list.
108 $FieldCount = ReadShort();
109 for ($i=0; $i<$FieldCount; $i++)
110 {push @Fields, ReadShort();}
112 # Figure out how many entries to read
113 $NumberOfEntries = ReadLong() / $FieldCount;
116 for ($i=0; $i<$NumberOfEntries; $i++) {
117 $Entry = ReadEntry();
119 if (!$Filter or &$Filter($Entry)){push @Entries, $Entry;}
129 # ReadPalmEntry reads a single entry from the datebook, stores it in a local
130 # hash, and returns a reference to that hash. The reference can safely
131 # be stored in an array for later use.
135 $Entry{RecordID} = ReadPilotField();
136 $Entry{Status} = ReadPilotField();
137 $Entry{Position} = ReadPilotField();
138 $Entry{StartTime} = ReadPilotField();
139 $Entry{EndTime} = ReadPilotField();
140 $Entry{Description} = ReadPilotField();
141 $Entry{Duration} = ReadPilotField();
142 $Entry{Note} = ReadPilotField();
143 $Entry{Untimed} = ReadPilotField();
144 $Entry{Private} = ReadPilotField();
145 $Entry{Category} = ReadPilotField();
146 $Entry{AlarmSet} = ReadPilotField();
147 $Entry{AlarmAdvanceAmount} = ReadPilotField();
148 $Entry{AlarmAdvanceType} = ReadPilotField();
149 $Entry{Repeat} = ReadPilotField();
151 #Should return as -1 if not set, but is returning as 4294967295
152 $Entry{AlarmAdvanceAmount} = "-1" if ($Entry{AlarmAdvanceAmount} eq '4294967295');
154 # Remove Crap from the DateBook5 Application
155 $Entry{Note} = '' if ($Entry{Note} =~ /^\#\#[fcdxX\@]/);
157 # Filter single quotes, \n\r
158 $Entry{Description} = &filter_quotes($Entry{Description});
159 $Entry{Note} = &filter_quotes($Entry{Note});
161 # Calculate duration in minutes
162 $Entry{Duration} = ($Entry{EndTime} - $Entry{StartTime}) / 60;
164 # Skip private records if $exc_private
165 if (($exc_private) && ($Entry{Private} == 1)) {
167 # Skip Record if not in Palm (no RecordID) or marked for deletion
168 } elsif (($Entry{RecordID} == 0) || ($Entry{Status} == 129) || ($Entry{Status} == 4)){
170 # Skip events that are past endtime (except repeats that aren't expired) unless $inc_expired
171 } elsif (($Entry{EndTime} < time()) && (!$Entry{Repeat}) && (!$inc_expired)){
173 } elsif (($Entry{Repeat}) && ($Entry{Repeat}{EndTime} < time())&& ($Entry{Repeat}{EndTime} != 0) && (!$inc_expired)){
176 #print $Entry{RecordID} . "\n";
184 # ReadPilotField returns a single field from the datebook file.
185 my ($Type, $N, $sun, $mon, $tue, $wed, $thu, $fri, $sat);
186 my ($i, $DatesToSkip, $Repeat, $Interval, $Frequency, $Duration, $Position, $EndTime, $exceptions, @E);
191 if ($Type == 1 or $Type == 3 or $Type == 6) {
193 } elsif ($Type == 5) {
194 ReadLong(); # Skip the long of all zeroes
195 return ReadPilotString();
196 } elsif ($Type == 8) {
197 $DatesToSkip = ReadShort();
198 for ($i=1; $i<=$DatesToSkip; $i++)
199 {$exceptions .= ReadLong().":";}
201 $Repeat = ReadShort();
202 if ($Repeat == 0xFFFF) {
206 } elsif ($Repeat and $Repeat != 0x8001) { #($Repeat == 0x1a40 or $Repeat == 0xb3c0 or $Repeat == 0xe750)
207 # print " DEBUG: Repeat is $Repeat\n";
211 $Interval = ReadLong();
212 $Frequency = ReadLong();
213 $EndTime = ReadLong();
214 if ($EndTime eq 1956542399) { # No EndTime
219 $DayNum = ReadLong();
220 if ($Interval == 2) {
221 $Position = ReadByte();
222 } elsif ($Interval == 3) {
223 $Position = ReadLong();
224 } elsif ($Interval == 5) {
225 $Position = ReadLong();
230 # Build the Repeat array to return
231 $RA{Interval} = $Interval;
232 $RA{Frequency} = $Frequency;
233 $RA{EndTime} = $EndTime;
234 if ($exceptions){ $RA{Exceptions} = $exceptions;}
236 if ($Interval == 2) { # Weekly repeat
237 # $Position is an integer that tells what days of the week
238 # to repeat on. (sun=1,mon=2,tue=4,wed=8,thu=16,fri=32,sat=64)
239 # The numbers are added together to give a unique integer.
240 # We will break it down since the WebCalendar doesn't use this format.
267 # Check for Wednesday
298 $RA{RepeatDays} = $sun.$mon.$tue.$wed.$thu.$fri.$sat;
299 } elsif ($Interval == 3) { # Monthlybyday repeat
300 $RA{DayNum} = $DayNum + 1; # Day of week (1=sun,2=mon,3=tue,4=wed,5=thu,6=fri,7=sat)
301 $RA{WeekNum} = $Position + 1; # Week number (1=first,2=second,3=third,4=fourth,5=last)
305 return 0; # No repeat
308 # print STDERR "There's a problem with this pilot field of type $Type\n";
317 # ReadByteString reads the number of bytes passed to it as a parameter
318 # and returns it as a character string.
321 $GlobalPos += $Count;
322 return substr ($_, $GlobalPos-$Count, $Count);
325 #====================
326 sub ReadPilotString {
327 #====================
328 # ReadPilotString reads a pilot formatted string, which is a size (one
329 # byte, unless the byte is 0xFF, then it's the two bytes after the 0xFF),
330 # followed by that many bytes, and returns a Perl string.
335 $Length = unpack ('C', substr ($_, $GlobalPos, 1));
337 if ($Length == 255) {
338 $Length = ReadShort();
341 $GlobalPos += $Length;
342 return substr ($_, $GlobalPos-$Length, $Length);
348 # SkipBytes is just like ReadByteString, except it throws away the data,
349 # rather than returning it.
352 $GlobalPos += $Count;
358 # ReadByte reads a single byte, and returns it as an integer.
361 return unpack ('C', substr($_, $GlobalPos-1, 1));
367 # ReadShort reads two bytes, and returns them as an integer (low order
368 # byte is the first one read).
371 return unpack ('S', substr($_, $GlobalPos-2, 2));
377 # ReadLong reads four bytes, and returns them as an integer (low order
378 # byte is the first one read).
381 return unpack ('L', substr($_, $GlobalPos-4, 4));
384 #====================
385 sub ByDateAscending {
386 #====================
387 # Sort records by StartTime
389 return $a->{StartTime} <=> $b->{StartTime};
395 # Filter newline/return
398 # $temp =~ s/'/\\'/g;
399 $temp =~ s/\n|\r/ /g; # Remove newline
406 # ReadCategory reads a single category entry from the datebook,
407 # stores it in a local hash, and returns a reference to that hash.
408 # The reference can safely be stored in an array for later use.
412 $Entry{Index} = ReadLong();
413 $Entry{CategoryID} = ReadLong();
414 $Entry{DirtyFlag} = ReadLong();
415 $Entry{LongName} = ReadPilotString();
416 $Entry{ShortName} = ReadPilotString();
420 #----------------------------- Main Program -------------------------------
422 foreach $Entry (sort ByDateAscending ReadDateBook($DateBookFileName)) {
423 $DATA .= $Entry->{RecordID}. $sep;
424 $DATA .= $Entry->{StartTime}. $sep;
425 $DATA .= $Entry->{EndTime}. $sep;
426 $DATA .= $Entry->{Description}. $sep;
427 $DATA .= $Entry->{Duration}. $sep;
428 $DATA .= $Entry->{Note}. $sep;
429 $DATA .= $Entry->{Untimed}. $sep;
430 $DATA .= $Entry->{Private}. $sep;
431 $DATA .= $Entry->{Category}. $sep;
432 $DATA .= $Entry->{AlarmSet}. $sep;
433 $DATA .= $Entry->{AlarmAdvanceAmount}. $sep;
434 $DATA .= $Entry->{AlarmAdvanceType}. $sep;
435 $DATA .= $Entry->{Repeat}->{Interval}. $sep;
436 $DATA .= $Entry->{Repeat}->{Frequency}. $sep;
437 $DATA .= $Entry->{Repeat}->{EndTime}. $sep;
438 $DATA .= $Entry->{Repeat}->{Exceptions}. $sep;
439 $DATA .= $Entry->{Repeat}->{RepeatDays}. $sep;
440 # $DATA .= $Entry->{Repeat}->{DayNum}. $sep;
441 $DATA .= $Entry->{Repeat}->{WeekNum}. $sep;
446 die "Couldn't open $outfile: $!" if ((open OUT, ">$outfile") eq undef);
447 flock (OUT, 2);print OUT $DATA;flock (OUT, 8);close OUT;