1 #include "otsdaq/CodeEditor/CodeEditor.h"
2 #include "otsdaq/CgiDataUtilities/CgiDataUtilities.h"
3 #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
12 #define CODE_EDITOR_DATA_PATH \
13 std::string(__ENV__("SERVICE_DATA_PATH")) + "/" + "CodeEditorData"
16 #define __MF_SUBJECT__ "CodeEditor"
18 const std::string CodeEditor::SPECIAL_TYPE_FEInterface =
"FEInterface";
19 const std::string CodeEditor::SPECIAL_TYPE_DataProcessor =
"DataProcessor";
20 const std::string CodeEditor::SPECIAL_TYPE_Table =
"Table";
21 const std::string CodeEditor::SPECIAL_TYPE_SlowControls =
"SlowControls";
22 const std::string CodeEditor::SPECIAL_TYPE_Tools =
"Tools";
23 const std::string CodeEditor::SPECIAL_TYPE_UserData =
"UserData";
24 const std::string CodeEditor::SPECIAL_TYPE_WebPath =
"WebPath";
25 const std::string CodeEditor::SPECIAL_TYPE_OutputData =
"OutputData";
27 const std::string CodeEditor::SOURCE_BASE_PATH = std::string(__ENV__(
"OTS_SOURCE")) +
"/";
28 const std::string CodeEditor::USER_DATA_PATH = std::string(__ENV__(
"USER_DATA")) +
"/";
29 const std::string CodeEditor::OTSDAQ_DATA_PATH =
30 std::string(__ENV__(
"OTSDAQ_DATA")) +
"/";
31 const std::string CodeEditor::OTSDAQ_WEB_PATH =
32 std::string(__ENV__(
"OTSDAQ_WEB_PATH")) +
"/";
36 CodeEditor::CodeEditor()
37 : ALLOWED_FILE_EXTENSIONS_({
"h",
"hh",
"hpp",
"hxx",
"c",
"cc",
"cpp",
38 "cxx",
"icc",
"dat",
"txt",
"sh",
"css",
"html",
39 "htm",
"js",
"py",
"fcl",
"xml",
"xsd",
"cfg"})
41 std::string path = CODE_EDITOR_DATA_PATH;
42 DIR* dir = opendir(path.c_str());
45 else if(-1 == mkdir(path.c_str(), 0755))
48 __SS__ <<
"Service directory creation failed: " << path << std::endl;
57 void CodeEditor::xmlRequest(
const std::string& option,
61 const std::string& username)
77 if(option ==
"getDirectoryContent")
79 getDirectoryContent(cgiIn, xmlOut);
81 else if(option ==
"getFileContent")
83 getFileContent(cgiIn, xmlOut);
85 else if(!readOnlyMode && option ==
"saveFileContent")
87 saveFileContent(cgiIn, xmlOut, username);
89 else if(!readOnlyMode && option ==
"build")
91 build(cgiIn, xmlOut, username);
93 else if(option ==
"getAllowedExtensions")
95 xmlOut->addTextElementToData(
99 else if(option ==
"getFileGitURL")
101 getFileGitURL(cgiIn, xmlOut);
105 __SS__ <<
"Unrecognized request option '" << option <<
".'" << __E__;
109 catch(
const std::runtime_error& e)
111 __SS__ <<
"Error encountered while handling the Code Editor request option '"
112 << option <<
"': " << e.what() << __E__;
113 xmlOut->addTextElementToData(
"Error", ss.str());
117 __SS__ <<
"Unknown error encountered while handling the Code Editor request option '"
118 << option <<
"!'" << __E__;
119 xmlOut->addTextElementToData(
"Error", ss.str());
124 std::string CodeEditor::safePathString(
const std::string& path)
126 __COUTVS__(20, path);
128 std::string fullpath =
"";
129 for(
unsigned int i = 0; i < path.length(); ++i)
130 if((path[i] >=
'a' && path[i] <=
'z') || (path[i] >=
'A' && path[i] <=
'Z') ||
131 path[i] >=
'_' || path[i] >=
'-' || path[i] >=
' ' || path[i] >=
'/')
133 __COUTVS__(20, fullpath);
134 if(!fullpath.length())
136 __SS__ <<
"Invalid path '" << fullpath <<
"' found!" << __E__;
145 std::string CodeEditor::safeExtensionString(
const std::string& extension)
149 std::string retExt =
"";
153 for(
unsigned int i = 0; i < extension.length(); ++i)
154 if((extension[i] >=
'a' && extension[i] <=
'z'))
155 retExt += extension[i];
156 else if((extension[i] >=
'A' && extension[i] <=
'Z'))
157 retExt += extension[i] + 32;
158 else if(i > 0 || extension[i] !=
'.')
160 __SS__ <<
"Invalid extension non-alpha " << int(extension[i]) <<
" found!"
167 if(ALLOWED_FILE_EXTENSIONS_.find(
169 ALLOWED_FILE_EXTENSIONS_.end())
171 __SS__ <<
"Invalid extension '" << retExt <<
"' found!" << __E__;
179 void CodeEditor::getDirectoryContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
184 __COUTV__(CodeEditor::SOURCE_BASE_PATH);
186 xmlOut->addTextElementToData(
"path", path);
188 const unsigned int numOfTypes = 8;
189 std::string specialTypeNames[] = {
"Front-End Plugins",
190 "Data Processor Plugins",
191 "Configuration Table Plugins",
192 "Slow Controls Interface Plugins",
197 std::string specialTypes[] = {SPECIAL_TYPE_FEInterface,
198 SPECIAL_TYPE_DataProcessor,
200 SPECIAL_TYPE_SlowControls,
202 SPECIAL_TYPE_UserData,
203 SPECIAL_TYPE_WebPath,
204 SPECIAL_TYPE_OutputData};
206 std::string pathMatchPrepend =
"/";
208 if(path.length() > 1 && path[1] ==
'/')
209 pathMatchPrepend +=
'/';
211 for(
unsigned int i = 0; i < numOfTypes; ++i)
212 if(path == pathMatchPrepend + specialTypeNames[i])
214 __COUT__ <<
"Getting all " << specialTypeNames[i] <<
"..." << __E__;
218 if(specialTypes[i] == SPECIAL_TYPE_UserData)
220 getPathContent(
"/", CodeEditor::USER_DATA_PATH, xmlOut);
223 else if(specialTypes[i] == SPECIAL_TYPE_WebPath)
225 getPathContent(
"/", CodeEditor::OTSDAQ_WEB_PATH, xmlOut);
228 else if(specialTypes[i] == SPECIAL_TYPE_OutputData)
230 getPathContent(
"/", CodeEditor::OTSDAQ_DATA_PATH, xmlOut);
234 std::map<std::string ,
235 std::set<std::string> >
236 retMap = CodeEditor::getSpecialsMap();
237 if(retMap.find(specialTypes[i]) != retMap.end())
239 for(
const auto& specialTypeFile : retMap[specialTypes[i]])
241 xmlOut->addTextElementToData(
"specialFile", specialTypeFile);
246 __SS__ <<
"No files for type '" << specialTypeNames[i] <<
"' were found."
255 for(
unsigned int i = 0; i < numOfTypes; ++i)
256 xmlOut->addTextElementToData(
"special", specialTypeNames[i]);
258 std::string contents;
260 if((i = path.find(
"$USER_DATA/")) == 0 ||
261 (i == 1 && path[0] ==
'/'))
262 getPathContent(CodeEditor::USER_DATA_PATH,
263 path.substr(std::string(
"/$USER_DATA/").size()),
265 else if((i = path.find(
"$OTSDAQ_WEB_PATH/")) == 0 ||
266 (i == 1 && path[0] ==
'/'))
267 getPathContent(CodeEditor::OTSDAQ_WEB_PATH,
268 path.substr(std::string(
"/$OTSDAQ_WEB_PATH/").size()),
270 else if((i = path.find(
"/WebPath/")) == 0 ||
271 (i == 1 && path[0] ==
'/'))
272 getPathContent(CodeEditor::OTSDAQ_WEB_PATH,
273 path.substr(std::string(
"/WebPath/").size()),
275 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
276 (i == 1 && path[0] ==
'/'))
277 getPathContent(CodeEditor::OTSDAQ_DATA_PATH,
278 path.substr(std::string(
"/$OTSDAQ_DATA/").size()),
281 getPathContent(CodeEditor::SOURCE_BASE_PATH, path, xmlOut);
287 void CodeEditor::getPathContent(
const std::string& basepath,
288 const std::string& path,
292 struct dirent* entry;
297 if(!(pDIR = opendir((basepath + path).c_str())))
299 __SS__ <<
"Path '" << path <<
"' could not be opened!" << __E__;
305 struct InsensitiveCompare
307 bool operator()(
const std::string& as,
const std::string& bs)
const
310 const char* a = as.c_str();
311 const char* b = bs.c_str();
315 while((d = (std::toupper(*a) - std::toupper(*b))) == 0 && *a)
318 __COUT_TYPE__(TLVL_DEBUG + 20) << __COUT_HDR__ << as <<
" vs " << bs <<
" = "
319 << d <<
" " << (d < 0) << __E__;
324 std::set<std::string, InsensitiveCompare> orderedDirectories;
325 std::set<std::string, InsensitiveCompare> orderedFiles;
327 std::string extension;
330 while((entry = readdir(pDIR)))
332 name = std::string(entry->d_name);
333 type = int(entry->d_type);
335 __COUT_TYPE__(TLVL_DEBUG + 2) << __COUT_HDR__ << type <<
" " << name <<
"\n"
347 if(type == 0 || type == 10)
350 DIR* pTmpDIR = opendir((basepath + path +
"/" + name).c_str());
357 __COUT_TYPE__(TLVL_DEBUG + 2)
358 << __COUT_HDR__ <<
"Unable to open path as directory: "
359 << (basepath + path +
"/" + name) << __E__;
369 __COUT_TYPE__(TLVL_DEBUG + 2)
370 << __COUT_HDR__ <<
"Directory: " << type <<
" " << name << __E__;
372 orderedDirectories.emplace(name);
376 __COUT_TYPE__(TLVL_DEBUG + 2)
377 << __COUT_HDR__ <<
"File: " << type <<
" " << name <<
"\n"
383 safeExtensionString(name.substr(name.rfind(
'.')));
384 __COUT_TYPE__(TLVL_DEBUG + 2)
385 << __COUT_HDR__ <<
"EditFile: " << type <<
" " << name << __E__;
387 orderedFiles.emplace(name);
391 __COUTT__ <<
"Invalid file extension, skipping '" << name <<
"' ..."
400 __COUT__ <<
"Found " << orderedDirectories.size() <<
" directories." << __E__;
401 __COUT__ <<
"Found " << orderedFiles.size() <<
" files." << __E__;
403 for(
const auto& name : orderedDirectories)
404 xmlOut->addTextElementToData(
"directory", name);
405 for(
const auto& name : orderedFiles)
406 xmlOut->addTextElementToData(
"file", name);
411 void CodeEditor::getFileContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
415 xmlOut->addTextElementToData(
"path", path);
418 if(extension ==
"ots")
421 if(!(extension ==
"bin" ||
422 (path.length() > 4 && path.substr(path.length() - 4) ==
"/ots")))
423 extension = safeExtensionString(extension);
424 xmlOut->addTextElementToData(
"ext", extension);
426 std::string contents;
428 if((i = path.find(
"$USER_DATA/")) == 0 ||
429 (i == 1 && path[0] ==
'/'))
430 CodeEditor::readFile(CodeEditor::USER_DATA_PATH,
431 path.substr(i + std::string(
"$USER_DATA/").size()) +
432 (extension.size() ?
"." :
"") + extension,
435 else if((i = path.find(
"$OTSDAQ_WEB_PATH/")) == 0 ||
436 (i == 1 && path[0] ==
'/'))
437 CodeEditor::readFile(CodeEditor::OTSDAQ_WEB_PATH,
438 path.substr(i + std::string(
"$OTSDAQ_WEB_PATH/").size()) +
439 (extension.size() ?
"." :
"") + extension,
442 else if((i = path.find(
"/WebPath/")) == 0 ||
443 (i == 1 && path[0] ==
'/'))
444 CodeEditor::readFile(CodeEditor::OTSDAQ_WEB_PATH,
445 path.substr(i + std::string(
"/WebPath/").size()) +
446 (extension.size() ?
"." :
"") + extension,
449 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
450 (i == 1 && path[0] ==
'/'))
451 CodeEditor::readFile(CodeEditor::OTSDAQ_DATA_PATH,
452 path.substr(std::string(
"/$OTSDAQ_DATA/").size()) +
453 (extension.size() ?
"." :
"") + extension,
457 CodeEditor::readFile(CodeEditor::SOURCE_BASE_PATH,
458 path + (extension.size() ?
"." :
"") + extension,
462 xmlOut->addTextElementToData(
"content", contents);
468 void CodeEditor::getFileGitURL(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
472 xmlOut->addTextElementToData(
"path", path);
475 if(extension ==
"ots")
478 if(!(extension ==
"bin" ||
479 (path.length() > 4 && path.substr(path.length() - 4) ==
"/ots")))
480 extension = safeExtensionString(extension);
481 xmlOut->addTextElementToData(
"ext", extension);
485 if((i = path.find(
"$USER_DATA/")) == 0 ||
486 (i == 1 && path[0] ==
'/'))
488 CodeEditor::getFileGitURL(CodeEditor::USER_DATA_PATH,
489 path.substr(i + std::string(
"$USER_DATA/").size()) +
490 (extension.size() ?
"." :
"") + extension);
491 else if((i = path.find(
"$OTSDAQ_WEB_PATH/")) == 0 ||
492 (i == 1 && path[0] ==
'/'))
493 gitPath = CodeEditor::getFileGitURL(
494 CodeEditor::OTSDAQ_WEB_PATH,
495 path.substr(i + std::string(
"$OTSDAQ_WEB_PATH/").size()) +
496 (extension.size() ?
"." :
"") + extension);
497 else if((i = path.find(
"/WebPath/")) == 0 ||
498 (i == 1 && path[0] ==
'/'))
500 CodeEditor::getFileGitURL(CodeEditor::OTSDAQ_WEB_PATH,
501 path.substr(i + std::string(
"/WebPath/").size()) +
502 (extension.size() ?
"." :
"") + extension);
503 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
504 (i == 1 && path[0] ==
'/'))
506 CodeEditor::getFileGitURL(CodeEditor::OTSDAQ_DATA_PATH,
507 path.substr(std::string(
"/$OTSDAQ_DATA/").size()) +
508 (extension.size() ?
"." :
"") + extension);
511 CodeEditor::getFileGitURL(CodeEditor::SOURCE_BASE_PATH,
512 path + (extension.size() ?
"." :
"") + extension);
514 xmlOut->addTextElementToData(
"gitPath", gitPath);
520 std::string CodeEditor::getFileGitURL(
const std::string& basepath,
521 const std::string& path)
528 std::string fullpath;
529 if(path.find(basepath) == 0)
532 fullpath = basepath +
"/" + path;
542 void CodeEditor::readFile(
const std::string& basepath,
543 const std::string& path,
544 std::string& contents,
549 std::string fullpath;
550 if(path.find(basepath) == 0)
553 fullpath = basepath +
"/" + path;
556 std::FILE* fp = std::fopen(fullpath.c_str(),
"rb");
559 __SS__ <<
"Could not open file at " << fullpath <<
". Error: " << errno <<
" - "
560 << strerror(errno) << __E__;
566 std::string binaryContents;
567 std::fseek(fp, 0, SEEK_END);
568 binaryContents.resize(std::ftell(fp));
570 std::fread(&binaryContents[0], 1, binaryContents.size(), fp);
573 for(
size_t i = 0; i < binaryContents.size(); i += 8)
578 for(
size_t j = 0; j < 8 && j < binaryContents.size() - i; ++j)
582 if(i + 8 > binaryContents.size())
583 jj = i + (binaryContents.size() - i) - j;
588 sprintf(&contents[contents.length() - 2],
"%2.2x", binaryContents[jj]);
597 std::fseek(fp, 0, SEEK_END);
598 contents.resize(std::ftell(fp));
600 std::fread(&contents[0], 1, contents.size(), fp);
606 void CodeEditor::writeFile(
const std::string& basepath,
607 const std::string& path,
608 const std::string& contents,
609 const std::string& username,
610 const unsigned long long& insertPos,
611 const std::string& insertString)
613 std::string fullpath = basepath + path;
618 long long int oldSize = 0;
622 fp = fopen(fullpath.c_str(),
"rb");
625 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
628 std::fseek(fp, 0, SEEK_END);
629 oldSize = std::ftell(fp);
634 __COUT_WARN__ <<
"Ignoring file not existing..." << __E__;
637 fp = fopen(fullpath.c_str(),
"wb");
640 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
644 if(insertPos == (
unsigned long long)-1)
645 std::fwrite(&contents[0], 1, contents.size(), fp);
648 std::fwrite(&contents[0], 1, insertPos, fp);
649 std::fwrite(&insertString[0], 1, insertString.size(), fp);
650 std::fwrite(&contents[insertPos], 1, contents.size() - insertPos, fp);
656 std::string logpath = CODE_EDITOR_DATA_PATH +
"/codeEditorChangeLog.txt";
657 fp = fopen(logpath.c_str(),
"a");
660 __SS__ <<
"Could not open change log for change tracking at " << logpath
665 "time=%lld author%s old-size=%lld new-size=%lld path=%s\n",
669 (
long long)contents.size(),
673 __COUT__ <<
"Changes logged to: " << logpath << __E__;
680 void CodeEditor::saveFileContent(cgicc::Cgicc& cgiIn,
682 const std::string& username)
686 xmlOut->addTextElementToData(
"path", path);
688 std::string basepath = CodeEditor::SOURCE_BASE_PATH;
690 std::string pathMatchPrepend =
"/";
692 if(path.length() > 1 && path[1] ==
'/')
693 pathMatchPrepend +=
'/';
699 if(path.substr(0, (pathMatchPrepend +
"$USER_DATA/").size()) ==
700 pathMatchPrepend +
"$USER_DATA/")
703 path = CodeEditor::USER_DATA_PATH +
"/" +
704 path.substr((pathMatchPrepend +
"$USER_DATA/").size());
706 else if(path.substr(0, (pathMatchPrepend +
"$OTSDAQ_WEB_PATH/").size()) ==
707 pathMatchPrepend +
"$OTSDAQ_WEB_PATH/")
710 path = CodeEditor::OTSDAQ_WEB_PATH +
"/" +
711 path.substr((pathMatchPrepend +
"$OTSDAQ_WEB_PATH/").size());
713 else if(path.substr(0, (pathMatchPrepend +
"$OTSDAQ_DATA/").size()) ==
714 pathMatchPrepend +
"$OTSDAQ_DATA/")
717 path = CodeEditor::OTSDAQ_DATA_PATH +
"/" +
718 path.substr((pathMatchPrepend +
"$OTSDAQ_DATA/").size());
724 if(!(path.length() > 4 && path.substr(path.length() - 4) ==
"/ots"))
725 extension = safeExtensionString(extension);
726 xmlOut->addTextElementToData(
"ext", extension);
732 CodeEditor::writeFile(
733 basepath, path + (extension.size() ?
"." :
"") + extension, contents, username);
740 void CodeEditor::build(cgicc::Cgicc& cgiIn,
742 const std::string& username)
744 bool clean = CgiDataUtilities::getDataAsInt(cgiIn,
"clean") ? true :
false;
746 __COUT_INFO__ <<
"Build (clean=" << clean <<
") launched by '" << username <<
"'..."
759 std::array<char, 128> buffer;
761 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
763 __THROW__(
"popen() failed!");
768 while(!feof(pipe.get()))
770 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
772 result += buffer.data();
775 i = result.find(
'\n');
776 __COUTV__(result.substr(0, i));
777 __COUT__ << result.substr(0, i);
778 result = result.substr(i + 1);
783 __COUT__ << result.substr(0, i);
789 cmd =
"source mrbSetEnv 2>&1";
791 std::array<char, 128> buffer;
793 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
795 __THROW__(
"popen() failed!");
800 while(!feof(pipe.get()))
802 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
804 result += buffer.data();
807 i = result.find(
'\n');
808 __COUTV__(result.substr(0, i));
809 __COUT__ << result.substr(0, i);
810 result = result.substr(i + 1);
815 __COUT__ << result.substr(0, i);
824 std::array<char, 128> buffer;
826 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
828 __THROW__(
"popen() failed!");
833 while(!feof(pipe.get()))
835 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
837 result += buffer.data();
840 i = result.find(
'\n');
842 __COUT__ << result.substr(0, i);
843 result = result.substr(i + 1);
848 __COUT__ << result.substr(0, i);
857 std::map<std::string , std::set<std::string> >
858 CodeEditor::getSpecialsMap(
void)
860 std::map<std::string , std::set<std::string> >
862 std::string path = std::string(__ENV__(
"OTS_SOURCE"));
866 std::vector<std::string> specialFolders({
"FEInterfaces",
867 "DataProcessorPlugins",
868 "UserTableDataFormats",
870 "TablePluginDataFormats",
872 "UserTablePluginDataFormats",
873 "SlowControlsInterfacePlugins",
874 "ControlsInterfacePlugins",
875 "FEInterfacePlugins",
877 std::vector<std::string> specialMapTypes({CodeEditor::SPECIAL_TYPE_FEInterface,
878 CodeEditor::SPECIAL_TYPE_DataProcessor,
879 CodeEditor::SPECIAL_TYPE_Table,
880 CodeEditor::SPECIAL_TYPE_Table,
881 CodeEditor::SPECIAL_TYPE_Table,
882 CodeEditor::SPECIAL_TYPE_Table,
883 CodeEditor::SPECIAL_TYPE_Table,
884 CodeEditor::SPECIAL_TYPE_SlowControls,
885 CodeEditor::SPECIAL_TYPE_SlowControls,
886 CodeEditor::SPECIAL_TYPE_FEInterface,
887 CodeEditor::SPECIAL_TYPE_Tools});
893 const std::string&,
const std::string&,
const unsigned int,
const int)>
894 localRecurse = [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
895 const std::string& path,
896 const std::string& offsetPath,
897 const unsigned int depth,
898 const int specialIndex) {
903 struct dirent* entry;
905 if(!(pDIR = opendir(path.c_str())))
907 __SS__ <<
"Plugin base path '" << path <<
"' could not be opened!"
915 int childSpecialIndex;
916 while((entry = readdir(pDIR)))
918 name = std::string(entry->d_name);
919 type = int(entry->d_type);
925 type == 4 || type == 8))
932 DIR* pTmpDIR = opendir((path +
"/" + name).c_str());
950 childSpecialIndex = -1;
951 for(
unsigned int i = 0; i < specialFolders.size(); ++i)
952 if(name == specialFolders[i])
958 childSpecialIndex = i;
964 localRecurse(path +
"/" + name,
965 offsetPath +
"/" + name,
969 else if(specialIndex >= 0)
973 if(name.find(
".h") == name.length() - 2 ||
974 name.find(
".cc") == name.length() - 3 ||
975 name.find(
".txt") == name.length() - 4 ||
976 name.find(
".sh") == name.length() - 3 ||
977 name.find(
".py") == name.length() - 3)
984 retMap[specialMapTypes[specialIndex]].emplace(offsetPath +
995 localRecurse(path,
"" , 0 , -1 );
static std::string postData(cgicc::Cgicc &cgi, const std::string &needle)
static std::string getData(cgicc::Cgicc &cgi, const std::string &needle)
static std::string setToString(const std::set< T > &setToReturn, const std::string &delimeter=", ")
setToString ~
static std::string decodeURIComponent(const std::string &data)