The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1065 lines
35KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. File::File (const String& fullPathName)
  19. : fullPath (parseAbsolutePath (fullPathName))
  20. {
  21. }
  22. File File::createFileWithoutCheckingPath (const String& path) noexcept
  23. {
  24. File f;
  25. f.fullPath = path;
  26. return f;
  27. }
  28. File::File (const File& other)
  29. : fullPath (other.fullPath)
  30. {
  31. }
  32. File& File::operator= (const String& newPath)
  33. {
  34. fullPath = parseAbsolutePath (newPath);
  35. return *this;
  36. }
  37. File& File::operator= (const File& other)
  38. {
  39. fullPath = other.fullPath;
  40. return *this;
  41. }
  42. #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
  43. File::File (File&& other) noexcept
  44. : fullPath (static_cast <String&&> (other.fullPath))
  45. {
  46. }
  47. File& File::operator= (File&& other) noexcept
  48. {
  49. fullPath = static_cast <String&&> (other.fullPath);
  50. return *this;
  51. }
  52. #endif
  53. const File File::nonexistent;
  54. //==============================================================================
  55. String File::parseAbsolutePath (const String& p)
  56. {
  57. if (p.isEmpty())
  58. return String::empty;
  59. #if JUCE_WINDOWS
  60. // Windows..
  61. String path (p.replaceCharacter ('/', '\\'));
  62. if (path.startsWithChar (separator))
  63. {
  64. if (path[1] != separator)
  65. {
  66. /* When you supply a raw string to the File object constructor, it must be an absolute path.
  67. If you're trying to parse a string that may be either a relative path or an absolute path,
  68. you MUST provide a context against which the partial path can be evaluated - you can do
  69. this by simply using File::getChildFile() instead of the File constructor. E.g. saying
  70. "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
  71. path if that's what was supplied, or would evaluate a partial path relative to the CWD.
  72. */
  73. jassertfalse;
  74. path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path;
  75. }
  76. }
  77. else if (! path.containsChar (':'))
  78. {
  79. /* When you supply a raw string to the File object constructor, it must be an absolute path.
  80. If you're trying to parse a string that may be either a relative path or an absolute path,
  81. you MUST provide a context against which the partial path can be evaluated - you can do
  82. this by simply using File::getChildFile() instead of the File constructor. E.g. saying
  83. "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
  84. path if that's what was supplied, or would evaluate a partial path relative to the CWD.
  85. */
  86. jassertfalse;
  87. return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName();
  88. }
  89. #else
  90. // Mac or Linux..
  91. // Yes, I know it's legal for a unix pathname to contain a backslash, but this assertion is here
  92. // to catch anyone who's trying to run code that was written on Windows with hard-coded path names.
  93. // If that's why you've ended up here, use File::getChildFile() to build your paths instead.
  94. jassert ((! p.containsChar ('\\')) || (p.indexOfChar ('/') >= 0 && p.indexOfChar ('/') < p.indexOfChar ('\\')));
  95. String path (p);
  96. if (path.startsWithChar ('~'))
  97. {
  98. if (path[1] == separator || path[1] == 0)
  99. {
  100. // expand a name of the form "~/abc"
  101. path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName()
  102. + path.substring (1);
  103. }
  104. else
  105. {
  106. // expand a name of type "~dave/abc"
  107. const String userName (path.substring (1).upToFirstOccurrenceOf ("/", false, false));
  108. struct passwd* const pw = getpwnam (userName.toUTF8());
  109. if (pw != nullptr)
  110. path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false);
  111. }
  112. }
  113. else if (! path.startsWithChar (separator))
  114. {
  115. /* When you supply a raw string to the File object constructor, it must be an absolute path.
  116. If you're trying to parse a string that may be either a relative path or an absolute path,
  117. you MUST provide a context against which the partial path can be evaluated - you can do
  118. this by simply using File::getChildFile() instead of the File constructor. E.g. saying
  119. "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
  120. path if that's what was supplied, or would evaluate a partial path relative to the CWD.
  121. */
  122. jassert (path.startsWith ("./") || path.startsWith ("../")); // (assume that a path "./xyz" is deliberately intended to be relative to the CWD)
  123. return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName();
  124. }
  125. #endif
  126. while (path.endsWithChar (separator) && path != separatorString) // careful not to turn a single "/" into an empty string.
  127. path = path.dropLastCharacters (1);
  128. return path;
  129. }
  130. String File::addTrailingSeparator (const String& path)
  131. {
  132. return path.endsWithChar (separator) ? path
  133. : path + separator;
  134. }
  135. //==============================================================================
  136. #if JUCE_LINUX
  137. #define NAMES_ARE_CASE_SENSITIVE 1
  138. #endif
  139. bool File::areFileNamesCaseSensitive()
  140. {
  141. #if NAMES_ARE_CASE_SENSITIVE
  142. return true;
  143. #else
  144. return false;
  145. #endif
  146. }
  147. static int compareFilenames (const String& name1, const String& name2) noexcept
  148. {
  149. #if NAMES_ARE_CASE_SENSITIVE
  150. return name1.compare (name2);
  151. #else
  152. return name1.compareIgnoreCase (name2);
  153. #endif
  154. }
  155. bool File::operator== (const File& other) const { return compareFilenames (fullPath, other.fullPath) == 0; }
  156. bool File::operator!= (const File& other) const { return compareFilenames (fullPath, other.fullPath) != 0; }
  157. bool File::operator< (const File& other) const { return compareFilenames (fullPath, other.fullPath) < 0; }
  158. bool File::operator> (const File& other) const { return compareFilenames (fullPath, other.fullPath) > 0; }
  159. //==============================================================================
  160. bool File::setReadOnly (const bool shouldBeReadOnly,
  161. const bool applyRecursively) const
  162. {
  163. bool worked = true;
  164. if (applyRecursively && isDirectory())
  165. {
  166. Array <File> subFiles;
  167. findChildFiles (subFiles, File::findFilesAndDirectories, false);
  168. for (int i = subFiles.size(); --i >= 0;)
  169. worked = subFiles.getReference(i).setReadOnly (shouldBeReadOnly, true) && worked;
  170. }
  171. return setFileReadOnlyInternal (shouldBeReadOnly) && worked;
  172. }
  173. bool File::deleteRecursively() const
  174. {
  175. bool worked = true;
  176. if (isDirectory())
  177. {
  178. Array<File> subFiles;
  179. findChildFiles (subFiles, File::findFilesAndDirectories, false);
  180. for (int i = subFiles.size(); --i >= 0;)
  181. worked = subFiles.getReference(i).deleteRecursively() && worked;
  182. }
  183. return deleteFile() && worked;
  184. }
  185. bool File::moveFileTo (const File& newFile) const
  186. {
  187. if (newFile.fullPath == fullPath)
  188. return true;
  189. if (! exists())
  190. return false;
  191. #if ! NAMES_ARE_CASE_SENSITIVE
  192. if (*this != newFile)
  193. #endif
  194. if (! newFile.deleteFile())
  195. return false;
  196. return moveInternal (newFile);
  197. }
  198. bool File::copyFileTo (const File& newFile) const
  199. {
  200. return (*this == newFile)
  201. || (exists() && newFile.deleteFile() && copyInternal (newFile));
  202. }
  203. bool File::copyDirectoryTo (const File& newDirectory) const
  204. {
  205. if (isDirectory() && newDirectory.createDirectory())
  206. {
  207. Array<File> subFiles;
  208. findChildFiles (subFiles, File::findFiles, false);
  209. for (int i = 0; i < subFiles.size(); ++i)
  210. if (! subFiles.getReference(i).copyFileTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName())))
  211. return false;
  212. subFiles.clear();
  213. findChildFiles (subFiles, File::findDirectories, false);
  214. for (int i = 0; i < subFiles.size(); ++i)
  215. if (! subFiles.getReference(i).copyDirectoryTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName())))
  216. return false;
  217. return true;
  218. }
  219. return false;
  220. }
  221. //==============================================================================
  222. String File::getPathUpToLastSlash() const
  223. {
  224. const int lastSlash = fullPath.lastIndexOfChar (separator);
  225. if (lastSlash > 0)
  226. return fullPath.substring (0, lastSlash);
  227. else if (lastSlash == 0)
  228. return separatorString;
  229. else
  230. return fullPath;
  231. }
  232. File File::getParentDirectory() const
  233. {
  234. File f;
  235. f.fullPath = getPathUpToLastSlash();
  236. return f;
  237. }
  238. //==============================================================================
  239. String File::getFileName() const
  240. {
  241. return fullPath.substring (fullPath.lastIndexOfChar (separator) + 1);
  242. }
  243. String File::getFileNameWithoutExtension() const
  244. {
  245. const int lastSlash = fullPath.lastIndexOfChar (separator) + 1;
  246. const int lastDot = fullPath.lastIndexOfChar ('.');
  247. if (lastDot > lastSlash)
  248. return fullPath.substring (lastSlash, lastDot);
  249. else
  250. return fullPath.substring (lastSlash);
  251. }
  252. bool File::isAChildOf (const File& potentialParent) const
  253. {
  254. if (potentialParent == File::nonexistent)
  255. return false;
  256. const String ourPath (getPathUpToLastSlash());
  257. if (compareFilenames (potentialParent.fullPath, ourPath) == 0)
  258. return true;
  259. if (potentialParent.fullPath.length() >= ourPath.length())
  260. return false;
  261. return getParentDirectory().isAChildOf (potentialParent);
  262. }
  263. int File::hashCode() const { return fullPath.hashCode(); }
  264. int64 File::hashCode64() const { return fullPath.hashCode64(); }
  265. //==============================================================================
  266. bool File::isAbsolutePath (const String& path)
  267. {
  268. return path.startsWithChar (separator)
  269. #if JUCE_WINDOWS
  270. || (path.isNotEmpty() && path[1] == ':');
  271. #else
  272. || path.startsWithChar ('~');
  273. #endif
  274. }
  275. File File::getChildFile (String relativePath) const
  276. {
  277. if (isAbsolutePath (relativePath))
  278. return File (relativePath);
  279. String path (fullPath);
  280. // It's relative, so remove any ../ or ./ bits at the start..
  281. if (relativePath[0] == '.')
  282. {
  283. #if JUCE_WINDOWS
  284. relativePath = relativePath.replaceCharacter ('/', '\\');
  285. #endif
  286. while (relativePath[0] == '.')
  287. {
  288. const juce_wchar secondChar = relativePath[1];
  289. if (secondChar == '.')
  290. {
  291. const juce_wchar thirdChar = relativePath[2];
  292. if (thirdChar == 0 || thirdChar == separator)
  293. {
  294. const int lastSlash = path.lastIndexOfChar (separator);
  295. if (lastSlash >= 0)
  296. path = path.substring (0, lastSlash);
  297. relativePath = relativePath.substring (3);
  298. }
  299. else
  300. {
  301. break;
  302. }
  303. }
  304. else if (secondChar == separator)
  305. {
  306. relativePath = relativePath.substring (2);
  307. }
  308. else
  309. {
  310. break;
  311. }
  312. }
  313. }
  314. return File (addTrailingSeparator (path) + relativePath);
  315. }
  316. File File::getSiblingFile (const String& fileName) const
  317. {
  318. return getParentDirectory().getChildFile (fileName);
  319. }
  320. //==============================================================================
  321. String File::descriptionOfSizeInBytes (const int64 bytes)
  322. {
  323. const char* suffix;
  324. double divisor = 0;
  325. if (bytes == 1) { suffix = " byte"; }
  326. else if (bytes < 1024) { suffix = " bytes"; }
  327. else if (bytes < 1024 * 1024) { suffix = " KB"; divisor = 1024.0; }
  328. else if (bytes < 1024 * 1024 * 1024) { suffix = " MB"; divisor = 1024.0 * 1024.0; }
  329. else { suffix = " GB"; divisor = 1024.0 * 1024.0 * 1024.0; }
  330. return (divisor > 0 ? String (bytes / divisor, 1) : String (bytes)) + suffix;
  331. }
  332. //==============================================================================
  333. Result File::create() const
  334. {
  335. if (exists())
  336. return Result::ok();
  337. const File parentDir (getParentDirectory());
  338. if (parentDir == *this)
  339. return Result::fail ("Cannot create parent directory");
  340. Result r (parentDir.createDirectory());
  341. if (r.wasOk())
  342. {
  343. FileOutputStream fo (*this, 8);
  344. r = fo.getStatus();
  345. }
  346. return r;
  347. }
  348. Result File::createDirectory() const
  349. {
  350. if (isDirectory())
  351. return Result::ok();
  352. const File parentDir (getParentDirectory());
  353. if (parentDir == *this)
  354. return Result::fail ("Cannot create parent directory");
  355. Result r (parentDir.createDirectory());
  356. if (r.wasOk())
  357. r = createDirectoryInternal (fullPath.trimCharactersAtEnd (separatorString));
  358. return r;
  359. }
  360. //==============================================================================
  361. Time File::getLastModificationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (m); }
  362. Time File::getLastAccessTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (a); }
  363. Time File::getCreationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (c); }
  364. bool File::setLastModificationTime (const Time& t) const { return setFileTimesInternal (t.toMilliseconds(), 0, 0); }
  365. bool File::setLastAccessTime (const Time& t) const { return setFileTimesInternal (0, t.toMilliseconds(), 0); }
  366. bool File::setCreationTime (const Time& t) const { return setFileTimesInternal (0, 0, t.toMilliseconds()); }
  367. //==============================================================================
  368. bool File::loadFileAsData (MemoryBlock& destBlock) const
  369. {
  370. if (! existsAsFile())
  371. return false;
  372. FileInputStream in (*this);
  373. return in.openedOk() && getSize() == in.readIntoMemoryBlock (destBlock);
  374. }
  375. String File::loadFileAsString() const
  376. {
  377. if (! existsAsFile())
  378. return String::empty;
  379. FileInputStream in (*this);
  380. return in.openedOk() ? in.readEntireStreamAsString()
  381. : String::empty;
  382. }
  383. void File::readLines (StringArray& destLines) const
  384. {
  385. destLines.addLines (loadFileAsString());
  386. }
  387. //==============================================================================
  388. int File::findChildFiles (Array<File>& results,
  389. const int whatToLookFor,
  390. const bool searchRecursively,
  391. const String& wildCardPattern) const
  392. {
  393. DirectoryIterator di (*this, searchRecursively, wildCardPattern, whatToLookFor);
  394. int total = 0;
  395. while (di.next())
  396. {
  397. results.add (di.getFile());
  398. ++total;
  399. }
  400. return total;
  401. }
  402. int File::getNumberOfChildFiles (const int whatToLookFor, const String& wildCardPattern) const
  403. {
  404. DirectoryIterator di (*this, false, wildCardPattern, whatToLookFor);
  405. int total = 0;
  406. while (di.next())
  407. ++total;
  408. return total;
  409. }
  410. bool File::containsSubDirectories() const
  411. {
  412. if (! isDirectory())
  413. return false;
  414. DirectoryIterator di (*this, false, "*", findDirectories);
  415. return di.next();
  416. }
  417. //==============================================================================
  418. File File::getNonexistentChildFile (const String& suggestedPrefix,
  419. const String& suffix,
  420. bool putNumbersInBrackets) const
  421. {
  422. File f (getChildFile (suggestedPrefix + suffix));
  423. if (f.exists())
  424. {
  425. int number = 1;
  426. String prefix (suggestedPrefix);
  427. // remove any bracketed numbers that may already be on the end..
  428. if (prefix.trim().endsWithChar (')'))
  429. {
  430. putNumbersInBrackets = true;
  431. const int openBracks = prefix.lastIndexOfChar ('(');
  432. const int closeBracks = prefix.lastIndexOfChar (')');
  433. if (openBracks > 0
  434. && closeBracks > openBracks
  435. && prefix.substring (openBracks + 1, closeBracks).containsOnly ("0123456789"))
  436. {
  437. number = prefix.substring (openBracks + 1, closeBracks).getIntValue();
  438. prefix = prefix.substring (0, openBracks);
  439. }
  440. }
  441. // also use brackets if it ends in a digit.
  442. putNumbersInBrackets = putNumbersInBrackets
  443. || CharacterFunctions::isDigit (prefix.getLastCharacter());
  444. do
  445. {
  446. String newName (prefix);
  447. if (putNumbersInBrackets)
  448. newName << '(' << ++number << ')';
  449. else
  450. newName << ++number;
  451. f = getChildFile (newName + suffix);
  452. } while (f.exists());
  453. }
  454. return f;
  455. }
  456. File File::getNonexistentSibling (const bool putNumbersInBrackets) const
  457. {
  458. if (! exists())
  459. return *this;
  460. return getParentDirectory().getNonexistentChildFile (getFileNameWithoutExtension(),
  461. getFileExtension(),
  462. putNumbersInBrackets);
  463. }
  464. //==============================================================================
  465. String File::getFileExtension() const
  466. {
  467. const int indexOfDot = fullPath.lastIndexOfChar ('.');
  468. if (indexOfDot > fullPath.lastIndexOfChar (separator))
  469. return fullPath.substring (indexOfDot);
  470. return String::empty;
  471. }
  472. bool File::hasFileExtension (const String& possibleSuffix) const
  473. {
  474. if (possibleSuffix.isEmpty())
  475. return fullPath.lastIndexOfChar ('.') <= fullPath.lastIndexOfChar (separator);
  476. const int semicolon = possibleSuffix.indexOfChar (0, ';');
  477. if (semicolon >= 0)
  478. {
  479. return hasFileExtension (possibleSuffix.substring (0, semicolon).trimEnd())
  480. || hasFileExtension (possibleSuffix.substring (semicolon + 1).trimStart());
  481. }
  482. else
  483. {
  484. if (fullPath.endsWithIgnoreCase (possibleSuffix))
  485. {
  486. if (possibleSuffix.startsWithChar ('.'))
  487. return true;
  488. const int dotPos = fullPath.length() - possibleSuffix.length() - 1;
  489. if (dotPos >= 0)
  490. return fullPath [dotPos] == '.';
  491. }
  492. }
  493. return false;
  494. }
  495. File File::withFileExtension (const String& newExtension) const
  496. {
  497. if (fullPath.isEmpty())
  498. return File::nonexistent;
  499. String filePart (getFileName());
  500. const int i = filePart.lastIndexOfChar ('.');
  501. if (i >= 0)
  502. filePart = filePart.substring (0, i);
  503. if (newExtension.isNotEmpty() && ! newExtension.startsWithChar ('.'))
  504. filePart << '.';
  505. return getSiblingFile (filePart + newExtension);
  506. }
  507. //==============================================================================
  508. bool File::startAsProcess (const String& parameters) const
  509. {
  510. return exists() && Process::openDocument (fullPath, parameters);
  511. }
  512. //==============================================================================
  513. FileInputStream* File::createInputStream() const
  514. {
  515. if (existsAsFile())
  516. return new FileInputStream (*this);
  517. return nullptr;
  518. }
  519. FileOutputStream* File::createOutputStream (const int bufferSize) const
  520. {
  521. ScopedPointer<FileOutputStream> out (new FileOutputStream (*this, bufferSize));
  522. return out->failedToOpen() ? nullptr
  523. : out.release();
  524. }
  525. //==============================================================================
  526. bool File::appendData (const void* const dataToAppend,
  527. const int numberOfBytes) const
  528. {
  529. if (numberOfBytes <= 0)
  530. return true;
  531. FileOutputStream out (*this, 8192);
  532. return out.openedOk() && out.write (dataToAppend, numberOfBytes);
  533. }
  534. bool File::replaceWithData (const void* const dataToWrite,
  535. const int numberOfBytes) const
  536. {
  537. jassert (numberOfBytes >= 0); // a negative number of bytes??
  538. if (numberOfBytes <= 0)
  539. return deleteFile();
  540. TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile);
  541. tempFile.getFile().appendData (dataToWrite, numberOfBytes);
  542. return tempFile.overwriteTargetFileWithTemporary();
  543. }
  544. bool File::appendText (const String& text,
  545. const bool asUnicode,
  546. const bool writeUnicodeHeaderBytes) const
  547. {
  548. FileOutputStream out (*this);
  549. if (out.failedToOpen())
  550. return false;
  551. out.writeText (text, asUnicode, writeUnicodeHeaderBytes);
  552. return true;
  553. }
  554. bool File::replaceWithText (const String& textToWrite,
  555. const bool asUnicode,
  556. const bool writeUnicodeHeaderBytes) const
  557. {
  558. TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile);
  559. tempFile.getFile().appendText (textToWrite, asUnicode, writeUnicodeHeaderBytes);
  560. return tempFile.overwriteTargetFileWithTemporary();
  561. }
  562. bool File::hasIdenticalContentTo (const File& other) const
  563. {
  564. if (other == *this)
  565. return true;
  566. if (getSize() == other.getSize() && existsAsFile() && other.existsAsFile())
  567. {
  568. FileInputStream in1 (*this), in2 (other);
  569. if (in1.openedOk() && in2.openedOk())
  570. {
  571. const int bufferSize = 4096;
  572. HeapBlock <char> buffer1 (bufferSize), buffer2 (bufferSize);
  573. for (;;)
  574. {
  575. const int num1 = in1.read (buffer1, bufferSize);
  576. const int num2 = in2.read (buffer2, bufferSize);
  577. if (num1 != num2)
  578. break;
  579. if (num1 <= 0)
  580. return true;
  581. if (memcmp (buffer1, buffer2, (size_t) num1) != 0)
  582. break;
  583. }
  584. }
  585. }
  586. return false;
  587. }
  588. //==============================================================================
  589. String File::createLegalPathName (const String& original)
  590. {
  591. String s (original);
  592. String start;
  593. if (s[1] == ':')
  594. {
  595. start = s.substring (0, 2);
  596. s = s.substring (2);
  597. }
  598. return start + s.removeCharacters ("\"#@,;:<>*^|?")
  599. .substring (0, 1024);
  600. }
  601. String File::createLegalFileName (const String& original)
  602. {
  603. String s (original.removeCharacters ("\"#@,;:<>*^|?\\/"));
  604. const int maxLength = 128; // only the length of the filename, not the whole path
  605. const int len = s.length();
  606. if (len > maxLength)
  607. {
  608. const int lastDot = s.lastIndexOfChar ('.');
  609. if (lastDot > jmax (0, len - 12))
  610. {
  611. s = s.substring (0, maxLength - (len - lastDot))
  612. + s.substring (lastDot);
  613. }
  614. else
  615. {
  616. s = s.substring (0, maxLength);
  617. }
  618. }
  619. return s;
  620. }
  621. //==============================================================================
  622. static int countNumberOfSeparators (String::CharPointerType s)
  623. {
  624. int num = 0;
  625. for (;;)
  626. {
  627. const juce_wchar c = s.getAndAdvance();
  628. if (c == 0)
  629. break;
  630. if (c == File::separator)
  631. ++num;
  632. }
  633. return num;
  634. }
  635. String File::getRelativePathFrom (const File& dir) const
  636. {
  637. String thisPath (fullPath);
  638. while (thisPath.endsWithChar (separator))
  639. thisPath = thisPath.dropLastCharacters (1);
  640. String dirPath (addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName()
  641. : dir.fullPath));
  642. int commonBitLength = 0;
  643. String::CharPointerType thisPathAfterCommon (thisPath.getCharPointer());
  644. String::CharPointerType dirPathAfterCommon (dirPath.getCharPointer());
  645. {
  646. String::CharPointerType thisPathIter (thisPath.getCharPointer());
  647. String::CharPointerType dirPathIter (dirPath.getCharPointer());
  648. for (int i = 0;;)
  649. {
  650. const juce_wchar c1 = thisPathIter.getAndAdvance();
  651. const juce_wchar c2 = dirPathIter.getAndAdvance();
  652. #if NAMES_ARE_CASE_SENSITIVE
  653. if (c1 != c2
  654. #else
  655. if ((c1 != c2 && CharacterFunctions::toLowerCase (c1) != CharacterFunctions::toLowerCase (c2))
  656. #endif
  657. || c1 == 0)
  658. break;
  659. ++i;
  660. if (c1 == separator)
  661. {
  662. thisPathAfterCommon = thisPathIter;
  663. dirPathAfterCommon = dirPathIter;
  664. commonBitLength = i;
  665. }
  666. }
  667. }
  668. // if the only common bit is the root, then just return the full path..
  669. if (commonBitLength == 0 || (commonBitLength == 1 && thisPath[1] == separator))
  670. return fullPath;
  671. const int numUpDirectoriesNeeded = countNumberOfSeparators (dirPathAfterCommon);
  672. if (numUpDirectoriesNeeded == 0)
  673. return thisPathAfterCommon;
  674. #if JUCE_WINDOWS
  675. String s (String::repeatedString ("..\\", numUpDirectoriesNeeded));
  676. #else
  677. String s (String::repeatedString ("../", numUpDirectoriesNeeded));
  678. #endif
  679. s.appendCharPointer (thisPathAfterCommon);
  680. return s;
  681. }
  682. //==============================================================================
  683. File File::createTempFile (const String& fileNameEnding)
  684. {
  685. const File tempFile (getSpecialLocation (tempDirectory)
  686. .getChildFile ("temp_" + String::toHexString (Random::getSystemRandom().nextInt()))
  687. .withFileExtension (fileNameEnding));
  688. if (tempFile.exists())
  689. return createTempFile (fileNameEnding);
  690. return tempFile;
  691. }
  692. //==============================================================================
  693. #if JUCE_UNIT_TESTS
  694. class FileTests : public UnitTest
  695. {
  696. public:
  697. FileTests() : UnitTest ("Files") {}
  698. void runTest()
  699. {
  700. beginTest ("Reading");
  701. const File home (File::getSpecialLocation (File::userHomeDirectory));
  702. const File temp (File::getSpecialLocation (File::tempDirectory));
  703. expect (! File::nonexistent.exists());
  704. expect (home.isDirectory());
  705. expect (home.exists());
  706. expect (! home.existsAsFile());
  707. expect (File::getSpecialLocation (File::userDocumentsDirectory).isDirectory());
  708. expect (File::getSpecialLocation (File::userApplicationDataDirectory).isDirectory());
  709. expect (File::getSpecialLocation (File::currentExecutableFile).exists());
  710. expect (File::getSpecialLocation (File::currentApplicationFile).exists());
  711. expect (File::getSpecialLocation (File::invokedExecutableFile).exists());
  712. expect (home.getVolumeTotalSize() > 1024 * 1024);
  713. expect (home.getBytesFreeOnVolume() > 0);
  714. expect (! home.isHidden());
  715. expect (home.isOnHardDisk());
  716. expect (! home.isOnCDRomDrive());
  717. expect (File::getCurrentWorkingDirectory().exists());
  718. expect (home.setAsCurrentWorkingDirectory());
  719. expect (File::getCurrentWorkingDirectory() == home);
  720. {
  721. Array<File> roots;
  722. File::findFileSystemRoots (roots);
  723. expect (roots.size() > 0);
  724. int numRootsExisting = 0;
  725. for (int i = 0; i < roots.size(); ++i)
  726. if (roots[i].exists())
  727. ++numRootsExisting;
  728. // (on windows, some of the drives may not contain media, so as long as at least one is ok..)
  729. expect (numRootsExisting > 0);
  730. }
  731. beginTest ("Writing");
  732. File demoFolder (temp.getChildFile ("Juce UnitTests Temp Folder.folder"));
  733. expect (demoFolder.deleteRecursively());
  734. expect (demoFolder.createDirectory());
  735. expect (demoFolder.isDirectory());
  736. expect (demoFolder.getParentDirectory() == temp);
  737. expect (temp.isDirectory());
  738. {
  739. Array<File> files;
  740. temp.findChildFiles (files, File::findFilesAndDirectories, false, "*");
  741. expect (files.contains (demoFolder));
  742. }
  743. {
  744. Array<File> files;
  745. temp.findChildFiles (files, File::findDirectories, true, "*.folder");
  746. expect (files.contains (demoFolder));
  747. }
  748. File tempFile (demoFolder.getNonexistentChildFile ("test", ".txt", false));
  749. expect (tempFile.getFileExtension() == ".txt");
  750. expect (tempFile.hasFileExtension (".txt"));
  751. expect (tempFile.hasFileExtension ("txt"));
  752. expect (tempFile.withFileExtension ("xyz").hasFileExtension (".xyz"));
  753. expect (tempFile.withFileExtension ("xyz").hasFileExtension ("abc;xyz;foo"));
  754. expect (tempFile.withFileExtension ("xyz").hasFileExtension ("xyz;foo"));
  755. expect (! tempFile.withFileExtension ("h").hasFileExtension ("bar;foo;xx"));
  756. expect (tempFile.getSiblingFile ("foo").isAChildOf (temp));
  757. expect (tempFile.hasWriteAccess());
  758. {
  759. FileOutputStream fo (tempFile);
  760. fo.write ("0123456789", 10);
  761. }
  762. expect (tempFile.exists());
  763. expect (tempFile.getSize() == 10);
  764. expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000);
  765. expectEquals (tempFile.loadFileAsString(), String ("0123456789"));
  766. expect (! demoFolder.containsSubDirectories());
  767. expectEquals (tempFile.getRelativePathFrom (demoFolder.getParentDirectory()), demoFolder.getFileName() + File::separatorString + tempFile.getFileName());
  768. expectEquals (demoFolder.getParentDirectory().getRelativePathFrom (tempFile), ".." + File::separatorString + ".." + File::separatorString + demoFolder.getParentDirectory().getFileName());
  769. expect (demoFolder.getNumberOfChildFiles (File::findFiles) == 1);
  770. expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 1);
  771. expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 0);
  772. demoFolder.getNonexistentChildFile ("tempFolder", "", false).createDirectory();
  773. expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 1);
  774. expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 2);
  775. expect (demoFolder.containsSubDirectories());
  776. expect (tempFile.hasWriteAccess());
  777. tempFile.setReadOnly (true);
  778. expect (! tempFile.hasWriteAccess());
  779. tempFile.setReadOnly (false);
  780. expect (tempFile.hasWriteAccess());
  781. Time t (Time::getCurrentTime());
  782. tempFile.setLastModificationTime (t);
  783. Time t2 = tempFile.getLastModificationTime();
  784. expect (std::abs ((int) (t2.toMilliseconds() - t.toMilliseconds())) <= 1000);
  785. {
  786. MemoryBlock mb;
  787. tempFile.loadFileAsData (mb);
  788. expect (mb.getSize() == 10);
  789. expect (mb[0] == '0');
  790. }
  791. {
  792. expect (tempFile.getSize() == 10);
  793. FileOutputStream fo (tempFile);
  794. expect (fo.openedOk());
  795. expect (fo.setPosition (7));
  796. expect (fo.truncate().wasOk());
  797. expect (tempFile.getSize() == 7);
  798. fo.write ("789", 3);
  799. fo.flush();
  800. expect (tempFile.getSize() == 10);
  801. }
  802. beginTest ("Memory-mapped files");
  803. {
  804. MemoryMappedFile mmf (tempFile, MemoryMappedFile::readOnly);
  805. expect (mmf.getSize() == 10);
  806. expect (mmf.getData() != nullptr);
  807. expect (memcmp (mmf.getData(), "0123456789", 10) == 0);
  808. }
  809. {
  810. const File tempFile2 (tempFile.getNonexistentSibling (false));
  811. expect (tempFile2.create());
  812. expect (tempFile2.appendData ("xxxxxxxxxx", 10));
  813. {
  814. MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite);
  815. expect (mmf.getSize() == 10);
  816. expect (mmf.getData() != nullptr);
  817. memcpy (mmf.getData(), "abcdefghij", 10);
  818. }
  819. {
  820. MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite);
  821. expect (mmf.getSize() == 10);
  822. expect (mmf.getData() != nullptr);
  823. expect (memcmp (mmf.getData(), "abcdefghij", 10) == 0);
  824. }
  825. expect (tempFile2.deleteFile());
  826. }
  827. beginTest ("More writing");
  828. expect (tempFile.appendData ("abcdefghij", 10));
  829. expect (tempFile.getSize() == 20);
  830. expect (tempFile.replaceWithData ("abcdefghij", 10));
  831. expect (tempFile.getSize() == 10);
  832. File tempFile2 (tempFile.getNonexistentSibling (false));
  833. expect (tempFile.copyFileTo (tempFile2));
  834. expect (tempFile2.exists());
  835. expect (tempFile2.hasIdenticalContentTo (tempFile));
  836. expect (tempFile.deleteFile());
  837. expect (! tempFile.exists());
  838. expect (tempFile2.moveFileTo (tempFile));
  839. expect (tempFile.exists());
  840. expect (! tempFile2.exists());
  841. expect (demoFolder.deleteRecursively());
  842. expect (! demoFolder.exists());
  843. }
  844. };
  845. static FileTests fileUnitTests;
  846. #endif