diff --git a/fhem/CHANGED b/fhem/CHANGED index 8fd5386d4..a98a521ba 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # 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: save writes a copy into restoreDirs (Forum #78769) - feature: WMBus: preliminary support for SML payload - feature: 98_DOIFtools: unattended save removed, Forum(78769) DOIFtools will perform save if allowed by diff --git a/fhem/FHEM/98_autocreate.pm b/fhem/FHEM/98_autocreate.pm index 51176febd..117b872f2 100644 --- a/fhem/FHEM/98_autocreate.pm +++ b/fhem/FHEM/98_autocreate.pm @@ -379,8 +379,7 @@ autocreate_Notify($$) } CommandSave(undef, undef) if(!$ret && $nrcreated && - AttrVal($me,"autosave", - AttrVal("global","autosave",1))); + AttrVal($me,"autosave", 1)); return $ret; } diff --git a/fhem/FHEM/98_update.pm b/fhem/FHEM/98_update.pm index f1a878338..eb58ca3b5 100644 --- a/fhem/FHEM/98_update.pm +++ b/fhem/FHEM/98_update.pm @@ -11,8 +11,6 @@ use Blocking; sub CommandUpdate($$); sub upd_getUrl($); sub upd_initRestoreDirs($); -sub upd_mkDir($$$); -sub upd_rmTree($); sub upd_writeFile($$$$); sub upd_mv($$); sub upd_metainit($); @@ -21,7 +19,6 @@ sub upd_saveConfig($$$); my $updateInBackground; my $updRet; -my %updDirs; my $updArg; my $mainPgm = "/fhem.pl\$"; my %upd_connecthash; @@ -274,7 +271,7 @@ doUpdate($$$$) ########################### # read in & digest the local control file my $root = $attr{global}{modpath}; - my $restoreDir = ($arg eq "check" ? "" : upd_initRestoreDirs($root)); + my $restoreDir = ($arg eq "check" ? "" : restoreDir_init()); my @locList; if(($arg eq "check" || $arg eq "all") && @@ -318,7 +315,7 @@ doUpdate($$$$) uLog 1, "Suspicious line $r, aborting"; return 1; } - upd_mkDir($root, $r[2], 0); + restoreDir_mkDir($root, $r[2], 0); my $mvret = upd_mv("$root/$r[1]", "$root/$r[2]"); uLog 4, "mv $root/$r[1] $root/$r[2]". ($mvret ? " FAILED:$mvret":""); } @@ -463,26 +460,6 @@ upd_mv($$) return undef; } -sub -upd_mkDir($$$) -{ - my ($root, $dir, $isFile) = @_; - if($isFile) { # Delete the file Component - $dir =~ m,^(.*)/([^/]*)$,; - $dir = $1; - } - return if($updDirs{$dir}); - $updDirs{$dir} = 1; - my @p = split("/", $dir); - for(my $i = 0; $i < int(@p); $i++) { - my $path = "$root/".join("/", @p[0..$i]); - if(!-d $path) { - mkdir $path; - uLog 4, "MKDIR $root/".join("/", @p[0..$i]); - } - } -} - sub upd_getChanges($$) { @@ -538,7 +515,7 @@ upd_saveConfig($$$) my($root, $restoreDir, $fName) = @_; return if(!$fName || !$restoreDir || configDBUsed() || !-r "$root/$fName"); - upd_mkDir($root, "$restoreDir/$fName", 1); + restoreDir_mkDir($root, "$restoreDir/$fName", 1); Log 1, "saving $fName"; if(!copy("$root/$fName", "$root/$restoreDir/$fName")) { uLog 1, "copy $root/$fName $root/$restoreDir/$fName failed:$!, ". @@ -553,8 +530,8 @@ upd_writeFile($$$$) my($root, $restoreDir, $fName, $content) = @_; # copy the old file and save the new - upd_mkDir($root, $fName, 1); - upd_mkDir($root, "$restoreDir/$fName", 1) if($restoreDir); + restoreDir_mkDir($root, $fName, 1); + restoreDir_mkDir($root, "$restoreDir/$fName", 1) if($restoreDir); if($restoreDir && -f "$root/$fName" && ! copy("$root/$fName", "$root/$restoreDir/$fName")) { uLog 1, "copy $root/$fName $root/$restoreDir/$fName failed:$!, ". @@ -587,69 +564,6 @@ upd_writeFile($$$$) return 1; } -sub -upd_rmTree($) -{ - my ($dir) = @_; - - my $dh; - if(!opendir($dh, $dir)) { - uLog 1, "opendir $dir: $!"; - return; - } - my @files = grep { $_ ne "." && $_ ne ".." } readdir($dh); - closedir($dh); - - foreach my $f (@files) { - if(-d "$dir/$f") { - upd_rmTree("$dir/$f"); - } else { - uLog 4, "rm $dir/$f"; - if(!unlink("$dir/$f")) { - uLog 1, "rm $dir/$f failed: $!"; - } - } - } - uLog 4, "rmdir $dir"; - if(!rmdir($dir)) { - uLog 1, "rmdir $dir failed: $!"; - } -} - -sub -upd_initRestoreDirs($) -{ - my ($root) = @_; - - my $nDirs = AttrVal("global","restoreDirs", 3); - if($nDirs !~ m/^\d+$/ || $nDirs < 0) { - uLog 1, "invalid restoreDirs value $nDirs, setting it to 3"; - $nDirs = 3; - } - return "" if($nDirs == 0); - - my $rdName = "restoreDir"; - my @t = localtime; - my $restoreDir = sprintf("$rdName/%04d-%02d-%02d", - $t[5]+1900, $t[4]+1, $t[3]); - Log 1, "MKDIR $restoreDir" if(! -d "$root/restoreDir"); - upd_mkDir($root, $restoreDir, 0); - - if(!opendir(DH, "$root/$rdName")) { - uLog 1, "opendir $root/$rdName: $!"; - return ""; - } - my @oldDirs = sort grep { $_ !~ m/^\./ && $_ ne $restoreDir } readdir(DH); - closedir(DH); - while(int(@oldDirs) > $nDirs) { - my $dir = "$root/$rdName/". shift(@oldDirs); - next if($dir =~ m/$restoreDir/); # Just in case - uLog 1, "RMDIR: $dir"; - upd_rmTree($dir); - } - - return $restoreDir; -} 1; =pod @@ -741,17 +655,7 @@ upd_initRestoreDirs($) updating it from another source, specify fhem.de.*:FILE.pm
- -
  • restoreDirs
    - update saves each file before overwriting it with the new version from - the Web. For this purpose update creates a directory restoreDir in the - global modpath directory, then a subdirectory with the current date, - where the old version of the currently replaced file is stored. - The default value of this attribute is 3, meaning that 3 old versions - (i.e. date-directories) are kept, and the older ones are deleted. If - the attribute is set to 0, the feature is deactivated. -

  • - +
  • restoreDirs

  • @@ -854,16 +758,7 @@ upd_initRestoreDirs($)
    -
  • restoreDirs - update sichert jede Datei vor dem Überschreiben mit der neuen - Version aus dem Web. Für diesen Zweck wird zuerst ein restoreDir - Verzeichnis in der global modpath Verzeichnis angelegt, und danach - ein Unterverzeichnis mit dem aktuellen Datum. In diesem Verzeichnis - werden vor dem Überschreiben die alten Versionen der Dateien - gerettet. Die Voreinstellung ist 3, d.h. die letzten 3 - Datums-Verzeichnisse werden aufgehoben, und die älteren entfernt. - Falls man den Wert auf 0 setzt, dann ist dieses Feature deaktiviert. -

  • +
  • restoreDirs

  • diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html index 3ef955358..fb7e2399b 100644 --- a/fhem/docs/commandref_frame.html +++ b/fhem/docs/commandref_frame.html @@ -1082,6 +1082,9 @@ The following local attributes are used by a wider range of devices: structures, but it won't work correctly if some of these files are not writeable. +
  • before overwriting the files, the old version will be saved, see the restoreDirs global attribute for details. + @@ -1539,6 +1542,20 @@ The following local attributes are used by a wider range of devices: regexp for hosts to exclude when using a proxy

  • + +
  • restoreDirs
    + update saves each file before overwriting it with the new version from + the Web. For this purpose update creates a directory restoreDir in the + global modpath directory, then a subdirectory with the current date, + where the old version of the currently replaced file is stored. + The default value of this attribute is 3, meaning that 3 old versions + (i.e. date-directories) are kept, and the older ones are deleted.
    + fhem.cfg and fhem.state will be also copied with this method before + executing save. To restore the files, you can use the restore FHEM + command.
    +
    If the attribute is set to 0, the feature is deactivated. +

  • +
  • sendStatistics
    diff --git a/fhem/docs/commandref_frame_DE.html b/fhem/docs/commandref_frame_DE.html index 13c2a247d..4d922f284 100644 --- a/fhem/docs/commandref_frame_DE.html +++ b/fhem/docs/commandref_frame_DE.html @@ -1148,6 +1148,8 @@ Die folgenden lokalen Attribute werden von mehreren Geräten verwendet: nicht korrekt wenn FHEM für diese Dateien keine Schreibrechte besitzt.
  • +
  • Vor dem Überschreiben der Dateien wird die alte Version gesichert, + siehe restoreDirs für Einzelheiten.
  • @@ -1644,6 +1646,21 @@ Die folgenden lokalen Attribute werden von mehreren Geräten verwendet: Regexp, um bestimmte Hosts nicht via proxy zu kontaktieren.
    + +
  • restoreDirs
    + update sichert jede Datei vor dem Überschreiben mit der neuen + Version aus dem Web. Für diesen Zweck wird zuerst ein restoreDir + Verzeichnis in der global modpath Verzeichnis angelegt, und danach + ein Unterverzeichnis mit dem aktuellen Datum. In diesem Verzeichnis + werden vor dem Überschreiben die alten Versionen der Dateien + gerettet. Die Voreinstellung ist 3, d.h. die letzten 3 + Datums-Verzeichnisse werden aufgehoben, und die älteren entfernt. +
    + Auch fhem.cfg und fhem.state wird auf diese Weise vor dem ausfüren + von save gesichert. Zum restaurieren der alten Dateien kann man das + restore Befehl verwenden.
    + Falls man den Wert auf 0 setzt, dann ist dieses Feature deaktiviert. +

  • sendStatistics
    diff --git a/fhem/fhem.pl b/fhem/fhem.pl index 6e9585958..fa182af54 100755 --- a/fhem/fhem.pl +++ b/fhem/fhem.pl @@ -38,6 +38,7 @@ use IO::Socket::INET; use Time::HiRes qw(gettimeofday); use Scalar::Util qw(looks_like_number); use POSIX; +use File::Copy qw(copy); ################################################## # Forward declarations @@ -134,6 +135,10 @@ sub readingsEndUpdate($$); sub readingsSingleUpdate($$$$); sub redirectStdinStdErr(); sub rejectDuplicate($$$); +sub restoreDir_init(); +sub restoreDir_rmTree($); +sub restoreDir_saveFile($$); +sub restoreDir_mkDir($$$); sub setGlobalAttrBeforeFork($); sub setReadingsVal($$$$); sub toJSON($); @@ -551,7 +556,8 @@ if(configDBUsed()) { } if($cfgRet) { - $attr{global}{motd} = "$cfgErrMsg\n$cfgRet"; + $attr{global}{autosave} = 0; + $attr{global}{motd} = "$cfgErrMsg\n$cfgRet\nAutosave deactivated"; Log 1, $cfgRet; } elsif($attr{global}{motd} && $attr{global}{motd} =~ m/^$cfgErrMsg/) { @@ -1566,10 +1572,19 @@ CommandSave($$) return "Last 10 structural changes:\n ".join("\n ", @structChangeHist); } + if(!$cl && !AttrVal("global", "autosave", 1)) { # Forum #78769 + Log 4, "Skipping save, as autosave is disabled"; + return; + } + my $restoreDir; + $restoreDir = restoreDir_init() if(!configDBUsed()); + @structChangeHist = (); DoTrigger("global", "SAVE", 1); + restoreDir_saveFile($restoreDir, $attr{global}{statefile}); my $ret = WriteStatefile(); + return $ret if($ret); $ret = ""; # cfgDB_SaveState may return undef @@ -1580,6 +1595,7 @@ CommandSave($$) $param = $attr{global}{configfile} if(!$param); return "No configfile attribute set and no argument specified" if(!$param); + restoreDir_saveFile($restoreDir, $param); if(!open(SFH, ">$param")) { return "Cannot open $param: $!"; } @@ -1606,6 +1622,7 @@ CommandSave($$) my $cfgfile = $h->{CFGFN} ? $h->{CFGFN} : "configfile"; my $fh = $fh{$cfgfile}; if(!$fh) { + restoreDir_saveFile($restoreDir, $cfgfile); if(!open($fh, ">$cfgfile")) { $ret .= "Cannot open $cfgfile: $!, ignoring its content\n"; $fh{$cfgfile} = 1; @@ -5238,4 +5255,104 @@ computeAlignTime($$@) return (undef, $ttime); } +############################ +my %restoreDir_dirs; +sub +restoreDir_mkDir($$$) +{ + my ($root, $dir, $isFile) = @_; + if($isFile) { # Delete the file Component + $dir =~ m,^(.*)/([^/]*)$,; + $dir = $1; + } + return if($restoreDir_dirs{$dir}); + $restoreDir_dirs{$dir} = 1; + my @p = split("/", $dir); + for(my $i = 0; $i < int(@p); $i++) { + my $path = "$root/".join("/", @p[0..$i]); + if(!-d $path) { + mkdir $path; + Log 4, "MKDIR $root/".join("/", @p[0..$i]); + } + } +} + +sub +restoreDir_rmTree($) +{ + my ($dir) = @_; + + my $dh; + if(!opendir($dh, $dir)) { + Log 1, "opendir $dir: $!"; + return; + } + my @files = grep { $_ ne "." && $_ ne ".." } readdir($dh); + closedir($dh); + + foreach my $f (@files) { + if(-d "$dir/$f") { + restoreDir_rmTree("$dir/$f"); + } else { + Log 4, "rm $dir/$f"; + if(!unlink("$dir/$f")) { + Log 1, "rm $dir/$f failed: $!"; + } + } + } + Log 4, "rmdir $dir"; + if(!rmdir($dir)) { + Log 1, "rmdir $dir failed: $!"; + } +} + +sub +restoreDir_init() +{ + my $root = $attr{global}{modpath}; + + my $nDirs = AttrVal("global","restoreDirs", 3); + if($nDirs !~ m/^\d+$/ || $nDirs < 0) { + Log 1, "invalid restoreDirs value $nDirs, setting it to 3"; + $nDirs = 3; + } + return "" if($nDirs == 0); + + my $rdName = "restoreDir"; + my @t = localtime; + my $restoreDir = sprintf("$rdName/%04d-%02d-%02d", + $t[5]+1900, $t[4]+1, $t[3]); + Log 1, "MKDIR $restoreDir" if(! -d "$root/restoreDir"); + restoreDir_mkDir($root, $restoreDir, 0); + + if(!opendir(DH, "$root/$rdName")) { + Log 1, "opendir $root/$rdName: $!"; + return ""; + } + my @oldDirs = sort grep { $_ !~ m/^\./ && $_ ne $restoreDir } readdir(DH); + closedir(DH); + while(int(@oldDirs) > $nDirs) { + my $dir = "$root/$rdName/". shift(@oldDirs); + next if($dir =~ m/$restoreDir/); # Just in case + Log 1, "RMDIR: $dir"; + restoreDir_rmTree($dir); + } + + return $restoreDir; +} + +sub +restoreDir_saveFile($$) +{ + my($restoreDir, $fName) = @_; + + return if(!$restoreDir || !$fName); + + my $root = $attr{global}{modpath}; + restoreDir_mkDir($root, "$restoreDir/$fName", 1); + if(!copy($fName, "$root/$restoreDir/$fName")) { + log 1, "copy $fName $root/$restoreDir/$fName failed:$!"; + } +} + 1;