From 53c96ec15d36feaae2f7e07a126390941f452b95 Mon Sep 17 00:00:00 2001 From: betateilchen <> Date: Sat, 1 Mar 2014 11:59:56 +0000 Subject: [PATCH] configDB.pm: added library file configDB.pm, including docu CHANGED: updated (betateilchen) git-svn-id: https://svn.fhem.de/fhem/trunk@5084 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/configDB.pm | 561 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 562 insertions(+) create mode 100644 fhem/configDB.pm diff --git a/fhem/CHANGED b/fhem/CHANGED index fc1e040d0..4e25e69f7 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,6 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. - SVN + - feature: configDB.pm use sql database instead of fhem.cfg (betateilchen) - feature: 98_pilight: Added support for Elso protocol - feature: readingsGroup: added sortDevices attribute - feature: ENIGMA2: new reading 'recordings', new command record diff --git a/fhem/configDB.pm b/fhem/configDB.pm new file mode 100644 index 000000000..3a2063dce --- /dev/null +++ b/fhem/configDB.pm @@ -0,0 +1,561 @@ +use DBI; + +################################################## +# Forward declarations for functions in fhem.pl +# +sub AnalyzeCommandChain($$;$); +sub Debug($); +sub Log3($$$); + +################################################## +# Read configuration file +# +if(!open(CONFIG, 'configDB.conf')) { + Log3('configDB', 1, 'Cannot open database configuration file configDB.conf'); + return 0; +} +my @config=; +close(CONFIG); + +my %dbconfig; +eval join("", @config); + +my $cfgDB_dbconn = $dbconfig{connection}; +my $cfgDB_dbuser = $dbconfig{user}; +my $cfgDB_dbpass = $dbconfig{password}; +my $cfgDB_dbtype; + +(%dbconfig, @config) = (undef,undef); + +if($cfgDB_dbconn =~ m/pg:/i) { + $cfgDB_dbtype ="POSTGRESQL"; + } elsif ($cfgDB_dbconn =~ m/mysql:/i) { + $cfgDB_dbtype = "MYSQL"; +# } elsif ($cfgDB_dbconn =~ m/oracle:/i) { +# $cfgDB_dbtype = "ORACLE"; + } elsif ($cfgDB_dbconn =~ m/sqlite:/i) { + $cfgDB_dbtype = "SQLITE"; + } else { + $cfgDB_dbtype = "unknown"; +} + +################################################## +# Connect to database and return handle +# +sub cfgDB_Connect { + my $fhem_dbh = DBI->connect( + "dbi:$cfgDB_dbconn", + $cfgDB_dbuser, + $cfgDB_dbpass, + { AutoCommit => 0, RaiseError => 1 }, + ) or die $DBI::errstr; + return $fhem_dbh; +} + +sub cfgDB_Init { +################################################## +# Create non-existing database tables +# Create default config entries if necessary +# + my $fhem_dbh = cfgDB_Connect; + +# create TABLE fhemconfig if nonexistent + $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemconfig(COMMAND CHAR(32), DEVICE CHAR(32), P1 CHAR(50), P2 TEXT, VERSION INT)"); +# check TABLE fhemconfig already populated + my $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemconfig'); + if($count < 1) { +# insert default entries to get fhem running + cfgDB_InsertLine($fhem_dbh, '# added by cfgDB_Init'); + cfgDB_InsertLine($fhem_dbh, 'attr global logfile ./log/fhem-%Y-%m-%d.log'); + cfgDB_InsertLine($fhem_dbh, 'attr global modpath .'); + cfgDB_InsertLine($fhem_dbh, 'attr global userattr devStateIcon devStateStyle icon sortby webCmd'); + cfgDB_InsertLine($fhem_dbh, 'attr global verbose 3'); + cfgDB_InsertLine($fhem_dbh, 'define telnetPort telnet 7072 global'); + cfgDB_InsertLine($fhem_dbh, 'define WEB FHEMWEB 8083 global'); + cfgDB_InsertLine($fhem_dbh, 'define Logfile FileLog ./log/fhem-%Y-%m-%d.log fakelog'); + } + +# create TABLE fhemstate if nonexistent + $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemstate(stateString TEXT)"); + $fhem_dbh->commit(); + $fhem_dbh->disconnect(); + + return; +} + +sub cfgDB_Info { + my $l = '--------------------'; + $l .= $l; + $l .= $l; + $l .= "\n"; + my $r = $l; + $r .= " configDB Database Information\n"; + $r .= $l; + $r .= " dbconn: $cfgDB_dbconn\n"; + $r .= " dbuser: $cfgDB_dbuser\n"; + $r .= " dbpass: $cfgDB_dbpass\n"; + $r .= " dbtype: $cfgDB_dbtype\n"; + $r .= " Unknown dbmodel type in configuration file.\n" if $dbtype eq 'unknown'; + $r .= " Only Mysql, Postgresql, Oracle, SQLite are fully supported.\n" if $dbtype eq 'unknown'; + $r .= $l; + + my $fhem_dbh = cfgDB_Connect; + my ($sth, @line, $row); + +# read versions table statistics + my $count; + $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemconfig'); + $r .= " fhemconfig: $count entries\n\n"; +# read versions creation time + $sth = $fhem_dbh->prepare( "SELECT * FROM fhemconfig WHERE COMMAND ='#created' ORDER by VERSION" ); + $sth->execute(); + while (@line = $sth->fetchrow_array()) { + $row = " Ver $line[4] saved: $line[1] $line[2] $line[3] def: ". + $fhem_dbh->selectrow_array("SELECT COUNT(*) from fhemconfig where COMMAND = 'define' and VERSION = $line[4]"); + $row .= " attr: ". + $fhem_dbh->selectrow_array("SELECT COUNT(*) from fhemconfig where COMMAND = 'attr' and VERSION = $line[4]"); + $r .= "$row\n"; + } + $r .= $l; + +# read state table statistics + $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemstate'); + $r .= " fhemstate: $count entries saved: "; +# read state table creation time + $sth = $fhem_dbh->prepare( "SELECT * FROM fhemstate WHERE STATESTRING like '#%'" ); + $sth->execute(); + while ($row = $sth->fetchrow_array()) { + (undef,$row) = split(/#/,$row); + $r .= "$row\n"; + } + $r .= $l; + + $fhem_dbh->disconnect(); + + return $r; +} + +sub cfgDB_Recover($) { + my ($version) = @_; + my ($cmd, $count, $ret); + + if($version > 0) { + my $fhem_dbh = cfgDB_Connect; + $cmd = "SELECT count(*) FROM fhemconfig WHERE VERSION = $version"; + $count = $fhem_dbh->selectrow_array($cmd); + + if($count > 0) { +# Delete current version 0 + $fhem_dbh->do("DELETE FROM fhemconfig WHERE VERSION = 0"); + +# Copy selected version to version 0 + my ($sth, $sth2, @line); + $cmd = "SELECT * FROM fhemconfig WHERE VERSION = $version"; + $sth = $fhem_dbh->prepare($cmd); + $sth->execute(); + $sth2 = $fhem_dbh->prepare('INSERT INTO fhemconfig values (?, ?, ?, ?, ?)'); + while (@line = $sth->fetchrow_array()) { + $sth2->execute($line[0], $line[1], $line[2], $line[3], 0); + } + $fhem_dbh->commit(); + $fhem_dbh->disconnect(); + +# Inform user about restart or rereadcfg needed + $ret = "Version 0 deleted.\n"; + $ret .= "Version $version copied to version 0\n\n"; + $ret .= "Please use rereadcfg or restart to activate configuration."; + } else { + $fhem_dbh->disconnect(); + $ret = "No entries found in version $version.\nNo changes committed to database."; + } + } else { + $ret = 'Please select version 1..n for recovery.'; + } + return $ret; +} + +sub cfgDB_Reorg(;$) { + my ($lastversion) = @_; + $lastversion = ($lastversion > 0) ? $lastversion : 3; + Log3('configDB', 4, "DB Reorg started, keeping last $lastversion versions."); + my $fhem_dbh = cfgDB_Connect; + $fhem_dbh->do("DELETE FROM fhemconfig WHERE VERSION > $lastversion"); + $fhem_dbh->commit(); + $fhem_dbh->disconnect(); + return " Result after database reorg:\n".cfgDB_Info; +} + +sub cfgDB_InsertLine($$) { + my ($fhem_dbh, $line) = @_; + my ($c,$d,$p1,$p2) = split(/ /, $line, 4); + my $sth = $fhem_dbh->prepare('INSERT INTO fhemconfig values (?, ?, ?, ?, ?)'); + $sth->execute($c, $d, $p1, $p2, 0); + return; +} + +sub cfgDB_Execute($@) { + my ($cl, @dbconfig) = @_; + foreach (@dbconfig){ + my $l = $_; + $l =~ s/[\r\n]//g; + AnalyzeCommandChain($cl, $l); + } + return; +} + +sub cfgDB_SaveCfg { + my (%devByNr, @rowList); + + map { $devByNr{$defs{$_}{NR}} = $_ } keys %defs; + + for(my $i = 0; $i < $devcount; $i++) { + + my ($h, $d); + if($comments{$i}) { + $h = $comments{$i}; + } else { + $d = $devByNr{$i}; + next if(!defined($d) || + $defs{$d}{TEMPORARY} || # e.g. WEBPGM connections + $defs{$d}{VOLATILE}); # e.g at, will be saved to the statefile + $h = $defs{$d}; + } + + if(!defined($d)) { + push @rowList, $h->{TEXT}; + next; + } + + if($d ne "global") { + my $def = $defs{$d}{DEF}; + if(defined($def)) { + $def =~ s/;/;;/g; + $def =~ s/\n/\\\n/g; + } else { + $dev = ""; + } + push @rowList, "define $d $defs{$d}{TYPE} $def"; + } + + foreach my $a (sort keys %{$attr{$d}}) { + next if($d eq "global" && + ($a eq "configfile" || $a eq "version")); + my $val = $attr{$d}{$a}; + $val =~ s/;/;;/g; + $val =~ s/\n/\\\n/g; + push @rowList, "attr $d $a $val"; + } + } + +# Insert @rowList into database table + my $fhem_dbh = cfgDB_Connect; + cfgDB_Rotate($fhem_dbh); + $t = localtime; + $out = "#created $t"; + push @rowList, $out; + foreach (@rowList) { cfgDB_InsertLine($fhem_dbh, $_); } + $fhem_dbh->commit(); + $fhem_dbh->disconnect(); + return 'configDB saved.'; +} + +sub cfgDB_SaveState { + my ($out,$val,$r,$rd,$t,@rowList); + + $t = localtime; + $out = "#$t"; + push @rowList, $out; + + foreach my $d (sort keys %defs) { + next if($defs{$d}{TEMPORARY}); + if($defs{$d}{VOLATILE}) { + $out = "define $d $defs{$d}{TYPE} $defs{$d}{DEF}"; + push @rowList, $out; + } + $val = $defs{$d}{STATE}; + if(defined($val) && + $val ne "unknown" && + $val ne "Initialized" && + $val ne "???") { + $val =~ s/;/;;/g; + $val =~ s/\n/\\\n/g; + $out = "setstate $d $val"; + push @rowList, $out; + } + $r = $defs{$d}{READINGS}; + if($r) { + foreach my $c (sort keys %{$r}) { + $rd = $r->{$c}; + if(!defined($rd->{TIME})) { + Log3(undef, 4, "WriteStatefile $d $c: Missing TIME, using current time"); + $rd->{TIME} = TimeNow(); + } + if(!defined($rd->{VAL})) { + Log3(undef, 4, "WriteStatefile $d $c: Missing VAL, setting it to 0"); + $rd->{VAL} = 0; + } + $val = $rd->{VAL}; + $val =~ s/;/;;/g; + $val =~ s/\n/\\\n/g; + $out = "setstate $d $rd->{TIME} $c $val"; + push @rowList, $out; + } + } + } + + my $fhem_dbh = cfgDB_Connect; + $fhem_dbh->do("DELETE FROM fhemstate"); + my $sth = $fhem_dbh->prepare('INSERT INTO fhemstate values ( ? )'); + foreach (@rowList) { $sth->execute( $_ ); } + $fhem_dbh->commit(); + $fhem_dbh->disconnect(); + return; +} + +sub cfgDB_ReadCfg(@) { + my (@dbconfig) = @_; + my $fhem_dbh = cfgDB_Connect; + my ($sth, @line, $row); + + $sth = $fhem_dbh->prepare( "SELECT * FROM fhemconfig WHERE VERSION = 0" ); + $sth->execute(); + while (@line = $sth->fetchrow_array()) { + $row = "$line[0] $line[1] $line[2] $line[3]"; + push @dbconfig, $row; + } + $fhem_dbh->disconnect(); + return @dbconfig; +} + +sub cfgDB_ReadState(@) { + my (@dbconfig) = @_; + my $fhem_dbh = cfgDB_Connect; + my ($sth, $row); + + $sth = $fhem_dbh->prepare( "SELECT * FROM fhemstate" ); + $sth->execute(); + while ($row = $sth->fetchrow_array()) { + push @dbconfig, $row; + } + $fhem_dbh->disconnect(); + return @dbconfig; +} + +sub cfgDB_GlobalAttr { + my ($sth, @line, $row, @dbconfig); + + my $fhem_dbh = cfgDB_Connect; + $sth = $fhem_dbh->prepare( "SELECT * FROM fhemconfig WHERE DEVICE = 'global'" ); + $sth->execute(); + + while (@line = $sth->fetchrow_array()) { + $row = "$line[0] $line[1] $line[2] $line[3]"; + $line[3] =~ s/#.*//; + $line[3] =~ s/ .*$//; + $attr{global}{$line[2]} = $line[3]; + } + $fhem_dbh->disconnect(); + return; +} + +sub cfgDB_Rotate($) { + my ($fhem_dbh) = @_; + $fhem_dbh->do("UPDATE fhemconfig SET VERSION = VERSION+1"); + return; +} + +sub cfgDB_ReadAll($){ + my ($cl) = @_; + # add Config Rows to commandfile + my @dbconfig = cfgDB_ReadCfg(@dbconfig); + # add State Rows to commandfile + @dbconfig = cfgDB_ReadState(@dbconfig); + # AnalyzeCommandChain for all entries + cfgDB_Execute($cl, @dbconfig); + return; +} + +sub cfgDB_Migrate { + Log3('configDB',4,'Starting migration.'); + Log3('configDB',4,'Processing: cfgDB_Init.'); + cfgDB_Init; + Log3('configDB',4,'Processing: cfgDB_SaveCfg.'); + cfgDB_SaveCfg; + Log3('configDB',4,'Processing: cfgDB_SaveState.'); + cfgDB_SaveState; + Log3('configDB',4,'Migration finished.'); + return 'Migration finished.'; +} + +1; + +=pod +not to be translated +=begin html + + +

configDB

+ + +=end html