Add initial bits for Qt6 support
[carla.git] / source / modules / water / files / File.cpp
blob0e6eee26ea7a4b9b8988a86a6cbb67cee71aaa62
1 /*
2 ==============================================================================
4 This file is part of the Water library.
5 Copyright (c) 2016 ROLI Ltd.
6 Copyright (C) 2017-2024 Filipe Coelho <falktx@falktx.com>
8 Permission is granted to use this software under the terms of the ISC license
9 http://www.isc.org/downloads/software-support-policy/isc-license/
11 Permission to use, copy, modify, and/or distribute this software for any
12 purpose with or without fee is hereby granted, provided that the above
13 copyright notice and this permission notice appear in all copies.
15 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
16 TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
17 FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
18 OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
19 USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
21 OF THIS SOFTWARE.
23 ==============================================================================
26 #include "File.h"
27 #include "DirectoryIterator.h"
28 #include "FileInputStream.h"
29 #include "FileOutputStream.h"
30 #include "TemporaryFile.h"
31 #include "../maths/Random.h"
32 #include "../misc/Time.h"
33 #include "../text/StringArray.h"
35 #ifdef CARLA_OS_WIN
36 # include <shlobj.h>
37 #else
38 # include <dlfcn.h>
39 # include <fcntl.h>
40 # include <fnmatch.h>
41 # include <pwd.h>
42 # include <sys/stat.h>
43 # ifdef CARLA_OS_MAC
44 # include <mach-o/dyld.h>
45 # import <Foundation/NSFileManager.h>
46 # import <Foundation/NSPathUtilities.h>
47 # import <Foundation/NSString.h>
48 # else
49 # include <dirent.h>
50 # endif
51 #endif
53 namespace water {
55 File::File () noexcept
56 : fullPath ()
60 File::File (const char* const absolutePath)
61 : fullPath (parseAbsolutePath (absolutePath))
65 File File::createFileWithoutCheckingPath (const String& path) noexcept
67 File f;
68 f.fullPath = path;
69 return f;
72 File::File (const File& other)
73 : fullPath (other.fullPath)
77 File& File::operator= (const char* const newAbsolutePath)
79 fullPath = parseAbsolutePath (newAbsolutePath);
80 return *this;
83 File& File::operator= (const File& other)
85 fullPath = other.fullPath;
86 return *this;
89 bool File::isNull() const
91 return fullPath.isEmpty();
94 bool File::isNotNull() const
96 return fullPath.isNotEmpty();
99 //==============================================================================
100 static String removeEllipsis (const String& path)
102 // This will quickly find both /../ and /./ at the expense of a minor
103 // false-positive performance hit when path elements end in a dot.
104 #ifdef CARLA_OS_WIN
105 if (path.contains (".\\"))
106 #else
107 if (path.contains ("./"))
108 #endif
110 StringArray toks;
111 toks.addTokens (path, CARLA_OS_SEP_STR, StringRef());
112 bool anythingChanged = false;
114 for (int i = 1; i < toks.size(); ++i)
116 const String& t = toks[i];
118 if (t == ".." && toks[i - 1] != "..")
120 anythingChanged = true;
121 toks.removeRange (i - 1, 2);
122 i = jmax (0, i - 2);
124 else if (t == ".")
126 anythingChanged = true;
127 toks.remove (i--);
131 if (anythingChanged)
132 return toks.joinIntoString (CARLA_OS_SEP_STR);
135 return path;
138 String File::parseAbsolutePath (const String& p)
140 if (p.isEmpty())
141 return String();
143 #ifdef CARLA_OS_WIN
144 // Windows..
145 String path (removeEllipsis (p.replaceCharacter ('/', '\\')));
147 if (path.startsWithChar (CARLA_OS_SEP))
149 if (path[1] != CARLA_OS_SEP)
151 // Check if path is valid under Wine
152 String testpath ("Z:" + path);
154 if (File(testpath.toRawUTF8()).exists())
156 path = testpath;
158 else
160 /* When you supply a raw string to the File object constructor, it must be an absolute path.
161 If you're trying to parse a string that may be either a relative path or an absolute path,
162 you MUST provide a context against which the partial path can be evaluated - you can do
163 this by simply using File::getChildFile() instead of the File constructor. E.g. saying
164 "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
165 path if that's what was supplied, or would evaluate a partial path relative to the CWD.
167 carla_safe_assert(testpath.toRawUTF8(), __FILE__, __LINE__);
169 path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path;
173 else if (! path.containsChar (':'))
175 /* When you supply a raw string to the File object constructor, it must be an absolute path.
176 If you're trying to parse a string that may be either a relative path or an absolute path,
177 you MUST provide a context against which the partial path can be evaluated - you can do
178 this by simply using File::getChildFile() instead of the File constructor. E.g. saying
179 "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
180 path if that's what was supplied, or would evaluate a partial path relative to the CWD.
182 carla_safe_assert(path.toRawUTF8(), __FILE__, __LINE__);
184 return File::getCurrentWorkingDirectory().getChildFile (path.toRawUTF8()).getFullPathName();
186 #else
187 // Mac or Linux..
189 // Yes, I know it's legal for a unix pathname to contain a backslash, but this assertion is here
190 // to catch anyone who's trying to run code that was written on Windows with hard-coded path names.
191 // If that's why you've ended up here, use File::getChildFile() to build your paths instead.
192 wassert ((! p.containsChar ('\\')) || (p.indexOfChar ('/') >= 0 && p.indexOfChar ('/') < p.indexOfChar ('\\')));
194 String path (removeEllipsis (p));
196 if (path.startsWithChar ('~'))
198 if (path[1] == CARLA_OS_SEP || path[1] == 0)
200 // expand a name of the form "~/abc"
201 path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName()
202 + path.substring (1);
204 else
206 // expand a name of type "~dave/abc"
207 const String userName (path.substring (1).upToFirstOccurrenceOf ("/", false, false));
209 if (struct passwd* const pw = getpwnam (userName.toUTF8()))
210 path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false);
213 else if (! path.startsWithChar (CARLA_OS_SEP))
215 return File::getCurrentWorkingDirectory().getChildFile (path.toRawUTF8()).getFullPathName();
217 #endif
219 while (path.endsWithChar (CARLA_OS_SEP) && path != CARLA_OS_SEP_STR) // careful not to turn a single "/" into an empty string.
220 path = path.dropLastCharacters (1);
222 return path;
225 String File::addTrailingSeparator (const String& path)
227 return path.endsWithChar (CARLA_OS_SEP) ? path
228 : path + CARLA_OS_SEP;
231 //==============================================================================
232 #if ! (defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN))
233 #define NAMES_ARE_CASE_SENSITIVE 1
234 #endif
236 bool File::areFileNamesCaseSensitive()
238 #if NAMES_ARE_CASE_SENSITIVE
239 return true;
240 #else
241 return false;
242 #endif
245 static int compareFilenames (const String& name1, const String& name2) noexcept
247 #if NAMES_ARE_CASE_SENSITIVE
248 return name1.compare (name2);
249 #else
250 return name1.compareIgnoreCase (name2);
251 #endif
254 bool File::operator== (const File& other) const { return compareFilenames (fullPath, other.fullPath) == 0; }
255 bool File::operator!= (const File& other) const { return compareFilenames (fullPath, other.fullPath) != 0; }
256 bool File::operator< (const File& other) const { return compareFilenames (fullPath, other.fullPath) < 0; }
257 bool File::operator> (const File& other) const { return compareFilenames (fullPath, other.fullPath) > 0; }
259 //==============================================================================
260 bool File::deleteRecursively() const
262 bool worked = true;
264 if (isDirectory())
266 std::vector<File> subFiles;
267 findChildFiles (subFiles, File::findFilesAndDirectories, false);
269 for (ssize_t i = subFiles.size(); --i >= 0;)
270 worked = subFiles[i].deleteRecursively() && worked;
273 return deleteFile() && worked;
276 bool File::moveFileTo (const File& newFile) const
278 if (newFile.fullPath == fullPath)
279 return true;
281 if (! exists())
282 return false;
284 #if ! NAMES_ARE_CASE_SENSITIVE
285 if (*this != newFile)
286 #endif
287 if (! newFile.deleteFile())
288 return false;
290 return moveInternal (newFile);
293 bool File::copyFileTo (const File& newFile) const
295 return (*this == newFile)
296 || (exists() && newFile.deleteFile() && copyInternal (newFile));
299 bool File::replaceFileIn (const File& newFile) const
301 if (newFile.fullPath == fullPath)
302 return true;
304 if (! newFile.exists())
305 return moveFileTo (newFile);
307 if (! replaceInternal (newFile))
308 return false;
310 deleteFile();
311 return true;
314 bool File::copyDirectoryTo (const File& newDirectory) const
316 if (isDirectory() && newDirectory.createDirectory())
318 std::vector<File> subFiles;
319 findChildFiles (subFiles, File::findFiles, false);
321 for (size_t i = 0; i < subFiles.size(); ++i)
323 const File& src (subFiles[i]);
324 const File& dst (newDirectory.getChildFile (src.getFileName().toRawUTF8()));
326 if (src.isSymbolicLink())
328 if (! src.getLinkedTarget().createSymbolicLink (dst, true))
329 return false;
331 else
333 if (! src.copyFileTo (dst))
334 return false;
338 subFiles.clear();
339 findChildFiles (subFiles, File::findDirectories, false);
341 for (size_t i = 0; i < subFiles.size(); ++i)
342 if (! subFiles[i].copyDirectoryTo (newDirectory.getChildFile (subFiles[i].getFileName().toRawUTF8())))
343 return false;
345 return true;
348 return false;
351 //==============================================================================
352 String File::getPathUpToLastSlash() const
354 const int lastSlash = fullPath.lastIndexOfChar (CARLA_OS_SEP);
356 if (lastSlash > 0)
357 return fullPath.substring (0, lastSlash);
359 if (lastSlash == 0)
360 return CARLA_OS_SEP_STR;
362 return fullPath;
365 File File::getParentDirectory() const
367 File f;
368 f.fullPath = getPathUpToLastSlash();
369 return f;
372 //==============================================================================
373 String File::getFileName() const
375 return fullPath.substring (fullPath.lastIndexOfChar (CARLA_OS_SEP) + 1);
378 String File::getFileNameWithoutExtension() const
380 const int lastSlash = fullPath.lastIndexOfChar (CARLA_OS_SEP) + 1;
381 const int lastDot = fullPath.lastIndexOfChar ('.');
383 if (lastDot > lastSlash)
384 return fullPath.substring (lastSlash, lastDot);
386 return fullPath.substring (lastSlash);
389 bool File::isAChildOf (const File& potentialParent) const
391 if (potentialParent.fullPath.isEmpty())
392 return false;
394 const String ourPath (getPathUpToLastSlash());
396 if (compareFilenames (potentialParent.fullPath, ourPath) == 0)
397 return true;
399 if (potentialParent.fullPath.length() >= ourPath.length())
400 return false;
402 return getParentDirectory().isAChildOf (potentialParent);
405 //==============================================================================
406 bool File::isAbsolutePath (const char* const path)
408 const char firstChar = *path;
410 return firstChar == CARLA_OS_SEP
411 #ifdef CARLA_OS_WIN
412 || (firstChar != '\0' && path[1] == ':');
413 #else
414 || firstChar == '~';
415 #endif
418 File File::getChildFile (const char* const relativePath) const
420 if (isAbsolutePath (relativePath))
421 return File (relativePath);
423 CharPointer_UTF8 r(relativePath);
425 #ifdef CARLA_OS_WIN
426 if (r.indexOf ((water_uchar) '/') >= 0)
427 return getChildFile (String (r).replaceCharacter ('/', '\\').toRawUTF8());
428 #endif
430 String path (fullPath);
432 while (*r == '.')
434 CharPointer_UTF8 lastPos = r;
435 const water_uchar secondChar = *++r;
437 if (secondChar == '.') // remove "../"
439 const water_uchar thirdChar = *++r;
441 if (thirdChar == CARLA_OS_SEP || thirdChar == 0)
443 const int lastSlash = path.lastIndexOfChar (CARLA_OS_SEP);
444 if (lastSlash >= 0)
445 path = path.substring (0, lastSlash);
447 while (*r == CARLA_OS_SEP) // ignore duplicate slashes
448 ++r;
450 else
452 r = lastPos;
453 break;
456 else if (secondChar == CARLA_OS_SEP || secondChar == 0) // remove "./"
458 while (*r == CARLA_OS_SEP) // ignore duplicate slashes
459 ++r;
461 else
463 r = lastPos;
464 break;
468 path = addTrailingSeparator (path);
469 path.appendCharPointer (r);
470 return File (path.toRawUTF8());
473 File File::getSiblingFile (const char* const fileName) const
475 return getParentDirectory().getChildFile (fileName);
478 //==============================================================================
479 Result File::create() const
481 if (exists())
482 return Result::ok();
484 const File parentDir (getParentDirectory());
486 if (parentDir == *this)
487 return Result::fail ("Cannot create parent directory");
489 Result r (parentDir.createDirectory());
491 if (r.wasOk())
493 FileOutputStream fo (*this, 8);
494 r = fo.getStatus();
497 return r;
500 Result File::createDirectory() const
502 if (isDirectory())
503 return Result::ok();
505 const File parentDir (getParentDirectory());
507 if (parentDir == *this)
508 return Result::fail ("Cannot create parent directory");
510 Result r (parentDir.createDirectory());
512 if (r.wasOk())
513 r = createDirectoryInternal (fullPath.trimCharactersAtEnd (CARLA_OS_SEP_STR));
515 return r;
518 //==============================================================================
519 int64 File::getLastModificationTime() const
521 int64 m, _;
522 getFileTimesInternal (m, _, _);
523 return m;
526 int64 File::getLastAccessTime() const
528 int64 a, _;
529 getFileTimesInternal (_, a, _);
530 return a;
533 int64 File::getCreationTime() const
535 int64 c, _;
536 getFileTimesInternal (_, _, c);
537 return c;
540 //==============================================================================
541 bool File::loadFileAsData (MemoryBlock& destBlock) const
543 if (! existsAsFile())
544 return false;
546 FileInputStream in (*this);
547 return in.openedOk() && getSize() == (int64) in.readIntoMemoryBlock (destBlock);
550 String File::loadFileAsString() const
552 if (! existsAsFile())
553 return String();
555 FileInputStream in (*this);
556 return in.openedOk() ? in.readEntireStreamAsString()
557 : String();
560 //==============================================================================
561 uint File::findChildFiles (std::vector<File>& results,
562 const int whatToLookFor,
563 const bool searchRecursively,
564 const char* const wildCardPattern) const
566 uint total = 0;
568 for (DirectoryIterator di (*this, searchRecursively, wildCardPattern, whatToLookFor); di.next();)
570 results.push_back (di.getFile());
571 ++total;
574 return total;
577 uint File::getNumberOfChildFiles (const int whatToLookFor, const char* const wildCardPattern) const
579 uint total = 0;
581 for (DirectoryIterator di (*this, false, wildCardPattern, whatToLookFor); di.next();)
582 ++total;
584 return total;
587 bool File::containsSubDirectories() const
589 if (! isDirectory())
590 return false;
592 DirectoryIterator di (*this, false, "*", findDirectories);
593 return di.next();
596 //==============================================================================
597 File File::getNonexistentChildFile (const String& suggestedPrefix,
598 const String& suffix,
599 bool putNumbersInBrackets) const
601 File f (getChildFile (String(suggestedPrefix + suffix).toRawUTF8()));
603 if (f.exists())
605 int number = 1;
606 String prefix (suggestedPrefix);
608 // remove any bracketed numbers that may already be on the end..
609 if (prefix.trim().endsWithChar (')'))
611 putNumbersInBrackets = true;
613 const int openBracks = prefix.lastIndexOfChar ('(');
614 const int closeBracks = prefix.lastIndexOfChar (')');
616 if (openBracks > 0
617 && closeBracks > openBracks
618 && prefix.substring (openBracks + 1, closeBracks).containsOnly ("0123456789"))
620 number = prefix.substring (openBracks + 1, closeBracks).getIntValue();
621 prefix = prefix.substring (0, openBracks);
627 String newName (prefix);
629 if (putNumbersInBrackets)
631 newName << '(' << ++number << ')';
633 else
635 if (CharacterFunctions::isDigit (prefix.getLastCharacter()))
636 newName << '_'; // pad with an underscore if the name already ends in a digit
638 newName << ++number;
641 f = getChildFile (String(newName + suffix).toRawUTF8());
643 } while (f.exists());
646 return f;
649 File File::getNonexistentSibling (const bool putNumbersInBrackets) const
651 if (! exists())
652 return *this;
654 return getParentDirectory().getNonexistentChildFile (getFileNameWithoutExtension(),
655 getFileExtension(),
656 putNumbersInBrackets);
659 //==============================================================================
660 String File::getFileExtension() const
662 const int indexOfDot = fullPath.lastIndexOfChar ('.');
664 if (indexOfDot > fullPath.lastIndexOfChar (CARLA_OS_SEP))
665 return fullPath.substring (indexOfDot);
667 return String();
670 bool File::hasFileExtension (const char* const possibleSuffix_) const
672 const CharPointer_UTF8 possibleSuffix(possibleSuffix_);
674 if (possibleSuffix.isEmpty())
675 return fullPath.lastIndexOfChar ('.') <= fullPath.lastIndexOfChar (CARLA_OS_SEP);
677 const int semicolon = possibleSuffix.indexOf ((water_uchar) ';');
679 if (semicolon >= 0)
680 return hasFileExtension (String (possibleSuffix).substring (0, semicolon).trimEnd().toRawUTF8())
681 || hasFileExtension ((possibleSuffix + (semicolon + 1)).findEndOfWhitespace());
683 if (fullPath.endsWithIgnoreCase (possibleSuffix))
685 if (possibleSuffix[0] == '.')
686 return true;
688 const int dotPos = fullPath.length() - possibleSuffix.length() - 1;
690 if (dotPos >= 0)
691 return fullPath [dotPos] == '.';
694 return false;
697 File File::withFileExtension (const char* const newExtension) const
699 if (fullPath.isEmpty())
700 return File();
702 String filePart (getFileName().toRawUTF8());
704 const int i = filePart.lastIndexOfChar ('.');
705 if (i >= 0)
706 filePart = filePart.substring (0, i);
708 if (newExtension[0] != '\0' && newExtension[0] != '.')
709 filePart << '.';
711 return getSiblingFile (String(filePart + newExtension).toRawUTF8());
714 //==============================================================================
715 FileInputStream* File::createInputStream() const
717 CarlaScopedPointer<FileInputStream> fin (new FileInputStream (*this));
719 if (fin->openedOk())
720 return fin.release();
722 return nullptr;
725 FileOutputStream* File::createOutputStream (const size_t bufferSize) const
727 CarlaScopedPointer<FileOutputStream> out (new FileOutputStream (*this, bufferSize));
729 return out->failedToOpen() ? nullptr
730 : out.release();
733 //==============================================================================
734 bool File::appendData (const void* const dataToAppend,
735 const size_t numberOfBytes) const
737 wassert (((ssize_t) numberOfBytes) >= 0);
739 if (numberOfBytes == 0)
740 return true;
742 FileOutputStream out (*this, 8192);
743 return out.openedOk() && out.write (dataToAppend, numberOfBytes);
746 bool File::replaceWithData (const void* const dataToWrite,
747 const size_t numberOfBytes) const
749 if (numberOfBytes == 0)
750 return deleteFile();
752 TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile);
753 tempFile.getFile().appendData (dataToWrite, numberOfBytes);
754 return tempFile.overwriteTargetFileWithTemporary();
757 bool File::appendText (const String& text,
758 const bool asUnicode,
759 const bool writeUnicodeHeaderBytes) const
761 FileOutputStream out (*this);
762 CARLA_SAFE_ASSERT_RETURN (! out.failedToOpen(), false);
764 out.writeText (text, asUnicode, writeUnicodeHeaderBytes);
765 return true;
768 bool File::replaceWithText (const String& textToWrite,
769 const bool asUnicode,
770 const bool writeUnicodeHeaderBytes) const
772 TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile);
773 tempFile.getFile().appendText (textToWrite, asUnicode, writeUnicodeHeaderBytes);
774 return tempFile.overwriteTargetFileWithTemporary();
777 bool File::hasIdenticalContentTo (const File& other) const
779 if (other == *this)
780 return true;
782 if (getSize() == other.getSize() && existsAsFile() && other.existsAsFile())
784 FileInputStream in1 (*this), in2 (other);
786 if (in1.openedOk() && in2.openedOk())
788 const int bufferSize = 4096;
789 HeapBlock<char> buffer1, buffer2;
791 CARLA_SAFE_ASSERT_RETURN(buffer1.malloc (bufferSize), false);
792 CARLA_SAFE_ASSERT_RETURN(buffer2.malloc (bufferSize), false);
794 for (;;)
796 const int num1 = in1.read (buffer1, bufferSize);
797 const int num2 = in2.read (buffer2, bufferSize);
799 if (num1 != num2)
800 break;
802 if (num1 <= 0)
803 return true;
805 if (memcmp (buffer1, buffer2, (size_t) num1) != 0)
806 break;
811 return false;
814 //==============================================================================
815 String File::createLegalPathName (const String& original)
817 String s (original);
818 String start;
820 if (s.isNotEmpty() && s[1] == ':')
822 start = s.substring (0, 2);
823 s = s.substring (2);
826 return start + s.removeCharacters ("\"#@,;:<>*^|?")
827 .substring (0, 1024);
830 String File::createLegalFileName (const String& original)
832 String s (original.removeCharacters ("\"#@,;:<>*^|?\\/"));
834 const int maxLength = 128; // only the length of the filename, not the whole path
835 const int len = s.length();
837 if (len > maxLength)
839 const int lastDot = s.lastIndexOfChar ('.');
841 if (lastDot > jmax (0, len - 12))
843 s = s.substring (0, maxLength - (len - lastDot))
844 + s.substring (lastDot);
846 else
848 s = s.substring (0, maxLength);
852 return s;
855 //==============================================================================
856 static int countNumberOfSeparators (CharPointer_UTF8 s)
858 int num = 0;
860 for (;;)
862 const water_uchar c = s.getAndAdvance();
864 if (c == 0)
865 break;
867 if (c == CARLA_OS_SEP)
868 ++num;
871 return num;
874 String File::getRelativePathFrom (const File& dir) const
876 String thisPath (fullPath);
878 while (thisPath.endsWithChar (CARLA_OS_SEP))
879 thisPath = thisPath.dropLastCharacters (1);
881 String dirPath (addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName()
882 : dir.fullPath));
884 int commonBitLength = 0;
885 CharPointer_UTF8 thisPathAfterCommon (thisPath.getCharPointer());
886 CharPointer_UTF8 dirPathAfterCommon (dirPath.getCharPointer());
889 CharPointer_UTF8 thisPathIter (thisPath.getCharPointer());
890 CharPointer_UTF8 dirPathIter (dirPath.getCharPointer());
892 for (int i = 0;;)
894 const water_uchar c1 = thisPathIter.getAndAdvance();
895 const water_uchar c2 = dirPathIter.getAndAdvance();
897 #if NAMES_ARE_CASE_SENSITIVE
898 if (c1 != c2
899 #else
900 if ((c1 != c2 && CharacterFunctions::toLowerCase (c1) != CharacterFunctions::toLowerCase (c2))
901 #endif
902 || c1 == 0)
903 break;
905 ++i;
907 if (c1 == CARLA_OS_SEP)
909 thisPathAfterCommon = thisPathIter;
910 dirPathAfterCommon = dirPathIter;
911 commonBitLength = i;
916 // if the only common bit is the root, then just return the full path..
917 if (commonBitLength == 0 || (commonBitLength == 1 && thisPath[1] == CARLA_OS_SEP))
918 return fullPath;
920 const int numUpDirectoriesNeeded = countNumberOfSeparators (dirPathAfterCommon);
922 if (numUpDirectoriesNeeded == 0)
923 return thisPathAfterCommon;
925 #ifdef CARLA_OS_WIN
926 String s (String::repeatedString ("..\\", numUpDirectoriesNeeded));
927 #else
928 String s (String::repeatedString ("../", numUpDirectoriesNeeded));
929 #endif
930 s.appendCharPointer (thisPathAfterCommon);
931 return s;
934 //==============================================================================
935 File File::createTempFile (const char* const fileNameEnding)
937 const File tempFile (getSpecialLocation (tempDirectory)
938 .getChildFile (String("temp_" + String::toHexString (Random::getSystemRandom().nextInt())).toRawUTF8())
939 .withFileExtension (fileNameEnding));
941 if (tempFile.exists())
942 return createTempFile (fileNameEnding);
944 return tempFile;
947 bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const
949 if (linkFileToCreate.exists())
951 // user has specified an existing file / directory as the link
952 // this is bad! the user could end up unintentionally destroying data
953 CARLA_SAFE_ASSERT_RETURN(linkFileToCreate.isSymbolicLink(), false);
955 if (overwriteExisting)
956 linkFileToCreate.deleteFile();
959 #ifdef CARLA_OS_WIN
960 carla_stderr("File::createSymbolicLink failed, unsupported");
961 return false;
962 #else
963 // one common reason for getting an error here is that the file already exists
964 return symlink(fullPath.toRawUTF8(), linkFileToCreate.getFullPathName().toRawUTF8()) != -1;
965 #endif
968 //=====================================================================================================================
969 #ifdef CARLA_OS_WIN
970 namespace WindowsFileHelpers
972 DWORD getAtts (const String& path)
974 return GetFileAttributesW (path.toUTF16().c_str());
977 int64 fileTimeToTime (const FILETIME* const ft)
979 #ifdef CARLA_PROPER_CPP11_SUPPORT
980 static_wassert (sizeof (ULARGE_INTEGER) == sizeof (FILETIME)); // tell me if this fails!
981 #endif
983 return (int64) ((reinterpret_cast<const ULARGE_INTEGER*> (ft)->QuadPart - 116444736000000000LL) / 10000);
986 File getSpecialFolderPath (int type)
988 WCHAR wpath [MAX_PATH + 256];
990 if (SHGetSpecialFolderPathW (nullptr, wpath, type, FALSE))
992 CHAR apath [MAX_PATH + 256];
994 if (WideCharToMultiByte (CP_UTF8, 0, wpath, -1, apath, numElementsInArray (apath), nullptr, nullptr))
995 return File (apath);
998 return File();
1001 File getModuleFileName (HINSTANCE moduleHandle)
1003 WCHAR wdest [MAX_PATH + 256];
1004 CHAR adest [MAX_PATH + 256];
1005 wdest[0] = 0;
1006 GetModuleFileNameW (moduleHandle, wdest, (DWORD) numElementsInArray (wdest));
1008 if (WideCharToMultiByte (CP_UTF8, 0, wdest, -1, adest, numElementsInArray (adest), nullptr, nullptr))
1009 return File (adest);
1011 return File();
1015 bool File::isDirectory() const
1017 const DWORD attr = WindowsFileHelpers::getAtts (fullPath);
1018 return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 && attr != INVALID_FILE_ATTRIBUTES;
1021 bool File::exists() const
1023 return fullPath.isNotEmpty()
1024 && WindowsFileHelpers::getAtts (fullPath) != INVALID_FILE_ATTRIBUTES;
1027 bool File::existsAsFile() const
1029 return fullPath.isNotEmpty()
1030 && (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_DIRECTORY) == 0;
1033 bool File::hasWriteAccess() const
1035 if (fullPath.isEmpty())
1036 return true;
1038 const DWORD attr = WindowsFileHelpers::getAtts (fullPath);
1040 // NB: According to MS, the FILE_ATTRIBUTE_READONLY attribute doesn't work for
1041 // folders, and can be incorrectly set for some special folders, so we'll just say
1042 // that folders are always writable.
1043 return attr == INVALID_FILE_ATTRIBUTES
1044 || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0
1045 || (attr & FILE_ATTRIBUTE_READONLY) == 0;
1048 int64 File::getSize() const
1050 WIN32_FILE_ATTRIBUTE_DATA attributes;
1052 if (GetFileAttributesExW (fullPath.toUTF16().c_str(), GetFileExInfoStandard, &attributes))
1053 return (((int64) attributes.nFileSizeHigh) << 32) | attributes.nFileSizeLow;
1055 return 0;
1058 void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int64& creationTime) const
1060 using namespace WindowsFileHelpers;
1061 WIN32_FILE_ATTRIBUTE_DATA attributes;
1063 if (GetFileAttributesExW (fullPath.toUTF16().c_str(), GetFileExInfoStandard, &attributes))
1065 modificationTime = fileTimeToTime (&attributes.ftLastWriteTime);
1066 creationTime = fileTimeToTime (&attributes.ftCreationTime);
1067 accessTime = fileTimeToTime (&attributes.ftLastAccessTime);
1069 else
1071 creationTime = accessTime = modificationTime = 0;
1075 bool File::deleteFile() const
1077 if (! exists())
1078 return true;
1080 return isDirectory() ? RemoveDirectoryW (fullPath.toUTF16().c_str()) != 0
1081 : DeleteFileW (fullPath.toUTF16().c_str()) != 0;
1084 File File::getLinkedTarget() const
1086 return *this;
1089 bool File::copyInternal (const File& dest) const
1091 return CopyFileW (fullPath.toUTF16().c_str(), dest.getFullPathName().toUTF16().c_str(), false) != 0;
1094 bool File::moveInternal (const File& dest) const
1096 return MoveFileW (fullPath.toUTF16().c_str(), dest.getFullPathName().toUTF16().c_str()) != 0;
1099 bool File::replaceInternal (const File& dest) const
1101 void* lpExclude = 0;
1102 void* lpReserved = 0;
1104 return ReplaceFileW (dest.getFullPathName().toUTF16().c_str(),
1105 fullPath.toUTF16().c_str(),
1106 0, REPLACEFILE_IGNORE_MERGE_ERRORS, lpExclude, lpReserved) != 0;
1109 Result File::createDirectoryInternal (const String& fileName) const
1111 return CreateDirectoryW (fileName.toUTF16().c_str(), 0) ? Result::ok()
1112 : getResultForLastError();
1115 File File::getCurrentWorkingDirectory()
1117 WCHAR wdest [MAX_PATH + 256];
1118 CHAR adest [MAX_PATH + 256];
1119 wdest[0] = 0;
1120 GetCurrentDirectoryW ((DWORD) numElementsInArray (wdest), wdest);
1122 if (WideCharToMultiByte (CP_UTF8, 0, wdest, -1, adest, numElementsInArray (adest), nullptr, nullptr))
1123 return File (adest);
1125 return File();
1128 bool File::setAsCurrentWorkingDirectory() const
1130 return SetCurrentDirectoryW (getFullPathName().toUTF16().c_str()) != FALSE;
1133 bool File::isSymbolicLink() const
1135 return (GetFileAttributesW (fullPath.toUTF16().c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
1138 File File::getSpecialLocation (const SpecialLocationType type)
1140 int csidlType = 0;
1142 switch (type)
1144 case userHomeDirectory:
1145 csidlType = CSIDL_PROFILE;
1146 break;
1148 case tempDirectory:
1150 WCHAR wdest [MAX_PATH + 256];
1151 CHAR adest [MAX_PATH + 256];
1152 wdest[0] = 0;
1153 GetTempPathW ((DWORD) numElementsInArray (wdest), wdest);
1155 if (WideCharToMultiByte (CP_UTF8, 0, wdest, -1, adest, numElementsInArray (adest), nullptr, nullptr))
1156 return File (adest);
1158 return File();
1161 case currentExecutableFile:
1162 return WindowsFileHelpers::getModuleFileName (water::getCurrentModuleInstanceHandle());
1164 case hostApplicationPath:
1165 return WindowsFileHelpers::getModuleFileName (nullptr);
1167 case winAppData:
1168 csidlType = CSIDL_APPDATA;
1169 break;
1171 case winProgramFiles:
1172 csidlType = CSIDL_PROGRAM_FILES;
1173 break;
1175 case winCommonProgramFiles:
1176 csidlType = CSIDL_PROGRAM_FILES_COMMON;
1177 break;
1179 case winMyDocuments:
1180 csidlType = CSIDL_MYDOCUMENTS;
1181 break;
1183 default:
1184 wassertfalse; // unknown type?
1185 return File();
1188 return WindowsFileHelpers::getSpecialFolderPath (csidlType);
1191 //=====================================================================================================================
1192 class DirectoryIterator::NativeIterator::Pimpl
1194 public:
1195 Pimpl (const File& directory, const String& wildCard)
1196 : directoryWithWildCard (directory.getFullPathName().isNotEmpty() ? File::addTrailingSeparator (directory.getFullPathName()) + wildCard : String()),
1197 handle (INVALID_HANDLE_VALUE)
1201 ~Pimpl()
1203 if (handle != INVALID_HANDLE_VALUE)
1204 FindClose (handle);
1207 bool next (String& filenameFound, bool* const isDir, int64* const fileSize, bool* const isReadOnly)
1209 using namespace WindowsFileHelpers;
1210 WIN32_FIND_DATAW findData;
1212 if (handle == INVALID_HANDLE_VALUE)
1214 handle = FindFirstFileW (directoryWithWildCard.toUTF16().c_str(), &findData);
1216 if (handle == INVALID_HANDLE_VALUE)
1217 return false;
1219 else
1221 if (FindNextFileW (handle, &findData) == 0)
1222 return false;
1225 CHAR apath [MAX_PATH + 256];
1227 if (WideCharToMultiByte (CP_UTF8, 0, findData.cFileName, -1, apath, numElementsInArray (apath), nullptr, nullptr))
1228 filenameFound = apath;
1230 if (isDir != nullptr) *isDir = ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
1231 if (isReadOnly != nullptr) *isReadOnly = ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
1232 if (fileSize != nullptr) *fileSize = findData.nFileSizeLow + (((int64) findData.nFileSizeHigh) << 32);
1234 return true;
1237 private:
1238 const String directoryWithWildCard;
1239 HANDLE handle;
1241 CARLA_DECLARE_NON_COPYABLE (Pimpl)
1243 #else
1244 //=====================================================================================================================
1245 namespace
1247 #ifdef __GLIBC__
1248 typedef struct stat64 water_statStruct;
1249 #define WATER_STAT stat64
1250 #else
1251 typedef struct stat water_statStruct;
1252 #define WATER_STAT stat
1253 #endif
1255 bool water_stat (const String& fileName, water_statStruct& info)
1257 return fileName.isNotEmpty()
1258 && WATER_STAT (fileName.toUTF8(), &info) == 0;
1261 void updateStatInfoForFile (const String& path, bool* const isDir, int64* const fileSize, bool* const isReadOnly)
1263 if (isDir != nullptr || fileSize != nullptr)
1265 water_statStruct info;
1266 const bool statOk = water_stat (path, info);
1268 if (isDir != nullptr) *isDir = statOk && ((info.st_mode & S_IFDIR) != 0);
1269 if (fileSize != nullptr) *fileSize = statOk ? (int64) info.st_size : 0;
1272 if (isReadOnly != nullptr)
1273 *isReadOnly = access (path.toUTF8(), W_OK) != 0;
1276 Result getResultForReturnValue (int value)
1278 return value == -1 ? getResultForErrno() : Result::ok();
1282 bool File::isDirectory() const
1284 water_statStruct info;
1286 return fullPath.isNotEmpty()
1287 && (water_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0));
1290 bool File::exists() const
1292 return fullPath.isNotEmpty()
1293 && access (fullPath.toUTF8(), F_OK) == 0;
1296 bool File::existsAsFile() const
1298 return exists() && ! isDirectory();
1301 bool File::hasWriteAccess() const
1303 if (exists())
1304 return access (fullPath.toUTF8(), W_OK) == 0;
1306 if ((! isDirectory()) && fullPath.containsChar (CARLA_OS_SEP))
1307 return getParentDirectory().hasWriteAccess();
1309 return false;
1312 void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int64& creationTime) const
1314 water_statStruct info;
1316 if (water_stat (fullPath, info))
1318 modificationTime = (int64) info.st_mtime * 1000;
1319 accessTime = (int64) info.st_atime * 1000;
1320 creationTime = (int64) info.st_ctime * 1000;
1322 else
1324 modificationTime = accessTime = creationTime = 0;
1328 int64 File::getSize() const
1330 water_statStruct info;
1331 return water_stat (fullPath, info) ? info.st_size : 0;
1334 bool File::deleteFile() const
1336 if (! exists() && ! isSymbolicLink())
1337 return true;
1339 if (isDirectory())
1340 return rmdir (fullPath.toUTF8()) == 0;
1342 return remove (fullPath.toUTF8()) == 0;
1345 bool File::moveInternal (const File& dest) const
1347 if (rename (fullPath.toUTF8(), dest.getFullPathName().toUTF8()) == 0)
1348 return true;
1350 if (hasWriteAccess() && copyInternal (dest))
1352 if (deleteFile())
1353 return true;
1355 dest.deleteFile();
1358 return false;
1361 bool File::replaceInternal (const File& dest) const
1363 return moveInternal (dest);
1366 Result File::createDirectoryInternal (const String& fileName) const
1368 return getResultForReturnValue (mkdir (fileName.toUTF8(), 0777));
1371 File File::getCurrentWorkingDirectory()
1373 HeapBlock<char> heapBuffer;
1375 char localBuffer [1024];
1376 char* cwd = getcwd (localBuffer, sizeof (localBuffer) - 1);
1378 size_t bufferSize = 4096;
1379 while (cwd == nullptr && errno == ERANGE)
1381 CARLA_SAFE_ASSERT_RETURN(heapBuffer.malloc (bufferSize), File());
1383 cwd = getcwd (heapBuffer, bufferSize - 1);
1384 bufferSize += 1024;
1387 return File (CharPointer_UTF8 (cwd));
1390 bool File::setAsCurrentWorkingDirectory() const
1392 return chdir (getFullPathName().toUTF8()) == 0;
1395 File water_getExecutableFile();
1396 File water_getExecutableFile()
1398 struct DLAddrReader
1400 static String getFilename()
1402 Dl_info exeInfo;
1403 void* localSymbol = (void*) water_getExecutableFile;
1404 dladdr (localSymbol, &exeInfo);
1405 const CharPointer_UTF8 filename (exeInfo.dli_fname);
1407 // if the filename is absolute simply return it
1408 if (File::isAbsolutePath (filename))
1409 return filename;
1411 // if the filename is relative construct from CWD
1412 if (filename[0] == '.')
1413 return File::getCurrentWorkingDirectory().getChildFile (filename).getFullPathName();
1415 // filename is abstract, look up in PATH
1416 if (const char* const envpath = ::getenv ("PATH"))
1418 StringArray paths (StringArray::fromTokens (envpath, ":", ""));
1420 for (int i=paths.size(); --i>=0;)
1422 const File filepath (File (paths[i].toRawUTF8()).getChildFile (filename));
1424 if (filepath.existsAsFile())
1425 return filepath.getFullPathName();
1429 // if we reach this, we failed to find ourselves...
1430 wassertfalse;
1431 return filename;
1435 static String filename (DLAddrReader::getFilename());
1436 return filename.toRawUTF8();
1439 #ifdef CARLA_OS_MAC
1440 static NSString* getFileLink (const String& path)
1442 return [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath: waterStringToNS (path) error: nil];
1445 bool File::isSymbolicLink() const
1447 return getFileLink (fullPath) != nil;
1450 File File::getLinkedTarget() const
1452 if (NSString* dest = getFileLink (fullPath))
1453 return getSiblingFile ([dest UTF8String]);
1455 return *this;
1458 bool File::copyInternal (const File& dest) const
1460 const AutoNSAutoreleasePool arpool;
1462 NSFileManager* fm = [NSFileManager defaultManager];
1464 return [fm fileExistsAtPath: waterStringToNS (fullPath)]
1465 #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
1466 && [fm copyItemAtPath: waterStringToNS (fullPath)
1467 toPath: waterStringToNS (dest.getFullPathName())
1468 error: nil];
1469 #else
1470 && [fm copyPath: waterStringToNS (fullPath)
1471 toPath: waterStringToNS (dest.getFullPathName())
1472 handler: nil];
1473 #endif
1476 File File::getSpecialLocation (const SpecialLocationType type)
1478 const AutoNSAutoreleasePool arpool;
1480 String resultPath;
1482 switch (type)
1484 case userHomeDirectory:
1485 resultPath = [NSHomeDirectory() UTF8String];
1486 break;
1488 case tempDirectory:
1490 File tmp (String("~/Library/Caches/" + water_getExecutableFile().getFileNameWithoutExtension()).toRawUTF8());
1491 tmp.createDirectory();
1492 return File (tmp.getFullPathName().toRawUTF8());
1495 case currentExecutableFile:
1496 return water_getExecutableFile();
1498 case hostApplicationPath:
1500 unsigned int size = 8192;
1501 char* const buffer = new char[size + 8];
1502 _NSGetExecutablePath (buffer, &size);
1503 buffer[size] = 0;
1504 return File (buffer);
1507 default:
1508 wassertfalse; // unknown type?
1509 break;
1512 if (resultPath.isNotEmpty())
1513 return File (resultPath.convertToPrecomposedUnicode().toRawUTF8());
1515 return File();
1517 //==============================================================================
1518 class DirectoryIterator::NativeIterator::Pimpl
1520 public:
1521 Pimpl (const File& directory, const String& wildCard_)
1522 : parentDir (File::addTrailingSeparator (directory.getFullPathName())),
1523 wildCard (wildCard_),
1524 enumerator (nil)
1526 const AutoNSAutoreleasePool arpool;
1528 enumerator = [[[NSFileManager defaultManager] enumeratorAtPath: waterStringToNS (directory.getFullPathName())] retain];
1531 ~Pimpl()
1533 [enumerator release];
1536 bool next (String& filenameFound, bool* const isDir, int64* const fileSize,bool* const isReadOnly)
1538 const AutoNSAutoreleasePool arpool;
1540 const char* wildcardUTF8 = nullptr;
1542 for (;;)
1544 NSString* file;
1545 if (enumerator == nil || (file = [enumerator nextObject]) == nil)
1546 return false;
1548 [enumerator skipDescendents];
1549 filenameFound = nsStringToWater (file).convertToPrecomposedUnicode();
1551 if (wildcardUTF8 == nullptr)
1552 wildcardUTF8 = wildCard.toUTF8();
1554 if (fnmatch (wildcardUTF8, filenameFound.toUTF8(), FNM_CASEFOLD) != 0)
1555 continue;
1557 const String fullPath (parentDir + filenameFound);
1558 updateStatInfoForFile (fullPath, isDir, fileSize, isReadOnly);
1560 return true;
1564 private:
1565 String parentDir, wildCard;
1566 NSDirectoryEnumerator* enumerator;
1568 CARLA_DECLARE_NON_COPYABLE (Pimpl)
1570 #else
1571 static String getLinkedFile (const String& file)
1573 HeapBlock<char> buffer;
1574 CARLA_SAFE_ASSERT_RETURN(buffer.malloc(8194), String());
1576 const int numBytes = (int) readlink (file.toRawUTF8(), buffer, 8192);
1577 return String::fromUTF8 (buffer, jmax (0, numBytes));
1580 bool File::isSymbolicLink() const
1582 return getLinkedFile (getFullPathName()).isNotEmpty();
1585 File File::getLinkedTarget() const
1587 String f (getLinkedFile (getFullPathName()));
1589 if (f.isNotEmpty())
1590 return getSiblingFile (f.toRawUTF8());
1592 return *this;
1595 bool File::copyInternal (const File& dest) const
1597 FileInputStream in (*this);
1599 if (dest.deleteFile())
1602 FileOutputStream out (dest);
1604 if (out.failedToOpen())
1605 return false;
1607 if (out.writeFromInputStream (in, -1) == getSize())
1608 return true;
1611 dest.deleteFile();
1614 return false;
1617 File File::getSpecialLocation (const SpecialLocationType type)
1619 switch (type)
1621 case userHomeDirectory:
1623 if (const char* homeDir = getenv ("HOME"))
1624 return File (CharPointer_UTF8 (homeDir));
1626 if (struct passwd* const pw = getpwuid (getuid()))
1627 return File (CharPointer_UTF8 (pw->pw_dir));
1629 return File();
1632 case tempDirectory:
1634 File tmp ("/var/tmp");
1636 if (! tmp.isDirectory())
1638 tmp = "/tmp";
1640 if (! tmp.isDirectory())
1641 tmp = File::getCurrentWorkingDirectory();
1644 return tmp;
1647 case currentExecutableFile:
1648 return water_getExecutableFile();
1650 case hostApplicationPath:
1652 const File f ("/proc/self/exe");
1653 return f.isSymbolicLink() ? f.getLinkedTarget() : water_getExecutableFile();
1656 default:
1657 wassertfalse; // unknown type?
1658 break;
1661 return File();
1663 //==============================================================================
1664 class DirectoryIterator::NativeIterator::Pimpl
1666 public:
1667 Pimpl (const File& directory, const String& wc)
1668 : parentDir (File::addTrailingSeparator (directory.getFullPathName())),
1669 wildCard (wc), dir (opendir (directory.getFullPathName().toUTF8()))
1673 ~Pimpl()
1675 if (dir != nullptr)
1676 closedir (dir);
1679 bool next (String& filenameFound, bool* const isDir, int64* const fileSize,bool* const isReadOnly)
1681 if (dir != nullptr)
1683 const char* wildcardUTF8 = nullptr;
1685 for (;;)
1687 struct dirent* const de = readdir (dir);
1689 if (de == nullptr)
1690 break;
1692 if (wildcardUTF8 == nullptr)
1693 wildcardUTF8 = wildCard.toUTF8();
1695 if (fnmatch (wildcardUTF8, de->d_name, FNM_CASEFOLD) == 0)
1697 filenameFound = CharPointer_UTF8 (de->d_name);
1699 updateStatInfoForFile (parentDir + filenameFound, isDir, fileSize, isReadOnly);
1701 return true;
1706 return false;
1709 private:
1710 String parentDir, wildCard;
1711 DIR* dir;
1713 CARLA_DECLARE_NON_COPYABLE (Pimpl)
1715 #endif
1716 #endif
1718 DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCardStr)
1719 : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCardStr))
1723 DirectoryIterator::NativeIterator::~NativeIterator() {}
1725 bool DirectoryIterator::NativeIterator::next (String& filenameFound, bool* isDir, int64* fileSize,bool* isReadOnly)
1727 return pimpl->next (filenameFound, isDir, fileSize, isReadOnly);