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

+ + +=end html +=begin html_DE + + +

restore

+ + + +=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";