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 
122 string VuoFileUtilities::makeTmpFile(string file, string extension, string directory)
123 {
124  if (directory.empty())
125  directory = getTmpDir();
126  if (directory.at(directory.length()-1) != '/')
127  directory += "/";
128  string suffix = (extension.empty() ? "" : ("." + extension));
129  string pathTemplate = directory + file + "-XXXXXX" + suffix;
130  char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
131  strncpy(path, pathTemplate.c_str(), pathTemplate.length());
132  int fd = mkstemps(path, suffix.length());
133  close(fd);
134  string pathStr(path);
135  free(path);
136  return pathStr;
137 }
138 
147 string VuoFileUtilities::makeTmpDir(string prefix)
148 {
149  string pathTemplate = getTmpDir() + "/" + prefix + ".XXXXXX";
150  char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
151  strncpy(path, pathTemplate.c_str(), pathTemplate.length());
152  mkdtemp(path);
153  string pathStr(path);
154  free(path);
155  return pathStr;
156 }
157 
167 {
169 }
170 
183 {
184  char *cwd = getcwd(NULL, 0);
185  if (strstr(cwd, "/Library/Containers/")) // We're in a sandbox.
186  {
187  // https://b33p.net/kosada/node/16374
188  // In the macOS 10.15 screen saver sandbox, at least,
189  // we're not allowed to use the system or user temporary directories for socket-files,
190  // but we are allowed to use the sandbox container directory.
191  string cwdS(cwd);
192  free(cwd);
193  return VuoStringUtilities::endsWith(cwdS, "/") ? VuoStringUtilities::substrBefore(cwdS, "/") : cwdS;
194  }
195  free(cwd);
196 
197  char userTempDir[PATH_MAX];
198  if (confstr(_CS_DARWIN_USER_TEMP_DIR, userTempDir, PATH_MAX) > 0)
199  {
200  string userTempDirS(userTempDir);
201  return VuoStringUtilities::endsWith(userTempDirS, "/") ? VuoStringUtilities::substrBefore(userTempDirS, "/") : userTempDirS;
202  }
203 
204  return "/tmp";
205 }
206 
212 void VuoFileUtilities::makeDir(string path)
213 {
214  if (path.empty())
215  throw VuoException("Couldn't create directory with empty path");
216 
217  if (dirExists(path))
218  return;
219 
220  const char FILE_SEPARATOR = '/';
221  size_t lastNonSeparator = path.find_last_not_of(FILE_SEPARATOR);
222  if (lastNonSeparator != string::npos)
223  path.resize(lastNonSeparator + 1);
224 
225  string parentDir, file, ext;
226  splitPath(path, parentDir, file, ext);
227  makeDir(parentDir);
228 
229  int ret = mkdir(path.c_str(), 0700);
230  if (ret != 0 && ! dirExists(path))
231  throw VuoException((string("Couldn't create directory \"" + path + "\": ") + strerror(errno)).c_str());
232 }
233 
241 {
242  static string frameworkPath;
243  static dispatch_once_t once = 0;
244  dispatch_once(&once, ^{
245 
246  // First check whether Vuo.framework is in the list of loaded dynamic libraries.
247  const char *frameworkPathFragment = "/Vuo.framework/Versions/";
248  uint32_t imageCount = _dyld_image_count();
249  for (uint32_t i = 0; i < imageCount; ++i)
250  {
251  const char *dylibPath = _dyld_get_image_name(i);
252  const char *found = strstr(dylibPath, frameworkPathFragment);
253  if (found)
254  {
255  char *pathC = strndup(dylibPath, found - dylibPath);
256  string path = string(pathC) + "/Vuo.framework";
257  free(pathC);
258  if (fileExists(path))
259  {
260  frameworkPath = path;
261  return;
262  }
263  }
264  }
265 
266  // Failing that, check for a Vuo.framework bundled with the executable app.
267  char executablePath[PATH_MAX + 1];
268  uint32_t size = sizeof(executablePath);
269 
270  if (! _NSGetExecutablePath(executablePath, &size))
271  {
272  char cleanedExecutablePath[PATH_MAX + 1];
273 
274  realpath(executablePath, cleanedExecutablePath); // remove extra references (e.g., "/./")
275  string path = cleanedExecutablePath;
276  string dir, file, ext;
277  splitPath(path, dir, file, ext); // remove executable name
278  path = dir.substr(0, dir.length() - 1); // remove "/"
279  splitPath(path, dir, file, ext); // remove "MacOS"
280  path = dir.substr(0, dir.length() - 1); // remove "/"
281  path += "/Frameworks/Vuo.framework";
282 
283  if (fileExists(path))
284  {
285  frameworkPath = path;
286  return;
287  }
288  }
289 
290  // Give up.
291  });
292 
293  return frameworkPath;
294 }
295 
301 {
302  static string runnerFrameworkPath;
303  static dispatch_once_t once = 0;
304  dispatch_once(&once, ^{
305  // Check for VuoRunner.framework alongside Vuo.framework.
306  string possibleFrameworkPath = getVuoFrameworkPath() + "/../VuoRunner.framework";
307  if (dirExists(possibleFrameworkPath))
308  {
309  runnerFrameworkPath = possibleFrameworkPath;
310  return;
311  }
312 
313  // Failing that, check whether VuoRunner.framework is in the list of loaded dynamic libraries.
314  const char *frameworkPathFragment = "/VuoRunner.framework/Versions/";
315  uint32_t imageCount = _dyld_image_count();
316  for (uint32_t i = 0; i < imageCount; ++i)
317  {
318  const char *dylibPath = _dyld_get_image_name(i);
319  const char *found = strstr(dylibPath, frameworkPathFragment);
320  if (found)
321  {
322  char *pathC = strndup(dylibPath, found - dylibPath);
323  string path = string(pathC) + "/VuoRunner.framework";
324  free(pathC);
325  if (fileExists(path))
326  {
327  runnerFrameworkPath = path;
328  return;
329  }
330  }
331  }
332 
333  // Failing that, check for a VuoRunner.framework bundled with the executable app.
334  char executablePath[PATH_MAX + 1];
335  uint32_t size = sizeof(executablePath);
336 
337  if (! _NSGetExecutablePath(executablePath, &size))
338  {
339  char cleanedExecutablePath[PATH_MAX + 1];
340 
341  realpath(executablePath, cleanedExecutablePath); // remove extra references (e.g., "/./")
342  string path = cleanedExecutablePath;
343  string dir, file, ext;
344  splitPath(path, dir, file, ext); // remove executable name
345  path = dir.substr(0, dir.length() - 1); // remove "/"
346  splitPath(path, dir, file, ext); // remove "MacOS"
347  path = dir.substr(0, dir.length() - 1); // remove "/"
348  path += "/Frameworks/VuoRunner.framework";
349 
350  if (fileExists(path))
351  {
352  runnerFrameworkPath = path;
353  return;
354  }
355  }
356 
357  // Give up.
358  });
359  return runnerFrameworkPath;
360 }
361 
370 string VuoFileUtilities::getCompositionLocalModulesPath(const string &compositionPath)
371 {
372  string compositionDir, compositionFile, ext;
373  splitPath(getAbsolutePath(compositionPath), compositionDir, compositionFile, ext);
374  canonicalizePath(compositionDir);
375 
376  string parentDir, compositionDirName;
377  splitPath(compositionDir, parentDir, compositionDirName, ext);
378  canonicalizePath(compositionDirName);
379 
380  string compositionBaseDir = (compositionDirName == "Modules" ? parentDir : compositionDir);
381 
382  string compositionModulesDir = compositionBaseDir + "/Modules";
383  canonicalizePath(compositionModulesDir);
384 
385  return compositionModulesDir;
386 }
387 
392 {
393  return string(getenv("HOME")) + "/Library/Application Support/Vuo/Modules";
394 }
395 
400 {
401  return "/Library/Application Support/Vuo/Modules";
402 }
403 
408 {
409  return string(getenv("HOME")) + "/Library/Caches/org.vuo/" + VUO_VERSION_AND_BUILD_STRING;
410 }
411 
416 {
417  if (path.empty())
418  return false;
419 
420  string compositionLocalModulesPath = getCompositionLocalModulesPath(path);
421 
422  string dir, file, ext;
423  VuoFileUtilities::splitPath(path, dir, file, ext);
424 
425  return VuoFileUtilities::arePathsEqual(compositionLocalModulesPath, dir);
426 }
427 
436 void VuoFileUtilities::preserveOriginalFileName(string &fileContents, string originalFileName)
437 {
438  fileContents.insert(getFirstInsertionIndex(fileContents), "#line 1 \"" + originalFileName + "\"\n");
439 }
440 
446 {
447  string bomUtf8 = "\xEF\xBB\xBF";
448  string bomUtf16Be = "\xFE\xFF";
449  string bomUtf16Le = "\xFF\xFE";
450  string boms[] = { bomUtf8, bomUtf16Be, bomUtf16Le };
451  for (int i = 0; i < 3; ++i)
452  {
453  bool isMatch = true;
454  for (int j = 0; j < boms[i].length(); ++j)
455  if (boms[i][j] != s[j])
456  isMatch = false;
457 
458  if (isMatch)
459  return boms[i].length();
460  }
461  return 0;
462 }
463 
468 {
469  // https://stackoverflow.com/questions/201992/how-to-read-until-eof-from-cin-in-c
470 
471  string contents;
472  string line;
473  while (getline(cin, line))
474  contents += line;
475 
476  return contents;
477 }
478 
485 {
486  // https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring
487 
488  ifstream in(path.c_str(), ios::in | ios::binary);
489  if (! in)
490  throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + path);
491 
492  string contents;
493  in.seekg(0, ios::end);
494  contents.resize(in.tellg());
495  in.seekg(0, ios::beg);
496  in.read(&contents[0], contents.size());
497  in.close();
498  return contents;
499 }
500 
508 void VuoFileUtilities::writeRawDataToFile(const char *data, size_t numBytes, string file)
509 {
510  FILE *f = fopen(file.c_str(), "wb");
511  if (! f)
512  throw VuoException(string("Couldn't open file for writing: ") + strerror(errno) + " — " + file);
513 
514  size_t numBytesWritten = fwrite(data, sizeof(char), numBytes, f);
515  if (numBytesWritten != numBytes)
516  {
517  fclose(f);
518  throw VuoException(string("Couldn't write all data: ") + strerror(errno) + " — " + file);
519  }
520 
521  fclose(f);
522 }
523 
531 void VuoFileUtilities::writeStringToFile(string s, string file)
532 {
533  writeRawDataToFile(s.data(), s.length(), file);
534 }
535 
544 void VuoFileUtilities::writeStringToFileSafely(string s, string path)
545 {
546  string dir, file, ext;
547  splitPath(path, dir, file, ext);
548  string tmpPath = makeTmpFile("." + file, ext, dir);
549 
550  writeStringToFile(s, tmpPath);
551 
552  moveFile(tmpPath, path);
553 }
554 
561 {
564 
565  // `access()` automatically dereferences symlinks.
566  return access(path.c_str(), 0) == 0;
567 }
568 
575 {
578 
579  // `stat()` automatically dereferences symlinks.
580  struct stat st_buf;
581  int status = stat(path.c_str(), &st_buf);
582  return (! status && S_ISDIR(st_buf.st_mode));
583 }
584 
589 {
590  struct stat st_buf;
591  int status = lstat(path.c_str(), &st_buf);
592  return (! status && S_ISLNK(st_buf.st_mode));
593 }
594 
601 {
604 
605  // `access()` automatically dereferences symlinks.
606  return access(path.c_str(), R_OK) == 0;
607 }
608 
615 {
618 
619  // `fopen()` automatically dereferences symlinks.
620  FILE *f = fopen(path.c_str(), "rb");
621  if (!f)
622  return false;
623 
624  fseek(f, 0, SEEK_END);
625  long pos = ftell(f);
626  if (pos <= 0)
627  {
628  fclose(f);
629  return false;
630  }
631 
632  // `fopen` and `fseek` both succeed on directories, so also attempt to actually read the data.
633  fseek(f, 0, SEEK_SET);
634  char z;
635  size_t ret = fread(&z, 1, 1, f);
636  fclose(f);
637  return ret > 0;
638 }
639 
644 {
645  FILE *f = fopen(path.c_str(), "a");
646  fclose(f);
647 }
648 
653 {
654  int ret = remove(path.c_str());
655  if (ret != 0 && fileExists(path))
656  VUserLog("Couldn't delete file: %s — %s", strerror(errno), path.c_str());
657 }
658 
663 {
664  if (! dirExists(path))
665  return;
666 
667  DIR *d = opendir(path.c_str());
668  if (! d)
669  {
670  VUserLog("Couldn't read directory: %s — %s", strerror(errno), path.c_str());
671  return;
672  }
673 
674  struct dirent *de;
675  while( (de=readdir(d)) )
676  {
677  string fileName = de->d_name;
678  string filePath = path + "/" + fileName;
679 
680  if (fileName == "." || fileName == "..")
681  continue;
682 
683  if (dirExists(filePath))
684  deleteDir(filePath);
685  else
686  deleteFile(filePath);
687  }
688 
689  closedir(d);
690 
691  deleteFile(path);
692 }
693 
699 void VuoFileUtilities::moveFile(string fromPath, string toPath)
700 {
701  int ret = rename(fromPath.c_str(), toPath.c_str());
702  if (ret != 0)
703  throw VuoException(string("Couldn't move file: ") + strerror(errno) + " — " + toPath);
704 }
705 
712 {
714 }
715 
724 void VuoFileUtilities::copyFile(string fromPath, string toPath, bool preserveMetadata)
725 {
726  int i = open(fromPath.c_str(), O_RDONLY);
727  if (i == -1)
728  throw VuoException(string("Couldn't open copy source: ") + strerror(errno) + " — " + fromPath);
729 
730  int o = open(toPath.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
731  if (o == -1)
732  {
733  close(i);
734  throw VuoException(string("Couldn't open copy destination: ") + strerror(errno) + " — " + toPath);
735  }
736 
737  int ret = fcopyfile(i, o, NULL, COPYFILE_DATA | (preserveMetadata ? COPYFILE_STAT : 0));
738  char *e = strerror(errno);
739  close(o);
740  close(i);
741 
742  if (ret)
743  throw VuoException(string("Couldn't copy ") + fromPath + " to " + toPath + ": " + e);
744 }
745 
753 void VuoFileUtilities::copyDirectory(string fromPath, string toPath)
754 {
755  if (isSymlink(fromPath))
756  {
757  // Preserve the relative symlinks found in framework bundles, instead of flattening them.
758  char linkDestination[PATH_MAX + 1];
759  ssize_t len = readlink(fromPath.c_str(), linkDestination, PATH_MAX);
760  linkDestination[len] = 0; // "readlink does not append a NUL character to buf."
761  if (symlink(linkDestination, toPath.c_str()) == -1)
762  throw VuoException(string("Couldn't copy symlink \"") + fromPath + "\" to \"" + toPath + "\": " + strerror(errno));
763  }
764 
765  else if (!dirExists(fromPath))
766  copyFile(fromPath, toPath);
767 
768  else
769  {
770  auto files = findAllFilesInDirectory(fromPath);
771  makeDir(toPath);
772  for (auto file : files)
773  {
774  string sourceFile = fromPath + "/" + file->getRelativePath();
775  string targetFile = toPath + "/" + file->getRelativePath();
776  copyDirectory(sourceFile, targetFile);
777  }
778  }
779 }
780 
786 {
787  struct stat s;
788  lstat(path.c_str(), &s);
789  return s.st_mtimespec.tv_sec; // s.st_mtimespec.tv_nsec is always 0 on some OSes, hence the resolution of 1 second
790 }
791 
796 {
797  struct stat s;
798  lstat(path.c_str(), &s);
799 
800  struct timeval t;
801  gettimeofday(&t, NULL);
802 
803  return t.tv_sec - s.st_atimespec.tv_sec;
804 }
805 
819 set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInDirectory(string dirPath, set<string> archiveExtensions,
820  bool shouldSearchRecursively)
821 {
822  set<File *> files;
823 
825  dirPath = VuoFileUtilitiesCocoa_resolveMacAlias(dirPath);
826 
827  // `opendir()` automatically dereferences symlinks.
828  DIR *d = opendir(dirPath.c_str());
829  if (! d)
830  {
831  if (access(dirPath.c_str(), F_OK) != -1)
832  throw VuoException(string("Couldn't read directory: ") + strerror(errno) + " — " + dirPath);
833  return files;
834  }
835 
836  struct dirent *de;
837  while( (de=readdir(d)) )
838  {
839  string fileName = de->d_name;
840  string relativeFilePath = dirPath + "/" + fileName;
841 
842  if (fileName == "." || fileName == "..")
843  continue;
844 
845  bool isArchive = false;
846  for (set<string>::iterator archiveExtension = archiveExtensions.begin(); archiveExtension != archiveExtensions.end(); ++archiveExtension)
847  if (VuoStringUtilities::endsWith(fileName, "." + *archiveExtension))
848  isArchive = true;
849 
850  if (isArchive)
851  {
852  set<File *> fs = findAllFilesInArchive(relativeFilePath);
853  if (fs.empty())
854  isArchive = false;
855  else
856  files.insert(fs.begin(), fs.end());
857  }
858 
859  if (! isArchive)
860  {
861  bool shouldSearchDir = shouldSearchRecursively
862  && dirExists(relativeFilePath)
863  && !isSymlink(relativeFilePath);
864  if (shouldSearchDir)
865  {
866  set<File *> filesInDir = findAllFilesInDirectory(relativeFilePath, archiveExtensions, true);
867  for (set<File *>::iterator i = filesInDir.begin(); i != filesInDir.end(); ++i)
868  {
869  File *f = *i;
870  f->dirPath = dirPath;
871  f->filePath = fileName + "/" + f->filePath;
872  }
873  files.insert(filesInDir.begin(), filesInDir.end());
874  }
875  else
876  {
877  File *f = new File(dirPath, fileName);
878  files.insert(f);
879  }
880  }
881  }
882 
883  closedir(d);
884 
885  return files;
886 }
887 
897 set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInDirectory(string dirPath, set<string> extensions, set<string> archiveExtensions)
898 {
899  set<File *> allFiles = findAllFilesInDirectory(dirPath, archiveExtensions);
900  set<File *> matchingFiles;
901 
902  for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
903  {
904  bool endsWithExtension = false;
905  for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
906  if (VuoStringUtilities::endsWith((*i)->getRelativePath(), "." + *extension))
907  endsWithExtension = true;
908 
909  if (endsWithExtension && ((*i)->isInArchive() || !VuoFileUtilities::dirExists((*i)->path())))
910  matchingFiles.insert(*i);
911  else
912  delete *i;
913  }
914 
915  return matchingFiles;
916 }
917 
924 set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInArchive(string archivePath)
925 {
926  set<File *> files;
927 
928  Archive *archive = new Archive(archivePath);
929  if (! archive->zipArchive)
930  {
931  delete archive;
932  return files;
933  }
934 
935  mz_uint numFiles = mz_zip_reader_get_num_files(archive->zipArchive);
936  for (mz_uint i = 0; i < numFiles; ++i)
937  {
938  mz_zip_archive_file_stat file_stat;
939  bool success = mz_zip_reader_file_stat(archive->zipArchive, i, &file_stat);
940  if (! success)
941  {
942  VUserLog("Error: Couldn't read file '%u' in zip archive '%s'.", i, archive->path.c_str());
943  break;
944  }
945 
946  File *f = new File(archive, file_stat.m_filename);
947  files.insert(f);
948  }
949 
950  if (archive->referenceCount == 0)
951  delete archive;
952 
953  return files;
954 }
955 
965 set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInArchive(string archivePath, string dirPath, set<string> extensions)
966 {
967  set<File *> allFiles = findAllFilesInArchive(archivePath);
968  set<File *> matchingFiles;
969 
970  for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
971  {
972  File *f = *i;
973 
974  string dir, file, ext;
975  splitPath(f->getRelativePath(), dir, file, ext);
976  bool beginsWithDir = VuoStringUtilities::beginsWith(dir, dirPath);
977 
978  bool endsWithExtension = false;
979  for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
980  if (VuoStringUtilities::endsWith(f->getRelativePath(), "." + *extension))
981  endsWithExtension = true;
982 
983  if (beginsWithDir && endsWithExtension)
984  matchingFiles.insert(f);
985  else
986  delete f;
987  }
988 
989  return matchingFiles;
990 }
991 
1001 string VuoFileUtilities::getArchiveFileContentsAsString(string archivePath, string filePath)
1002 {
1003  string contents;
1004  set<File *> archiveFiles = findAllFilesInArchive(archivePath);
1005  for (set<File *>::iterator i = archiveFiles.begin(); i != archiveFiles.end(); ++i)
1006  {
1007  File *file = *i;
1008  if (filePath == file->getRelativePath())
1009  contents = file->getContentsAsString();
1010  delete file;
1011  }
1012 
1013  return contents;
1014 }
1015 
1024 {
1026 }
1027 
1034  path(path)
1035 {
1036  this->referenceCount = 0;
1037 
1038  // mz_zip_reader_init_file sometimes ends up with a garbage function pointer if the mz_zip_archive isn't initialized to zeroes
1039  this->zipArchive = (mz_zip_archive *)calloc(1, sizeof(mz_zip_archive));
1040 
1041  bool success = mz_zip_reader_init_file(zipArchive, path.c_str(), 0);
1042  if (! success)
1043  {
1044  free(zipArchive);
1045  zipArchive = NULL;
1046  }
1047 }
1048 
1053 {
1054  if (zipArchive)
1055  {
1056  mz_zip_reader_end(zipArchive);
1057  free(zipArchive);
1058  }
1059 }
1060 
1067 {
1068 }
1069 
1073 VuoFileUtilities::File::File(string dirPath, string filePath) :
1074  filePath(filePath),
1075  dirPath(dirPath)
1076 {
1077  this->fileDescriptor = -1;
1078  this->archive = NULL;
1079 }
1080 
1087 VuoFileUtilities::File::File(Archive *archive, string filePath) :
1088  filePath(filePath)
1089 {
1090  this->fileDescriptor = -1;
1091  this->archive = archive;
1092  ++archive->referenceCount;
1093 }
1094 
1100 {
1101  string newFilePath = basename() + "." + extension;
1102 
1103  if (archive)
1104  return File(archive, newFilePath);
1105  else
1106  return File(dirPath, newFilePath);
1107 }
1108 
1114 {
1115  if (archive && --archive->referenceCount == 0)
1116  delete archive;
1117 }
1118 
1123 {
1124  return (archive != NULL);
1125 }
1126 
1133 {
1134  return (archive ? archive->path : "");
1135 }
1136 
1141 {
1142  return filePath;
1143 }
1144 
1151 {
1152  if (archive)
1153  throw VuoException("Can't return a simple absolute path for a file in an archive.");
1154 
1155  return dirPath + "/" + 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;
1169 }
1170 
1175 {
1176  return filePath.substr(0, filePath.find_last_of('.'));
1177 }
1178 
1183 {
1184  return filePath.substr(filePath.find_last_of('.') + 1);
1185 }
1186 
1191 {
1192  if (archive)
1193  return mz_zip_reader_locate_file(archive->zipArchive, filePath.c_str(), nullptr, MZ_ZIP_FLAG_CASE_SENSITIVE) != -1;
1194  else
1195  return VuoFileUtilities::fileExists((dirPath.empty() ? "" : (dirPath + "/")) + filePath);
1196 }
1197 
1207 {
1208  char * buffer;
1209  numBytes = 0;
1210 
1211  if (! archive)
1212  {
1213  // http://www.cplusplus.com/reference/cstdio/fread/
1214 
1215  string fullPath = (dirPath.empty() ? "" : (dirPath + "/")) + filePath;
1216 
1217  FILE *pFile = fopen ( fullPath.c_str() , "rb" );
1218  if (pFile==NULL)
1219  throw VuoException(string("Couldn't open file: ") + strerror(errno) + " — " + fullPath);
1220 
1221  // obtain file size:
1222  fseek (pFile , 0 , SEEK_END);
1223  size_t lSize = ftell (pFile);
1224  rewind (pFile);
1225 
1226  // allocate memory to contain the whole file:
1227  buffer = (char*) malloc (sizeof(char)*lSize);
1228 
1229  // copy the file into the buffer:
1230  numBytes = fread (buffer,1,lSize,pFile);
1231  fclose(pFile);
1232  if (numBytes != lSize)
1233  {
1234  free(buffer);
1235  throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + fullPath);
1236  }
1237  }
1238  else
1239  {
1240  buffer = (char *)mz_zip_reader_extract_file_to_heap(archive->zipArchive, filePath.c_str(), &numBytes, 0);
1241  }
1242 
1243  return buffer;
1244 }
1245 
1252 {
1253  size_t numBytes;
1254  char *buffer = getContentsAsRawData(numBytes);
1255  string s(buffer, numBytes);
1256  free(buffer);
1257  return s;
1258 }
1259 
1270 {
1271  if (fileDescriptor < 0)
1272  fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1273  int ret = flock(fileDescriptor, LOCK_SH | (nonBlocking ? LOCK_NB : 0));
1274  return ret == 0;
1275 }
1276 
1287 {
1288  if (fileDescriptor < 0)
1289  fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1290  int ret = flock(fileDescriptor, LOCK_EX | (nonBlocking ? LOCK_NB : 0));
1291  return ret == 0;
1292 }
1293 
1299 {
1300  flock(fileDescriptor, LOCK_UN);
1301  close(fileDescriptor);
1302  fileDescriptor = -1;
1303 }
1304 
1310 void VuoFileUtilities::focusProcess(pid_t pid, bool force)
1311 {
1313 }
1314 
1321 void VuoFileUtilities::executeProcess(vector<string> processAndArgs)
1322 {
1323  string binaryDir, binaryFile, binaryExt;
1324  splitPath(processAndArgs[0], binaryDir, binaryFile, binaryExt);
1325 
1326  string errorPrefix = "Couldn't execute " + binaryFile + ": ";
1327 
1328  // Capture stdout and stderr.
1329  int outputPipe[2];
1330  int ret = pipe(outputPipe);
1331  if (ret)
1332  throw VuoException(errorPrefix + "couldn't open pipe: " + strerror(ret));
1333  int pipeRead = outputPipe[0];
1334  int pipeWrite = outputPipe[1];
1335  VuoDefer(^{ close(pipeRead); });
1336  posix_spawn_file_actions_t fileActions;
1337  posix_spawn_file_actions_init(&fileActions);
1338  posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDOUT_FILENO);
1339  posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDERR_FILENO);
1340  posix_spawn_file_actions_addclose(&fileActions, pipeRead);
1341 
1342  // Convert args.
1343  // posix_spawn requires a null-terminated array, which `&processAndArgs[0]` doesn't guarantee.
1344  char **processAndArgsZ = (char **)malloc(sizeof(char *) * (processAndArgs.size() + 1));
1345  int argCount = 0;
1346  for (auto arg : processAndArgs)
1347  if (argCount == 0)
1348  processAndArgsZ[argCount++] = strdup(binaryFile.c_str());
1349  else
1350  processAndArgsZ[argCount++] = strdup(arg.c_str());
1351  processAndArgsZ[argCount] = nullptr;
1352 
1353  // Execute.
1354  pid_t pid;
1355  ret = posix_spawn(&pid, processAndArgs[0].c_str(), &fileActions, NULL, (char * const *)processAndArgsZ, NULL);
1356  close(pipeWrite);
1357  posix_spawn_file_actions_destroy(&fileActions);
1358  for (int i = 0; i < argCount; ++i)
1359  free(processAndArgsZ[i]);
1360 
1361  if (ret)
1362  throw VuoException(errorPrefix + strerror(ret));
1363  else
1364  {
1365  int status;
1366  if (waitpid(pid, &status, 0) == -1)
1367  throw VuoException(errorPrefix + "waitpid failed: " + strerror(errno));
1368  else if (status != 0)
1369  {
1370  string output;
1371  char buf[256];
1372  bzero(buf, 256);
1373  while (read(pipeRead, &buf, 255) > 0)
1374  {
1375  output += buf;
1376  bzero(buf, 256);
1377  }
1378 
1379  throw VuoException(binaryFile + " failed: " + output);
1380  }
1381  }
1382 }
1383 
1388 {
1389  return extension == "vuo";
1390 }
1391 
1396 {
1397  set<string> e;
1398  e.insert("c");
1399  e.insert("cc");
1400  e.insert("C");
1401  e.insert("cpp");
1402  e.insert("cxx");
1403  e.insert("c++");
1404  e.insert("m");
1405  e.insert("mm");
1406  return e;
1407 }
1408 
1413 {
1414  auto extensions = getCSourceExtensions();
1415  return extensions.find(extension) != extensions.end();
1416 }
1417 
1422 {
1423  set<string> e;
1424  e.insert("fs");
1425  e.insert("vs");
1426  e.insert("vuoshader");
1427  e.insert("vuoobjectfilter");
1428  return e.find(extension) != e.end();
1429 }