Merge pull request 'feature-improve-updater' (#6) from feature-improve-updater into beta

Reviewed-on: didirus/AstralRinth#6
This commit is contained in:
2025-07-11 04:10:01 +03:00
12 changed files with 329 additions and 256 deletions

View File

@@ -42,7 +42,7 @@ import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue'
import { handleError, useNotifications } from '@/store/notifications.js' import { handleError, useNotifications } from '@/store/notifications.js'
import { command_listener, warning_listener } from '@/helpers/events.js' import { command_listener, warning_listener } from '@/helpers/events.js'
import { type } from '@tauri-apps/plugin-os' import { type } from '@tauri-apps/plugin-os'
import { getOS, isDev, restartApp } from '@/helpers/utils.js' import { getOS, isDev } from '@/helpers/utils.js'
import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics' import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
import { getCurrentWindow } from '@tauri-apps/api/window' import { getCurrentWindow } from '@tauri-apps/api/window'
import { getVersion } from '@tauri-apps/api/app' import { getVersion } from '@tauri-apps/api/app'
@@ -72,6 +72,9 @@ import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
import { get_available_capes, get_available_skins } from './helpers/skins' import { get_available_capes, get_available_skins } from './helpers/skins'
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer' import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
// [AR] Feature
import { getRemote, updateState } from '@/helpers/update.js'
const themeStore = useTheming() const themeStore = useTheming()
const news = ref([]) const news = ref([])
@@ -99,6 +102,7 @@ const isMaximized = ref(false)
onMounted(async () => { onMounted(async () => {
await useCheckDisableMouseover() await useCheckDisableMouseover()
await getRemote(false) // [AR] Check for updates
document.querySelector('body').addEventListener('click', handleClick) document.querySelector('body').addEventListener('click', handleClick)
document.querySelector('body').addEventListener('auxclick', handleAuxClick) document.querySelector('body').addEventListener('auxclick', handleAuxClick)
@@ -465,12 +469,20 @@ function handleAuxClick(e) {
<PlusIcon /> <PlusIcon />
</NavButton> </NavButton>
<div class="flex flex-grow"></div> <div class="flex flex-grow"></div>
<NavButton v-if="updateAvailable" v-tooltip.right="'Install update'" :to="() => restartApp()"> <!-- [AR] TODO -->
<!-- <NavButton v-if="updateAvailable" v-tooltip.right="'Install update'" :to="() => restartApp()">
<DownloadIcon /> <DownloadIcon />
</NavButton> </NavButton> -->
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()"> <template v-if="updateState">
<SettingsIcon /> <NavButton class="neon-icon pulse" v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
</NavButton> <SettingsIcon />
</NavButton>
</template>
<template v-else>
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
<SettingsIcon />
</NavButton>
</template>
<ButtonStyled v-if="credentials" type="transparent" circular> <ButtonStyled v-if="credentials" type="transparent" circular>
<OverflowMenu <OverflowMenu
:options="[ :options="[
@@ -659,6 +671,9 @@ function handleAuxClick(e) {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../packages/assets/styles/neon-icon.scss';
@import '../../../packages/assets/styles/neon-text.scss';
.window-controls { .window-controls {
z-index: 20; z-index: 20;
display: none; display: none;

View File

@@ -19,6 +19,7 @@ import { install } from '@/helpers/profile.js'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { applyMigrationFix } from '@/helpers/utils.js' import { applyMigrationFix } from '@/helpers/utils.js'
import { restartApp } from '@/helpers/utils.js'
const errorModal = ref() const errorModal = ref()
const error = ref() const error = ref()
@@ -168,6 +169,11 @@ async function onApplyMigrationFix(eol) {
migrationFixSuccess.value = false migrationFixSuccess.value = false
} finally { } finally {
migrationFixCallbackModel.value?.show?.() migrationFixCallbackModel.value?.show?.()
if (migrationFixSuccess.value === true) {
setTimeout(async () => {
await restartApp()
}, 3000)
}
} }
} }
@@ -321,12 +327,12 @@ async function onApplyMigrationFix(eol) {
<template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template> <template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template>
<template v-else> <CopyIcon /> Copy debug info </template> <template v-else> <CopyIcon /> Copy debug info </template>
</button> </button>
<ButtonStyled class="btn-wrapper neon"> <ButtonStyled class="neon-button neon">
<a href="https://me.astralium.su/get/ar/help" target="_blank" rel="noopener noreferrer"> <a href="https://me.astralium.su/get/ar/help" target="_blank" rel="noopener noreferrer">
Get AstralRinth support Get AstralRinth support
</a> </a>
</ButtonStyled> </ButtonStyled>
<ButtonStyled class="btn-wrapper neon" > <ButtonStyled class="neon-button neon" >
<a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer"> <a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">
Checkout latest releases Checkout latest releases
</a> </a>
@@ -334,7 +340,7 @@ async function onApplyMigrationFix(eol) {
</ButtonStyled> </ButtonStyled>
</div> </div>
<template v-if="hasDebugInfo"> <template v-if="hasDebugInfo">
<div class="bg-button-bg rounded-xl mt-2 overflow-clip"> <div class="bg-button-bg rounded-xl mt-2 overflow-hidden">
<button <button
class="flex items-center justify-between w-full bg-transparent border-0 px-4 py-3 cursor-pointer" class="flex items-center justify-between w-full bg-transparent border-0 px-4 py-3 cursor-pointer"
@click="errorCollapsed = !errorCollapsed" @click="errorCollapsed = !errorCollapsed"
@@ -346,7 +352,9 @@ async function onApplyMigrationFix(eol) {
/> />
</button> </button>
<Collapsible :collapsed="errorCollapsed"> <Collapsible :collapsed="errorCollapsed">
<pre class="m-0 px-4 py-3 bg-bg rounded-none">{{ debugInfo }}</pre> <pre
class="m-0 px-4 py-3 bg-bg rounded-none whitespace-pre-wrap break-words overflow-x-auto max-w-full"
>{{ debugInfo }}</pre>
</Collapsible> </Collapsible>
</div> </div>
<template v-if="errorType === 'state_init'"> <template v-if="errorType === 'state_init'">
@@ -392,7 +400,7 @@ async function onApplyMigrationFix(eol) {
<div class="flex justify-between"> <div class="flex justify-between">
<ol class="flex flex-col gap-3"> <ol class="flex flex-col gap-3">
<li> <li>
<ButtonStyled class="btn-wrapper neon"> <ButtonStyled class="neon-button neon">
<button <button
:title="language === 'en' :title="language === 'en'
? 'Convert all line endings in migration files to LF (Unix-style: \\n)' ? 'Convert all line endings in migration files to LF (Unix-style: \\n)'
@@ -405,7 +413,7 @@ async function onApplyMigrationFix(eol) {
</ButtonStyled> </ButtonStyled>
</li> </li>
<li> <li>
<ButtonStyled class="btn-wrapper neon"> <ButtonStyled class="neon-button neon">
<button <button
:title="language === 'en' :title="language === 'en'
? 'Convert all line endings in migration files to CRLF (Windows-style: \\r\\n)' ? 'Convert all line endings in migration files to CRLF (Windows-style: \\r\\n)'
@@ -432,13 +440,13 @@ async function onApplyMigrationFix(eol) {
<div class="modal-body"> <div class="modal-body">
<h2 class="text-lg font-bold text-contrast space-y-2"> <h2 class="text-lg font-bold text-contrast space-y-2">
<template v-if="migrationFixSuccess === true"> <template v-if="migrationFixSuccess === true">
<p class="flex items-center gap-2 text-green-600"> <p class="flex items-center gap-2 neon-text">
{{ language === 'en' {{ language === 'en'
? 'The migration fix has been applied successfully. Please restart the launcher and try to log in to the game :)' ? 'The migration fix has been applied successfully. Please restart the launcher and try to log in to the game :)'
: 'Исправление миграции успешно применено. Пожалуйста, перезапустите лаунчер и попробуйте снова авторизоваться в игре :)' }} : 'Исправление миграции успешно применено. Пожалуйста, перезапустите лаунчер и попробуйте снова авторизоваться в игре :)' }}
</p> </p>
<p class="mt-2 text-sm text-gray-600"> <p class="mt-2 text-sm neon-text">
{{ language === 'en' {{ language === 'en'
? 'If the problem persists, please try the other fix.' ? 'If the problem persists, please try the other fix.'
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }} : 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
@@ -446,13 +454,13 @@ async function onApplyMigrationFix(eol) {
</template> </template>
<template v-else-if="migrationFixSuccess === false"> <template v-else-if="migrationFixSuccess === false">
<p class="flex items-center gap-2 text-red-600"> <p class="flex items-center gap-2 neon-text">
{{ language === 'en' {{ language === 'en'
? 'The migration fix failed or had no effect.' ? 'The migration fix failed or had no effect.'
: 'Исправление миграции не было успешно применено или не имело эффекта.' }} : 'Исправление миграции не было успешно применено или не имело эффекта.' }}
</p> </p>
<p class="mt-2 text-sm text-gray-600"> <p class="mt-2 text-sm neon-text">
{{ language === 'en' {{ language === 'en'
? 'If the problem persists, please try the other fix.' ? 'If the problem persists, please try the other fix.'
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }} : 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
@@ -476,6 +484,7 @@ async function onApplyMigrationFix(eol) {
<style scoped lang="scss"> <style scoped lang="scss">
@import '../../../../../packages/assets/styles/neon-button.scss'; @import '../../../../../packages/assets/styles/neon-button.scss';
@import '../../../../../packages/assets/styles/neon-text.scss';
.cta-button { .cta-button {
display: flex; display: flex;

View File

@@ -36,60 +36,6 @@
<span class="circle stopped" /> <span class="circle stopped" />
<span class="running-text"> No instances running </span> <span class="running-text"> No instances running </span>
</div> </div>
<div v-if="updateState">
<a>
<Button class="download" :disabled="installState" @click="initUpdateModal(), getRemote(false)">
<DownloadIcon />
{{
installState
? "Downloading new update..."
: "Download new update"
}}
</Button>
</a>
</div>
<ModalWrapper ref="updateModalView" :has-to-type="false" header="Request to update the AstralRinth launcher">
<div class="modal-body">
<div class="markdown-body">
<p>The new version of the AstralRinth launcher is available.</p>
<p>Your version is outdated. We recommend that you update to the latest version.</p>
<p><strong> Warning </strong></p>
<p>
Before updating, make sure that you have saved all running instances and made a backup copy of the instances
that are valuable to you. Remember that the authors of the product are not responsible for the breakdown of
your files, so you should always make copies of them and keep them in a safe place.
</p>
</div>
<span>Source Git Astralium</span>
<span>Version on remote server <p id="releaseData" class="cosmic inline-fix"></p></span>
<span>Version on local device
<p class="cosmic inline-fix">v{{ version }}</p>
</span>
<div class="button-group push-right">
<Button class="updater-modal" @click="updateModalView.hide()">
Cancel</Button>
<Button class="updater-modal" @click="initDownload()">
Download file
</Button>
</div>
</div>
</ModalWrapper>
<ModalWrapper ref="updateRequestFailView" :has-to-type="false" header="Failed to request a file from the server :(">
<div class="modal-body">
<div class="markdown-body">
<p><strong>Error occurred</strong></p>
<p>Unfortunately, the program was unable to download the file from our servers.</p>
<p>Please try downloading it yourself from <a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">Git Astralium</a> if there are any updates available.</p>
</div>
<span>Local AstralRinth
<p class="cosmic inline-fix">v{{ version }}</p>
</span>
</div>
<div class="button-group push-right">
<Button class="updater-modal" @click="updateRequestFailView.hide()">
Close</Button>
</div>
</ModalWrapper>
</div> </div>
<transition name="download"> <transition name="download">
<Card v-if="showCard === true && currentLoadingBars.length > 0" ref="card" class="info-card"> <Card v-if="showCard === true && currentLoadingBars.length > 0" ref="card" class="info-card">
@@ -138,29 +84,6 @@ import ProgressBar from '@/components/ui/ProgressBar.vue'
import { handleError } from '@/store/notifications.js' import { handleError } from '@/store/notifications.js'
import { get_many } from '@/helpers/profile.js' import { get_many } from '@/helpers/profile.js'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import { getVersion } from '@tauri-apps/api/app'
const version = await getVersion()
import { installState, getRemote, updateState } from '@/helpers/update.js'
import ModalWrapper from './modal/ModalWrapper.vue'
const updateModalView = ref(null)
const updateRequestFailView = ref(null)
const initUpdateModal = async () => {
updateModalView.value.show()
}
const initDownload = async () => {
updateModalView.value.hide()
const result = await getRemote(true);
if (!result) {
updateRequestFailView.value.show()
}
}
await getRemote(false)
const router = useRouter() const router = useRouter()
const card = ref(null) const card = ref(null)
@@ -318,101 +241,6 @@ onBeforeUnmount(() => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.inline-fix {
display: inline-flex;
margin-top: -2rem;
margin-bottom: -2rem;
//margin-left: 0.3rem;
}
.cosmic {
color: #3e8cde;
text-decoration: none;
text-shadow:
0 0 4px rgba(79, 173, 255, 0.5),
0 0 8px rgba(14, 98, 204, 0.5),
0 0 12px rgba(122, 31, 199, 0.5);
transition: color 0.35s ease;
}
.markdown-body {
:deep(table) {
width: auto;
}
:deep(hr),
:deep(h1),
:deep(h2) {
max-width: max(60rem, 90%);
}
:deep(ul),
:deep(ol) {
margin-left: 2rem;
}
}
.modal-body {
display: flex;
flex-direction: column;
gap: 1rem;
padding: var(--gap-lg);
text-align: left;
.button-group {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
strong {
color: var(--color-contrast);
}
}
.download {
color: #3e8cde;
border-radius: var(--radius-md);
border: 1px solid var(--color-button-bg);
// padding: var(--gap-sm) var(--gap-lg);
background-color: rgba(0, 0, 0, 0);
text-decoration: none;
text-shadow:
0 0 4px rgba(79, 173, 255, 0.5),
0 0 8px rgba(14, 98, 204, 0.5),
0 0 12px rgba(122, 31, 199, 0.5);
transition: color 0.35s ease;
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
}
.download:hover,
.download:focus,
.download:active {
color: #10fae5;
text-shadow: #26065e;
}
.updater-modal {
color: #3e8cde;
padding: var(--gap-sm) var(--gap-lg);
text-decoration: none;
text-shadow:
0 0 4px rgba(79, 173, 255, 0.5),
0 0 8px rgba(14, 98, 204, 0.5),
0 0 12px rgba(122, 31, 199, 0.5);
transition: color 0.35s ease;
}
.updater-modal:hover,
.updater-modal:focus,
.updater-modal:active {
color: #10fae5;
text-shadow: #26065e;
}
.action-groups { .action-groups {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@@ -8,6 +8,8 @@ import {
PaintbrushIcon, PaintbrushIcon,
GameIcon, GameIcon,
CoffeeIcon, CoffeeIcon,
DownloadIcon,
SpinnerIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { TabbedModal } from '@modrinth/ui' import { TabbedModal } from '@modrinth/ui'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
@@ -23,6 +25,23 @@ import { useTheming } from '@/store/state'
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue' import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get, set } from '@/helpers/settings.ts' import { get, set } from '@/helpers/settings.ts'
// [AR] Imports
import { installState, getRemote, updateState } from '@/helpers/update.js'
const updateModalView = ref(null)
const updateRequestFailView = ref(null)
const initUpdateModal = async () => {
updateModalView.value.show()
}
const initDownload = async () => {
updateModalView.value.hide()
const result = await getRemote(true);
if (!result) {
updateRequestFailView.value.show()
}
}
const themeStore = useTheming() const themeStore = useTheming()
@@ -141,8 +160,7 @@ function devModeCount() {
<button <button
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation" class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
:class="{ 'text-brand': themeStore.devMode, 'text-secondary': !themeStore.devMode }" :class="{ 'text-brand': themeStore.devMode, 'text-secondary': !themeStore.devMode }"
@click="devModeCount" @click="devModeCount">
>
<AstralRinthLogo class="w-6 h-6" /> <AstralRinthLogo class="w-6 h-6" />
</button> </button>
<div> <div>
@@ -153,9 +171,80 @@ function devModeCount() {
{{ osVersion }} {{ osVersion }}
</p> </p>
</div> </div>
<div v-if="updateState" class="w-8 h-8 cursor-pointer hover:brightness-75 neon-icon pulse">
<template v-if="installState">
<SpinnerIcon class="size-6 animate-spin" v-tooltip.bottom="'Installing in process...'" />
</template>
<template v-else>
<DownloadIcon class="size-6" v-tooltip.bottom="'View update info'" @click="!installState && (initUpdateModal(), getRemote(false))" />
</template>
</div>
</div> </div>
</div> </div>
</template> </template>
</TabbedModal> </TabbedModal>
<!-- [AR] Feature -->
<ModalWrapper ref="updateModalView" :has-to-type="false" header="Request to update the AstralRinth launcher">
<div class="space-y-4">
<div class="space-y-2">
<p>The new version of the AstralRinth launcher is available.</p>
<p>Your version is outdated. We recommend that you update to the latest version.</p>
<p><strong> Warning </strong></p>
<p>
Before updating, make sure that you have saved all running instances and made a backup copy of the instances
that are valuable to you. Remember that the authors of the product are not responsible for the breakdown of
your files, so you should always make copies of them and keep them in a safe place.
</p>
</div>
<div class="text-sm text-secondary space-y-1">
<a class="neon-text" href="https://me.astralium.su/get/ar" target="_blank"
rel="noopener noreferrer"><strong>Source:</strong> Git Astralium</a>
<p>
<strong>Version on remote server:</strong>
<span id="releaseData" class="neon-text"></span>
</p>
<p>
<strong>Version on local device:</strong>
<span class="neon-text">v{{ version }}</span>
</p>
</div>
<div class="absolute bottom-4 right-4 flex items-center gap-4 neon-button neon">
<Button class="bordered" @click="updateModalView.hide()">Cancel</Button>
<Button class="bordered" @click="initDownload()">Download file</Button>
</div>
</div>
</ModalWrapper>
<ModalWrapper ref="updateRequestFailView" :has-to-type="false" header="Failed to request a file from the server :(">
<div class="space-y-4">
<div class="space-y-2">
<p><strong>Error occurred</strong></p>
<p>Unfortunately, the program was unable to download the file from our servers.</p>
<p>
Please try downloading it yourself from
<a class="neon-text" href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">Git
Astralium</a>
if there are any updates available.
</p>
</div>
<div class="text-sm text-secondary">
<p>
<strong>Local AstralRinth:</strong>
<span class="neon-text">v{{ version }}</span>
</p>
</div>
<div class="absolute bottom-4 right-4 flex items-center gap-4 neon-button neon">
<Button class="bordered" @click="updateRequestFailView.hide()">Close</Button>
</div>
</div>
</ModalWrapper>
</ModalWrapper> </ModalWrapper>
</template> </template>
<style lang="scss" scoped>
@import '../../../../../../packages/assets/styles/neon-icon.scss';
@import '../../../../../../packages/assets/styles/neon-button.scss';
@import '../../../../../../packages/assets/styles/neon-text.scss';
</style>

View File

@@ -11,7 +11,7 @@ const releaseLink = `https://git.astralium.su/api/v1/repos/didirus/AstralRinth/r
const failedFetch = [`Failed to fetch remote releases:`, `Failed to fetch remote commits:`] const failedFetch = [`Failed to fetch remote releases:`, `Failed to fetch remote commits:`]
const osList = ['macos', 'windows', 'linux'] const osList = ['macos', 'windows', 'linux']
const macExtensionList = ['.app', '.dmg'] const macExtensionList = ['.dmg', '.pkg']
const windowsExtensionList = ['.exe', '.msi'] const windowsExtensionList = ['.exe', '.msi']
const blacklistPrefixes = [ const blacklistPrefixes = [

View File

@@ -157,7 +157,7 @@ fn main() {
*/ */
let _log_guard = theseus::start_logger(); let _log_guard = theseus::start_logger();
tracing::info!("Initialized tracing subscriber. Loading Modrinth App!"); tracing::info!("Initialized tracing subscriber. Loading AstralRinth App!");
let mut builder = tauri::Builder::default(); let mut builder = tauri::Builder::default();

View File

@@ -1,42 +1,117 @@
use reqwest; use reqwest;
use std::path::PathBuf;
use tokio::fs::File as AsyncFile; use tokio::fs::File as AsyncFile;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::process::Command; use tokio::process::Command;
pub(crate) async fn download_file(download_url: &str, local_filename: &str, os_type: &str, auto_update_supported: bool) -> Result<(), Box<dyn std::error::Error>> { pub(crate) async fn get_resource(
let download_dir = dirs::download_dir().ok_or("[AR] • Failed to determine download directory")?; 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")?;
let full_path = download_dir.join(local_filename); let full_path = download_dir.join(local_filename);
let response = reqwest::get(download_url).await?; let response = reqwest::get(download_url).await?;
let bytes = response.bytes().await?; let bytes = response.bytes().await?;
let mut dest_file = AsyncFile::create(&full_path).await?; let mut dest_file = AsyncFile::create(&full_path).await?;
dest_file.write_all(&bytes).await?; dest_file.write_all(&bytes).await?;
println!("[AR] • File downloaded to: {:?}", full_path); tracing::info!("[AR] • File downloaded to: {:?}", full_path);
if auto_update_supported { if auto_update_supported {
let status; let result = match os_type.to_lowercase().as_str() {
if os_type.to_lowercase() == "Windows".to_lowercase() { "windows" => handle_windows_file(&full_path).await,
status = Command::new("explorer") "macos" => open_macos_file(&full_path).await,
.arg(download_dir.display().to_string()) _ => open_default(&full_path).await,
.status() };
.await
.expect("[AR] • Failed to open downloads folder"); match result {
} else if os_type.to_lowercase() == "MacOS".to_lowercase() { Ok(_) => tracing::info!("[AR] • File opened successfully!"),
status = Command::new("open") Err(e) => tracing::info!("[AR] • Failed to open file: {e}"),
.arg(full_path.to_str().unwrap_or_default())
.status()
.await
.expect("[AR] • Failed to execute command");
} else {
status = Command::new(".")
.arg(full_path.to_str().unwrap_or_default())
.status()
.await
.expect("[AR] • Failed to execute command");
}
if status.success() {
println!("[AR] • File opened successfully!");
} else {
eprintln!("[AR] • Failed to open the file. Exit code: {:?}", status.code());
} }
} }
Ok(()) Ok(())
} }
async fn handle_windows_file(path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let filename = path
.file_name()
.and_then(|f| f.to_str())
.unwrap_or_default()
.to_lowercase();
if filename.ends_with(".exe") || filename.ends_with(".msi") {
tracing::info!("[AR] • Detected installer: {}", filename);
run_windows_installer(path).await
} else {
open_windows_folder(path).await
}
}
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") {
Command::new("msiexec")
.args(&["/i", installer_path, "/quiet"])
.status()
.await?
} else {
Command::new("cmd")
.args(&["/C", installer_path])
.status()
.await?
};
if status.success() {
tracing::info!("[AR] • Installer started successfully.");
Ok(())
} else {
tracing::error!("Installer failed. Exit code: {:?}", status.code());
tracing::info!("[AR] • Trying to open folder...");
open_windows_folder(path).await
}
}
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_macos_file(path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let status = Command::new("open")
.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>> {
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(())
}
}

View File

@@ -1,5 +1,5 @@
use crate::state::DirectoryInfo;
use crate::ErrorKind; use crate::ErrorKind;
use crate::state::DirectoryInfo;
use sqlx::sqlite::{ use sqlx::sqlite::{
SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions,
}; };
@@ -78,23 +78,23 @@ Problem files, view detailed information in .gitattributes:
CRLF -> 4c47e326f16f2b1efca548076ce638d4c90dd610172fe48c47d6de9bc46ef1c5abeadfdea05041ddd72c3819fa10c040 CRLF -> 4c47e326f16f2b1efca548076ce638d4c90dd610172fe48c47d6de9bc46ef1c5abeadfdea05041ddd72c3819fa10c040
LF -> e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21 LF -> e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21
/packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol /packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol
CRLF -> 10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9 CRLF -> C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D
LF -> 5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206 LF -> 5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206
/packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol /packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol
CRLF -> c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702 CRLF -> C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57
LF -> c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57 LF -> c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57
/packages/app-lib/migrations/20241222013857_feature-flags.sql !eol /packages/app-lib/migrations/20241222013857_feature-flags.sql !eol
CRLF -> f8c55065e2563fa4738976eb13a052ae4c28da8d33143185550f6e1cee394a3243b1dca090b3e8bc50a93a8286a78c09 CRLF -> 6B6F097E5BB45A397C96C3F1DC9C2A18433564E81DB264FE08A4775198CCEAC03C9E63C3605994ECB19C281C37D8F6AE
LF -> c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704 LF -> c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704
*/ */
pub(crate) async fn fix_version_hash( pub(crate) async fn apply_migration_fix(eol: &str) -> crate::Result<bool> {
eol: &str,
) -> crate::Result<bool> {
let started = Instant::now(); let started = Instant::now();
// Create connection to the database without migrations // Create connection to the database without migrations
let pool = connect_without_migrate().await?; let pool = connect_without_migrate().await?;
tracing::info!("⚙️ Patching Modrinth corrupted migration checksums using EOL standard: {eol}"); tracing::info!(
"⚙️ Patching Modrinth corrupted migration checksums using EOL standard: {eol}"
);
// validate EOL input // validate EOL input
if eol != "lf" && eol != "crlf" { if eol != "lf" && eol != "crlf" {
@@ -117,7 +117,7 @@ pub(crate) async fn fix_version_hash(
), ),
( (
("crlf", "20240813205023"), ("crlf", "20240813205023"),
"10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9", "C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D",
), ),
( (
("lf", "20240930001852"), ("lf", "20240930001852"),
@@ -125,7 +125,7 @@ pub(crate) async fn fix_version_hash(
), ),
( (
("crlf", "20240930001852"), ("crlf", "20240930001852"),
"c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702", "C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57",
), ),
( (
("lf", "20241222013857"), ("lf", "20241222013857"),
@@ -133,7 +133,7 @@ pub(crate) async fn fix_version_hash(
), ),
( (
("crlf", "20241222013857"), ("crlf", "20241222013857"),
"f8c55065e2563fa4738976eb13a052ae4c28da8d33143185550f6e1cee394a3243b1dca090b3e8bc50a93a8286a78c09", "6B6F097E5BB45A397C96C3F1DC9C2A18433564E81DB264FE08A4775198CCEAC03C9E63C3605994ECB19C281C37D8F6AE",
), ),
]); ]);

View File

@@ -26,7 +26,7 @@ pub fn read_package_json() -> io::Result<Launcher> {
pub async fn apply_migration_fix(eol: &str) -> Result<bool> { pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
tracing::info!("[AR] • Attempting to apply migration fix"); tracing::info!("[AR] • Attempting to apply migration fix");
let patched = db::fix_version_hash(eol).await?; let patched = db::apply_migration_fix(eol).await?;
if patched { if patched {
tracing::info!("[AR] • Successfully applied migration fix"); tracing::info!("[AR] • Successfully applied migration fix");
} else { } else {
@@ -43,7 +43,7 @@ pub async fn init_download(
) -> Result<()> { ) -> Result<()> {
println!("[AR] • Initialize downloading from • {:?}", download_url); println!("[AR] • Initialize downloading from • {:?}", download_url);
println!("[AR] • Save local file name • {:?}", local_filename); println!("[AR] • Save local file name • {:?}", local_filename);
if let Err(e) = update::download_file( if let Err(e) = update::get_resource(
download_url, download_url,
local_filename, local_filename,
os_type, os_type,

View File

@@ -1,13 +1,8 @@
// [AR] Feature // [AR] Feature
.btn-wrapper.neon :deep(:is(button, a, .button-like):first-child), .neon-button.neon :deep(:is(button, a, .button-like)),
.btn-wrapper.neon :slotted(:is(button, a, .button-like):first-child), .neon-button.neon :slotted(:is(button, a, .button-like)),
.btn-wrapper.neon :slotted(*) > :is(button, a, .button-like):first-child, .neon-button.neon :slotted(*) :is(button, a, .button-like) {
.btn-wrapper.neon :slotted(*) > *:first-child > :is(button, a, .button-like):first-child, cursor: pointer;
.btn-wrapper.neon
:slotted(*)
> *:first-child
> *:first-child
> :is(button, a, .button-like):first-child {
background-color: transparent; background-color: transparent;
border: 1px solid #3e8cde; border: 1px solid #3e8cde;
color: #3e8cde; color: #3e8cde;
@@ -22,20 +17,17 @@
box-shadow: 0 0 4px rgba(79, 173, 255, 0.5); box-shadow: 0 0 4px rgba(79, 173, 255, 0.5);
} }
.bordered {
border-radius: 12px;
}
/* Hover */ /* Hover */
.btn-wrapper.neon .neon-button.neon
:deep(:is(button, a, .button-like):first-child):hover:not([disabled]):not(.disabled), :deep(:is(button, a, .button-like):hover):not([disabled]):not(.disabled),
.btn-wrapper.neon .neon-button.neon
:slotted(:is(button, a, .button-like):first-child):hover:not([disabled]):not(.disabled), :slotted(:is(button, a, .button-like):hover):not([disabled]):not(.disabled),
.btn-wrapper.neon .neon-button.neon
:slotted(*) > :is(button, a, .button-like):first-child:hover:not([disabled]):not(.disabled), :slotted(*) :is(button, a, .button-like):hover:not([disabled]):not(.disabled) {
.btn-wrapper.neon
:slotted(*) > *:first-child > :is(button, a, .button-like):first-child:hover:not([disabled]):not(.disabled),
.btn-wrapper.neon
:slotted(*)
> *:first-child
> *:first-child
> :is(button, a, .button-like):first-child:hover:not([disabled]):not(.disabled) {
color: #10fae5; color: #10fae5;
transform: scale(1.02); transform: scale(1.02);
box-shadow: box-shadow:

View File

@@ -0,0 +1,37 @@
// [AR] Feature
.neon-icon {
background-color: transparent;
color: #3e8cde;
text-shadow:
0 0 4px rgba(79, 173, 255, 0.5),
0 0 8px rgba(14, 98, 204, 0.5),
0 0 12px rgba(122, 31, 199, 0.5);
transition: transform 0.25s ease, color 0.25s ease, text-shadow 0.25s ease;
cursor: pointer;
display: inline-block;
}
/* Hover */
.neon-icon:hover {
color: #10fae5;
transform: scale(1.05);
text-shadow:
0 0 2px rgba(16, 250, 229, 0.4),
0 0 4px rgba(16, 250, 229, 0.25);
}
.neon-icon.pulse {
position: relative;
animation: neon-pulse 1s ease-in-out infinite;
filter: drop-shadow(0 0 6px #10fae5);
box-shadow: none;
}
@keyframes neon-pulse {
0%, 100% {
filter: drop-shadow(0 0 4px #10fae5);
}
50% {
filter: drop-shadow(0 0 12px #10fae5);
}
}

View File

@@ -0,0 +1,28 @@
// [AR] Feature
.neon-text {
background-color: transparent;
color: #3e8cde;
text-shadow:
0 0 4px rgba(79, 173, 255, 0.5),
0 0 8px rgba(14, 98, 204, 0.5),
0 0 12px rgba(122, 31, 199, 0.5);
transition:
color 0.25s ease,
box-shadow 0.3s ease,
transform 0.15s ease;
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;
display: inline-block;
padding: 4px 8px;
}
/* Hover */
.neon-text:hover:not([disabled]):not(.disabled) {
color: #10fae5;
text-shadow:
0 0 2px rgba(16, 250, 229, 0.4),
0 0 4px rgba(16, 250, 229, 0.25);
}