Vuo  2.0.0
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 (strstr(cwd, "/Library/Containers/")) // We're in a sandbox.
188  {
189  // https://b33p.net/kosada/node/16374
190  // In the macOS 10.15 screen saver sandbox, at least,
191  // we're not allowed to use the system or user temporary directories for socket-files,
192  // but we are allowed to use the sandbox container directory.
193  string cwdS(cwd);
194  free(cwd);
195  return VuoStringUtilities::endsWith(cwdS, "/") ? VuoStringUtilities::substrBefore(cwdS, "/") : cwdS;
196  }
197  free(cwd);
198 
199  char userTempDir[PATH_MAX];
200  if (confstr(_CS_DARWIN_USER_TEMP_DIR, userTempDir, PATH_MAX) > 0)
201  {
202  string userTempDirS(userTempDir);
203  return VuoStringUtilities::endsWith(userTempDirS, "/") ? VuoStringUtilities::substrBefore(userTempDirS, "/") : userTempDirS;
204  }
205 
206  return "/tmp";
207 }
208 
216 void VuoFileUtilities::makeDir(string path)
217 {
218  if (path.empty())
219  throw VuoException("Couldn't create directory with empty path");
220 
221  if (dirExists(path))
222  return;
223 
224  const char FILE_SEPARATOR = '/';
225  size_t lastNonSeparator = path.find_last_not_of(FILE_SEPARATOR);
226  if (lastNonSeparator != string::npos)
227  path.resize(lastNonSeparator + 1);
228 
229  string parentDir, file, ext;
230  splitPath(path, parentDir, file, ext);
231  makeDir(parentDir);
232 
233  // `mkdir` ANDs the mode with the process's umask,
234  // so by default the created directory's mode will end up being 0755.
235  int ret = mkdir(path.c_str(), 0777);
236  if (ret != 0 && ! dirExists(path))
237  throw VuoException((string("Couldn't create directory \"" + path + "\": ") + strerror(errno)).c_str());
238 }
239 
247 {
248  static string frameworkPath;
249  static dispatch_once_t once = 0;
250  dispatch_once(&once, ^{
251 
252  // First check whether Vuo.framework is in the list of loaded dynamic libraries.
253  const char *frameworkPathFragment = "/Vuo.framework/Versions/";
254  uint32_t imageCount = _dyld_image_count();
255  for (uint32_t i = 0; i < imageCount; ++i)
256  {
257  const char *dylibPath = _dyld_get_image_name(i);
258  const char *found = strstr(dylibPath, frameworkPathFragment);
259  if (found)
260  {
261  char *pathC = strndup(dylibPath, found - dylibPath);
262  string path = string(pathC) + "/Vuo.framework";
263  free(pathC);
264  if (fileExists(path))
265  {
266  frameworkPath = path;
267  return;
268  }
269  }
270  }
271 
272  // Failing that, check for a Vuo.framework bundled with the executable app.
273  char executablePath[PATH_MAX + 1];
274  uint32_t size = sizeof(executablePath);
275 
276  if (! _NSGetExecutablePath(executablePath, &size))
277  {
278  char cleanedExecutablePath[PATH_MAX + 1];
279 
280  realpath(executablePath, cleanedExecutablePath); // remove extra references (e.g., "/./")
281  string path = cleanedExecutablePath;
282  string dir, file, ext;
283  splitPath(path, dir, file, ext); // remove executable name
284  path = dir.substr(0, dir.length() - 1); // remove "/"
285  splitPath(path, dir, file, ext); // remove "MacOS"
286  path = dir.substr(0, dir.length() - 1); // remove "/"
287  path += "/Frameworks/Vuo.framework";
288 
289  if (fileExists(path))
290  {
291  frameworkPath = path;
292  return;
293  }
294  }
295 
296  // Give up.
297  });
298 
299  return frameworkPath;
300 }
301 
307 {
308  static string runnerFrameworkPath;
309  static dispatch_once_t once = 0;
310  dispatch_once(&once, ^{
311  // Check for VuoRunner.framework alongside Vuo.framework.
312  string possibleFrameworkPath = getVuoFrameworkPath() + "/../VuoRunner.framework";
313  if (dirExists(possibleFrameworkPath))
314  {
315  runnerFrameworkPath = possibleFrameworkPath;
316  return;
317  }
318 
319  // Failing that, check whether VuoRunner.framework is in the list of loaded dynamic libraries.
320  const char *frameworkPathFragment = "/VuoRunner.framework/Versions/";
321  uint32_t imageCount = _dyld_image_count();
322  for (uint32_t i = 0; i < imageCount; ++i)
323  {
324  const char *dylibPath = _dyld_get_image_name(i);
325  const char *found = strstr(dylibPath, frameworkPathFragment);
326  if (found)
327  {
328  char *pathC = strndup(dylibPath, found - dylibPath);
329  string path = string(pathC) + "/VuoRunner.framework";
330  free(pathC);
331  if (fileExists(path))
332  {
333  runnerFrameworkPath = path;
334  return;
335  }
336  }
337  }
338 
339  // Failing that, check for a VuoRunner.framework bundled with the executable app.
340  char executablePath[PATH_MAX + 1];
341  uint32_t size = sizeof(executablePath);
342 
343  if (! _NSGetExecutablePath(executablePath, &size))
344  {
345  char cleanedExecutablePath[PATH_MAX + 1];
346 
347  realpath(executablePath, cleanedExecutablePath); // remove extra references (e.g., "/./")
348  string path = cleanedExecutablePath;
349  string dir, file, ext;
350  splitPath(path, dir, file, ext); // remove executable name
351  path = dir.substr(0, dir.length() - 1); // remove "/"
352  splitPath(path, dir, file, ext); // remove "MacOS"
353  path = dir.substr(0, dir.length() - 1); // remove "/"
354  path += "/Frameworks/VuoRunner.framework";
355 
356  if (fileExists(path))
357  {
358  runnerFrameworkPath = path;
359  return;
360  }
361  }
362 
363  // Give up.
364  });
365  return runnerFrameworkPath;
366 }
367 
376 string VuoFileUtilities::getCompositionLocalModulesPath(const string &compositionPath)
377 {
378  string compositionDir, compositionFile, ext;
379  splitPath(getAbsolutePath(compositionPath), compositionDir, compositionFile, ext);
380  canonicalizePath(compositionDir);
381 
382  string parentDir, compositionDirName;
383  splitPath(compositionDir, parentDir, compositionDirName, ext);
384  canonicalizePath(compositionDirName);
385 
386  string compositionBaseDir = (compositionDirName == "Modules" ? parentDir : compositionDir);
387 
388  string compositionModulesDir = compositionBaseDir + "/Modules";
389  canonicalizePath(compositionModulesDir);
390 
391  return compositionModulesDir;
392 }
393 
398 {
399  return string(getenv("HOME")) + "/Library/Application Support/Vuo/Modules";
400 }
401 
406 {
407  return "/Library/Application Support/Vuo/Modules";
408 }
409 
414 {
415  return string(getenv("HOME")) + "/Library/Caches/org.vuo/" + VUO_VERSION_AND_BUILD_STRING;
416 }
417 
422 {
423  if (path.empty())
424  return false;
425 
426  string compositionLocalModulesPath = getCompositionLocalModulesPath(path);
427 
428  string dir, file, ext;
429  VuoFileUtilities::splitPath(path, dir, file, ext);
430 
431  return VuoFileUtilities::arePathsEqual(compositionLocalModulesPath, dir);
432 }
433 
442 void VuoFileUtilities::preserveOriginalFileName(string &fileContents, string originalFileName)
443 {
444  fileContents.insert(getFirstInsertionIndex(fileContents), "#line 1 \"" + originalFileName + "\"\n");
445 }
446 
452 {
453  string bomUtf8 = "\xEF\xBB\xBF";
454  string bomUtf16Be = "\xFE\xFF";
455  string bomUtf16Le = "\xFF\xFE";
456  string boms[] = { bomUtf8, bomUtf16Be, bomUtf16Le };
457  for (int i = 0; i < 3; ++i)
458  {
459  bool isMatch = true;
460  for (int j = 0; j < boms[i].length(); ++j)
461  if (boms[i][j] != s[j])
462  isMatch = false;
463 
464  if (isMatch)
465  return boms[i].length();
466  }
467  return 0;
468 }
469 
474 {
475  // https://stackoverflow.com/questions/201992/how-to-read-until-eof-from-cin-in-c
476 
477  string contents;
478  string line;
479  while (getline(cin, line))
480  contents += line;
481 
482  return contents;
483 }
484 
491 {
492  // https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring
493 
494  ifstream in(path.c_str(), ios::in | ios::binary);
495  if (! in)
496  throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + path);
497 
498  string contents;
499  in.seekg(0, ios::end);
500  contents.resize(in.tellg());
501  in.seekg(0, ios::beg);
502  in.read(&contents[0], contents.size());
503  in.close();
504  return contents;
505 }
506 
514 void VuoFileUtilities::writeRawDataToFile(const char *data, size_t numBytes, string file)
515 {
516  FILE *f = fopen(file.c_str(), "wb");
517  if (! f)
518  throw VuoException(string("Couldn't open file for writing: ") + strerror(errno) + " — " + file);
519 
520  size_t numBytesWritten = fwrite(data, sizeof(char), numBytes, f);
521  if (numBytesWritten != numBytes)
522  {
523  fclose(f);
524  throw VuoException(string("Couldn't write all data: ") + strerror(errno) + " — " + file);
525  }
526 
527  fclose(f);
528 }
529 
537 void VuoFileUtilities::writeStringToFile(string s, string file)
538 {
539  writeRawDataToFile(s.data(), s.length(), file);
540 }
541 
550 void VuoFileUtilities::writeStringToFileSafely(string s, string path)
551 {
552  string dir, file, ext;
553  splitPath(path, dir, file, ext);
554  string tmpPath = makeTmpFile("." + file, ext, dir);
555 
556  writeStringToFile(s, tmpPath);
557 
558  moveFile(tmpPath, path);
559 }
560 
567 {
570 
571  // `access()` automatically dereferences symlinks.
572  return access(path.c_str(), 0) == 0;
573 }
574 
581 {
584 
585  // `stat()` automatically dereferences symlinks.
586  struct stat st_buf;
587  int status = stat(path.c_str(), &st_buf);
588  return (! status && S_ISDIR(st_buf.st_mode));
589 }
590 
595 {
596  struct stat st_buf;
597  int status = lstat(path.c_str(), &st_buf);
598  return (! status && S_ISLNK(st_buf.st_mode));
599 }
600 
607 {
610 
611  // `access()` automatically dereferences symlinks.
612  return access(path.c_str(), R_OK) == 0;
613 }
614 
621 {
624 
625  // `fopen()` automatically dereferences symlinks.
626  FILE *f = fopen(path.c_str(), "rb");
627  if (!f)
628  return false;
629 
630  fseek(f, 0, SEEK_END);
631  long pos = ftell(f);
632  if (pos <= 0)
633  {
634  fclose(f);
635  return false;
636  }
637 
638  // `fopen` and `fseek` both succeed on directories, so also attempt to actually read the data.
639  fseek(f, 0, SEEK_SET);
640  char z;
641  size_t ret = fread(&z, 1, 1, f);
642  fclose(f);
643  return ret > 0;
644 }
645 
650 {
651  FILE *f = fopen(path.c_str(), "a");
652  fclose(f);
653 }
654 
659 {
660  int ret = remove(path.c_str());
661  if (ret != 0 && fileExists(path))
662  VUserLog("Couldn't delete file: %s — %s", strerror(errno), path.c_str());
663 }
664 
669 {
670  if (! dirExists(path))
671  return;
672 
673  DIR *d = opendir(path.c_str());
674  if (! d)
675  {
676  VUserLog("Couldn't read directory: %s — %s", strerror(errno), path.c_str());
677  return;
678  }
679 
680  struct dirent *de;
681  while( (de=readdir(d)) )
682  {
683  string fileName = de->d_name;
684  string filePath = path + "/" + fileName;
685 
686  if (fileName == "." || fileName == "..")
687  continue;
688 
689  if (dirExists(filePath))
690  deleteDir(filePath);
691  else
692  deleteFile(filePath);
693  }
694 
695  closedir(d);
696 
697  deleteFile(path);
698 }
699 
705 void VuoFileUtilities::moveFile(string fromPath, string toPath)
706 {
707  int ret = rename(fromPath.c_str(), toPath.c_str());
708  if (ret != 0)
709  throw VuoException(string("Couldn't move file: ") + strerror(errno) + " — " + toPath);
710 }
711 
718 {
720 }
721 
733 void VuoFileUtilities::copyFile(string fromPath, string toPath, bool preserveMetadata)
734 {
735  int i = open(fromPath.c_str(), O_RDONLY);
736  if (i == -1)
737  throw VuoException(string("Couldn't open copy source: ") + strerror(errno) + " — " + fromPath);
738 
739  struct stat s;
740  fstat(i, &s);
741  int o = open(toPath.c_str(), O_WRONLY | O_CREAT, s.st_mode & 0777);
742  if (o == -1)
743  {
744  close(i);
745  throw VuoException(string("Couldn't open copy destination: ") + strerror(errno) + " — " + toPath);
746  }
747 
748  int ret = fcopyfile(i, o, NULL, COPYFILE_DATA | (preserveMetadata ? COPYFILE_STAT : 0));
749  char *e = strerror(errno);
750  close(o);
751  close(i);
752 
753  if (ret)
754  throw VuoException(string("Couldn't copy ") + fromPath + " to " + toPath + ": " + e);
755 }
756 
766 void VuoFileUtilities::copyDirectory(string fromPath, string toPath)
767 {
768  if (isSymlink(fromPath))
769  {
770  // Preserve the relative symlinks found in framework bundles, instead of flattening them.
771  char linkDestination[PATH_MAX + 1];
772  ssize_t len = readlink(fromPath.c_str(), linkDestination, PATH_MAX);
773  linkDestination[len] = 0; // "readlink does not append a NUL character to buf."
774  if (symlink(linkDestination, toPath.c_str()) == -1)
775  throw VuoException(string("Couldn't copy symlink \"") + fromPath + "\" to \"" + toPath + "\": " + strerror(errno));
776  }
777 
778  else if (!dirExists(fromPath))
779  copyFile(fromPath, toPath);
780 
781  else
782  {
783  auto files = findAllFilesInDirectory(fromPath);
784  makeDir(toPath);
785  for (auto file : files)
786  {
787  string sourceFile = fromPath + "/" + file->getRelativePath();
788  string targetFile = toPath + "/" + file->getRelativePath();
789  copyDirectory(sourceFile, targetFile);
790  }
791  }
792 }
793 
799 {
800  struct stat s;
801  lstat(path.c_str(), &s);
802  return s.st_mtimespec.tv_sec; // s.st_mtimespec.tv_nsec is always 0 on some OSes, hence the resolution of 1 second
803 }
804 
809 {
810  struct stat s;
811  lstat(path.c_str(), &s);
812 
813  struct timeval t;
814  gettimeofday(&t, NULL);
815 
816  return t.tv_sec - s.st_atimespec.tv_sec;
817 }
818 
832 set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInDirectory(string dirPath, set<string> archiveExtensions,
833  bool shouldSearchRecursively)
834 {
835  set<File *> files;
836 
838  dirPath = VuoFileUtilitiesCocoa_resolveMacAlias(dirPath);
839 
840  // `opendir()` automatically dereferences symlinks.
841  DIR *d = opendir(dirPath.c_str());
842  if (! d)
843  {
844  if (access(dirPath.c_str(), F_OK) != -1)
845  throw VuoException(string("Couldn't read directory: ") + strerror(errno) + " — " + dirPath);
846  return files;
847  }
848 
849  struct dirent *de;
850  while( (de=readdir(d)) )
851  {
852  string fileName = de->d_name;
853  string relativeFilePath = dirPath + "/" + fileName;
854 
855  if (fileName == "." || fileName == "..")
856  continue;
857 
858  bool isArchive = false;
859  for (set<string>::iterator archiveExtension = archiveExtensions.begin(); archiveExtension != archiveExtensions.end(); ++archiveExtension)
860  if (VuoStringUtilities::endsWith(fileName, "." + *archiveExtension))
861  isArchive = true;
862 
863  if (isArchive)
864  {
865  set<File *> fs = findAllFilesInArchive(relativeFilePath);
866  if (fs.empty())
867  isArchive = false;
868  else
869  files.insert(fs.begin(), fs.end());
870  }
871 
872  if (! isArchive)
873  {
874  bool shouldSearchDir = shouldSearchRecursively
875  && dirExists(relativeFilePath)
876  && !isSymlink(relativeFilePath);
877  if (shouldSearchDir)
878  {
879  set<File *> filesInDir = findAllFilesInDirectory(relativeFilePath, archiveExtensions, true);
880  for (set<File *>::iterator i = filesInDir.begin(); i != filesInDir.end(); ++i)
881  {
882  File *f = *i;
883  f->dirPath = dirPath;
884  f->filePath = fileName + "/" + f->filePath;
885  }
886  files.insert(filesInDir.begin(), filesInDir.end());
887  }
888  else
889  {
890  File *f = new File(dirPath, fileName);
891  files.insert(f);
892  }
893  }
894  }
895 
896  closedir(d);
897 
898  return files;
899 }
900 
910 set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInDirectory(string dirPath, set<string> extensions, set<string> archiveExtensions)
911 {
912  set<File *> allFiles = findAllFilesInDirectory(dirPath, archiveExtensions);
913  set<File *> matchingFiles;
914 
915  for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
916  {
917  bool endsWithExtension = false;
918  for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
919  if (VuoStringUtilities::endsWith((*i)->getRelativePath(), "." + *extension))
920  endsWithExtension = true;
921 
922  if (endsWithExtension && ((*i)->isInArchive() || !VuoFileUtilities::dirExists((*i)->path())))
923  matchingFiles.insert(*i);
924  else
925  delete *i;
926  }
927 
928  return matchingFiles;
929 }
930 
937 set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInArchive(string archivePath)
938 {
939  set<File *> files;
940 
941  Archive *archive = new Archive(archivePath);
942  if (! archive->zipArchive)
943  {
944  delete archive;
945  return files;
946  }
947 
948  mz_uint numFiles = mz_zip_reader_get_num_files(archive->zipArchive);
949  for (mz_uint i = 0; i < numFiles; ++i)
950  {
951  mz_zip_archive_file_stat file_stat;
952  bool success = mz_zip_reader_file_stat(archive->zipArchive, i, &file_stat);
953  if (! success)
954  {
955  VUserLog("Error: Couldn't read file '%u' in zip archive '%s'.", i, archive->path.c_str());
956  break;
957  }
958 
959  File *f = new File(archive, file_stat.m_filename);
960  files.insert(f);
961  }
962 
963  if (archive->referenceCount == 0)
964  delete archive;
965 
966  return files;
967 }
968 
978 set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInArchive(string archivePath, string dirPath, set<string> extensions)
979 {
980  set<File *> allFiles = findAllFilesInArchive(archivePath);
981  set<File *> matchingFiles;
982 
983  for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
984  {
985  File *f = *i;
986 
987  string dir, file, ext;
988  splitPath(f->getRelativePath(), dir, file, ext);
989  bool beginsWithDir = VuoStringUtilities::beginsWith(dir, dirPath);
990 
991  bool endsWithExtension = false;
992  for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
993  if (VuoStringUtilities::endsWith(f->getRelativePath(), "." + *extension))
994  endsWithExtension = true;
995 
996  if (beginsWithDir && endsWithExtension)
997  matchingFiles.insert(f);
998  else
999  delete f;
1000  }
1001 
1002  return matchingFiles;
1003 }
1004 
1014 string VuoFileUtilities::getArchiveFileContentsAsString(string archivePath, string filePath)
1015 {
1016  string contents;
1017  set<File *> archiveFiles = findAllFilesInArchive(archivePath);
1018  for (set<File *>::iterator i = archiveFiles.begin(); i != archiveFiles.end(); ++i)
1019  {
1020  File *file = *i;
1021  if (filePath == file->getRelativePath())
1022  contents = file->getContentsAsString();
1023  delete file;
1024  }
1025 
1026  return contents;
1027 }
1028 
1037 {
1039 }
1040 
1047  path(path)
1048 {
1049  this->referenceCount = 0;
1050 
1051  // mz_zip_reader_init_file sometimes ends up with a garbage function pointer if the mz_zip_archive isn't initialized to zeroes
1052  this->zipArchive = (mz_zip_archive *)calloc(1, sizeof(mz_zip_archive));
1053 
1054  bool success = mz_zip_reader_init_file(zipArchive, path.c_str(), 0);
1055  if (! success)
1056  {
1057  free(zipArchive);
1058  zipArchive = NULL;
1059  }
1060 }
1061 
1066 {
1067  if (zipArchive)
1068  {
1069  mz_zip_reader_end(zipArchive);
1070  free(zipArchive);
1071  }
1072 }
1073 
1080 {
1081 }
1082 
1086 VuoFileUtilities::File::File(string dirPath, string filePath) :
1087  filePath(filePath),
1088  dirPath(dirPath)
1089 {
1090  this->fileDescriptor = -1;
1091  this->archive = NULL;
1092 }
1093 
1100 VuoFileUtilities::File::File(Archive *archive, string filePath) :
1101  filePath(filePath)
1102 {
1103  this->fileDescriptor = -1;
1104  this->archive = archive;
1105  ++archive->referenceCount;
1106 }
1107 
1113 {
1114  string newFilePath = basename() + "." + extension;
1115 
1116  if (archive)
1117  return File(archive, newFilePath);
1118  else
1119  return File(dirPath, newFilePath);
1120 }
1121 
1127 {
1128  if (archive && --archive->referenceCount == 0)
1129  delete archive;
1130 }
1131 
1136 {
1137  return (archive != NULL);
1138 }
1139 
1146 {
1147  return (archive ? archive->path : "");
1148 }
1149 
1154 {
1155  return filePath;
1156 }
1157 
1164 {
1165  if (archive)
1166  throw VuoException("Can't return a simple absolute path for a file in an archive.");
1167 
1168  return dirPath + "/" + filePath;
1169 }
1170 
1177 {
1178  if (archive)
1179  throw VuoException("Can't return a simple absolute path for a file in an archive.");
1180 
1181  return dirPath;
1182 }
1183 
1188 {
1189  return filePath.substr(0, filePath.find_last_of('.'));
1190 }
1191 
1196 {
1197  return filePath.substr(filePath.find_last_of('.') + 1);
1198 }
1199 
1204 {
1205  if (archive)
1206  return mz_zip_reader_locate_file(archive->zipArchive, filePath.c_str(), nullptr, MZ_ZIP_FLAG_CASE_SENSITIVE) != -1;
1207  else
1208  return VuoFileUtilities::fileExists((dirPath.empty() ? "" : (dirPath + "/")) + filePath);
1209 }
1210 
1220 {
1221  char * buffer;
1222  numBytes = 0;
1223 
1224  if (! archive)
1225  {
1226  // http://www.cplusplus.com/reference/cstdio/fread/
1227 
1228  string fullPath = (dirPath.empty() ? "" : (dirPath + "/")) + filePath;
1229 
1230  FILE *pFile = fopen ( fullPath.c_str() , "rb" );
1231  if (pFile==NULL)
1232  throw VuoException(string("Couldn't open file: ") + strerror(errno) + " — " + fullPath);
1233 
1234  // obtain file size:
1235  fseek (pFile , 0 , SEEK_END);
1236  size_t lSize = ftell (pFile);
1237  rewind (pFile);
1238 
1239  // allocate memory to contain the whole file:
1240  buffer = (char*) malloc (sizeof(char)*lSize);
1241 
1242  // copy the file into the buffer:
1243  numBytes = fread (buffer,1,lSize,pFile);
1244  fclose(pFile);
1245  if (numBytes != lSize)
1246  {
1247  free(buffer);
1248  throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + fullPath);
1249  }
1250  }
1251  else
1252  {
1253  buffer = (char *)mz_zip_reader_extract_file_to_heap(archive->zipArchive, filePath.c_str(), &numBytes, 0);
1254  }
1255 
1256  return buffer;
1257 }
1258 
1265 {
1266  size_t numBytes;
1267  char *buffer = getContentsAsRawData(numBytes);
1268  string s(buffer, numBytes);
1269  free(buffer);
1270  return s;
1271 }
1272 
1283 {
1284  if (fileDescriptor < 0)
1285  fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1286  int ret = flock(fileDescriptor, LOCK_SH | (nonBlocking ? LOCK_NB : 0));
1287  return ret == 0;
1288 }
1289 
1300 {
1301  if (fileDescriptor < 0)
1302  fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1303  int ret = flock(fileDescriptor, LOCK_EX | (nonBlocking ? LOCK_NB : 0));
1304  return ret == 0;
1305 }
1306 
1312 {
1313  flock(fileDescriptor, LOCK_UN);
1314  close(fileDescriptor);
1315  fileDescriptor = -1;
1316 }
1317 
1323 void VuoFileUtilities::focusProcess(pid_t pid, bool force)
1324 {
1326 }
1327 
1334 void VuoFileUtilities::executeProcess(vector<string> processAndArgs)
1335 {
1336  string binaryDir, binaryFile, binaryExt;
1337  splitPath(processAndArgs[0], binaryDir, binaryFile, binaryExt);
1338 
1339  string errorPrefix = "Couldn't execute " + binaryFile + ": ";
1340 
1341  // Capture stdout and stderr.
1342  int outputPipe[2];
1343  int ret = pipe(outputPipe);
1344  if (ret)
1345  throw VuoException(errorPrefix + "couldn't open pipe: " + strerror(ret));
1346  int pipeRead = outputPipe[0];
1347  int pipeWrite = outputPipe[1];
1348  VuoDefer(^{ close(pipeRead); });
1349  posix_spawn_file_actions_t fileActions;
1350  posix_spawn_file_actions_init(&fileActions);
1351  posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDOUT_FILENO);
1352  posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDERR_FILENO);
1353  posix_spawn_file_actions_addclose(&fileActions, pipeRead);
1354 
1355  // Convert args.
1356  // posix_spawn requires a null-terminated array, which `&processAndArgs[0]` doesn't guarantee.
1357  char **processAndArgsZ = (char **)malloc(sizeof(char *) * (processAndArgs.size() + 1));
1358  int argCount = 0;
1359  for (auto arg : processAndArgs)
1360  if (argCount == 0)
1361  processAndArgsZ[argCount++] = strdup(binaryFile.c_str());
1362  else
1363  processAndArgsZ[argCount++] = strdup(arg.c_str());
1364  processAndArgsZ[argCount] = nullptr;
1365 
1366  // Execute.
1367  pid_t pid;
1368  ret = posix_spawn(&pid, processAndArgs[0].c_str(), &fileActions, NULL, (char * const *)processAndArgsZ, NULL);
1369  close(pipeWrite);
1370  posix_spawn_file_actions_destroy(&fileActions);
1371  for (int i = 0; i < argCount; ++i)
1372  free(processAndArgsZ[i]);
1373 
1374  if (ret)
1375  throw VuoException(errorPrefix + strerror(ret));
1376  else
1377  {
1378  int status;
1379  if (waitpid(pid, &status, 0) == -1)
1380  throw VuoException(errorPrefix + "waitpid failed: " + strerror(errno));
1381  else if (status != 0)
1382  {
1383  string output;
1384  char buf[256];
1385  bzero(buf, 256);
1386  while (read(pipeRead, &buf, 255) > 0)
1387  {
1388  output += buf;
1389  bzero(buf, 256);
1390  }
1391 
1392  throw VuoException(binaryFile + " failed: " + output);
1393  }
1394  }
1395 }
1396 
1401 {
1402  return extension == "vuo";
1403 }
1404 
1409 {
1410  set<string> e;
1411  e.insert("c");
1412  e.insert("cc");
1413  e.insert("C");
1414  e.insert("cpp");
1415  e.insert("cxx");
1416  e.insert("c++");
1417  e.insert("m");
1418  e.insert("mm");
1419  return e;
1420 }
1421 
1426 {
1427  auto extensions = getCSourceExtensions();
1428  return extensions.find(extension) != extensions.end();
1429 }
1430 
1435 {
1436  set<string> e;
1437  e.insert("fs");
1438  e.insert("vs");
1439  e.insert("vuoshader");
1440  e.insert("vuoobjectfilter");
1441  return e.find(extension) != e.end();
1442 }