Feat: Plugin lazy init(#1104)

* move plugins to single folder

* lazy initial

* clean up

* fix: restore novels
This commit is contained in:
nyagami
2024-06-12 16:26:55 +07:00
committed by GitHub
parent 6f3278f6ff
commit 772a9ef3a2
9 changed files with 50 additions and 93 deletions
+1 -2
View File
@@ -14,7 +14,6 @@ import BackgroundService from 'react-native-background-actions';
import { createTables } from '@database/db';
import AppErrorBoundary from '@components/AppErrorBoundary/AppErrorBoundary';
import { deserializePlugins } from '@plugins/pluginManager';
import Main from './src/navigators/Main';
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
@@ -32,7 +31,7 @@ Notifications.setNotificationHandler({
});
createTables();
deserializePlugins().then(() => LottieSplashScreen.hide());
LottieSplashScreen.hide();
if (!BackgroundService.isRunning()) {
MMKVStorage.delete(BACKGROUND_ACTION);
}
@@ -123,17 +123,9 @@ class FileManager(context: ReactApplicationContext) :
}
}
@ReactMethod
fun readFile(path: String?, promise: Promise) {
try {
val sb = StringBuilder()
val br = BufferedReader(FileReader(path))
var line: String?
while (br.readLine().also { line = it } != null) sb.append(line).append('\n')
promise.resolve(sb.toString())
} catch (e: Exception) {
promise.reject(e)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun readFile(path: String): String {
return File(path).bufferedReader().readText()
}
@ReactMethod
+2 -2
View File
@@ -8,12 +8,12 @@ interface ReadDirResult {
interface FileManagerInterface {
writeFile: (path: string, content: string) => Promise<void>;
readFile: (path: string) => Promise<string>;
readFile: (path: string) => string;
resolveExternalContentUri: (uriString: string) => Promise<string | null>;
copyFile: (sourcePath: string, destPath: string) => Promise<void>;
moveFile: (sourcePath: string, destPath: string) => Promise<void>;
exists: (filePath: string) => Promise<boolean>;
mkdir: (filePath: string) => Promise<void>; // create parents;
mkdir: (filePath: string) => Promise<void>; // create parents, and do nothing if exists;
unlink: (filePath: string) => Promise<void>; // remove recursively
readDir: (dirPath: string) => Promise<ReadDirResult[]>; // file/sub-folder names
pickFolder: () => Promise<string | null>; // return path of folderc
+23 -52
View File
@@ -19,8 +19,6 @@ import { getRepositoriesFromDb } from '@database/queries/RepositoryQueries';
import { showToast } from '@utils/showToast';
import { PLUGIN_STORAGE } from '@utils/Storages';
const pluginFilePath = PLUGIN_STORAGE + '/plugins.json';
const packages: Record<string, any> = {
'htmlparser2': { Parser },
'cheerio': { load },
@@ -61,52 +59,6 @@ const initPlugin = (pluginId: string, rawCode: string) => {
};
const plugins: Record<string, Plugin | undefined> = {};
let serializedPlugins: Record<string, string | undefined>;
const serializePlugin = async (
pluginId: string,
rawCode: string,
installed: boolean,
) => {
if (!serializedPlugins) {
if (await FileManager.exists(pluginFilePath)) {
const content = await FileManager.readFile(pluginFilePath);
serializedPlugins = JSON.parse(content);
} else {
serializedPlugins = {};
}
}
if (installed) {
serializedPlugins[pluginId] = rawCode;
} else {
serializedPlugins[pluginId] = undefined;
}
if (!(await FileManager.exists(PLUGIN_STORAGE))) {
await FileManager.mkdir(PLUGIN_STORAGE);
}
await FileManager.writeFile(
pluginFilePath,
JSON.stringify(serializedPlugins),
);
};
const deserializePlugins = () => {
return FileManager.readFile(pluginFilePath)
.then(content => {
serializedPlugins = JSON.parse(content);
Object.entries(serializedPlugins).forEach(([pluginId, script]) => {
if (script) {
const plugin = initPlugin(pluginId, script);
if (plugin) {
plugins[plugin.id] = plugin;
}
}
});
})
.catch(() => {
// nothing to read
});
};
const installPlugin = async (
pluginId: string,
@@ -126,7 +78,12 @@ const installPlugin = async (
if (!currentPlugin || newer(plugin.version, currentPlugin.version)) {
plugins[plugin.id] = plugin;
currentPlugin = plugin;
await serializePlugin(plugin.id, rawCode, true);
// save plugin code;
const pluginDir = `${PLUGIN_STORAGE}/${pluginId}`;
await FileManager.mkdir(pluginDir);
const filePath = pluginDir + '/index.js';
await FileManager.writeFile(filePath, rawCode);
}
return currentPlugin;
});
@@ -142,7 +99,10 @@ const uninstallPlugin = async (_plugin: PluginItem) => {
store.delete(key);
}
});
return serializePlugin(_plugin.id, '', false);
const pluginFilePath = `${PLUGIN_STORAGE}/${_plugin.id}/index.js`;
if (await FileManager.exists(pluginFilePath)) {
await FileManager.unlink(pluginFilePath);
}
};
const updatePlugin = async (plugin: PluginItem) => {
@@ -168,7 +128,19 @@ const fetchPlugins = async (): Promise<PluginItem[]> => {
return uniqBy(reverse(allPlugins), 'id');
};
const getPlugin = (pluginId: string) => plugins[pluginId];
const getPlugin = (pluginId: string) => {
if (!plugins[pluginId]) {
const filePath = `${PLUGIN_STORAGE}/${pluginId}/index.js`;
try {
const code = FileManager.readFile(filePath);
const plugin = initPlugin(pluginId, code);
plugins[pluginId] = plugin;
} catch {
// file doesnt exist
}
}
return plugins[pluginId];
};
const LOCAL_PLUGIN_ID = 'local';
@@ -177,7 +149,6 @@ export {
installPlugin,
uninstallPlugin,
updatePlugin,
deserializePlugins,
fetchPlugins,
LOCAL_PLUGIN_ID,
};
@@ -70,6 +70,7 @@ export const useGlobalSearch = ({ defaultSearchText }: Props) => {
),
);
} catch (error: any) {
const errorMessage = error?.message || String(error);
setSearchResults(prevState =>
prevState.map(prevResult =>
prevResult.plugin.id === _plugin.id
@@ -77,7 +78,7 @@ export const useGlobalSearch = ({ defaultSearchText }: Props) => {
...prevResult,
novels: [],
isLoading: false,
error: error?.message,
error: errorMessage,
}
: { ...prevResult },
),
+1 -1
View File
@@ -170,7 +170,7 @@ export const ChapterContent = ({
try {
const filePath = `${NOVEL_STORAGE}/${novel.pluginId}/${chapter.novelId}/${chapter.id}/index.html`;
if (await FileManager.exists(filePath)) {
sourceChapter.chapterText = await FileManager.readFile(filePath);
sourceChapter.chapterText = FileManager.readFile(filePath);
} else {
await fetchChapter(novel.pluginId, chapter.path)
.then(res => {
+2 -2
View File
@@ -51,13 +51,13 @@ export const restoreBackup = async (filePath?: string) => {
let novelsString = '';
if (backup.assets && backup.assets[0]) {
novelsString = await FileManager.readFile(backup.assets[0].uri);
novelsString = FileManager.readFile(backup.assets[0].uri);
} else if (filePath) {
if (!(await FileManager.exists(filePath))) {
showToast(getString('backupScreen.legacy.noErrorNovel'));
return; //neither backup nor error backup
}
novelsString = await FileManager.readFile(filePath);
novelsString = FileManager.readFile(filePath);
}
const novels: NovelInfo[] = await JSON.parse(novelsString);
+15 -19
View File
@@ -119,31 +119,27 @@ export const restoreData = async (cacheDirPath: string) => {
await FileManager.readDir(novelDirPath).then(async items => {
for (const item of items) {
if (!item.isDirectory) {
await FileManager.readFile(item.path).then(content => {
const backupNovel = JSON.parse(content);
if (!backupNovel.cover?.startsWith('http')) {
backupNovel.cover = APP_STORAGE_URI + backupNovel.cover;
}
return _restoreNovelAndChapters(backupNovel);
});
const backupNovel = JSON.parse(FileManager.readFile(item.path));
if (!backupNovel.cover?.startsWith('http')) {
backupNovel.cover = APP_STORAGE_URI + backupNovel.cover;
}
await _restoreNovelAndChapters(backupNovel);
}
}
});
// categories
await FileManager.readFile(
cacheDirPath + '/' + BackupEntryName.CATEGORY,
).then(async content => {
const categories: BackupCategory[] = JSON.parse(content);
for (const category of categories) {
await _restoreCategory(category);
}
});
const categories: BackupCategory[] = JSON.parse(
FileManager.readFile(cacheDirPath + '/' + BackupEntryName.CATEGORY),
);
for (const category of categories) {
await _restoreCategory(category);
}
// settings
await FileManager.readFile(cacheDirPath + '/' + BackupEntryName.SETTING).then(
content => {
restoreMMKVData(JSON.parse(content));
},
restoreMMKVData(
JSON.parse(
FileManager.readFile(cacheDirPath + '/' + BackupEntryName.SETTING),
),
);
};
+1 -3
View File
@@ -106,9 +106,7 @@ const insertLocalChapter = (
} catch {
// nothing to do
}
await FileManager.readFile(path)
.then(r => (chapterText = r))
.catch(e => reject(e));
chapterText = FileManager.readFile(path);
if (!chapterText) {
return;
}