1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
File(const String & fullPathName)26 File::File (const String& fullPathName)
27     : fullPath (parseAbsolutePath (fullPathName))
28 {
29 }
30 
createFileWithoutCheckingPath(const String & path)31 File File::createFileWithoutCheckingPath (const String& path) noexcept
32 {
33     File f;
34     f.fullPath = path;
35     return f;
36 }
37 
File(const File & other)38 File::File (const File& other)
39     : fullPath (other.fullPath)
40 {
41 }
42 
operator =(const String & newPath)43 File& File::operator= (const String& newPath)
44 {
45     fullPath = parseAbsolutePath (newPath);
46     return *this;
47 }
48 
operator =(const File & other)49 File& File::operator= (const File& other)
50 {
51     fullPath = other.fullPath;
52     return *this;
53 }
54 
File(File && other)55 File::File (File&& other) noexcept
56     : fullPath (std::move (other.fullPath))
57 {
58 }
59 
operator =(File && other)60 File& File::operator= (File&& other) noexcept
61 {
62     fullPath = std::move (other.fullPath);
63     return *this;
64 }
65 
JUCE_DECLARE_DEPRECATED_STATIC(const File File::nonexistent{};)66 JUCE_DECLARE_DEPRECATED_STATIC (const File File::nonexistent{};)
67 
68 //==============================================================================
69 static String removeEllipsis (const String& path)
70 {
71     // This will quickly find both /../ and /./ at the expense of a minor
72     // false-positive performance hit when path elements end in a dot.
73    #if JUCE_WINDOWS
74     if (path.contains (".\\"))
75    #else
76     if (path.contains ("./"))
77    #endif
78     {
79         StringArray toks;
80         toks.addTokens (path, File::getSeparatorString(), {});
81         bool anythingChanged = false;
82 
83         for (int i = 1; i < toks.size(); ++i)
84         {
85             auto& t = toks[i];
86 
87             if (t == ".." && toks[i - 1] != "..")
88             {
89                 anythingChanged = true;
90                 toks.removeRange (i - 1, 2);
91                 i = jmax (0, i - 2);
92             }
93             else if (t == ".")
94             {
95                 anythingChanged = true;
96                 toks.remove (i--);
97             }
98         }
99 
100         if (anythingChanged)
101             return toks.joinIntoString (File::getSeparatorString());
102     }
103 
104     return path;
105 }
106 
normaliseSeparators(const String & path)107 static String normaliseSeparators (const String& path)
108 {
109     auto normalisedPath = path;
110 
111     String separator (File::getSeparatorString());
112     String doubleSeparator (separator + separator);
113 
114     auto uncPath = normalisedPath.startsWith (doubleSeparator)
115                   && ! normalisedPath.fromFirstOccurrenceOf (doubleSeparator, false, false).startsWith (separator);
116 
117     if (uncPath)
118         normalisedPath = normalisedPath.fromFirstOccurrenceOf (doubleSeparator, false, false);
119 
120     while (normalisedPath.contains (doubleSeparator))
121          normalisedPath = normalisedPath.replace (doubleSeparator, separator);
122 
123     return uncPath ? doubleSeparator + normalisedPath
124                    : normalisedPath;
125 }
126 
isRoot() const127 bool File::isRoot() const
128 {
129     return fullPath.isNotEmpty() && *this == getParentDirectory();
130 }
131 
parseAbsolutePath(const String & p)132 String File::parseAbsolutePath (const String& p)
133 {
134     if (p.isEmpty())
135         return {};
136 
137    #if JUCE_WINDOWS
138     // Windows..
139     auto path = normaliseSeparators (removeEllipsis (p.replaceCharacter ('/', '\\')));
140 
141     if (path.startsWithChar (getSeparatorChar()))
142     {
143         if (path[1] != getSeparatorChar())
144         {
145             /*  When you supply a raw string to the File object constructor, it must be an absolute path.
146                 If you're trying to parse a string that may be either a relative path or an absolute path,
147                 you MUST provide a context against which the partial path can be evaluated - you can do
148                 this by simply using File::getChildFile() instead of the File constructor. E.g. saying
149                 "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
150                 path if that's what was supplied, or would evaluate a partial path relative to the CWD.
151             */
152             jassertfalse;
153 
154             path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path;
155         }
156     }
157     else if (! path.containsChar (':'))
158     {
159         /*  When you supply a raw string to the File object constructor, it must be an absolute path.
160             If you're trying to parse a string that may be either a relative path or an absolute path,
161             you MUST provide a context against which the partial path can be evaluated - you can do
162             this by simply using File::getChildFile() instead of the File constructor. E.g. saying
163             "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
164             path if that's what was supplied, or would evaluate a partial path relative to the CWD.
165         */
166         jassertfalse;
167 
168         return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName();
169     }
170    #else
171     // Mac or Linux..
172 
173     // Yes, I know it's legal for a unix pathname to contain a backslash, but this assertion is here
174     // to catch anyone who's trying to run code that was written on Windows with hard-coded path names.
175     // If that's why you've ended up here, use File::getChildFile() to build your paths instead.
176     jassert ((! p.containsChar ('\\')) || (p.indexOfChar ('/') >= 0 && p.indexOfChar ('/') < p.indexOfChar ('\\')));
177 
178     auto path = normaliseSeparators (removeEllipsis (p));
179 
180     if (path.startsWithChar ('~'))
181     {
182         if (path[1] == getSeparatorChar() || path[1] == 0)
183         {
184             // expand a name of the form "~/abc"
185             path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName()
186                     + path.substring (1);
187         }
188         else
189         {
190             // expand a name of type "~dave/abc"
191             auto userName = path.substring (1).upToFirstOccurrenceOf ("/", false, false);
192 
193             if (auto* pw = getpwnam (userName.toUTF8()))
194                 path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false);
195         }
196     }
197     else if (! path.startsWithChar (getSeparatorChar()))
198     {
199        #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS
200         if (! (path.startsWith ("./") || path.startsWith ("../")))
201         {
202             /*  When you supply a raw string to the File object constructor, it must be an absolute path.
203                 If you're trying to parse a string that may be either a relative path or an absolute path,
204                 you MUST provide a context against which the partial path can be evaluated - you can do
205                 this by simply using File::getChildFile() instead of the File constructor. E.g. saying
206                 "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
207                 path if that's what was supplied, or would evaluate a partial path relative to the CWD.
208             */
209             jassertfalse;
210 
211            #if JUCE_LOG_ASSERTIONS
212             Logger::writeToLog ("Illegal absolute path: " + path);
213            #endif
214         }
215        #endif
216 
217         return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName();
218     }
219    #endif
220 
221     while (path.endsWithChar (getSeparatorChar()) && path != getSeparatorString()) // careful not to turn a single "/" into an empty string.
222         path = path.dropLastCharacters (1);
223 
224     return path;
225 }
226 
addTrailingSeparator(const String & path)227 String File::addTrailingSeparator (const String& path)
228 {
229     return path.endsWithChar (getSeparatorChar()) ? path
230                                                   : path + getSeparatorChar();
231 }
232 
233 //==============================================================================
234 #if JUCE_LINUX
235  #define NAMES_ARE_CASE_SENSITIVE 1
236 #endif
237 
areFileNamesCaseSensitive()238 bool File::areFileNamesCaseSensitive()
239 {
240    #if NAMES_ARE_CASE_SENSITIVE
241     return true;
242    #else
243     return false;
244    #endif
245 }
246 
compareFilenames(const String & name1,const String & name2)247 static int compareFilenames (const String& name1, const String& name2) noexcept
248 {
249    #if NAMES_ARE_CASE_SENSITIVE
250     return name1.compare (name2);
251    #else
252     return name1.compareIgnoreCase (name2);
253    #endif
254 }
255 
operator ==(const File & other) const256 bool File::operator== (const File& other) const     { return compareFilenames (fullPath, other.fullPath) == 0; }
operator !=(const File & other) const257 bool File::operator!= (const File& other) const     { return compareFilenames (fullPath, other.fullPath) != 0; }
operator <(const File & other) const258 bool File::operator<  (const File& other) const     { return compareFilenames (fullPath, other.fullPath) <  0; }
operator >(const File & other) const259 bool File::operator>  (const File& other) const     { return compareFilenames (fullPath, other.fullPath) >  0; }
260 
261 //==============================================================================
setReadOnly(const bool shouldBeReadOnly,const bool applyRecursively) const262 bool File::setReadOnly (const bool shouldBeReadOnly,
263                         const bool applyRecursively) const
264 {
265     bool worked = true;
266 
267     if (applyRecursively && isDirectory())
268         for (auto& f : findChildFiles (File::findFilesAndDirectories, false))
269             worked = f.setReadOnly (shouldBeReadOnly, true) && worked;
270 
271     return setFileReadOnlyInternal (shouldBeReadOnly) && worked;
272 }
273 
setExecutePermission(bool shouldBeExecutable) const274 bool File::setExecutePermission (bool shouldBeExecutable) const
275 {
276     return setFileExecutableInternal (shouldBeExecutable);
277 }
278 
deleteRecursively(bool followSymlinks) const279 bool File::deleteRecursively (bool followSymlinks) const
280 {
281     bool worked = true;
282 
283     if (isDirectory() && (followSymlinks || ! isSymbolicLink()))
284         for (auto& f : findChildFiles (File::findFilesAndDirectories, false))
285             worked = f.deleteRecursively (followSymlinks) && worked;
286 
287     return deleteFile() && worked;
288 }
289 
moveFileTo(const File & newFile) const290 bool File::moveFileTo (const File& newFile) const
291 {
292     if (newFile.fullPath == fullPath)
293         return true;
294 
295     if (! exists())
296         return false;
297 
298    #if ! NAMES_ARE_CASE_SENSITIVE
299     if (*this != newFile)
300    #endif
301         if (! newFile.deleteFile())
302             return false;
303 
304     return moveInternal (newFile);
305 }
306 
copyFileTo(const File & newFile) const307 bool File::copyFileTo (const File& newFile) const
308 {
309     return (*this == newFile)
310             || (exists() && newFile.deleteFile() && copyInternal (newFile));
311 }
312 
replaceFileIn(const File & newFile) const313 bool File::replaceFileIn (const File& newFile) const
314 {
315     if (newFile.fullPath == fullPath)
316         return true;
317 
318     if (! newFile.exists())
319         return moveFileTo (newFile);
320 
321     if (! replaceInternal (newFile))
322         return false;
323 
324     deleteFile();
325     return true;
326 }
327 
copyDirectoryTo(const File & newDirectory) const328 bool File::copyDirectoryTo (const File& newDirectory) const
329 {
330     if (isDirectory() && newDirectory.createDirectory())
331     {
332         for (auto& f : findChildFiles (File::findFiles, false))
333             if (! f.copyFileTo (newDirectory.getChildFile (f.getFileName())))
334                 return false;
335 
336         for (auto& f : findChildFiles (File::findDirectories, false))
337             if (! f.copyDirectoryTo (newDirectory.getChildFile (f.getFileName())))
338                 return false;
339 
340         return true;
341     }
342 
343     return false;
344 }
345 
346 //==============================================================================
getPathUpToLastSlash() const347 String File::getPathUpToLastSlash() const
348 {
349     auto lastSlash = fullPath.lastIndexOfChar (getSeparatorChar());
350 
351     if (lastSlash > 0)
352         return fullPath.substring (0, lastSlash);
353 
354     if (lastSlash == 0)
355         return getSeparatorString();
356 
357     return fullPath;
358 }
359 
getParentDirectory() const360 File File::getParentDirectory() const
361 {
362     return createFileWithoutCheckingPath (getPathUpToLastSlash());
363 }
364 
365 //==============================================================================
getFileName() const366 String File::getFileName() const
367 {
368     return fullPath.substring (fullPath.lastIndexOfChar (getSeparatorChar()) + 1);
369 }
370 
getFileNameWithoutExtension() const371 String File::getFileNameWithoutExtension() const
372 {
373     auto lastSlash = fullPath.lastIndexOfChar (getSeparatorChar()) + 1;
374     auto lastDot   = fullPath.lastIndexOfChar ('.');
375 
376     if (lastDot > lastSlash)
377         return fullPath.substring (lastSlash, lastDot);
378 
379     return fullPath.substring (lastSlash);
380 }
381 
isAChildOf(const File & potentialParent) const382 bool File::isAChildOf (const File& potentialParent) const
383 {
384     if (potentialParent.fullPath.isEmpty())
385         return false;
386 
387     auto ourPath = getPathUpToLastSlash();
388 
389     if (compareFilenames (potentialParent.fullPath, ourPath) == 0)
390         return true;
391 
392     if (potentialParent.fullPath.length() >= ourPath.length())
393         return false;
394 
395     return getParentDirectory().isAChildOf (potentialParent);
396 }
397 
hashCode() const398 int   File::hashCode() const    { return fullPath.hashCode(); }
hashCode64() const399 int64 File::hashCode64() const  { return fullPath.hashCode64(); }
400 
401 //==============================================================================
isAbsolutePath(StringRef path)402 bool File::isAbsolutePath (StringRef path)
403 {
404     auto firstChar = *(path.text);
405 
406     return firstChar == getSeparatorChar()
407            #if JUCE_WINDOWS
408             || (firstChar != 0 && path.text[1] == ':');
409            #else
410             || firstChar == '~';
411            #endif
412 }
413 
getChildFile(StringRef relativePath) const414 File File::getChildFile (StringRef relativePath) const
415 {
416     auto r = relativePath.text;
417 
418     if (isAbsolutePath (r))
419         return File (String (r));
420 
421    #if JUCE_WINDOWS
422     if (r.indexOf ((juce_wchar) '/') >= 0)
423         return getChildFile (String (r).replaceCharacter ('/', '\\'));
424    #endif
425 
426     auto path = fullPath;
427     auto separatorChar = getSeparatorChar();
428 
429     while (*r == '.')
430     {
431         auto lastPos = r;
432         auto secondChar = *++r;
433 
434         if (secondChar == '.') // remove "../"
435         {
436             auto thirdChar = *++r;
437 
438             if (thirdChar == separatorChar || thirdChar == 0)
439             {
440                 auto lastSlash = path.lastIndexOfChar (separatorChar);
441 
442                 if (lastSlash >= 0)
443                     path = path.substring (0, lastSlash);
444 
445                 while (*r == separatorChar) // ignore duplicate slashes
446                     ++r;
447             }
448             else
449             {
450                 r = lastPos;
451                 break;
452             }
453         }
454         else if (secondChar == separatorChar || secondChar == 0)  // remove "./"
455         {
456             while (*r == separatorChar) // ignore duplicate slashes
457                 ++r;
458         }
459         else
460         {
461             r = lastPos;
462             break;
463         }
464     }
465 
466     path = addTrailingSeparator (path);
467     path.appendCharPointer (r);
468     return File (path);
469 }
470 
getSiblingFile(StringRef fileName) const471 File File::getSiblingFile (StringRef fileName) const
472 {
473     return getParentDirectory().getChildFile (fileName);
474 }
475 
476 //==============================================================================
descriptionOfSizeInBytes(const int64 bytes)477 String File::descriptionOfSizeInBytes (const int64 bytes)
478 {
479     const char* suffix;
480     double divisor = 0;
481 
482     if (bytes == 1)                       { suffix = " byte"; }
483     else if (bytes < 1024)                { suffix = " bytes"; }
484     else if (bytes < 1024 * 1024)         { suffix = " KB"; divisor = 1024.0; }
485     else if (bytes < 1024 * 1024 * 1024)  { suffix = " MB"; divisor = 1024.0 * 1024.0; }
486     else                                  { suffix = " GB"; divisor = 1024.0 * 1024.0 * 1024.0; }
487 
488     return (divisor > 0 ? String ((double) bytes / divisor, 1) : String (bytes)) + suffix;
489 }
490 
491 //==============================================================================
create() const492 Result File::create() const
493 {
494     if (exists())
495         return Result::ok();
496 
497     auto parentDir = getParentDirectory();
498 
499     if (parentDir == *this)
500         return Result::fail ("Cannot create parent directory");
501 
502     auto r = parentDir.createDirectory();
503 
504     if (r.wasOk())
505     {
506         FileOutputStream fo (*this, 8);
507         r = fo.getStatus();
508     }
509 
510     return r;
511 }
512 
createDirectory() const513 Result File::createDirectory() const
514 {
515     if (isDirectory())
516         return Result::ok();
517 
518     auto parentDir = getParentDirectory();
519 
520     if (parentDir == *this)
521         return Result::fail ("Cannot create parent directory");
522 
523     auto r = parentDir.createDirectory();
524 
525     if (r.wasOk())
526         r = createDirectoryInternal (fullPath.trimCharactersAtEnd (getSeparatorString()));
527 
528     return r;
529 }
530 
531 //==============================================================================
getLastModificationTime() const532 Time File::getLastModificationTime() const           { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (m); }
getLastAccessTime() const533 Time File::getLastAccessTime() const                 { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (a); }
getCreationTime() const534 Time File::getCreationTime() const                   { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (c); }
535 
setLastModificationTime(Time t) const536 bool File::setLastModificationTime (Time t) const    { return setFileTimesInternal (t.toMilliseconds(), 0, 0); }
setLastAccessTime(Time t) const537 bool File::setLastAccessTime (Time t) const          { return setFileTimesInternal (0, t.toMilliseconds(), 0); }
setCreationTime(Time t) const538 bool File::setCreationTime (Time t) const            { return setFileTimesInternal (0, 0, t.toMilliseconds()); }
539 
540 //==============================================================================
loadFileAsData(MemoryBlock & destBlock) const541 bool File::loadFileAsData (MemoryBlock& destBlock) const
542 {
543     if (! existsAsFile())
544         return false;
545 
546     FileInputStream in (*this);
547     return in.openedOk() && getSize() == (int64) in.readIntoMemoryBlock (destBlock);
548 }
549 
loadFileAsString() const550 String File::loadFileAsString() const
551 {
552     if (! existsAsFile())
553         return {};
554 
555     FileInputStream in (*this);
556     return in.openedOk() ? in.readEntireStreamAsString()
557                          : String();
558 }
559 
readLines(StringArray & destLines) const560 void File::readLines (StringArray& destLines) const
561 {
562     destLines.addLines (loadFileAsString());
563 }
564 
565 //==============================================================================
findChildFiles(int whatToLookFor,bool searchRecursively,const String & wildcard) const566 Array<File> File::findChildFiles (int whatToLookFor, bool searchRecursively, const String& wildcard) const
567 {
568     Array<File> results;
569     findChildFiles (results, whatToLookFor, searchRecursively, wildcard);
570     return results;
571 }
572 
findChildFiles(Array<File> & results,int whatToLookFor,bool searchRecursively,const String & wildcard) const573 int File::findChildFiles (Array<File>& results, int whatToLookFor, bool searchRecursively, const String& wildcard) const
574 {
575     int total = 0;
576 
577     for (const auto& di : RangedDirectoryIterator (*this, searchRecursively, wildcard, whatToLookFor))
578     {
579         results.add (di.getFile());
580         ++total;
581     }
582 
583     return total;
584 }
585 
getNumberOfChildFiles(const int whatToLookFor,const String & wildCardPattern) const586 int File::getNumberOfChildFiles (const int whatToLookFor, const String& wildCardPattern) const
587 {
588     return std::accumulate (RangedDirectoryIterator (*this, false, wildCardPattern, whatToLookFor),
589                             RangedDirectoryIterator(),
590                             0,
591                             [] (int acc, const DirectoryEntry&) { return acc + 1; });
592 }
593 
containsSubDirectories() const594 bool File::containsSubDirectories() const
595 {
596     if (! isDirectory())
597         return false;
598 
599     return RangedDirectoryIterator (*this, false, "*", findDirectories) != RangedDirectoryIterator();
600 }
601 
602 //==============================================================================
getNonexistentChildFile(const String & suggestedPrefix,const String & suffix,bool putNumbersInBrackets) const603 File File::getNonexistentChildFile (const String& suggestedPrefix,
604                                     const String& suffix,
605                                     bool putNumbersInBrackets) const
606 {
607     auto f = getChildFile (suggestedPrefix + suffix);
608 
609     if (f.exists())
610     {
611         int number = 1;
612         auto prefix = suggestedPrefix;
613 
614         // remove any bracketed numbers that may already be on the end..
615         if (prefix.trim().endsWithChar (')'))
616         {
617             putNumbersInBrackets = true;
618 
619             auto openBracks  = prefix.lastIndexOfChar ('(');
620             auto closeBracks = prefix.lastIndexOfChar (')');
621 
622             if (openBracks > 0
623                  && closeBracks > openBracks
624                  && prefix.substring (openBracks + 1, closeBracks).containsOnly ("0123456789"))
625             {
626                 number = prefix.substring (openBracks + 1, closeBracks).getIntValue();
627                 prefix = prefix.substring (0, openBracks);
628             }
629         }
630 
631         do
632         {
633             auto newName = prefix;
634 
635             if (putNumbersInBrackets)
636             {
637                 newName << '(' << ++number << ')';
638             }
639             else
640             {
641                 if (CharacterFunctions::isDigit (prefix.getLastCharacter()))
642                     newName << '_'; // pad with an underscore if the name already ends in a digit
643 
644                 newName << ++number;
645             }
646 
647             f = getChildFile (newName + suffix);
648 
649         } while (f.exists());
650     }
651 
652     return f;
653 }
654 
getNonexistentSibling(const bool putNumbersInBrackets) const655 File File::getNonexistentSibling (const bool putNumbersInBrackets) const
656 {
657     if (! exists())
658         return *this;
659 
660     return getParentDirectory().getNonexistentChildFile (getFileNameWithoutExtension(),
661                                                          getFileExtension(),
662                                                          putNumbersInBrackets);
663 }
664 
665 //==============================================================================
getFileExtension() const666 String File::getFileExtension() const
667 {
668     auto indexOfDot = fullPath.lastIndexOfChar ('.');
669 
670     if (indexOfDot > fullPath.lastIndexOfChar (getSeparatorChar()))
671         return fullPath.substring (indexOfDot);
672 
673     return {};
674 }
675 
hasFileExtension(StringRef possibleSuffix) const676 bool File::hasFileExtension (StringRef possibleSuffix) const
677 {
678     if (possibleSuffix.isEmpty())
679         return fullPath.lastIndexOfChar ('.') <= fullPath.lastIndexOfChar (getSeparatorChar());
680 
681     auto semicolon = possibleSuffix.text.indexOf ((juce_wchar) ';');
682 
683     if (semicolon >= 0)
684         return hasFileExtension (String (possibleSuffix.text).substring (0, semicolon).trimEnd())
685                 || hasFileExtension ((possibleSuffix.text + (semicolon + 1)).findEndOfWhitespace());
686 
687     if (fullPath.endsWithIgnoreCase (possibleSuffix))
688     {
689         if (possibleSuffix.text[0] == '.')
690             return true;
691 
692         auto dotPos = fullPath.length() - possibleSuffix.length() - 1;
693 
694         if (dotPos >= 0)
695             return fullPath[dotPos] == '.';
696     }
697 
698     return false;
699 }
700 
withFileExtension(StringRef newExtension) const701 File File::withFileExtension (StringRef newExtension) const
702 {
703     if (fullPath.isEmpty())
704         return {};
705 
706     auto filePart = getFileName();
707 
708     auto lastDot = filePart.lastIndexOfChar ('.');
709 
710     if (lastDot >= 0)
711         filePart = filePart.substring (0, lastDot);
712 
713     if (newExtension.isNotEmpty() && newExtension.text[0] != '.')
714         filePart << '.';
715 
716     return getSiblingFile (filePart + newExtension);
717 }
718 
719 //==============================================================================
startAsProcess(const String & parameters) const720 bool File::startAsProcess (const String& parameters) const
721 {
722     return exists() && Process::openDocument (fullPath, parameters);
723 }
724 
725 //==============================================================================
createInputStream() const726 std::unique_ptr<FileInputStream> File::createInputStream() const
727 {
728     auto fin = std::make_unique<FileInputStream> (*this);
729 
730     if (fin->openedOk())
731         return fin;
732 
733     return nullptr;
734 }
735 
createOutputStream(size_t bufferSize) const736 std::unique_ptr<FileOutputStream> File::createOutputStream (size_t bufferSize) const
737 {
738     auto fout = std::make_unique<FileOutputStream> (*this, bufferSize);
739 
740     if (fout->openedOk())
741         return fout;
742 
743     return nullptr;
744 }
745 
746 //==============================================================================
appendData(const void * const dataToAppend,const size_t numberOfBytes) const747 bool File::appendData (const void* const dataToAppend,
748                        const size_t numberOfBytes) const
749 {
750     jassert (((ssize_t) numberOfBytes) >= 0);
751 
752     if (numberOfBytes == 0)
753         return true;
754 
755     FileOutputStream fout (*this, 8192);
756     return fout.openedOk() && fout.write (dataToAppend, numberOfBytes);
757 }
758 
replaceWithData(const void * const dataToWrite,const size_t numberOfBytes) const759 bool File::replaceWithData (const void* const dataToWrite,
760                             const size_t numberOfBytes) const
761 {
762     if (numberOfBytes == 0)
763         return deleteFile();
764 
765     TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile);
766     tempFile.getFile().appendData (dataToWrite, numberOfBytes);
767     return tempFile.overwriteTargetFileWithTemporary();
768 }
769 
appendText(const String & text,bool asUnicode,bool writeHeaderBytes,const char * lineFeed) const770 bool File::appendText (const String& text, bool asUnicode, bool writeHeaderBytes, const char* lineFeed) const
771 {
772     FileOutputStream fout (*this);
773 
774     if (fout.failedToOpen())
775         return false;
776 
777     return fout.writeText (text, asUnicode, writeHeaderBytes, lineFeed);
778 }
779 
replaceWithText(const String & textToWrite,bool asUnicode,bool writeHeaderBytes,const char * lineFeed) const780 bool File::replaceWithText (const String& textToWrite, bool asUnicode, bool writeHeaderBytes, const char* lineFeed) const
781 {
782     TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile);
783     tempFile.getFile().appendText (textToWrite, asUnicode, writeHeaderBytes, lineFeed);
784     return tempFile.overwriteTargetFileWithTemporary();
785 }
786 
hasIdenticalContentTo(const File & other) const787 bool File::hasIdenticalContentTo (const File& other) const
788 {
789     if (other == *this)
790         return true;
791 
792     if (getSize() == other.getSize() && existsAsFile() && other.existsAsFile())
793     {
794         FileInputStream in1 (*this), in2 (other);
795 
796         if (in1.openedOk() && in2.openedOk())
797         {
798             const int bufferSize = 4096;
799             HeapBlock<char> buffer1 (bufferSize), buffer2 (bufferSize);
800 
801             for (;;)
802             {
803                 auto num1 = in1.read (buffer1, bufferSize);
804                 auto num2 = in2.read (buffer2, bufferSize);
805 
806                 if (num1 != num2)
807                     break;
808 
809                 if (num1 <= 0)
810                     return true;
811 
812                 if (memcmp (buffer1, buffer2, (size_t) num1) != 0)
813                     break;
814             }
815         }
816     }
817 
818     return false;
819 }
820 
821 //==============================================================================
createLegalPathName(const String & original)822 String File::createLegalPathName (const String& original)
823 {
824     auto s = original;
825     String start;
826 
827     if (s.isNotEmpty() && s[1] == ':')
828     {
829         start = s.substring (0, 2);
830         s = s.substring (2);
831     }
832 
833     return start + s.removeCharacters ("\"#@,;:<>*^|?")
834                     .substring (0, 1024);
835 }
836 
createLegalFileName(const String & original)837 String File::createLegalFileName (const String& original)
838 {
839     auto s = original.removeCharacters ("\"#@,;:<>*^|?\\/");
840 
841     const int maxLength = 128; // only the length of the filename, not the whole path
842     auto len = s.length();
843 
844     if (len > maxLength)
845     {
846         auto lastDot = s.lastIndexOfChar ('.');
847 
848         if (lastDot > jmax (0, len - 12))
849         {
850             s = s.substring (0, maxLength - (len - lastDot))
851                  + s.substring (lastDot);
852         }
853         else
854         {
855             s = s.substring (0, maxLength);
856         }
857     }
858 
859     return s;
860 }
861 
862 //==============================================================================
countNumberOfSeparators(String::CharPointerType s)863 static int countNumberOfSeparators (String::CharPointerType s)
864 {
865     int num = 0;
866 
867     for (;;)
868     {
869         auto c = s.getAndAdvance();
870 
871         if (c == 0)
872             break;
873 
874         if (c == File::getSeparatorChar())
875             ++num;
876     }
877 
878     return num;
879 }
880 
getRelativePathFrom(const File & dir) const881 String File::getRelativePathFrom (const File& dir) const
882 {
883     if (dir == *this)
884         return ".";
885 
886     auto thisPath = fullPath;
887 
888     while (thisPath.endsWithChar (getSeparatorChar()))
889         thisPath = thisPath.dropLastCharacters (1);
890 
891     auto dirPath = addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName()
892                                                             : dir.fullPath);
893 
894     int commonBitLength = 0;
895     auto thisPathAfterCommon = thisPath.getCharPointer();
896     auto dirPathAfterCommon  = dirPath.getCharPointer();
897 
898     {
899         auto thisPathIter = thisPath.getCharPointer();
900         auto dirPathIter = dirPath.getCharPointer();
901 
902         for (int i = 0;;)
903         {
904             auto c1 = thisPathIter.getAndAdvance();
905             auto c2 = dirPathIter.getAndAdvance();
906 
907            #if NAMES_ARE_CASE_SENSITIVE
908             if (c1 != c2
909            #else
910             if ((c1 != c2 && CharacterFunctions::toLowerCase (c1) != CharacterFunctions::toLowerCase (c2))
911            #endif
912                  || c1 == 0)
913                 break;
914 
915             ++i;
916 
917             if (c1 == getSeparatorChar())
918             {
919                 thisPathAfterCommon = thisPathIter;
920                 dirPathAfterCommon  = dirPathIter;
921                 commonBitLength = i;
922             }
923         }
924     }
925 
926     // if the only common bit is the root, then just return the full path..
927     if (commonBitLength == 0 || (commonBitLength == 1 && thisPath[1] == getSeparatorChar()))
928         return fullPath;
929 
930     auto numUpDirectoriesNeeded = countNumberOfSeparators (dirPathAfterCommon);
931 
932     if (numUpDirectoriesNeeded == 0)
933         return thisPathAfterCommon;
934 
935    #if JUCE_WINDOWS
936     auto s = String::repeatedString ("..\\", numUpDirectoriesNeeded);
937    #else
938     auto s = String::repeatedString ("../",  numUpDirectoriesNeeded);
939    #endif
940     s.appendCharPointer (thisPathAfterCommon);
941     return s;
942 }
943 
944 //==============================================================================
createTempFile(StringRef fileNameEnding)945 File File::createTempFile (StringRef fileNameEnding)
946 {
947     auto tempFile = getSpecialLocation (tempDirectory)
948                       .getChildFile ("temp_" + String::toHexString (Random::getSystemRandom().nextInt()))
949                       .withFileExtension (fileNameEnding);
950 
951     if (tempFile.exists())
952         return createTempFile (fileNameEnding);
953 
954     return tempFile;
955 }
956 
createSymbolicLink(const File & linkFileToCreate,const String & nativePathOfTarget,bool overwriteExisting)957 bool File::createSymbolicLink (const File& linkFileToCreate,
958                                const String& nativePathOfTarget,
959                                bool overwriteExisting)
960 {
961     if (linkFileToCreate.exists())
962     {
963         if (! linkFileToCreate.isSymbolicLink())
964         {
965             // user has specified an existing file / directory as the link
966             // this is bad! the user could end up unintentionally destroying data
967             jassertfalse;
968             return false;
969         }
970 
971         if (overwriteExisting)
972             linkFileToCreate.deleteFile();
973     }
974 
975    #if JUCE_MAC || JUCE_LINUX
976     // one common reason for getting an error here is that the file already exists
977     if (symlink (nativePathOfTarget.toRawUTF8(), linkFileToCreate.getFullPathName().toRawUTF8()) == -1)
978     {
979         jassertfalse;
980         return false;
981     }
982 
983     return true;
984    #elif JUCE_MSVC
985     File targetFile (linkFileToCreate.getSiblingFile (nativePathOfTarget));
986 
987     return CreateSymbolicLink (linkFileToCreate.getFullPathName().toWideCharPointer(),
988                                nativePathOfTarget.toWideCharPointer(),
989                                targetFile.isDirectory() ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) != FALSE;
990    #else
991     ignoreUnused (nativePathOfTarget);
992     jassertfalse; // symbolic links not supported on this platform!
993     return false;
994    #endif
995 }
996 
createSymbolicLink(const File & linkFileToCreate,bool overwriteExisting) const997 bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const
998 {
999     return createSymbolicLink (linkFileToCreate, getFullPathName(), overwriteExisting);
1000 }
1001 
1002 #if ! JUCE_WINDOWS
getLinkedTarget() const1003 File File::getLinkedTarget() const
1004 {
1005     if (isSymbolicLink())
1006         return getSiblingFile (getNativeLinkedTarget());
1007 
1008     return *this;
1009 }
1010 #endif
1011 
1012 //==============================================================================
MemoryMappedFile(const File & file,MemoryMappedFile::AccessMode mode,bool exclusive)1013 MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode, bool exclusive)
1014     : range (0, file.getSize())
1015 {
1016     openInternal (file, mode, exclusive);
1017 }
1018 
MemoryMappedFile(const File & file,const Range<int64> & fileRange,AccessMode mode,bool exclusive)1019 MemoryMappedFile::MemoryMappedFile (const File& file, const Range<int64>& fileRange, AccessMode mode, bool exclusive)
1020     : range (fileRange.getIntersectionWith (Range<int64> (0, file.getSize())))
1021 {
1022     openInternal (file, mode, exclusive);
1023 }
1024 
1025 
1026 //==============================================================================
1027 //==============================================================================
1028 #if JUCE_UNIT_TESTS
1029 
1030 class FileTests  : public UnitTest
1031 {
1032 public:
FileTests()1033     FileTests()
1034         : UnitTest ("Files", UnitTestCategories::files)
1035     {}
1036 
runTest()1037     void runTest() override
1038     {
1039         beginTest ("Reading");
1040 
1041         const File home (File::getSpecialLocation (File::userHomeDirectory));
1042         const File temp (File::getSpecialLocation (File::tempDirectory));
1043 
1044         expect (! File().exists());
1045         expect (! File().existsAsFile());
1046         expect (! File().isDirectory());
1047        #if ! JUCE_WINDOWS
1048         expect (File("/").isDirectory());
1049        #endif
1050         expect (home.isDirectory());
1051         expect (home.exists());
1052         expect (! home.existsAsFile());
1053         expect (File::getSpecialLocation (File::userApplicationDataDirectory).isDirectory());
1054         expect (File::getSpecialLocation (File::currentExecutableFile).exists());
1055         expect (File::getSpecialLocation (File::currentApplicationFile).exists());
1056         expect (File::getSpecialLocation (File::invokedExecutableFile).exists());
1057         expect (home.getVolumeTotalSize() > 1024 * 1024);
1058         expect (home.getBytesFreeOnVolume() > 0);
1059         expect (! home.isHidden());
1060         expect (home.isOnHardDisk());
1061         expect (! home.isOnCDRomDrive());
1062         expect (File::getCurrentWorkingDirectory().exists());
1063         expect (home.setAsCurrentWorkingDirectory());
1064         expect (File::getCurrentWorkingDirectory() == home);
1065 
1066         {
1067             Array<File> roots;
1068             File::findFileSystemRoots (roots);
1069             expect (roots.size() > 0);
1070 
1071             int numRootsExisting = 0;
1072             for (int i = 0; i < roots.size(); ++i)
1073                 if (roots[i].exists())
1074                     ++numRootsExisting;
1075 
1076             // (on windows, some of the drives may not contain media, so as long as at least one is ok..)
1077             expect (numRootsExisting > 0);
1078         }
1079 
1080         beginTest ("Writing");
1081 
1082         File demoFolder (temp.getChildFile ("JUCE UnitTests Temp Folder.folder"));
1083         expect (demoFolder.deleteRecursively());
1084         expect (demoFolder.createDirectory());
1085         expect (demoFolder.isDirectory());
1086         expect (demoFolder.getParentDirectory() == temp);
1087         expect (temp.isDirectory());
1088         expect (temp.findChildFiles (File::findFilesAndDirectories, false, "*").contains (demoFolder));
1089         expect (temp.findChildFiles (File::findDirectories, true, "*.folder").contains (demoFolder));
1090 
1091         File tempFile (demoFolder.getNonexistentChildFile ("test", ".txt", false));
1092 
1093         expect (tempFile.getFileExtension() == ".txt");
1094         expect (tempFile.hasFileExtension (".txt"));
1095         expect (tempFile.hasFileExtension ("txt"));
1096         expect (tempFile.withFileExtension ("xyz").hasFileExtension (".xyz"));
1097         expect (tempFile.withFileExtension ("xyz").hasFileExtension ("abc;xyz;foo"));
1098         expect (tempFile.withFileExtension ("xyz").hasFileExtension ("xyz;foo"));
1099         expect (! tempFile.withFileExtension ("h").hasFileExtension ("bar;foo;xx"));
1100         expect (tempFile.getSiblingFile ("foo").isAChildOf (temp));
1101         expect (tempFile.hasWriteAccess());
1102 
1103         expect (home.getChildFile (".") == home);
1104         expect (home.getChildFile ("..") == home.getParentDirectory());
1105         expect (home.getChildFile (".xyz").getFileName() == ".xyz");
1106         expect (home.getChildFile ("..xyz").getFileName() == "..xyz");
1107         expect (home.getChildFile ("...xyz").getFileName() == "...xyz");
1108         expect (home.getChildFile ("./xyz") == home.getChildFile ("xyz"));
1109         expect (home.getChildFile ("././xyz") == home.getChildFile ("xyz"));
1110         expect (home.getChildFile ("../xyz") == home.getParentDirectory().getChildFile ("xyz"));
1111         expect (home.getChildFile (".././xyz") == home.getParentDirectory().getChildFile ("xyz"));
1112         expect (home.getChildFile (".././xyz/./abc") == home.getParentDirectory().getChildFile ("xyz/abc"));
1113         expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz"));
1114         expect (home.getChildFile ("a1/a2/a3/./../../a4") == home.getChildFile ("a1/a4"));
1115 
1116         {
1117             FileOutputStream fo (tempFile);
1118             fo.write ("0123456789", 10);
1119         }
1120 
1121         expect (tempFile.exists());
1122         expect (tempFile.getSize() == 10);
1123         expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000);
1124         expectEquals (tempFile.loadFileAsString(), String ("0123456789"));
1125         expect (! demoFolder.containsSubDirectories());
1126 
1127         expectEquals (tempFile.getRelativePathFrom (demoFolder.getParentDirectory()), demoFolder.getFileName() + File::getSeparatorString() + tempFile.getFileName());
1128         expectEquals (demoFolder.getParentDirectory().getRelativePathFrom (tempFile), ".." + File::getSeparatorString() + ".." + File::getSeparatorString() + demoFolder.getParentDirectory().getFileName());
1129 
1130         expect (demoFolder.getNumberOfChildFiles (File::findFiles) == 1);
1131         expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 1);
1132         expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 0);
1133         demoFolder.getNonexistentChildFile ("tempFolder", "", false).createDirectory();
1134         expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 1);
1135         expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 2);
1136         expect (demoFolder.containsSubDirectories());
1137 
1138         expect (tempFile.hasWriteAccess());
1139         tempFile.setReadOnly (true);
1140         expect (! tempFile.hasWriteAccess());
1141         tempFile.setReadOnly (false);
1142         expect (tempFile.hasWriteAccess());
1143 
1144         Time t (Time::getCurrentTime());
1145         tempFile.setLastModificationTime (t);
1146         Time t2 = tempFile.getLastModificationTime();
1147         expect (std::abs ((int) (t2.toMilliseconds() - t.toMilliseconds())) <= 1000);
1148 
1149         {
1150             MemoryBlock mb;
1151             tempFile.loadFileAsData (mb);
1152             expect (mb.getSize() == 10);
1153             expect (mb[0] == '0');
1154         }
1155 
1156         {
1157             expect (tempFile.getSize() == 10);
1158             FileOutputStream fo (tempFile);
1159             expect (fo.openedOk());
1160 
1161             expect (fo.setPosition  (7));
1162             expect (fo.truncate().wasOk());
1163             expect (tempFile.getSize() == 7);
1164             fo.write ("789", 3);
1165             fo.flush();
1166             expect (tempFile.getSize() == 10);
1167         }
1168 
1169         beginTest ("Memory-mapped files");
1170 
1171         {
1172             MemoryMappedFile mmf (tempFile, MemoryMappedFile::readOnly);
1173             expect (mmf.getSize() == 10);
1174             expect (mmf.getData() != nullptr);
1175             expect (memcmp (mmf.getData(), "0123456789", 10) == 0);
1176         }
1177 
1178         {
1179             const File tempFile2 (tempFile.getNonexistentSibling (false));
1180             expect (tempFile2.create());
1181             expect (tempFile2.appendData ("xxxxxxxxxx", 10));
1182 
1183             {
1184                 MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite);
1185                 expect (mmf.getSize() == 10);
1186                 expect (mmf.getData() != nullptr);
1187                 memcpy (mmf.getData(), "abcdefghij", 10);
1188             }
1189 
1190             {
1191                 MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite);
1192                 expect (mmf.getSize() == 10);
1193                 expect (mmf.getData() != nullptr);
1194                 expect (memcmp (mmf.getData(), "abcdefghij", 10) == 0);
1195             }
1196 
1197             expect (tempFile2.deleteFile());
1198         }
1199 
1200         beginTest ("More writing");
1201 
1202         expect (tempFile.appendData ("abcdefghij", 10));
1203         expect (tempFile.getSize() == 20);
1204         expect (tempFile.replaceWithData ("abcdefghij", 10));
1205         expect (tempFile.getSize() == 10);
1206 
1207         File tempFile2 (tempFile.getNonexistentSibling (false));
1208         expect (tempFile.copyFileTo (tempFile2));
1209         expect (tempFile2.exists());
1210         expect (tempFile2.hasIdenticalContentTo (tempFile));
1211         expect (tempFile.deleteFile());
1212         expect (! tempFile.exists());
1213         expect (tempFile2.moveFileTo (tempFile));
1214         expect (tempFile.exists());
1215         expect (! tempFile2.exists());
1216 
1217         expect (demoFolder.deleteRecursively());
1218         expect (! demoFolder.exists());
1219 
1220         {
1221             URL url ("https://audio.dev/foo/bar/");
1222             expectEquals (url.toString (false), String ("https://audio.dev/foo/bar/"));
1223             expectEquals (url.getChildURL ("x").toString (false), String ("https://audio.dev/foo/bar/x"));
1224             expectEquals (url.getParentURL().toString (false), String ("https://audio.dev/foo"));
1225             expectEquals (url.getParentURL().getParentURL().toString (false), String ("https://audio.dev/"));
1226             expectEquals (url.getParentURL().getParentURL().getParentURL().toString (false), String ("https://audio.dev/"));
1227             expectEquals (url.getParentURL().getChildURL ("x").toString (false), String ("https://audio.dev/foo/x"));
1228             expectEquals (url.getParentURL().getParentURL().getParentURL().getChildURL ("x").toString (false), String ("https://audio.dev/x"));
1229         }
1230 
1231         {
1232             URL url ("https://audio.dev/foo/bar");
1233             expectEquals (url.toString (false), String ("https://audio.dev/foo/bar"));
1234             expectEquals (url.getChildURL ("x").toString (false), String ("https://audio.dev/foo/bar/x"));
1235             expectEquals (url.getParentURL().toString (false), String ("https://audio.dev/foo"));
1236             expectEquals (url.getParentURL().getParentURL().toString (false), String ("https://audio.dev/"));
1237             expectEquals (url.getParentURL().getParentURL().getParentURL().toString (false), String ("https://audio.dev/"));
1238             expectEquals (url.getParentURL().getChildURL ("x").toString (false), String ("https://audio.dev/foo/x"));
1239             expectEquals (url.getParentURL().getParentURL().getParentURL().getChildURL ("x").toString (false), String ("https://audio.dev/x"));
1240         }
1241     }
1242 };
1243 
1244 static FileTests fileUnitTests;
1245 
1246 #endif
1247 
1248 } // namespace juce
1249