Skip to content

Commit 10ea6d4

Browse files
feat: impl auto-update
1 parent 6b1744d commit 10ea6d4

18 files changed

Lines changed: 660 additions & 272 deletions

File tree

src/shell/config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ struct config {
132132
std::vector<std::string> plugin_load_order = {};
133133

134134
std::string language;
135+
bool auto_update = true;
135136

136137
std::string $schema;
137138
static std::unique_ptr<config> current;

src/shell/script/binding_qjs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,7 @@ template<> struct js_bind<mb_shell::js::network> {
11491149
.static_fun<&mb_shell::js::network::get_async>("get_async")
11501150
.static_fun<&mb_shell::js::network::post_async>("post_async")
11511151
.static_fun<&mb_shell::js::network::download_async>("download_async")
1152+
.static_fun<&mb_shell::js::network::download_with_progress_async>("download_with_progress_async")
11521153
;
11531154
}
11541155
};

src/shell/script/binding_types.cc

Lines changed: 193 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -499,37 +499,39 @@ std::string network::post(std::string url, std::string data) {
499499
return response;
500500
}
501501

502-
std::string network::get(std::string url) { return post(url, ""); }
503-
504-
void network::get_async(std::string url,
505-
std::function<void(std::string)> callback,
506-
std::function<void(std::string)> error_callback) {
502+
std::string network::get(std::string url) { return post(url, ""); }
503+
504+
void network::get_async(std::string url,
505+
std::function<void(std::string)> callback,
506+
std::function<void(std::string)> error_callback) {
507507
std::thread([url, callback, error_callback,
508508
&ctx = *qjs::Context::current]() {
509-
try {
510-
auto res = get(url);
511-
ctx.enqueueJob([=]() { callback(res); });
512-
} catch (std::exception &e) {
513-
spdlog::error("Error in network::get_async: {}", e.what());
514-
error_callback(e.what());
515-
}
516-
}).detach();
517-
}
518-
519-
void network::post_async(std::string url, std::string data,
509+
try {
510+
auto res = get(url);
511+
ctx.enqueueJob([=]() { callback(res); });
512+
} catch (std::exception &e) {
513+
spdlog::error("Error in network::get_async: {}", e.what());
514+
auto error = std::string(e.what());
515+
ctx.enqueueJob([=]() { error_callback(error); });
516+
}
517+
}).detach();
518+
}
519+
520+
void network::post_async(std::string url, std::string data,
520521
std::function<void(std::string)> callback,
521522
std::function<void(std::string)> error_callback) {
522523
std::thread([url, data, callback, error_callback,
523524
&ctx = *qjs::Context::current]() {
524-
try {
525-
auto res = post(url, data);
526-
ctx.enqueueJob([=]() { callback(res); });
527-
} catch (std::exception &e) {
528-
spdlog::error("Error in network::post_async: {}", e.what());
529-
error_callback(e.what());
530-
}
531-
}).detach();
532-
}
525+
try {
526+
auto res = post(url, data);
527+
ctx.enqueueJob([=]() { callback(res); });
528+
} catch (std::exception &e) {
529+
spdlog::error("Error in network::post_async: {}", e.what());
530+
auto error = std::string(e.what());
531+
ctx.enqueueJob([=]() { error_callback(error); });
532+
}
533+
}).detach();
534+
}
533535
subproc_result_data subproc::run(std::string cmd) {
534536
subproc_result_data result;
535537
SECURITY_ATTRIBUTES sa;
@@ -672,24 +674,172 @@ std::vector<std::string> fs::readdir(std::string path) {
672674
std::back_inserter(result));
673675
return result;
674676
}
675-
bool breeze::is_light_theme() { return is_light_mode(); }
676-
void network::download_async(std::string url, std::string path,
677-
std::function<void()> callback,
678-
std::function<void(std::string)> error_callback) {
679-
680-
std::thread([url, path, callback, error_callback,
681-
&ctx = *qjs::Context::current]() {
682-
try {
683-
auto data = get(url);
684-
fs::write_binary(path,
685-
std::vector<uint8_t>(data.begin(), data.end()));
686-
ctx.enqueueJob([=]() { callback(); });
687-
callback();
688-
} catch (std::exception &e) {
689-
error_callback(e.what());
690-
}
691-
}).detach();
692-
}
677+
bool breeze::is_light_theme() { return is_light_mode(); }
678+
void network::download_async(std::string url, std::string path,
679+
std::function<void()> callback,
680+
std::function<void(std::string)> error_callback) {
681+
std::thread([url, path, callback, error_callback,
682+
&ctx = *qjs::Context::current]() {
683+
try {
684+
auto data = get(url);
685+
fs::write_binary(path,
686+
std::vector<uint8_t>(data.begin(), data.end()));
687+
ctx.enqueueJob([=]() { callback(); });
688+
} catch (std::exception &e) {
689+
auto error = std::string(e.what());
690+
spdlog::error("Error in network::download_async: {}", error);
691+
ctx.enqueueJob([=]() { error_callback(error); });
692+
}
693+
}).detach();
694+
}
695+
696+
void network::download_with_progress_async(
697+
std::string url, std::string path, std::function<void()> callback,
698+
std::function<void(std::string)> error_callback,
699+
std::function<void(size_t, size_t)> progress_callback) {
700+
std::thread([url, path, callback, error_callback,
701+
progress_callback, &ctx = *qjs::Context::current]() {
702+
HINTERNET hSession = nullptr;
703+
HINTERNET hConnect = nullptr;
704+
HINTERNET hRequest = nullptr;
705+
auto close_handles = [&]() {
706+
if (hRequest) {
707+
WinHttpCloseHandle(hRequest);
708+
hRequest = nullptr;
709+
}
710+
if (hConnect) {
711+
WinHttpCloseHandle(hConnect);
712+
hConnect = nullptr;
713+
}
714+
if (hSession) {
715+
WinHttpCloseHandle(hSession);
716+
hSession = nullptr;
717+
}
718+
};
719+
720+
try {
721+
hSession =
722+
WinHttpOpen(L"BreezeShell", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
723+
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
724+
if (!hSession) {
725+
throw std::runtime_error("Failed to initialize WinHTTP");
726+
}
727+
728+
URL_COMPONENTS urlComp = {sizeof(URL_COMPONENTS)};
729+
wchar_t hostName[256] = {0};
730+
wchar_t urlPath[1024] = {0};
731+
urlComp.lpszHostName = hostName;
732+
urlComp.dwHostNameLength = sizeof(hostName) / sizeof(wchar_t);
733+
urlComp.lpszUrlPath = urlPath;
734+
urlComp.dwUrlPathLength = sizeof(urlPath) / sizeof(wchar_t);
735+
736+
std::wstring wideUrl = utf8_to_wstring(url);
737+
738+
if (!WinHttpCrackUrl(wideUrl.c_str(), wideUrl.length(), 0,
739+
&urlComp)) {
740+
throw std::runtime_error("Invalid URL format");
741+
}
742+
743+
hConnect =
744+
WinHttpConnect(hSession, hostName,
745+
urlComp.nScheme == INTERNET_SCHEME_HTTPS
746+
? INTERNET_DEFAULT_HTTPS_PORT
747+
: INTERNET_DEFAULT_HTTP_PORT,
748+
0);
749+
if (!hConnect) {
750+
throw std::runtime_error("Failed to connect to server");
751+
}
752+
753+
DWORD flags = WINHTTP_FLAG_REFRESH;
754+
if (urlComp.nScheme == INTERNET_SCHEME_HTTPS) {
755+
flags |= WINHTTP_FLAG_SECURE;
756+
}
757+
758+
hRequest = WinHttpOpenRequest(
759+
hConnect, L"GET", urlPath, nullptr, WINHTTP_NO_REFERER,
760+
WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
761+
if (!hRequest) {
762+
throw std::runtime_error("Failed to create request");
763+
}
764+
765+
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
766+
WINHTTP_NO_REQUEST_DATA, 0, 0, 0) ||
767+
!WinHttpReceiveResponse(hRequest, nullptr)) {
768+
throw std::runtime_error("Failed to send/receive request");
769+
}
770+
771+
DWORD statusCode = 0;
772+
DWORD statusCodeSize = sizeof(statusCode);
773+
WinHttpQueryHeaders(
774+
hRequest,
775+
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
776+
WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize,
777+
WINHTTP_NO_HEADER_INDEX);
778+
779+
if (statusCode >= 400) {
780+
throw std::runtime_error("Server returned error: " +
781+
std::to_string(statusCode));
782+
}
783+
784+
DWORD contentLength = 0;
785+
DWORD contentLengthSize = sizeof(contentLength);
786+
size_t totalBytes = 0;
787+
if (WinHttpQueryHeaders(
788+
hRequest,
789+
WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
790+
WINHTTP_HEADER_NAME_BY_INDEX, &contentLength,
791+
&contentLengthSize, WINHTTP_NO_HEADER_INDEX)) {
792+
totalBytes = contentLength;
793+
}
794+
795+
std::ofstream file(path, std::ios::binary | std::ios::trunc);
796+
if (!file.is_open()) {
797+
throw std::runtime_error("Failed to open file for writing");
798+
}
799+
800+
size_t downloadedBytes = 0;
801+
DWORD bytesAvailable = 0;
802+
do {
803+
bytesAvailable = 0;
804+
if (!WinHttpQueryDataAvailable(hRequest, &bytesAvailable)) {
805+
throw std::runtime_error("Failed to query download data");
806+
}
807+
if (!bytesAvailable)
808+
break;
809+
810+
std::vector<char> buffer(bytesAvailable);
811+
DWORD bytesRead = 0;
812+
if (!WinHttpReadData(hRequest, buffer.data(), bytesAvailable,
813+
&bytesRead)) {
814+
throw std::runtime_error("Failed to read download data");
815+
}
816+
if (!bytesRead)
817+
continue;
818+
819+
file.write(buffer.data(), bytesRead);
820+
if (!file.good()) {
821+
throw std::runtime_error("Failed to write download file");
822+
}
823+
824+
downloadedBytes += bytesRead;
825+
ctx.enqueueJob([=]() {
826+
progress_callback(downloadedBytes, totalBytes);
827+
});
828+
} while (bytesAvailable > 0);
829+
830+
file.close();
831+
close_handles();
832+
ctx.enqueueJob([=]() { callback(); });
833+
} catch (std::exception &e) {
834+
close_handles();
835+
std::filesystem::remove(path);
836+
auto error = std::string(e.what());
837+
spdlog::error("Error in network::download_with_progress_async: {}",
838+
error);
839+
ctx.enqueueJob([=]() { error_callback(error); });
840+
}
841+
}).detach();
842+
}
693843
std::string win32::resid_from_string(std::string str) {
694844
return res_string_loader::string_to_id_string(utf8_to_wstring(str));
695845
}

src/shell/script/binding_types.d.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -942,10 +942,21 @@ export class network {
942942
* @param url: string
943943
* @param path: string
944944
* @param callback: (() => void)
945-
* @param error_callback: ((arg1: string) => void)
945+
* @param error_callback: ((arg1: string) => void)
946946
* @returns void
947947
*/
948948
static download_async(url: string, path: string, callback: (() => void), error_callback: ((arg1: string) => void)): void
949+
/**
950+
* 带进度回调的异步下载文件
951+
* Download file asynchronously with progress callback
952+
* @param url: string
953+
* @param path: string
954+
* @param callback: (() => void)
955+
* @param error_callback: ((arg1: string) => void)
956+
* @param progress_callback: ((arg1: number, arg2: number) => void)
957+
* @returns void
958+
*/
959+
static download_with_progress_async(url: string, path: string, callback: (() => void), error_callback: ((arg1: string) => void), progress_callback: ((arg1: number, arg2: number) => void)): void
949960
}
950961
export class subproc_result_data {
951962
/**
@@ -1400,4 +1411,4 @@ declare module "mshell" {
14001411
type intptr_t = number;
14011412
type uintptr_t = number;
14021413
type ssize_t = number;
1403-
}
1414+
}

src/shell/script/binding_types.hpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -505,12 +505,19 @@ struct network {
505505
std::function<void(std::string)> callback,
506506
std::function<void(std::string)> error_callback);
507507

508-
// 下载文件
509-
// Download file
510-
static void download_async(std::string url, std::string path,
511-
std::function<void()> callback,
512-
std::function<void(std::string)> error_callback);
513-
};
508+
// 下载文件
509+
// Download file
510+
static void download_async(std::string url, std::string path,
511+
std::function<void()> callback,
512+
std::function<void(std::string)> error_callback);
513+
514+
// 带进度回调的异步下载文件
515+
// Download file asynchronously with progress callback
516+
static void download_with_progress_async(
517+
std::string url, std::string path, std::function<void()> callback,
518+
std::function<void(std::string)> error_callback,
519+
std::function<void(size_t, size_t)> progress_callback);
520+
};
514521

515522
// 子进程执行结果
516523
// Subprocess execution result

src/shell/script/script.js

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)