Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.rajarsheechatterjee.NativeFile

import android.net.Uri
import android.os.Build
import android.util.Base64
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
Expand Down Expand Up @@ -79,10 +80,24 @@ class NativeFile(context: ReactApplicationContext) :
fw.close()
}

override fun writeFileFromBase64(path: String, base64Content: String) {
val bytes = Base64.decode(base64Content, Base64.NO_WRAP)
val file = File(path)
val fos = FileOutputStream(file)
fos.write(bytes)
fos.close()
}

override fun readFile(path: String): String {
return File(path).bufferedReader().readText()
}

override fun readFileAsBase64(path: String): String {
val file = File(path)
val bytes = file.readBytes()
return Base64.encodeToString(bytes, Base64.NO_WRAP)
}

override fun copyFile(filepath: String, destPath: String) {
copyFileContent(filepath, destPath)
}
Expand Down
2 changes: 2 additions & 0 deletions specs/NativeFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ interface ReadDirResult {

export interface Spec extends TurboModule {
writeFile: (path: string, content: string) => void;
writeFileFromBase64: (path: string, base64Content: string) => void;
readFile: (path: string) => string;
readFileAsBase64: (path: string) => string;
copyFile: (sourcePath: string, destPath: string) => void;
moveFile: (sourcePath: string, destPath: string) => void;
exists: (filePath: string) => boolean;
Expand Down
8 changes: 6 additions & 2 deletions src/components/ListView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { StyleSheet, View, Text, Pressable, Image } from 'react-native';

import { coverPlaceholderColor } from '../theme/colors';
Expand All @@ -7,6 +7,7 @@ import color from 'color';
import { ThemeColors } from '@theme/types';
import { NovelItem } from '@plugins/types';
import { NovelInfo } from '@database/types';
import { toImageUri } from '@utils/coverUri';

interface ListViewProps {
item: NovelItem | NovelInfo;
Expand All @@ -30,6 +31,9 @@ const ListView = ({
onLongPress,
}: ListViewProps) => {
const fadedImage = { opacity: inLibraryBadge ? 0.5 : 1 };

const coverUri = useMemo(() => toImageUri(item.cover as any), [item.cover]);

return (
<Pressable
android_ripple={{ color: theme.rippleColor }}
Expand All @@ -44,7 +48,7 @@ const ListView = ({
>
<Image
source={{
uri: item.cover,
uri: coverUri,
}}
style={[styles.extensionIcon, fadedImage]}
/>
Expand Down
5 changes: 3 additions & 2 deletions src/components/NovelCover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { useLibrarySettings } from '@hooks/persisted';
import { getUserAgent } from '@hooks/persisted/useUserAgent';
import { getString } from '@strings/translations';
import SourceScreenSkeletonLoading from '@screens/browse/loadingAnimation/SourceScreenSkeletonLoading';
import { defaultCover } from '@plugins/helpers/constants';
import { ActivityIndicator } from 'react-native-paper';
import { toImageUri } from '@utils/coverUri';

interface UnreadBadgeProps {
chaptersDownloaded: number;
Expand Down Expand Up @@ -118,7 +118,8 @@ function NovelCover<

const selectNovel = () => onLongPress(item);

const uri = item.cover || defaultCover;
const uri = useMemo(() => toImageUri(item.cover), [item.cover]);

if (item.completeRow) {
if (!addSkeletonLoading) {
return <></>;
Expand Down
212 changes: 147 additions & 65 deletions src/database/db.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as SQLite from 'expo-sqlite';
import {
createCategoriesTableQuery,
createCategoryDefaultQuery,
createCategoryTriggerQuery,
} from './tables/CategoryTable';
import {
Expand All @@ -20,48 +19,77 @@ import {
} from './tables/ChapterTable';

import { createRepositoryTableQuery } from './tables/RepositoryTable';
import { MMKVStorage } from '@utils/mmkv/mmkv';
import { showToast } from '@utils/showToast';
import { getString } from '@strings/translations';
import NativeFile from '@specs/NativeFile';

const dbName = 'lnreader.db';

export const db = SQLite.openDatabaseSync(dbName);

export const createTables = () => {
const isOnBoard = MMKVStorage.getBoolean('IS_ONBOARDED');
function columnExists(table: string, column: string): boolean {
try {
const rows = db.getAllSync<{ name: string }>(`PRAGMA table_info(${table})`);
return rows.some(r => r.name === column);
} catch {
return false;
}
}

export const createTables = () => {
// These values are not persistent and need to be set on every app start
db.execSync('PRAGMA busy_timeout = 5000');
db.execSync('PRAGMA cache_size = 10000');
db.execSync('PRAGMA foreign_keys = ON');
db.execSync('PRAGMA journal_mode = WAL');
db.execSync('PRAGMA synchronous = NORMAL');
db.execSync('PRAGMA temp_store = MEMORY');

const userVersion =
let userVersion =
db.getFirstSync<{ user_version: number }>('PRAGMA user_version')
?.user_version ?? 0;

if (!isOnBoard) {
db.execSync('PRAGMA journal_mode = WAL');
db.execSync('PRAGMA synchronous = NORMAL');
db.execSync('PRAGMA temp_store = MEMORY');
if (userVersion === 0) {
db.withTransactionSync(() => {
db.runSync(createNovelTableQuery);
db.runSync(createNovelIndexQuery);
db.runSync(createCategoriesTableQuery);
db.runSync(createCategoryDefaultQuery);
db.runSync(createNovelCategoryTableQuery);
db.runSync(createChapterTableQuery);
db.runSync(createCategoryTriggerQuery);
db.runSync(createChapterIndexQuery);
db.runSync(createCategoriesTableQuery);
try {
db.runSync(
'INSERT OR IGNORE INTO Category (id,name,sort) VALUES (1, ?, 1)',
[
((getString as any)
? (getString as any)('categories.default')
: 'Default') as any,
],
);
db.runSync(
'INSERT OR IGNORE INTO Category (id,name,sort) VALUES (2, ?, 2)',
[
((getString as any)
? (getString as any)('categories.local')
: 'Local') as any,
],
);
} catch {}
db.runSync(createCategoryTriggerQuery);
db.runSync(createNovelCategoryTableQuery);
db.runSync(createRepositoryTableQuery);
db.runSync(createNovelTriggerQueryInsert);
db.runSync(createNovelTriggerQueryUpdate);
db.runSync(createNovelTriggerQueryDelete);
db.execSync('PRAGMA user_version = 1');
});
} else {
if (userVersion < 1) {
updateToDBVersion1();
}
}

if (userVersion < 1) {
updateToDBVersion1();
userVersion = 1;
}
if (userVersion < 2) {
updateToDBVersion2();
userVersion = 2;
}
};

Expand Down Expand Up @@ -89,57 +117,111 @@ export const recreateDBIndex = () => {
};

function updateToDBVersion1() {
db.execSync('PRAGMA journal_mode = WAL');
db.execSync('PRAGMA synchronous = NORMAL');
db.execSync('PRAGMA temp_store = MEMORY');

db.withTransactionSync(() => {
db.runSync(
'ALTER TABLE Novel ADD COLUMN chaptersDownloaded INTEGER DEFAULT 0',
);

db.runSync('ALTER TABLE Novel ADD COLUMN chaptersUnread INTEGER DEFAULT 0');
db.runSync('ALTER TABLE Novel ADD COLUMN totalChapters INTEGER DEFAULT 0');
db.runSync('ALTER TABLE Novel ADD COLUMN lastReadAt TEXT');
db.runSync('ALTER TABLE Novel ADD COLUMN lastUpdatedAt TEXT');
db.runSync(`UPDATE Novel
SET chaptersDownloaded = (
SELECT COUNT(*)
FROM Chapter
WHERE Chapter.novelId = Novel.id AND Chapter.isDownloaded = 1
);
`);
db.runSync(`UPDATE Novel
SET chaptersUnread = (
SELECT COUNT(*)
FROM Chapter
WHERE Chapter.novelId = Novel.id AND Chapter.unread = 1
);
`);
db.runSync(`UPDATE Novel
SET totalChapters = (
SELECT COUNT(*)
FROM Chapter
WHERE Chapter.novelId = Novel.id
);
`);
db.runSync(`UPDATE Novel
SET lastReadAt = (
SELECT MAX(readTime)
FROM Chapter
WHERE Chapter.novelId = Novel.id
);
`);
db.runSync(`UPDATE Novel
SET lastUpdatedAt = (
SELECT MAX(updatedTime)
FROM Chapter
WHERE Chapter.novelId = Novel.id
);
`);
const cols = [
['chaptersDownloaded', 'INTEGER DEFAULT 0'],
['chaptersUnread', 'INTEGER DEFAULT 0'],
['totalChapters', 'INTEGER DEFAULT 0'],
['lastReadAt', 'TEXT'],
['lastUpdatedAt', 'TEXT'],
] as const;
for (const [col, def] of cols) {
if (!columnExists('Novel', col)) {
try {
db.runSync(`ALTER TABLE Novel ADD COLUMN ${col} ${def}`);
} catch (e) {
// ignore duplicate column errors
}
}
}
try {
db.runSync(`UPDATE Novel
SET chaptersDownloaded = (
SELECT COUNT(*)
FROM Chapter
WHERE Chapter.novelId = Novel.id AND Chapter.isDownloaded = 1
);`);
db.runSync(`UPDATE Novel
SET chaptersUnread = (
SELECT COUNT(*)
FROM Chapter
WHERE Chapter.novelId = Novel.id AND Chapter.unread = 1
);`);
db.runSync(`UPDATE Novel
SET totalChapters = (
SELECT COUNT(*)
FROM Chapter
WHERE Chapter.novelId = Novel.id
);`);
db.runSync(`UPDATE Novel
SET lastReadAt = (
SELECT MAX(readTime)
FROM Chapter
WHERE Chapter.novelId = Novel.id
);`);
db.runSync(`UPDATE Novel
SET lastUpdatedAt = (
SELECT MAX(updatedTime)
FROM Chapter
WHERE Chapter.novelId = Novel.id
);`);
} catch {}
db.runSync(createNovelTriggerQueryInsert);
db.runSync(createNovelTriggerQueryUpdate);
db.runSync(createNovelTriggerQueryDelete);
db.execSync('PRAGMA user_version = 1');
});
}

function updateToDBVersion2() {
db.withTransactionSync(() => {
try {
if (
columnExists('Novel', 'cover') &&
!columnExists('Novel', 'cover_path')
) {
db.runSync('ALTER TABLE Novel RENAME COLUMN cover TO cover_path');
}

if (!columnExists('Novel', 'cover')) {
db.runSync('ALTER TABLE Novel ADD COLUMN cover BLOB');
}

const novelsToMigrate = db.getAllSync<{ id: number; cover_path: string }>(
"SELECT id, cover_path FROM Novel WHERE cover_path IS NOT NULL AND cover_path LIKE 'file://%'",
);

const migratedFilePaths: string[] = [];
for (const novel of novelsToMigrate) {
let filePath = novel.cover_path.replace('file://', '');
const queryParamIndex = filePath.indexOf('?');
if (queryParamIndex > -1) {
filePath = filePath.substring(0, queryParamIndex);
}

if (NativeFile.exists(filePath)) {
const base64 = NativeFile.readFileAsBase64(filePath);
db.runSync('UPDATE Novel SET cover = ? WHERE id = ?', [
base64,
novel.id,
]);
migratedFilePaths.push(filePath);
}
}

if (columnExists('Novel', 'cover_path')) {
db.runSync('ALTER TABLE Novel DROP COLUMN cover_path');
}

db.execSync('PRAGMA user_version = 2');

for (const path of migratedFilePaths) {
try {
NativeFile.unlink(path);
} catch (e) {}
}
} catch (error) {
throw error;
}
});
}
Loading
Loading