[Lib > Storage] Move from MMKVManager to FS (#206)

* init migration

* fixes
This commit is contained in:
Amsyar Rasyiq 2023-12-17 10:09:12 +08:00 committed by GitHub
parent c58530080e
commit 80efe7026c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 68 additions and 14 deletions

2
src/def.d.ts vendored
View file

@ -292,11 +292,13 @@ interface FileManager {
* @returns Promise that resolves to path of the file once it got written * @returns Promise that resolves to path of the file once it got written
*/ */
writeFile(storageDir: "cache" | "documents", path: string, data: string, encoding: "base64" | "utf8"): Promise<string>; writeFile(storageDir: "cache" | "documents", path: string, data: string, encoding: "base64" | "utf8"): Promise<string>;
removeFile(storageDir: "cache" | "documents", path: string): Promise<unknown>;
getConstants: () => { getConstants: () => {
/** /**
* The path the `documents` storage dir (see {@link writeFile}) represents. * The path the `documents` storage dir (see {@link writeFile}) represents.
*/ */
DocumentsDirPath: string; DocumentsDirPath: string;
CacheDirPath: string;
}; };
/** /**
* Will apparently cease to exist some time in the future so please use {@link getConstants} instead. * Will apparently cease to exist some time in the future so please use {@link getConstants} instead.

View file

@ -29,7 +29,7 @@ export const constants = findByProps("Fonts", "Permissions");
export const channels = findByProps("getVoiceChannelId"); export const channels = findByProps("getVoiceChannelId");
export const i18n = findByProps("Messages"); export const i18n = findByProps("Messages");
export const url = findByProps("openURL", "openDeeplink"); export const url = findByProps("openURL", "openDeeplink");
export const toasts = find(m => m.open && m.close && !m.startDrag && !m.init && !m.openReplay && !m.setAlwaysOnTop); export const toasts = find(m => m.open && m.close && !m.startDrag && !m.init && !m.openReplay && !m.setAlwaysOnTop && !m.setAccountFlag);
// Compatible with pre-204201 versions since createThemedStyleSheet is undefined. // Compatible with pre-204201 versions since createThemedStyleSheet is undefined.
export const stylesheet = { export const stylesheet = {

View file

@ -1,6 +1,6 @@
import { PluginManifest, Plugin } from "@types"; import { PluginManifest, Plugin } from "@types";
import { safeFetch } from "@lib/utils"; import { safeFetch } from "@lib/utils";
import { awaitSyncWrapper, createMMKVBackend, createStorage, wrapSync } from "@lib/storage"; import { awaitSyncWrapper, createMMKVBackend, createStorage, purgeStorage, wrapSync } from "@lib/storage";
import { MMKVManager } from "@lib/native"; import { MMKVManager } from "@lib/native";
import { allSettled } from "@lib/polyfills"; import { allSettled } from "@lib/polyfills";
import logger, { logModule } from "@lib/logger"; import logger, { logModule } from "@lib/logger";
@ -117,12 +117,12 @@ export function stopPlugin(id: string, disable = true) {
disable && (plugin.enabled = false); disable && (plugin.enabled = false);
} }
export function removePlugin(id: string) { export async function removePlugin(id: string) {
if (!id.endsWith("/")) id += "/"; if (!id.endsWith("/")) id += "/";
const plugin = plugins[id]; const plugin = plugins[id];
if (plugin.enabled) stopPlugin(id); if (plugin.enabled) stopPlugin(id);
MMKVManager.removeItem(id);
delete plugins[id]; delete plugins[id];
await purgeStorage(id);
} }
export async function initPlugins() { export async function initPlugins() {

View file

@ -1,25 +1,77 @@
import { StorageBackend } from "@types"; import { StorageBackend } from "@types";
import { ReactNative as RN } from "@metro/common";
import { MMKVManager, FileManager } from "@lib/native"; import { MMKVManager, FileManager } from "@lib/native";
import { ReactNative as RN } from "@metro/common";
export const createMMKVBackend = (store: string): StorageBackend => ({ const ILLEGAL_CHARS_REGEX = /[<>:"\/\\|?*]/g;
get: async () => JSON.parse((await MMKVManager.getItem(store)) ?? "{}"),
set: (data) => MMKVManager.setItem(store, JSON.stringify(data)), const filePathFixer = (file: string): string => RN.Platform.select({
default: file,
ios: FileManager.saveFileToGallery ? file : `Documents/${file}`,
}); });
export const createFileBackend = (file: string): StorageBackend => { const getMMKVPath = (name: string): string => {
const filePathFixer: (file: string) => string = RN.Platform.select({ if (ILLEGAL_CHARS_REGEX.test(name)) {
default: (f) => f, // Replace forbidden characters with hyphens
ios: (f) => FileManager.saveFileToGallery ? f : `Documents/${f}`, name = name.replace(ILLEGAL_CHARS_REGEX, '-').replace(/-+/g, '-');
}); }
return `vd_mmkv/${name}`;
}
export const purgeStorage = async (store: string) => {
if (await MMKVManager.getItem(store)) {
MMKVManager.removeItem(store);
}
const mmkvPath = getMMKVPath(store);
if (await FileManager.fileExists(`${FileManager.getConstants().DocumentsDirPath}/${mmkvPath}`)) {
await FileManager.removeFile?.("documents", mmkvPath);
}
}
export const createMMKVBackend = (store: string) => {
const mmkvPath = getMMKVPath(store);
return createFileBackend(mmkvPath, (async () => {
try {
const path = `${FileManager.getConstants().DocumentsDirPath}/${mmkvPath}`;
if (await FileManager.fileExists(path)) return;
let oldData = await MMKVManager.getItem(store) ?? "{}";
// From the testing on Android, it seems to return this if the data is too large
if (oldData === "!!LARGE_VALUE!!") {
const cachePath = `${FileManager.getConstants().CacheDirPath}/mmkv/${store}`;
if (await FileManager.fileExists(cachePath)) {
oldData = await FileManager.readFile(cachePath, "utf8")
} else {
console.log(`${store}: Experienced data loss :(`);
oldData = "{}";
}
}
await FileManager.writeFile("documents", filePathFixer(mmkvPath), oldData, "utf8");
if (await MMKVManager.getItem(store) !== null) {
MMKVManager.removeItem(store);
console.log(`Successfully migrated ${store} store from MMKV storage to fs`);
}
} catch (err) {
console.error("Failed to migrate to fs from MMKVManager ", err)
}
})());
}
export const createFileBackend = (file: string, migratePromise?: Promise<void>): StorageBackend => {
let created: boolean; let created: boolean;
return { return {
get: async () => { get: async () => {
await migratePromise;
const path = `${FileManager.getConstants().DocumentsDirPath}/${file}`; const path = `${FileManager.getConstants().DocumentsDirPath}/${file}`;
if (!created && !(await FileManager.fileExists(path))) return (created = true), FileManager.writeFile("documents", filePathFixer(file), "{}", "utf8"); if (!created && !(await FileManager.fileExists(path))) return (created = true), FileManager.writeFile("documents", filePathFixer(file), "{}", "utf8");
return JSON.parse(await FileManager.readFile(path, "utf8")); return JSON.parse(await FileManager.readFile(path, "utf8"));
}, },
set: async (data) => void await FileManager.writeFile("documents", filePathFixer(file), JSON.stringify(data), "utf8"), set: async (data) => {
await migratePromise;
await FileManager.writeFile("documents", filePathFixer(file), JSON.stringify(data), "utf8");
}
}; };
}; };