configDB.pm 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279
  1. # $Id: configDB.pm 17297 2018-09-08 11:01:04Z betateilchen $
  2. =for comment
  3. ##############################################################################
  4. #
  5. # configDB.pm
  6. #
  7. # A fhem library to enable configuration from sql database
  8. # instead of plain text file, e.g. fhem.cfg
  9. #
  10. # READ COMMANDREF DOCUMENTATION FOR CORRECT USE!
  11. #
  12. # Copyright: betateilchen ®
  13. #
  14. # This file is part of fhem.
  15. #
  16. # Fhem is free software: you can redistribute it and/or modify
  17. # it under the terms of the GNU General Public License as published by
  18. # the Free Software Foundation, either version 2 of the License, or
  19. # (at your option) any later version.
  20. #
  21. # Fhem is distributed in the hope that it will be useful,
  22. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. # GNU General Public License for more details.
  25. #
  26. # You should have received a copy of the GNU General Public License
  27. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  28. #
  29. ##############################################################################
  30. #
  31. # ChangeLog
  32. #
  33. # 2014-03-01 - SVN 5080 - initial release of interface inside fhem.pl
  34. # - initial release of configDB.pm
  35. #
  36. # 2014-03-02 - added template files for sqlite in contrib/configDB
  37. # - updated commandref (EN) documentation
  38. # - added commandref (DE) documentation
  39. #
  40. # 2014-03-03 - changed performance optimized by using version uuid table
  41. # - updated commandref docu for migration
  42. # - added cfgDB_svnId for fhem.pl CommandVersion
  43. # - added cfgDB_List to show device info from database
  44. # - updated commandref docu for cfgDB_List
  45. #
  46. # 2014-03-06 - added cfgDB_Diff to compare device in two versions
  47. #
  48. # 2014-03-07 - changed optimized cfgDB_Diff
  49. # restructured libraray internally
  50. # improved source code documentation
  51. #
  52. # 2014-03-20 - added export/import
  53. # 2014-04-01 - removed export/import due to not working properly
  54. #
  55. # 2014-04-03 - fixed global attributes not read from version 0
  56. #
  57. # 2014-04-18 - added commands fileimport, fileexport
  58. # 2014-04-19 - added commands filelist, filedelete
  59. # interface cfgDB_Readfile for interaction
  60. # with other modules
  61. #
  62. # 2014-04-21 - added interface functions for FHEMWEB and fhem.pl
  63. # to show files in "Edit files" and use them
  64. # with CommandReload() mechanism
  65. #
  66. # modified _cfgDB_Info to show number of files in db
  67. #
  68. # 2014-04-23 - added command fileshow, filemove
  69. #
  70. # 2014-04-26 - added migration to generic file handling
  71. # fixed problem on migration of multiline DEFs
  72. #
  73. # 2014-04-27 - added new functions for binfile handling
  74. #
  75. # 2014-05-11 - removed command binfileimport
  76. # changed store all files as binary
  77. # added _cfgDB_Move to move all files from text
  78. # to binary filesave on first load of configDB
  79. #
  80. # 2014-05-12 - added sorted write & read for config data
  81. #
  82. # 2014-05-15 - fixed handling of multiline defs
  83. #
  84. # 2014-05-20 - removed no longer needed functions for file handling
  85. # changed code improvement; use strict; use warnings;
  86. #
  87. # 2014-08-22 - added automatic fileimport during migration
  88. #
  89. # 2014-09-30 - added support for device based userattr
  90. #
  91. # 2015-01-12 - changed use fhem function createUniqueId()
  92. # instead of database calls
  93. #
  94. # 2015-01-15 - changed remove 99_Utils.pm from filelist
  95. #
  96. # 2015-01-17 - added configdb diff all current
  97. # shows diff table between version 0
  98. # and currently running version (in memory)
  99. #
  100. # 2015-01-23 - changed attribute handling for internal configDB attrs
  101. #
  102. # 2015-01-23 - added FileRead() caching - experimental
  103. #
  104. # 2015-10-14 - changed search conditions use ESCAPE, forum #42190
  105. #
  106. # 2016-03-19 - changed use modpath, forum #51036
  107. #
  108. # 2016-03-26 - added log entry for search (verbose=5)
  109. #
  110. # 2016-05-22 - added configdb dump (for sqlite)
  111. #
  112. # 2016-05-28 - added configdb dump (for mysql)
  113. #
  114. # 2016-05-29 - changed improve support for postgresql (tnx to Matze)
  115. # added configdb dump (for postgresql)
  116. #
  117. # 2016-07-03 - added support for multiple hosts (experimental)
  118. # 2016-07-04 - fixed improve config file read
  119. # 2016-07-07 - bugfix select configuration
  120. #
  121. # 2017-03-24 - added use index on fhemconfig (only sqlite)
  122. #
  123. # 2017-07-17 - changed store files base64 encoded
  124. #
  125. # 2017-08-31 - changed improve table_info for migration check
  126. #
  127. # 2018-02-17 - changed remove experimenatal cache functions
  128. # 2018-02-18 - changed move dump processing to backend
  129. #
  130. # 2018-03-24 - changed set privacy as default for username and password
  131. # 2018-03-25 - changed move rescue modes from ENV to config file
  132. #
  133. # 2018-06-17 - changed remove migration on FHEM start by default
  134. # check migration only if parameter migrate => 1
  135. # is set in configDB.conf
  136. #
  137. # 2018-07-04 - bugfix change rescue mode persistence
  138. #
  139. # 2018-07-07 - change lastReorg added to info output
  140. #
  141. # 2018-09-08 - change remove base64 migration functions
  142. #
  143. ##############################################################################
  144. =cut
  145. use strict;
  146. use warnings;
  147. use Text::Diff;
  148. use DBI;
  149. use Sys::Hostname;
  150. use MIME::Base64;
  151. ##################################################
  152. # Forward declarations for functions in fhem.pl
  153. #
  154. sub AnalyzeCommandChain($$;$);
  155. sub Log($$);
  156. sub Log3($$$);
  157. sub createUniqueId();
  158. ##################################################
  159. # Forward declarations inside this library
  160. #
  161. sub cfgDB_AttrRead($);
  162. sub cfgDB_Init();
  163. sub cfgDB_FileRead($);
  164. sub cfgDB_FileUpdate($);
  165. sub cfgDB_Fileversion($$);
  166. sub cfgDB_FileWrite($@);
  167. sub cfgDB_FW_fileList($$@);
  168. sub cfgDB_Read99();
  169. sub cfgDB_ReadAll($);
  170. sub cfgDB_SaveCfg(;$);
  171. sub cfgDB_SaveState();
  172. sub cfgDB_svnId();
  173. sub _cfgDB_binFileimport($$;$);
  174. sub _cfgDB_Connect();
  175. sub _cfgDB_DeleteTemp();
  176. sub _cfgDB_Diff($$);
  177. sub __cfgDB_Diff($$$$);
  178. sub _cfgDB_InsertLine($$$$);
  179. sub _cfgDB_Execute($@);
  180. sub _cfgDB_Filedelete($);
  181. sub _cfgDB_Fileexport($;$);
  182. sub _cfgDB_Filelist(;$);
  183. sub _cfgDB_Info($);
  184. sub _cfgDB_Migrate();
  185. sub _cfgDB_ReadCfg(@);
  186. sub _cfgDB_ReadState(@);
  187. sub _cfgDB_Recover($);
  188. sub _cfgDB_Reorg(;$$);
  189. sub _cfgDB_Rotate($$);
  190. sub _cfgDB_Search($$;$);
  191. sub _cfgDB_Uuid();
  192. sub _cfgDB_table_exists($$);
  193. sub _cfgDB_dump($);
  194. ##################################################
  195. # Read configuration file for DB connection
  196. #
  197. if(!open(CONFIG, 'configDB.conf')) {
  198. Log3('configDB', 1, 'Cannot open database configuration file configDB.conf');
  199. return 0;
  200. }
  201. my @config;
  202. while (<CONFIG>){
  203. my $line = $_;
  204. $line =~ s/^\s+|\s+$//g; # remove whitespaces etc.
  205. $line =~ s/;$/;;/; # duplicate ; at end-of-line
  206. push (@config,$line) if($line !~ m/^#/ && length($line) > 0);
  207. }
  208. close CONFIG;
  209. use vars qw(%configDB);
  210. my %dbconfig;
  211. my $configs = join("",@config);
  212. my @configs = split(/;;/,$configs);
  213. my $count = @configs;
  214. my $fhemhost = hostname;
  215. if ($count > 1) {
  216. foreach my $c (@configs) {
  217. next unless $c =~ m/^%dbconfig.*/;
  218. $dbconfig{fhemhost} = "";
  219. eval $c;
  220. last if ($dbconfig{fhemhost} eq $fhemhost);
  221. }
  222. eval $configs[0] if ($dbconfig{fhemhost} eq "");
  223. } else {
  224. eval $configs[0];
  225. }
  226. my $cfgDB_dbconn = $dbconfig{connection};
  227. my $cfgDB_dbuser = $dbconfig{user};
  228. my $cfgDB_dbpass = $dbconfig{password};
  229. my $cfgDB_dbtype;
  230. my $cfgDB_filename;
  231. if($cfgDB_dbconn =~ m/pg:/i) {
  232. $cfgDB_dbtype ="POSTGRESQL";
  233. } elsif ($cfgDB_dbconn =~ m/mysql:/i) {
  234. $cfgDB_dbtype = "MYSQL";
  235. } elsif ($cfgDB_dbconn =~ m/sqlite:/i) {
  236. $cfgDB_dbtype = "SQLITE";
  237. (undef,$cfgDB_filename) = split(/=/,$cfgDB_dbconn);
  238. } else {
  239. $cfgDB_dbtype = "unknown";
  240. }
  241. $configDB{attr}{nostate} = defined($dbconfig{nostate}) ? $dbconfig{nostate} : 0;
  242. $configDB{attr}{rescue} = defined($dbconfig{rescue}) ? $dbconfig{rescue} : 0;
  243. $configDB{attr}{loadversion} = defined($dbconfig{loadversion}) ? $dbconfig{loadversion} : 0;
  244. %dbconfig = ();
  245. @config = ();
  246. $configs = undef;
  247. $count = undef;
  248. ##################################################
  249. # Basic functions needed for DB configuration
  250. # directly called from fhem.pl
  251. #
  252. # initialize database, create tables if necessary
  253. sub cfgDB_Init() {
  254. ##################################################
  255. # Create non-existing database tables
  256. # Create default config entries if necessary
  257. #
  258. my $fhem_dbh = _cfgDB_Connect;
  259. # create TABLE fhemversions ifnonexistent
  260. $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemversions(VERSION INT, VERSIONUUID CHAR(50))");
  261. # create TABLE fhemconfig if nonexistent
  262. $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemconfig(COMMAND VARCHAR(32), DEVICE VARCHAR(64), P1 VARCHAR(50), P2 TEXT, VERSION INT, VERSIONUUID CHAR(50))");
  263. # create INDEX on fhemconfig if nonexistent (only if SQLITE)
  264. $fhem_dbh->do("CREATE INDEX IF NOT EXISTS config_idx on 'fhemconfig' (versionuuid,version)")
  265. if($cfgDB_dbtype eq "SQLITE");
  266. # check TABLE fhemconfig already populated
  267. my $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemconfig');
  268. if($count < 1) {
  269. # insert default entries to get fhem running
  270. $fhem_dbh->commit();
  271. my $uuid = _cfgDB_Uuid;
  272. $fhem_dbh->do("INSERT INTO fhemversions values (0, '$uuid')");
  273. _cfgDB_InsertLine($fhem_dbh, $uuid, '#created by cfgDB_Init',0);
  274. _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global logdir ./log',1);
  275. _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global logfile %L/fhem-%Y-%m-%d.log',2);
  276. _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global modpath .',3);
  277. _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global userattr devStateIcon devStateStyle icon sortby webCmd',4);
  278. _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr global verbose 3',5);
  279. _cfgDB_InsertLine($fhem_dbh, $uuid, 'define telnetPort telnet 7072 global',6);
  280. _cfgDB_InsertLine($fhem_dbh, $uuid, 'define web FHEMWEB 8083 global',7);
  281. _cfgDB_InsertLine($fhem_dbh, $uuid, 'attr web allowfrom .*',8);
  282. _cfgDB_InsertLine($fhem_dbh, $uuid, 'define Logfile FileLog %L/fhem-%Y-%m-%d.log fakelog',9);
  283. }
  284. # create TABLE fhemstate if nonexistent
  285. $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemstate(stateString TEXT)");
  286. # create TABLE fhemb64filesave if nonexistent
  287. if($cfgDB_dbtype eq "MYSQL") {
  288. $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemb64filesave(filename TEXT, content MEDIUMBLOB)");
  289. } elsif ($cfgDB_dbtype eq "POSTGRESQL") {
  290. $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemb64filesave(filename TEXT, content bytea)");
  291. } else {
  292. $fhem_dbh->do("CREATE TABLE IF NOT EXISTS fhemb64filesave(filename TEXT, content BLOB)");
  293. }
  294. # close database connection
  295. $fhem_dbh->commit();
  296. $fhem_dbh->disconnect();
  297. return;
  298. }
  299. # read attributes
  300. sub cfgDB_AttrRead($) {
  301. my ($readSpec) = @_;
  302. my ($row, $sql, @line, @rets);
  303. my $fhem_dbh = _cfgDB_Connect;
  304. my $uuid = $fhem_dbh->selectrow_array('SELECT versionuuid FROM fhemversions WHERE version = 0');
  305. $sql = "SELECT * FROM fhemconfig WHERE COMMAND = 'attr' AND DEVICE = '$readSpec' AND VERSIONUUID = '$uuid'";
  306. $sql = "SELECT * FROM fhemconfig WHERE COMMAND = 'attr' AND (DEVICE = 'global' OR DEVICE = 'configdb') and VERSIONUUID = '$uuid'"
  307. if($readSpec eq 'global');
  308. my $sth = $fhem_dbh->prepare( $sql );
  309. $sth->execute();
  310. while (@line = $sth->fetchrow_array()) {
  311. if($line[1] eq 'configdb') {
  312. $configDB{attr}{$line[2]} = $line[3];
  313. } else {
  314. push @rets, "attr $line[1] $line[2] $line[3]";
  315. }
  316. }
  317. $fhem_dbh->disconnect();
  318. return @rets;
  319. }
  320. # generic file functions called from fhem.pl
  321. sub cfgDB_FileRead($) {
  322. my ($filename) = @_;
  323. Log3(undef, 4, "configDB reading file: $filename");
  324. my ($err, @ret, $counter);
  325. my $fhem_dbh = _cfgDB_Connect;
  326. my $sth = $fhem_dbh->prepare( "SELECT content FROM fhemb64filesave WHERE filename LIKE '$filename'" );
  327. $sth->execute();
  328. my $blobContent = $sth->fetchrow_array();
  329. $sth->finish();
  330. $fhem_dbh->disconnect();
  331. $blobContent = decode_base64($blobContent) if ($blobContent);
  332. $counter = length($blobContent);
  333. if($counter) {
  334. @ret = split(/\n/,$blobContent);
  335. $err = "";
  336. } else {
  337. @ret = undef;
  338. $err = "Error on reading $filename from database!";
  339. }
  340. return ($err, @ret);
  341. }
  342. sub cfgDB_FileWrite($@) {
  343. my ($filename,@content) = @_;
  344. Log3(undef, 4, "configDB writing file: $filename");
  345. my $fhem_dbh = _cfgDB_Connect;
  346. $fhem_dbh->do("delete from fhemb64filesave where filename = '$filename'");
  347. my $sth = $fhem_dbh->prepare('INSERT INTO fhemb64filesave values (?, ?)');
  348. $sth->execute($filename,encode_base64(join("\n", @content)));
  349. $sth->finish();
  350. $fhem_dbh->commit();
  351. $fhem_dbh->disconnect();
  352. return;
  353. }
  354. sub cfgDB_FileUpdate($) {
  355. my ($filename) = @_;
  356. my $fhem_dbh = _cfgDB_Connect;
  357. my $id = $fhem_dbh->selectrow_array("SELECT filename from fhemb64filesave where filename = '$filename'");
  358. $fhem_dbh->disconnect();
  359. if($id) {
  360. my $filesize = -s $filename;
  361. _cfgDB_binFileimport($filename,$filesize,1) if ($id) ;
  362. Log(5, "file $filename updated in configDB");
  363. }
  364. return;
  365. }
  366. # read and execute fhemconfig and fhemstate
  367. sub cfgDB_ReadAll($) {
  368. my ($cl) = @_;
  369. my ($ret, @dbconfig);
  370. if ($configDB{attr}{rescue} == 1) {
  371. Log (0, 'configDB starting in rescue mode!');
  372. push (@dbconfig, 'attr global modpath .');
  373. push (@dbconfig, 'attr global verbose 3');
  374. push (@dbconfig, 'define telnetPort telnet 7072 global');
  375. push (@dbconfig, 'define web FHEMWEB 8083 global');
  376. push (@dbconfig, 'attr web allowfrom .*');
  377. push (@dbconfig, 'define Logfile FileLog ./log/fhem-%Y-%m-%d.log fakelog');
  378. } else {
  379. # add Config Rows to commandfile
  380. @dbconfig = _cfgDB_ReadCfg(@dbconfig);
  381. # add State Rows to commandfile
  382. @dbconfig = _cfgDB_ReadState(@dbconfig) unless $configDB{attr}{nostate} == 1;
  383. }
  384. # AnalyzeCommandChain for all entries
  385. $ret = _cfgDB_Execute($cl, @dbconfig);
  386. return $ret if($ret);
  387. return undef;
  388. }
  389. # save running configuration to version 0
  390. sub cfgDB_SaveCfg(;$) {
  391. my ($internal) = shift;
  392. $internal = defined($internal) ? $internal : 0;
  393. my @dontSave = qw(configdb:rescue configdb:nostate configdb:loadversion
  394. global:configfile global:version);
  395. my (%devByNr, @rowList, %comments, $t, $out);
  396. map { $devByNr{$defs{$_}{NR}} = $_ } keys %defs;
  397. for(my $i = 0; $i < $devcount; $i++) {
  398. my ($h, $d);
  399. if($comments{$i}) {
  400. $h = $comments{$i};
  401. } else {
  402. $d = $devByNr{$i};
  403. next if(!defined($d) ||
  404. $defs{$d}{TEMPORARY} || # e.g. WEBPGM connections
  405. $defs{$d}{VOLATILE}); # e.g at, will be saved to the statefile
  406. $h = $defs{$d};
  407. }
  408. if(!defined($d)) {
  409. push @rowList, $h->{TEXT};
  410. next;
  411. }
  412. if($d ne "global") {
  413. my $def = $defs{$d}{DEF};
  414. if(defined($def)) {
  415. $def =~ s/;/;;/g;
  416. $def =~ s/\n/\\\n/g;
  417. } else {
  418. $def = "";
  419. }
  420. push @rowList, "define $d $defs{$d}{TYPE} $def";
  421. }
  422. foreach my $a (sort {
  423. return -1 if($a eq "userattr"); # userattr must be first
  424. return 1 if($b eq "userattr");
  425. return $a cmp $b;
  426. } keys %{$attr{$d}}) {
  427. next if (grep { $_ eq "$d:$a" } @dontSave);
  428. my $val = $attr{$d}{$a};
  429. $val =~ s/;/;;/g;
  430. push @rowList, "attr $d $a $val";
  431. }
  432. }
  433. foreach my $a (sort keys %{$configDB{attr}}) {
  434. my $val = $configDB{attr}{$a};
  435. next unless $val;
  436. $val =~ s/;/;;/g;
  437. push @rowList, "attr configdb $a $val";
  438. }
  439. # Insert @rowList into database table
  440. my $fhem_dbh = _cfgDB_Connect;
  441. my $uuid = _cfgDB_Rotate($fhem_dbh,$internal);
  442. $t = localtime;
  443. $out = "#created $t";
  444. push @rowList, $out;
  445. my $counter = 0;
  446. foreach (@rowList) {
  447. _cfgDB_InsertLine($fhem_dbh, $uuid, $_, $counter);
  448. $counter++;
  449. }
  450. $fhem_dbh->commit();
  451. $fhem_dbh->disconnect();
  452. my $maxVersions = $configDB{attr}{maxversions};
  453. $maxVersions = ($maxVersions) ? $maxVersions : 0;
  454. _cfgDB_Reorg($maxVersions,1) if($maxVersions && $internal != -1);
  455. return 'configDB saved.';
  456. }
  457. # save statefile
  458. sub cfgDB_SaveState() {
  459. my ($out,$val,$r,$rd,$t,@rowList);
  460. $t = localtime;
  461. $out = "#$t";
  462. push @rowList, $out;
  463. foreach my $d (sort keys %defs) {
  464. next if($defs{$d}{TEMPORARY});
  465. if($defs{$d}{VOLATILE}) {
  466. $out = "define $d $defs{$d}{TYPE} $defs{$d}{DEF}";
  467. push @rowList, $out;
  468. }
  469. $val = $defs{$d}{STATE};
  470. if(defined($val) &&
  471. $val ne "unknown" &&
  472. $val ne "Initialized" &&
  473. $val ne "" &&
  474. $val ne "???") {
  475. $val =~ s/;/;;/g;
  476. $val =~ s/\n/\\\n/g;
  477. $out = "setstate $d $val";
  478. push @rowList, $out;
  479. }
  480. $r = $defs{$d}{READINGS};
  481. if($r) {
  482. foreach my $c (sort keys %{$r}) {
  483. $rd = $r->{$c};
  484. if(!defined($rd->{TIME})) {
  485. Log3(undef, 4, "WriteStatefile $d $c: Missing TIME, using current time");
  486. $rd->{TIME} = TimeNow();
  487. }
  488. if(!defined($rd->{VAL})) {
  489. Log3(undef, 4, "WriteStatefile $d $c: Missing VAL, setting it to 0");
  490. $rd->{VAL} = 0;
  491. }
  492. $val = $rd->{VAL};
  493. $val =~ s/;/;;/g;
  494. $val =~ s/\n/\\\n/g;
  495. $out = "setstate $d $rd->{TIME} $c $val";
  496. push @rowList, $out;
  497. }
  498. }
  499. }
  500. my $fhem_dbh = _cfgDB_Connect;
  501. $fhem_dbh->do("DELETE FROM fhemstate");
  502. my $sth = $fhem_dbh->prepare('INSERT INTO fhemstate values ( ? )');
  503. foreach (@rowList) { $sth->execute( $_ ); }
  504. $fhem_dbh->commit();
  505. $fhem_dbh->disconnect();
  506. return;
  507. }
  508. # import existing files during migration
  509. sub cfgDB_MigrationImport() {
  510. my ($ret, $filename, @files, @def);
  511. my $modpath = AttrVal("global","modpath",".");
  512. # find eventTypes file
  513. $filename = '';
  514. @def = '';
  515. @def = _cfgDB_findDef('TYPE=eventTypes');
  516. foreach $filename (@def) {
  517. next unless $filename;
  518. push @files, $filename;
  519. }
  520. # import templateDB.gplot
  521. $filename = "$modpath/www/gplot/template.gplot";
  522. push @files, $filename;
  523. $filename = "$modpath/www/gplot/templateDB.gplot";
  524. push @files, $filename;
  525. # import template.layout
  526. $filename = "$modpath/FHEM/template.layout";
  527. push @files, $filename;
  528. # find used gplot files
  529. $filename ='';
  530. @def = '';
  531. @def = _cfgDB_findDef('TYPE=SVG','GPLOTFILE');
  532. foreach $filename (@def) {
  533. next unless $filename;
  534. push @files, "$modpath/www/gplot/".$filename.".gplot";
  535. }
  536. # find DbLog configs
  537. $filename ='';
  538. @def = '';
  539. @def = _cfgDB_findDef('TYPE=DbLog','CONFIGURATION');
  540. foreach $filename (@def) {
  541. next unless $filename;
  542. push @files, $filename;
  543. }
  544. # find RSS layouts
  545. $filename ='';
  546. @def = '';
  547. @def = _cfgDB_findDef('TYPE=RSS','LAYOUTFILE');
  548. foreach $filename (@def) {
  549. next unless $filename;
  550. push @files, $filename;
  551. }
  552. # find InfoPanel layouts
  553. $filename ='';
  554. @def = '';
  555. @def = _cfgDB_findDef('TYPE=InfoPanel','LAYOUTFILE');
  556. foreach $filename (@def) {
  557. next unless $filename;
  558. push @files, $filename;
  559. }
  560. # find holiday files
  561. $filename ='';
  562. @def = '';
  563. @def = _cfgDB_findDef('TYPE=holiday','NAME');
  564. foreach $filename (@def) {
  565. next unless $filename;
  566. if(defined($defs{$filename}{HOLIDAYFILE})) {
  567. push @files, $defs{$filename}{HOLIDAYFILE};
  568. } else {
  569. push @files, "$modpath/FHEM/".$filename.".holiday";
  570. }
  571. }
  572. # import uniqueID file
  573. $filename = "$modpath/FHEM/FhemUtils/uniqueID";
  574. push @files,$filename if (-e $filename);
  575. # do the import
  576. $filename = '';
  577. foreach $filename (@files) {
  578. if ( -r $filename ) {
  579. my $filesize = -s $filename;
  580. _cfgDB_binFileimport($filename,$filesize);
  581. $ret .= "importing: $filename\n";
  582. }
  583. }
  584. return $ret;
  585. }
  586. # return SVN Id, called by fhem's CommandVersion
  587. sub cfgDB_svnId() {
  588. return "# ".'$Id: configDB.pm 17297 2018-09-08 11:01:04Z betateilchen $'
  589. }
  590. # return filelist depending on directory and regexp
  591. sub cfgDB_FW_fileList($$@) {
  592. my ($dir,$re,@ret) = @_;
  593. my @files = split(/\n/, _cfgDB_Filelist('notitle'));
  594. foreach my $f (@files) {
  595. next if( $f !~ m/^$dir/ );
  596. $f =~ s,$dir\/,,;
  597. next if($f !~ m,^$re$, || $f eq '99_Utils.pm');
  598. push @ret, "$f.configDB";
  599. }
  600. return @ret;
  601. }
  602. # read filelist containing 99_ files in database
  603. sub cfgDB_Read99() {
  604. my $ret = "";
  605. my $fhem_dbh = _cfgDB_Connect;
  606. my $sth = $fhem_dbh->prepare( "SELECT filename FROM fhemb64filesave WHERE filename like '%/99_%.pm' group by filename" );
  607. $sth->execute();
  608. while (my $line = $sth->fetchrow_array()) {
  609. $line =~ m,^(.*)/([^/]*)$,; # Split into dir and file
  610. $ret .= "$2,"; #
  611. }
  612. $sth->finish();
  613. $fhem_dbh->disconnect();
  614. $ret =~ s/,$//;
  615. return $ret;
  616. }
  617. # return SVN Id from file stored in database
  618. sub cfgDB_Fileversion($$) {
  619. my ($file,$ret) = @_;
  620. $ret = "No Id found for $file";
  621. my ($err,@in) = cfgDB_FileRead($file);
  622. foreach(@in){ $ret = $_ if($_ =~ m/# \$Id:/); }
  623. return $ret;
  624. }
  625. ##################################################
  626. # Basic functions needed for DB configuration
  627. # but not called from fhem.pl directly
  628. #
  629. # connect do database
  630. sub _cfgDB_Connect() {
  631. my $fhem_dbh = DBI->connect(
  632. "dbi:$cfgDB_dbconn",
  633. $cfgDB_dbuser,
  634. $cfgDB_dbpass,
  635. { AutoCommit => 0, RaiseError => 1 },
  636. ) or die $DBI::errstr;
  637. return $fhem_dbh;
  638. }
  639. # add configuration entry into fhemconfig
  640. sub _cfgDB_InsertLine($$$$) {
  641. my ($fhem_dbh, $uuid, $line, $counter) = @_;
  642. my ($c,$d,$p1,$p2) = split(/ /, $line, 4);
  643. my $sth = $fhem_dbh->prepare('INSERT INTO fhemconfig values (?, ?, ?, ?, ?, ?)');
  644. $sth->execute($c, $d, $p1, $p2, $counter, $uuid);
  645. return;
  646. }
  647. # pass command table to AnalyzeCommandChain
  648. sub _cfgDB_Execute($@) {
  649. my ($cl, @dbconfig) = @_;
  650. my (@ret);
  651. foreach my $l (@dbconfig) {
  652. $l =~ s/[\r\n]/\n/g;
  653. $l =~ s/\\\n/\n/g;
  654. my $tret = AnalyzeCommandChain($cl, $l);
  655. push @ret, $tret if(defined($tret));
  656. }
  657. return join("\n", @ret) if(@ret);
  658. return undef;
  659. }
  660. # read all entries from fhemconfig
  661. # and add them to command table for execution
  662. sub _cfgDB_ReadCfg(@) {
  663. my (@dbconfig) = @_;
  664. my $fhem_dbh = _cfgDB_Connect;
  665. my ($sth, @line, $row);
  666. my $version = $configDB{attr}{loadversion};
  667. delete $configDB{attr}{loadversion};
  668. if ($version > 0) {
  669. my $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemversions');
  670. $count--;
  671. $version = $version > $count ? $count : $version;
  672. Log 0, "configDB loading version $version on user request.";
  673. }
  674. # maybe this will be done with join later
  675. my $uuid = $fhem_dbh->selectrow_array("SELECT versionuuid FROM fhemversions WHERE version = '$version'");
  676. $sth = $fhem_dbh->prepare( "SELECT * FROM fhemconfig WHERE versionuuid = '$uuid' and device <>'configdb' order by version" );
  677. $sth->execute();
  678. while (@line = $sth->fetchrow_array()) {
  679. $row = "$line[0] $line[1] $line[2]";
  680. $row .= " $line[3]" if defined($line[3]);
  681. push @dbconfig, $row;
  682. }
  683. $fhem_dbh->disconnect();
  684. return @dbconfig;
  685. }
  686. # read all entries from fhemstate
  687. # and add them to command table for execution
  688. sub _cfgDB_ReadState(@) {
  689. my (@dbconfig) = @_;
  690. my $fhem_dbh = _cfgDB_Connect;
  691. my ($sth, $row);
  692. $sth = $fhem_dbh->prepare( "SELECT * FROM fhemstate" );
  693. $sth->execute();
  694. while ($row = $sth->fetchrow_array()) {
  695. push @dbconfig, $row;
  696. }
  697. $fhem_dbh->disconnect();
  698. return @dbconfig;
  699. }
  700. # rotate all versions to versionnum + 1
  701. # return uuid for new version 0
  702. sub _cfgDB_Rotate($$) {
  703. my ($fhem_dbh,$newversion) = @_;
  704. my $uuid = _cfgDB_Uuid;
  705. $fhem_dbh->do("UPDATE fhemversions SET VERSION = VERSION+1 where VERSION >= 0") if $newversion == 0;
  706. $fhem_dbh->do("INSERT INTO fhemversions values ('$newversion', '$uuid')");
  707. return $uuid;
  708. }
  709. # 2015-01-12 use the fhem default function
  710. sub _cfgDB_Uuid() {
  711. return createUniqueId();
  712. }
  713. sub _cfgDB_filesize_str($) {
  714. my ($size) = @_;
  715. if ($size > 1099511627776) # TiB: 1024 GiB
  716. {
  717. return sprintf("%.2f TB", $size / 1099511627776);
  718. }
  719. elsif ($size > 1073741824) # GiB: 1024 MiB
  720. {
  721. return sprintf("%.2f GB", $size / 1073741824);
  722. }
  723. elsif ($size > 1048576) # MiB: 1024 KiB
  724. {
  725. return sprintf("%.2f MB", $size / 1048576);
  726. }
  727. elsif ($size > 1024) # KiB: 1024 B
  728. {
  729. return sprintf("%.2f KB", $size / 1024);
  730. }
  731. else # bytes
  732. {
  733. return "$size byte" . ($size == 1 ? "" : "s");
  734. }
  735. }
  736. ##################################################
  737. # Additional backend functions
  738. # not called from fhem.pl directly
  739. #
  740. # migrate existing fhem config into database
  741. sub _cfgDB_Migrate() {
  742. my $ret;
  743. $ret = "Starting migration...\n";
  744. Log3('configDB',4,'Starting migration');
  745. $ret .= "Processing: database initialization\n";
  746. Log3('configDB',4,'Processing: cfgDB_Init');
  747. cfgDB_Init;
  748. $ret .= "Processing: save config\n";
  749. Log3('configDB',4,'Processing: cfgDB_SaveCfg');
  750. cfgDB_SaveCfg;
  751. $ret .= "Processing: save state\n";
  752. Log3('configDB',4,'Processing: cfgDB_SaveState');
  753. cfgDB_SaveState;
  754. $ret .= "Processing: fileimport\n";
  755. Log3('configDB',4,'Processing: cfgDB_MigrationImport');
  756. $ret .= cfgDB_MigrationImport;
  757. $ret .= "Migration completed\n\n";
  758. Log3('configDB',4,'Migration completed.');
  759. $ret .= _cfgDB_Info(undef);
  760. return $ret;
  761. }
  762. # show database statistics
  763. sub _cfgDB_Info($) {
  764. my ($info2) = @_;
  765. $info2 //= 'unknown';
  766. my ($l, @r, $f);
  767. for my $i (1..65){ $l .= '-';}
  768. $configDB{attr}{private} //= 1;
  769. push @r, $l;
  770. push @r, " configDB Database Information";
  771. push @r, $l;
  772. my $info1 = cfgDB_svnId;
  773. $info1 =~ s/# //;
  774. push @r, " d:$info1";
  775. push @r, " c:$info2";
  776. push @r, $l;
  777. push @r, " dbconn: $cfgDB_dbconn";
  778. push @r, " dbuser: $cfgDB_dbuser" if !$configDB{attr}{private};
  779. push @r, " dbpass: $cfgDB_dbpass" if !$configDB{attr}{private};
  780. push @r, " dbtype: $cfgDB_dbtype";
  781. push @r, " Unknown dbmodel type in configuration file." if $cfgDB_dbtype eq 'unknown';
  782. push @r, " Only Mysql, Postgresql, SQLite are fully supported." if $cfgDB_dbtype eq 'unknown';
  783. if ($cfgDB_dbtype eq "SQLITE") {
  784. my $size = -s $cfgDB_filename;
  785. $size = _cfgDB_filesize_str($size);
  786. push @r, " dbsize: $size";
  787. }
  788. push @r, $l;
  789. my $fhem_dbh = _cfgDB_Connect;
  790. my ($sql, $sth, @line, $row);
  791. # read versions table statistics
  792. my $maxVersions = $configDB{attr}{maxversions};
  793. $maxVersions = ($maxVersions) ? $maxVersions : 0;
  794. push @r, " max Versions: $maxVersions" if($maxVersions);
  795. push @r, " lastReorg: ".$configDB{attr}{'lastReorg'};
  796. my $count;
  797. $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemconfig');
  798. push @r, " config: $count entries";
  799. push @r, "";
  800. # read versions creation time
  801. $sql = "SELECT * FROM fhemconfig as c join fhemversions as v on v.versionuuid=c.versionuuid ".
  802. "WHERE COMMAND like '#created%' ORDER by v.VERSION";
  803. $sth = $fhem_dbh->prepare( $sql );
  804. $sth->execute();
  805. while (@line = $sth->fetchrow_array()) {
  806. $line[3] = "" unless defined $line[3];
  807. $row = " Ver $line[6] saved: $line[1] $line[2] $line[3] def: ".
  808. $fhem_dbh->selectrow_array("SELECT COUNT(*) from fhemconfig where COMMAND = 'define' and VERSIONUUID = '$line[5]'");
  809. $row .= " attr: ".
  810. $fhem_dbh->selectrow_array("SELECT COUNT(*) from fhemconfig where COMMAND = 'attr' and VERSIONUUID = '$line[5]'");
  811. push @r, $row;
  812. }
  813. push @r, $l;
  814. # read state table statistics
  815. $count = $fhem_dbh->selectrow_array('SELECT count(*) FROM fhemstate');
  816. $f = ($count>1) ? "s" : "";
  817. # read state table creation time
  818. $sth = $fhem_dbh->prepare( "SELECT * FROM fhemstate WHERE STATESTRING like '#%'" );
  819. $sth->execute();
  820. while ($row = $sth->fetchrow_array()) {
  821. (undef,$row) = split(/#/,$row);
  822. $row = " state: $count entrie$f saved: $row";
  823. push @r, $row;
  824. }
  825. push @r, $l;
  826. $row = $fhem_dbh->selectall_arrayref("SELECT filename from fhemb64filesave group by filename");
  827. $count = @$row;
  828. $count = ($count)?$count:'No';
  829. $f = ("$count" ne '1') ? "s" : "";
  830. $row = " filesave: $count file$f stored in database";
  831. push @r, $row;
  832. push @r, $l;
  833. $fhem_dbh->disconnect();
  834. return join("\n", @r);
  835. }
  836. # recover former config from database archive
  837. sub _cfgDB_Recover($) {
  838. my ($version) = @_;
  839. my ($cmd, $count, $ret);
  840. if($version > 0) {
  841. my $fhem_dbh = _cfgDB_Connect;
  842. $cmd = "SELECT count(*) FROM fhemconfig WHERE VERSIONUUID in (select versionuuid from fhemversions where version = $version)";
  843. $count = $fhem_dbh->selectrow_array($cmd);
  844. if($count > 0) {
  845. my $fromuuid = $fhem_dbh->selectrow_array("select versionuuid from fhemversions where version = $version");
  846. my $touuid = _cfgDB_Uuid;
  847. # Delete current version 0
  848. $fhem_dbh->do("DELETE FROM fhemconfig WHERE VERSIONUUID in (select versionuuid from fhemversions where version = 0)");
  849. $fhem_dbh->do("update fhemversions set versionuuid = '$touuid' where version = 0");
  850. # Copy selected version to version 0
  851. my ($sth, $sth2, @line);
  852. $cmd = "SELECT * FROM fhemconfig WHERE VERSIONUUID = '$fromuuid'";
  853. $sth = $fhem_dbh->prepare($cmd);
  854. $sth->execute();
  855. $sth2 = $fhem_dbh->prepare('INSERT INTO fhemconfig values (?, ?, ?, ?, ?, ?)');
  856. while (@line = $sth->fetchrow_array()) {
  857. $sth2->execute($line[0], $line[1], $line[2], $line[3], $line[4], $touuid);
  858. }
  859. $fhem_dbh->commit();
  860. $fhem_dbh->disconnect();
  861. # Inform user about restart or rereadcfg needed
  862. $ret = "Version 0 deleted.\n";
  863. $ret .= "Version $version copied to version 0\n\n";
  864. $ret .= "Please use rereadcfg or restart to activate configuration.";
  865. } else {
  866. $fhem_dbh->disconnect();
  867. $ret = "No entries found in version $version.\nNo changes committed to database.";
  868. }
  869. } else {
  870. $ret = 'Please select version 1..n for recovery.';
  871. }
  872. return $ret;
  873. }
  874. # delete old configurations
  875. sub _cfgDB_Reorg(;$$) {
  876. my ($lastversion,$quiet) = @_;
  877. $lastversion = (defined($lastversion)) ? $lastversion : 3;
  878. Log3('configDB', 4, "DB Reorg started, keeping last $lastversion versions.");
  879. my $fhem_dbh = _cfgDB_Connect;
  880. my $uuid = $fhem_dbh->selectrow_array("select versionuuid from fhemversions where version = 0");
  881. $fhem_dbh->do("delete FROM fhemconfig where versionuuid in (select versionuuid from fhemversions where version > $lastversion)");
  882. $fhem_dbh->do("delete from fhemversions where version > $lastversion");
  883. $fhem_dbh->do("delete FROM fhemconfig where versionuuid in (select versionuuid from fhemversions where version = -1)");
  884. $fhem_dbh->do("delete from fhemversions where version = -1");
  885. my $ts = localtime(time);
  886. $configDB{attr}{'lastReorg'} = $ts;
  887. _cfgDB_InsertLine($fhem_dbh,$uuid,"attr configdb lastReorg $ts",-1);
  888. $fhem_dbh->commit();
  889. $fhem_dbh->disconnect();
  890. eval qx(sqlite3 $cfgDB_filename vacuum) if($cfgDB_dbtype eq "SQLITE");
  891. return if(defined($quiet));
  892. return " Result after database reorg:\n"._cfgDB_Info(undef);
  893. }
  894. # delete temporary version
  895. sub _cfgDB_DeleteTemp() {
  896. Log3('configDB', 4, "configDB: delete temporary Version -1");
  897. my $fhem_dbh = _cfgDB_Connect;
  898. $fhem_dbh->do("delete FROM fhemconfig where versionuuid in (select versionuuid from fhemversions where version = -1)");
  899. $fhem_dbh->do("delete from fhemversions where version = -1");
  900. $fhem_dbh->commit();
  901. $fhem_dbh->disconnect();
  902. return;
  903. }
  904. # search for device or fulltext in db
  905. sub _cfgDB_Search($$;$) {
  906. my ($search,$searchversion,$dsearch) = @_;
  907. return 'Syntax error.' if(!(defined($search)));
  908. my $fhem_dbh = _cfgDB_Connect;
  909. my ($sql, $sth, @line, $row, @result, $ret, $text);
  910. $sql = "SELECT command, device, p1, p2 FROM fhemconfig as c join fhemversions as v ON v.versionuuid=c.versionuuid ";
  911. $sql .= "WHERE v.version = '$searchversion' AND command not like '#create%' ";
  912. # 2015-10-24 - changed, forum #42190
  913. if($cfgDB_dbtype eq 'SQLITE') {;
  914. $sql .= "AND device like '$search%' ESCAPE '\\' " if($dsearch);
  915. $sql .= "AND (device like '$search%' ESCAPE '\\' OR P1 like '$search%' ESCAPE '\\' OR P2 like '$search%' ESCAPE '\\') " if(!$dsearch);
  916. } else {
  917. $sql .= "AND device like '$search%' " if($dsearch);
  918. $sql .= "AND (device like '$search%' OR P1 like '$search%' OR P2 like '$search%') " if(!$dsearch);
  919. }
  920. $sql .= "ORDER BY lower(device),command DESC";
  921. $sth = $fhem_dbh->prepare( $sql);
  922. Log 5,"configDB: $sql";
  923. $sth->execute();
  924. $text = " device" if($dsearch);
  925. push @result, "search result for$text: $search in version: $searchversion";
  926. push @result, "--------------------------------------------------------------------------------";
  927. while (@line = $sth->fetchrow_array()) {
  928. $row = "$line[0] $line[1] $line[2] $line[3]";
  929. push @result, "$row";
  930. }
  931. $fhem_dbh->disconnect();
  932. $ret = join("\n", @result);
  933. return $ret;
  934. }
  935. # called from cfgDB_Diff
  936. sub __cfgDB_Diff($$$$) {
  937. my ($fhem_dbh,$search,$searchversion,$svinternal) = @_;
  938. my ($sql, $sth, @line, $ret);
  939. if($svinternal != -1) {
  940. $sql = "SELECT command, device, p1, p2 FROM fhemconfig as c join fhemversions as v ON v.versionuuid=c.versionuuid ".
  941. "WHERE v.version = '$searchversion' AND device = '$search' ORDER BY command DESC";
  942. } else {
  943. $sql = "SELECT command, device, p1, p2 FROM fhemconfig as c join fhemversions as v ON v.versionuuid=c.versionuuid ".
  944. "WHERE v.version = '$searchversion' ORDER BY command DESC";
  945. }
  946. $sth = $fhem_dbh->prepare( $sql);
  947. $sth->execute();
  948. while (@line = $sth->fetchrow_array()) {
  949. $ret .= "$line[0] $line[1] $line[2] $line[3]\n";
  950. }
  951. return $ret;
  952. }
  953. # compare device configurations from 2 versions
  954. sub _cfgDB_Diff($$) {
  955. my ($search,$searchversion) = @_;
  956. my ($ret, $v0, $v1);
  957. if ($search eq 'all' && $searchversion eq 'current') {
  958. _cfgDB_DeleteTemp();
  959. cfgDB_SaveCfg(-1);
  960. $searchversion = -1;
  961. }
  962. my $fhem_dbh = _cfgDB_Connect;
  963. $v0 = __cfgDB_Diff($fhem_dbh,$search,0,$searchversion);
  964. $v1 = __cfgDB_Diff($fhem_dbh,$search,$searchversion,$searchversion);
  965. $fhem_dbh->disconnect();
  966. $ret = diff \$v0, \$v1, { STYLE => "Table" };
  967. if($searchversion == -1) {
  968. _cfgDB_DeleteTemp();
  969. $searchversion = "UNSAVED";
  970. }
  971. $ret = "\nNo differences found!" if !$ret;
  972. $ret = "compare device: $search in current version 0 (left) to version: $searchversion (right)\n$ret\n";
  973. return $ret;
  974. }
  975. # find DEF, input supports devspec definitions
  976. sub _cfgDB_findDef($;$) {
  977. my ($search,$internal) = @_;
  978. $internal = 'DEF' unless defined($internal);
  979. my @ret;
  980. my @etDev = devspec2array($search);
  981. foreach my $d (@etDev) {
  982. next unless $d;
  983. push @ret, $defs{$d}{$internal};
  984. }
  985. return @ret;
  986. }
  987. sub _cfgDB_type() {
  988. return "$cfgDB_dbtype (b64)";
  989. }
  990. sub _cfgDB_dump($) {
  991. my ($param1) = @_;
  992. $param1 //= '';
  993. my ($dbconn,$dbuser,$dbpass,$dbtype) = _cfgDB_readConfig();
  994. my ($dbname,$dbhostname,$dbport,$gzip,$mp,$ret,$size,$source,$target,$ts);
  995. $ts = strftime('%Y-%m-%d_%H-%M-%S',localtime);
  996. $mp = $configDB{attr}{'dumpPath'};
  997. $mp //= AttrVal('global','modpath','.').'/log';
  998. $target = "$mp/configDB_$ts.dump";
  999. if (lc($param1) eq 'unzipped') {
  1000. $gzip = '';
  1001. } else {
  1002. $gzip = '| gzip -c';
  1003. $target .= '.gz';
  1004. }
  1005. if ($dbtype eq 'SQLITE') {
  1006. (undef,$source) = split (/=/, $dbconn);
  1007. my $dumpcmd = "echo '.dump fhem%' | sqlite3 $source $gzip > $target";
  1008. Log 4,"configDB: $dumpcmd";
  1009. $ret = qx($dumpcmd);
  1010. return $ret if $ret; # return error message if available
  1011. } elsif ($dbtype eq 'MYSQL') {
  1012. ($dbname,$dbhostname,$dbport) = split (/;/,$dbconn);
  1013. $dbport //= '=3306';
  1014. (undef,$dbname) = split (/=/,$dbname);
  1015. (undef,$dbhostname) = split (/=/,$dbhostname);
  1016. (undef,$dbport) = split (/=/,$dbport);
  1017. my $dbtables = "fhemversions fhemconfig fhemstate fhemb64filesave";
  1018. my $dumpcmd = "mysqldump --user=$dbuser --password=$dbpass --host=$dbhostname --port=$dbport -Q $dbname $dbtables $gzip > $target";
  1019. Log 4,"configDB: $dumpcmd";
  1020. $ret = qx($dumpcmd);
  1021. return $ret if $ret;
  1022. $source = $dbname;
  1023. } elsif ($dbtype eq 'POSTGRESQL') {
  1024. ($dbname,$dbhostname,$dbport) = split (/;/,$dbconn);
  1025. $dbport //= '=5432';
  1026. (undef,$dbname) = split (/=/,$dbname);
  1027. (undef,$dbhostname) = split (/=/,$dbhostname);
  1028. (undef,$dbport) = split (/=/,$dbport);
  1029. my $dbtables = "-t fhemversions -t fhemconfig -t fhemstate -t fhemb64filesave";
  1030. my $dumpcmd = "PGPASSWORD=$dbpass pg_dump -U $dbuser -h $dbhostname -p $dbport $dbname $dbtables $gzip > $target";
  1031. Log 4,"configDB: $dumpcmd";
  1032. $ret = qx($dumpcmd);
  1033. return $ret if $ret;
  1034. $source = $dbname;
  1035. } else {
  1036. return "configdb dump not supported for $dbtype!";
  1037. }
  1038. $size = -s $target;
  1039. $size //= 0;
  1040. $ret = "configDB dumped $size bytes\nfrom: $source\n to: $target";
  1041. return $ret;
  1042. }
  1043. ##################################################
  1044. # functions used for file handling
  1045. # called by 98_configdb.pm
  1046. #
  1047. # delete file from database
  1048. sub _cfgDB_Filedelete($) {
  1049. my ($filename) = @_;
  1050. my $fhem_dbh = _cfgDB_Connect;
  1051. my $ret = $fhem_dbh->do("delete from fhemb64filesave where filename = '$filename'");
  1052. $fhem_dbh->commit();
  1053. $fhem_dbh->disconnect();
  1054. $ret = ($ret > 0) ? 1 : undef;
  1055. return $ret;
  1056. }
  1057. # export file from database to filesystem
  1058. sub _cfgDB_Fileexport($;$) {
  1059. my ($filename,$raw) = @_;
  1060. my $fhem_dbh = _cfgDB_Connect;
  1061. my $sth = $fhem_dbh->prepare( "SELECT content FROM fhemb64filesave WHERE filename = '$filename'" );
  1062. $sth->execute();
  1063. my $blobContent = $sth->fetchrow_array();
  1064. $blobContent = decode_base64($blobContent);
  1065. my $counter = length($blobContent);
  1066. $sth->finish();
  1067. $fhem_dbh->disconnect();
  1068. return "No data found for file $filename" unless $counter;
  1069. return ($blobContent,$counter) if $raw;
  1070. open( FILE,">$filename" );
  1071. binmode(FILE);
  1072. print FILE $blobContent;
  1073. close( FILE );
  1074. return "$counter bytes written from database into file $filename";
  1075. }
  1076. # import file into database
  1077. sub _cfgDB_binFileimport($$;$) {
  1078. my ($filename,$filesize,$doDelete) = @_;
  1079. $doDelete = (defined($doDelete)) ? 1 : 0;
  1080. open (inFile,"<$filename") || die $!;
  1081. my $blobContent;
  1082. binmode(inFile);
  1083. my $readBytes = read(inFile, $blobContent, $filesize);
  1084. close(inFile);
  1085. $blobContent = encode_base64($blobContent);
  1086. my $fhem_dbh = _cfgDB_Connect;
  1087. $fhem_dbh->do("delete from fhemb64filesave where filename = '$filename'");
  1088. my $sth = $fhem_dbh->prepare('INSERT INTO fhemb64filesave values (?, ?)');
  1089. # add support for postgresql by Matze
  1090. $sth->bind_param( 1, $filename );
  1091. if ($cfgDB_dbtype eq "POSTGRESQL") {
  1092. $sth->bind_param( 2, $blobContent, { pg_type => DBD::Pg::PG_BYTEA() } );
  1093. } else {
  1094. $sth->bind_param( 2, $blobContent );
  1095. }
  1096. $sth->execute($filename, $blobContent);
  1097. $sth->finish();
  1098. $fhem_dbh->commit();
  1099. $fhem_dbh->disconnect();
  1100. unlink($filename) if(($configDB{attr}{deleteimported} || $doDelete) && $readBytes);
  1101. return "$readBytes bytes written from file $filename to database";
  1102. }
  1103. # list all files stored in database
  1104. sub _cfgDB_Filelist(;$) {
  1105. my ($notitle) = @_;
  1106. my $ret = "Files found in database:\n".
  1107. "------------------------------------------------------------\n";
  1108. $ret = "" if $notitle;
  1109. my $fhem_dbh = _cfgDB_Connect;
  1110. my $sql = "SELECT filename FROM fhemb64filesave group by filename order by filename";
  1111. my $content = $fhem_dbh->selectall_arrayref($sql);
  1112. foreach my $row (@$content) {
  1113. $ret .= "@$row[0]\n" if(defined(@$row[0]));
  1114. }
  1115. $fhem_dbh->disconnect();
  1116. return $ret;
  1117. }
  1118. 1;
  1119. =pod
  1120. =item helper
  1121. =item summary configDB backend
  1122. =item summary_DE configDB backend
  1123. =begin html
  1124. <a name="configDB"></a>
  1125. <h3>configDB</h3>
  1126. <ul>
  1127. <a href="https://forum.fhem.de/index.php?board=46.0">Link to FHEM forum</a><br/><br/>
  1128. This is the core backend library for configuration from SQL database.<br/>
  1129. See <a href="#configdb">configdb command documentation</a> for detailed info.<br/>
  1130. </ul>
  1131. =end html
  1132. =begin html_DE
  1133. <a name="configDB"></a>
  1134. <h3>configDB</h3>
  1135. <ul>
  1136. configDB ist die Funktionsbibliothek f&uuml;r die Konfiguration aus einer SQL Datenbank.<br/>
  1137. Die ausf&uuml;hrliche Dokumentation findet sich in der <a href="#configdb">configdb Befehlsbeschreibung</a>.
  1138. </ul>
  1139. =end html_DE
  1140. =cut