/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-10 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. ============================================================================== */ // (This file gets included by juce_win32_NativeCode.cpp, rather than being // compiled on its own). #if JUCE_INCLUDED_FILE //============================================================================== #ifndef CSIDL_MYMUSIC #define CSIDL_MYMUSIC 0x000d #endif #ifndef CSIDL_MYVIDEO #define CSIDL_MYVIDEO 0x000e #endif #ifndef INVALID_FILE_ATTRIBUTES #define INVALID_FILE_ATTRIBUTES ((DWORD) -1) #endif //============================================================================== const juce_wchar File::separator = '\\'; const String File::separatorString ("\\"); //============================================================================== bool File::exists() const { return fullPath.isNotEmpty() && GetFileAttributes (fullPath) != INVALID_FILE_ATTRIBUTES; } bool File::existsAsFile() const { return fullPath.isNotEmpty() && (GetFileAttributes (fullPath) & FILE_ATTRIBUTE_DIRECTORY) == 0; } bool File::isDirectory() const { const DWORD attr = GetFileAttributes (fullPath); return ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) && (attr != INVALID_FILE_ATTRIBUTES); } bool juce_canWriteToFile (const String& fileName) { const DWORD attr = GetFileAttributes (fileName); return (attr & FILE_ATTRIBUTE_READONLY) == 0; } bool juce_setFileReadOnly (const String& fileName, bool isReadOnly) { DWORD attr = GetFileAttributes (fileName); if (attr == INVALID_FILE_ATTRIBUTES) return false; if (isReadOnly != juce_canWriteToFile (fileName)) return true; if (isReadOnly) attr |= FILE_ATTRIBUTE_READONLY; else attr &= ~FILE_ATTRIBUTE_READONLY; return SetFileAttributes (fileName, attr) != FALSE; } bool File::isHidden() const { return (GetFileAttributes (getFullPathName()) & FILE_ATTRIBUTE_HIDDEN) != 0; } //============================================================================== bool File::deleteFile() const { if (! exists()) return true; else if (isDirectory()) return RemoveDirectory (fullPath) != 0; else return DeleteFile (fullPath) != 0; } bool File::moveToTrash() const { if (! exists()) return true; SHFILEOPSTRUCT fos; zerostruct (fos); // The string we pass in must be double null terminated.. String doubleNullTermPath (getFullPathName() + " "); TCHAR* const p = const_cast (static_cast (doubleNullTermPath)); p [getFullPathName().length()] = 0; fos.wFunc = FO_DELETE; fos.pFrom = p; fos.fFlags = FOF_ALLOWUNDO | FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_RENAMEONCOLLISION; return SHFileOperation (&fos) == 0; } bool juce_moveFile (const String& source, const String& dest) { return MoveFile (source, dest) != 0; } bool juce_copyFile (const String& source, const String& dest) { return CopyFile (source, dest, false) != 0; } void File::createDirectoryInternal (const String& fileName) const { CreateDirectory (fileName, 0); } //============================================================================== // return 0 if not possible void* juce_fileOpen (const File& file, bool forWriting) { HANDLE h; if (forWriting) { h = CreateFile (file.getFullPathName(), GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (h != INVALID_HANDLE_VALUE) SetFilePointer (h, 0, 0, FILE_END); else h = 0; } else { h = CreateFile (file.getFullPathName(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0); if (h == INVALID_HANDLE_VALUE) h = 0; } return h; } void juce_fileClose (void* handle) { CloseHandle (handle); } //============================================================================== int juce_fileRead (void* handle, void* buffer, int size) { DWORD num = 0; ReadFile ((HANDLE) handle, buffer, size, &num, 0); return (int) num; } int juce_fileWrite (void* handle, const void* buffer, int size) { DWORD num; WriteFile ((HANDLE) handle, buffer, size, &num, 0); return (int) num; } int64 juce_fileSetPosition (void* handle, int64 pos) { LARGE_INTEGER li; li.QuadPart = pos; li.LowPart = SetFilePointer ((HANDLE) handle, li.LowPart, &li.HighPart, FILE_BEGIN); // (returns -1 if it fails) return li.QuadPart; } int64 FileOutputStream::getPositionInternal() const { if (fileHandle == 0) return -1; LARGE_INTEGER li; li.QuadPart = 0; li.LowPart = SetFilePointer ((HANDLE) fileHandle, 0, &li.HighPart, FILE_CURRENT); // (returns -1 if it fails) return jmax ((int64) 0, li.QuadPart); } void FileOutputStream::flushInternal() { if (fileHandle != 0) FlushFileBuffers ((HANDLE) fileHandle); } int64 File::getSize() const { WIN32_FILE_ATTRIBUTE_DATA attributes; if (GetFileAttributesEx (fullPath, GetFileExInfoStandard, &attributes)) return (((int64) attributes.nFileSizeHigh) << 32) | attributes.nFileSizeLow; return 0; } //============================================================================== static int64 fileTimeToTime (const FILETIME* const ft) { static_jassert (sizeof (ULARGE_INTEGER) == sizeof (FILETIME)); // tell me if this fails! return (reinterpret_cast (ft)->QuadPart - literal64bit (116444736000000000)) / 10000; } static void timeToFileTime (const int64 time, FILETIME* const ft) { reinterpret_cast (ft)->QuadPart = time * 10000 + literal64bit (116444736000000000); } void juce_getFileTimes (const String& fileName, int64& modificationTime, int64& accessTime, int64& creationTime) { WIN32_FILE_ATTRIBUTE_DATA attributes; if (GetFileAttributesEx (fileName, GetFileExInfoStandard, &attributes)) { modificationTime = fileTimeToTime (&attributes.ftLastWriteTime); creationTime = fileTimeToTime (&attributes.ftCreationTime); accessTime = fileTimeToTime (&attributes.ftLastAccessTime); } else { creationTime = accessTime = modificationTime = 0; } } bool juce_setFileTimes (const String& fileName, int64 modificationTime, int64 accessTime, int64 creationTime) { void* const h = juce_fileOpen (fileName, true); bool ok = false; if (h != 0) { FILETIME m, a, c; timeToFileTime (modificationTime, &m); timeToFileTime (accessTime, &a); timeToFileTime (creationTime, &c); ok = SetFileTime ((HANDLE) h, creationTime > 0 ? &c : 0, accessTime > 0 ? &a : 0, modificationTime > 0 ? &m : 0) != 0; juce_fileClose (h); } return ok; } //============================================================================== void File::findFileSystemRoots (Array& destArray) { TCHAR buffer [2048]; buffer[0] = 0; buffer[1] = 0; GetLogicalDriveStrings (2048, buffer); const TCHAR* n = buffer; StringArray roots; while (*n != 0) { roots.add (String (n)); while (*n++ != 0) {} } roots.sort (true); for (int i = 0; i < roots.size(); ++i) destArray.add (roots [i]); } //============================================================================== static const String getDriveFromPath (const String& path) { if (path.isNotEmpty() && path[1] == ':') return path.substring (0, 2) + '\\'; return path; } const String File::getVolumeLabel() const { TCHAR dest[64]; if (! GetVolumeInformation (getDriveFromPath (getFullPathName()), dest, numElementsInArray (dest), 0, 0, 0, 0, 0)) dest[0] = 0; return dest; } int File::getVolumeSerialNumber() const { TCHAR dest[64]; DWORD serialNum; if (! GetVolumeInformation (getDriveFromPath (getFullPathName()), dest, numElementsInArray (dest), &serialNum, 0, 0, 0, 0)) return 0; return (int) serialNum; } static int64 getDiskSpaceInfo (const String& path, const bool total) { ULARGE_INTEGER spc, tot, totFree; if (GetDiskFreeSpaceEx (getDriveFromPath (path), &spc, &tot, &totFree)) return total ? (int64) tot.QuadPart : (int64) spc.QuadPart; return 0; } int64 File::getBytesFreeOnVolume() const { return getDiskSpaceInfo (getFullPathName(), false); } int64 File::getVolumeTotalSize() const { return getDiskSpaceInfo (getFullPathName(), true); } //============================================================================== static unsigned int getWindowsDriveType (const String& path) { return GetDriveType (getDriveFromPath (path)); } bool File::isOnCDRomDrive() const { return getWindowsDriveType (getFullPathName()) == DRIVE_CDROM; } bool File::isOnHardDisk() const { if (fullPath.isEmpty()) return false; const unsigned int n = getWindowsDriveType (getFullPathName()); if (fullPath.toLowerCase()[0] <= 'b' && fullPath[1] == ':') return n != DRIVE_REMOVABLE; else return n != DRIVE_CDROM && n != DRIVE_REMOTE; } bool File::isOnRemovableDrive() const { if (fullPath.isEmpty()) return false; const unsigned int n = getWindowsDriveType (getFullPathName()); return n == DRIVE_CDROM || n == DRIVE_REMOTE || n == DRIVE_REMOVABLE || n == DRIVE_RAMDISK; } //============================================================================== static const File juce_getSpecialFolderPath (int type) { WCHAR path [MAX_PATH + 256]; if (SHGetSpecialFolderPath (0, path, type, FALSE)) return File (String (path)); return File::nonexistent; } const File JUCE_CALLTYPE File::getSpecialLocation (const SpecialLocationType type) { int csidlType = 0; switch (type) { case userHomeDirectory: csidlType = CSIDL_PROFILE; break; case userDocumentsDirectory: csidlType = CSIDL_PERSONAL; break; case userDesktopDirectory: csidlType = CSIDL_DESKTOP; break; case userApplicationDataDirectory: csidlType = CSIDL_APPDATA; break; case commonApplicationDataDirectory: csidlType = CSIDL_COMMON_APPDATA; break; case globalApplicationsDirectory: csidlType = CSIDL_PROGRAM_FILES; break; case userMusicDirectory: csidlType = CSIDL_MYMUSIC; break; case userMoviesDirectory: csidlType = CSIDL_MYVIDEO; break; case tempDirectory: { WCHAR dest [2048]; dest[0] = 0; GetTempPath (numElementsInArray (dest), dest); return File (String (dest)); } case invokedExecutableFile: case currentExecutableFile: case currentApplicationFile: { HINSTANCE moduleHandle = (HINSTANCE) PlatformUtilities::getCurrentModuleInstanceHandle(); WCHAR dest [MAX_PATH + 256]; dest[0] = 0; GetModuleFileName (moduleHandle, dest, numElementsInArray (dest)); return File (String (dest)); } break; default: jassertfalse // unknown type? return File::nonexistent; } return juce_getSpecialFolderPath (csidlType); } //============================================================================== const File File::getCurrentWorkingDirectory() { WCHAR dest [MAX_PATH + 256]; dest[0] = 0; GetCurrentDirectory (numElementsInArray (dest), dest); return File (String (dest)); } bool File::setAsCurrentWorkingDirectory() const { return SetCurrentDirectory (getFullPathName()) != FALSE; } //============================================================================== const String File::getVersion() const { String result; DWORD handle = 0; DWORD bufferSize = GetFileVersionInfoSize (getFullPathName(), &handle); HeapBlock buffer; buffer.calloc (bufferSize); if (GetFileVersionInfo (getFullPathName(), 0, bufferSize, buffer)) { VS_FIXEDFILEINFO* vffi; UINT len = 0; if (VerQueryValue (buffer, (LPTSTR) _T("\\"), (LPVOID*) &vffi, &len)) { result << (int) HIWORD (vffi->dwFileVersionMS) << '.' << (int) LOWORD (vffi->dwFileVersionMS) << '.' << (int) HIWORD (vffi->dwFileVersionLS) << '.' << (int) LOWORD (vffi->dwFileVersionLS); } } return result; } //============================================================================== const File File::getLinkedTarget() const { File result (*this); String p (getFullPathName()); if (! exists()) p += ".lnk"; else if (getFileExtension() != ".lnk") return result; ComSmartPtr shellLink; if (SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink, CLSCTX_INPROC_SERVER))) { ComSmartPtr persistFile; if (SUCCEEDED (shellLink->QueryInterface (IID_IPersistFile, (LPVOID*) &persistFile))) { if (SUCCEEDED (persistFile->Load ((const WCHAR*) p, STGM_READ)) && SUCCEEDED (shellLink->Resolve (0, SLR_ANY_MATCH | SLR_NO_UI))) { WIN32_FIND_DATA winFindData; WCHAR resolvedPath [MAX_PATH]; if (SUCCEEDED (shellLink->GetPath (resolvedPath, MAX_PATH, &winFindData, SLGP_UNCPRIORITY))) result = File (resolvedPath); } } } return result; } //============================================================================== class DirectoryIterator::NativeIterator::Pimpl { public: Pimpl (const File& directory, const String& wildCard) : directoryWithWildCard (File::addTrailingSeparator (directory.getFullPathName()) + wildCard), handle (INVALID_HANDLE_VALUE) { } ~Pimpl() { if (handle != INVALID_HANDLE_VALUE) FindClose (handle); } bool next (String& filenameFound, bool* const isDir, bool* const isHidden, int64* const fileSize, Time* const modTime, Time* const creationTime, bool* const isReadOnly) { WIN32_FIND_DATA findData; if (handle == INVALID_HANDLE_VALUE) { handle = FindFirstFile (directoryWithWildCard, &findData); if (handle == INVALID_HANDLE_VALUE) return false; } else { if (FindNextFile (handle, &findData) == 0) return false; } filenameFound = findData.cFileName; if (isDir != 0) *isDir = ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); if (isHidden != 0) *isHidden = ((findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0); if (fileSize != 0) *fileSize = findData.nFileSizeLow + (((int64) findData.nFileSizeHigh) << 32); if (modTime != 0) *modTime = fileTimeToTime (&findData.ftLastWriteTime); if (creationTime != 0) *creationTime = fileTimeToTime (&findData.ftCreationTime); if (isReadOnly != 0) *isReadOnly = ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0); return true; } juce_UseDebuggingNewOperator private: const String directoryWithWildCard; HANDLE handle; Pimpl (const Pimpl&); Pimpl& operator= (const Pimpl&); }; DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCard) : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCard)) { } DirectoryIterator::NativeIterator::~NativeIterator() { } bool DirectoryIterator::NativeIterator::next (String& filenameFound, bool* const isDir, bool* const isHidden, int64* const fileSize, Time* const modTime, Time* const creationTime, bool* const isReadOnly) { return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); } //============================================================================== bool juce_launchFile (const String& fileName, const String& parameters) { HINSTANCE hInstance = 0; JUCE_TRY { hInstance = ShellExecute (0, 0, fileName, parameters, 0, SW_SHOWDEFAULT); } JUCE_CATCH_ALL return hInstance > (HINSTANCE) 32; } void File::revealToUser() const { if (isDirectory()) startAsProcess(); else if (getParentDirectory().exists()) getParentDirectory().startAsProcess(); } //============================================================================== class NamedPipeInternal { public: NamedPipeInternal (const String& file, const bool isPipe_) : pipeH (0), cancelEvent (0), connected (false), isPipe (isPipe_) { cancelEvent = CreateEvent (0, FALSE, FALSE, 0); pipeH = isPipe ? CreateNamedPipe (file, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0, PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, 0) : CreateFile (file, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); } ~NamedPipeInternal() { disconnectPipe(); if (pipeH != 0) CloseHandle (pipeH); CloseHandle (cancelEvent); } bool connect (const int timeOutMs) { if (! isPipe) return true; if (! connected) { OVERLAPPED over; zerostruct (over); over.hEvent = CreateEvent (0, TRUE, FALSE, 0); if (ConnectNamedPipe (pipeH, &over)) { connected = false; // yes, you read that right. In overlapped mode it should always return 0. } else { const int err = GetLastError(); if (err == ERROR_IO_PENDING || err == ERROR_PIPE_LISTENING) { HANDLE handles[] = { over.hEvent, cancelEvent }; if (WaitForMultipleObjects (2, handles, FALSE, timeOutMs >= 0 ? timeOutMs : INFINITE) == WAIT_OBJECT_0) connected = true; } else if (err == ERROR_PIPE_CONNECTED) { connected = true; } } CloseHandle (over.hEvent); } return connected; } void disconnectPipe() { if (connected) { DisconnectNamedPipe (pipeH); connected = false; } } HANDLE pipeH; HANDLE cancelEvent; bool connected, isPipe; }; void NamedPipe::close() { cancelPendingReads(); const ScopedLock sl (lock); delete static_cast (internal); internal = 0; } bool NamedPipe::openInternal (const String& pipeName, const bool createPipe) { close(); ScopedPointer intern (new NamedPipeInternal ("\\\\.\\pipe\\" + pipeName, createPipe)); if (intern->pipeH != INVALID_HANDLE_VALUE) { internal = intern.release(); return true; } return false; } int NamedPipe::read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds) { const ScopedLock sl (lock); int bytesRead = -1; bool waitAgain = true; while (waitAgain && internal != 0) { NamedPipeInternal* const intern = static_cast (internal); waitAgain = false; if (! intern->connect (timeOutMilliseconds)) break; if (maxBytesToRead <= 0) return 0; OVERLAPPED over; zerostruct (over); over.hEvent = CreateEvent (0, TRUE, FALSE, 0); unsigned long numRead; if (ReadFile (intern->pipeH, destBuffer, maxBytesToRead, &numRead, &over)) { bytesRead = (int) numRead; } else if (GetLastError() == ERROR_IO_PENDING) { HANDLE handles[] = { over.hEvent, intern->cancelEvent }; DWORD waitResult = WaitForMultipleObjects (2, handles, FALSE, timeOutMilliseconds >= 0 ? timeOutMilliseconds : INFINITE); if (waitResult != WAIT_OBJECT_0) { // if the operation timed out, let's cancel it... CancelIo (intern->pipeH); WaitForSingleObject (over.hEvent, INFINITE); // makes sure cancel is complete } if (GetOverlappedResult (intern->pipeH, &over, &numRead, FALSE)) { bytesRead = (int) numRead; } else if (GetLastError() == ERROR_BROKEN_PIPE && intern->isPipe) { intern->disconnectPipe(); waitAgain = true; } } else { waitAgain = internal != 0; Sleep (5); } CloseHandle (over.hEvent); } return bytesRead; } int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) { int bytesWritten = -1; NamedPipeInternal* const intern = static_cast (internal); if (intern != 0 && intern->connect (timeOutMilliseconds)) { if (numBytesToWrite <= 0) return 0; OVERLAPPED over; zerostruct (over); over.hEvent = CreateEvent (0, TRUE, FALSE, 0); unsigned long numWritten; if (WriteFile (intern->pipeH, sourceBuffer, numBytesToWrite, &numWritten, &over)) { bytesWritten = (int) numWritten; } else if (GetLastError() == ERROR_IO_PENDING) { HANDLE handles[] = { over.hEvent, intern->cancelEvent }; DWORD waitResult; waitResult = WaitForMultipleObjects (2, handles, FALSE, timeOutMilliseconds >= 0 ? timeOutMilliseconds : INFINITE); if (waitResult != WAIT_OBJECT_0) { CancelIo (intern->pipeH); WaitForSingleObject (over.hEvent, INFINITE); } if (GetOverlappedResult (intern->pipeH, &over, &numWritten, FALSE)) { bytesWritten = (int) numWritten; } else if (GetLastError() == ERROR_BROKEN_PIPE && intern->isPipe) { intern->disconnectPipe(); } } CloseHandle (over.hEvent); } return bytesWritten; } void NamedPipe::cancelPendingReads() { if (internal != 0) SetEvent (static_cast (internal)->cancelEvent); } #endif