|
solarpowerlog trunk
|
00001 /* 00002 * CHTMLWriter.cpp 00003 * 00004 * Created on: Dec 20, 2009 00005 * Author: tobi 00006 */ 00007 00008 #ifdef HAVE_CONFIG_H 00009 #include "config.h" 00010 #include "porting.h" 00011 #endif 00012 00013 #ifdef HAVE_FILTER_HTMLWRITER 00014 00015 #include <fstream> 00016 #include <boost/date_time/gregorian/gregorian.hpp> 00017 #include <boost/date_time/local_time/local_time.hpp> 00018 00019 #include "DataFilters/HTMLWriter/CHTMLWriter.h" 00020 00021 #include "configuration/Registry.h" 00022 #include "configuration/CConfigHelper.h" 00023 #include "interfaces/CWorkScheduler.h" 00024 00025 #include "Inverters/Capabilites.h" 00026 #include "Inverters/interfaces/ICapaIterator.h" 00027 #include "patterns/CValue.h" 00028 #include "configuration/ILogger.h" 00029 00030 #include "DataFilters/HTMLWriter/formatter/IFormater.h" 00031 #include <string> 00032 00033 // helper, because we do not want the multi map for the formatters sorted. 00034 00035 struct unsortedmultimap 00036 { 00037 //bool operator ()( const std::vector<std::string>, const std::vector<std::string>) const { 00038 bool operator ()(const std::string &, const std::string &) const 00039 { 00040 00041 return false; 00042 } 00043 }; 00044 00048 void CHTMLWriter::ScheduleCyclicEvent(enum Commands cmd) 00049 { 00050 // set all members 00051 ICommand *ncmd = new ICommand(cmd, this); 00052 CCapability *c; 00053 struct timespec ts; 00054 00055 if (derivetiming) { 00056 c = GetConcreteCapability(CAPA_INVERTER_QUERYINTERVAL); 00057 if (c && c->getValue()->GetType() == IValue::float_type) { 00058 CValue<float> *v = (CValue<float> *) c->getValue(); 00059 ts.tv_sec = v->Get(); 00060 ts.tv_nsec = ((v->Get() - ts.tv_sec) * 1e9); 00061 } else { 00062 LOGINFO(logger, 00063 "INFO: The associated inverter does not specify the " 00064 "queryinterval. Defaulting to 300 seconds. Consider specifying writeevery"); 00065 ts.tv_sec = 300; 00066 ts.tv_nsec = 0; 00067 } 00068 } else if (writeevery > 0.0001) { 00069 ts.tv_sec = writeevery; 00070 ts.tv_nsec = ((long) (writeevery - ts.tv_sec)) * 1e9; 00071 } else { 00072 // writevery 0. 00073 ts.tv_sec = 300; 00074 ts.tv_nsec = 0; 00075 } 00076 00077 Registry::GetMainScheduler()->ScheduleWork(ncmd, ts); 00078 } 00079 00080 CHTMLWriter::CHTMLWriter(const string & name, const string & configurationpath) : 00081 IDataFilter(name, configurationpath), updated(false), datavalid(false) 00082 { 00083 00084 writeevery = 0; 00085 derivetiming = 0; 00086 generatetemplate = 0; 00087 updated = 0; 00088 datavalid = 0; 00089 00090 // Schedule the initialization and subscriptions later... 00091 ICommand *cmd = new ICommand(CMD_INIT, this); 00092 Registry::GetMainScheduler()->ScheduleWork(cmd); 00093 00094 // We do not anything on these capabilities, so we remove our list. 00095 // any cascaded filter will automatically use the parents one... 00096 CCapability *c = IInverterBase::GetConcreteCapability( 00097 CAPA_INVERTER_DATASTATE); 00098 CapabilityMap.erase(CAPA_INVERTER_DATASTATE); 00099 delete c; 00100 00101 // Also we wont fiddle with the caps requiring our listeners to unsubscribe. 00102 // They also should get that info from our base. 00103 c = IInverterBase::GetConcreteCapability(CAPA_CAPAS_REMOVEALL); 00104 CapabilityMap.erase(CAPA_CAPAS_REMOVEALL); 00105 delete c; 00106 00107 } 00108 00109 CHTMLWriter::~CHTMLWriter() 00110 { 00111 // TODO Auto-generated destructor stub 00112 } 00113 00114 bool CHTMLWriter::CheckConfig() 00115 { 00116 bool fail = false; 00117 CConfigHelper hlp(configurationpath); 00118 00119 // Get writeevery, if not existant, remember that for later extrapolation out 00120 // of the parents Capability. 00121 if (hlp.CheckConfig("writeevery", libconfig::Setting::TypeFloat, true)) { 00122 if (!hlp.GetConfig("writeevery", writeevery, (float) 300.0)) { 00123 derivetiming = true; 00124 } 00125 if (writeevery < 0.0) { 00126 LOGERROR(logger, "Configuration Error: writeevery must be positive."); 00127 fail = true; 00128 } else if (writeevery < 0.00001) { 00129 LOGERROR(logger, "Configuration Error: writeevery=0 not yet implemented."); 00130 fail = true; 00131 } 00132 00133 } else { 00134 fail = true; 00135 } 00136 00137 if (hlp.CheckConfig("generate_template", libconfig::Setting::TypeBoolean, 00138 true) && hlp.CheckConfig("generate_template_dir", 00139 libconfig::Setting::TypeString, true)) { 00140 hlp.GetConfig("generate_template", generatetemplate, false); 00141 hlp.GetConfig("generate_template_dir", generatetemplatedir, 00142 std::string("/tmp/")); 00143 00144 if (generatetemplatedir.size() 00145 && generatetemplatedir[generatetemplatedir.size() - 1] != '/') { 00146 generatetemplatedir += '/'; 00147 } 00148 } else { 00149 fail = true; 00150 } 00151 00152 if (hlp.CheckConfig("htmlfile", libconfig::Setting::TypeString)) { 00153 hlp.GetConfig("htmlfile", htmlfile); 00154 } else { 00155 fail = true; 00156 } 00157 00158 if (hlp.CheckConfig("templatefile", libconfig::Setting::TypeString)) { 00159 hlp.GetConfig("templatefile", templatefile); 00160 } else { 00161 fail = true; 00162 } 00163 00164 return !fail; 00165 } 00166 00167 void CHTMLWriter::Update(const IObserverSubject *subject) 00168 { 00169 // note: the subject must be a CCapability here. 00170 // to avoid neverending casting we do that once. 00171 CCapability *parentcap = (CCapability *) subject; 00172 00173 // check for the mandatory Capas now, as they might require 00174 // immediate actions. 00175 if (parentcap->getDescription() == CAPA_CAPAS_REMOVEALL) { 00176 CCapability *ourcap; 00177 // forward the notification. 00178 // but -- to be nice -- update the value first 00179 ourcap = IInverterBase::GetConcreteCapability(CAPA_CAPAS_REMOVEALL); 00180 assert (ourcap); 00181 assert (ourcap->getValue()->GetType() == CAPA_CAPAS_REMOVEALL_TYPE); 00182 assert (parentcap->getValue()->GetType() == CAPA_CAPAS_REMOVEALL_TYPE); 00183 00184 CValue<bool> *a, *b; 00185 a = (CValue<bool> *) (ourcap->getValue()); 00186 b = (CValue<bool> *) (parentcap->getValue()); 00187 *a = *b; 00188 ourcap->Notify(); 00189 00190 CheckOrUnSubscribe(false); 00191 return; 00192 } 00193 00194 if (parentcap->getDescription() == CAPA_CAPAS_UPDATED) { 00195 CCapability *c = IInverterBase::GetConcreteCapability( 00196 CAPA_CAPAS_UPDATED); 00197 *(CValue<bool> *) c->getValue() 00198 = *(CValue<bool> *) parentcap->getValue(); 00199 c->Notify(); 00200 // there are new caps, but currently we won't do anything with it 00201 // later we want to check the list if we have attach ourself to the 00202 // listernes. 00203 return; 00204 } 00205 00206 if (parentcap->getDescription() == CAPA_INVERTER_DATASTATE) { 00207 datavalid = ((CValue<bool> *) parentcap->getValue())->Get(); 00208 return; 00209 } 00210 00211 if (!updated) { 00212 ICommand *cmd = new ICommand(CMD_UPDATED, this); 00213 Registry::GetMainScheduler()->ScheduleWork(cmd); 00214 } 00215 updated = true; 00216 00217 } 00218 00219 void CHTMLWriter::ExecuteCommand(const ICommand *cmd) 00220 { 00221 00222 switch (cmd->getCmd()) 00223 { 00224 00225 case CMD_INIT: 00226 { 00227 string tmp; 00228 CConfigHelper cfghlp(configurationpath); 00229 CCapability *c; 00230 00231 // Subscribe to this->base inverter, all the required ones... 00232 if (cfghlp.GetConfig("datasource", tmp)) { 00233 base = Registry::Instance().GetInverter(tmp); 00234 if (base) { 00235 c = base->GetConcreteCapability(CAPA_CAPAS_UPDATED); 00236 assert(c); // this is required to have.... 00237 c->Subscribe(this); 00238 00239 c = base->GetConcreteCapability(CAPA_CAPAS_REMOVEALL); 00240 assert(c); 00241 c->Subscribe(this); 00242 00243 c = base->GetConcreteCapability(CAPA_INVERTER_DATASTATE); 00244 assert(c); 00245 c->Subscribe(this); 00246 } else { 00247 LOGWARN(logger, 00248 "Warning: Could not find data source to connect. Filter: " 00249 << configurationpath << "." << name); 00250 } 00251 } 00252 00253 ScheduleCyclicEvent(CMD_CYCLIC); 00254 } 00255 break; 00256 00257 case CMD_CYCLIC: 00258 { 00259 00260 DoCyclicCmd(cmd); 00261 ScheduleCyclicEvent(CMD_CYCLIC); 00262 } 00263 00264 case CMD_UPDATED: 00265 { 00266 // prepared for the next version. 00267 // "do on update mode" 00268 break; 00269 } 00270 } 00271 00272 } 00273 00274 void CHTMLWriter::DoCyclicCmd(const ICommand *) 00275 { 00276 ofstream fs; 00277 00278 // C-Template vars 00279 TMPL_varlist *tmpl_looplist = NULL; 00280 TMPL_loop *tmpl_loop = NULL; 00281 TMPL_varlist *tmpl_list = NULL; 00283 if (!datavalid) { 00284 return; 00285 } 00286 00287 if (generatetemplate) { 00288 std::string s; 00289 CConfigHelper cfg(configurationpath); 00290 cfg.GetConfig("name", s); 00291 s = generatetemplatedir + s + ".html"; 00292 fs.open(s.c_str(), fstream::out | fstream::trunc | fstream::binary); 00293 00294 #ifdef HAVE_WIN32_API 00295 if (fs.fail()) { 00296 fs.clear(); 00297 fs.open(s.c_str(), fstream::out | fstream::app | fstream::binary); 00298 } 00299 #endif 00300 if (fs.fail()) { 00301 LOGWARN(logger,"Template-Assistant: Failed to open file: " << s); 00302 fs.close(); 00303 } 00304 00305 fs << "<html><head></head><body><h1>This are the variables known to " 00306 "the template system: </h1>" << endl << "<table border=1>" 00307 "<tr><th>Name of the Attribute</th><th>Current Value</th></tr>"; 00308 } 00309 00310 // Get the configuration to the formatting specs and map them 00311 // TODO make this only once.... 00312 multimap<std::string, std::vector<std::string> > 00313 formattermap; 00314 00315 std::string s = configurationpath + ".formatters"; 00316 std::string s1, s2; 00317 CConfigHelper formatters(s); 00318 00319 for (int i = 0;; i++) { 00320 // LOG_TRACE(logger, "In formatter loop: i="<<i); 00321 // 00322 if (formatters.GetConfigArray(i, 0, s1) && formatters.GetConfigArray(i, 00323 1, s2)) { 00324 00325 // LOG_TRACE(logger,"s1="<<s1 << " s2=" << s2); 00326 00327 // We store the information this way: 00328 // we iterate over the configuration array and make a vector out 00329 // of it. 00330 // so the vector will look like: 00331 // [0] formatter name 00332 // [1] target template variable (maybe empty) 00333 // [2] parameter for formatter 00334 // [3...] other parameters for the formatter 00335 std::vector<std::string> vs; 00336 vs.push_back(s2); // we already got the formatter's name 00337 int j; 00338 j = 2; 00339 while (formatters.GetConfigArray(i, j++, s2)) { 00340 vs.push_back(s2); 00341 } 00342 00343 formattermap.insert(pair<std::string, std::vector<std::string> > ( 00344 s1, vs)); 00345 00346 LOGTRACE(logger, "Inserting formatter spec <" << s1 << ',' << vs[0] << '>'); 00347 } else { 00348 break; 00349 } 00350 } 00351 00352 // reminder: this first version only attaches one inverter! 00353 // Step one: 00354 // loop over all capabilites and add it to the list 00355 00356 // Add the number of the inverter to the exported vars, as the snippets 00357 // probably want to do something special on the first one only. 00358 // TODO FIXME currently, there is only one. So we make this dynamic later. 00359 00360 tmpl_looplist = TMPL_add_var(tmpl_looplist, "iteration", "0", NULL); 00361 if (generatetemplate) { 00362 fs << "<tr><td> iteration </td><td> " << "0" << " </td>\n"; 00363 } 00364 00365 auto_ptr<ICapaIterator> cit(GetCapaNewIterator()); 00366 while (cit->HasNext()) { 00367 multimap<std::string, vector<std::string> >::iterator it; 00368 pair<string, CCapability*> cappair = cit->GetNext(); 00369 00370 // get the value of the capability 00371 std::string value = *(cappair.second->getValue()); 00372 // cache the name of the capability 00373 std::string templatename = cappair.first.c_str(); 00374 00375 // LOG_TRACE(logger, "Capability: " << cappair.first << "\tValue: "<< value); 00376 00377 // if ( formattermap.find(templatename) != formattermap.end()) 00378 // { 00379 // LOG_TRACE(logger, "****"); 00380 // } 00381 00382 // TODO Rip this part into its own function -- this way, we can also do some daisy-chain 00383 // formatting: Modify value x to value y, modify value y to value z .... 00384 00385 // debug code: dump the multimap find results. 00386 #if 0 00387 for (it = formattermap.find(cappair.first); it != formattermap.end(); it++) { 00388 00389 LOGTRACE(logger, "***** " << templatename <<": found 1st=" << (*it).first << " 2nd " << (*it).second[0]); 00390 00391 } 00392 #endif 00393 00394 for (it = formattermap.find(cappair.first); it != formattermap.end(); it++) { 00395 IFormater *frmt; 00396 00397 // the multimap returns everything after the first result 00398 // so we have to recheck we really want this result. 00399 // FIXME the multimap seems not to be best for the task, so maybe 00400 // code should be reworked to use another container. 00401 if (cappair.first != (*it).first ) break; 00402 00403 string formatter_to_create = (*it).second[0]; 00404 LOGTRACE(logger, "reformatting " << templatename << " with a " << (*it).second[0] ); 00405 00406 if ((frmt = IFormater::Factory(formatter_to_create))) { 00407 00408 if (!frmt->Format(value, value, (*it).second)) { 00409 LOGERROR(logger,"Could not reformat " << cappair.first << 00410 ": Formater reported error."); 00411 } 00412 00413 // check if we should store the result to another template 00414 // variable not the original one. 00415 if ((*it).second.size() > 2 && (*it).second[1] != "") { 00416 // yes, store the result to the new template var. 00417 tmpl_looplist = TMPL_add_var(tmpl_looplist, 00418 (*it).second[1].c_str(), value.c_str(), NULL); 00419 00420 if (this->generatetemplate) { 00421 fs << "<tr><td> " << (*it).second[1].c_str() << 00422 "<br/><p style=\"font-size:x-small\">reformatted from " 00423 << templatename << 00424 "</p></td><td> " << value 00425 << " </td>\n"; 00426 } 00427 00428 // in this case, restore the original value before checking 00429 // out a possible next formatter. 00430 value = *(cappair.second->getValue()); 00431 } 00432 00433 delete frmt; 00434 } else { 00435 LOGERROR(logger,"Failed to create formatter " << formatter_to_create ); 00436 } 00437 } 00438 00439 tmpl_looplist = TMPL_add_var(tmpl_looplist, templatename.c_str(), 00440 value.c_str(), NULL); 00441 00442 if (this->generatetemplate) { 00443 fs << "<tr><td> " << cappair.first << " </td><td> " << value 00444 << " </td>\n"; 00445 } 00446 } 00447 00448 // adding the loop entry to the loop varlist. 00449 tmpl_loop = TMPL_add_varlist(tmpl_loop, tmpl_looplist); 00450 tmpl_looplist = NULL; 00451 00452 // (here, we would add the other inverters linked to this story) 00453 00454 // now adding all the stuff to the main list. 00455 tmpl_list = TMPL_add_loop(tmpl_list, "inverters", tmpl_loop); 00456 00457 if (generatetemplate) { 00458 fs 00459 << "<tr><th>HTMWriter Capabilities</th><th>(these are outside of the loop)</th></tr>" 00460 << endl; 00461 } 00462 map<string, CCapability*>::const_iterator it; 00463 for (it = CapabilityMap.begin(); it != CapabilityMap.end(); it++) { 00464 pair<string, CCapability*> cappair = *it; 00465 std::string tmpstring = *(cappair.second->getValue()); 00466 00467 tmpl_list = TMPL_add_var(tmpl_list, cappair.first.c_str(), 00468 tmpstring.c_str(), NULL); 00469 00470 if (this->generatetemplate) { 00471 fs << "<tr><td> " << cappair.first << " </td><td> " << tmpstring 00472 << " </td>\n"; 00473 } 00474 00475 } 00476 00477 // we add some others here.... 00478 tmpl_list = TMPL_add_var(tmpl_list, "spl_version", PACKAGE_VERSION, NULL); 00479 00480 // template assistance is now done, so close the file. 00481 if (generatetemplate) { 00482 fs << "</table></body></html>"; 00483 fs.close(); 00484 } 00485 00486 // okay, now open a file in memory to catch template errors. 00487 // (if supported at least) 00488 FILE *errfile = NULL; 00489 FILE *out = NULL; 00490 #ifdef HAVE_OPEN_MEMSTREAM 00491 char *ptr = NULL; 00492 size_t size = 0; 00493 #endif 00494 00495 #ifdef HAVE_OPEN_MEMSTREAM 00496 errfile = open_memstream(&ptr, &size); 00497 #else 00498 #warning Note: opem_memstream not available on this platform. Will not be able to tell you template errors 00499 #endif 00500 // generate filename of the output file 00501 if (htmlfile.find("%s") != std::string::npos) { 00502 boost::gregorian::date today(boost::gregorian::day_clock::local_day()); 00503 char buf[htmlfile.size() + 10]; //note: the %s will be removed, so +10 is enough. 00504 int year = today.year(); 00505 int month = today.month(); 00506 int day = today.day(); 00507 00508 snprintf(buf, sizeof(buf) - 1, "%s%04d-%02d-%02d%s", htmlfile.substr(0, 00509 htmlfile.find("%s")).c_str(), year, month, day, 00510 htmlfile.substr(htmlfile.find("%s") + 2, string::npos).c_str()); 00511 00512 out = fopen(buf, "w+"); 00513 if (out == 0) { 00514 LOGERROR(logger, "Could not open filename "<< buf); 00515 } 00516 00517 } else { 00518 out = fopen(htmlfile.c_str(), "w+"); 00519 if (out == NULL) { 00520 LOGERROR(logger, "Could not open filename "<< htmlfile); 00521 } 00522 } 00523 00524 if (out && -1 == TMPL_write(templatefile.c_str(), NULL, NULL, tmpl_list, 00525 out, errfile)) { 00526 // error while writing. lets examine errfile. 00527 // we need first to close the file to update ptr and size. 00528 #ifdef HAVE_OPEN_MEMSTREAM 00529 fclose(errfile); 00530 errfile = NULL; // avoid closing a 2nd time later. 00531 LOGERROR(logger, "Error while writing html file." 00532 " The template library reported this: " << ptr); 00533 #else 00534 // esp. for the cygwin 1.5 port, we cannot tell the reason why it happened. 00535 // patches are welcome to open a temporary file on disk instead and then 00536 // (when cagwin 1.7 comes out, this is no longer an issue: They implemented 00537 // the GNU extended syscall) 00538 LOGERROR(logger, "Error while writing html file (template error)"); 00539 #endif 00540 00541 } else if (out) { 00542 LOGTRACE(logger, "Done writing HTML File. Wrote " << ftell(out) << " Bytes"); 00543 } 00544 00545 // cleanup. 00546 if (tmpl_list) 00547 TMPL_free_varlist(tmpl_list); 00548 if (out) 00549 fclose(out); 00550 #ifdef HAVE_OPEN_MEMSTREAM 00551 if (errfile) 00552 fclose(errfile); 00553 if (ptr) 00554 free(ptr); 00555 #endif 00556 } 00557 00558 void CHTMLWriter::CheckOrUnSubscribe(bool subscribe) 00559 { 00560 if (!base) 00561 return; 00562 00563 CCapability *cap = base->GetConcreteCapability(CAPA_INVERTER_DATASTATE); 00564 if (cap) 00565 cap->SetSubscription(this, subscribe); 00566 00567 } 00568 00569 #endif