otsdaq  v2_04_02
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_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";
25 
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")) + "/";
30 
31 //========================================================================================================================
32 // CodeEditor
33 CodeEditor::CodeEditor()
34  : ALLOWED_FILE_EXTENSIONS_({"h", "hh", "hpp", "hxx", "c", "cc", "cpp",
35  "cxx", "icc", "dat", "txt", "sh", "css", "html",
36  "htm", "js", "py", "fcl", "xml", "cfg"})
37 {
38  std::string path = CODE_EDITOR_DATA_PATH;
39  DIR* dir = opendir(path.c_str());
40  if(dir)
41  closedir(dir);
42  else if(-1 == mkdir(path.c_str(), 0755))
43  {
44  // lets create the service folder (for first time)
45  __SS__ << "Service directory creation failed: " << path << std::endl;
46  __SS_THROW__;
47  }
48 
49 } // end CodeEditor()
50 
51 //========================================================================================================================
52 // xmlRequest
53 // all requests are handled here
54 void CodeEditor::xmlRequest(const std::string& option,
55  bool readOnlyMode,
56  cgicc::Cgicc& cgiIn,
57  HttpXmlDocument* xmlOut,
58  const std::string& username) try
59 {
60  __COUTV__(option);
61 
62  // request options:
63  //
64  // getDirectoryContent
65  // getFileContent
66  // saveFileContent
67  // cleanBuild
68  // incrementalBuild
69  // getAllowedExtensions
70  //
71 
72  if(option == "getDirectoryContent")
73  {
74  getDirectoryContent(cgiIn, xmlOut);
75  }
76  else if(option == "getFileContent")
77  {
78  getFileContent(cgiIn, xmlOut);
79  }
80  else if(!readOnlyMode && option == "saveFileContent")
81  {
82  saveFileContent(cgiIn, xmlOut, username);
83  }
84  else if(!readOnlyMode && option == "build")
85  {
86  build(cgiIn, xmlOut, username);
87  }
88  else if(option == "getAllowedExtensions")
89  {
90  xmlOut->addTextElementToData(
91  "AllowedExtensions",
92  StringMacros::setToString(ALLOWED_FILE_EXTENSIONS_, ","));
93  }
94  else
95  {
96  __SS__ << "Unrecognized request option '" << option << ".'" << __E__;
97  __SS_THROW__;
98  }
99 }
100 catch(const std::runtime_error& e)
101 {
102  __SS__ << "Error encountered while handling the Code Editor request option '"
103  << option << "': " << e.what() << __E__;
104  xmlOut->addTextElementToData("Error", ss.str());
105 }
106 catch(...)
107 {
108  __SS__ << "Unknown error encountered while handling the Code Editor request option '"
109  << option << "!'" << __E__;
110  xmlOut->addTextElementToData("Error", ss.str());
111 } // end xmlRequest()
112 
113 //========================================================================================================================
114 // safePathString
115 std::string CodeEditor::safePathString(const std::string& path)
116 {
117  //__COUTV__(path);
118  // remove all non ascii and non /, -, _,, space
119  std::string fullpath = "";
120  for(unsigned int i = 0; i < path.length(); ++i)
121  if((path[i] >= 'a' && path[i] <= 'z') || (path[i] >= 'A' && path[i] <= 'Z') ||
122  path[i] >= '_' || path[i] >= '-' || path[i] >= ' ' || path[i] >= '/')
123  fullpath += path[i];
124  //__COUTV__(fullpath);
125  if(!fullpath.length())
126  {
127  __SS__ << "Invalid path '" << fullpath << "' found!" << __E__;
128  __SS_THROW__;
129  }
130  return fullpath;
131 } // end safePathString()
132 
133 //========================================================================================================================
134 // safeExtensionString
135 // remove all non ascii and make lower case
136 std::string CodeEditor::safeExtensionString(const std::string& extension)
137 {
138  //__COUTV__(extension);
139 
140  std::string retExt = "";
141  // remove all non ascii
142  // skip first potential '.' (depends on parent calling function if extension includes
143  //'.')
144  for(unsigned int i = 0; i < extension.length(); ++i)
145  if((extension[i] >= 'a' && extension[i] <= 'z'))
146  retExt += extension[i];
147  else if((extension[i] >= 'A' && extension[i] <= 'Z'))
148  retExt += extension[i] + 32; // make lowercase
149  else if(i > 0 || extension[i] != '.')
150  {
151  __SS__ << "Invalid extension non-alpha " << int(extension[i]) << " found!"
152  << __E__;
153  __SS_ONLY_THROW__;
154  }
155 
156  //__COUTV__(retExt);
157 
158  if(ALLOWED_FILE_EXTENSIONS_.find(
159  retExt) == // should match get directory content restrictions
160  ALLOWED_FILE_EXTENSIONS_.end())
161  {
162  __SS__ << "Invalid extension '" << retExt << "' found!" << __E__;
163  __SS_ONLY_THROW__;
164  }
165  return retExt;
166 } // end safeExtensionString()
167 
168 //========================================================================================================================
169 // getDirectoryContent
170 void CodeEditor::getDirectoryContent(cgicc::Cgicc& cgiIn, HttpXmlDocument* xmlOut)
171 {
172  std::string path = CgiDataUtilities::getData(cgiIn, "path");
173  path = safePathString(StringMacros::decodeURIComponent(path));
174  __COUTV__(path);
175  __COUTV__(CodeEditor::SOURCE_BASE_PATH);
176 
177  xmlOut->addTextElementToData("path", path);
178 
179  const unsigned int numOfTypes = 7;
180  std::string specialTypeNames[] = {"Front-End Plugins",
181  "Data Processor Plugins",
182  "Configuration Table Plugins",
183  "Controls Interface Plugins",
184  "Tools and Scripts",
185  "$USER_DATA",
186  "$OTSDAQ_DATA"};
187  std::string specialTypes[] = {SPECIAL_TYPE_FEInterface,
188  SPECIAL_TYPE_DataProcessor,
189  SPECIAL_TYPE_Table,
190  SPECIAL_TYPE_ControlsInterface,
191  SPECIAL_TYPE_Tools,
192  SPECIAL_TYPE_UserData,
193  SPECIAL_TYPE_OutputData};
194 
195  std::string pathMatchPrepend = "/"; // some requests come in with leading "/" and
196  // "//"
197  if(path.length() > 1 && path[1] == '/')
198  pathMatchPrepend += '/';
199 
200  for(unsigned int i = 0; i < numOfTypes; ++i)
201  if(path == pathMatchPrepend + specialTypeNames[i])
202  {
203  __COUT__ << "Getting all " << specialTypeNames[i] << "..." << __E__;
204 
205  // handle UserData and OutputData differently
206  // since there is only one path to check
207  if(specialTypes[i] == SPECIAL_TYPE_UserData)
208  {
209  getPathContent("/", CodeEditor::USER_DATA_PATH, xmlOut);
210  return;
211  }
212  else if(specialTypes[i] == SPECIAL_TYPE_OutputData)
213  {
214  getPathContent("/", CodeEditor::OTSDAQ_DATA_PATH, xmlOut);
215  return;
216  }
217 
218  std::map<std::string /*special type*/,
219  std::set<std::string> /*special file paths*/>
220  retMap = CodeEditor::getSpecialsMap();
221  if(retMap.find(specialTypes[i]) != retMap.end())
222  {
223  for(const auto& specialTypeFile : retMap[specialTypes[i]])
224  {
225  xmlOut->addTextElementToData("specialFile", specialTypeFile);
226  }
227  }
228  else
229  {
230  __SS__ << "No files for type '" << specialTypeNames[i] << "' were found."
231  << __E__;
232  __SS_THROW__;
233  }
234  return;
235  }
236 
237  // if root directory, add special directory for types
238  if(path == "/")
239  for(unsigned int i = 0; i < numOfTypes; ++i)
240  xmlOut->addTextElementToData("special", specialTypeNames[i]);
241 
242  std::string contents;
243  size_t i;
244  if((i = path.find("$USER_DATA/")) == 0 ||
245  (i == 1 && path[0] == '/')) // if leading / or without
246  getPathContent(CodeEditor::USER_DATA_PATH,
247  path.substr(std::string("/$USER_DATA/").size()),
248  xmlOut);
249  else if((i = path.find("$OTSDAQ_DATA/")) == 0 ||
250  (i == 1 && path[0] == '/')) // if leading / or without
251  getPathContent(CodeEditor::OTSDAQ_DATA_PATH,
252  path.substr(std::string("/$OTSDAQ_DATA/").size()),
253  xmlOut);
254  else
255  getPathContent(CodeEditor::SOURCE_BASE_PATH, path, xmlOut);
256 
257 } // end getDirectoryContent()
258 
259 //========================================================================================================================
260 // getPathContent
261 void CodeEditor::getPathContent(const std::string& basepath,
262  const std::string& path,
263  HttpXmlDocument* xmlOut)
264 {
265  DIR* pDIR;
266  struct dirent* entry;
267  bool isDir;
268  std::string name;
269  int type;
270 
271  if(!(pDIR = opendir((basepath + path).c_str())))
272  {
273  __SS__ << "Path '" << path << "' could not be opened!" << __E__;
274  __SS_THROW__;
275  }
276 
277  // add to set for alpha ordering
278  // with insensitive compare
279  struct InsensitiveCompare
280  {
281  bool operator()(const std::string& as, const std::string& bs) const
282  {
283  // return true, if as < bs
284  const char* a = as.c_str();
285  const char* b = bs.c_str();
286  int d;
287 
288  // compare each character, until difference, or end of string
289  while((d = (std::toupper(*a) - std::toupper(*b))) == 0 && *a)
290  ++a, ++b;
291 
292  //__COUT__ << as << " vs " << bs << " = " << d << " " << (d<0) << __E__;
293 
294  return d < 0;
295  }
296  };
297  std::set<std::string, InsensitiveCompare> orderedDirectories;
298  std::set<std::string, InsensitiveCompare> orderedFiles;
299 
300  std::string extension;
301 
302  // else directory good, get all folders, .h, .cc, .txt files
303  while((entry = readdir(pDIR)))
304  {
305  name = std::string(entry->d_name);
306  type = int(entry->d_type);
307 
308  //__COUT__ << type << " " << name << "\n" << std::endl;
309 
310  if(name[0] != '.' &&
311  (type == 0 || // 0 == UNKNOWN (which can happen - seen in SL7 VM)
312  type == 4 || // directory type
313  type == 8 || // file type
314  type == 10 // 10 == link (could be directory or file, treat as unknown)
315  ))
316  {
317  isDir = false;
318 
319  if(type == 0 || type == 10)
320  {
321  // unknown type .. determine if directory
322  DIR* pTmpDIR = opendir((basepath + path + "/" + name).c_str());
323  if(pTmpDIR)
324  {
325  isDir = true;
326  closedir(pTmpDIR);
327  }
328  // else //assume file
329  // __COUT__ << "Unable to open path as directory: " <<
330  // (basepath + path + "/" + name) << __E__;
331  }
332 
333  if(type == 4)
334  isDir = true; // flag directory types
335 
336  // handle directories
337 
338  if(isDir)
339  {
340  //__COUT__ << "Directory: " << type << " " << name << __E__;
341 
342  orderedDirectories.emplace(name);
343  // xmlOut->addTextElementToData("directory",name);
344  }
345  else // type 8 or 0 is file
346  {
347  //__COUT__ << "File: " << type << " " << name << "\n" << std::endl;
348 
349  try
350  {
351  if(name != "ots")
352  safeExtensionString(name.substr(name.rfind('.')));
353  //__COUT__ << "EditFile: " << type << " " << name << __E__;
354 
355  orderedFiles.emplace(name);
356  }
357  catch(...)
358  {
359  __COUT__ << "Invalid file extension, skipping '" << name << "' ..."
360  << __E__;
361  }
362  }
363  }
364  } // end directory traversal
365 
366  closedir(pDIR);
367 
368  __COUT__ << "Found " << orderedDirectories.size() << " directories." << __E__;
369  __COUT__ << "Found " << orderedFiles.size() << " files." << __E__;
370 
371  for(const auto& name : orderedDirectories)
372  xmlOut->addTextElementToData("directory", name);
373  for(const auto& name : orderedFiles)
374  xmlOut->addTextElementToData("file", name);
375 } // end getPathContent()
376 
377 //========================================================================================================================
378 // getFileContent
379 void CodeEditor::getFileContent(cgicc::Cgicc& cgiIn, HttpXmlDocument* xmlOut)
380 {
381  std::string path = CgiDataUtilities::getData(cgiIn, "path");
382  path = safePathString(StringMacros::decodeURIComponent(path));
383  xmlOut->addTextElementToData("path", path);
384 
385  std::string extension = CgiDataUtilities::getData(cgiIn, "ext");
386  if(extension == "ots")
387  extension = ""; // special handling of ots extension (to get bash script
388  // properly)
389  if(!(path.length() > 4 && path.substr(path.length() - 4) == "/ots"))
390  extension = safeExtensionString(extension);
391  xmlOut->addTextElementToData("ext", extension);
392 
393  std::string contents;
394  size_t i;
395  if((i = path.find("$USER_DATA/")) == 0 ||
396  (i == 1 && path[0] == '/')) // if leading / or without
397  CodeEditor::readFile(CodeEditor::USER_DATA_PATH,
398  path.substr(i + std::string("$USER_DATA/").size()) +
399  (extension.size() ? "." : "") + extension,
400  contents);
401  else if((i = path.find("$OTSDAQ_DATA/")) == 0 ||
402  (i == 1 && path[0] == '/')) // if leading / or without
403  CodeEditor::readFile(CodeEditor::OTSDAQ_DATA_PATH,
404  path.substr(std::string("/$OTSDAQ_DATA/").size()) +
405  (extension.size() ? "." : "") + extension,
406  contents);
407  else
408  CodeEditor::readFile(CodeEditor::SOURCE_BASE_PATH,
409  path + (extension.size() ? "." : "") + extension,
410  contents);
411 
412  xmlOut->addTextElementToData("content", contents);
413 
414 } // end getFileContent()
415 
416 //========================================================================================================================
417 // readFile
418 void CodeEditor::readFile(const std::string& basepath,
419  const std::string& path,
420  std::string& contents)
421 {
422  std::string fullpath = basepath + "/" + path;
423  __COUTV__(fullpath);
424 
425  std::FILE* fp = std::fopen(fullpath.c_str(), "rb");
426  if(!fp)
427  {
428  __SS__ << "Could not open file at " << path << __E__;
429  __SS_THROW__;
430  }
431 
432  std::fseek(fp, 0, SEEK_END);
433  contents.resize(std::ftell(fp));
434  std::rewind(fp);
435  std::fread(&contents[0], 1, contents.size(), fp);
436  std::fclose(fp);
437 } // end readFile
438 
439 //========================================================================================================================
440 // writeFile
441 void CodeEditor::writeFile(const std::string& basepath,
442  const std::string& path,
443  const std::string& contents,
444  const std::string& username,
445  const unsigned long long& insertPos,
446  const std::string& insertString)
447 {
448  std::string fullpath = basepath + path;
449  __COUTV__(fullpath);
450 
451  FILE* fp;
452 
453  long long int oldSize = 0;
454  try
455  {
456  // get old file size
457  fp = fopen(fullpath.c_str(), "rb");
458  if(!fp)
459  {
460  __SS__ << "Could not open file for saving at " << fullpath << __E__;
461  __SS_THROW__;
462  }
463  std::fseek(fp, 0, SEEK_END);
464  oldSize = std::ftell(fp);
465  fclose(fp);
466  }
467  catch(...)
468  {
469  __COUT_WARN__ << "Ignoring file not existing..." << __E__;
470  }
471 
472  fp = fopen(fullpath.c_str(), "wb");
473  if(!fp)
474  {
475  __SS__ << "Could not open file for saving at " << fullpath << __E__;
476  __SS_THROW__;
477  }
478 
479  if(insertPos == (unsigned long long)-1)
480  std::fwrite(&contents[0], 1, contents.size(), fp);
481  else // do insert
482  {
483  std::fwrite(&contents[0], 1, insertPos, fp);
484  std::fwrite(&insertString[0], 1, insertString.size(), fp);
485  std::fwrite(&contents[insertPos], 1, contents.size() - insertPos, fp);
486  }
487  std::fclose(fp);
488 
489  // log changes
490  {
491  std::string logpath = CODE_EDITOR_DATA_PATH + "/codeEditorChangeLog.txt";
492  fp = fopen(logpath.c_str(), "a");
493  if(!fp)
494  {
495  __SS__ << "Could not open change log for change tracking at " << logpath
496  << __E__;
497  __SS_THROW__;
498  }
499  fprintf(fp,
500  "time=%lld author%s old-size=%lld new-size=%lld path=%s\n",
501  (long long)time(0),
502  username.c_str(),
503  oldSize,
504  (long long)contents.size(),
505  fullpath.c_str());
506 
507  fclose(fp);
508  __COUT__ << "Changes logged to: " << logpath << __E__;
509  } // end log changes
510 
511 } // end writeFile
512 
513 //========================================================================================================================
514 // saveFileContent
515 void CodeEditor::saveFileContent(cgicc::Cgicc& cgiIn,
516  HttpXmlDocument* xmlOut,
517  const std::string& username)
518 {
519  std::string path = CgiDataUtilities::getData(cgiIn, "path");
520  path = safePathString(StringMacros::decodeURIComponent(path));
521  xmlOut->addTextElementToData("path", path);
522 
523  std::string basepath = CodeEditor::SOURCE_BASE_PATH;
524 
525  std::string pathMatchPrepend = "/"; // some requests come in with leading "/" and
526  // "//"
527  if(path.length() > 1 && path[1] == '/')
528  pathMatchPrepend += '/';
529 
530  __COUTV__(path);
531  __COUTV__(pathMatchPrepend);
532 
533  // fix path for special environment variables
534  if(path.substr(0, (pathMatchPrepend + "$USER_DATA/").size()) ==
535  pathMatchPrepend + "$USER_DATA/")
536  {
537  basepath = "/";
538  path = CodeEditor::USER_DATA_PATH + "/" +
539  path.substr((pathMatchPrepend + "$USER_DATA/").size());
540  }
541  else if(path.substr(0, (pathMatchPrepend + "$OTSDAQ_DATA/").size()) ==
542  pathMatchPrepend + "$OTSDAQ_DATA/")
543  {
544  basepath = "/";
545  path = CodeEditor::OTSDAQ_DATA_PATH + "/" +
546  path.substr((pathMatchPrepend + "$OTSDAQ_DATA/").size());
547  }
548  __COUTV__(path);
549  __COUTV__(basepath);
550 
551  std::string extension = CgiDataUtilities::getData(cgiIn, "ext");
552  if(!(path.length() > 4 && path.substr(path.length() - 4) == "/ots"))
553  extension = safeExtensionString(extension);
554  xmlOut->addTextElementToData("ext", extension);
555 
556  std::string contents = CgiDataUtilities::postData(cgiIn, "content");
557  //__COUTV__(contents);
558  contents = StringMacros::decodeURIComponent(contents);
559 
560  CodeEditor::writeFile(
561  basepath, path + (extension.size() ? "." : "") + extension, contents, username);
562 
563 } // end saveFileContent
564 
565 //========================================================================================================================
566 // build
567 // cleanBuild and incrementalBuild
568 void CodeEditor::build(cgicc::Cgicc& cgiIn,
569  HttpXmlDocument* xmlOut,
570  const std::string& username)
571 {
572  bool clean = CgiDataUtilities::getDataAsInt(cgiIn, "clean") ? true : false;
573 
574  __MCOUT_INFO__("Build (clean=" << clean << ") launched by '" << username << "'..."
575  << __E__);
576 
577  // launch as thread so it does not lock up rest of code
578  std::thread(
579  [](bool clean) {
580 
581  std::string cmd;
582  if(clean)
583  {
584  // clean
585  {
586  cmd = "mrb z 2>&1";
587 
588  std::array<char, 128> buffer;
589  std::string result;
590  std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
591  if(!pipe)
592  __THROW__("popen() failed!");
593 
594  size_t i;
595  size_t j;
596 
597  while(!feof(pipe.get()))
598  {
599  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
600  {
601  result += buffer.data();
602 
603  // each time there is a new line print out
604  i = result.find('\n');
605  __COUTV__(result.substr(0, i));
606  __MOUT__ << result.substr(0, i);
607  result = result.substr(i + 1); // discard before new line
608  }
609  }
610 
611  __COUTV__(result);
612  __MOUT__ << result.substr(0, i);
613  }
614 
615  sleep(1);
616  // mrbsetenv
617  {
618  cmd = "source mrbSetEnv 2>&1";
619 
620  std::array<char, 128> buffer;
621  std::string result;
622  std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
623  if(!pipe)
624  __THROW__("popen() failed!");
625 
626  size_t i;
627  size_t j;
628 
629  while(!feof(pipe.get()))
630  {
631  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
632  {
633  result += buffer.data();
634 
635  // each time there is a new line print out
636  i = result.find('\n');
637  __COUTV__(result.substr(0, i));
638  __MOUT__ << result.substr(0, i);
639  result = result.substr(i + 1); // discard before new line
640  }
641  }
642 
643  __COUTV__(result);
644  __MOUT__ << result.substr(0, i);
645  }
646  sleep(1);
647  }
648 
649  // build
650  {
651  cmd = "mrb b 2>&1";
652 
653  std::array<char, 128> buffer;
654  std::string result;
655  std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
656  if(!pipe)
657  __THROW__("popen() failed!");
658 
659  size_t i;
660  size_t j;
661 
662  while(!feof(pipe.get()))
663  {
664  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
665  {
666  result += buffer.data();
667 
668  // each time there is a new line print out
669  i = result.find('\n');
670  //__COUTV__(result.substr(0,i));
671  __MOUT__ << result.substr(0, i);
672  result = result.substr(i + 1); // discard before new line
673  }
674  }
675 
676  //__COUTV__(result);
677  __MOUT__ << result.substr(0, i);
678  }
679  },
680  clean)
681  .detach();
682 
683 } // end build()
684 
685 //========================================================================================================================
686 std::map<std::string /*special type*/, std::set<std::string> /*special file paths*/>
687 CodeEditor::getSpecialsMap(void)
688 {
689  std::map<std::string /*special type*/, std::set<std::string> /*special file paths*/>
690  retMap;
691  std::string path = std::string(__ENV__("MRB_SOURCE"));
692 
693  __COUTV__(path);
694 
695  const unsigned int numOfSpecials = 7;
696  std::string specialFolders[] = {"FEInterfaces",
697  "DataProcessorPlugins",
698  "UserTableDataFormats",
699  "TablePlugins",
700  "TablePluginDataFormats",
701  "UserTablePlugins",
702  "UserTablePluginDataFormats",
703  "SlowControlsInterfacePlugins",
704  "ControlsInterfacePlugins",
705  "FEInterfacePlugins",
706  "tools"};
707  std::string specialMapTypes[] = {CodeEditor::SPECIAL_TYPE_FEInterface,
708  CodeEditor::SPECIAL_TYPE_DataProcessor,
709  CodeEditor::SPECIAL_TYPE_Table,
710  CodeEditor::SPECIAL_TYPE_Table,
711  CodeEditor::SPECIAL_TYPE_Table,
712  CodeEditor::SPECIAL_TYPE_Table,
713  CodeEditor::SPECIAL_TYPE_Table,
714  CodeEditor::SPECIAL_TYPE_ControlsInterface,
715  CodeEditor::SPECIAL_TYPE_ControlsInterface,
716  CodeEditor::SPECIAL_TYPE_FEInterface,
717  CodeEditor::SPECIAL_TYPE_Tools};
718 
719  // Note: can not do lambda recursive function if using auto to declare the function,
720  // and must capture reference to the function. Also, must capture specialFolders
721  // reference for use internally (const values already are captured).
722  std::function<void(
723  const std::string&, const std::string&, const unsigned int, const int)>
724  localRecurse = [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
725  const std::string& path,
726  const std::string& offsetPath,
727  const unsigned int depth,
728  const int specialIndex) {
729 
730  //__COUTV__(path);
731  //__COUTV__(depth);
732 
733  DIR* pDIR;
734  struct dirent* entry;
735  bool isDir;
736  if(!(pDIR = opendir(path.c_str())))
737  {
738  __SS__ << "Plugin base path '" << path << "' could not be opened!"
739  << __E__;
740  __SS_THROW__;
741  }
742 
743  // else directory good, get all folders and look for special folders
744  std::string name;
745  int type;
746  int childSpecialIndex;
747  while((entry = readdir(pDIR)))
748  {
749  name = std::string(entry->d_name);
750  type = int(entry->d_type);
751 
752  //__COUT__ << type << " " << name << "\n" << std::endl;
753 
754  if(name[0] != '.' &&
755  (type == 0 || // 0 == UNKNOWN (which can happen - seen in SL7 VM)
756  type == 4 || type == 8))
757  {
758  isDir = false;
759 
760  if(type == 0)
761  {
762  // unknown type .. determine if directory
763  DIR* pTmpDIR = opendir((path + "/" + name).c_str());
764  if(pTmpDIR)
765  {
766  isDir = true;
767  closedir(pTmpDIR);
768  }
769  // else //assume file
770  }
771 
772  if(type == 4)
773  isDir = true; // flag directory types
774 
775  // handle directories
776 
777  if(isDir)
778  {
779  //__COUT__ << "Directory: " << type << " " << name << __E__;
780 
781  childSpecialIndex = -1;
782  for(unsigned int i = 0; i < numOfSpecials; ++i)
783  if(name == specialFolders[i])
784  {
785  //__COUT__ << "Found special folder '" <<
786  // specialFolders[i] <<
787  // "' at path " << path << __E__;
788 
789  childSpecialIndex = i;
790  break;
791  }
792 
793  // recurse deeper!
794  if(depth < 4) // limit search depth
795  localRecurse(path + "/" + name,
796  offsetPath + "/" + name,
797  depth + 1,
798  childSpecialIndex);
799  }
800  else if(specialIndex >= 0)
801  {
802  // get special files!!
803 
804  if(name.find(".h") == name.length() - 2 ||
805  name.find(".cc") == name.length() - 3 ||
806  name.find(".txt") == name.length() - 4 ||
807  name.find(".sh") == name.length() - 3 ||
808  name.find(".py") == name.length() - 3)
809  {
810  //__COUT__ << "Found special '" <<
811  // specialFolders[specialIndex] <<
812  // "' file '" << name << "' at path " <<
813  // path << " " << specialIndex << __E__;
814 
815  retMap[specialMapTypes[specialIndex]].emplace(offsetPath +
816  "/" + name);
817  }
818  }
819  }
820  } // end directory traversal
821 
822  closedir(pDIR);
823 
824  }; //end localRecurse() definition
825 
826  // start recursive traversal to find special folders
827  localRecurse(path, "" /*offsetPath*/, 0 /*depth*/, -1 /*specialIndex*/);
828 
829  //__COUTV__(StringMacros::mapToString(retMap));
830  return retMap;
831 } // end getSpecialsMap()