1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21 */
22
23 namespace juce
24 {
25
26 #ifndef INVALID_FILE_ATTRIBUTES
27 #define INVALID_FILE_ATTRIBUTES ((DWORD) -1)
28 #endif
29
30 //==============================================================================
31 namespace WindowsFileHelpers
32 {
33 //==============================================================================
34 #if JUCE_WINDOWS
35 typedef struct _REPARSE_DATA_BUFFER {
36 ULONG ReparseTag;
37 USHORT ReparseDataLength;
38 USHORT Reserved;
39 union {
40 struct {
41 USHORT SubstituteNameOffset;
42 USHORT SubstituteNameLength;
43 USHORT PrintNameOffset;
44 USHORT PrintNameLength;
45 ULONG Flags;
46 WCHAR PathBuffer[1];
47 } SymbolicLinkReparseBuffer;
48 struct {
49 USHORT SubstituteNameOffset;
50 USHORT SubstituteNameLength;
51 USHORT PrintNameOffset;
52 USHORT PrintNameLength;
53 WCHAR PathBuffer[1];
54 } MountPointReparseBuffer;
55 struct {
56 UCHAR DataBuffer[1];
57 } GenericReparseBuffer;
58 } DUMMYUNIONNAME;
59 } *PREPARSE_DATA_BUFFER, REPARSE_DATA_BUFFER;
60 #endif
61
62 //==============================================================================
getAtts(const String & path)63 DWORD getAtts (const String& path) noexcept
64 {
65 return GetFileAttributes (path.toWideCharPointer());
66 }
67
changeAtts(const String & path,DWORD bitsToSet,DWORD bitsToClear)68 bool changeAtts (const String& path, DWORD bitsToSet, DWORD bitsToClear) noexcept
69 {
70 auto oldAtts = getAtts (path);
71
72 if (oldAtts == INVALID_FILE_ATTRIBUTES)
73 return false;
74
75 auto newAtts = ((oldAtts | bitsToSet) & ~bitsToClear);
76
77 return newAtts == oldAtts
78 || SetFileAttributes (path.toWideCharPointer(), newAtts) != FALSE;
79 }
80
fileTimeToTime(const FILETIME * const ft)81 int64 fileTimeToTime (const FILETIME* const ft) noexcept
82 {
83 static_assert (sizeof (ULARGE_INTEGER) == sizeof (FILETIME),
84 "ULARGE_INTEGER is too small to hold FILETIME: please report!");
85
86 return (int64) ((reinterpret_cast<const ULARGE_INTEGER*> (ft)->QuadPart - 116444736000000000LL) / 10000);
87 }
88
timeToFileTime(const int64 time,FILETIME * const ft)89 FILETIME* timeToFileTime (const int64 time, FILETIME* const ft) noexcept
90 {
91 if (time <= 0)
92 return nullptr;
93
94 reinterpret_cast<ULARGE_INTEGER*> (ft)->QuadPart = (ULONGLONG) (time * 10000 + 116444736000000000LL);
95 return ft;
96 }
97
getDriveFromPath(String path)98 String getDriveFromPath (String path)
99 {
100 if (path.isNotEmpty() && path[1] == ':' && path[2] == 0)
101 path << '\\';
102
103 const size_t numBytes = CharPointer_UTF16::getBytesRequiredFor (path.getCharPointer()) + 4;
104 HeapBlock<WCHAR> pathCopy;
105 pathCopy.calloc (numBytes, 1);
106 path.copyToUTF16 (pathCopy, numBytes);
107
108 if (PathStripToRoot (pathCopy))
109 path = static_cast<const WCHAR*> (pathCopy);
110
111 return path;
112 }
113
getDiskSpaceInfo(const String & path,const bool total)114 int64 getDiskSpaceInfo (const String& path, const bool total)
115 {
116 ULARGE_INTEGER spc, tot, totFree;
117
118 if (GetDiskFreeSpaceEx (getDriveFromPath (path).toWideCharPointer(), &spc, &tot, &totFree))
119 return total ? (int64) tot.QuadPart
120 : (int64) spc.QuadPart;
121
122 return 0;
123 }
124
getWindowsDriveType(const String & path)125 unsigned int getWindowsDriveType (const String& path)
126 {
127 return GetDriveType (getDriveFromPath (path).toWideCharPointer());
128 }
129
getSpecialFolderPath(int type)130 File getSpecialFolderPath (int type)
131 {
132 WCHAR path[MAX_PATH + 256];
133
134 if (SHGetSpecialFolderPath (nullptr, path, type, FALSE))
135 return File (String (path));
136
137 return {};
138 }
139
getModuleFileName(HINSTANCE moduleHandle)140 File getModuleFileName (HINSTANCE moduleHandle)
141 {
142 WCHAR dest[MAX_PATH + 256];
143 dest[0] = 0;
144 GetModuleFileName (moduleHandle, dest, (DWORD) numElementsInArray (dest));
145 return File (String (dest));
146 }
147
getResultForLastError()148 Result getResultForLastError()
149 {
150 TCHAR messageBuffer[256] = {};
151
152 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
153 nullptr, GetLastError(), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
154 messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr);
155
156 return Result::fail (String (messageBuffer));
157 }
158 }
159
160 //==============================================================================
161 JUCE_DECLARE_DEPRECATED_STATIC (const juce_wchar File::separator = '\\';)
162 JUCE_DECLARE_DEPRECATED_STATIC (const StringRef File::separatorString ("\\");)
163
getSeparatorChar()164 juce_wchar File::getSeparatorChar() { return '\\'; }
getSeparatorString()165 StringRef File::getSeparatorString() { return "\\"; }
166
167 void* getUser32Function (const char*);
168
169 //==============================================================================
exists() const170 bool File::exists() const
171 {
172 return fullPath.isNotEmpty()
173 && WindowsFileHelpers::getAtts (fullPath) != INVALID_FILE_ATTRIBUTES;
174 }
175
existsAsFile() const176 bool File::existsAsFile() const
177 {
178 return fullPath.isNotEmpty()
179 && (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_DIRECTORY) == 0;
180 }
181
isDirectory() const182 bool File::isDirectory() const
183 {
184 auto attr = WindowsFileHelpers::getAtts (fullPath);
185 return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 && attr != INVALID_FILE_ATTRIBUTES;
186 }
187
hasWriteAccess() const188 bool File::hasWriteAccess() const
189 {
190 if (fullPath.isEmpty())
191 return true;
192
193 auto attr = WindowsFileHelpers::getAtts (fullPath);
194
195 // NB: According to MS, the FILE_ATTRIBUTE_READONLY attribute doesn't work for
196 // folders, and can be incorrectly set for some special folders, so we'll just say
197 // that folders are always writable.
198 return attr == INVALID_FILE_ATTRIBUTES
199 || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0
200 || (attr & FILE_ATTRIBUTE_READONLY) == 0;
201 }
202
setFileReadOnlyInternal(bool shouldBeReadOnly) const203 bool File::setFileReadOnlyInternal (bool shouldBeReadOnly) const
204 {
205 return WindowsFileHelpers::changeAtts (fullPath,
206 shouldBeReadOnly ? FILE_ATTRIBUTE_READONLY : 0,
207 shouldBeReadOnly ? 0 : FILE_ATTRIBUTE_READONLY);
208 }
209
setFileExecutableInternal(bool) const210 bool File::setFileExecutableInternal (bool /*shouldBeExecutable*/) const
211 {
212 // XXX is this possible?
213 return false;
214 }
215
isHidden() const216 bool File::isHidden() const
217 {
218 return (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_HIDDEN) != 0;
219 }
220
221 //==============================================================================
deleteFile() const222 bool File::deleteFile() const
223 {
224 if (! exists())
225 return true;
226
227 return isDirectory() ? RemoveDirectory (fullPath.toWideCharPointer()) != 0
228 : DeleteFile (fullPath.toWideCharPointer()) != 0;
229 }
230
moveToTrash() const231 bool File::moveToTrash() const
232 {
233 if (! exists())
234 return true;
235
236 // The string we pass in must be double null terminated..
237 const size_t numBytes = CharPointer_UTF16::getBytesRequiredFor (fullPath.getCharPointer()) + 8;
238 HeapBlock<WCHAR> doubleNullTermPath;
239 doubleNullTermPath.calloc (numBytes, 1);
240 fullPath.copyToUTF16 (doubleNullTermPath, numBytes);
241
242 SHFILEOPSTRUCT fos = {};
243 fos.wFunc = FO_DELETE;
244 fos.pFrom = doubleNullTermPath;
245 fos.fFlags = FOF_ALLOWUNDO | FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION
246 | FOF_NOCONFIRMMKDIR | FOF_RENAMEONCOLLISION;
247
248 return SHFileOperation (&fos) == 0;
249 }
250
copyInternal(const File & dest) const251 bool File::copyInternal (const File& dest) const
252 {
253 return CopyFile (fullPath.toWideCharPointer(),
254 dest.getFullPathName().toWideCharPointer(), false) != 0;
255 }
256
moveInternal(const File & dest) const257 bool File::moveInternal (const File& dest) const
258 {
259 return MoveFile (fullPath.toWideCharPointer(),
260 dest.getFullPathName().toWideCharPointer()) != 0;
261 }
262
replaceInternal(const File & dest) const263 bool File::replaceInternal (const File& dest) const
264 {
265 return ReplaceFile (dest.getFullPathName().toWideCharPointer(),
266 fullPath.toWideCharPointer(),
267 nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS | 4 /*REPLACEFILE_IGNORE_ACL_ERRORS*/,
268 nullptr, nullptr) != 0;
269 }
270
createDirectoryInternal(const String & fileName) const271 Result File::createDirectoryInternal (const String& fileName) const
272 {
273 return CreateDirectory (fileName.toWideCharPointer(), nullptr) ? Result::ok()
274 : WindowsFileHelpers::getResultForLastError();
275 }
276
277 //==============================================================================
juce_fileSetPosition(void * handle,int64 pos)278 int64 juce_fileSetPosition (void* handle, int64 pos)
279 {
280 LARGE_INTEGER li;
281 li.QuadPart = pos;
282 li.LowPart = SetFilePointer ((HANDLE) handle, (LONG) li.LowPart,
283 &li.HighPart, FILE_BEGIN); // (returns -1 if it fails)
284 return li.QuadPart;
285 }
286
openHandle()287 void FileInputStream::openHandle()
288 {
289 auto h = CreateFile (file.getFullPathName().toWideCharPointer(),
290 GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
291 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
292
293 if (h != INVALID_HANDLE_VALUE)
294 fileHandle = (void*) h;
295 else
296 status = WindowsFileHelpers::getResultForLastError();
297 }
298
~FileInputStream()299 FileInputStream::~FileInputStream()
300 {
301 CloseHandle ((HANDLE) fileHandle);
302 }
303
readInternal(void * buffer,size_t numBytes)304 size_t FileInputStream::readInternal (void* buffer, size_t numBytes)
305 {
306 if (fileHandle != nullptr)
307 {
308 DWORD actualNum = 0;
309
310 if (! ReadFile ((HANDLE) fileHandle, buffer, (DWORD) numBytes, &actualNum, nullptr))
311 status = WindowsFileHelpers::getResultForLastError();
312
313 return (size_t) actualNum;
314 }
315
316 return 0;
317 }
318
319 //==============================================================================
openHandle()320 void FileOutputStream::openHandle()
321 {
322 auto h = CreateFile (file.getFullPathName().toWideCharPointer(),
323 GENERIC_WRITE, FILE_SHARE_READ, nullptr,
324 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
325
326 if (h != INVALID_HANDLE_VALUE)
327 {
328 LARGE_INTEGER li;
329 li.QuadPart = 0;
330 li.LowPart = SetFilePointer (h, 0, &li.HighPart, FILE_END);
331
332 if (li.LowPart != INVALID_SET_FILE_POINTER)
333 {
334 fileHandle = (void*) h;
335 currentPosition = li.QuadPart;
336 return;
337 }
338 }
339
340 status = WindowsFileHelpers::getResultForLastError();
341 }
342
closeHandle()343 void FileOutputStream::closeHandle()
344 {
345 CloseHandle ((HANDLE) fileHandle);
346 }
347
writeInternal(const void * bufferToWrite,size_t numBytes)348 ssize_t FileOutputStream::writeInternal (const void* bufferToWrite, size_t numBytes)
349 {
350 DWORD actualNum = 0;
351
352 if (fileHandle != nullptr)
353 if (! WriteFile ((HANDLE) fileHandle, bufferToWrite, (DWORD) numBytes, &actualNum, nullptr))
354 status = WindowsFileHelpers::getResultForLastError();
355
356 return (ssize_t) actualNum;
357 }
358
flushInternal()359 void FileOutputStream::flushInternal()
360 {
361 if (fileHandle != nullptr)
362 if (! FlushFileBuffers ((HANDLE) fileHandle))
363 status = WindowsFileHelpers::getResultForLastError();
364 }
365
truncate()366 Result FileOutputStream::truncate()
367 {
368 if (fileHandle == nullptr)
369 return status;
370
371 flush();
372 return SetEndOfFile ((HANDLE) fileHandle) ? Result::ok()
373 : WindowsFileHelpers::getResultForLastError();
374 }
375
376 //==============================================================================
openInternal(const File & file,AccessMode mode,bool exclusive)377 void MemoryMappedFile::openInternal (const File& file, AccessMode mode, bool exclusive)
378 {
379 jassert (mode == readOnly || mode == readWrite);
380
381 if (range.getStart() > 0)
382 {
383 SYSTEM_INFO systemInfo;
384 GetNativeSystemInfo (&systemInfo);
385
386 range.setStart (range.getStart() - (range.getStart() % systemInfo.dwAllocationGranularity));
387 }
388
389 DWORD accessMode = GENERIC_READ, createType = OPEN_EXISTING;
390 DWORD protect = PAGE_READONLY, access = FILE_MAP_READ;
391
392 if (mode == readWrite)
393 {
394 accessMode = GENERIC_READ | GENERIC_WRITE;
395 createType = OPEN_ALWAYS;
396 protect = PAGE_READWRITE;
397 access = FILE_MAP_ALL_ACCESS;
398 }
399
400 auto h = CreateFile (file.getFullPathName().toWideCharPointer(), accessMode,
401 exclusive ? 0 : (FILE_SHARE_READ | FILE_SHARE_DELETE | (mode == readWrite ? FILE_SHARE_WRITE : 0)), nullptr,
402 createType, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
403
404 if (h != INVALID_HANDLE_VALUE)
405 {
406 fileHandle = (void*) h;
407
408 auto mappingHandle = CreateFileMapping (h, nullptr, protect,
409 (DWORD) (range.getEnd() >> 32),
410 (DWORD) range.getEnd(), nullptr);
411
412 if (mappingHandle != nullptr)
413 {
414 address = MapViewOfFile (mappingHandle, access, (DWORD) (range.getStart() >> 32),
415 (DWORD) range.getStart(), (SIZE_T) range.getLength());
416
417 if (address == nullptr)
418 range = Range<int64>();
419
420 CloseHandle (mappingHandle);
421 }
422 }
423 }
424
~MemoryMappedFile()425 MemoryMappedFile::~MemoryMappedFile()
426 {
427 if (address != nullptr)
428 UnmapViewOfFile (address);
429
430 if (fileHandle != nullptr)
431 CloseHandle ((HANDLE) fileHandle);
432 }
433
434 //==============================================================================
getSize() const435 int64 File::getSize() const
436 {
437 WIN32_FILE_ATTRIBUTE_DATA attributes;
438
439 if (GetFileAttributesEx (fullPath.toWideCharPointer(), GetFileExInfoStandard, &attributes))
440 return (((int64) attributes.nFileSizeHigh) << 32) | attributes.nFileSizeLow;
441
442 return 0;
443 }
444
getFileTimesInternal(int64 & modificationTime,int64 & accessTime,int64 & creationTime) const445 void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int64& creationTime) const
446 {
447 using namespace WindowsFileHelpers;
448 WIN32_FILE_ATTRIBUTE_DATA attributes;
449
450 if (GetFileAttributesEx (fullPath.toWideCharPointer(), GetFileExInfoStandard, &attributes))
451 {
452 modificationTime = fileTimeToTime (&attributes.ftLastWriteTime);
453 creationTime = fileTimeToTime (&attributes.ftCreationTime);
454 accessTime = fileTimeToTime (&attributes.ftLastAccessTime);
455 }
456 else
457 {
458 creationTime = accessTime = modificationTime = 0;
459 }
460 }
461
setFileTimesInternal(int64 modificationTime,int64 accessTime,int64 creationTime) const462 bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 creationTime) const
463 {
464 using namespace WindowsFileHelpers;
465
466 bool ok = false;
467 auto h = CreateFile (fullPath.toWideCharPointer(),
468 GENERIC_WRITE, FILE_SHARE_READ, nullptr,
469 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
470
471 if (h != INVALID_HANDLE_VALUE)
472 {
473 FILETIME m, a, c;
474
475 ok = SetFileTime (h,
476 timeToFileTime (creationTime, &c),
477 timeToFileTime (accessTime, &a),
478 timeToFileTime (modificationTime, &m)) != 0;
479
480 CloseHandle (h);
481 }
482
483 return ok;
484 }
485
486 //==============================================================================
findFileSystemRoots(Array<File> & destArray)487 void File::findFileSystemRoots (Array<File>& destArray)
488 {
489 TCHAR buffer[2048] = {};
490 GetLogicalDriveStrings (2048, buffer);
491
492 const TCHAR* n = buffer;
493 StringArray roots;
494
495 while (*n != 0)
496 {
497 roots.add (String (n));
498
499 while (*n++ != 0)
500 {}
501 }
502
503 roots.sort (true);
504
505 for (int i = 0; i < roots.size(); ++i)
506 destArray.add (roots[i]);
507 }
508
509 //==============================================================================
getVolumeLabel() const510 String File::getVolumeLabel() const
511 {
512 TCHAR dest[64];
513
514 if (! GetVolumeInformation (WindowsFileHelpers::getDriveFromPath (getFullPathName()).toWideCharPointer(), dest,
515 (DWORD) numElementsInArray (dest), nullptr, nullptr, nullptr, nullptr, 0))
516 dest[0] = 0;
517
518 return dest;
519 }
520
getVolumeSerialNumber() const521 int File::getVolumeSerialNumber() const
522 {
523 TCHAR dest[64];
524 DWORD serialNum;
525
526 if (! GetVolumeInformation (WindowsFileHelpers::getDriveFromPath (getFullPathName()).toWideCharPointer(), dest,
527 (DWORD) numElementsInArray (dest), &serialNum, nullptr, nullptr, nullptr, 0))
528 return 0;
529
530 return (int) serialNum;
531 }
532
getBytesFreeOnVolume() const533 int64 File::getBytesFreeOnVolume() const
534 {
535 return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), false);
536 }
537
getVolumeTotalSize() const538 int64 File::getVolumeTotalSize() const
539 {
540 return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), true);
541 }
542
getFileIdentifier() const543 uint64 File::getFileIdentifier() const
544 {
545 uint64 result = 0;
546
547 String path = getFullPathName();
548
549 if (isRoot())
550 path += "\\";
551
552 auto h = CreateFile (path.toWideCharPointer(),
553 GENERIC_READ, FILE_SHARE_READ, nullptr,
554 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
555
556 if (h != INVALID_HANDLE_VALUE)
557 {
558 BY_HANDLE_FILE_INFORMATION info;
559 zerostruct (info);
560
561 if (GetFileInformationByHandle (h, &info))
562 result = (((uint64) info.nFileIndexHigh) << 32) | info.nFileIndexLow;
563
564 CloseHandle (h);
565 }
566
567 return result;
568 }
569
570 //==============================================================================
isOnCDRomDrive() const571 bool File::isOnCDRomDrive() const
572 {
573 return WindowsFileHelpers::getWindowsDriveType (getFullPathName()) == DRIVE_CDROM;
574 }
575
isOnHardDisk() const576 bool File::isOnHardDisk() const
577 {
578 if (fullPath.isEmpty())
579 return false;
580
581 auto n = WindowsFileHelpers::getWindowsDriveType (getFullPathName());
582
583 return n != DRIVE_REMOVABLE
584 && n != DRIVE_CDROM
585 && n != DRIVE_REMOTE
586 && n != DRIVE_NO_ROOT_DIR;
587 }
588
isOnRemovableDrive() const589 bool File::isOnRemovableDrive() const
590 {
591 if (fullPath.isEmpty())
592 return false;
593
594 auto n = WindowsFileHelpers::getWindowsDriveType (getFullPathName());
595
596 return n == DRIVE_CDROM
597 || n == DRIVE_REMOTE
598 || n == DRIVE_REMOVABLE
599 || n == DRIVE_RAMDISK;
600 }
601
602 //==============================================================================
getSpecialLocation(const SpecialLocationType type)603 File JUCE_CALLTYPE File::getSpecialLocation (const SpecialLocationType type)
604 {
605 int csidlType = 0;
606
607 switch (type)
608 {
609 case userHomeDirectory: csidlType = CSIDL_PROFILE; break;
610 case userDocumentsDirectory: csidlType = CSIDL_PERSONAL; break;
611 case userDesktopDirectory: csidlType = CSIDL_DESKTOP; break;
612 case userApplicationDataDirectory: csidlType = CSIDL_APPDATA; break;
613 case commonApplicationDataDirectory: csidlType = CSIDL_COMMON_APPDATA; break;
614 case commonDocumentsDirectory: csidlType = CSIDL_COMMON_DOCUMENTS; break;
615 case globalApplicationsDirectory: csidlType = CSIDL_PROGRAM_FILES; break;
616 case globalApplicationsDirectoryX86: csidlType = CSIDL_PROGRAM_FILESX86; break;
617 case userMusicDirectory: csidlType = 0x0d; /*CSIDL_MYMUSIC*/ break;
618 case userMoviesDirectory: csidlType = 0x0e; /*CSIDL_MYVIDEO*/ break;
619 case userPicturesDirectory: csidlType = 0x27; /*CSIDL_MYPICTURES*/ break;
620
621 case tempDirectory:
622 {
623 WCHAR dest[2048];
624 dest[0] = 0;
625 GetTempPath ((DWORD) numElementsInArray (dest), dest);
626 return File (String (dest));
627 }
628
629 case windowsSystemDirectory:
630 {
631 WCHAR dest[2048];
632 dest[0] = 0;
633 GetSystemDirectoryW (dest, (UINT) numElementsInArray (dest));
634 return File (String (dest));
635 }
636
637 case invokedExecutableFile:
638 case currentExecutableFile:
639 case currentApplicationFile:
640 return WindowsFileHelpers::getModuleFileName ((HINSTANCE) Process::getCurrentModuleInstanceHandle());
641
642 case hostApplicationPath:
643 return WindowsFileHelpers::getModuleFileName (nullptr);
644
645 default:
646 jassertfalse; // unknown type?
647 return {};
648 }
649
650 return WindowsFileHelpers::getSpecialFolderPath (csidlType);
651 }
652
653 //==============================================================================
getCurrentWorkingDirectory()654 File File::getCurrentWorkingDirectory()
655 {
656 WCHAR dest[MAX_PATH + 256];
657 dest[0] = 0;
658 GetCurrentDirectory ((DWORD) numElementsInArray (dest), dest);
659 return File (String (dest));
660 }
661
setAsCurrentWorkingDirectory() const662 bool File::setAsCurrentWorkingDirectory() const
663 {
664 return SetCurrentDirectory (getFullPathName().toWideCharPointer()) != FALSE;
665 }
666
667 //==============================================================================
getVersion() const668 String File::getVersion() const
669 {
670 String result;
671
672 DWORD handle = 0;
673 DWORD bufferSize = GetFileVersionInfoSize (getFullPathName().toWideCharPointer(), &handle);
674 HeapBlock<char> buffer;
675 buffer.calloc (bufferSize);
676
677 if (GetFileVersionInfo (getFullPathName().toWideCharPointer(), 0, bufferSize, buffer))
678 {
679 VS_FIXEDFILEINFO* vffi;
680 UINT len = 0;
681
682 if (VerQueryValue (buffer, (LPTSTR) _T("\\"), (LPVOID*) &vffi, &len))
683 {
684 result << (int) HIWORD (vffi->dwFileVersionMS) << '.'
685 << (int) LOWORD (vffi->dwFileVersionMS) << '.'
686 << (int) HIWORD (vffi->dwFileVersionLS) << '.'
687 << (int) LOWORD (vffi->dwFileVersionLS);
688 }
689 }
690
691 return result;
692 }
693
694 //==============================================================================
isSymbolicLink() const695 bool File::isSymbolicLink() const
696 {
697 return (GetFileAttributes (fullPath.toWideCharPointer()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
698 }
699
isShortcut() const700 bool File::isShortcut() const
701 {
702 return hasFileExtension (".lnk");
703 }
704
readWindowsLnkFile(File lnkFile,bool wantsAbsolutePath)705 static String readWindowsLnkFile (File lnkFile, bool wantsAbsolutePath)
706 {
707 if (! lnkFile.exists())
708 lnkFile = File (lnkFile.getFullPathName() + ".lnk");
709
710 if (lnkFile.exists())
711 {
712 ComSmartPtr<IShellLink> shellLink;
713 ComSmartPtr<IPersistFile> persistFile;
714
715 if (SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink))
716 && SUCCEEDED (shellLink.QueryInterface (persistFile))
717 && SUCCEEDED (persistFile->Load (lnkFile.getFullPathName().toWideCharPointer(), STGM_READ))
718 && (! wantsAbsolutePath || SUCCEEDED (shellLink->Resolve (nullptr, SLR_ANY_MATCH | SLR_NO_UI))))
719 {
720 WIN32_FIND_DATA winFindData;
721 WCHAR resolvedPath[MAX_PATH];
722
723 DWORD flags = SLGP_UNCPRIORITY;
724
725 if (! wantsAbsolutePath)
726 flags |= SLGP_RAWPATH;
727
728 if (SUCCEEDED (shellLink->GetPath (resolvedPath, MAX_PATH, &winFindData, flags)))
729 return resolvedPath;
730 }
731 }
732
733 return {};
734 }
735
readWindowsShortcutOrLink(const File & shortcut,bool wantsAbsolutePath)736 static String readWindowsShortcutOrLink (const File& shortcut, bool wantsAbsolutePath)
737 {
738 #if JUCE_WINDOWS
739 if (! wantsAbsolutePath)
740 {
741 HANDLE h = CreateFile (shortcut.getFullPathName().toWideCharPointer(),
742 GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
743 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
744 nullptr);
745
746 if (h != INVALID_HANDLE_VALUE)
747 {
748 HeapBlock<WindowsFileHelpers::REPARSE_DATA_BUFFER> reparseData;
749
750 reparseData.calloc (1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
751 DWORD bytesReturned = 0;
752
753 bool success = DeviceIoControl (h, FSCTL_GET_REPARSE_POINT, nullptr, 0,
754 reparseData.getData(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
755 &bytesReturned, nullptr) != 0;
756 CloseHandle (h);
757
758 if (success)
759 {
760 if (IsReparseTagMicrosoft (reparseData->ReparseTag))
761 {
762 String targetPath;
763
764 switch (reparseData->ReparseTag)
765 {
766 case IO_REPARSE_TAG_SYMLINK:
767 {
768 auto& symlinkData = reparseData->SymbolicLinkReparseBuffer;
769 targetPath = {symlinkData.PathBuffer + (symlinkData.SubstituteNameOffset / sizeof (WCHAR)),
770 symlinkData.SubstituteNameLength / sizeof (WCHAR)};
771 }
772 break;
773
774 case IO_REPARSE_TAG_MOUNT_POINT:
775 {
776 auto& mountData = reparseData->MountPointReparseBuffer;
777 targetPath = {mountData.PathBuffer + (mountData.SubstituteNameOffset / sizeof (WCHAR)),
778 mountData.SubstituteNameLength / sizeof (WCHAR)};
779 }
780 break;
781
782 default:
783 break;
784 }
785
786 if (targetPath.isNotEmpty())
787 {
788 const StringRef prefix ("\\??\\");
789
790 if (targetPath.startsWith (prefix))
791 targetPath = targetPath.substring (prefix.length());
792
793 return targetPath;
794 }
795 }
796 }
797 }
798 }
799
800 if (! wantsAbsolutePath)
801 return readWindowsLnkFile (shortcut, false);
802
803 typedef DWORD (WINAPI* GetFinalPathNameByHandleFunc) (HANDLE, LPTSTR, DWORD, DWORD);
804
805 static GetFinalPathNameByHandleFunc getFinalPathNameByHandle
806 = (GetFinalPathNameByHandleFunc) getUser32Function ("GetFinalPathNameByHandle");
807
808 if (getFinalPathNameByHandle != nullptr)
809 {
810 HANDLE h = CreateFile (shortcut.getFullPathName().toWideCharPointer(),
811 GENERIC_READ, FILE_SHARE_READ, nullptr,
812 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
813
814 if (h != INVALID_HANDLE_VALUE)
815 {
816 if (DWORD requiredSize = getFinalPathNameByHandle (h, nullptr, 0, 0 /* FILE_NAME_NORMALIZED */))
817 {
818 HeapBlock<WCHAR> buffer (requiredSize + 2, true);
819
820 if (getFinalPathNameByHandle (h, buffer, requiredSize, 0 /* FILE_NAME_NORMALIZED */) > 0)
821 {
822 CloseHandle (h);
823
824 const StringRef prefix ("\\\\?\\");
825 const String path (buffer.get());
826
827 // It turns out that GetFinalPathNameByHandleW prepends \\?\ to the path.
828 // This is not a bug, it's feature. See MSDN for more information.
829 return path.startsWith (prefix) ? path.substring (prefix.length()) : path;
830 }
831 }
832
833 CloseHandle (h);
834 }
835 }
836 #endif
837
838 // as last resort try the resolve method of the ShellLink
839 return readWindowsLnkFile (shortcut, true);
840 }
841
getNativeLinkedTarget() const842 String File::getNativeLinkedTarget() const
843 {
844 return readWindowsShortcutOrLink (*this, false);
845 }
846
getLinkedTarget() const847 File File::getLinkedTarget() const
848 {
849 auto target = readWindowsShortcutOrLink (*this, true);
850
851 if (target.isNotEmpty() && File::isAbsolutePath (target))
852 return File (target);
853
854 return *this;
855 }
856
createShortcut(const String & description,const File & linkFileToCreate) const857 bool File::createShortcut (const String& description, const File& linkFileToCreate) const
858 {
859 linkFileToCreate.deleteFile();
860
861 ComSmartPtr<IShellLink> shellLink;
862 ComSmartPtr<IPersistFile> persistFile;
863
864 CoInitialize (nullptr);
865
866 return SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink))
867 && SUCCEEDED (shellLink->SetPath (getFullPathName().toWideCharPointer()))
868 && SUCCEEDED (shellLink->SetDescription (description.toWideCharPointer()))
869 && SUCCEEDED (shellLink.QueryInterface (persistFile))
870 && SUCCEEDED (persistFile->Save (linkFileToCreate.getFullPathName().toWideCharPointer(), TRUE));
871 }
872
873 //==============================================================================
874 class DirectoryIterator::NativeIterator::Pimpl
875 {
876 public:
Pimpl(const File & directory,const String & wildCard)877 Pimpl (const File& directory, const String& wildCard)
878 : directoryWithWildCard (directory.getFullPathName().isNotEmpty() ? File::addTrailingSeparator (directory.getFullPathName()) + wildCard : String()),
879 handle (INVALID_HANDLE_VALUE)
880 {
881 }
882
~Pimpl()883 ~Pimpl()
884 {
885 if (handle != INVALID_HANDLE_VALUE)
886 FindClose (handle);
887 }
888
next(String & filenameFound,bool * const isDir,bool * const isHidden,int64 * const fileSize,Time * const modTime,Time * const creationTime,bool * const isReadOnly)889 bool next (String& filenameFound,
890 bool* const isDir, bool* const isHidden, int64* const fileSize,
891 Time* const modTime, Time* const creationTime, bool* const isReadOnly)
892 {
893 using namespace WindowsFileHelpers;
894 WIN32_FIND_DATA findData;
895
896 if (handle == INVALID_HANDLE_VALUE)
897 {
898 handle = FindFirstFile (directoryWithWildCard.toWideCharPointer(), &findData);
899
900 if (handle == INVALID_HANDLE_VALUE)
901 return false;
902 }
903 else
904 {
905 if (FindNextFile (handle, &findData) == 0)
906 return false;
907 }
908
909 filenameFound = findData.cFileName;
910
911 if (isDir != nullptr) *isDir = ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
912 if (isHidden != nullptr) *isHidden = ((findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0);
913 if (isReadOnly != nullptr) *isReadOnly = ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
914 if (fileSize != nullptr) *fileSize = findData.nFileSizeLow + (((int64) findData.nFileSizeHigh) << 32);
915 if (modTime != nullptr) *modTime = Time (fileTimeToTime (&findData.ftLastWriteTime));
916 if (creationTime != nullptr) *creationTime = Time (fileTimeToTime (&findData.ftCreationTime));
917
918 return true;
919 }
920
921 private:
922 const String directoryWithWildCard;
923 HANDLE handle;
924
925 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
926 };
927
NativeIterator(const File & directory,const String & wildCard)928 DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCard)
929 : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCard))
930 {
931 }
932
~NativeIterator()933 DirectoryIterator::NativeIterator::~NativeIterator()
934 {
935 }
936
next(String & filenameFound,bool * const isDir,bool * const isHidden,int64 * const fileSize,Time * const modTime,Time * const creationTime,bool * const isReadOnly)937 bool DirectoryIterator::NativeIterator::next (String& filenameFound,
938 bool* const isDir, bool* const isHidden, int64* const fileSize,
939 Time* const modTime, Time* const creationTime, bool* const isReadOnly)
940 {
941 return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly);
942 }
943
944
945 //==============================================================================
openDocument(const String & fileName,const String & parameters)946 bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& parameters)
947 {
948 HINSTANCE hInstance = ShellExecute (nullptr, nullptr, fileName.toWideCharPointer(),
949 parameters.toWideCharPointer(), nullptr, SW_SHOWDEFAULT);
950
951 return hInstance > (HINSTANCE) 32;
952 }
953
revealToUser() const954 void File::revealToUser() const
955 {
956 DynamicLibrary dll ("Shell32.dll");
957 JUCE_LOAD_WINAPI_FUNCTION (dll, ILCreateFromPathW, ilCreateFromPathW, ITEMIDLIST*, (LPCWSTR))
958 JUCE_LOAD_WINAPI_FUNCTION (dll, ILFree, ilFree, void, (ITEMIDLIST*))
959 JUCE_LOAD_WINAPI_FUNCTION (dll, SHOpenFolderAndSelectItems, shOpenFolderAndSelectItems, HRESULT, (ITEMIDLIST*, UINT, void*, DWORD))
960
961 if (ilCreateFromPathW != nullptr && shOpenFolderAndSelectItems != nullptr && ilFree != nullptr)
962 {
963 if (ITEMIDLIST* const itemIDList = ilCreateFromPathW (fullPath.toWideCharPointer()))
964 {
965 shOpenFolderAndSelectItems (itemIDList, 0, nullptr, 0);
966 ilFree (itemIDList);
967 }
968 }
969 }
970
971 //==============================================================================
972 class NamedPipe::Pimpl
973 {
974 public:
Pimpl(const String & pipeName,const bool createPipe,bool mustNotExist)975 Pimpl (const String& pipeName, const bool createPipe, bool mustNotExist)
976 : filename ("\\\\.\\pipe\\" + File::createLegalFileName (pipeName)),
977 pipeH (INVALID_HANDLE_VALUE),
978 cancelEvent (CreateEvent (nullptr, FALSE, FALSE, nullptr)),
979 connected (false), ownsPipe (createPipe), shouldStop (false)
980 {
981 if (createPipe)
982 {
983 pipeH = CreateNamedPipe (filename.toWideCharPointer(),
984 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0,
985 PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, nullptr);
986
987 if (mustNotExist && GetLastError() == ERROR_ALREADY_EXISTS)
988 closePipeHandle();
989 }
990 }
991
~Pimpl()992 ~Pimpl()
993 {
994 closePipeHandle();
995 CloseHandle (cancelEvent);
996 }
997
connect(const int timeOutMs)998 bool connect (const int timeOutMs)
999 {
1000 if (! ownsPipe)
1001 {
1002 if (pipeH != INVALID_HANDLE_VALUE)
1003 return true;
1004
1005 const Time timeOutEnd (Time::getCurrentTime() + RelativeTime::milliseconds (timeOutMs));
1006
1007 for (;;)
1008 {
1009 {
1010 const ScopedLock sl (createFileLock);
1011
1012 if (pipeH == INVALID_HANDLE_VALUE)
1013 pipeH = CreateFile (filename.toWideCharPointer(),
1014 GENERIC_READ | GENERIC_WRITE, 0, nullptr,
1015 OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
1016 }
1017
1018 if (pipeH != INVALID_HANDLE_VALUE)
1019 return true;
1020
1021 if (shouldStop || (timeOutMs >= 0 && Time::getCurrentTime() > timeOutEnd))
1022 return false;
1023
1024 Thread::sleep (1);
1025 }
1026 }
1027
1028 if (! connected)
1029 {
1030 OverlappedEvent over;
1031
1032 if (ConnectNamedPipe (pipeH, &over.over) == 0)
1033 {
1034 switch (GetLastError())
1035 {
1036 case ERROR_PIPE_CONNECTED: connected = true; break;
1037 case ERROR_IO_PENDING:
1038 case ERROR_PIPE_LISTENING: connected = waitForIO (over, timeOutMs); break;
1039 default: break;
1040 }
1041 }
1042 }
1043
1044 return connected;
1045 }
1046
disconnectPipe()1047 void disconnectPipe()
1048 {
1049 if (ownsPipe && connected)
1050 {
1051 DisconnectNamedPipe (pipeH);
1052 connected = false;
1053 }
1054 }
1055
closePipeHandle()1056 void closePipeHandle()
1057 {
1058 if (pipeH != INVALID_HANDLE_VALUE)
1059 {
1060 disconnectPipe();
1061 CloseHandle (pipeH);
1062 pipeH = INVALID_HANDLE_VALUE;
1063 }
1064 }
1065
read(void * destBuffer,const int maxBytesToRead,const int timeOutMilliseconds)1066 int read (void* destBuffer, const int maxBytesToRead, const int timeOutMilliseconds)
1067 {
1068 while (connect (timeOutMilliseconds))
1069 {
1070 if (maxBytesToRead <= 0)
1071 return 0;
1072
1073 OverlappedEvent over;
1074 unsigned long numRead;
1075
1076 if (ReadFile (pipeH, destBuffer, (DWORD) maxBytesToRead, &numRead, &over.over))
1077 return (int) numRead;
1078
1079 const DWORD lastError = GetLastError();
1080
1081 if (lastError == ERROR_IO_PENDING)
1082 {
1083 if (! waitForIO (over, timeOutMilliseconds))
1084 return -1;
1085
1086 if (GetOverlappedResult (pipeH, &over.over, &numRead, FALSE))
1087 return (int) numRead;
1088 }
1089
1090 if (ownsPipe && (GetLastError() == ERROR_BROKEN_PIPE || GetLastError() == ERROR_PIPE_NOT_CONNECTED))
1091 disconnectPipe();
1092 else
1093 break;
1094 }
1095
1096 return -1;
1097 }
1098
write(const void * sourceBuffer,int numBytesToWrite,int timeOutMilliseconds)1099 int write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds)
1100 {
1101 if (connect (timeOutMilliseconds))
1102 {
1103 if (numBytesToWrite <= 0)
1104 return 0;
1105
1106 OverlappedEvent over;
1107 unsigned long numWritten;
1108
1109 if (WriteFile (pipeH, sourceBuffer, (DWORD) numBytesToWrite, &numWritten, &over.over))
1110 return (int) numWritten;
1111
1112 if (GetLastError() == ERROR_IO_PENDING)
1113 {
1114 if (! waitForIO (over, timeOutMilliseconds))
1115 return -1;
1116
1117 if (GetOverlappedResult (pipeH, &over.over, &numWritten, FALSE))
1118 return (int) numWritten;
1119
1120 if (GetLastError() == ERROR_BROKEN_PIPE && ownsPipe)
1121 disconnectPipe();
1122 }
1123 }
1124
1125 return -1;
1126 }
1127
1128 const String filename;
1129 HANDLE pipeH, cancelEvent;
1130 bool connected, ownsPipe, shouldStop;
1131 CriticalSection createFileLock;
1132
1133 private:
1134 struct OverlappedEvent
1135 {
OverlappedEventjuce::NamedPipe::Pimpl::OverlappedEvent1136 OverlappedEvent()
1137 {
1138 zerostruct (over);
1139 over.hEvent = CreateEvent (nullptr, TRUE, FALSE, nullptr);
1140 }
1141
~OverlappedEventjuce::NamedPipe::Pimpl::OverlappedEvent1142 ~OverlappedEvent()
1143 {
1144 CloseHandle (over.hEvent);
1145 }
1146
1147 OVERLAPPED over;
1148 };
1149
waitForIO(OverlappedEvent & over,int timeOutMilliseconds)1150 bool waitForIO (OverlappedEvent& over, int timeOutMilliseconds)
1151 {
1152 if (shouldStop)
1153 return false;
1154
1155 HANDLE handles[] = { over.over.hEvent, cancelEvent };
1156 DWORD waitResult = WaitForMultipleObjects (2, handles, FALSE,
1157 timeOutMilliseconds >= 0 ? (DWORD) timeOutMilliseconds
1158 : INFINITE);
1159
1160 if (waitResult == WAIT_OBJECT_0)
1161 return true;
1162
1163 CancelIo (pipeH);
1164 return false;
1165 }
1166
1167 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
1168 };
1169
close()1170 void NamedPipe::close()
1171 {
1172 if (pimpl != nullptr)
1173 {
1174 pimpl->shouldStop = true;
1175 SetEvent (pimpl->cancelEvent);
1176
1177 ScopedWriteLock sl (lock);
1178 pimpl.reset();
1179 }
1180 }
1181
openInternal(const String & pipeName,const bool createPipe,bool mustNotExist)1182 bool NamedPipe::openInternal (const String& pipeName, const bool createPipe, bool mustNotExist)
1183 {
1184 pimpl.reset (new Pimpl (pipeName, createPipe, mustNotExist));
1185
1186 if (createPipe)
1187 {
1188 if (pimpl->pipeH == INVALID_HANDLE_VALUE)
1189 {
1190 pimpl.reset();
1191 return false;
1192 }
1193 }
1194 else if (! pimpl->connect (200))
1195 {
1196 pimpl.reset();
1197 return false;
1198 }
1199
1200 return true;
1201 }
1202
read(void * destBuffer,int maxBytesToRead,int timeOutMilliseconds)1203 int NamedPipe::read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds)
1204 {
1205 ScopedReadLock sl (lock);
1206 return pimpl != nullptr ? pimpl->read (destBuffer, maxBytesToRead, timeOutMilliseconds) : -1;
1207 }
1208
write(const void * sourceBuffer,int numBytesToWrite,int timeOutMilliseconds)1209 int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds)
1210 {
1211 ScopedReadLock sl (lock);
1212 return pimpl != nullptr ? pimpl->write (sourceBuffer, numBytesToWrite, timeOutMilliseconds) : -1;
1213 }
1214
1215 } // namespace juce
1216