1 #include "otsdaq/CodeEditor/CodeEditor.h"
2 #include "otsdaq/CgiDataUtilities/CgiDataUtilities.h"
3 #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
12 #define CODE_EDITOR_DATA_PATH std::string(__ENV__("SERVICE_DATA_PATH")) + "/" + "CodeEditorData"
15 #define __MF_SUBJECT__ "CodeEditor"
17 const std::string CodeEditor::SPECIAL_TYPE_FEInterface =
"FEInterface";
18 const std::string CodeEditor::SPECIAL_TYPE_DataProcessor =
"DataProcessor";
19 const std::string CodeEditor::SPECIAL_TYPE_Table =
"Table";
20 const std::string CodeEditor::SPECIAL_TYPE_SlowControls =
"SlowControls";
21 const std::string CodeEditor::SPECIAL_TYPE_Tools =
"Tools";
22 const std::string CodeEditor::SPECIAL_TYPE_UserData =
"UserData";
23 const std::string CodeEditor::SPECIAL_TYPE_OutputData =
"OutputData";
25 const std::string CodeEditor::SOURCE_BASE_PATH = std::string(__ENV__(
"MRB_SOURCE")) +
"/";
26 const std::string CodeEditor::USER_DATA_PATH = std::string(__ENV__(
"USER_DATA")) +
"/";
27 const std::string CodeEditor::OTSDAQ_DATA_PATH = std::string(__ENV__(
"OTSDAQ_DATA")) +
"/";
31 CodeEditor::CodeEditor()
32 : ALLOWED_FILE_EXTENSIONS_(
33 {
"h",
"hh",
"hpp",
"hxx",
"c",
"cc",
"cpp",
"cxx",
"icc",
"dat",
"txt",
"sh",
"css",
"html",
"htm",
"js",
"py",
"fcl",
"xml",
"cfg" })
35 std::string path = CODE_EDITOR_DATA_PATH;
36 DIR* dir = opendir(path.c_str());
39 else if (-1 == mkdir(path.c_str(), 0755))
42 __SS__ <<
"Service directory creation failed: " << path << std::endl;
51 void CodeEditor::xmlRequest(
const std::string& option,
bool readOnlyMode, cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut,
const std::string& username)
try
65 if (option ==
"getDirectoryContent")
67 getDirectoryContent(cgiIn, xmlOut);
69 else if (option ==
"getFileContent")
71 getFileContent(cgiIn, xmlOut);
73 else if (!readOnlyMode && option ==
"saveFileContent")
75 saveFileContent(cgiIn, xmlOut, username);
77 else if (!readOnlyMode && option ==
"build")
79 build(cgiIn, xmlOut, username);
81 else if (option ==
"getAllowedExtensions")
83 xmlOut->addTextElementToData(
"AllowedExtensions", StringMacros::setToString(ALLOWED_FILE_EXTENSIONS_,
","));
87 __SS__ <<
"Unrecognized request option '" << option <<
".'" << __E__;
91 catch (
const std::runtime_error& e)
93 __SS__ <<
"Error encountered while handling the Code Editor request option '" << option <<
"': " << e.what() << __E__;
94 xmlOut->addTextElementToData(
"Error", ss.str());
98 __SS__ <<
"Unknown error encountered while handling the Code Editor request option '" << option <<
"!'" << __E__;
99 xmlOut->addTextElementToData(
"Error", ss.str());
104 std::string CodeEditor::safePathString(
const std::string& path)
108 std::string fullpath =
"";
109 for (
unsigned int i = 0; i < path.length(); ++i)
110 if ((path[i] >=
'a' && path[i] <=
'z') || (path[i] >=
'A' && path[i] <=
'Z') || path[i] >=
'_' || path[i] >=
'-' || path[i] >=
' ' || path[i] >=
'/')
113 if (!fullpath.length())
115 __SS__ <<
"Invalid path '" << fullpath <<
"' found!" << __E__;
124 std::string CodeEditor::safeExtensionString(
const std::string& extension)
128 std::string retExt =
"";
132 for (
unsigned int i = 0; i < extension.length(); ++i)
133 if ((extension[i] >=
'a' && extension[i] <=
'z'))
134 retExt += extension[i];
135 else if ((extension[i] >=
'A' && extension[i] <=
'Z'))
136 retExt += extension[i] + 32;
137 else if (i > 0 || extension[i] !=
'.')
139 __SS__ <<
"Invalid extension non-alpha " << int(extension[i]) <<
" found!" << __E__;
145 if (ALLOWED_FILE_EXTENSIONS_.find(retExt) ==
146 ALLOWED_FILE_EXTENSIONS_.end())
148 __SS__ <<
"Invalid extension '" << retExt <<
"' found!" << __E__;
156 void CodeEditor::getDirectoryContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
158 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
159 path = safePathString(StringMacros::decodeURIComponent(path));
161 __COUTV__(CodeEditor::SOURCE_BASE_PATH);
163 xmlOut->addTextElementToData(
"path", path);
165 const unsigned int numOfTypes = 7;
166 std::string specialTypeNames[] = {
"Front-End Plugins",
167 "Data Processor Plugins",
168 "Configuration Table Plugins",
169 "Slow Controls Interface Plugins",
173 std::string specialTypes[] = { SPECIAL_TYPE_FEInterface,
174 SPECIAL_TYPE_DataProcessor,
176 SPECIAL_TYPE_SlowControls,
178 SPECIAL_TYPE_UserData,
179 SPECIAL_TYPE_OutputData };
181 std::string pathMatchPrepend =
"/";
183 if (path.length() > 1 && path[1] ==
'/')
184 pathMatchPrepend +=
'/';
186 for (
unsigned int i = 0; i < numOfTypes; ++i)
187 if (path == pathMatchPrepend + specialTypeNames[i])
189 __COUT__ <<
"Getting all " << specialTypeNames[i] <<
"..." << __E__;
193 if (specialTypes[i] == SPECIAL_TYPE_UserData)
195 getPathContent(
"/", CodeEditor::USER_DATA_PATH, xmlOut);
198 else if (specialTypes[i] == SPECIAL_TYPE_OutputData)
200 getPathContent(
"/", CodeEditor::OTSDAQ_DATA_PATH, xmlOut);
204 std::map<std::string , std::set<std::string> > retMap = CodeEditor::getSpecialsMap();
205 if (retMap.find(specialTypes[i]) != retMap.end())
207 for (
const auto& specialTypeFile : retMap[specialTypes[i]])
209 xmlOut->addTextElementToData(
"specialFile", specialTypeFile);
214 __SS__ <<
"No files for type '" << specialTypeNames[i] <<
"' were found." << __E__;
222 for (
unsigned int i = 0; i < numOfTypes; ++i)
223 xmlOut->addTextElementToData(
"special", specialTypeNames[i]);
225 std::string contents;
227 if ((i = path.find(
"$USER_DATA/")) == 0 || (i == 1 && path[0] ==
'/'))
228 getPathContent(CodeEditor::USER_DATA_PATH, path.substr(std::string(
"/$USER_DATA/").size()), xmlOut);
229 else if ((i = path.find(
"$OTSDAQ_DATA/")) == 0 || (i == 1 && path[0] ==
'/'))
230 getPathContent(CodeEditor::OTSDAQ_DATA_PATH, path.substr(std::string(
"/$OTSDAQ_DATA/").size()), xmlOut);
232 getPathContent(CodeEditor::SOURCE_BASE_PATH, path, xmlOut);
238 void CodeEditor::getPathContent(
const std::string& basepath,
const std::string& path,
HttpXmlDocument* xmlOut)
241 struct dirent* entry;
246 if (!(pDIR = opendir((basepath + path).c_str())))
248 __SS__ <<
"Path '" << path <<
"' could not be opened!" << __E__;
254 struct InsensitiveCompare
256 bool operator()(
const std::string& as,
const std::string& bs)
const
259 const char* a = as.c_str();
260 const char* b = bs.c_str();
264 while ((d = (std::toupper(*a) - std::toupper(*b))) == 0 && *a)
272 std::set<std::string, InsensitiveCompare> orderedDirectories;
273 std::set<std::string, InsensitiveCompare> orderedFiles;
275 std::string extension;
278 while ((entry = readdir(pDIR)))
280 name = std::string(entry->d_name);
281 type = int(entry->d_type);
285 if (name[0] !=
'.' && (type == 0 ||
293 if (type == 0 || type == 10)
296 DIR* pTmpDIR = opendir((basepath + path +
"/" + name).c_str());
316 orderedDirectories.emplace(name);
326 safeExtensionString(name.substr(name.rfind(
'.')));
329 orderedFiles.emplace(name);
333 __COUT__ <<
"Invalid file extension, skipping '" << name <<
"' ..." << __E__;
341 __COUT__ <<
"Found " << orderedDirectories.size() <<
" directories." << __E__;
342 __COUT__ <<
"Found " << orderedFiles.size() <<
" files." << __E__;
344 for (
const auto& name : orderedDirectories)
345 xmlOut->addTextElementToData(
"directory", name);
346 for (
const auto& name : orderedFiles)
347 xmlOut->addTextElementToData(
"file", name);
352 void CodeEditor::getFileContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
354 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
355 path = safePathString(StringMacros::decodeURIComponent(path));
356 xmlOut->addTextElementToData(
"path", path);
358 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
359 if (extension ==
"ots")
362 if (!(path.length() > 4 && path.substr(path.length() - 4) ==
"/ots"))
363 extension = safeExtensionString(extension);
364 xmlOut->addTextElementToData(
"ext", extension);
366 std::string contents;
368 if ((i = path.find(
"$USER_DATA/")) == 0 || (i == 1 && path[0] ==
'/'))
369 CodeEditor::readFile(
370 CodeEditor::USER_DATA_PATH, path.substr(i + std::string(
"$USER_DATA/").size()) + (extension.size() ?
"." :
"") + extension, contents);
371 else if ((i = path.find(
"$OTSDAQ_DATA/")) == 0 || (i == 1 && path[0] ==
'/'))
372 CodeEditor::readFile(
373 CodeEditor::OTSDAQ_DATA_PATH, path.substr(std::string(
"/$OTSDAQ_DATA/").size()) + (extension.size() ?
"." :
"") + extension, contents);
375 CodeEditor::readFile(CodeEditor::SOURCE_BASE_PATH, path + (extension.size() ?
"." :
"") + extension, contents);
377 xmlOut->addTextElementToData(
"content", contents);
383 void CodeEditor::readFile(
const std::string& basepath,
const std::string& path, std::string& contents)
385 std::string fullpath = basepath +
"/" + path;
388 std::FILE* fp = std::fopen(fullpath.c_str(),
"rb");
391 __SS__ <<
"Could not open file at " << path << __E__;
395 std::fseek(fp, 0, SEEK_END);
396 contents.resize(std::ftell(fp));
398 std::fread(&contents[0], 1, contents.size(), fp);
404 void CodeEditor::writeFile(
const std::string& basepath,
405 const std::string& path,
406 const std::string& contents,
407 const std::string& username,
408 const unsigned long long& insertPos,
409 const std::string& insertString)
411 std::string fullpath = basepath + path;
416 long long int oldSize = 0;
420 fp = fopen(fullpath.c_str(),
"rb");
423 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
426 std::fseek(fp, 0, SEEK_END);
427 oldSize = std::ftell(fp);
432 __COUT_WARN__ <<
"Ignoring file not existing..." << __E__;
435 fp = fopen(fullpath.c_str(),
"wb");
438 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
442 if (insertPos == (
unsigned long long) - 1)
443 std::fwrite(&contents[0], 1, contents.size(), fp);
446 std::fwrite(&contents[0], 1, insertPos, fp);
447 std::fwrite(&insertString[0], 1, insertString.size(), fp);
448 std::fwrite(&contents[insertPos], 1, contents.size() - insertPos, fp);
454 std::string logpath = CODE_EDITOR_DATA_PATH +
"/codeEditorChangeLog.txt";
455 fp = fopen(logpath.c_str(),
"a");
458 __SS__ <<
"Could not open change log for change tracking at " << logpath << __E__;
462 "time=%lld author%s old-size=%lld new-size=%lld path=%s\n",
466 (
long long)contents.size(),
470 __COUT__ <<
"Changes logged to: " << logpath << __E__;
477 void CodeEditor::saveFileContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut,
const std::string& username)
479 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
480 path = safePathString(StringMacros::decodeURIComponent(path));
481 xmlOut->addTextElementToData(
"path", path);
483 std::string basepath = CodeEditor::SOURCE_BASE_PATH;
485 std::string pathMatchPrepend =
"/";
487 if (path.length() > 1 && path[1] ==
'/')
488 pathMatchPrepend +=
'/';
494 if (path.substr(0, (pathMatchPrepend +
"$USER_DATA/").size()) == pathMatchPrepend +
"$USER_DATA/")
497 path = CodeEditor::USER_DATA_PATH +
"/" + path.substr((pathMatchPrepend +
"$USER_DATA/").size());
499 else if (path.substr(0, (pathMatchPrepend +
"$OTSDAQ_DATA/").size()) == pathMatchPrepend +
"$OTSDAQ_DATA/")
502 path = CodeEditor::OTSDAQ_DATA_PATH +
"/" + path.substr((pathMatchPrepend +
"$OTSDAQ_DATA/").size());
507 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
508 if (!(path.length() > 4 && path.substr(path.length() - 4) ==
"/ots"))
509 extension = safeExtensionString(extension);
510 xmlOut->addTextElementToData(
"ext", extension);
512 std::string contents = CgiDataUtilities::postData(cgiIn,
"content");
514 contents = StringMacros::decodeURIComponent(contents);
516 CodeEditor::writeFile(basepath, path + (extension.size() ?
"." :
"") + extension, contents, username);
523 void CodeEditor::build(cgicc::Cgicc& cgiIn,
HttpXmlDocument* ,
const std::string& username)
525 bool clean = CgiDataUtilities::getDataAsInt(cgiIn,
"clean") ?
true :
false;
527 __MCOUT_INFO__(
"Build (clean=" << clean <<
") launched by '" << username <<
"'..." << __E__);
540 std::array<char, 128> buffer;
542 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
544 __THROW__(
"popen() failed!");
549 while (!feof(pipe.get()))
551 if (fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
553 result += buffer.data();
556 i = result.find(
'\n');
557 __COUTV__(result.substr(0, i));
558 __MOUT__ << result.substr(0, i);
559 result = result.substr(i + 1);
564 __MOUT__ << result.substr(0, i);
570 cmd =
"source mrbSetEnv 2>&1";
572 std::array<char, 128> buffer;
574 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
576 __THROW__(
"popen() failed!");
581 while (!feof(pipe.get()))
583 if (fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
585 result += buffer.data();
588 i = result.find(
'\n');
589 __COUTV__(result.substr(0, i));
590 __MOUT__ << result.substr(0, i);
591 result = result.substr(i + 1);
596 __MOUT__ << result.substr(0, i);
605 std::array<char, 128> buffer;
607 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
609 __THROW__(
"popen() failed!");
614 while (!feof(pipe.get()))
616 if (fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
618 result += buffer.data();
621 i = result.find(
'\n');
623 __MOUT__ << result.substr(0, i);
624 result = result.substr(i + 1);
629 __MOUT__ << result.substr(0, i);
638 std::map<std::string , std::set<std::string> > CodeEditor::getSpecialsMap(
void)
640 std::map<std::string , std::set<std::string> > retMap;
641 std::string path = std::string(__ENV__(
"MRB_SOURCE"));
645 std::vector<std::string> specialFolders({
"FEInterfaces",
646 "DataProcessorPlugins",
647 "UserTableDataFormats",
649 "TablePluginDataFormats",
651 "UserTablePluginDataFormats",
652 "SlowControlsInterfacePlugins",
653 "ControlsInterfacePlugins",
654 "FEInterfacePlugins",
656 std::vector<std::string> specialMapTypes({ CodeEditor::SPECIAL_TYPE_FEInterface,
657 CodeEditor::SPECIAL_TYPE_DataProcessor,
658 CodeEditor::SPECIAL_TYPE_Table,
659 CodeEditor::SPECIAL_TYPE_Table,
660 CodeEditor::SPECIAL_TYPE_Table,
661 CodeEditor::SPECIAL_TYPE_Table,
662 CodeEditor::SPECIAL_TYPE_Table,
663 CodeEditor::SPECIAL_TYPE_SlowControls,
664 CodeEditor::SPECIAL_TYPE_SlowControls,
665 CodeEditor::SPECIAL_TYPE_FEInterface,
666 CodeEditor::SPECIAL_TYPE_Tools });
671 std::function<void(const std::string&, const std::string&, const unsigned int, const int)> localRecurse =
672 [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
673 const std::string& path,
const std::string& offsetPath,
const unsigned int depth,
const int specialIndex) {
679 struct dirent* entry;
681 if (!(pDIR = opendir(path.c_str())))
683 __SS__ <<
"Plugin base path '" << path <<
"' could not be opened!" << __E__;
690 int childSpecialIndex;
691 while ((entry = readdir(pDIR)))
693 name = std::string(entry->d_name);
694 type = int(entry->d_type);
698 if (name[0] !=
'.' && (type == 0 ||
699 type == 4 || type == 8))
706 DIR* pTmpDIR = opendir((path +
"/" + name).c_str());
724 childSpecialIndex = -1;
725 for (
unsigned int i = 0; i < specialFolders.size(); ++i)
726 if (name == specialFolders[i])
732 childSpecialIndex = i;
738 localRecurse(path +
"/" + name, offsetPath +
"/" + name, depth + 1, childSpecialIndex);
740 else if (specialIndex >= 0)
744 if (name.find(
".h") == name.length() - 2 || name.find(
".cc") == name.length() - 3 || name.find(
".txt") == name.length() - 4 ||
745 name.find(
".sh") == name.length() - 3 || name.find(
".py") == name.length() - 3)
752 retMap[specialMapTypes[specialIndex]].emplace(offsetPath +
"/" + name);
763 localRecurse(path,
"" , 0 , -1 );