mirror of
https://github.com/modernw/App-Installer-For-Windows-8.x-Reset.git
synced 2026-04-11 17:57:19 +10:00
402 lines
9.4 KiB
C++
402 lines
9.4 KiB
C++
#pragma once
|
|
#include <Windows.h>
|
|
#include <winhttp.h>
|
|
#include <string>
|
|
#include "strcode.h"
|
|
#include "mpstr.h"
|
|
#include <chrono>
|
|
#include <rapidjson/writer.h>
|
|
#include <rapidjson/stringbuffer.h>
|
|
#include "syncutil.h"
|
|
|
|
// Generated by ChatGTP
|
|
|
|
using namespace System;
|
|
using namespace System::Threading;
|
|
using namespace System::Reflection;
|
|
|
|
std::wstring GetLastErrorString ()
|
|
{
|
|
DWORD errCode = GetLastError ();
|
|
if (errCode == 0)
|
|
return L"No error";
|
|
|
|
wchar_t* msgBuffer = nullptr;
|
|
|
|
// FORMAT_MESSAGE_ALLOCATE_BUFFER: 让系统分配缓冲区
|
|
// FORMAT_MESSAGE_FROM_SYSTEM: 从系统获取错误信息
|
|
// FORMAT_MESSAGE_IGNORE_INSERTS: 忽略 %1 %2
|
|
DWORD size = FormatMessageW (
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
nullptr,
|
|
errCode,
|
|
0, // 默认语言
|
|
(LPWSTR)&msgBuffer,
|
|
0,
|
|
nullptr
|
|
);
|
|
|
|
std::wstring msg;
|
|
if (size && msgBuffer)
|
|
{
|
|
msg = msgBuffer;
|
|
LocalFree (msgBuffer); // 释放缓冲区
|
|
}
|
|
else
|
|
{
|
|
msg = L"Unknown error code: " + std::to_wstring (errCode);
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
CriticalSection g_download_cs;
|
|
|
|
public ref class DownloadHelper
|
|
{
|
|
public:
|
|
static void DownloadFile (
|
|
String^ httpUrl,
|
|
String^ savePath,
|
|
Object^ onProgress,
|
|
Object^ onComplete,
|
|
Object^ onError)
|
|
{
|
|
DownloadHelper^ obj = gcnew DownloadHelper ();
|
|
obj->m_url = httpUrl;
|
|
obj->m_savePath = savePath;
|
|
obj->cbProgress = onProgress;
|
|
obj->cbComplete = onComplete;
|
|
obj->cbError = onError;
|
|
|
|
Thread^ th = gcnew Thread (gcnew ThreadStart (obj, &DownloadHelper::Worker));
|
|
th->IsBackground = true;
|
|
th->Start ();
|
|
}
|
|
|
|
private:
|
|
HINTERNET hSession = nullptr,
|
|
hConnect = nullptr,
|
|
hRequest = nullptr;
|
|
void CancelHttpHandle (HINTERNET hInternet)
|
|
{
|
|
if (hInternet) WinHttpCloseHandle (hInternet);
|
|
hInternet = nullptr;
|
|
}
|
|
std::wstring FormatSpeed (long long speed)
|
|
{
|
|
if (speed < 0) return L"--/s";
|
|
|
|
const wchar_t* units [] = {L"B/s", L"KB/s", L"MB/s", L"GB/s", L"TB/s"};
|
|
double s = (double)speed;
|
|
int idx = 0;
|
|
|
|
while (s >= 1024.0 && idx < 4) {
|
|
s /= 1024.0;
|
|
if (s / 1024.0 < 1) break;
|
|
idx++;
|
|
}
|
|
|
|
wchar_t buf [64];
|
|
swprintf (buf, 64, L"%.2f %s", s, units [idx]);
|
|
return buf;
|
|
}
|
|
public:
|
|
~DownloadHelper ()
|
|
{
|
|
if (hSession) CancelHttpHandle (hSession);
|
|
if (hConnect) CancelHttpHandle (hConnect);
|
|
if (hRequest) CancelHttpHandle (hRequest);
|
|
}
|
|
private:
|
|
void Worker ()
|
|
{
|
|
CreateScopedLock (g_download_cs);
|
|
std::wstring url = MPStringToStdW (m_url);
|
|
std::wstring outPath = MPStringToStdW (m_savePath);
|
|
|
|
URL_COMPONENTS urlComp = {0};
|
|
urlComp.dwStructSize = sizeof (urlComp);
|
|
|
|
wchar_t host [256];
|
|
wchar_t path [2048];
|
|
|
|
urlComp.lpszHostName = host;
|
|
urlComp.dwHostNameLength = _countof (host);
|
|
|
|
urlComp.lpszUrlPath = path;
|
|
urlComp.dwUrlPathLength = _countof (path);
|
|
|
|
if (!WinHttpCrackUrl (url.c_str (), 0, 0, &urlComp))
|
|
{
|
|
ReportError (outPath, L"WinHttpCrackUrl failed: " + GetLastErrorString ());
|
|
return;
|
|
}
|
|
|
|
BOOL isHttps = (urlComp.nScheme == INTERNET_SCHEME_HTTPS);
|
|
|
|
hSession = WinHttpOpen (
|
|
L"MyDownloader",
|
|
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
|
WINHTTP_NO_PROXY_NAME,
|
|
WINHTTP_NO_PROXY_BYPASS,
|
|
0);
|
|
|
|
if (!hSession)
|
|
{
|
|
ReportError (outPath, L"WinHttpOpen failed: " + GetLastErrorString ());
|
|
return;
|
|
}
|
|
|
|
DWORD protocols =
|
|
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
|
|
|
|
WinHttpSetOption (
|
|
hSession,
|
|
WINHTTP_OPTION_SECURE_PROTOCOLS,
|
|
&protocols,
|
|
sizeof (protocols)
|
|
);
|
|
|
|
hConnect = WinHttpConnect (
|
|
hSession,
|
|
urlComp.lpszHostName,
|
|
urlComp.nPort,
|
|
0);
|
|
|
|
if (!hConnect)
|
|
{
|
|
CancelHttpHandle (hSession); hSession = nullptr;
|
|
ReportError (outPath, L"WinHttpConnect failed: " + GetLastErrorString ());
|
|
return;
|
|
}
|
|
|
|
hRequest = WinHttpOpenRequest (
|
|
hConnect,
|
|
L"GET",
|
|
urlComp.lpszUrlPath,
|
|
NULL,
|
|
WINHTTP_NO_REFERER,
|
|
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
|
isHttps ? WINHTTP_FLAG_SECURE : 0);
|
|
DWORD redirect = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS;
|
|
WinHttpSetOption (
|
|
hRequest,
|
|
WINHTTP_OPTION_REDIRECT_POLICY,
|
|
&redirect,
|
|
sizeof (redirect)
|
|
);
|
|
|
|
if (!hRequest)
|
|
{
|
|
CancelHttpHandle (hConnect); hConnect = nullptr;
|
|
CancelHttpHandle (hSession); hSession = nullptr;
|
|
ReportError (outPath, L"WinHttpOpenRequest failed: " + GetLastErrorString ());
|
|
return;
|
|
}
|
|
LPCWSTR ua = L"Mozilla/5.0 (Windows NT 6.2; Win32; x86) AppInstallerUpdater/1.0";
|
|
WinHttpAddRequestHeaders (
|
|
hRequest,
|
|
(std::wstring (L"User-Agent: ") + ua).c_str (),
|
|
-1,
|
|
WINHTTP_ADDREQ_FLAG_ADD
|
|
);
|
|
|
|
if (!WinHttpSendRequest (hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
|
WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
|
|
{
|
|
CancelHttpHandle (hRequest);
|
|
CancelHttpHandle (hConnect);
|
|
CancelHttpHandle (hSession);
|
|
ReportError (outPath, L"WinHttpSendRequest failed: " + GetLastErrorString ());
|
|
return;
|
|
}
|
|
|
|
if (!WinHttpReceiveResponse (hRequest, NULL))
|
|
{
|
|
CancelHttpHandle (hRequest); hRequest = nullptr;
|
|
CancelHttpHandle (hConnect); hConnect = nullptr;
|
|
CancelHttpHandle (hSession); hSession = nullptr;
|
|
DWORD status = 0;
|
|
DWORD size = sizeof (status);
|
|
|
|
WinHttpQueryHeaders (
|
|
hRequest,
|
|
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
|
NULL,
|
|
&status,
|
|
&size,
|
|
NULL
|
|
);
|
|
|
|
if (status >= 400)
|
|
{
|
|
ReportError (outPath,
|
|
L"HTTP error: " + std::to_wstring (status));
|
|
return;
|
|
}
|
|
|
|
ReportError (outPath, L"WinHttpReceiveResponse failed: " + GetLastErrorString ());
|
|
return;
|
|
}
|
|
|
|
// ---- 获取 Content-Length ----
|
|
DWORD dwSize = sizeof (DWORD);
|
|
DWORD fileSize = 0;
|
|
WinHttpQueryHeaders (
|
|
hRequest,
|
|
WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
|
|
NULL,
|
|
&fileSize,
|
|
&dwSize,
|
|
NULL);
|
|
|
|
HANDLE hOut = CreateFileW (outPath.c_str (), GENERIC_WRITE, 0, NULL,
|
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
if (hOut == INVALID_HANDLE_VALUE)
|
|
{
|
|
CancelHttpHandle (hRequest); hRequest = nullptr;
|
|
CancelHttpHandle (hConnect); hConnect = nullptr;
|
|
CancelHttpHandle (hSession); hSession = nullptr;
|
|
ReportError (outPath, L"Cannot create output file: " + GetLastErrorString ());
|
|
return;
|
|
}
|
|
|
|
BYTE buffer [8192];
|
|
DWORD bytesRead = 0;
|
|
DWORD bytesWritten = 0;
|
|
long long received = 0;
|
|
|
|
auto t0 = std::chrono::high_resolution_clock::now ();
|
|
auto lastCheck = t0;
|
|
unsigned long long lastBytes = 0;
|
|
|
|
while (WinHttpReadData (hRequest, buffer, sizeof (buffer), &bytesRead) && bytesRead > 0)
|
|
{
|
|
WriteFile (hOut, buffer, bytesRead, &bytesWritten, NULL);
|
|
received += bytesRead;
|
|
|
|
auto now = std::chrono::high_resolution_clock::now ();
|
|
double intervalSec = std::chrono::duration<double> (now - lastCheck).count ();
|
|
|
|
long long speed = -1; // -1 表示“保持上次速度”
|
|
|
|
// 每 0.5 秒刷新一次速度(可调)
|
|
if (intervalSec >= 0.5)
|
|
{
|
|
unsigned long long bytesInInterval = received - lastBytes;
|
|
if (intervalSec > 0)
|
|
speed = (long long)(bytesInInterval / intervalSec); // B/s
|
|
|
|
lastCheck = now;
|
|
lastBytes = received;
|
|
}
|
|
|
|
ReportProgress (received, fileSize, speed);
|
|
}
|
|
|
|
|
|
CloseHandle (hOut);
|
|
CancelHttpHandle (hRequest); hRequest = nullptr;
|
|
CancelHttpHandle (hConnect); hConnect = nullptr;
|
|
CancelHttpHandle (hSession); hSession = nullptr;
|
|
|
|
ReportComplete (outPath, received);
|
|
}
|
|
|
|
// ---------------- 回调组装 JSON ----------------
|
|
|
|
void ReportProgress (long long received, long long total, long long speed)
|
|
{
|
|
if (!cbProgress) return;
|
|
|
|
rapidjson::StringBuffer buf;
|
|
rapidjson::Writer<rapidjson::StringBuffer> w (buf);
|
|
|
|
w.StartObject ();
|
|
w.Key ("received"); w.Uint64 (received);
|
|
w.Key ("total"); w.Uint64 (total);
|
|
std::wstring speedText = FormatSpeed (speed);
|
|
std::string speedUtf8 = WStringToString (speedText, CP_UTF8);
|
|
|
|
w.Key ("speed");
|
|
w.String (speedUtf8.c_str ());
|
|
|
|
w.Key ("progress"); w.Double (received / (double)total * 100);
|
|
w.EndObject ();
|
|
|
|
CallJS (cbProgress, CStringToMPString (StringToWString (buf.GetString (), CP_UTF8)));
|
|
}
|
|
|
|
void ReportComplete (const std::wstring& file, long long size)
|
|
{
|
|
if (!cbComplete) return;
|
|
|
|
rapidjson::StringBuffer buf;
|
|
rapidjson::Writer<rapidjson::StringBuffer> w (buf);
|
|
|
|
w.StartObject ();
|
|
w.Key ("file"); w.String (WStringToString (file, CP_UTF8).c_str ());
|
|
w.Key ("status"); w.String ("ok");
|
|
w.Key ("size"); w.Uint64 (size);
|
|
w.EndObject ();
|
|
|
|
CallJS (cbComplete, CStringToMPString (StringToWString (buf.GetString (), CP_UTF8)));
|
|
}
|
|
|
|
void ReportError (const std::wstring& file, const std::wstring &reason)
|
|
{
|
|
if (!cbError) return;
|
|
|
|
rapidjson::StringBuffer buf;
|
|
rapidjson::Writer<rapidjson::StringBuffer> w (buf);
|
|
|
|
w.StartObject ();
|
|
w.Key ("file"); w.String (WStringToString (file, CP_UTF8).c_str ());
|
|
w.Key ("status"); w.String ("failed");
|
|
w.Key ("reason"); w.String (WStringToString (reason, CP_UTF8).c_str ());
|
|
w.EndObject ();
|
|
|
|
CallJS (cbError, CStringToMPString (StringToWString (buf.GetString (), CP_UTF8)));
|
|
}
|
|
|
|
// ---------------- 调用 JS 回调 ----------------
|
|
void CallJS (Object^ jsFunc, String^ arg)
|
|
{
|
|
if (!jsFunc) return;
|
|
try
|
|
{
|
|
jsFunc->GetType ()->InvokeMember (
|
|
"call",
|
|
BindingFlags::InvokeMethod,
|
|
nullptr,
|
|
jsFunc,
|
|
gcnew array<Object^>{ 1, arg }
|
|
);
|
|
}
|
|
catch (...)
|
|
{
|
|
// 失败可忽略
|
|
}
|
|
}
|
|
|
|
private:
|
|
String^ m_url;
|
|
String^ m_savePath;
|
|
|
|
Object^ cbProgress;
|
|
Object^ cbComplete;
|
|
Object^ cbError;
|
|
};
|
|
|
|
using namespace System::Runtime::InteropServices;
|
|
[ComVisible (true)]
|
|
public ref class _I_Download
|
|
{
|
|
public:
|
|
void WorkAsync (String ^httpurl, String ^saveFilePath, Object ^onComplete, Object ^onError, Object ^onProgress)
|
|
{
|
|
auto download = gcnew DownloadHelper ();
|
|
download->DownloadFile (httpurl, saveFilePath, onProgress, onComplete, onError);
|
|
}
|
|
}; |