mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Feat: Implement Storage-based (JSON) Settings & Data-Storage (WIP) (#56)
This commit is contained in:
+2
-1
@@ -6,7 +6,7 @@
|
||||
import { platform } from "@tauri-apps/plugin-os";
|
||||
import { store, setActiveDownloads } from "@store/state.svelte";
|
||||
import { downloadStore } from "@features/downloads/store/downloadState.svelte";
|
||||
import { boot, startProbe, stopProbe, retryBoot, bypassBoot } from "@store/boot.svelte";
|
||||
import { boot, initStore, startProbe, stopProbe, retryBoot, bypassBoot } from "@store/boot.svelte";
|
||||
import { initRpc, setIdle, clearReading, destroyRpc } from "@store/discord";
|
||||
import { applyTheme } from "@core/theme";
|
||||
import { applyZoom, mountZoomKey, mountIdleDetection } from "@core/ui";
|
||||
@@ -100,6 +100,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
await initStore();
|
||||
startProbe();
|
||||
|
||||
const unlistenDownload = await listen<{ chapterId: number; mangaId: number; progress: number }[]>(
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { loadAllStores, persistSettings, persistLibrary, persistUpdates } from "./persist";
|
||||
export type { PersistedData } from "./persist";
|
||||
@@ -0,0 +1,135 @@
|
||||
import { LazyStore } from "@tauri-apps/plugin-store";
|
||||
|
||||
const settingsStore = new LazyStore("settings.json", { autoSave: false });
|
||||
const libraryStore = new LazyStore("library.json", { autoSave: false });
|
||||
const updatesStore = new LazyStore("updates.json", { autoSave: false });
|
||||
|
||||
export interface PersistedData {
|
||||
settings: any;
|
||||
storeVersion: number | null;
|
||||
history: any[];
|
||||
bookmarks: any[];
|
||||
markers: any[];
|
||||
readLog: any[];
|
||||
readingStats: any | null;
|
||||
dailyReadCounts: Record<string, number>;
|
||||
libraryUpdates: any[];
|
||||
lastLibraryRefresh: number;
|
||||
acknowledgedUpdateIds: number[];
|
||||
}
|
||||
|
||||
export async function loadAllStores(): Promise<PersistedData> {
|
||||
const migrated = await migrateFromLocalStorage();
|
||||
if (migrated) return migrated;
|
||||
|
||||
const [sv, s, hist, bk, mk, rl, rs, dc, lu, llr, au] = await Promise.all([
|
||||
settingsStore.get<number>("storeVersion"),
|
||||
settingsStore.get<any>("settings"),
|
||||
libraryStore.get<any[]>("history"),
|
||||
libraryStore.get<any[]>("bookmarks"),
|
||||
libraryStore.get<any[]>("markers"),
|
||||
libraryStore.get<any[]>("readLog"),
|
||||
libraryStore.get<any>("readingStats"),
|
||||
libraryStore.get<Record<string, number>>("dailyReadCounts"),
|
||||
updatesStore.get<any[]>("libraryUpdates"),
|
||||
updatesStore.get<number>("lastLibraryRefresh"),
|
||||
updatesStore.get<number[]>("acknowledgedUpdateIds"),
|
||||
]);
|
||||
|
||||
return {
|
||||
storeVersion: sv ?? null,
|
||||
settings: s ?? null,
|
||||
history: hist ?? [],
|
||||
bookmarks: bk ?? [],
|
||||
markers: mk ?? [],
|
||||
readLog: rl ?? [],
|
||||
readingStats: rs ?? null,
|
||||
dailyReadCounts: dc ?? {},
|
||||
libraryUpdates: lu ?? [],
|
||||
lastLibraryRefresh: llr ?? 0,
|
||||
acknowledgedUpdateIds: au ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
async function migrateFromLocalStorage(): Promise<PersistedData | null> {
|
||||
try {
|
||||
const raw = localStorage.getItem("moku-store");
|
||||
if (!raw) return null;
|
||||
const data = JSON.parse(raw);
|
||||
|
||||
await Promise.all([
|
||||
persistSettings({ settings: data.settings ?? null, storeVersion: data.storeVersion ?? 1 }),
|
||||
persistLibrary({
|
||||
history: data.history ?? [],
|
||||
bookmarks: data.bookmarks ?? [],
|
||||
markers: data.markers ?? [],
|
||||
readLog: data.readLog ?? [],
|
||||
readingStats: data.readingStats ?? null,
|
||||
dailyReadCounts: data.dailyReadCounts ?? {},
|
||||
}),
|
||||
persistUpdates({
|
||||
libraryUpdates: data.libraryUpdates ?? [],
|
||||
lastLibraryRefresh: data.lastLibraryRefresh ?? 0,
|
||||
acknowledgedUpdateIds: data.acknowledgedUpdateIds ?? [],
|
||||
}),
|
||||
]);
|
||||
|
||||
localStorage.removeItem("moku-store");
|
||||
|
||||
return {
|
||||
storeVersion: data.storeVersion ?? null,
|
||||
settings: data.settings ?? null,
|
||||
history: data.history ?? [],
|
||||
bookmarks: data.bookmarks ?? [],
|
||||
markers: data.markers ?? [],
|
||||
readLog: data.readLog ?? [],
|
||||
readingStats: data.readingStats ?? null,
|
||||
dailyReadCounts: data.dailyReadCounts ?? {},
|
||||
libraryUpdates: data.libraryUpdates ?? [],
|
||||
lastLibraryRefresh: data.lastLibraryRefresh ?? 0,
|
||||
acknowledgedUpdateIds: data.acknowledgedUpdateIds ?? [],
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function persistSettings(data: { settings: any; storeVersion: number }) {
|
||||
await Promise.all([
|
||||
settingsStore.set("settings", data.settings),
|
||||
settingsStore.set("storeVersion", data.storeVersion),
|
||||
]);
|
||||
await settingsStore.save();
|
||||
}
|
||||
|
||||
export async function persistLibrary(data: {
|
||||
history: any[];
|
||||
bookmarks: any[];
|
||||
markers: any[];
|
||||
readLog: any[];
|
||||
readingStats: any;
|
||||
dailyReadCounts: Record<string, number>;
|
||||
}) {
|
||||
await Promise.all([
|
||||
libraryStore.set("history", data.history),
|
||||
libraryStore.set("bookmarks", data.bookmarks),
|
||||
libraryStore.set("markers", data.markers),
|
||||
libraryStore.set("readLog", data.readLog),
|
||||
libraryStore.set("readingStats", data.readingStats),
|
||||
libraryStore.set("dailyReadCounts", data.dailyReadCounts),
|
||||
]);
|
||||
await libraryStore.save();
|
||||
}
|
||||
|
||||
export async function persistUpdates(data: {
|
||||
libraryUpdates: any[];
|
||||
lastLibraryRefresh: number;
|
||||
acknowledgedUpdateIds: number[];
|
||||
}) {
|
||||
await Promise.all([
|
||||
updatesStore.set("libraryUpdates", data.libraryUpdates),
|
||||
updatesStore.set("lastLibraryRefresh", data.lastLibraryRefresh),
|
||||
updatesStore.set("acknowledgedUpdateIds", data.acknowledgedUpdateIds),
|
||||
]);
|
||||
await updatesStore.save();
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { store } from "@store/state.svelte";
|
||||
import { probeServer, loginBasic } from "@core/auth";
|
||||
import { trackingState } from "@features/tracking/store/trackingState.svelte";
|
||||
import { store } from "@store/state.svelte";
|
||||
import { probeServer, loginBasic } from "@core/auth";
|
||||
import { trackingState } from "@features/tracking/store/trackingState.svelte";
|
||||
import { loadAllStores } from "@core/persistence/persist";
|
||||
|
||||
const MAX_ATTEMPTS = 40;
|
||||
|
||||
@@ -18,6 +19,11 @@ export const boot = $state({
|
||||
|
||||
let probeGeneration = 0;
|
||||
|
||||
export async function initStore() {
|
||||
const saved = await loadAllStores();
|
||||
store.hydrate(saved);
|
||||
}
|
||||
|
||||
export function startProbe() {
|
||||
const gen = ++probeGeneration;
|
||||
boot.failed = false;
|
||||
@@ -109,4 +115,4 @@ export function bypassBoot(onReady: () => void) {
|
||||
boot.loginRequired = false;
|
||||
boot.unsupportedMode = false;
|
||||
onReady();
|
||||
}
|
||||
}
|
||||
|
||||
+58
-44
@@ -8,6 +8,8 @@ import { DEFAULT_SETTINGS } from "../t
|
||||
import { DEFAULT_READING_STATS } from "../types/history";
|
||||
import { notifications } from "./notifications.svelte";
|
||||
import { app } from "./app.svelte";
|
||||
import { persistSettings, persistLibrary, persistUpdates } from "../core/persistence/persist";
|
||||
import type { PersistedData } from "../core/persistence/persist";
|
||||
|
||||
export type { NavPage } from "./app.svelte";
|
||||
export type { Toast, ActiveDownload } from "./notifications.svelte";
|
||||
@@ -27,29 +29,6 @@ const STORE_VERSION = 3;
|
||||
const AVG_MIN_PER_CHAPTER = 5;
|
||||
const RESET_ON_UPGRADE: (keyof Settings)[] = ["serverBinary", "readerZoom", "uiZoom"];
|
||||
|
||||
function loadPersisted(): any {
|
||||
try { const raw = localStorage.getItem("moku-store"); return raw ? JSON.parse(raw) : null; }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
function persist(patch: Record<string, unknown>) {
|
||||
try { localStorage.setItem("moku-store", JSON.stringify({ ...loadPersisted() ?? {}, ...patch })); }
|
||||
catch {}
|
||||
}
|
||||
|
||||
const saved = (() => {
|
||||
const data = loadPersisted();
|
||||
if (!data) return null;
|
||||
if ((data.storeVersion ?? 1) < STORE_VERSION) {
|
||||
const resetPatch: Partial<Settings> = {};
|
||||
for (const key of RESET_ON_UPGRADE) (resetPatch as any)[key] = (DEFAULT_SETTINGS as any)[key];
|
||||
const migrated = { ...data, storeVersion: STORE_VERSION, settings: { ...data.settings, ...resetPatch } };
|
||||
try { localStorage.setItem("moku-store", JSON.stringify(migrated)); } catch {}
|
||||
return migrated;
|
||||
}
|
||||
return data;
|
||||
})();
|
||||
|
||||
function mergeSettings(saved: any): Settings {
|
||||
return {
|
||||
...DEFAULT_SETTINGS, ...saved?.settings,
|
||||
@@ -73,7 +52,7 @@ function mergeSettings(saved: any): Settings {
|
||||
}
|
||||
|
||||
class Store {
|
||||
settings: Settings = $state(mergeSettings(saved));
|
||||
settings: Settings = $state(mergeSettings(null));
|
||||
activeManga: Manga | null = $state(null);
|
||||
previewManga: Manga | null = $state(null);
|
||||
activeChapter: Chapter | null = $state(null);
|
||||
@@ -84,19 +63,22 @@ class Store {
|
||||
categories: Category[] = $state([]);
|
||||
activeSource: Source | null = $state(null);
|
||||
libraryTagFilter: string[] = $state([]);
|
||||
history: HistoryEntry[] = $state(saved?.history ?? []);
|
||||
bookmarks: BookmarkEntry[]= $state(saved?.bookmarks ?? []);
|
||||
markers: MarkerEntry[] = $state(saved?.markers ?? []);
|
||||
readLog: ReadLogEntry[] = $state(saved?.readLog ?? []);
|
||||
readingStats: ReadingStats = $state(saved?.readingStats ?? { ...DEFAULT_READING_STATS });
|
||||
dailyReadCounts: Record<string, number> = $state(saved?.dailyReadCounts ?? {});
|
||||
history: HistoryEntry[] = $state([]);
|
||||
bookmarks: BookmarkEntry[]= $state([]);
|
||||
markers: MarkerEntry[] = $state([]);
|
||||
readLog: ReadLogEntry[] = $state([]);
|
||||
readingStats: ReadingStats = $state({ ...DEFAULT_READING_STATS });
|
||||
dailyReadCounts: Record<string, number> = $state({});
|
||||
searchCache: Map<string, any> = $state(new Map());
|
||||
searchLibraryIds: Set<number> = $state(new Set());
|
||||
searchSrcOffset: number = $state(0);
|
||||
readerSessionId: number = $state(0);
|
||||
libraryUpdates: LibraryUpdateEntry[] = $state(saved?.libraryUpdates ?? []);
|
||||
lastLibraryRefresh: number = $state(saved?.lastLibraryRefresh ?? 0);
|
||||
acknowledgedUpdates: Set<number> = $state(new Set(saved?.acknowledgedUpdateIds ?? []));
|
||||
libraryUpdates: LibraryUpdateEntry[] = $state([]);
|
||||
lastLibraryRefresh: number = $state(0);
|
||||
acknowledgedUpdates: Set<number> = $state(new Set());
|
||||
isFullscreen: boolean = $state(false);
|
||||
|
||||
#ready = false;
|
||||
|
||||
get toasts() { return notifications.toasts; }
|
||||
get activeDownloads() { return notifications.activeDownloads; }
|
||||
@@ -109,19 +91,51 @@ class Store {
|
||||
get genreFilter() { return app.genreFilter; }
|
||||
set genreFilter(v) { app.setGenreFilter(v); }
|
||||
|
||||
constructor() {
|
||||
hydrate(saved: PersistedData) {
|
||||
if (this.#ready) return;
|
||||
|
||||
if ((saved.storeVersion ?? 1) < STORE_VERSION && saved.settings) {
|
||||
for (const key of RESET_ON_UPGRADE)
|
||||
(saved.settings as any)[key] = (DEFAULT_SETTINGS as any)[key];
|
||||
}
|
||||
|
||||
this.settings = mergeSettings(saved);
|
||||
this.history = saved.history ?? [];
|
||||
this.bookmarks = saved.bookmarks ?? [];
|
||||
this.markers = saved.markers ?? [];
|
||||
this.readLog = saved.readLog ?? [];
|
||||
this.readingStats = saved.readingStats ?? { ...DEFAULT_READING_STATS };
|
||||
this.dailyReadCounts = saved.dailyReadCounts ?? {};
|
||||
this.libraryUpdates = saved.libraryUpdates ?? [];
|
||||
this.lastLibraryRefresh = saved.lastLibraryRefresh ?? 0;
|
||||
this.acknowledgedUpdates = new Set(saved.acknowledgedUpdateIds ?? []);
|
||||
|
||||
this.#ready = true;
|
||||
|
||||
$effect.root(() => {
|
||||
$effect(() => {
|
||||
persist({
|
||||
settings: this.settings, history: this.history,
|
||||
bookmarks: this.bookmarks, markers: this.markers,
|
||||
readLog: this.readLog, readingStats: this.readingStats,
|
||||
dailyReadCounts: this.dailyReadCounts,
|
||||
libraryUpdates: this.libraryUpdates,
|
||||
lastLibraryRefresh: this.lastLibraryRefresh,
|
||||
acknowledgedUpdateIds: [...this.acknowledgedUpdates],
|
||||
storeVersion: STORE_VERSION,
|
||||
});
|
||||
const s = this.settings;
|
||||
if (!this.#ready) return;
|
||||
persistSettings({ settings: s, storeVersion: STORE_VERSION });
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const h = this.history;
|
||||
const bk = this.bookmarks;
|
||||
const mk = this.markers;
|
||||
const rl = this.readLog;
|
||||
const rs = this.readingStats;
|
||||
const dc = this.dailyReadCounts;
|
||||
if (!this.#ready) return;
|
||||
persistLibrary({ history: h, bookmarks: bk, markers: mk, readLog: rl, readingStats: rs, dailyReadCounts: dc });
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const lu = this.libraryUpdates;
|
||||
const llr = this.lastLibraryRefresh;
|
||||
const au = this.acknowledgedUpdates;
|
||||
if (!this.#ready) return;
|
||||
persistUpdates({ libraryUpdates: lu, lastLibraryRefresh: llr, acknowledgedUpdateIds: [...au] });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user