#!/usr/bin/perl # Filename: vidb # Author: David Ljung Madison # See License: http://MarginalHacks.com/License # Description: Edits a database like a text file # Uses DB_File::Lock style locking # but not between read and write! (so writes can be lost) use strict; use DB_File::Lock; use Fcntl ':flock'; my $PROGNAME = $0; $PROGNAME =~ s|.*/||; my $EDITOR = $ENV{EDITOR} || "vi"; my $TMP = "/tmp/$PROGNAME.$$"; ################################################## # Usage ################################################## sub usage { foreach my $msg (@_) { print STDERR "ERROR: $msg\n"; } print STDERR "\n"; print STDERR "Usage:\t$PROGNAME [-l] [-d] \n"; print STDERR "\tEdits a database like a text file\n"; print STDERR "\tUses DB_File::Lock style locking\n"; print STDERR "\t-l\tJust list the database contents\n"; print STDERR "\t-n\tCreate db if not existing\n"; print STDERR "\t-L\tHold lock for db while editing\n"; print STDERR "\n"; print STDERR "\t-d\tSet debug mode\n"; print STDERR "\n"; exit -1; } sub parse_args { my ($file,$list,$hold); while (my $arg=shift(@ARGV)) { if ($arg =~ /^-h$/) { usage(); } if ($arg =~ /^-d$/) { $MAIN::DEBUG=1; next; } if ($arg =~ /^-l$/) { $list=1; next; } if ($arg =~ /^-L$/) { $hold=1; next; } if ($arg =~ /^-n$/) { $MAIN::CREATE=1; next; } if ($arg =~ /^-/) { usage("Unknown option: $arg"); } usage("Too many databases specified [$arg and $file]") if (defined($file)); $file=$arg; } usage("No database specified") if (!defined($file)); ($file,$list,$hold); } sub debug { return unless $MAIN::DEBUG; foreach my $msg (@_) { print STDERR "[$PROGNAME] $msg\n"; } } ################################################## # Code ################################################## sub from_db { my ($str) = @_; $str =~ s/([%\t\n])/"%".sprintf("%0.2x",ord($1))/eg; $str; } sub to_db { my ($str) = @_; $str =~ s/%([0-9a-f]{2})/chr(hex($1))/eig; $str; } sub list_db { my ($file) = @_; open(TMP,">$TMP") || die("Couldn't write [$TMP]\n"); my %db; tie %db, 'DB_File::Lock', $file, O_RDONLY, 0666, $DB_HASH, "read" or die("Can't read db [$file]"); my %copy = %db; # Faster untie, since we need the whole thing untie %db; foreach my $k ( sort keys %copy ) { print "$k\t-> $copy{$k}\n"; } } my %_db; sub read_db { my ($file,$hold) = @_; open(TMP,">$TMP") || die("Couldn't write [$TMP]\n"); my $flags = $hold ? O_RDWR : O_RDONLY; $flags |= O_CREAT if $MAIN::CREATE; my $lock = ($MAIN::CREATE || $hold) ? "write" : "read"; tie %_db, 'DB_File::Lock', $file, $flags, 0666, $DB_HASH, $lock or die("Can't $lock db [$file]\n"); my %copy = %_db; # Faster untie, since we need the whole thing untie %_db unless $hold; foreach my $k ( sort keys %copy ) { print TMP from_db($k),"\t",from_db($copy{$k}),"\n"; debug("Read: $k\t-> $copy{$k}\n"); } close TMP; } sub edit_db() { my $mod = -M $TMP; system("$EDITOR $TMP"); return -M $TMP != $mod ? 1 : 0; } sub write_db { my ($file,$hold) = @_; open(TMP,"<$TMP") || die("Couldn't read [$TMP]\n"); unless ($hold) { tie %_db, 'DB_File::Lock', $file, O_CREAT|O_RDWR, 0666, $DB_HASH, "write" or die("Can't write db [$file]"); } # Clear the hash first delete @_db{keys %_db}; my $errors=0; while() { chomp; if (/^([^\t]+)(\t(.+)?)?$/) { my ($k,$v) = (to_db($1),to_db($3)); $_db{$k} = $v; debug("Wrote: $k->$v\n"); } else { print STDERR "Can't parse database line [$.]:\n $_"; $errors++; } } untie %_db; close TMP; while ($errors) { print STDERR "\nE)dit it again, F)orget it? e"; # Char mode my $ttyname=`/usr/bin/tty`; system "/bin/stty -icanon -echo min 1 < $ttyname " if (! $?); my $ans; read(STDIN,$ans,1); # Line mode `/usr/bin/tty -s`; system "/bin/stty icanon echo < $ttyname " if (! $? ); print "$ans\n"; return if ($ans =~ /F/i); return edit_db if ($ans =~ /E/i); } } ################################################## # Main code ################################################## sub main { my ($file,$list,$hold) = parse_args(); return list_db($file) if $list; read_db($file,$hold); write_db($file,$hold) if edit_db; unlink($TMP); } main();