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.

1071 lines
34KB

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