gnucash crash!, ? recovery options ?
linas@linas.org
linas@linas.org
Wed, 7 Feb 2001 17:09:16 -0600 (CST)
It's been rumoured that Lincoln D. Durey said:
>
> Linas,
> we are happy users of gnucash 1.4.9. But we had a system crash while a
> gnucash was running (most likely not gnucash's fault). As this session
> involved about 2-3 hours of hard work, we are very interested in any
> available recovery options.
>
> We have our previos data file (gc_emp), which is exactly in step with
> the state of our accounts before the data entry began, and we have the .log
> file (gc_emp.20010206224731.log) time stamped just moments before the crash.
> there was no .xac file generated at crash time.
>
> Is there a way to apply the log file (which I can see has the data we
> entered) to the original file, and arrive at a nice new gnucash file with all
> our updates? Either manually, or with a nice front end.
Do you know perl?
As of about an hour ago, no one had bothered to do this. So I 'just did
it;'. Its dirty, it doesn't check for errors, its minimal.
It was harder to create than it should have been; the gnucash engine
doesn't have a 'get account by name' function, and it doesn't grok
dates quite the way it should.
Backup your data, the script may mangle things.
You will need to double-check.
--linas
p.s. I'll check this into cvs under the name 'gnc-restore.pl' or
something like that. Maybe it'll be in 1.4.11 as an undocumented
feature.
#! /usr/bin/perl
#
# restore gnucash transactions from a gnucash log file.
#
# Warning! this script probably does the wrong thing,
# and has never been tested!!
# It will probably destroy your data! Use at your own risk!
#
# set the path below to where your gnucash.pm is located
use lib '/usr/local/gnucash-1.4/lib/gnucash/perl';
use lib '/usr/local/gnucash-1.4/share/gnucash/perl';
use gnucash;
# --------------------------------------------------
# @account_list = &account_flatlist ($account_group);
# This routine accepts a pointer to a group, returns
# a flat list of all of the children in the group.
sub account_flatlist
{
my $grp = $_[0];
my $naccts = gnucash::xaccGroupGetNumAccounts ($grp);
my $n;
my (@acctlist, @childlist);
my $children;
foreach $n (0..$naccts-1)
{
$acct = gnucash::xaccGroupGetAccount ($grp, $n);
push (@acctlist, $acct);
$children = gnucash::xaccAccountGetChildren ($acct);
if ($children)
{
@childlist = &account_flatlist ($children);
push (@acctlist, @childlist);
}
}
return (@acctlist);
}
# --------------------------------------------------
# If the gnucash engine had a 'get account by name'
# utility function, then we wouldn't need this and the above mess.
sub get_account_by_name
{
my $accname = $_[0];
my $name;
# loop over the accounts, look for stock and mutual funds.
foreach $acct (@acctlist)
{
$name = gnucash::xaccAccountGetName ($acct);
if ($name eq $accname) {
$found = $acct;
break;
}
}
return ($found);
}
# --------------------------------------------------
die "Usage: cat <logfile> | $0 <gnucash-filename>" if $#ARGV < 0;
# open the file
print "Opening file $ARGV[0]\n";
$sess = gnucash::xaccMallocSession ();
$grp = gnucash::xaccSessionBeginFile ($sess,$ARGV[0]);
die "failed to read file $ARGV[0], maybe its locked? " if (! $grp);
# get a flat list of accounts in the file
@acctlist = &account_flatlist ($grp);
$got_data = 0;
$nsplit = 0;
while (<STDIN>) {
# start of transaction
if (/^===== START/) {
$nsplit = 0;
next;
}
# end of transaction
if (/^===== END/) {
if ($got_data == 1) {
gnucash::xaccTransCommitEdit ($trans);
}
$got_data = 0;
next;
}
# ignore 'begin' lines
if (/^B/) { next; }
if (/^D/) {
print "WARNING: deletes not handled, you will have to manually delete\n";
next;
}
# ignore any line that's not a 'commit'
if (!/^C/) { next; }
chop;
# get journal entry
($mod, $id, $time_now, $date_entered, $date_posted,
$account, $num, $description, $memo, $action,
$reconciled, $amount, $price, $date_reconciled)
= split (/ /);
# parse amount & price
# gnucash-1.4 : float pt, gnucash1.5 : ratio
($anum, $adeno) = split (/\//, $amount);
if (0 != $adeno) {
$amount = $anum / $adeno;
}
($pnum, $pdeno) = split (/\//, $price);
if (0 != $pdeno) {
$price = $pnum / $pdeno;
# value, not price ...
if (0 != $amount) {
$price = $price/$amount;
}
}
$dyear = int($date_posted/10000000000);
$dmonth = int($date_posted/100000000) - 100*$dyear;
$dday = int($date_posted/1000000) - 10000*$dyear - 100*$dmonth;
$dpost = $dmonth . "/" . $dday . "/" . $dyear;
# do a 'commit'
if ($mod == C) {
print "restoring '$account' '$description' for $pric and '$quant'\n";
print "date is $dpost $date_posted\n";
if ($got_data == 0) {
$trans = gnucash::xaccMallocTransaction();
gnucash::xaccTransBeginEdit( $trans, 1);
$got_data = 1;
}
gnucash::xaccTransSetDescription( $trans, $description);
gnucash::xaccTransSetDateStr ($trans, $dpost);
gnucash::xaccTransSetNum ($trans, $num);
if ($nsplit == 0) {
$split = gnucash::xaccTransGetSplit ($trans, $nsplit);
} else {
$split = gnucash::xaccMallocSplit();
gnucash::xaccTransAppendSplit($trans, $split);
}
gnucash::xaccSplitSetAction ($split, $action);
gnucash::xaccSplitSetMemo ($split, $memo);
gnucash::xaccSplitSetReconcile ($split, $reconciled);
# hack alert -- fixme: the reconcile date is not set ...
# need to convert date_reconciled to 'seconds' ...
# gnucash::xaccSplitSetDateReconciled ($split, $date_reconciled);
gnucash::xaccSplitSetSharePriceAndAmount($split, $price, $amount);
$acct = get_account_by_name ($account);
gnucash::xaccAccountBeginEdit ($acct, 1);
gnucash::xaccAccountInsertSplit ($acct, $split);
gnucash::xaccAccountCommitEdit ($acct);
$nsplit ++;
}
}
gnucash::xaccSessionSave ($sess);
gnucash::xaccSessionEnd ($sess);