42_AptToDate.pm 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. ###############################################################################
  2. #
  3. # Developed with Kate
  4. #
  5. # (c) 2017-2018 Copyright: Marko Oldenburg (leongaultier at gmail dot com)
  6. # All rights reserved
  7. #
  8. # Special thanks goes to:
  9. # - Dusty Wilson Inspiration and parts of Code
  10. # http://search.cpan.org/~wilsond/Linux-APT-0.02/lib/Linux/APT.pm
  11. #
  12. #
  13. # This script is free software; you can redistribute it and/or modify
  14. # it under the terms of the GNU General Public License as published by
  15. # the Free Software Foundation; either version 2 of the License, or
  16. # any later version.
  17. #
  18. # The GNU General Public License can be found at
  19. # http://www.gnu.org/copyleft/gpl.html.
  20. # A copy is found in the textfile GPL.txt and important notices to the license
  21. # from the author is found in LICENSE.txt distributed with these scripts.
  22. #
  23. # This script is distributed in the hope that it will be useful,
  24. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. # GNU General Public License for more details.
  27. #
  28. #
  29. # $Id: 42_AptToDate.pm 16995 2018-07-17 13:43:54Z CoolTux $
  30. #
  31. ###############################################################################
  32. package main;
  33. my $missingModul = "";
  34. use strict;
  35. use warnings;
  36. use POSIX;
  37. use Data::Dumper; #only for Debugging
  38. eval "use JSON;1" or $missingModul .= "JSON ";
  39. my $version = "1.0.0";
  40. # Declare functions
  41. sub AptToDate_Initialize($);
  42. sub AptToDate_Define($$);
  43. sub AptToDate_Undef($$);
  44. sub AptToDate_Attr(@);
  45. sub AptToDate_Set($$@);
  46. sub AptToDate_Get($$@);
  47. sub AptToDate_Notify($$);
  48. sub AptToDate_ProcessUpdateTimer($);
  49. sub AptToDate_CleanSubprocess($);
  50. sub AptToDate_AsynchronousExecuteAptGetCommand($);
  51. sub AptToDate_OnRun();
  52. sub AptToDate_PollChild($);
  53. sub AptToDate_ExecuteAptGetCommand($);
  54. sub AptToDate_GetDistribution($);
  55. sub AptToDate_AptUpdate($);
  56. sub AptToDate_AptUpgradeList($);
  57. sub AptToDate_AptToUpgrade($);
  58. sub AptToDate_PreProcessing($$);
  59. sub AptToDate_WriteReadings($$);
  60. sub AptToDate_CreateUpgradeList($$);
  61. sub AptToDate_CreateWarningList($);
  62. sub AptToDate_CreateErrorList($);
  63. sub AptToDate_ToDay();
  64. my %regex = ( 'en' => { 'update' => '^Reading package lists...$', 'upgrade' => '^Unpacking (\S+)\s\((\S+)\)\s+over\s+\((\S+)\)'},
  65. 'de' => { 'update' => '^Paketlisten werden gelesen...$' ,'upgrade' => '^Entpacken von (\S+)\s\((\S+)\)\s+über\s+\((\S+)\)'}
  66. );
  67. sub AptToDate_Initialize($) {
  68. my ($hash) = @_;
  69. $hash->{SetFn} = "AptToDate_Set";
  70. $hash->{GetFn} = "AptToDate_Get";
  71. $hash->{DefFn} = "AptToDate_Define";
  72. $hash->{NotifyFn} = "AptToDate_Notify";
  73. $hash->{UndefFn} = "AptToDate_Undef";
  74. $hash->{AttrFn} = "AptToDate_Attr";
  75. $hash->{AttrList} = "disable:1 ".
  76. "disabledForIntervals ".
  77. "upgradeListReading:1 ".
  78. "distupgrade:1 ".
  79. $readingFnAttributes;
  80. foreach my $d(sort keys %{$modules{AptToDate}{defptr}}) {
  81. my $hash = $modules{AptToDate}{defptr}{$d};
  82. $hash->{VERSION} = $version;
  83. }
  84. }
  85. sub AptToDate_Define($$) {
  86. my ( $hash, $def ) = @_;
  87. my @a = split( "[ \t][ \t]*", $def );
  88. return "too few parameters: define <name> AptToDate <HOST>" if( @a != 3 );
  89. return "Cannot define AptToDate device. Perl modul ${missingModul}is missing." if ( $missingModul );
  90. my $name = $a[0];
  91. my $host = $a[2];
  92. $hash->{VERSION} = $version;
  93. $hash->{HOST} = $host;
  94. $hash->{NOTIFYDEV} = "global,$name";
  95. readingsSingleUpdate($hash,"state","initialized", 1) if( ReadingsVal($name,'state','none') ne 'none');
  96. CommandAttr(undef,$name . ' room AptToDate') if( AttrVal($name,'room','none') eq 'none' );
  97. Log3 $name, 3, "AptToDate ($name) - defined";
  98. $modules{AptToDate}{defptr}{$hash->{HOST}} = $hash;
  99. return undef;
  100. }
  101. sub AptToDate_Undef($$) {
  102. my ($hash,$arg) = @_;
  103. my $name = $hash->{NAME};
  104. if(exists($hash->{".fhem"}{subprocess})) {
  105. my $subprocess = $hash->{".fhem"}{subprocess};
  106. $subprocess->terminate();
  107. $subprocess->wait();
  108. }
  109. delete($modules{AptToDate}{defptr}{$hash->{HOST}});
  110. Log3 $name, 3, "Sub AptToDate_Undef ($name) - delete device $name";
  111. return undef;
  112. }
  113. sub AptToDate_Attr(@) {
  114. my ( $cmd, $name, $attrName, $attrVal ) = @_;
  115. my $hash = $defs{$name};
  116. if( $attrName eq "disable" ) {
  117. if( $cmd eq "set" and $attrVal eq "1" ) {
  118. RemoveInternalTimer($hash);
  119. readingsSingleUpdate ( $hash, "state", "disabled", 1 );
  120. Log3 $name, 3, "AptToDate ($name) - disabled";
  121. }
  122. elsif( $cmd eq "del" ) {
  123. Log3 $name, 3, "AptToDate ($name) - enabled";
  124. }
  125. }
  126. elsif( $attrName eq "disabledForIntervals" ) {
  127. if( $cmd eq "set" ) {
  128. return "check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'"
  129. unless($attrVal =~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/);
  130. Log3 $name, 3, "AptToDate ($name) - disabledForIntervals";
  131. readingsSingleUpdate ( $hash, "state", "disabled", 1 );
  132. }
  133. elsif( $cmd eq "del" ) {
  134. Log3 $name, 3, "AptToDate ($name) - enabled";
  135. readingsSingleUpdate ( $hash, "state", "active", 1 );
  136. }
  137. }
  138. return undef;
  139. }
  140. sub AptToDate_Notify($$) {
  141. my ($hash,$dev) = @_;
  142. my $name = $hash->{NAME};
  143. return if (IsDisabled($name));
  144. my $devname = $dev->{NAME};
  145. my $devtype = $dev->{TYPE};
  146. my $events = deviceEvents($dev,1);
  147. return if (!$events);
  148. Log3 $name, 5, "AptToDate ($name) - Notify: ".Dumper $events; # mit Dumper
  149. if( ((grep /^DEFINED.$name$/,@{$events} or grep /^DELETEATTR.$name.disable$/,@{$events}
  150. or grep /^ATTR.$name.disable.0$/,@{$events})
  151. and $devname eq 'global' and $init_done)
  152. or ((grep /^INITIALIZED$/,@{$events}
  153. or grep /^REREADCFG$/,@{$events}
  154. or grep /^MODIFIED.$name$/,@{$events}) and $devname eq 'global')
  155. or grep /^os-release_language:.(de|en)$/,@{$events} ) {
  156. if( ReadingsVal($name,'os-release_language','none') ne 'none' ) {
  157. AptToDate_ProcessUpdateTimer($hash);
  158. } else {
  159. $hash->{".fhem"}{aptget}{cmd} = 'getDistribution';
  160. AptToDate_AsynchronousExecuteAptGetCommand($hash);
  161. }
  162. }
  163. if( $devname eq $name and (grep /^repoSync:.fetched.done$/,@{$events}
  164. or grep /^toUpgrade:.successful$/,@{$events}) ) {
  165. $hash->{".fhem"}{aptget}{cmd} = 'getUpdateList';
  166. AptToDate_AsynchronousExecuteAptGetCommand($hash);
  167. }
  168. return;
  169. }
  170. sub AptToDate_Set($$@) {
  171. my ($hash, $name, @aa) = @_;
  172. my ($cmd, @args) = @aa;
  173. if( $cmd eq 'repoSync' ) {
  174. return "usage: $cmd" if( @args != 0 );
  175. $hash->{".fhem"}{aptget}{cmd} = $cmd;
  176. } elsif( $cmd eq 'toUpgrade' ) {
  177. return "usage: $cmd" if( @args != 0 );
  178. $hash->{".fhem"}{aptget}{cmd} = $cmd;
  179. } else {
  180. my $list = "repoSync:noArg";
  181. $list .= " toUpgrade:noArg" if( defined($hash->{".fhem"}{aptget}{packages}) and scalar keys %{$hash->{".fhem"}{aptget}{packages}} > 0 );
  182. return "Unknown argument $cmd, choose one of $list";
  183. }
  184. if( ReadingsVal($name,'os-release_language','none') eq 'de' or ReadingsVal($name,'os-release_language','none') eq 'en' ) {
  185. AptToDate_AsynchronousExecuteAptGetCommand($hash);
  186. } else {
  187. readingsSingleUpdate($hash,"state","language not supported", 1);
  188. Log3 $name, 2, "AptToDate ($name) - sorry, your systems language is not supported";
  189. }
  190. return undef;
  191. }
  192. sub AptToDate_Get($$@) {
  193. my ($hash, $name, @aa) = @_;
  194. my ($cmd, @args) = @aa;
  195. if( $cmd eq 'showUpgradeList' ) {
  196. return "usage: $cmd" if( @args != 0 );
  197. my $ret = AptToDate_CreateUpgradeList($hash,$cmd);
  198. return $ret;
  199. } elsif( $cmd eq 'showUpdatedList' ) {
  200. return "usage: $cmd" if( @args != 0 );
  201. my $ret = AptToDate_CreateUpgradeList($hash,$cmd);
  202. return $ret;
  203. } elsif( $cmd eq 'showWarningList' ) {
  204. return "usage: $cmd" if( @args != 0 );
  205. my $ret = AptToDate_CreateWarningList($hash);
  206. return $ret;
  207. } elsif( $cmd eq 'showErrorList' ) {
  208. return "usage: $cmd" if( @args != 0 );
  209. my $ret = AptToDate_CreateErrorList($hash);
  210. return $ret;
  211. } else {
  212. my $list = "";
  213. $list .= " showUpgradeList:noArg" if( defined($hash->{".fhem"}{aptget}{packages}) and scalar keys %{$hash->{".fhem"}{aptget}{packages}} > 0 );
  214. $list .= " showUpdatedList:noArg" if( defined($hash->{".fhem"}{aptget}{updatedpackages}) and scalar keys %{$hash->{".fhem"}{aptget}{updatedpackages}} > 0 );
  215. $list .= " showWarningList:noArg" if( defined($hash->{".fhem"}{aptget}{'warnings'}) and scalar @{$hash->{".fhem"}{aptget}{'warnings'}} > 0 );
  216. $list .= " showErrorList:noArg" if( defined($hash->{".fhem"}{aptget}{'errors'}) and scalar @{$hash->{".fhem"}{aptget}{'errors'}} > 0 );
  217. return "Unknown argument $cmd, choose one of $list";
  218. }
  219. }
  220. ###################################
  221. sub AptToDate_ProcessUpdateTimer($) {
  222. my $hash = shift;
  223. my $name = $hash->{NAME};
  224. RemoveInternalTimer($hash);
  225. InternalTimer( gettimeofday()+14400, "AptToDate_ProcessUpdateTimer", $hash,0 );
  226. Log3 $name, 4, "AptToDate ($name) - stateRequestTimer: Call Request Timer";
  227. if( ReadingsVal($name,'os-release_language','none') eq 'de' or ReadingsVal($name,'os-release_language','none') eq 'en' ) {
  228. if( !IsDisabled($name) ) {
  229. if(exists($hash->{".fhem"}{subprocess})) {
  230. Log3 $name, 2, "AptToDate ($name) - update in progress, process aborted.";
  231. return 0;
  232. }
  233. readingsSingleUpdate($hash,"state","ready", 1) if( ReadingsVal($name,'state','none') eq 'none' or ReadingsVal($name,'state','none') eq 'initialized' );
  234. if( AptToDate_ToDay() ne (split(' ',ReadingsTimestamp($name,'repoSync','1970-01-01')))[0]) {
  235. $hash->{".fhem"}{aptget}{cmd} = 'repoSync';
  236. AptToDate_AsynchronousExecuteAptGetCommand($hash);
  237. }
  238. }
  239. } else {
  240. readingsSingleUpdate($hash,"state","language not supported", 1);
  241. Log3 $name, 2, "AptToDate ($name) - sorry, your systems language is not supported";
  242. }
  243. }
  244. sub AptToDate_CleanSubprocess($) {
  245. my $hash = shift;
  246. my $name = $hash->{NAME};
  247. delete($hash->{".fhem"}{subprocess});
  248. Log3 $name, 4, "AptToDate ($name) - clean Subprocess";
  249. }
  250. use constant POLLINTERVAL => 1;
  251. sub AptToDate_AsynchronousExecuteAptGetCommand($) {
  252. require "SubProcess.pm";
  253. my ($hash) = shift;
  254. my $name = $hash->{NAME};
  255. $hash->{".fhem"}{aptget}{lang} = ReadingsVal($name,'os-release_language','none');
  256. my $subprocess = SubProcess->new({ onRun => \&AptToDate_OnRun });
  257. $subprocess->{aptget} = $hash->{".fhem"}{aptget};
  258. $subprocess->{aptget}{host} = $hash->{HOST};
  259. $subprocess->{aptget}{debug} = ( AttrVal($name,'verbose',0) > 3 ? 1 : 0 );
  260. $subprocess->{aptget}{distupgrade} = ( AttrVal($name,'distupgrade',0) == 1 ? 1 : 0 );
  261. my $pid = $subprocess->run();
  262. readingsSingleUpdate($hash,'state',$hash->{".fhem"}{aptget}{cmd}.' in progress', 1);
  263. if(!defined($pid)) {
  264. Log3 $name, 1, "AptToDate ($name) - Cannot execute command asynchronously";
  265. AptToDate_CleanSubprocess($hash);
  266. readingsSingleUpdate($hash,'state','Cannot execute command asynchronously', 1);
  267. return undef;
  268. }
  269. Log3 $name, 4, "AptToDate ($name) - execute command asynchronously (PID= $pid)";
  270. $hash->{".fhem"}{subprocess} = $subprocess;
  271. InternalTimer(gettimeofday()+POLLINTERVAL, "AptToDate_PollChild", $hash, 0);
  272. Log3 $hash, 4, "AptToDate ($name) - control passed back to main loop.";
  273. }
  274. sub AptToDate_PollChild($) {
  275. my $hash = shift;
  276. my $name = $hash->{NAME};
  277. my $subprocess = $hash->{".fhem"}{subprocess};
  278. my $json = $subprocess->readFromChild();
  279. if(!defined($json)) {
  280. Log3 $name, 5, "AptToDate ($name) - still waiting (". $subprocess->{lasterror} .").";
  281. InternalTimer(gettimeofday()+POLLINTERVAL, "AptToDate_PollChild", $hash, 0);
  282. return;
  283. } else {
  284. Log3 $name, 4, "AptToDate ($name) - got result from asynchronous parsing.";
  285. $subprocess->wait();
  286. Log3 $name, 4, "AptToDate ($name) - asynchronous finished.";
  287. AptToDate_CleanSubprocess($hash);
  288. AptToDate_PreProcessing($hash,$json);
  289. }
  290. }
  291. ######################################
  292. # Begin Childprozess
  293. ######################################
  294. sub AptToDate_OnRun() {
  295. my $subprocess = shift;
  296. my $response = AptToDate_ExecuteAptGetCommand($subprocess->{aptget});
  297. my $json = eval{encode_json($response)};
  298. if($@){
  299. Log3 'AptToDate OnRun', 3, "AptToDate - JSON error: $@";
  300. $json = '{"jsonerror":"$@"}';
  301. }
  302. $subprocess->writeToParent($json);
  303. }
  304. sub AptToDate_ExecuteAptGetCommand($) {
  305. my $aptget = shift;
  306. my $apt = { };
  307. $apt->{lang} = $aptget->{lang};
  308. $apt->{debug} = $aptget->{debug};
  309. if( $aptget->{host} ne 'localhost' ) {
  310. $apt->{aptgetupdate} = 'ssh '.$aptget->{host}.' \'echo n | sudo apt-get -q update\'';
  311. $apt->{distri} = 'ssh '.$aptget->{host}.' cat /etc/os-release |';
  312. $apt->{'locale'} = 'ssh '.$aptget->{host}.' locale';
  313. $apt->{aptgetupgrade} = 'ssh '.$aptget->{host}.' \'echo n | sudo apt-get -s -q -V upgrade\'';
  314. $apt->{aptgettoupgrade} = 'ssh '.$aptget->{host}.' \'echo n | sudo apt-get -y -q -V upgrade\'' if($aptget->{distupgrade} == 0);
  315. $apt->{aptgettoupgrade} = 'ssh '.$aptget->{host}.' \'echo n | sudo apt-get -y -q -V dist-upgrade\'' if($aptget->{distupgrade} == 1);
  316. } else {
  317. $apt->{aptgetupdate} = 'echo n | sudo apt-get -q update';
  318. $apt->{distri} = '</etc/os-release';
  319. $apt->{'locale'} = 'locale';
  320. $apt->{aptgetupgrade} = 'echo n | sudo apt-get -s -q -V upgrade';
  321. $apt->{aptgettoupgrade} = 'echo n | sudo apt-get -y -q -V upgrade' if($aptget->{distupgrade} == 0);
  322. $apt->{aptgettoupgrade} = 'echo n | sudo apt-get -y -q -V dist-upgrade' if($aptget->{distupgrade} == 1);
  323. }
  324. my $response;
  325. if( $aptget->{cmd} eq 'repoSync' ) {
  326. $response = AptToDate_AptUpdate($apt);
  327. } elsif( $aptget->{cmd} eq 'getUpdateList' ) {
  328. $response = AptToDate_AptUpgradeList($apt);
  329. } elsif( $aptget->{cmd} eq 'getDistribution' ) {
  330. $response = AptToDate_GetDistribution($apt);
  331. } elsif( $aptget->{cmd} eq 'toUpgrade' ) {
  332. $response = AptToDate_AptToUpgrade($apt);
  333. }
  334. return $response;
  335. }
  336. sub AptToDate_GetDistribution($) {
  337. my $apt = shift;
  338. my $update = {};
  339. if(open(DISTRI, "$apt->{distri}")) {
  340. while (my $line = <DISTRI>) {
  341. chomp($line);
  342. print qq($line\n) if( $apt->{debug} == 1 );
  343. if($line =~ m#^(.*)="?(.*)"$#i or $line =~ m#^(.*)=([a-z]+)$#i) {
  344. $update->{'os-release'}{'os-release_'.$1} = $2;
  345. Log3 'Update', 4, "Distribution Daten erhalten"
  346. }
  347. }
  348. close(DISTRI);
  349. } else {
  350. die "Couldn't use DISTRI: $!\n";
  351. $update->{error} = 'Couldn\'t use DISTRI: '.$;
  352. }
  353. if(open(LOCALE, "$apt->{'locale'} 2>&1 |")) {
  354. while(my $line = <LOCALE>) {
  355. chomp($line);
  356. print qq($line\n) if( $apt->{debug} == 1 );
  357. if($line =~ m#^LANG=([a-z]+).*$#) {
  358. $update->{'os-release'}{'os-release_language'} = $1;
  359. Log3 'Update', 4, "Language Daten erhalten"
  360. }
  361. }
  362. $update->{'os-release'}{'os-release_language'} = 'en' if( not defined($update->{'os-release'}{'os-release_language'}) );
  363. close(LOCALE);
  364. } else {
  365. die "Couldn't use APT: $!\n";
  366. $update->{error} = 'Couldn\'t use LOCALE: '.$;
  367. }
  368. return $update;
  369. }
  370. sub AptToDate_AptUpdate($) {
  371. my $apt = shift;
  372. my $update = {};
  373. if(open(APT, "$apt->{aptgetupdate} 2>&1 | ")) {
  374. while (my $line = <APT>) {
  375. chomp($line);
  376. print qq($line\n) if( $apt->{debug} == 1 );
  377. if($line =~ m#$regex{$apt->{lang}}{update}#i) {
  378. $update->{'state'} = 'done';
  379. Log3 'Update', 4, "Daten erhalten";
  380. } elsif($line =~ s#^E: ##) { # error
  381. my $error = {};
  382. $error->{message} = $line;
  383. push(@{$update->{error}}, $error);
  384. $update->{'state'} = 'errors';
  385. Log3 'Update', 4, "Error";
  386. } elsif($line =~ s#^W: ##) { # warning
  387. my $warning = {};
  388. $warning->{message} = $line;
  389. push(@{$update->{warning}}, $warning);
  390. $update->{'state'} = 'warnings';
  391. Log3 'Update', 4, "Warning";
  392. }
  393. }
  394. close(APT);
  395. } else {
  396. die "Couldn't use APT: $!\n";
  397. $update->{error} = 'Couldn\'t use APT: '.$;
  398. }
  399. return $update;
  400. }
  401. sub AptToDate_AptUpgradeList($) {
  402. my $apt = shift;
  403. my $updates = {};
  404. if(open(APT, "$apt->{aptgetupgrade} 2>&1 |")) {
  405. while(my $line = <APT>) {
  406. chomp($line);
  407. print qq($line\n) if( $apt->{debug} == 1 );
  408. if($line =~ m#^\s+(\S+)\s+\((\S+)\s+=>\s+(\S+)\)#) {
  409. my $update = {};
  410. my $package = $1;
  411. $update->{current} = $2;
  412. $update->{new} = $3;
  413. $updates->{packages}->{$package} = $update;
  414. } elsif($line =~ s#^W: ##) { # warning
  415. my $warning = {};
  416. $warning->{message} = $line;
  417. push(@{$updates->{warning}}, $warning);
  418. } elsif($line =~ s#^E: ##) { # error
  419. my $error = {};
  420. $error->{message} = $line;
  421. push(@{$updates->{error}}, $error);
  422. }
  423. }
  424. close(APT);
  425. } else {
  426. die "Couldn't use APT: $!\n";
  427. $updates->{error} = 'Couldn\'t use APT: '.$;
  428. }
  429. return $updates;
  430. }
  431. sub AptToDate_AptToUpgrade($) {
  432. my $apt = shift;
  433. my $updates = {};
  434. if(open(APT, "$apt->{aptgettoupgrade} 2>&1 |")) {
  435. while(my $line = <APT>) {
  436. chomp($line);
  437. print qq($line\n) if( $apt->{debug} == 1 );
  438. if($line =~ m#$regex{$apt->{lang}}{upgrade}#) {
  439. my $update = {};
  440. my $package = $1;
  441. $update->{new} = $2;
  442. $update->{current} = $3;
  443. $updates->{packages}->{$package} = $update;
  444. } elsif($line =~ s#^W: ##) { # warning
  445. my $warning = {};
  446. $warning->{message} = $line;
  447. push(@{$updates->{warning}}, $warning);
  448. } elsif($line =~ s#^E: ##) { # error
  449. my $error = {};
  450. $error->{message} = $line;
  451. push(@{$updates->{error}}, $error);
  452. }
  453. }
  454. close(APT);
  455. } else {
  456. die "Couldn't use APT: $!\n";
  457. $updates->{error} = 'Couldn\'t use APT: '.$;
  458. }
  459. return $updates;
  460. }
  461. ####################################################
  462. # End Childprozess
  463. ####################################################
  464. sub AptToDate_PreProcessing($$) {
  465. my ($hash,$json) = @_;
  466. my $name = $hash->{NAME};
  467. my $decode_json = eval{decode_json($json)};
  468. if($@){
  469. Log3 $name, 2, "AptToDate ($name) - JSON error: $@";
  470. return;
  471. }
  472. Log3 $hash, 4, "AptToDate ($name) - JSON: $json";
  473. $hash->{".fhem"}{aptget}{packages} = $decode_json->{packages} if( $hash->{".fhem"}{aptget}{cmd} eq 'getUpdateList' );
  474. $hash->{".fhem"}{aptget}{updatedpackages} = $decode_json->{packages} if( $hash->{".fhem"}{aptget}{cmd} eq 'toUpgrade' );
  475. if( defined($decode_json->{warning}) or defined($decode_json->{error}) ) {
  476. $hash->{".fhem"}{aptget}{'warnings'} = $decode_json->{warning} if( defined($decode_json->{warning}) );
  477. $hash->{".fhem"}{aptget}{errors} = $decode_json->{error} if( defined($decode_json->{error}) );
  478. } else {
  479. delete $hash->{".fhem"}{aptget}{'warnings'};
  480. delete $hash->{".fhem"}{aptget}{errors};
  481. }
  482. AptToDate_WriteReadings($hash,$decode_json);
  483. }
  484. sub AptToDate_WriteReadings($$) {
  485. my ($hash,$decode_json) = @_;
  486. my $name = $hash->{NAME};
  487. Log3 $hash, 4, "AptToDate ($name) - Write Readings";
  488. Log3 $hash, 5, "AptToDate ($name) - ".Dumper $decode_json;
  489. Log3 $hash, 5, "AptToDate ($name) - Packges Anzahl: ".scalar keys %{$decode_json->{packages}};
  490. Log3 $hash, 5, "AptToDate ($name) - Inhalt aptget cmd: ".scalar keys %{$decode_json->{packages}};
  491. readingsBeginUpdate($hash);
  492. if( $hash->{".fhem"}{aptget}{cmd} eq 'repoSync' ) {
  493. readingsBulkUpdate($hash,'repoSync',(defined($decode_json->{'state'}) ? 'fetched '.$decode_json->{'state'} : 'fetched error') );
  494. $hash->{helper}{lastSync} = AptToDate_ToDay();
  495. }
  496. readingsBulkUpdateIfChanged($hash,'updatesAvailable',scalar keys %{$decode_json->{packages}}) if( $hash->{".fhem"}{aptget}{cmd} eq 'getUpdateList' );
  497. readingsBulkUpdateIfChanged($hash,'upgradeListAsJSON',eval{encode_json($hash->{".fhem"}{aptget}{packages})}) if( AttrVal($name,'upgradeListReading','none') ne 'none');
  498. 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'}) );
  499. if( $hash->{".fhem"}{aptget}{cmd} eq 'getDistribution' ) {
  500. while( my ($r,$v) = each %{$decode_json->{'os-release'}} ) {
  501. readingsBulkUpdateIfChanged($hash,$r,$v);
  502. }
  503. }
  504. if( defined($decode_json->{error}) ) {
  505. readingsBulkUpdate($hash,'state',$hash->{".fhem"}{aptget}{cmd}.' Errors (get showErrorList)');
  506. readingsBulkUpdate($hash,'state','errors');
  507. } elsif( defined($decode_json->{warning}) ) {
  508. readingsBulkUpdate($hash,'state',$hash->{".fhem"}{aptget}{cmd}.' Warnings (get showWarningList)');
  509. readingsBulkUpdate($hash,'state','warnings');
  510. } else {
  511. readingsBulkUpdate($hash,'state',(scalar keys %{$decode_json->{packages}} > 0 ? 'system updates available' : 'system is up to date') );
  512. }
  513. readingsEndUpdate($hash,1);
  514. AptToDate_ProcessUpdateTimer($hash) if( $hash->{".fhem"}{aptget}{cmd} eq 'getDistribution' );
  515. }
  516. sub AptToDate_CreateUpgradeList($$) {
  517. my ($hash,$getCmd) = @_;
  518. my $packages;
  519. $packages = $hash->{".fhem"}{aptget}{packages} if($getCmd eq 'showUpgradeList');
  520. $packages = $hash->{".fhem"}{aptget}{updatedpackages} if($getCmd eq 'showUpdatedList');
  521. my $ret = '<html><table><tr><td>';
  522. $ret .= '<table class="block wide">';
  523. $ret .= '<tr class="even">';
  524. $ret .= "<td><b>Packagename</b></td>";
  525. $ret .= "<td><b>Current Version</b></td>" if($getCmd eq 'showUpgradeList');
  526. $ret .= "<td><b>Over Version</b></td>" if($getCmd eq 'showUpdatedList');
  527. $ret .= "<td><b>New Version</b></td>";;
  528. $ret .= "<td></td>";
  529. $ret .= '</tr>';
  530. if( ref($packages) eq "HASH" ) {
  531. my $linecount = 1;
  532. foreach my $package (keys (%{$packages}) ) {
  533. if ( $linecount % 2 == 0 ) {
  534. $ret .= '<tr class="even">';
  535. } else {
  536. $ret .= '<tr class="odd">';
  537. }
  538. $ret .= "<td>$package</td>";
  539. $ret .= "<td>$packages->{$package}{current}</td>";
  540. $ret .= "<td>$packages->{$package}{new}</td>";
  541. $ret .= '</tr>';
  542. $linecount++;
  543. }
  544. }
  545. $ret .= '</table></td></tr>';
  546. $ret .= '</table></html>';
  547. return $ret;
  548. }
  549. sub AptToDate_CreateWarningList($) {
  550. my $hash = shift;
  551. my $warnings = $hash->{".fhem"}{aptget}{'warnings'};
  552. my $ret = '<html><table><tr><td>';
  553. $ret .= '<table class="block wide">';
  554. $ret .= '<tr class="even">';
  555. $ret .= "<td><b>Warning List</b></td>";
  556. $ret .= "<td></td>";
  557. $ret .= '</tr>';
  558. if( ref($warnings) eq "ARRAY" ) {
  559. my $linecount = 1;
  560. foreach my $warning (@{$warnings}) {
  561. if ( $linecount % 2 == 0 ) {
  562. $ret .= '<tr class="even">';
  563. } else {
  564. $ret .= '<tr class="odd">';
  565. }
  566. $ret .= "<td>$warning->{message}</td>";
  567. $ret .= '</tr>';
  568. $linecount++;
  569. }
  570. }
  571. $ret .= '</table></td></tr>';
  572. $ret .= '</table></html>';
  573. return $ret;
  574. }
  575. sub AptToDate_CreateErrorList($) {
  576. my $hash = shift;
  577. my $errors = $hash->{".fhem"}{aptget}{'errors'};
  578. my $ret = '<html><table><tr><td>';
  579. $ret .= '<table class="block wide">';
  580. $ret .= '<tr class="even">';
  581. $ret .= "<td><b>Error List</b></td>";
  582. $ret .= "<td></td>";
  583. $ret .= '</tr>';
  584. if( ref($errors) eq "ARRAY" ) {
  585. my $linecount = 1;
  586. foreach my $error (@{$errors}) {
  587. if ( $linecount % 2 == 0 ) {
  588. $ret .= '<tr class="even">';
  589. } else {
  590. $ret .= '<tr class="odd">';
  591. }
  592. $ret .= "<td>$error->{message}</td>";
  593. $ret .= '</tr>';
  594. $linecount++;
  595. }
  596. }
  597. $ret .= '</table></td></tr>';
  598. $ret .= '</table></html>';
  599. return $ret;
  600. }
  601. #### my little helper
  602. sub AptToDate_ToDay() {
  603. my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime(gettimeofday());
  604. $month++;
  605. $year+=1900;
  606. my $today = sprintf('%04d-%02d-%02d', $year,$month,$mday);
  607. return $today;
  608. }
  609. 1;
  610. =pod
  611. =item device
  612. =item summary Modul to retrieves apt information about Debian update state
  613. =item summary_DE Modul um apt Updateinformationen von Debian Systemen zu bekommen
  614. =begin html
  615. <a name="AptToDate"></a>
  616. <h3>Apt Update Information</h3>
  617. <ul>
  618. <u><b>AptToDate - Retrieves apt information about Debian update state state</b></u>
  619. <br>
  620. With this module it is possible to read the apt update information from a Debian System.</br>
  621. It's required to insert "fhem ALL=NOPASSWD: /usr/bin/apt-get" via "visudo".
  622. <br><br>
  623. <a name="AptToDatedefine"></a>
  624. <b>Define</b>
  625. <ul><br>
  626. <code>define &lt;name&gt; AptToDate &lt;HOST&gt;</code>
  627. <br><br>
  628. Example:
  629. <ul><br>
  630. <code>define fhemServer AptToDate localhost</code><br>
  631. </ul>
  632. <br>
  633. This statement creates a AptToDate Device with the name fhemServer and the Host localhost.<br>
  634. After the device has been created the Modul is fetch all information about update state. This will need a moment.
  635. </ul>
  636. <br><br>
  637. <a name="AptToDatereadings"></a>
  638. <b>Readings</b>
  639. <ul>
  640. <li>state - update status about the server</li>
  641. <li>os-release_ - all information from /etc/os-release</li>
  642. <li>repoSync - status about last repository sync.</li>
  643. <li>toUpgrade - status about last upgrade</li>
  644. <li>updatesAvailable - number of available updates</li>
  645. </ul>
  646. <br><br>
  647. <a name="AptToDateset"></a>
  648. <b>Set</b>
  649. <ul>
  650. <li>repoSync - fetch information about update state</li>
  651. <li>toUpgrade - to run update process. this will take a moment</li>
  652. <br>
  653. </ul>
  654. <br><br>
  655. <a name="AptToDateget"></a>
  656. <b>Get</b>
  657. <ul>
  658. <li>showUpgradeList - list about available updates</li>
  659. <li>showUpdatedList - list about updated packages, from old version to new version</li>
  660. <li>showWarningList - list of all last warnings</li>
  661. <li>showErrorList - list of all last errors</li>
  662. <br>
  663. </ul>
  664. <br><br>
  665. <a name="AptToDate attribut"></a>
  666. <b>Attributes</b>
  667. <ul>
  668. <li>disable - disables the device</li>
  669. <li>upgradeListReading - add Upgrade List Reading as JSON</li>
  670. <li>distupgrade - change upgrade prozess to dist-upgrade</li>
  671. <li>disabledForIntervals - disable device for interval time (13:00-18:30 or 13:00-18:30 22:00-23:00)</li>
  672. </ul>
  673. </ul>
  674. =end html
  675. =begin html_DE
  676. <a name="AptToDate"></a>
  677. <h3>Apt Update Information</h3>
  678. <ul>
  679. <u><b>AptToDate - Stellt aktuelle Update Informationen von apt Debian Systemen bereit</b></u>
  680. <br>
  681. Das Modul synct alle Repositotys und stellt dann Informationen &uuml;ber zu aktualisierende Packete bereit.</br>
  682. Es ist Voraussetzung das folgende Zeile via "visudo" eingef&uuml;gt wird: "fhem ALL=NOPASSWD: /usr/bin/apt-get".
  683. <br><br>
  684. <a name="AptToDatedefine"></a>
  685. <b>Define</b>
  686. <ul><br>
  687. <code>define &lt;name&gt; AptToDate &lt;HOST&gt;</code>
  688. <br><br>
  689. Beispiel:
  690. <ul><br>
  691. <code>define fhemServer AptToDate localhost</code><br>
  692. </ul>
  693. <br>
  694. Der Befehl erstellt eine AptToDate Instanz mit dem Namen fhemServer und dem Host localhost.<br>
  695. Nachdem die Instanz erstellt wurde werden die ben&ouml;tigten Informationen geholt und als Readings angezeigt.
  696. Dies kann einen Moment dauern.
  697. </ul>
  698. <br><br>
  699. <a name="AptToDatereadings"></a>
  700. <b>Readings</b>
  701. <ul>
  702. <li>state - update Status des Servers, liegen neue Updates an oder nicht</li>
  703. <li>os-release_ - alle Informationen aus /etc/os-release</li>
  704. <li>repoSync - status des letzten repository sync.</li>
  705. <li>toUpgrade - status des letzten upgrade Befehles</li>
  706. <li>updatesAvailable - Anzahl der verf&uuml;gbaren Paketupdates</li>
  707. </ul>
  708. <br><br>
  709. <a name="AptToDateset"></a>
  710. <b>Set</b>
  711. <ul>
  712. <li>repoSync - holt aktuelle Informationen &uuml;ber den Updatestatus</li>
  713. <li>toUpgrade - f&uuml;hrt den upgrade prozess aus.</li>
  714. <br>
  715. </ul>
  716. <br><br>
  717. <a name="AptToDateget"></a>
  718. <b>Get</b>
  719. <ul>
  720. <li>showUpgradeList - Paketiste aller zur Verf&uuml;gung stehender Updates</li>
  721. <li>showUpdatedList - Liste aller als letztes aktualisierter Pakete, von der alten Version zur neuen Version</li>
  722. <li>showWarningList - Liste der letzten Warnings</li>
  723. <li>showErrorList - Liste der letzten Fehler</li>
  724. <br>
  725. </ul>
  726. <br><br>
  727. <a name="AptToDate attribut"></a>
  728. <b>Attributes</b>
  729. <ul>
  730. <li>disable - Deaktiviert das Device</li>
  731. <li>upgradeListReading - f&uuml;gt die Upgrade Liste als ein zus&auml;iches Reading im JSON Format ein.</li>
  732. <li>distupgrade - wechselt den upgrade Prozess nach dist-upgrade</li>
  733. <li>disabledForIntervals - Deaktiviert das Device f&uuml;r eine bestimmte Zeit (13:00-18:30 or 13:00-18:30 22:00-23:00)</li>
  734. </ul>
  735. </ul>
  736. =end html_DE
  737. =cut