diff --git a/fhem/CHANGED b/fhem/CHANGED index 756419d84..c9a7238f1 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,10 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 93_DbRep: V7.4.0, new commands dumpSQLite/restoreSQLite, + execute commands before/after set optimizeTables, + vacuum, restoreMySQL, restoreSQLite available, + CAUTION !! attributes executeBeforeDump/executeAfterDump + are renamed to executeBeforeProc/executeAfterProc - feature: 74_Unifi: add new set commands to block/unblock clients, enable/disable WLAN, new client-Reading essid - feature: structure: implement dynamic members via devspec (Forum #82604) diff --git a/fhem/FHEM/93_DbRep.pm b/fhem/FHEM/93_DbRep.pm index d0e4835a6..c0973af5f 100644 --- a/fhem/FHEM/93_DbRep.pm +++ b/fhem/FHEM/93_DbRep.pm @@ -37,6 +37,11 @@ ########################################################################################################################### # Versions History: # +# 7.4.0 09.01.2018 dumpSQLite/restoreSQLite, +# backup/restore now available when DbLog-device has reopen xxxx running, +# executeBeforeDump executeAfterDump also available for optimizeTables, vacuum, restoreMySQL, +# restoreSQLite, +# attribute executeBeforeDump / executeAfterDump renamed to executeBeforeProc & executeAfterProc # 7.3.1 08.01.2018 fix syntax error for perl < 5.20 # 7.3.0 07.01.2018 DbRep-charfilter avoid control characters in datasets to export, impfile_Push errortext # improved, @@ -63,7 +68,7 @@ # fix currentfillup_Push PostgreSQL -> use $runtime_string_next as Timestring during current # insert # 6.3.0 04.12.2017 support addition format d:xx h:xx m:xx s:xx for attributes timeDiffToNow, timeOlderThan -# 6.2.3 04.12.2017 fix localtime(time); (current time deduction) in createTimeArray +# 6.2.3 04.12.2017 fix localtime(time); (current time deduction) in DbRep_createTimeArray # 6.2.2 01.12.2017 support all aggregations for delSeqDoublets, better output filesize when mysql dump finished # 6.2.1 30.11.2017 support delSeqDoublets without device,reading is set and support device-devspec, reading list, # minor fixes in delSeqDoublets @@ -75,7 +80,7 @@ # 5.8.5 19.10.2017 filter unwanted characters in "procinfo"-result # 5.8.4 17.10.2017 createSelectSql, createDeleteSql, currentfillup_Push switch to devspec # 5.8.3 16.10.2017 change to use createSelectSql: minValue,diffValue - createDeleteSql: delEntries -# 5.8.2 15.10.2017 sub createTimeArray +# 5.8.2 15.10.2017 sub DbRep_createTimeArray # 5.8.1 15.10.2017 change to use createSelectSql: sumValue,averageValue,exportToFile,maxValue # 5.8.0 15.10.2017 adapt createSelectSql for better performance if time/aggregation not set, # can set table as flexible argument for countEntries, fetchrows (default: history), @@ -145,7 +150,7 @@ # 4.11.0 18.02.2017 added [current|previous]_[month|week|day|hour]_begin and # [current|previous]_[month|week|day|hour]_end as options of timestamp # 4.10.3 01.02.2017 rename reading "diff-overrun_limit-" to "diff_overrun_limit_", -# collaggstr day aggregation changed back from 4.7.5 change +# DbRep_collaggstr day aggregation changed back from 4.7.5 change # 4.10.2 16.01.2017 bugfix uninitialized value $renmode if RenameAgent # 4.10.1 30.11.2016 bugfix importFromFile format problem if UNIT-field wasn't set # 4.10 28.12.2016 del_DoParse changed to use Wildcards, del_ParseDone changed to use readingNameMap @@ -163,7 +168,7 @@ # 4.8 09.12.2016 diffValue selection chenged to "between" # 4.7.7 08.12.2016 code review # 4.7.6 07.12.2016 DbRep version as internal, check if perl module DBI is installed -# 4.7.5 05.12.2016 collaggstr day aggregation changed +# 4.7.5 05.12.2016 DbRep_collaggstr day aggregation changed # 4.7.4 28.11.2016 sub calcount changed due to Forum #msg529312 # 4.7.3 20.11.2016 new diffValue function made suitable to SQLite # 4.7.2 20.11.2016 commandref adapted, state = Warnings adapted @@ -259,7 +264,7 @@ # 2.1 25.05.2016 codechange # 2.0 24.05.2016 added nonblocking function for fetchrow # 1.2 21.05.2016 function and attribute for delEntries added -# 1.1 20.05.2016 change result-format of "count", move runtime-counter to sub collaggstr +# 1.1 20.05.2016 change result-format of "count", move runtime-counter to sub DbRep_collaggstr # 1.0 19.05.2016 Initial # @@ -279,7 +284,7 @@ use Encode qw(encode_utf8); sub DbRep_Main($$;$); -my $DbRepVersion = "7.3.1"; +my $DbRepVersion = "7.4.0"; my %dbrep_col = ("DEVICE" => 64, "TYPE" => 64, @@ -312,8 +317,8 @@ sub DbRep_Initialize($) { "dumpMemlimit ". "dumpSpeed ". "dumpFilesKeep:1,2,3,4,5,6,7,8,9,10 ". - "executeBeforeDump ". - "executeAfterDump ". + "executeBeforeProc ". + "executeAfterProc ". "expimpfile ". "fetchRoute:ascent,descent ". "ftpUse:1,0 ". @@ -405,13 +410,22 @@ sub DbRep_Set($@) { $hash->{dbloghash} = $defs{$dblogdevice}; my $dbmodel = $hash->{dbloghash}{MODEL}; my $dbname = $hash->{DATABASE}; + my $sd =""; my (@bkps,$dir); $dir = AttrVal($name, "dumpDirLocal", "./"); # 'dumpDirRemote' (Backup-Verz. auf dem MySQL-Server) muß gemountet sein und in 'dumpDirLocal' eingetragen sein $dir = $dir."/" unless($dir =~ m/\/$/); opendir(DIR,$dir); - my $sd = $dbname."_history_.*.csv"; + if ($dbmodel =~ /MYSQL/) { + $dbname = $hash->{DATABASE}; + $sd = $dbname."_history_.*.csv"; + } elsif ($dbmodel =~ /SQLITE/) { + $dbname = $hash->{DATABASE}; + $dbname = (split /[\/]/, $dbname)[-1]; + $dbname = (split /\./, $dbname)[0]; + $sd = $dbname."_.*.sqlitebkp"; + } while (my $file = readdir(DIR)) { next unless (-f "$dir/$file"); next unless ($file =~ /^$sd/); @@ -438,22 +452,17 @@ sub DbRep_Set($@) { (($hash->{ROLE} ne "Agent")?"tableCurrentFillup:noArg ":""). (($hash->{ROLE} ne "Agent")?"tableCurrentPurge:noArg ":""). (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/ )?"dumpMySQL:clientSide,serverSide ":""). + (($hash->{ROLE} ne "Agent" && $dbmodel =~ /SQLITE/ )?"dumpSQLite:noArg ":""). (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/ )?"optimizeTables:noArg ":""). (($hash->{ROLE} ne "Agent" && $dbmodel =~ /SQLITE|POSTGRESQL/ )?"vacuum:noArg ":""). (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/)?"restoreMySQL:".$cj." ":""). + (($hash->{ROLE} ne "Agent" && $dbmodel =~ /SQLITE/)?"restoreSQLite:".$cj." ":""). (($hash->{ROLE} ne "Agent")?"countEntries:history,current ":""); return if(IsDisabled($name)); - if ($hash->{dbloghash}{HELPER}{REOPEN_RUNS} && $opt !~ /\?/) { - my $ro = (split(" ",FmtDateTime(gettimeofday()+$hash->{dbloghash}{HELPER}{REOPEN_RUNS})))[1]; - Log3 ($name, 3, "DbRep $name - connection $dblogdevice to db $dbname is closed until $ro - $opt postponed"); - ReadingsSingleUpdateValue ($hash, "state", "connection $dblogdevice to $dbname is closed until $ro - $opt postponed", 1); - return; - } if ($opt eq "dumpMySQL" && $hash->{ROLE} ne "Agent") { $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; - if ($prop eq "serverSide") { Log3 ($name, 3, "DbRep $name - ################################################################"); Log3 ($name, 3, "DbRep $name - ### New database serverSide dump ###"); @@ -463,40 +472,41 @@ sub DbRep_Set($@) { Log3 ($name, 3, "DbRep $name - ### New database clientSide dump ###"); Log3 ($name, 3, "DbRep $name - ################################################################"); } - - # Befehl vor Dump ausführen - my $ebd = AttrVal($name, "executeBeforeDump", undef); - if($ebd) { - Log3 ($name, 4, "DbRep $name - execute command before dump: '$ebd' "); - my $err = AnalyzeCommandChain(undef, $ebd); - if ($err) { - Log3 ($name, 2, "DbRep $name - $err"); - ReadingsSingleUpdateValue ($hash, "errortext", $err, 1); - ReadingsSingleUpdateValue ($hash, "state", "error - command before dump not successful", 1); - return undef; - } - } - + # Befehl vor Procedure ausführen + DbRep_beforeproc($hash, "dump"); DbRep_Main($hash,$opt,$prop); return undef; } - if ($opt eq "restoreMySQL" && $hash->{ROLE} ne "Agent") { + if ($opt eq "dumpSQLite" && $hash->{ROLE} ne "Agent") { + $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; + Log3 ($name, 3, "DbRep $name - ################################################################"); + Log3 ($name, 3, "DbRep $name - ### New SQLite dump ###"); + Log3 ($name, 3, "DbRep $name - ################################################################"); + # Befehl vor Procedure ausführen + DbRep_beforeproc($hash, "dump"); + DbRep_Main($hash,$opt,$prop); + return undef; + } + + if ($opt =~ /restoreMySQL|restoreSQLite/ && $hash->{ROLE} ne "Agent") { $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; Log3 ($name, 3, "DbRep $name - ################################################################"); Log3 ($name, 3, "DbRep $name - ### New database Restore/Recovery ###"); Log3 ($name, 3, "DbRep $name - ################################################################"); + # Befehl vor Procedure ausführen + DbRep_beforeproc($hash, "restore"); DbRep_Main($hash,$opt,$prop); return undef; } if ($opt =~ /optimizeTables|vacuum/ && $hash->{ROLE} ne "Agent") { $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; - Log3 ($name, 3, "DbRep $name - ################################################################"); Log3 ($name, 3, "DbRep $name - ### New optimize table / vacuum execution ###"); Log3 ($name, 3, "DbRep $name - ################################################################"); - + # Befehl vor Procedure ausführen + DbRep_beforeproc($hash, "optimize"); DbRep_Main($hash,$opt); return undef; } @@ -506,16 +516,29 @@ sub DbRep_Set($@) { (($hash->{ROLE} ne "Agent")?"cancelDump:noArg ":""); } - if ($opt =~ /countEntries/ && $hash->{ROLE} ne "Agent") { - $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; - my $table = $prop?$prop:"history"; - DbRep_Main($hash,$opt,$table); - - } elsif ($opt eq "cancelDump" && $hash->{ROLE} ne "Agent") { + if ($opt eq "cancelDump" && $hash->{ROLE} ne "Agent") { $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; BlockingKill($hash->{HELPER}{RUNNING_BACKUP_CLIENT}); Log3 ($name, 3, "DbRep $name -> running Dump has been canceled"); ReadingsSingleUpdateValue ($hash, "state", "Dump canceled", 1); + return undef; + } + + ####################################################################################################### + ## keine Aktionen außer Backup/Restore solange Reopen xxxx im DbLog-Device läuft + ####################################################################################################### + if ($hash->{dbloghash}{HELPER}{REOPEN_RUNS} && $opt !~ /\?/) { + my $ro = (split(" ",FmtDateTime(gettimeofday()+$hash->{dbloghash}{HELPER}{REOPEN_RUNS})))[1]; + Log3 ($name, 3, "DbRep $name - connection $dblogdevice to db $dbname is closed until $ro - $opt postponed"); + ReadingsSingleUpdateValue ($hash, "state", "connection $dblogdevice to $dbname is closed until $ro - $opt postponed", 1); + return; + } + ####################################################################################################### + + if ($opt =~ /countEntries/ && $hash->{ROLE} ne "Agent") { + $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; + my $table = $prop?$prop:"history"; + DbRep_Main($hash,$opt,$table); } elsif ($opt =~ m/delSeqDoublets/ && $hash->{ROLE} ne "Agent") { $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; @@ -524,7 +547,7 @@ sub DbRep_Set($@) { } DbRep_Main($hash,$opt,$prop); - } elsif ($opt eq "fetchrows" && $hash->{ROLE} ne "Agent") { + } elsif ($opt =~ /fetchrows/ && $hash->{ROLE} ne "Agent") { $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; my $table = $prop?$prop:"history"; DbRep_Main($hash,$opt,$table); @@ -737,8 +760,8 @@ sub DbRep_Attr($$$$) { readingPreventFromDel device diffAccept - executeBeforeDump - executeAfterDump + executeBeforeProc + executeAfterProc expimpfile ftpUse ftpUser @@ -1107,6 +1130,8 @@ sub DbRep_Main($$;$) { my $to = AttrVal($name, "timeout", "86400"); my $reading = AttrVal($name, "reading", "%"); my $device = AttrVal($name, "device", "%"); + my $dbloghash = $hash->{dbloghash}; + my $dbmodel = $dbloghash->{MODEL}; # Entkommentieren für Testroutine im Vordergrund # testexit($hash); @@ -1117,18 +1142,24 @@ sub DbRep_Main($$;$) { # Readings löschen die nicht in der Ausnahmeliste (Attr readingPreventFromDel) stehen delread($hash); - if ($opt =~ /dumpMySQL/) { + if ($opt =~ /dumpMySQL|dumpSQLite/) { BlockingKill($hash->{HELPER}{RUNNING_BACKUP_CLIENT}) if (exists($hash->{HELPER}{RUNNING_BACKUP_CLIENT})); BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER})); BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE})); - if ($prop eq "serverSide") { - $hash->{HELPER}{RUNNING_BCKPREST_SERVER} = BlockingCall("mysql_DoDumpServerSide", "$name", "DumpDone", $to, "DumpAborted", $hash); - ReadingsSingleUpdateValue ($hash, "state", "serverSide Dump is running - be patient and see Logfile !", 1); - } else { - $hash->{HELPER}{RUNNING_BACKUP_CLIENT} = BlockingCall("mysql_DoDumpClientSide", "$name", "DumpDone", $to, "DumpAborted", $hash); - ReadingsSingleUpdateValue ($hash, "state", "clientSide Dump is running - be patient and see Logfile !", 1); - } + if($dbmodel =~ /MYSQL/) { + if ($prop eq "serverSide") { + $hash->{HELPER}{RUNNING_BCKPREST_SERVER} = BlockingCall("mysql_DoDumpServerSide", "$name", "DumpDone", $to, "DumpAborted", $hash); + ReadingsSingleUpdateValue ($hash, "state", "serverSide Dump is running - be patient and see Logfile !", 1); + } else { + $hash->{HELPER}{RUNNING_BACKUP_CLIENT} = BlockingCall("mysql_DoDumpClientSide", "$name", "DumpDone", $to, "DumpAborted", $hash); + ReadingsSingleUpdateValue ($hash, "state", "clientSide Dump is running - be patient and see Logfile !", 1); + } + } + if($dbmodel =~ /SQLITE/) { + $hash->{HELPER}{RUNNING_BACKUP_CLIENT} = BlockingCall("sqlite_DoDump", "$name", "DumpDone", $to, "DumpAborted", $hash); + ReadingsSingleUpdateValue ($hash, "state", "SQLite Dump is running - be patient and see Logfile !", 1); + } return; } @@ -1139,6 +1170,13 @@ sub DbRep_Main($$;$) { return; } + if ($opt =~ /restoreSQLite/) { + BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER})); + $hash->{HELPER}{RUNNING_BCKPREST_SERVER} = BlockingCall("sqlite_Restore", "$name|$prop", "RestoreDone", $to, "RestoreAborted", $hash); + ReadingsSingleUpdateValue ($hash, "state", "restore database is running - be patient and see Logfile !", 1); + return; + } + if ($opt =~ /optimizeTables|vacuum/) { BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE})); $hash->{HELPER}{RUNNING_OPTIMIZE} = BlockingCall("DbRep_optimizeTables", "$name", "OptimizeDone", $to, "OptimizeAborted", $hash); @@ -1165,7 +1203,7 @@ sub DbRep_Main($$;$) { my $ts = "no_aggregation"; # Dummy für eine Select-Schleife wenn != $IsTimeSet || $IsAggrSet my ($IsTimeSet,$IsAggrSet,$aggregation) = checktimeaggr($hash); if($IsTimeSet || $IsAggrSet) { - ($epoch_seconds_begin,$epoch_seconds_end,$runtime_string_first,$runtime_string_next,$ts) = createTimeArray($hash,$aggregation,$opt); + ($epoch_seconds_begin,$epoch_seconds_end,$runtime_string_first,$runtime_string_next,$ts) = DbRep_createTimeArray($hash,$aggregation,$opt); } else { Log3 ($name, 4, "DbRep $name - Timestamp begin human readable: not set") if($opt !~ /tableCurrentPurge/); Log3 ($name, 4, "DbRep $name - Timestamp end human readable: not set") if($opt !~ /tableCurrentPurge/); @@ -1235,7 +1273,7 @@ return; ################################################################################################################ # Create zentrales Timsstamp-Array ################################################################################################################ -sub createTimeArray($$$) { +sub DbRep_createTimeArray($$$) { my ($hash,$aggregation,$opt) = @_; my $name = $hash->{NAME}; @@ -1594,7 +1632,7 @@ $hash->{HELPER}{CV} = \%cv; # Aufbau Timestampstring mit Zeitgrenzen entsprechend Aggregation while (!$ll) { # collect aggregation strings - ($runtime,$runtime_string,$runtime_string_first,$runtime_string_next,$ll) = collaggstr($hash,$runtime,$i,$runtime_string_next); + ($runtime,$runtime_string,$runtime_string_first,$runtime_string_next,$ll) = DbRep_collaggstr($hash,$runtime,$i,$runtime_string_next); $ts .= $runtime_string."#".$runtime_string_first."#".$runtime_string_next."|"; $i++; } @@ -1605,7 +1643,7 @@ return ($epoch_seconds_begin,$epoch_seconds_end,$runtime_string_first,$runtime_s #################################################################################################### # Zusammenstellung Aggregationszeiträume #################################################################################################### -sub collaggstr($$$$) { +sub DbRep_collaggstr($$$$) { my ($hash,$runtime,$i,$runtime_string_next) = @_; my $name = $hash->{NAME}; my $runtime_string; # Datum/Zeit im SQL-Format für Readingname Teilstring @@ -5105,7 +5143,7 @@ return; } #################################################################################################### -# optimize Tables MySQL +# optimize Tables alle Datenbanken #################################################################################################### sub DbRep_optimizeTables($) { my ($name) = @_; @@ -5299,7 +5337,7 @@ return "$name|$rt|''|$db_MB_start|$db_MB_end"; } #################################################################################################### -# Auswertungsroutine optimze tables +# Auswertungsroutine optimize tables #################################################################################################### sub OptimizeDone($) { my ($string) = @_; @@ -5311,6 +5349,7 @@ sub OptimizeDone($) { my $db_MB_start = $a[3]; my $db_MB_end = $a[4]; my $name = $hash->{NAME}; + my $erread; Log3 ($name, 4, "DbRep $name -> Start BlockingCall OptimizeDone"); @@ -5326,10 +5365,16 @@ sub OptimizeDone($) { # only for this block because of warnings if details of readings are not set no warnings 'uninitialized'; - my $state = "optimize tables finished"; readingsBeginUpdate($hash); ReadingsBulkUpdateValue($hash, "SizeDbBegin_MB", $db_MB_start); ReadingsBulkUpdateValue($hash, "SizeDbEnd_MB", $db_MB_end); + readingsEndUpdate($hash, 1); + + # Befehl nach Procedure ausführen + $erread = DbRep_afterproc($hash, "optimize"); + + my $state = $erread?$erread:"optimize tables finished"; + readingsBeginUpdate($hash); ReadingsBulkUpdateTimeState($hash,$brt,undef,$state); readingsEndUpdate($hash, 1); @@ -5359,8 +5404,8 @@ sub mysql_DoDumpClientSide($) { my $memory_limit = AttrVal($name, "dumpMemlimit", 100000); my $my_comment = AttrVal($name, "dumpComment", ""); my $dumpspeed = AttrVal($name, "dumpSpeed", 10000); - my $ebd = AttrVal($name, "executeBeforeDump", undef); - my $ead = AttrVal($name, "executeAfterDump", undef); + my $ebd = AttrVal($name, "executeBeforeProc", undef); + my $ead = AttrVal($name, "executeAfterProc", undef); my $mysql_commentstring = "-- "; my $character_set = "utf8"; my $repver = $hash->{VERSION}; @@ -5816,8 +5861,8 @@ sub mysql_DoDumpServerSide($) { my $optimize_tables_beforedump = AttrVal($name, "optimizeTablesBeforeDump", 0); my $dump_path_rem = AttrVal($name, "dumpDirRemote", "./"); $dump_path_rem = $dump_path_rem."/" unless($dump_path_rem =~ m/\/$/); - my $ebd = AttrVal($name, "executeBeforeDump", undef); - my $ead = AttrVal($name, "executeAfterDump", undef); + my $ebd = AttrVal($name, "executeBeforeProc", undef); + my $ead = AttrVal($name, "executeAfterProc", undef); my $table = "history"; my ($dbh,$sth,$err,$db_MB_start,$db_MB_end,$drh); my (%db_tables,@tablenames); @@ -5850,7 +5895,7 @@ sub mysql_DoDumpServerSide($) { if ($@) { $err = encode_base64($@,""); Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@); - Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished"); + Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpServerSide finished"); $dbh->disconnect; return "$name|''|$err|''|''|''|''|''|''"; } @@ -5879,7 +5924,7 @@ sub mysql_DoDumpServerSide($) { $err = "There are no tables inside database $dbname ! It doesn't make sense to backup an empty database. Skipping this one."; Log3 ($name, 2, "DbRep $name - $err"); $err = encode_base64($@,""); - Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished"); + Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpServerSide finished"); $dbh->disconnect; return "$name|''|$err|''|''|''|''|''|''"; } @@ -5901,7 +5946,7 @@ sub mysql_DoDumpServerSide($) { $Jahr += 1900; $Monat += 1; $Jahrestag += 1; - my $time_stamp = $Jahr."_".sprintf("%02d",$Monat)."_".sprintf("%02d",$Monatstag)."_".sprintf("%02d",$Stunden)."_".sprintf("%02d",$Minuten); + my $time_stamp = $Jahr."_".sprintf("%02d",$Monat)."_".sprintf("%02d",$Monatstag)."_".sprintf("%02d",$Stunden)."_".sprintf("%02d",$Minuten); my $bfile = $dbname."_".$table."_".$time_stamp.".csv"; Log3 ($name, 5, "DbRep $name - Use Outfile: $dump_path_rem$bfile"); @@ -5962,6 +6007,127 @@ sub mysql_DoDumpServerSide($) { return "$name|$rt|''|$dump_path_rem$bfile|n.a.|$drh|$fsize|$ftp|$bfd"; } +#################################################################################################### +# Dump-Routine SQLite +#################################################################################################### +sub sqlite_DoDump($) { + my ($name) = @_; + my $hash = $defs{$name}; + my $dbloghash = $hash->{dbloghash}; + my $dbname = $hash->{DATABASE}; + my $dbconn = $dbloghash->{dbconn}; + my $dbuser = $dbloghash->{dbuser}; + my $dblogname = $dbloghash->{NAME}; + my $dbpassword = $attr{"sec$dblogname"}{secret}; + my $dump_path_def = $attr{global}{modpath}."/log/"; + my $dump_path = AttrVal($name, "dumpDirLocal", $dump_path_def); + $dump_path = $dump_path."/" unless($dump_path =~ m/\/$/); + my $optimize_tables_beforedump = AttrVal($name, "optimizeTablesBeforeDump", 0); + my $ebd = AttrVal($name, "executeBeforeProc", undef); + my $ead = AttrVal($name, "executeAfterProc", undef); + my ($dbh,$err,$db_MB,$r,$query,$sth); + + Log3 ($name, 4, "DbRep $name -> Start BlockingCall sqlite_DoDump"); + + # Background-Startzeit + my $bst = [gettimeofday]; + + # Verbindung mit DB + eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });}; + if ($@) { + $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - $@"); + Log3 ($name, 4, "DbRep $name -> BlockingCall sqlite_DoDump finished"); + return "$name|''|$err|''|''|''|''|''|''"; + } + + if($optimize_tables_beforedump) { + # Vacuum vor Dump + # Anfangsgröße ermitteln + $db_MB = (split(' ',qx(du -m $dbname)))[0] if ($^O =~ m/linux/i || $^O =~ m/unix/i); + Log3 ($name, 3, "DbRep $name - Size of database $dbname before optimize (MB): $db_MB"); + $query ="VACUUM"; + Log3 ($name, 5, "DbRep $name - current query: $query "); + + Log3 ($name, 3, "DbRep $name - VACUUM database $dbname...."); + eval {$sth = $dbh->prepare($query); + $r = $sth->execute(); + }; + if ($@) { + $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! SQLite-Error: ".$@); + Log3 ($name, 4, "DbRep $name -> BlockingCall sqlite_DoDump finished"); + $sth->finish; + $dbh->disconnect; + return "$name|''|$err|''|''|''|''|''|''"; + } + + # Endgröße ermitteln + $db_MB = (split(' ',qx(du -m $dbname)))[0] if ($^O =~ m/linux/i || $^O =~ m/unix/i); + Log3 ($name, 3, "DbRep $name - Size of database $dbname after optimize (MB): $db_MB"); + } + + $dbname = (split /[\/]/, $dbname)[-1]; + + Log3 ($name, 3, "DbRep $name - Starting dump of database '$dbname'"); + + # Startzeit ermitteln + my ($Sekunden, $Minuten, $Stunden, $Monatstag, $Monat, $Jahr, $Wochentag, $Jahrestag, $Sommerzeit) = localtime(time); + $Jahr += 1900; + $Monat += 1; + $Jahrestag += 1; + my $time_stamp = $Jahr."_".sprintf("%02d",$Monat)."_".sprintf("%02d",$Monatstag)."_".sprintf("%02d",$Stunden)."_".sprintf("%02d",$Minuten); + + $dbname = (split /\./, $dbname)[0]; + my $bfile = $dbname."_".$time_stamp.".sqlitebkp"; + Log3 ($name, 5, "DbRep $name - Use Outfile: $dump_path$bfile"); + + # SQL-Startzeit + my $st = [gettimeofday]; + + eval { $dbh->sqlite_backup_to_file($dump_path.$bfile); }; + if ($@) { + $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - $@"); + Log3 ($name, 4, "DbRep $name -> BlockingCall sqlite_DoDump finished"); + $dbh->disconnect; + return "$name|''|$err|''|''|''|''|''|''"; + } + + $dbh->disconnect; + + # SQL-Laufzeit ermitteln + my $rt = tv_interval($st); + + # Größe Dumpfile ermitteln + my @a = split(' ',qx(du $dump_path$bfile)) if ($^O =~ m/linux/i || $^O =~ m/unix/i); + + my $filesize = ($a[0])?($a[0]*1024):"n.a."; + my $fsize = byte_output($filesize); + Log3 ($name, 3, "DbRep $name - Size of backupfile: ".$fsize); + + # Dumpfile per FTP senden + my ($ftperr,$ftpmsg) = sendftp($hash,$bfile); + my $ftp = $ftperr?encode_base64($ftperr,""):$ftpmsg?encode_base64($ftpmsg,""):0; + + # alte Dumpfiles löschen + my @fd = deldumpfiles($hash,$bfile); + my $bfd = join(", ", @fd ); + $bfd = $bfd?encode_base64($bfd,""):0; + + # Background-Laufzeit ermitteln + my $brt = tv_interval($bst); + + $fsize = encode_base64($fsize,""); + + $rt = $rt.",".$brt; + + Log3 ($name, 3, "DbRep $name - Finished backup of database $dbname - total time used: ".sprintf("%.0f",$brt)." seconds"); + Log3 ($name, 4, "DbRep $name -> BlockingCall sqlite_DoDump finished"); + +return "$name|$rt|''|$dump_path$bfile|n.a.|n.a.|$fsize|$ftp|$bfd"; +} + #################################################################################################### # Auswertungsroutine der nicht blockierenden DB-Funktion Dump #################################################################################################### @@ -6005,17 +6171,8 @@ sub DumpDone($) { ReadingsBulkUpdateValue($hash, "FTP_Message", $ftp) if($ftp); readingsEndUpdate($hash, 1); - # Befehl nach Dump ausführen - my $ead = AttrVal($name, "executeAfterDump", undef); - if($ead) { - Log3 ($name, 4, "DbRep $name - execute command after dump: '$ead' "); - $err = AnalyzeCommandChain(undef, $ead); - if ($err) { - Log3 ($name, 2, "DbRep $name - $err"); - ReadingsSingleUpdateValue ($hash, "errortext", $err, 1); - $erread = "Warning - Database backup finished but command after dump not successful"; - } - } + # Befehl nach Procedure ausführen + $erread = DbRep_afterproc($hash, "dump"); my $state = $erread?$erread:"Database backup finished"; readingsBeginUpdate($hash); @@ -6029,6 +6186,80 @@ sub DumpDone($) { return; } +#################################################################################################### +# Restore SQLite +#################################################################################################### +sub sqlite_Restore ($) { + my ($string) = @_; + my ($name, $bfile) = split("\\|", $string); + my $hash = $defs{$name}; + my $dbloghash = $hash->{dbloghash}; + my $dbconn = $dbloghash->{dbconn}; + my $dbuser = $dbloghash->{dbuser}; + my $dblogname = $dbloghash->{NAME}; + my $dbpassword = $attr{"sec$dblogname"}{secret}; + my $dump_path_def = $attr{global}{modpath}."/log/"; + my $dump_path = AttrVal($name, "dumpDirLocal", $dump_path_def); + $dump_path = $dump_path."/" unless($dump_path =~ m/\/$/); + my $ebd = AttrVal($name, "executeBeforeProc", undef); + my $ead = AttrVal($name, "executeAfterProc", undef); + my ($dbh,$err,$dbname); + + Log3 ($name, 4, "DbRep $name -> Start BlockingCall sqlite_Restore"); + + # Background-Startzeit + my $bst = [gettimeofday]; + + # Verbindung mit DB + eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });}; + if ($@) { + $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - $@"); + Log3 ($name, 4, "DbRep $name -> BlockingCall sqlite_Restore finished"); + return "$name|''|$err|''|''"; + } + + eval { $dbname = $dbh->sqlite_db_filename(); }; + if ($@) { + $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - $@"); + Log3 ($name, 4, "DbRep $name -> BlockingCall sqlite_DoDump finished"); + $dbh->disconnect; + return "$name|''|$err|''|''"; + } + + $dbname = (split /[\/]/, $dbname)[-1]; + + Log3 ($name, 3, "DbRep $name - Starting restore of database '$dbname'"); + + # SQL-Startzeit + my $st = [gettimeofday]; + + eval { $dbh->sqlite_backup_from_file($dump_path.$bfile); }; + if ($@) { + $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - $@"); + Log3 ($name, 4, "DbRep $name -> BlockingCall sqlite_DoDump finished"); + $dbh->disconnect; + return "$name|''|$err|''|''"; + } + + $dbh->disconnect; + + # SQL-Laufzeit ermitteln + my $rt = tv_interval($st); + + # Background-Laufzeit ermitteln + my $brt = tv_interval($bst); + + $rt = $rt.",".$brt; + + Log3 ($name, 3, "DbRep $name - Restore of $dump_path$bfile into '$dbname' finished - total time used: ".sprintf("%.0f",$brt)." seconds."); + Log3 ($name, 4, "DbRep $name -> BlockingCall sqlite_Restore finished"); + +return "$name|$rt|''|$dump_path$bfile|n.a."; +} + #################################################################################################### # Restore MySQL (serverSide) #################################################################################################### @@ -6058,7 +6289,7 @@ sub mysql_RestoreServerSide($) { $err = encode_base64($@,""); Log3 ($name, 2, "DbRep $name - $@"); Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_RestoreServerSide finished"); - return "$name|''|$err|''|''|''|''"; + return "$name|''|$err|''|''"; } Log3 ($name, 3, "DbRep $name - Starting restore of database '$dbname', table '$table'."); @@ -6078,7 +6309,7 @@ sub mysql_RestoreServerSide($) { Log3 ($name, 2, "DbRep $name - $@"); Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_RestoreServerSide finished"); $dbh->disconnect; - return "$name|''|$err|''|''|''|''"; + return "$name|''|$err|''|''"; } $sth->finish; @@ -6092,11 +6323,10 @@ sub mysql_RestoreServerSide($) { $rt = $rt.",".$brt; - Log3 ($name, 3, "DbRep $name - Restore of $dump_path_rem$bfile into '$dbname', '$table' finished - total time used: ".sprintf("%.0f",$brt)." sec for $drh datasets."); - Log3 ($name, 3, "DbRep $name - Number of imported datasets: $drh."); + Log3 ($name, 3, "DbRep $name - Restore of $dump_path_rem$bfile into '$dbname', '$table' finished - total time used: ".sprintf("%.0f",$brt)." seconds."); Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_RestoreServerSide finished"); -return "$name|$rt|''|$dump_path_rem$bfile|$drh"; +return "$name|$rt|''|$dump_path_rem$bfile|n.a."; } #################################################################################################### @@ -6112,6 +6342,7 @@ sub RestoreDone($) { my $bfile = $a[3]; my $drh = $a[4]; my $name = $hash->{NAME}; + my $erread; Log3 ($name, 4, "DbRep $name -> Start BlockingCall RestoreDone"); @@ -6125,9 +6356,15 @@ sub RestoreDone($) { return; } - my $state = "Restore of $bfile finished"; readingsBeginUpdate($hash); ReadingsBulkUpdateValue($hash, "RestoreRowsHistory", $drh); + readingsEndUpdate($hash, 1); + + # Befehl nach Procedure ausführen + $erread = DbRep_afterproc($hash, "restore"); + + my $state = $erread?$erread:"Restore of $bfile finished"; + readingsBeginUpdate($hash); ReadingsBulkUpdateTimeState($hash,$brt,undef,$state); readingsEndUpdate($hash, 1); @@ -6144,17 +6381,23 @@ return; sub RestoreAborted(@) { my ($hash,$cause) = @_; my $name = $hash->{NAME}; - my $dbh = $hash->{DBH} if ($hash->{DBH}); + my $dbh = $hash->{DBH}; + my $erread; $cause = $cause?$cause:"Timeout: process terminated"; Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BACKUP_CLIENT}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause") if($hash->{HELPER}{RUNNING_BACKUP_CLIENT}); Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BCKPREST_SERVER}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause") if($hash->{HELPER}{RUNNING_BCKPREST_SERVER}); + + # Befehl nach Procedure ausführen + no warnings 'uninitialized'; + $erread = DbRep_afterproc($hash, "restore"); + $erread = ", ".(split("but", $erread))[1] if($erread); - my $state = "Database restore $cause"; + my $state = $cause.$erread; $dbh->disconnect() if(defined($dbh)); ReadingsSingleUpdateValue ($hash, "state", $state, 1); - Log3 ($name, 3, "DbRep $name - Database restore aborted by \"$cause\" "); + Log3 ($name, 2, "DbRep $name - Database restore aborted by \"$cause\" "); delete($hash->{HELPER}{RUNNING_BACKUP_CLIENT}); delete($hash->{HELPER}{RUNNING_BCKPREST_SERVER}); @@ -6171,10 +6414,11 @@ sub ParseAborted(@) { $cause = $cause?$cause:"Timeout: process terminated"; Log3 ($name, 1, "DbRep $name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause"); + $dbh->disconnect() if(defined($dbh)); ReadingsSingleUpdateValue ($hash,"state",$cause, 1); - delete($hash->{HELPER}{RUNNING_PID}); + delete($hash->{HELPER}{RUNNING_PID}); return; } @@ -6184,26 +6428,19 @@ return; sub DumpAborted(@) { my ($hash,$cause) = @_; my $name = $hash->{NAME}; - my $dbh = $hash->{DBH} if ($hash->{DBH}); - my ($err,$erread); + my $dbh = $hash->{DBH}; + my ($erread); $cause = $cause?$cause:"Timeout: process terminated"; Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BACKUP_CLIENT}{fn} pid:$hash->{HELPER}{RUNNING_BACKUP_CLIENT}{pid} $cause") if($hash->{HELPER}{RUNNING_BACKUP_CLIENT}); Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BCKPREST_SERVER}{fn} pid:$hash->{HELPER}{RUNNING_BCKPREST_SERVER}{pid} $cause") if($hash->{HELPER}{RUNNING_BCKPREST_SERVER}); - # Befehl nach Dump ausführen - my $ead = AttrVal($name, "executeAfterDump", undef); - if($ead) { - Log3 ($name, 4, "DbRep $name - execute command after dump: '$ead' "); - $err = AnalyzeCommandChain(undef, $ead); - if ($err) { - Log3 ($name, 2, "DbRep $name - $err"); - ReadingsSingleUpdateValue ($hash, "errortext", $err, 1); - $erread = "Warning - Database backup ended with \"$cause\" and command after dump not successful"; - } - } + # Befehl nach Procedure ausführen + no warnings 'uninitialized'; + $erread = DbRep_afterproc($hash, "dump"); + $erread = ", ".(split("but", $erread))[1] if($erread); - my $state = $erread?$erread:$cause; + my $state = $cause.$erread; $dbh->disconnect() if(defined($dbh)); ReadingsSingleUpdateValue ($hash, "state", $state, 1); @@ -6221,13 +6458,23 @@ sub OptimizeAborted(@) { my ($hash,$cause) = @_; my $name = $hash->{NAME}; my $dbh = $hash->{DBH}; + my ($erread); $cause = $cause?$cause:"Timeout: process terminated"; Log3 ($name, 1, "DbRep $name -> BlockingCall $hash->{HELPER}{RUNNING_OPTIMIZE}}{fn} pid:$hash->{HELPER}{RUNNING_OPTIMIZE}{pid} $cause"); - $dbh->disconnect() if(defined($dbh)); - ReadingsSingleUpdateValue ($hash, "state", $cause, 1); - delete($hash->{HELPER}{RUNNING_OPTIMIZE}); + # Befehl nach Procedure ausführen + no warnings 'uninitialized'; + $erread = DbRep_afterproc($hash, "optimize"); + $erread = ", ".(split("but", $erread))[1] if($erread); + + my $state = $cause.$erread; + $dbh->disconnect() if(defined($dbh)); + ReadingsSingleUpdateValue ($hash, "state", $state, 1); + + Log3 ($name, 2, "DbRep $name - Database optimize aborted by \"$cause\" "); + + delete($hash->{HELPER}{RUNNING_OPTIMIZE}); return; } @@ -6550,6 +6797,53 @@ sub DbRep_charfilter ($) { return($txt); } +################################################################################### +# Befehl vor Procedure ausführen +################################################################################### +sub DbRep_beforeproc ($$) { + my ($hash, $txt) = @_; + my $name = $hash->{NAME}; + + # Befehl vor Procedure ausführen + my $ebd = AttrVal($name, "executeBeforeProc", undef); + if($ebd) { + Log3 ($name, 3, "DbRep $name - execute command before $txt: '$ebd' "); + my $err = AnalyzeCommandChain(undef, $ebd); + if ($err) { + Log3 ($name, 2, "DbRep $name - command before $txt message: \"$err\" "); + my $erread = "Warning - message from command before $txt appeared"; + ReadingsSingleUpdateValue ($hash, "before".$txt."_message", $err, 1); + ReadingsSingleUpdateValue ($hash, "state", $erread, 1); + } + } + +return; +} + +################################################################################### +# Befehl nach Procedure ausführen +################################################################################### +sub DbRep_afterproc ($$) { + my ($hash, $txt) = @_; + my $name = $hash->{NAME}; + my $erread; + + # Befehl nach Procedure ausführen + no warnings 'uninitialized'; + my $ead = AttrVal($name, "executeAfterProc", undef); + if($ead) { + Log3 ($name, 4, "DbRep $name - execute command after $txt: '$ead' "); + my $err = AnalyzeCommandChain(undef, $ead); + if ($err) { + Log3 ($name, 2, "DbRep $name - command after $txt message: \"$err\" "); + ReadingsSingleUpdateValue ($hash, "after".$txt."_message", $err, 1); + $erread = "Warning - $txt finished, but command after $txt message appeared"; + } + } + +return $erread; +} + ############################################################################################## # timestamp_begin, timestamp_end bei Einsatz datetime-Picker entsprechend # den Anforderungen formatieren @@ -6630,7 +6924,7 @@ return undef; #################################################################################################### # erstellen neues SQL-File für Dumproutine #################################################################################################### -sub NewDumpFilename { +sub NewDumpFilename ($$$$$){ my ($sql_text,$dump_path,$dbname,$time_stamp,$character_set) = @_; my $part = ""; my $sql_file = $dump_path.$dbname."_".$time_stamp.$part.".sql"; @@ -6653,7 +6947,7 @@ return ($sql_text,$first_insert,$sql_file,$backupfile,undef); #################################################################################################### # Schreiben DB-Dumps in SQL-File #################################################################################################### -sub WriteToDumpFile { +sub WriteToDumpFile ($$) { my ($inh,$sql_file) = @_; my $filesize; my $err = 0; @@ -6675,7 +6969,7 @@ return ($filesize,undef); #################################################################################################### # Filesize (Byte) umwandeln in KB bzw. MB #################################################################################################### -sub byte_output { +sub byte_output ($) { my $bytes = shift; return if(!defined($bytes)); @@ -6692,7 +6986,7 @@ return $ret; #################################################################################################### # Tabellenoptimierung MySQL #################################################################################################### -sub mysql_optimize_tables { +sub mysql_optimize_tables ($$@) { my ($hash,$dbh,@tablenames) = @_; my $name = $hash->{NAME}; my $dbname = $hash->{DATABASE}; @@ -6771,8 +7065,8 @@ sub deldumpfiles ($$) { my $dump_path_def = $attr{global}{modpath}."/log/"; my $dump_path_loc = AttrVal($name,"dumpDirLocal", $dump_path_def); my $dfk = AttrVal($name,"dumpFilesKeep", 3); - my $pfix = (split '\.', $bfile)[ -1 ]; - my $dbname = $hash->{DATABASE}; + my $pfix = (split '\.', $bfile)[-1]; + my $dbname = (split '_', $bfile)[0]; my $file = $dbname."_.*".$pfix; my @fd; @@ -7067,9 +7361,9 @@ return;