diff --git a/fhem/CHANGED b/fhem/CHANGED index 9d4ced557..d51e17d1d 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # 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. + - new: 42_AptToDate: Modul to retrieves apt information about Debian + update state - bugfix: 74_NUKIDevice: fix Perl Warnings - feature: 88_HMCCU: Support for channel number in attribute ccuscaleval - change: 98_DOIFtools: more precise hint related to DOELSE (Forum #89260) diff --git a/fhem/FHEM/42_AptToDate.pm b/fhem/FHEM/42_AptToDate.pm new file mode 100644 index 000000000..2e98ee7bd --- /dev/null +++ b/fhem/FHEM/42_AptToDate.pm @@ -0,0 +1,992 @@ +############################################################################### +# +# Developed with Kate +# +# (c) 2017-2018 Copyright: Marko Oldenburg (leongaultier at gmail dot com) +# All rights reserved +# +# Special thanks goes to: +# - Dusty Wilson Inspiration and parts of Code +# http://search.cpan.org/~wilsond/Linux-APT-0.02/lib/Linux/APT.pm +# +# +# 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 +# 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. +# +# +# $Id$ +# +############################################################################### + + + + + +package main; + +my $missingModul = ""; + +use strict; +use warnings; +use POSIX; + +use Data::Dumper; #only for Debugging + +eval "use JSON;1" or $missingModul .= "JSON "; + + + + +my $version = "0.2.2"; + + + + +# Declare functions +sub AptToDate_Initialize($); +sub AptToDate_Define($$); +sub AptToDate_Undef($$); +sub AptToDate_Attr(@); +sub AptToDate_Set($$@); +sub AptToDate_Get($$@); +sub AptToDate_Notify($$); +sub AptToDate_ProcessUpdateTimer($); +sub AptToDate_CleanSubprocess($); +sub AptToDate_AsynchronousExecuteAptGetCommand($); +sub AptToDate_OnRun(); +sub AptToDate_PollChild($); +sub AptToDate_ExecuteAptGetCommand($); +sub AptToDate_GetDistribution($); +sub AptToDate_AptUpdate($); +sub AptToDate_AptUpgradeList($); +sub AptToDate_AptToUpgrade($); +sub AptToDate_PreProcessing($$); +sub AptToDate_WriteReadings($$); +sub AptToDate_CreateUpgradeList($$); +sub AptToDate_CreateWarningList($); +sub AptToDate_CreateErrorList($); +sub AptToDate_ToDay(); + + + + + +my %regex = ( 'en' => { 'update' => '^Reading package lists...$', 'upgrade' => '^Unpacking (\S+)\s\((\S+)\)\s+over\s+\((\S+)\)'}, + 'de' => { 'update' => '^Paketlisten werden gelesen...$' ,'upgrade' => '^Entpacken von (\S+)\s\((\S+)\)\s+über\s+\((\S+)\)'} +); + + + + +sub AptToDate_Initialize($) { + + my ($hash) = @_; + + $hash->{SetFn} = "AptToDate_Set"; + $hash->{GetFn} = "AptToDate_Get"; + $hash->{DefFn} = "AptToDate_Define"; + $hash->{NotifyFn} = "AptToDate_Notify"; + $hash->{UndefFn} = "AptToDate_Undef"; + $hash->{AttrFn} = "AptToDate_Attr"; + $hash->{AttrList} = "disable:1 ". + "disabledForIntervals ". + $readingFnAttributes; + + + foreach my $d(sort keys %{$modules{AptToDate}{defptr}}) { + my $hash = $modules{AptToDate}{defptr}{$d}; + $hash->{VERSION} = $version; + } +} + +sub AptToDate_Define($$) { + + my ( $hash, $def ) = @_; + my @a = split( "[ \t][ \t]*", $def ); + + return "too few parameters: define AptToDate " if( @a != 3 ); + return "Cannot define AptToDate device. Perl modul ${missingModul}is missing." if ( $missingModul ); + + + my $name = $a[0]; + my $host = $a[2]; + + $hash->{VERSION} = $version; + $hash->{HOST} = $host; + $hash->{NOTIFYDEV} = "global,$name"; + + + readingsSingleUpdate($hash,"state","initialized", 1) if( ReadingsVal($name,'state','none') ne 'none'); + CommandAttr(undef,$name . ' room AptToDate') if( AttrVal($name,'room','none') eq 'none' ); + + Log3 $name, 3, "AptToDate ($name) - defined"; + + $modules{AptToDate}{defptr}{$hash->{HOST}} = $hash; + + return undef; +} + +sub AptToDate_Undef($$) { + + my ($hash,$arg) = @_; + + + my $name = $hash->{NAME}; + + if(exists($hash->{".fhem"}{subprocess})) { + my $subprocess = $hash->{".fhem"}{subprocess}; + $subprocess->terminate(); + $subprocess->wait(); + } + + delete($modules{AptToDate}{defptr}{$hash->{HOST}}); + Log3 $name, 3, "Sub AptToDate_Undef ($name) - delete device $name"; + return undef; +} + +sub AptToDate_Attr(@) { + + my ( $cmd, $name, $attrName, $attrVal ) = @_; + my $hash = $defs{$name}; + + + if( $attrName eq "disable" ) { + if( $cmd eq "set" and $attrVal eq "1" ) { + RemoveInternalTimer($hash); + + readingsSingleUpdate ( $hash, "state", "disabled", 1 ); + Log3 $name, 3, "AptToDate ($name) - disabled"; + } + + elsif( $cmd eq "del" ) { + Log3 $name, 3, "AptToDate ($name) - enabled"; + } + } + + elsif( $attrName eq "disabledForIntervals" ) { + if( $cmd eq "set" ) { + return "check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'" + unless($attrVal =~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/); + Log3 $name, 3, "AptToDate ($name) - disabledForIntervals"; + readingsSingleUpdate ( $hash, "state", "disabled", 1 ); + } + + elsif( $cmd eq "del" ) { + Log3 $name, 3, "AptToDate ($name) - enabled"; + readingsSingleUpdate ( $hash, "state", "active", 1 ); + } + } + + return undef; +} + +sub AptToDate_Notify($$) { + + my ($hash,$dev) = @_; + my $name = $hash->{NAME}; + return if (IsDisabled($name)); + + my $devname = $dev->{NAME}; + my $devtype = $dev->{TYPE}; + my $events = deviceEvents($dev,1); + return if (!$events); + + Log3 $name, 5, "AptToDate ($name) - Notify: ".Dumper $events; # mit Dumper + + + if( ((grep /^DEFINED.$name$/,@{$events} or grep /^DELETEATTR.$name.disable$/,@{$events} + or grep /^ATTR.$name.disable.0$/,@{$events}) + and $devname eq 'global' and $init_done) + or ((grep /^INITIALIZED$/,@{$events} + or grep /^REREADCFG$/,@{$events} + or grep /^MODIFIED.$name$/,@{$events}) and $devname eq 'global') + or grep /^os-release_language:.(de|en)$/,@{$events} ) { + + if( ReadingsVal($name,'os-release_language','none') ne 'none' ) { + AptToDate_ProcessUpdateTimer($hash); + + } else { + $hash->{".fhem"}{aptget}{cmd} = 'getDistribution'; + AptToDate_AsynchronousExecuteAptGetCommand($hash); + } + } + + if( $devname eq $name and (grep /^repoSync:.fetched.done$/,@{$events} + or grep /^toUpgrade:.successful$/,@{$events}) ) { + $hash->{".fhem"}{aptget}{cmd} = 'getUpdateList'; + AptToDate_AsynchronousExecuteAptGetCommand($hash); + } + + return; +} + +sub AptToDate_Set($$@) { + + my ($hash, $name, @aa) = @_; + + + my ($cmd, @args) = @aa; + + if( $cmd eq 'repoSync' ) { + return "usage: $cmd" if( @args != 0 ); + + $hash->{".fhem"}{aptget}{cmd} = $cmd; + + } elsif( $cmd eq 'toUpgrade' ) { + return "usage: $cmd" if( @args != 0 ); + + $hash->{".fhem"}{aptget}{cmd} = $cmd; + + } else { + my $list = "repoSync:noArg"; + $list .= " toUpgrade:noArg" if( defined($hash->{".fhem"}{aptget}{packages}) and scalar keys %{$hash->{".fhem"}{aptget}{packages}} > 0 ); + + return "Unknown argument $cmd, choose one of $list"; + } + + if( ReadingsVal($name,'os-release_language','none') eq 'de' or ReadingsVal($name,'os-release_language','none') eq 'en' ) { + AptToDate_AsynchronousExecuteAptGetCommand($hash); + } else { + readingsSingleUpdate($hash,"state","language not supported", 1); + Log3 $name, 2, "AptToDate ($name) - sorry, your systems language is not supported"; + } + + return undef; +} + +sub AptToDate_Get($$@) { + + my ($hash, $name, @aa) = @_; + + + my ($cmd, @args) = @aa; + + if( $cmd eq 'showUpgradeList' ) { + return "usage: $cmd" if( @args != 0 ); + + my $ret = AptToDate_CreateUpgradeList($hash,$cmd); + return $ret; + + } elsif( $cmd eq 'showUpdatedList' ) { + return "usage: $cmd" if( @args != 0 ); + + my $ret = AptToDate_CreateUpgradeList($hash,$cmd); + return $ret; + + } elsif( $cmd eq 'showWarningList' ) { + return "usage: $cmd" if( @args != 0 ); + + my $ret = AptToDate_CreateWarningList($hash); + return $ret; + + } elsif( $cmd eq 'showErrorList' ) { + return "usage: $cmd" if( @args != 0 ); + + my $ret = AptToDate_CreateErrorList($hash); + return $ret; + + } else { + my $list = ""; + $list .= " showUpgradeList:noArg" if( defined($hash->{".fhem"}{aptget}{packages}) and scalar keys %{$hash->{".fhem"}{aptget}{packages}} > 0 ); + $list .= " showUpdatedList:noArg" if( defined($hash->{".fhem"}{aptget}{updatedpackages}) and scalar keys %{$hash->{".fhem"}{aptget}{updatedpackages}} > 0 ); + $list .= " showWarningList:noArg" if( defined($hash->{".fhem"}{aptget}{'warnings'}) and scalar @{$hash->{".fhem"}{aptget}{'warnings'}} > 0 ); + $list .= " showErrorList:noArg" if( defined($hash->{".fhem"}{aptget}{'errors'}) and scalar @{$hash->{".fhem"}{aptget}{'errors'}} > 0 ); + + return "Unknown argument $cmd, choose one of $list"; + } +} + +################################### +sub AptToDate_ProcessUpdateTimer($) { + + my $hash = shift; + + + my $name = $hash->{NAME}; + + RemoveInternalTimer($hash); + InternalTimer( gettimeofday()+14400, "AptToDate_ProcessUpdateTimer", $hash,0 ); + Log3 $name, 4, "AptToDate ($name) - stateRequestTimer: Call Request Timer"; + + if( ReadingsVal($name,'os-release_language','none') eq 'de' or ReadingsVal($name,'os-release_language','none') eq 'en' ) { + if( !IsDisabled($name) ) { + if(exists($hash->{".fhem"}{subprocess})) { + Log3 $name, 2, "AptToDate ($name) - update in progress, process aborted."; + return 0; + } + + readingsSingleUpdate($hash,"state","ready", 1) if( ReadingsVal($name,'state','none') eq 'none' or ReadingsVal($name,'state','none') eq 'initialized' ); + + if( AptToDate_ToDay() ne (split(' ',ReadingsTimestamp($name,'repoSync','1970-01-01')))[0]) { + $hash->{".fhem"}{aptget}{cmd} = 'repoSync'; + AptToDate_AsynchronousExecuteAptGetCommand($hash); + } + } + } else { + readingsSingleUpdate($hash,"state","language not supported", 1); + Log3 $name, 2, "AptToDate ($name) - sorry, your systems language is not supported"; + } +} + +sub AptToDate_CleanSubprocess($) { + + my $hash = shift; + + + my $name = $hash->{NAME}; + + delete($hash->{".fhem"}{subprocess}); + Log3 $name, 4, "AptToDate ($name) - clean Subprocess"; +} + + +use constant POLLINTERVAL => 1; + +sub AptToDate_AsynchronousExecuteAptGetCommand($) { + + require "SubProcess.pm"; + my ($hash) = shift; + + + my $name = $hash->{NAME}; + $hash->{".fhem"}{aptget}{lang} = ReadingsVal($name,'os-release_language','none'); + + my $subprocess = SubProcess->new({ onRun => \&AptToDate_OnRun }); + $subprocess->{aptget} = $hash->{".fhem"}{aptget}; + $subprocess->{aptget}{host} = $hash->{HOST}; + $subprocess->{aptget}{debug} = ( AttrVal($name,'verbose',0) > 3 ? 1 : 0 ); + my $pid = $subprocess->run(); + + readingsSingleUpdate($hash,'state',$hash->{".fhem"}{aptget}{cmd}.' in progress', 1); + + + if(!defined($pid)) { + Log3 $name, 1, "AptToDate ($name) - Cannot execute command asynchronously"; + + AptToDate_CleanSubprocess($hash); + readingsSingleUpdate($hash,'state','Cannot execute command asynchronously', 1); + return undef; + } + + Log3 $name, 4, "AptToDate ($name) - execute command asynchronously (PID= $pid)"; + + $hash->{".fhem"}{subprocess} = $subprocess; + + InternalTimer(gettimeofday()+POLLINTERVAL, "AptToDate_PollChild", $hash, 0); + Log3 $hash, 4, "AptToDate ($name) - control passed back to main loop."; +} + +sub AptToDate_PollChild($) { + + my $hash = shift; + + + my $name = $hash->{NAME}; + my $subprocess = $hash->{".fhem"}{subprocess}; + my $json = $subprocess->readFromChild(); + + if(!defined($json)) { + Log3 $name, 5, "AptToDate ($name) - still waiting (". $subprocess->{lasterror} .")."; + InternalTimer(gettimeofday()+POLLINTERVAL, "AptToDate_PollChild", $hash, 0); + return; + } else { + Log3 $name, 4, "AptToDate ($name) - got result from asynchronous parsing."; + $subprocess->wait(); + Log3 $name, 4, "AptToDate ($name) - asynchronous finished."; + + AptToDate_CleanSubprocess($hash); + AptToDate_PreProcessing($hash,$json); + } +} + +###################################### +# Begin Childprozess +###################################### + +sub AptToDate_OnRun() { + + my $subprocess = shift; + + + my $response = AptToDate_ExecuteAptGetCommand($subprocess->{aptget}); + + my $json = eval{encode_json($response)}; + if($@){ + Log3 'AptToDate OnRun', 3, "AptToDate - JSON error: $@"; + $json = '{"jsonerror":"$@"}'; + } + + $subprocess->writeToParent($json); +} + +sub AptToDate_ExecuteAptGetCommand($) { + + my $aptget = shift; + + + my $apt = { }; + $apt->{lang} = $aptget->{lang}; + $apt->{debug} = $aptget->{debug}; + + if( $aptget->{host} ne 'localhost' ) { + + $apt->{aptgetupdate} = 'ssh '.$aptget->{host}.' \'echo n | sudo apt-get -q update\''; + $apt->{distri} = 'ssh '.$aptget->{host}.' cat /etc/os-release |'; + $apt->{'locale'} = 'ssh '.$aptget->{host}.' locale'; + $apt->{aptgetupgrade} = 'ssh '.$aptget->{host}.' \'echo n | sudo apt-get -s -q -V upgrade\''; + $apt->{aptgettoupgrade} = 'ssh '.$aptget->{host}.' \'echo n | sudo apt-get -y -q -V upgrade\''; + + } else { + + $apt->{aptgetupdate} = 'echo n | sudo apt-get -q update'; + $apt->{distri} = '{'locale'} = 'locale'; + $apt->{aptgetupgrade} = 'echo n | sudo apt-get -s -q -V upgrade'; + $apt->{aptgettoupgrade} = 'echo n | sudo apt-get -y -q -V upgrade'; + } + + my $response; + + if( $aptget->{cmd} eq 'repoSync' ) { + $response = AptToDate_AptUpdate($apt); + } elsif( $aptget->{cmd} eq 'getUpdateList' ) { + $response = AptToDate_AptUpgradeList($apt); + } elsif( $aptget->{cmd} eq 'getDistribution' ) { + $response = AptToDate_GetDistribution($apt); + } elsif( $aptget->{cmd} eq 'toUpgrade' ) { + $response = AptToDate_AptToUpgrade($apt); + } + + return $response; +} + +sub AptToDate_GetDistribution($) { + + my $apt = shift; + + + my $update = {}; + + if(open(DISTRI, "$apt->{distri}")) { + while (my $line = ) { + + chomp($line); + print qq($line\n) if( $apt->{debug} == 1 ); + if($line =~ m#^(.*)="?(.*)"$#i or $line =~ m#^(.*)=([a-z]+)$#i) { + $update->{'os-release'}{'os-release_'.$1} = $2; + Log3 'Update', 4, "Distribution Daten erhalten" + } + } + + close(DISTRI); + } else { + die "Couldn't use DISTRI: $!\n"; + $update->{error} = 'Couldn\'t use DISTRI: '.$; + } + + if(open(LOCALE, "$apt->{'locale'} 2>&1 |")) { + while(my $line = ) { + + chomp($line); + print qq($line\n) if( $apt->{debug} == 1 ); + if($line =~ m#^LANG=([a-z]+).*$#) { + $update->{'os-release'}{'os-release_language'} = $1; + Log3 'Update', 4, "Language Daten erhalten" + } + } + + $update->{'os-release'}{'os-release_language'} = 'en' if( not defined($update->{'os-release'}{'os-release_language'}) ); + close(LOCALE); + } else { + die "Couldn't use APT: $!\n"; + $update->{error} = 'Couldn\'t use LOCALE: '.$; + } + + return $update; +} + +sub AptToDate_AptUpdate($) { + + my $apt = shift; + + + my $update = {}; + + if(open(APT, "$apt->{aptgetupdate} 2>&1 | ")) { + while (my $line = ) { + chomp($line); + print qq($line\n) if( $apt->{debug} == 1 ); + if($line =~ m#$regex{$apt->{lang}}{update}#i) { + $update->{'state'} = 'done'; + Log3 'Update', 4, "Daten erhalten"; + + } elsif($line =~ s#^E: ##) { # error + my $error = {}; + $error->{message} = $line; + push(@{$update->{error}}, $error); + $update->{'state'} = 'errors'; + Log3 'Update', 4, "Error"; + + } elsif($line =~ s#^W: ##) { # warning + my $warning = {}; + $warning->{message} = $line; + push(@{$update->{warning}}, $warning); + $update->{'state'} = 'warnings'; + Log3 'Update', 4, "Warning"; + } + } + + close(APT); + } else { + die "Couldn't use APT: $!\n"; + $update->{error} = 'Couldn\'t use APT: '.$; + } + + return $update; +} + +sub AptToDate_AptUpgradeList($) { + + my $apt = shift; + + + my $updates = {}; + + if(open(APT, "$apt->{aptgetupgrade} 2>&1 |")) { + while(my $line = ) { + chomp($line); + print qq($line\n) if( $apt->{debug} == 1 ); + + if($line =~ m#^\s+(\S+)\s+\((\S+)\s+=>\s+(\S+)\)#) { + my $update = {}; + my $package = $1; + $update->{current} = $2; + $update->{new} = $3; + $updates->{packages}->{$package} = $update; + + } elsif($line =~ s#^W: ##) { # warning + my $warning = {}; + $warning->{message} = $line; + push(@{$updates->{warning}}, $warning); + + } elsif($line =~ s#^E: ##) { # error + my $error = {}; + $error->{message} = $line; + push(@{$updates->{error}}, $error); + } + } + + close(APT); + } else { + die "Couldn't use APT: $!\n"; + $updates->{error} = 'Couldn\'t use APT: '.$; + } + + return $updates; +} + +sub AptToDate_AptToUpgrade($) { + + my $apt = shift; + + + my $updates = {}; + + if(open(APT, "$apt->{aptgettoupgrade} 2>&1 |")) { + while(my $line = ) { + chomp($line); + print qq($line\n) if( $apt->{debug} == 1 ); + + if($line =~ m#$regex{$apt->{lang}}{upgrade}#) { + my $update = {}; + my $package = $1; + $update->{new} = $2; + $update->{current} = $3; + $updates->{packages}->{$package} = $update; + + } elsif($line =~ s#^W: ##) { # warning + my $warning = {}; + $warning->{message} = $line; + push(@{$updates->{warning}}, $warning); + + } elsif($line =~ s#^E: ##) { # error + my $error = {}; + $error->{message} = $line; + push(@{$updates->{error}}, $error); + } + } + + close(APT); + } else { + die "Couldn't use APT: $!\n"; + $updates->{error} = 'Couldn\'t use APT: '.$; + } + + return $updates; +} + +#################################################### +# End Childprozess +#################################################### + +sub AptToDate_PreProcessing($$) { + + my ($hash,$json) = @_; + + + my $name = $hash->{NAME}; + + my $decode_json = eval{decode_json($json)}; + if($@){ + Log3 $name, 2, "AptToDate ($name) - JSON error: $@"; + return; + } + + Log3 $hash, 4, "AptToDate ($name) - JSON: $json"; + + $hash->{".fhem"}{aptget}{packages} = $decode_json->{packages} if( $hash->{".fhem"}{aptget}{cmd} eq 'getUpdateList' ); + $hash->{".fhem"}{aptget}{updatedpackages} = $decode_json->{packages} if( $hash->{".fhem"}{aptget}{cmd} eq 'toUpgrade' ); + + if( defined($decode_json->{warning}) or defined($decode_json->{error}) ) { + $hash->{".fhem"}{aptget}{'warnings'} = $decode_json->{warning} if( defined($decode_json->{warning}) ); + $hash->{".fhem"}{aptget}{errors} = $decode_json->{error} if( defined($decode_json->{error}) ); + } else { + delete $hash->{".fhem"}{aptget}{'warnings'}; + delete $hash->{".fhem"}{aptget}{errors}; + } + + AptToDate_WriteReadings($hash,$decode_json); +} + +sub AptToDate_WriteReadings($$) { + + my ($hash,$decode_json) = @_; + + my $name = $hash->{NAME}; + + Log3 $hash, 4, "AptToDate ($name) - Write Readings"; + Log3 $hash, 5, "AptToDate ($name) - ".Dumper $decode_json; + Log3 $hash, 5, "AptToDate ($name) - Packges Anzahl: ".scalar keys %{$decode_json->{packages}}; + Log3 $hash, 5, "AptToDate ($name) - Inhalt aptget cmd: ".scalar keys %{$decode_json->{packages}}; + + + readingsBeginUpdate($hash); + + if( $hash->{".fhem"}{aptget}{cmd} eq 'repoSync' ) { + readingsBulkUpdate($hash,'repoSync',(defined($decode_json->{'state'}) ? 'fetched '.$decode_json->{'state'} : 'fetched error') ); + $hash->{helper}{lastSync} = AptToDate_ToDay(); + } + + readingsBulkUpdateIfChanged($hash,'updatesAvailable',scalar keys %{$decode_json->{packages}}) if( $hash->{".fhem"}{aptget}{cmd} eq 'getUpdateList' ); + readingsBulkUpdate($hash,'toUpgrade','successful') if( $hash->{".fhem"}{aptget}{cmd} eq 'toUpgrade' and not defined($hash->{".fhem"}{aptget}{'errors'}) and not defined($hash->{".fhem"}{aptget}{'warnings'}) ); + + if( $hash->{".fhem"}{aptget}{cmd} eq 'getDistribution' ) { + while( my ($r,$v) = each %{$decode_json->{'os-release'}} ) { + readingsBulkUpdateIfChanged($hash,$r,$v); + } + } + + if( defined($decode_json->{error}) ) { + readingsBulkUpdate($hash,'state',$hash->{".fhem"}{aptget}{cmd}.' Errors (get showErrorList)'); + readingsBulkUpdate($hash,'state','errors'); + } elsif( defined($decode_json->{warning}) ) { + readingsBulkUpdate($hash,'state',$hash->{".fhem"}{aptget}{cmd}.' Warnings (get showWarningList)'); + readingsBulkUpdate($hash,'state','warnings'); + } else { + + readingsBulkUpdate($hash,'state',(scalar keys %{$decode_json->{packages}} > 0 ? 'system updates available' : 'system is up to date') ); + } + + readingsEndUpdate($hash,1); + + AptToDate_ProcessUpdateTimer($hash) if( $hash->{".fhem"}{aptget}{cmd} eq 'getDistribution' ); +} + +sub AptToDate_CreateUpgradeList($$) { + + my ($hash,$getCmd) = @_; + + + my $packages; + $packages = $hash->{".fhem"}{aptget}{packages} if($getCmd eq 'showUpgradeList'); + $packages = $hash->{".fhem"}{aptget}{updatedpackages} if($getCmd eq 'showUpdatedList'); + + my $ret = ''; + $ret .= '
'; + $ret .= ''; + $ret .= ''; + $ret .= ""; + $ret .= "" if($getCmd eq 'showUpgradeList'); + $ret .= "" if($getCmd eq 'showUpdatedList'); + $ret .= "";; + $ret .= ""; + $ret .= ''; + + if( ref($packages) eq "HASH" ) { + + my $linecount = 1; + foreach my $package (keys (%{$packages}) ) { + if ( $linecount % 2 == 0 ) { + $ret .= ''; + } else { + $ret .= ''; + } + + $ret .= ""; + $ret .= ""; + $ret .= ""; + + $ret .= ''; + $linecount++; + } + } + + $ret .= '
PackagenameCurrent VersionOver VersionNew Version
$package$packages->{$package}{current}$packages->{$package}{new}
'; + + return $ret; +} + +sub AptToDate_CreateWarningList($) { + + my $hash = shift; + + + my $warnings = $hash->{".fhem"}{aptget}{'warnings'}; + + my $ret = ''; + $ret .= '
'; + $ret .= ''; + $ret .= ''; + $ret .= ""; + $ret .= ""; + $ret .= ''; + + if( ref($warnings) eq "ARRAY" ) { + + my $linecount = 1; + foreach my $warning (@{$warnings}) { + if ( $linecount % 2 == 0 ) { + $ret .= ''; + } else { + $ret .= ''; + } + + $ret .= ""; + + $ret .= ''; + $linecount++; + } + } + + $ret .= '
Warning List
$warning->{message}
'; + + return $ret; +} + +sub AptToDate_CreateErrorList($) { + + my $hash = shift; + + + my $errors = $hash->{".fhem"}{aptget}{'errors'}; + + my $ret = ''; + $ret .= '
'; + $ret .= ''; + $ret .= ''; + $ret .= ""; + $ret .= ""; + $ret .= ''; + + if( ref($errors) eq "ARRAY" ) { + + my $linecount = 1; + foreach my $error (@{$errors}) { + if ( $linecount % 2 == 0 ) { + $ret .= ''; + } else { + $ret .= ''; + } + + $ret .= ""; + + $ret .= ''; + $linecount++; + } + } + + $ret .= '
Error List
$error->{message}
'; + + return $ret; +} + +#### my little helper +sub AptToDate_ToDay() { + + my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime(gettimeofday()); + + $month++; + $year+=1900; + + my $today = sprintf('%04d-%02d-%02d', $year,$month,$mday); + + return $today; +} + + + + + + +1; + + + + + + + + +=pod +=item device +=item summary Modul to retrieves apt information about Debian update state +=item summary_DE Modul um apt Updateinformationen von Debian Systemen zu bekommen + +=begin html + + +

Apt Update Information

+
    + AptToDate - Retrieves apt information about Debian update state state +
    + With this module it is possible to read the apt update information from a Debian System.
    + It's required to insert "fhem ALL=NOPASSWD: /usr/bin/apt-get" via "visudo". +

    + + Define +

      + define <name> AptToDate <HOST> +

      + Example: +

        + define fhemServer AptToDate localhost
        +
      +
      + This statement creates a AptToDate Device with the name fhemServer and the Host localhost.
      + After the device has been created the Modul is fetch all information about update state. This will need a moment. +
    +

    + + Readings +
      +
    • state - update status about the server
    • +
    • os-release_ - all information from /etc/os-release
    • +
    • repoSync - status about last repository sync.
    • +
    • toUpgrade - status about last upgrade
    • +
    • updatesAvailable - number of available updates
    • +
    +

    + + Set +
      +
    • repoSync - fetch information about update state
    • +
    • toUpgrade - to run update process. this will take a moment
    • +
      +
    +

    + + Get +
      +
    • showUpgradeList - list about available updates
    • +
    • showUpdatedList - list about updated packages, from old version to new version
    • +
    • showWarningList - list of all last warnings
    • +
    • showErrorList - list of all last errors
    • +
      +
    +

    + + Attributes +
      +
    • disable - disables the device
    • +
    • disabledForIntervals - disable device for interval time (13:00-18:30 or 13:00-18:30 22:00-23:00)
    • +
    +
+ +=end html + +=begin html_DE + + +

Apt Update Information

+
    + AptToDate - Stellt aktuelle Update Informationen von apt Debian Systemen bereit +
    + Das Modul synct alle Repositotys und stellt dann Informationen über zu aktualisierende Packete bereit.
    + Es ist Voraussetzung das folgende Zeile via "visudo" eingefügt wird: "fhem ALL=NOPASSWD: /usr/bin/apt-get". +

    + + Define +

      + define <name> AptToDate <HOST> +

      + Beispiel: +

        + define fhemServer AptToDate localhost
        +
      +
      + Der Befehl erstellt eine AptToDate Instanz mit dem Namen fhemServer und dem Host localhost.
      + Nachdem die Instanz erstellt wurde werden die benötigten Informationen geholt und als Readings angezeigt. + Dies kann einen Moment dauern. +
    +

    + + Readings +
      +
    • state - update Status des Servers, liegen neue Updates an oder nicht
    • +
    • os-release_ - alle Informationen aus /etc/os-release
    • +
    • repoSync - status des letzten repository sync.
    • +
    • toUpgrade - status des letzten upgrade Befehles
    • +
    • updatesAvailable - Anzahl der verfügbaren Paketupdates
    • +
    +

    + + Set +
      +
    • repoSync - holt aktuelle Informationen über den Updatestatus
    • +
    • toUpgrade - führt den upgrade prozess aus.
    • +
      +
    +

    + + Get +
      +
    • showUpgradeList - Paketiste aller zur Verfügung stehender Updates
    • +
    • showUpdatedList - Liste aller als letztes aktualisierter Pakete, von der alten Version zur neuen Version
    • +
    • showWarningList - Liste der letzten Warnings
    • +
    • showErrorList - Liste der letzten Fehler
    • +
      +
    +

    + + Attributes +
      +
    • disable - Deaktiviert das Device
    • +
    • disabledForIntervals - Deaktiviert das Device für eine bestimmte Zeit (13:00-18:30 or 13:00-18:30 22:00-23:00)
    • +
    +
+ +=end html_DE + +=cut