LCOV - code coverage report
Current view: top level - otsdaq-utilities/otsdaq-utilities/ECLWriter - ECLSupervisor.cc (source / functions) Coverage Total Hit
Test: mu2edaq.info.cleaned Lines: 0.0 % 456 0
Test Date: 2026-07-30 01:56:44 Functions: 0.0 % 90 0

            Line data    Source code
       1              : #include "otsdaq-utilities/ECLWriter/ECLSupervisor.h"
       2              : #include "otsdaq/CgiDataUtilities/CgiDataUtilities.h"
       3              : #include "otsdaq/ConfigurationInterface/ConfigurationManager.h"
       4              : #include "otsdaq/Macros/CoutMacros.h"
       5              : #include "otsdaq/MessageFacility/MessageFacility.h"
       6              : #include "otsdaq/SOAPUtilities/SOAPCommand.h"
       7              : #include "otsdaq/SOAPUtilities/SOAPParameters.h"
       8              : #include "otsdaq/SOAPUtilities/SOAPUtilities.h"
       9              : #include "otsdaq/TablePlugins/XDAQContextTable/XDAQContextTable.h"
      10              : #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
      11              : 
      12              : #include <dirent.h>   /*DIR and dirent*/
      13              : #include <sys/stat.h> /*mkdir*/
      14              : 
      15              : #include <xdaq/NamespaceURI.h>
      16              : 
      17              : #include <iomanip>
      18              : #include <iostream>
      19              : #include "otsdaq/TableCore/TableGroupKey.h"
      20              : 
      21              : using namespace ots;
      22              : 
      23              : #undef __MF_SUBJECT__
      24              : #define __MF_SUBJECT__ "ECL"
      25              : 
      26              : #define XML_ADMIN_STATUS "logbook_admin_status"
      27              : #define XML_STATUS "logbook_status"
      28              : #define XML_MOST_RECENT_DAY "most_recent_day"
      29              : #define XML_TIMEZONE_OFFSET "timezone_offset_hours"
      30              : #define XML_CATEGORY_ROOT "categories"
      31              : #define XML_CATEGORY "category"
      32              : #define XML_ACTIVE_CATEGORY "active_category"
      33              : #define XML_SAFE_URL "ecl_url"
      34              : #define XML_RESPONSE_CATEGORY "response_category"
      35              : #define XML_CATEGORY_CREATE "create_time"
      36              : #define XML_CATEGORY_CREATOR "creator"
      37              : 
      38              : #define XML_LOGBOOK_ENTRY "logbook_entry"
      39              : #define XML_LOGBOOK_ENTRY_SUBJECT "logbook_entry_subject"
      40              : #define XML_LOGBOOK_ENTRY_TEXT "logbook_entry_text"
      41              : #define XML_LOGBOOK_ENTRY_FILE "logbook_entry_file"
      42              : #define XML_LOGBOOK_ENTRY_TIME "logbook_entry_time"
      43              : #define XML_LOGBOOK_ENTRY_CREATOR "logbook_entry_creator"
      44              : #define XML_LOGBOOK_ENTRY_HIDDEN "logbook_entry_hidden"
      45              : #define XML_LOGBOOK_ENTRY_HIDER "logbook_entry_hider"
      46              : #define XML_LOGBOOK_ENTRY_HIDDEN_TIME "logbook_entry_hidden_time"
      47              : 
      48            0 : XDAQ_INSTANTIATOR_IMPL(ECLSupervisor)
      49              : 
      50              : //==============================================================================
      51            0 : ECLSupervisor::ECLSupervisor(xdaq::ApplicationStub* stub) : CoreSupervisorBase(stub)
      52              : {
      53            0 :         __SUP_COUT__ << "Constructor." << __E__;
      54              : 
      55            0 :         INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
      56              : 
      57            0 :         xoap::bind(
      58              :             this, &ECLSupervisor::MakeSystemLogEntry, "MakeSystemLogEntry", XDAQ_NS_URI);
      59              : 
      60            0 :         init();
      61              : 
      62            0 :         __SUP_COUT__ << "Constructed." << __E__;
      63            0 : }  // end constructor
      64              : 
      65              : //==============================================================================
      66            0 : void ECLSupervisor::init(void)
      67              : try
      68              : {
      69              :         // do not put username/pw in saved/committed text files
      70            0 :         ECLUser_     = __ENV__("ECL_USER_NAME");
      71            0 :         ECLHost_     = __ENV__("ECL_URL");  // e.g. https://dbweb6.fnal.gov:8443/ECL/test_beam
      72            0 :         ECLPwd_      = __ENV__("ECL_PASSWORD");
      73            0 :         ECLCategory_ = __ENV__("ECL_CATEGORY");
      74            0 :         CategoryName_ = __ENV__("OTS_OWNER") + std::string(" ots");
      75              : 
      76              :         // Determine the timezone offset from UTC time in hours
      77              :         // Get the current time
      78            0 :         std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
      79              : 
      80              :         // Get the current time in UTC
      81            0 :         std::chrono::zoned_time<std::chrono::system_clock::duration> utcTime{"UTC", now};
      82              : 
      83              :         // Get the current local time using the current system's timezone
      84              :         std::chrono::zoned_time<std::chrono::system_clock::duration> localTime{
      85            0 :             std::chrono::current_zone(), now};
      86              : 
      87              :         // Calculate the difference between local time and UTC
      88            0 :         std::chrono::seconds offset = std::chrono::duration_cast<std::chrono::seconds>(
      89            0 :             localTime.get_local_time() - utcTime.get_local_time());
      90              : 
      91              :         // Convert the offset to hours and minutes
      92            0 :         timezoneHourOffset_ = offset.count() / 3600;
      93              : 
      94            0 :         __SUP_COUTV__(timezoneHourOffset_);
      95              : 
      96            0 :         eclConn_ = std::make_unique<ECLConnection>(ECLUser_, ECLPwd_, ECLHost_);
      97              : 
      98              : }  // end init()
      99            0 : catch(const std::runtime_error& e)
     100              : {
     101            0 :         __COUT_ERR__ << "ECL environment variables not setup: " << e.what() << __E__;
     102            0 :         ECLUser_ = "";  //clearing to disable
     103            0 : }
     104              : 
     105              : //==============================================================================
     106            0 : ECLSupervisor::~ECLSupervisor(void) { destroy(); }
     107              : 
     108              : //==============================================================================
     109            0 : void ECLSupervisor::destroy(void)
     110              : {
     111              :         // // called by destructor
     112              :         // delete theConfigurationManager_;
     113              : 
     114            0 :         __SUP_COUT__ << "Destructed." << __E__;
     115            0 : }  // end destroy()
     116              : 
     117              : //==============================================================================
     118            0 : void ECLSupervisor::defaultPage(xgi::Input* /*in*/, xgi::Output* out)
     119              : {
     120            0 :         __COUT__ << " active category " << ECLCategory_ << std::endl;
     121              :         *out << "<!DOCTYPE HTML><html lang='en'><head><title>ots</title>"
     122            0 :              << ECLSupervisor::getIconHeaderString() <<
     123              :             // end show ots icon
     124              :             "</head>"
     125              :              << "<frameset col='100%' row='100%'><frame "
     126            0 :                 "src='/WebPath/html/Logbook.html?urn="
     127            0 :              << this->getApplicationDescriptor()->getLocalId()
     128            0 :              << "&active_category=" << ECLCategory_ << "'></frameset></html>";
     129            0 : }  //end defaultPage()
     130              : 
     131              : //==============================================================================
     132            0 : std::string ECLSupervisor::getIconHeaderString(void)
     133              : {
     134              :         // show ots icon
     135              :         //      from http://www.favicon-generator.org/
     136            0 :         return "<link rel='apple-touch-icon' sizes='57x57' href='/WebPath/images/otsdaqIcons/apple-icon-57x57.png'>\
     137              :         <link rel='apple-touch-icon' sizes='60x60' href='/WebPath/images/otsdaqIcons/apple-icon-60x60.png'>\
     138              :         <link rel='apple-touch-icon' sizes='72x72' href='/WebPath/images/otsdaqIcons/apple-icon-72x72.png'>\
     139              :         <link rel='apple-touch-icon' sizes='76x76' href='/WebPath/images/otsdaqIcons/apple-icon-76x76.png'>\
     140              :         <link rel='apple-touch-icon' sizes='114x114' href='/WebPath/images/otsdaqIcons/apple-icon-114x114.png'>\
     141              :         <link rel='apple-touch-icon' sizes='120x120' href='/WebPath/images/otsdaqIcons/apple-icon-120x120.png'>\
     142              :         <link rel='apple-touch-icon' sizes='144x144' href='/WebPath/images/otsdaqIcons/apple-icon-144x144.png'>\
     143              :         <link rel='apple-touch-icon' sizes='152x152' href='/WebPath/images/otsdaqIcons/apple-icon-152x152.png'>\
     144              :         <link rel='apple-touch-icon' sizes='180x180' href='/WebPath/images/otsdaqIcons/apple-icon-180x180.png'>\
     145              :         <link rel='icon' type='image/png' sizes='192x192'  href='/WebPath/images/otsdaqIcons/android-icon-192x192.png'>\
     146              :         <link rel='icon' type='image/png' sizes='144x144'  href='/WebPath/images/otsdaqIcons/android-icon-144x144.png'>\
     147              :         <link rel='icon' type='image/png' sizes='48x48'  href='/WebPath/images/otsdaqIcons/android-icon-48x48.png'>\
     148              :         <link rel='icon' type='image/png' sizes='72x72'  href='/WebPath/images/otsdaqIcons/android-icon-72x72.png'>\
     149              :         <link rel='icon' type='image/png' sizes='32x32' href='/WebPath/images/otsdaqIcons/favicon-32x32.png'>\
     150              :         <link rel='icon' type='image/png' sizes='96x96' href='/WebPath/images/otsdaqIcons/favicon-96x96.png'>\
     151              :         <link rel='icon' type='image/png' sizes='16x16' href='/WebPath/images/otsdaqIcons/favicon-16x16.png'>\
     152              :         <link rel='manifest' href='/WebPath/images/otsdaqIcons/manifest.json'>\
     153              :         <meta name='msapplication-TileColor' content='#ffffff'>\
     154              :         <meta name='msapplication-TileImage' content='/WebPath/images/otsdaqIcons/ms-icon-144x144.png'>\
     155              :         <meta name='theme-color' content='#ffffff'>";
     156              : 
     157              : }  // end getIconHeaderString()
     158              : 
     159              : //==============================================================================
     160              : /// setSupervisorPropertyDefaults
     161              : ///             override to set defaults for supervisor property values (before user settings
     162              : /// override)
     163            0 : void ECLSupervisor::setSupervisorPropertyDefaults()
     164              : {
     165            0 :         CorePropertySupervisorBase::setSupervisorProperty(
     166              :             CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.UserPermissionsThreshold,
     167            0 :             std::string() +
     168              :                 "*=1 | CreateCategory=-1 | RemoveCategory=-1 | GetCategoryListAdmin=-1 "
     169            0 :                 "| SetActiveCategory=-1" +
     170              :                 " | AdminRemoveRestoreEntry=-1");
     171              : 
     172            0 :         CorePropertySupervisorBase::setSupervisorProperty(
     173              :             CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AllowNoLoginRequestTypes,
     174              :             "RefreshLogbook | GetCategoryList");
     175              : 
     176            0 : }  //end setSupervisorPropertyDefaults()
     177              : 
     178              : //==============================================================================
     179              : /// forceSupervisorPropertyValues
     180              : ///             override to force supervisor property values (and ignore user settings)
     181            0 : void ECLSupervisor::forceSupervisorPropertyValues()
     182              : {
     183            0 :         CorePropertySupervisorBase::addSupervisorProperty(
     184              :             CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AutomatedRequestTypes,
     185              :             "RefreshLogbook");
     186            0 :         CorePropertySupervisorBase::setSupervisorProperty(
     187              :             CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.NonXMLRequestTypes,
     188              :             "LogImage | LogReport");
     189            0 :         CorePropertySupervisorBase::addSupervisorProperty(
     190              :             CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.RequireUserLockRequestTypes,
     191              :             "CreateCategory | RemoveCategory | PreviewEntry | AdminRemoveRestoreEntry");
     192            0 : }  //end forceSupervisorPropertyValues()
     193              : 
     194              : //==============================================================================
     195              : ///     request
     196              : ///             Handles Web Interface requests to Logbook supervisor.
     197              : ///             Does not refresh cookie for automatic update checks.
     198            0 : void ECLSupervisor::request(const std::string&               requestType,
     199              :                             cgicc::Cgicc&                    cgiIn,
     200              :                             HttpXmlDocument&                 xmlOut,
     201              :                             const WebUsers::RequestUserInfo& userInfo)
     202              : {
     203            0 :         __COUTTV__(requestType);
     204              : 
     205              :         // Commands - Note: treat 'Category' as ECL Category
     206              :         //      N/A CreateCategory
     207              :         //      N/A RemoveCategory
     208              :         //      GetCategoryList
     209              :         //      SetActiveCategory
     210              :         //      RefreshLogbook
     211              :         //      PreviewEntry
     212              :         //      ApproveEntry
     213              :         //      N/A AdminRemoveRestoreEntry
     214              : 
     215              :         // to report to logbook admin status use
     216              :         // xmlOut.addTextElementToData(XML_ADMIN_STATUS,tempStr);
     217              : 
     218              :         if(0 && requestType == "CreateCategory")
     219              :         {
     220              :                 // check that category directory does not exist, and it is not in xml list
     221              :                 // create category (TODO - could set env variable ECLConnection/$ECL_CATEGORY)
     222              :                 //
     223              : 
     224              :                 // get creator name
     225              :                 std::string creator = userInfo.username_;
     226              : 
     227              :                 // createCategory(
     228              :                 //     CgiDataUtilities::postData(cgiIn, "Category"), creator, &xmlOut);
     229              : 
     230              :                 __COUT__ << "Created" << std::endl;
     231              :         }
     232              :         else if(0 && requestType == "RemoveCategory")
     233              :         {
     234              :                 // remove from xml list, but do not remove directory (requires manual delete so
     235              :                 // mistakes aren't made)
     236              :                 //(TODO - could unset env variable ECLConnection/$ECL_CATEGORY)
     237              : 
     238              :                 // get remover name
     239              :                 std::string remover = userInfo.username_;
     240              :                 // removeCategory(
     241              :                 //     CgiDataUtilities::postData(cgiIn, "Category"), remover, &xmlOut);
     242              :         }
     243            0 :         else if(requestType == "GetCategoryList")
     244              :         {
     245              :                 //allow all users to ECL categories, but tell GUI not admin since have no access to managing ECL posts directly
     246            0 :                 xmlOut.addTextElementToData("is_admin", "0");  // indicate not an admin
     247            0 :                 xmlOut.addTextElementToData("no_post_preview",
     248              :                                             "1");  // indicate no post preview, just post directly
     249            0 :                 xmlOut.addTextElementToData("no_post_attachments",
     250              :                                             "1");  // indicate no post attachments supported
     251            0 :                 getCategories(&xmlOut);
     252              :         }
     253            0 :         else if(requestType == "SetActiveCategory")
     254              :         {
     255              :                 // check that category exists
     256              :                 // set active category
     257              : 
     258              :                 //              if(userPermissions < ADMIN_PERMISSIONS_THRESHOLD)
     259              :                 //              {
     260              :                 //                      xmlOut.addTextElementToData(XML_ADMIN_STATUS,"Error - Insufficient
     261              :                 // permissions.");                         goto CLEANUP;
     262              :                 //              }
     263              : 
     264            0 :                 webUserSetActiveCategory(CgiDataUtilities::postData(cgiIn, "Category"), &xmlOut);
     265              :         }
     266            0 :         else if(requestType == "RefreshLogbook")
     267              :         {
     268              :                 // returns logbook for currently active category based on date and duration
     269              :                 // parameters
     270              : 
     271            0 :                 std::string Date           = CgiDataUtilities::postData(cgiIn, "Date");
     272            0 :                 uint32_t    Duration       = CgiDataUtilities::postDataAsInt(cgiIn, "Duration");
     273            0 :                 std::string CategoryFilter = CgiDataUtilities::postData(cgiIn, "CategoryFilter");
     274              : 
     275            0 :                 __COUTV__(CategoryFilter);
     276              : 
     277              :                 time_t date;
     278            0 :                 sscanf(Date.c_str(), "%li", &date);  // scan for unsigned long
     279              : 
     280            0 :                 __COUT__ << "date " << date << " duration " << Duration << std::endl;
     281            0 :                 std::stringstream str;
     282            0 :                 refreshLogbook(date,
     283              :                                Duration,
     284              :                                &xmlOut,
     285              :                                (std::ostringstream*)&str,
     286            0 :                                StringMacros::decodeURIComponent(CategoryFilter));
     287            0 :                 __COUT__ << str.str() << std::endl;
     288            0 :         }
     289            0 :         else if(requestType == "PostEntry")
     290              :         {
     291              :                 // NOTE: all input parameters for PostEntry will be attached to form
     292              :                 //      so use cgiIn(xxx) to get values.
     293              : 
     294            0 :                 std::string EntryText = cgiIn("EntryText");
     295            0 :                 __SUP_COUTV__(EntryText);
     296            0 :                 std::string EntrySubject = cgiIn("EntrySubject");
     297            0 :                 __SUP_COUTV__(EntrySubject);
     298              : 
     299              :                 // get creator name
     300            0 :                 std::string creator = userInfo.username_;
     301            0 :                 __SUP_COUTV__(creator);
     302              : 
     303              :                 //return; //to debug, this breaks link to ECL
     304            0 :                 ECLEntry_t eclEntry;
     305            0 :                 eclEntry.author(StringMacros::escapeString(ECLUser_));
     306            0 :                 eclEntry.category(StringMacros::escapeString(ECLCategory_));
     307            0 :                 eclEntry.subject(StringMacros::escapeString(EntrySubject));
     308              : 
     309            0 :                 Form_t                 form;
     310            0 :                 Field_t                field;
     311            0 :                 Form_t::field_sequence fields;
     312            0 :                 std::string            users = theRemoteWebUsers_.getActiveUserList();
     313              : 
     314            0 :                 form.name("default");  // these form names must be created in advance? ... default
     315              :                                        // seems to have one field and be generic: 'text' field
     316              : 
     317              :                 {
     318            0 :                         std::stringstream ss;
     319            0 :                         ss << "Author: " << creator << " (" << creator << ")" << __E__ << __E__;
     320            0 :                         ss << "Message: " << __E__ << EntryText << __E__ << __E__;
     321              : 
     322            0 :                         ss << "This was a Manual Log Entry from '" << CategoryName_ << "' at host '"
     323            0 :                            << __ENV__("THIS_HOST") << "'" << __E__;
     324            0 :                         ss << "Active ots users: " << users << __E__;
     325            0 :                         ss << "USER_DATA: " << __ENV__("USER_DATA") << __E__;
     326              :                         ss << "Uptime: "
     327            0 :                            << StringMacros::getTimeDurationString(
     328            0 :                                   CorePropertySupervisorBase::getSupervisorUptime())
     329            0 :                            << __E__;
     330              :                         field =
     331            0 :                             Field_t(StringMacros::escapeString(ss.str(), true /* keep white space */),
     332            0 :                                     "text");
     333            0 :                         fields.push_back(field);
     334            0 :                 }
     335              : 
     336            0 :                 std::string retStr = "1";  //1 for success
     337            0 :                 form.field(fields);
     338            0 :                 eclEntry.form(form);
     339              :                 try
     340              :                 {
     341              :                         // ECLConnection eclConn(ECLUser_, ECLPwd_, ECLHost_);
     342            0 :                         if(!eclConn_->Post(eclEntry))
     343              :                         {
     344            0 :                                 __SS__ << "Failure to post ECL entry." << __E__;
     345            0 :                                 __COUT_ERR__ << ss.str();
     346            0 :                                 retStr = ss.str();
     347            0 :                         }
     348              :                 }
     349            0 :                 catch(const std::runtime_error& e)
     350              :                 {
     351            0 :                         __SS__ << "Exception caught during ECL message post and ECL connection: "
     352            0 :                                << e.what();
     353            0 :                         __COUT_ERR__ << ss.str();
     354            0 :                         retStr = ss.str();
     355            0 :                 }
     356              : 
     357            0 :                 xmlOut.addTextElementToData(XML_STATUS, retStr);
     358            0 :         }
     359              :         // else if(requestType == "PreviewEntry")
     360              :         // {
     361              :         //      // cleanup temporary folder
     362              :         //      // NOTE: all input parameters for PreviewEntry will be attached to form
     363              :         //      //      so use cgiIn(xxx) to get values.
     364              :         //      // increment number for each temporary preview, previewPostTempIndex_
     365              :         //      // save entry and uploads to previewPath / previewPostTempIndex_ /.
     366              : 
     367              :         //      cleanUpPreviews();
     368              :         //      std::string EntryText = cgiIn("EntryText");
     369              :         //      __COUT__ << "EntryText " << EntryText << std::endl << std::endl;
     370              :         //      std::string EntrySubject = cgiIn("EntrySubject");
     371              :         //      __COUT__ << "EntrySubject " << EntrySubject << std::endl << std::endl;
     372              : 
     373              :         //      // get creator name
     374              :         //      std::string creator = userInfo.username_;
     375              : 
     376              :         //      savePostPreview(EntrySubject, EntryText, cgiIn.getFiles(), creator, &xmlOut);
     377              :         //      // else xmlOut.addTextElementToData(XML_STATUS,"Failed - could not get username
     378              :         //      // info.");
     379              :         // }
     380              :         // else if(requestType == "ApproveEntry")
     381              :         // {
     382              :         //      // If Approve = "1", then previewed Log entry specified by PreviewNumber
     383              :         //      //  is moved to logbook
     384              :         //      // Else the specified Log entry is deleted.
     385              :         //      std::string PreviewNumber = CgiDataUtilities::postData(cgiIn, "PreviewNumber");
     386              :         //      std::string Approve       = CgiDataUtilities::postData(cgiIn, "Approve");
     387              : 
     388              :         //      movePreviewEntry(PreviewNumber, Approve == "1", &xmlOut);
     389              :         // }
     390              :         // else if(requestType == "AdminRemoveRestoreEntry")
     391              :         // {
     392              :         //      //              if(userPermissions < ADMIN_PERMISSIONS_THRESHOLD)
     393              :         //      //              {
     394              :         //      //                      xmlOut.addTextElementToData(XML_ADMIN_STATUS,"Error - Insufficient
     395              :         //      // permissions.");                         goto CLEANUP;
     396              :         //      //              }
     397              : 
     398              :         //      std::string EntryId = CgiDataUtilities::postData(cgiIn, "EntryId");
     399              :         //      bool Hide = CgiDataUtilities::postData(cgiIn, "Hide") == "1" ? true : false;
     400              : 
     401              :         //      // get creator name
     402              :         //      std::string hider = userInfo.username_;
     403              : 
     404              :         //      hideLogbookEntry(EntryId, Hide, hider);
     405              : 
     406              :         //      xmlOut.addTextElementToData(XML_ADMIN_STATUS, "1");  // success
     407              :         // }
     408              :         else
     409              :         {
     410            0 :                 __SUP_SS__ << "requestType Request, " << requestType
     411              :                            << ", not recognized by the ECL Supervisor (was it intended for "
     412            0 :                               "another Supervisor?)."
     413            0 :                            << __E__;
     414            0 :                 __SUP_SS_THROW__;
     415            0 :         }
     416            0 : }  //end request()
     417              : 
     418              : //==============================================================================
     419              : /// getCategories
     420              : ///             if xmlOut, then output categories to xml
     421              : ///             if out, then output to stream
     422            0 : void ECLSupervisor::getCategories(HttpXmlDocument* xmlOut, std::ostringstream* out)
     423              : {
     424            0 :         if(ECLUser_ == "" ||
     425            0 :            ECLHost_ == "")  //ignore ECL when environment variables are not set
     426              :         {
     427            0 :                 __SS__ << "No ECL user/host specified for logbook access." << __E__;
     428            0 :                 __SS_THROW__;
     429            0 :         }
     430              : 
     431            0 :         std::string response, url = "/A/xml_category_list";
     432            0 :         eclConn_->Get(url, response);
     433            0 :         __COUTTV__(response);
     434              : 
     435            0 :         std::vector<std::string> exps;
     436            0 :         std::string              name;
     437            0 :         size_t                   after = 0;
     438              : 
     439              :         //example response:
     440              :         // <?xml version="1.0" encoding="UTF-8"?>
     441              :         // <category_list>
     442              :         //              <category path="Accelerator"/>
     443              :         //              <category path="CRV"/>
     444              :         //              <category path="CRV/Vertical Slice Test"/>
     445              :         //              <category path="Calorimeter"/>
     446              :         //              <category path="Cryogenics"/>
     447              :         //              <category path="Cryogenics/Cryogenics Construction"/>
     448              :         //              <category path="Cryogenics/Mu2e Controls"/>
     449              :         //              <category path="Cryogenics/Mu2e ODH System"/>
     450              :         //              <category path="Cryogenics/Mu2e Operations"/>
     451              :         //              <category path="Cryogenics/Mu2e Vacuum"/>
     452              :         //              <category path="Cryogenics/Muon Campus Operations"/>
     453              :         //              <category path="Extinction Monitor"/>
     454              :         //              <category path="Facilities / Building"/>
     455              :         //              <category path="Global Run"/>
     456              :         //              <category path="Leak checking"/>
     457              :         //              <category path="Mu2e team member location"/>
     458              :         //              <category path="Muon beamline"/>
     459              :         //              <category path="Planning"/>
     460              :         //              <category path="Production"/>
     461              :         //              <category path="Production/MDC18"/>
     462              :         //              <category path="Production/su2020"/>
     463              :         //              <category path="Safety"/>
     464              :         //              <category path="Solenoids"/>
     465              :         //              <category path="Stopping Target Monitor"/>
     466              :         //              <category path="TDAQ"/>
     467              :         //              <category path="Tracker"/>
     468              :         //              <category path="Tracker/VST"/>
     469              :         //              <category path="Transfer Lines"/>
     470              :         //              <category path="help"/>
     471              :         //              <category path="test"/>
     472              :         //              <category path="testbeam"/>
     473              :         // </category_list>
     474              : 
     475            0 :         while((name = StringMacros::extractXmlField(
     476            0 :                    response, "category", 0, after, &after, "path=", "\"")) != "")
     477              :         {
     478            0 :                 after +=
     479            0 :                     std::string("category").size();  //move forward to prepare for next search
     480            0 :                 __COUTTV__(name);
     481            0 :                 exps.push_back(name);
     482              :         }
     483              : 
     484              :         // // check that category listing doesn't already exist
     485              :         // HttpXmlDocument expXml;
     486              :         // if(!expXml.loadXmlDocument((std::string)LOGBOOK_CATEGORY_LIST_PATH))
     487              :         // {
     488              :         //      __COUT__ << "Fatal Error - Category database." << std::endl;
     489              :         //      __COUT__ << "Creating empty category database." << std::endl;
     490              : 
     491              :         //      expXml.addTextElementToData((std::string)XML_CATEGORY_ROOT);
     492              :         //      expXml.saveXmlDocument((std::string)LOGBOOK_CATEGORY_LIST_PATH);
     493              :         //      return;
     494              :         // }
     495              : 
     496              :         // expXml.getAllMatchingValues(XML_CATEGORY, exps);
     497              : 
     498            0 :         if(xmlOut)
     499              :         {
     500            0 :                 xmlOut->addTextElementToData(XML_ACTIVE_CATEGORY, ECLCategory_);
     501            0 :                 xmlOut->addTextElementToData(XML_SAFE_URL, eclConn_->getSafeURL());
     502              :         }
     503              : 
     504            0 :         for(unsigned int i = 0; i < exps.size(); ++i)  // loop categories
     505              :         {
     506            0 :                 if(xmlOut)
     507            0 :                         xmlOut->addTextElementToData(XML_CATEGORY, exps[i]);
     508            0 :                 if(out)
     509            0 :                         *out << exps[i] << std::endl;
     510              :         }
     511            0 : }  //end getCategories()
     512              : 
     513              : //==============================================================================
     514              : /// webUserSetActiveCategory
     515              : ///             if category exists, set as active
     516              : ///             to clear active category set to ""
     517            0 : void ECLSupervisor::webUserSetActiveCategory(std::string      category,
     518              :                                              HttpXmlDocument* xmlOut)
     519              : {
     520              :         // no check, just set
     521            0 :         ECLCategory_ = category;
     522            0 :         if(xmlOut)
     523            0 :                 xmlOut->addTextElementToData(
     524            0 :                     XML_ADMIN_STATUS, "Active category set to " + category + " successfully.");
     525            0 : }  //end webUserSetActiveCategory()
     526              : 
     527              : //==============================================================================
     528              : ///     refreshLogbook
     529              : ///             returns all the logbook data for active category from starting date and back in
     530              : /// time for                    duration total number of days.
     531              : ///             e.g. date = today, and duration = 1 returns logbook for today from active
     532              : /// category            The entries are returns from oldest to newest
     533            0 : void ECLSupervisor::refreshLogbook(time_t              date,
     534              :                                    size_t              duration,
     535              :                                    HttpXmlDocument*    xmlOut,
     536              :                                    std::ostringstream* out,
     537              :                                    std::string         categoryFilter)
     538              : {
     539            0 :         if(ECLUser_ == "" ||
     540            0 :            ECLHost_ == "")  //ignore ECL when environment variables are not set
     541              :         {
     542            0 :                 __SS__ << "No ECL user/host specified for logbook access." << __E__;
     543            0 :                 __SS_THROW__;
     544            0 :         }
     545              : 
     546            0 :         if(categoryFilter == "")
     547            0 :                 categoryFilter = ECLCategory_;  // default to active category
     548            0 :         if(xmlOut)
     549            0 :                 xmlOut->addTextElementToData(XML_ACTIVE_CATEGORY, ECLCategory_);  // for success
     550            0 :         if(xmlOut)
     551            0 :                 xmlOut->addTextElementToData(XML_RESPONSE_CATEGORY,
     552              :                                              categoryFilter);  // for success
     553              : 
     554            0 :         int64_t mostRecentTime = 0;
     555              :         time_t  baseTime;
     556              : 
     557            0 :         __COUTTV__(date);
     558            0 :         if(!date)                // if date is 0 take most recent day and update it
     559            0 :                 baseTime = time(0);  // / (60 * 60 * 24);
     560              :         else
     561            0 :                 baseTime = date - timezoneHourOffset_ * 60 * 60 +
     562              :                            1;  //date is 12:00a GMT, so could give wrong day in local timezone
     563              : 
     564              :         if(0)  //test xml_get
     565              :         {
     566              :                 std::string response, url = "/E/xml_get?e=" + std::string("1843");
     567              :                 __COUTV__(url);
     568              :                 eclConn_->Get(url, response);
     569              :                 __COUTV__(response);
     570              :         }
     571              :         if(0)  //test xml_search
     572              :         {
     573              :                 std::string response, url = "/E/xml_search?l=5";  //limit to 5
     574              :                 // url += "&c=Facilities / Building";
     575              :                 __COUTV__(url);
     576              :                 eclConn_->Get(url, response);
     577              :                 __COUTV__(response);
     578              :         }
     579              :         if(0)  //test xml_search
     580              :         {
     581              :                 std::string response, url = "/E/xml_search?l=5";  //limit to 5
     582              :                 url +=
     583              :                     "&c=" + StringMacros::encodeURIComponent("Facilities / Building");  //category
     584              :                 __COUTV__(url);
     585              :                 eclConn_->Get(url, response);
     586              :                 __COUTV__(response);
     587              :         }
     588              :         if(0)  //test xml_search
     589              :         {
     590              :                 std::string response, url = "/E/xml_search?l=5";  //limit to 5
     591              :                 url += "&c=" +
     592              :                        StringMacros::encodeURIComponent("Facilities &#46 Building");  //category
     593              :                 __COUTV__(url);
     594              :                 eclConn_->Get(url, response);
     595              :                 __COUTV__(response);
     596              :         }
     597              : 
     598              :         //add all posts that match date/duration criteria
     599              :         {
     600            0 :                 __COUTTV__(categoryFilter);
     601            0 :                 std::string response, url = "/E/xml_search?";  //l=100"; //limit to 100
     602              :                     // "&a=" + std::to_string(duration) + "days" + //after
     603              :                     // "&b=" + baseTimeTmBuffer; //before
     604              : 
     605            0 :                 bool                     applyCategoryFilter         = false;
     606            0 :                 bool                     applyInvertedCategoryFilter = false;
     607            0 :                 std::vector<std::string> acceptCategories;
     608              :                 //filter can start with * for all, or with ! for inverted selection (i.e. all without certain categories)
     609            0 :                 if(categoryFilter.size() && categoryFilter[0] != '*' &&
     610            0 :                    categoryFilter[0] != '!' && categoryFilter.find(',') == std::string::npos &&
     611            0 :                    categoryFilter.find(" / ") == std::string::npos)
     612              :                         url +=
     613            0 :                             "l=100&c=" + StringMacros::encodeURIComponent(categoryFilter);  //category
     614              :                 else
     615              :                 {
     616            0 :                         if(duration > 14)
     617            0 :                                 url += "l=1000";  //get more so better chance to find in filter
     618              :                         else
     619            0 :                                 url += "l=300";  //get more so better chance to find in filter
     620              : 
     621            0 :                         applyCategoryFilter =
     622            0 :                             (categoryFilter.size() && categoryFilter[0] != '*') ? true : false;
     623            0 :                         applyInvertedCategoryFilter =
     624            0 :                             (categoryFilter.size() && categoryFilter[0] == '!') ? true : false;
     625            0 :                         if(applyCategoryFilter)
     626              :                         {
     627            0 :                                 if(applyInvertedCategoryFilter)  //skip 1st char
     628              :                                         acceptCategories =
     629            0 :                                             StringMacros::getVectorFromString(categoryFilter.substr(1));
     630              :                                 else
     631            0 :                                         acceptCategories = StringMacros::getVectorFromString(categoryFilter);
     632            0 :                                 __COUTTV__(StringMacros::vectorToString(acceptCategories));
     633              :                         }
     634            0 :                         __COUTTV__(applyInvertedCategoryFilter);
     635            0 :                         __COUTTV__(applyCategoryFilter);
     636              :                 }
     637              : 
     638              :                 //apply date range
     639              :                 {
     640            0 :                         __COUTTV__(baseTime);
     641            0 :                         __COUTTV__(duration);
     642              : 
     643            0 :                         if(TTEST(30))  //debug date
     644              :                         {
     645            0 :                                 for(size_t i = 0; i < 24; ++i)
     646              :                                 {
     647            0 :                                         __COUTT__ << "i-: " << i << " "
     648            0 :                                                   << ((baseTime - i * 60 * 60) / (60 * 60 * 24));
     649              : 
     650            0 :                                         time_t   modTime = baseTime - i * 60 * 60;
     651              :                                         std::tm* baseTimeTm =
     652            0 :                                             std::localtime(&modTime);  // Convert to local time
     653              :                                         char translatedDate[256];
     654            0 :                                         strftime(
     655              :                                             translatedDate, sizeof(translatedDate), "%Y-%m-%d", baseTimeTm);
     656            0 :                                         __COUTTV__(translatedDate);
     657              :                                 }
     658              : 
     659            0 :                                 for(size_t i = 0; i < 24; ++i)
     660              :                                 {
     661            0 :                                         __COUTT__ << "i+: " << i << " "
     662            0 :                                                   << ((baseTime + i * 60 * 60) / (60 * 60 * 24));
     663            0 :                                         time_t   modTime = baseTime + i * 60 * 60;
     664              :                                         std::tm* baseTimeTm =
     665            0 :                                             std::localtime(&modTime);  // Convert to local time
     666              :                                         char translatedDate[256];
     667            0 :                                         strftime(
     668              :                                             translatedDate, sizeof(translatedDate), "%Y-%m-%d", baseTimeTm);
     669            0 :                                         __COUTTV__(translatedDate);
     670              :                                 }
     671              :                         }
     672              : 
     673              :                         // add one day to calculate before
     674            0 :                         baseTime += 1 * (60 * 60 * 24);  //before is non-inclusive, after is inclusive
     675            0 :                         std::tm* baseTimeTm = std::localtime(&baseTime);  // Convert to local time
     676              :                         char     baseTimeTmBuffer[256];
     677            0 :                         strftime(baseTimeTmBuffer, sizeof(baseTimeTmBuffer), "%Y-%m-%d", baseTimeTm);
     678            0 :                         __COUTTV__(baseTimeTmBuffer);
     679            0 :                         url += "&b=" + std::string(baseTimeTmBuffer) + "+00:00:00";  //before
     680              : 
     681              :                         //now calculate after from duration in days
     682            0 :                         baseTime -=
     683            0 :                             duration * (60 * 60 * 24);  //before is non-inclusive, after is inclusive
     684            0 :                         baseTimeTm = std::localtime(&baseTime);  // Convert to local time
     685            0 :                         strftime(baseTimeTmBuffer, sizeof(baseTimeTmBuffer), "%Y-%m-%d", baseTimeTm);
     686            0 :                         __COUTTV__(baseTimeTmBuffer);
     687            0 :                         url += "&a=" + std::string(baseTimeTmBuffer) + "+00:00:00";  //after
     688              :                 }
     689              :                 // ECLConnection eclConn(ECLUser_, ECLPwd_, ECLHost_);
     690            0 :                 eclConn_->Get(url, response);
     691            0 :                 __COUTVS__(3, response);
     692              : 
     693              :                 //example response:
     694              :                 // <?xml version="1.0" encoding="UTF-8"?>
     695              :                 // <entry_list ids_only="False">
     696              : 
     697              :                 //              <entry
     698              :                 //                      id="2502"
     699              :                 //                      author="mu2e_ots"
     700              :                 //                      category="Global Run"
     701              :                 //                      timestamp="12/05/2024 17:49:53"
     702              :                 //                      html="yes"
     703              :                 //                      formatted="no"
     704              :                 //                      form="default"
     705              :                 //                      images="0"
     706              :                 //                      files="0">
     707              :                 //                              <text>Message: &amp;#010;Run stopped. Run &amp;apos;105214&amp;apos; duration so far of 00:11:57.35 seconds.&amp;#010;&amp;#010;This was a System Generated Log Entry from &amp;apos;Mu2e ot>
     708              :                 //                              <text-html><![CDATA[<pre class="html_safe_entry">Message: &#010;Run stopped. Run &apos;105214&apos; duration so far of 00:11:57.35 seconds.&#010;&#010;This was a System Generated Log Entry from &apos;Mu2e>
     709              :                 //                              <text-cdata><![CDATA[<pre class="html_safe_entry">Message: &#010;Run stopped. Run &apos;105214&apos; duration so far of 00:11:57.35 seconds.&#010;&#010;This was a System Generated Log Entry from &apos;Mu2>
     710              :                 //              </entry>
     711              :                 //              <entry
     712              :                 //                      id="2501"
     713              :                 //                      author="mu2e_ots"
     714              :                 //              ...
     715              : 
     716              :                 //and result to request is:
     717              :                 //  <XML_LOGBOOK_ENTRY>
     718              :                 //              <XML_LOGBOOK_ENTRY_TIME>
     719              :                 //              <XML_LOGBOOK_ENTRY_CREATOR>
     720              :                 //      <XML_LOGBOOK_ENTRY_SUBJECT>
     721              :                 //      <XML_LOGBOOK_ENTRY_TEXT>
     722              :                 //      <XML_LOGBOOK_ENTRY_FILE value=fileType0>
     723              :                 //      <XML_LOGBOOK_ENTRY_FILE value=fileType1> ...
     724              :                 //  </XML_LOGBOOK_ENTRY>
     725              : 
     726            0 :                 std::string id, author, subject, category, timestamp, files, images, text;
     727            0 :                 size_t      after = 0, before = -1, entryCount = 0, lastBefore = -1;
     728              :                 std::tm     tm;
     729            0 :                 std::string preText, postText;
     730              : 
     731            0 :                 while((id =  //StringMacros::extractXmlField(response, "entry", 0, after, &after,
     732            0 :                        StringMacros::rextractXmlField(response,
     733              :                                                       "entry",
     734              :                                                       0,
     735              :                                                       before,
     736              :                                                       &before,  //e.g. for reverse order
     737              :                                                       "id=",
     738            0 :                                                       "\"")) != "")
     739              :                 {
     740            0 :                         __COUTVS__(2, id);
     741            0 :                         ++entryCount;
     742              : 
     743            0 :                         after = before;
     744              : 
     745            0 :                         author = StringMacros::extractXmlField(
     746            0 :                             response, "entry", 0, after, nullptr, "author=", "\"");
     747            0 :                         subject = StringMacros::extractXmlField(
     748            0 :                             response, "entry", 0, after, nullptr, "subject=", "\"");
     749            0 :                         category = StringMacros::extractXmlField(
     750            0 :                             response, "entry", 0, after, nullptr, "category=", "\"");
     751              : 
     752            0 :                         if(applyCategoryFilter)
     753              :                         {
     754            0 :                                 bool found = applyInvertedCategoryFilter;
     755            0 :                                 for(const auto& acceptCategory : acceptCategories)
     756            0 :                                         if(category == acceptCategory ||
     757            0 :                                            category.find(acceptCategory + "/") != std::string::npos)
     758              :                                         {
     759            0 :                                                 found = !applyInvertedCategoryFilter;
     760            0 :                                                 break;
     761              :                                         }
     762              : 
     763            0 :                                 if(!found)
     764              :                                 {
     765            0 :                                         __COUTS__(10)
     766            0 :                                             << "Skipping unaccepted category: " << category << __E__;
     767            0 :                                         lastBefore = before;
     768            0 :                                         --before;  //move back to prepare for next search
     769            0 :                                         continue;
     770            0 :                                 }
     771              :                         }
     772              : 
     773            0 :                         timestamp = StringMacros::extractXmlField(
     774            0 :                             response, "entry", 0, after, nullptr, "timestamp=", "\"");
     775            0 :                         images = StringMacros::extractXmlField(
     776            0 :                             response, "entry", 0, after, nullptr, "images=", "\"");
     777            0 :                         files = StringMacros::extractXmlField(
     778            0 :                             response, "entry", 0, after, nullptr, "files=", "\"");
     779              : 
     780            0 :                         __COUTVS__(2, author);
     781            0 :                         __COUTVS__(2, timestamp);
     782            0 :                         tm = {};  //clear
     783            0 :                         std::istringstream ss(timestamp);
     784              :                         ss >>
     785              :                             std::get_time(
     786            0 :                                 &tm, "%m/%d/%Y %H:%M:%S");  // Parse the string into the tm structure
     787            0 :                         time_t t = std::mktime(&tm);        // Convert tm structure to time_t
     788              : 
     789            0 :                         __COUTVS__(2, t);
     790            0 :                         __COUTVS__(2, mostRecentTime);
     791            0 :                         if(!date && t > mostRecentTime)
     792            0 :                                 mostRecentTime = t;  //track most recent entry
     793            0 :                         __COUTVS__(2, mostRecentTime);
     794              : 
     795              :                         size_t foundTextPos;
     796            0 :                         text = StringMacros::extractXmlField(response,
     797              :                                                              "pre class=\"html_safe_entry\"",
     798              :                                                              0,
     799              :                                                              after,
     800              :                                                              &foundTextPos,
     801              :                                                              "",
     802            0 :                                                              ">");
     803            0 :                         __COUTVS__(2, text.size());
     804            0 :                         if(text.size() == 0 ||         //if not found
     805            0 :                            foundTextPos > lastBefore)  //or found in different entry!
     806              :                         {
     807            0 :                                 text = StringMacros::extractXmlField(
     808            0 :                                     response, "text", 0, after, nullptr, "", ">");
     809            0 :                                 __COUTVS__(2, text.size());
     810              :                         }
     811            0 :                         __COUTVS__(3, text);
     812            0 :                         if(text.size() > std::string("Message: &#010;").size() && text[0] == 'M' &&
     813            0 :                            text[6] == 'e' && text[7] == ':' && text[9] == '&' && text[10] == '#')
     814            0 :                                 text = text.substr(
     815            0 :                                     std::string("Message: &#010;").size());  //skip Message header
     816              : 
     817            0 :                         preText              = "";
     818            0 :                         postText             = "";
     819            0 :                         bool needAttachments = false;
     820            0 :                         if(atoi(images.c_str()))
     821              :                         {
     822            0 :                                 needAttachments = true;
     823            0 :                                 preText += "Attached Images: " + images + "<br>";
     824              :                         }
     825            0 :                         if(atoi(files.c_str()))
     826              :                         {
     827            0 :                                 needAttachments = true;
     828            0 :                                 preText += "Attached Files: " + files + "<br>";
     829              :                         }
     830              : 
     831            0 :                         if(needAttachments)
     832              :                         {
     833            0 :                                 std::string response, url = "/E/xml_get?e=" + id;
     834              :                                 // ECLConnection eclConn(ECLUser_, ECLPwd_, ECLHost_);
     835            0 :                                 eclConn_->Get(url, response);
     836            0 :                                 __COUTVS__(3, response);
     837              : 
     838            0 :                                 size_t fileCount = atoi(files.c_str());
     839            0 :                                 __COUTV__(fileCount);
     840            0 :                                 size_t attachmentAfter = 0;
     841              : 
     842            0 :                                 if(fileCount)
     843            0 :                                         postText += "<br><br>=====> Attached Files:";
     844            0 :                                 for(size_t j = 0; j < fileCount; ++j)
     845              :                                 {
     846            0 :                                         __COUTTV__(attachmentAfter);
     847            0 :                                         std::string furl = StringMacros::extractXmlField(response,
     848              :                                                                                          "attachment",
     849              :                                                                                          0,
     850              :                                                                                          attachmentAfter,
     851              :                                                                                          &attachmentAfter,
     852              :                                                                                          "url=",
     853            0 :                                                                                          "\"");
     854              : 
     855            0 :                                         if(eclConn_->safeURLisHTTPS())
     856              :                                         {
     857            0 :                                                 __COUTS__(2) << "Force file URL to https: " << furl << __E__;
     858            0 :                                                 if(furl.size() > 5 && furl[0] == 'h' && furl[1] == 't' &&
     859            0 :                                                    furl[2] == 't' && furl[3] == 'p' && furl[4] == ':')
     860            0 :                                                         furl.insert(4, 1, 's');  // insert 's' at index 4
     861              :                                         }
     862            0 :                                         __COUTVS__(2, furl);
     863            0 :                                         std::string fname = StringMacros::extractXmlField(response,
     864              :                                                                                           "attachment",
     865              :                                                                                           0,
     866              :                                                                                           attachmentAfter,
     867              :                                                                                           nullptr,
     868              :                                                                                           "filename=",
     869            0 :                                                                                           "\"");
     870              : 
     871            0 :                                         __COUTVS__(2, fname);
     872            0 :                                         postText += "<br>";
     873            0 :                                         if(fileCount > 1)
     874            0 :                                                 postText += "Attached File #" + std::to_string(j + 1) + ": ";
     875              :                                         postText +=
     876            0 :                                             "<a target='_blank' href='" + furl + "'>" + fname + "</a>";
     877            0 :                                         attachmentAfter += 20;  //advance to next
     878            0 :                                 }
     879              : 
     880            0 :                                 size_t imageCount = atoi(images.c_str());
     881            0 :                                 __COUTVS__(3, imageCount);
     882            0 :                                 attachmentAfter = 0;
     883            0 :                                 if(imageCount)
     884            0 :                                         postText += "<br><br>=====> Attached Images:";
     885            0 :                                 for(size_t j = 0; j < imageCount; ++j)
     886              :                                 {
     887            0 :                                         __COUTTV__(attachmentAfter);
     888            0 :                                         attachmentAfter = response.find("type=\"image\"", attachmentAfter);
     889            0 :                                         if(attachmentAfter == std::string::npos)
     890            0 :                                                 break;
     891            0 :                                         attachmentAfter = response.rfind("<attachment", attachmentAfter);
     892            0 :                                         if(attachmentAfter == std::string::npos)
     893            0 :                                                 break;
     894              : 
     895            0 :                                         std::string furl = StringMacros::extractXmlField(response,
     896              :                                                                                          "attachment",
     897              :                                                                                          0,
     898              :                                                                                          attachmentAfter,
     899              :                                                                                          nullptr,
     900              :                                                                                          "full_url=",
     901            0 :                                                                                          "\"");
     902              : 
     903            0 :                                         if(eclConn_->safeURLisHTTPS())
     904              :                                         {
     905            0 :                                                 __COUTS__(2) << "Force file URL to https: " << furl << __E__;
     906            0 :                                                 if(furl.size() > 5 && furl[0] == 'h' && furl[1] == 't' &&
     907            0 :                                                    furl[2] == 't' && furl[3] == 'p' && furl[4] == ':')
     908            0 :                                                         furl.insert(4, 1, 's');  // insert 's' at index 4
     909              :                                         }
     910            0 :                                         __COUTVS__(2, furl);
     911            0 :                                         std::string fname = StringMacros::extractXmlField(response,
     912              :                                                                                           "attachment",
     913              :                                                                                           0,
     914              :                                                                                           attachmentAfter,
     915              :                                                                                           nullptr,
     916              :                                                                                           "filename=",
     917            0 :                                                                                           "\"");
     918              : 
     919            0 :                                         __COUTVS__(2, fname);
     920            0 :                                         postText += "<br>";
     921            0 :                                         if(imageCount > 1)
     922            0 :                                                 postText += "Attached Image #" + std::to_string(j + 1) + ": ";
     923              :                                         postText +=
     924            0 :                                             "<a target='_blank' href='" + furl + "'>" + fname + "</a>";
     925              : 
     926            0 :                                         attachmentAfter += 50;  //advance to next
     927            0 :                                 }
     928            0 :                         }  //end attachment handling
     929              : 
     930              :                         // Lambda function to wrap links
     931            0 :                         auto wrapLinks = [](const std::string& inputText) -> std::string {
     932            0 :                                 std::string            result;
     933            0 :                                 std::string::size_type pos = 0;
     934              :                                 std::string::size_type start;
     935              : 
     936            0 :                                 while((start = inputText.find("http://", pos)) != std::string::npos ||
     937            0 :                                       (start = inputText.find("https://", pos)) != std::string::npos)
     938              :                                 {
     939              :                                         // Append text before the link
     940            0 :                                         result.append(inputText, pos, start - pos);
     941              : 
     942              :                                         // Find the end of the URL (stop at space or end of string)
     943              :                                         std::string::size_type end =
     944            0 :                                             inputText.find_first_of(" \t\n!<>(),\"\'", start);
     945            0 :                                         if(end == std::string::npos)
     946              :                                         {
     947            0 :                                                 end = inputText.size();
     948              :                                         }
     949              : 
     950              :                                         // Extract the URL
     951            0 :                                         std::string url = inputText.substr(start, end - start);
     952              : 
     953              :                                         // Append the <a> tag
     954            0 :                                         result += "<a href=\"" + url + "\" target=\"_blank\">" + url + "</a>";
     955              : 
     956              :                                         // Move position to after the URL
     957            0 :                                         pos = end;
     958            0 :                                 }
     959              : 
     960              :                                 // Append any remaining text
     961            0 :                                 result.append(inputText, pos, inputText.size() - pos);
     962              : 
     963            0 :                                 return result;
     964            0 :                         };
     965              : 
     966              :                         text =
     967            0 :                             preText + (preText.size() ? "\n<br>" : "") + wrapLinks(text) + postText;
     968            0 :                         __COUTVS__(2, text);
     969              : 
     970            0 :                         if(xmlOut)
     971              :                         {
     972            0 :                                 auto entryParent = xmlOut->addTextElementToData(XML_LOGBOOK_ENTRY);
     973              : 
     974            0 :                                 xmlOut->addTextElementToParent(
     975            0 :                                     XML_LOGBOOK_ENTRY_TIME, std::to_string(t), entryParent);
     976            0 :                                 xmlOut->addTextElementToParent(
     977              :                                     XML_LOGBOOK_ENTRY_CREATOR, author, entryParent);
     978            0 :                                 xmlOut->addTextElementToParent(XML_LOGBOOK_ENTRY_TEXT, text, entryParent);
     979            0 :                                 xmlOut->addTextElementToParent(
     980              :                                     XML_LOGBOOK_ENTRY_SUBJECT,
     981            0 :                                     category + " - entry #" + id + " - " + subject,
     982              :                                     entryParent);
     983              :                         }
     984              : 
     985            0 :                         lastBefore = before;
     986            0 :                         --before;  //move back to prepare for next search
     987              :                         // after += std::string("entry").size(); //move forward to prepare for next search
     988              : 
     989            0 :                 }  //end primary entry extraction loop
     990              : 
     991            0 :                 __COUTV__(entryCount);
     992            0 :         }  //end add all posts that match
     993              : 
     994            0 :         if(xmlOut)
     995            0 :                 xmlOut->addTextElementToData(XML_STATUS, "1");  // for success
     996            0 :         if(out)
     997            0 :                 *out << __COUT_HDR_FL__ << "Today: " << time(0) / (60 * 60 * 24) << std::endl;
     998              : 
     999            0 :         if(TTEST(30))
    1000              :         {
    1001            0 :                 __COUTTV__(mostRecentTime);
    1002            0 :                 __COUTTV__(time(0));
    1003            0 :                 __COUTTV__(time(0) - mostRecentTime);
    1004            0 :                 __COUTTV__((time(0) - mostRecentTime) / (60 * 60 * 24));
    1005            0 :                 __COUTTV__(timezoneHourOffset_);
    1006            0 :                 for(size_t i = 0; i < 24; ++i)
    1007            0 :                         __COUTT__ << "i: " << i << " "
    1008            0 :                                   << ((time(0) - i * 60 * 60 + timezoneHourOffset_ * 60 * 60) /
    1009            0 :                                       (60 * 60 * 24));
    1010              :         }
    1011              : 
    1012            0 :         if(xmlOut)
    1013              :         {
    1014            0 :                 xmlOut->addNumberElementToData(XML_TIMEZONE_OFFSET, timezoneHourOffset_);
    1015              : 
    1016              :                 if(0 &&
    1017              :                    mostRecentTime)  //always return 0 for ECL, because category filter may change, and may want live view of today..
    1018              :                 {
    1019              :                         int64_t mostRecentDayIndex =
    1020              :                             (mostRecentTime + timezoneHourOffset_ * 60 * 60) / (60 * 60 * 24);
    1021              :                         int64_t nowDayIndex =
    1022              :                             (time(0) + timezoneHourOffset_ * 60 * 60) / (60 * 60 * 24);
    1023              :                         if(TTEST(30))
    1024              :                         {
    1025              :                                 __COUTTV__(mostRecentDayIndex);
    1026              :                                 __COUTTV__(nowDayIndex);
    1027              :                                 __COUTTV__(mostRecentTime);
    1028              :                                 __COUTTV__(((time(0) - mostRecentTime + timezoneHourOffset_ * 60 * 60) /
    1029              :                                             (60 * 60 * 24)));
    1030              :                         }
    1031              :                         __COUTTV__(nowDayIndex - mostRecentDayIndex);
    1032              :                         xmlOut->addNumberElementToData(
    1033              :                             XML_MOST_RECENT_DAY,  //0 is today
    1034              :                             nowDayIndex -
    1035              :                                 mostRecentDayIndex);  // send most recent day index found in response
    1036              :                 }
    1037              :                 else
    1038            0 :                         xmlOut->addNumberElementToData(XML_MOST_RECENT_DAY, 0);
    1039              :         }
    1040              : 
    1041            0 : }  //end refreshLogbook()
    1042              : 
    1043              : //==============================================================================
    1044              : /// xoap::MakeSystemLogEntry
    1045              : ///     make a system logbook entry into active category's logbook from Supervisor only
    1046              : ///     TODO: (how to enforce?)
    1047            0 : xoap::MessageReference ECLSupervisor::MakeSystemLogEntry(xoap::MessageReference msg)
    1048              : {
    1049            0 :         SOAPParameters parameters("EntryText");
    1050            0 :         parameters.addParameter("SubjectText");
    1051            0 :         SOAPUtilities::receive(msg, parameters);
    1052              :         std::string EntryText =
    1053            0 :             StringMacros::decodeURIComponent(parameters.getValue("EntryText"));
    1054              :         std::string SubjectText =
    1055            0 :             StringMacros::decodeURIComponent(parameters.getValue("SubjectText"));
    1056              : 
    1057            0 :         __COUT__ << "Received External Supervisor System Entry " << EntryText << std::endl;
    1058            0 :         __COUTV__(SubjectText);
    1059              : 
    1060            0 :         std::string retStr = "Success";
    1061              : 
    1062            0 :         if(ECLUser_ == "" ||
    1063            0 :            ECLHost_ == "")  //ignore ECL when environment variables are not set
    1064              :         {
    1065            0 :                 __COUT_INFO__ << "No ECL user/host specified for log entry: " << EntryText
    1066            0 :                               << __E__;
    1067              : 
    1068              :                 // fill return parameters
    1069            0 :                 SOAPParameters retParameters("Status", retStr);
    1070              : 
    1071            0 :                 return SOAPUtilities::makeSOAPMessageReference("SystemLogEntryStatusResponse",
    1072            0 :                                                                retParameters);
    1073            0 :         }
    1074              : 
    1075            0 :         ECLEntry_t eclEntry;
    1076            0 :         eclEntry.author(StringMacros::escapeString(ECLUser_));
    1077            0 :         eclEntry.category(StringMacros::escapeString(ECLCategory_));
    1078            0 :         eclEntry.subject(StringMacros::escapeString(SubjectText));
    1079              : 
    1080            0 :         Form_t                 form;
    1081            0 :         Field_t                field;
    1082            0 :         Form_t::field_sequence fields;
    1083            0 :         std::string            users = theRemoteWebUsers_.getActiveUserList();
    1084              : 
    1085            0 :         form.name("default");  // these form names must be created in advance? ... default
    1086              :                                // seems to have one field and be generic: 'text' field
    1087              : 
    1088              :         {
    1089            0 :                 std::stringstream ss;
    1090            0 :                 ss << "Message: " << __E__ << EntryText << __E__ << __E__;
    1091            0 :                 ss << "This was a System Generated Log Entry from '" << CategoryName_
    1092            0 :                    << "' at host '" << __ENV__("THIS_HOST") << "'" << __E__;
    1093            0 :                 ss << "Active ots users: " << users << __E__;
    1094            0 :                 ss << "USER_DATA: " << __ENV__("USER_DATA") << __E__;
    1095              :                 ss << "Uptime: "
    1096            0 :                    << StringMacros::getTimeDurationString(
    1097            0 :                           CorePropertySupervisorBase::getSupervisorUptime())
    1098            0 :                    << __E__;
    1099            0 :                 field = Field_t(StringMacros::escapeString(ss.str(), true /* keep white space */),
    1100            0 :                                 "text");
    1101            0 :                 fields.push_back(field);
    1102            0 :         }
    1103              : 
    1104            0 :         form.field(fields);
    1105            0 :         eclEntry.form(form);
    1106              :         try
    1107              :         {
    1108              :                 // ECLConnection eclConn(ECLUser_, ECLPwd_, ECLHost_);
    1109            0 :                 if(!eclConn_->Post(eclEntry))
    1110              :                 {
    1111            0 :                         __COUT_ERR__ << "Failure to post ECL entry." << __E__;
    1112            0 :                         retStr = "Failure";
    1113              :                 }
    1114              :         }
    1115            0 :         catch(const std::runtime_error& e)
    1116              :         {
    1117            0 :                 __SS__ << "Exception caught during Logbook ECL connection: " << e.what();
    1118              :                 ss << "\n\nIf the problem persists, you can turn off ECL communication by "
    1119              :                       "clearing the environment variable ECL_USER_NAME (e.g. "
    1120              :                    << "export ECL_USER_NAME="
    1121              :                       ") at the terminal and restarting ots, or you can permanently disable the "
    1122              :                       "requirement by editing the Configuration Tree and setting Gateway "
    1123              :                       "Supervisor --> State Machine --> "
    1124              :                       "RequireUserLogInputOnConfigureTransition/"
    1125            0 :                       "RequireUserLogInputOnRunTransition = false.";
    1126            0 :                 __COUT_ERR__ << ss.str();
    1127            0 :                 retStr = ss.str();
    1128            0 :         }
    1129              : 
    1130              :         // fill return parameters
    1131            0 :         SOAPParameters retParameters("Status", retStr);
    1132              : 
    1133            0 :         return SOAPUtilities::makeSOAPMessageReference("SystemLogEntryStatusResponse",
    1134            0 :                                                        retParameters);
    1135            0 : }  // end MakeSystemLogEntry()
        

Generated by: LCOV version 2.0-1