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.

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