/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #ifdef _MSC_VER #pragma warning (disable: 4514) #pragma warning (push) #endif #include "../../core/juce_StandardHeader.h" #if ! JUCE_WINDOWS #include #endif BEGIN_JUCE_NAMESPACE #include "juce_File.h" #include "juce_FileInputStream.h" #include "juce_FileOutputStream.h" #include "../../core/juce_SystemStats.h" #ifdef _MSC_VER #pragma warning (pop) #endif //============================================================================== void* juce_fileOpen (const String& path, bool forWriting); void juce_fileClose (void* handle); int juce_fileWrite (void* handle, const void* buffer, int size); int64 juce_fileGetPosition (void* handle); int64 juce_fileSetPosition (void* handle, int64 pos); void juce_fileFlush (void* handle); bool juce_fileExists (const String& fileName, const bool dontCountDirectories); bool juce_isDirectory (const String& fileName); int64 juce_getFileSize (const String& fileName); bool juce_canWriteToFile (const String& fileName); bool juce_setFileReadOnly (const String& fileName, bool isReadOnly); void juce_getFileTimes (const String& fileName, int64& modificationTime, int64& accessTime, int64& creationTime); bool juce_setFileTimes (const String& fileName, int64 modificationTime, int64 accessTime, int64 creationTime); bool juce_deleteFile (const String& fileName); bool juce_copyFile (const String& source, const String& dest); bool juce_moveFile (const String& source, const String& dest); // this must also create all paths involved in the directory. void juce_createDirectory (const String& fileName); bool juce_launchFile (const String& fileName, const String& parameters); const StringArray juce_getFileSystemRoots(); const String juce_getVolumeLabel (const String& filenameOnVolume, int& volumeSerialNumber); // starts a directory search operation with a wildcard, returning a handle for // use in calls to juce_findFileNext. // juce_firstResultFile gets the name of the file (not the whole pathname) and // the other pointers, if non-null, are set based on the properties of the file. void* juce_findFileStart (const String& directory, const String& wildCard, String& firstResultFile, bool* isDirectory, bool* isHidden, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly); // returns false when no more files are found bool juce_findFileNext (void* handle, String& resultFile, bool* isDirectory, bool* isHidden, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly); void juce_findFileClose (void* handle); //============================================================================== static const String juce_addTrailingSeparator (const String& path) { return path.endsWithChar (File::separator) ? path : path + File::separator; } //============================================================================== static const String parseAbsolutePath (String path) { if (path.isEmpty()) return String::empty; #if JUCE_WINDOWS // Windows.. path = path.replaceCharacter (T('/'), T('\\')); if (path.startsWithChar (File::separator)) { if (path[1] != File::separator) { jassertfalse // using a filename that starts with a slash is a bit dodgy on // Windows, because it needs a drive letter, which in this case // we'll take from the CWD.. but this is a bit of an assumption that // could be wrong.. path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path; } } else if (path.indexOfChar (T(':')) < 0) { if (path.isEmpty()) return String::empty; jassertfalse // using a partial filename is a bad way to initialise a file, because // we don't know what directory to put it in. // Here we'll assume it's in the CWD, but this might not be what was // intended.. return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); } #else // Mac or Linux.. path = path.replaceCharacter (T('\\'), T('/')); if (path.startsWithChar (T('~'))) { const char* homeDir = 0; if (path[1] == File::separator || path[1] == 0) { // expand a name of the form "~/abc" path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName() + path.substring (1); } else { // expand a name of type "~dave/abc" const String userName (path.substring (1) .upToFirstOccurrenceOf (T("/"), false, false)); struct passwd* const pw = getpwnam (userName); if (pw != 0) { String home (homeDir); if (home.endsWithChar (File::separator)) home [home.length() - 1] = 0; path = String (pw->pw_dir) + path.substring (userName.length()); } } } else if (! path.startsWithChar (File::separator)) { while (path.startsWith (T("./"))) path = path.substring (2); if (path.isEmpty()) return String::empty; jassertfalse // using a partial filename is a bad way to initialise a file, because // we don't know what directory to put it in. // Here we'll assume it's in the CWD, but this might not be what was // intended.. return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); } #endif int len = path.length(); while (--len > 0 && path [len] == File::separator) path [len] = 0; return path; } //============================================================================== const File File::nonexistent; //============================================================================== File::File (const String& fullPathName) : fullPath (parseAbsolutePath (fullPathName)) { } File::File (const String& path, int) : fullPath (path) { } File::File (const File& other) : fullPath (other.fullPath) { } const File& File::operator= (const String& newPath) { fullPath = parseAbsolutePath (newPath); return *this; } const File& File::operator= (const File& other) { fullPath = other.fullPath; return *this; } //============================================================================== #if JUCE_LINUX #define NAMES_ARE_CASE_SENSITIVE 1 #endif bool File::areFileNamesCaseSensitive() { #if NAMES_ARE_CASE_SENSITIVE return true; #else return false; #endif } bool File::operator== (const File& other) const { // case-insensitive on Windows, but not on linux. #if NAMES_ARE_CASE_SENSITIVE return fullPath == other.fullPath; #else return fullPath.equalsIgnoreCase (other.fullPath); #endif } bool File::operator!= (const File& other) const { return ! operator== (other); } //============================================================================== bool File::exists() const { return juce_fileExists (fullPath, false); } bool File::existsAsFile() const { return juce_fileExists (fullPath, true); } bool File::isDirectory() const { return juce_isDirectory (fullPath); } bool File::hasWriteAccess() const { if (exists()) return juce_canWriteToFile (fullPath); #if ! JUCE_WINDOWS else if ((! isDirectory()) && fullPath.containsChar (separator)) return getParentDirectory().hasWriteAccess(); else return false; #else // on windows, it seems that even read-only directories can still be written into, // so checking the parent directory's permissions would return the wrong result.. else return true; #endif } bool File::setReadOnly (const bool shouldBeReadOnly, const bool applyRecursively) const { bool worked = true; if (applyRecursively && isDirectory()) { OwnedArray subFiles; findChildFiles (subFiles, File::findFilesAndDirectories, false); for (int i = subFiles.size(); --i >= 0;) worked = subFiles[i]->setReadOnly (shouldBeReadOnly, true) && worked; } return juce_setFileReadOnly (fullPath, shouldBeReadOnly) && worked; } bool File::deleteFile() const { return (! exists()) || juce_deleteFile (fullPath); } bool File::deleteRecursively() const { bool worked = true; if (isDirectory()) { OwnedArray subFiles; findChildFiles (subFiles, File::findFilesAndDirectories, false); for (int i = subFiles.size(); --i >= 0;) worked = subFiles[i]->deleteRecursively() && worked; } return deleteFile() && worked; } bool File::moveFileTo (const File& newFile) const { if (newFile.fullPath == fullPath) return true; #if ! NAMES_ARE_CASE_SENSITIVE if (*this != newFile) #endif if (! newFile.deleteFile()) return false; return juce_moveFile (fullPath, newFile.fullPath); } bool File::copyFileTo (const File& newFile) const { if (*this == newFile) return true; if (! newFile.deleteFile()) return false; return juce_copyFile (fullPath, newFile.fullPath); } bool File::copyDirectoryTo (const File& newDirectory) const { if (isDirectory() && newDirectory.createDirectory()) { OwnedArray subFiles; findChildFiles (subFiles, File::findFiles, false); int i; for (i = 0; i < subFiles.size(); ++i) if (! subFiles[i]->copyFileTo (newDirectory.getChildFile (subFiles[i]->getFileName()))) return false; subFiles.clear(); findChildFiles (subFiles, File::findDirectories, false); for (i = 0; i < subFiles.size(); ++i) if (! subFiles[i]->copyDirectoryTo (newDirectory.getChildFile (subFiles[i]->getFileName()))) return false; return true; } return false; } //============================================================================== const String File::getPathUpToLastSlash() const { const int lastSlash = fullPath.lastIndexOfChar (separator); if (lastSlash > 0) return fullPath.substring (0, lastSlash); else if (lastSlash == 0) return separatorString; else return fullPath; } const File File::getParentDirectory() const { return File (getPathUpToLastSlash()); } //============================================================================== const String File::getFileName() const { return fullPath.substring (fullPath.lastIndexOfChar (separator) + 1); } int File::hashCode() const { return fullPath.hashCode(); } int64 File::hashCode64() const { return fullPath.hashCode64(); } const String File::getFileNameWithoutExtension() const { const int lastSlash = fullPath.lastIndexOfChar (separator) + 1; const int lastDot = fullPath.lastIndexOfChar (T('.')); if (lastDot > lastSlash) return fullPath.substring (lastSlash, lastDot); else return fullPath.substring (lastSlash); } bool File::isAChildOf (const File& potentialParent) const { const String ourPath (getPathUpToLastSlash()); #if NAMES_ARE_CASE_SENSITIVE if (potentialParent.fullPath == ourPath) #else if (potentialParent.fullPath.equalsIgnoreCase (ourPath)) #endif { return true; } else if (potentialParent.fullPath.length() >= ourPath.length()) { return false; } else { return getParentDirectory().isAChildOf (potentialParent); } } //============================================================================== bool File::isAbsolutePath (const String& path) { return path.startsWithChar (T('/')) || path.startsWithChar (T('\\')) #if JUCE_WINDOWS || (path.isNotEmpty() && ((const String&) path)[1] == T(':')); #else || path.startsWithChar (T('~')); #endif } const File File::getChildFile (String relativePath) const { if (isAbsolutePath (relativePath)) { // the path is really absolute.. return File (relativePath); } else { // it's relative, so remove any ../ or ./ bits at the start. String path (fullPath); if (relativePath[0] == T('.')) { #if JUCE_WINDOWS relativePath = relativePath.replaceCharacter (T('/'), T('\\')).trimStart(); #else relativePath = relativePath.replaceCharacter (T('\\'), T('/')).trimStart(); #endif while (relativePath[0] == T('.')) { if (relativePath[1] == T('.')) { if (relativePath [2] == 0 || relativePath[2] == separator) { const int lastSlash = path.lastIndexOfChar (separator); if (lastSlash >= 0) path = path.substring (0, lastSlash); relativePath = relativePath.substring (3); } else { break; } } else if (relativePath[1] == separator) { relativePath = relativePath.substring (2); } else { break; } } } return File (juce_addTrailingSeparator (path) + relativePath); } } const File File::getSiblingFile (const String& fileName) const { return getParentDirectory().getChildFile (fileName); } //============================================================================== int64 File::getSize() const { return juce_getFileSize (fullPath); } const String File::descriptionOfSizeInBytes (const int64 bytes) { if (bytes == 1) { return "1 byte"; } else if (bytes < 1024) { return String ((int) bytes) + " bytes"; } else if (bytes < 1024 * 1024) { return String (bytes / 1024.0, 1) + " KB"; } else if (bytes < 1024 * 1024 * 1024) { return String (bytes / (1024.0 * 1024.0), 1) + " MB"; } else { return String (bytes / (1024.0 * 1024.0 * 1024.0), 1) + " GB"; } } //============================================================================== bool File::create() const { if (! exists()) { const File parentDir (getParentDirectory()); if (parentDir == *this || ! parentDir.createDirectory()) return false; void* const fh = juce_fileOpen (fullPath, true); if (fh == 0) return false; juce_fileClose (fh); } return true; } bool File::createDirectory() const { if (! isDirectory()) { const File parentDir (getParentDirectory()); if (parentDir == *this || ! parentDir.createDirectory()) return false; String dir (fullPath); while (dir.endsWithChar (separator)) dir [dir.length() - 1] = 0; juce_createDirectory (dir); return isDirectory(); } return true; } //============================================================================== const Time File::getCreationTime() const { int64 m, a, c; juce_getFileTimes (fullPath, m, a, c); return Time (c); } bool File::setCreationTime (const Time& t) const { return juce_setFileTimes (fullPath, 0, 0, t.toMilliseconds()); } const Time File::getLastModificationTime() const { int64 m, a, c; juce_getFileTimes (fullPath, m, a, c); return Time (m); } bool File::setLastModificationTime (const Time& t) const { return juce_setFileTimes (fullPath, t.toMilliseconds(), 0, 0); } const Time File::getLastAccessTime() const { int64 m, a, c; juce_getFileTimes (fullPath, m, a, c); return Time (a); } bool File::setLastAccessTime (const Time& t) const { return juce_setFileTimes (fullPath, 0, t.toMilliseconds(), 0); } //============================================================================== bool File::loadFileAsData (MemoryBlock& destBlock) const { if (! existsAsFile()) return false; FileInputStream in (*this); return getSize() == in.readIntoMemoryBlock (destBlock); } const String File::loadFileAsString() const { if (! existsAsFile()) return String::empty; FileInputStream in (*this); return in.readEntireStreamAsString(); } //============================================================================== static inline bool fileTypeMatches (const int whatToLookFor, const bool isDir, const bool isHidden) { return (whatToLookFor & (isDir ? File::findDirectories : File::findFiles)) != 0 && ((! isHidden) || (whatToLookFor & File::ignoreHiddenFiles) == 0); } int File::findChildFiles (OwnedArray& results, const int whatToLookFor, const bool searchRecursively, const String& wildCardPattern) const { // you have to specify the type of files you're looking for! jassert ((whatToLookFor & (findFiles | findDirectories)) != 0); int total = 0; // find child files or directories in this directory first.. if (isDirectory()) { const String path (juce_addTrailingSeparator (fullPath)); String filename; bool itemIsDirectory, itemIsHidden; void* const handle = juce_findFileStart (path, wildCardPattern, filename, &itemIsDirectory, &itemIsHidden, 0, 0, 0, 0); if (handle != 0) { do { if (fileTypeMatches (whatToLookFor, itemIsDirectory, itemIsHidden) && ! filename.containsOnly (T("."))) { results.add (new File (path + filename, 0)); ++total; } } while (juce_findFileNext (handle, filename, &itemIsDirectory, &itemIsHidden, 0, 0, 0, 0)); juce_findFileClose (handle); } } else { // trying to search for files inside a non-directory? //jassertfalse } // and recurse down if required. if (searchRecursively) { OwnedArray subDirectories; findChildFiles (subDirectories, File::findDirectories, false); for (int i = 0; i < subDirectories.size(); ++i) { total += subDirectories.getUnchecked(i) ->findChildFiles (results, whatToLookFor, true, wildCardPattern); } } return total; } int File::getNumberOfChildFiles (const int whatToLookFor, const String& wildCardPattern) const { // you have to specify the type of files you're looking for! jassert (whatToLookFor > 0 && whatToLookFor <= 3); int count = 0; if (isDirectory()) { String filename; bool itemIsDirectory, itemIsHidden; void* const handle = juce_findFileStart (fullPath, wildCardPattern, filename, &itemIsDirectory, &itemIsHidden, 0, 0, 0, 0); if (handle != 0) { do { if (fileTypeMatches (whatToLookFor, itemIsDirectory, itemIsHidden) && ! filename.containsOnly (T("."))) { ++count; } } while (juce_findFileNext (handle, filename, &itemIsDirectory, &itemIsHidden, 0, 0, 0, 0)); juce_findFileClose (handle); } } else { // trying to search for files inside a non-directory? jassertfalse } return count; } bool File::containsSubDirectories() const { bool result = false; if (isDirectory()) { String filename; bool itemIsDirectory, itemIsHidden; void* const handle = juce_findFileStart (juce_addTrailingSeparator (fullPath), T("*"), filename, &itemIsDirectory, &itemIsHidden, 0, 0, 0, 0); if (handle != 0) { do { if (itemIsDirectory) { result = true; break; } } while (juce_findFileNext (handle, filename, &itemIsDirectory, &itemIsHidden, 0, 0, 0, 0)); juce_findFileClose (handle); } } return result; } //============================================================================== const File File::getNonexistentChildFile (const String& prefix_, const String& suffix, bool putNumbersInBrackets) const { File f (getChildFile (prefix_ + suffix)); if (f.exists()) { int num = 2; String prefix (prefix_); // remove any bracketed numbers that may already be on the end.. if (prefix.trim().endsWithChar (T(')'))) { putNumbersInBrackets = true; const int openBracks = prefix.lastIndexOfChar (T('(')); const int closeBracks = prefix.lastIndexOfChar (T(')')); if (openBracks > 0 && closeBracks > openBracks && prefix.substring (openBracks + 1, closeBracks).containsOnly (T("0123456789"))) { num = prefix.substring (openBracks + 1, closeBracks).getIntValue() + 1; prefix = prefix.substring (0, openBracks); } } // also use brackets if it ends in a digit. putNumbersInBrackets = putNumbersInBrackets || CharacterFunctions::isDigit (prefix.getLastCharacter()); do { if (putNumbersInBrackets) f = getChildFile (prefix + T('(') + String (num++) + T(')') + suffix); else f = getChildFile (prefix + String (num++) + suffix); } while (f.exists()); } return f; } const File File::getNonexistentSibling (const bool putNumbersInBrackets) const { if (exists()) { return getParentDirectory() .getNonexistentChildFile (getFileNameWithoutExtension(), getFileExtension(), putNumbersInBrackets); } else { return *this; } } //============================================================================== const String File::getFileExtension() const { String ext; if (! isDirectory()) { const int indexOfDot = fullPath.lastIndexOfChar (T('.')); if (indexOfDot > fullPath.lastIndexOfChar (separator)) ext = fullPath.substring (indexOfDot); } return ext; } bool File::hasFileExtension (const String& possibleSuffix) const { if (possibleSuffix.isEmpty()) return fullPath.lastIndexOfChar (T('.')) <= fullPath.lastIndexOfChar (separator); const int semicolon = possibleSuffix.indexOfChar (0, T(';')); if (semicolon >= 0) { return hasFileExtension (possibleSuffix.substring (0, semicolon).trimEnd()) || hasFileExtension (possibleSuffix.substring (semicolon + 1).trimStart()); } else { if (fullPath.endsWithIgnoreCase (possibleSuffix)) { if (possibleSuffix.startsWithChar (T('.'))) return true; const int dotPos = fullPath.length() - possibleSuffix.length() - 1; if (dotPos >= 0) return fullPath [dotPos] == T('.'); } } return false; } const File File::withFileExtension (const String& newExtension) const { if (fullPath.isEmpty()) return File::nonexistent; String filePart (getFileName()); int i = filePart.lastIndexOfChar (T('.')); if (i < 0) i = filePart.length(); String newExt (newExtension); if (newExt.isNotEmpty() && ! newExt.startsWithChar (T('.'))) newExt = T(".") + newExt; return getSiblingFile (filePart.substring (0, i) + newExt); } //============================================================================== bool File::startAsProcess (const String& parameters) const { return exists() && juce_launchFile (fullPath, parameters); } //============================================================================== FileInputStream* File::createInputStream() const { if (existsAsFile()) return new FileInputStream (*this); else return 0; } FileOutputStream* File::createOutputStream (const int bufferSize) const { ScopedPointer out (new FileOutputStream (*this, bufferSize)); if (out->failedToOpen()) return 0; return out.release(); } //============================================================================== bool File::appendData (const void* const dataToAppend, const int numberOfBytes) const { if (numberOfBytes > 0) { const ScopedPointer out (createOutputStream()); if (out == 0) return false; out->write (dataToAppend, numberOfBytes); } return true; } bool File::replaceWithData (const void* const dataToWrite, const int numberOfBytes) const { jassert (numberOfBytes >= 0); // a negative number of bytes?? if (numberOfBytes <= 0) return deleteFile(); const File tempFile (getSiblingFile (T(".") + getFileName()).getNonexistentSibling (false)); if (tempFile.appendData (dataToWrite, numberOfBytes) && tempFile.moveFileTo (*this)) { return true; } tempFile.deleteFile(); return false; } bool File::appendText (const String& text, const bool asUnicode, const bool writeUnicodeHeaderBytes) const { const ScopedPointer out (createOutputStream()); if (out != 0) { out->writeText (text, asUnicode, writeUnicodeHeaderBytes); return true; } return false; } bool File::replaceWithText (const String& textToWrite, const bool asUnicode, const bool writeUnicodeHeaderBytes) const { const File tempFile (getSiblingFile (T(".") + getFileName()).getNonexistentSibling (false)); if (tempFile.appendText (textToWrite, asUnicode, writeUnicodeHeaderBytes) && tempFile.moveFileTo (*this)) { return true; } tempFile.deleteFile(); return false; } //============================================================================== const String File::createLegalPathName (const String& original) { String s (original); String start; if (s[1] == T(':')) { start = s.substring (0, 2); s = s.substring (2); } return start + s.removeCharacters (T("\"#@,;:<>*^|?")) .substring (0, 1024); } const String File::createLegalFileName (const String& original) { String s (original.removeCharacters (T("\"#@,;:<>*^|?\\/"))); const int maxLength = 128; // only the length of the filename, not the whole path const int len = s.length(); if (len > maxLength) { const int lastDot = s.lastIndexOfChar (T('.')); if (lastDot > jmax (0, len - 12)) { s = s.substring (0, maxLength - (len - lastDot)) + s.substring (lastDot); } else { s = s.substring (0, maxLength); } } return s; } //============================================================================== const String File::getRelativePathFrom (const File& dir) const { String thisPath (fullPath); { int len = thisPath.length(); while (--len >= 0 && thisPath [len] == File::separator) thisPath [len] = 0; } String dirPath (juce_addTrailingSeparator ((dir.existsAsFile()) ? dir.getParentDirectory().getFullPathName() : dir.fullPath)); const int len = jmin (thisPath.length(), dirPath.length()); int commonBitLength = 0; for (int i = 0; i < len; ++i) { #if NAMES_ARE_CASE_SENSITIVE if (thisPath[i] != dirPath[i]) #else if (CharacterFunctions::toLowerCase (thisPath[i]) != CharacterFunctions::toLowerCase (dirPath[i])) #endif { break; } ++commonBitLength; } while (commonBitLength > 0 && thisPath [commonBitLength - 1] != File::separator) --commonBitLength; // if the only common bit is the root, then just return the full path.. if (commonBitLength <= 0 || (commonBitLength == 1 && thisPath [1] == File::separator)) return fullPath; thisPath = thisPath.substring (commonBitLength); dirPath = dirPath.substring (commonBitLength); while (dirPath.isNotEmpty()) { #if JUCE_WINDOWS thisPath = T("..\\") + thisPath; #else thisPath = T("../") + thisPath; #endif const int sep = dirPath.indexOfChar (separator); if (sep >= 0) dirPath = dirPath.substring (sep + 1); else dirPath = String::empty; } return thisPath; } //============================================================================== void File::findFileSystemRoots (OwnedArray& destArray) { const StringArray roots (juce_getFileSystemRoots()); for (int i = 0; i < roots.size(); ++i) destArray.add (new File (roots[i])); } const String File::getVolumeLabel() const { int serialNum; return juce_getVolumeLabel (fullPath, serialNum); } int File::getVolumeSerialNumber() const { int serialNum; juce_getVolumeLabel (fullPath, serialNum); return serialNum; } //============================================================================== const File File::createTempFile (const String& fileNameEnding) { String tempName (T("temp")); static int tempNum = 0; tempName << tempNum++ << fileNameEnding; const File tempFile (getSpecialLocation (tempDirectory) .getChildFile (tempName)); if (tempFile.exists()) return createTempFile (fileNameEnding); else return tempFile; } END_JUCE_NAMESPACE