2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-12 16:46:35 +00:00

fhem.pl: save writes a copy into restoreDirs (Forum #78769)

git-svn-id: https://svn.fhem.de/fhem/trunk@15377 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rudolfkoenig 2017-11-01 16:59:23 +00:00
parent c74f8a0060
commit 130bd1476c
6 changed files with 161 additions and 115 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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
</li><br>
<a name="restoreDirs"></a>
<li>restoreDirs<br>
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.
</li><br>
<li><a href="#restoreDirs">restoreDirs</a></li><br>
</ul>
</ul>
@ -854,16 +758,7 @@ upd_initRestoreDirs($)
</li><br>
<li><a href="#restoreDirs">restoreDirs</a>
update sichert jede Datei vor dem &Uuml;berschreiben mit der neuen
Version aus dem Web. F&uuml;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 &Uuml;berschreiben die alten Versionen der Dateien
gerettet. Die Voreinstellung ist 3, d.h. die letzten 3
Datums-Verzeichnisse werden aufgehoben, und die &auml;lteren entfernt.
Falls man den Wert auf 0 setzt, dann ist dieses Feature deaktiviert.
</li><br>
<li><a href="#restoreDirs">restoreDirs</a></li><br>
</ul>
</ul>

View File

@ -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.</li>
<li>before overwriting the files, the old version will be saved, see the <a
href="#restoreDirs">restoreDirs</a> global attribute for details.
</ul>
</ul>
<!-- save end -->
@ -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
</li><br>
<a name="restoreDirs"></a>
<li>restoreDirs<br>
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.<br>
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.<br>
<br>If the attribute is set to 0, the feature is deactivated.
</li><br>
<li><a href="#fheminfo">sendStatistics</a><br>
<a name="statefile"></a>

View File

@ -1148,6 +1148,8 @@ Die folgenden lokalen Attribute werden von mehreren Ger&auml;ten verwendet:
nicht korrekt wenn FHEM f&uuml;r diese Dateien keine Schreibrechte
besitzt.</li>
<li>Vor dem &Uuml;berschreiben der Dateien wird die alte Version gesichert,
siehe <a href="#restoreDirs">restoreDirs</a> f&uuml;r Einzelheiten.</li>
</ul>
</ul>
<!-- save end -->
@ -1644,6 +1646,21 @@ Die folgenden lokalen Attribute werden von mehreren Ger&auml;ten verwendet:
Regexp, um bestimmte Hosts nicht via proxy zu kontaktieren.
</li><br>
<a name="restoreDirs"></a>
<li><a name="restoreDirs">restoreDirs</a><br>
update sichert jede Datei vor dem &Uuml;berschreiben mit der neuen
Version aus dem Web. F&uuml;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 &Uuml;berschreiben die alten Versionen der Dateien
gerettet. Die Voreinstellung ist 3, d.h. die letzten 3
Datums-Verzeichnisse werden aufgehoben, und die &auml;lteren entfernt.
<br>
Auch fhem.cfg und fhem.state wird auf diese Weise vor dem ausf&uuml;ren
von save gesichert. Zum restaurieren der alten Dateien kann man das
restore Befehl verwenden.<br>
Falls man den Wert auf 0 setzt, dann ist dieses Feature deaktiviert.
</li><br>
<li><a href="#fheminfo">sendStatistics</a><br>

View File

@ -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;