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