42_AptToDate.pm 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  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 17627 2018-10-27 18:08:14Z CoolTux $
  30. #
  31. ###############################################################################
  32. package main;
  33. use strict;
  34. use warnings;
  35. my $version = "1.4.0";
  36. sub AptToDate_Initialize($) {
  37. my ($hash) = @_;
  38. $hash->{SetFn} = "AptToDate::Set";
  39. $hash->{GetFn} = "AptToDate::Get";
  40. $hash->{DefFn} = "AptToDate::Define";
  41. $hash->{NotifyFn} = "AptToDate::Notify";
  42. $hash->{UndefFn} = "AptToDate::Undef";
  43. $hash->{AttrFn} = "AptToDate::Attr";
  44. $hash->{AttrList} =
  45. "disable:1 "
  46. . "disabledForIntervals "
  47. . "upgradeListReading:1 "
  48. . "distupgrade:1 "
  49. . $readingFnAttributes;
  50. foreach my $d ( sort keys %{ $modules{AptToDate}{defptr} } ) {
  51. my $hash = $modules{AptToDate}{defptr}{$d};
  52. $hash->{VERSION} = $version;
  53. }
  54. }
  55. ## unserer packagename
  56. package AptToDate;
  57. use strict;
  58. use warnings;
  59. use POSIX;
  60. use GPUtils qw(:all)
  61. ; # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
  62. use Data::Dumper; #only for Debugging
  63. my $missingModul = "";
  64. eval "use JSON;1" or $missingModul .= "JSON ";
  65. ## Import der FHEM Funktionen
  66. BEGIN {
  67. GP_Import(
  68. qw(readingsSingleUpdate
  69. readingsBulkUpdate
  70. readingsBulkUpdateIfChanged
  71. readingsBeginUpdate
  72. readingsEndUpdate
  73. ReadingsTimestamp
  74. defs
  75. modules
  76. Log3
  77. CommandAttr
  78. attr
  79. AttrVal
  80. ReadingsVal
  81. Value
  82. IsDisabled
  83. deviceEvents
  84. init_done
  85. gettimeofday
  86. InternalTimer
  87. RemoveInternalTimer)
  88. );
  89. }
  90. my %regex = (
  91. 'en' => {
  92. 'update' => '^Reading package lists...$',
  93. 'upgrade' => '^Unpacking (\S+)\s\((\S+)\)\s+over\s+\((\S+)\)'
  94. },
  95. 'de' => {
  96. 'update' => '^Paketlisten werden gelesen...$',
  97. 'upgrade' => '^Entpacken von (\S+)\s\((\S+)\)\s+über\s+\((\S+)\)'
  98. }
  99. );
  100. sub Define($$) {
  101. my ( $hash, $def ) = @_;
  102. my @a = split( "[ \t][ \t]*", $def );
  103. return "too few parameters: define <name> AptToDate <HOST>" if ( @a != 3 );
  104. return
  105. "Cannot define AptToDate device. Perl modul ${missingModul}is missing."
  106. if ($missingModul);
  107. my $name = $a[0];
  108. my $host = $a[2];
  109. $hash->{VERSION} = $version;
  110. $hash->{HOST} = $host;
  111. $hash->{NOTIFYDEV} = "global,$name";
  112. readingsSingleUpdate( $hash, "state", "initialized", 1 )
  113. if ( ReadingsVal( $name, 'state', 'none' ) ne 'none' );
  114. CommandAttr( undef, $name . ' room AptToDate' )
  115. if ( AttrVal( $name, 'room', 'none' ) eq 'none' );
  116. CommandAttr( undef,
  117. $name
  118. . ' devStateIcon system.updates.available:security@red system.is.up.to.date:security@green .*in.progress:system_fhem_reboot@orange errors:message_attention@red'
  119. ) if ( AttrVal( $name, 'devStateIcon', 'none' ) eq 'none' );
  120. Log3 $name, 3, "AptToDate ($name) - defined";
  121. $modules{AptToDate}{defptr}{ $hash->{HOST} } = $hash;
  122. return undef;
  123. }
  124. sub Undef($$) {
  125. my ( $hash, $arg ) = @_;
  126. my $name = $hash->{NAME};
  127. if ( exists( $hash->{".fhem"}{subprocess} ) ) {
  128. my $subprocess = $hash->{".fhem"}{subprocess};
  129. $subprocess->terminate();
  130. $subprocess->wait();
  131. }
  132. RemoveInternalTimer($hash);
  133. delete( $modules{AptToDate}{defptr}{ $hash->{HOST} } );
  134. Log3 $name, 3, "Sub AptToDate ($name) - delete device $name";
  135. return undef;
  136. }
  137. sub Attr(@) {
  138. my ( $cmd, $name, $attrName, $attrVal ) = @_;
  139. my $hash = $defs{$name};
  140. if ( $attrName eq "disable" ) {
  141. if ( $cmd eq "set" and $attrVal eq "1" ) {
  142. RemoveInternalTimer($hash);
  143. readingsSingleUpdate( $hash, "state", "disabled", 1 );
  144. Log3 $name, 3, "AptToDate ($name) - disabled";
  145. }
  146. elsif ( $cmd eq "del" ) {
  147. Log3 $name, 3, "AptToDate ($name) - enabled";
  148. }
  149. }
  150. elsif ( $attrName eq "disabledForIntervals" ) {
  151. if ( $cmd eq "set" ) {
  152. return
  153. "check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'"
  154. unless ( $attrVal =~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/ );
  155. Log3 $name, 3, "AptToDate ($name) - disabledForIntervals";
  156. readingsSingleUpdate( $hash, "state", "disabled", 1 );
  157. }
  158. elsif ( $cmd eq "del" ) {
  159. Log3 $name, 3, "AptToDate ($name) - enabled";
  160. readingsSingleUpdate( $hash, "state", "active", 1 );
  161. }
  162. }
  163. return undef;
  164. }
  165. sub Notify($$) {
  166. my ( $hash, $dev ) = @_;
  167. my $name = $hash->{NAME};
  168. return if ( IsDisabled($name) );
  169. my $devname = $dev->{NAME};
  170. my $devtype = $dev->{TYPE};
  171. my $events = deviceEvents( $dev, 1 );
  172. return if ( !$events );
  173. Log3 $name, 5, "AptToDate ($name) - Notify: " . Dumper $events; # mit Dumper
  174. if (
  175. (
  176. (
  177. grep /^DEFINED.$name$/,
  178. @{$events}
  179. or grep /^DELETEATTR.$name.disable$/,
  180. @{$events}
  181. or grep /^ATTR.$name.disable.0$/,
  182. @{$events}
  183. )
  184. and $devname eq 'global'
  185. and $init_done
  186. )
  187. or (
  188. (
  189. grep /^INITIALIZED$/,
  190. @{$events}
  191. or grep /^REREADCFG$/,
  192. @{$events}
  193. or grep /^MODIFIED.$name$/,
  194. @{$events}
  195. )
  196. and $devname eq 'global'
  197. )
  198. or grep /^os-release_language:.(de|en)$/,
  199. @{$events}
  200. )
  201. {
  202. if (
  203. ref(
  204. eval { decode_json( ReadingsVal( $name, '.upgradeList', '' ) ) }
  205. ) eq "HASH"
  206. )
  207. {
  208. $hash->{".fhem"}{aptget}{packages} =
  209. eval { decode_json( ReadingsVal( $name, '.upgradeList', '' ) ) }
  210. ->{packages};
  211. }
  212. elsif (
  213. ref(
  214. eval { decode_json( ReadingsVal( $name, '.updatedList', '' ) ) }
  215. ) eq "HASH"
  216. )
  217. {
  218. $hash->{".fhem"}{aptget}{updatedpackages} =
  219. eval { decode_json( ReadingsVal( $name, '.updatedList', '' ) ) }
  220. ->{packages};
  221. }
  222. if ( ReadingsVal( $name, 'os-release_language', 'none' ) ne 'none' ) {
  223. ProcessUpdateTimer($hash);
  224. }
  225. else {
  226. $hash->{".fhem"}{aptget}{cmd} = 'getDistribution';
  227. AsynchronousExecuteAptGetCommand($hash);
  228. }
  229. }
  230. if (
  231. $devname eq $name
  232. and (
  233. grep /^repoSync:.fetched.done$/,
  234. @{$events} or grep /^toUpgrade:.successful$/,
  235. @{$events}
  236. )
  237. )
  238. {
  239. $hash->{".fhem"}{aptget}{cmd} = 'getUpdateList';
  240. AsynchronousExecuteAptGetCommand($hash);
  241. }
  242. return;
  243. }
  244. sub Set($$@) {
  245. my ( $hash, $name, @aa ) = @_;
  246. my ( $cmd, @args ) = @aa;
  247. if ( $cmd eq 'repoSync' ) {
  248. return "usage: $cmd" if ( @args != 0 );
  249. $hash->{".fhem"}{aptget}{cmd} = $cmd;
  250. }
  251. elsif ( $cmd eq 'toUpgrade' ) {
  252. return "usage: $cmd" if ( @args != 0 );
  253. $hash->{".fhem"}{aptget}{cmd} = $cmd;
  254. }
  255. else {
  256. my $list = "repoSync:noArg";
  257. $list .= " toUpgrade:noArg"
  258. if ( defined( $hash->{".fhem"}{aptget}{packages} )
  259. and scalar keys %{ $hash->{".fhem"}{aptget}{packages} } > 0 );
  260. return "Unknown argument $cmd, choose one of $list";
  261. }
  262. if ( ReadingsVal( $name, 'os-release_language', 'none' ) eq 'de'
  263. or ReadingsVal( $name, 'os-release_language', 'none' ) eq 'en' )
  264. {
  265. AsynchronousExecuteAptGetCommand($hash);
  266. }
  267. else {
  268. readingsSingleUpdate( $hash, "state", "language not supported", 1 );
  269. Log3 $name, 2,
  270. "AptToDate ($name) - sorry, your systems language is not supported";
  271. }
  272. return undef;
  273. }
  274. sub Get($$@) {
  275. my ( $hash, $name, @aa ) = @_;
  276. my ( $cmd, @args ) = @aa;
  277. if ( $cmd eq 'showUpgradeList' ) {
  278. return "usage: $cmd" if ( @args != 0 );
  279. my $ret = CreateUpgradeList( $hash, $cmd );
  280. return $ret;
  281. }
  282. elsif ( $cmd eq 'showUpdatedList' ) {
  283. return "usage: $cmd" if ( @args != 0 );
  284. my $ret = CreateUpgradeList( $hash, $cmd );
  285. return $ret;
  286. }
  287. elsif ( $cmd eq 'showWarningList' ) {
  288. return "usage: $cmd" if ( @args != 0 );
  289. my $ret = CreateWarningList($hash);
  290. return $ret;
  291. }
  292. elsif ( $cmd eq 'showErrorList' ) {
  293. return "usage: $cmd" if ( @args != 0 );
  294. my $ret = CreateErrorList($hash);
  295. return $ret;
  296. }
  297. else {
  298. my $list = "";
  299. $list .= " showUpgradeList:noArg"
  300. if ( defined( $hash->{".fhem"}{aptget}{packages} )
  301. and scalar keys %{ $hash->{".fhem"}{aptget}{packages} } > 0 );
  302. $list .= " showUpdatedList:noArg"
  303. if ( defined( $hash->{".fhem"}{aptget}{updatedpackages} )
  304. and scalar
  305. keys %{ $hash->{".fhem"}{aptget}{updatedpackages} } > 0 );
  306. $list .= " showWarningList:noArg"
  307. if ( defined( $hash->{".fhem"}{aptget}{'warnings'} )
  308. and scalar @{ $hash->{".fhem"}{aptget}{'warnings'} } > 0 );
  309. $list .= " showErrorList:noArg"
  310. if ( defined( $hash->{".fhem"}{aptget}{'errors'} )
  311. and scalar @{ $hash->{".fhem"}{aptget}{'errors'} } > 0 );
  312. return "Unknown argument $cmd, choose one of $list";
  313. }
  314. }
  315. ###################################
  316. sub ProcessUpdateTimer($) {
  317. my $hash = shift;
  318. my $name = $hash->{NAME};
  319. RemoveInternalTimer($hash);
  320. InternalTimer(
  321. gettimeofday() + 14400,
  322. "AptToDate::ProcessUpdateTimer",
  323. $hash, 0
  324. );
  325. Log3 $name, 4, "AptToDate ($name) - stateRequestTimer: Call Request Timer";
  326. if ( ReadingsVal( $name, 'os-release_language', 'none' ) eq 'de'
  327. or ReadingsVal( $name, 'os-release_language', 'none' ) eq 'en' )
  328. {
  329. if ( !IsDisabled($name) ) {
  330. if ( exists( $hash->{".fhem"}{subprocess} ) ) {
  331. Log3 $name, 2,
  332. "AptToDate ($name) - update in progress, process aborted.";
  333. return 0;
  334. }
  335. readingsSingleUpdate( $hash, "state", "ready", 1 )
  336. if ( ReadingsVal( $name, 'state', 'none' ) eq 'none'
  337. or ReadingsVal( $name, 'state', 'none' ) eq 'initialized' );
  338. if (
  339. ToDay() ne (
  340. split(
  341. ' ',
  342. ReadingsTimestamp( $name, 'repoSync', '1970-01-01' )
  343. )
  344. )[0]
  345. )
  346. {
  347. $hash->{".fhem"}{aptget}{cmd} = 'repoSync';
  348. AsynchronousExecuteAptGetCommand($hash);
  349. }
  350. }
  351. }
  352. else {
  353. readingsSingleUpdate( $hash, "state", "language not supported", 1 );
  354. Log3 $name, 2,
  355. "AptToDate ($name) - sorry, your systems language is not supported";
  356. }
  357. }
  358. sub CleanSubprocess($) {
  359. my $hash = shift;
  360. my $name = $hash->{NAME};
  361. delete( $hash->{".fhem"}{subprocess} );
  362. Log3 $name, 4, "AptToDate ($name) - clean Subprocess";
  363. }
  364. use constant POLLINTERVAL => 1;
  365. sub AsynchronousExecuteAptGetCommand($) {
  366. require "SubProcess.pm";
  367. my ($hash) = shift;
  368. my $name = $hash->{NAME};
  369. $hash->{".fhem"}{aptget}{lang} =
  370. ReadingsVal( $name, 'os-release_language', 'none' );
  371. my $subprocess = SubProcess->new( { onRun => \&OnRun } );
  372. $subprocess->{aptget} = $hash->{".fhem"}{aptget};
  373. $subprocess->{aptget}{host} = $hash->{HOST};
  374. $subprocess->{aptget}{debug} =
  375. ( AttrVal( $name, 'verbose', 0 ) > 3 ? 1 : 0 );
  376. $subprocess->{aptget}{distupgrade} =
  377. ( AttrVal( $name, 'distupgrade', 0 ) == 1 ? 1 : 0 );
  378. my $pid = $subprocess->run();
  379. readingsSingleUpdate( $hash, 'state',
  380. $hash->{".fhem"}{aptget}{cmd} . ' in progress', 1 );
  381. if ( !defined($pid) ) {
  382. Log3 $name, 1,
  383. "AptToDate ($name) - Cannot execute command asynchronously";
  384. CleanSubprocess($hash);
  385. readingsSingleUpdate( $hash, 'state',
  386. 'Cannot execute command asynchronously', 1 );
  387. return undef;
  388. }
  389. Log3 $name, 4,
  390. "AptToDate ($name) - execute command asynchronously (PID= $pid)";
  391. $hash->{".fhem"}{subprocess} = $subprocess;
  392. InternalTimer( gettimeofday() + POLLINTERVAL,
  393. "AptToDate::PollChild", $hash, 0 );
  394. Log3 $hash, 4, "AptToDate ($name) - control passed back to main loop.";
  395. }
  396. sub PollChild($) {
  397. my $hash = shift;
  398. my $name = $hash->{NAME};
  399. my $subprocess = $hash->{".fhem"}{subprocess};
  400. my $json = $subprocess->readFromChild();
  401. if ( !defined($json) ) {
  402. Log3 $name, 5, "AptToDate ($name) - still waiting ("
  403. . $subprocess->{lasterror} . ").";
  404. InternalTimer( gettimeofday() + POLLINTERVAL,
  405. "AptToDate::PollChild", $hash, 0 );
  406. return;
  407. }
  408. else {
  409. Log3 $name, 4,
  410. "AptToDate ($name) - got result from asynchronous parsing.";
  411. $subprocess->wait();
  412. Log3 $name, 4, "AptToDate ($name) - asynchronous finished.";
  413. CleanSubprocess($hash);
  414. PreProcessing( $hash, $json );
  415. }
  416. }
  417. ######################################
  418. # Begin Childprozess
  419. ######################################
  420. sub OnRun() {
  421. my $subprocess = shift;
  422. my $response = ExecuteAptGetCommand( $subprocess->{aptget} );
  423. my $json = eval { encode_json($response) };
  424. if ($@) {
  425. Log3 'AptToDate OnRun', 3, "AptToDate - JSON error: $@";
  426. $json = '{"jsonerror":"$@"}';
  427. }
  428. $subprocess->writeToParent($json);
  429. }
  430. sub ExecuteAptGetCommand($) {
  431. my $aptget = shift;
  432. my $apt = {};
  433. $apt->{lang} = $aptget->{lang};
  434. $apt->{debug} = $aptget->{debug};
  435. if ( $aptget->{host} ne 'localhost' ) {
  436. $apt->{aptgetupdate} =
  437. 'ssh ' . $aptget->{host} . ' \'echo n | sudo apt-get -q update\'';
  438. $apt->{distri} = 'ssh ' . $aptget->{host} . ' cat /etc/os-release |';
  439. $apt->{'locale'} = 'ssh ' . $aptget->{host} . ' locale';
  440. $apt->{aptgetupgrade} = 'ssh '
  441. . $aptget->{host}
  442. . ' \'echo n | sudo apt-get -s -q -V upgrade\'';
  443. $apt->{aptgettoupgrade} = 'ssh '
  444. . $aptget->{host}
  445. . ' \'echo n | sudo apt-get -y -q -V upgrade\''
  446. if ( $aptget->{distupgrade} == 0 );
  447. $apt->{aptgettoupgrade} = 'ssh '
  448. . $aptget->{host}
  449. . ' \'echo n | sudo apt-get -y -q -V dist-upgrade\''
  450. if ( $aptget->{distupgrade} == 1 );
  451. }
  452. else {
  453. $apt->{aptgetupdate} = 'echo n | sudo apt-get -q update';
  454. $apt->{distri} = '</etc/os-release';
  455. $apt->{'locale'} = 'locale';
  456. $apt->{aptgetupgrade} = 'echo n | sudo apt-get -s -q -V upgrade';
  457. $apt->{aptgettoupgrade} = 'echo n | sudo apt-get -y -q -V upgrade'
  458. if ( $aptget->{distupgrade} == 0 );
  459. $apt->{aptgettoupgrade} = 'echo n | sudo apt-get -y -q -V dist-upgrade'
  460. if ( $aptget->{distupgrade} == 1 );
  461. }
  462. my $response;
  463. if ( $aptget->{cmd} eq 'repoSync' ) {
  464. $response = AptUpdate($apt);
  465. }
  466. elsif ( $aptget->{cmd} eq 'getUpdateList' ) {
  467. $response = AptUpgradeList($apt);
  468. }
  469. elsif ( $aptget->{cmd} eq 'getDistribution' ) {
  470. $response = GetDistribution($apt);
  471. }
  472. elsif ( $aptget->{cmd} eq 'toUpgrade' ) {
  473. $response = AptToUpgrade($apt);
  474. }
  475. return $response;
  476. }
  477. sub GetDistribution($) {
  478. my $apt = shift;
  479. my $update = {};
  480. if ( open( DISTRI, "$apt->{distri}" ) ) {
  481. while ( my $line = <DISTRI> ) {
  482. chomp($line);
  483. print qq($line\n) if ( $apt->{debug} == 1 );
  484. if ( $line =~ m#^(.*)="?(.*)"$#i or $line =~ m#^(.*)=([a-z]+)$#i ) {
  485. $update->{'os-release'}{ 'os-release_' . $1 } = $2;
  486. Log3 'Update', 4, "Distribution Daten erhalten";
  487. }
  488. }
  489. close(DISTRI);
  490. }
  491. else {
  492. die "Couldn't use DISTRI: $!\n";
  493. $update->{error} = 'Couldn\'t use DISTRI: ' . $;;
  494. }
  495. if ( open( LOCALE, "$apt->{'locale'} 2>&1 |" ) ) {
  496. while ( my $line = <LOCALE> ) {
  497. chomp($line);
  498. print qq($line\n) if ( $apt->{debug} == 1 );
  499. if ( $line =~ m#^LANG=([a-z]+).*$# ) {
  500. $update->{'os-release'}{'os-release_language'} = $1;
  501. Log3 'Update', 4, "Language Daten erhalten";
  502. }
  503. }
  504. $update->{'os-release'}{'os-release_language'} = 'en'
  505. if ( not defined( $update->{'os-release'}{'os-release_language'} ) );
  506. close(LOCALE);
  507. }
  508. else {
  509. die "Couldn't use APT: $!\n";
  510. $update->{error} = 'Couldn\'t use LOCALE: ' . $;;
  511. }
  512. return $update;
  513. }
  514. sub AptUpdate($) {
  515. my $apt = shift;
  516. my $update = {};
  517. if ( open( APT, "$apt->{aptgetupdate} 2>&1 | " ) ) {
  518. while ( my $line = <APT> ) {
  519. chomp($line);
  520. print qq($line\n) if ( $apt->{debug} == 1 );
  521. if ( $line =~ m#$regex{$apt->{lang}}{update}#i ) {
  522. $update->{'state'} = 'done';
  523. Log3 'Update', 4, "Daten erhalten";
  524. }
  525. elsif ( $line =~ s#^E: ## ) { # error
  526. my $error = {};
  527. $error->{message} = $line;
  528. push( @{ $update->{error} }, $error );
  529. $update->{'state'} = 'errors';
  530. Log3 'Update', 4, "Error";
  531. }
  532. elsif ( $line =~ s#^W: ## ) { # warning
  533. my $warning = {};
  534. $warning->{message} = $line;
  535. push( @{ $update->{warning} }, $warning );
  536. $update->{'state'} = 'warnings';
  537. Log3 'Update', 4, "Warning";
  538. }
  539. }
  540. close(APT);
  541. }
  542. else {
  543. die "Couldn't use APT: $!\n";
  544. $update->{error} = 'Couldn\'t use APT: ' . $;;
  545. }
  546. return $update;
  547. }
  548. sub AptUpgradeList($) {
  549. my $apt = shift;
  550. my $updates = {};
  551. if ( open( APT, "$apt->{aptgetupgrade} 2>&1 |" ) ) {
  552. while ( my $line = <APT> ) {
  553. chomp($line);
  554. print qq($line\n) if ( $apt->{debug} == 1 );
  555. if ( $line =~ m#^\s+(\S+)\s+\((\S+)\s+=>\s+(\S+)\)# ) {
  556. my $update = {};
  557. my $package = $1;
  558. $update->{current} = $2;
  559. $update->{new} = $3;
  560. $updates->{packages}->{$package} = $update;
  561. }
  562. elsif ( $line =~ s#^W: ## ) { # warning
  563. my $warning = {};
  564. $warning->{message} = $line;
  565. push( @{ $updates->{warning} }, $warning );
  566. }
  567. elsif ( $line =~ s#^E: ## ) { # error
  568. my $error = {};
  569. $error->{message} = $line;
  570. push( @{ $updates->{error} }, $error );
  571. }
  572. }
  573. close(APT);
  574. }
  575. else {
  576. die "Couldn't use APT: $!\n";
  577. $updates->{error} = 'Couldn\'t use APT: ' . $;;
  578. }
  579. return $updates;
  580. }
  581. sub AptToUpgrade($) {
  582. my $apt = shift;
  583. my $updates = {};
  584. if ( open( APT, "$apt->{aptgettoupgrade} 2>&1 |" ) ) {
  585. while ( my $line = <APT> ) {
  586. chomp($line);
  587. print qq($line\n) if ( $apt->{debug} == 1 );
  588. if ( $line =~ m#$regex{$apt->{lang}}{upgrade}# ) {
  589. my $update = {};
  590. my $package = $1;
  591. $update->{new} = $2;
  592. $update->{current} = $3;
  593. $updates->{packages}->{$package} = $update;
  594. }
  595. elsif ( $line =~ s#^W: ## ) { # warning
  596. my $warning = {};
  597. $warning->{message} = $line;
  598. push( @{ $updates->{warning} }, $warning );
  599. }
  600. elsif ( $line =~ s#^E: ## ) { # error
  601. my $error = {};
  602. $error->{message} = $line;
  603. push( @{ $updates->{error} }, $error );
  604. }
  605. }
  606. close(APT);
  607. }
  608. else {
  609. die "Couldn't use APT: $!\n";
  610. $updates->{error} = 'Couldn\'t use APT: ' . $;;
  611. }
  612. return $updates;
  613. }
  614. ####################################################
  615. # End Childprozess
  616. ####################################################
  617. sub PreProcessing($$) {
  618. my ( $hash, $json ) = @_;
  619. my $name = $hash->{NAME};
  620. my $decode_json = eval { decode_json($json) };
  621. if ($@) {
  622. Log3 $name, 2, "AptToDate ($name) - JSON error: $@";
  623. return;
  624. }
  625. Log3 $hash, 4, "AptToDate ($name) - JSON: $json";
  626. if ( $hash->{".fhem"}{aptget}{cmd} eq 'getUpdateList' ) {
  627. $hash->{".fhem"}{aptget}{packages} = $decode_json->{packages};
  628. readingsSingleUpdate( $hash, '.upgradeList', $json, 0 );
  629. }
  630. elsif ( $hash->{".fhem"}{aptget}{cmd} eq 'toUpgrade' ) {
  631. $hash->{".fhem"}{aptget}{updatedpackages} = $decode_json->{packages};
  632. readingsSingleUpdate( $hash, '.updatedList', $json, 0 );
  633. }
  634. if ( defined( $decode_json->{warning} )
  635. or defined( $decode_json->{error} ) )
  636. {
  637. $hash->{".fhem"}{aptget}{'warnings'} = $decode_json->{warning}
  638. if ( defined( $decode_json->{warning} ) );
  639. $hash->{".fhem"}{aptget}{errors} = $decode_json->{error}
  640. if ( defined( $decode_json->{error} ) );
  641. }
  642. else {
  643. delete $hash->{".fhem"}{aptget}{'warnings'};
  644. delete $hash->{".fhem"}{aptget}{errors};
  645. }
  646. WriteReadings( $hash, $decode_json );
  647. }
  648. sub WriteReadings($$) {
  649. my ( $hash, $decode_json ) = @_;
  650. my $name = $hash->{NAME};
  651. Log3 $hash, 4, "AptToDate ($name) - Write Readings";
  652. Log3 $hash, 5, "AptToDate ($name) - " . Dumper $decode_json;
  653. Log3 $hash, 5, "AptToDate ($name) - Packges Anzahl: " . scalar
  654. keys %{ $decode_json->{packages} };
  655. Log3 $hash, 5, "AptToDate ($name) - Inhalt aptget cmd: " . scalar
  656. keys %{ $decode_json->{packages} };
  657. readingsBeginUpdate($hash);
  658. if ( $hash->{".fhem"}{aptget}{cmd} eq 'repoSync' ) {
  659. readingsBulkUpdate(
  660. $hash,
  661. 'repoSync',
  662. (
  663. defined( $decode_json->{'state'} )
  664. ? 'fetched ' . $decode_json->{'state'}
  665. : 'fetched error'
  666. )
  667. );
  668. $hash->{helper}{lastSync} = ToDay();
  669. }
  670. readingsBulkUpdateIfChanged( $hash, 'updatesAvailable',
  671. scalar keys %{ $decode_json->{packages} } )
  672. if ( $hash->{".fhem"}{aptget}{cmd} eq 'getUpdateList' );
  673. readingsBulkUpdateIfChanged( $hash, 'upgradeListAsJSON',
  674. eval { encode_json( $hash->{".fhem"}{aptget}{packages} ) } )
  675. if ( AttrVal( $name, 'upgradeListReading', 'none' ) ne 'none' );
  676. readingsBulkUpdate( $hash, 'toUpgrade', 'successful' )
  677. if ( $hash->{".fhem"}{aptget}{cmd} eq 'toUpgrade'
  678. and not defined( $hash->{".fhem"}{aptget}{'errors'} )
  679. and not defined( $hash->{".fhem"}{aptget}{'warnings'} ) );
  680. if ( $hash->{".fhem"}{aptget}{cmd} eq 'getDistribution' ) {
  681. while ( my ( $r, $v ) = each %{ $decode_json->{'os-release'} } ) {
  682. readingsBulkUpdateIfChanged( $hash, $r, $v );
  683. }
  684. }
  685. if ( defined( $decode_json->{error} ) ) {
  686. readingsBulkUpdate( $hash, 'state',
  687. $hash->{".fhem"}{aptget}{cmd} . ' Errors (get showErrorList)' );
  688. readingsBulkUpdate( $hash, 'state', 'errors' );
  689. }
  690. elsif ( defined( $decode_json->{warning} ) ) {
  691. readingsBulkUpdate( $hash, 'state',
  692. $hash->{".fhem"}{aptget}{cmd} . ' Warnings (get showWarningList)' );
  693. readingsBulkUpdate( $hash, 'state', 'warnings' );
  694. }
  695. else {
  696. readingsBulkUpdate(
  697. $hash, 'state',
  698. (
  699. scalar keys %{ $decode_json->{packages} } > 0
  700. ? 'system updates available'
  701. : 'system is up to date'
  702. )
  703. );
  704. }
  705. readingsEndUpdate( $hash, 1 );
  706. ProcessUpdateTimer($hash)
  707. if ( $hash->{".fhem"}{aptget}{cmd} eq 'getDistribution' );
  708. }
  709. sub CreateUpgradeList($$) {
  710. my ( $hash, $getCmd ) = @_;
  711. my $packages;
  712. $packages = $hash->{".fhem"}{aptget}{packages}
  713. if ( $getCmd eq 'showUpgradeList' );
  714. $packages = $hash->{".fhem"}{aptget}{updatedpackages}
  715. if ( $getCmd eq 'showUpdatedList' );
  716. my $ret = '<html><table><tr><td>';
  717. $ret .= '<table class="block wide">';
  718. $ret .= '<tr class="even">';
  719. $ret .= "<td><b>Packagename</b></td>";
  720. $ret .= "<td><b>Current Version</b></td>"
  721. if ( $getCmd eq 'showUpgradeList' );
  722. $ret .= "<td><b>Over Version</b></td>" if ( $getCmd eq 'showUpdatedList' );
  723. $ret .= "<td><b>New Version</b></td>";
  724. $ret .= "<td></td>";
  725. $ret .= '</tr>';
  726. if ( ref($packages) eq "HASH" ) {
  727. my $linecount = 1;
  728. foreach my $package ( keys( %{$packages} ) ) {
  729. if ( $linecount % 2 == 0 ) {
  730. $ret .= '<tr class="even">';
  731. }
  732. else {
  733. $ret .= '<tr class="odd">';
  734. }
  735. $ret .= "<td>$package</td>";
  736. $ret .= "<td>$packages->{$package}{current}</td>";
  737. $ret .= "<td>$packages->{$package}{new}</td>";
  738. $ret .= '</tr>';
  739. $linecount++;
  740. }
  741. }
  742. $ret .= '</table></td></tr>';
  743. $ret .= '</table></html>';
  744. return $ret;
  745. }
  746. sub CreateWarningList($) {
  747. my $hash = shift;
  748. my $warnings = $hash->{".fhem"}{aptget}{'warnings'};
  749. my $ret = '<html><table><tr><td>';
  750. $ret .= '<table class="block wide">';
  751. $ret .= '<tr class="even">';
  752. $ret .= "<td><b>Warning List</b></td>";
  753. $ret .= "<td></td>";
  754. $ret .= '</tr>';
  755. if ( ref($warnings) eq "ARRAY" ) {
  756. my $linecount = 1;
  757. foreach my $warning ( @{$warnings} ) {
  758. if ( $linecount % 2 == 0 ) {
  759. $ret .= '<tr class="even">';
  760. }
  761. else {
  762. $ret .= '<tr class="odd">';
  763. }
  764. $ret .= "<td>$warning->{message}</td>";
  765. $ret .= '</tr>';
  766. $linecount++;
  767. }
  768. }
  769. $ret .= '</table></td></tr>';
  770. $ret .= '</table></html>';
  771. return $ret;
  772. }
  773. sub CreateErrorList($) {
  774. my $hash = shift;
  775. my $errors = $hash->{".fhem"}{aptget}{'errors'};
  776. my $ret = '<html><table><tr><td>';
  777. $ret .= '<table class="block wide">';
  778. $ret .= '<tr class="even">';
  779. $ret .= "<td><b>Error List</b></td>";
  780. $ret .= "<td></td>";
  781. $ret .= '</tr>';
  782. if ( ref($errors) eq "ARRAY" ) {
  783. my $linecount = 1;
  784. foreach my $error ( @{$errors} ) {
  785. if ( $linecount % 2 == 0 ) {
  786. $ret .= '<tr class="even">';
  787. }
  788. else {
  789. $ret .= '<tr class="odd">';
  790. }
  791. $ret .= "<td>$error->{message}</td>";
  792. $ret .= '</tr>';
  793. $linecount++;
  794. }
  795. }
  796. $ret .= '</table></td></tr>';
  797. $ret .= '</table></html>';
  798. return $ret;
  799. }
  800. #### my little helper
  801. sub ToDay() {
  802. my ( $sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst ) =
  803. localtime( gettimeofday() );
  804. $month++;
  805. $year += 1900;
  806. my $today = sprintf( '%04d-%02d-%02d', $year, $month, $mday );
  807. return $today;
  808. }
  809. 1;
  810. =pod
  811. =item device
  812. =item summary Modul to retrieves apt information about Debian update state
  813. =item summary_DE Modul um apt Updateinformationen von Debian Systemen zu bekommen
  814. =begin html
  815. <a name="AptToDate"></a>
  816. <h3>Apt Update Information</h3>
  817. <ul>
  818. <u><b>AptToDate - Retrieves apt information about Debian update state state</b></u>
  819. <br>
  820. With this module it is possible to read the apt update information from a Debian System.</br>
  821. It's required to insert "fhem ALL=NOPASSWD: /usr/bin/apt-get" via "visudo".
  822. <br><br>
  823. <a name="AptToDatedefine"></a>
  824. <b>Define</b>
  825. <ul><br>
  826. <code>define &lt;name&gt; AptToDate &lt;HOST&gt;</code>
  827. <br><br>
  828. Example:
  829. <ul><br>
  830. <code>define fhemServer AptToDate localhost</code><br>
  831. </ul>
  832. <br>
  833. This statement creates a AptToDate Device with the name fhemServer and the Host localhost.<br>
  834. After the device has been created the Modul is fetch all information about update state. This will need a moment.
  835. </ul>
  836. <br><br>
  837. <a name="AptToDatereadings"></a>
  838. <b>Readings</b>
  839. <ul>
  840. <li>state - update status about the server</li>
  841. <li>os-release_ - all information from /etc/os-release</li>
  842. <li>repoSync - status about last repository sync.</li>
  843. <li>toUpgrade - status about last upgrade</li>
  844. <li>updatesAvailable - number of available updates</li>
  845. </ul>
  846. <br><br>
  847. <a name="AptToDateset"></a>
  848. <b>Set</b>
  849. <ul>
  850. <li>repoSync - fetch information about update state</li>
  851. <li>toUpgrade - to run update process. this will take a moment</li>
  852. <br>
  853. </ul>
  854. <br><br>
  855. <a name="AptToDateget"></a>
  856. <b>Get</b>
  857. <ul>
  858. <li>showUpgradeList - list about available updates</li>
  859. <li>showUpdatedList - list about updated packages, from old version to new version</li>
  860. <li>showWarningList - list of all last warnings</li>
  861. <li>showErrorList - list of all last errors</li>
  862. <br>
  863. </ul>
  864. <br><br>
  865. <a name="AptToDate attribut"></a>
  866. <b>Attributes</b>
  867. <ul>
  868. <li>disable - disables the device</li>
  869. <li>upgradeListReading - add Upgrade List Reading as JSON</li>
  870. <li>distupgrade - change upgrade prozess to dist-upgrade</li>
  871. <li>disabledForIntervals - disable device for interval time (13:00-18:30 or 13:00-18:30 22:00-23:00)</li>
  872. </ul>
  873. </ul>
  874. =end html
  875. =begin html_DE
  876. <a name="AptToDate"></a>
  877. <h3>Apt Update Information</h3>
  878. <ul>
  879. <u><b>AptToDate - Stellt aktuelle Update Informationen von apt Debian Systemen bereit</b></u>
  880. <br>
  881. Das Modul synct alle Repositotys und stellt dann Informationen &uuml;ber zu aktualisierende Packete bereit.</br>
  882. Es ist Voraussetzung das folgende Zeile via "visudo" eingef&uuml;gt wird: "fhem ALL=NOPASSWD: /usr/bin/apt-get".
  883. <br><br>
  884. <a name="AptToDatedefine"></a>
  885. <b>Define</b>
  886. <ul><br>
  887. <code>define &lt;name&gt; AptToDate &lt;HOST&gt;</code>
  888. <br><br>
  889. Beispiel:
  890. <ul><br>
  891. <code>define fhemServer AptToDate localhost</code><br>
  892. </ul>
  893. <br>
  894. Der Befehl erstellt eine AptToDate Instanz mit dem Namen fhemServer und dem Host localhost.<br>
  895. Nachdem die Instanz erstellt wurde werden die ben&ouml;tigten Informationen geholt und als Readings angezeigt.
  896. Dies kann einen Moment dauern.
  897. </ul>
  898. <br><br>
  899. <a name="AptToDatereadings"></a>
  900. <b>Readings</b>
  901. <ul>
  902. <li>state - update Status des Servers, liegen neue Updates an oder nicht</li>
  903. <li>os-release_ - alle Informationen aus /etc/os-release</li>
  904. <li>repoSync - status des letzten repository sync.</li>
  905. <li>toUpgrade - status des letzten upgrade Befehles</li>
  906. <li>updatesAvailable - Anzahl der verf&uuml;gbaren Paketupdates</li>
  907. </ul>
  908. <br><br>
  909. <a name="AptToDateset"></a>
  910. <b>Set</b>
  911. <ul>
  912. <li>repoSync - holt aktuelle Informationen &uuml;ber den Updatestatus</li>
  913. <li>toUpgrade - f&uuml;hrt den upgrade prozess aus.</li>
  914. <br>
  915. </ul>
  916. <br><br>
  917. <a name="AptToDateget"></a>
  918. <b>Get</b>
  919. <ul>
  920. <li>showUpgradeList - Paketiste aller zur Verf&uuml;gung stehender Updates</li>
  921. <li>showUpdatedList - Liste aller als letztes aktualisierter Pakete, von der alten Version zur neuen Version</li>
  922. <li>showWarningList - Liste der letzten Warnings</li>
  923. <li>showErrorList - Liste der letzten Fehler</li>
  924. <br>
  925. </ul>
  926. <br><br>
  927. <a name="AptToDate attribut"></a>
  928. <b>Attributes</b>
  929. <ul>
  930. <li>disable - Deaktiviert das Device</li>
  931. <li>upgradeListReading - f&uuml;gt die Upgrade Liste als ein zus&auml;iches Reading im JSON Format ein.</li>
  932. <li>distupgrade - wechselt den upgrade Prozess nach dist-upgrade</li>
  933. <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>
  934. </ul>
  935. </ul>
  936. =end html_DE
  937. =cut