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