Files
App-Installer-For-Windows-8…/settings/download.h
2026-01-04 13:17:59 +08:00

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);
}
};