# $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 " 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 " 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 " 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 " 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 " 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 " 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 ('recover') { return "\n Syntax: configdb recover " 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 [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 = _cfgDB_Uuid; Log3('configdb', 4, "configdb: uuid requested: $param1"); $ret = $param1; } default { $ret = "\n Syntax:\n". " configdb attr [attribute] [value]\n". " configdb diff \n". " configdb dump\n". " configDB filedelete \n". " configDB fileimport \n". " configDB fileexport \n". " configDB filelist\n". " configDB filemove \n". " configDB fileshow \n". " configdb info\n". " configdb list [device] [version]\n". " configdb migrate\n". " configdb recover \n". " configdb reorg [keepVersions]\n". " configdb search [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

configdb

    Link to FHEM forum

    Starting with version 5079, fhem can be used with a configuration database instead of a plain text file (e.g. fhem.cfg).
    This offers the possibility to completely waive all cfg-files, "include"-problems and so on.
    Furthermore, configDB offers a versioning of several configuration together with the possibility to restore a former configuration.
    Access to database is provided via perl's database interface DBI.

    Interaction with other modules

      Currently the fhem modules

    • 02_RSS.pm
    • 55_InfoPanel.pm
    • 91_eventTypes
    • 93_DbLog.pm
    • 95_holiday.pm
    • 98_SVG.pm
    • 98_weekprofile.pm

    • will use configDB to read their configuration data from database
      instead of formerly used configuration files inside the filesystem.

      This requires you to import your configuration files from filesystem into database.

      Examples:
      configdb fileimport FHEM/nrw.holiday
      configdb fileimport FHEM/myrss.layout
      configdb fileimport www/gplot/xyz.gplot

      This does not affect the definitons of your holiday or RSS entities.

      During migration all external configfiles used in current configuration
      will be imported aufmatically.


      Each fileimport into database will overwrite the file if it already exists in database.



    Prerequisits / Installation

    • Please install perl package Text::Diff if not already installed on your system.

    • You must have access to a SQL database. Supported database types are SQLITE, MYSQL and POSTGRESQL.

    • The corresponding DBD module must be available in your perl environment,
      e.g. sqlite3 running on a Debian systems requires package libdbd-sqlite3-perl

    • Create an empty database, e.g. with sqlite3:
      	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$ 
      			
    • The database tables will be created automatically.

    • Create a configuration file containing the connection string to access database.

      IMPORTANT:

      • This file must be named "configDB.conf"
      • This file must be located in the same directory containing fhem.pl and configDB.pm, e.g. /opt/fhem

      ## 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 => ""
      #);
      ################################################################
      			

    Start with a complete new "fresh" fhem Installation

      It's easy... simply start fhem by issuing following command:

        perl fhem.pl configDB

      configDB is a keyword which is recognized by fhem to use database for configuration.

      That's all. Everything (save, rereadcfg etc) should work as usual.

    or:

    Migrate your existing fhem configuration into the database

      It's easy, too...

    • start your fhem the last time with fhem.cfg

        perl fhem.pl fhem.cfg


    • transfer your existing configuration into the database

        enter

        configdb migrate

        into frontend's command line


      Be patient! Migration can take some time, especially on mini-systems like RaspberryPi or Beaglebone.
      Completed migration will be indicated by showing database statistics.
      Your original configfile will not be touched or modified by this step.

    • shutdown fhem

    • restart fhem with keyword configDB

        perl fhem.pl configDB

    • configDB is a keyword which is recognized by fhem to use database for configuration.

      That's all. Everything (save, rereadcfg etc) should work as usual.


    Additional functions provided

      A new command configdb is propagated to fhem.
      This command can be used with different parameters.

    • configdb attr [attribute] [value]

    • Provides the possibility to pass attributes to backend and frontend.

      configdb attr private 1 - set the attribute named 'private' to value 1.

      configdb attr private - delete the attribute named 'private'

      configdb attr - show all defined attributes.

      configdb attr ?|help - show a list of available attributes.

    • configdb diff <device> <version>

    • Compare configuration dataset for device <device> from current version 0 with version <version>
      Example for valid request:

      configdb diff telnetPort 1

      will show a result like this:
      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           *  |                                      |
      +--+--------------------------------------+--+--------------------------------------+
      Special: configdb diff all current

      Will show a diff table containing all changes between saved version 0
      and UNSAVED version from memory (currently running installation).

    • configdb dump [unzipped]

    • Create a gzipped dump file from from database.
      If optional parameter 'unzipped' provided, dump file will be written unzipped.


    • configdb filedelete <Filename>

    • Delete file from database.


    • configdb fileexport <targetFilename>|all

    • Exports specified file (or all files) from database into filesystem.
      Example:

      configdb fileexport FHEM/99_myUtils.pm


    • configdb fileimport <sourceFilename>

    • Imports specified text file from from filesystem into database.
      Example:

      configdb fileimport FHEM/99_myUtils.pm


    • configdb filelist

    • Show a list with all filenames stored in database.


    • configdb filemove <sourceFilename>

    • Imports specified fhem file from from filesystem into database and
      deletes the file from local filesystem afterwards.
      Example:

      configdb filemove FHEM/99_myUtils.pm


    • configdb fileshow <Filename>

    • Show content of specified file stored in database.


    • configdb info

    • Returns some database statistics
      --------------------------------------------------------------------------------
       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
      --------------------------------------------------------------------------------
      
      Ver 0 always indicates the currently running configuration.

    • configdb list [device] [version]

    • Search for device named [device] in configuration version [version]
      in database archive.
      Default value for [device] = % to show all devices.
      Default value for [version] = 0 to show devices from current version.
      Examples for valid requests:

      get configDB list
      get configDB list global
      get configDB list '' 1
      get configDB list global 1

    • configdb recover <version>

    • Restores an older version from database archive.
      configdb recover 3 will copy version #3 from database to version #0.
      Original version #0 will be lost.

      Important!
      The restored version will NOT be activated automatically!
      You must do a rereadcfg or - even better - shutdown restart yourself.

    • configdb reorg [keep]

    • Deletes all stored versions with version number higher than [keep].
      Default value for optional parameter keep = 3.
      This function can be used to create a nightly running job for
      database reorganisation when called from an at-Definition.

    • configdb search [searchVersion]

    • Search for specified searchTerm in any given version (default=0)
      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, 
      

    • configdb uuid

    • Returns a uuid that can be used for own purposes.



    Author's notes

    • You can find two template files for datebase and configfile (sqlite only!) for easy installation.
      Just copy them to your fhem installation directory (/opt/fhem) and have fun.

    • The frontend option "Edit files"->"config file" will be removed when running configDB.

    • Please be patient when issuing a "save" command (either manually or by clicking on "save config").
      This will take some moments, due to writing version informations.
      Finishing the save-process will be indicated by a corresponding message in frontend.

    • There still will be some more (planned) development to this extension, especially regarding some perfomance issues.

    • Have fun!
=end html =cut