Feat: Plugin lazy init(#1104)
* move plugins to single folder * lazy initial * clean up * fix: restore novels
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
),
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user