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);