mirror of
https://github.com/vczh-libraries/Release.git
synced 2026-02-05 19:40:03 +08:00
1978 lines
50 KiB
C++
1978 lines
50 KiB
C++
/***********************************************************************
|
|
THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY
|
|
DEVELOPER: Zihan Chen(vczh)
|
|
***********************************************************************/
|
|
#include "VlppOS.h"
|
|
#include "Vlpp.h"
|
|
|
|
/***********************************************************************
|
|
.\FILESYSTEM.WINDOWS.CPP
|
|
***********************************************************************/
|
|
/***********************************************************************
|
|
Author: Zihan Chen (vczh)
|
|
Licensed under https://github.com/vczh-libraries/License
|
|
***********************************************************************/
|
|
|
|
#include <Windows.h>
|
|
#include <Shlwapi.h>
|
|
|
|
#ifndef VCZH_MSVC
|
|
static_assert(false, "Do not build this file for non-Windows applications.");
|
|
#endif
|
|
|
|
#pragma comment(lib, "Shlwapi.lib")
|
|
|
|
namespace vl
|
|
{
|
|
namespace filesystem
|
|
{
|
|
using namespace collections;
|
|
using namespace stream;
|
|
|
|
/***********************************************************************
|
|
FilePath
|
|
***********************************************************************/
|
|
|
|
void FilePath::Initialize()
|
|
{
|
|
{
|
|
Array<wchar_t> buffer(fullPath.Length() + 1);
|
|
wcscpy_s(&buffer[0], fullPath.Length() + 1, fullPath.Buffer());
|
|
NormalizeDelimiters(buffer);
|
|
fullPath = &buffer[0];
|
|
}
|
|
|
|
if (fullPath != L"")
|
|
{
|
|
if (fullPath.Length() < 2 || fullPath[1] != L':')
|
|
{
|
|
wchar_t buffer[MAX_PATH + 1] = { 0 };
|
|
auto result = GetCurrentDirectory(sizeof(buffer) / sizeof(*buffer), buffer);
|
|
if (result > MAX_PATH + 1 || result == 0)
|
|
{
|
|
throw ArgumentException(L"Failed to call GetCurrentDirectory.", L"vl::filesystem::FilePath::Initialize", L"");
|
|
}
|
|
fullPath = WString(buffer) + L"\\" + fullPath;
|
|
}
|
|
{
|
|
wchar_t buffer[MAX_PATH + 1] = { 0 };
|
|
if (fullPath.Length() == 2 && fullPath[1] == L':')
|
|
{
|
|
fullPath += L"\\";
|
|
}
|
|
auto result = GetFullPathName(fullPath.Buffer(), sizeof(buffer) / sizeof(*buffer), buffer, NULL);
|
|
if (result > MAX_PATH + 1 || result == 0)
|
|
{
|
|
throw ArgumentException(L"The path is illegal.", L"vl::filesystem::FilePath::FilePath", L"_filePath");
|
|
}
|
|
|
|
{
|
|
wchar_t shortPath[MAX_PATH + 1];
|
|
wchar_t longPath[MAX_PATH + 1];
|
|
if (GetShortPathName(buffer, shortPath, MAX_PATH) > 0)
|
|
{
|
|
if (GetLongPathName(shortPath, longPath, MAX_PATH) > 0)
|
|
{
|
|
memcpy(buffer, longPath, sizeof(buffer));
|
|
}
|
|
}
|
|
}
|
|
fullPath = buffer;
|
|
}
|
|
}
|
|
|
|
TrimLastDelimiter(fullPath);
|
|
}
|
|
|
|
bool FilePath::IsFile()const
|
|
{
|
|
WIN32_FILE_ATTRIBUTE_DATA info;
|
|
BOOL result = GetFileAttributesEx(fullPath.Buffer(), GetFileExInfoStandard, &info);
|
|
if (!result) return false;
|
|
return (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
|
}
|
|
|
|
bool FilePath::IsFolder()const
|
|
{
|
|
WIN32_FILE_ATTRIBUTE_DATA info;
|
|
BOOL result = GetFileAttributesEx(fullPath.Buffer(), GetFileExInfoStandard, &info);
|
|
if (!result) return false;
|
|
return (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
}
|
|
|
|
bool FilePath::IsRoot()const
|
|
{
|
|
return fullPath == L"";
|
|
}
|
|
|
|
WString FilePath::GetRelativePathFor(const FilePath& _filePath)
|
|
{
|
|
if (fullPath.Length() == 0 || _filePath.fullPath.Length() == 0 || fullPath[0] != _filePath.fullPath[0])
|
|
{
|
|
return _filePath.fullPath;
|
|
}
|
|
|
|
wchar_t buffer[MAX_PATH + 1] = { 0 };
|
|
PathRelativePathTo(
|
|
buffer,
|
|
fullPath.Buffer(),
|
|
(IsFolder() ? FILE_ATTRIBUTE_DIRECTORY : 0),
|
|
_filePath.fullPath.Buffer(),
|
|
(_filePath.IsFolder() ? FILE_ATTRIBUTE_DIRECTORY : 0)
|
|
);
|
|
return buffer;
|
|
}
|
|
|
|
/***********************************************************************
|
|
File
|
|
***********************************************************************/
|
|
|
|
bool File::Delete()const
|
|
{
|
|
return DeleteFile(filePath.GetFullPath().Buffer()) != 0;
|
|
}
|
|
|
|
bool File::Rename(const WString& newName)const
|
|
{
|
|
WString oldFileName = filePath.GetFullPath();
|
|
WString newFileName = (filePath.GetFolder() / newName).GetFullPath();
|
|
return MoveFile(oldFileName.Buffer(), newFileName.Buffer()) != 0;
|
|
}
|
|
|
|
/***********************************************************************
|
|
Folder
|
|
***********************************************************************/
|
|
|
|
bool Folder::GetFolders(collections::List<Folder>& folders)const
|
|
{
|
|
if (filePath.IsRoot())
|
|
{
|
|
auto bufferSize = GetLogicalDriveStrings(0, nullptr);
|
|
if (bufferSize > 0)
|
|
{
|
|
Array<wchar_t> buffer(bufferSize);
|
|
if (GetLogicalDriveStrings((DWORD)buffer.Count(), &buffer[0]) > 0)
|
|
{
|
|
auto begin = &buffer[0];
|
|
auto end = begin + buffer.Count();
|
|
while (begin < end && *begin)
|
|
{
|
|
WString driveString = begin;
|
|
begin += driveString.Length() + 1;
|
|
folders.Add(Folder(FilePath(driveString)));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!Exists()) return false;
|
|
WIN32_FIND_DATA findData;
|
|
HANDLE findHandle = INVALID_HANDLE_VALUE;
|
|
|
|
while (true)
|
|
{
|
|
if (findHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
WString searchPath = (filePath / L"*").GetFullPath();
|
|
findHandle = FindFirstFile(searchPath.Buffer(), &findData);
|
|
if (findHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BOOL result = FindNextFile(findHandle, &findData);
|
|
if (result == 0)
|
|
{
|
|
FindClose(findHandle);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
if (wcscmp(findData.cFileName, L".") != 0 && wcscmp(findData.cFileName, L"..") != 0)
|
|
{
|
|
folders.Add(Folder(filePath / findData.cFileName));
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool Folder::GetFiles(collections::List<File>& files)const
|
|
{
|
|
if (filePath.IsRoot())
|
|
{
|
|
return true;
|
|
}
|
|
if (!Exists()) return false;
|
|
WIN32_FIND_DATA findData;
|
|
HANDLE findHandle = INVALID_HANDLE_VALUE;
|
|
|
|
while (true)
|
|
{
|
|
if (findHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
WString searchPath = (filePath / L"*").GetFullPath();
|
|
findHandle = FindFirstFile(searchPath.Buffer(), &findData);
|
|
if (findHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BOOL result = FindNextFile(findHandle, &findData);
|
|
if (result == 0)
|
|
{
|
|
FindClose(findHandle);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
files.Add(File(filePath / findData.cFileName));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Folder::CreateNonRecursively()const
|
|
{
|
|
return CreateDirectory(filePath.GetFullPath().Buffer(), NULL) != 0;
|
|
}
|
|
|
|
bool Folder::DeleteNonRecursively()const
|
|
{
|
|
return RemoveDirectory(filePath.GetFullPath().Buffer()) != 0;
|
|
}
|
|
|
|
bool Folder::Rename(const WString& newName)const
|
|
{
|
|
WString oldFileName = filePath.GetFullPath();
|
|
WString newFileName = (filePath.GetFolder() / newName).GetFullPath();
|
|
return MoveFile(oldFileName.Buffer(), newFileName.Buffer()) != 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
.\HTTPUTILITY.WINDOWS.CPP
|
|
***********************************************************************/
|
|
/***********************************************************************
|
|
Author: Zihan Chen (vczh)
|
|
Licensed under https://github.com/vczh-libraries/License
|
|
***********************************************************************/
|
|
|
|
#include <winhttp.h>
|
|
|
|
#ifndef VCZH_MSVC
|
|
static_assert(false, "Do not build this file for non-Windows applications.");
|
|
#endif
|
|
|
|
#pragma comment(lib, "WinHttp.lib")
|
|
|
|
namespace vl
|
|
{
|
|
using namespace collections;
|
|
|
|
/***********************************************************************
|
|
HttpRequest
|
|
***********************************************************************/
|
|
|
|
bool HttpRequest::SetHost(const WString& inputQuery)
|
|
{
|
|
if (method == L"")
|
|
{
|
|
method = L"GET";
|
|
}
|
|
|
|
server = L"";
|
|
query = L"";
|
|
port = 0;
|
|
secure = false;
|
|
|
|
{
|
|
if (server == L"")
|
|
{
|
|
if (inputQuery.Length() > 7)
|
|
{
|
|
WString protocol = inputQuery.Sub(0, 8);
|
|
if (_wcsicmp(protocol.Buffer(), L"https://") == 0)
|
|
{
|
|
const wchar_t* reading = inputQuery.Buffer() + 8;
|
|
const wchar_t* index1 = wcschr(reading, L':');
|
|
const wchar_t* index2 = wcschr(reading, L'/');
|
|
if (index2)
|
|
{
|
|
query = index2;
|
|
server = WString::CopyFrom(reading, (index1 ? index1 : index2) - reading);
|
|
port = INTERNET_DEFAULT_HTTPS_PORT;
|
|
secure = true;
|
|
if (index1)
|
|
{
|
|
auto portString = WString::CopyFrom(index1 + 1, index2 - index1 - 1);
|
|
port = _wtoi(portString.Buffer());
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (server == L"")
|
|
{
|
|
if (inputQuery.Length() > 6)
|
|
{
|
|
WString protocol = inputQuery.Sub(0, 7);
|
|
if (_wcsicmp(protocol.Buffer(), L"http://") == 0)
|
|
{
|
|
const wchar_t* reading = inputQuery.Buffer() + 7;
|
|
const wchar_t* index1 = wcschr(reading, L':');
|
|
const wchar_t* index2 = wcschr(reading, L'/');
|
|
if (index2)
|
|
{
|
|
query = index2;
|
|
server = WString::CopyFrom(reading, (index1 ? index1 : index2) - reading);
|
|
port = INTERNET_DEFAULT_HTTP_PORT;
|
|
if (index1)
|
|
{
|
|
auto portString = WString::CopyFrom(index1 + 1, index2 - index1 - 1);
|
|
port = _wtoi(portString.Buffer());
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void HttpRequest::SetBodyUtf8(const WString& bodyString)
|
|
{
|
|
vint utf8Size = WideCharToMultiByte(CP_UTF8, 0, bodyString.Buffer(), (int)bodyString.Length(), NULL, 0, NULL, NULL);
|
|
char* utf8 = new char[utf8Size + 1];
|
|
ZeroMemory(utf8, utf8Size + 1);
|
|
WideCharToMultiByte(CP_UTF8, 0, bodyString.Buffer(), (int)bodyString.Length(), utf8, (int)utf8Size, NULL, NULL);
|
|
|
|
body.Resize(utf8Size);
|
|
memcpy(&body[0], utf8, utf8Size);
|
|
delete[] utf8;
|
|
}
|
|
|
|
/***********************************************************************
|
|
HttpResponse
|
|
***********************************************************************/
|
|
|
|
WString HttpResponse::GetBodyUtf8()
|
|
{
|
|
WString response;
|
|
char* utf8 = &body[0];
|
|
vint totalSize = body.Count();
|
|
vint utf16Size = MultiByteToWideChar(CP_UTF8, 0, utf8, (int)totalSize, NULL, 0);
|
|
wchar_t* utf16 = new wchar_t[utf16Size + 1];
|
|
ZeroMemory(utf16, (utf16Size + 1) * sizeof(wchar_t));
|
|
MultiByteToWideChar(CP_UTF8, 0, utf8, (int)totalSize, utf16, (int)utf16Size);
|
|
response = utf16;
|
|
delete[] utf16;
|
|
return response;
|
|
}
|
|
|
|
/***********************************************************************
|
|
Utilities
|
|
***********************************************************************/
|
|
|
|
struct BufferPair
|
|
{
|
|
char* buffer;
|
|
vint length;
|
|
|
|
BufferPair()
|
|
:buffer(0)
|
|
, length(0)
|
|
{
|
|
}
|
|
|
|
BufferPair(char* _buffer, vint _length)
|
|
:buffer(_buffer)
|
|
, length(_length)
|
|
{
|
|
}
|
|
};
|
|
|
|
bool HttpQuery(const HttpRequest& request, HttpResponse& response)
|
|
{
|
|
// initialize
|
|
response.statusCode = -1;
|
|
HINTERNET internet = NULL;
|
|
HINTERNET connectedInternet = NULL;
|
|
HINTERNET requestInternet = NULL;
|
|
BOOL httpResult = FALSE;
|
|
DWORD error = 0;
|
|
List<LPCWSTR> acceptTypes;
|
|
List<BufferPair> availableBuffers;
|
|
|
|
// access http
|
|
internet = WinHttpOpen(L"vczh", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
|
|
error = GetLastError();
|
|
if (!internet) goto CLEANUP;
|
|
|
|
// connect
|
|
connectedInternet = WinHttpConnect(internet, request.server.Buffer(), (int)request.port, 0);
|
|
error = GetLastError();
|
|
if (!connectedInternet) goto CLEANUP;
|
|
|
|
// open request
|
|
// TODO: (enumerable) Linq:Select
|
|
for (vint i = 0; i < request.acceptTypes.Count(); i++)
|
|
{
|
|
acceptTypes.Add(request.acceptTypes.Get(i).Buffer());
|
|
}
|
|
acceptTypes.Add(nullptr);
|
|
requestInternet = WinHttpOpenRequest(connectedInternet, request.method.Buffer(), request.query.Buffer(), NULL, WINHTTP_NO_REFERER, &acceptTypes[0], (request.secure ? WINHTTP_FLAG_SECURE : 0));
|
|
error = GetLastError();
|
|
if (!requestInternet) goto CLEANUP;
|
|
|
|
// authentication, cookie and request
|
|
if (request.username != L"" && request.password != L"")
|
|
{
|
|
WinHttpSetCredentials(requestInternet, WINHTTP_AUTH_TARGET_SERVER, WINHTTP_AUTH_SCHEME_BASIC, request.username.Buffer(), request.password.Buffer(), NULL);
|
|
}
|
|
if (request.contentType != L"")
|
|
{
|
|
httpResult = WinHttpAddRequestHeaders(requestInternet, (L"Content-type:" + request.contentType).Buffer(), -1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
|
|
}
|
|
if (request.cookie != L"")
|
|
{
|
|
WinHttpAddRequestHeaders(requestInternet, (L"Cookie:" + request.cookie).Buffer(), -1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
|
|
}
|
|
|
|
// extra headers
|
|
for (int i = 0; i < request.extraHeaders.Count(); i++)
|
|
{
|
|
WString key = request.extraHeaders.Keys()[i];
|
|
WString value = request.extraHeaders.Values().Get(i);
|
|
WinHttpAddRequestHeaders(requestInternet, (key + L":" + value).Buffer(), -1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
|
|
}
|
|
|
|
if (request.body.Count() > 0)
|
|
{
|
|
httpResult = WinHttpSendRequest(requestInternet, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)&request.body.Get(0), (int)request.body.Count(), (int)request.body.Count(), NULL);
|
|
}
|
|
else
|
|
{
|
|
httpResult = WinHttpSendRequest(requestInternet, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, NULL);
|
|
}
|
|
error = GetLastError();
|
|
if (httpResult == FALSE) goto CLEANUP;
|
|
|
|
// receive response
|
|
httpResult = WinHttpReceiveResponse(requestInternet, NULL);
|
|
error = GetLastError();
|
|
if (httpResult != TRUE) goto CLEANUP;
|
|
|
|
// read response status code
|
|
{
|
|
DWORD headerLength = sizeof(DWORD);
|
|
DWORD statusCode = 0;
|
|
httpResult = WinHttpQueryHeaders(requestInternet, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &headerLength, WINHTTP_NO_HEADER_INDEX);
|
|
error = GetLastError();
|
|
if (httpResult == FALSE) goto CLEANUP;
|
|
response.statusCode = statusCode;
|
|
}
|
|
// read respons cookie
|
|
{
|
|
DWORD headerLength = sizeof(DWORD);
|
|
httpResult = WinHttpQueryHeaders(requestInternet, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &headerLength, WINHTTP_NO_HEADER_INDEX);
|
|
error = GetLastError();
|
|
if (error == ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
wchar_t* rawHeader = new wchar_t[headerLength / sizeof(wchar_t)];
|
|
ZeroMemory(rawHeader, headerLength);
|
|
httpResult = WinHttpQueryHeaders(requestInternet, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, rawHeader, &headerLength, WINHTTP_NO_HEADER_INDEX);
|
|
|
|
const wchar_t* cookieStart = wcsstr(rawHeader, L"Cookie:");
|
|
if (cookieStart)
|
|
{
|
|
const wchar_t* cookieEnd = wcsstr(cookieStart, L";");
|
|
if (cookieEnd)
|
|
{
|
|
response.cookie = WString::CopyFrom(cookieStart + 7, cookieEnd - cookieStart - 7);
|
|
}
|
|
}
|
|
delete[] rawHeader;
|
|
}
|
|
}
|
|
|
|
// read response body
|
|
while (true)
|
|
{
|
|
DWORD bytesAvailable = 0;
|
|
BOOL queryDataAvailableResult = WinHttpQueryDataAvailable(requestInternet, &bytesAvailable);
|
|
error = GetLastError();
|
|
if (queryDataAvailableResult == TRUE && bytesAvailable != 0)
|
|
{
|
|
char* utf8 = new char[bytesAvailable];
|
|
DWORD bytesRead = 0;
|
|
BOOL readDataResult = WinHttpReadData(requestInternet, utf8, bytesAvailable, &bytesRead);
|
|
error = GetLastError();
|
|
if (readDataResult == TRUE)
|
|
{
|
|
availableBuffers.Add(BufferPair(utf8, bytesRead));
|
|
}
|
|
else
|
|
{
|
|
delete[] utf8;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
// concatincate response body
|
|
vint totalSize = 0;
|
|
for (auto p : availableBuffers)
|
|
{
|
|
totalSize += p.length;
|
|
}
|
|
response.body.Resize(totalSize);
|
|
if (totalSize > 0)
|
|
{
|
|
char* utf8 = new char[totalSize];
|
|
{
|
|
char* temp = utf8;
|
|
for (auto p : availableBuffers)
|
|
{
|
|
memcpy(temp, p.buffer, p.length);
|
|
temp += p.length;
|
|
}
|
|
}
|
|
memcpy(&response.body[0], utf8, totalSize);
|
|
delete[] utf8;
|
|
}
|
|
for (auto p : availableBuffers)
|
|
{
|
|
delete[] p.buffer;
|
|
}
|
|
}
|
|
CLEANUP:
|
|
if (requestInternet) WinHttpCloseHandle(requestInternet);
|
|
if (connectedInternet) WinHttpCloseHandle(connectedInternet);
|
|
if (internet) WinHttpCloseHandle(internet);
|
|
return response.statusCode != -1;
|
|
}
|
|
|
|
WString UrlEncodeQuery(const WString& query)
|
|
{
|
|
vint utf8Size = WideCharToMultiByte(CP_UTF8, 0, query.Buffer(), (int)query.Length(), NULL, 0, NULL, NULL);
|
|
char* utf8 = new char[utf8Size + 1];
|
|
ZeroMemory(utf8, utf8Size + 1);
|
|
WideCharToMultiByte(CP_UTF8, 0, query.Buffer(), (int)query.Length(), utf8, (int)utf8Size, NULL, NULL);
|
|
|
|
wchar_t* encoded = new wchar_t[utf8Size * 3 + 1];
|
|
ZeroMemory(encoded, (utf8Size * 3 + 1) * sizeof(wchar_t));
|
|
wchar_t* writing = encoded;
|
|
for (vint i = 0; i < utf8Size; i++)
|
|
{
|
|
unsigned char x = (unsigned char)utf8[i];
|
|
if (L'a' <= x && x <= 'z' || L'A' <= x && x <= L'Z' || L'0' <= x && x <= L'9')
|
|
{
|
|
writing[0] = x;
|
|
writing += 1;
|
|
}
|
|
else
|
|
{
|
|
writing[0] = L'%';
|
|
writing[1] = L"0123456789ABCDEF"[x / 16];
|
|
writing[2] = L"0123456789ABCDEF"[x % 16];
|
|
writing += 3;
|
|
}
|
|
}
|
|
|
|
WString result = encoded;
|
|
delete[] encoded;
|
|
delete[] utf8;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
.\LOCALE.WINDOWS.CPP
|
|
***********************************************************************/
|
|
/***********************************************************************
|
|
Author: Zihan Chen (vczh)
|
|
Licensed under https://github.com/vczh-libraries/License
|
|
***********************************************************************/
|
|
|
|
|
|
#ifndef VCZH_MSVC
|
|
static_assert(false, "Do not build this file for non-Windows applications.");
|
|
#endif
|
|
|
|
namespace vl
|
|
{
|
|
using namespace collections;
|
|
|
|
extern SYSTEMTIME DateTimeToSystemTime(const DateTime& dateTime);
|
|
|
|
BOOL CALLBACK Locale_EnumLocalesProcEx(
|
|
_In_ LPWSTR lpLocaleString,
|
|
_In_ DWORD dwFlags,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
((List<Locale>*)lParam)->Add(Locale(lpLocaleString));
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CALLBACK Locale_EnumDateFormatsProcExEx(
|
|
_In_ LPWSTR lpDateFormatString,
|
|
_In_ CALID CalendarID,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
((List<WString>*)lParam)->Add(lpDateFormatString);
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CALLBACK EnumTimeFormatsProcEx(
|
|
_In_ LPWSTR lpTimeFormatString,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
((List<WString>*)lParam)->Add(lpTimeFormatString);
|
|
return TRUE;
|
|
}
|
|
|
|
WString Transform(const WString& localeName, const WString& input, DWORD flag)
|
|
{
|
|
int length = LCMapStringEx(localeName.Buffer(), flag, input.Buffer(), (int)input.Length() + 1, NULL, 0, NULL, NULL, NULL);
|
|
Array<wchar_t> buffer(length);
|
|
LCMapStringEx(localeName.Buffer(), flag, input.Buffer(), (int)input.Length() + 1, &buffer[0], (int)buffer.Count(), NULL, NULL, NULL);
|
|
return &buffer[0];
|
|
}
|
|
|
|
DWORD TranslateNormalization(Locale::Normalization normalization)
|
|
{
|
|
DWORD result = 0;
|
|
if (normalization & Locale::IgnoreCase) result |= NORM_IGNORECASE;
|
|
if (normalization & Locale::IgnoreCaseLinguistic) result |= NORM_IGNORECASE | NORM_LINGUISTIC_CASING;
|
|
if (normalization & Locale::IgnoreKanaType) result |= NORM_IGNOREKANATYPE;
|
|
if (normalization & Locale::IgnoreNonSpace) result |= NORM_IGNORENONSPACE;
|
|
if (normalization & Locale::IgnoreSymbol) result |= NORM_IGNORESYMBOLS;
|
|
if (normalization & Locale::IgnoreWidth) result |= NORM_IGNOREWIDTH;
|
|
if (normalization & Locale::DigitsAsNumbers) result |= SORT_DIGITSASNUMBERS;
|
|
if (normalization & Locale::StringSoft) result |= SORT_STRINGSORT;
|
|
return result;
|
|
}
|
|
|
|
/***********************************************************************
|
|
Locale
|
|
***********************************************************************/
|
|
|
|
Locale Locale::Invariant()
|
|
{
|
|
return Locale(LOCALE_NAME_INVARIANT);
|
|
}
|
|
|
|
Locale Locale::SystemDefault()
|
|
{
|
|
wchar_t buffer[LOCALE_NAME_MAX_LENGTH + 1] = { 0 };
|
|
GetSystemDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH);
|
|
return Locale(buffer);
|
|
}
|
|
|
|
Locale Locale::UserDefault()
|
|
{
|
|
wchar_t buffer[LOCALE_NAME_MAX_LENGTH + 1] = { 0 };
|
|
GetUserDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH);
|
|
return Locale(buffer);
|
|
}
|
|
|
|
void Locale::Enumerate(collections::List<Locale>& locales)
|
|
{
|
|
EnumSystemLocalesEx(&Locale_EnumLocalesProcEx, LOCALE_ALL, (LPARAM)&locales, NULL);
|
|
}
|
|
|
|
void Locale::GetShortDateFormats(collections::List<WString>& formats)const
|
|
{
|
|
EnumDateFormatsExEx(&Locale_EnumDateFormatsProcExEx, localeName.Buffer(), DATE_SHORTDATE, (LPARAM)&formats);
|
|
}
|
|
|
|
void Locale::GetLongDateFormats(collections::List<WString>& formats)const
|
|
{
|
|
EnumDateFormatsExEx(&Locale_EnumDateFormatsProcExEx, localeName.Buffer(), DATE_LONGDATE, (LPARAM)&formats);
|
|
}
|
|
|
|
void Locale::GetYearMonthDateFormats(collections::List<WString>& formats)const
|
|
{
|
|
EnumDateFormatsExEx(&Locale_EnumDateFormatsProcExEx, localeName.Buffer(), DATE_YEARMONTH, (LPARAM)&formats);
|
|
}
|
|
|
|
void Locale::GetLongTimeFormats(collections::List<WString>& formats)const
|
|
{
|
|
EnumTimeFormatsEx(&EnumTimeFormatsProcEx, localeName.Buffer(), 0, (LPARAM)&formats);
|
|
}
|
|
|
|
void Locale::GetShortTimeFormats(collections::List<WString>& formats)const
|
|
{
|
|
EnumTimeFormatsEx(&EnumTimeFormatsProcEx, localeName.Buffer(), TIME_NOSECONDS, (LPARAM)&formats);
|
|
}
|
|
|
|
WString Locale::FormatDate(const WString& format, DateTime date)const
|
|
{
|
|
SYSTEMTIME st = DateTimeToSystemTime(date);
|
|
int length = GetDateFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), NULL, 0, NULL);
|
|
if (length == 0) return L"";
|
|
Array<wchar_t> buffer(length);
|
|
GetDateFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), &buffer[0], (int)buffer.Count(), NULL);
|
|
return &buffer[0];
|
|
}
|
|
|
|
WString Locale::FormatTime(const WString& format, DateTime time)const
|
|
{
|
|
SYSTEMTIME st = DateTimeToSystemTime(time);
|
|
int length = GetTimeFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), NULL, 0);
|
|
if (length == 0) return L"";
|
|
Array<wchar_t> buffer(length);
|
|
GetTimeFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), &buffer[0], (int)buffer.Count());
|
|
return &buffer[0];
|
|
}
|
|
|
|
WString Locale::FormatNumber(const WString& number)const
|
|
{
|
|
int length = GetNumberFormatEx(localeName.Buffer(), 0, number.Buffer(), NULL, NULL, 0);
|
|
if (length == 0) return L"";
|
|
Array<wchar_t> buffer(length);
|
|
GetNumberFormatEx(localeName.Buffer(), 0, number.Buffer(), NULL, &buffer[0], (int)buffer.Count());
|
|
return &buffer[0];
|
|
}
|
|
|
|
WString Locale::FormatCurrency(const WString& currency)const
|
|
{
|
|
int length = GetCurrencyFormatEx(localeName.Buffer(), 0, currency.Buffer(), NULL, NULL, 0);
|
|
if (length == 0) return L"";
|
|
Array<wchar_t> buffer(length);
|
|
GetCurrencyFormatEx(localeName.Buffer(), 0, currency.Buffer(), NULL, &buffer[0], (int)buffer.Count());
|
|
return &buffer[0];
|
|
}
|
|
|
|
WString Locale::GetShortDayOfWeekName(vint dayOfWeek)const
|
|
{
|
|
return FormatDate(L"ddd", DateTime::FromDateTime(2000, 1, 2 + dayOfWeek));
|
|
}
|
|
|
|
WString Locale::GetLongDayOfWeekName(vint dayOfWeek)const
|
|
{
|
|
return FormatDate(L"dddd", DateTime::FromDateTime(2000, 1, 2 + dayOfWeek));
|
|
}
|
|
|
|
WString Locale::GetShortMonthName(vint month)const
|
|
{
|
|
return FormatDate(L"MMM", DateTime::FromDateTime(2000, month, 1));
|
|
}
|
|
|
|
WString Locale::GetLongMonthName(vint month)const
|
|
{
|
|
return FormatDate(L"MMMM", DateTime::FromDateTime(2000, month, 1));
|
|
}
|
|
|
|
WString Locale::ToFullWidth(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_FULLWIDTH);
|
|
}
|
|
|
|
WString Locale::ToHalfWidth(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_HALFWIDTH);
|
|
}
|
|
|
|
WString Locale::ToHiragana(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_HIRAGANA);
|
|
}
|
|
|
|
WString Locale::ToKatagana(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_KATAKANA);
|
|
}
|
|
|
|
WString Locale::ToLower(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_LOWERCASE);
|
|
}
|
|
|
|
WString Locale::ToUpper(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_UPPERCASE);
|
|
}
|
|
|
|
WString Locale::ToLinguisticLower(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING);
|
|
}
|
|
|
|
WString Locale::ToLinguisticUpper(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING);
|
|
}
|
|
|
|
WString Locale::ToSimplifiedChinese(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_SIMPLIFIED_CHINESE);
|
|
}
|
|
|
|
WString Locale::ToTraditionalChinese(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_TRADITIONAL_CHINESE);
|
|
}
|
|
|
|
WString Locale::ToTileCase(const WString& str)const
|
|
{
|
|
return Transform(localeName, str, LCMAP_TITLECASE);
|
|
}
|
|
|
|
vint Locale::Compare(const WString& s1, const WString& s2, Normalization normalization)const
|
|
{
|
|
switch (CompareStringEx(localeName.Buffer(), TranslateNormalization(normalization), s1.Buffer(), (int)s1.Length(), s2.Buffer(), (int)s2.Length(), NULL, NULL, NULL))
|
|
{
|
|
case CSTR_LESS_THAN: return -1;
|
|
case CSTR_GREATER_THAN: return 1;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
vint Locale::CompareOrdinal(const WString& s1, const WString& s2)const
|
|
{
|
|
switch (CompareStringOrdinal(s1.Buffer(), (int)s1.Length(), s2.Buffer(), (int)s2.Length(), FALSE))
|
|
{
|
|
case CSTR_LESS_THAN: return -1;
|
|
case CSTR_GREATER_THAN: return 1;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
vint Locale::CompareOrdinalIgnoreCase(const WString& s1, const WString& s2)const
|
|
{
|
|
switch (CompareStringOrdinal(s1.Buffer(), (int)s1.Length(), s2.Buffer(), (int)s2.Length(), TRUE))
|
|
{
|
|
case CSTR_LESS_THAN: return -1;
|
|
case CSTR_GREATER_THAN: return 1;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
collections::Pair<vint, vint> Locale::FindFirst(const WString& text, const WString& find, Normalization normalization)const
|
|
{
|
|
int length = 0;
|
|
int result = FindNLSStringEx(localeName.Buffer(), FIND_FROMSTART | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), &length, NULL, NULL, NULL);
|
|
return result == -1 ? Pair<vint, vint>(-1, 0) : Pair<vint, vint>(result, length);
|
|
}
|
|
|
|
collections::Pair<vint, vint> Locale::FindLast(const WString& text, const WString& find, Normalization normalization)const
|
|
{
|
|
int length = 0;
|
|
int result = FindNLSStringEx(localeName.Buffer(), FIND_FROMEND | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), &length, NULL, NULL, NULL);
|
|
return result == -1 ? Pair<vint, vint>(-1, 0) : Pair<vint, vint>(result, length);
|
|
}
|
|
|
|
bool Locale::StartsWith(const WString& text, const WString& find, Normalization normalization)const
|
|
{
|
|
int result = FindNLSStringEx(localeName.Buffer(), FIND_STARTSWITH | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), NULL, NULL, NULL, NULL);
|
|
return result != -1;
|
|
}
|
|
|
|
bool Locale::EndsWith(const WString& text, const WString& find, Normalization normalization)const
|
|
{
|
|
int result = FindNLSStringEx(localeName.Buffer(), FIND_ENDSWITH | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), NULL, NULL, NULL, NULL);
|
|
return result != -1;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
.\THREADING.WINDOWS.CPP
|
|
***********************************************************************/
|
|
/***********************************************************************
|
|
Author: Zihan Chen (vczh)
|
|
Licensed under https://github.com/vczh-libraries/License
|
|
***********************************************************************/
|
|
|
|
|
|
#ifndef VCZH_MSVC
|
|
static_assert(false, "Do not build this file for non-Windows applications.");
|
|
#endif
|
|
|
|
namespace vl
|
|
{
|
|
using namespace threading_internal;
|
|
using namespace collections;
|
|
|
|
/***********************************************************************
|
|
WaitableObject
|
|
***********************************************************************/
|
|
|
|
namespace threading_internal
|
|
{
|
|
struct WaitableData
|
|
{
|
|
HANDLE handle;
|
|
|
|
WaitableData(HANDLE _handle)
|
|
:handle(_handle)
|
|
{
|
|
}
|
|
};
|
|
}
|
|
|
|
WaitableObject::WaitableObject()
|
|
:waitableData(0)
|
|
{
|
|
}
|
|
|
|
void WaitableObject::SetData(threading_internal::WaitableData* data)
|
|
{
|
|
waitableData=data;
|
|
}
|
|
|
|
bool WaitableObject::IsCreated()
|
|
{
|
|
return waitableData!=0;
|
|
}
|
|
|
|
bool WaitableObject::Wait()
|
|
{
|
|
return WaitForTime(INFINITE);
|
|
}
|
|
|
|
bool WaitableObject::WaitForTime(vint ms)
|
|
{
|
|
if(IsCreated())
|
|
{
|
|
if(WaitForSingleObject(waitableData->handle, (DWORD)ms)==WAIT_OBJECT_0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool WaitableObject::WaitAll(WaitableObject** objects, vint count)
|
|
{
|
|
Array<HANDLE> handles(count);
|
|
for(vint i=0;i<count;i++)
|
|
{
|
|
handles[i]=objects[i]->waitableData->handle;
|
|
}
|
|
DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], TRUE, INFINITE);
|
|
return result==WAIT_OBJECT_0 || result==WAIT_ABANDONED_0;
|
|
|
|
}
|
|
|
|
bool WaitableObject::WaitAllForTime(WaitableObject** objects, vint count, vint ms)
|
|
{
|
|
Array<HANDLE> handles(count);
|
|
for(vint i=0;i<count;i++)
|
|
{
|
|
handles[i]=objects[i]->waitableData->handle;
|
|
}
|
|
DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], TRUE, (DWORD)ms);
|
|
return result==WAIT_OBJECT_0 || result==WAIT_ABANDONED_0;
|
|
}
|
|
|
|
vint WaitableObject::WaitAny(WaitableObject** objects, vint count, bool* abandoned)
|
|
{
|
|
Array<HANDLE> handles(count);
|
|
for(vint i=0;i<count;i++)
|
|
{
|
|
handles[i]=objects[i]->waitableData->handle;
|
|
}
|
|
DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], FALSE, INFINITE);
|
|
if(WAIT_OBJECT_0 <= result && result<WAIT_OBJECT_0+count)
|
|
{
|
|
*abandoned=false;
|
|
return result-WAIT_OBJECT_0;
|
|
}
|
|
else if(WAIT_ABANDONED_0 <= result && result<WAIT_ABANDONED_0+count)
|
|
{
|
|
*abandoned=true;
|
|
return result-WAIT_ABANDONED_0;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
vint WaitableObject::WaitAnyForTime(WaitableObject** objects, vint count, vint ms, bool* abandoned)
|
|
{
|
|
Array<HANDLE> handles(count);
|
|
for(vint i=0;i<count;i++)
|
|
{
|
|
handles[i]=objects[i]->waitableData->handle;
|
|
}
|
|
DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], FALSE, (DWORD)ms);
|
|
if(WAIT_OBJECT_0 <= result && result<WAIT_OBJECT_0+count)
|
|
{
|
|
*abandoned=false;
|
|
return result-WAIT_OBJECT_0;
|
|
}
|
|
else if(WAIT_ABANDONED_0 <= result && result<WAIT_ABANDONED_0+count)
|
|
{
|
|
*abandoned=true;
|
|
return result-WAIT_ABANDONED_0;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
Thread
|
|
***********************************************************************/
|
|
|
|
namespace threading_internal
|
|
{
|
|
struct ThreadData : public WaitableData
|
|
{
|
|
DWORD id;
|
|
|
|
ThreadData()
|
|
:WaitableData(NULL)
|
|
{
|
|
id=-1;
|
|
}
|
|
};
|
|
|
|
class ProceduredThread : public Thread
|
|
{
|
|
private:
|
|
Thread::ThreadProcedure procedure;
|
|
void* argument;
|
|
bool deleteAfterStopped;
|
|
|
|
protected:
|
|
void Run()
|
|
{
|
|
bool deleteAfterStopped = this->deleteAfterStopped;
|
|
ThreadLocalStorage::FixStorages();
|
|
try
|
|
{
|
|
procedure(this, argument);
|
|
threadState=Thread::Stopped;
|
|
ThreadLocalStorage::ClearStorages();
|
|
}
|
|
catch (...)
|
|
{
|
|
ThreadLocalStorage::ClearStorages();
|
|
throw;
|
|
}
|
|
if(deleteAfterStopped)
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
public:
|
|
ProceduredThread(Thread::ThreadProcedure _procedure, void* _argument, bool _deleteAfterStopped)
|
|
:procedure(_procedure)
|
|
,argument(_argument)
|
|
,deleteAfterStopped(_deleteAfterStopped)
|
|
{
|
|
}
|
|
};
|
|
|
|
class LambdaThread : public Thread
|
|
{
|
|
private:
|
|
Func<void()> procedure;
|
|
bool deleteAfterStopped;
|
|
|
|
protected:
|
|
void Run()
|
|
{
|
|
bool deleteAfterStopped = this->deleteAfterStopped;
|
|
ThreadLocalStorage::FixStorages();
|
|
try
|
|
{
|
|
procedure();
|
|
threadState=Thread::Stopped;
|
|
ThreadLocalStorage::ClearStorages();
|
|
}
|
|
catch (...)
|
|
{
|
|
ThreadLocalStorage::ClearStorages();
|
|
throw;
|
|
}
|
|
if(deleteAfterStopped)
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
public:
|
|
LambdaThread(const Func<void()>& _procedure, bool _deleteAfterStopped)
|
|
:procedure(_procedure)
|
|
,deleteAfterStopped(_deleteAfterStopped)
|
|
{
|
|
}
|
|
};
|
|
}
|
|
|
|
void InternalThreadProc(Thread* thread)
|
|
{
|
|
thread->Run();
|
|
}
|
|
|
|
DWORD WINAPI InternalThreadProcWrapper(LPVOID lpParameter)
|
|
{
|
|
InternalThreadProc((Thread*)lpParameter);
|
|
return 0;
|
|
}
|
|
|
|
Thread::Thread()
|
|
{
|
|
internalData=new ThreadData;
|
|
internalData->handle=CreateThread(NULL, 0, InternalThreadProcWrapper, this, CREATE_SUSPENDED, &internalData->id);
|
|
threadState=Thread::NotStarted;
|
|
SetData(internalData);
|
|
}
|
|
|
|
Thread::~Thread()
|
|
{
|
|
if (internalData)
|
|
{
|
|
Stop();
|
|
CloseHandle(internalData->handle);
|
|
delete internalData;
|
|
}
|
|
}
|
|
|
|
Thread* Thread::CreateAndStart(ThreadProcedure procedure, void* argument, bool deleteAfterStopped)
|
|
{
|
|
if(procedure)
|
|
{
|
|
Thread* thread=new ProceduredThread(procedure, argument, deleteAfterStopped);
|
|
if(thread->Start())
|
|
{
|
|
return thread;
|
|
}
|
|
else
|
|
{
|
|
delete thread;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Thread* Thread::CreateAndStart(const Func<void()>& procedure, bool deleteAfterStopped)
|
|
{
|
|
Thread* thread=new LambdaThread(procedure, deleteAfterStopped);
|
|
if(thread->Start())
|
|
{
|
|
return thread;
|
|
}
|
|
else
|
|
{
|
|
delete thread;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Thread::Sleep(vint ms)
|
|
{
|
|
::Sleep((DWORD)ms);
|
|
}
|
|
|
|
|
|
vint Thread::GetCPUCount()
|
|
{
|
|
SYSTEM_INFO info;
|
|
GetSystemInfo(&info);
|
|
return info.dwNumberOfProcessors;
|
|
}
|
|
|
|
vint Thread::GetCurrentThreadId()
|
|
{
|
|
return (vint)::GetCurrentThreadId();
|
|
}
|
|
|
|
bool Thread::Start()
|
|
{
|
|
if(threadState==Thread::NotStarted && internalData->handle!=NULL)
|
|
{
|
|
if(ResumeThread(internalData->handle)!=-1)
|
|
{
|
|
threadState=Thread::Running;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Thread::Stop()
|
|
{
|
|
if(internalData->handle!=NULL)
|
|
{
|
|
if (SuspendThread(internalData->handle) != -1)
|
|
{
|
|
threadState=Thread::Stopped;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Thread::ThreadState Thread::GetState()
|
|
{
|
|
return threadState;
|
|
}
|
|
|
|
void Thread::SetCPU(vint index)
|
|
{
|
|
SetThreadAffinityMask(internalData->handle, ((vint)1 << index));
|
|
}
|
|
|
|
/***********************************************************************
|
|
Mutex
|
|
***********************************************************************/
|
|
|
|
namespace threading_internal
|
|
{
|
|
struct MutexData : public WaitableData
|
|
{
|
|
MutexData(HANDLE _handle)
|
|
:WaitableData(_handle)
|
|
{
|
|
}
|
|
};
|
|
}
|
|
|
|
Mutex::Mutex()
|
|
:internalData(0)
|
|
{
|
|
}
|
|
|
|
Mutex::~Mutex()
|
|
{
|
|
if(internalData)
|
|
{
|
|
CloseHandle(internalData->handle);
|
|
delete internalData;
|
|
}
|
|
}
|
|
|
|
bool Mutex::Create(bool owned, const WString& name)
|
|
{
|
|
if(IsCreated())return false;
|
|
BOOL aOwned=owned?TRUE:FALSE;
|
|
LPCTSTR aName=name==L""?NULL:name.Buffer();
|
|
HANDLE handle=CreateMutex(NULL, aOwned, aName);
|
|
if(handle)
|
|
{
|
|
internalData=new MutexData(handle);
|
|
SetData(internalData);
|
|
}
|
|
return IsCreated();
|
|
}
|
|
|
|
bool Mutex::Open(bool inheritable, const WString& name)
|
|
{
|
|
if(IsCreated())return false;
|
|
BOOL aInteritable=inheritable?TRUE:FALSE;
|
|
HANDLE handle=OpenMutex(SYNCHRONIZE, aInteritable, name.Buffer());
|
|
if(handle)
|
|
{
|
|
internalData=new MutexData(handle);
|
|
SetData(internalData);
|
|
}
|
|
return IsCreated();
|
|
}
|
|
|
|
bool Mutex::Release()
|
|
{
|
|
if(IsCreated())
|
|
{
|
|
return ReleaseMutex(internalData->handle)!=0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/***********************************************************************
|
|
Semaphore
|
|
***********************************************************************/
|
|
|
|
namespace threading_internal
|
|
{
|
|
struct SemaphoreData : public WaitableData
|
|
{
|
|
SemaphoreData(HANDLE _handle)
|
|
:WaitableData(_handle)
|
|
{
|
|
}
|
|
};
|
|
}
|
|
|
|
Semaphore::Semaphore()
|
|
:internalData(0)
|
|
{
|
|
}
|
|
|
|
Semaphore::~Semaphore()
|
|
{
|
|
if(internalData)
|
|
{
|
|
CloseHandle(internalData->handle);
|
|
delete internalData;
|
|
}
|
|
}
|
|
|
|
bool Semaphore::Create(vint initialCount, vint maxCount, const WString& name)
|
|
{
|
|
if(IsCreated())return false;
|
|
LONG aInitial=(LONG)initialCount;
|
|
LONG aMax=(LONG)maxCount;
|
|
LPCTSTR aName=name==L""?NULL:name.Buffer();
|
|
HANDLE handle=CreateSemaphore(NULL, aInitial, aMax, aName);
|
|
if(handle)
|
|
{
|
|
internalData=new SemaphoreData(handle);
|
|
SetData(internalData);
|
|
}
|
|
return IsCreated();
|
|
}
|
|
|
|
bool Semaphore::Open(bool inheritable, const WString& name)
|
|
{
|
|
if(IsCreated())return false;
|
|
BOOL aInteritable=inheritable?TRUE:FALSE;
|
|
HANDLE handle=OpenSemaphore(SYNCHRONIZE, aInteritable, name.Buffer());
|
|
if(handle)
|
|
{
|
|
internalData=new SemaphoreData(handle);
|
|
SetData(internalData);
|
|
}
|
|
return IsCreated();
|
|
}
|
|
|
|
bool Semaphore::Release()
|
|
{
|
|
if(IsCreated())
|
|
{
|
|
return Release(1)!=-1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
vint Semaphore::Release(vint count)
|
|
{
|
|
if(IsCreated())
|
|
{
|
|
LONG previous=-1;
|
|
if(ReleaseSemaphore(internalData->handle, (LONG)count, &previous)!=0)
|
|
{
|
|
return (vint)previous;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/***********************************************************************
|
|
EventObject
|
|
***********************************************************************/
|
|
|
|
namespace threading_internal
|
|
{
|
|
struct EventData : public WaitableData
|
|
{
|
|
EventData(HANDLE _handle)
|
|
:WaitableData(_handle)
|
|
{
|
|
}
|
|
};
|
|
}
|
|
|
|
EventObject::EventObject()
|
|
:internalData(0)
|
|
{
|
|
}
|
|
|
|
EventObject::~EventObject()
|
|
{
|
|
if(internalData)
|
|
{
|
|
CloseHandle(internalData->handle);
|
|
delete internalData;
|
|
}
|
|
}
|
|
|
|
bool EventObject::CreateAutoUnsignal(bool signaled, const WString& name)
|
|
{
|
|
if(IsCreated())return false;
|
|
BOOL aSignaled=signaled?TRUE:FALSE;
|
|
LPCTSTR aName=name==L""?NULL:name.Buffer();
|
|
HANDLE handle=CreateEvent(NULL, FALSE, aSignaled, aName);
|
|
if(handle)
|
|
{
|
|
internalData=new EventData(handle);
|
|
SetData(internalData);
|
|
}
|
|
return IsCreated();
|
|
}
|
|
|
|
bool EventObject::CreateManualUnsignal(bool signaled, const WString& name)
|
|
{
|
|
if(IsCreated())return false;
|
|
BOOL aSignaled=signaled?TRUE:FALSE;
|
|
LPCTSTR aName=name==L""?NULL:name.Buffer();
|
|
HANDLE handle=CreateEvent(NULL, TRUE, aSignaled, aName);
|
|
if(handle)
|
|
{
|
|
internalData=new EventData(handle);
|
|
SetData(internalData);
|
|
}
|
|
return IsCreated();
|
|
}
|
|
|
|
bool EventObject::Open(bool inheritable, const WString& name)
|
|
{
|
|
if(IsCreated())return false;
|
|
BOOL aInteritable=inheritable?TRUE:FALSE;
|
|
HANDLE handle=OpenEvent(SYNCHRONIZE, aInteritable, name.Buffer());
|
|
if(handle)
|
|
{
|
|
internalData=new EventData(handle);
|
|
SetData(internalData);
|
|
}
|
|
return IsCreated();
|
|
}
|
|
|
|
bool EventObject::Signal()
|
|
{
|
|
if(IsCreated())
|
|
{
|
|
return SetEvent(internalData->handle)!=0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool EventObject::Unsignal()
|
|
{
|
|
if(IsCreated())
|
|
{
|
|
return ResetEvent(internalData->handle)!=0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/***********************************************************************
|
|
ThreadPoolLite
|
|
***********************************************************************/
|
|
|
|
DWORD WINAPI ThreadPoolQueueFunc(void* argument)
|
|
{
|
|
auto proc=Ptr((Func<void()>*)argument);
|
|
ThreadLocalStorage::FixStorages();
|
|
try
|
|
{
|
|
(*proc.Obj())();
|
|
ThreadLocalStorage::ClearStorages();
|
|
}
|
|
catch (...)
|
|
{
|
|
ThreadLocalStorage::ClearStorages();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ThreadPoolLite::ThreadPoolLite()
|
|
{
|
|
}
|
|
|
|
ThreadPoolLite::~ThreadPoolLite()
|
|
{
|
|
}
|
|
|
|
bool ThreadPoolLite::Queue(void(*proc)(void*), void* argument)
|
|
{
|
|
return Queue([=]() {proc(argument); });
|
|
}
|
|
|
|
bool ThreadPoolLite::Queue(const Func<void()>& proc)
|
|
{
|
|
Func<void()>* p=new Func<void()>(proc);
|
|
if(QueueUserWorkItem(&ThreadPoolQueueFunc, p, WT_EXECUTEDEFAULT))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
delete p;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
CriticalSection
|
|
***********************************************************************/
|
|
|
|
namespace threading_internal
|
|
{
|
|
struct CriticalSectionData
|
|
{
|
|
CRITICAL_SECTION criticalSection;
|
|
};
|
|
}
|
|
|
|
CriticalSection::Scope::Scope(CriticalSection& _criticalSection)
|
|
:criticalSection(&_criticalSection)
|
|
{
|
|
criticalSection->Enter();
|
|
}
|
|
|
|
CriticalSection::Scope::~Scope()
|
|
{
|
|
criticalSection->Leave();
|
|
}
|
|
|
|
CriticalSection::CriticalSection()
|
|
{
|
|
internalData=new CriticalSectionData;
|
|
InitializeCriticalSection(&internalData->criticalSection);
|
|
}
|
|
|
|
CriticalSection::~CriticalSection()
|
|
{
|
|
DeleteCriticalSection(&internalData->criticalSection);
|
|
delete internalData;
|
|
}
|
|
|
|
bool CriticalSection::TryEnter()
|
|
{
|
|
return TryEnterCriticalSection(&internalData->criticalSection)!=0;
|
|
}
|
|
|
|
void CriticalSection::Enter()
|
|
{
|
|
EnterCriticalSection(&internalData->criticalSection);
|
|
}
|
|
|
|
void CriticalSection::Leave()
|
|
{
|
|
LeaveCriticalSection(&internalData->criticalSection);
|
|
}
|
|
|
|
/***********************************************************************
|
|
ReaderWriterLock
|
|
***********************************************************************/
|
|
|
|
namespace threading_internal
|
|
{
|
|
struct ReaderWriterLockData
|
|
{
|
|
SRWLOCK lock;
|
|
};
|
|
}
|
|
|
|
ReaderWriterLock::ReaderScope::ReaderScope(ReaderWriterLock& _lock)
|
|
:lock(&_lock)
|
|
{
|
|
lock->EnterReader();
|
|
}
|
|
|
|
ReaderWriterLock::ReaderScope::~ReaderScope()
|
|
{
|
|
lock->LeaveReader();
|
|
}
|
|
|
|
ReaderWriterLock::WriterScope::WriterScope(ReaderWriterLock& _lock)
|
|
:lock(&_lock)
|
|
{
|
|
lock->EnterWriter();
|
|
}
|
|
|
|
ReaderWriterLock::WriterScope::~WriterScope()
|
|
{
|
|
lock->LeaveWriter();
|
|
}
|
|
|
|
ReaderWriterLock::ReaderWriterLock()
|
|
:internalData(new threading_internal::ReaderWriterLockData)
|
|
{
|
|
InitializeSRWLock(&internalData->lock);
|
|
}
|
|
|
|
ReaderWriterLock::~ReaderWriterLock()
|
|
{
|
|
delete internalData;
|
|
}
|
|
|
|
bool ReaderWriterLock::TryEnterReader()
|
|
{
|
|
return TryAcquireSRWLockShared(&internalData->lock)!=0;
|
|
}
|
|
|
|
void ReaderWriterLock::EnterReader()
|
|
{
|
|
AcquireSRWLockShared(&internalData->lock);
|
|
}
|
|
|
|
void ReaderWriterLock::LeaveReader()
|
|
{
|
|
ReleaseSRWLockShared(&internalData->lock);
|
|
}
|
|
|
|
bool ReaderWriterLock::TryEnterWriter()
|
|
{
|
|
return TryAcquireSRWLockExclusive(&internalData->lock)!=0;
|
|
}
|
|
|
|
void ReaderWriterLock::EnterWriter()
|
|
{
|
|
AcquireSRWLockExclusive(&internalData->lock);
|
|
}
|
|
|
|
void ReaderWriterLock::LeaveWriter()
|
|
{
|
|
ReleaseSRWLockExclusive(&internalData->lock);
|
|
}
|
|
|
|
/***********************************************************************
|
|
ConditionVariable
|
|
***********************************************************************/
|
|
|
|
namespace threading_internal
|
|
{
|
|
struct ConditionVariableData
|
|
{
|
|
CONDITION_VARIABLE variable;
|
|
};
|
|
}
|
|
|
|
ConditionVariable::ConditionVariable()
|
|
:internalData(new threading_internal::ConditionVariableData)
|
|
{
|
|
InitializeConditionVariable(&internalData->variable);
|
|
}
|
|
|
|
ConditionVariable::~ConditionVariable()
|
|
{
|
|
delete internalData;
|
|
}
|
|
|
|
bool ConditionVariable::SleepWith(CriticalSection& cs)
|
|
{
|
|
return SleepConditionVariableCS(&internalData->variable, &cs.internalData->criticalSection, INFINITE)!=0;
|
|
}
|
|
|
|
bool ConditionVariable::SleepWithForTime(CriticalSection& cs, vint ms)
|
|
{
|
|
return SleepConditionVariableCS(&internalData->variable, &cs.internalData->criticalSection, (DWORD)ms)!=0;
|
|
}
|
|
|
|
bool ConditionVariable::SleepWithReader(ReaderWriterLock& lock)
|
|
{
|
|
return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED)!=0;
|
|
}
|
|
|
|
bool ConditionVariable::SleepWithReaderForTime(ReaderWriterLock& lock, vint ms)
|
|
{
|
|
return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, (DWORD)ms, CONDITION_VARIABLE_LOCKMODE_SHARED)!=0;
|
|
}
|
|
|
|
bool ConditionVariable::SleepWithWriter(ReaderWriterLock& lock)
|
|
{
|
|
return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, INFINITE, 0)!=0;
|
|
}
|
|
|
|
bool ConditionVariable::SleepWithWriterForTime(ReaderWriterLock& lock, vint ms)
|
|
{
|
|
return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, (DWORD)ms, 0)!=0;
|
|
}
|
|
|
|
void ConditionVariable::WakeOnePending()
|
|
{
|
|
WakeConditionVariable(&internalData->variable);
|
|
}
|
|
|
|
void ConditionVariable::WakeAllPendings()
|
|
{
|
|
WakeAllConditionVariable(&internalData->variable);
|
|
}
|
|
|
|
/***********************************************************************
|
|
ThreadLocalStorage
|
|
***********************************************************************/
|
|
|
|
#define KEY ((DWORD&)key)
|
|
|
|
ThreadLocalStorage::ThreadLocalStorage(Destructor _destructor)
|
|
:destructor(_destructor)
|
|
{
|
|
static_assert(sizeof(key) >= sizeof(DWORD), "ThreadLocalStorage's key storage is not large enouth.");
|
|
PushStorage(this);
|
|
KEY = TlsAlloc();
|
|
CHECK_ERROR(KEY != TLS_OUT_OF_INDEXES, L"vl::ThreadLocalStorage::ThreadLocalStorage()#Failed to alloc new thread local storage index.");
|
|
}
|
|
|
|
ThreadLocalStorage::~ThreadLocalStorage()
|
|
{
|
|
TlsFree(KEY);
|
|
}
|
|
|
|
void* ThreadLocalStorage::Get()
|
|
{
|
|
CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Get()#Cannot access a disposed ThreadLocalStorage.");
|
|
return TlsGetValue(KEY);
|
|
}
|
|
|
|
void ThreadLocalStorage::Set(void* data)
|
|
{
|
|
CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Set()#Cannot access a disposed ThreadLocalStorage.");
|
|
TlsSetValue(KEY, data);
|
|
}
|
|
|
|
#undef KEY
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
.\STREAM\CHARFORMAT.WINDOWS.CPP
|
|
***********************************************************************/
|
|
/***********************************************************************
|
|
Author: Zihan Chen (vczh)
|
|
Licensed under https://github.com/vczh-libraries/License
|
|
***********************************************************************/
|
|
|
|
#include <windows.h>
|
|
|
|
#ifndef VCZH_MSVC
|
|
static_assert(false, "Do not build this file for non-Windows applications.");
|
|
#endif
|
|
|
|
namespace vl
|
|
{
|
|
namespace stream
|
|
{
|
|
bool IsMbcsLeadByte(char c)
|
|
{
|
|
return IsDBCSLeadByte(c);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Mbcs
|
|
***********************************************************************/
|
|
|
|
vint MbcsEncoder::WriteString(wchar_t* _buffer, vint chars, bool freeToUpdate)
|
|
{
|
|
vint length = WideCharToMultiByte(CP_THREAD_ACP, 0, _buffer, (int)chars, NULL, NULL, NULL, NULL);
|
|
char* mbcs = new char[length];
|
|
WideCharToMultiByte(CP_THREAD_ACP, 0, _buffer, (int)chars, mbcs, (int)length, NULL, NULL);
|
|
vint result = stream->Write(mbcs, length);
|
|
delete[] mbcs;
|
|
|
|
if (result != length)
|
|
{
|
|
Close();
|
|
return 0;
|
|
}
|
|
return chars;
|
|
}
|
|
|
|
void MbcsToWChar(wchar_t* wideBuffer, vint wideChars, vint wideReaded, char* mbcsBuffer, vint mbcsChars)
|
|
{
|
|
MultiByteToWideChar(CP_THREAD_ACP, 0, mbcsBuffer, (int)mbcsChars, wideBuffer, (int)wideChars);
|
|
}
|
|
|
|
/***********************************************************************
|
|
Utf8
|
|
***********************************************************************/
|
|
|
|
vint Utf8Encoder::WriteString(wchar_t* _buffer, vint chars, bool freeToUpdate)
|
|
{
|
|
vint length = WideCharToMultiByte(CP_UTF8, 0, _buffer, (int)chars, NULL, NULL, NULL, NULL);
|
|
char* mbcs = new char[length];
|
|
WideCharToMultiByte(CP_UTF8, 0, _buffer, (int)chars, mbcs, (int)length, NULL, NULL);
|
|
vint result = stream->Write(mbcs, length);
|
|
delete[] mbcs;
|
|
if (result != length)
|
|
{
|
|
Close();
|
|
return 0;
|
|
}
|
|
return chars;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
.\STREAM\CHARFORMAT_TESTENCODING.WINDOWS.CPP
|
|
***********************************************************************/
|
|
/***********************************************************************
|
|
Author: Zihan Chen (vczh)
|
|
Licensed under https://github.com/vczh-libraries/License
|
|
***********************************************************************/
|
|
|
|
|
|
#ifndef VCZH_MSVC
|
|
static_assert(false, "Do not build this file for non-Windows applications.");
|
|
#endif
|
|
|
|
namespace vl
|
|
{
|
|
namespace stream
|
|
{
|
|
/***********************************************************************
|
|
Helper Functions
|
|
***********************************************************************/
|
|
|
|
extern bool CanBeMbcs(unsigned char* buffer, vint size);
|
|
extern bool CanBeUtf8(unsigned char* buffer, vint size);
|
|
extern bool CanBeUtf16(unsigned char* buffer, vint size, bool& hitSurrogatePairs);
|
|
extern bool CanBeUtf16BE(unsigned char* buffer, vint size, bool& hitSurrogatePairs);
|
|
|
|
template<vint Count>
|
|
bool GetEncodingResult(int(&tests)[Count], bool(&results)[Count], int test)
|
|
{
|
|
for (vint i = 0; i < Count; i++)
|
|
{
|
|
if (tests[i] & test)
|
|
{
|
|
if (results[i]) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/***********************************************************************
|
|
TestEncoding
|
|
***********************************************************************/
|
|
|
|
extern void TestEncodingInternal(
|
|
unsigned char* buffer,
|
|
vint size,
|
|
BomEncoder::Encoding& encoding,
|
|
bool containsBom,
|
|
bool utf16HitSurrogatePairs,
|
|
bool utf16BEHitSurrogatePairs,
|
|
bool roughMbcs,
|
|
bool roughUtf8,
|
|
bool roughUtf16,
|
|
bool roughUtf16BE
|
|
)
|
|
{
|
|
int tests[] =
|
|
{
|
|
IS_TEXT_UNICODE_REVERSE_ASCII16,
|
|
IS_TEXT_UNICODE_REVERSE_STATISTICS,
|
|
IS_TEXT_UNICODE_REVERSE_CONTROLS,
|
|
|
|
IS_TEXT_UNICODE_ASCII16,
|
|
IS_TEXT_UNICODE_STATISTICS,
|
|
IS_TEXT_UNICODE_CONTROLS,
|
|
|
|
IS_TEXT_UNICODE_ILLEGAL_CHARS,
|
|
IS_TEXT_UNICODE_ODD_LENGTH,
|
|
IS_TEXT_UNICODE_NULL_BYTES,
|
|
};
|
|
|
|
const vint TestCount = sizeof(tests) / sizeof(*tests);
|
|
bool results[TestCount];
|
|
for (vint i = 0; i < TestCount; i++)
|
|
{
|
|
int test = tests[i];
|
|
results[i] = IsTextUnicode(buffer, (int)size, &test) != 0;
|
|
}
|
|
|
|
if (size % 2 == 0
|
|
&& !GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_ASCII16)
|
|
&& !GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_STATISTICS)
|
|
&& !GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_CONTROLS)
|
|
)
|
|
{
|
|
for (vint i = 0; i < size; i += 2)
|
|
{
|
|
unsigned char c = buffer[i];
|
|
buffer[i] = buffer[i + 1];
|
|
buffer[i + 1] = c;
|
|
}
|
|
// 3 = (count of reverse group) = (count of unicode group)
|
|
for (vint i = 0; i < 3; i++)
|
|
{
|
|
int test = tests[i + 3];
|
|
results[i] = IsTextUnicode(buffer, (int)size, &test) != 0;
|
|
}
|
|
for (vint i = 0; i < size; i += 2)
|
|
{
|
|
unsigned char c = buffer[i];
|
|
buffer[i] = buffer[i + 1];
|
|
buffer[i + 1] = c;
|
|
}
|
|
}
|
|
|
|
if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_NOT_UNICODE_MASK))
|
|
{
|
|
if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_NOT_ASCII_MASK))
|
|
{
|
|
encoding = BomEncoder::Utf8;
|
|
}
|
|
else if (roughUtf8 || !roughMbcs)
|
|
{
|
|
encoding = BomEncoder::Utf8;
|
|
}
|
|
}
|
|
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_ASCII16))
|
|
{
|
|
encoding = BomEncoder::Utf16;
|
|
}
|
|
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_ASCII16))
|
|
{
|
|
encoding = BomEncoder::Utf16BE;
|
|
}
|
|
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_CONTROLS))
|
|
{
|
|
encoding = BomEncoder::Utf16;
|
|
}
|
|
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_CONTROLS))
|
|
{
|
|
encoding = BomEncoder::Utf16BE;
|
|
}
|
|
else
|
|
{
|
|
if (!roughUtf8)
|
|
{
|
|
if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_STATISTICS))
|
|
{
|
|
encoding = BomEncoder::Utf16;
|
|
}
|
|
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_STATISTICS))
|
|
{
|
|
encoding = BomEncoder::Utf16BE;
|
|
}
|
|
}
|
|
else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_NOT_UNICODE_MASK))
|
|
{
|
|
encoding = BomEncoder::Utf8;
|
|
}
|
|
else if (roughUtf8 || !roughMbcs)
|
|
{
|
|
encoding = BomEncoder::Utf8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|