Vuo  2.0.2
VuoFileUtilities.cc
Go to the documentation of this file.
1 
10 #include <dirent.h>
11 #include <spawn.h>
12 #include <sys/stat.h>
13 #include <copyfile.h>
14 #include <fstream>
15 #include <iostream>
16 #include <mach-o/dyld.h>
17 #include <sys/time.h>
18 #include "VuoException.hh"
19 #include "VuoFileUtilities.hh"
20 #include "VuoFileUtilitiesCocoa.hh"
21 #include "VuoStringUtilities.hh"
22 
23 
34 void VuoFileUtilities::splitPath(string path, string &dir, string &file, string &extension)
35 {
36  dir = "";
37  file = "";
38  extension = "";
39  const char FILE_SEPARATOR = '/';
40 
41  size_t separatorIndex = path.rfind(FILE_SEPARATOR);
42  string fileAndExtension;
43  if (separatorIndex != string::npos)
44  {
45  dir = path.substr(0, separatorIndex + 1);
46  if (separatorIndex < path.length())
47  fileAndExtension = path.substr(separatorIndex + 1, path.length());
48  }
49  else
50  {
51  fileAndExtension = path;
52  }
53 
54  size_t dotIndex = fileAndExtension.rfind(".");
55  if (dotIndex != string::npos)
56  {
57  file = fileAndExtension.substr(0, dotIndex);
58  extension = fileAndExtension.substr(dotIndex + 1, fileAndExtension.length());
59  }
60  else
61  {
62  file = fileAndExtension;
63  }
64 }
65 
71 {
72  // Remove repeated file separators.
73  for (int i = 0; i < path.length(); ++i)
74  if (path[i] == '/')
75  while (i+1 < path.length() && path[i+1] == '/')
76  path.erase(i+1, 1);
77 
78  // Remove trailing file separator.
79  if (path.length() > 1 && VuoStringUtilities::endsWith(path, "/"))
80  path.erase(path.length()-1);
81 }
82 
89 bool VuoFileUtilities::arePathsEqual(string path1, string path2)
90 {
91  canonicalizePath(path1);
92  canonicalizePath(path2);
93  return path1 == path2;
94 }
95 
99 string VuoFileUtilities::getAbsolutePath(const string &path)
100 {
101  if (VuoStringUtilities::beginsWith(path, "/"))
102  return path;
103 
104  char *cwd = getcwd(NULL, 0);
105  if (! cwd)
106  {
107  VUserLog("Couldn't get current working directory: %s", strerror(errno));
108  return path;
109  }
110 
111  string absolutePath = string(cwd) + "/" + path;
112  free(cwd);
113  return absolutePath;
114 }
115 
123 string VuoFileUtilities::makeTmpFile(string file, string extension, string directory)
124 {
125  if (directory.empty())
126  directory = getTmpDir();
127  if (directory.at(directory.length()-1) != '/')
128  directory += "/";
129  string suffix = (extension.empty() ? "" : ("." + extension));
130  string pathTemplate = directory + file + "-XXXXXX" + suffix;
131  char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
132  strncpy(path, pathTemplate.c_str(), pathTemplate.length());
133  int fd = mkstemps(path, suffix.length());
134  close(fd);
135  string pathStr(path);
136  free(path);
137  return pathStr;
138 }
139 
149 string VuoFileUtilities::makeTmpDir(string prefix)
150 {
151  string pathTemplate = getTmpDir() + "/" + prefix + ".XXXXXX";
152  char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
153  strncpy(path, pathTemplate.c_str(), pathTemplate.length());
154  mkdtemp(path);
155  string pathStr(path);
156  free(path);
157  return pathStr;
158 }
159 
169 {
171 }
172 
185 {
186  char *cwd = getcwd(NULL, 0);
187  if (!cwd)
188  VUserLog("Error in getcwd(): %s", strerror(errno));
189 
190  if (cwd && strstr(cwd, "/Library/Containers/")) // We're in a sandbox.
191  {
192  // https://b33p.net/kosada/node/16374
193  // In the macOS 10.15 screen saver sandbox, at least,
194  // we're not allowed to use the system or user temporary directories for socket-files,
195  // but we are allowed to use the sandbox container directory.
196  string cwdS(cwd);
197  free(cwd);
198  return VuoStringUtilities::endsWith(cwdS, "/") ? VuoStringUtilities::substrBefore(cwdS, "/") : cwdS;
199  }
200  free(cwd);
201 
202  char userTempDir[PATH_MAX];
203  if (confstr(_CS_DARWIN_USER_TEMP_DIR, userTempDir, PATH_MAX) > 0)
204  {
205  string userTempDirS(userTempDir);
206  return VuoStringUtilities::endsWith(userTempDirS, "/") ? VuoStringUtilities::substrBefore(userTempDirS, "/") : userTempDirS;
207  }
208 
209  return "/tmp";
210 }
211 
219 void VuoFileUtilities::makeDir(string path)
220 {
221  if (path.empty())
222  throw VuoException("Couldn't create directory with empty path");
223 
224  if (dirExists(path))
225  return;
226 
227  const char FILE_SEPARATOR = '/';
228  size_t lastNonSeparator = path.find_last_not_of(FILE_SEPARATOR);
229  if (lastNonSeparator != string::npos)
230  path.resize(lastNonSeparator + 1);
231 
232  string parentDir, file, ext;
233  splitPath(path, parentDir, file, ext);
234  makeDir(parentDir);
235 
236  // `mkdir` ANDs the mode with the process's umask,
237  // so by default the created directory's mode will end up being 0755.
238  int ret = mkdir(path.c_str(), 0777);
239  if (ret != 0 && ! dirExists(path))
240  throw VuoException((string("Couldn't create directory \"" + path + "\": ") + strerror(errno)).c_str());
241 }
242 
250 {
251  static string frameworkPath;
252  static dispatch_once_t once = 0;
253  dispatch_once(&once, ^{
254 
255  // First check whether Vuo.framework is in the list of loaded dynamic libraries.
256  const char *frameworkPathFragment = "/Vuo.framework/Versions/";
257  uint32_t imageCount = _dyld_image_count();
258  for (uint32_t i = 0; i < imageCount; ++i)
259  {
260  const char *dylibPath = _dyld_get_image_name(i);
261  const char *found = strstr(dylibPath, frameworkPathFragment);
262  if (found)
263  {
264  char *pathC = strndup(dylibPath, found - dylibPath);
265  string path = string(pathC) + "/Vuo.framework";
266  free(pathC);
267  if (fileExists(path))
268  {
269  frameworkPath = path;
270  return;
271  }
272  }
273  }
274 
275  // Failing that, check for a Vuo.framework bundled with the executable app.
276  char executablePath[PATH_MAX + 1];
277  uint32_t size = sizeof(executablePath);
278 
279  if (! _NSGetExecutablePath(executablePath, &size))
280  {
281  char cleanedExecutablePath[PATH_MAX + 1];
282 
283  realpath(executablePath, cleanedExecutablePath); // remove extra references (e.g., "/./")
284  string path = cleanedExecutablePath;
285  string dir, file, ext;
286  splitPath(path, dir, file, ext); // remove executable name
287  path = dir.substr(0, dir.length() - 1); // remove "/"
288  splitPath(path, dir, file, ext); // remove "MacOS"
289  path = dir.substr(0, dir.length() - 1); // remove "/"
290  path += "/Frameworks/Vuo.framework";
291 
292  if (fileExists(path))
293  {
294  frameworkPath = path;
295  return;
296  }
297  }
298 
299  // Give up.
300  });
301 
302  return frameworkPath;
303 }
304 
310 {
311  static string runnerFrameworkPath;
312  static dispatch_once_t once = 0;
313  dispatch_once(&once, ^{
314  // Check for VuoRunner.framework alongside Vuo.framework.
315  string possibleFrameworkPath = getVuoFrameworkPath() + "/../VuoRunner.framework";
316  if (dirExists(possibleFrameworkPath))
317  {
318  runnerFrameworkPath = possibleFrameworkPath;
319  return;
320  }
321 
322  // Failing that, check whether VuoRunner.framework is in the list of loaded dynamic libraries.
323  const char *frameworkPathFragment = "/VuoRunner.framework/Versions/";
324  uint32_t imageCount = _dyld_image_count();
325  for (uint32_t i = 0; i < imageCount; ++i)
326  {
327  const char *dylibPath = _dyld_get_image_name(i);
328  const char *found = strstr(dylibPath, frameworkPathFragment);
329  if (found)
330  {
331  char *pathC = strndup(dylibPath, found - dylibPath);
332  string path = string(pathC) + "/VuoRunner.framework";
333  free(pathC);
334  if (fileExists(path))
335  {
336  runnerFrameworkPath = path;
337  return;
338  }
339  }
340  }
341 
342  // Failing that, check for a VuoRunner.framework bundled with the executable app.
343  char executablePath[PATH_MAX + 1];
344  uint32_t size = sizeof(executablePath);
345 
346  if (! _NSGetExecutablePath(executablePath, &size))
347  {
348  char cleanedExecutablePath[PATH_MAX + 1];
349 
350  realpath(executablePath, cleanedExecutablePath); // remove extra references (e.g., "/./")
351  string path = cleanedExecutablePath;
352  string dir, file, ext;
353  splitPath(path, dir, file, ext); // remove executable name
354  path = dir.substr(0, dir.length() - 1); // remove "/"
355  splitPath(path, dir, file, ext); // remove "MacOS"
356  path = dir.substr(0, dir.length() - 1); // remove "/"
357  path += "/Frameworks/VuoRunner.framework";
358 
359  if (fileExists(path))
360  {
361  runnerFrameworkPath = path;
362  return;
363  }
364  }
365 
366  // Give up.
367  });
368  return runnerFrameworkPath;
369 }
370 
379 string VuoFileUtilities::getCompositionLocalModulesPath(const string &compositionPath)
380 {
381  string compositionDir, compositionFile, ext;
382  splitPath(getAbsolutePath(compositionPath), compositionDir, compositionFile, ext);
383  canonicalizePath(compositionDir);
384 
385  string parentDir, compositionDirName;
386  splitPath(compositionDir, parentDir, compositionDirName, ext);
387  canonicalizePath(compositionDirName);
388 
389  string compositionBaseDir = (compositionDirName == "Modules" ? parentDir : compositionDir);
390 
391  string compositionModulesDir = compositionBaseDir + "/Modules";
392  canonicalizePath(compositionModulesDir);
393 
394  return compositionModulesDir;
395 }
396 
401 {
402  return string(getenv("HOME")) + "/Library/Application Support/Vuo/Modules";
403 }
404 
409 {
410  return "/Library/Application Support/Vuo/Modules";
411 }
412 
417 {
418  return string(getenv("HOME")) + "/Library/Caches/org.vuo/" + VUO_VERSION_AND_BUILD_STRING;
419 }
420 
425 {
426  if (path.empty())
427  return false;
428 
429  string compositionLocalModulesPath = getCompositionLocalModulesPath(path);
430 
431  string dir, file, ext;
432  VuoFileUtilities::splitPath(path, dir, file, ext);
433 
434  return VuoFileUtilities::arePathsEqual(compositionLocalModulesPath, dir);
435 }
436 
445 void VuoFileUtilities::preserveOriginalFileName(string &fileContents, string originalFileName)
446 {
447  fileContents.insert(getFirstInsertionIndex(fileContents), "#line 1 \"" + originalFileName + "\"\n");
448 }
449 
455 {
456  string bomUtf8 = "\xEF\xBB\xBF";
457  string bomUtf16Be = "\xFE\xFF";
458  string bomUtf16Le = "\xFF\xFE";
459  string boms[] = { bomUtf8, bomUtf16Be, bomUtf16Le };
460  for (int i = 0; i < 3; ++i)
461  {
462  bool isMatch = true;
463  for (int j = 0; j < boms[i].length(); ++j)
464  if (boms[i][j] != s[j])
465  isMatch = false;
466 
467  if (isMatch)
468  return boms[i].length();
469  }
470  return 0;
471 }
472 
477 {
478  // https://stackoverflow.com/questions/201992/how-to-read-until-eof-from-cin-in-c
479 
480  string contents;
481  string line;
482  while (getline(cin, line))
483  contents += line;
484 
485  return contents;
486 }
487 
494 {
495  // https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring
496 
497  ifstream in(path.c_str(), ios::in | ios::binary);
498  if (! in)
499  throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + path);
500 
501  string contents;
502  in.seekg(0, ios::end);
503  contents.resize(in.tellg());
504  in.seekg(0, ios::beg);
505  in.read(&contents[0], contents.size());
506  in.close();
507  return contents;
508 }
509 
517 void VuoFileUtilities::writeRawDataToFile(const char *data, size_t numBytes, string file)
518 {
519  FILE *f = fopen(file.c_str(), "wb");
520  if (! f)
521  throw VuoException(string("Couldn't open file for writing: ") + strerror(errno) + " — " + file);
522 
523  size_t numBytesWritten = fwrite(data, sizeof(char), numBytes, f);
524  if (numBytesWritten != numBytes)
525  {
526  fclose(f);
527  throw VuoException(string("Couldn't write all data: ") + strerror(errno) + " — " + file);
528  }
529 
530  fclose(f);
531 }
532 
540 void VuoFileUtilities::writeStringToFile(string s, string file)
541 {
542  writeRawDataToFile(s.data(), s.length(), file);
543 }
544 
553 void VuoFileUtilities::writeStringToFileSafely(string s, string path)
554 {
555  string dir, file, ext;
556  splitPath(path, dir, file, ext);
557  string tmpPath = makeTmpFile("." + file, ext, dir);
558 
559  writeStringToFile(s, tmpPath);
560 
561  moveFile(tmpPath, path);
562 }
563 
570 {
573 
574  // `access()` automatically dereferences symlinks.
575  return access(path.c_str(), 0) == 0;
576 }
577 
584 {
587 
588  // `stat()` automatically dereferences symlinks.
589  struct stat st_buf;
590  int status = stat(path.c_str(), &st_buf);
591  return (! status && S_ISDIR(st_buf.st_mode));
592 }
593 
598 {
599  struct stat st_buf;
600  int status = lstat(path.c_str(), &st_buf);
601  return (! status && S_ISLNK(st_buf.st_mode));
602 }
603 
610 {
613 
614  // `access()` automatically dereferences symlinks.
615  return access(path.c_str(), R_OK) == 0;
616 }
617 
624 {
627 
628  // `fopen()` automatically dereferences symlinks.
629  FILE *f = fopen(path.c_str(), "rb");
630  if (!f)
631  return false;
632 
633  fseek(f, 0, SEEK_END);
634  long pos = ftell(f);
635  if (pos <= 0)
636  {
637  fclose(f);
638  return false;
639  }
640 
641  // `fopen` and `fseek` both succeed on directories, so also attempt to actually read the data.
642  fseek(f, 0, SEEK_SET);
643  char z;
644  size_t ret = fread(&z, 1, 1, f);
645  fclose(f);
646  return ret > 0;
647 }
648 
653 {
654  FILE *f = fopen(path.c_str(), "a");
655  fclose(f);
656 }
657 
662 {
663  int ret = remove(path.c_str());
664  if (ret != 0 && fileExists(path))
665  VUserLog("Couldn't delete file: %s — %s", strerror(errno), path.c_str());
666 }
667 
672 {
673  if (! dirExists(path))
674  return;
675 
676  DIR *d = opendir(path.c_str());
677  if (! d)
678  {
679  VUserLog("Couldn't read directory: %s — %s", strerror(errno), path.c_str());
680  return;
681  }
682 
683  struct dirent *de;
684  while( (de=readdir(d)) )
685  {
686  string fileName = de->d_name;
687  string filePath = path + "/" + fileName;
688 
689  if (fileName == "." || fileName == "..")
690  continue;
691 
692  if (dirExists(filePath))
693  deleteDir(filePath);
694  else
695  deleteFile(filePath);
696  }
697 
698  closedir(d);
699 
700  deleteFile(path);
701 }
702 
708 void VuoFileUtilities::moveFile(string fromPath, string toPath)
709 {
710  int ret = rename(fromPath.c_str(), toPath.c_str());
711  if (ret != 0)
712  throw VuoException(string("Couldn't move file: ") + strerror(errno) + " — " + toPath);
713 }
714 
721 {
723 }
724 
736 void VuoFileUtilities::copyFile(string fromPath, string toPath, bool preserveMetadata)
737 {
738  int i = open(fromPath.c_str(), O_RDONLY);
739  if (i == -1)
740  throw VuoException(string("Couldn't open copy source: ") + strerror(errno) + " — " + fromPath);
741 
742  struct stat s;
743  fstat(i, &s);
744  int o = open(toPath.c_str(), O_WRONLY | O_CREAT, s.st_mode & 0777);
745  if (o == -1)
746  {
747  close(i);
748  throw VuoException(string("Couldn't open copy destination: ") + strerror(errno) + " — " + toPath);
749  }
750 
751  int ret = fcopyfile(i, o, NULL, COPYFILE_DATA | (preserveMetadata ? COPYFILE_STAT : 0));
752  char *e = strerror(errno);
753  close(o);
754  close(i);
755 
756  if (ret)
757  throw VuoException(string("Couldn't copy ") + fromPath + " to " + toPath + ": " + e);
758 }
759 
769 void VuoFileUtilities::copyDirectory(string fromPath, string toPath)
770 {
771  if (isSymlink(fromPath))
772  {
773  // Preserve the relative symlinks found in framework bundles, instead of flattening them.
774  char linkDestination[PATH_MAX + 1];
775  ssize_t len = readlink(fromPath.c_str(), linkDestination, PATH_MAX);
776  linkDestination[len] = 0; // "readlink does not append a NUL character to buf."
777  if (symlink(linkDestination, toPath.c_str()) == -1)
778  throw VuoException(string("Couldn't copy symlink \"") + fromPath + "\" to \"" + toPath + "\": " + strerror(errno));
779  }
780 
781  else if (!dirExists(fromPath))
782  copyFile(fromPath, toPath);
783 
784  else
785  {
786  auto files = findAllFilesInDirectory(fromPath);
787  makeDir(toPath);
788  for (auto file : files)
789  {
790  string sourceFile = fromPath + "/" + file->getRelativePath();
791  string targetFile = toPath + "/" + file->getRelativePath();
792  copyDirectory(sourceFile, targetFile);
793  }
794  }
795 }
796 
802 {
803  struct stat s;
804  lstat(path.c_str(), &s);
805  return s.st_mtimespec.tv_sec; // s.st_mtimespec.tv_nsec is always 0 on some OSes, hence the resolution of 1 second
806 }
807 
812 {
813  struct stat s;
814  lstat(path.c_str(), &s);
815 
816  struct timeval t;
817  gettimeofday(&t, NULL);
818 
819  return t.tv_sec - s.st_atimespec.tv_sec;
820 }
821 
835 set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInDirectory(string dirPath, set<string> archiveExtensions,
836  bool shouldSearchRecursively)
837 {
838  set<File *> files;
839 
841  dirPath = VuoFileUtilitiesCocoa_resolveMacAlias(dirPath);
842 
843  // `opendir()` automatically dereferences symlinks.
844  DIR *d = opendir(dirPath.c_str());
845  if (! d)
846  {
847  if (access(dirPath.c_str(), F_OK) != -1)
848  throw VuoException(string("Couldn't read directory: ") + strerror(errno) + " — " + dirPath);
849  return files;
850  }
851 
852  struct dirent *de;
853  while( (de=readdir(d)) )
854  {
855  string fileName = de->d_name;
856  string relativeFilePath = dirPath + "/" + fileName;
857 
858  if (fileName == "." || fileName == "..")
859  continue;
860 
861  bool isArchive = false;
862  for (set<string>::iterator archiveExtension = archiveExtensions.begin(); archiveExtension != archiveExtensions.end(); ++archiveExtension)
863  if (VuoStringUtilities::endsWith(fileName, "." + *archiveExtension))
864  isArchive = true;
865 
866  if (isArchive)
867  {
868  set<File *> fs = findAllFilesInArchive(relativeFilePath);
869  if (fs.empty())
870  isArchive = false;
871  else
872  files.insert(fs.begin(), fs.end());
873  }
874 
875  if (! isArchive)
876  {
877  bool shouldSearchDir = shouldSearchRecursively
878  && dirExists(relativeFilePath)
879  && !isSymlink(relativeFilePath);
880  if (shouldSearchDir)
881  {
882  set<File *> filesInDir = findAllFilesInDirectory(relativeFilePath, archiveExtensions, true);
883  for (set<File *>::iterator i = filesInDir.begin(); i != filesInDir.end(); ++i)
884  {
885  File *f = *i;
886  f->dirPath = dirPath;
887  f->filePath = fileName + "/" + f->filePath;
888  }
889  files.insert(filesInDir.begin(), filesInDir.end());
890  }
891  else
892  {
893  File *f = new File(dirPath, fileName);
894  files.insert(f);
895  }
896  }
897  }
898 
899  closedir(d);
900 
901  return files;
902 }
903 
913 set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInDirectory(string dirPath, set<string> extensions, set<string> archiveExtensions)
914 {
915  set<File *> allFiles = findAllFilesInDirectory(dirPath, archiveExtensions);
916  set<File *> matchingFiles;
917 
918  for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
919  {
920  bool endsWithExtension = false;
921  for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
922  if (VuoStringUtilities::endsWith((*i)->getRelativePath(), "." + *extension))
923  endsWithExtension = true;
924 
925  if (endsWithExtension && ((*i)->isInArchive() || !VuoFileUtilities::dirExists((*i)->path())))
926  matchingFiles.insert(*i);
927  else
928  delete *i;
929  }
930 
931  return matchingFiles;
932 }
933 
940 set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInArchive(string archivePath)
941 {
942  set<File *> files;
943 
944  Archive *archive = new Archive(archivePath);
945  if (! archive->zipArchive)
946  {
947  delete archive;
948  return files;
949  }
950 
951  mz_uint numFiles = mz_zip_reader_get_num_files(archive->zipArchive);
952  for (mz_uint i = 0; i < numFiles; ++i)
953  {
954  mz_zip_archive_file_stat file_stat;
955  bool success = mz_zip_reader_file_stat(archive->zipArchive, i, &file_stat);
956  if (! success)
957  {
958  VUserLog("Error: Couldn't read file '%u' in zip archive '%s'.", i, archive->path.c_str());
959  break;
960  }
961 
962  File *f = new File(archive, file_stat.m_filename);
963  files.insert(f);
964  }
965 
966  if (archive->referenceCount == 0)
967  delete archive;
968 
969  return files;
970 }
971 
981 set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInArchive(string archivePath, string dirPath, set<string> extensions)
982 {
983  set<File *> allFiles = findAllFilesInArchive(archivePath);
984  set<File *> matchingFiles;
985 
986  for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
987  {
988  File *f = *i;
989 
990  string dir, file, ext;
991  splitPath(f->getRelativePath(), dir, file, ext);
992  bool beginsWithDir = VuoStringUtilities::beginsWith(dir, dirPath);
993 
994  bool endsWithExtension = false;
995  for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
996  if (VuoStringUtilities::endsWith(f->getRelativePath(), "." + *extension))
997  endsWithExtension = true;
998 
999  if (beginsWithDir && endsWithExtension)
1000  matchingFiles.insert(f);
1001  else
1002  delete f;
1003  }
1004 
1005  return matchingFiles;
1006 }
1007 
1017 string VuoFileUtilities::getArchiveFileContentsAsString(string archivePath, string filePath)
1018 {
1019  string contents;
1020  set<File *> archiveFiles = findAllFilesInArchive(archivePath);
1021  for (set<File *>::iterator i = archiveFiles.begin(); i != archiveFiles.end(); ++i)
1022  {
1023  File *file = *i;
1024  if (filePath == file->getRelativePath())
1025  contents = file->getContentsAsString();
1026  delete file;
1027  }
1028 
1029  return contents;
1030 }
1031 
1040 {
1042 }
1043 
1050  path(path)
1051 {
1052  this->referenceCount = 0;
1053 
1054  // mz_zip_reader_init_file sometimes ends up with a garbage function pointer if the mz_zip_archive isn't initialized to zeroes
1055  this->zipArchive = (mz_zip_archive *)calloc(1, sizeof(mz_zip_archive));
1056 
1057  bool success = mz_zip_reader_init_file(zipArchive, path.c_str(), 0);
1058  if (! success)
1059  {
1060  free(zipArchive);
1061  zipArchive = NULL;
1062  }
1063 }
1064 
1069 {
1070  if (zipArchive)
1071  {
1072  mz_zip_reader_end(zipArchive);
1073  free(zipArchive);
1074  }
1075 }
1076 
1083 {
1084 }
1085 
1089 VuoFileUtilities::File::File(string dirPath, string filePath) :
1090  filePath(filePath),
1091  dirPath(dirPath)
1092 {
1093  this->fileDescriptor = -1;
1094  this->archive = NULL;
1095 }
1096 
1103 VuoFileUtilities::File::File(Archive *archive, string filePath) :
1104  filePath(filePath)
1105 {
1106  this->fileDescriptor = -1;
1107  this->archive = archive;
1108  ++archive->referenceCount;
1109 }
1110 
1116 {
1117  string newFilePath = basename() + "." + extension;
1118 
1119  if (archive)
1120  return File(archive, newFilePath);
1121  else
1122  return File(dirPath, newFilePath);
1123 }
1124 
1130 {
1131  if (archive && --archive->referenceCount == 0)
1132  delete archive;
1133 }
1134 
1139 {
1140  return (archive != NULL);
1141 }
1142 
1149 {
1150  return (archive ? archive->path : "");
1151 }
1152 
1157 {
1158  return filePath;
1159 }
1160 
1167 {
1168  if (archive)
1169  throw VuoException("Can't return a simple absolute path for a file in an archive.");
1170 
1171  return dirPath + "/" + filePath;
1172 }
1173 
1180 {
1181  if (archive)
1182  throw VuoException("Can't return a simple absolute path for a file in an archive.");
1183 
1184  return dirPath;
1185 }
1186 
1191 {
1192  return filePath.substr(0, filePath.find_last_of('.'));
1193 }
1194 
1199 {
1200  return filePath.substr(filePath.find_last_of('.') + 1);
1201 }
1202 
1207 {
1208  if (archive)
1209  return mz_zip_reader_locate_file(archive->zipArchive, filePath.c_str(), nullptr, MZ_ZIP_FLAG_CASE_SENSITIVE) != -1;
1210  else
1211  return VuoFileUtilities::fileExists((dirPath.empty() ? "" : (dirPath + "/")) + filePath);
1212 }
1213 
1223 {
1224  char * buffer;
1225  numBytes = 0;
1226 
1227  if (! archive)
1228  {
1229  // http://www.cplusplus.com/reference/cstdio/fread/
1230 
1231  string fullPath = (dirPath.empty() ? "" : (dirPath + "/")) + filePath;
1232 
1233  FILE *pFile = fopen ( fullPath.c_str() , "rb" );
1234  if (pFile==NULL)
1235  throw VuoException(string("Couldn't open file: ") + strerror(errno) + " — " + fullPath);
1236 
1237  // obtain file size:
1238  fseek (pFile , 0 , SEEK_END);
1239  size_t lSize = ftell (pFile);
1240  rewind (pFile);
1241 
1242  // allocate memory to contain the whole file:
1243  buffer = (char*) malloc (sizeof(char)*lSize);
1244 
1245  // copy the file into the buffer:
1246  numBytes = fread (buffer,1,lSize,pFile);
1247  fclose(pFile);
1248  if (numBytes != lSize)
1249  {
1250  free(buffer);
1251  throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + fullPath);
1252  }
1253  }
1254  else
1255  {
1256  buffer = (char *)mz_zip_reader_extract_file_to_heap(archive->zipArchive, filePath.c_str(), &numBytes, 0);
1257  }
1258 
1259  return buffer;
1260 }
1261 
1268 {
1269  size_t numBytes;
1270  char *buffer = getContentsAsRawData(numBytes);
1271  string s(buffer, numBytes);
1272  free(buffer);
1273  return s;
1274 }
1275 
1286 {
1287  if (fileDescriptor < 0)
1288  fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1289  int ret = flock(fileDescriptor, LOCK_SH | (nonBlocking ? LOCK_NB : 0));
1290  return ret == 0;
1291 }
1292 
1303 {
1304  if (fileDescriptor < 0)
1305  fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1306  int ret = flock(fileDescriptor, LOCK_EX | (nonBlocking ? LOCK_NB : 0));
1307  return ret == 0;
1308 }
1309 
1315 {
1316  flock(fileDescriptor, LOCK_UN);
1317  close(fileDescriptor);
1318  fileDescriptor = -1;
1319 }
1320 
1326 void VuoFileUtilities::focusProcess(pid_t pid, bool force)
1327 {
1329 }
1330 
1337 void VuoFileUtilities::executeProcess(vector<string> processAndArgs)
1338 {
1339  string binaryDir, binaryFile, binaryExt;
1340  splitPath(processAndArgs[0], binaryDir, binaryFile, binaryExt);
1341 
1342  string errorPrefix = "Couldn't execute " + binaryFile + ": ";
1343 
1344  // Capture stdout and stderr.
1345  int outputPipe[2];
1346  int ret = pipe(outputPipe);
1347  if (ret)
1348  throw VuoException(errorPrefix + "couldn't open pipe: " + strerror(ret));
1349  int pipeRead = outputPipe[0];
1350  int pipeWrite = outputPipe[1];
1351  VuoDefer(^{ close(pipeRead); });
1352  posix_spawn_file_actions_t fileActions;
1353  posix_spawn_file_actions_init(&fileActions);
1354  posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDOUT_FILENO);
1355  posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDERR_FILENO);
1356  posix_spawn_file_actions_addclose(&fileActions, pipeRead);
1357 
1358  // Convert args.
1359  // posix_spawn requires a null-terminated array, which `&processAndArgs[0]` doesn't guarantee.
1360  char **processAndArgsZ = (char **)malloc(sizeof(char *) * (processAndArgs.size() + 1));
1361  int argCount = 0;
1362  for (auto arg : processAndArgs)
1363  if (argCount == 0)
1364  processAndArgsZ[argCount++] = strdup(binaryFile.c_str());
1365  else
1366  processAndArgsZ[argCount++] = strdup(arg.c_str());
1367  processAndArgsZ[argCount] = nullptr;
1368 
1369  // Execute.
1370  pid_t pid;
1371  ret = posix_spawn(&pid, processAndArgs[0].c_str(), &fileActions, NULL, (char * const *)processAndArgsZ, NULL);
1372  close(pipeWrite);
1373  posix_spawn_file_actions_destroy(&fileActions);
1374  for (int i = 0; i < argCount; ++i)
1375  free(processAndArgsZ[i]);
1376 
1377  if (ret)
1378  throw VuoException(errorPrefix + strerror(ret));
1379  else
1380  {
1381  int status;
1382  if (waitpid(pid, &status, 0) == -1)
1383  throw VuoException(errorPrefix + "waitpid failed: " + strerror(errno));
1384  else if (status != 0)
1385  {
1386  string output;
1387  char buf[256];
1388  bzero(buf, 256);
1389  while (read(pipeRead, &buf, 255) > 0)
1390  {
1391  output += buf;
1392  bzero(buf, 256);
1393  }
1394 
1395  throw VuoException(binaryFile + " failed: " + output);
1396  }
1397  }
1398 }
1399 
1404 {
1405  return extension == "vuo";
1406 }
1407 
1412 {
1413  set<string> e;
1414  e.insert("c");
1415  e.insert("cc");
1416  e.insert("C");
1417  e.insert("cpp");
1418  e.insert("cxx");
1419  e.insert("c++");
1420  e.insert("m");
1421  e.insert("mm");
1422  return e;
1423 }
1424 
1429 {
1430  auto extensions = getCSourceExtensions();
1431  return extensions.find(extension) != extensions.end();
1432 }
1433 
1438 {
1439  set<string> e;
1440  e.insert("fs");
1441  e.insert("vs");
1442  e.insert("vuoshader");
1443  e.insert("vuoobjectfilter");
1444  return e.find(extension) != e.end();
1445 }