Files
lnreader/src/database/queries/ChapterQueries.ts
T
2025-05-04 09:49:55 +02:00

373 lines
9.2 KiB
TypeScript

import { showToast } from '@utils/showToast';
import { ChapterInfo, DownloadedChapter, UpdateOverview } from '../types';
import { ChapterItem } from '@plugins/types';
import { Update } from '../types';
import { getString } from '@strings/translations';
import { NOVEL_STORAGE } from '@utils/Storages';
import { db } from '@database/db';
import NativeFile from '@specs/NativeFile';
// #region Mutations
export const insertChapters = async (
novelId: number,
chapters?: ChapterItem[],
) => {
if (!chapters?.length) {
return;
}
await db
.withTransactionAsync(async () => {
const statement = db.prepareSync(`
INSERT INTO Chapter (path, name, releaseTime, novelId, chapterNumber, page, position)
VALUES (?, ?, ?, ${novelId}, ?, ?, ?)
ON CONFLICT(path, novelId) DO UPDATE SET
page = excluded.page,
position = excluded.position,
name = excluded.name,
releaseTime = excluded.releaseTime,
chapterNumber = excluded.chapterNumber;
`);
try {
chapters.map((chapter, index) =>
statement.executeSync(
chapter.path,
chapter.name ?? 'Chapter ' + (index + 1),
chapter.releaseTime || '',
chapter.chapterNumber || null,
chapter.page || '1',
index,
),
);
} finally {
statement.finalizeSync();
}
})
.catch(e => {
console.error(e);
});
return;
};
export const markChapterRead = (chapterId: number) =>
db.runAsync('UPDATE Chapter SET `unread` = 0 WHERE id = ?', chapterId);
export const markChaptersRead = (chapterIds: number[]) =>
db.execAsync(
`UPDATE Chapter SET \`unread\` = 0 WHERE id IN (${chapterIds.join(',')})`,
);
export const markChapterUnread = (chapterId: number) =>
db.runAsync('UPDATE Chapter SET `unread` = 1 WHERE id = ?', chapterId);
export const markChaptersUnread = (chapterIds: number[]) =>
db.execAsync(
`UPDATE Chapter SET \`unread\` = 1 WHERE id IN (${chapterIds.join(',')})`,
);
export const markAllChaptersRead = (novelId: number) =>
db.runAsync('UPDATE Chapter SET `unread` = 0 WHERE novelId = ?', novelId);
export const markAllChaptersUnread = (novelId: number) =>
db.runAsync('UPDATE Chapter SET `unread` = 1 WHERE novelId = ?', novelId);
const deleteDownloadedFiles = async (
pluginId: string,
novelId: number,
chapterId: number,
) => {
try {
const chapterFolder = `${NOVEL_STORAGE}/${pluginId}/${novelId}/${chapterId}`;
NativeFile.unlink(chapterFolder);
} catch {
throw new Error(getString('novelScreen.deleteChapterError'));
}
};
// delete downloaded chapter
export const deleteChapter = async (
pluginId: string,
novelId: number,
chapterId: number,
) => {
await deleteDownloadedFiles(pluginId, novelId, chapterId);
await db.runAsync(
'UPDATE Chapter SET isDownloaded = 0 WHERE id = ?',
chapterId,
);
};
export const deleteChapters = async (
pluginId: string,
novelId: number,
chapters?: ChapterInfo[],
) => {
if (!chapters?.length) {
return;
}
const chapterIdsString = chapters?.map(chapter => chapter.id).toString();
await Promise.all(
chapters?.map(chapter =>
deleteDownloadedFiles(pluginId, novelId, chapter.id),
),
);
await db.execAsync(
`UPDATE Chapter SET isDownloaded = 0 WHERE id IN (${chapterIdsString})`,
);
};
export const deleteDownloads = async (chapters: DownloadedChapter[]) => {
await Promise.all(
chapters?.map(chapter => {
deleteDownloadedFiles(chapter.pluginId, chapter.novelId, chapter.id);
}),
);
await db.execAsync('UPDATE Chapter SET isDownloaded = 0');
};
export const deleteReadChaptersFromDb = async () => {
const chapters = await getReadDownloadedChapters();
await Promise.all(
chapters?.map(chapter => {
deleteDownloadedFiles(chapter.pluginId, chapter.novelId, chapter.novelId);
}),
);
const chapterIdsString = chapters?.map(chapter => chapter.id).toString();
db.execAsync(
`UPDATE Chapter SET isDownloaded = 0 WHERE id IN (${chapterIdsString})`,
);
showToast(getString('novelScreen.readChaptersDeleted'));
};
export const updateChapterProgress = (chapterId: number, progress: number) =>
db.runAsync(
'UPDATE Chapter SET progress = ? WHERE id = ?',
progress,
chapterId,
);
export const updateChapterProgressByIds = (
chapterIds: number[],
progress: number,
) =>
db.runAsync(
`UPDATE Chapter SET progress = ? WHERE id in (${chapterIds.join(',')})`,
progress,
);
export const bookmarkChapter = (chapterId: number) =>
db.runAsync(
'UPDATE Chapter SET bookmark = (CASE WHEN bookmark = 0 THEN 1 ELSE 0 END) WHERE id = ?',
chapterId,
);
export const markPreviuschaptersRead = (chapterId: number, novelId: number) =>
db.runAsync(
'UPDATE Chapter SET `unread` = 0 WHERE id <= ? AND novelId = ?',
chapterId,
novelId,
);
export const markPreviousChaptersUnread = (
chapterId: number,
novelId: number,
) =>
db.runAsync(
'UPDATE Chapter SET `unread` = 1 WHERE id <= ? AND novelId = ?',
chapterId,
novelId,
);
export const clearUpdates = () =>
db.execAsync('UPDATE Chapter SET updatedTime = NULL');
// #endregion
// #region Selectors
export const getCustomPages = (novelId: number) =>
db.getAllSync<{ page: string }>(
'SELECT DISTINCT page from Chapter WHERE novelId = ?',
novelId,
);
export const getNovelChapters = (novelId: number) =>
db.getAllAsync<ChapterInfo>(
'SELECT * FROM Chapter WHERE novelId = ?',
novelId,
);
export const getChapter = (chapterId: number) =>
db.getFirstAsync<ChapterInfo>(
'SELECT * FROM Chapter WHERE id = ?',
chapterId,
);
const getPageChaptersQuery = (
sort = 'ORDER BY position ASC',
filter = '',
limit?: number,
offset?: number,
) =>
`
SELECT * FROM Chapter
WHERE novelId = ? AND page = ?
${filter} ${sort}
${limit ? `LIMIT ${limit}` : ''}
${offset ? `OFFSET ${offset}` : ''}`;
export const getPageChapters = (
novelId: number,
sort?: string,
filter?: string,
page?: string,
offset?: number,
limit?: number,
) => {
return db.getAllAsync<ChapterInfo>(
getPageChaptersQuery(sort, filter, limit, offset),
novelId,
page || '1',
);
};
export const getChapterCount = (novelId: number, page: string = '1') =>
db.getFirstSync<{ 'COUNT(*)': number }>(
'SELECT COUNT(*) FROM Chapter WHERE novelId = ? AND page = ?',
novelId,
page,
)?.['COUNT(*)'] ?? 0;
export const getPageChaptersBatched = (
novelId: number,
sort?: string,
filter?: string,
page?: string,
batch: number = 0,
) => {
return db.getAllSync<ChapterInfo>(
getPageChaptersQuery(sort, filter, 300, 300 * batch),
novelId,
page || '1',
);
};
export const getPrevChapter = (
novelId: number,
chapterPosition: number,
page: string,
) =>
db.getFirstAsync<ChapterInfo>(
`SELECT * FROM Chapter
WHERE novelId = ?
AND (
(position < ? AND page = ?)
OR page < ?
)
ORDER BY position DESC, page DESC`,
novelId,
chapterPosition,
page,
page,
);
export const getNextChapter = (
novelId: number,
chapterPosition: number,
page: string,
) =>
db.getFirstAsync<ChapterInfo>(
`SELECT * FROM Chapter
WHERE novelId = ?
AND (
(page = ? AND position > ?)
OR (position = 0 AND page > ?)
)
ORDER BY position ASC, page ASC`,
novelId,
page,
chapterPosition,
page,
);
const getReadDownloadedChapters = () =>
db.getAllAsync<DownloadedChapter>(`
SELECT Chapter.id, Chapter.novelId, pluginId
FROM Chapter
JOIN Novel
ON Novel.id = Chapter.novelId AND unread = 0 AND isDownloaded = 1`);
export const getDownloadedChapters = () =>
db.getAllAsync<DownloadedChapter>(`
SELECT
Chapter.*,
Novel.pluginId, Novel.name as novelName, Novel.cover as novelCover, Novel.path as novelPath
FROM Chapter
JOIN Novel
ON Chapter.novelId = Novel.id
WHERE Chapter.isDownloaded = 1
`);
export const getUpdatedOverviewFromDb = () =>
db.getAllAsync<UpdateOverview>(`SELECT
Novel.id AS novelId,
Novel.name AS novelName,
Novel.cover AS novelCover,
Novel.path AS novelPath,
DATE(Chapter.updatedTime) AS updateDate, -- Extract the date from updatedTime
COUNT(*) AS updatesPerDay
FROM
Chapter
JOIN
Novel
ON
Chapter.novelId = Novel.id
WHERE
Chapter.updatedTime IS NOT NULL
GROUP BY
Novel.id,
Novel.name,
Novel.cover,
Novel.path,
DATE(Chapter.updatedTime) -- Group by date and novelId
ORDER BY
novelId,
updateDate;
`);
export const getDetailedUpdatesFromDb = async (
novelId: number,
onlyDownloadableChapters?: boolean,
) => {
const result = db.getAllAsync<Update>(
`
SELECT
Chapter.*,
pluginId, Novel.id as novelId, Novel.name as novelName, Novel.path as novelPath, cover as novelCover
FROM
Chapter
JOIN
Novel
ON Chapter.novelId = Novel.id
WHERE novelId = ? ${
onlyDownloadableChapters
? 'AND Chapter.isDownloaded = 1 '
: 'AND updatedTime IS NOT NULL'
}
ORDER BY updatedTime DESC;
`,
novelId,
);
return await result;
};
export const isChapterDownloaded = (chapterId: number) =>
!!db.getFirstSync<ChapterInfo>(
'SELECT * FROM Chapter WHERE id = ? AND isDownloaded = 1',
chapterId,
);