1 #include "otsdaq-core/CodeEditor/CodeEditor.h"
2 #include "otsdaq-core/CgiDataUtilities/CgiDataUtilities.h"
3 #include "otsdaq-core/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_ControlsInterface =
"ControlsInterface";
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_OutputData =
"OutputData";
26 const std::string CodeEditor::SOURCE_BASE_PATH = std::string(__ENV__(
"MRB_SOURCE")) +
"/";
27 const std::string CodeEditor::USER_DATA_PATH = std::string(__ENV__(
"USER_DATA")) +
"/";
28 const std::string CodeEditor::OTSDAQ_DATA_PATH =
29 std::string(__ENV__(
"OTSDAQ_DATA")) +
"/";
33 CodeEditor::CodeEditor()
34 : ALLOWED_FILE_EXTENSIONS_({
"h",
53 std::string path = CODE_EDITOR_DATA_PATH;
54 DIR* dir = opendir(path.c_str());
57 else if(-1 == mkdir(path.c_str(), 0755))
60 __SS__ <<
"Service directory creation failed: " << path << std::endl;
69 void CodeEditor::xmlRequest(
const std::string& option,
72 const std::string& username)
try
86 if(option ==
"getDirectoryContent")
88 getDirectoryContent(cgiIn, xmlOut);
90 else if(option ==
"getFileContent")
92 getFileContent(cgiIn, xmlOut);
94 else if(option ==
"saveFileContent")
96 saveFileContent(cgiIn, xmlOut, username);
98 else if(option ==
"build")
100 build(cgiIn, xmlOut, username);
102 else if(option ==
"getAllowedExtensions")
104 xmlOut->addTextElementToData(
106 StringMacros::setToString(ALLOWED_FILE_EXTENSIONS_,
","));
110 __SS__ <<
"Unrecognized request option '" << option <<
".'" << __E__;
114 catch(
const std::runtime_error& e)
116 __SS__ <<
"Error encountered while handling the Code Editor request option '"
117 << option <<
"': " << e.what() << __E__;
118 xmlOut->addTextElementToData(
"Error", ss.str());
122 __SS__ <<
"Unknown error encountered while handling the Code Editor request option '"
123 << option <<
"!'" << __E__;
124 xmlOut->addTextElementToData(
"Error", ss.str());
129 std::string CodeEditor::safePathString(
const std::string& path)
133 std::string fullpath =
"";
134 for(
unsigned int i = 0; i < path.length(); ++i)
135 if((path[i] >=
'a' && path[i] <=
'z') || (path[i] >=
'A' && path[i] <=
'Z') ||
136 path[i] >=
'_' || path[i] >=
'-' || path[i] >=
' ' || path[i] >=
'/')
139 if(!fullpath.length())
141 __SS__ <<
"Invalid path '" << fullpath <<
"' found!" << __E__;
150 std::string CodeEditor::safeExtensionString(
const std::string& extension)
154 std::string retExt =
"";
156 for(
unsigned int i = 0; i < extension.length(); ++i)
157 if((extension[i] >=
'a' && extension[i] <=
'z'))
158 retExt += extension[i];
159 else if((extension[i] >=
'A' && extension[i] <=
'Z'))
160 retExt += extension[i] + 32;
164 if(ALLOWED_FILE_EXTENSIONS_.find(
166 ALLOWED_FILE_EXTENSIONS_.end())
168 __SS__ <<
"Invalid extension '" << retExt <<
"' found!" << __E__;
176 void CodeEditor::getDirectoryContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
178 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
179 path = safePathString(CgiDataUtilities::decodeURIComponent(path));
181 __COUTV__(CodeEditor::SOURCE_BASE_PATH);
183 xmlOut->addTextElementToData(
"path", path);
185 const unsigned int numOfTypes = 7;
186 std::string specialTypeNames[] = {
"Front-End Plugins",
187 "Data Processor Plugins",
188 "Configuration Table Plugins",
189 "Controls Interface Plugins",
193 std::string specialTypes[] = {SPECIAL_TYPE_FEInterface,
194 SPECIAL_TYPE_DataProcessor,
196 SPECIAL_TYPE_ControlsInterface,
198 SPECIAL_TYPE_UserData,
199 SPECIAL_TYPE_OutputData};
201 std::string pathMatchPrepend =
"/";
203 if(path.length() > 1 && path[1] ==
'/')
204 pathMatchPrepend +=
'/';
206 for(
unsigned int i = 0; i < numOfTypes; ++i)
207 if(path == pathMatchPrepend + specialTypeNames[i])
209 __COUT__ <<
"Getting all " << specialTypeNames[i] <<
"..." << __E__;
213 if(specialTypes[i] == SPECIAL_TYPE_UserData)
215 getPathContent(
"/", CodeEditor::USER_DATA_PATH, xmlOut);
218 else if(specialTypes[i] == SPECIAL_TYPE_OutputData)
220 getPathContent(
"/", CodeEditor::OTSDAQ_DATA_PATH, xmlOut);
224 std::map<std::string ,
225 std::set<std::string> >
226 retMap = CodeEditor::getSpecialsMap();
227 if(retMap.find(specialTypes[i]) != retMap.end())
229 for(
const auto& specialTypeFile : retMap[specialTypes[i]])
231 xmlOut->addTextElementToData(
"specialFile", specialTypeFile);
236 __SS__ <<
"No files for type '" << specialTypeNames[i] <<
"' were found."
245 for(
unsigned int i = 0; i < numOfTypes; ++i)
246 xmlOut->addTextElementToData(
"special", specialTypeNames[i]);
248 std::string contents;
250 if((i = path.find(
"$USER_DATA/")) == 0 ||
251 (i == 1 && path[0] ==
'/'))
252 getPathContent(CodeEditor::USER_DATA_PATH,
253 path.substr(std::string(
"/$USER_DATA/").size()),
255 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
256 (i == 1 && path[0] ==
'/'))
257 getPathContent(CodeEditor::OTSDAQ_DATA_PATH,
258 path.substr(std::string(
"/$OTSDAQ_DATA/").size()),
261 getPathContent(CodeEditor::SOURCE_BASE_PATH, path, xmlOut);
267 void CodeEditor::getPathContent(
const std::string& basepath,
268 const std::string& path,
272 struct dirent* entry;
277 if(!(pDIR = opendir((basepath + path).c_str())))
279 __SS__ <<
"Path '" << path <<
"' could not be opened!" << __E__;
285 struct InsensitiveCompare
287 bool operator()(
const std::string& as,
const std::string& bs)
const
290 const char* a = as.c_str();
291 const char* b = bs.c_str();
295 while((d = (std::toupper(*a) - std::toupper(*b))) == 0 && *a)
303 std::set<std::string, InsensitiveCompare> orderedDirectories;
304 std::set<std::string, InsensitiveCompare> orderedFiles;
306 std::string extension;
309 while((entry = readdir(pDIR)))
311 name = std::string(entry->d_name);
312 type = int(entry->d_type);
318 type == 4 || type == 8))
326 opendir((CodeEditor::SOURCE_BASE_PATH + path +
"/" + name).c_str());
344 orderedDirectories.emplace(name);
353 safeExtensionString(name.substr(name.rfind(
'.')));
356 orderedFiles.emplace(name);
360 __COUT__ <<
"Invalid file extension, skipping '" << name <<
"' ..."
369 __COUT__ <<
"Found " << orderedDirectories.size() <<
" directories." << __E__;
370 __COUT__ <<
"Found " << orderedFiles.size() <<
" files." << __E__;
372 for(
const auto& name : orderedDirectories)
373 xmlOut->addTextElementToData(
"directory", name);
374 for(
const auto& name : orderedFiles)
375 xmlOut->addTextElementToData(
"file", name);
380 void CodeEditor::getFileContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
382 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
383 path = safePathString(CgiDataUtilities::decodeURIComponent(path));
384 xmlOut->addTextElementToData(
"path", path);
386 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
387 extension = safeExtensionString(extension);
388 xmlOut->addTextElementToData(
"ext", extension);
390 std::string contents;
392 if((i = path.find(
"$USER_DATA/")) == 0 ||
393 (i == 1 && path[0] ==
'/'))
394 CodeEditor::readFile(
395 CodeEditor::USER_DATA_PATH,
396 path.substr(i + std::string(
"$USER_DATA/").size()) +
"." + extension,
398 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
399 (i == 1 && path[0] ==
'/'))
400 CodeEditor::readFile(
401 CodeEditor::OTSDAQ_DATA_PATH,
402 path.substr(std::string(
"/$OTSDAQ_DATA/").size()) +
"." + extension,
405 CodeEditor::readFile(
406 CodeEditor::SOURCE_BASE_PATH, path +
"." + extension, contents);
408 xmlOut->addTextElementToData(
"content", contents);
414 void CodeEditor::readFile(
const std::string& basepath,
415 const std::string& path,
416 std::string& contents)
418 std::string fullpath = basepath +
"/" + path;
421 std::FILE* fp = std::fopen(fullpath.c_str(),
"rb");
424 __SS__ <<
"Could not open file at " << path << __E__;
428 std::fseek(fp, 0, SEEK_END);
429 contents.resize(std::ftell(fp));
431 std::fread(&contents[0], 1, contents.size(), fp);
437 void CodeEditor::writeFile(
const std::string& basepath,
438 const std::string& path,
439 const std::string& contents,
440 const std::string& username,
441 const unsigned long long& insertPos,
442 const std::string& insertString)
444 std::string fullpath = basepath + path;
450 fp = fopen(fullpath.c_str(),
"rb");
453 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
456 std::fseek(fp, 0, SEEK_END);
457 long long int oldSize = std::ftell(fp);
460 fp = fopen(fullpath.c_str(),
"wb");
463 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
467 if(insertPos == (
unsigned long long)-1)
468 std::fwrite(&contents[0], 1, contents.size(), fp);
471 std::fwrite(&contents[0], 1, insertPos, fp);
472 std::fwrite(&insertString[0], 1, insertString.size(), fp);
473 std::fwrite(&contents[insertPos], 1, contents.size() - insertPos, fp);
479 std::string logpath = CODE_EDITOR_DATA_PATH +
"/codeEditorChangeLog.txt";
480 fp = fopen(logpath.c_str(),
"a");
483 __SS__ <<
"Could not open change log for change tracking at " << logpath
488 "time=%lld author%s old-size=%lld new-size=%lld path=%s\n",
492 (
long long)contents.size(),
496 __COUT__ <<
"Changes logged to: " << logpath << __E__;
503 void CodeEditor::saveFileContent(cgicc::Cgicc& cgiIn,
505 const std::string& username)
507 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
508 path = safePathString(CgiDataUtilities::decodeURIComponent(path));
509 xmlOut->addTextElementToData(
"path", path);
511 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
512 extension = safeExtensionString(extension);
513 xmlOut->addTextElementToData(
"ext", extension);
515 std::string contents = CgiDataUtilities::postData(cgiIn,
"content");
517 contents = StringMacros::decodeURIComponent(contents);
519 CodeEditor::writeFile(
520 CodeEditor::SOURCE_BASE_PATH, path +
"." + extension, contents, username);
527 void CodeEditor::build(cgicc::Cgicc& cgiIn,
529 const std::string& username)
531 bool clean = CgiDataUtilities::getDataAsInt(cgiIn,
"clean") ?
true :
false;
533 __MCOUT_INFO__(
"Build (clean=" << clean <<
") launched by '" << username <<
"'..."
547 std::array<char, 128> buffer;
549 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
551 __THROW__(
"popen() failed!");
556 while(!feof(pipe.get()))
558 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
560 result += buffer.data();
563 i = result.find(
'\n');
564 __COUTV__(result.substr(0, i));
565 __MOUT__ << result.substr(0, i);
566 result = result.substr(i + 1);
571 __MOUT__ << result.substr(0, i);
577 cmd =
"source mrbSetEnv 2>&1";
579 std::array<char, 128> buffer;
581 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
583 __THROW__(
"popen() failed!");
588 while(!feof(pipe.get()))
590 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
592 result += buffer.data();
595 i = result.find(
'\n');
596 __COUTV__(result.substr(0, i));
597 __MOUT__ << result.substr(0, i);
598 result = result.substr(i + 1);
603 __MOUT__ << result.substr(0, i);
612 std::array<char, 128> buffer;
614 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
616 __THROW__(
"popen() failed!");
621 while(!feof(pipe.get()))
623 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
625 result += buffer.data();
628 i = result.find(
'\n');
630 __MOUT__ << result.substr(0, i);
631 result = result.substr(i + 1);
636 __MOUT__ << result.substr(0, i);
645 std::map<std::string , std::set<std::string> >
646 CodeEditor::getSpecialsMap(
void)
648 std::map<std::string , std::set<std::string> >
650 std::string path = std::string(__ENV__(
"MRB_SOURCE"));
654 const unsigned int numOfSpecials = 7;
655 std::string specialFolders[] = {
"FEInterfaces",
656 "DataProcessorPlugins",
657 "UserTableDataFormats",
658 "TablePluginDataFormats",
659 "SlowControlsInterfacePlugins",
660 "ControlsInterfacePlugins",
661 "FEInterfacePlugins",
663 std::string specialMapTypes[] = {CodeEditor::SPECIAL_TYPE_FEInterface,
664 CodeEditor::SPECIAL_TYPE_DataProcessor,
665 CodeEditor::SPECIAL_TYPE_Table,
666 CodeEditor::SPECIAL_TYPE_Table,
667 CodeEditor::SPECIAL_TYPE_ControlsInterface,
668 CodeEditor::SPECIAL_TYPE_ControlsInterface,
669 CodeEditor::SPECIAL_TYPE_FEInterface,
670 CodeEditor::SPECIAL_TYPE_Tools};
676 const std::string&,
const std::string&,
const unsigned int,
const int)>
677 localRecurse = [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
678 const std::string& path,
679 const std::string& offsetPath,
680 const unsigned int depth,
681 const int specialIndex) {
687 struct dirent* entry;
689 if(!(pDIR = opendir(path.c_str())))
691 __SS__ <<
"Plugin base path '" << path <<
"' could not be opened!"
699 int childSpecialIndex;
700 while((entry = readdir(pDIR)))
702 name = std::string(entry->d_name);
703 type = int(entry->d_type);
709 type == 4 || type == 8))
716 DIR* pTmpDIR = opendir((path +
"/" + name).c_str());
734 childSpecialIndex = -1;
735 for(
unsigned int i = 0; i < numOfSpecials; ++i)
736 if(name == specialFolders[i])
742 childSpecialIndex = i;
748 localRecurse(path +
"/" + name,
749 offsetPath +
"/" + name,
753 else if(specialIndex >= 0)
757 if(name.find(
".h") == name.length() - 2 ||
758 name.find(
".cc") == name.length() - 3 ||
759 name.find(
".txt") == name.length() - 4 ||
760 name.find(
".sh") == name.length() - 3 ||
761 name.find(
".py") == name.length() - 3)
768 retMap[specialMapTypes[specialIndex]].emplace(offsetPath +
780 localRecurse(path,
"" , 0 , -1 );