solarpowerlog trunk
/home/tobi/workspace/solarpowerlog/src/DataFilters/HTMLWriter/CHTMLWriter.cpp
Go to the documentation of this file.
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