File Coverage

blib/lib/Palm/Progect.pm
Criterion Covered Total %
statement 33 159 20.7
branch 0 36 0.0
condition 0 23 0.0
subroutine 11 20 55.0
pod 7 7 100.0
total 51 245 20.8


line stmt bran cond sub pod time code
1             # Palm::Progect.pm
2             #
3             # Perl class for dealing with Palm Progect databases.
4             #
5             # Author: Michael Graham
6             # Thanks to Andrew Arensburger's great Palm::* modules
7              
8 7     7   186662 use strict;
  7         18  
  7         350  
9             package Palm::Progect;
10 7     7   9472 use Palm::StdAppInfo;
  7         75883  
  7         64  
11 7     7   320 use Palm::PDB;
  7         21  
  7         149  
12 7     7   34 use Palm::Raw;
  7         13  
  7         35  
13              
14 7     7   4811 use Palm::Progect::Constants;
  7         18  
  7         723  
15 7     7   4017 use Palm::Progect::Record;
  7         147  
  7         61  
16 7     7   4090 use Palm::Progect::Prefs;
  7         19  
  7         164  
17 7     7   3576 use Palm::Progect::Converter;
  7         18  
  7         48  
18              
19 7     7   210 use vars '$VERSION';
  7         13  
  7         578  
20              
21             $VERSION = '2.0.4';
22              
23             =head1 NAME
24              
25             Palm::Progect - Handler for Palm Progect databases.
26              
27             =head1 SYNOPSIS
28              
29             use Palm::Progect;
30             use Palm::Progect::Constants;
31              
32             my $progect = Palm::Progect->new('options' => { 'quiet' => 1 });
33              
34             $progect->load_db(
35             file => $some_file,
36             );
37              
38             $progect->export_records(
39             file => $some_other_file,
40             format => 'text',
41             options => {
42             tabstop => 4,
43             fill_with_spaces => 1,
44             date_format => 'dd-mm-yyyy',
45             },
46             );
47              
48             =head1 DESCRIPTION
49              
50             Palm::Progect is a class for handling Progect Database files.
51              
52             Progect is a hierarchical organizer for the Palm OS. You can find it at:
53              
54             L
55              
56             Palm::Progect allows you to load and save Progect databases (and to convert
57             between database versions), and to import and export records in various formats.
58              
59             If all you are interested in doing is converting from one format to another,
60             you should probably look at the C utility program which does just that.
61              
62             These docs are for developers who want to manipulate Progect C files
63             programatically.
64              
65             =head1 OVERVIEW
66              
67             You should be able to access all functions of the C system
68             directly from the C module.
69              
70             Although the various database drivers and record converters all live in
71             their own Perl modules, C is the interface to their
72             functionality. It will transparently delegate to the appropriate module
73             behind the scenes necessary.
74              
75             You can load a C database from a Progect C file (via
76             the C method), or import records and/or preferences from
77             another format (such as Text or CSV) (via the C and
78             C methods).
79              
80             After a Progect database has been loaded or imported, you will have
81             a list of records (in C<$progect-Erecords>), and a preferences object
82             (in C<$progect-Epreferences>).
83              
84             Each record in C<$progect-Erecords> is an object of type
85             L.
86              
87             for my $rec (@{ $progect->records }) {
88             my $description = $rec->description;
89             my $priority = $rec->priority;
90             print "[$priority] $description\n";
91             }
92              
93             See L for the format of these records.
94              
95             Once you have loaded the records and preferences, you can save them
96             to a Progect C file (via the C method), or export
97             them to another format (such as Text or CSV), via the C
98             and C methods.
99              
100             Currently the C interface is not well defined and is
101             mainly there to allow for future development. See L.
102              
103             This module was largely written in support of the B utility,
104             which is a conversion utility which imports and exports between
105             Progect PDB files and other formats.
106              
107             =head2 Constructor
108              
109             =over 4
110              
111             =item new
112              
113             Create a new C object:
114              
115             my $progect = Palm::Progect->new(options => \%Options);
116              
117             options takes an optional hashref containing arguments to the
118             system. Currently this allows only a single option:
119              
120             =over 4
121              
122             =item quiet
123              
124             Suppress informational messages when loading and saving databases.
125              
126             =back
127              
128             =back
129              
130             =head2 Methods
131              
132             =over 4
133              
134             =item records
135              
136             A reference to the list of records within the database. Each record
137             is an object of type C.
138              
139             =item prefs
140              
141             A reference to the preferences object within the database. It is an
142             object of type C. For now the prefs object
143             doesn't do very much and is mostly a placeholder to allow for future
144             development.
145              
146             =item options
147              
148             Reference to the hash of user options passed to the C constructor.
149             See the C constructor for details.
150              
151             =item version
152              
153             The Progect database version currently in use. This can come directly
154             from the source database (loaded with C) or from the user (as
155             an argument to C or C).
156              
157             =begin internal_use_only
158              
159             =item _palm_pdb
160              
161             The underlying C database which C uses
162             to access the database file.
163              
164             =end internal_use_only
165              
166             =cut
167              
168 7     7   39 use CLASS;
  7         15  
  7         28  
169 7     7   227 use base qw(Class::Accessor Class::Constructor);
  7         12  
  7         6609  
170              
171             my @Accessors = qw(
172             _palm_pdb
173             records
174             prefs
175             options
176             version
177             );
178              
179             CLASS->mk_accessors(@Accessors);
180             CLASS->mk_constructor(
181             Auto_Init => \@Accessors,
182             Init_Methods => '_init',
183             );
184              
185             sub _init {
186 0     0     my $self = shift;
187              
188 0           &Palm::PDB::RegisterPDBHandlers('Palm::Raw', [ "lbPG", "DATA" ], );
189 0           $self->_palm_pdb(
190             Palm::Raw->new
191             );
192             }
193              
194             =item load_db(file =E $filename, version =E $version)
195              
196             Load the Progect database file specified by $filename.
197              
198             The C parameter is optional. Normally you would
199             leave it out and let C determine the version
200             from the database file itself.
201              
202             If you specify a particular C, then C will attempt
203             to read the database as that version. This would be useful for instance
204             in the case of a corrupt PDB that indicates an incorrect version, or a
205             PDB of a version that Palm::Progect does not support (but you want to
206             try and see if it can read it anyway).
207              
208             Currently supported versions are C<18> (for Progect database version 0.18) and
209             C<23> (for Progect database version 0.23).
210              
211             Progect database version 0.18 was used all the way up until Progect version
212             0.22, so if you saved a database with Progect 0.22, the database will be
213             a version 0.18 database.
214              
215             =cut
216              
217             sub load_db {
218 0     0 1   my $self = shift;
219 0           my %args = @_;
220              
221 0           my $file = $args{'file'};
222              
223 0 0         print STDERR "Loading Progect database from $file\n" unless $self->options->{'quiet'};
224 0           $self->_palm_pdb->Load($file);
225              
226             # Determine the version from the database
227             # Lucky for us, the db version number is the first byte
228             # of the appinfo block.
229              
230 0           my $appinfo = {};
231              
232 0 0         if ($self->_palm_pdb->{'appinfo'}) {
233 0           &Palm::StdAppInfo::parse_StdAppInfo($appinfo, $self->_palm_pdb->{'appinfo'});
234             }
235             else {
236 0           $appinfo = {
237             'categories' => [],
238             'other' => pack('C', 23),
239             };
240             }
241              
242 0           my $version = unpack 'C', $appinfo->{'other'};
243 0 0         print STDERR "Progect database is version $version\n" unless $self->options->{'quiet'};
244              
245             # Allow the user to manually override the version
246             # (after all, the database prefs might be corrupt)
247 0 0 0       if ($args{version} and $version != $args{version}) {
248 0           $version = $args{version};
249 0 0         print STDERR "Forcing version to $version\n" unless $self->options->{'quiet'};
250             }
251              
252 0           my @raw_records = @{ $self->_palm_pdb->{'records'} };
  0            
253              
254             # Categories will always be a list of unique names
255 0           my @categories = @{$appinfo->{'categories'}};
  0            
256              
257             # Tell the Record class which categories we know about
258              
259 0           Palm::Progect::Record->set_categories(@categories);
260              
261             # Build @records from @raw_records:
262              
263 0           my @records;
264              
265 0           for my $raw_record (@raw_records) {
266 0           my $record = Palm::Progect::Record->new(
267             version => $version,
268             raw_record => $raw_record,
269             );
270              
271 0           push @records, $record;
272             }
273              
274             # This doesn't do much at present
275 0           my $prefs = Palm::Progect::Prefs->new(
276             version => $version,
277             appinfo => $appinfo,
278             name => _db_name_from_filename($file),
279             );
280 0           $prefs->categories(@categories);
281              
282 0           $self->records(@records);
283 0           $self->prefs($prefs);
284 0           $self->version($version);
285             }
286              
287             =item save_db(file =E $filename, version =E $version)
288              
289             Save the records and prefs as a Progect database of version C<$version>
290             to the filename C<$filename>.
291              
292             If you do not specify a version then the latest available version is
293             assumed, unless you have set C before, by a previous call
294             to C or C.
295              
296             Currently supported versions are C<18> (for Progect database version 0.18) and
297             C<23> (for Progect database version 0.23).
298              
299             Progect database version 0.18 was used all the way up until Progect version
300             0.22, so if you saved a database with Progect 0.22, the database will be
301             a version 0.18 database.
302              
303             =cut
304              
305             sub save_db {
306 0     0 1   my $self = shift;
307 0           my %args = @_;
308              
309 0           my $file = $args{'file'};
310 0   0       my $save_version = $args{'version'} || 0;
311 0   0       my $loaded_version = $self->version || 0;
312              
313             # Repair the records tree
314 0           $self->repair_tree;
315              
316             # Pack the raw records from our list of records
317              
318 0           my @records = @{ $self->records };
  0            
319              
320 0           my (@raw_records);
321              
322 0           for my $record (@records) {
323              
324 0           my $new_record = Palm::Progect::Record->new(
325             version => $save_version,
326             from_record => $record,
327             );
328              
329 0           push @raw_records, $new_record->raw_record;
330             }
331              
332             # Use our prefs object, if it exists.
333             # Otherwise, create a new one.
334              
335 0           my $prefs = $self->prefs;
336              
337 0           $prefs = Palm::Progect::Prefs->new(
338             version => $save_version,
339             from_prefs => $self->prefs,
340             );
341 0           $self->prefs($prefs);
342              
343             # Fetch the final category list from the Record object
344 0           my @categories = Palm::Progect::Record::get_categories();
345              
346             # Pack the categories into the prefs
347 0           $prefs->categories(@categories);
348 0           $prefs->name(_db_name_from_filename($file));
349              
350             # $version is now our preferred db version
351 0           $self->version($save_version);
352              
353             # Save our records
354 0           $self->records(\@raw_records);
355              
356             # put @raw_records, $raw_prefs, and some constant stuff into $self->_palm_pdb
357              
358 0           $self->_palm_pdb->{'records'} = \@raw_records;
359 0           $self->_palm_pdb->{'appinfo'} = $prefs->packed_appinfo;
360              
361 0           $self->_palm_pdb->{'creator'} = 'lbPG';
362 0           $self->_palm_pdb->{'type'} = "DATA";
363 0           $self->_palm_pdb->{'attributes'}{'resource'} = 0;
364              
365             # This may move to Palm::Progect::Prefs eventually...
366 0           $self->_palm_pdb->{'name'} = $prefs->name;
367              
368             # Finally, write the pdb file
369 0 0         print STDERR "Saving Progect database in version $save_version to $file\n" unless $self->options->{'quiet'};
370 0           $self->_palm_pdb->Write($file);
371             }
372              
373             =item import_records(%args)
374              
375             Import records from a file.
376              
377             The options passed in C<%args> are as follows:
378              
379             =over 4
380              
381             =item file
382              
383             The file to import the records from.
384              
385             =item format
386              
387             The conversion format to use when importing the records.
388              
389             Internally, this determines which module will do the actual conversion.
390              
391             For instance, specifying a format of C will cause
392             C module to handle the import.
393              
394             =item append
395              
396             If true, then C will B the records imported from C
397             to the internal records list. If false, C will B
398             the internal records list with the records imported from C.
399              
400             =back
401              
402             You can pass other options to C, and these will be passed
403             directly to the module that does the eventual conversion. For instance:
404              
405             $progect->import_records(
406             file => 'somefile.csv',
407             format => 'CSV',
408             date_format => 'dd-mm-yyyy',
409             );
410              
411             In this example, the value of C will get passed directly
412             to the C module.
413              
414             =cut
415              
416             sub import_records {
417 0     0 1   my $self = shift;
418 0           my %args = @_;
419              
420 0           my $file = delete $args{'file'};
421 0           my $append = delete $args{'append'};
422              
423 0           my $converter = Palm::Progect::Converter->new(
424             %args,
425             );
426              
427 0           $converter->load_records($file, $append);
428 0           $self->records($converter->records);
429 0           $self->prefs($converter->prefs);
430             }
431              
432             =item export_records(%args)
433              
434             Export records to a file.
435              
436             The options passed in C<%args> are as follows:
437              
438             =over 4
439              
440             =item file
441              
442             The file to export the records to. If blank, then the
443             exported records will be written to STDOUT.
444              
445             =item format
446              
447             The conversion format to use when exporting the records.
448              
449             Internally, this determines which module will do the actual conversion.
450              
451             For instance, specifying a format of C will cause
452             C module to handle the export.
453              
454             =item append
455              
456             If true, then C will B the exported records to C.
457             If false, C will overwrite C (if it exists)
458             before exporting the records.
459              
460             =back
461              
462             You can pass other options to C, and these will be passed
463             directly to the module that does the eventual conversion. For instance:
464              
465             $progect->export_records(
466             file => 'somefile.csv',
467             format => 'CSV',
468             date_format => 'dd-mm-yyyy',
469             );
470              
471             In this example, the value of C will get passed directly
472             to the C module.
473              
474             =cut
475              
476             sub export_records {
477 0     0 1   my $self = shift;
478 0           my %args = @_;
479              
480 0           my $file = delete $args{'file'};
481 0           my $append = delete $args{'append'};
482              
483 0           my $converter = Palm::Progect::Converter->new(
484             %args,
485             records => $self->records,
486             prefs => $self->prefs,
487             );
488              
489 0           $converter->save_records($file, $append);
490             }
491              
492             =item import_prefs
493              
494             Import preferences from a file. Currently this is not supported.
495              
496             =cut
497              
498             sub import_prefs {
499 0     0 1   my $self = shift;
500 0           my %args = @_;
501             }
502              
503             =item export_prefs
504              
505             Export preferences to a file. Currently this is not supported.
506              
507             =cut
508              
509             sub export_prefs {
510 0     0 1   my $self = shift;
511 0           my %args = @_;
512             }
513              
514             =item repair_tree
515              
516             Goes through the list of records and repairs the relationships between them:
517              
518             $progect->repair_tree;
519              
520             C calls this method internally just before it saves a Progect
521             database file.
522              
523             That means:
524              
525             =over 4
526              
527             =item *
528              
529             Insert the root record (no description, level 0) if necessary.
530              
531             =item *
532              
533             Fix the parent/child/sibling relationships (C, C,
534             C, etc.) if necessary.
535              
536             =back
537              
538             =cut
539              
540             sub repair_tree {
541 0     0 1   my $self = shift;
542              
543 0           my @records = @{ $self->records };
  0            
544              
545             # Insert the "root record" if necessary
546 0 0 0       if ($records[0]->level or $records[0]->description) {
547 0           my $root_record = new Palm::Progect::Record( version => $self->version );
548              
549 0           $root_record->has_child(1);
550 0           $root_record->level(0);
551 0           $root_record->is_opened(1);
552              
553 0           unshift @records, $root_record;
554             }
555              
556             # Fix relations between records
557              
558 0           for (my $i = 0; $i < @records; $i++) {
559 0           my $rec = $records[$i];
560              
561 0           $rec->has_child(0);
562 0           $rec->has_next(0);
563 0           $rec->has_prev(0);
564              
565 0 0 0       if ($i == 0 and @records > 0) {
566 0           $rec->has_prev(0);
567              
568 0           my $next_rec = $records[$i+1];
569 0 0 0       $rec->has_child(1) if $next_rec and $next_rec->level > $rec->level;
570              
571             # Look ahead to other records, see if we
572             # can find one at the same level as us,
573             # before we cross one at a previous level
574 0           for (my $j = $i + 1; $j < @records; $j++) {
575              
576 0           my $other_record = $records[$j];
577              
578 0 0         last if $other_record->level < $rec->level;
579              
580 0 0         if ($other_record->level == $rec->level) {
581 0           $rec->has_next(1);
582 0           last;
583             }
584             }
585             }
586             else {
587 0           my $prev_rec = $records[$i-1];
588 0 0         if (@records > $i) {
589 0           my $next_rec = $records[$i+1];
590 0 0 0       $rec->has_child(1) if $next_rec and ($next_rec->level || 0) > ($rec->level || 0);
      0        
      0        
591             }
592             # Look ahead to other records, see if we
593             # can find one at the same level as us,
594             # before we cross one at a previous level
595 0 0         if ($i < @records) {
596 0           for (my $j = $i + 1; $j < @records; $j++) {
597              
598 0           my $other_record = $records[$j];
599              
600 0 0         last if $other_record->level < $rec->level;
601              
602 0 0         if ($other_record->level == $rec->level) {
603 0           $rec->has_next(1);
604 0           last;
605             }
606             }
607             }
608             # Same thing, working backwards
609 0           for (my $j = $i - 1; $j > 0; $j--) {
610              
611 0           my $other_record = $records[$j];
612              
613 0 0         last if $other_record->level < $rec->level;
614              
615 0 0         if ($other_record->level == $rec->level) {
616 0           $rec->has_prev(1);
617 0           last;
618             }
619             }
620             }
621             }
622              
623 0           $self->records(@records);
624             }
625              
626             =back
627              
628             =begin internal_use_only
629              
630             =head2 Utility Subroutines
631              
632              
633              
634             =over 4
635              
636             =item _db_name_from_filename
637              
638             This is a subroutine, not a method. Call it like:
639              
640             my $db_name = _db_name_from_filename($filename);
641              
642             Given a filename, try to come up with a sensible name for the progect
643             database. Remove the extension, the C prefix (if any), etc.
644              
645             =back
646              
647             =end internal_use_only
648              
649             =cut
650              
651             sub _db_name_from_filename {
652 0     0     my $filename = shift;
653 0           $filename =~ tr{\\}{/};
654 0           $filename =~ tr{:}{/};
655 0           $filename = (split m{/}, $filename)[-1];
656 0           $filename =~ s/^lbPG-//;
657 0           $filename =~ s/\..*?$//;
658 0           return $filename;
659             }
660              
661             1;
662              
663             __END__