Fix: revert to app storage
This commit is contained in:
@@ -5,6 +5,10 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
||||
|
||||
<!-- Storage -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<!-- For background jobs -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
@@ -179,8 +179,10 @@ class FileManager(context: ReactApplicationContext) :
|
||||
fun mkdir(filepath: String, promise: Promise) {
|
||||
try {
|
||||
val file = File(filepath)
|
||||
val created = file.mkdirs()
|
||||
if (!created) throw Exception("Directory could not be created")
|
||||
if(!file.exists()){
|
||||
val created = file.mkdirs()
|
||||
if (!created) throw Exception("Directory could not be created")
|
||||
}
|
||||
promise.resolve(null)
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Update } from '../types';
|
||||
import { noop } from 'lodash-es';
|
||||
import { getString } from '@strings/translations';
|
||||
import FileManager from '@native/FileManager';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import { NOVEL_STORAGE } from '@utils/Storages';
|
||||
|
||||
const db = SQLite.openDatabase('lnreader.db');
|
||||
const insertChapterQuery = `
|
||||
@@ -247,7 +247,6 @@ const downloadFiles = async (
|
||||
chapterId: number,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
const folder = await createChapterFolder(NOVEL_STORAGE, {
|
||||
pluginId: plugin.id,
|
||||
novelId,
|
||||
@@ -307,7 +306,6 @@ const deleteDownloadedFiles = async (
|
||||
chapterId: number,
|
||||
) => {
|
||||
try {
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
const path = await createChapterFolder(NOVEL_STORAGE, {
|
||||
pluginId,
|
||||
novelId,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { noop } from 'lodash-es';
|
||||
import { getString } from '@strings/translations';
|
||||
import { BackupNovel, NovelInfo } from '../types';
|
||||
import { SourceNovel } from '@plugins/types';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import { NOVEL_STORAGE } from '@utils/Storages';
|
||||
import FileManager from '@native/FileManager';
|
||||
|
||||
export const insertNovelAndChapters = async (
|
||||
@@ -45,7 +45,6 @@ export const insertNovelAndChapters = async (
|
||||
if (novelId) {
|
||||
const promises = [insertChapters(novelId, sourceNovel.chapters)];
|
||||
if (sourceNovel.cover) {
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
const novelDir = NOVEL_STORAGE + '/' + pluginId + '/' + novelId;
|
||||
await FileManager.mkdir(novelDir);
|
||||
const novelCoverUri = 'file://' + novelDir + '/cover.png';
|
||||
@@ -271,7 +270,6 @@ export const updateNovelInfo = async (info: NovelInfo) => {
|
||||
export const pickCustomNovelCover = async (novel: NovelInfo) => {
|
||||
const image = await DocumentPicker.getDocumentAsync({ type: 'image/*' });
|
||||
if (image.assets && image.assets[0]) {
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
const novelDir = NOVEL_STORAGE + '/' + novel.pluginId + '/' + novel.id;
|
||||
let novelCoverUri = 'file://' + novelDir + '/cover.png';
|
||||
if (!(await FileManager.exists(novelDir))) {
|
||||
|
||||
@@ -28,7 +28,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { getString } from '@strings/translations';
|
||||
import dayjs from 'dayjs';
|
||||
import { parseChapterNumber } from '@utils/parseChapterNumber';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import { NOVEL_STORAGE } from '@utils/Storages';
|
||||
import FileManager from '@native/FileManager';
|
||||
|
||||
// store key: '<PREFIX>_<novel.pluginId>_<novel.path>',
|
||||
@@ -417,7 +417,6 @@ export const deleteCachedNovels = async () => {
|
||||
`${NOVEL_SETTINSG_PREFIX}_${novel.pluginId}_${novel.path}`,
|
||||
);
|
||||
MMKVStorage.delete(`${LAST_READ_PREFIX}_${novel.pluginId}_${novel.path}`);
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
const novelDir = NOVEL_STORAGE + '/' + novel.pluginId + '/' + novel.id;
|
||||
if (await FileManager.exists(novelDir)) {
|
||||
await FileManager.unlink(novelDir);
|
||||
|
||||
@@ -35,17 +35,15 @@ import { updateLibrary } from '@services/updates';
|
||||
import WebviewScreen from '@screens/WebviewScreen/WebviewScreen';
|
||||
import { RootStackParamList } from './types';
|
||||
import Color from 'color';
|
||||
import OnboardingScreen from '../screens/onboarding/OnboardingScreen';
|
||||
import { APP_STORAGE_KEY } from '@utils/Storages';
|
||||
import { useMMKVString } from 'react-native-mmkv';
|
||||
|
||||
import { useMMKVBoolean } from 'react-native-mmkv';
|
||||
import OnboardingScreen from '@screens/onboarding/OnboardingScreen';
|
||||
const Stack = createStackNavigator<RootStackParamList>();
|
||||
|
||||
const MainNavigator = () => {
|
||||
const theme = useTheme();
|
||||
const [ROOT_STORAGE] = useMMKVString(APP_STORAGE_KEY);
|
||||
const { updateLibraryOnLaunch } = useAppSettings();
|
||||
const { refreshPlugins } = usePlugins();
|
||||
const [isOnboarded] = useMMKVBoolean('IS_ONBOARDED');
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(async () => {
|
||||
@@ -62,16 +60,18 @@ const MainNavigator = () => {
|
||||
if (updateLibraryOnLaunch) {
|
||||
updateLibrary();
|
||||
}
|
||||
if (ROOT_STORAGE) {
|
||||
if (isOnboarded) {
|
||||
// hack this helps app has enough time to initialize database;
|
||||
refreshPlugins();
|
||||
}
|
||||
}, [ROOT_STORAGE]);
|
||||
}, [isOnboarded]);
|
||||
|
||||
const { isNewVersion, latestRelease } = useGithubUpdateChecker();
|
||||
if (!ROOT_STORAGE) {
|
||||
|
||||
if (!isOnboarded) {
|
||||
return <OnboardingScreen />;
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationContainer theme={{ colors: theme, dark: theme.isDark }}>
|
||||
{isNewVersion && <NewUpdateDialog newVersion={latestRelease} />}
|
||||
|
||||
@@ -17,12 +17,9 @@ import { Parser } from 'htmlparser2';
|
||||
import FileManager from '@native/FileManager';
|
||||
import { getRepositoriesFromDb } from '@database/queries/RepositoryQueries';
|
||||
import { showToast } from '@utils/showToast';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import { PLUGIN_STORAGE } from '@utils/Storages';
|
||||
|
||||
const _getPluginsFilePath = () => {
|
||||
const { PLUGIN_STORAGE } = getAppStorages();
|
||||
return PLUGIN_STORAGE + '/plugins.json';
|
||||
};
|
||||
const pluginFilePath = PLUGIN_STORAGE + '/plugins.json';
|
||||
|
||||
const packages: Record<string, any> = {
|
||||
'htmlparser2': { Parser },
|
||||
@@ -72,8 +69,8 @@ const serializePlugin = async (
|
||||
installed: boolean,
|
||||
) => {
|
||||
if (!serializedPlugins) {
|
||||
if (await FileManager.exists(_getPluginsFilePath())) {
|
||||
const content = await FileManager.readFile(_getPluginsFilePath());
|
||||
if (await FileManager.exists(pluginFilePath)) {
|
||||
const content = await FileManager.readFile(pluginFilePath);
|
||||
serializedPlugins = JSON.parse(content);
|
||||
} else {
|
||||
serializedPlugins = {};
|
||||
@@ -84,18 +81,17 @@ const serializePlugin = async (
|
||||
} else {
|
||||
serializedPlugins[pluginId] = undefined;
|
||||
}
|
||||
const { PLUGIN_STORAGE } = getAppStorages();
|
||||
if (!(await FileManager.exists(PLUGIN_STORAGE))) {
|
||||
await FileManager.mkdir(PLUGIN_STORAGE);
|
||||
}
|
||||
await FileManager.writeFile(
|
||||
_getPluginsFilePath(),
|
||||
pluginFilePath,
|
||||
JSON.stringify(serializedPlugins),
|
||||
);
|
||||
};
|
||||
|
||||
const deserializePlugins = () => {
|
||||
return FileManager.readFile(_getPluginsFilePath())
|
||||
return FileManager.readFile(pluginFilePath)
|
||||
.then(content => {
|
||||
serializedPlugins = JSON.parse(content);
|
||||
Object.entries(serializedPlugins).forEach(([pluginId, script]) => {
|
||||
|
||||
@@ -5,30 +5,19 @@ import { Image, StyleSheet, View } from 'react-native';
|
||||
import { Button } from '@components';
|
||||
import PickThemeStep from './PickThemeStep';
|
||||
import { useState } from 'react';
|
||||
import StorageStep from './StorageStep';
|
||||
import { setAppStorage } from '@utils/Storages';
|
||||
import { useBackHandler } from '@hooks/index';
|
||||
import { MMKVStorage } from '@utils/mmkv/mmkv';
|
||||
|
||||
enum OnboardingStep {
|
||||
PICK_THEME,
|
||||
STORAGE_LOCATION,
|
||||
}
|
||||
|
||||
export default function OnboardingScreen() {
|
||||
const theme = useTheme();
|
||||
const [rootStorage, setRootStorage] = useState('');
|
||||
const [step, setStep] = useState<OnboardingStep>(OnboardingStep.PICK_THEME);
|
||||
const [step] = useState<OnboardingStep>(OnboardingStep.PICK_THEME);
|
||||
const renderStep = () => {
|
||||
switch (step) {
|
||||
case OnboardingStep.PICK_THEME:
|
||||
return <PickThemeStep />;
|
||||
case OnboardingStep.STORAGE_LOCATION:
|
||||
return (
|
||||
<StorageStep
|
||||
rootStorage={rootStorage}
|
||||
onPathChange={setRootStorage}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <PickThemeStep />;
|
||||
}
|
||||
@@ -37,21 +26,11 @@ export default function OnboardingScreen() {
|
||||
switch (step) {
|
||||
case OnboardingStep.PICK_THEME:
|
||||
return 'Pick a theme';
|
||||
case OnboardingStep.STORAGE_LOCATION:
|
||||
return 'Select storage for LNReader';
|
||||
default:
|
||||
return <PickThemeStep />;
|
||||
}
|
||||
};
|
||||
|
||||
useBackHandler(() => {
|
||||
if (step === OnboardingStep.STORAGE_LOCATION) {
|
||||
setStep(OnboardingStep.PICK_THEME);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.root, { backgroundColor: theme.background }]}>
|
||||
<Image
|
||||
@@ -91,25 +70,14 @@ export default function OnboardingScreen() {
|
||||
</View>
|
||||
<View style={{ flex: 1 }} />
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||
{step !== OnboardingStep.STORAGE_LOCATION ? (
|
||||
<Button
|
||||
style={{ flex: 1 }}
|
||||
title="Next"
|
||||
mode="contained"
|
||||
onPress={() => setStep(step + 1)}
|
||||
/>
|
||||
) : null}
|
||||
{step === OnboardingStep.STORAGE_LOCATION ? (
|
||||
<Button
|
||||
style={{ flex: 1 }}
|
||||
title="Complete"
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setAppStorage(rootStorage);
|
||||
}}
|
||||
disabled={!rootStorage}
|
||||
/>
|
||||
) : null}
|
||||
<Button
|
||||
style={{ flex: 1 }}
|
||||
title="Complete"
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
MMKVStorage.set('IS_ONBOARDED', true);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTheme } from '@hooks/persisted';
|
||||
import { PermissionsAndroid, StyleSheet, View } from 'react-native';
|
||||
import { Button } from '@components';
|
||||
import FileManager from '@native/FileManager';
|
||||
import { showToast } from '@utils/showToast';
|
||||
import { Text } from 'react-native-paper';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
||||
interface StorageStepProps {
|
||||
rootStorage: string;
|
||||
onPathChange: (path: string) => void;
|
||||
}
|
||||
|
||||
export default function StorageStep({
|
||||
rootStorage,
|
||||
onPathChange,
|
||||
}: StorageStepProps) {
|
||||
const [granted, setGranted] = useState<boolean | undefined>(undefined);
|
||||
const theme = useTheme();
|
||||
const pickFolder = () => {
|
||||
FileManager.pickFolder().then(path => {
|
||||
if (path) {
|
||||
FileManager.readDir(path).then(res => {
|
||||
if (res.length > 0) {
|
||||
showToast('Please select an empty folder');
|
||||
} else {
|
||||
onPathChange(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const grantPermissions = () => {
|
||||
PermissionsAndroid.requestMultiple([
|
||||
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
|
||||
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
|
||||
]).then(value => {
|
||||
if (
|
||||
value['android.permission.READ_EXTERNAL_STORAGE'] === 'granted' &&
|
||||
value['android.permission.WRITE_EXTERNAL_STORAGE'] === 'granted'
|
||||
) {
|
||||
setGranted(true);
|
||||
} else {
|
||||
setGranted(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
PermissionsAndroid.check(
|
||||
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
|
||||
),
|
||||
PermissionsAndroid.check(
|
||||
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
|
||||
),
|
||||
])
|
||||
.then(([readGranted, writeGRanted]) => {
|
||||
if (readGranted && writeGRanted) {
|
||||
setGranted(true);
|
||||
} else {
|
||||
setGranted(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setGranted(false);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<View style={{ paddingHorizontal: 16 }}>
|
||||
<View style={styles.section}>
|
||||
<View>
|
||||
<Text style={[styles.title, { color: theme.onSurface }]}>
|
||||
App permissions
|
||||
</Text>
|
||||
<Text style={[{ color: theme.onSurfaceVariant }]}>
|
||||
Read and write external storage.
|
||||
</Text>
|
||||
</View>
|
||||
<Button
|
||||
mode="outlined"
|
||||
compact
|
||||
style={styles.grantButton}
|
||||
loading={granted === undefined}
|
||||
onPress={() => {
|
||||
if (granted === false) {
|
||||
grantPermissions();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{granted === true ? (
|
||||
<MaterialCommunityIcons name="check" size={24} />
|
||||
) : (
|
||||
<Text style={{ color: theme.primary, fontWeight: '600' }}>
|
||||
{granted === undefined ? 'Checking' : 'Allow'}
|
||||
</Text>
|
||||
)}
|
||||
</Button>
|
||||
</View>
|
||||
<View style={styles.section}>
|
||||
<View>
|
||||
<Text style={[styles.title, { color: theme.onSurface }]}>
|
||||
Select an empty folder.
|
||||
</Text>
|
||||
{rootStorage ? (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.onSurfaceVariant,
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
}}
|
||||
>
|
||||
Selected folder: {rootStorage}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
labelStyle={{
|
||||
flex: 1,
|
||||
}}
|
||||
title="Select a folder"
|
||||
mode="contained"
|
||||
onPress={pickFolder}
|
||||
disabled={!granted}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
section: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 16,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
grantButton: {
|
||||
alignSelf: 'center',
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
});
|
||||
@@ -51,7 +51,7 @@ import { ChapterInfo } from '@database/types';
|
||||
import WebView, { WebViewNavigation } from 'react-native-webview';
|
||||
import { getString } from '@strings/translations';
|
||||
import FileManager from '@native/FileManager';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import { NOVEL_STORAGE } from '@utils/Storages';
|
||||
|
||||
const Chapter = ({ route, navigation }: ChapterScreenProps) => {
|
||||
const drawerRef = useRef<DrawerLayoutAndroid>(null);
|
||||
@@ -168,7 +168,6 @@ export const ChapterContent = ({
|
||||
|
||||
const getChapter = async () => {
|
||||
try {
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
const filePath = `${NOVEL_STORAGE}/${novel.pluginId}/${chapter.novelId}/${chapter.id}/index.html`;
|
||||
if (await FileManager.exists(filePath)) {
|
||||
sourceChapter.chapterText = await FileManager.readFile(filePath);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getString } from '@strings/translations';
|
||||
import { CACHE_DIR_PATH, prepareBackupData, restoreData } from '../utils';
|
||||
import { download, updateMetadata, uploadMedia } from '@api/drive/request';
|
||||
import { ZipBackupName } from '../types';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import { ROOT_STORAGE } from '@utils/Storages';
|
||||
|
||||
interface TaskData {
|
||||
delay: number;
|
||||
@@ -18,7 +18,6 @@ interface TaskData {
|
||||
|
||||
const driveBackupAction = async (taskData?: TaskData) => {
|
||||
try {
|
||||
const { ROOT_STORAGE } = getAppStorages();
|
||||
MMKVStorage.set(BACKGROUND_ACTION, BackgoundAction.BACKUP);
|
||||
|
||||
if (!taskData) {
|
||||
@@ -127,7 +126,6 @@ export const createBackup = async (backupFolder: DriveFile) => {
|
||||
|
||||
const driveRestoreAction = async (taskData?: TaskData) => {
|
||||
try {
|
||||
const { ROOT_STORAGE } = getAppStorages();
|
||||
MMKVStorage.set(BACKGROUND_ACTION, BackgoundAction.RESTORE);
|
||||
if (!taskData) {
|
||||
throw new Error(getString('backupScreen.noDataProvided'));
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BACKGROUND_ACTION, BackgoundAction } from '@services/constants';
|
||||
import { getString } from '@strings/translations';
|
||||
import { CACHE_DIR_PATH, prepareBackupData, restoreData } from '../utils';
|
||||
import { ZipBackupName } from '../types';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import { ROOT_STORAGE } from '@utils/Storages';
|
||||
|
||||
interface TaskData {
|
||||
delay: number;
|
||||
@@ -17,7 +17,6 @@ interface TaskData {
|
||||
|
||||
const remoteBackupAction = async (taskData?: TaskData) => {
|
||||
try {
|
||||
const { ROOT_STORAGE } = getAppStorages();
|
||||
MMKVStorage.set(BACKGROUND_ACTION, BackgoundAction.BACKUP);
|
||||
if (!taskData) {
|
||||
throw new Error(getString('backupScreen.noDataProvided'));
|
||||
@@ -111,7 +110,6 @@ export const createBackup = async (host: string, backupFolder: string) => {
|
||||
|
||||
const remoteRestoreAction = async (taskData?: TaskData) => {
|
||||
try {
|
||||
const { ROOT_STORAGE } = getAppStorages();
|
||||
MMKVStorage.set(BACKGROUND_ACTION, BackgoundAction.RESTORE);
|
||||
if (!taskData) {
|
||||
throw new Error(getString('backupScreen.noDataProvided'));
|
||||
|
||||
@@ -17,12 +17,10 @@ import {
|
||||
import { BackupCategory } from '@database/types';
|
||||
import { BackupEntryName } from './types';
|
||||
import FileManager from '@native/FileManager';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import { ROOT_STORAGE } from '@utils/Storages';
|
||||
|
||||
const APP_STORAGE_URI = 'file://' + ROOT_STORAGE;
|
||||
|
||||
const _getAppStorageUriPrefix = () => {
|
||||
const { ROOT_STORAGE } = getAppStorages();
|
||||
return 'file://' + ROOT_STORAGE;
|
||||
};
|
||||
export const CACHE_DIR_PATH =
|
||||
FileManager.ExternalCachesDirectoryPath + '/BackupData';
|
||||
|
||||
@@ -79,7 +77,7 @@ export const prepareBackupData = async (cacheDirPath: string) => {
|
||||
JSON.stringify({
|
||||
chapters: chapters,
|
||||
...novel,
|
||||
cover: novel.cover?.replace(_getAppStorageUriPrefix(), ''),
|
||||
cover: novel.cover?.replace(APP_STORAGE_URI, ''),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -124,7 +122,7 @@ export const restoreData = async (cacheDirPath: string) => {
|
||||
await FileManager.readFile(item.path).then(content => {
|
||||
const backupNovel = JSON.parse(content);
|
||||
if (!backupNovel.cover?.startsWith('http')) {
|
||||
backupNovel.cover = _getAppStorageUriPrefix() + backupNovel.cover;
|
||||
backupNovel.cover = APP_STORAGE_URI + backupNovel.cover;
|
||||
}
|
||||
return _restoreNovelAndChapters(backupNovel);
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ import { getString } from '@strings/translations';
|
||||
import { showToast } from '@utils/showToast';
|
||||
import FileManager from '@native/FileManager';
|
||||
import EpubUtil from '@native/EpubUtil';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import { NOVEL_STORAGE } from '@utils/Storages';
|
||||
|
||||
interface TaskData {
|
||||
delay: number;
|
||||
@@ -33,7 +33,6 @@ const insertLocalNovel = (
|
||||
artist?: string,
|
||||
summary?: string,
|
||||
): Promise<number> => {
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.transaction(tx => {
|
||||
tx.executeSql(
|
||||
@@ -88,7 +87,6 @@ const insertLocalChapter = (
|
||||
path: string,
|
||||
releaseTime: string,
|
||||
): Promise<string[]> => {
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.transaction(tx => {
|
||||
tx.executeSql(
|
||||
@@ -153,7 +151,6 @@ const insertLocalChapter = (
|
||||
|
||||
const importEpubAction = async (taskData?: TaskData) => {
|
||||
try {
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
if (!taskData) {
|
||||
throw new Error(getString('backupScreen.noDataProvided'));
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import { downloadChapter } from '../../database/queries/ChapterQueries';
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
import { ChapterItem, SourceNovel } from '@plugins/types';
|
||||
import { LOCAL_PLUGIN_ID } from '@plugins/pluginManager';
|
||||
import { getAppStorages } from '@utils/Storages';
|
||||
import FileManager from '@native/FileManager';
|
||||
import { NOVEL_STORAGE } from '@utils/Storages';
|
||||
const db = SQLite.openDatabase('lnreader.db');
|
||||
|
||||
const updateNovelMetadata = (
|
||||
@@ -13,7 +13,6 @@ const updateNovelMetadata = (
|
||||
novelId: number,
|
||||
novel: SourceNovel,
|
||||
) => {
|
||||
const { NOVEL_STORAGE } = getAppStorages();
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let { name, cover, summary, author, artist, genres, status, totalPages } =
|
||||
novel;
|
||||
|
||||
+3
-37
@@ -1,39 +1,5 @@
|
||||
import FileManager from '@native/FileManager';
|
||||
import { MMKVStorage } from '@utils/mmkv/mmkv';
|
||||
|
||||
export const APP_STORAGE_KEY = 'APP_STORAGE_KEY';
|
||||
|
||||
const NOVEL_STORAGE_PATH = 'Novels';
|
||||
const PLUGIN_STORAGE_PATH = 'Plugins';
|
||||
const DEFAULT_ROOT_STORAGE = FileManager.ExternalDirectoryPath;
|
||||
interface AppStorage {
|
||||
ROOT_STORAGE: string;
|
||||
NOVEL_STORAGE: string;
|
||||
PLUGIN_STORAGE: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this funtion in a context instead of global.
|
||||
* Because if rootStorage is changed, app would not be updated if the function is called globally
|
||||
*/
|
||||
export const getAppStorages = (): AppStorage => {
|
||||
const rootStorage =
|
||||
MMKVStorage.getString(APP_STORAGE_KEY) || DEFAULT_ROOT_STORAGE;
|
||||
return {
|
||||
ROOT_STORAGE: rootStorage,
|
||||
NOVEL_STORAGE: rootStorage + '/' + NOVEL_STORAGE_PATH,
|
||||
PLUGIN_STORAGE: rootStorage + '/' + PLUGIN_STORAGE_PATH,
|
||||
};
|
||||
};
|
||||
|
||||
export const setAppStorage = async (rootStorage: string) => {
|
||||
if (!(await FileManager.exists(rootStorage))) {
|
||||
throw new Error('Folder does not exist!');
|
||||
}
|
||||
const novelStorage = rootStorage + '/' + NOVEL_STORAGE_PATH;
|
||||
const pluginStorage = rootStorage + '/' + PLUGIN_STORAGE_PATH;
|
||||
for (const folder of [novelStorage, pluginStorage]) {
|
||||
await FileManager.mkdir(folder);
|
||||
}
|
||||
MMKVStorage.set(APP_STORAGE_KEY, rootStorage);
|
||||
};
|
||||
export const ROOT_STORAGE = FileManager.ExternalDirectoryPath;
|
||||
export const PLUGIN_STORAGE = ROOT_STORAGE + '/Plugins';
|
||||
export const NOVEL_STORAGE = ROOT_STORAGE + '/Novels';
|
||||
|
||||
Reference in New Issue
Block a user