1 /*
2   ==============================================================================
3 
4    This file is part of the Water library.
5    Copyright (c) 2016 ROLI Ltd.
6    Copyright (C) 2017-2018 Filipe Coelho <falktx@falktx.com>
7 
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/
10 
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.
14 
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.
22 
23   ==============================================================================
24 */
25 
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"
34 
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
52 
53 namespace water {
54 
File()55 File::File () noexcept
56     : fullPath ()
57 {
58 }
59 
File(const String & fullPathName)60 File::File (const String& fullPathName)
61     : fullPath (parseAbsolutePath (fullPathName))
62 {
63 }
64 
createFileWithoutCheckingPath(const String & path)65 File File::createFileWithoutCheckingPath (const String& path) noexcept
66 {
67     File f;
68     f.fullPath = path;
69     return f;
70 }
71 
File(const File & other)72 File::File (const File& other)
73     : fullPath (other.fullPath)
74 {
75 }
76 
operator =(const String & newPath)77 File& File::operator= (const String& newPath)
78 {
79     fullPath = parseAbsolutePath (newPath);
80     return *this;
81 }
82 
operator =(const File & other)83 File& File::operator= (const File& other)
84 {
85     fullPath = other.fullPath;
86     return *this;
87 }
88 
89 #if WATER_COMPILER_SUPPORTS_MOVE_SEMANTICS
File(File && other)90 File::File (File&& other) noexcept
91     : fullPath (static_cast<String&&> (other.fullPath))
92 {
93 }
94 
operator =(File && other)95 File& File::operator= (File&& other) noexcept
96 {
97     fullPath = static_cast<String&&> (other.fullPath);
98     return *this;
99 }
100 #endif
101 
isNull() const102 bool File::isNull() const
103 {
104     return fullPath.isEmpty();
105 }
106 
isNotNull() const107 bool File::isNotNull() const
108 {
109     return fullPath.isNotEmpty();
110 }
111 
112 //==============================================================================
removeEllipsis(const String & path)113 static String removeEllipsis (const String& path)
114 {
115     // This will quickly find both /../ and /./ at the expense of a minor
116     // false-positive performance hit when path elements end in a dot.
117    #ifdef CARLA_OS_WIN
118     if (path.contains (".\\"))
119    #else
120     if (path.contains ("./"))
121    #endif
122     {
123         StringArray toks;
124         toks.addTokens (path, File::separatorString, StringRef());
125         bool anythingChanged = false;
126 
127         for (int i = 1; i < toks.size(); ++i)
128         {
129             const String& t = toks[i];
130 
131             if (t == ".." && toks[i - 1] != "..")
132             {
133                 anythingChanged = true;
134                 toks.removeRange (i - 1, 2);
135                 i = jmax (0, i - 2);
136             }
137             else if (t == ".")
138             {
139                 anythingChanged = true;
140                 toks.remove (i--);
141             }
142         }
143 
144         if (anythingChanged)
145             return toks.joinIntoString (File::separatorString);
146     }
147 
148     return path;
149 }
150 
parseAbsolutePath(const String & p)151 String File::parseAbsolutePath (const String& p)
152 {
153     if (p.isEmpty())
154         return String();
155 
156 #ifdef CARLA_OS_WIN
157     // Windows..
158     String path (removeEllipsis (p.replaceCharacter ('/', '\\')));
159 
160     if (path.startsWithChar (separator))
161     {
162         if (path[1] != separator)
163         {
164             // Check if path is valid under Wine
165             String testpath ("Z:" + path);
166 
167             if (File(testpath).exists())
168             {
169                 path = testpath;
170             }
171             else
172             {
173                 /*  When you supply a raw string to the File object constructor, it must be an absolute path.
174                     If you're trying to parse a string that may be either a relative path or an absolute path,
175                     you MUST provide a context against which the partial path can be evaluated - you can do
176                     this by simply using File::getChildFile() instead of the File constructor. E.g. saying
177                     "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
178                     path if that's what was supplied, or would evaluate a partial path relative to the CWD.
179                 */
180                 carla_safe_assert(testpath.toRawUTF8(), __FILE__, __LINE__);
181 
182                 path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path;
183             }
184         }
185     }
186     else if (! path.containsChar (':'))
187     {
188         /*  When you supply a raw string to the File object constructor, it must be an absolute path.
189             If you're trying to parse a string that may be either a relative path or an absolute path,
190             you MUST provide a context against which the partial path can be evaluated - you can do
191             this by simply using File::getChildFile() instead of the File constructor. E.g. saying
192             "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
193             path if that's what was supplied, or would evaluate a partial path relative to the CWD.
194         */
195         carla_safe_assert(path.toRawUTF8(), __FILE__, __LINE__);
196 
197         return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName();
198     }
199 #else
200     // Mac or Linux..
201 
202     // Yes, I know it's legal for a unix pathname to contain a backslash, but this assertion is here
203     // to catch anyone who's trying to run code that was written on Windows with hard-coded path names.
204     // If that's why you've ended up here, use File::getChildFile() to build your paths instead.
205     wassert ((! p.containsChar ('\\')) || (p.indexOfChar ('/') >= 0 && p.indexOfChar ('/') < p.indexOfChar ('\\')));
206 
207     String path (removeEllipsis (p));
208 
209     if (path.startsWithChar ('~'))
210     {
211         if (path[1] == separator || path[1] == 0)
212         {
213             // expand a name of the form "~/abc"
214             path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName()
215                     + path.substring (1);
216         }
217         else
218         {
219             // expand a name of type "~dave/abc"
220             const String userName (path.substring (1).upToFirstOccurrenceOf ("/", false, false));
221 
222             if (struct passwd* const pw = getpwnam (userName.toUTF8()))
223                 path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false);
224         }
225     }
226     else if (! path.startsWithChar (separator))
227     {
228         return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName();
229     }
230 #endif
231 
232     while (path.endsWithChar (separator) && path != separatorString) // careful not to turn a single "/" into an empty string.
233         path = path.dropLastCharacters (1);
234 
235     return path;
236 }
237 
addTrailingSeparator(const String & path)238 String File::addTrailingSeparator (const String& path)
239 {
240     return path.endsWithChar (separator) ? path
241                                          : path + separator;
242 }
243 
244 //==============================================================================
245 #if ! (defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN))
246  #define NAMES_ARE_CASE_SENSITIVE 1
247 #endif
248 
areFileNamesCaseSensitive()249 bool File::areFileNamesCaseSensitive()
250 {
251    #if NAMES_ARE_CASE_SENSITIVE
252     return true;
253    #else
254     return false;
255    #endif
256 }
257 
compareFilenames(const String & name1,const String & name2)258 static int compareFilenames (const String& name1, const String& name2) noexcept
259 {
260    #if NAMES_ARE_CASE_SENSITIVE
261     return name1.compare (name2);
262    #else
263     return name1.compareIgnoreCase (name2);
264    #endif
265 }
266 
operator ==(const File & other) const267 bool File::operator== (const File& other) const     { return compareFilenames (fullPath, other.fullPath) == 0; }
operator !=(const File & other) const268 bool File::operator!= (const File& other) const     { return compareFilenames (fullPath, other.fullPath) != 0; }
operator <(const File & other) const269 bool File::operator<  (const File& other) const     { return compareFilenames (fullPath, other.fullPath) <  0; }
operator >(const File & other) const270 bool File::operator>  (const File& other) const     { return compareFilenames (fullPath, other.fullPath) >  0; }
271 
272 //==============================================================================
deleteRecursively() const273 bool File::deleteRecursively() const
274 {
275     bool worked = true;
276 
277     if (isDirectory())
278     {
279         Array<File> subFiles;
280         findChildFiles (subFiles, File::findFilesAndDirectories, false);
281 
282         for (int i = subFiles.size(); --i >= 0;)
283             worked = subFiles.getReference(i).deleteRecursively() && worked;
284     }
285 
286     return deleteFile() && worked;
287 }
288 
moveFileTo(const File & newFile) const289 bool File::moveFileTo (const File& newFile) const
290 {
291     if (newFile.fullPath == fullPath)
292         return true;
293 
294     if (! exists())
295         return false;
296 
297    #if ! NAMES_ARE_CASE_SENSITIVE
298     if (*this != newFile)
299    #endif
300         if (! newFile.deleteFile())
301             return false;
302 
303     return moveInternal (newFile);
304 }
305 
copyFileTo(const File & newFile) const306 bool File::copyFileTo (const File& newFile) const
307 {
308     return (*this == newFile)
309             || (exists() && newFile.deleteFile() && copyInternal (newFile));
310 }
311 
replaceFileIn(const File & newFile) const312 bool File::replaceFileIn (const File& newFile) const
313 {
314     if (newFile.fullPath == fullPath)
315         return true;
316 
317     if (! newFile.exists())
318         return moveFileTo (newFile);
319 
320     if (! replaceInternal (newFile))
321         return false;
322 
323     deleteFile();
324     return true;
325 }
326 
copyDirectoryTo(const File & newDirectory) const327 bool File::copyDirectoryTo (const File& newDirectory) const
328 {
329     if (isDirectory() && newDirectory.createDirectory())
330     {
331         Array<File> subFiles;
332         findChildFiles (subFiles, File::findFiles, false);
333 
334         for (int i = 0; i < subFiles.size(); ++i)
335         {
336             const File& src (subFiles.getReference(i));
337             const File& dst (newDirectory.getChildFile (src.getFileName()));
338 
339             if (src.isSymbolicLink())
340             {
341                 if (! src.getLinkedTarget().createSymbolicLink (dst, true))
342                     return false;
343             }
344             else
345             {
346                 if (! src.copyFileTo (dst))
347                     return false;
348             }
349         }
350 
351         subFiles.clear();
352         findChildFiles (subFiles, File::findDirectories, false);
353 
354         for (int i = 0; i < subFiles.size(); ++i)
355             if (! subFiles.getReference(i).copyDirectoryTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName())))
356                 return false;
357 
358         return true;
359     }
360 
361     return false;
362 }
363 
364 //==============================================================================
getPathUpToLastSlash() const365 String File::getPathUpToLastSlash() const
366 {
367     const int lastSlash = fullPath.lastIndexOfChar (separator);
368 
369     if (lastSlash > 0)
370         return fullPath.substring (0, lastSlash);
371 
372     if (lastSlash == 0)
373         return separatorString;
374 
375     return fullPath;
376 }
377 
getParentDirectory() const378 File File::getParentDirectory() const
379 {
380     File f;
381     f.fullPath = getPathUpToLastSlash();
382     return f;
383 }
384 
385 //==============================================================================
getFileName() const386 String File::getFileName() const
387 {
388     return fullPath.substring (fullPath.lastIndexOfChar (separator) + 1);
389 }
390 
getFileNameWithoutExtension() const391 String File::getFileNameWithoutExtension() const
392 {
393     const int lastSlash = fullPath.lastIndexOfChar (separator) + 1;
394     const int lastDot   = fullPath.lastIndexOfChar ('.');
395 
396     if (lastDot > lastSlash)
397         return fullPath.substring (lastSlash, lastDot);
398 
399     return fullPath.substring (lastSlash);
400 }
401 
isAChildOf(const File & potentialParent) const402 bool File::isAChildOf (const File& potentialParent) const
403 {
404     if (potentialParent.fullPath.isEmpty())
405         return false;
406 
407     const String ourPath (getPathUpToLastSlash());
408 
409     if (compareFilenames (potentialParent.fullPath, ourPath) == 0)
410         return true;
411 
412     if (potentialParent.fullPath.length() >= ourPath.length())
413         return false;
414 
415     return getParentDirectory().isAChildOf (potentialParent);
416 }
417 
418 //==============================================================================
isAbsolutePath(StringRef path)419 bool File::isAbsolutePath (StringRef path)
420 {
421     const water_uchar firstChar = *(path.text);
422 
423     return firstChar == separator
424            #ifdef CARLA_OS_WIN
425             || (firstChar != 0 && path.text[1] == ':');
426            #else
427             || firstChar == '~';
428            #endif
429 }
430 
getChildFile(StringRef relativePath) const431 File File::getChildFile (StringRef relativePath) const
432 {
433     String::CharPointerType r = relativePath.text;
434 
435     if (isAbsolutePath (r))
436         return File (String (r));
437 
438    #ifdef CARLA_OS_WIN
439     if (r.indexOf ((water_uchar) '/') >= 0)
440         return getChildFile (String (r).replaceCharacter ('/', '\\'));
441    #endif
442 
443     String path (fullPath);
444 
445     while (*r == '.')
446     {
447         String::CharPointerType lastPos = r;
448         const water_uchar secondChar = *++r;
449 
450         if (secondChar == '.') // remove "../"
451         {
452             const water_uchar thirdChar = *++r;
453 
454             if (thirdChar == separator || thirdChar == 0)
455             {
456                 const int lastSlash = path.lastIndexOfChar (separator);
457                 if (lastSlash >= 0)
458                     path = path.substring (0, lastSlash);
459 
460                 while (*r == separator) // ignore duplicate slashes
461                     ++r;
462             }
463             else
464             {
465                 r = lastPos;
466                 break;
467             }
468         }
469         else if (secondChar == separator || secondChar == 0)  // remove "./"
470         {
471             while (*r == separator) // ignore duplicate slashes
472                 ++r;
473         }
474         else
475         {
476             r = lastPos;
477             break;
478         }
479     }
480 
481     path = addTrailingSeparator (path);
482     path.appendCharPointer (r);
483     return File (path);
484 }
485 
getSiblingFile(StringRef fileName) const486 File File::getSiblingFile (StringRef fileName) const
487 {
488     return getParentDirectory().getChildFile (fileName);
489 }
490 
491 //==============================================================================
descriptionOfSizeInBytes(const int64 bytes)492 String File::descriptionOfSizeInBytes (const int64 bytes)
493 {
494     const char* suffix;
495     double divisor = 0;
496 
497     if (bytes == 1)                       { suffix = " byte"; }
498     else if (bytes < 1024)                { suffix = " bytes"; }
499     else if (bytes < 1024 * 1024)         { suffix = " KB"; divisor = 1024.0; }
500     else if (bytes < 1024 * 1024 * 1024)  { suffix = " MB"; divisor = 1024.0 * 1024.0; }
501     else                                  { suffix = " GB"; divisor = 1024.0 * 1024.0 * 1024.0; }
502 
503     return (divisor > 0 ? String (bytes / divisor, 1) : String (bytes)) + suffix;
504 }
505 
506 //==============================================================================
create() const507 Result File::create() const
508 {
509     if (exists())
510         return Result::ok();
511 
512     const File parentDir (getParentDirectory());
513 
514     if (parentDir == *this)
515         return Result::fail ("Cannot create parent directory");
516 
517     Result r (parentDir.createDirectory());
518 
519     if (r.wasOk())
520     {
521         FileOutputStream fo (*this, 8);
522         r = fo.getStatus();
523     }
524 
525     return r;
526 }
527 
createDirectory() const528 Result File::createDirectory() const
529 {
530     if (isDirectory())
531         return Result::ok();
532 
533     const File parentDir (getParentDirectory());
534 
535     if (parentDir == *this)
536         return Result::fail ("Cannot create parent directory");
537 
538     Result r (parentDir.createDirectory());
539 
540     if (r.wasOk())
541         r = createDirectoryInternal (fullPath.trimCharactersAtEnd (separatorString));
542 
543     return r;
544 }
545 
546 //==============================================================================
loadFileAsData(MemoryBlock & destBlock) const547 bool File::loadFileAsData (MemoryBlock& destBlock) const
548 {
549     if (! existsAsFile())
550         return false;
551 
552     FileInputStream in (*this);
553     return in.openedOk() && getSize() == (int64) in.readIntoMemoryBlock (destBlock);
554 }
555 
loadFileAsString() const556 String File::loadFileAsString() const
557 {
558     if (! existsAsFile())
559         return String();
560 
561     FileInputStream in (*this);
562     return in.openedOk() ? in.readEntireStreamAsString()
563                          : String();
564 }
565 
readLines(StringArray & destLines) const566 void File::readLines (StringArray& destLines) const
567 {
568     destLines.addLines (loadFileAsString());
569 }
570 
571 //==============================================================================
findChildFiles(Array<File> & results,const int whatToLookFor,const bool searchRecursively,const String & wildCardPattern) const572 int File::findChildFiles (Array<File>& results,
573                           const int whatToLookFor,
574                           const bool searchRecursively,
575                           const String& wildCardPattern) const
576 {
577     int total = 0;
578 
579     for (DirectoryIterator di (*this, searchRecursively, wildCardPattern, whatToLookFor); di.next();)
580     {
581         results.add (di.getFile());
582         ++total;
583     }
584 
585     return total;
586 }
587 
getNumberOfChildFiles(const int whatToLookFor,const String & wildCardPattern) const588 int File::getNumberOfChildFiles (const int whatToLookFor, const String& wildCardPattern) const
589 {
590     int total = 0;
591 
592     for (DirectoryIterator di (*this, false, wildCardPattern, whatToLookFor); di.next();)
593         ++total;
594 
595     return total;
596 }
597 
containsSubDirectories() const598 bool File::containsSubDirectories() const
599 {
600     if (! isDirectory())
601         return false;
602 
603     DirectoryIterator di (*this, false, "*", findDirectories);
604     return di.next();
605 }
606 
607 //==============================================================================
getNonexistentChildFile(const String & suggestedPrefix,const String & suffix,bool putNumbersInBrackets) const608 File File::getNonexistentChildFile (const String& suggestedPrefix,
609                                     const String& suffix,
610                                     bool putNumbersInBrackets) const
611 {
612     File f (getChildFile (suggestedPrefix + suffix));
613 
614     if (f.exists())
615     {
616         int number = 1;
617         String prefix (suggestedPrefix);
618 
619         // remove any bracketed numbers that may already be on the end..
620         if (prefix.trim().endsWithChar (')'))
621         {
622             putNumbersInBrackets = true;
623 
624             const int openBracks  = prefix.lastIndexOfChar ('(');
625             const int closeBracks = prefix.lastIndexOfChar (')');
626 
627             if (openBracks > 0
628                  && closeBracks > openBracks
629                  && prefix.substring (openBracks + 1, closeBracks).containsOnly ("0123456789"))
630             {
631                 number = prefix.substring (openBracks + 1, closeBracks).getIntValue();
632                 prefix = prefix.substring (0, openBracks);
633             }
634         }
635 
636         do
637         {
638             String newName (prefix);
639 
640             if (putNumbersInBrackets)
641             {
642                 newName << '(' << ++number << ')';
643             }
644             else
645             {
646                 if (CharacterFunctions::isDigit (prefix.getLastCharacter()))
647                     newName << '_'; // pad with an underscore if the name already ends in a digit
648 
649                 newName << ++number;
650             }
651 
652             f = getChildFile (newName + suffix);
653 
654         } while (f.exists());
655     }
656 
657     return f;
658 }
659 
getNonexistentSibling(const bool putNumbersInBrackets) const660 File File::getNonexistentSibling (const bool putNumbersInBrackets) const
661 {
662     if (! exists())
663         return *this;
664 
665     return getParentDirectory().getNonexistentChildFile (getFileNameWithoutExtension(),
666                                                          getFileExtension(),
667                                                          putNumbersInBrackets);
668 }
669 
670 //==============================================================================
getFileExtension() const671 String File::getFileExtension() const
672 {
673     const int indexOfDot = fullPath.lastIndexOfChar ('.');
674 
675     if (indexOfDot > fullPath.lastIndexOfChar (separator))
676         return fullPath.substring (indexOfDot);
677 
678     return String();
679 }
680 
hasFileExtension(StringRef possibleSuffix) const681 bool File::hasFileExtension (StringRef possibleSuffix) const
682 {
683     if (possibleSuffix.isEmpty())
684         return fullPath.lastIndexOfChar ('.') <= fullPath.lastIndexOfChar (separator);
685 
686     const int semicolon = possibleSuffix.text.indexOf ((water_uchar) ';');
687 
688     if (semicolon >= 0)
689         return hasFileExtension (String (possibleSuffix.text).substring (0, semicolon).trimEnd())
690                 || hasFileExtension ((possibleSuffix.text + (semicolon + 1)).findEndOfWhitespace());
691 
692     if (fullPath.endsWithIgnoreCase (possibleSuffix))
693     {
694         if (possibleSuffix.text[0] == '.')
695             return true;
696 
697         const int dotPos = fullPath.length() - possibleSuffix.length() - 1;
698 
699         if (dotPos >= 0)
700             return fullPath [dotPos] == '.';
701     }
702 
703     return false;
704 }
705 
withFileExtension(StringRef newExtension) const706 File File::withFileExtension (StringRef newExtension) const
707 {
708     if (fullPath.isEmpty())
709         return File();
710 
711     String filePart (getFileName());
712 
713     const int i = filePart.lastIndexOfChar ('.');
714     if (i >= 0)
715         filePart = filePart.substring (0, i);
716 
717     if (newExtension.isNotEmpty() && newExtension.text[0] != '.')
718         filePart << '.';
719 
720     return getSiblingFile (filePart + newExtension);
721 }
722 
723 //==============================================================================
createInputStream() const724 FileInputStream* File::createInputStream() const
725 {
726     CarlaScopedPointer<FileInputStream> fin (new FileInputStream (*this));
727 
728     if (fin->openedOk())
729         return fin.release();
730 
731     return nullptr;
732 }
733 
createOutputStream(const size_t bufferSize) const734 FileOutputStream* File::createOutputStream (const size_t bufferSize) const
735 {
736     CarlaScopedPointer<FileOutputStream> out (new FileOutputStream (*this, bufferSize));
737 
738     return out->failedToOpen() ? nullptr
739                                : out.release();
740 }
741 
742 //==============================================================================
appendData(const void * const dataToAppend,const size_t numberOfBytes) const743 bool File::appendData (const void* const dataToAppend,
744                        const size_t numberOfBytes) const
745 {
746     wassert (((ssize_t) numberOfBytes) >= 0);
747 
748     if (numberOfBytes == 0)
749         return true;
750 
751     FileOutputStream out (*this, 8192);
752     return out.openedOk() && out.write (dataToAppend, numberOfBytes);
753 }
754 
replaceWithData(const void * const dataToWrite,const size_t numberOfBytes) const755 bool File::replaceWithData (const void* const dataToWrite,
756                             const size_t numberOfBytes) const
757 {
758     if (numberOfBytes == 0)
759         return deleteFile();
760 
761     TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile);
762     tempFile.getFile().appendData (dataToWrite, numberOfBytes);
763     return tempFile.overwriteTargetFileWithTemporary();
764 }
765 
appendText(const String & text,const bool asUnicode,const bool writeUnicodeHeaderBytes) const766 bool File::appendText (const String& text,
767                        const bool asUnicode,
768                        const bool writeUnicodeHeaderBytes) const
769 {
770     FileOutputStream out (*this);
771     CARLA_SAFE_ASSERT_RETURN (! out.failedToOpen(), false);
772 
773     out.writeText (text, asUnicode, writeUnicodeHeaderBytes);
774     return true;
775 }
776 
replaceWithText(const String & textToWrite,const bool asUnicode,const bool writeUnicodeHeaderBytes) const777 bool File::replaceWithText (const String& textToWrite,
778                             const bool asUnicode,
779                             const bool writeUnicodeHeaderBytes) const
780 {
781     TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile);
782     tempFile.getFile().appendText (textToWrite, asUnicode, writeUnicodeHeaderBytes);
783     return tempFile.overwriteTargetFileWithTemporary();
784 }
785 
hasIdenticalContentTo(const File & other) const786 bool File::hasIdenticalContentTo (const File& other) const
787 {
788     if (other == *this)
789         return true;
790 
791     if (getSize() == other.getSize() && existsAsFile() && other.existsAsFile())
792     {
793         FileInputStream in1 (*this), in2 (other);
794 
795         if (in1.openedOk() && in2.openedOk())
796         {
797             const int bufferSize = 4096;
798             HeapBlock<char> buffer1, buffer2;
799 
800             CARLA_SAFE_ASSERT_RETURN(buffer1.malloc (bufferSize), false);
801             CARLA_SAFE_ASSERT_RETURN(buffer2.malloc (bufferSize), false);
802 
803             for (;;)
804             {
805                 const int num1 = in1.read (buffer1, bufferSize);
806                 const int num2 = in2.read (buffer2, bufferSize);
807 
808                 if (num1 != num2)
809                     break;
810 
811                 if (num1 <= 0)
812                     return true;
813 
814                 if (memcmp (buffer1, buffer2, (size_t) num1) != 0)
815                     break;
816             }
817         }
818     }
819 
820     return false;
821 }
822 
823 //==============================================================================
createLegalPathName(const String & original)824 String File::createLegalPathName (const String& original)
825 {
826     String s (original);
827     String start;
828 
829     if (s.isNotEmpty() && s[1] == ':')
830     {
831         start = s.substring (0, 2);
832         s = s.substring (2);
833     }
834 
835     return start + s.removeCharacters ("\"#@,;:<>*^|?")
836                     .substring (0, 1024);
837 }
838 
createLegalFileName(const String & original)839 String File::createLegalFileName (const String& original)
840 {
841     String s (original.removeCharacters ("\"#@,;:<>*^|?\\/"));
842 
843     const int maxLength = 128; // only the length of the filename, not the whole path
844     const int len = s.length();
845 
846     if (len > maxLength)
847     {
848         const int lastDot = s.lastIndexOfChar ('.');
849 
850         if (lastDot > jmax (0, len - 12))
851         {
852             s = s.substring (0, maxLength - (len - lastDot))
853                  + s.substring (lastDot);
854         }
855         else
856         {
857             s = s.substring (0, maxLength);
858         }
859     }
860 
861     return s;
862 }
863 
864 //==============================================================================
countNumberOfSeparators(String::CharPointerType s)865 static int countNumberOfSeparators (String::CharPointerType s)
866 {
867     int num = 0;
868 
869     for (;;)
870     {
871         const water_uchar c = s.getAndAdvance();
872 
873         if (c == 0)
874             break;
875 
876         if (c == File::separator)
877             ++num;
878     }
879 
880     return num;
881 }
882 
getRelativePathFrom(const File & dir) const883 String File::getRelativePathFrom (const File& dir)  const
884 {
885     String thisPath (fullPath);
886 
887     while (thisPath.endsWithChar (separator))
888         thisPath = thisPath.dropLastCharacters (1);
889 
890     String dirPath (addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName()
891                                                              : dir.fullPath));
892 
893     int commonBitLength = 0;
894     String::CharPointerType thisPathAfterCommon (thisPath.getCharPointer());
895     String::CharPointerType dirPathAfterCommon  (dirPath.getCharPointer());
896 
897     {
898         String::CharPointerType thisPathIter (thisPath.getCharPointer());
899         String::CharPointerType dirPathIter  (dirPath.getCharPointer());
900 
901         for (int i = 0;;)
902         {
903             const water_uchar c1 = thisPathIter.getAndAdvance();
904             const water_uchar c2 = dirPathIter.getAndAdvance();
905 
906            #if NAMES_ARE_CASE_SENSITIVE
907             if (c1 != c2
908            #else
909             if ((c1 != c2 && CharacterFunctions::toLowerCase (c1) != CharacterFunctions::toLowerCase (c2))
910            #endif
911                  || c1 == 0)
912                 break;
913 
914             ++i;
915 
916             if (c1 == separator)
917             {
918                 thisPathAfterCommon = thisPathIter;
919                 dirPathAfterCommon  = dirPathIter;
920                 commonBitLength = i;
921             }
922         }
923     }
924 
925     // if the only common bit is the root, then just return the full path..
926     if (commonBitLength == 0 || (commonBitLength == 1 && thisPath[1] == separator))
927         return fullPath;
928 
929     const int numUpDirectoriesNeeded = countNumberOfSeparators (dirPathAfterCommon);
930 
931     if (numUpDirectoriesNeeded == 0)
932         return thisPathAfterCommon;
933 
934    #ifdef CARLA_OS_WIN
935     String s (String::repeatedString ("..\\", numUpDirectoriesNeeded));
936    #else
937     String s (String::repeatedString ("../",  numUpDirectoriesNeeded));
938    #endif
939     s.appendCharPointer (thisPathAfterCommon);
940     return s;
941 }
942 
943 //==============================================================================
createTempFile(StringRef fileNameEnding)944 File File::createTempFile (StringRef fileNameEnding)
945 {
946     const File tempFile (getSpecialLocation (tempDirectory)
947                             .getChildFile ("temp_" + String::toHexString (Random::getSystemRandom().nextInt()))
948                             .withFileExtension (fileNameEnding));
949 
950     if (tempFile.exists())
951         return createTempFile (fileNameEnding);
952 
953     return tempFile;
954 }
955 
createSymbolicLink(const File & linkFileToCreate,bool overwriteExisting) const956 bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const
957 {
958     if (linkFileToCreate.exists())
959     {
960         // user has specified an existing file / directory as the link
961         // this is bad! the user could end up unintentionally destroying data
962         CARLA_SAFE_ASSERT_RETURN(linkFileToCreate.isSymbolicLink(), false);
963 
964         if (overwriteExisting)
965             linkFileToCreate.deleteFile();
966     }
967 
968    #ifdef CARLA_OS_WIN
969     typedef BOOLEAN (WINAPI* PFUNC)(LPCTSTR, LPCTSTR, DWORD);
970 
971 # if defined(__GNUC__) && (__GNUC__ >= 9)
972 #  pragma GCC diagnostic push
973 #  pragma GCC diagnostic ignored "-Wcast-function-type"
974 # endif
975     const PFUNC pfn = (PFUNC)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CreateSymbolicLinkA");
976 # if defined(__GNUC__) && (__GNUC__ >= 9)
977 #  pragma GCC diagnostic pop
978 # endif
979     CARLA_SAFE_ASSERT_RETURN(pfn != nullptr, false);
980 
981     return pfn(linkFileToCreate.getFullPathName().toRawUTF8(), fullPath.toRawUTF8(),
982                isDirectory() ? 0x1 /*SYMBOLIC_LINK_FLAG_DIRECTORY*/ : 0x0) != FALSE;
983    #else
984     // one common reason for getting an error here is that the file already exists
985     return symlink(fullPath.toRawUTF8(), linkFileToCreate.getFullPathName().toRawUTF8()) != -1;
986    #endif
987 }
988 
989 //=====================================================================================================================
990 #ifdef CARLA_OS_WIN
991 namespace WindowsFileHelpers
992 {
getAtts(const String & path)993     DWORD getAtts (const String& path)
994     {
995         return GetFileAttributes (path.toUTF8());
996     }
997 
fileTimeToTime(const FILETIME * const ft)998     int64 fileTimeToTime (const FILETIME* const ft)
999     {
1000 #ifdef CARLA_PROPER_CPP11_SUPPORT
1001         static_wassert (sizeof (ULARGE_INTEGER) == sizeof (FILETIME)); // tell me if this fails!
1002 #endif
1003 
1004         return (int64) ((reinterpret_cast<const ULARGE_INTEGER*> (ft)->QuadPart - 116444736000000000LL) / 10000);
1005     }
1006 
getSpecialFolderPath(int type)1007     File getSpecialFolderPath (int type)
1008     {
1009         CHAR path [MAX_PATH + 256];
1010 
1011         if (SHGetSpecialFolderPath (0, path, type, FALSE))
1012             return File (String (path));
1013 
1014         return File();
1015     }
1016 
getModuleFileName(HINSTANCE moduleHandle)1017     File getModuleFileName (HINSTANCE moduleHandle)
1018     {
1019         CHAR dest [MAX_PATH + 256];
1020         dest[0] = 0;
1021         GetModuleFileName (moduleHandle, dest, (DWORD) numElementsInArray (dest));
1022         return File (String (dest));
1023     }
1024 }
1025 
1026 const water_uchar File::separator = '\\';
1027 const String File::separatorString ("\\");
1028 
isDirectory() const1029 bool File::isDirectory() const
1030 {
1031     const DWORD attr = WindowsFileHelpers::getAtts (fullPath);
1032     return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 && attr != INVALID_FILE_ATTRIBUTES;
1033 }
1034 
exists() const1035 bool File::exists() const
1036 {
1037     return fullPath.isNotEmpty()
1038             && WindowsFileHelpers::getAtts (fullPath) != INVALID_FILE_ATTRIBUTES;
1039 }
1040 
existsAsFile() const1041 bool File::existsAsFile() const
1042 {
1043     return fullPath.isNotEmpty()
1044             && (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_DIRECTORY) == 0;
1045 }
1046 
hasWriteAccess() const1047 bool File::hasWriteAccess() const
1048 {
1049     if (fullPath.isEmpty())
1050         return true;
1051 
1052     const DWORD attr = WindowsFileHelpers::getAtts (fullPath);
1053 
1054     // NB: According to MS, the FILE_ATTRIBUTE_READONLY attribute doesn't work for
1055     // folders, and can be incorrectly set for some special folders, so we'll just say
1056     // that folders are always writable.
1057     return attr == INVALID_FILE_ATTRIBUTES
1058             || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0
1059             || (attr & FILE_ATTRIBUTE_READONLY) == 0;
1060 }
1061 
getSize() const1062 int64 File::getSize() const
1063 {
1064     WIN32_FILE_ATTRIBUTE_DATA attributes;
1065 
1066     if (GetFileAttributesEx (fullPath.toUTF8(), GetFileExInfoStandard, &attributes))
1067         return (((int64) attributes.nFileSizeHigh) << 32) | attributes.nFileSizeLow;
1068 
1069     return 0;
1070 }
1071 
deleteFile() const1072 bool File::deleteFile() const
1073 {
1074     if (! exists())
1075         return true;
1076 
1077     return isDirectory() ? RemoveDirectory (fullPath.toUTF8()) != 0
1078                          : DeleteFile (fullPath.toUTF8()) != 0;
1079 }
1080 
getLinkedTarget() const1081 File File::getLinkedTarget() const
1082 {
1083     return *this;
1084 }
1085 
copyInternal(const File & dest) const1086 bool File::copyInternal (const File& dest) const
1087 {
1088     return CopyFile (fullPath.toUTF8(), dest.getFullPathName().toUTF8(), false) != 0;
1089 }
1090 
moveInternal(const File & dest) const1091 bool File::moveInternal (const File& dest) const
1092 {
1093     return MoveFile (fullPath.toUTF8(), dest.getFullPathName().toUTF8()) != 0;
1094 }
1095 
replaceInternal(const File & dest) const1096 bool File::replaceInternal (const File& dest) const
1097 {
1098     void* lpExclude = 0;
1099     void* lpReserved = 0;
1100 
1101     return ReplaceFile (dest.getFullPathName().toUTF8(), fullPath.toUTF8(),
1102                         0, REPLACEFILE_IGNORE_MERGE_ERRORS, lpExclude, lpReserved) != 0;
1103 }
1104 
createDirectoryInternal(const String & fileName) const1105 Result File::createDirectoryInternal (const String& fileName) const
1106 {
1107     return CreateDirectory (fileName.toUTF8(), 0) ? Result::ok()
1108                                                   : getResultForLastError();
1109 }
1110 
getCurrentWorkingDirectory()1111 File File::getCurrentWorkingDirectory()
1112 {
1113     CHAR dest [MAX_PATH + 256];
1114     dest[0] = 0;
1115     GetCurrentDirectory ((DWORD) numElementsInArray (dest), dest);
1116     return File (String (dest));
1117 }
1118 
setAsCurrentWorkingDirectory() const1119 bool File::setAsCurrentWorkingDirectory() const
1120 {
1121     return SetCurrentDirectory (getFullPathName().toUTF8()) != FALSE;
1122 }
1123 
isSymbolicLink() const1124 bool File::isSymbolicLink() const
1125 {
1126     return (GetFileAttributes (fullPath.toUTF8()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
1127 }
1128 
getSpecialLocation(const SpecialLocationType type)1129 File File::getSpecialLocation (const SpecialLocationType type)
1130 {
1131     int csidlType = 0;
1132 
1133     switch (type)
1134     {
1135         case userHomeDirectory:
1136             csidlType = CSIDL_PROFILE;
1137             break;
1138 
1139         case tempDirectory:
1140         {
1141             CHAR dest [2048];
1142             dest[0] = 0;
1143             GetTempPath ((DWORD) numElementsInArray (dest), dest);
1144             return File (String (dest));
1145         }
1146 
1147         case currentExecutableFile:
1148             return WindowsFileHelpers::getModuleFileName (water::getCurrentModuleInstanceHandle());
1149 
1150         case hostApplicationPath:
1151             return WindowsFileHelpers::getModuleFileName (nullptr);
1152 
1153         default:
1154             wassertfalse; // unknown type?
1155             return File();
1156     }
1157 
1158     return WindowsFileHelpers::getSpecialFolderPath (csidlType);
1159 }
1160 
1161 //=====================================================================================================================
1162 class DirectoryIterator::NativeIterator::Pimpl
1163 {
1164 public:
Pimpl(const File & directory,const String & wildCard)1165     Pimpl (const File& directory, const String& wildCard)
1166         : directoryWithWildCard (directory.getFullPathName().isNotEmpty() ? File::addTrailingSeparator (directory.getFullPathName()) + wildCard : String()),
1167           handle (INVALID_HANDLE_VALUE)
1168     {
1169     }
1170 
~Pimpl()1171     ~Pimpl()
1172     {
1173         if (handle != INVALID_HANDLE_VALUE)
1174             FindClose (handle);
1175     }
1176 
next(String & filenameFound,bool * const isDir,int64 * const fileSize,Time * const modTime,Time * const creationTime,bool * const isReadOnly)1177     bool next (String& filenameFound,
1178                bool* const isDir, int64* const fileSize,
1179                Time* const modTime, Time* const creationTime, bool* const isReadOnly)
1180     {
1181         using namespace WindowsFileHelpers;
1182         WIN32_FIND_DATA findData;
1183 
1184         if (handle == INVALID_HANDLE_VALUE)
1185         {
1186             handle = FindFirstFile (directoryWithWildCard.toUTF8(), &findData);
1187 
1188             if (handle == INVALID_HANDLE_VALUE)
1189                 return false;
1190         }
1191         else
1192         {
1193             if (FindNextFile (handle, &findData) == 0)
1194                 return false;
1195         }
1196 
1197         filenameFound = findData.cFileName;
1198 
1199         if (isDir != nullptr)         *isDir        = ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
1200         if (isReadOnly != nullptr)    *isReadOnly   = ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
1201         if (fileSize != nullptr)      *fileSize     = findData.nFileSizeLow + (((int64) findData.nFileSizeHigh) << 32);
1202         if (modTime != nullptr)       *modTime      = Time (fileTimeToTime (&findData.ftLastWriteTime));
1203         if (creationTime != nullptr)  *creationTime = Time (fileTimeToTime (&findData.ftCreationTime));
1204 
1205         return true;
1206     }
1207 
1208 private:
1209     const String directoryWithWildCard;
1210     HANDLE handle;
1211 
1212     CARLA_DECLARE_NON_COPY_CLASS (Pimpl)
1213 };
1214 #else
1215 //=====================================================================================================================
1216 namespace
1217 {
1218    #ifdef CARLA_OS_LINUX
1219     typedef struct stat64 water_statStruct;
1220     #define WATER_STAT    stat64
1221    #else
1222     typedef struct stat   water_statStruct;
1223     #define WATER_STAT    stat
1224    #endif
1225 
water_stat(const String & fileName,water_statStruct & info)1226     bool water_stat (const String& fileName, water_statStruct& info)
1227     {
1228         return fileName.isNotEmpty()
1229                  && WATER_STAT (fileName.toUTF8(), &info) == 0;
1230     }
1231 
1232    #if 0 //def CARLA_OS_MAC
1233     static int64 getCreationTime (const water_statStruct& s) noexcept     { return (int64) s.st_birthtime; }
1234    #else
getCreationTime(const water_statStruct & s)1235     static int64 getCreationTime (const water_statStruct& s) noexcept     { return (int64) s.st_ctime; }
1236    #endif
1237 
updateStatInfoForFile(const String & path,bool * const isDir,int64 * const fileSize,Time * const modTime,Time * const creationTime,bool * const isReadOnly)1238     void updateStatInfoForFile (const String& path, bool* const isDir, int64* const fileSize,
1239                                 Time* const modTime, Time* const creationTime, bool* const isReadOnly)
1240     {
1241         if (isDir != nullptr || fileSize != nullptr || modTime != nullptr || creationTime != nullptr)
1242         {
1243             water_statStruct info;
1244             const bool statOk = water_stat (path, info);
1245 
1246             if (isDir != nullptr)         *isDir        = statOk && ((info.st_mode & S_IFDIR) != 0);
1247             if (fileSize != nullptr)      *fileSize     = statOk ? (int64) info.st_size : 0;
1248             if (modTime != nullptr)       *modTime      = Time (statOk ? (int64) info.st_mtime  * 1000 : 0);
1249             if (creationTime != nullptr)  *creationTime = Time (statOk ? getCreationTime (info) * 1000 : 0);
1250         }
1251 
1252         if (isReadOnly != nullptr)
1253             *isReadOnly = access (path.toUTF8(), W_OK) != 0;
1254     }
1255 
getResultForReturnValue(int value)1256     Result getResultForReturnValue (int value)
1257     {
1258         return value == -1 ? getResultForErrno() : Result::ok();
1259     }
1260 }
1261 
1262 const water_uchar File::separator = '/';
1263 const String File::separatorString ("/");
1264 
isDirectory() const1265 bool File::isDirectory() const
1266 {
1267     water_statStruct info;
1268 
1269     return fullPath.isNotEmpty()
1270              && (water_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0));
1271 }
1272 
exists() const1273 bool File::exists() const
1274 {
1275     return fullPath.isNotEmpty()
1276              && access (fullPath.toUTF8(), F_OK) == 0;
1277 }
1278 
existsAsFile() const1279 bool File::existsAsFile() const
1280 {
1281     return exists() && ! isDirectory();
1282 }
1283 
hasWriteAccess() const1284 bool File::hasWriteAccess() const
1285 {
1286     if (exists())
1287         return access (fullPath.toUTF8(), W_OK) == 0;
1288 
1289     if ((! isDirectory()) && fullPath.containsChar (separator))
1290         return getParentDirectory().hasWriteAccess();
1291 
1292     return false;
1293 }
1294 
getSize() const1295 int64 File::getSize() const
1296 {
1297     water_statStruct info;
1298     return water_stat (fullPath, info) ? info.st_size : 0;
1299 }
1300 
deleteFile() const1301 bool File::deleteFile() const
1302 {
1303     if (! exists() && ! isSymbolicLink())
1304         return true;
1305 
1306     if (isDirectory())
1307         return rmdir (fullPath.toUTF8()) == 0;
1308 
1309     return remove (fullPath.toUTF8()) == 0;
1310 }
1311 
moveInternal(const File & dest) const1312 bool File::moveInternal (const File& dest) const
1313 {
1314     if (rename (fullPath.toUTF8(), dest.getFullPathName().toUTF8()) == 0)
1315         return true;
1316 
1317     if (hasWriteAccess() && copyInternal (dest))
1318     {
1319         if (deleteFile())
1320             return true;
1321 
1322         dest.deleteFile();
1323     }
1324 
1325     return false;
1326 }
1327 
replaceInternal(const File & dest) const1328 bool File::replaceInternal (const File& dest) const
1329 {
1330     return moveInternal (dest);
1331 }
1332 
createDirectoryInternal(const String & fileName) const1333 Result File::createDirectoryInternal (const String& fileName) const
1334 {
1335     return getResultForReturnValue (mkdir (fileName.toUTF8(), 0777));
1336 }
1337 
getCurrentWorkingDirectory()1338 File File::getCurrentWorkingDirectory()
1339 {
1340     HeapBlock<char> heapBuffer;
1341 
1342     char localBuffer [1024];
1343     char* cwd = getcwd (localBuffer, sizeof (localBuffer) - 1);
1344 
1345     size_t bufferSize = 4096;
1346     while (cwd == nullptr && errno == ERANGE)
1347     {
1348         CARLA_SAFE_ASSERT_RETURN(heapBuffer.malloc (bufferSize), File());
1349 
1350         cwd = getcwd (heapBuffer, bufferSize - 1);
1351         bufferSize += 1024;
1352     }
1353 
1354     return File (CharPointer_UTF8 (cwd));
1355 }
1356 
setAsCurrentWorkingDirectory() const1357 bool File::setAsCurrentWorkingDirectory() const
1358 {
1359     return chdir (getFullPathName().toUTF8()) == 0;
1360 }
1361 
1362 File water_getExecutableFile();
water_getExecutableFile()1363 File water_getExecutableFile()
1364 {
1365     struct DLAddrReader
1366     {
1367         static String getFilename()
1368         {
1369             Dl_info exeInfo;
1370             void* localSymbol = (void*) water_getExecutableFile;
1371             dladdr (localSymbol, &exeInfo);
1372             const CharPointer_UTF8 filename (exeInfo.dli_fname);
1373 
1374             // if the filename is absolute simply return it
1375             if (File::isAbsolutePath (filename))
1376                 return filename;
1377 
1378             // if the filename is relative construct from CWD
1379             if (filename[0] == '.')
1380                 return File::getCurrentWorkingDirectory().getChildFile (filename).getFullPathName();
1381 
1382             // filename is abstract, look up in PATH
1383             if (const char* const envpath = ::getenv ("PATH"))
1384             {
1385                 StringArray paths (StringArray::fromTokens (envpath, ":", ""));
1386 
1387                 for (int i=paths.size(); --i>=0;)
1388                 {
1389                     const File filepath (File (paths[i]).getChildFile (filename));
1390 
1391                     if (filepath.existsAsFile())
1392                         return filepath.getFullPathName();
1393                 }
1394             }
1395 
1396             // if we reach this, we failed to find ourselves...
1397             wassertfalse;
1398             return filename;
1399         }
1400     };
1401 
1402     static String filename (DLAddrReader::getFilename());
1403     return filename;
1404 }
1405 
1406 #ifdef CARLA_OS_MAC
getFileLink(const String & path)1407 static NSString* getFileLink (const String& path)
1408 {
1409     return [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath: waterStringToNS (path) error: nil];
1410 }
1411 
isSymbolicLink() const1412 bool File::isSymbolicLink() const
1413 {
1414     return getFileLink (fullPath) != nil;
1415 }
1416 
getLinkedTarget() const1417 File File::getLinkedTarget() const
1418 {
1419     if (NSString* dest = getFileLink (fullPath))
1420         return getSiblingFile (nsStringToWater (dest));
1421 
1422     return *this;
1423 }
1424 
copyInternal(const File & dest) const1425 bool File::copyInternal (const File& dest) const
1426 {
1427     const AutoNSAutoreleasePool arpool;
1428 
1429     NSFileManager* fm = [NSFileManager defaultManager];
1430 
1431     return [fm fileExistsAtPath: waterStringToNS (fullPath)]
1432            #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
1433             && [fm copyItemAtPath: waterStringToNS (fullPath)
1434                            toPath: waterStringToNS (dest.getFullPathName())
1435                             error: nil];
1436            #else
1437             && [fm copyPath: waterStringToNS (fullPath)
1438                      toPath: waterStringToNS (dest.getFullPathName())
1439                     handler: nil];
1440            #endif
1441 }
1442 
getSpecialLocation(const SpecialLocationType type)1443 File File::getSpecialLocation (const SpecialLocationType type)
1444 {
1445     const AutoNSAutoreleasePool arpool;
1446 
1447     String resultPath;
1448 
1449     switch (type)
1450     {
1451         case userHomeDirectory:
1452           resultPath = nsStringToWater (NSHomeDirectory());
1453           break;
1454 
1455         case tempDirectory:
1456         {
1457             File tmp ("~/Library/Caches/" + water_getExecutableFile().getFileNameWithoutExtension());
1458             tmp.createDirectory();
1459             return File (tmp.getFullPathName());
1460         }
1461 
1462         case currentExecutableFile:
1463             return water_getExecutableFile();
1464 
1465         case hostApplicationPath:
1466         {
1467             unsigned int size = 8192;
1468             HeapBlock<char> buffer;
1469             buffer.calloc (size + 8);
1470 
1471             _NSGetExecutablePath (buffer.getData(), &size);
1472             return File (String::fromUTF8 (buffer, (int) size));
1473         }
1474 
1475         default:
1476             wassertfalse; // unknown type?
1477             break;
1478     }
1479 
1480     if (resultPath.isNotEmpty())
1481         return File (resultPath.convertToPrecomposedUnicode());
1482 
1483     return File();
1484 }
1485 //==============================================================================
1486 class DirectoryIterator::NativeIterator::Pimpl
1487 {
1488 public:
Pimpl(const File & directory,const String & wildCard_)1489     Pimpl (const File& directory, const String& wildCard_)
1490         : parentDir (File::addTrailingSeparator (directory.getFullPathName())),
1491           wildCard (wildCard_),
1492           enumerator (nil)
1493     {
1494         const AutoNSAutoreleasePool arpool;
1495 
1496         enumerator = [[[NSFileManager defaultManager] enumeratorAtPath: waterStringToNS (directory.getFullPathName())] retain];
1497     }
1498 
~Pimpl()1499     ~Pimpl()
1500     {
1501         [enumerator release];
1502     }
1503 
next(String & filenameFound,bool * const isDir,int64 * const fileSize,Time * const modTime,Time * const creationTime,bool * const isReadOnly)1504     bool next (String& filenameFound,
1505                bool* const isDir, int64* const fileSize,
1506                Time* const modTime, Time* const creationTime, bool* const isReadOnly)
1507     {
1508         const AutoNSAutoreleasePool arpool;
1509 
1510         const char* wildcardUTF8 = nullptr;
1511 
1512         for (;;)
1513         {
1514             NSString* file;
1515             if (enumerator == nil || (file = [enumerator nextObject]) == nil)
1516                 return false;
1517 
1518             [enumerator skipDescendents];
1519             filenameFound = nsStringToWater (file).convertToPrecomposedUnicode();
1520 
1521             if (wildcardUTF8 == nullptr)
1522                 wildcardUTF8 = wildCard.toUTF8();
1523 
1524             if (fnmatch (wildcardUTF8, filenameFound.toUTF8(), FNM_CASEFOLD) != 0)
1525                 continue;
1526 
1527             const String fullPath (parentDir + filenameFound);
1528             updateStatInfoForFile (fullPath, isDir, fileSize, modTime, creationTime, isReadOnly);
1529 
1530             return true;
1531         }
1532     }
1533 
1534 private:
1535     String parentDir, wildCard;
1536     NSDirectoryEnumerator* enumerator;
1537 
1538     CARLA_DECLARE_NON_COPY_CLASS (Pimpl)
1539 };
1540 #else
getLinkedFile(const String & file)1541 static String getLinkedFile (const String& file)
1542 {
1543     HeapBlock<char> buffer;
1544     CARLA_SAFE_ASSERT_RETURN(buffer.malloc(8194), String());
1545 
1546     const int numBytes = (int) readlink (file.toRawUTF8(), buffer, 8192);
1547     return String::fromUTF8 (buffer, jmax (0, numBytes));
1548 }
1549 
isSymbolicLink() const1550 bool File::isSymbolicLink() const
1551 {
1552     return getLinkedFile (getFullPathName()).isNotEmpty();
1553 }
1554 
getLinkedTarget() const1555 File File::getLinkedTarget() const
1556 {
1557     String f (getLinkedFile (getFullPathName()));
1558 
1559     if (f.isNotEmpty())
1560         return getSiblingFile (f);
1561 
1562     return *this;
1563 }
1564 
copyInternal(const File & dest) const1565 bool File::copyInternal (const File& dest) const
1566 {
1567     FileInputStream in (*this);
1568 
1569     if (dest.deleteFile())
1570     {
1571         {
1572             FileOutputStream out (dest);
1573 
1574             if (out.failedToOpen())
1575                 return false;
1576 
1577             if (out.writeFromInputStream (in, -1) == getSize())
1578                 return true;
1579         }
1580 
1581         dest.deleteFile();
1582     }
1583 
1584     return false;
1585 }
1586 
getSpecialLocation(const SpecialLocationType type)1587 File File::getSpecialLocation (const SpecialLocationType type)
1588 {
1589     switch (type)
1590     {
1591         case userHomeDirectory:
1592         {
1593             if (const char* homeDir = getenv ("HOME"))
1594                 return File (CharPointer_UTF8 (homeDir));
1595 
1596             if (struct passwd* const pw = getpwuid (getuid()))
1597                 return File (CharPointer_UTF8 (pw->pw_dir));
1598 
1599             return File();
1600         }
1601 
1602         case tempDirectory:
1603         {
1604             File tmp ("/var/tmp");
1605 
1606             if (! tmp.isDirectory())
1607             {
1608                 tmp = "/tmp";
1609 
1610                 if (! tmp.isDirectory())
1611                     tmp = File::getCurrentWorkingDirectory();
1612             }
1613 
1614             return tmp;
1615         }
1616 
1617         case currentExecutableFile:
1618             return water_getExecutableFile();
1619 
1620         case hostApplicationPath:
1621         {
1622             const File f ("/proc/self/exe");
1623             return f.isSymbolicLink() ? f.getLinkedTarget() : water_getExecutableFile();
1624         }
1625 
1626         default:
1627             wassertfalse; // unknown type?
1628             break;
1629     }
1630 
1631     return File();
1632 }
1633 //==============================================================================
1634 class DirectoryIterator::NativeIterator::Pimpl
1635 {
1636 public:
Pimpl(const File & directory,const String & wc)1637     Pimpl (const File& directory, const String& wc)
1638         : parentDir (File::addTrailingSeparator (directory.getFullPathName())),
1639           wildCard (wc), dir (opendir (directory.getFullPathName().toUTF8()))
1640     {
1641     }
1642 
~Pimpl()1643     ~Pimpl()
1644     {
1645         if (dir != nullptr)
1646             closedir (dir);
1647     }
1648 
next(String & filenameFound,bool * const isDir,int64 * const fileSize,Time * const modTime,Time * const creationTime,bool * const isReadOnly)1649     bool next (String& filenameFound,
1650                bool* const isDir, int64* const fileSize,
1651                Time* const modTime, Time* const creationTime, bool* const isReadOnly)
1652     {
1653         if (dir != nullptr)
1654         {
1655             const char* wildcardUTF8 = nullptr;
1656 
1657             for (;;)
1658             {
1659                 struct dirent* const de = readdir (dir);
1660 
1661                 if (de == nullptr)
1662                     break;
1663 
1664                 if (wildcardUTF8 == nullptr)
1665                     wildcardUTF8 = wildCard.toUTF8();
1666 
1667                 if (fnmatch (wildcardUTF8, de->d_name, FNM_CASEFOLD) == 0)
1668                 {
1669                     filenameFound = CharPointer_UTF8 (de->d_name);
1670 
1671                     updateStatInfoForFile (parentDir + filenameFound, isDir, fileSize, modTime, creationTime, isReadOnly);
1672 
1673                     return true;
1674                 }
1675             }
1676         }
1677 
1678         return false;
1679     }
1680 
1681 private:
1682     String parentDir, wildCard;
1683     DIR* dir;
1684 
1685     CARLA_DECLARE_NON_COPY_CLASS (Pimpl)
1686 };
1687 #endif
1688 #endif
1689 
NativeIterator(const File & directory,const String & wildCardStr)1690 DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCardStr)
1691     : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCardStr))
1692 {
1693 }
1694 
~NativeIterator()1695 DirectoryIterator::NativeIterator::~NativeIterator() {}
1696 
next(String & filenameFound,bool * isDir,int64 * fileSize,Time * modTime,Time * creationTime,bool * isReadOnly)1697 bool DirectoryIterator::NativeIterator::next (String& filenameFound,
1698                                               bool* isDir, int64* fileSize,
1699                                               Time* modTime, Time* creationTime, bool* isReadOnly)
1700 {
1701     return pimpl->next (filenameFound, isDir, fileSize, modTime, creationTime, isReadOnly);
1702 }
1703 
1704 }
1705