chore: replace json config with database

This commit is contained in:
HorizonCode committed 2026-05-28 11:35:34 +02:00
1 parent 2ba094f07e
commit 20e8389409
16 files changed
+761 -226

No files matched your search

@@ -1,6 +1,7 @@
<script lang="ts">
import { config } from '$lib/config';
import { custom_theme_audio_playing, custom_theme_volume } from '$lib/global';
import { reduceAnimations, userSettings } from '$lib/userSettings';
import { reduceAnimations } from '$lib/userSettings';
import { Volume, Volume1, VolumeOff } from '@lucide/svelte';
import Minimize from '@lucide/svelte/icons/minus';
import Volume2 from '@lucide/svelte/icons/volume-2';
@@ -62,8 +63,7 @@
step="0.01"
bind:value={$custom_theme_volume}
on:input={() => {
$userSettings.value('volume').set($custom_theme_volume);
$userSettings.save();
config.value<number>('volume').set($custom_theme_volume);
}}
class="volume-slider"
/>
+31 -79
View File
@@ -1,87 +1,39 @@
import { Crypto } from './crypto';
import { getHWID } from './osuUtil';
import * as path from '@tauri-apps/api/path';
import { exists, mkdir, readTextFile, writeFile } from '@tauri-apps/plugin-fs';
import { invoke } from '@tauri-apps/api/core';
export class Config {
private fileName: string;
private config: Record<string, unknown> = {};
private crypto: Crypto | undefined;
private configFilePath: string | undefined;
private encrypt: boolean;
interface ConfigValue<T> {
set(value: T, opts?: { encrypt?: boolean }): Promise<void>;
get(fallback: T): Promise<T>;
exists(): Promise<boolean>;
delete(): Promise<void>;
}
constructor(fileName: string, encrypt?: boolean) {
this.fileName = fileName;
this.encrypt = encrypt ?? false;
}
async init(): Promise<boolean> {
const hwid = (await getHWID()) ?? 'recorderinsandybridge';
this.crypto = new Crypto(hwid);
const homeDir = await path.homeDir();
const folderPath = await path.join(homeDir, '.ezpplauncher');
this.configFilePath = await path.join(folderPath, this.fileName);
const createFolder = !(await exists(folderPath));
if (createFolder) await mkdir(folderPath);
const configExists = await exists(this.configFilePath);
if (!configExists) return true;
await this.load();
return false;
}
private async load() {
if (!this.configFilePath) throw Error('configFilePath not set');
if (this.encrypt && !this.crypto) throw Error('crypto not initialized');
const fileStream = await readTextFile(this.configFilePath);
try {
const decryptedJSON = JSON.parse(
this.encrypt && this.crypto ? this.crypto.decrypt(fileStream) : fileStream
) as Record<string, unknown>;
this.config = decryptedJSON;
} catch (err) {
console.log(err);
this.config = {};
await this.save();
}
}
async save() {
if (!this.configFilePath) throw Error('configFilePath not set');
if (this.encrypt && !this.crypto) throw Error('crypto not initialized');
const encryptedJSON =
this.encrypt && this.crypto
? this.crypto.encrypt(JSON.stringify(this.config))
: JSON.stringify(this.config);
await writeFile(this.configFilePath, Buffer.from(encryptedJSON), {
append: false,
});
}
values() {
return this.config;
}
value(key: string) {
export const config = {
value<T = unknown>(key: string): ConfigValue<T> {
return {
set: <T>(val: T) => {
this.config[key] = val;
set(value: T, opts?: { encrypt?: boolean }): Promise<void> {
return invoke('config_set', { key, value, encrypt: opts?.encrypt ?? false });
},
exists: () => {
return this.config[key] !== undefined;
async get(fallback: T): Promise<T> {
const result = await invoke<T | null>('config_get', { key });
return result ?? fallback;
},
get: <T>(fallback: T): T => {
return (this.config[key] as T) ?? fallback;
exists(): Promise<boolean> {
return invoke<boolean>('config_exists', { key });
},
del: () => {
delete this.config[key];
delete(): Promise<void> {
return invoke('config_delete', { key });
},
};
}
}
},
all(): Promise<[string, unknown][]> {
return invoke<[string, unknown][]>('config_all');
},
clear(): Promise<void> {
return invoke('config_clear');
},
};
+94
View File
@@ -0,0 +1,94 @@
import { Crypto } from './crypto';
import { getHWID } from './osuUtil';
import * as path from '@tauri-apps/api/path';
import { exists, mkdir, readTextFile, writeFile, remove } from '@tauri-apps/plugin-fs';
export class Config {
private fileName: string;
private config: Record<string, unknown> = {};
private crypto: Crypto | undefined;
private configFilePath: string | undefined;
private encrypt: boolean;
constructor(fileName: string, encrypt?: boolean) {
this.fileName = fileName;
this.encrypt = encrypt ?? false;
}
async init(): Promise<boolean> {
const hwid = (await getHWID()) ?? 'recorderinsandybridge';
this.crypto = new Crypto(hwid);
const homeDir = await path.homeDir();
const folderPath = await path.join(homeDir, '.ezpplauncher');
this.configFilePath = await path.join(folderPath, this.fileName);
const createFolder = !(await exists(folderPath));
if (createFolder) await mkdir(folderPath);
const configExists = await exists(this.configFilePath);
if (configExists) {
await this.load();
return true;
}
return false;
}
private async load() {
if (!this.configFilePath) throw Error('configFilePath not set');
if (this.encrypt && !this.crypto) throw Error('crypto not initialized');
const fileStream = await readTextFile(this.configFilePath);
try {
const decryptedJSON = JSON.parse(
this.encrypt && this.crypto ? this.crypto.decrypt(fileStream) : fileStream
) as Record<string, unknown>;
this.config = decryptedJSON;
} catch (err) {
console.log(err);
this.config = {};
await this.save();
}
}
async delete() {
if (!this.configFilePath) throw Error('configFilePath not set');
await remove(this.configFilePath);
}
async save() {
if (!this.configFilePath) throw Error('configFilePath not set');
if (this.encrypt && !this.crypto) throw Error('crypto not initialized');
const encryptedJSON =
this.encrypt && this.crypto
? this.crypto.encrypt(JSON.stringify(this.config))
: JSON.stringify(this.config);
await writeFile(this.configFilePath, Buffer.from(encryptedJSON), {
append: false,
});
}
values() {
return this.config;
}
value(key: string) {
return {
set: <T>(val: T) => {
this.config[key] = val;
},
exists: () => {
return this.config[key] !== undefined;
},
get: <T>(fallback: T): T => {
return (this.config[key] as T) ?? fallback;
},
del: () => {
delete this.config[key];
},
};
}
}
-1
View File
@@ -262,7 +262,6 @@ export const loadTheme = async (theme: Theme, themeContainer: HTMLElement, volum
};
export const deleteTheme = async (themeToUninstall: Theme) => {
console.log('Uninstall', themeToUninstall);
if (themeToUninstall.status !== 'installed') {
return false;
}
-2
View File
@@ -1,6 +1,4 @@
import { Config } from './config';
import type { EZPPUser } from './types';
import { writable } from 'svelte/store';
export const userAuth = writable<Config>(new Config('user_auth', true));
export const currentUser = writable<EZPPUser | undefined>(undefined);
-3
View File
@@ -1,8 +1,5 @@
import { Config } from './config';
import { writable } from 'svelte/store';
export const userSettings = writable<Config>(new Config('user_settings', false));
export const customCursor = writable<boolean>(false);
export const cursorSmoothening = writable<boolean>(false);
export const cursorSmoothness = writable<number>(180);
+65 -35
View File
@@ -6,6 +6,8 @@
import OsuCursor from '$lib/components/ui/osu-cursor/OsuCursor.svelte';
import SileoToast from '$lib/components/ui/sileo/sileo-toast.svelte';
import Titlebar from '$lib/components/ui/titlebar/titlebar.svelte';
import { config } from '$lib/config';
import { Config } from '$lib/config_old';
import {
active_custom_theme,
currentLoadingInfo,
@@ -32,7 +34,6 @@
osuInstallationPath,
patch,
reduceAnimations,
userSettings,
} from '$lib/userSettings';
import { trackMediaContainer } from '$lib/utils';
import '../app.css';
@@ -94,51 +95,80 @@
disableReload();
setupValues();
launcherVersion.set(await getLauncherVersion());
const isFirstStartup = await $userSettings.init();
firstStartup.set(isFirstStartup);
const oldUserConfig = new Config('user_settings', false);
const oldUserConfigExists = await oldUserConfig.init();
if (oldUserConfigExists) {
const oldUserConfigValues = oldUserConfig.values();
if (Object.keys(oldUserConfigValues).length > 0) {
await config.value('setup_complete').set(true);
for (const key in oldUserConfigValues) {
const value = oldUserConfigValues[key];
await config.value<typeof value>(key).set(value);
}
}
await oldUserConfig.delete();
}
const oldUserAuth = new Config('user_auth', true);
const oldUserAuthExists = await oldUserAuth.init();
if (oldUserAuthExists) {
const oldUserAuthValues = oldUserAuth.values();
if (Object.keys(oldUserAuthValues).length > 0) {
for (const key in oldUserAuthValues) {
const value = oldUserAuthValues[key];
await config.value<typeof value>(key).set(value);
}
}
await oldUserAuth.delete();
}
const isSetupComplete = await config.value<boolean>('setup_complete').get(false);
firstStartup.set(!isSetupComplete);
$userAuth.init();
currentLoadingInfo.set('Loading config...');
const config_theme = $userSettings.value('theme');
const config_theme_volume = $userSettings.value('volume');
const config_patching = $userSettings.value('patching');
const config_custom_cursor = $userSettings.value('custom_cursor');
const config_cursor_smoothening = $userSettings.value('cursor_smoothening');
const config_reduce_animations = $userSettings.value('reduce_animations');
const config_osu_installation_path = $userSettings.value('osu_installation_path');
const config_discord_presence = $userSettings.value('discord_presence');
const config_tracking_enabled = $userSettings.value('tracking_consent');
const config_otd_enabled = $userSettings.value('otd_enabled');
const config_otd_path = $userSettings.value('otd_path');
const config_theme = config.value<string>('theme');
const config_theme_volume = config.value<number>('volume');
const config_patching = config.value<boolean>('patching');
const config_custom_cursor = config.value<boolean>('custom_cursor');
const config_cursor_smoothening = config.value<boolean>('cursor_smoothening');
const config_reduce_animations = config.value<boolean>('reduce_animations');
const config_osu_installation_path = config.value<string>('osu_installation_path');
const config_discord_presence = config.value<boolean>('discord_presence');
const config_tracking_enabled = config.value<boolean>('tracking_consent');
const config_otd_enabled = config.value<boolean>('otd_enabled');
const config_otd_path = config.value<string>('otd_path');
const localThemes = await getThemes();
const last_theme = localThemes.find((t) => t.name === config_theme.get('Default'));
const lastThemeName = await config_theme.get('Default');
const last_theme = localThemes.find((t) => t.name === lastThemeName);
if (!last_theme) {
$userSettings.value('theme').set('Default');
await $userSettings.save();
loadTheme(localThemes[0], $custom_theme_container!, config_theme_volume.get(0.15));
await config.value('theme').set('Default');
loadTheme(localThemes[0], $custom_theme_container!, await config_theme_volume.get(0.15));
} else {
loadTheme(last_theme, $custom_theme_container!, config_theme_volume.get(0.15));
loadTheme(last_theme, $custom_theme_container!, await config_theme_volume.get(0.15));
}
custom_theme_volume.set(config_theme_volume.get(0.15));
patch.set(config_patching.get(true));
customCursor.set(config_custom_cursor.get(true));
cursorSmoothening.set(config_cursor_smoothening.get(true));
reduceAnimations.set(config_reduce_animations.get(false));
osuInstallationPath.set(config_osu_installation_path.get(''));
discordPresence.set(config_discord_presence.get(true));
openTabletDriverEnabled.set(config_otd_enabled.get(false));
openTabletDriverPath.set(config_otd_path.get(''));
if (config_tracking_enabled.exists()) {
trackingEnabled.set(config_tracking_enabled.get(false));
custom_theme_volume.set(await config_theme_volume.get(0.15));
patch.set(await config_patching.get(true));
customCursor.set(await config_custom_cursor.get(true));
cursorSmoothening.set(await config_cursor_smoothening.get(true));
reduceAnimations.set(await config_reduce_animations.get(false));
osuInstallationPath.set(await config_osu_installation_path.get(''));
discordPresence.set(await config_discord_presence.get(true));
openTabletDriverEnabled.set(await config_otd_enabled.get(false));
openTabletDriverPath.set(await config_otd_path.get(''));
if (await config_tracking_enabled.exists()) {
trackingEnabled.set(await config_tracking_enabled.get(false));
}
patch.subscribe((val) => config_patching.set(val));
customCursor.subscribe((val) => config_custom_cursor.set(val));
cursorSmoothening.subscribe((val) => config_cursor_smoothening.set(val));
reduceAnimations.subscribe((val) => config_reduce_animations.set(val));
openTabletDriverEnabled.subscribe((val) => config_otd_enabled.set(val));
patch.subscribe(async (val) => await config_patching.set(val));
customCursor.subscribe(async (val) => await config_custom_cursor.set(val));
cursorSmoothening.subscribe(async (val) => await config_cursor_smoothening.set(val));
reduceAnimations.subscribe(async (val) => await config_reduce_animations.set(val));
openTabletDriverEnabled.subscribe(async (val) => config_otd_enabled.set(val));
discordPresence.subscribe(async (val) => {
config_discord_presence.set(val);
+30 -41
View File
@@ -15,6 +15,7 @@
import Progress from '$lib/components/ui/progress/progress.svelte';
import ScrollContainer from '$lib/components/ui/scroll-container/ScrollContainer.svelte';
import * as Select from '$lib/components/ui/select';
import { config } from '$lib/config';
import { currentUserInfo } from '$lib/data';
import { useDropZone } from '$lib/dropZone.svelte';
import {
@@ -86,7 +87,7 @@
type ThemeInfo,
} from '$lib/themes';
import { EZPPActionStatus } from '$lib/types';
import { currentUser, userAuth } from '$lib/userAuthentication';
import { currentUser } from '$lib/userAuthentication';
import {
cursorSmoothening,
customCursor,
@@ -95,7 +96,6 @@
preferredMode,
preferredType,
reduceAnimations,
userSettings,
} from '$lib/userSettings';
import {
compareBuildNumbers,
@@ -216,8 +216,7 @@
return;
}
osuInstallationPath.set(selectedPath);
$userSettings.value('osu_installation_path').set(selectedPath);
await $userSettings.save();
await config.value<string>('osu_installation_path').set(selectedPath);
sileo.success({
title: 'Yay!',
description: 'osu! installation path set successfully.',
@@ -400,8 +399,10 @@
launchInfo = 'You are up to date!';
}
if ($currentUser) {
const username = $userAuth.value('username').get('');
const password = $userAuth.value('password').get('');
const [username, password] = await Promise.all([
config.value<string>('username').get(''),
config.value<string>('password').get(''),
]);
if (username.length > 0 && password.length > 0) {
await setUserConfigValues(osuPath, [
{
@@ -681,10 +682,10 @@
title: 'Login successful!',
description: `Welcome back, ${loginResult.user.name}!`,
});
$userAuth.value('username').set(username);
$userAuth.value('password').set(password);
await $userAuth.save();
await Promise.all([
config.value<string>('username').set(username),
config.value<string>('password').set(password),
]);
currentUser.set(loginResult.user);
selectedView = 'home';
@@ -815,8 +816,7 @@
}
openTabletDriverPath.set(selectedPath);
$userSettings.value('otd_path').set(selectedPath);
await $userSettings.save();
await config.value('otd_path').set(selectedPath);
sileo.success({
title: 'Yay!',
description: 'OpenTabletDriver set successfully.',
@@ -825,13 +825,14 @@
};
onMount(() => {
const config = $userSettings;
const trackingConsent = config.value('tracking_consent');
if (trackingConsent.exists()) {
trackingEnabled.set(trackingConsent.get(false));
} else {
askForTrackingPermission = true;
}
(async () => {
const trackingConsent = config.value<boolean>('tracking_consent');
if (await trackingConsent.exists()) {
trackingEnabled.set(await trackingConsent.get(false));
} else {
askForTrackingPermission = true;
}
})();
animate(ezppLogo, {
opacity: [0, 1],
@@ -984,9 +985,7 @@
<Button
onclick={async () => {
trackingEnabled.set(true);
const config = $userSettings;
config.value('tracking_consent').set(true);
await config.save();
await config.value<boolean>('tracking_consent').set(true);
askForTrackingPermission = false;
}}>Yes, I consent</Button
>
@@ -994,9 +993,7 @@
variant="outline"
onclick={async () => {
trackingEnabled.set(false);
const config = $userSettings;
config.value('tracking_consent').set(false);
await config.save();
await config.value<boolean>('tracking_consent').set(false);
askForTrackingPermission = false;
}}>No, I do not consent</Button
>
@@ -1170,9 +1167,10 @@
<DropdownMenu.Item
class="text-destructive focus:text-destructive text-xs"
onclick={async () => {
$userAuth.value('username').del();
$userAuth.value('password').del();
await $userAuth.save();
await Promise.all([
config.value('username').set(''),
config.value('password').set(''),
]);
sileo.success({
title: 'Logout successful!',
description: 'See you soon!',
@@ -1542,7 +1540,6 @@
disabled={$platform !== 'windows'}
onCheckedChange={async (e) => {
patch.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
@@ -1561,8 +1558,6 @@
cursorSmoothening.set(false);
}
customCursor.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
@@ -1579,7 +1574,6 @@
onCheckedChange={async (e) => {
if (!$customCursor) return;
cursorSmoothening.set(e);
$userSettings.save();
}}
disabled={!$customCursor}
class="flex items-center justify-center w-5 h-5"
@@ -1596,7 +1590,6 @@
checked={$reduceAnimations}
onCheckedChange={async (e) => {
reduceAnimations.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
@@ -1632,8 +1625,7 @@
checked={$trackingEnabled}
onCheckedChange={async (e) => {
trackingEnabled.set(e);
$userSettings.value('tracking_consent').set(e);
await $userSettings.save();
await config.value<boolean>('tracking_consent').set(e);
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
@@ -1714,9 +1706,8 @@
});
return;
}
$userSettings.value('patcherStream').set(newStream);
await config.value<string>('patcherStream').set(newStream);
launcherStream.set(newStream);
await $userSettings.save();
}}
>
<Select.Trigger
@@ -1836,8 +1827,7 @@
if (theme.status === 'installed') {
if ($custom_theme_container) {
loadTheme(theme, $custom_theme_container, $custom_theme_volume);
$userSettings.value('theme').set(theme.name);
$userSettings.save();
await config.value<string>('theme').set(theme.name);
} else {
sileo.error({
title: 'Uhhm..',
@@ -1889,8 +1879,7 @@
const defaultTheme = $custom_themes.find((t) => t.name === 'Default');
if (defaultTheme) {
loadTheme(defaultTheme, $custom_theme_container!, $custom_theme_volume);
$userSettings.value('theme').set(defaultTheme.name);
$userSettings.save();
await config.value<string>('theme').set(defaultTheme.name);
if (!(await downloadTheme(theme, true))) {
sileo.error({
+6 -8
View File
@@ -2,6 +2,7 @@
import Logo from '$assets/logo.png';
import { ezppfarm } from '$lib/api/ezpp';
import { git } from '$lib/api/git';
import { config } from '$lib/config';
import { currentUserInfo } from '$lib/data';
import { estimateFrameRate } from '$lib/displayUtils';
import {
@@ -29,13 +30,12 @@
isValidOsuFolder,
} from '$lib/osuUtil';
import type { EZPPUser } from '$lib/types';
import { currentUser, userAuth } from '$lib/userAuthentication';
import { currentUser } from '$lib/userAuthentication';
import {
cursorSmoothness,
osuInstallationPath,
preferredMode,
preferredType,
userSettings,
} from '$lib/userSettings';
import Launch from './Launch.svelte';
import SetupWizard from './SetupWizard.svelte';
@@ -75,15 +75,14 @@
const clamped = Math.min(Math.max(refreshRate, hzMin), hzMax);
const duration =
durationMin + ((clamped - hzMin) / (hzMax - hzMin)) * (durationMax - durationMin);
console.log(Math.round(duration));
cursorSmoothness.set(Math.round(duration));
};
const prepare = async () => {
await calculateCursorSmoothness();
const username = $userAuth.value('username').get('');
const password = $userAuth.value('password').get('');
const username = await config.value<string>('username').get('');
const password = await config.value<string>('password').get('');
const shouldLogin = username.length > 0 && password.length > 0;
let userLoginResult: { message: string; user?: EZPPUser } = {
@@ -137,8 +136,7 @@
const validFolder = await isValidOsuFolder($osuInstallationPath);
if (!validFolder) {
osuInstallationPath.set('');
$userSettings.value('osu_installation_path').del();
await $userSettings.save();
await config.value<string>('osu_installation_path').set('');
sileo.error({
title: 'Hmm...',
description: 'Your previously set osu! installation path seems to be invalid.',
@@ -177,7 +175,7 @@
const ezpplauncherStreams = await getEZPPLauncherStreams();
if (ezpplauncherStreams) launcherStreams.set(ezpplauncherStreams);
const selectedLauncherStream = $userSettings.value('patcherStream').get('stable');
const selectedLauncherStream = await config.value<string>('patcherStream').get('stable');
if ($launcherStreams.includes(selectedLauncherStream)) {
launcherStream.set(selectedLauncherStream);
}
+4 -4
View File
@@ -5,6 +5,7 @@
import Checkbox from '$lib/components/ui/checkbox/checkbox.svelte';
import Input from '$lib/components/ui/input/input.svelte';
import Label from '$lib/components/ui/label/label.svelte';
import { config } from '$lib/config';
import {
beatmapSets,
currentSkin,
@@ -32,7 +33,6 @@
osuInstallationPath,
patch,
reduceAnimations,
userSettings,
} from '$lib/userSettings';
import Launch from './Launch.svelte';
import { Check, CircleCheckBig, CircleOff, LoaderCircle } from '@lucide/svelte';
@@ -95,9 +95,9 @@
};
const saveConfig = async () => {
$userSettings.value('osu_installation_path').set(osuInstallPath);
await $userSettings.save();
await config.value<string>('osu_installation_path').set(osuInstallPath);
osuInstallationPath.set(osuInstallPath);
await config.value<boolean>('setup_complete').set(true);
const beatmapSetCount = await getBeatmapSetsCount(osuInstallPath);
if (beatmapSetCount) {
@@ -128,7 +128,7 @@
if (osuPath) {
osuInstallPath = osuPath;
autoDetectedOsuPath = true;
$userSettings.value('osu_installation_path').set(osuInstallPath);
await config.value<string>('osu_installation_path').set(osuInstallPath);
}
});
</script>