feature-improve-updater #6

Merged
didirus merged 7 commits from feature-improve-updater into beta 2025-07-11 01:10:02 +00: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 { command_listener, warning_listener } from '@/helpers/events.js'
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 { getCurrentWindow } from '@tauri-apps/api/window'
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 { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
// [AR] Feature
import { getRemote, updateState } from '@/helpers/update.js'
const themeStore = useTheming()
const news = ref([])
@@ -99,6 +102,7 @@ const isMaximized = ref(false)
onMounted(async () => {
await useCheckDisableMouseover()
await getRemote(false) // [AR] Check for updates
document.querySelector('body').addEventListener('click', handleClick)
document.querySelector('body').addEventListener('auxclick', handleAuxClick)
@@ -465,12 +469,20 @@ function handleAuxClick(e) {
<PlusIcon />
</NavButton>
<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 />
</NavButton>
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
<SettingsIcon />
</NavButton>
</NavButton> -->
<template v-if="updateState">
<NavButton class="neon-icon pulse" v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
<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>
<OverflowMenu
:options="[
@@ -659,6 +671,9 @@ function handleAuxClick(e) {
</template>
<style lang="scss" scoped>
@import '../../../packages/assets/styles/neon-icon.scss';
@import '../../../packages/assets/styles/neon-text.scss';
.window-controls {
z-index: 20;
display: none;

View File

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

View File

@@ -36,60 +36,6 @@
<span class="circle stopped" />
<span class="running-text"> No instances running </span>
</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>
<transition name="download">
<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 { get_many } from '@/helpers/profile.js'
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 card = ref(null)
@@ -318,101 +241,6 @@ onBeforeUnmount(() => {
</script>
<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 {
display: flex;
flex-direction: row;

View File

@@ -8,6 +8,8 @@ import {
PaintbrushIcon,
GameIcon,
CoffeeIcon,
DownloadIcon,
SpinnerIcon,
} from '@modrinth/assets'
import { TabbedModal } from '@modrinth/ui'
import { computed, ref, watch } from 'vue'
@@ -23,6 +25,23 @@ import { useTheming } from '@/store/state'
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
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()
@@ -138,11 +157,10 @@ function devModeCount() {
{{ formatMessage(developerModeEnabled) }}
</p>
<div class="flex items-center gap-3">
<button
<button
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
:class="{ 'text-brand': themeStore.devMode, 'text-secondary': !themeStore.devMode }"
@click="devModeCount"
>
@click="devModeCount">
<AstralRinthLogo class="w-6 h-6" />
</button>
<div>
@@ -153,9 +171,80 @@ function devModeCount() {
{{ osVersion }}
</p>
</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>
</template>
</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>
</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 osList = ['macos', 'windows', 'linux']
const macExtensionList = ['.app', '.dmg']
const macExtensionList = ['.dmg', '.pkg']
const windowsExtensionList = ['.exe', '.msi']
const blacklistPrefixes = [

View File

@@ -157,7 +157,7 @@ fn main() {
*/
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();

View File

@@ -1,42 +1,117 @@
use reqwest;
use std::path::PathBuf;
use tokio::fs::File as AsyncFile;
use tokio::io::AsyncWriteExt;
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>> {
let download_dir = dirs::download_dir().ok_or("[AR] • Failed to determine download directory")?;
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")?;
let full_path = download_dir.join(local_filename);
let response = reqwest::get(download_url).await?;
let bytes = response.bytes().await?;
let mut dest_file = AsyncFile::create(&full_path).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 {
let status;
if os_type.to_lowercase() == "Windows".to_lowercase() {
status = Command::new("explorer")
.arg(download_dir.display().to_string())
.status()
.await
.expect("[AR] • Failed to open downloads folder");
} else if os_type.to_lowercase() == "MacOS".to_lowercase() {
status = Command::new("open")
.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());
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}"),
}
}
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::state::DirectoryInfo;
use sqlx::sqlite::{
SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions,
};
@@ -78,23 +78,23 @@ Problem files, view detailed information in .gitattributes:
CRLF -> 4c47e326f16f2b1efca548076ce638d4c90dd610172fe48c47d6de9bc46ef1c5abeadfdea05041ddd72c3819fa10c040
LF -> e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21
/packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol
CRLF -> 10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9
CRLF -> C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D
LF -> 5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206
/packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol
CRLF -> c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702
CRLF -> C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57
LF -> c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57
/packages/app-lib/migrations/20241222013857_feature-flags.sql !eol
CRLF -> f8c55065e2563fa4738976eb13a052ae4c28da8d33143185550f6e1cee394a3243b1dca090b3e8bc50a93a8286a78c09
CRLF -> 6B6F097E5BB45A397C96C3F1DC9C2A18433564E81DB264FE08A4775198CCEAC03C9E63C3605994ECB19C281C37D8F6AE
LF -> c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704
*/
pub(crate) async fn fix_version_hash(
eol: &str,
) -> crate::Result<bool> {
pub(crate) async fn apply_migration_fix(eol: &str) -> crate::Result<bool> {
let started = Instant::now();
// Create connection to the database without migrations
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
if eol != "lf" && eol != "crlf" {
@@ -117,7 +117,7 @@ pub(crate) async fn fix_version_hash(
),
(
("crlf", "20240813205023"),
"10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9",
"C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D",
),
(
("lf", "20240930001852"),
@@ -125,7 +125,7 @@ pub(crate) async fn fix_version_hash(
),
(
("crlf", "20240930001852"),
"c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702",
"C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57",
),
(
("lf", "20241222013857"),
@@ -133,7 +133,7 @@ pub(crate) async fn fix_version_hash(
),
(
("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> {
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 {
tracing::info!("[AR] • Successfully applied migration fix");
} else {
@@ -43,7 +43,7 @@ pub async fn init_download(
) -> Result<()> {
println!("[AR] • Initialize downloading from • {:?}", download_url);
println!("[AR] • Save local file name • {:?}", local_filename);
if let Err(e) = update::download_file(
if let Err(e) = update::get_resource(
download_url,
local_filename,
os_type,

View File

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