You've already forked AstralRinth
feat(astralrinth): add launcher update installer selection
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { Button, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Button, Combobox, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import {
|
||||
downloadLatestRelease,
|
||||
getAvailableInstallers,
|
||||
isUpdateInstalling,
|
||||
LAUNCHER_RELEASES_URL,
|
||||
LAUNCHER_REPOSITORY_URL,
|
||||
latestLauncherRelease,
|
||||
latestLauncherReleases,
|
||||
} from '@/helpers/astralrinth/update'
|
||||
|
||||
type ModalHandle = {
|
||||
@@ -24,9 +25,17 @@ const { formatMessage } = useVIntl()
|
||||
|
||||
const updateModalView = ref<ModalHandle | null>(null)
|
||||
const updateRequestFailView = ref<ModalHandle | null>(null)
|
||||
const selectedInstallerName = ref<string | null>(null)
|
||||
|
||||
const releaseTag = computed(() => latestLauncherRelease.value?.tag_name ?? '')
|
||||
const releaseTitle = computed(() => latestLauncherRelease.value?.name ?? '')
|
||||
const releaseTag = computed(() => latestLauncherReleases.value?.tag_name ?? '')
|
||||
const releaseTitle = computed(() => latestLauncherReleases.value?.name ?? '')
|
||||
const availableInstallers = computed(() => getAvailableInstallers())
|
||||
const selectedInstaller = computed(
|
||||
() =>
|
||||
availableInstallers.value.find((installer) => installer.name === selectedInstallerName.value) ??
|
||||
null,
|
||||
)
|
||||
const selectedInstallerUrl = computed(() => selectedInstaller.value?.browser_download_url ?? null)
|
||||
|
||||
const messages = defineMessages({
|
||||
updateHeader: {
|
||||
@@ -63,6 +72,18 @@ const messages = defineMessages({
|
||||
id: 'astralrinth.app.launcher-update-modal.update.notice-outro',
|
||||
defaultMessage: 'To avoid data loss, keep a backup copy in a safe place before continuing.',
|
||||
},
|
||||
installerTitle: {
|
||||
id: 'astralrinth.app.launcher-update-modal.update.installer-title',
|
||||
defaultMessage: 'Installer type',
|
||||
},
|
||||
installerDescription: {
|
||||
id: 'astralrinth.app.launcher-update-modal.update.installer-description',
|
||||
defaultMessage: 'Choose the installer package you want to continue with.',
|
||||
},
|
||||
selectInstaller: {
|
||||
id: 'astralrinth.app.launcher-update-modal.update.select-installer',
|
||||
defaultMessage: 'Select an installer',
|
||||
},
|
||||
latestReleaseTag: {
|
||||
id: 'astralrinth.app.launcher-update-modal.update.latest-release-tag',
|
||||
defaultMessage: '☁️ Latest release tag:',
|
||||
@@ -85,7 +106,7 @@ const messages = defineMessages({
|
||||
},
|
||||
downloadAction: {
|
||||
id: 'astralrinth.app.launcher-update-modal.update.download-action',
|
||||
defaultMessage: 'Download update and close',
|
||||
defaultMessage: 'Download update',
|
||||
},
|
||||
errorHeader: {
|
||||
id: 'astralrinth.app.launcher-update-modal.error.header',
|
||||
@@ -121,13 +142,29 @@ const messages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
availableInstallers,
|
||||
(installers) => {
|
||||
const hasSelectedInstaller = installers.some(
|
||||
(installer) => installer.name === selectedInstallerName.value,
|
||||
)
|
||||
|
||||
if (hasSelectedInstaller) {
|
||||
return
|
||||
}
|
||||
|
||||
selectedInstallerName.value = installers.length === 1 ? installers[0].name : null
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
async function show() {
|
||||
updateModalView.value?.show()
|
||||
}
|
||||
|
||||
async function initDownload() {
|
||||
updateModalView.value?.hide()
|
||||
const result = await downloadLatestRelease()
|
||||
const result = await downloadLatestRelease(selectedInstaller.value)
|
||||
|
||||
if (!result) {
|
||||
updateRequestFailView.value?.show()
|
||||
@@ -197,11 +234,37 @@ defineExpose({
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 rounded-2xl border border-solid border-[rgba(255,255,255,0.12)] p-3">
|
||||
<div>
|
||||
<p class="m-0 text-base">
|
||||
<strong>{{ formatMessage(messages.installerTitle) }}</strong>
|
||||
</p>
|
||||
<p class="m-0 text-secondary text-sm">
|
||||
{{ formatMessage(messages.installerDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
<Combobox
|
||||
v-model="selectedInstallerName"
|
||||
name="AstralRinth launcher installer"
|
||||
:options="
|
||||
availableInstallers.map((installer) => ({
|
||||
value: installer.name,
|
||||
label: installer.name,
|
||||
}))
|
||||
"
|
||||
:display-value="selectedInstallerName ?? formatMessage(messages.selectInstaller)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-4 right-4 flex items-center gap-4 neon-button neon">
|
||||
<Button class="bordered" @click="updateModalView?.hide()">
|
||||
{{ formatMessage(messages.cancelAction) }}
|
||||
</Button>
|
||||
<Button class="bordered" :disabled="isUpdateInstalling" @click="initDownload()">
|
||||
<Button
|
||||
class="bordered"
|
||||
:disabled="isUpdateInstalling || !selectedInstallerUrl"
|
||||
@click="initDownload()"
|
||||
>
|
||||
{{ formatMessage(messages.downloadAction) }}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -215,7 +278,9 @@ defineExpose({
|
||||
>
|
||||
<div class="space-y-3 pb-16">
|
||||
<div class="space-y-2 rounded-2xl border border-solid border-[rgba(255,255,255,0.12)] p-3">
|
||||
<p><strong>{{ formatMessage(messages.errorTitle) }}</strong></p>
|
||||
<p>
|
||||
<strong>{{ formatMessage(messages.errorTitle) }}</strong>
|
||||
</p>
|
||||
<p class="m-0 text-secondary">{{ formatMessage(messages.errorDescription) }}</p>
|
||||
<p class="m-0 text-sm">
|
||||
{{ formatMessage(messages.errorHelpText) }}
|
||||
@@ -231,7 +296,9 @@ defineExpose({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-solid border-[rgba(255,255,255,0.12)] p-3 text-sm text-secondary">
|
||||
<div
|
||||
class="rounded-2xl border border-solid border-[rgba(255,255,255,0.12)] p-3 text-sm text-secondary"
|
||||
>
|
||||
<p class="m-0">
|
||||
<strong>{{ formatMessage(messages.localVersion) }}</strong>
|
||||
<span class="neon-text">v{{ props.version }}</span>
|
||||
|
||||
@@ -22,15 +22,15 @@ const LAUNCHER_LATEST_RELEASE_API = `${import.meta.env.GIT_ASTRALIUM_API_URL}rep
|
||||
|
||||
export const isUpdateInstalling = ref(false)
|
||||
export const isUpdateAvailable = ref(false)
|
||||
export const latestLauncherRelease = ref<LauncherRelease | null>(null)
|
||||
export const latestLauncherReleases = ref<LauncherRelease | null>(null)
|
||||
|
||||
const currentOS = ref('')
|
||||
|
||||
const systems = ['macos', 'windows', 'linux'] as const
|
||||
const osExtensions = {
|
||||
"linux": ['.deb'],
|
||||
"macos": ['.dmg', '.pkg', '.app'],
|
||||
"windows": ['.exe', '.msi']
|
||||
linux: ['.deb', '.rpm', '.AppImage'],
|
||||
macos: ['.dmg', '.pkg', '.app'],
|
||||
windows: ['.exe', '.msi'],
|
||||
}
|
||||
|
||||
const isDeveloper = await isDev()
|
||||
@@ -47,15 +47,17 @@ const blacklistBeginPrefixes = [
|
||||
|
||||
export async function fetchRemote(): Promise<void> {
|
||||
currentOS.value = (await getOS()).toLowerCase()
|
||||
|
||||
try {
|
||||
if (!currentOS.value) {
|
||||
throw new Error(String('Current OS is undefined'))
|
||||
}
|
||||
const response = await fetch(LAUNCHER_LATEST_RELEASE_API)
|
||||
if (!response.ok) {
|
||||
throw new Error(String(response.status))
|
||||
}
|
||||
|
||||
const remoteData = (await response.json()) as LauncherRelease
|
||||
latestLauncherRelease.value = remoteData
|
||||
latestLauncherReleases.value = remoteData
|
||||
|
||||
if (systems.includes(currentOS.value as (typeof systems)[number])) {
|
||||
const rawLocalVersion = await getVersion()
|
||||
@@ -89,14 +91,16 @@ export async function fetchRemote(): Promise<void> {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch remote releases:', error)
|
||||
latestLauncherRelease.value = null
|
||||
latestLauncherReleases.value = null
|
||||
isUpdateAvailable.value = false
|
||||
isUpdateInstalling.value = false
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadLatestRelease(): Promise<boolean> {
|
||||
if (!latestLauncherRelease.value) {
|
||||
export async function downloadLatestRelease(
|
||||
selectedInstaller?: LauncherReleaseAsset | null,
|
||||
): Promise<boolean> {
|
||||
if (!latestLauncherReleases.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -104,7 +108,7 @@ export async function downloadLatestRelease(): Promise<boolean> {
|
||||
currentOS.value = (await getOS()).toLowerCase()
|
||||
}
|
||||
|
||||
const installer = getInstaller(resolveOperationalSystemExtension(), latestLauncherRelease.value.assets)
|
||||
const installer = selectedInstaller ?? null
|
||||
if (isDeveloper) {
|
||||
console.debug(installer)
|
||||
}
|
||||
@@ -119,43 +123,51 @@ export async function downloadLatestRelease(): Promise<boolean> {
|
||||
installer.browser_download_url,
|
||||
installer.name,
|
||||
currentOS.value,
|
||||
true,
|
||||
)
|
||||
} finally {
|
||||
isUpdateInstalling.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getInstaller(
|
||||
osExtensions: string[],
|
||||
builds: LauncherReleaseAsset[],
|
||||
): LauncherReleaseAsset | null {
|
||||
for (const build of builds) {
|
||||
if (blacklistBeginPrefixes.some((prefix) => build.name.startsWith(prefix))) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (osExtensions.some((extension) => build.name.endsWith(extension))) {
|
||||
if (isDeveloper) {
|
||||
console.debug(build.name, build.browser_download_url)
|
||||
}
|
||||
return build
|
||||
}
|
||||
export function getAvailableInstallers(): LauncherReleaseAsset[] {
|
||||
if (!latestLauncherReleases.value) {
|
||||
return []
|
||||
}
|
||||
|
||||
return null
|
||||
return getInstallers(resolveOperationalSystemExtension(), latestLauncherReleases.value.assets)
|
||||
}
|
||||
|
||||
function getInstallers(os: string[], builds: LauncherReleaseAsset[]): LauncherReleaseAsset[] {
|
||||
return builds.filter((build) => {
|
||||
if (blacklistBeginPrefixes.some((prefix) => build.name.startsWith(prefix))) {
|
||||
return false
|
||||
}
|
||||
|
||||
const matchesExtension = os.some((extension) => build.name.endsWith(extension))
|
||||
if (matchesExtension && isDeveloper) {
|
||||
console.debug(build.name, build.browser_download_url)
|
||||
}
|
||||
|
||||
return matchesExtension
|
||||
})
|
||||
}
|
||||
|
||||
function resolveOperationalSystemExtension(): string[] {
|
||||
if (currentOS.value === 'macos') {
|
||||
return osExtensions["macos"]
|
||||
try {
|
||||
switch (currentOS.value) {
|
||||
case 'macos':
|
||||
return osExtensions.macos
|
||||
case 'windows':
|
||||
return osExtensions.windows
|
||||
case 'linux':
|
||||
return osExtensions.linux
|
||||
default:
|
||||
throw new Error(String("Operational System can't be resolved"))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Operational System can't be resolved")
|
||||
return []
|
||||
}
|
||||
|
||||
if (currentOS.value === 'linux') {
|
||||
return osExtensions["linux"]
|
||||
}
|
||||
|
||||
return osExtensions["windows"]
|
||||
}
|
||||
|
||||
function normalizeVersion(version: string): string {
|
||||
|
||||
@@ -33,8 +33,8 @@ export async function getOS() {
|
||||
|
||||
// This code is modified by AstralRinth
|
||||
export async function initUpdateLauncher(downloadUrl, filename, osType, autoUpdateSupported) {
|
||||
console.log('Downloading build', downloadUrl, filename, osType, autoUpdateSupported)
|
||||
return await invoke('plugin:utils|init_update_launcher', { downloadUrl, filename, osType, autoUpdateSupported })
|
||||
console.log('Downloading build', downloadUrl, filename, osType)
|
||||
return await invoke('plugin:utils|init_update_launcher', { downloadUrl, filename, osType })
|
||||
}
|
||||
|
||||
export async function isNetworkMetered() {
|
||||
|
||||
@@ -165,11 +165,17 @@
|
||||
"message": "You are using an older version. We recommend updating now for the latest fixes and improvements."
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.download-action": {
|
||||
"message": "Download update and close"
|
||||
"message": "Download update"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.header": {
|
||||
"message": "AstralRinth launcher update"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.installer-description": {
|
||||
"message": "Choose the installer package you want to continue with."
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.installer-title": {
|
||||
"message": "Installer type"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.installed-version": {
|
||||
"message": "💾 Installed & Running version:"
|
||||
},
|
||||
@@ -197,6 +203,9 @@
|
||||
"astralrinth.app.launcher-update-modal.update.repository-link": {
|
||||
"message": "Open the project repository"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.select-installer": {
|
||||
"message": "Select an installer"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.title": {
|
||||
"message": "A new version of the AstralRinth launcher is available."
|
||||
},
|
||||
|
||||
@@ -156,11 +156,17 @@
|
||||
"message": "Вы используете более старую версию. Рекомендуем обновиться сейчас, чтобы получить последние исправления и улучшения."
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.download-action": {
|
||||
"message": "Скачать обновление и закрыть"
|
||||
"message": "Скачать обновление"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.header": {
|
||||
"message": "Обновление лаунчера AstralRinth"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.installer-description": {
|
||||
"message": "Выберите пакет установщика, с которым хотите продолжить."
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.installer-title": {
|
||||
"message": "Тип установщика"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.installed-version": {
|
||||
"message": "💾 Установленная и запущенная версия:"
|
||||
},
|
||||
@@ -188,6 +194,9 @@
|
||||
"astralrinth.app.launcher-update-modal.update.repository-link": {
|
||||
"message": "Открыть репозиторий проекта"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.select-installer": {
|
||||
"message": "Выберите установщик"
|
||||
},
|
||||
"astralrinth.app.launcher-update-modal.update.title": {
|
||||
"message": "Доступна новая версия лаунчера AstralRinth."
|
||||
},
|
||||
|
||||
@@ -35,13 +35,11 @@ pub async fn init_update_launcher(
|
||||
download_url: &str,
|
||||
filename: &str,
|
||||
os_type: &str,
|
||||
auto_update_supported: bool,
|
||||
) -> Result<()> {
|
||||
let _ = utils::init_update_launcher(
|
||||
download_url,
|
||||
filename,
|
||||
os_type,
|
||||
auto_update_supported,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
|
||||
@@ -4,11 +4,12 @@ use tokio::fs::File as AsyncFile;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::process::Command;
|
||||
|
||||
const EMPTY_OPEN_ARGS: &[&str] = &[];
|
||||
|
||||
pub(crate) async fn get_resource(
|
||||
download_url: &str,
|
||||
local_filename: &str,
|
||||
os_type: &str,
|
||||
auto_update_supported: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let download_dir = dirs::download_dir()
|
||||
.ok_or("[AR] • Failed to determine download directory")?;
|
||||
@@ -20,23 +21,23 @@ pub(crate) async fn get_resource(
|
||||
dest_file.write_all(&bytes).await?;
|
||||
tracing::info!("[AR] • File downloaded to: {:?}", full_path);
|
||||
|
||||
if auto_update_supported {
|
||||
let result = match os_type.to_lowercase().as_str() {
|
||||
"windows" => handle_windows_file(&full_path).await,
|
||||
"macos" => open_macos_file(&full_path).await,
|
||||
_ => open_default(&full_path).await,
|
||||
};
|
||||
let result = match os_type.to_lowercase().as_str() {
|
||||
"windows" => handle_windows_file(&full_path).await,
|
||||
"macos" => open_macos_file(&full_path).await,
|
||||
_ => open_default(&full_path).await,
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => tracing::info!("[AR] • File opened successfully!"),
|
||||
Err(e) => tracing::info!("[AR] • Failed to open file: {e}"),
|
||||
}
|
||||
match result {
|
||||
Ok(_) => tracing::info!("[AR] • File opened successfully!"),
|
||||
Err(e) => tracing::info!("[AR] • Failed to open file: {e}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_windows_file(path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn handle_windows_file(
|
||||
path: &PathBuf,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let filename = path
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
@@ -51,7 +52,9 @@ async fn handle_windows_file(path: &PathBuf) -> Result<(), Box<dyn std::error::E
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_windows_installer(path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn run_windows_installer(
|
||||
path: &PathBuf,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let installer_path = path.to_str().unwrap_or_default();
|
||||
|
||||
let status = if installer_path.ends_with(".msi") {
|
||||
@@ -76,21 +79,15 @@ async fn run_windows_installer(path: &PathBuf) -> Result<(), Box<dyn std::error:
|
||||
}
|
||||
}
|
||||
|
||||
async fn open_windows_folder(path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let folder = path.parent().unwrap_or(path);
|
||||
let status = Command::new("explorer")
|
||||
.arg(folder.display().to_string())
|
||||
.status()
|
||||
.await?;
|
||||
|
||||
if !status.success() {
|
||||
Err(format!("Exit code: {:?}", status.code()).into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
async fn open_windows_folder(
|
||||
path: &PathBuf,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
open_in_file_manager(path, &[("explorer", EMPTY_OPEN_ARGS)]).await
|
||||
}
|
||||
|
||||
async fn open_macos_file(path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn open_macos_file(
|
||||
path: &PathBuf,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let status = Command::new("open")
|
||||
.arg(path.to_str().unwrap_or_default())
|
||||
.status()
|
||||
@@ -103,15 +100,49 @@ async fn open_macos_file(path: &PathBuf) -> Result<(), Box<dyn std::error::Error
|
||||
}
|
||||
}
|
||||
|
||||
async fn open_default(path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let status = Command::new(".")
|
||||
.arg(path.to_str().unwrap_or_default())
|
||||
.status()
|
||||
.await?;
|
||||
|
||||
if !status.success() {
|
||||
Err(format!("Exit code: {:?}", status.code()).into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
async fn open_default(
|
||||
path: &PathBuf,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
open_in_file_manager(
|
||||
path,
|
||||
&[
|
||||
("xdg-open", EMPTY_OPEN_ARGS),
|
||||
("gio", &["open"]),
|
||||
("kioclient5", &["exec"]),
|
||||
("kioclient", &["exec"]),
|
||||
("kde-open", EMPTY_OPEN_ARGS),
|
||||
],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn open_in_file_manager(
|
||||
path: &PathBuf,
|
||||
commands: &[(&str, &[&str])],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let folder = path.parent().unwrap_or(path);
|
||||
let folder_path = folder.to_str().unwrap_or_default();
|
||||
let mut last_error = None;
|
||||
|
||||
for (command, args) in commands {
|
||||
let mut process = Command::new(command);
|
||||
process.args(*args).arg(folder_path);
|
||||
|
||||
match process.status().await {
|
||||
Ok(status) if status.success() => return Ok(()),
|
||||
Ok(status) => {
|
||||
last_error = Some(format!(
|
||||
"{command} exited with code {:?}",
|
||||
status.code()
|
||||
));
|
||||
}
|
||||
Err(error) => {
|
||||
last_error = Some(format!("{command} failed: {error}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(last_error
|
||||
.unwrap_or_else(|| "No file manager command succeeded".to_string())
|
||||
.into())
|
||||
}
|
||||
|
||||
@@ -215,18 +215,15 @@ pub async fn init_update_launcher(
|
||||
download_url: &str,
|
||||
local_filename: &str,
|
||||
os_type: &str,
|
||||
auto_update_supported: bool,
|
||||
) -> Result<()> {
|
||||
tracing::info!("[AR] • Initialize downloading from • {:?}", download_url);
|
||||
tracing::info!("[AR] • Save local file name • {:?}", local_filename);
|
||||
tracing::info!("[AR] • OS type • {}", os_type);
|
||||
tracing::info!("[AR] • Auto update supported • {}", auto_update_supported);
|
||||
|
||||
if let Err(e) = update::get_resource(
|
||||
download_url,
|
||||
local_filename,
|
||||
os_type,
|
||||
auto_update_supported,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user