mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-28 11:01:59 +00:00
configDB: add statefile versioning
git-svn-id: https://svn.fhem.de/fhem/trunk@25765 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
8a9d115c23
commit
8c4e7f5af4
@ -196,6 +196,13 @@ sub CommandConfigdb {
|
|||||||
$ret = _cfgDB_Migrate;
|
$ret = _cfgDB_Migrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when ('rawList'){
|
||||||
|
$data{cfgDB_rawList} = 1;
|
||||||
|
my @out = cfgDB_SaveCfg();
|
||||||
|
delete $data{cfgDB_rawList};
|
||||||
|
return join("\n",@out);
|
||||||
|
}
|
||||||
|
|
||||||
when ('recover') {
|
when ('recover') {
|
||||||
return "\n Syntax: configdb recover <version>" if @a != 2;
|
return "\n Syntax: configdb recover <version>" if @a != 2;
|
||||||
return "Invalid paramaeter '$param1' for recover. Must be a number."
|
return "Invalid paramaeter '$param1' for recover. Must be a number."
|
||||||
@ -240,6 +247,7 @@ sub CommandConfigdb {
|
|||||||
" configdb info\n".
|
" configdb info\n".
|
||||||
" configdb list [device] [version]\n".
|
" configdb list [device] [version]\n".
|
||||||
" configdb migrate\n".
|
" configdb migrate\n".
|
||||||
|
" configdb rawList\n".
|
||||||
" configdb recover <version>\n".
|
" configdb recover <version>\n".
|
||||||
" configdb reorg [keepVersions]\n".
|
" configdb reorg [keepVersions]\n".
|
||||||
" configdb search <searchTerm> [version]\n".
|
" configdb search <searchTerm> [version]\n".
|
||||||
@ -556,6 +564,11 @@ Ver 0 always indicates the currently running configuration.<br/>
|
|||||||
<code>get configDB list global 1</code><br/>
|
<code>get configDB list global 1</code><br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
<li><code>configdb rawList</code></li><br/>
|
||||||
|
Lists the running configuration in the same order that <br/>
|
||||||
|
would be written to a configuration file.<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
<li><code>configdb recover <version></code></li><br/>
|
<li><code>configdb recover <version></code></li><br/>
|
||||||
Restores an older version from database archive.<br/>
|
Restores an older version from database archive.<br/>
|
||||||
<code>configdb recover 3</code> will <b>copy</b> version #3 from database
|
<code>configdb recover 3</code> will <b>copy</b> version #3 from database
|
||||||
|
184
fhem/configDB.pm
184
fhem/configDB.pm
@ -1,6 +1,6 @@
|
|||||||
# $Id$
|
# $Id$
|
||||||
|
|
||||||
=for comment
|
=for comment (License)
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@ -29,6 +29,9 @@
|
|||||||
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
|
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
=cut
|
||||||
|
|
||||||
|
=for comment (changelog before 2022)
|
||||||
#
|
#
|
||||||
# ChangeLog
|
# ChangeLog
|
||||||
#
|
#
|
||||||
@ -162,6 +165,17 @@
|
|||||||
#
|
#
|
||||||
# 2021-10-24 - added delete old files for large readings
|
# 2021-10-24 - added delete old files for large readings
|
||||||
#
|
#
|
||||||
|
=cut
|
||||||
|
|
||||||
|
=for comment (changelog starting 2022)
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# 2022-02-20 - changed use createUniqueId() for uuids
|
||||||
|
# remove _cfgDB_Uuid()
|
||||||
|
#
|
||||||
|
# 2022-02-20 - added statefile versioning - begin
|
||||||
|
# 2022-03-03 statefile versioning - completed
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
=cut
|
=cut
|
||||||
|
|
||||||
@ -179,7 +193,6 @@ use MIME::Base64;
|
|||||||
sub AnalyzeCommandChain($$;$);
|
sub AnalyzeCommandChain($$;$);
|
||||||
sub GetDefAndAttr($;$);
|
sub GetDefAndAttr($;$);
|
||||||
sub Log($$);
|
sub Log($$);
|
||||||
sub Log3($$$);
|
|
||||||
sub createUniqueId();
|
sub createUniqueId();
|
||||||
## use critic
|
## use critic
|
||||||
|
|
||||||
@ -217,11 +230,11 @@ sub _cfgDB_Recover;
|
|||||||
sub _cfgDB_Reorg;
|
sub _cfgDB_Reorg;
|
||||||
sub _cfgDB_Rotate;
|
sub _cfgDB_Rotate;
|
||||||
sub _cfgDB_Search;
|
sub _cfgDB_Search;
|
||||||
sub _cfgDB_Uuid;
|
|
||||||
sub _cfgDB_table_exists;
|
sub _cfgDB_table_exists;
|
||||||
sub _cfgDB_dump;
|
sub _cfgDB_dump;
|
||||||
sub _cfgDB_knownAttr;
|
sub _cfgDB_knownAttr;
|
||||||
sub _cfgDB_deleteRF;
|
sub _cfgDB_deleteRF;
|
||||||
|
sub _cfgDB_deleteStatefiles;
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
# Read configuration file for DB connection
|
# Read configuration file for DB connection
|
||||||
@ -322,7 +335,7 @@ sub cfgDB_Init {
|
|||||||
if($count < 1) {
|
if($count < 1) {
|
||||||
# insert default entries to get fhem running
|
# insert default entries to get fhem running
|
||||||
$fhem_dbh->commit();
|
$fhem_dbh->commit();
|
||||||
my $uuid = _cfgDB_Uuid;
|
my $uuid = createUniqueId();
|
||||||
$fhem_dbh->do("INSERT INTO fhemversions values (0, '$uuid')");
|
$fhem_dbh->do("INSERT INTO fhemversions values (0, '$uuid')");
|
||||||
_cfgDB_InsertLine($fhem_dbh, $uuid, '#created by cfgDB_Init',0);
|
_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 logdir ./log',1);
|
||||||
@ -337,7 +350,7 @@ sub cfgDB_Init {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# create TABLE fhemstate if nonexistent
|
# create TABLE fhemstate if nonexistent
|
||||||
$fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemstate(stateString TEXT)");
|
# $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemstate(stateString TEXT)");
|
||||||
|
|
||||||
# create TABLE fhemb64filesave if nonexistent
|
# create TABLE fhemb64filesave if nonexistent
|
||||||
if($cfgDB_dbtype eq "MYSQL") {
|
if($cfgDB_dbtype eq "MYSQL") {
|
||||||
@ -382,11 +395,10 @@ sub cfgDB_FileRead {
|
|||||||
my ($filename,$fhem_dbh) = @_;
|
my ($filename,$fhem_dbh) = @_;
|
||||||
my $internal_call = 1 if $fhem_dbh;
|
my $internal_call = 1 if $fhem_dbh;
|
||||||
|
|
||||||
Log3(undef, 4, "configDB reading file: $filename");
|
Log 4, "configDB reading file: $filename";
|
||||||
my ($err, @ret, $counter);
|
my ($err, @ret, $counter);
|
||||||
$fhem_dbh = _cfgDB_Connect unless $fhem_dbh;
|
$fhem_dbh = _cfgDB_Connect unless $fhem_dbh;
|
||||||
my $read_cmd = "SELECT content FROM fhemb64filesave WHERE filename = '$filename'";
|
my $read_cmd = "SELECT content FROM fhemb64filesave WHERE filename = '$filename'";
|
||||||
# my $sth = $fhem_dbh->prepare( "SELECT content FROM fhemb64filesave WHERE filename LIKE '$filename'" );
|
|
||||||
my $sth = $fhem_dbh->prepare( $read_cmd );
|
my $sth = $fhem_dbh->prepare( $read_cmd );
|
||||||
$sth->execute();
|
$sth->execute();
|
||||||
my $blobContent = $sth->fetchrow_array();
|
my $blobContent = $sth->fetchrow_array();
|
||||||
@ -406,7 +418,7 @@ sub cfgDB_FileRead {
|
|||||||
|
|
||||||
sub cfgDB_FileWrite {
|
sub cfgDB_FileWrite {
|
||||||
my ($filename,@content) = @_;
|
my ($filename,@content) = @_;
|
||||||
Log3(undef, 4, "configDB writing file: $filename");
|
Log 4, "configDB writing file: $filename";
|
||||||
my $fhem_dbh = _cfgDB_Connect;
|
my $fhem_dbh = _cfgDB_Connect;
|
||||||
$fhem_dbh->do("delete from fhemb64filesave where filename = '$filename'");
|
$fhem_dbh->do("delete from fhemb64filesave where filename = '$filename'");
|
||||||
my $sth = $fhem_dbh->prepare('INSERT INTO fhemb64filesave values (?, ?)');
|
my $sth = $fhem_dbh->prepare('INSERT INTO fhemb64filesave values (?, ?)');
|
||||||
@ -425,7 +437,7 @@ sub cfgDB_FileUpdate {
|
|||||||
if($id) {
|
if($id) {
|
||||||
my $filesize = -s $filename;
|
my $filesize = -s $filename;
|
||||||
_cfgDB_binFileimport($filename,$filesize,1) if ($id) ;
|
_cfgDB_binFileimport($filename,$filesize,1) if ($id) ;
|
||||||
Log(5, "file $filename updated in configDB");
|
Log 4, "file $filename updated in configDB";
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -436,13 +448,13 @@ sub cfgDB_ReadAll { ## prototype used in fhem.pl
|
|||||||
my ($ret, @dbconfig);
|
my ($ret, @dbconfig);
|
||||||
|
|
||||||
if ($configDB{attr}{rescue} == 1) {
|
if ($configDB{attr}{rescue} == 1) {
|
||||||
Log (0, 'configDB starting in rescue mode!');
|
Log 0, 'configDB starting in rescue mode!';
|
||||||
push (@dbconfig, 'attr global modpath .');
|
push (@dbconfig, 'attr global modpath .');
|
||||||
push (@dbconfig, 'attr global verbose 3');
|
push (@dbconfig, 'attr global verbose 3');
|
||||||
push (@dbconfig, 'define telnetPort telnet 7072 global');
|
push (@dbconfig, 'define telnetPort telnet 7072 global');
|
||||||
push (@dbconfig, 'define web FHEMWEB 8083 global');
|
push (@dbconfig, 'define web FHEMWEB 8083 global');
|
||||||
push (@dbconfig, 'attr web allowfrom .*');
|
push (@dbconfig, 'attr web allowfrom .*');
|
||||||
push (@dbconfig, 'define Logfile FileLog ./log/fhem-%Y-%m-%d.log fakelog');
|
push (@dbconfig, 'define Logfile FileLog ./log/fhem-%Y-%m-%d.log Logfile');
|
||||||
} else {
|
} else {
|
||||||
# add Config Rows to commandfile
|
# add Config Rows to commandfile
|
||||||
@dbconfig = _cfgDB_ReadCfg(@dbconfig);
|
@dbconfig = _cfgDB_ReadCfg(@dbconfig);
|
||||||
@ -458,12 +470,12 @@ sub cfgDB_ReadAll { ## prototype used in fhem.pl
|
|||||||
|
|
||||||
# save running configuration to version 0
|
# save running configuration to version 0
|
||||||
sub cfgDB_SaveCfg { ## prototype used in fhem.pl
|
sub cfgDB_SaveCfg { ## prototype used in fhem.pl
|
||||||
|
Log 4, "configDB save config ".$data{saveID} if(defined($data{saveID}));
|
||||||
my ($internal) = shift;
|
my ($internal) = shift;
|
||||||
$internal = defined($internal) ? $internal : 0;
|
$internal = defined($internal) ? $internal : 0;
|
||||||
my $c = "configdb";
|
my $c = "configdb";
|
||||||
my @dontSave = qw(configdb:rescue configdb:nostate configdb:loadversion
|
my @dontSave = qw(configdb:rescue configdb:nostate configdb:loadversion
|
||||||
global:configfile global:version);
|
global:configfile global:statefile global:version);
|
||||||
my (%devByNr, @rowList, %comments, $t, $out);
|
my (%devByNr, @rowList, %comments, $t, $out);
|
||||||
|
|
||||||
map { $devByNr{$defs{$_}{NR}} = $_ } keys %defs;
|
map { $devByNr{$defs{$_}{NR}} = $_ } keys %defs;
|
||||||
@ -498,12 +510,14 @@ sub cfgDB_SaveCfg { ## prototype used in fhem.pl
|
|||||||
push @rowList, "attr $c $a $val";
|
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;
|
$t = localtime;
|
||||||
$out = "#created $t";
|
$out = "#created $t";
|
||||||
push @rowList, $out;
|
push @rowList, $out;
|
||||||
|
return @rowList if defined($data{cfgDB_rawList});
|
||||||
|
|
||||||
|
# Insert @rowList into database table
|
||||||
|
my $fhem_dbh = _cfgDB_Connect;
|
||||||
|
my $uuid = _cfgDB_Rotate($fhem_dbh,$internal);
|
||||||
my $counter = 0;
|
my $counter = 0;
|
||||||
foreach (@rowList) {
|
foreach (@rowList) {
|
||||||
_cfgDB_InsertLine($fhem_dbh, $uuid, $_, $counter);
|
_cfgDB_InsertLine($fhem_dbh, $uuid, $_, $counter);
|
||||||
@ -540,8 +554,9 @@ sub cfgDB_SaveState {
|
|||||||
$val ne "" &&
|
$val ne "" &&
|
||||||
$val ne "???") {
|
$val ne "???") {
|
||||||
$val =~ s/;/;;/g;
|
$val =~ s/;/;;/g;
|
||||||
$val =~ s/\n/\\\n/g;
|
$val =~ s/\n/\$xyz\$/g;
|
||||||
$out = "setstate $d $val";
|
$out = "setstate $d $val";
|
||||||
|
Log 4, "configDB: $out";
|
||||||
push @rowList, $out;
|
push @rowList, $out;
|
||||||
}
|
}
|
||||||
$r = $defs{$d}{READINGS};
|
$r = $defs{$d}{READINGS};
|
||||||
@ -549,34 +564,28 @@ sub cfgDB_SaveState {
|
|||||||
foreach my $c (sort keys %{$r}) {
|
foreach my $c (sort keys %{$r}) {
|
||||||
$rd = $r->{$c};
|
$rd = $r->{$c};
|
||||||
if(!defined($rd->{TIME})) {
|
if(!defined($rd->{TIME})) {
|
||||||
Log3(undef, 4, "WriteStatefile $d $c: Missing TIME, using current time");
|
Log 4, "WriteStatefile $d $c: Missing TIME, using current time";
|
||||||
$rd->{TIME} = TimeNow();
|
$rd->{TIME} = TimeNow();
|
||||||
}
|
}
|
||||||
if(!defined($rd->{VAL})) {
|
if(!defined($rd->{VAL})) {
|
||||||
Log3(undef, 4, "WriteStatefile $d $c: Missing VAL, setting it to 0");
|
Log 4, "WriteStatefile $d $c: Missing VAL, setting it to 0";
|
||||||
$rd->{VAL} = 0;
|
$rd->{VAL} = 0;
|
||||||
}
|
}
|
||||||
$val = $rd->{VAL};
|
$val = $rd->{VAL};
|
||||||
$val =~ s/;/;;/g;
|
$val =~ s/;/;;/g;
|
||||||
$val =~ s/\n/\\\n/g;
|
$val =~ s/\n/\$xyz\$/g;
|
||||||
$out = "setstate $d $rd->{TIME} $c $val";
|
$out = "setstate $d $rd->{TIME} $c $val";
|
||||||
if (length($out) > 65530) {
|
Log 4, "configDB: $out";
|
||||||
my $uid = _cfgDB_Uuid();
|
|
||||||
FileWrite($uid,$val);
|
|
||||||
$out = "setstate $d $rd->{TIME} $c cfgDBkey:$uid";
|
|
||||||
Log 5, "configDB: r:$c d:$d key:$uid";
|
|
||||||
}
|
|
||||||
push @rowList, $out;
|
push @rowList, $out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my $fhem_dbh = _cfgDB_Connect;
|
my $fileName = defined($data{saveID}) ? $data{saveID} : $configDB{loaded};
|
||||||
$fhem_dbh->do("DELETE FROM fhemstate");
|
$fileName .= ".fhem.save";
|
||||||
my $sth = $fhem_dbh->prepare('INSERT INTO fhemstate values ( ? )');
|
Log 4, "configDB save state $fileName";
|
||||||
foreach (@rowList) { $sth->execute( $_ ); }
|
cfgDB_FileWrite($fileName,@rowList);
|
||||||
$fhem_dbh->commit();
|
|
||||||
$fhem_dbh->disconnect();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,7 +596,6 @@ sub cfgDB_MigrationImport {
|
|||||||
my $modpath = AttrVal("global","modpath",".");
|
my $modpath = AttrVal("global","modpath",".");
|
||||||
|
|
||||||
# find eventTypes file
|
# find eventTypes file
|
||||||
# $filename = '';
|
|
||||||
@def = '';
|
@def = '';
|
||||||
@def = _cfgDB_findDef('TYPE=eventTypes');
|
@def = _cfgDB_findDef('TYPE=eventTypes');
|
||||||
foreach my $fn (@def) {
|
foreach my $fn (@def) {
|
||||||
@ -606,7 +614,6 @@ sub cfgDB_MigrationImport {
|
|||||||
push @files, $filename;
|
push @files, $filename;
|
||||||
|
|
||||||
# find used gplot files
|
# find used gplot files
|
||||||
# $filename ='';
|
|
||||||
@def = '';
|
@def = '';
|
||||||
@def = _cfgDB_findDef('TYPE=SVG','GPLOTFILE');
|
@def = _cfgDB_findDef('TYPE=SVG','GPLOTFILE');
|
||||||
foreach my $fn (@def) {
|
foreach my $fn (@def) {
|
||||||
@ -615,7 +622,6 @@ sub cfgDB_MigrationImport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# find DbLog configs
|
# find DbLog configs
|
||||||
# $filename ='';
|
|
||||||
@def = '';
|
@def = '';
|
||||||
@def = _cfgDB_findDef('TYPE=DbLog','CONFIGURATION');
|
@def = _cfgDB_findDef('TYPE=DbLog','CONFIGURATION');
|
||||||
foreach my $fn (@def) {
|
foreach my $fn (@def) {
|
||||||
@ -624,7 +630,6 @@ sub cfgDB_MigrationImport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# find RSS layouts
|
# find RSS layouts
|
||||||
# $filename ='';
|
|
||||||
@def = '';
|
@def = '';
|
||||||
@def = _cfgDB_findDef('TYPE=RSS','LAYOUTFILE');
|
@def = _cfgDB_findDef('TYPE=RSS','LAYOUTFILE');
|
||||||
foreach my $fn (@def) {
|
foreach my $fn (@def) {
|
||||||
@ -633,7 +638,6 @@ sub cfgDB_MigrationImport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# find InfoPanel layouts
|
# find InfoPanel layouts
|
||||||
# $filename ='';
|
|
||||||
@def = '';
|
@def = '';
|
||||||
@def = _cfgDB_findDef('TYPE=InfoPanel','LAYOUTFILE');
|
@def = _cfgDB_findDef('TYPE=InfoPanel','LAYOUTFILE');
|
||||||
foreach my $fn (@def) {
|
foreach my $fn (@def) {
|
||||||
@ -642,7 +646,6 @@ sub cfgDB_MigrationImport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# find weekprofile configurations
|
# find weekprofile configurations
|
||||||
# $filename ='';
|
|
||||||
@def = '';
|
@def = '';
|
||||||
@def = _cfgDB_findDef('TYPE=weekprofile','CONFIGFILE');
|
@def = _cfgDB_findDef('TYPE=weekprofile','CONFIGFILE');
|
||||||
foreach my $fn (@def) {
|
foreach my $fn (@def) {
|
||||||
@ -651,7 +654,6 @@ sub cfgDB_MigrationImport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# find holiday files
|
# find holiday files
|
||||||
# $filename ='';
|
|
||||||
@def = '';
|
@def = '';
|
||||||
@def = _cfgDB_findDef('TYPE=holiday','NAME');
|
@def = _cfgDB_findDef('TYPE=holiday','NAME');
|
||||||
foreach my $fn (@def) {
|
foreach my $fn (@def) {
|
||||||
@ -669,7 +671,6 @@ sub cfgDB_MigrationImport {
|
|||||||
|
|
||||||
|
|
||||||
# do the import
|
# do the import
|
||||||
# $filename = '';
|
|
||||||
foreach my $fn (@files) {
|
foreach my $fn (@files) {
|
||||||
if ( -r $fn ) {
|
if ( -r $fn ) {
|
||||||
my $filesize = -s $fn;
|
my $filesize = -s $fn;
|
||||||
@ -782,6 +783,8 @@ sub _cfgDB_ReadCfg {
|
|||||||
|
|
||||||
# maybe this will be done with join later
|
# maybe this will be done with join later
|
||||||
my $uuid = $fhem_dbh->selectrow_array("SELECT versionuuid FROM fhemversions WHERE version = '$version'");
|
my $uuid = $fhem_dbh->selectrow_array("SELECT versionuuid FROM fhemversions WHERE version = '$version'");
|
||||||
|
$configDB{loaded} = $uuid;
|
||||||
|
Log 4, "configDB read config ".$configDB{loaded};
|
||||||
$sth = $fhem_dbh->prepare( "SELECT * FROM fhemconfig WHERE versionuuid = '$uuid' and device <>'configdb' order by version" );
|
$sth = $fhem_dbh->prepare( "SELECT * FROM fhemconfig WHERE versionuuid = '$uuid' and device <>'configdb' order by version" );
|
||||||
|
|
||||||
$sth->execute();
|
$sth->execute();
|
||||||
@ -798,9 +801,20 @@ sub _cfgDB_ReadCfg {
|
|||||||
# and add them to command table for execution
|
# and add them to command table for execution
|
||||||
sub _cfgDB_ReadState {
|
sub _cfgDB_ReadState {
|
||||||
my (@dbconfig) = @_;
|
my (@dbconfig) = @_;
|
||||||
|
|
||||||
|
my $stateFileName = $configDB{loaded}.".fhem.save";
|
||||||
|
my ($err,@state) = cfgDB_FileRead($stateFileName);
|
||||||
|
if ($err eq "") {
|
||||||
|
Log 4, "configDB read state ".$stateFileName;
|
||||||
|
map { my $a = $_; $a =~ s/\$xyz\$/\\n/g; push @dbconfig, $a } @state;
|
||||||
|
my $fhem_dbh = _cfgDB_Connect;
|
||||||
|
$fhem_dbh->do("delete from fhemstate");
|
||||||
|
$fhem_dbh->commit();
|
||||||
|
$fhem_dbh->disconnect();
|
||||||
|
} else {
|
||||||
|
Log 4, "configDB read state from table fhemstate";
|
||||||
my $fhem_dbh = _cfgDB_Connect;
|
my $fhem_dbh = _cfgDB_Connect;
|
||||||
my ($sth, $row,$f);
|
my ($sth, $row,$f);
|
||||||
|
|
||||||
$sth = $fhem_dbh->prepare( "SELECT * FROM fhemstate" );
|
$sth = $fhem_dbh->prepare( "SELECT * FROM fhemstate" );
|
||||||
$sth->execute();
|
$sth->execute();
|
||||||
while ($row = $sth->fetchrow_array()) {
|
while ($row = $sth->fetchrow_array()) {
|
||||||
@ -813,6 +827,7 @@ sub _cfgDB_ReadState {
|
|||||||
push @dbconfig, $row;
|
push @dbconfig, $row;
|
||||||
}
|
}
|
||||||
$fhem_dbh->disconnect();
|
$fhem_dbh->disconnect();
|
||||||
|
}
|
||||||
return @dbconfig;
|
return @dbconfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -820,17 +835,14 @@ sub _cfgDB_ReadState {
|
|||||||
# return uuid for new version 0
|
# return uuid for new version 0
|
||||||
sub _cfgDB_Rotate {
|
sub _cfgDB_Rotate {
|
||||||
my ($fhem_dbh,$newversion) = @_;
|
my ($fhem_dbh,$newversion) = @_;
|
||||||
my $uuid = _cfgDB_Uuid;
|
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("UPDATE fhemversions SET VERSION = VERSION+1 where VERSION >= 0") if $newversion == 0;
|
||||||
$fhem_dbh->do("INSERT INTO fhemversions values ('$newversion', '$uuid')");
|
$fhem_dbh->do("INSERT INTO fhemversions values ('$newversion', '$uuid')");
|
||||||
return $uuid;
|
return $uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2015-01-12 use the fhem default function
|
|
||||||
sub _cfgDB_Uuid {
|
|
||||||
return createUniqueId();
|
|
||||||
}
|
|
||||||
|
|
||||||
sub _cfgDB_filesize_str {
|
sub _cfgDB_filesize_str {
|
||||||
my ($size) = @_;
|
my ($size) = @_;
|
||||||
|
|
||||||
@ -865,21 +877,21 @@ sub _cfgDB_filesize_str {
|
|||||||
sub _cfgDB_Migrate {
|
sub _cfgDB_Migrate {
|
||||||
my $ret;
|
my $ret;
|
||||||
$ret = "Starting migration...\n";
|
$ret = "Starting migration...\n";
|
||||||
Log3('configDB',4,'Starting migration');
|
Log 4, 'configDB: Starting migration';
|
||||||
$ret .= "Processing: database initialization\n";
|
$ret .= "Processing: database initialization\n";
|
||||||
Log3('configDB',4,'Processing: cfgDB_Init');
|
Log 4, 'configDB: Processing: cfgDB_Init';
|
||||||
cfgDB_Init;
|
cfgDB_Init;
|
||||||
$ret .= "Processing: save config\n";
|
$ret .= "Processing: save config\n";
|
||||||
Log3('configDB',4,'Processing: cfgDB_SaveCfg');
|
Log 4, 'configDB: Processing: cfgDB_SaveCfg';
|
||||||
cfgDB_SaveCfg;
|
cfgDB_SaveCfg;
|
||||||
$ret .= "Processing: save state\n";
|
$ret .= "Processing: save state\n";
|
||||||
Log3('configDB',4,'Processing: cfgDB_SaveState');
|
Log 4, 'configDB: Processing: cfgDB_SaveState';
|
||||||
cfgDB_SaveState;
|
cfgDB_SaveState;
|
||||||
$ret .= "Processing: fileimport\n";
|
$ret .= "Processing: fileimport\n";
|
||||||
Log3('configDB',4,'Processing: cfgDB_MigrationImport');
|
Log 4, 'configDB: Processing: cfgDB_MigrationImport';
|
||||||
$ret .= cfgDB_MigrationImport;
|
$ret .= cfgDB_MigrationImport;
|
||||||
$ret .= "Migration completed\n\n";
|
$ret .= "Migration completed\n\n";
|
||||||
Log3('configDB',4,'Migration completed.');
|
Log 4, 'configDB: Migration completed.';
|
||||||
$ret .= _cfgDB_Info(undef);
|
$ret .= _cfgDB_Info(undef);
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
@ -941,18 +953,18 @@ sub _cfgDB_Info {
|
|||||||
}
|
}
|
||||||
push @r, $l;
|
push @r, $l;
|
||||||
|
|
||||||
# read state table statistics
|
## read state table statistics
|
||||||
$count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemstate');
|
# $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemstate');
|
||||||
$f = ($count>1) ? "s" : "";
|
# $f = ($count>1) ? "s" : "";
|
||||||
# read state table creation time
|
## read state table creation time
|
||||||
$sth = $fhem_dbh->prepare( "SELECT * FROM fhemstate WHERE STATESTRING like '#%'" );
|
# $sth = $fhem_dbh->prepare( "SELECT * FROM fhemstate WHERE STATESTRING like '#%'" );
|
||||||
$sth->execute();
|
# $sth->execute();
|
||||||
while ($row = $sth->fetchrow_array()) {
|
# while ($row = $sth->fetchrow_array()) {
|
||||||
(undef,$row) = split(/#/,$row);
|
# (undef,$row) = split(/#/,$row);
|
||||||
$row = " state: $count entrie$f saved: $row";
|
# $row = " state: $count entrie$f saved: $row";
|
||||||
push @r, $row;
|
# push @r, $row;
|
||||||
}
|
# }
|
||||||
push @r, $l;
|
# push @r, $l;
|
||||||
|
|
||||||
$row = $fhem_dbh->selectall_arrayref("SELECT filename from fhemb64filesave group by filename");
|
$row = $fhem_dbh->selectall_arrayref("SELECT filename from fhemb64filesave group by filename");
|
||||||
$count = @$row;
|
$count = @$row;
|
||||||
@ -1002,7 +1014,7 @@ sub _cfgDB_Recover {
|
|||||||
|
|
||||||
if($count > 0) {
|
if($count > 0) {
|
||||||
my $fromuuid = $fhem_dbh->selectrow_array("select versionuuid from fhemversions where version = $version");
|
my $fromuuid = $fhem_dbh->selectrow_array("select versionuuid from fhemversions where version = $version");
|
||||||
my $touuid = _cfgDB_Uuid;
|
my $touuid = createUniqueId();
|
||||||
# Delete current version 0
|
# Delete current version 0
|
||||||
$fhem_dbh->do("DELETE FROM fhemconfig WHERE VERSIONUUID in (select versionuuid from fhemversions where 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");
|
$fhem_dbh->do("update fhemversions set versionuuid = '$touuid' where version = 0");
|
||||||
@ -1019,7 +1031,13 @@ sub _cfgDB_Recover {
|
|||||||
$fhem_dbh->commit();
|
$fhem_dbh->commit();
|
||||||
$fhem_dbh->disconnect();
|
$fhem_dbh->disconnect();
|
||||||
|
|
||||||
# Inform user about restart or rereadcfg needed
|
# Copy corresponding statefile
|
||||||
|
my $filename = $fromuuid.".fhem.save";
|
||||||
|
my ($err,@statefile) = FileRead($filename);
|
||||||
|
$filename = $touuid.".fhem.save";
|
||||||
|
FileWrite($filename,@statefile);
|
||||||
|
|
||||||
|
# Inform user about restart required
|
||||||
$ret = "Version 0 deleted.\n";
|
$ret = "Version 0 deleted.\n";
|
||||||
$ret .= "Version $version copied to version 0\n\n";
|
$ret .= "Version $version copied to version 0\n\n";
|
||||||
$ret .= "Please restart FHEM to activate configuration.";
|
$ret .= "Please restart FHEM to activate configuration.";
|
||||||
@ -1037,18 +1055,18 @@ sub _cfgDB_Recover {
|
|||||||
sub _cfgDB_Reorg {
|
sub _cfgDB_Reorg {
|
||||||
my ($lastversion,$quiet) = @_;
|
my ($lastversion,$quiet) = @_;
|
||||||
$lastversion = (defined($lastversion)) ? $lastversion : 3;
|
$lastversion = (defined($lastversion)) ? $lastversion : 3;
|
||||||
Log3('configDB', 4, "DB Reorg started, keeping last $lastversion versions.");
|
Log 4, "configDB reorg started, keeping last $lastversion versions.";
|
||||||
my $fhem_dbh = _cfgDB_Connect;
|
my $fhem_dbh = _cfgDB_Connect;
|
||||||
my $uuid = $fhem_dbh->selectrow_array("select versionuuid from fhemversions where version = 0");
|
|
||||||
$fhem_dbh->do("delete FROM fhemconfig where versionuuid in (select versionuuid from fhemversions where version > $lastversion)");
|
$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 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 fhemconfig where versionuuid in (select versionuuid from fhemversions where version = -1)");
|
||||||
$fhem_dbh->do("delete from fhemversions where version = -1");
|
$fhem_dbh->do("delete from fhemversions where version = -1");
|
||||||
my $ts = localtime(time);
|
my $ts = localtime(time);
|
||||||
$configDB{attr}{'lastReorg'} = $ts;
|
$configDB{attr}{'lastReorg'} = $ts;
|
||||||
_cfgDB_InsertLine($fhem_dbh,$uuid,"attr configdb lastReorg $ts",-1);
|
_cfgDB_InsertLine($fhem_dbh,$configDB{loaded},"attr configdb lastReorg $ts",-1);
|
||||||
$fhem_dbh->commit();
|
$fhem_dbh->commit();
|
||||||
$fhem_dbh->disconnect();
|
$fhem_dbh->disconnect();
|
||||||
|
_cfgDB_deleteStatefiles();
|
||||||
eval { qx(sqlite3 $cfgDB_filename vacuum) } if($cfgDB_dbtype eq "SQLITE");
|
eval { qx(sqlite3 $cfgDB_filename vacuum) } if($cfgDB_dbtype eq "SQLITE");
|
||||||
return if(defined($quiet));
|
return if(defined($quiet));
|
||||||
return " Result after database reorg:\n"._cfgDB_Info(undef);
|
return " Result after database reorg:\n"._cfgDB_Info(undef);
|
||||||
@ -1056,7 +1074,7 @@ sub _cfgDB_Reorg {
|
|||||||
|
|
||||||
# delete temporary version
|
# delete temporary version
|
||||||
sub _cfgDB_DeleteTemp {
|
sub _cfgDB_DeleteTemp {
|
||||||
Log3('configDB', 4, "configDB: delete temporary Version -1");
|
Log 4, "configDB: delete temporary Version -1";
|
||||||
my $fhem_dbh = _cfgDB_Connect;
|
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 fhemconfig where versionuuid in (select versionuuid from fhemversions where version = -1)");
|
||||||
$fhem_dbh->do("delete from fhemversions where version = -1");
|
$fhem_dbh->do("delete from fhemversions where version = -1");
|
||||||
@ -1083,7 +1101,7 @@ sub _cfgDB_Search {
|
|||||||
}
|
}
|
||||||
$sql .= "ORDER BY lower(device),command DESC";
|
$sql .= "ORDER BY lower(device),command DESC";
|
||||||
$sth = $fhem_dbh->prepare( $sql);
|
$sth = $fhem_dbh->prepare( $sql);
|
||||||
Log 5,"configDB: $sql";
|
Log 4, "configDB: $sql";
|
||||||
$sth->execute();
|
$sth->execute();
|
||||||
$text = $dsearch ? " device" : "";
|
$text = $dsearch ? " device" : "";
|
||||||
push @result, "search result for$text: $search in version: $searchversion";
|
push @result, "search result for$text: $search in version: $searchversion";
|
||||||
@ -1091,7 +1109,7 @@ sub _cfgDB_Search {
|
|||||||
while (@line = $sth->fetchrow_array()) {
|
while (@line = $sth->fetchrow_array()) {
|
||||||
$row = "$line[0] $line[1] $line[2]";
|
$row = "$line[0] $line[1] $line[2]";
|
||||||
$row .= " $line[3]" if defined($line[3]);
|
$row .= " $line[3]" if defined($line[3]);
|
||||||
Log 5,"configDB: $row";
|
Log 4, "configDB: $row";
|
||||||
push @result, "$row" unless ($line[0] eq 'setuuid');
|
push @result, "$row" unless ($line[0] eq 'setuuid');
|
||||||
}
|
}
|
||||||
$fhem_dbh->disconnect();
|
$fhem_dbh->disconnect();
|
||||||
@ -1252,7 +1270,7 @@ sub _cfgDB_deleteRF {
|
|||||||
$sth->execute();
|
$sth->execute();
|
||||||
while ($filename = $sth->fetchrow_array()) {
|
while ($filename = $sth->fetchrow_array()) {
|
||||||
if ($filename =~ m/^[0-9A-F]+$/i) {
|
if ($filename =~ m/^[0-9A-F]+$/i) {
|
||||||
Log 5, "configDB delete file: $filename";
|
Log 4, "configDB delete file: $filename";
|
||||||
$fhem_dbh2->do("delete from fhemb64filesave where filename = '$filename'");
|
$fhem_dbh2->do("delete from fhemb64filesave where filename = '$filename'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1260,6 +1278,24 @@ sub _cfgDB_deleteRF {
|
|||||||
$fhem_dbh2->disconnect();
|
$fhem_dbh2->disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub _cfgDB_deleteStatefiles {
|
||||||
|
my $filename;
|
||||||
|
my $fhem_dbh = _cfgDB_Connect;
|
||||||
|
my $sth = $fhem_dbh->prepare( "SELECT filename FROM fhemb64filesave where filename like '%.fhem.save'" );
|
||||||
|
$sth->execute();
|
||||||
|
while ($filename = $sth->fetchrow_array()) {
|
||||||
|
my $uuid = "";
|
||||||
|
$uuid = substr($filename,0,32);
|
||||||
|
my $found = $fhem_dbh->selectrow_array("SELECT versionuuid FROM fhemversions WHERE versionuuid = '$uuid'");
|
||||||
|
$found //= -1; # to prevent perl warning
|
||||||
|
unless ($uuid eq $found) {
|
||||||
|
$fhem_dbh->do("delete from fhemb64filesave where filename = '$filename'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fhem_dbh->commit();
|
||||||
|
$fhem_dbh->disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
# functions used for file handling
|
# functions used for file handling
|
||||||
# called by 98_configdb.pm
|
# called by 98_configdb.pm
|
||||||
|
@ -1,642 +0,0 @@
|
|||||||
# $Id$
|
|
||||||
#
|
|
||||||
|
|
||||||
package main;
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use feature qw/say switch/;
|
|
||||||
use POSIX;
|
|
||||||
use configDB;
|
|
||||||
|
|
||||||
no if $] >= 5.017011, warnings => 'experimental';
|
|
||||||
|
|
||||||
sub CommandConfigdb;
|
|
||||||
sub _cfgDB_readConfig();
|
|
||||||
|
|
||||||
my @pathname;
|
|
||||||
|
|
||||||
sub configdb_Initialize {
|
|
||||||
my %hash = ( Fn => "CommandConfigdb",
|
|
||||||
Hlp => "help ,access additional functions from configDB" );
|
|
||||||
$cmds{configdb} = \%hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub CommandConfigdb {
|
|
||||||
my ($cl, $param) = @_;
|
|
||||||
|
|
||||||
my @a = split("[ \t][ \t]*", $param);
|
|
||||||
my ($cmd, $param1, $param2) = @a;
|
|
||||||
$cmd //= "";
|
|
||||||
$param1 //= "";
|
|
||||||
$param2 //= "";
|
|
||||||
|
|
||||||
my $configfile = $attr{global}{configfile};
|
|
||||||
return "\n error: configDB not used!" unless($configfile eq 'configDB' || $cmd eq 'migrate');
|
|
||||||
|
|
||||||
my $ret;
|
|
||||||
|
|
||||||
given ($cmd) {
|
|
||||||
|
|
||||||
when ('attr') {
|
|
||||||
Log3('configdb', 4, "configdb: attr $param1 $param2 requested.");
|
|
||||||
if ($param1 eq '' && $param2 eq '') {
|
|
||||||
# list attributes
|
|
||||||
foreach my $c (sort keys %{$configDB{attr}}) {
|
|
||||||
my $val = $configDB{attr}{$c};
|
|
||||||
$val =~ s/;/;;/g;
|
|
||||||
$val =~ s/\n/\\\n/g;
|
|
||||||
$ret .= "configdb attr $c $val\n";
|
|
||||||
}
|
|
||||||
} elsif(lc($param1) eq '?' || lc($param1) eq 'help') {
|
|
||||||
# list all available attributes
|
|
||||||
my $l = 0;
|
|
||||||
foreach my $c (sort keys %{$configDB{knownAttr}}) {
|
|
||||||
$l = length($c) > $l ? length($c) : $l;
|
|
||||||
}
|
|
||||||
foreach my $c (sort keys %{$configDB{knownAttr}}) {
|
|
||||||
my $val = $configDB{knownAttr}{$c};
|
|
||||||
$val =~ s/;/;;/g;
|
|
||||||
$val =~ s/\n/\\\n/g;
|
|
||||||
$ret .= sprintf("%-*s : ",$l,$c)."$val\n";
|
|
||||||
}
|
|
||||||
} elsif($param2 eq '') {
|
|
||||||
# delete attribute
|
|
||||||
delete $configDB{attr}{$param1};
|
|
||||||
$ret = " attribute $param1 deleted";
|
|
||||||
addStructChange('configdb attr',undef,"$param1 (deleted)");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
# set attribute
|
|
||||||
$configDB{attr}{$param1} = $param2;
|
|
||||||
$ret = " attribute $param1 set to value $param2";
|
|
||||||
addStructChange('configdb attr',undef,"$param1 $param2 (set)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('dump') {
|
|
||||||
return _cfgDB_dump($param1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
when ('diff') {
|
|
||||||
return "\n Syntax: configdb diff <device> <version>" if @a != 3;
|
|
||||||
return "Invalid paramaeter '$param2' for diff. Must be a number."
|
|
||||||
unless (looks_like_number($param2) || $param2 eq 'current');
|
|
||||||
Log3('configdb', 4, "configdb: diff requested for device: $param1 in version $param2.");
|
|
||||||
$ret = _cfgDB_Diff($param1, $param2);
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('filedelete') {
|
|
||||||
return "\n Syntax: configdb filedelete <pathToFile>" if @a != 2;
|
|
||||||
my $filename;
|
|
||||||
if($param1 =~ m,^[./],) {
|
|
||||||
$filename = $param1;
|
|
||||||
} else {
|
|
||||||
$filename = $attr{global}{modpath};
|
|
||||||
$filename .= "/$param1";
|
|
||||||
}
|
|
||||||
$ret = "File $filename ";
|
|
||||||
$ret .= defined(_cfgDB_Filedelete($filename)) ? "deleted from" : "not found in";
|
|
||||||
$ret .= " database.";
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('fileexport') {
|
|
||||||
return "\n Syntax: configdb fileexport <pathToFile>" if @a != 2;
|
|
||||||
if ($param1 ne 'all') {
|
|
||||||
my $filename;
|
|
||||||
if($param1 =~ m,^[./],) {
|
|
||||||
$filename = $param1;
|
|
||||||
} else {
|
|
||||||
$filename = $attr{global}{modpath};
|
|
||||||
$filename .= "/$param1";
|
|
||||||
}
|
|
||||||
$ret = _cfgDB_Fileexport $filename;
|
|
||||||
} else { # start export all
|
|
||||||
my $flist = _cfgDB_Filelist(1);
|
|
||||||
my @filelist = split(/\n/,$flist);
|
|
||||||
undef $flist;
|
|
||||||
foreach my $f (@filelist) {
|
|
||||||
Log3 (4,undef,"configDB: exporting $f");
|
|
||||||
my ($path,$file) = $f =~ m|^(.*[/\\])([^/\\]+?)$|;
|
|
||||||
$path = "/tmp/$path";
|
|
||||||
eval { qx(mkdir -p $path) } unless (-e "$path");
|
|
||||||
$ret .= _cfgDB_Fileexport $f;
|
|
||||||
$ret .= "\n";
|
|
||||||
}
|
|
||||||
} # end export all
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('fileimport') {
|
|
||||||
return "\n Syntax: configdb fileimport <pathToFile>" if @a != 2;
|
|
||||||
my $filename;
|
|
||||||
if($param1 =~ m,^[./],) {
|
|
||||||
$filename = $param1;
|
|
||||||
} else {
|
|
||||||
$filename = $attr{global}{modpath};
|
|
||||||
$filename .= "/$param1";
|
|
||||||
}
|
|
||||||
if ( -r $filename ) {
|
|
||||||
my $filesize = -s $filename;
|
|
||||||
$ret = _cfgDB_binFileimport($filename,$filesize);
|
|
||||||
} elsif ( -e $filename) {
|
|
||||||
$ret = "\n Read error on file $filename";
|
|
||||||
} else {
|
|
||||||
$ret = "\n File $filename not found.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('filelist') {
|
|
||||||
return _cfgDB_Filelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('filemove') {
|
|
||||||
return "\n Syntax: configdb filemove <pathToFile>" if @a != 2;
|
|
||||||
my $filename;
|
|
||||||
if($param1 =~ m,^[./],) {
|
|
||||||
$filename = $param1;
|
|
||||||
} else {
|
|
||||||
$filename = $attr{global}{modpath};
|
|
||||||
$filename .= "/$param1";
|
|
||||||
}
|
|
||||||
if ( -r $filename ) {
|
|
||||||
my $filesize = -s $filename;
|
|
||||||
$ret = _cfgDB_binFileimport ($filename,$filesize,1);
|
|
||||||
$ret .= "\nFile $filename deleted from local filesystem.";
|
|
||||||
} elsif ( -e $filename) {
|
|
||||||
$ret = "\n Read error on file $filename";
|
|
||||||
} else {
|
|
||||||
$ret = "\n File $filename not found.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('fileshow') {
|
|
||||||
return "\n Syntax: configdb fileshow <pathToFile>" if @a != 2;
|
|
||||||
my @rets = cfgDB_FileRead($param1);
|
|
||||||
my $r = (int(@rets)) ? join "\n",@rets : "File $param1 not found in database.";
|
|
||||||
return $r;
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('info') {
|
|
||||||
Log3('configdb', 4, "info requested.");
|
|
||||||
$ret = _cfgDB_Info('$Id$');
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('list') {
|
|
||||||
$param1 = $param1 ? $param1 : '%';
|
|
||||||
$param2 = looks_like_number($param2) ? $param2 : 0;
|
|
||||||
$ret = "list not allowed for configDB itself.";
|
|
||||||
break if($param1 =~ m/configdb/i);
|
|
||||||
Log3('configdb', 4, "configdb: list requested for device: $param1 in version $param2.");
|
|
||||||
$ret = _cfgDB_Search($param1,$param2,1);
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('migrate') {
|
|
||||||
return "\n Migration not possible. Already running with configDB!" if $configfile eq 'configDB';
|
|
||||||
Log3('configdb', 4, "configdb: migration requested.");
|
|
||||||
$ret = _cfgDB_Migrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('rawList'){
|
|
||||||
$data{cfgDB_rawList} = 1;
|
|
||||||
my @out = cfgDB_SaveCfg();
|
|
||||||
delete $data{cfgDB_rawList};
|
|
||||||
return join("\n",@out);
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('recover') {
|
|
||||||
return "\n Syntax: configdb recover <version>" if @a != 2;
|
|
||||||
return "Invalid paramaeter '$param1' for recover. Must be a number."
|
|
||||||
unless looks_like_number($param1);
|
|
||||||
Log3('configdb', 4, "configdb: recover for version $param1 requested.");
|
|
||||||
$ret = _cfgDB_Recover($param1);
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('reorg') {
|
|
||||||
$param1 = 3 unless $param1;
|
|
||||||
return "Invalid paramaeter '$param1' for reorg. Must be a number."
|
|
||||||
unless looks_like_number($param1);
|
|
||||||
Log3('configdb', 4, "configdb: reorg requested with keep: $param1.");
|
|
||||||
$ret = _cfgDB_Reorg($param1);
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('search') {
|
|
||||||
return "\n Syntax: configdb search <searchTerm> [searchVersion]" if @a < 2;
|
|
||||||
$param1 = $param1 ? $param1 : '%';
|
|
||||||
$param2 = looks_like_number($param2) ? $param2 : 0;
|
|
||||||
Log3('configdb', 4, "configdb: list requested for device: $param1 in version $param2.");
|
|
||||||
$ret = _cfgDB_Search($param1,$param2,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
when ('uuid') {
|
|
||||||
$param1 = createUniqueId();
|
|
||||||
Log3('configdb', 4, "configdb: uuid requested: $param1");
|
|
||||||
$ret = $param1;
|
|
||||||
}
|
|
||||||
|
|
||||||
default {
|
|
||||||
$ret = "\n Syntax:\n".
|
|
||||||
" configdb attr [attribute] [value]\n".
|
|
||||||
" configdb diff <device> <version>\n".
|
|
||||||
" configdb dump\n".
|
|
||||||
" configDB filedelete <pathToFilename>\n".
|
|
||||||
" configDB fileimport <pathToFilename>\n".
|
|
||||||
" configDB fileexport <pathToFilename>\n".
|
|
||||||
" configDB filelist\n".
|
|
||||||
" configDB filemove <pathToFilename>\n".
|
|
||||||
" configDB fileshow <pathToFilename>\n".
|
|
||||||
" configdb info\n".
|
|
||||||
" configdb list [device] [version]\n".
|
|
||||||
" configdb migrate\n".
|
|
||||||
" configdb rawList\n".
|
|
||||||
" configdb recover <version>\n".
|
|
||||||
" configdb reorg [keepVersions]\n".
|
|
||||||
" configdb search <searchTerm> [version]\n".
|
|
||||||
" configdb uuid\n".
|
|
||||||
"";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ret;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
sub _cfgDB_readConfig() {
|
|
||||||
my ($conf,@config);
|
|
||||||
if(!open($conf, '<', 'configDB.conf')) {
|
|
||||||
Log3('configDB', 1, 'Cannot open database configuration file configDB.conf');
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@config=<$conf>;
|
|
||||||
close($conf);
|
|
||||||
|
|
||||||
use vars qw(%configDB);
|
|
||||||
|
|
||||||
my %dbconfig;
|
|
||||||
## no critic
|
|
||||||
eval join("", @config) ;
|
|
||||||
## use critic
|
|
||||||
|
|
||||||
my $cfgDB_dbconn = $dbconfig{connection};
|
|
||||||
my $cfgDB_dbuser = $dbconfig{user};
|
|
||||||
my $cfgDB_dbpass = $dbconfig{password};
|
|
||||||
my $cfgDB_dbtype;
|
|
||||||
|
|
||||||
%dbconfig = ();
|
|
||||||
@config = ();
|
|
||||||
|
|
||||||
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";
|
|
||||||
} else {
|
|
||||||
$cfgDB_dbtype = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
return($cfgDB_dbconn,$cfgDB_dbuser,$cfgDB_dbpass,$cfgDB_dbtype);
|
|
||||||
}
|
|
||||||
|
|
||||||
1;
|
|
||||||
|
|
||||||
=pod
|
|
||||||
=item command
|
|
||||||
=item summary frontend command for configDB configuration
|
|
||||||
=item summary_DE Befehl zur Konfiguration der configDB
|
|
||||||
=begin html
|
|
||||||
|
|
||||||
<a name="configdb"></a>
|
|
||||||
<h3>configdb</h3>
|
|
||||||
<ul>
|
|
||||||
<a href="https://forum.fhem.de/index.php?board=46.0">Link to FHEM forum</a><br/><br/>
|
|
||||||
Starting with version 5079, fhem can be used with a configuration database instead of a plain text file (e.g. fhem.cfg).<br/>
|
|
||||||
This offers the possibility to completely waive all cfg-files, "include"-problems and so on.<br/>
|
|
||||||
Furthermore, configDB offers a versioning of several configuration together with the possibility to restore a former configuration.<br/>
|
|
||||||
Access to database is provided via perl's database interface DBI.<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<b>Interaction with other modules</b><br/>
|
|
||||||
<ul><br/>
|
|
||||||
Currently the fhem modules<br/>
|
|
||||||
<br/>
|
|
||||||
<li>02_RSS.pm</li>
|
|
||||||
<li>55_InfoPanel.pm</li>
|
|
||||||
<li>91_eventTypes</li>
|
|
||||||
<li>93_DbLog.pm</li>
|
|
||||||
<li>95_holiday.pm</li>
|
|
||||||
<li>98_SVG.pm</li>
|
|
||||||
<li>98_weekprofile.pm</li>
|
|
||||||
<br/>
|
|
||||||
will use configDB to read their configuration data from database<br/>
|
|
||||||
instead of formerly used configuration files inside the filesystem.<br/>
|
|
||||||
<br/>
|
|
||||||
This requires you to import your configuration files from filesystem into database.<br/>
|
|
||||||
<br/>
|
|
||||||
Examples:<br/>
|
|
||||||
<code>configdb fileimport FHEM/nrw.holiday</code><br/>
|
|
||||||
<code>configdb fileimport FHEM/myrss.layout</code><br/>
|
|
||||||
<code>configdb fileimport www/gplot/xyz.gplot</code><br/>
|
|
||||||
<br/>
|
|
||||||
<b>This does not affect the definitons of your holiday or RSS entities.</b><br/>
|
|
||||||
<br/>
|
|
||||||
<b>During migration all external configfiles used in current configuration<br/>
|
|
||||||
will be imported aufmatically.</b><br>
|
|
||||||
<br/>
|
|
||||||
Each fileimport into database will overwrite the file if it already exists in database.<br/>
|
|
||||||
<br/>
|
|
||||||
</ul><br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<b>Prerequisits / Installation</b><br/>
|
|
||||||
<ul><br/>
|
|
||||||
<li>Please install perl package Text::Diff if not already installed on your system.</li><br/>
|
|
||||||
<li>You must have access to a SQL database. Supported database types are SQLITE, MYSQL and POSTGRESQL.</li><br/>
|
|
||||||
<li>The corresponding DBD module must be available in your perl environment,<br/>
|
|
||||||
e.g. sqlite3 running on a Debian systems requires package libdbd-sqlite3-perl</li><br/>
|
|
||||||
<li>Create an empty database, e.g. with sqlite3:<br/>
|
|
||||||
<pre>
|
|
||||||
mba:fhem udo$ sqlite3 configDB.db
|
|
||||||
|
|
||||||
SQLite version 3.7.13 2012-07-17 17:46:21
|
|
||||||
Enter ".help" for instructions
|
|
||||||
Enter SQL statements terminated with a ";"
|
|
||||||
sqlite> pragma auto_vacuum=2;
|
|
||||||
sqlite> .quit
|
|
||||||
|
|
||||||
mba:fhem udo$
|
|
||||||
</pre></li>
|
|
||||||
<li>The database tables will be created automatically.</li><br/>
|
|
||||||
<li>Create a configuration file containing the connection string to access database.<br/>
|
|
||||||
<br/>
|
|
||||||
<b>IMPORTANT:</b>
|
|
||||||
<ul><br/>
|
|
||||||
<li>This file <b>must</b> be named "configDB.conf"</li>
|
|
||||||
<li>This file <b>must</b> be located in the same directory containing fhem.pl and configDB.pm, e.g. /opt/fhem</li>
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
<pre>
|
|
||||||
## for MySQL
|
|
||||||
################################################################
|
|
||||||
#%dbconfig= (
|
|
||||||
# connection => "mysql:database=configDB;host=db;port=3306",
|
|
||||||
# user => "fhemuser",
|
|
||||||
# password => "fhempassword",
|
|
||||||
#);
|
|
||||||
################################################################
|
|
||||||
#
|
|
||||||
## for PostgreSQL
|
|
||||||
################################################################
|
|
||||||
#%dbconfig= (
|
|
||||||
# connection => "Pg:database=configDB;host=localhost",
|
|
||||||
# user => "fhemuser",
|
|
||||||
# password => "fhempassword"
|
|
||||||
#);
|
|
||||||
################################################################
|
|
||||||
#
|
|
||||||
## for SQLite (username and password stay empty for SQLite)
|
|
||||||
################################################################
|
|
||||||
#%dbconfig= (
|
|
||||||
# connection => "SQLite:dbname=/opt/fhem/configDB.db",
|
|
||||||
# user => "",
|
|
||||||
# password => ""
|
|
||||||
#);
|
|
||||||
################################################################
|
|
||||||
</pre></li><br/>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<b>Start with a complete new "fresh" fhem Installation</b><br/>
|
|
||||||
<ul><br/>
|
|
||||||
It's easy... simply start fhem by issuing following command:<br/><br/>
|
|
||||||
<ul><code>perl fhem.pl configDB</code></ul><br/>
|
|
||||||
|
|
||||||
<b>configDB</b> is a keyword which is recognized by fhem to use database for configuration.<br/>
|
|
||||||
<br/>
|
|
||||||
<b>That's all.</b> Everything (save, rereadcfg etc) should work as usual.
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<b>or:</b><br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<b>Migrate your existing fhem configuration into the database</b><br/>
|
|
||||||
<ul><br/>
|
|
||||||
It's easy, too... <br/>
|
|
||||||
<br/>
|
|
||||||
<li>start your fhem the last time with fhem.cfg<br/><br/>
|
|
||||||
<ul><code>perl fhem.pl fhem.cfg</code></ul></li><br/>
|
|
||||||
<br/>
|
|
||||||
<li>transfer your existing configuration into the database<br/><br/>
|
|
||||||
<ul>enter<br/><br/><code>configdb migrate</code><br/>
|
|
||||||
<br/>
|
|
||||||
into frontend's command line</ul><br/></br>
|
|
||||||
Be patient! Migration can take some time, especially on mini-systems like RaspberryPi or Beaglebone.<br/>
|
|
||||||
Completed migration will be indicated by showing database statistics.<br/>
|
|
||||||
Your original configfile will not be touched or modified by this step.</li><br/>
|
|
||||||
<li>shutdown fhem</li><br/>
|
|
||||||
<li>restart fhem with keyword configDB<br/><br/>
|
|
||||||
<ul><code>perl fhem.pl configDB</code></ul></li><br/>
|
|
||||||
<b>configDB</b> is a keyword which is recognized by fhem to use database for configuration.<br/>
|
|
||||||
<br/>
|
|
||||||
<b>That's all.</b> Everything (save, rereadcfg etc) should work as usual.
|
|
||||||
</ul>
|
|
||||||
<br/><br/>
|
|
||||||
|
|
||||||
<b>Additional functions provided</b><br/>
|
|
||||||
<ul><br/>
|
|
||||||
A new command <code>configdb</code> is propagated to fhem.<br/>
|
|
||||||
This command can be used with different parameters.<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb attr [attribute] [value]</code></li><br/>
|
|
||||||
Provides the possibility to pass attributes to backend and frontend.<br/>
|
|
||||||
<br/>
|
|
||||||
<code> configdb attr private 1</code> - set the attribute named 'private' to value 1.<br/>
|
|
||||||
<br/>
|
|
||||||
<code> configdb attr private</code> - delete the attribute named 'private'<br/>
|
|
||||||
<br/>
|
|
||||||
<code> configdb attr</code> - show all defined attributes.<br/>
|
|
||||||
<br/>
|
|
||||||
<code> configdb attr ?|help</code> - show a list of available attributes.<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb diff <device> <version></code></li><br/>
|
|
||||||
Compare configuration dataset for device <device>
|
|
||||||
from current version 0 with version <version><br/>
|
|
||||||
Example for valid request:<br/>
|
|
||||||
<br/>
|
|
||||||
<code>configdb diff telnetPort 1</code><br/>
|
|
||||||
<br/>
|
|
||||||
will show a result like this:
|
|
||||||
<pre>
|
|
||||||
compare device: telnetPort in current version 0 (left) to version: 1 (right)
|
|
||||||
+--+--------------------------------------+--+--------------------------------------+
|
|
||||||
| 1|define telnetPort telnet 7072 global | 1|define telnetPort telnet 7072 global |
|
|
||||||
* 2|attr telnetPort room telnet * | |
|
|
||||||
+--+--------------------------------------+--+--------------------------------------+</pre>
|
|
||||||
<b>Special: configdb diff all current</b><br/>
|
|
||||||
<br/>
|
|
||||||
Will show a diff table containing all changes between saved version 0<br/>
|
|
||||||
and UNSAVED version from memory (currently running installation).<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb dump [unzipped]</code></li><br/>
|
|
||||||
Create a gzipped dump file from from database.<br/>
|
|
||||||
If optional parameter 'unzipped' provided, dump file will be written unzipped.<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb filedelete <Filename></code></li><br/>
|
|
||||||
Delete file from database.<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb fileexport <targetFilename>|all</code></li><br/>
|
|
||||||
Exports specified file (or all files) from database into filesystem.<br/>
|
|
||||||
Example:<br/>
|
|
||||||
<br/>
|
|
||||||
<code>configdb fileexport FHEM/99_myUtils.pm</code><br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb fileimport <sourceFilename></code></li><br/>
|
|
||||||
Imports specified text file from from filesystem into database.<br/>
|
|
||||||
Example:<br/>
|
|
||||||
<br/>
|
|
||||||
<code>configdb fileimport FHEM/99_myUtils.pm</code><br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb filelist</code></li><br/>
|
|
||||||
Show a list with all filenames stored in database.<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb filemove <sourceFilename></code></li><br/>
|
|
||||||
Imports specified fhem file from from filesystem into database and<br/>
|
|
||||||
deletes the file from local filesystem afterwards.<br/>
|
|
||||||
Example:<br/>
|
|
||||||
<br/>
|
|
||||||
<code>configdb filemove FHEM/99_myUtils.pm</code><br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb fileshow <Filename></code></li><br/>
|
|
||||||
Show content of specified file stored in database.<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb info</code></li><br/>
|
|
||||||
Returns some database statistics<br/>
|
|
||||||
<pre>
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
configDB Database Information
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
dbconn: SQLite:dbname=/opt/fhem/configDB.db
|
|
||||||
dbuser:
|
|
||||||
dbpass:
|
|
||||||
dbtype: SQLITE
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
fhemconfig: 7707 entries
|
|
||||||
|
|
||||||
Ver 0 saved: Sat Mar 1 11:37:00 2014 def: 293 attr: 1248
|
|
||||||
Ver 1 saved: Fri Feb 28 23:55:13 2014 def: 293 attr: 1248
|
|
||||||
Ver 2 saved: Fri Feb 28 23:49:01 2014 def: 293 attr: 1248
|
|
||||||
Ver 3 saved: Fri Feb 28 22:24:40 2014 def: 293 attr: 1247
|
|
||||||
Ver 4 saved: Fri Feb 28 22:14:03 2014 def: 293 attr: 1246
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
fhemstate: 1890 entries saved: Sat Mar 1 12:05:00 2014
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
</pre>
|
|
||||||
Ver 0 always indicates the currently running configuration.<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb list [device] [version]</code></li><br/>
|
|
||||||
Search for device named [device] in configuration version [version]<br/>
|
|
||||||
in database archive.<br/>
|
|
||||||
Default value for [device] = % to show all devices.<br/>
|
|
||||||
Default value for [version] = 0 to show devices from current version.<br/>
|
|
||||||
Examples for valid requests:<br/>
|
|
||||||
<br/>
|
|
||||||
<code>get configDB list</code><br/>
|
|
||||||
<code>get configDB list global</code><br/>
|
|
||||||
<code>get configDB list '' 1</code><br/>
|
|
||||||
<code>get configDB list global 1</code><br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb rawList</code></li><br/>
|
|
||||||
Lists the running configuration in the same order that <br/>
|
|
||||||
would be written to a configuration file.<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb recover <version></code></li><br/>
|
|
||||||
Restores an older version from database archive.<br/>
|
|
||||||
<code>configdb recover 3</code> will <b>copy</b> version #3 from database
|
|
||||||
to version #0.<br/>
|
|
||||||
Original version #0 will be lost.<br/><br/>
|
|
||||||
<b>Important!</b><br/>
|
|
||||||
The restored version will <b>NOT</b> be activated automatically!<br/>
|
|
||||||
You must do a <code>rereadcfg</code> or - even better - <code>shutdown restart</code> yourself.<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb reorg [keep]</code></li><br/>
|
|
||||||
Deletes all stored versions with version number higher than [keep].<br/>
|
|
||||||
Default value for optional parameter keep = 3.<br/>
|
|
||||||
This function can be used to create a nightly running job for<br/>
|
|
||||||
database reorganisation when called from an at-Definition.<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb search <searchTerm> [searchVersion]</code></li><br/>
|
|
||||||
Search for specified searchTerm in any given version (default=0)<br/>
|
|
||||||
<pre>
|
|
||||||
Example:
|
|
||||||
|
|
||||||
configdb search %2286BC%
|
|
||||||
|
|
||||||
Result:
|
|
||||||
|
|
||||||
search result for: %2286BC% in version: 0
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
define az_RT CUL_HM 2286BC
|
|
||||||
define az_RT_Clima CUL_HM 2286BC04
|
|
||||||
define az_RT_Climate CUL_HM 2286BC02
|
|
||||||
define az_RT_ClimaTeam CUL_HM 2286BC05
|
|
||||||
define az_RT_remote CUL_HM 2286BC06
|
|
||||||
define az_RT_Weather CUL_HM 2286BC01
|
|
||||||
define az_RT_WindowRec CUL_HM 2286BC03
|
|
||||||
attr Melder_FAl peerIDs 00000000,2286BC03,
|
|
||||||
attr Melder_FAr peerIDs 00000000,2286BC03,
|
|
||||||
</pre>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<li><code>configdb uuid</code></li><br/>
|
|
||||||
Returns a uuid that can be used for own purposes.<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<b>Author's notes</b><br/>
|
|
||||||
<br/>
|
|
||||||
<ul>
|
|
||||||
<li>You can find two template files for datebase and configfile (sqlite only!) for easy installation.<br/>
|
|
||||||
Just copy them to your fhem installation directory (/opt/fhem) and have fun.</li>
|
|
||||||
<br/>
|
|
||||||
<li>The frontend option "Edit files"->"config file" will be removed when running configDB.</li>
|
|
||||||
<br/>
|
|
||||||
<li>Please be patient when issuing a "save" command
|
|
||||||
(either manually or by clicking on "save config").<br/>
|
|
||||||
This will take some moments, due to writing version informations.<br/>
|
|
||||||
Finishing the save-process will be indicated by a corresponding message in frontend.</li>
|
|
||||||
<br/>
|
|
||||||
<li>There still will be some more (planned) development to this extension,
|
|
||||||
especially regarding some perfomance issues.</li>
|
|
||||||
<br/>
|
|
||||||
<li>Have fun!</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
=end html
|
|
||||||
|
|
||||||
=cut
|
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user