diff --git a/fhem/CHANGED b/fhem/CHANGED
index 31c80a670..6641e80c5 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: update rewritten, restore added
- feature: enabled JavaScript in 02_RSS to support WebViewControl
- added: new module 36_WMBUS.pm (kaihs) Wireless M-Bus
- feature: SYSMON: aded new plots (power infos for cubietruck)
diff --git a/fhem/FHEM/98_fheminfo.pm b/fhem/FHEM/98_fheminfo.pm
index 1237be08b..063c6743d 100644
--- a/fhem/FHEM/98_fheminfo.pm
+++ b/fhem/FHEM/98_fheminfo.pm
@@ -217,7 +217,7 @@ CommandFheminfo($$)
$ret = $str;
if(@args != 0 && $args[0] eq "send") {
- my $uri = "http://fhem.de/stats/statistics.cgi";
+ my $uri = "http://fhem.de/stats/statistics.html";
my $req = HTTP::Request->new("POST",$uri);
$req->content_type("application/x-www-form-urlencoded");
my $contInfo;
@@ -410,7 +410,7 @@ sub _myDiv($$) {
The optional parameter send
transmitts the collected data
to a central server in order to support the development of FHEM. The
transmitted data is processed graphically. The results can be viewed
- on http://fhem.de/stats/statistics.cgi.
+ on http://fhem.de/stats/statistics.html.
Based on the IP address, the approximate location is determined with
an accuracy of about 40-80 km. The IP address is not saved.
@@ -530,7 +530,7 @@ sub _myDiv($$) {
Der optionale Parameter send
überträgt die Informationen
an einen zentralen Server um die Entwicklung von FHEM zu unterstützen.
Die übermittelten Daten werden grafisch aufbereitet und können auf
- http://fhem.de/stats/statistics.cgi
+ http://fhem.de/stats/statistics.html
abgerufen werden. Anhand der IP-Adresse wird der ungefähre Standort mit
einer Genauigkeit von ca. 40-80 km ermittelt. Die IP-Adresse wird nicht gespeichert.
diff --git a/fhem/FHEM/98_restore.pm b/fhem/FHEM/98_restore.pm
new file mode 100644
index 000000000..4f9780dba
--- /dev/null
+++ b/fhem/FHEM/98_restore.pm
@@ -0,0 +1,123 @@
+################################################################
+# $Id$
+
+package main;
+use strict;
+use warnings;
+use File::Copy qw(cp);
+
+sub CommandUpdate($$);
+sub restoreFile($$);
+sub restoreDir($$);
+
+########################################
+sub
+restore_Initialize($$)
+{
+ my %hash = (
+ Fn => "CommandRestore",
+ Hlp => "[list] [],restore files saved by update",
+ );
+ $cmds{restore} = \%hash;
+}
+
+########################################
+sub
+CommandRestore($$)
+{
+ my ($cl,$param) = @_;
+ my @args = split(/ +/,$param);
+ my $list = (@args > 0 && $args[0] eq "list");
+ shift @args if($list);
+ my $filename = shift @args;
+ my $dest = $attr{global}{modpath};
+ my $src = "$dest/restoreDir";
+
+ $list = 1 if(!$list && !$filename);
+ return "Usage: restore [list] filename|directory"
+ if(@args);
+
+ $filename = "" if(!$filename);
+ $filename =~ s/\.\.//g;
+
+ return "restoreDir is not yet created" if(!-d $src);
+ return "list argument must be a directory" if($list && !-d "$src/$filename");
+ if($list) {
+ my $dh;
+ opendir($dh, "$src/$filename") || return "opendir $src/$filename: $!";
+ my @files = readdir($dh);
+ closedir($dh);
+ return "Available for restore".($filename ? " in $filename":"").":\n ".
+ join("\n ", sort grep { $_ ne "." && $_ ne ".." } @files);
+ }
+
+ return "$filename is not available for restore" if(!-e "$src/$filename");
+
+ $filename .= "/" if($filename !~ m,/,); # needed for the regexp below
+ $filename =~ m,^([^/]*)/(.*)$,;
+ $src = "$src/$filename";
+ $dest = "$dest/$2" if($2);
+
+ return (-f $src ? restoreFile($src, $dest) : restoreDir($src, $dest));
+}
+
+sub
+restoreFile($$)
+{
+ my ($src, $dest) = @_;
+ cp($src, $dest) || return "cp $src $dest failed: $!";
+ return "restore $dest";
+}
+
+sub
+restoreDir($$)
+{
+ my ($src, $dest, $dh, @ret) = @_;
+ opendir($dh, $src) || return "opendir $src: $!";
+ my @files = sort grep { $_ ne "." && $_ ne ".." } readdir($dh);
+ closedir($dh);
+ foreach my $f (@files){
+ if(-d "$src/$f") {
+ push @ret, restoreDir("$src/$f", "$dest/$f");
+ } else {
+ push @ret, restoreFile("$src/$f", "$dest/$f");
+ }
+ }
+ return join("\n", @ret);
+}
+
+1;
+
+=pod
+=begin html
+
+
+restore
+
+ restore list []
+ restore []
+
+ Restore the files saved previously by the update command. Check the available
+ files with the list argument. See also the update command and its restoreDirs
+ attribute. After a restore normally a "shutdown restart" is necessary.
+
+
+=end html
+=begin html_DE
+
+
+restore
+
+ restore list []
+ restore
+
+ Restauriert die beim update gesicherten Dateien. Mit dem Argument list kann
+ man die Liste der verf&ügbaeren Sicherungen anzeigen, und mit der Angabe
+ der direkten Datei/Verzeichnis kann man das zurücksichern anstossen.
+ Siehe auch das update Befehl, bzw. das restoreDirs Attribut.
+ Nach restore ist meistens ein "shutdown restart" notwendig.
+
+
+
+=end html_DE
+=cut
diff --git a/fhem/FHEM/98_update.pm b/fhem/FHEM/98_update.pm
index 7db8193d1..c8d258fbe 100644
--- a/fhem/FHEM/98_update.pm
+++ b/fhem/FHEM/98_update.pm
@@ -1,64 +1,34 @@
################################################################
# $Id$
-# vim: ts=2:et
-################################################################
-#
-# (c) 2012 Copyright: Martin Fischer (m_fischer at gmx dot de)
-# All rights reserved
-#
-# This script is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# The GNU General Public License can be found at
-# http://www.gnu.org/copyleft/gpl.html.
-# A copy is found in the textfile GPL.txt and important notices to the license
-# from the author is found in LICENSE.txt distributed with these scripts.
-#
-# This script is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-################################################################
+
package main;
use strict;
use warnings;
use HttpUtils;
-use File::Copy qw(cp mv);
+use File::Copy qw(mv);
use Blocking;
-my %UPDATE;
-
sub CommandUpdate($$);
-sub update_CheckNotice($$$);
-sub update_CheckUpdates($$$$$);
-sub update_CleanUpLocalFiles($$$);
-sub update_DoUpdate(@);
-sub update_DoHousekeeping($$);
-sub update_GetRemoteFiles($$$$);
-sub update_ListChanges($$$);
-sub update_MakeDirectory($);
-sub update_ParseControlFile($$$$);
-sub update_WriteLocalControlFile($$$$$);
+sub upd_getUrl($);
+sub upd_initRestoreDirs($);
+sub upd_mkDir($$$);
+sub upd_rmTree($);
+sub upd_writeFile($$$$);
+
+my $updateInBackground;
+my $updRet;
+my %updDirs;
########################################
sub
update_Initialize($$)
{
- $UPDATE{server} = "http://fhem.de";
- $UPDATE{path} = "fhemupdate4";
- $UPDATE{packages} = "FHEM";
- $UPDATE{FHEM}{control} = "controls_fhem.txt";
-
my %hash = (
Fn => "CommandUpdate",
- Hlp => "[development|stable] [|check|fhem],update Fhem",
+ Hlp => "[|all|check|force] [http://.../controlfile],update FHEM",
);
$cmds{update} = \%hash;
-
}
########################################
@@ -66,153 +36,42 @@ sub
CommandUpdate($$)
{
my ($cl,$param) = @_;
- my $modpath = (-d "updatefhem.dir" ? "updatefhem.dir":$attr{global}{modpath});
- my $moddir = "$modpath/FHEM";
- my $srcdir = "";
- my $update = "";
- my $force = 0;
- my $ret = "";
-
- # split arguments
my @args = split(/ +/,$param);
+ my $arg = (defined($args[0]) ? $args[0] : "all");
+ my $src = (defined($args[1]) ? $args[1] :
+ "http://fhem.de/fhemupdate/controls_fhem.txt");
- # set default trunk
- my $BRANCH = (!defined($attr{global}{updatebranch}) ? "DEVELOPMENT" : uc($attr{global}{updatebranch}));
- if ($BRANCH ne "STABLE" && $BRANCH ne "DEVELOPMENT") {
- $ret = "global attribute 'updatebranch': unknown keyword: '$BRANCH'. Keyword should be 'STABLE' or 'DEVELOPMENT'";
- Log 1, "update $ret";
- return "$ret";
- }
+ my $ret = eval { "Hello" =~ m/$arg/ };
+ return "first argument must be a valid regexp, all, force or check"
+ if($arg =~ m/^\?/ || $arg =~ m/^\*/ || $ret);
- if (!defined($args[0])) {
- push(@args,$BRANCH);
- } elsif (uc($args[0]) ne "STABLE" && uc($args[0]) ne "DEVELOPMENT" && uc($args[0]) ne "THIRDPARTY") {
- unshift(@args,$BRANCH);
- } elsif (uc($args[0]) eq "STABLE" || uc($args[0]) eq "DEVELOPMENT" || uc($args[0]) eq "THIRDPARTY") {
- $args[0] = uc($args[0]);
- $BRANCH = uc($args[0]);
- }
+ $updateInBackground = AttrVal("global","updateInBackground",undef);
+ $updateInBackground = 0 if($arg ne "all");
+ if($updateInBackground) {
+ CallFn($cl->{NAME}, "ActivateInformFn", $cl);
+ BlockingCall("doUpdateInBackground", {src=>$src,arg=>$arg});
+ return "Executing the update the background.";
- # check arguments
- if (defined($args[1]) && $args[1] eq "?") {
- # return complete usage
- $ret = "Usage:\n";
- $ret .= "=> FHEM update / check for updates:\n";
- $ret .= "\tupdate [development|stable] [] [force]\n";
- $ret .= "\tupdate [development|stable] check\n";
- $ret .= "\tupdate housekeeping\n";
- $ret .= "=> Third party package update / check for a package update:\n";
- $ret .= "\tupdate thirdparty [force]\n";
- $ret .= "\tupdate thirdparty check";
+ } else {
+ doUpdate($src, $arg);
+ my $ret = $updRet; $updRet = "";
return $ret;
- } elsif (int(@args) <= 2 && $BRANCH eq "THIRDPARTY") {
- # missing arguments for third party package
- $ret = "Usage:\n";
- $ret .= "\tupdate thirdparty [force]\n";
- $ret .= "\tupdate thirdparty check";
- return $ret;
-
- } elsif (int(@args) > 2) {
-
- if (grep (m/^HOUSEKEEPING$/i,@args)) {
- return "Usage: update housekeeping";
-
- } elsif (grep (m/^CHECK$/i,@args)) {
-
- if (($BRANCH eq "STABLE" ||
- $BRANCH eq "DEVELOPMENT") &&
- $BRANCH ne "THIRDPARTY"
- ) {
- return "Usage: update [development|stable] check";
-
- } elsif ($BRANCH eq "THIRDPARTY" &&
- (uc($args[1]) !~ m/^(HTTP|HTTPS):/ ||
- uc($args[3]) !~ "CHECK" ||
- int(@args) != 4)
- ) {
- return "Usage: update thirdparty check";
- }
-
- } elsif ($BRANCH eq "THIRDPARTY" &&
- (uc($args[1]) !~ m/^(HTTP|HTTPS):/ ||
- (int(@args) == 4 &&
- uc($args[3]) ne "FORCE") ||
- int(@args) > 4)
- ) {
- return "Usage: update thirdparty [force]";
-
- } elsif ($BRANCH ne "THIRDPARTY" &&
- (exists $UPDATE{uc($args[1])} &&
- (int(@args) == 3 &&
- uc($args[2]) ne "FORCE") ||
- int(@args) > 3)
- ) {
- return "Usage: update [development|stable] [force]";
- }
-
}
+}
- # set default update
- if (!defined($args[1]) ||
- (defined($args[1]) &&
- (uc($args[1]) eq "FORCE") ||
- uc($args[1]) eq "HOUSEKEEPING")) {
- $update = "FHEM";
- } elsif (exists $UPDATE{uc($args[1])}) {
- $update = uc($args[1]);
+sub
+uLog($$)
+{
+ my ($loglevel, $arg) = @_;
+ return if($loglevel > $attr{global}{verbose} || !defined($arg));
+
+ if($updateInBackground) {
+ Log 1, $arg;
} else {
- $update = $args[1];
+ Log $loglevel, $arg;
+ $updRet .= "$arg\n";
}
-
- # build sourcedir for update
- if ($BRANCH ne "THIRDPARTY") {
- # set path for fhem.de
- my $branch = lc($BRANCH);
- $branch = "SVN" if ($BRANCH eq "DEVELOPMENT");
- $srcdir = $UPDATE{path}."/".lc($branch);
- } else {
- # set path for 3rd-party
- $srcdir = $args[1];
- $update = $args[2];
- }
-
- # force update
- $force = 1 if (grep (m/^FORCE$/i,@args));
-
- if (defined($args[1]) && uc($args[1]) eq "CHECK" ||
- $BRANCH eq "THIRDPARTY" && defined($args[3]) && uc($args[3]) eq "CHECK") {
- $ret = update_ListChanges($srcdir,$BRANCH,$update);
- } elsif (defined($args[1]) && uc($args[1]) eq "HOUSEKEEPING") {
- $ret = update_DoHousekeeping($BRANCH,$update);
- $ret = "nothing to do..." if (!$ret);
- } else {
- my $unconfirmed;
- my $notice;
- ($notice,$unconfirmed) = update_CheckNotice($BRANCH,$update,"before");
- $ret .= $notice if(defined($notice));
- return $ret if($unconfirmed);
-
- if(AttrVal("global","updateInBackground",undef)) {
- CallFn($cl->{NAME}, "ActivateInformFn", $cl);
- BlockingCall("update_DoUpdateInBackground", {srcdir=>$srcdir,
- BRANCH=>$BRANCH, update=>$update, force=>$force,cl=>$cl});
- $ret = "Executing the update the background.";
-
- } else {
- $ret .= update_DoUpdate($srcdir,$BRANCH,$update,$force,$cl);
- ($notice,$unconfirmed) = update_CheckNotice($BRANCH,$update,"after");
- $ret .= $notice if(defined($notice));
- my $sendStatistics = AttrVal("global","sendStatistics",undef);
- if(defined($sendStatistics) && lc($sendStatistics) eq "onupdate") {
- $ret .= "\n\n";
- $ret .= AnalyzeCommandChain(undef, "fheminfo send");
- }
- }
-
- }
-
- return $ret;
}
my $inLog = 0;
@@ -228,899 +87,286 @@ update_Log2Event($$)
}
sub
-update_DoUpdateInBackground($)
+doUpdateInBackground($)
{
my ($h) = @_;
no warnings 'redefine'; # The main process is not affected
*Log = \&update_Log2Event;
sleep(2); # Give time for ActivateInform / FHEMWEB / JavaScript
-
- my $ret = update_DoUpdate($h->{srcdir}, $h->{BRANCH}, $h->{update},
- $h->{force}, $h->{cl});
- my ($notice,$unconfirmed) =
- update_CheckNotice($h->{BRANCH}, $h->{update}, "after");
- $ret .= $notice if(defined($notice));
- if(lc(AttrVal("global","sendStatistics","")) eq "onupdate") {
- $ret .= "\n\n";
- $ret .= AnalyzeCommandChain(undef, "fheminfo send");
- }
+ doUpdate($h->{src}, $h->{arg});
}
-########################################
+
sub
-update_CheckNotice($$$)
+doUpdate($$)
{
- my ($BRANCH,$update,$position) = @_;
- my $modpath = (-d "updatefhem.dir" ? "updatefhem.dir":$attr{global}{modpath});
- my $moddir = "$modpath/FHEM";
- my $noticeDir = "$moddir/FhemUtils";
- my @notes = ("thirdparty", $update);
- my $ret;
- my $result;
-
- my @published;
- my @unconfirmed;
- my @confirmed;
-
- if ($BRANCH ne "THIRDPARTY") {
- $result = AnalyzeCommandChain(undef, "notice get update 6");
- @published = split(",",$result) if($result);
- $result = AnalyzeCommandChain(undef, "notice get update 7");
- @unconfirmed = split(",",$result) if($result);
- $result = AnalyzeCommandChain(undef, "notice get update 8");
- @confirmed = split(",",$result) if($result);
- } else {
- foreach my $note (@notes) {
- $result = AnalyzeCommandChain(undef, "notice get $note 6");
- push(@published, split(",",$result)) if($result);
- $result = AnalyzeCommandChain(undef, "notice get $note 7");
- push(@unconfirmed, split(",",$result)) if($result);
- $result = AnalyzeCommandChain(undef, "notice get $note 8");
- push(@confirmed, split(",",$result)) if($result);
- }
+ my ($src, $arg) = @_;
+ my ($basePath, $ctrlFileName);
+ if($src !~ m,^(.*)/([^/]*)$,) {
+ uLog 1, "Cannot parse $src, probably not a valid http control file";
+ return;
}
+ $basePath = $1;
+ $ctrlFileName = $2;
- # remove confirmed from published
- my %c;
- @c{@confirmed} = undef;
- @published = grep {not exists $c{$_}} @published;
-
- my @merged = (@published,@unconfirmed);
- my %unique = ();
- foreach my $notice (@merged) {
- $unique{$notice} ++;
- }
- my @noticeList = keys %unique;
-
- if(@noticeList) {
- foreach my $notice (sort @noticeList) {
- my $sendStatistics;
- my $condition = AnalyzeCommandChain(undef, "notice condition $notice");
- if($condition) {
- my @conditions = split(/\|/,$condition);
- foreach my $pair (@conditions) {
- my ($key,$val,$con) = split(":",$pair);
- if(lc($key) eq "sendstatistics") {
- # skip this message if attrib sendStatistics is already defined
- $sendStatistics = AttrVal("global","sendStatistics",undef);
- }
- }
- }
- next if(defined($sendStatistics) && $sendStatistics ne "");
- my $cmdret = AnalyzeCommandChain(undef, "notice position $notice");
- if( defined($cmdret) && lc($cmdret) eq lc($position) ||
- ( defined($cmdret) && grep (m/^$notice$/,@unconfirmed) && lc($cmdret) eq "after") ||
- (!defined($cmdret) && grep (m/^$notice$/,@unconfirmed) && $position eq "before") ||
- (!defined($cmdret) && !grep (m/^$notice$/,@unconfirmed) && $position eq "after") ) {
- $ret .= "\n==> Message-ID: $notice\n";
- my $noticeDE = AnalyzeCommandChain(undef, "notice view $notice noheader de");
- my $noticeEN = AnalyzeCommandChain(undef, "notice view $notice noheader en");
- if($noticeDE && $noticeEN) {
- # $ret .= "(English Translation: Please see below.)\n\n";
- $ret .= $noticeDE;
- $ret .= "~~~~~~~~~~\n";
- $ret .= $noticeEN;
- } elsif($noticeDE) {
- $ret .= $noticeDE;
- } elsif($noticeEN) {
- $ret .= $noticeEN;
- } else {
- $ret .= "==> Message file is corrupt. Please report this!\n"
- }
- }
- }
- if(@unconfirmed) {
- $ret .= "==> Action required:\n\n";
- if($position eq "before") {
- $ret .= " There is at least one unconfirmed message. Before updating FHEM\n";
- $ret .= " these messages have to be confirmed first:\n";
- } else {
- $ret .= " There is at least one unconfirmed message. You have to confirm\n";
- $ret .= " these messages before you can update FHEM again.\n";
- }
- foreach my $notice (@unconfirmed) {
- $ret .= " ID: $notice\n";
- Log 1,"update Action required: please run 'notice view $notice'";
- }
- $ret .= "\n";
- $ret .= " To view a message (again), please enter 'notice view '.\n";
- $ret .= " To confirm a message, please enter 'notice confirm [value]'.\n";
- $ret .= " '[value]' is an optional argument. Please refer to the message,\n";
- $ret .= " whether the disclosure of '[value]' is necessary.\n\n";
- $ret .= " For further information please consult the manual for the command\n";
- $ret .= " 'notice' in the documentation of FHEM (commandref.html).";
- if($position eq "before") {
- $ret .= "\n\n";
- $ret .= " The update is canceled for now.";
- }
- }
- } else {
- $ret = undef;
- }
- return ($ret,(@unconfirmed) ? 1 : 0);
-}
-
-########################################
-sub
-update_DoUpdate(@)
-{
- my ($srcdir,$BRANCH,$update,$force,$cl) = @_;
- my $modpath = (-d "updatefhem.dir" ? "updatefhem.dir":$attr{global}{modpath});
- my $moddir = "$modpath/FHEM";
- my $server = $UPDATE{server};
- my $uri;
- my $url;
- my $controlsTxt;
- my @packages;
- my $fail;
- my $ret = "";
-
- if ($BRANCH ne "THIRDPARTY") {
- # set packages
- @packages = split(" ",uc($UPDATE{packages}));
- } else {
- # set packages
- @packages = ($update);
- }
-
- # get list of files
- my $rControl_ref = {};
- my $lControl_ref = {};
- foreach my $pack (@packages) {
- if ($BRANCH ne "THIRDPARTY") {
- $controlsTxt = $UPDATE{$pack}{control};
- $uri = "$server/$srcdir/$controlsTxt";
- $url = "$server/$srcdir";
- } else {
- $controlsTxt = "controls_$pack.txt";
- $uri = "$srcdir/$controlsTxt";
- $server = $srcdir;
- $url = $server;
- }
- Log 3, "update get $uri";
- my $controlFile = GetFileFromURL($uri);
- return "Can't get '$controlsTxt' from $server" if (!$controlFile);
- # parse remote controlfile
- ($fail,$rControl_ref) = update_ParseControlFile($pack,$controlFile,$rControl_ref,0);
- return "$fail\nUpdate canceled..." if ($fail);
-
- # parse local controlfile
- ($fail,$lControl_ref) = update_ParseControlFile($pack,"$moddir/$controlsTxt",$lControl_ref,1);
- return "$fail\nUpdate canceled..." if ($fail);
- }
-
- # Check for new / modified files
- my ($checkUpdates,$updateFiles_ref) = update_CheckUpdates($BRANCH,$update,$force,$lControl_ref,$rControl_ref);
- if (!keys %$updateFiles_ref) {
- return $checkUpdates;
- } else {
- $ret = $checkUpdates;
- }
-
- # save statefile
- $ret .= "\nSaving statefile: ";
- my $cmdret = WriteStatefile();
- if (!$cmdret) {
- Log 1, "update saving statefile";
- $ret .= "done\n\n";
- } else {
- Log 1, "update statefile: $cmdret";
- $ret .= "Something went wrong with statefile:\n$cmdret\n\n";
- }
-
- # do a backup first
- my $configfile = AttrVal("global", "configfile", "");
- my $doBackup = AttrVal("global", "backup_before_update", 1);
-
- if ($doBackup) {
+ if(AttrVal("global", "backup_before_update", 0)) {
my $cmdret = AnalyzeCommand(undef, "backup");
if ($cmdret !~ m/backup done.*/) {
- Log 1, "update Backup: The operation was canceled. Please check manually!";
- $ret .= "Something went wrong during backup:\n$cmdret\n";
- $ret .= "The operation was canceled. Please check manually!";
- return $ret;
+ uLog 1, "Something went wrong during backup: $cmdret";
+ uLog 1, "update was canceled. Please check manually!";
+ return;
}
- $ret .= "Backup:\n$cmdret\n";
+ uLog 1, "Backup: $cmdret";
}
- # get new / modified files
- my $getUpdates;
- ($fail,$getUpdates) = update_GetRemoteFiles($BRANCH,$url,$updateFiles_ref,$cl);
- $ret .= $getUpdates if($getUpdates);
- return $ret if($fail);
+ my $remCtrlFile = upd_getUrl($src);
+ return if(!$remCtrlFile);
+ my @remList = split("\n", $remCtrlFile);
+ uLog 4, "Got remote controlfile with ".int(@remList)." entries.";
- foreach my $pack (@packages) {
- # write local controlfile
- my $localControlFile = update_WriteLocalControlFile($BRANCH,$pack,$lControl_ref,$rControl_ref,$updateFiles_ref);
- $ret .= $localControlFile if ($localControlFile);
+ ###########################
+ # read in & digest the local control file
+ my $root = $attr{global}{modpath};
+ my $restoreDir = ($arg eq "check" ? "" : upd_initRestoreDirs($root));
+
+ my @locList;
+ if(($arg eq "check" || $arg eq "all") &&
+ open(FD, "$root/FHEM/$ctrlFileName")) {
+ @locList = map { $_ =~ s/[\r\n]//; $_ } ;
+ close(FD);
+ uLog 4, "Got local controlfile with ".int(@locList)." entries.";
}
- undef($updateFiles_ref);
-
- return $ret if($fail);
- if (uc($update) eq "FHEM" || $BRANCH eq "THIRDPARTY") {
- my $doHousekeeping = update_DoHousekeeping($BRANCH,$update);
- $ret .= $doHousekeeping if ($doHousekeeping);
+ my %lh;
+ foreach my $l (@locList) {
+ my @l = split(" ", $l, 4);
+ next if($l[0] ne "UPD");
+ $lh{$l[3]}{TS} = $l[1];
+ $lh{$l[3]}{LEN} = $l[2];
}
- $ret .= "\nUpdate completed!";
- return $ret;
-}
+ my @excl = split(" ", AttrVal("global", "exclude_from_update", ""));
-########################################
-sub
-update_DoHousekeeping($$)
-{
- my ($BRANCH,$update) = @_;
- my $modpath = (-d "updatefhem.dir" ? "updatefhem.dir":$attr{global}{modpath});
- my $moddir = "$modpath/FHEM";
- my $cleanup;
- my $pack;
- my $ret;
+ uLog 1, "List of new / modified files since last update:"
+ if($arg eq "check");
+ ###########################
+ # process the remote controlfile
+ my $nChanged = 0;
+ my $isSingle = ($arg ne "all" && $arg ne "force" && $arg ne "check");
+ foreach my $r (@remList) {
+ my @r = split(" ", $r, 4);
- # parse local controlfile
- my $controlsTxt;
- if ($BRANCH ne "THIRDPARTY") {
- $pack = uc($update);
- $controlsTxt = $UPDATE{$pack}{control};
- } else {
- $pack = $update;
- $controlsTxt = "controls_$pack.txt";
- }
-
- my $fail;
- my $lControl_ref;
- ($fail,$lControl_ref) = update_ParseControlFile($pack,"$moddir/$controlsTxt",$lControl_ref,1);
- return "$fail\nHousekeeping canceled..." if ($fail);
-
-
- my %lControl = %$lControl_ref;
-
- # first run: create directories
- foreach my $f (sort keys %{$lControl{$pack}}) {
- my $lCtrl = $lControl{$pack}{$f}{ctrl};
- my $str;
- next if ($lCtrl ne "DIR");
-
- if ($lCtrl eq "DIR") {
- $str = update_CleanUpLocalFiles($lCtrl,"$f","");
- if($str) {
- $cleanup .= "==> $str\n" ;
- Log 1, "update $str";
+ if($r[0] eq "MOV" && ($arg eq "all" || $arg eq "force")) {
+ if($r[1] =~ m+\.\.+ || $r[2] =~ m+\.\.+) {
+ uLog 1, "Suspicious line $r, aborting";
+ return 1;
}
+ upd_mkDir($root, $r[2], 0);
+ uLog 4, "mv $root/$r[1] $root/$r[2]";
}
- }
- # second run: move named files
- foreach my $f (sort keys %{$lControl{$pack}}) {
- my $lCtrl = $lControl{$pack}{$f}{ctrl};
- my $str;
- next if ($lCtrl ne "MOV");
-
- if ($lCtrl eq "MOV" && $f !~ /\*/) {
- $str = update_CleanUpLocalFiles($lCtrl,"$modpath/$f","$modpath/$lControl{$pack}{$f}{move}");
- $cleanup .= "==> $str\n";
- Log 1, "update $str";
+ next if($r[0] ne "UPD");
+ my $fName = $r[3];
+ if($fName =~ m+\.\.+) {
+ uLog 1, "Suspicious line $r, aborting";
+ return 1;
}
- }
- # third run: move glob
- foreach my $f (sort keys %{$lControl{$pack}}) {
- my $lCtrl = $lControl{$pack}{$f}{ctrl};
- my $str;
- next if ($lCtrl ne "MOV");
-
- if ($lCtrl eq "MOV" && $f =~ /\*/) {
- # get filename and path
- #my $fname = substr $f,rindex($f,'/')+1;
- my $fpath = substr $f,0,rindex($f,'/')+1;
-
- foreach my $file (<$modpath/$f>) {
- $str = update_CleanUpLocalFiles($lCtrl,"$file","$modpath/$lControl{$pack}{$f}{move}/");
- $cleanup .= "==> $str\n";
- Log 1, "update $str";
- }
+ my $isExcl;
+ foreach my $ex (@excl) {
+ $isExcl = 1 if($fName =~ m/$ex/);
}
- }
+ next if($isExcl);
- # last run: delete
- foreach my $f (sort keys %{$lControl{$pack}}) {
- my $lCtrl = $lControl{$pack}{$f}{ctrl};
- my $str;
- next if ($lCtrl ne "DEL");
+ if($isSingle) {
+ next if($fName !~ m/$arg/);
- if ($f =~ /\*/) {
- # get filename and path
- #my $fname = substr $f,rindex($f,'/')+1;
- my $fpath = substr $f,0,rindex($f,'/')+1;
-
- foreach my $file (<$modpath/$f>) {
- if ($lCtrl eq "DEL") {
- $str = update_CleanUpLocalFiles($lCtrl,"$file","");
- $cleanup .= "==> $str\n";
- Log 1, "update $str";
- }
- }
} else {
- if ($lCtrl eq "DEL") {
- $str = update_CleanUpLocalFiles($lCtrl,"$modpath/$f","");
- $cleanup .= "==> $str\n";
- Log 1, "update $str";
- }
+ next if($lh{$fName} &&
+ $lh{$fName}{TS} eq $r[1] &&
+ $lh{$fName}{LEN} eq $r[2]);
+
}
+
+ uLog 1, "$r[0] $fName";
+ $nChanged++;
+ next if($arg eq "check");
+
+ my $remFile = upd_getUrl("$basePath/$fName");
+ return if(!$remFile); # Error already reported
+
+ if(length($remFile) ne $r[2]) {
+ uLog 1, "$remFile is ".length($remFile)." bytes, not $r[2] as expected";
+ uLog 1, "aborting.";
+ return;
+ }
+
+ return if(!upd_writeFile($root, $restoreDir, $fName, $remFile));
+
}
- if ($cleanup) {
- $ret = "\nHousekeeping:\n$cleanup";
+ if($nChanged == 0) {
+ uLog 1, "nothing to do...";
+ return;
+ }
+
+ if($arg eq "check") {
+ my @lines = split(/[\r\n]/,upd_getUrl("$basePath/CHANGED"));
+ my $ret = "";
+ foreach my $line (@lines) {
+ next if($line =~ m/^#/);
+ last if($line eq "");
+ $ret .= $line."\n";
+ }
+ uLog 1, "\nList of last changes:\n".$ret;
+ return;
+ }
+
+ if($arg eq "all" || $arg eq "force") { # store the controlfile
+ return if(!upd_writeFile($root, $restoreDir,
+ "FHEM/$ctrlFileName", $remCtrlFile));
+ }
+
+ uLog(1, "");
+ uLog 1,
+ 'update finished, "shutdown restart" is needed to activate the changes';
+ my $ss = AttrVal("global","sendStatistics",undef);
+ if(!defined($ss)) {
+ uLog(1, "Please consider using the global attribute sendStatistics");
+ } elsif(defined($ss) && lc($ss) eq "onupdate") {
+ uLog(1, AnalyzeCommandChain(undef, "fheminfo send"));
}
- return $ret;
}
-########################################
sub
-update_CheckUpdates($$$$$)
+upd_mkDir($$$)
{
- my ($BRANCH,$update,$force,$lControl_ref,$rControl_ref) = @_;
- return "Wildcards are not ( yet ;-) ) allowed." if ($update =~ /(\*|\?)/);
-
- my @exclude;
- my $excluded = (!defined($attr{global}{exclude_from_update}) ? "" : $attr{global}{exclude_from_update});
- my $found = 0;
- my $pack;
- my $ret;
- my $singleFile = 0;
- my $search = lc($update);
- my @suggest;
- my %updateFiles = ();
-
- # select package
- if($BRANCH ne "THIRDPARTY") {
- $pack = "FHEM";
- $pack = uc($update) if ($force);
- } else {
- $pack = $update;
+ my ($root, $dir, $isFile) = @_;
+ if($isFile) { # Delete the file Component
+ $dir =~ m,^(.*)/([^/]*)$,;
+ $dir = $1;
}
-
- # build searchstring
- if (uc($update) ne "FHEM" && $BRANCH ne "THIRDPARTY") {
- $singleFile = 1;
- if ($update =~ m/^(\S+)\.(.*)$/) {
- $search = lc($1);
- if ($search =~ m/(\d+)_(.*)/) {
- $search = lc($2);
- }
+ 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 1, "MKDIR $root/".join("/", @p[0..$i]);
}
}
-
- # search for new / modified files
- my %rControl = %$rControl_ref;
- my %lControl = %$lControl_ref;
- foreach my $f (sort keys %{$rControl{$pack}}) {
- # skip housekeeping
- next if ($rControl{$pack}{$f}{ctrl} eq "DEL" ||
- $rControl{$pack}{$f}{ctrl} eq "DIR" ||
- $rControl{$pack}{$f}{ctrl} eq "MOV");
-
- # get remote filename
- my $fname = substr $f,rindex($f,'/')+1;
-
- # suggest filenames for given searchstring
- if ($singleFile && lc($fname) ne lc($update)) {
- if ($search && lc($fname) =~ m/$search/) {
- push(@suggest,$fname);
- }
- }
- # skip if some file was specified and the name does not match
- next if ($singleFile && $fname ne $update);
-
- if ($excluded =~ /$fname/) {
- $lControl{$pack}{$f}{ctrl} = "EXC";
- $lControl{$pack}{$f}{date} = (!defined($lControl{$pack}{$f}{date}) ?
- $rControl{$pack}{$f}{date} :
- $lControl{$pack}{$f}{date});
- $lControl{$pack}{$f}{size} = (!defined($lControl{$pack}{$f}{size}) ?
- $rControl{$pack}{$f}{size} :
- $lControl{$pack}{$f}{size});
- $lControl{$pack}{$f}{file} = (!defined($lControl{$pack}{$f}{file}) ?
- $rControl{$pack}{$f}{file} :
- $lControl{$pack}{$f}{file});
- Log 1, "update excluded by configuration: $fname";
- push(@exclude,$f);
- }
-
- if ($singleFile && $fname eq $update) {
- #if (!@exclude && $lControl{$pack}{$f}{date} &&
- # $rControl{$pack}{$f}{date} ne $lControl{$pack}{$f}{date}) {
- if (!@exclude && $lControl{$pack}{$f}{date}) {
- $updateFiles{$f}{file} = $fname;
- $updateFiles{$f}{size} = $rControl{$pack}{$f}{size};
- $updateFiles{$f}{date} = $rControl{$pack}{$f}{date};
- }
- $found = 1;
- last;
- }
-
- next if (!$force &&
- $lControl{$pack}{$f}{date} &&
- $rControl{$pack}{$f}{date} eq $lControl{$pack}{$f}{date});
-
- if ($excluded !~ /$fname/) {
- $updateFiles{$f}{file} = $fname;
- $updateFiles{$f}{size} = $rControl{$pack}{$f}{size};
- $updateFiles{$f}{date} = $rControl{$pack}{$f}{date};
- }
-
- $found = 1;
- }
-
-
- my $nothing = "nothing to do...";
- if ($found) {
- if (@exclude) {
- my $exc;
- foreach my $f (sort @exclude) {
- my $fname = substr $f,rindex($f,'/')+1;
- $exc .= "==> $fname\n" if ($rControl{$pack}{$f}{date} ne $lControl{$pack}{$f}{date});
- }
- if (!keys(%updateFiles)) {
- $ret .= $nothing;
- Log 1, "update $nothing";
- } else {
- if ($exc) {
- $ret = "\nFile(s) skipped for an update! Excluded by configuration:\n$exc";
- }
- }
- }
- } else {
- if ($singleFile && !@suggest) {
- $ret = "'$update' not found.";
- Log 1, "update $nothing";
- }
- if ($singleFile && @suggest) {
- $ret = "'$update' not found. Did you mean:\n";
- foreach my $f (sort @suggest) {
- if ($excluded =~ /$f/) {
- $ret .= "==> $f (excluded from updates)\n";
- } else {
- $ret .= "==> $f\n";
- }
- }
- $ret .= $nothing;
- Log 1, "update $nothing";
- }
- if (!$singleFile && !keys(%updateFiles)) {
- $ret .= $nothing;
- Log 1, "update $nothing";
- }
- }
- return ($ret,\%updateFiles);
}
-########################################
sub
-update_GetRemoteFiles($$$$)
+upd_getUrl($)
{
- my ($BRANCH,$url,$updateFiles_ref,$cl) = @_;
- my $modpath = (-d "updatefhem.dir" ? "updatefhem.dir":$attr{global}{modpath});
- my $moddir = "$modpath/FHEM";
- my $server = $UPDATE{server};
- my %diffSize = ();
- my $fail = 0;
- my $localFile;
- my $newFhem = 0;
- my $remoteFile;
- my @reloadModules;
- my $writeError;
- my $ret;
+ my ($url) = @_;
+ my ($err, $data) = HttpUtils_BlockingGet({ url=>$url });
+ if($err) {
+ uLog 1, $err;
+ return "";
+ }
+ if(length($data) == 0) {
+ uLog 1, "$url: empty file received";
+ return "";
+ }
+ return $data;
+}
- foreach my $f (sort keys %$updateFiles_ref) {
- $remoteFile = $f;
- $localFile = "$modpath/$f";
+sub
+upd_writeFile($$$$)
+{
+ my($root, $restoreDir, $fName, $content) = @_;
- if ($BRANCH ne "THIRDPARTY") {
- # mark for a restart of fhem.pl
- if ($f =~ m/fhem.pl$/) {
- $newFhem = 1;
- $localFile = $0 if (! -d "updatefhem.dir");
- $remoteFile = "$f.txt";
- }
- }
+ # copy the old file and save the new
+ upd_mkDir($root, $fName, 1);
+ upd_mkDir($root, "$restoreDir/$fName", 1) if($restoreDir);
+ if($restoreDir && -f "$root/$fName" &&
+ ! mv("$root/$fName", "$root/$restoreDir/$fName")) {
+ uLog 1, "mv $root/$fName $root/$restoreDir/$fName failed:$!, ".
+ "aborting the update";
+ return 0;
+ }
- # replace special char % in filename
- $remoteFile =~ s/%/%25/g;
+ my $rest = ($restoreDir ? "trying to restore the previous version and ":"").
+ "aborting the update";
+ if(!open(FD, ">$root/$fName")) {
+ uLog 1, "open $root/$fName failed: $!, $rest";
+ mv "$root/$restoreDir/$fName", "$root/$fName" if($restoreDir);
+ return 0;
+ }
+ print FD $content;
+ close(FD);
- # get remote filename
- my $fname = substr $f,rindex($f,'/')+1;
- my $fpath = $f;
- $fpath =~ s/$fname//g;
+ my $written = -s "$root/$fName";
+ if($written != length($content)) {
+ uLog 1, "writing $root/$fName failed: $!, $rest";
+ mv "$root/$restoreDir/$fName", "$root/$fName" if($restoreDir);
+ return;
+ }
- # get remote File
- Log 3, "update get $url/$remoteFile";
- my $fileContent = GetFileFromURL("$url/$remoteFile");
- my $fileLength = length($fileContent);
- my $ctrlLength = $updateFiles_ref->{$f}->{size};
+ return 1;
+}
- if ($fileLength ne $ctrlLength) {
- $diffSize{$fname}{filelength} = $fileLength;
- $diffSize{$fname}{ctrllength} = $ctrlLength;
- $diffSize{$fname}{updatefile} = $f;
- Log 1, "update skip '$fname'. Size does not correspond to " .
- "controlfile: $ctrlLength bytes download: $fileLength bytes";
+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 {
-
- # mark for a reload of a modified module if its in use
- if ($f =~ m/^.*(\d\d_)(.*).pm$/) {
- my $modFile = "$1$2";
- my $modName = $2;
- push(@reloadModules,$modFile) if ($modules{$modName} && $modules{$modName}{LOADED});
- }
-
- my $mkdir;
- $mkdir = update_MakeDirectory($fpath);
- $ret .= $mkdir if ($mkdir);
-
- next if ($mkdir);
-
- if (open (FH, ">$localFile")) {
- binmode FH;
- print FH $fileContent;
- close (FH);
- Log 5, "update write $localFile";
- $writeError .= cfgDB_FileUpdate($localFile) if(configDBUsed());
-
- } else {
- delete $updateFiles_ref->{$f};
- Log 1, "update Can't write $localFile: $!";
- $writeError .= "Can't write $localFile: $!\n";
- }
-
+ uLog 4, "rm $dir/$f";
+ unlink("$dir/$f");
}
}
-
- if ($writeError) {
- $ret .= "\nFile(s) skipped for an update! Error while writing:\n";
- $ret .= "$writeError";
- }
-
- if (keys(%diffSize)) {
- $ret .= "\nFile(s) skipped for an update! Size does not correspond:\n";
- foreach my $f (sort keys(%diffSize)) {
- delete $updateFiles_ref->{$diffSize{$f}{updatefile}};
- $ret .= "==> $f: size from controlfile: $diffSize{$f}{ctrllength} bytes, " .
- "size after download: $diffSize{$f}{filelength} bytes\n";
- }
- }
-
- if (keys(%$updateFiles_ref)) {
- my $str = keys(%$updateFiles_ref)." file(s) have been updated";
- $ret .= "\n$str:\n";
- Log 1, "update $str.";
- foreach my $f (sort keys(%$updateFiles_ref)) {
- my ($date,$time) = split("_",$updateFiles_ref->{$f}->{date});
- $ret .= "==> $date $time $f\n";
- }
-
- if (!$newFhem && @reloadModules) {
- $ret .= "A new version of one or more module(s) was installed, 'shutdown restart' is required!";
- #$ret .= "\nModule(s) reloaded:\n";
- #foreach my $modFile (@reloadModules) {
- # my $cmdret = CommandReload($cl,$modFile);
- # if (!$cmdret) {
- # Log 1, "update reloaded module: $modFile";
- # $ret .= "==> $modFile\n";
- # } else {
- # $ret .= "==> $modFile:\n$cmdret\n";
- # }
- #}
- }
-
- if ($newFhem && $BRANCH ne "THIRDPARTY") {
- my $str = "A new version of fhem.pl was installed, 'shutdown restart' is required!";
- Log 1, "update $str";
- $ret .= "\n$str\n";
- }
-
- } else {
- my $str = "No files have been updated because one or more errors have occurred!";
- $fail = 1;
- $ret .= "\n$str\n";
- Log 1, "update $str";
- }
-
- return ($fail,$ret);
+ uLog 4, "rmdir $dir";
+ rmdir($dir);
}
-########################################
sub
-update_ListChanges($$$)
+upd_initRestoreDirs($)
{
- my ($srcdir,$BRANCH,$update) = @_;
- my $modpath = (-d "updatefhem.dir" ? "updatefhem.dir":$attr{global}{modpath});
- my $moddir = "$modpath/FHEM";
- my $excluded = (!defined($attr{global}{exclude_from_update}) ? "" : $attr{global}{exclude_from_update});
- my $fail;
- my $pack;
- my $server = $UPDATE{server};
- my $ret = "List of new / modified files since last update:\n";
- my $uri;
- my $localControlFile;
- my $changedFile;
+ my ($root) = @_;
- if($BRANCH ne "THIRDPARTY") {
- $pack = "FHEM";
- $uri = "$server/$srcdir/$UPDATE{$pack}{control}";
- $localControlFile = "$moddir/$UPDATE{$pack}{control}";
- $changedFile = "$server/$srcdir/CHANGED";
- } else {
- $pack = $update;
- $uri = "$srcdir/controls_$update.txt";
- $localControlFile = "$moddir/controls_$update.txt";
- $changedFile = "$srcdir/CHANGED";
+ 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);
- # get list of files
- Log 3, "update get $uri";
- my $controlFile = GetFileFromURL($uri);
- return "Can't get controlfile from $uri" if (!$controlFile);
+ my $rdName = "restoreDir";
+ my @t = localtime;
+ my $restoreDir = sprintf("$rdName/%04d-%02d-%02d",
+ $t[5]+1900, $t[4]+1, $t[3]);
+ upd_mkDir($root, $restoreDir, 0);
- # parse remote controlfile
- my $rControl_ref = {};;
- ($fail,$rControl_ref) = update_ParseControlFile($pack,$controlFile,$rControl_ref,0);
- return "$fail" if ($fail);
-
- # parse local controlfile
- my $lControl_ref = {};
- ($fail,$lControl_ref) = update_ParseControlFile($pack,$localControlFile,$lControl_ref,1);
- return "$fail" if ($fail);
-
- # Check for new / modified files
- my $str;
- my %rControl = %$rControl_ref;
- my %lControl = %$lControl_ref;
- foreach my $f (sort keys %{$rControl{$pack}}) {
- next if ($rControl{$pack}{$f}{ctrl} eq "DEL" ||
- $rControl{$pack}{$f}{ctrl} eq "DIR" ||
- $rControl{$pack}{$f}{ctrl} eq "MOV");
- next if ($lControl{$pack}{$f} &&
- $rControl{$pack}{$f}{date} eq $lControl{$pack}{$f}{date});
- $str = "$rControl{$pack}{$f}{ctrl} $f\n";
- if ($f !~ m/\*/) {
- my $ef = substr $f,rindex($f,'/')+1;
- if ($excluded =~ /$ef/) {
- $str = "--- $f (excluded from updates)\n";
- }
- }
- $ret .= $str;
+ if(!opendir(DH, "$root/$rdName")) {
+ uLog 1, "opendir $root/$rdName: $!";
+ return "";
}
-
- if (!$str) {
- $ret .= "nothing to do...";
- } else {
- # get list of changes
- $ret .= "\nList of last changes:\n";
- my $changed = GetFileFromURL($changedFile);
- if (!$changed || $changed =~ m/Error 404/g) {
- $ret .= "Can't get list of changes from $server";
- } else {
- my @lines = split(/\015\012|\012|\015/,$changed);
- foreach my $line (@lines) {
- next if($line =~ m/^#/);
- last if($line eq "");
- $ret .= $line."\n";
- }
- }
+ 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, "deleting: $dir";
+ upd_rmTree($dir);
}
-
- return $ret;
+
+ return $restoreDir;
}
-
-########################################
-sub
-update_WriteLocalControlFile($$$$$)
-{
- my ($BRANCH,$pack,$lControl_ref,$rControl_ref,$updateFiles_ref) = @_;
- my $modpath = (-d "updatefhem.dir" ? "updatefhem.dir":$attr{global}{modpath});
- my $moddir = "$modpath/FHEM";
- my $controlsTxt;
- if ($BRANCH ne "THIRDPARTY") {
- $controlsTxt = $UPDATE{$pack}{control};
- } else {
- $controlsTxt = "controls_$pack.txt";
- }
- return "Can't write $moddir/$controlsTxt: $!" if (!open(FH, ">$moddir/$controlsTxt"));
- Log 5, "update write $moddir/$controlsTxt";
- my %rControl = %$rControl_ref;
- my %lControl = %$lControl_ref;
- my %updFiles = %$updateFiles_ref;
-
- foreach my $f (sort keys %{$rControl{$pack}}) {
- my $ctrl = $rControl{$pack}{$f}{ctrl} if (defined($rControl{$pack}{$f}{ctrl}));
- my $date = $rControl{$pack}{$f}{date} if (defined($rControl{$pack}{$f}{date}));
- my $size = $rControl{$pack}{$f}{size} if (defined($rControl{$pack}{$f}{size}));
- my $file = $rControl{$pack}{$f}{file} if (defined($rControl{$pack}{$f}{file}));
- my $move = $rControl{$pack}{$f}{move} if (defined($rControl{$pack}{$f}{move}));
-
- if ($ctrl eq "UPD") {
- if (defined($lControl{$pack}{$f}{ctrl}) &&
- ($lControl{$pack}{$f}{ctrl} eq "EXC" || !exists $updFiles{$f})) {
- $date = defined($lControl{$pack}{$f}{date}) ? $lControl{$pack}{$f}{date} :
- $rControl{$pack}{$f}{date};
- $size = defined($lControl{$pack}{$f}{size}) ? $lControl{$pack}{$f}{size} :
- $rControl{$pack}{$f}{size};
- $file = defined($lControl{$pack}{$f}{file}) ? $lControl{$pack}{$f}{file} :
- $rControl{$pack}{$f}{file};
- }
- Log 5, "update $controlsTxt: $ctrl $date $size $file";
- print FH "$ctrl $date $size $file\n";
- }
-
- if ($ctrl eq "DIR") {
- Log 5, "update $controlsTxt: $ctrl $file";
- print FH "$ctrl $file\n";
- }
- if ($ctrl eq "MOV") {
- Log 5, "update $controlsTxt: $ctrl $file $move";
- print FH "$ctrl $file $move\n";
- }
-
- if ($ctrl eq "DEL") {
- Log 5, "update $controlsTxt: $ctrl $file";
- print FH "$ctrl $file\n";
- }
-
- }
- close(FH);
- return undef;
-}
-
-########################################
-sub
-update_ParseControlFile($$$$)
-{
- my ($pack,$controlFile,$control_ref,$local) = @_;
- my %control = %$control_ref if ($control_ref && ref($control_ref) eq "HASH");
- my $from = ($local ? "local" : "remote");
- my $ret;
-
- if ($local) {
- my $str = "";
- # read local controlfile in string
- if (open FH, "$controlFile") {
- $str = do { local $/; };
- }
- close(FH);
- $controlFile = $str
- }
-
- # parse file
- if ($controlFile) {
- foreach my $l (split("[\r\n]", $controlFile)) {
- chomp($l);
- Log 5, "update $from controls_".lc($pack).".txt: $l";
- my ($ctrl,$date,$size,$file,$move) = "";
- if ($l =~ m/^(UPD) (20\d\d-\d\d-\d\d_\d\d:\d\d:\d\d) (\d+) (\S+)$/) {
- $ctrl = $1;
- $date = $2;
- $size = $3;
- $file = $4;
- } elsif ($l =~ m/^(DIR) (\S+)$/) {
- $ctrl = $1;
- $file = $2;
- } elsif ($l =~ m/^(MOV) (\S+) (\S+)$/) {
- $ctrl = $1;
- $file = $2;
- $move = $3;
- } elsif ($l =~ m/^(DEL) (\S+)$/) {
- $ctrl = $1;
- $file = $2;
- } else {
- $ctrl = "ESC"
- }
- if ($ctrl eq "ESC") {
- Log 1, "update File 'controls_".lc($pack).".txt' ($from) is corrupt";
- $ret = "File 'controls_".lc($pack).".txt' ($from) is corrupt";
- }
- last if ($ret);
-# if ($local) {
-# next if ($l =~ m/^(DEL|MOV) /);
-# }
- $control{$pack}{$file}{ctrl} = $ctrl;
- $control{$pack}{$file}{date} = $date;
- $control{$pack}{$file}{size} = $size;
- $control{$pack}{$file}{file} = $file;
- $control{$pack}{$file}{move} = $move;
- }
- }
- return ($ret, \%control);
-}
-
-########################################
-sub
-update_CleanUpLocalFiles($$$)
-{
- my ($ctrl,$file,$move) = @_;
- my $modpath = (-d "updatefhem.dir" ? "updatefhem.dir":$attr{global}{modpath});
- my $ret;
-
- # make dir
- if ($ctrl eq "DIR") {
- my $mret = update_MakeDirectory($file);
- if ($mret) {
- $ret = "create directory $modpath/$file failed: $mret";
- }
- }
- # move file
- if ($ctrl eq "MOV") {
- my $mvret = mv "$file", "$move" if (-f $file);
- if ($mvret) {
- $ret = "moving $file to $move";
- } else {
- $ret = "moving $file to $move failed: $!";
- }
- }
- # delete file
- if ($ctrl eq "DEL") {
- unlink "$file" if (-f $file);
- if (!$!) {
- $ret = "deleting $file";
- } else {
- $ret = "deleting $file failed: $!";
- }
- }
-
- return $ret;
-}
-
-########################################
-sub
-update_MakeDirectory($)
-{
- my $fullPath = shift;
- my $modpath = (-d "updatefhem.dir" ? "updatefhem.dir":$attr{global}{modpath});
- my $error;
- my $dir;
- my $recPath = "";
- my $ret;
-
- foreach $dir (split(/\//, $fullPath)) {
- $recPath = "$recPath$dir/";
- if ($dir ne "") {
- if (! -d "$modpath/$recPath") {
- undef($!);
- mkdir "$modpath/$recPath";
- if($!) {
- $error .= "==> $modpath/$recPath: $!\n";
- Log 1, "update Error while creating: $modpath/$recPath: $!";
- }
- }
- }
- }
- if ($error) {
- $ret = "\nError while creating:\n$error";
- }
- return $ret;
-}
-
1;
=pod
@@ -1129,86 +375,81 @@ update_MakeDirectory($)
update
- FHEM update / check for updates:
+ update [<fileName>|all|check|force]
+ [http://.../controlfile]
- update [development|stable] [<file|package>] [force]
- update [development|stable] check
- update housekeeping
- Third party package update / check for a package update:
- update thirdparty <url> <package> [force]
- update thirdparty <url> <package> check
+ Update the FHEM installation. Technically this means update will download
+ http://fhem.de/fhemupdate/controls_fhem.txt first, compare it to the local
+ version in FHEM/controls_fhem.txt, and will download each file where the
+ attributes (timestamp and filelength) are different.
- The installed fhem distribution and its installed extensions (just like the
- webGUI PGM2) are updated via this command from the online repository. The
- locally installed files will be checked against the online repository and
- will be updated in case the files online are in a newer version.
-
-
- The update function will process more advanced distribution information
- as well as control commands for updating, removing or renaming existing files.
- New file structures can also be set up via control command files.
- The update process will exclusively work with the file path which is
- given by the global attribute "modpath" except for the fhem.pl file. The user
- decides whether to use a stable, or a developer-rated version of fhem.
- stable is not yet implemented, so an update use always the developer-rated version.
-
-
- Furthermore, the use of packages is supported just like in a manual installation
- of fhem. On the moment this only refers to FHEM including PGM2 (FHEMWEB), others
- may follow up. By using the update in this way, only files which are acutally
- used will be updated.
-
-
- The update function supports the installation of third-party packages like
- modules or GUIs that are not part of the FHEM distribution.
-
-
- Notice for Developers of third-party packages:
-
- Further information can be obtained from the file 'docs/LIESMICH.update-thirdparty'.
-
-
- Examples:
- Check for new updates:
- fhem> update check
-
+ Notes:
+
+ - The contrib directory will not be updated.
+ - The files are automatically transferred from the source repository
+ (SVN) to the web site once a day, at 7:45 CET / CEST.
+ - The all argument is default.
+ - The force argument will disregard the local file.
+ - The check argument will only display the files it would download, and
+ the last section of the CHANGED file.
+ - Specifying a filename will only download matching files (regexp).
+
+ See also the restore command.
+
+ Examples:
+
+ - update check
+ - update
+ - update force
+ - update check http://fhem.de/fhemupdate/controls_fhem.txt
+
+
- FHEM update:
- fhem> update
-
+
+ Attributes (use attr global ...)
+
+
+ - updateInBackground
+ If this attribute is set (to 1), the update will be executed in a
+ background process. The return message is communicated via events, and
+ in telnet the inform command is activated, in FHEMWEB the Event
+ Monitor.
+
- Force FHEM update (all files are updated!):
- fhem> update force
-
+
+ - backup_before_update
+ If this attribute is set, an update will back up your complete
+ installation via the backup command. The default
+ is not set as update relies on the restore feature (see below).
+ Example:
+
+ attr global backup_before_update
+
+
- Update a single file:
- fhem> update 98_foobar.pm
-
+
+ - exclude_from_update
+ Contains a space separated list of fileNames (regexps) which will be
+ excluded by an update.
+ Example:
+
+ attr global exclude_from_update 21_OWTEMP.pm FS20.off.png
+
+
- Search for a filename:
- fhem> update backup
- 'backup' not found. Did you mean:
- ==> 98_backup.pm
- nothing to do...
-
+
+ - 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.
+
- Update / install a third-party package:
- fhem> update thirdparty http://domain.tld/path packagename
-
- Check a third-party package for new updates:
- fhem> update thirdparty http://domain.tld/path packagename check
-
-
-
- Attributes
-
@@ -1218,91 +459,87 @@ update_MakeDirectory($)
update
- FHEM aktualisieren / auf Aktualisierungen prüfen:
+ update [<fileName>|all|check|force]
+ [http://.../controlfile]
- update [development|stable] [<Date|Paket>] [force]
- update [development|stable] check
- update housekeeping
- Aktualisierung eines Fremdpaketes / Fremdpaket auf Aktualisierungen prüfen:
- update thirdparty <url> <Paketname> [force]
- update thirdparty <url> <Paketname> check
+ Erneuert die FHEM Installation. D.h. es wird zuerst die Datei
+ http://fhem.de/fhemupdate/controls_fhem.txt heruntergeladen, mit der lokalen
+ Version dieser Datei (FHEM/controls_fhem.txt) verglichen. Danach werden
+ alle Programmdateien heruntergeladen, deren Größe oder Zeitstempel
+ sich unterscheidet.
- Die installierte FHEM Distribution und deren Erweiterungen (z.B. das Web-Interface
- PGM2 (FHEMWEB)) werden mit diesem Befehl über ein Online Repository aktualisiert.
- Enthält das Repository neuere Dateien oder Dateiversionen als die lokale
- Installation, werden die aktualisierten Dateien aus dem Repository installiert.
-
-
- Die Update-Funktion unterstützt erweiterte Distributions Informationen sowie
- die Steuerung über spezielle Befehle zum aktualisieren, verschieben oder
- umbenennen von bestehenden Dateien. Neue Verzeichnisstrukturen können ebenfalls
- über die Aktualisierung erzeugt werden. Die Aktualisierung erfolgt exklusiv
- im Verzeichnispfad der über das globale Attribut "modpath" gesetzt wurde
- (Ausnahme: fhem.pl). Der Anwender kann die Aktualisierung wahlweise
- aus dem stabilen (stable) oder Entwicklungs-Zweig (development) vornehmen lassen.
- Ein Aktualisierung erfolgt zur Zeit ausschliesslich nur über den
- Entwicklungs-Zweig (development).
-
-
- Weiterhin unterstützt der update Befehl die manuelle Installation von Paketen
- die Bestandteil der FHEM Distribution sind. Aktuell werden noch keine erweiterten FHEM
- Pakete bereitgestellt.
-
-
- Der update Befehl unterstützt auch die Installation von Paketen die kein Bestandteil
- der FHEM Distribution sind. Diese Pakete können z.B. von Entwicklern angebotene
- Module oder Benutzerinterfaces beinhalten.
-
-
- Hinweis für Anbieter von zusätzlichen Paketen:
-
- Weiterführende Informationen zur Bereitstellung von Paketen sind der Datei
- 'docs/LIESMICH.update-thirdparty' zu entnehmen.
-
-
- Beispiele:
- Auf neue Aktualisierungen prüfen:
- fhem> update check
-
+ Zu beachten:
+
+ - Das contrib Verzeichnis wird nicht heruntergeladen.
+ - Die Dateien werden auf der Webseite einmal am Tag um 07:45 MET/MEST aus
+ der Quell-Verwaltungssystem (SVN) bereitgestellt.
+ - Das all Argument ist die Voreinstellung.
+ - Das force Argument beachtet die lokale controls_fhem.txt Datei
+ nicht.
+ - Das check Argument zeigt die neueren Dateien an, und den letzten
+ Abschnitt aus der CHANGED Datei
+ - Falls man <fileName> spezifiziert, dann werden nur die Dateien
+ heruntergeladen, die diesem Regexp entsprechen.
+
+ Siehe also das restore Befehl.
+
+ Beispiele:
+
+ - update check
+ - update
+ - update force
+ - update check http://fhem.de/fhemupdate/controls_fhem.txt
+
+
- FHEM aktualisieren:
- fhem> update
-
+
+ Attribute (sind mit attr global zu setzen)
+
+
+ - updateInBackground
+ Wenn dieses Attribut gesetzt ist, wird das update Befehl in einem
+ separaten Prozess ausgeführt, und alle Meldungen werden per Event
+ übermittelt. In der telnet Sitzung wird inform, in FHEMWEB wird
+ das Event Monitor aktiviert.
+
- FHEM Aktualisierung erzwingen (alle Dateien werden aktualisiert!):
- fhem> update force
-
+
+ - backup_before_update
+ Wenn dieses Attribut gesetzt ist, erstellt FHEM eine Sicherheitskopie
+ der FHEM Installation vor dem update mit dem backup Befehl. Die
+ Voreinstellung is "nicht gesetzt", da update sich auf das restore
+ Feature verlässt, s.u.
+ Beispiel:
+
+ attr global backup_before_update
+
+
- Eine einzelne Datei aktualisieren:
- fhem> update 98_foobar.pm
-
+
+ - exclude_from_update
+ Enthält eine Liste durch Leerzeichen getrennter Dateinamen
+ (regexp), welche nicht im update berücksichtigt werden.
+ Beispiel:
+
+ attr global exclude_from_update 21_OWTEMP.pm temp4hum4.gplot
+
+
- Nach einem Dateinamen suchen:
- fhem> update backup
- 'backup' not found. Did you mean:
- ==> 98_backup.pm
- nothing to do...
-
+ - 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.
+
- Aktualisierung oder Installation eines Fremdpaketes:
- fhem> update thirdparty http://domain.tld/path paketname
-
-
- Fremdpaket auf neue Aktualisierungen prüfen:
- fhem> update thirdparty http://domain.tld/path paketname check
-
-
-
- Attribute
-
+
=end html_DE
=cut
diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html
index 9f74aa160..623b9a455 100644
--- a/fhem/docs/commandref_frame.html
+++ b/fhem/docs/commandref_frame.html
@@ -61,6 +61,7 @@
quit
reload
rename
+ restore
rereadcfg
save
set
@@ -1103,27 +1104,6 @@ A line ending with \ will be concatenated with the next one, so long lines
receiving a corresponding message.
-
- - updateInBackground
- If this attribute is set to 1, the update will be executed in the
- backgrund process. The return message is communicated via events, and
- in telnet the inform command is activated, in FHEMWEB the Event
- Monitor.
-
-
-
- - backup_before_update
- If this attribute is set to 0, an update skip always backing up your
- installation via the backup command. The default
- is to backup always before updates.
- Note: Set this attribute only if you know what you do!
- This Attribute is used by the update command.
- Example:
-
- attr global backup_before_update 0
-
-
-
- backupcmd
You could pass the backup to your own command / script by using this attribute.
@@ -1174,16 +1154,6 @@ A line ending with \ will be concatenated with the next one, so long lines
be written to this file.
-
- - exclude_from_update
- Contains a space separated list of file which will be excluded by an update.
- This Attribute is used by the update command.
- Example:
-
- attr global exclude_from_update 21_OWTEMP.pm temp4hum4.gplot FS20.on.png FS20.off.png
-
-
-
- holiday2we
If this attribute is set, then the $we variable
@@ -1275,25 +1245,6 @@ A line ending with \ will be concatenated with the next one, so long lines
- uniqueID
-
-
- updatebranch
- The update branch will be set by the file FhemUtils/release.pm contained
- in the modpath. For example, if a stable version (version 5.3 upwards) of
- fhem is installed via a direct download connection of the archieve on the
- fhem-website, then the branch of the update is automatically on "stable".
- In this branch, only updates fixing confirmed errors, relevant security
- fixes or new stable versions are provided.
- By using the command "update development <filename>", particular files
- or packages can always be installed directly from the development branch
- (e.g. "update development <package>").
- If you want to update from the development branch in stable verion in
- general, you can force this behaviour by using the attribute "updatebranch DEVELOPMENT".
- In case the installation of fhem should generally using the development
- branch, this attribute would not have to be set. Instead, use "update development force"
- to update all files including release.pm (containing the release-information)
- to the newest version.
-
-
- userattr
A space separated list which contains the names of additional
diff --git a/fhem/docs/commandref_frame_DE.html b/fhem/docs/commandref_frame_DE.html
index f3c692fc8..bfc4b81ad 100644
--- a/fhem/docs/commandref_frame_DE.html
+++ b/fhem/docs/commandref_frame_DE.html
@@ -60,6 +60,7 @@
reload
rename
rereadcfg
+ restore
save
set
setdefaultattr
@@ -1159,28 +1160,6 @@ Zeilen erstreckende Befehle, indem man keine \ am Zeilenende eingeben muss.
Nachricht zu erstellen.
-
- updateInBackground
- wenn dieses Attribut gesetzt ist, wird das update Befehl in einem
- separaten Prozess ausgeführt, und alle Meldungen werden per Event
- übermittelt. In der telnet Sitzung wird inform, in FHEMWEB wird
- das Event Monitor aktiviert.
-
-
-
- backup_before_update
- Wenn dieses Attribut auf "0" gesetzt wurde, erstellt FHEM keine
- Sicherheitskopie Ihrer Installation bei Verwendung des Befehls backup.
- Die Standardeinstellung ist die Erstellung einer Sicherheitskopie vor einem
- Update.
- Hinweis: Setzen Sie dieses Attribut nur wenn Sie sich sicher sind!
- Das Attribut wird vom update Befehl benutzt.
- Beispiel:
-
- attr global backup_before_update 0
-
-
-
backupcmd
Sie können das Update durch Ihre eigenen Befehle/Skripts durchführen
@@ -1234,17 +1213,6 @@ Zeilen erstreckende Befehle, indem man keine \ am Zeilenende eingeben muss.
Dateinamen gespeichert.
-
- exclude_from_update
- Enthält eine Liste durch Leerzeichen getrennter Dateien welche nicht im
- Update berücksichtigt werden. Dieses Attribut wird vom update
- Befehl benutzt.
- Beispiel:
-
- attr global exclude_from_update 21_OWTEMP.pm temp4hum4.gplot FS20.on.png FS20.off.png
-
-
-
holiday2we
Wenn dieses Attribut gesetzt wurde, dann wird die $we
diff --git a/fhem/fhem.pl b/fhem/fhem.pl
index a151984b6..27e90d9ac 100755
--- a/fhem/fhem.pl
+++ b/fhem/fhem.pl
@@ -244,7 +244,7 @@ $modules{Global}{AttrList} =
"mseclog:1,0 version nofork:1,0 logdir holiday2we " .
"autoload_undefined_devices:1,0 dupTimeout latitude longitude altitude " .
"backupcmd backupdir backupsymlink backup_before_update " .
- "exclude_from_update motd updatebranch uniqueID ".
+ "exclude_from_update motd restoreDirs uniqueID ".
"sendStatistics:onUpdate,manually,never updateInBackground:1,0 ".
"showInternalValues:1,0 ";
$modules{Global}{AttrFn} = "GlobalAttr";