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