Vuo  2.1.2
VuoFileUtilities.cc
Go to the documentation of this file.
1 
10 #include <CommonCrypto/CommonDigest.h>
11 #include <dirent.h>
12 #include <spawn.h>
13 #include <sstream>
14 #include <sys/mman.h>
15 #include <sys/stat.h>
16 #include <copyfile.h>
17 #include <fstream>
18 #include <iomanip>
19 #include <iostream>
20 #include <mach-o/dyld.h>
21 #include <sys/time.h>
22 #include "VuoException.hh"
23 #include "VuoFileUtilities.hh"
24 #include "VuoFileUtilitiesCocoa.hh"
25 #include "VuoStringUtilities.hh"
26 
27 
38 void VuoFileUtilities::splitPath(string path, string &dir, string &file, string &extension)
39 {
40  dir = "";
41  file = "";
42  extension = "";
43  const char FILE_SEPARATOR = '/';
44 
45  size_t separatorIndex = path.rfind(FILE_SEPARATOR);
46  string fileAndExtension;
47  if (separatorIndex != string::npos)
48  {
49  dir = path.substr(0, separatorIndex + 1);
50  if (separatorIndex < path.length())
51  fileAndExtension = path.substr(separatorIndex + 1, path.length());
52  }
53  else
54  {
55  fileAndExtension = path;
56  }
57 
58  size_t dotIndex = fileAndExtension.rfind(".");
59  if (dotIndex != string::npos)
60  {
61  file = fileAndExtension.substr(0, dotIndex);
62  extension = fileAndExtension.substr(dotIndex + 1, fileAndExtension.length());
63  }
64  else
65  {
66  file = fileAndExtension;
67  }
68 }
69 
75 {
76  // Remove repeated file separators.
77  for (int i = 0; i < path.length(); ++i)
78  if (path[i] == '/')
79  while (i+1 < path.length() && path[i+1] == '/')
80  path.erase(i+1, 1);
81 
82  // Remove trailing file separator.
83  if (path.length() > 1 && VuoStringUtilities::endsWith(path, "/"))
84  path.erase(path.length()-1);
85 }
86 
93 bool VuoFileUtilities::arePathsEqual(string path1, string path2)
94 {
95  canonicalizePath(path1);
96  canonicalizePath(path2);
97  return path1 == path2;
98 }
99 
103 string VuoFileUtilities::getAbsolutePath(const string &path)
104 {
105  if (VuoStringUtilities::beginsWith(path, "/"))
106  return path;
107 
108  char *cwd = getcwd(NULL, 0);
109  if (! cwd)
110  {
111  VUserLog("Couldn't get current working directory: %s", strerror(errno));
112  return path;
113  }
114 
115  string absolutePath = string(cwd) + "/" + path;
116  free(cwd);
117  return absolutePath;
118 }
119 
127 string VuoFileUtilities::makeTmpFile(string file, string extension, string directory)
128 {
129  if (directory.empty())
130  directory = getTmpDir();
131  if (directory.at(directory.length()-1) != '/')
132  directory += "/";
133  string suffix = (extension.empty() ? "" : ("." + extension));
134  string pathTemplate = directory + file + "-XXXXXX" + suffix;
135  char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
136  strncpy(path, pathTemplate.c_str(), pathTemplate.length());
137  int fd = mkstemps(path, suffix.length());
138  close(fd);
139  string pathStr(path);
140  free(path);
141  return pathStr;
142 }
143 
153 string VuoFileUtilities::makeTmpDir(string prefix)
154 {
155  string pathTemplate = getTmpDir() + "/" + prefix + ".XXXXXX";
156  char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
157  strncpy(path, pathTemplate.c_str(), pathTemplate.length());
158  mkdtemp(path);
159  string pathStr(path);
160  free(path);
161  return pathStr;
162 }
163 
173 {
175 }
176 
189 {
190  char *cwd = getcwd(NULL, 0);
191  if (!cwd)
192  VUserLog("Error in getcwd(): %s", strerror(errno));
193 
194  if (cwd && strstr(cwd, "/Library/Containers/")) // We're in a sandbox.
195  {
196  // https://b33p.net/kosada/node/16374
197  // In the macOS 10.15 screen saver sandbox, at least,
198  // we're not allowed to use the system or user temporary directories for socket-files,
199  // but we are allowed to use the sandbox container directory.
200  string cwdS(cwd);
201  free(cwd);
202  return VuoStringUtilities::endsWith(cwdS, "/") ? VuoStringUtilities::substrBefore(cwdS, "/") : cwdS;
203  }
204  free(cwd);
205 
206  char userTempDir[PATH_MAX];
207  if (confstr(_CS_DARWIN_USER_TEMP_DIR, userTempDir, PATH_MAX) > 0)
208  {
209  string userTempDirS(userTempDir);
210  return VuoStringUtilities::endsWith(userTempDirS, "/") ? VuoStringUtilities::substrBefore(userTempDirS, "/") : userTempDirS;
211  }
212 
213  return "/tmp";
214 }
215 
223 void VuoFileUtilities::makeDir(string path)
224 {
225  if (path.empty())
226  throw VuoException("Couldn't create directory with empty path");
227 
228  if (dirExists(path))
229  return;
230 
231  const char FILE_SEPARATOR = '/';
232  size_t lastNonSeparator = path.find_last_not_of(FILE_SEPARATOR);
233  if (lastNonSeparator != string::npos)
234  path.resize(lastNonSeparator + 1);
235 
236  string parentDir, file, ext;
237  splitPath(path, parentDir, file, ext);
238  makeDir(parentDir);
239 
240  // `mkdir` ANDs the mode with the process's umask,
241  // so by default the created directory's mode will end up being 0755.
242  int ret = mkdir(path.c_str(), 0777);
243  if (ret != 0 && ! dirExists(path))
244  throw VuoException((string("Couldn't create directory \"" + path + "\": ") + strerror(errno)).c_str());
245 }
246 
254 {
255  static string frameworkPath;
256  static dispatch_once_t once = 0;
257  dispatch_once(&once, ^{
258 
259  // First check whether Vuo.framework is in the list of loaded dynamic libraries.
260  const char *frameworkPathFragment = "/Vuo.framework/Versions/";
261  uint32_t imageCount = _dyld_image_count();
262  for (uint32_t i = 0; i < imageCount; ++i)
263  {
264  const char *dylibPath = _dyld_get_image_name(i);
265  const char *found = strstr(dylibPath, frameworkPathFragment);
266  if (found)
267  {
268  char *pathC = strndup(dylibPath, found - dylibPath);
269  string path = string(pathC) + "/Vuo.framework";
270  free(pathC);
271  if (fileExists(path))
272  {
273  frameworkPath = path;
274  return;
275  }
276  }
277  }
278 
279  // Failing that, check for a Vuo.framework bundled with the executable app.
280  char executablePath[PATH_MAX + 1];
281  uint32_t size = sizeof(executablePath);
282 
283  if (! _NSGetExecutablePath(executablePath, &size))
284  {
285  char cleanedExecutablePath[PATH_MAX + 1];
286 
287  realpath(executablePath, cleanedExecutablePath); // remove extra references (e.g., "/./")
288  string path = cleanedExecutablePath;
289  string dir, file, ext;
290  splitPath(path, dir, file, ext); // remove executable name
291  path = dir.substr(0, dir.length() - 1); // remove "/"
292  splitPath(path, dir, file, ext); // remove "MacOS"
293  path = dir.substr(0, dir.length() - 1); // remove "/"
294  path += "/Frameworks/Vuo.framework";
295 
296  if (fileExists(path))
297  {
298  frameworkPath = path;
299  return;
300  }
301  }
302 
303  // Give up.
304  });
305 
306  return frameworkPath;
307 }
308 
314 {
315  static string runnerFrameworkPath;
316  static dispatch_once_t once = 0;
317  dispatch_once(&once, ^{
318  // Check for VuoRunner.framework alongside Vuo.framework.
319  string possibleFrameworkPath = getVuoFrameworkPath() + "/../VuoRunner.framework";
320  if (dirExists(possibleFrameworkPath))
321  {
322  runnerFrameworkPath = possibleFrameworkPath;
323  return;
324  }
325 
326  // Failing that, check whether VuoRunner.framework is in the list of loaded dynamic libraries.
327  const char *frameworkPathFragment = "/VuoRunner.framework/Versions/";
328  uint32_t imageCount = _dyld_image_count();
329  for (uint32_t i = 0; i < imageCount; ++i)
330  {
331  const char *dylibPath = _dyld_get_image_name(i);
332  const char *found = strstr(dylibPath, frameworkPathFragment);
333  if (found)
334  {
335  char *pathC = strndup(dylibPath, found - dylibPath);
336  string path = string(pathC) + "/VuoRunner.framework";
337  free(pathC);
338  if (fileExists(path))
339  {
340  runnerFrameworkPath = path;
341  return;
342  }
343  }
344  }
345 
346  // Failing that, check for a VuoRunner.framework bundled with the executable app.
347  char executablePath[PATH_MAX + 1];
348  uint32_t size = sizeof(executablePath);
349 
350  if (! _NSGetExecutablePath(executablePath, &size))
351  {
352  char cleanedExecutablePath[PATH_MAX + 1];
353 
354  realpath(executablePath, cleanedExecutablePath); // remove extra references (e.g., "/./")
355  string path = cleanedExecutablePath;
356  string dir, file, ext;
357  splitPath(path, dir, file, ext); // remove executable name
358  path = dir.substr(0, dir.length() - 1); // remove "/"
359  splitPath(path, dir, file, ext); // remove "MacOS"
360  path = dir.substr(0, dir.length() - 1); // remove "/"
361  path += "/Frameworks/VuoRunner.framework";
362 
363  if (fileExists(path))
364  {
365  runnerFrameworkPath = path;
366  return;
367  }
368  }
369 
370  // Give up.
371  });
372  return runnerFrameworkPath;
373 }
374 
383 string VuoFileUtilities::getCompositionLocalModulesPath(const string &compositionPath)
384 {
385  string compositionDir, compositionFile, ext;
386  splitPath(getAbsolutePath(compositionPath), compositionDir, compositionFile, ext);
387  canonicalizePath(compositionDir);
388 
389  string parentDir, compositionDirName;
390  splitPath(compositionDir, parentDir, compositionDirName, ext);
391  canonicalizePath(compositionDirName);
392 
393  string compositionBaseDir = (compositionDirName == "Modules" ? parentDir : compositionDir);
394 
395  string compositionModulesDir = compositionBaseDir + "/Modules";
396  canonicalizePath(compositionModulesDir);
397 
398  return compositionModulesDir;
399 }
400 
405 {
406  return string(getenv("HOME")) + "/Library/Application Support/Vuo/Modules";
407 }
408 
413 {
414  return "/Library/Application Support/Vuo/Modules";
415 }
416 
421 {
422  return string(getenv("HOME")) + "/Library/Caches/org.vuo/" + VUO_VERSION_AND_BUILD_STRING;
423 }
424 
429 {
430  if (path.empty())
431  return false;
432 
433  string compositionLocalModulesPath = getCompositionLocalModulesPath(path);
434 
435  string dir, file, ext;
436  VuoFileUtilities::splitPath(path, dir, file, ext);
437 
438  return VuoFileUtilities::arePathsEqual(compositionLocalModulesPath, dir);
439 }
440 
449 void VuoFileUtilities::preserveOriginalFileName(string &fileContents, string originalFileName)
450 {
451  fileContents.insert(getFirstInsertionIndex(fileContents), "#line 1 \"" + originalFileName + "\"\n");
452 }
453 
459 {
460  string bomUtf8 = "\xEF\xBB\xBF";
461  string bomUtf16Be = "\xFE\xFF";
462  string bomUtf16Le = "\xFF\xFE";
463  string boms[] = { bomUtf8, bomUtf16Be, bomUtf16Le };
464  for (int i = 0; i < 3; ++i)
465  {
466  bool isMatch = true;
467  for (int j = 0; j < boms[i].length(); ++j)
468  if (boms[i][j] != s[j])
469  isMatch = false;
470 
471  if (isMatch)
472  return boms[i].length();
473  }
474  return 0;
475 }
476 
481 {
482  // https://stackoverflow.com/questions/201992/how-to-read-until-eof-from-cin-in-c
483 
484  string contents;
485  string line;
486  while (getline(cin, line))
487  contents += line;
488 
489  return contents;
490 }
491 
498 {
499  // https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring
500 
501  ifstream in(path.c_str(), ios::in | ios::binary);
502  if (! in)
503  throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + path);
504 
505  string contents;
506  in.seekg(0, ios::end);
507  contents.resize(in.tellg());
508  in.seekg(0, ios::beg);
509  in.read(&contents[0], contents.size());
510  in.close();
511  return contents;
512 }
513 
521 void VuoFileUtilities::writeRawDataToFile(const char *data, size_t numBytes, string file)
522 {
523  FILE *f = fopen(file.c_str(), "wb");
524  if (! f)
525  throw VuoException(string("Couldn't open file for writing: ") + strerror(errno) + " — " + file);
526 
527  size_t numBytesWritten = fwrite(data, sizeof(char), numBytes, f);
528  if (numBytesWritten != numBytes)
529  {
530  fclose(f);
531  throw VuoException(string("Couldn't write all data: ") + strerror(errno) + " — " + file);
532  }
533 
534  fclose(f);
535 }
536 
544 void VuoFileUtilities::writeStringToFile(string s, string file)
545 {
546  writeRawDataToFile(s.data(), s.length(), file);
547 }
548 
557 void VuoFileUtilities::writeStringToFileSafely(string s, string path)
558 {
559  string dir, file, ext;
560  splitPath(path, dir, file, ext);
561  string tmpPath = makeTmpFile("." + file, ext, dir);
562 
563  writeStringToFile(s, tmpPath);
564 
565  moveFile(tmpPath, path);
566 }
567 
574 {
577 
578  // `access()` automatically dereferences symlinks.
579  return access(path.c_str(), 0) == 0;
580 }
581 
588 {
591 
592  // `stat()` automatically dereferences symlinks.
593  struct stat st_buf;
594  int status = stat(path.c_str(), &st_buf);
595  return (! status && S_ISDIR(st_buf.st_mode));
596 }
597 
602 {
603  struct stat st_buf;
604  int status = lstat(path.c_str(), &st_buf);
605  return (! status && S_ISLNK(st_buf.st_mode));
606 }
607 
614 {
617 
618  // `access()` automatically dereferences symlinks.
619  return access(path.c_str(), R_OK) == 0;
620 }
621 
628 {
631 
632  // `fopen()` automatically dereferences symlinks.
633  FILE *f = fopen(path.c_str(), "rb");
634  if (!f)
635  return false;
636 
637  fseek(f, 0, SEEK_END);
638  long pos = ftell(f);
639  if (pos <= 0)
640  {
641  fclose(f);
642  return false;
643  }
644 
645  // `fopen` and `fseek` both succeed on directories, so also attempt to actually read the data.
646  fseek(f, 0, SEEK_SET);
647  char z;
648  size_t ret = fread(&z, 1, 1, f);
649  fclose(f);
650  return ret > 0;
651 }
652 
657 {
658  FILE *f = fopen(path.c_str(), "a");
659  fclose(f);
660 }
661 
666 {
667  int ret = remove(path.c_str());
668  if (ret != 0 && fileExists(path))
669  VUserLog("Couldn't delete file: %s — %s", strerror(errno), path.c_str());
670 }
671 
676 {
677  if (! dirExists(path))
678  return;
679 
680  DIR *d = opendir(path.c_str());
681  if (! d)
682  {
683  VUserLog("Couldn't read directory: %s — %s", strerror(errno), path.c_str());
684  return;
685  }
686 
687  struct dirent *de;
688  while( (de=readdir(d)) )
689  {
690  string fileName = de->d_name;
691  string filePath = path + "/" + fileName;
692 
693  if (fileName == "." || fileName == "..")
694  continue;
695 
696  if (dirExists(filePath))
697  deleteDir(filePath);
698  else
699  deleteFile(filePath);
700  }
701 
702  closedir(d);
703 
704  deleteFile(path);
705 }
706 
712 void VuoFileUtilities::moveFile(string fromPath, string toPath)
713 {
714  int ret = rename(fromPath.c_str(), toPath.c_str());
715  if (ret != 0)
716  throw VuoException(string("Couldn't move file: ") + strerror(errno) + " — " + toPath);
717 }
718 
725 {
727 }
728 
740 void VuoFileUtilities::copyFile(string fromPath, string toPath, bool preserveMetadata)
741 {
742  int i = open(fromPath.c_str(), O_RDONLY);
743  if (i == -1)
744  throw VuoException(string("Couldn't open copy source: ") + strerror(errno) + " — " + fromPath);
745 
746  struct stat s;
747  fstat(i, &s);
748  int o = open(toPath.c_str(), O_WRONLY | O_CREAT, s.st_mode & 0777);
749  if (o == -1)
750  {
751  close(i);
752  throw VuoException(string("Couldn't open copy destination: ") + strerror(errno) + " — " + toPath);
753  }
754 
755  int ret = fcopyfile(i, o, NULL, COPYFILE_DATA | (preserveMetadata ? COPYFILE_STAT : 0));
756  char *e = strerror(errno);
757  close(o);
758  close(i);
759 
760  if (ret)
761  throw VuoException(string("Couldn't copy ") + fromPath + " to " + toPath + ": " + e);
762 
763  VDebugLog("%s -> %s", fromPath.c_str(), toPath.c_str());
764 }
765 
775 void VuoFileUtilities::copyDirectory(string fromPath, string toPath)
776 {
777  if (isSymlink(fromPath))
778  {
779  // Preserve the relative symlinks found in framework bundles, instead of flattening them.
780  char linkDestination[PATH_MAX + 1];
781  ssize_t len = readlink(fromPath.c_str(), linkDestination, PATH_MAX);
782  linkDestination[len] = 0; // "readlink does not append a NUL character to buf."
783  if (symlink(linkDestination, toPath.c_str()) == -1)
784  throw VuoException(string("Couldn't copy symlink \"") + fromPath + "\" to \"" + toPath + "\": " + strerror(errno));
785 
786  VDebugLog("%s -> %s (symlink)", fromPath.c_str(), toPath.c_str());
787  }
788 
789  else if (!dirExists(fromPath))
790  copyFile(fromPath, toPath);
791 
792  else
793  {
794  VDebugLog("%s ::", fromPath.c_str());
795  auto files = findAllFilesInDirectory(fromPath);
796  makeDir(toPath);
797  for (auto file : files)
798  {
799  string sourceFile = fromPath + "/" + file->getRelativePath();
800  string targetFile = toPath + "/" + file->getRelativePath();
801  copyDirectory(sourceFile, targetFile);
802  }
803  }
804 }
805 
811 string VuoFileUtilities::calculateFileSHA256(const string &path)
812 {
813  int fd = open(path.c_str(), O_RDONLY);
814  if (fd < 0)
815  throw VuoException("Error: Couldn't open \"" + path + "\": " + strerror(errno));
816  VuoDefer(^{ close(fd); });
817 
818  struct stat stat;
819  if (fstat(fd, &stat) != 0)
820  throw VuoException("Error: Couldn't fstat \"" + path + "\": " + strerror(errno));
821 
822  // Instead of reading the file into a string and calling `VuoStringUtilities::calculateSHA256`,
823  // use `mmap` so the OS can efficiently read parts of the file at a time (reducing memory required).
824  void *data = mmap(nullptr, stat.st_size, PROT_READ, MAP_PRIVATE | MAP_NOCACHE, fd, 0);
825  if (data == MAP_FAILED)
826  throw VuoException("Error: Couldn't mmap \"" + path + "\": " + strerror(errno));
827  VuoDefer(^{ munmap(data, stat.st_size); });
828 
829  unsigned char hash[CC_SHA256_DIGEST_LENGTH];
830  if (!CC_SHA256(data, stat.st_size, hash))
831  throw VuoException("Error: CC_SHA256 failed on file \"" + path + "\"");
832 
833  ostringstream oss;
834  oss << setfill('0') << hex;
835  for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i)
836  oss << setw(2) << (int)hash[i];
837  return oss.str();
838 }
839 
845 {
846  struct stat s;
847  lstat(path.c_str(), &s);
848  return s.st_mtimespec.tv_sec; // s.st_mtimespec.tv_nsec is always 0 on some OSes, hence the resolution of 1 second
849 }
850 
855 {
856  struct stat s;
857  lstat(path.c_str(), &s);
858 
859  struct timeval t;
860  gettimeofday(&t, NULL);
861 
862  return t.tv_sec - s.st_atimespec.tv_sec;
863 }
864 
878 set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInDirectory(string dirPath, set<string> archiveExtensions,
879  bool shouldSearchRecursively)
880 {
881  set<File *> files;
882 
884  dirPath = VuoFileUtilitiesCocoa_resolveMacAlias(dirPath);
885 
886  // `opendir()` automatically dereferences symlinks.
887  DIR *d = opendir(dirPath.c_str());
888  if (! d)
889  {
890  if (access(dirPath.c_str(), F_OK) != -1)
891  throw VuoException(string("Couldn't read directory: ") + strerror(errno) + " — " + dirPath);
892  return files;
893  }
894 
895  struct dirent *de;
896  while( (de=readdir(d)) )
897  {
898  string fileName = de->d_name;
899  string relativeFilePath = dirPath + "/" + fileName;
900 
901  if (fileName == "." || fileName == "..")
902  continue;
903 
904  bool isArchive = false;
905  for (set<string>::iterator archiveExtension = archiveExtensions.begin(); archiveExtension != archiveExtensions.end(); ++archiveExtension)
906  if (VuoStringUtilities::endsWith(fileName, "." + *archiveExtension))
907  isArchive = true;
908 
909  if (isArchive)
910  {
911  set<File *> fs = findAllFilesInArchive(relativeFilePath);
912  if (fs.empty())
913  isArchive = false;
914  else
915  files.insert(fs.begin(), fs.end());
916  }
917 
918  if (! isArchive)
919  {
920  bool shouldSearchDir = shouldSearchRecursively
921  && dirExists(relativeFilePath)
922  && !isSymlink(relativeFilePath);
923  if (shouldSearchDir)
924  {
925  set<File *> filesInDir = findAllFilesInDirectory(relativeFilePath, archiveExtensions, true);
926  for (set<File *>::iterator i = filesInDir.begin(); i != filesInDir.end(); ++i)
927  {
928  File *f = *i;
929  f->dirPath = dirPath;
930  f->filePath = fileName + "/" + f->filePath;
931  }
932  files.insert(filesInDir.begin(), filesInDir.end());
933  }
934  else
935  {
936  File *f = new File(dirPath, fileName);
937  files.insert(f);
938  }
939  }
940  }
941 
942  closedir(d);
943 
944  return files;
945 }
946 
956 set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInDirectory(string dirPath, set<string> extensions, set<string> archiveExtensions)
957 {
958  set<File *> allFiles = findAllFilesInDirectory(dirPath, archiveExtensions);
959  set<File *> matchingFiles;
960 
961  for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
962  {
963  bool endsWithExtension = false;
964  for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
965  if (VuoStringUtilities::endsWith((*i)->getRelativePath(), "." + *extension))
966  endsWithExtension = true;
967 
968  if (endsWithExtension && ((*i)->isInArchive() || !VuoFileUtilities::dirExists((*i)->path())))
969  matchingFiles.insert(*i);
970  else
971  delete *i;
972  }
973 
974  return matchingFiles;
975 }
976 
983 set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInArchive(string archivePath)
984 {
985  set<File *> files;
986 
987  Archive *archive = new Archive(archivePath);
988  if (! archive->zipArchive)
989  {
990  delete archive;
991  return files;
992  }
993 
994  mz_uint numFiles = mz_zip_reader_get_num_files(archive->zipArchive);
995  for (mz_uint i = 0; i < numFiles; ++i)
996  {
997  mz_zip_archive_file_stat file_stat;
998  bool success = mz_zip_reader_file_stat(archive->zipArchive, i, &file_stat);
999  if (! success)
1000  {
1001  VUserLog("Error: Couldn't read file '%u' in zip archive '%s'.", i, archive->path.c_str());
1002  break;
1003  }
1004 
1005  File *f = new File(archive, file_stat.m_filename);
1006  files.insert(f);
1007  }
1008 
1009  if (archive->referenceCount == 0)
1010  delete archive;
1011 
1012  return files;
1013 }
1014 
1024 set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInArchive(string archivePath, string dirPath, set<string> extensions)
1025 {
1026  set<File *> allFiles = findAllFilesInArchive(archivePath);
1027  set<File *> matchingFiles;
1028 
1029  for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
1030  {
1031  File *f = *i;
1032 
1033  string dir, file, ext;
1034  splitPath(f->getRelativePath(), dir, file, ext);
1035  bool beginsWithDir = VuoStringUtilities::beginsWith(dir, dirPath);
1036 
1037  bool endsWithExtension = false;
1038  for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
1039  if (VuoStringUtilities::endsWith(f->getRelativePath(), "." + *extension))
1040  endsWithExtension = true;
1041 
1042  if (beginsWithDir && endsWithExtension)
1043  matchingFiles.insert(f);
1044  else
1045  delete f;
1046  }
1047 
1048  return matchingFiles;
1049 }
1050 
1060 string VuoFileUtilities::getArchiveFileContentsAsString(string archivePath, string filePath)
1061 {
1062  string contents;
1063  set<File *> archiveFiles = findAllFilesInArchive(archivePath);
1064  for (set<File *>::iterator i = archiveFiles.begin(); i != archiveFiles.end(); ++i)
1065  {
1066  File *file = *i;
1067  if (filePath == file->getRelativePath())
1068  contents = file->getContentsAsString();
1069  delete file;
1070  }
1071 
1072  return contents;
1073 }
1074 
1083 {
1085 }
1086 
1093  path(path)
1094 {
1095  this->referenceCount = 0;
1096 
1097  // mz_zip_reader_init_file sometimes ends up with a garbage function pointer if the mz_zip_archive isn't initialized to zeroes
1098  this->zipArchive = (mz_zip_archive *)calloc(1, sizeof(mz_zip_archive));
1099 
1100  bool success = mz_zip_reader_init_file(zipArchive, path.c_str(), 0);
1101  if (! success)
1102  {
1103  free(zipArchive);
1104  zipArchive = NULL;
1105  }
1106 }
1107 
1112 {
1113  if (zipArchive)
1114  {
1115  mz_zip_reader_end(zipArchive);
1116  free(zipArchive);
1117  }
1118 }
1119 
1126 {
1127 }
1128 
1132 VuoFileUtilities::File::File(string dirPath, string filePath) :
1133  filePath(filePath),
1134  dirPath(dirPath)
1135 {
1136  this->fileDescriptor = -1;
1137  this->archive = NULL;
1138 }
1139 
1146 VuoFileUtilities::File::File(Archive *archive, string filePath) :
1147  filePath(filePath)
1148 {
1149  this->fileDescriptor = -1;
1150  this->archive = archive;
1151  ++archive->referenceCount;
1152 }
1153 
1159 {
1160  string newFilePath = basename() + "." + extension;
1161 
1162  if (archive)
1163  return File(archive, newFilePath);
1164  else
1165  return File(dirPath, newFilePath);
1166 }
1167 
1173 {
1174  if (archive && --archive->referenceCount == 0)
1175  delete archive;
1176 }
1177 
1182 {
1183  return (archive != NULL);
1184 }
1185 
1192 {
1193  return (archive ? archive->path : "");
1194 }
1195 
1200 {
1201  return filePath;
1202 }
1203 
1210 {
1211  if (archive)
1212  throw VuoException("Can't return a simple absolute path for a file in an archive.");
1213 
1214  return dirPath + "/" + filePath;
1215 }
1216 
1223 {
1224  if (archive)
1225  throw VuoException("Can't return a simple absolute path for a file in an archive.");
1226 
1227  return dirPath;
1228 }
1229 
1234 {
1235  return filePath.substr(0, filePath.find_last_of('.'));
1236 }
1237 
1242 {
1243  return filePath.substr(filePath.find_last_of('.') + 1);
1244 }
1245 
1250 {
1251  if (archive)
1252  return mz_zip_reader_locate_file(archive->zipArchive, filePath.c_str(), nullptr, MZ_ZIP_FLAG_CASE_SENSITIVE) != -1;
1253  else
1254  return VuoFileUtilities::fileExists((dirPath.empty() ? "" : (dirPath + "/")) + filePath);
1255 }
1256 
1266 {
1267  char * buffer;
1268  numBytes = 0;
1269 
1270  if (! archive)
1271  {
1272  // http://www.cplusplus.com/reference/cstdio/fread/
1273 
1274  string fullPath = (dirPath.empty() ? "" : (dirPath + "/")) + filePath;
1275 
1276  FILE *pFile = fopen ( fullPath.c_str() , "rb" );
1277  if (pFile==NULL)
1278  throw VuoException(string("Couldn't open file: ") + strerror(errno) + " — " + fullPath);
1279 
1280  // obtain file size:
1281  fseek (pFile , 0 , SEEK_END);
1282  size_t lSize = ftell (pFile);
1283  rewind (pFile);
1284 
1285  // allocate memory to contain the whole file:
1286  buffer = (char*) malloc (sizeof(char)*lSize);
1287 
1288  // copy the file into the buffer:
1289  numBytes = fread (buffer,1,lSize,pFile);
1290  fclose(pFile);
1291  if (numBytes != lSize)
1292  {
1293  free(buffer);
1294  throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + fullPath);
1295  }
1296  }
1297  else
1298  {
1299  buffer = (char *)mz_zip_reader_extract_file_to_heap(archive->zipArchive, filePath.c_str(), &numBytes, 0);
1300  }
1301 
1302  return buffer;
1303 }
1304 
1311 {
1312  size_t numBytes;
1313  char *buffer = getContentsAsRawData(numBytes);
1314  string s(buffer, numBytes);
1315  free(buffer);
1316  return s;
1317 }
1318 
1329 {
1330  if (fileDescriptor < 0)
1331  fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1332  int ret = flock(fileDescriptor, LOCK_SH | (nonBlocking ? LOCK_NB : 0));
1333  return ret == 0;
1334 }
1335 
1346 {
1347  if (fileDescriptor < 0)
1348  fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1349  int ret = flock(fileDescriptor, LOCK_EX | (nonBlocking ? LOCK_NB : 0));
1350  return ret == 0;
1351 }
1352 
1358 {
1359  flock(fileDescriptor, LOCK_UN);
1360  close(fileDescriptor);
1361  fileDescriptor = -1;
1362 }
1363 
1369 void VuoFileUtilities::focusProcess(pid_t pid, bool force)
1370 {
1372 }
1373 
1380 void VuoFileUtilities::executeProcess(vector<string> processAndArgs)
1381 {
1382  string binaryDir, binaryFile, binaryExt;
1383  splitPath(processAndArgs[0], binaryDir, binaryFile, binaryExt);
1384 
1385  string errorPrefix = "Couldn't execute " + binaryFile + ": ";
1386 
1387  // Capture stdout and stderr.
1388  int outputPipe[2];
1389  int ret = pipe(outputPipe);
1390  if (ret)
1391  throw VuoException(errorPrefix + "couldn't open pipe: " + strerror(ret));
1392  int pipeRead = outputPipe[0];
1393  int pipeWrite = outputPipe[1];
1394  VuoDefer(^{ close(pipeRead); });
1395  posix_spawn_file_actions_t fileActions;
1396  posix_spawn_file_actions_init(&fileActions);
1397  posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDOUT_FILENO);
1398  posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDERR_FILENO);
1399  posix_spawn_file_actions_addclose(&fileActions, pipeRead);
1400 
1401  // Convert args.
1402  // posix_spawn requires a null-terminated array, which `&processAndArgs[0]` doesn't guarantee.
1403  char **processAndArgsZ = (char **)malloc(sizeof(char *) * (processAndArgs.size() + 1));
1404  int argCount = 0;
1405  for (auto arg : processAndArgs)
1406  if (argCount == 0)
1407  processAndArgsZ[argCount++] = strdup(binaryFile.c_str());
1408  else
1409  processAndArgsZ[argCount++] = strdup(arg.c_str());
1410  processAndArgsZ[argCount] = nullptr;
1411 
1412  // Execute.
1413  pid_t pid;
1414  ret = posix_spawn(&pid, processAndArgs[0].c_str(), &fileActions, NULL, (char * const *)processAndArgsZ, NULL);
1415  close(pipeWrite);
1416  posix_spawn_file_actions_destroy(&fileActions);
1417  for (int i = 0; i < argCount; ++i)
1418  free(processAndArgsZ[i]);
1419 
1420  if (ret)
1421  throw VuoException(errorPrefix + strerror(ret));
1422  else
1423  {
1424  int status;
1425  if (waitpid(pid, &status, 0) == -1)
1426  throw VuoException(errorPrefix + "waitpid failed: " + strerror(errno));
1427  else if (status != 0)
1428  {
1429  string output;
1430  char buf[256];
1431  bzero(buf, 256);
1432  while (read(pipeRead, &buf, 255) > 0)
1433  {
1434  output += buf;
1435  bzero(buf, 256);
1436  }
1437 
1438  throw VuoException(binaryFile + " failed: " + output);
1439  }
1440  }
1441 }
1442 
1447 {
1448  return extension == "vuo";
1449 }
1450 
1455 {
1456  set<string> e;
1457  e.insert("c");
1458  e.insert("cc");
1459  e.insert("C");
1460  e.insert("cpp");
1461  e.insert("cxx");
1462  e.insert("c++");
1463  e.insert("m");
1464  e.insert("mm");
1465  return e;
1466 }
1467 
1472 {
1473  auto extensions = getCSourceExtensions();
1474  return extensions.find(extension) != extensions.end();
1475 }
1476 
1481 {
1482  set<string> e;
1483  e.insert("fs");
1484  e.insert("vs");
1485  e.insert("vuoshader");
1486  e.insert("vuoobjectfilter");
1487  return e.find(extension) != e.end();
1488 }