configDB.pm 38 KB

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