tdaq-develop-2025-02-12
CodeEditor.cc
1 #include "otsdaq/CodeEditor/CodeEditor.h"
2 #include "otsdaq/CgiDataUtilities/CgiDataUtilities.h"
3 #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
4 
5 #include <dirent.h> //DIR and dirent
6 #include <sys/stat.h> //for mkdir
7 #include <cctype> //for std::toupper
8 #include <thread> //for std::thread
9 
10 using namespace ots;
11 
12 #define CODE_EDITOR_DATA_PATH \
13  std::string(__ENV__("SERVICE_DATA_PATH")) + "/" + "CodeEditorData"
14 
15 #undef __MF_SUBJECT__
16 #define __MF_SUBJECT__ "CodeEditor"
17 
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";
26 
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")) + "/";
33 
34 //==============================================================================
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"})
40 {
41  std::string path = CODE_EDITOR_DATA_PATH;
42  DIR* dir = opendir(path.c_str());
43  if(dir)
44  closedir(dir);
45  else if(-1 == mkdir(path.c_str(), 0755))
46  {
47  // lets create the service folder (for first time)
48  __SS__ << "Service directory creation failed: " << path << std::endl;
49  __SS_THROW__;
50  }
51 
52 } // end CodeEditor()
53 
54 //==============================================================================
57 void CodeEditor::xmlRequest(const std::string& option,
58  bool readOnlyMode,
59  cgicc::Cgicc& cgiIn,
60  HttpXmlDocument* xmlOut,
61  const std::string& username)
62 try
63 {
64  __COUTV__(option);
65 
66  // request options:
67  //
68  // getDirectoryContent
69  // getFileContent
70  // saveFileContent
71  // cleanBuild
72  // incrementalBuild
73  // getAllowedExtensions
74  // getFileGitURL
75  //
76 
77  if(option == "getDirectoryContent")
78  {
79  getDirectoryContent(cgiIn, xmlOut);
80  }
81  else if(option == "getFileContent")
82  {
83  getFileContent(cgiIn, xmlOut);
84  }
85  else if(!readOnlyMode && option == "saveFileContent")
86  {
87  saveFileContent(cgiIn, xmlOut, username);
88  }
89  else if(!readOnlyMode && option == "build")
90  {
91  build(cgiIn, xmlOut, username);
92  }
93  else if(option == "getAllowedExtensions")
94  {
95  xmlOut->addTextElementToData(
96  "AllowedExtensions",
97  StringMacros::setToString(ALLOWED_FILE_EXTENSIONS_, ","));
98  }
99  else if(option == "getFileGitURL")
100  {
101  getFileGitURL(cgiIn, xmlOut);
102  }
103  else
104  {
105  __SS__ << "Unrecognized request option '" << option << ".'" << __E__;
106  __SS_THROW__;
107  }
108 }
109 catch(const std::runtime_error& e)
110 {
111  __SS__ << "Error encountered while handling the Code Editor request option '"
112  << option << "': " << e.what() << __E__;
113  xmlOut->addTextElementToData("Error", ss.str());
114 }
115 catch(...)
116 {
117  __SS__ << "Unknown error encountered while handling the Code Editor request option '"
118  << option << "!'" << __E__;
119  xmlOut->addTextElementToData("Error", ss.str());
120 } // end xmlRequest()
121 
122 //==============================================================================
124 std::string CodeEditor::safePathString(const std::string& path)
125 {
126  __COUTVS__(20, path);
127  // remove all non ascii and non /, -, _,, space
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] >= '/')
132  fullpath += path[i];
133  __COUTVS__(20, fullpath);
134  if(!fullpath.length())
135  {
136  __SS__ << "Invalid path '" << fullpath << "' found!" << __E__;
137  __SS_THROW__;
138  }
139  return fullpath;
140 } // end safePathString()
141 
142 //==============================================================================
145 std::string CodeEditor::safeExtensionString(const std::string& extension)
146 {
147  //__COUTV__(extension);
148 
149  std::string retExt = "";
150  // remove all non ascii
151  // skip first potential '.' (depends on parent calling function if extension includes
152  //'.')
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; // make lowercase
158  else if(i > 0 || extension[i] != '.')
159  {
160  __SS__ << "Invalid extension non-alpha " << int(extension[i]) << " found!"
161  << __E__;
162  __SS_ONLY_THROW__;
163  }
164 
165  //__COUTV__(retExt);
166 
167  if(ALLOWED_FILE_EXTENSIONS_.find(
168  retExt) == // should match get directory content restrictions
169  ALLOWED_FILE_EXTENSIONS_.end())
170  {
171  __SS__ << "Invalid extension '" << retExt << "' found!" << __E__;
172  __SS_ONLY_THROW__;
173  }
174  return retExt;
175 } // end safeExtensionString()
176 
177 //==============================================================================
179 void CodeEditor::getDirectoryContent(cgicc::Cgicc& cgiIn, HttpXmlDocument* xmlOut)
180 {
181  std::string path = CgiDataUtilities::getData(cgiIn, "path");
182  path = safePathString(StringMacros::decodeURIComponent(path));
183  __COUTV__(path);
184  __COUTV__(CodeEditor::SOURCE_BASE_PATH);
185 
186  xmlOut->addTextElementToData("path", path);
187 
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",
193  "Tools and Scripts",
194  "$USER_DATA",
195  "$OTSDAQ_WEB_PATH",
196  "$OTSDAQ_DATA"};
197  std::string specialTypes[] = {SPECIAL_TYPE_FEInterface,
198  SPECIAL_TYPE_DataProcessor,
199  SPECIAL_TYPE_Table,
200  SPECIAL_TYPE_SlowControls,
201  SPECIAL_TYPE_Tools,
202  SPECIAL_TYPE_UserData,
203  SPECIAL_TYPE_WebPath,
204  SPECIAL_TYPE_OutputData};
205 
206  std::string pathMatchPrepend = "/"; // some requests come in with leading "/" and
207  // "//"
208  if(path.length() > 1 && path[1] == '/')
209  pathMatchPrepend += '/';
210 
211  for(unsigned int i = 0; i < numOfTypes; ++i)
212  if(path == pathMatchPrepend + specialTypeNames[i])
213  {
214  __COUT__ << "Getting all " << specialTypeNames[i] << "..." << __E__;
215 
216  // handle UserData and OutputData differently
217  // since there is only one path to check
218  if(specialTypes[i] == SPECIAL_TYPE_UserData)
219  {
220  getPathContent("/", CodeEditor::USER_DATA_PATH, xmlOut);
221  return;
222  }
223  else if(specialTypes[i] == SPECIAL_TYPE_WebPath)
224  {
225  getPathContent("/", CodeEditor::OTSDAQ_WEB_PATH, xmlOut);
226  return;
227  }
228  else if(specialTypes[i] == SPECIAL_TYPE_OutputData)
229  {
230  getPathContent("/", CodeEditor::OTSDAQ_DATA_PATH, xmlOut);
231  return;
232  }
233 
234  std::map<std::string /*special type*/,
235  std::set<std::string> /*special file paths*/>
236  retMap = CodeEditor::getSpecialsMap();
237  if(retMap.find(specialTypes[i]) != retMap.end())
238  {
239  for(const auto& specialTypeFile : retMap[specialTypes[i]])
240  {
241  xmlOut->addTextElementToData("specialFile", specialTypeFile);
242  }
243  }
244  else
245  {
246  __SS__ << "No files for type '" << specialTypeNames[i] << "' were found."
247  << __E__;
248  __SS_THROW__;
249  }
250  return;
251  }
252 
253  // if root directory, add special directory for types
254  if(path == "/")
255  for(unsigned int i = 0; i < numOfTypes; ++i)
256  xmlOut->addTextElementToData("special", specialTypeNames[i]);
257 
258  std::string contents;
259  size_t i;
260  if((i = path.find("$USER_DATA/")) == 0 ||
261  (i == 1 && path[0] == '/')) // if leading / or without
262  getPathContent(CodeEditor::USER_DATA_PATH,
263  path.substr(std::string("/$USER_DATA/").size()),
264  xmlOut);
265  else if((i = path.find("$OTSDAQ_WEB_PATH/")) == 0 ||
266  (i == 1 && path[0] == '/')) // if leading / or without
267  getPathContent(CodeEditor::OTSDAQ_WEB_PATH,
268  path.substr(std::string("/$OTSDAQ_WEB_PATH/").size()),
269  xmlOut);
270  else if((i = path.find("/WebPath/")) == 0 ||
271  (i == 1 && path[0] == '/')) // if leading / or without
272  getPathContent(CodeEditor::OTSDAQ_WEB_PATH,
273  path.substr(std::string("/WebPath/").size()),
274  xmlOut);
275  else if((i = path.find("$OTSDAQ_DATA/")) == 0 ||
276  (i == 1 && path[0] == '/')) // if leading / or without
277  getPathContent(CodeEditor::OTSDAQ_DATA_PATH,
278  path.substr(std::string("/$OTSDAQ_DATA/").size()),
279  xmlOut);
280  else
281  getPathContent(CodeEditor::SOURCE_BASE_PATH, path, xmlOut);
282 
283 } // end getDirectoryContent()
284 
285 //==============================================================================
287 void CodeEditor::getPathContent(const std::string& basepath,
288  const std::string& path,
289  HttpXmlDocument* xmlOut)
290 {
291  DIR* pDIR;
292  struct dirent* entry;
293  bool isDir;
294  std::string name;
295  int type;
296 
297  if(!(pDIR = opendir((basepath + path).c_str())))
298  {
299  __SS__ << "Path '" << path << "' could not be opened!" << __E__;
300  __SS_THROW__;
301  }
302 
303  // add to set for alpha ordering
304  // with insensitive compare
305  struct InsensitiveCompare
306  {
307  bool operator()(const std::string& as, const std::string& bs) const
308  {
309  // return true, if as < bs
310  const char* a = as.c_str();
311  const char* b = bs.c_str();
312  int d;
313 
314  // compare each character, until difference, or end of string
315  while((d = (std::toupper(*a) - std::toupper(*b))) == 0 && *a)
316  ++a, ++b;
317 
318  __COUT_TYPE__(TLVL_DEBUG + 20) << __COUT_HDR__ << as << " vs " << bs << " = "
319  << d << " " << (d < 0) << __E__;
320 
321  return d < 0;
322  }
323  };
324  std::set<std::string, InsensitiveCompare> orderedDirectories;
325  std::set<std::string, InsensitiveCompare> orderedFiles;
326 
327  std::string extension;
328 
329  // else directory good, get all folders, .h, .cc, .txt files
330  while((entry = readdir(pDIR)))
331  {
332  name = std::string(entry->d_name);
333  type = int(entry->d_type);
334 
335  __COUT_TYPE__(TLVL_DEBUG + 2) << __COUT_HDR__ << type << " " << name << "\n"
336  << std::endl;
337 
338  if(name[0] != '.' &&
339  (type == 0 || // 0 == UNKNOWN (which can happen - seen in SL7 VM)
340  type == 4 || // directory type
341  type == 8 || // file type
342  type == 10 // 10 == link (could be directory or file, treat as unknown)
343  ))
344  {
345  isDir = false;
346 
347  if(type == 0 || type == 10)
348  {
349  // unknown type .. determine if directory
350  DIR* pTmpDIR = opendir((basepath + path + "/" + name).c_str());
351  if(pTmpDIR)
352  {
353  isDir = true;
354  closedir(pTmpDIR);
355  }
356  else //assume file
357  __COUT_TYPE__(TLVL_DEBUG + 2)
358  << __COUT_HDR__ << "Unable to open path as directory: "
359  << (basepath + path + "/" + name) << __E__;
360  }
361 
362  if(type == 4)
363  isDir = true; // flag directory types
364 
365  // handle directories
366 
367  if(isDir)
368  {
369  __COUT_TYPE__(TLVL_DEBUG + 2)
370  << __COUT_HDR__ << "Directory: " << type << " " << name << __E__;
371 
372  orderedDirectories.emplace(name);
373  }
374  else // type 8 or 0 is file
375  {
376  __COUT_TYPE__(TLVL_DEBUG + 2)
377  << __COUT_HDR__ << "File: " << type << " " << name << "\n"
378  << std::endl;
379 
380  try
381  {
382  if(name != "ots")
383  safeExtensionString(name.substr(name.rfind('.')));
384  __COUT_TYPE__(TLVL_DEBUG + 2)
385  << __COUT_HDR__ << "EditFile: " << type << " " << name << __E__;
386 
387  orderedFiles.emplace(name);
388  }
389  catch(...)
390  {
391  __COUTT__ << "Invalid file extension, skipping '" << name << "' ..."
392  << __E__;
393  }
394  }
395  }
396  } // end directory traversal
397 
398  closedir(pDIR);
399 
400  __COUT__ << "Found " << orderedDirectories.size() << " directories." << __E__;
401  __COUT__ << "Found " << orderedFiles.size() << " files." << __E__;
402 
403  for(const auto& name : orderedDirectories)
404  xmlOut->addTextElementToData("directory", name);
405  for(const auto& name : orderedFiles)
406  xmlOut->addTextElementToData("file", name);
407 } // end getPathContent()
408 
409 //==============================================================================
411 void CodeEditor::getFileContent(cgicc::Cgicc& cgiIn, HttpXmlDocument* xmlOut)
412 {
413  std::string path = CgiDataUtilities::getData(cgiIn, "path");
414  path = safePathString(StringMacros::decodeURIComponent(path));
415  xmlOut->addTextElementToData("path", path);
416 
417  std::string extension = CgiDataUtilities::getData(cgiIn, "ext");
418  if(extension == "ots")
419  extension = ""; // special handling of ots extension (to get bash script
420  // properly)
421  if(!(extension == "bin" || //allow bin for read-only
422  (path.length() > 4 && path.substr(path.length() - 4) == "/ots")))
423  extension = safeExtensionString(extension);
424  xmlOut->addTextElementToData("ext", extension);
425 
426  std::string contents;
427  size_t i;
428  if((i = path.find("$USER_DATA/")) == 0 ||
429  (i == 1 && path[0] == '/')) // if leading / or without
430  CodeEditor::readFile(CodeEditor::USER_DATA_PATH,
431  path.substr(i + std::string("$USER_DATA/").size()) +
432  (extension.size() ? "." : "") + extension,
433  contents,
434  extension == "bin");
435  else if((i = path.find("$OTSDAQ_WEB_PATH/")) == 0 ||
436  (i == 1 && path[0] == '/')) // if leading / or without
437  CodeEditor::readFile(CodeEditor::OTSDAQ_WEB_PATH,
438  path.substr(i + std::string("$OTSDAQ_WEB_PATH/").size()) +
439  (extension.size() ? "." : "") + extension,
440  contents,
441  extension == "bin");
442  else if((i = path.find("/WebPath/")) == 0 ||
443  (i == 1 && path[0] == '/')) // if leading / or without
444  CodeEditor::readFile(CodeEditor::OTSDAQ_WEB_PATH,
445  path.substr(i + std::string("/WebPath/").size()) +
446  (extension.size() ? "." : "") + extension,
447  contents,
448  extension == "bin");
449  else if((i = path.find("$OTSDAQ_DATA/")) == 0 ||
450  (i == 1 && path[0] == '/')) // if leading / or without
451  CodeEditor::readFile(CodeEditor::OTSDAQ_DATA_PATH,
452  path.substr(std::string("/$OTSDAQ_DATA/").size()) +
453  (extension.size() ? "." : "") + extension,
454  contents,
455  extension == "bin");
456  else
457  CodeEditor::readFile(CodeEditor::SOURCE_BASE_PATH,
458  path + (extension.size() ? "." : "") + extension,
459  contents,
460  extension == "bin");
461 
462  xmlOut->addTextElementToData("content", contents);
463 
464 } // end getFileContent()
465 
466 //==============================================================================
468 void CodeEditor::getFileGitURL(cgicc::Cgicc& cgiIn, HttpXmlDocument* xmlOut)
469 {
470  std::string path = CgiDataUtilities::getData(cgiIn, "path");
471  path = safePathString(StringMacros::decodeURIComponent(path));
472  xmlOut->addTextElementToData("path", path);
473 
474  std::string extension = CgiDataUtilities::getData(cgiIn, "ext");
475  if(extension == "ots")
476  extension = ""; // special handling of ots extension (to get bash script
477  // properly)
478  if(!(extension == "bin" || //allow bin for read-only
479  (path.length() > 4 && path.substr(path.length() - 4) == "/ots")))
480  extension = safeExtensionString(extension);
481  xmlOut->addTextElementToData("ext", extension);
482 
483  std::string gitPath;
484  size_t i;
485  if((i = path.find("$USER_DATA/")) == 0 ||
486  (i == 1 && path[0] == '/')) // if leading / or without
487  gitPath =
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] == '/')) // if leading / or without
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] == '/')) // if leading / or without
499  gitPath =
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] == '/')) // if leading / or without
505  gitPath =
506  CodeEditor::getFileGitURL(CodeEditor::OTSDAQ_DATA_PATH,
507  path.substr(std::string("/$OTSDAQ_DATA/").size()) +
508  (extension.size() ? "." : "") + extension);
509  else
510  gitPath =
511  CodeEditor::getFileGitURL(CodeEditor::SOURCE_BASE_PATH,
512  path + (extension.size() ? "." : "") + extension);
513 
514  xmlOut->addTextElementToData("gitPath", gitPath);
515 
516 } // end getFileGitURL()
517 
518 //==============================================================================
520 std::string CodeEditor::getFileGitURL(const std::string& basepath,
521  const std::string& path)
522 {
523  //TODO! October 2024 by rrivera
524  //TODO! request with linux exec to spack environment
525 
526  __COUTV__(basepath);
527  __COUTV__(path);
528  std::string fullpath;
529  if(path.find(basepath) == 0) //check if path is already complete
530  fullpath = path;
531  else
532  fullpath = basepath + "/" + path;
533  __COUTV__(fullpath);
534 
535  //look for environments
536 
537  return "unknown";
538 } // end getFileGitURL()
539 
540 //==============================================================================
542 void CodeEditor::readFile(const std::string& basepath,
543  const std::string& path,
544  std::string& contents,
545  bool binaryRead /* = false*/)
546 {
547  __COUTV__(basepath);
548  __COUTV__(path);
549  std::string fullpath;
550  if(path.find(basepath) == 0) //check if path is already complete
551  fullpath = path;
552  else
553  fullpath = basepath + "/" + path;
554  __COUTV__(fullpath);
555 
556  std::FILE* fp = std::fopen(fullpath.c_str(), "rb");
557  if(!fp)
558  {
559  __SS__ << "Could not open file at " << fullpath << ". Error: " << errno << " - "
560  << strerror(errno) << __E__;
561  __SS_THROW__;
562  }
563 
564  if(binaryRead)
565  {
566  std::string binaryContents;
567  std::fseek(fp, 0, SEEK_END);
568  binaryContents.resize(std::ftell(fp));
569  std::rewind(fp);
570  std::fread(&binaryContents[0], 1, binaryContents.size(), fp);
571  std::fclose(fp);
572 
573  for(size_t i = 0; i < binaryContents.size(); i += 8)
574  {
575  contents +=
576  "0x"; //no need to send line number (it is in web display already)
577 
578  for(size_t j = 0; j < 8 && j < binaryContents.size() - i; ++j)
579  {
580  size_t jj =
581  i + 7 - j; //go in reverse order so least significant is at right
582  if(i + 8 > binaryContents.size()) //if not a multiple of 8 at end
583  jj = i + (binaryContents.size() - i) - j;
584 
585  if(j == 4)
586  contents += ' '; //one space at 32b groups
587  contents += " "; //declare the 2 bytes of space, then fill with sprintf
588  sprintf(&contents[contents.length() - 2], "%2.2x", binaryContents[jj]);
589  } //end binary hex char loop
590  contents += '\n';
591  } //end binary line loop
592  contents += '\n';
593 
594  return;
595  }
596  //else standard text read
597  std::fseek(fp, 0, SEEK_END);
598  contents.resize(std::ftell(fp));
599  std::rewind(fp);
600  std::fread(&contents[0], 1, contents.size(), fp);
601  std::fclose(fp);
602 } // end readFile
603 
604 //==============================================================================
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)
612 {
613  std::string fullpath = basepath + path;
614  __COUTV__(fullpath);
615 
616  FILE* fp;
617 
618  long long int oldSize = 0;
619  try
620  {
621  // get old file size
622  fp = fopen(fullpath.c_str(), "rb");
623  if(!fp)
624  {
625  __SS__ << "Could not open file for saving at " << fullpath << __E__;
626  __SS_THROW__;
627  }
628  std::fseek(fp, 0, SEEK_END);
629  oldSize = std::ftell(fp);
630  fclose(fp);
631  }
632  catch(...)
633  {
634  __COUT_WARN__ << "Ignoring file not existing..." << __E__;
635  }
636 
637  fp = fopen(fullpath.c_str(), "wb");
638  if(!fp)
639  {
640  __SS__ << "Could not open file for saving at " << fullpath << __E__;
641  __SS_THROW__;
642  }
643 
644  if(insertPos == (unsigned long long)-1)
645  std::fwrite(&contents[0], 1, contents.size(), fp);
646  else // do insert
647  {
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);
651  }
652  std::fclose(fp);
653 
654  // log changes
655  {
656  std::string logpath = CODE_EDITOR_DATA_PATH + "/codeEditorChangeLog.txt";
657  fp = fopen(logpath.c_str(), "a");
658  if(!fp)
659  {
660  __SS__ << "Could not open change log for change tracking at " << logpath
661  << __E__;
662  __SS_THROW__;
663  }
664  fprintf(fp,
665  "time=%lld author%s old-size=%lld new-size=%lld path=%s\n",
666  (long long)time(0),
667  username.c_str(),
668  oldSize,
669  (long long)contents.size(),
670  fullpath.c_str());
671 
672  fclose(fp);
673  __COUT__ << "Changes logged to: " << logpath << __E__;
674  } // end log changes
675 
676 } // end writeFile
677 
678 //==============================================================================
680 void CodeEditor::saveFileContent(cgicc::Cgicc& cgiIn,
681  HttpXmlDocument* xmlOut,
682  const std::string& username)
683 {
684  std::string path = CgiDataUtilities::getData(cgiIn, "path");
685  path = safePathString(StringMacros::decodeURIComponent(path));
686  xmlOut->addTextElementToData("path", path);
687 
688  std::string basepath = CodeEditor::SOURCE_BASE_PATH;
689 
690  std::string pathMatchPrepend = "/"; // some requests come in with leading "/" and
691  // "//"
692  if(path.length() > 1 && path[1] == '/')
693  pathMatchPrepend += '/';
694 
695  //__COUTV__(path);
696  //__COUTV__(pathMatchPrepend);
697 
698  // fix path for special environment variables
699  if(path.substr(0, (pathMatchPrepend + "$USER_DATA/").size()) ==
700  pathMatchPrepend + "$USER_DATA/")
701  {
702  basepath = "/";
703  path = CodeEditor::USER_DATA_PATH + "/" +
704  path.substr((pathMatchPrepend + "$USER_DATA/").size());
705  }
706  else if(path.substr(0, (pathMatchPrepend + "$OTSDAQ_WEB_PATH/").size()) ==
707  pathMatchPrepend + "$OTSDAQ_WEB_PATH/")
708  {
709  basepath = "/";
710  path = CodeEditor::OTSDAQ_WEB_PATH + "/" +
711  path.substr((pathMatchPrepend + "$OTSDAQ_WEB_PATH/").size());
712  }
713  else if(path.substr(0, (pathMatchPrepend + "$OTSDAQ_DATA/").size()) ==
714  pathMatchPrepend + "$OTSDAQ_DATA/")
715  {
716  basepath = "/";
717  path = CodeEditor::OTSDAQ_DATA_PATH + "/" +
718  path.substr((pathMatchPrepend + "$OTSDAQ_DATA/").size());
719  }
720  //__COUTV__(path);
721  //__COUTV__(basepath);
722 
723  std::string extension = CgiDataUtilities::getData(cgiIn, "ext");
724  if(!(path.length() > 4 && path.substr(path.length() - 4) == "/ots"))
725  extension = safeExtensionString(extension);
726  xmlOut->addTextElementToData("ext", extension);
727 
728  std::string contents = CgiDataUtilities::postData(cgiIn, "content");
729  //__COUTV__(contents);
730  contents = StringMacros::decodeURIComponent(contents);
731 
732  CodeEditor::writeFile(
733  basepath, path + (extension.size() ? "." : "") + extension, contents, username);
734 
735 } // end saveFileContent
736 
737 //==============================================================================
740 void CodeEditor::build(cgicc::Cgicc& cgiIn,
741  HttpXmlDocument* /*xmlOut*/,
742  const std::string& username)
743 {
744  bool clean = CgiDataUtilities::getDataAsInt(cgiIn, "clean") ? true : false;
745 
746  __COUT_INFO__ << "Build (clean=" << clean << ") launched by '" << username << "'..."
747  << __E__;
748 
749  // launch as thread so it does not lock up rest of code
750  std::thread(
751  [](bool clean) {
752  std::string cmd;
753  if(clean)
754  {
755  // clean
756  {
757  cmd = "mrb z 2>&1";
758 
759  std::array<char, 128> buffer;
760  std::string result;
761  std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
762  if(!pipe)
763  __THROW__("popen() failed!");
764 
765  size_t i = 0;
766  // size_t j;
767 
768  while(!feof(pipe.get()))
769  {
770  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
771  {
772  result += buffer.data();
773 
774  // each time there is a new line print out
775  i = result.find('\n');
776  __COUTV__(result.substr(0, i));
777  __COUT__ << result.substr(0, i);
778  result = result.substr(i + 1); // discard before new line
779  }
780  }
781 
782  __COUTV__(result);
783  __COUT__ << result.substr(0, i);
784  }
785 
786  sleep(1);
787  // mrbsetenv
788  {
789  cmd = "source mrbSetEnv 2>&1";
790 
791  std::array<char, 128> buffer;
792  std::string result;
793  std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
794  if(!pipe)
795  __THROW__("popen() failed!");
796 
797  size_t i = 0;
798  // size_t j;
799 
800  while(!feof(pipe.get()))
801  {
802  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
803  {
804  result += buffer.data();
805 
806  // each time there is a new line print out
807  i = result.find('\n');
808  __COUTV__(result.substr(0, i));
809  __COUT__ << result.substr(0, i);
810  result = result.substr(i + 1); // discard before new line
811  }
812  }
813 
814  __COUTV__(result);
815  __COUT__ << result.substr(0, i);
816  }
817  sleep(1);
818  }
819 
820  // build
821  {
822  cmd = "mrb b 2>&1";
823 
824  std::array<char, 128> buffer;
825  std::string result;
826  std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
827  if(!pipe)
828  __THROW__("popen() failed!");
829 
830  size_t i = 0;
831  // size_t j;
832 
833  while(!feof(pipe.get()))
834  {
835  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
836  {
837  result += buffer.data();
838 
839  // each time there is a new line print out
840  i = result.find('\n');
841  //__COUTV__(result.substr(0,i));
842  __COUT__ << result.substr(0, i);
843  result = result.substr(i + 1); // discard before new line
844  }
845  }
846 
847  //__COUTV__(result);
848  __COUT__ << result.substr(0, i);
849  }
850  },
851  clean)
852  .detach();
853 
854 } // end build()
855 
856 //==============================================================================
857 std::map<std::string /*special type*/, std::set<std::string> /*special file paths*/>
858 CodeEditor::getSpecialsMap(void)
859 {
860  std::map<std::string /*special type*/, std::set<std::string> /*special file paths*/>
861  retMap;
862  std::string path = std::string(__ENV__("OTS_SOURCE"));
863 
864  __COUTV__(path);
865 
866  std::vector<std::string> specialFolders({"FEInterfaces",
867  "DataProcessorPlugins",
868  "UserTableDataFormats",
869  "TablePlugins",
870  "TablePluginDataFormats",
871  "UserTablePlugins",
872  "UserTablePluginDataFormats",
873  "SlowControlsInterfacePlugins",
874  "ControlsInterfacePlugins",
875  "FEInterfacePlugins",
876  "tools"});
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});
888 
889  // Note: can not do lambda recursive function if using auto to declare the function,
890  // and must capture reference to the function. Also, must capture specialFolders
891  // reference for use internally (const values already are captured).
892  std::function<void(
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) {
899  //__COUTV__(path);
900  //__COUTV__(depth);
901 
902  DIR* pDIR;
903  struct dirent* entry;
904  bool isDir;
905  if(!(pDIR = opendir(path.c_str())))
906  {
907  __SS__ << "Plugin base path '" << path << "' could not be opened!"
908  << __E__;
909  __SS_THROW__;
910  }
911 
912  // else directory good, get all folders and look for special folders
913  std::string name;
914  int type;
915  int childSpecialIndex;
916  while((entry = readdir(pDIR)))
917  {
918  name = std::string(entry->d_name);
919  type = int(entry->d_type);
920 
921  //__COUT__ << type << " " << name << "\n" << std::endl;
922 
923  if(name[0] != '.' &&
924  (type == 0 || // 0 == UNKNOWN (which can happen - seen in SL7 VM)
925  type == 4 || type == 8))
926  {
927  isDir = false;
928 
929  if(type == 0)
930  {
931  // unknown type .. determine if directory
932  DIR* pTmpDIR = opendir((path + "/" + name).c_str());
933  if(pTmpDIR)
934  {
935  isDir = true;
936  closedir(pTmpDIR);
937  }
938  // else //assume file
939  }
940 
941  if(type == 4)
942  isDir = true; // flag directory types
943 
944  // handle directories
945 
946  if(isDir)
947  {
948  //__COUT__ << "Directory: " << type << " " << name << __E__;
949 
950  childSpecialIndex = -1;
951  for(unsigned int i = 0; i < specialFolders.size(); ++i)
952  if(name == specialFolders[i])
953  {
954  //__COUT__ << "Found special folder '" <<
955  // specialFolders[i] <<
956  // "' at path " << path << __E__;
957 
958  childSpecialIndex = i;
959  break;
960  }
961 
962  // recurse deeper!
963  if(depth < 4) // limit search depth
964  localRecurse(path + "/" + name,
965  offsetPath + "/" + name,
966  depth + 1,
967  childSpecialIndex);
968  }
969  else if(specialIndex >= 0)
970  {
971  // get special files!!
972 
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)
978  {
979  //__COUT__ << "Found special '" <<
980  // specialFolders[specialIndex] <<
981  // "' file '" << name << "' at path " <<
982  // path << " " << specialIndex << __E__;
983 
984  retMap[specialMapTypes[specialIndex]].emplace(offsetPath +
985  "/" + name);
986  }
987  }
988  }
989  } // end directory traversal
990 
991  closedir(pDIR);
992  }; // end localRecurse() definition
993 
994  // start recursive traversal to find special folders
995  localRecurse(path, "" /*offsetPath*/, 0 /*depth*/, -1 /*specialIndex*/);
996 
997  //__COUTV__(StringMacros::mapToString(retMap));
998  return retMap;
999 } // end getSpecialsMap()
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)