#!/usr/bin/perl -w ### # wat - what a todo ### # $Id: wat.txt,v 1.9 2001/08/07 11:16:54 danny Exp $ ### # # WAT # === # WAT syncs a Palm todo list with a todo list held as a text file. # Changes in either will be mirrored in the other. ##### # External requirements: PDA::Pilot, Data::Dumper; ##### use PDA::Pilot; use Data::Dumper; ##### # Global settings ##### $VERSION = "0.02"; if (($#ARGV == 0) && ($ARGV[0] eq "-V")) { print "wat $VERSION\n"; exit } $TODOFILE = "~/.wat"; $TODOFILE =~ s{^~([^/]*)}{$1?(getpwname($1))[7]:($ENV{HOME}||($ENV{LOGDIR}||(getpwuid($>)))[7])}ex; $DEBUG=$^D; $DONT_SHOW_COMPLETED = 1; $PILOT_DEV="/dev/pilot"; # open todo file # parse todo file into data structure my ( $pc_inorder, $pc_byid ) = parse_todofile($TODOFILE); # structure of todo file # description # memo data # %id number # set autoflush select( ( select(STDOUT), $| = 1 )[0] ); # open connection to palm print "Now press the HotSync button\n"; while (!(open FILE, ">$PILOT_DEV")) { } close FILE; my $socket = PDA::Pilot::openPort("$PILOT_DEV"); my $dlp = PDA::Pilot::accept($socket); my $db = $dlp->open("ToDoDB"); # for each record on the palm # load into a database (seems to get confused if we try # getting records *and* changing them) # # for each record in the palm db # if it's completed (and we're not processing completed), skip # if it's new , add it to the pc database # if it's existent, check it's the same as the pc db # if it's different # syncronize the two records # mark record as "seen" in data structure my $pc_record; my $palm_db = []; print "Loading ToDo Items: "; while ( defined( my $palm_record = $db->getRecord( $i++ ) ) ) { if ( $DONT_SHOW_COMPLETED && $palm_record->{'complete'} ) { print "."; next; } push @$palm_db, $palm_record; print "#"; } print "\nSyncing ToDo Items:\n"; foreach $palm_record (@$palm_db) { my $id = $palm_record->{'id'}; $pc_record = $pc_byid->{"$id"}; if ( !defined $pc_record ) { # new record on palm? if ($palm_record->{'deleted'}) { # removed? next; } print "PC append: $id:$palm_record->{'description'}\n"; unshift @$pc_inorder, $palm_record; $palm_record->{'seen'} = 1; $$pc_byid{$id} = $palm_record; next; } sync_record( $palm_record, $pc_record ); $pc_record->{'seen'} = 1; } # # for each record in the pc data structure that hasn't been seen # if it's got an id number, mark it as "deleted" # if it hasn't, add it to palm foreach $pc_record (@$pc_inorder) { if ( defined $pc_record->{'seen'} ) { next; } if ( $pc_record->{'id'} ) { print "PC delete: $pc_record->{'description'}\n "; $pc_record->{'deleted'} = 1; } else { print "Palm adding: $pc_record->{'description'}\n"; my $new_record = $db->newRecord(0,0,1); foreach $k ( keys %$pc_record ) { if ($k eq "seen") { next; }; $new_record->{$k} = $pc_record->{$k}; } # Set record consistently returns with a # "" not numeric warning, but I can't see why. # Still, it seems to work, so silence it. no warnings; $pc_record->{'id'}=$db->setRecord($new_record); } } # write the data structure back out (taking care to preserve order) write_todofile( $pc_inorder, "$TODOFILE.new" ); system "/bin/cp", "-f",$TODOFILE, "$TODOFILE~"; system "/bin/mv", "$TODOFILE.new", "$TODOFILE"; # sync_record # in: $palm_record - palm data # $pc_record - pc data # out: $pc_record is updated to reflect changes in both # # here are the rules # pc has precedence # a tick in the palm "complete" category gives precedence to palm # a tick in the palm "modified" status gives precedence to palm # a tick in the palm "deleted" status gives precedence to palm sub sync_record { my ( $palm_record, $pc_record ) = @_; my $needs_syncing; if ($DEBUG) { print STDERR "SYNCING RECORDS $pc_record->{'description'}\n"; } foreach $k ( keys %$pc_record ) { next if ( !defined $palm_record->{$k} ); if ( $palm_record->{$k} ne $pc_record->{$k} ) { next if ( $k eq "seen" ); $needs_syncing = 1; } } return unless $needs_syncing; my $precedence = "pc"; if ( $palm_record->{'complete'} eq 1 ) { $precedence = "palm"; } if ( $palm_record->{'modified'} eq 1 ) { $precedence = "palm"; } if ( $palm_record->{'deleted'} eq 1 ) { $precedence = "palm"; } if ( $pc_record->{'complete'} eq 1 ) { $precedence = "pc"; } if ( $pc_record->{'deleted'} eq 1 ) { $precedence = "pc"; } if ( $precedence eq "palm" ) { print STDERR "PC change: $pc_record->{'description'}\n"; foreach $k ( keys %$palm_record ) { $pc_record->{$k} = $palm_record->{$k}; } } if ( $precedence eq "pc" ) { print STDERR "Palm change: $pc_record->{'description'}\n"; foreach $k ( keys %$pc_record ) { next if ( $k eq "seen" ); $palm_record->{$k} = $pc_record->{$k}; } $db->setRecord($palm_record); } } # write_todofile # in: $order_of_ids - an array of pointers to record hashes (with a # significant order) # $todo_fname - filename to write to (allow us to write to a tmp file) # # outputs a (hopefully) parseable todo file. can be set with # DONT_SHOW_COMPLETED to not save finished itesm # sub write_todofile { my ( $order_of_ids, $todo_fname ) = @_; open TODO, ">", "$todo_fname" or die "$!: Couldn't open $todo_fname\n"; foreach $i (@$order_of_ids) { if ( $i->{'deleted'} ) { next; } if ( $i->{'complete'} ) { if ($DONT_SHOW_COMPLETED) { next; } print TODO "X "; } else { print TODO " "; } $i->{'description'} =~ s/\n/\\/g; print TODO "$i->{'priority'} "; print TODO "$i->{'description'}\n"; if ( $i->{'note'} ) { print TODO "$i->{'note'}\n"; } print TODO "%$i->{'id'}\n"; } } sub TEST_writetodofile { my ( $r, @m ); while ( defined( $r = $db->getRecord( $i++ ) ) ) { push @m, $r; } write_todofile( \@m, $TODOFILE ); } # parse_todofile # in: $todo_fname - filename to read from # out: ref to array of pointers to record hashes (with significant order) # ref to hash of pointers to record hashes (indexed by id) sub parse_todofile { my $todo_fname = shift; if (! -e $todo_fname) { open TODO, ">$todo_fname" or die "$!: Couldn't create $todo_fname"; } open TODO, "<$todo_fname" or die "$!: Couldn't open $todo_fname"; my $inside_memo; my $memo = ""; my $new_todo; my @todo_by_order; my %todo_by_id; while () { if (/^[CD X] [0-9]/) { #top line of a todo entry $new_todo = {}; parse_topline( $new_todo, $_ ); $inside_memo = 1; $memo = ""; next; } if (/^%([0-9]*)$/) { # idnumber (and end of todo entry $new_todo->{'id'} = $1?$1:0; chomp( $new_todo->{'note'} = $memo ); $todo_by_id{"$1"} = $new_todo; push @todo_by_order, $new_todo; undef $inside_memo; next; } if ($inside_memo) { $memo .= $_; next; } } return ( \@todo_by_order, \%todo_by_id ); } sub TEST_parse_todofile { my ( $todo_byorder, undef )= parse_todofile("/home/danny/tmp/todo"); print Dumper($todo_byorder); write_todofile( $todo_byorder, "/home/danny/tmp/todo.copy" ); } # parse_topline # in: $r = ref to todo record hash # $line = top line of text entry in form # <0-9> sub parse_topline { my ( $r, $line ) = @_; my ( $status, $priority, $desc ); ( $status, $priority, $desc ) = ( $line =~ /^([XD ]) ([0-9]+) (.*)$/ ); $r->{'deleted'} = ""; if ( $status eq "X" ) { $r->{'complete'} = 1; } elsif ( $status eq "D" ) { $r->{'deleted'} = 1; # TODO TODO TODO FIXME } elsif ( $status eq " " ) { $r->{'complete'} = 0; } $desc =~ s/\\/\n/g; $r->{'priority'} = $priority?$priority:0; $r->{'description'} = $desc; } sub TEST_parse_topline { my $r = {}; parse_topline( $r, " 1 Hello this is a description" ); print Dumper($r); $r = {}; parse_topline( $r, "X 3 Tooty wooty big boy cuty" ); print Dumper($r); $r = {}; parse_topline( $r, "D 99 This is a weird, but acceptable entry" ); print Dumper($r); }