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