From 18bdba32b093e36c716aff37d0ff37224baefe9d Mon Sep 17 00:00:00 2001
From: betateilchen <>
Date: Sun, 20 Feb 2022 18:43:15 +0000
Subject: [PATCH] :debug version
git-svn-id: https://svn.fhem.de/fhem/trunk@25718 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/contrib/betateilchen/debug/configDB.pm | 1397 +++++++++++++++++++
1 file changed, 1397 insertions(+)
create mode 100644 fhem/contrib/betateilchen/debug/configDB.pm
diff --git a/fhem/contrib/betateilchen/debug/configDB.pm b/fhem/contrib/betateilchen/debug/configDB.pm
new file mode 100644
index 000000000..d419a4e05
--- /dev/null
+++ b/fhem/contrib/betateilchen/debug/configDB.pm
@@ -0,0 +1,1397 @@
+# $Id: configDB.pm 25615 2022-02-02 12:36:27Z betateilchen $
+
+=for comment (License)
+
+##############################################################################
+#
+# configDB.pm
+#
+# A fhem library to enable configuration from sql database
+# instead of plain text file, e.g. fhem.cfg
+#
+# READ COMMANDREF DOCUMENTATION FOR CORRECT USE!
+#
+# Copyright: betateilchen ®
+#
+# This file is part of fhem.
+#
+# Fhem is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Fhem is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with fhem. If not, see .
+#
+##############################################################################
+=cut
+
+=for comment (changelog before 2022)
+#
+# ChangeLog
+#
+# 2014-03-01 - SVN 5080 - initial release of interface inside fhem.pl
+# - initial release of configDB.pm
+#
+# 2014-03-02 - added template files for sqlite in contrib/configDB
+# - updated commandref (EN) documentation
+# - added commandref (DE) documentation
+#
+# 2014-03-03 - changed performance optimized by using version uuid table
+# - updated commandref docu for migration
+# - added cfgDB_svnId for fhem.pl CommandVersion
+# - added cfgDB_List to show device info from database
+# - updated commandref docu for cfgDB_List
+#
+# 2014-03-06 - added cfgDB_Diff to compare device in two versions
+#
+# 2014-03-07 - changed optimized cfgDB_Diff
+# restructured libraray internally
+# improved source code documentation
+#
+# 2014-03-20 - added export/import
+# 2014-04-01 - removed export/import due to not working properly
+#
+# 2014-04-03 - fixed global attributes not read from version 0
+#
+# 2014-04-18 - added commands fileimport, fileexport
+# 2014-04-19 - added commands filelist, filedelete
+# interface cfgDB_Readfile for interaction
+# with other modules
+#
+# 2014-04-21 - added interface functions for FHEMWEB and fhem.pl
+# to show files in "Edit files" and use them
+# with CommandReload() mechanism
+#
+# modified _cfgDB_Info to show number of files in db
+#
+# 2014-04-23 - added command fileshow, filemove
+#
+# 2014-04-26 - added migration to generic file handling
+# fixed problem on migration of multiline DEFs
+#
+# 2014-04-27 - added new functions for binfile handling
+#
+# 2014-05-11 - removed command binfileimport
+# changed store all files as binary
+# added _cfgDB_Move to move all files from text
+# to binary filesave on first load of configDB
+#
+# 2014-05-12 - added sorted write & read for config data
+#
+# 2014-05-15 - fixed handling of multiline defs
+#
+# 2014-05-20 - removed no longer needed functions for file handling
+# changed code improvement; use strict; use warnings;
+#
+# 2014-08-22 - added automatic fileimport during migration
+#
+# 2014-09-30 - added support for device based userattr
+#
+# 2015-01-12 - changed use fhem function createUniqueId()
+# instead of database calls
+#
+# 2015-01-15 - changed remove 99_Utils.pm from filelist
+#
+# 2015-01-17 - added configdb diff all current
+# shows diff table between version 0
+# and currently running version (in memory)
+#
+# 2015-01-23 - changed attribute handling for internal configDB attrs
+#
+# 2015-01-23 - added FileRead() caching - experimental
+#
+# 2015-10-14 - changed search conditions use ESCAPE, forum #42190
+#
+# 2016-03-19 - changed use modpath, forum #51036
+#
+# 2016-03-26 - added log entry for search (verbose=5)
+#
+# 2016-05-22 - added configdb dump (for sqlite)
+#
+# 2016-05-28 - added configdb dump (for mysql)
+#
+# 2016-05-29 - changed improve support for postgresql (tnx to Matze)
+# added configdb dump (for postgresql)
+#
+# 2016-07-03 - added support for multiple hosts (experimental)
+# 2016-07-04 - fixed improve config file read
+# 2016-07-07 - bugfix select configuration
+#
+# 2017-03-24 - added use index on fhemconfig (only sqlite)
+#
+# 2017-07-17 - changed store files base64 encoded
+#
+# 2017-08-31 - changed improve table_info for migration check
+#
+# 2018-02-17 - changed remove experimenatal cache functions
+# 2018-02-18 - changed move dump processing to backend
+#
+# 2018-03-24 - changed set privacy as default for username and password
+# 2018-03-25 - changed move rescue modes from ENV to config file
+#
+# 2018-06-17 - changed remove migration on FHEM start by default
+# check migration only if parameter migrate => 1
+# is set in configDB.conf
+#
+# 2018-07-04 - bugfix change rescue mode persistence
+#
+# 2018-07-07 - change lastReorg added to info output
+#
+# 2018-09-08 - change remove base64 migration functions
+#
+# 2019-01-17 - added support for device specific uuid (setuuid)
+# 2019-01-18 - changed use GetDefAndAttr()
+#
+# 2019-02-16 - changed default field length for table creation
+#
+# 2020-02-25 - added support weekprofile in automatic migration
+#
+# 2020-06-37 - added support for special strange readings (length check)
+#
+# 2020-06-29 - added support for mysqldump parameter by attribute
+#
+# 2020-07-02 - changed code cleanup after last changes (remove debug code)
+# add "configdb attr ?" to show known attributes
+#
+# 2021-04-17 - bugfix problem in File.* commands regarding case sensitivity
+#
+# 2021-08-17 - changed adopt to Rudi's funny fakelog changes
+#
+# 2021-10-24 - added delete old files for large readings
+#
+=cut
+
+=for comment
+##############################################################################
+#
+# 2022-02-20 - added begin of statefile versioning
+# todo:
+# done - fhem.pl must deliver ID in CommandSave
+# done - this ID must be used for config version 0
+# done - this ID must be used for statefile
+# done - manual statfile creation must use existing ID for version 0
+# done - table fhemstate no longer needed
+# configdb reorg must delete old statefiles
+# remove special handling for large readings
+# check recovery
+#
+##############################################################################
+=cut
+
+use strict;
+use warnings;
+use Text::Diff;
+use DBI;
+use Sys::Hostname;
+use MIME::Base64;
+
+##################################################
+# Forward declarations for functions in fhem.pl
+#
+## no critic
+sub AnalyzeCommandChain($$;$);
+sub GetDefAndAttr($;$);
+sub Log($$);
+sub createUniqueId();
+## use critic
+
+##################################################
+# Forward declarations inside this library
+#
+sub cfgDB_AttrRead;
+sub cfgDB_ReadAll;
+sub cfgDB_Init;
+sub cfgDB_FileRead;
+sub cfgDB_FileUpdate;
+sub cfgDB_Fileversion;
+sub cfgDB_FileWrite;
+sub cfgDB_FW_fileList;
+sub cfgDB_Read99;
+sub cfgDB_SaveCfg;
+sub cfgDB_SaveState;
+sub cfgDB_svnId;
+
+sub _cfgDB_binFileimport;
+sub _cfgDB_Connect;
+sub _cfgDB_DeleteTemp;
+sub _cfgDB_Diff;
+sub __cfgDB_Diff;
+sub _cfgDB_InsertLine;
+sub _cfgDB_Execute;
+sub _cfgDB_Filedelete;
+sub _cfgDB_Fileexport;
+sub _cfgDB_Filelist;
+sub _cfgDB_Info;
+sub _cfgDB_Migrate;
+sub _cfgDB_ReadCfg;
+sub _cfgDB_ReadState;
+sub _cfgDB_Recover;
+sub _cfgDB_Reorg;
+sub _cfgDB_Rotate;
+sub _cfgDB_Search;
+sub _cfgDB_table_exists;
+sub _cfgDB_dump;
+sub _cfgDB_knownAttr;
+sub _cfgDB_deleteRF;
+
+##################################################
+# Read configuration file for DB connection
+#
+
+
+my ($err,@c) = FileRead({FileName => 'configDB.conf',
+ ForceType => "file"});
+return 0 if ($err);
+
+my @config;
+
+foreach my $line (@c) {
+ $line =~ s/^\s+|\s+$//g; # remove whitespaces etc.
+ $line =~ s/;$/;;/; # duplicate ; at end-of-line
+ push (@config,$line) if($line !~ m/^#/ && length($line) > 0);
+}
+
+use vars qw(%configDB);
+
+my %dbconfig;
+
+my $configs = join("",@config);
+my @configs = split(/;;/,$configs);
+my $count = @configs;
+my $fhemhost = hostname;
+
+## no critic
+if ($count > 1) {
+ foreach my $c (@configs) {
+ next unless $c =~ m/^%dbconfig.*/;
+ $dbconfig{fhemhost} = "";
+ eval $c;
+ last if ($dbconfig{fhemhost} eq $fhemhost);
+ }
+ eval $configs[0] if ($dbconfig{fhemhost} eq "");
+} else {
+ eval $configs[0];
+}
+## use critic
+
+my $cfgDB_dbconn = $dbconfig{connection};
+my $cfgDB_dbuser = $dbconfig{user};
+my $cfgDB_dbpass = $dbconfig{password};
+my $cfgDB_dbtype;
+my $cfgDB_filename;
+
+
+if($cfgDB_dbconn =~ m/pg:/i) {
+ $cfgDB_dbtype ="POSTGRESQL";
+ } elsif ($cfgDB_dbconn =~ m/mysql:/i) {
+ $cfgDB_dbtype = "MYSQL";
+ } elsif ($cfgDB_dbconn =~ m/sqlite:/i) {
+ $cfgDB_dbtype = "SQLITE";
+ (undef,$cfgDB_filename) = split(/=/,$cfgDB_dbconn);
+ $configDB{filename} = $cfgDB_filename;
+ } else {
+ $cfgDB_dbtype = "unknown";
+}
+
+$configDB{type} = $cfgDB_dbtype;
+$configDB{attr}{nostate} = defined($dbconfig{nostate}) ? $dbconfig{nostate} : 0;
+$configDB{attr}{rescue} = defined($dbconfig{rescue}) ? $dbconfig{rescue} : 0;
+$configDB{attr}{loadversion} = defined($dbconfig{loadversion}) ? $dbconfig{loadversion} : 0;
+
+_cfgDB_knownAttr();
+
+%dbconfig = ();
+@config = ();
+$configs = undef;
+$count = undef;
+
+##################################################
+# Basic functions needed for DB configuration
+# directly called from fhem.pl
+#
+
+# initialize database, create tables if necessary
+sub cfgDB_Init {
+##################################################
+# Create non-existing database tables
+# Create default config entries if necessary
+#
+ my $fhem_dbh = _cfgDB_Connect;
+
+# create TABLE fhemversions ifnonexistent
+ $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemversions(VERSION INT, VERSIONUUID CHAR(50))");
+
+# create TABLE fhemconfig if nonexistent
+ $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemconfig(COMMAND VARCHAR(32), DEVICE VARCHAR(64), P1 VARCHAR(64), P2 TEXT, VERSION INT, VERSIONUUID CHAR(50))");
+
+# create INDEX on fhemconfig if nonexistent (only if SQLITE)
+ $fhem_dbh->do("CREATE INDEX IF NOT EXISTS config_idx on 'fhemconfig' (versionuuid,version)")
+ if($cfgDB_dbtype eq "SQLITE");
+
+# 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
+ $fhem_dbh->commit();
+ my $uuid = createUniqueId();
+ $fhem_dbh->do("INSERT INTO fhemversions values (0, '$uuid')");
+ _cfgDB_InsertLine($fhem_dbh, $uuid, '#created by cfgDB_Init',0);
+ _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global logdir ./log',1);
+ _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global logfile %L/fhem-%Y-%m-%d.log',2);
+ _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global modpath .',3);
+ _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global userattr devStateIcon devStateStyle icon sortby webCmd',4);
+ _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global verbose 3',5);
+ _cfgDB_InsertLine($fhem_dbh, $uuid, 'define telnetPort telnet 7072 global',6);
+ _cfgDB_InsertLine($fhem_dbh, $uuid, 'define web FHEMWEB 8083 global',7);
+ _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr web allowfrom .*',8);
+ _cfgDB_InsertLine($fhem_dbh, $uuid, 'define Logfile FileLog %L/fhem-%Y-%m-%d.log Logfile',9);
+ }
+
+# create TABLE fhemstate if nonexistent
+# $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemstate(stateString TEXT)");
+
+# create TABLE fhemb64filesave if nonexistent
+ if($cfgDB_dbtype eq "MYSQL") {
+ $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemb64filesave(filename TEXT, content MEDIUMBLOB)");
+ } elsif ($cfgDB_dbtype eq "POSTGRESQL") {
+ $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemb64filesave(filename TEXT, content bytea)");
+ } else {
+ $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemb64filesave(filename TEXT, content BLOB)");
+ }
+
+# close database connection
+ $fhem_dbh->commit();
+ $fhem_dbh->disconnect();
+
+ return;
+}
+
+# read attributes
+sub cfgDB_AttrRead {
+ my ($readSpec) = @_;
+ my ($row, $sql, @line, @rets);
+ my $fhem_dbh = _cfgDB_Connect;
+ my $uuid = $fhem_dbh->selectrow_array('SELECT versionuuid FROM fhemversions WHERE version = 0');
+ $sql = "SELECT * FROM fhemconfig WHERE COMMAND = 'attr' AND DEVICE = '$readSpec' AND VERSIONUUID = '$uuid'";
+ $sql = "SELECT * FROM fhemconfig WHERE COMMAND = 'attr' AND (DEVICE = 'global' OR DEVICE = 'configdb') and VERSIONUUID = '$uuid'"
+ if($readSpec eq 'global');
+ my $sth = $fhem_dbh->prepare( $sql );
+ $sth->execute();
+ while (@line = $sth->fetchrow_array()) {
+ if($line[1] eq 'configdb') {
+ $configDB{attr}{$line[2]} = $line[3];
+ } else {
+ push @rets, "attr $line[1] $line[2] $line[3]";
+ }
+ }
+ $fhem_dbh->disconnect();
+ return @rets;
+}
+
+# generic file functions called from fhem.pl
+sub cfgDB_FileRead {
+ my ($filename,$fhem_dbh) = @_;
+ my $internal_call = 1 if $fhem_dbh;
+
+ Log 4, "configDB reading file: $filename";
+ my ($err, @ret, $counter);
+ $fhem_dbh = _cfgDB_Connect unless $fhem_dbh;
+ my $read_cmd = "SELECT content FROM fhemb64filesave WHERE filename = '$filename'";
+ my $sth = $fhem_dbh->prepare( $read_cmd );
+ $sth->execute();
+ my $blobContent = $sth->fetchrow_array();
+ $sth->finish();
+ $fhem_dbh->disconnect() unless $internal_call;
+ $blobContent = decode_base64($blobContent) if ($blobContent);
+ $counter = length($blobContent);
+ if($counter) {
+ @ret = split(/\n/,$blobContent);
+ $err = "";
+ } else {
+ @ret = undef;
+ $err = "Error on reading $filename from database!";
+ }
+ return ($err, @ret);
+}
+
+sub cfgDB_FileWrite {
+ my ($filename,@content) = @_;
+ Log 4, "configDB writing file: $filename";
+ my $fhem_dbh = _cfgDB_Connect;
+ $fhem_dbh->do("delete from fhemb64filesave where filename = '$filename'");
+ my $sth = $fhem_dbh->prepare('INSERT INTO fhemb64filesave values (?, ?)');
+ $sth->execute($filename,encode_base64(join("\n", @content)));
+ $sth->finish();
+ $fhem_dbh->commit();
+ $fhem_dbh->disconnect();
+ return;
+}
+
+sub cfgDB_FileUpdate {
+ my ($filename) = @_;
+ my $fhem_dbh = _cfgDB_Connect;
+ my $id = $fhem_dbh->selectrow_array("SELECT filename from fhemb64filesave where filename = '$filename'");
+ $fhem_dbh->disconnect();
+ if($id) {
+ my $filesize = -s $filename;
+ _cfgDB_binFileimport($filename,$filesize,1) if ($id) ;
+ Log 5, "file $filename updated in configDB";
+ }
+ return;
+}
+
+# read and execute fhemconfig and fhemstate
+sub cfgDB_ReadAll { ## prototype used in fhem.pl
+ my ($cl) = @_;
+ my ($ret, @dbconfig);
+
+ if ($configDB{attr}{rescue} == 1) {
+ Log 0, 'configDB starting in rescue mode!';
+ push (@dbconfig, 'attr global modpath .');
+ push (@dbconfig, 'attr global verbose 3');
+ push (@dbconfig, 'define telnetPort telnet 7072 global');
+ push (@dbconfig, 'define web FHEMWEB 8083 global');
+ push (@dbconfig, 'attr web allowfrom .*');
+ push (@dbconfig, 'define Logfile FileLog ./log/fhem-%Y-%m-%d.log Logfile');
+ } else {
+ # add Config Rows to commandfile
+ @dbconfig = _cfgDB_ReadCfg(@dbconfig);
+ # add State Rows to commandfile
+ @dbconfig = _cfgDB_ReadState(@dbconfig) unless $configDB{attr}{nostate} == 1;
+ }
+
+ # AnalyzeCommandChain for all entries
+ $ret = _cfgDB_Execute($cl, @dbconfig);
+ return $ret if($ret);
+ return;
+}
+
+# save running configuration to version 0
+sub cfgDB_SaveCfg { ## prototype used in fhem.pl
+ Log 1, "configDB save config ".$data{saveID};
+ my ($internal) = shift;
+ $internal = defined($internal) ? $internal : 0;
+ my $c = "configdb";
+ my @dontSave = qw(configdb:rescue configdb:nostate configdb:loadversion
+ global:configfile global:statefile global:version);
+ my (%devByNr, @rowList, %comments, $t, $out);
+
+ 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;
+ }
+
+ push (@rowList, GetDefAndAttr($d,1));
+
+ }
+
+ foreach my $a (sort keys %{$configDB{attr}}) {
+ my $val = $configDB{attr}{$a};
+ next unless $val;
+ next if grep {$_ eq "$c:$a";} @dontSave;
+ $val =~ s/;/;;/g;
+ push @rowList, "attr $c $a $val";
+ }
+
+# Insert @rowList into database table
+ my $fhem_dbh = _cfgDB_Connect;
+ my $uuid = _cfgDB_Rotate($fhem_dbh,$internal);
+ $t = localtime;
+ $out = "#created $t";
+ push @rowList, $out;
+ my $counter = 0;
+ foreach (@rowList) {
+ _cfgDB_InsertLine($fhem_dbh, $uuid, $_, $counter);
+ $counter++;
+ }
+ $fhem_dbh->commit();
+ $fhem_dbh->disconnect();
+ my $maxVersions = $configDB{attr}{maxversions};
+ $maxVersions = ($maxVersions) ? $maxVersions : 0;
+ _cfgDB_Reorg($maxVersions,1) if($maxVersions && $internal != -1);
+ return 'configDB saved.';
+}
+
+# save statefile
+sub cfgDB_SaveState {
+ my ($out,$val,$r,$rd,$t,@rowList);
+
+ _cfgDB_deleteRF;
+
+ $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 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})) {
+ Log 4, "WriteStatefile $d $c: Missing TIME, using current time";
+ $rd->{TIME} = TimeNow();
+ }
+ if(!defined($rd->{VAL})) {
+ Log 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";
+ if (length($out) > 65530) {
+ my $uid = createUniqueId();
+ FileWrite($uid,$val);
+ $out = "setstate $d $rd->{TIME} $c cfgDBkey:$uid";
+ Log 4, "configDB: r:$c d:$d key:$uid";
+ }
+ push @rowList, $out;
+ }
+ }
+ }
+
+ my $fileName = defined($data{saveID}) ? $data{saveID} : $configDB{loaded};
+ $fileName .= ".fhem.save";
+ Log 1, "configDB save state $fileName";
+ cfgDB_FileWrite($fileName,@rowList);
+
+ 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;
+}
+
+# import existing files during migration
+sub cfgDB_MigrationImport {
+
+ my ($ret, $filename, @files, @def);
+ my $modpath = AttrVal("global","modpath",".");
+
+# find eventTypes file
+ @def = '';
+ @def = _cfgDB_findDef('TYPE=eventTypes');
+ foreach my $fn (@def) {
+ next unless $fn;
+ push @files, $fn;
+ }
+
+# import templateDB.gplot
+ $filename = "$modpath/www/gplot/template.gplot";
+ push @files, $filename;
+ $filename = "$modpath/www/gplot/templateDB.gplot";
+ push @files, $filename;
+
+# import template.layout
+ $filename = "$modpath/FHEM/template.layout";
+ push @files, $filename;
+
+# find used gplot files
+ @def = '';
+ @def = _cfgDB_findDef('TYPE=SVG','GPLOTFILE');
+ foreach my $fn (@def) {
+ next unless $fn;
+ push @files, "$modpath/www/gplot/".$fn.".gplot";
+ }
+
+# find DbLog configs
+ @def = '';
+ @def = _cfgDB_findDef('TYPE=DbLog','CONFIGURATION');
+ foreach my $fn (@def) {
+ next unless $fn;
+ push @files, $fn;
+ }
+
+# find RSS layouts
+ @def = '';
+ @def = _cfgDB_findDef('TYPE=RSS','LAYOUTFILE');
+ foreach my $fn (@def) {
+ next unless $fn;
+ push @files, $fn;
+ }
+
+# find InfoPanel layouts
+ @def = '';
+ @def = _cfgDB_findDef('TYPE=InfoPanel','LAYOUTFILE');
+ foreach my $fn (@def) {
+ next unless $fn;
+ push @files, $fn;
+ }
+
+# find weekprofile configurations
+ @def = '';
+ @def = _cfgDB_findDef('TYPE=weekprofile','CONFIGFILE');
+ foreach my $fn (@def) {
+ next unless $fn;
+ push @files, $fn;
+ }
+
+# find holiday files
+ @def = '';
+ @def = _cfgDB_findDef('TYPE=holiday','NAME');
+ foreach my $fn (@def) {
+ next unless $fn;
+ if(defined($defs{$fn}{HOLIDAYFILE})) {
+ push @files, $defs{$fn}{HOLIDAYFILE};
+ } else {
+ push @files, "$modpath/FHEM/holiday/".$fn.".holiday";
+ }
+ }
+
+# import uniqueID file
+ $filename = "$modpath/FHEM/FhemUtils/uniqueID";
+ push @files,$filename if (-e $filename);
+
+
+# do the import
+ foreach my $fn (@files) {
+ if ( -r $fn ) {
+ my $filesize = -s $fn;
+ _cfgDB_binFileimport($fn,$filesize);
+ $ret .= "importing: $fn\n";
+ }
+ }
+
+ return $ret;
+}
+
+# return SVN Id, called by fhem's CommandVersion
+sub cfgDB_svnId {
+ return "# ".'$Id: configDB.pm 25615 2022-02-02 12:36:27Z betateilchen $'
+}
+
+# return filelist depending on directory and regexp
+sub cfgDB_FW_fileList {
+ my ($dir,$re,@ret) = @_;
+ my @files = split(/\n/, _cfgDB_Filelist('notitle'));
+ foreach my $f (@files) {
+ next if( $f !~ m/^$dir/ );
+ $f =~ s,$dir\/,,;
+ next if($f !~ m,^$re$, || $f eq '99_Utils.pm');
+ push @ret, "$f.configDB";
+ }
+ return @ret;
+}
+
+# read filelist containing 99_ files in database
+sub cfgDB_Read99 {
+ my $ret = "";
+ my $fhem_dbh = _cfgDB_Connect;
+ my $sth = $fhem_dbh->prepare( "SELECT filename FROM fhemb64filesave WHERE filename like '%/99_%.pm' group by filename" );
+ $sth->execute();
+ while (my $line = $sth->fetchrow_array()) {
+ $line =~ m,^(.*)/([^/]*)$,; # Split into dir and file
+ $ret .= "$2,"; #
+ }
+ $sth->finish();
+ $fhem_dbh->disconnect();
+ $ret =~ s/,$//;
+ return $ret;
+}
+
+# return SVN Id from file stored in database
+sub cfgDB_Fileversion {
+ my ($file,$ret) = @_;
+ $ret = "No Id found for $file";
+ my ($err,@in) = cfgDB_FileRead($file);
+ foreach(@in){ $ret = $_ if($_ =~ m/# \$Id:/); }
+ return $ret;
+}
+
+##################################################
+# Basic functions needed for DB configuration
+# but not called from fhem.pl directly
+#
+
+# connect do database
+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;
+}
+
+# add configuration entry into fhemconfig
+sub _cfgDB_InsertLine {
+ my ($fhem_dbh, $uuid, $line, $counter) = @_;
+ my ($c,$d,$p1,$p2) = split(/ /, $line, 4);
+ my $sth = $fhem_dbh->prepare('INSERT INTO fhemconfig values (?, ?, ?, ?, ?, ?)');
+ $sth->execute($c, $d, $p1, $p2, $counter, $uuid);
+ return;
+}
+
+# pass command table to AnalyzeCommandChain
+sub _cfgDB_Execute {
+ my ($cl, @dbconfig) = @_;
+ my (@ret);
+
+ foreach my $l (@dbconfig) {
+ $l =~ s/[\r\n]/\n/g;
+ $l =~ s/\\\n/\n/g;
+ my $tret = AnalyzeCommandChain($cl, $l);
+ push @ret, $tret if(defined($tret));
+ }
+ return join("\n", @ret) if(@ret);
+ return;
+}
+
+# read all entries from fhemconfig
+# and add them to command table for execution
+sub _cfgDB_ReadCfg {
+ my (@dbconfig) = @_;
+ my $fhem_dbh = _cfgDB_Connect;
+ my ($sth, @line, $row);
+
+ my $version = $configDB{attr}{loadversion};
+ delete $configDB{attr}{loadversion};
+ if ($version > 0) {
+ my $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemversions');
+ $count--;
+ $version = $version > $count ? $count : $version;
+ Log 0, "configDB loading version $version on user request.";
+ }
+
+# maybe this will be done with join later
+ my $uuid = $fhem_dbh->selectrow_array("SELECT versionuuid FROM fhemversions WHERE version = '$version'");
+ $configDB{loaded} = $uuid;
+ Log 1, "configDB read config ".$configDB{loaded};
+ $sth = $fhem_dbh->prepare( "SELECT * FROM fhemconfig WHERE versionuuid = '$uuid' and device <>'configdb' order by version" );
+
+ $sth->execute();
+ while (@line = $sth->fetchrow_array()) {
+ $row = "$line[0] $line[1] $line[2]";
+ $row .= " $line[3]" if defined($line[3]);
+ push @dbconfig, $row;
+ }
+ $fhem_dbh->disconnect();
+ return @dbconfig;
+}
+
+# read all entries from fhemstate
+# and add them to command table for execution
+sub _cfgDB_ReadState {
+ my (@dbconfig) = @_;
+
+ my $stateFileName = $configDB{loaded}.".fhem.save";
+ my ($err,@state) = cfgDB_FileRead($stateFileName);
+ if ($err eq "") {
+ Log 1, "configDB read state ".$stateFileName;
+ push @dbconfig,@state;
+ } else {
+ Log 1, "configDB read state from table fhemstate";
+ my $fhem_dbh = _cfgDB_Connect;
+ my ($sth, $row,$f);
+ $sth = $fhem_dbh->prepare( "SELECT * FROM fhemstate" );
+ $sth->execute();
+ while ($row = $sth->fetchrow_array()) {
+ if($row =~ m/(cfgDBkey:)(.{32})/) {
+ my $f = $2;
+ my (undef, $content) = cfgDB_FileRead($f,$fhem_dbh);
+ $row =~ s/cfgDB:................................$/$content/;
+ _cfgDB_Filedelete($f,$fhem_dbh);
+ }
+ push @dbconfig, $row;
+ }
+ $fhem_dbh->disconnect();
+ }
+ return @dbconfig;
+}
+
+# rotate all versions to versionnum + 1
+# return uuid for new version 0
+sub _cfgDB_Rotate {
+ my ($fhem_dbh,$newversion) = @_;
+ my $uuid = $data{saveID};
+ delete $data{saveID}; # no longer needed in memory
+ $configDB{loaded} = $uuid;
+ $fhem_dbh->do("UPDATE fhemversions SET VERSION = VERSION+1 where VERSION >= 0") if $newversion == 0;
+ $fhem_dbh->do("INSERT INTO fhemversions values ('$newversion', '$uuid')");
+ return $uuid;
+}
+
+sub _cfgDB_filesize_str {
+ my ($size) = @_;
+
+ if ($size > 1099511627776) # TiB: 1024 GiB
+ {
+ return sprintf("%.2f TB", $size / 1099511627776);
+ }
+ elsif ($size > 1073741824) # GiB: 1024 MiB
+ {
+ return sprintf("%.2f GB", $size / 1073741824);
+ }
+ elsif ($size > 1048576) # MiB: 1024 KiB
+ {
+ return sprintf("%.2f MB", $size / 1048576);
+ }
+ elsif ($size > 1024) # KiB: 1024 B
+ {
+ return sprintf("%.2f KB", $size / 1024);
+ }
+ else # bytes
+ {
+ return "$size byte" . ($size == 1 ? "" : "s");
+ }
+}
+
+##################################################
+# Additional backend functions
+# not called from fhem.pl directly
+#
+
+# migrate existing fhem config into database
+sub _cfgDB_Migrate {
+ my $ret;
+ $ret = "Starting migration...\n";
+ Log 4, 'configDB: Starting migration';
+ $ret .= "Processing: database initialization\n";
+ Log 4, 'configDB: Processing: cfgDB_Init';
+ cfgDB_Init;
+ $ret .= "Processing: save config\n";
+ Log 4, 'configDB: Processing: cfgDB_SaveCfg';
+ cfgDB_SaveCfg;
+ $ret .= "Processing: save state\n";
+ Log 4, 'configDB: Processing: cfgDB_SaveState';
+ cfgDB_SaveState;
+ $ret .= "Processing: fileimport\n";
+ Log 4, 'configDB: Processing: cfgDB_MigrationImport';
+ $ret .= cfgDB_MigrationImport;
+ $ret .= "Migration completed\n\n";
+ Log 4, 'configDB: Migration completed.';
+ $ret .= _cfgDB_Info(undef);
+ return $ret;
+}
+
+# show database statistics
+sub _cfgDB_Info {
+ my ($info2) = @_;
+ $info2 //= 'unknown';
+ my ($l, @r, $f);
+ for my $i (1..65){ $l .= '-';}
+
+ $configDB{attr}{private} //= 1;
+
+ push @r, $l;
+ push @r, " configDB Database Information";
+ push @r, $l;
+ my $info1 = cfgDB_svnId;
+ $info1 =~ s/# //;
+ push @r, " d:$info1";
+ push @r, " c:$info2";
+ push @r, $l;
+ push @r, " dbconn: $cfgDB_dbconn";
+ push @r, " dbuser: $cfgDB_dbuser" if !$configDB{attr}{private};
+ push @r, " dbpass: $cfgDB_dbpass" if !$configDB{attr}{private};
+ push @r, " dbtype: $cfgDB_dbtype";
+ push @r, " Unknown dbmodel type in configuration file." if $cfgDB_dbtype eq 'unknown';
+ push @r, " Only Mysql, Postgresql, SQLite are fully supported." if $cfgDB_dbtype eq 'unknown';
+ if ($cfgDB_dbtype eq "SQLITE") {
+ my $size = -s $cfgDB_filename;
+ $size = _cfgDB_filesize_str($size);
+ push @r, " dbsize: $size";
+ }
+ push @r, $l;
+ my $fhem_dbh = _cfgDB_Connect;
+ my ($sql, $sth, @line, $row);
+
+# read versions table statistics
+ my $maxVersions = $configDB{attr}{maxversions};
+ $maxVersions = ($maxVersions) ? $maxVersions : 0;
+ push @r, " max Versions: $maxVersions" if($maxVersions);
+ push @r, " lastReorg: ".$configDB{attr}{'lastReorg'};
+ my $count;
+ $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemconfig');
+ push @r, " config: $count entries";
+ push @r, "";
+
+# read versions creation time
+ $sql = "SELECT * FROM fhemconfig as c join fhemversions as v on v.versionuuid=c.versionuuid ".
+ "WHERE COMMAND like '#created%' ORDER by v.VERSION";
+ $sth = $fhem_dbh->prepare( $sql );
+ $sth->execute();
+ while (@line = $sth->fetchrow_array()) {
+ $line[3] = "" unless defined $line[3];
+ $row = " Ver $line[6] saved: $line[1] $line[2] $line[3] def: ".
+ $fhem_dbh->selectrow_array("SELECT COUNT(*) from fhemconfig where COMMAND = 'define' and VERSIONUUID = '$line[5]'");
+ $row .= " attr: ".
+ $fhem_dbh->selectrow_array("SELECT COUNT(*) from fhemconfig where COMMAND = 'attr' and VERSIONUUID = '$line[5]'");
+ push @r, $row;
+ }
+ push @r, $l;
+
+## read state table statistics
+# $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemstate');
+# $f = ($count>1) ? "s" : "";
+## 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);
+# $row = " state: $count entrie$f saved: $row";
+# push @r, $row;
+# }
+# push @r, $l;
+
+ $row = $fhem_dbh->selectall_arrayref("SELECT filename from fhemb64filesave group by filename");
+ $count = @$row;
+ $count = ($count)?$count:'No';
+ $f = ("$count" ne '1') ? "s" : "";
+ $row = " filesave: $count file$f stored in database";
+ push @r, $row;
+ push @r, $l;
+
+ $fhem_dbh->disconnect();
+
+ return join("\n", @r);
+}
+
+sub _cfgDB_Info_Json {
+ my $cSVN = shift;
+ $cSVN //= 'unknown';
+ my %info = ();
+
+# add ./FHEM/98_configdb.pm svn id
+ $info{cSVN} = $cSVN;
+
+# add ./configDB.pm svn id
+ my $dSVN = cfgDB_svnId;
+ $dSVN =~ s/# //;
+ $info{dSVN} = $dSVN;
+
+# add configDB database info
+ $info{dbconn} = $cfgDB_dbconn;
+ $info{dbuser} = $configDB{attr}{private} ? 'private' : $cfgDB_dbuser;
+ $info{dbpass} = $configDB{attr}{private} ? 'private' : $cfgDB_dbpass;
+ $info{dbtype} = $cfgDB_dbtype;
+ $info{dbsize} = _cfgDB_filesize_str(-s $cfgDB_filename) if ($cfgDB_dbtype eq "SQLITE");
+
+ return toJSON \%info;
+}
+
+# recover former config from database archive
+sub _cfgDB_Recover {
+ my ($version) = @_;
+ my ($cmd, $count, $ret);
+
+ if($version > 0) {
+ my $fhem_dbh = _cfgDB_Connect;
+ $cmd = "SELECT count(*) FROM fhemconfig WHERE VERSIONUUID in (select versionuuid from fhemversions where version = $version)";
+ $count = $fhem_dbh->selectrow_array($cmd);
+
+ if($count > 0) {
+ my $fromuuid = $fhem_dbh->selectrow_array("select versionuuid from fhemversions where version = $version");
+ my $touuid = createUniqueId();
+# Delete current version 0
+ $fhem_dbh->do("DELETE FROM fhemconfig WHERE VERSIONUUID in (select versionuuid from fhemversions where version = 0)");
+ $fhem_dbh->do("update fhemversions set versionuuid = '$touuid' where version = 0");
+
+# Copy selected version to version 0
+ my ($sth, $sth2, @line);
+ $cmd = "SELECT * FROM fhemconfig WHERE VERSIONUUID = '$fromuuid'";
+ $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], $line[4], $touuid);
+ }
+ $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 restart FHEM 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;
+}
+
+# delete old configurations
+sub _cfgDB_Reorg {
+ my ($lastversion,$quiet) = @_;
+ $lastversion = (defined($lastversion)) ? $lastversion : 3;
+ Log 4, "configDB reorg started, keeping last $lastversion versions.";
+ my $fhem_dbh = _cfgDB_Connect;
+ $fhem_dbh->do("delete FROM fhemconfig where versionuuid in (select versionuuid from fhemversions where version > $lastversion)");
+ $fhem_dbh->do("delete from fhemversions where version > $lastversion");
+ $fhem_dbh->do("delete FROM fhemconfig where versionuuid in (select versionuuid from fhemversions where version = -1)");
+ $fhem_dbh->do("delete from fhemversions where version = -1");
+ my $ts = localtime(time);
+ $configDB{attr}{'lastReorg'} = $ts;
+ _cfgDB_InsertLine($fhem_dbh,$configDB{loaded},"attr configdb lastReorg $ts",-1);
+ $fhem_dbh->commit();
+ $fhem_dbh->disconnect();
+ eval { qx(sqlite3 $cfgDB_filename vacuum) } if($cfgDB_dbtype eq "SQLITE");
+ return if(defined($quiet));
+ return " Result after database reorg:\n"._cfgDB_Info(undef);
+}
+
+# delete temporary version
+sub _cfgDB_DeleteTemp {
+ Log 4, "configDB: delete temporary Version -1";
+ my $fhem_dbh = _cfgDB_Connect;
+ $fhem_dbh->do("delete FROM fhemconfig where versionuuid in (select versionuuid from fhemversions where version = -1)");
+ $fhem_dbh->do("delete from fhemversions where version = -1");
+ $fhem_dbh->commit();
+ $fhem_dbh->disconnect();
+ return;
+}
+
+# search for device or fulltext in db
+sub _cfgDB_Search {
+ my ($search,$searchversion,$dsearch) = @_;
+ return 'Syntax error.' if(!(defined($search)));
+ my $fhem_dbh = _cfgDB_Connect;
+ my ($sql, $sth, @line, $row, @result, $ret, $text);
+ $sql = "SELECT command, device, p1, p2 FROM fhemconfig as c join fhemversions as v ON v.versionuuid=c.versionuuid ";
+ $sql .= "WHERE v.version = '$searchversion' AND command not like '#create%' ";
+ # 2015-10-24 - changed, forum #42190
+ if($cfgDB_dbtype eq 'SQLITE') {;
+ $sql .= "AND device like '$search%' ESCAPE '\\' " if($dsearch);
+ $sql .= "AND (device like '$search%' ESCAPE '\\' OR P1 like '$search%' ESCAPE '\\' OR P2 like '$search%' ESCAPE '\\') " if(!$dsearch);
+ } else {
+ $sql .= "AND device like '$search%' " if($dsearch);
+ $sql .= "AND (device like '$search%' OR P1 like '$search%' OR P2 like '$search%') " if(!$dsearch);
+ }
+ $sql .= "ORDER BY lower(device),command DESC";
+ $sth = $fhem_dbh->prepare( $sql);
+ Log 4, "configDB: $sql";
+ $sth->execute();
+ $text = $dsearch ? " device" : "";
+ push @result, "search result for$text: $search in version: $searchversion";
+ push @result, "--------------------------------------------------------------------------------";
+ while (@line = $sth->fetchrow_array()) {
+ $row = "$line[0] $line[1] $line[2]";
+ $row .= " $line[3]" if defined($line[3]);
+ Log 4, "configDB: $row";
+ push @result, "$row" unless ($line[0] eq 'setuuid');
+ }
+ $fhem_dbh->disconnect();
+ $ret = join("\n", @result);
+ return $ret;
+}
+
+# called from cfgDB_Diff
+sub __cfgDB_Diff {
+ my ($fhem_dbh,$search,$searchversion,$svinternal) = @_;
+ my ($sql, $sth, @line, $ret);
+if($svinternal != -1) {
+ $sql = "SELECT command, device, p1, p2 FROM fhemconfig as c join fhemversions as v ON v.versionuuid=c.versionuuid ".
+ "WHERE v.version = '$searchversion' AND device = '$search' ORDER BY command DESC";
+} else {
+ $sql = "SELECT command, device, p1, p2 FROM fhemconfig as c join fhemversions as v ON v.versionuuid=c.versionuuid ".
+ "WHERE v.version = '$searchversion' ORDER BY command DESC";
+}
+ $sth = $fhem_dbh->prepare( $sql);
+ $sth->execute();
+ while (@line = $sth->fetchrow_array()) {
+ $ret .= "$line[0] $line[1] $line[2] $line[3]\n";
+ }
+ return $ret;
+}
+
+# compare device configurations from 2 versions
+sub _cfgDB_Diff {
+ my ($search,$searchversion) = @_;
+ my ($ret, $v0, $v1);
+
+ if ($search eq 'all' && $searchversion eq 'current') {
+ _cfgDB_DeleteTemp();
+ cfgDB_SaveCfg(-1);
+ $searchversion = -1;
+ }
+
+ my $fhem_dbh = _cfgDB_Connect;
+ $v0 = __cfgDB_Diff($fhem_dbh,$search,0,$searchversion);
+ $v1 = __cfgDB_Diff($fhem_dbh,$search,$searchversion,$searchversion);
+ $fhem_dbh->disconnect();
+ $ret = diff \$v0, \$v1, { STYLE => "Table" };
+ if($searchversion == -1) {
+ _cfgDB_DeleteTemp();
+ $searchversion = "UNSAVED";
+ }
+ $ret = "\nNo differences found!" if !$ret;
+ $ret = "compare device: $search in current version 0 (left) to version: $searchversion (right)\n$ret\n";
+ return $ret;
+}
+
+# find DEF, input supports devspec definitions
+sub _cfgDB_findDef {
+ my ($search,$internal) = @_;
+ $internal = 'DEF' unless defined($internal);
+
+ my @ret;
+ my @etDev = devspec2array($search);
+ foreach my $d (@etDev) {
+ next unless $d;
+ push @ret, $defs{$d}{$internal};
+ }
+
+ return @ret;
+}
+
+sub _cfgDB_type {
+ return $cfgDB_dbtype;
+}
+
+sub _cfgDB_dump {
+ my ($param1) = @_;
+ $param1 //= '';
+
+ my ($dbconn,$dbuser,$dbpass,$dbtype) = _cfgDB_readConfig();
+ my ($dbname,$dbhostname,$dbport,$gzip,$mp,$ret,$size,$source,$target,$ts);
+ $ts = strftime('%Y-%m-%d_%H-%M-%S',localtime);
+ $mp = $configDB{attr}{'dumpPath'};
+ $mp //= AttrVal('global','modpath','.').'/log';
+ $target = "$mp/configDB_$ts.dump";
+
+ if (lc($param1) eq 'unzipped') {
+ $gzip = '';
+ } else {
+ $gzip = '| gzip -c';
+ $target .= '.gz';
+ }
+
+ if ($dbtype eq 'SQLITE') {
+ (undef,$source) = split (/=/, $dbconn);
+ my $dumpcmd = "echo '.dump fhem%' | sqlite3 $source $gzip > $target";
+ Log 4, "configDB: $dumpcmd";
+ $ret = qx($dumpcmd);
+ return $ret if $ret; # return error message if available
+
+ } elsif ($dbtype eq 'MYSQL') {
+ ($dbname,$dbhostname,$dbport) = split (/;/,$dbconn);
+ $dbport //= '=3306';
+ (undef,$dbname) = split (/=/,$dbname);
+ (undef,$dbhostname) = split (/=/,$dbhostname);
+ (undef,$dbport) = split (/=/,$dbport);
+ my $xparam = defined($configDB{attr}{mysqldump}) ? $configDB{attr}{mysqldump} : '';
+ my $dbtables = "fhemversions fhemconfig fhemstate fhemb64filesave";
+ my $dumpcmd = "mysqldump $xparam --user=$dbuser --password=$dbpass --host=$dbhostname --port=$dbport -Q $dbname $dbtables $gzip > $target";
+ Log 4, "configDB: $dumpcmd";
+ $ret = qx($dumpcmd);
+ return $ret if $ret;
+ $source = $dbname;
+
+ } elsif ($dbtype eq 'POSTGRESQL') {
+ ($dbname,$dbhostname,$dbport) = split (/;/,$dbconn);
+ $dbport //= '=5432';
+ (undef,$dbname) = split (/=/,$dbname);
+ (undef,$dbhostname) = split (/=/,$dbhostname);
+ (undef,$dbport) = split (/=/,$dbport);
+ my $dbtables = "-t fhemversions -t fhemconfig -t fhemstate -t fhemb64filesave";
+ my $dumpcmd = "PGPASSWORD=$dbpass pg_dump -U $dbuser -h $dbhostname -p $dbport $dbname $dbtables $gzip > $target";
+ Log 4, "configDB: $dumpcmd";
+ $ret = qx($dumpcmd);
+ return $ret if $ret;
+ $source = $dbname;
+
+ } else {
+ return "configdb dump not supported for $dbtype!";
+ }
+
+ $size = -s $target;
+ $size //= 0;
+ $ret = "configDB dumped $size bytes\nfrom: $source\n to: $target";
+ return $ret;
+
+}
+
+sub _cfgDB_knownAttr {
+ $configDB{knownAttr}{deleteimported} =
+ "(0|1) delete file from filesystem after import";
+ $configDB{knownAttr}{dumpPath} =
+ "(valid path) define path for database dump";
+# $configDB{knownAttr}{loadversion}=
+# "for internal use only";
+ $configDB{knownAttr}{maxversions}=
+ "(number) define maximum number of configurations stored in database";
+ $configDB{knownAttr}{mysqldump}=
+ "(valid parameter string) define additional parameters used for dump in mysql environment";
+# $configDB{knownAttr}{nostate}=
+# "for internal use only";
+ $configDB{knownAttr}{private}=
+ "(0|1) show or supress userdata in info output";
+# $configDB{knownAttr}{rescue}=
+# "for internal use only";
+}
+
+sub _cfgDB_deleteRF {
+# Delete old files containing large readings
+ my $filename;
+ my $fhem_dbh2 = _cfgDB_Connect;
+ my $sth = $fhem_dbh2->prepare( "SELECT filename FROM fhemb64filesave" );
+ $sth->execute();
+ while ($filename = $sth->fetchrow_array()) {
+ if ($filename =~ m/^[0-9A-F]+$/i) {
+ Log 4, "configDB delete file: $filename";
+ $fhem_dbh2->do("delete from fhemb64filesave where filename = '$filename'");
+ }
+ }
+ $fhem_dbh2->commit();
+ $fhem_dbh2->disconnect();
+}
+
+##################################################
+# functions used for file handling
+# called by 98_configdb.pm
+#
+
+# delete file from database
+sub _cfgDB_Filedelete {
+ my ($filename,$fhem_dbh) = @_;
+ my $internal_call = 1 if $fhem_dbh;
+ $fhem_dbh = _cfgDB_Connect unless $internal_call;
+ my $ret = $fhem_dbh->do("delete from fhemb64filesave where filename = '$filename'");
+ $fhem_dbh->commit();
+ $fhem_dbh->disconnect() unless $internal_call;
+ $ret = ($ret > 0) ? 1 : undef;
+ return $ret;
+}
+
+# export file from database to filesystem
+sub _cfgDB_Fileexport {
+ my ($filename,$raw) = @_;
+ my $fhem_dbh = _cfgDB_Connect;
+ my $sth = $fhem_dbh->prepare( "SELECT content FROM fhemb64filesave WHERE filename = '$filename'" );
+ $sth->execute();
+ my $blobContent = $sth->fetchrow_array();
+ $blobContent = decode_base64($blobContent) if($blobContent);
+ my $counter = length($blobContent);
+ $sth->finish();
+ $fhem_dbh->disconnect();
+ return "No data found for file $filename" unless $counter;
+ return ($blobContent,$counter) if $raw;
+
+ open( my $f,">","$filename" );
+ binmode($f);
+ print $f $blobContent;
+ close( $f );
+ return "$counter bytes written from database into file $filename";
+}
+
+# import file into database
+sub _cfgDB_binFileimport {
+ my ($filename,$filesize,$doDelete) = @_;
+ $doDelete = (defined($doDelete)) ? 1 : 0;
+
+ open (my $inFile,"<","$filename") || die $!;
+ my $blobContent;
+ binmode($inFile);
+ my $readBytes = read($inFile, $blobContent, $filesize);
+ close($inFile);
+ $blobContent = encode_base64($blobContent);
+ my $fhem_dbh = _cfgDB_Connect;
+ $fhem_dbh->do("delete from fhemb64filesave where filename = '$filename'");
+ my $sth = $fhem_dbh->prepare('INSERT INTO fhemb64filesave values (?, ?)');
+
+# add support for postgresql by Matze
+ $sth->bind_param( 1, $filename );
+ if ($cfgDB_dbtype eq "POSTGRESQL") {
+ $sth->bind_param( 2, $blobContent, { pg_type => DBD::Pg::PG_BYTEA() } );
+ } else {
+ $sth->bind_param( 2, $blobContent );
+ }
+
+ $sth->execute($filename, $blobContent);
+ $sth->finish();
+ $fhem_dbh->commit();
+ $fhem_dbh->disconnect();
+
+ unlink($filename) if(($configDB{attr}{deleteimported} || $doDelete) && $readBytes);
+ return "$readBytes bytes written from file $filename to database";
+}
+
+# list all files stored in database
+sub _cfgDB_Filelist {
+ my ($notitle) = @_;
+ my $ret = "Files found in database:\n".
+ "------------------------------------------------------------\n";
+ $ret = "" if $notitle;
+ my $fhem_dbh = _cfgDB_Connect;
+ my $sql = "SELECT filename FROM fhemb64filesave group by filename order by filename";
+ my $content = $fhem_dbh->selectall_arrayref($sql);
+ foreach my $row (@$content) {
+ $ret .= "@$row[0]\n" if(defined(@$row[0]));
+ }
+ $fhem_dbh->disconnect();
+ return $ret;
+}
+
+1;
+
+=pod
+=item helper
+=item summary configDB backend
+=item summary_DE configDB backend
+=begin html
+
+
+
configDB
+
+
+=end html
+
+=begin html_DE
+
+
+configDB
+
+ configDB ist die Funktionsbibliothek für die Konfiguration aus einer SQL Datenbank.
+ Die ausführliche Dokumentation findet sich in der configdb Befehlsbeschreibung.
+
+
+=end html_DE
+
+=cut