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