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