From cfee61d1c4bd66cfeaf163fcde1776444f311c70 Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Mon, 5 Feb 2024 23:53:17 +0100 Subject: [PATCH 01/11] Added functionalities - Added auto download on startup - Added auto download on loading series detail page - Added auto download when loading new chapter - Added value to determine how many unread chapters should be downloaded - Added auto delete read chapters on startup - Added auto delete read chapters on loading series detail page - Added a regex to check if url is valid to check if series is a site or filesystem (can be used for other things) --- src/components/general/DashboardPage.tsx | 20 ++- src/components/library/SeriesDetails.tsx | 18 +++ src/components/reader/ReaderPage.tsx | 11 ++ src/components/settings/GeneralSettings.tsx | 114 +++++++++++++++++- src/features/library/chapterDownloadUtils.tsx | 91 +++++++++++++- src/models/types.ts | 21 ++++ src/services/library.ts | 11 ++ src/state/settingStates.ts | 7 ++ 8 files changed, 290 insertions(+), 3 deletions(-) diff --git a/src/components/general/DashboardPage.tsx b/src/components/general/DashboardPage.tsx index 217ee1db3..787352bad 100644 --- a/src/components/general/DashboardPage.tsx +++ b/src/components/general/DashboardPage.tsx @@ -35,10 +35,16 @@ import { autoBackupState, chapterLanguagesState, refreshOnStartState, + OnStartDownloadUnreadCountState, + OnStartUpDeleteReadState, + OnStartUpDownloadUnreadState, + customDownloadsDirState, } from '../../state/settingStates'; import DashboardSidebarLink from './DashboardSidebarLink'; import { downloadCover } from '../../util/download'; import { createAutoBackup } from '../../util/backup'; +import { DeleteReadChapters, DownloadUnreadChapters } from '../../features/library/chapterDownloadUtils'; +import { getDefaultDownloadDir } from '../settings/GeneralSettings'; // eslint-disable-next-line @typescript-eslint/no-empty-interface interface Props {} @@ -56,6 +62,11 @@ const DashboardPage: React.FC = () => { const [importing, setImporting] = useRecoilState(importingState); const categoryList = useRecoilValue(categoryListState); + const OnStartUpDownloadUnread = useRecoilValue(OnStartUpDownloadUnreadState); + const OnStartUpDownloadUnreadCount = useRecoilValue(OnStartDownloadUnreadCountState); + const OnStartUpDeleteRead = useRecoilValue(OnStartUpDeleteReadState); + const customDownloadsDir = useRecoilValue(customDownloadsDirState); + useEffect(() => { if (autoBackup) { createAutoBackup(autoBackupCount); @@ -68,7 +79,14 @@ const DashboardPage: React.FC = () => { setReloadingSeriesList, chapterLanguages, categoryList - ).catch((e) => log.error(e)); + ).then(() => { + if(OnStartUpDeleteRead){ + DeleteReadChapters(library.fetchSeriesList(), customDownloadsDir || String(getDefaultDownloadDir())); + } + if(OnStartUpDownloadUnread){ + DownloadUnreadChapters(library.fetchSeriesList(), customDownloadsDir || String(getDefaultDownloadDir()), OnStartUpDownloadUnreadCount); + } + }).catch((e) => log.error(e)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeSeriesList]); diff --git a/src/components/library/SeriesDetails.tsx b/src/components/library/SeriesDetails.tsx index 12f591d7f..29aa82b87 100644 --- a/src/components/library/SeriesDetails.tsx +++ b/src/components/library/SeriesDetails.tsx @@ -29,11 +29,16 @@ import SeriesDetailsBanner from './series/SeriesDetailsBanner'; import SeriesDetailsIntro from './series/SeriesDetailsIntro'; import SeriesDetailsInfoGrid from './series/SeriesDetailsInfoGrid'; +import { OnSeriesDetailsDeleteReadState, OnSeriesDetailsDownloadUnreadState, OnStartDownloadUnreadCountState, OnStartUpDeleteReadState, OnStartUpDownloadUnreadState, customDownloadsDirState } from '../../state/settingStates'; +import { DeleteReadChapters, DownloadUnreadChapters } from '../../features/library/chapterDownloadUtils'; +import { getDefaultDownloadDir } from '../settings/GeneralSettings'; + type Props = unknown; const SeriesDetails: React.FC = () => { const { id } = useParams<{ id: string }>(); let series: Series = library.fetchSeries(id!)!; + let seriesArr: Series[] = new Array(1); const location = useLocation(); const setExtensionMetadata = useSetRecoilState(currentExtensionMetadataState); @@ -47,6 +52,12 @@ const SeriesDetails: React.FC = () => { const setSeriesBannerUrl = useSetRecoilState(seriesBannerUrlState); const setChapterFilterTitle = useSetRecoilState(chapterFilterTitleState); const setChapterFilterGroup = useSetRecoilState(chapterFilterGroupState); + + const customDownloadsDir = useRecoilValue(customDownloadsDirState); + const OnStartUpDownloadUnreadCount = useRecoilValue(OnStartDownloadUnreadCountState); + const OnSeriesDetailsDownloadUnread = useRecoilValue(OnSeriesDetailsDownloadUnreadState); + const OnSeriesDetailsDeleteRead = useRecoilValue(OnSeriesDetailsDeleteReadState); + const loadContent = async () => { log.info(`Series page is loading details from database for series ${id}`); @@ -70,6 +81,13 @@ const SeriesDetails: React.FC = () => { useEffect(() => { loadContent(); + seriesArr[0] = series; + if(OnSeriesDetailsDeleteRead){ + DeleteReadChapters(seriesArr, customDownloadsDir || String(getDefaultDownloadDir())); + } + if(OnSeriesDetailsDownloadUnread){ + DownloadUnreadChapters(seriesArr, customDownloadsDir || String(getDefaultDownloadDir()), OnStartUpDownloadUnreadCount); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [id, seriesList]); diff --git a/src/components/reader/ReaderPage.tsx b/src/components/reader/ReaderPage.tsx index d8b85d2df..9b3fee46c 100644 --- a/src/components/reader/ReaderPage.tsx +++ b/src/components/reader/ReaderPage.tsx @@ -25,12 +25,15 @@ import { updateTitlebarText } from '../../util/titlebar'; import * as libraryStates from '../../state/libraryStates'; import * as readerStates from '../../state/readerStates'; import * as settingStates from '../../state/settingStates'; +import { DownloadUnreadChapters } from '../../features/library/chapterDownloadUtils'; +import { getDefaultDownloadDir } from '../settings/GeneralSettings'; import { nextOffsetPages, nextPageStyle, nextReadingDirection, } from '../../features/settings/utils'; + const defaultDownloadsDir = await ipcRenderer.invoke(ipcChannels.GET_PATH.DEFAULT_DOWNLOADS_DIR); // eslint-disable-next-line @typescript-eslint/ban-types @@ -99,6 +102,10 @@ const ReaderPage: React.FC = (props: Props) => { const keyToggleFullscreen = useRecoilValue(settingStates.keyToggleFullscreenState); const keyExit = useRecoilValue(settingStates.keyExitState); const keyCloseOrBack = useRecoilValue(settingStates.keyCloseOrBackState); + const OnStartUpDownloadUnreadCount = useRecoilValue(settingStates.OnStartDownloadUnreadCountState); + const OnScrollingChaptersDownloadUnread = useRecoilValue(settingStates.OnScrollingChaptersDownloadUnreadState); + + let seriesArr: Series[] = new Array(1); /** * Populate the relevantChapterList prop. @@ -281,6 +288,10 @@ const ReaderPage: React.FC = (props: Props) => { if (newChapterId === null) return false; const desiredPage = fromPageMovement && previous ? Infinity : 1; setChapter(newChapterId, desiredPage); + seriesArr[0] != library.fetchSeries(series_id!); + if(OnScrollingChaptersDownloadUnread){ + DownloadUnreadChapters(seriesArr, customDownloadsDir || String(getDefaultDownloadDir()), OnStartUpDownloadUnreadCount); + } return true; }; diff --git a/src/components/settings/GeneralSettings.tsx b/src/components/settings/GeneralSettings.tsx index 89661a8d9..30b2e50c8 100644 --- a/src/components/settings/GeneralSettings.tsx +++ b/src/components/settings/GeneralSettings.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Language, LanguageKey, Languages } from '@tiyo/common'; import { ipcRenderer } from 'electron'; import { useRecoilState } from 'recoil'; @@ -13,6 +13,7 @@ import { Stack, Text, Tooltip, + Accordion, } from '@mantine/core'; import { IconArrowBack } from '@tabler/icons'; import { GeneralSetting } from '../../models/types'; @@ -27,6 +28,13 @@ import { customDownloadsDirState, libraryCropCoversState, refreshOnStartState, + + OnScrollingChaptersDownloadUnreadState, + OnSeriesDetailsDeleteReadState, + OnSeriesDetailsDownloadUnreadState, + OnStartDownloadUnreadCountState, + OnStartUpDeleteReadState, + OnStartUpDownloadUnreadState, } from '../../state/settingStates'; const languageOptions = Object.values(Languages) @@ -48,6 +56,14 @@ const GeneralSettings: React.FC = () => { const [libraryCropCovers, setLibraryCropCovers] = useRecoilState(libraryCropCoversState); const [customDownloadsDir, setCustomDownloadsDir] = useRecoilState(customDownloadsDirState); + const [OnStartUpDownloadUnread, setOnStartUpDownloadUnread] = useRecoilState(OnStartUpDownloadUnreadState); + const [OnSeriesDetailsDownloadUnread, setOnSeriesDetailsDownloadUnread] = useRecoilState(OnSeriesDetailsDownloadUnreadState); + const [OnScrollingChaptersDownloadUnread, setOnScrollingChaptersDownloadUnread] = useRecoilState(OnScrollingChaptersDownloadUnreadState); + const [OnSeriesDetailsDeleteRead, setOnSeriesDetailsDeleteRead] = useRecoilState(OnSeriesDetailsDeleteReadState); + const [OnStartUpDeleteRead, setOnStartUpDeleteRead] = useRecoilState(OnStartUpDeleteReadState); + const [OnStartDownloadUnreadCount, setOnStartDownloadUnreadCount] = useRecoilState(OnStartDownloadUnreadCountState); + const [automation, setautomation] = useState([]); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const updateGeneralSetting = (generalSetting: GeneralSetting, value: any) => { switch (generalSetting) { @@ -75,6 +91,30 @@ const GeneralSettings: React.FC = () => { case GeneralSetting.autoBackupCount: setAutoBackupCount(value); break; + case GeneralSetting.OnStartUpDownloadUnread: + setOnStartUpDownloadUnread(value); + break; + case GeneralSetting.OnStartUpDeleteRead: + setOnStartUpDeleteRead(value); + break; + case GeneralSetting.OnStartUpDownloadUnreadCount: + setOnStartDownloadUnreadCount(value); + break; + case GeneralSetting.OnSeriesDetailsDownloadUnread: + setOnSeriesDetailsDownloadUnread(value); + break; + case GeneralSetting.OnSeriesDetailsDeleteRead: + setOnSeriesDetailsDeleteRead(value); + break; + case GeneralSetting.OnScrollingChaptersDownloadUnread: + setOnScrollingChaptersDownloadUnread(value); + break; + case GeneralSetting.autoBackup: + setAutoBackup(value); + break; + case GeneralSetting.autoBackupCount: + setAutoBackupCount(value); + break; default: break; } @@ -208,8 +248,80 @@ const GeneralSettings: React.FC = () => { + + Automation + + + Auto Download + + + + updateGeneralSetting(GeneralSetting.OnStartUpDownloadUnread, e.target.checked) + } + /> + + updateGeneralSetting(GeneralSetting.OnSeriesDetailsDownloadUnread, e.target.checked) + } + /> + + updateGeneralSetting(GeneralSetting.OnScrollingChaptersDownloadUnread, e.target.checked) + } + /> + how many unread chapters to keep downloaded + + updateGeneralSetting(GeneralSetting.OnStartUpDownloadUnreadCount, value) + } + /> +
+
+
+
+ + Auto Delete + + + + updateGeneralSetting(GeneralSetting.OnStartUpDeleteRead, e.target.checked) + } + /> + + updateGeneralSetting(GeneralSetting.OnSeriesDetailsDeleteRead, e.target.checked) + } + /> + + + +
); }; export default GeneralSettings; + +export async function getDefaultDownloadDir(): Promise { + return await ipcRenderer.invoke(ipcChannels.GET_PATH.DEFAULT_DOWNLOADS_DIR); +} \ No newline at end of file diff --git a/src/features/library/chapterDownloadUtils.tsx b/src/features/library/chapterDownloadUtils.tsx index 4bee25d96..908fa9566 100644 --- a/src/features/library/chapterDownloadUtils.tsx +++ b/src/features/library/chapterDownloadUtils.tsx @@ -1,6 +1,8 @@ import { Chapter, Series } from '@tiyo/common'; import { downloaderClient, DownloadTask } from '../../services/downloader'; -import { getChapterDownloaded } from '../../util/filesystem'; +import { deleteDownloadedChapter, getChapterDownloaded, getChaptersDownloaded } from '../../util/filesystem'; +import library from '../../services/library'; + export async function downloadNextX( chapterList: Chapter[], @@ -78,3 +80,90 @@ export async function downloadAll( ); downloaderClient.start(); } + + +/** + * The function `DownloadUnreadChapters` downloads a specified number of unread chapters from a list of + * series, filtering out already downloaded chapters. + * @param {Series[]} seriesList - An array of objects representing a list of series. Each series object + * should have properties like `sourceId`, `numberUnread`, and `id`. + * @param {string} downloadsDir - The `downloadsDir` parameter is a string that represents the + * directory where the downloaded chapters will be saved. + * @param {number} [count=1] - The `count` parameter specifies the number of unread chapters to + * download for each series. By default, it is set to 1, meaning it will download the latest unread + * chapter. However, you can provide a different value to download a specific number of unread + * chapters. + * @param {Chapter[]} serieChapters - An array of Chapter objects representing the chapters of a + * series. + * @returns The function does not have a return statement. + */ +export async function DownloadUnreadChapters( + seriesList: Series[], + downloadsDir: string, + count: number = 1, + serieChapters: Chapter[] = [] +){ + for (const series of seriesList) { + if(!library.validURL(series.sourceId)){return;} + + if(series.numberUnread <= 0 || !series.id) continue; + serieChapters = library.fetchChapters(series.id); + serieChapters = serieChapters.filter(x => !x.read); + serieChapters.sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)); + serieChapters = serieChapters.slice(0,count); + var nonDownloadedChapters: Chapter[] = []; + for(const x of serieChapters){ + const result = await getChapterDownloaded(series, x, downloadsDir); + if(result !== true){ + nonDownloadedChapters.push(x) + } + } + downloaderClient.add( + nonDownloadedChapters.map( + (chapter: Chapter) => + ({ + chapter, + series, + downloadsDir, + } as DownloadTask)) + ); + downloaderClient.start(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + }; +} + +/** + * The function `DeleteReadChapters` deletes downloaded chapters for a list of series that have been + * marked as read. + * @param {Series[]} seriesList - An array of objects representing a list of series. Each object in the + * array should have an "id" property. + * @param {string} downloadsDir - The `downloadsDir` parameter is a string that represents the + * directory where the downloaded chapters are stored. + * @param {Chapter[]} [serieChapters] - An optional array of Chapter objects representing the chapters + * of a series. + * @returns The function does not have a return statement. + */ +export async function DeleteReadChapters( + seriesList: Series[], + downloadsDir: string, + serieChapters?: Chapter[] +){ + for(const series of seriesList){ + if(!series.id) return; + serieChapters = library.fetchChapters(series.id); + serieChapters = serieChapters.filter(x => x.read); + var DownloadedChapters: Chapter[] = []; + const downloadedChapters = await getChaptersDownloaded(series, serieChapters, downloadsDir); + for(const key in downloadedChapters){ + if(downloadedChapters.hasOwnProperty(key)){ + var foundChapter = serieChapters.find((chapter) => chapter.id === key); + if(foundChapter){ + DownloadedChapters.push(foundChapter); + } + } + } + for(const x of DownloadedChapters){ + await deleteDownloadedChapter(series, x, downloadsDir) + } + } +} \ No newline at end of file diff --git a/src/models/types.ts b/src/models/types.ts index a31c4177a..5a9c5b51f 100644 --- a/src/models/types.ts +++ b/src/models/types.ts @@ -98,6 +98,13 @@ export enum GeneralSetting { ChapterListVolOrder = 'ChapterListVolOrder', ChapterListChOrder = 'ChapterListChOrder', ChapterListPageSize = 'ChapterListPageSize', + + OnStartUpDownloadUnread = 'OnStartUpDownloadUnread', + OnSeriesDetailsDownloadUnread = 'OnSeriesDetailsDownloadUnread', + OnScrollingChaptersDownloadUnread = 'OnScrollingChaptersDownloadUnread', + OnStartUpDownloadUnreadCount = 'OnStartUpDownloadUnreadCount', + OnStartUpDeleteRead = 'OnStartUpDeleteRead', + OnSeriesDetailsDeleteRead = 'OnSeriesDetailsDeleteRead', } export enum ProgressFilter { @@ -237,6 +244,13 @@ export const SettingTypes = { [TrackerSetting.TrackerAutoUpdate]: SettingType.BOOLEAN, [IntegrationSetting.DiscordPresenceEnabled]: SettingType.BOOLEAN, + + [GeneralSetting.OnStartUpDownloadUnread]: SettingType.BOOLEAN, + [GeneralSetting.OnSeriesDetailsDownloadUnread]: SettingType.BOOLEAN, + [GeneralSetting.OnScrollingChaptersDownloadUnread]: SettingType.BOOLEAN, + [GeneralSetting.OnStartUpDownloadUnreadCount]: SettingType.NUMBER, + [GeneralSetting.OnStartUpDeleteRead]: SettingType.BOOLEAN, + [GeneralSetting.OnSeriesDetailsDeleteRead]: SettingType.BOOLEAN, }; export const DefaultSettings = { @@ -290,4 +304,11 @@ export const DefaultSettings = { [TrackerSetting.TrackerAutoUpdate]: true, [IntegrationSetting.DiscordPresenceEnabled]: false, + + [GeneralSetting.OnStartUpDownloadUnread]: false, + [GeneralSetting.OnSeriesDetailsDownloadUnread]: false, + [GeneralSetting.OnScrollingChaptersDownloadUnread]: false, + [GeneralSetting.OnStartUpDownloadUnreadCount]: 0, + [GeneralSetting.OnStartUpDeleteRead]: false, + [GeneralSetting.OnSeriesDetailsDeleteRead]: false, }; diff --git a/src/services/library.ts b/src/services/library.ts index 7cb18e6f5..8f906f865 100644 --- a/src/services/library.ts +++ b/src/services/library.ts @@ -105,6 +105,16 @@ const removeCategory = (categoryId: string): void => { ); }; +const validURL = (str: string): boolean => { + var pattern = new RegExp('^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator + return !!pattern.test(str); +} + export default { fetchSeriesList, fetchSeries, @@ -117,4 +127,5 @@ export default { fetchCategoryList, upsertCategory, removeCategory, + validURL, }; diff --git a/src/state/settingStates.ts b/src/state/settingStates.ts index a7de9e703..31c1bcd81 100644 --- a/src/state/settingStates.ts +++ b/src/state/settingStates.ts @@ -104,4 +104,11 @@ export const optimizeContrastState = atomFromSetting(ReaderSetting.Opti export const trackerAutoUpdateState = atomFromSetting(TrackerSetting.TrackerAutoUpdate); export const discordPresenceEnabledState = atomFromSetting(IntegrationSetting.DiscordPresenceEnabled); + +export const OnStartUpDownloadUnreadState = atomFromSetting(GeneralSetting.OnStartUpDownloadUnread); +export const OnSeriesDetailsDownloadUnreadState = atomFromSetting(GeneralSetting.OnSeriesDetailsDownloadUnread); +export const OnScrollingChaptersDownloadUnreadState = atomFromSetting(GeneralSetting.OnScrollingChaptersDownloadUnread); +export const OnStartDownloadUnreadCountState = atomFromSetting(GeneralSetting.OnStartUpDownloadUnreadCount); +export const OnStartUpDeleteReadState = atomFromSetting(GeneralSetting.OnStartUpDeleteRead); +export const OnSeriesDetailsDeleteReadState = atomFromSetting(GeneralSetting.OnSeriesDetailsDeleteRead); /* eslint-enable */ From 74c2f2f565ed14f3663c11c055b76ca1be2a702a Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Wed, 7 Feb 2024 11:18:14 +0100 Subject: [PATCH 02/11] ran yarn lint --fix --- src/components/general/DashboardPage.tsx | 30 ++- src/components/library/SeriesDetails.tsx | 26 ++- src/components/reader/ReaderPage.tsx | 19 +- src/components/settings/GeneralSettings.tsx | 189 ++++++++++-------- src/features/library/chapterDownloadUtils.tsx | 59 +++--- src/services/library.ts | 17 +- 6 files changed, 200 insertions(+), 140 deletions(-) diff --git a/src/components/general/DashboardPage.tsx b/src/components/general/DashboardPage.tsx index 787352bad..fd295b8cb 100644 --- a/src/components/general/DashboardPage.tsx +++ b/src/components/general/DashboardPage.tsx @@ -43,7 +43,10 @@ import { import DashboardSidebarLink from './DashboardSidebarLink'; import { downloadCover } from '../../util/download'; import { createAutoBackup } from '../../util/backup'; -import { DeleteReadChapters, DownloadUnreadChapters } from '../../features/library/chapterDownloadUtils'; +import { + DeleteReadChapters, + DownloadUnreadChapters, +} from '../../features/library/chapterDownloadUtils'; import { getDefaultDownloadDir } from '../settings/GeneralSettings'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -79,14 +82,23 @@ const DashboardPage: React.FC = () => { setReloadingSeriesList, chapterLanguages, categoryList - ).then(() => { - if(OnStartUpDeleteRead){ - DeleteReadChapters(library.fetchSeriesList(), customDownloadsDir || String(getDefaultDownloadDir())); - } - if(OnStartUpDownloadUnread){ - DownloadUnreadChapters(library.fetchSeriesList(), customDownloadsDir || String(getDefaultDownloadDir()), OnStartUpDownloadUnreadCount); - } - }).catch((e) => log.error(e)); + ) + .then(() => { + if (OnStartUpDeleteRead) { + DeleteReadChapters( + library.fetchSeriesList(), + customDownloadsDir || String(getDefaultDownloadDir()) + ); + } + if (OnStartUpDownloadUnread) { + DownloadUnreadChapters( + library.fetchSeriesList(), + customDownloadsDir || String(getDefaultDownloadDir()), + OnStartUpDownloadUnreadCount + ); + } + }) + .catch((e) => log.error(e)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeSeriesList]); diff --git a/src/components/library/SeriesDetails.tsx b/src/components/library/SeriesDetails.tsx index 29aa82b87..75a8072c0 100644 --- a/src/components/library/SeriesDetails.tsx +++ b/src/components/library/SeriesDetails.tsx @@ -29,8 +29,18 @@ import SeriesDetailsBanner from './series/SeriesDetailsBanner'; import SeriesDetailsIntro from './series/SeriesDetailsIntro'; import SeriesDetailsInfoGrid from './series/SeriesDetailsInfoGrid'; -import { OnSeriesDetailsDeleteReadState, OnSeriesDetailsDownloadUnreadState, OnStartDownloadUnreadCountState, OnStartUpDeleteReadState, OnStartUpDownloadUnreadState, customDownloadsDirState } from '../../state/settingStates'; -import { DeleteReadChapters, DownloadUnreadChapters } from '../../features/library/chapterDownloadUtils'; +import { + OnSeriesDetailsDeleteReadState, + OnSeriesDetailsDownloadUnreadState, + OnStartDownloadUnreadCountState, + OnStartUpDeleteReadState, + OnStartUpDownloadUnreadState, + customDownloadsDirState, +} from '../../state/settingStates'; +import { + DeleteReadChapters, + DownloadUnreadChapters, +} from '../../features/library/chapterDownloadUtils'; import { getDefaultDownloadDir } from '../settings/GeneralSettings'; type Props = unknown; @@ -38,7 +48,7 @@ type Props = unknown; const SeriesDetails: React.FC = () => { const { id } = useParams<{ id: string }>(); let series: Series = library.fetchSeries(id!)!; - let seriesArr: Series[] = new Array(1); + const seriesArr: Series[] = new Array(1); const location = useLocation(); const setExtensionMetadata = useSetRecoilState(currentExtensionMetadataState); @@ -82,11 +92,15 @@ const SeriesDetails: React.FC = () => { useEffect(() => { loadContent(); seriesArr[0] = series; - if(OnSeriesDetailsDeleteRead){ + if (OnSeriesDetailsDeleteRead) { DeleteReadChapters(seriesArr, customDownloadsDir || String(getDefaultDownloadDir())); } - if(OnSeriesDetailsDownloadUnread){ - DownloadUnreadChapters(seriesArr, customDownloadsDir || String(getDefaultDownloadDir()), OnStartUpDownloadUnreadCount); + if (OnSeriesDetailsDownloadUnread) { + DownloadUnreadChapters( + seriesArr, + customDownloadsDir || String(getDefaultDownloadDir()), + OnStartUpDownloadUnreadCount + ); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [id, seriesList]); diff --git a/src/components/reader/ReaderPage.tsx b/src/components/reader/ReaderPage.tsx index 9b3fee46c..34595aaeb 100644 --- a/src/components/reader/ReaderPage.tsx +++ b/src/components/reader/ReaderPage.tsx @@ -33,7 +33,6 @@ import { nextReadingDirection, } from '../../features/settings/utils'; - const defaultDownloadsDir = await ipcRenderer.invoke(ipcChannels.GET_PATH.DEFAULT_DOWNLOADS_DIR); // eslint-disable-next-line @typescript-eslint/ban-types @@ -102,10 +101,14 @@ const ReaderPage: React.FC = (props: Props) => { const keyToggleFullscreen = useRecoilValue(settingStates.keyToggleFullscreenState); const keyExit = useRecoilValue(settingStates.keyExitState); const keyCloseOrBack = useRecoilValue(settingStates.keyCloseOrBackState); - const OnStartUpDownloadUnreadCount = useRecoilValue(settingStates.OnStartDownloadUnreadCountState); - const OnScrollingChaptersDownloadUnread = useRecoilValue(settingStates.OnScrollingChaptersDownloadUnreadState); + const OnStartUpDownloadUnreadCount = useRecoilValue( + settingStates.OnStartDownloadUnreadCountState + ); + const OnScrollingChaptersDownloadUnread = useRecoilValue( + settingStates.OnScrollingChaptersDownloadUnreadState + ); - let seriesArr: Series[] = new Array(1); + const seriesArr: Series[] = new Array(1); /** * Populate the relevantChapterList prop. @@ -289,8 +292,12 @@ const ReaderPage: React.FC = (props: Props) => { const desiredPage = fromPageMovement && previous ? Infinity : 1; setChapter(newChapterId, desiredPage); seriesArr[0] != library.fetchSeries(series_id!); - if(OnScrollingChaptersDownloadUnread){ - DownloadUnreadChapters(seriesArr, customDownloadsDir || String(getDefaultDownloadDir()), OnStartUpDownloadUnreadCount); + if (OnScrollingChaptersDownloadUnread) { + DownloadUnreadChapters( + seriesArr, + customDownloadsDir || String(getDefaultDownloadDir()), + OnStartUpDownloadUnreadCount + ); } return true; }; diff --git a/src/components/settings/GeneralSettings.tsx b/src/components/settings/GeneralSettings.tsx index 30b2e50c8..8ddc1678e 100644 --- a/src/components/settings/GeneralSettings.tsx +++ b/src/components/settings/GeneralSettings.tsx @@ -28,7 +28,6 @@ import { customDownloadsDirState, libraryCropCoversState, refreshOnStartState, - OnScrollingChaptersDownloadUnreadState, OnSeriesDetailsDeleteReadState, OnSeriesDetailsDownloadUnreadState, @@ -56,12 +55,22 @@ const GeneralSettings: React.FC = () => { const [libraryCropCovers, setLibraryCropCovers] = useRecoilState(libraryCropCoversState); const [customDownloadsDir, setCustomDownloadsDir] = useRecoilState(customDownloadsDirState); - const [OnStartUpDownloadUnread, setOnStartUpDownloadUnread] = useRecoilState(OnStartUpDownloadUnreadState); - const [OnSeriesDetailsDownloadUnread, setOnSeriesDetailsDownloadUnread] = useRecoilState(OnSeriesDetailsDownloadUnreadState); - const [OnScrollingChaptersDownloadUnread, setOnScrollingChaptersDownloadUnread] = useRecoilState(OnScrollingChaptersDownloadUnreadState); - const [OnSeriesDetailsDeleteRead, setOnSeriesDetailsDeleteRead] = useRecoilState(OnSeriesDetailsDeleteReadState); + const [OnStartUpDownloadUnread, setOnStartUpDownloadUnread] = useRecoilState( + OnStartUpDownloadUnreadState + ); + const [OnSeriesDetailsDownloadUnread, setOnSeriesDetailsDownloadUnread] = useRecoilState( + OnSeriesDetailsDownloadUnreadState + ); + const [OnScrollingChaptersDownloadUnread, setOnScrollingChaptersDownloadUnread] = useRecoilState( + OnScrollingChaptersDownloadUnreadState + ); + const [OnSeriesDetailsDeleteRead, setOnSeriesDetailsDeleteRead] = useRecoilState( + OnSeriesDetailsDeleteReadState + ); const [OnStartUpDeleteRead, setOnStartUpDeleteRead] = useRecoilState(OnStartUpDeleteReadState); - const [OnStartDownloadUnreadCount, setOnStartDownloadUnreadCount] = useRecoilState(OnStartDownloadUnreadCountState); + const [OnStartDownloadUnreadCount, setOnStartDownloadUnreadCount] = useRecoilState( + OnStartDownloadUnreadCountState + ); const [automation, setautomation] = useState([]); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -94,26 +103,26 @@ const GeneralSettings: React.FC = () => { case GeneralSetting.OnStartUpDownloadUnread: setOnStartUpDownloadUnread(value); break; - case GeneralSetting.OnStartUpDeleteRead: - setOnStartUpDeleteRead(value); + case GeneralSetting.OnStartUpDeleteRead: + setOnStartUpDeleteRead(value); break; - case GeneralSetting.OnStartUpDownloadUnreadCount: - setOnStartDownloadUnreadCount(value); + case GeneralSetting.OnStartUpDownloadUnreadCount: + setOnStartDownloadUnreadCount(value); break; - case GeneralSetting.OnSeriesDetailsDownloadUnread: - setOnSeriesDetailsDownloadUnread(value); + case GeneralSetting.OnSeriesDetailsDownloadUnread: + setOnSeriesDetailsDownloadUnread(value); break; - case GeneralSetting.OnSeriesDetailsDeleteRead: - setOnSeriesDetailsDeleteRead(value); + case GeneralSetting.OnSeriesDetailsDeleteRead: + setOnSeriesDetailsDeleteRead(value); break; - case GeneralSetting.OnScrollingChaptersDownloadUnread: - setOnScrollingChaptersDownloadUnread(value); + case GeneralSetting.OnScrollingChaptersDownloadUnread: + setOnScrollingChaptersDownloadUnread(value); break; - case GeneralSetting.autoBackup: - setAutoBackup(value); + case GeneralSetting.autoBackup: + setAutoBackup(value); break; - case GeneralSetting.autoBackupCount: - setAutoBackupCount(value); + case GeneralSetting.autoBackupCount: + setAutoBackupCount(value); break; default: break; @@ -251,70 +260,80 @@ const GeneralSettings: React.FC = () => { Automation - - Auto Download - - - - updateGeneralSetting(GeneralSetting.OnStartUpDownloadUnread, e.target.checked) - } - /> - - updateGeneralSetting(GeneralSetting.OnSeriesDetailsDownloadUnread, e.target.checked) - } - /> - - updateGeneralSetting(GeneralSetting.OnScrollingChaptersDownloadUnread, e.target.checked) - } - /> - how many unread chapters to keep downloaded - - updateGeneralSetting(GeneralSetting.OnStartUpDownloadUnreadCount, value) - } - /> -
-
-
-
- - Auto Delete - - - - updateGeneralSetting(GeneralSetting.OnStartUpDeleteRead, e.target.checked) - } - /> + + Auto Download + + - updateGeneralSetting(GeneralSetting.OnSeriesDetailsDeleteRead, e.target.checked) - } - /> - - - + label="Download unread chapters upon startup" + size="md" + checked={OnStartUpDownloadUnread} + onChange={(e) => + updateGeneralSetting(GeneralSetting.OnStartUpDownloadUnread, e.target.checked) + } + /> + + updateGeneralSetting( + GeneralSetting.OnSeriesDetailsDownloadUnread, + e.target.checked + ) + } + /> + + updateGeneralSetting( + GeneralSetting.OnScrollingChaptersDownloadUnread, + e.target.checked + ) + } + /> + how many unread chapters to keep downloaded + + updateGeneralSetting(GeneralSetting.OnStartUpDownloadUnreadCount, value) + } + /> +
+
+
+
+ + Auto Delete + + + + updateGeneralSetting(GeneralSetting.OnStartUpDeleteRead, e.target.checked) + } + /> + + updateGeneralSetting(GeneralSetting.OnSeriesDetailsDeleteRead, e.target.checked) + } + /> + + +
); @@ -323,5 +342,5 @@ const GeneralSettings: React.FC = () => { export default GeneralSettings; export async function getDefaultDownloadDir(): Promise { - return await ipcRenderer.invoke(ipcChannels.GET_PATH.DEFAULT_DOWNLOADS_DIR); -} \ No newline at end of file + return ipcRenderer.invoke(ipcChannels.GET_PATH.DEFAULT_DOWNLOADS_DIR); +} diff --git a/src/features/library/chapterDownloadUtils.tsx b/src/features/library/chapterDownloadUtils.tsx index 908fa9566..c9ac97699 100644 --- a/src/features/library/chapterDownloadUtils.tsx +++ b/src/features/library/chapterDownloadUtils.tsx @@ -1,9 +1,12 @@ import { Chapter, Series } from '@tiyo/common'; import { downloaderClient, DownloadTask } from '../../services/downloader'; -import { deleteDownloadedChapter, getChapterDownloaded, getChaptersDownloaded } from '../../util/filesystem'; +import { + deleteDownloadedChapter, + getChapterDownloaded, + getChaptersDownloaded, +} from '../../util/filesystem'; import library from '../../services/library'; - export async function downloadNextX( chapterList: Chapter[], series: Series, @@ -81,7 +84,6 @@ export async function downloadAll( downloaderClient.start(); } - /** * The function `DownloadUnreadChapters` downloads a specified number of unread chapters from a list of * series, filtering out already downloaded chapters. @@ -100,22 +102,24 @@ export async function downloadAll( export async function DownloadUnreadChapters( seriesList: Series[], downloadsDir: string, - count: number = 1, + count = 1, serieChapters: Chapter[] = [] -){ +) { for (const series of seriesList) { - if(!library.validURL(series.sourceId)){return;} + if (!library.validURL(series.sourceId)) { + return; + } - if(series.numberUnread <= 0 || !series.id) continue; + if (series.numberUnread <= 0 || !series.id) continue; serieChapters = library.fetchChapters(series.id); - serieChapters = serieChapters.filter(x => !x.read); + serieChapters = serieChapters.filter((x) => !x.read); serieChapters.sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)); - serieChapters = serieChapters.slice(0,count); - var nonDownloadedChapters: Chapter[] = []; - for(const x of serieChapters){ + serieChapters = serieChapters.slice(0, count); + const nonDownloadedChapters: Chapter[] = []; + for (const x of serieChapters) { const result = await getChapterDownloaded(series, x, downloadsDir); - if(result !== true){ - nonDownloadedChapters.push(x) + if (result !== true) { + nonDownloadedChapters.push(x); } } downloaderClient.add( @@ -125,11 +129,12 @@ export async function DownloadUnreadChapters( chapter, series, downloadsDir, - } as DownloadTask)) + } as DownloadTask) + ) ); downloaderClient.start(); await new Promise((resolve) => setTimeout(resolve, 1000)); - }; + } } /** @@ -147,23 +152,23 @@ export async function DeleteReadChapters( seriesList: Series[], downloadsDir: string, serieChapters?: Chapter[] -){ - for(const series of seriesList){ - if(!series.id) return; +) { + for (const series of seriesList) { + if (!series.id) return; serieChapters = library.fetchChapters(series.id); - serieChapters = serieChapters.filter(x => x.read); - var DownloadedChapters: Chapter[] = []; + serieChapters = serieChapters.filter((x) => x.read); + const DownloadedChapters: Chapter[] = []; const downloadedChapters = await getChaptersDownloaded(series, serieChapters, downloadsDir); - for(const key in downloadedChapters){ - if(downloadedChapters.hasOwnProperty(key)){ - var foundChapter = serieChapters.find((chapter) => chapter.id === key); - if(foundChapter){ + for (const key in downloadedChapters) { + if (downloadedChapters.hasOwnProperty(key)) { + const foundChapter = serieChapters.find((chapter) => chapter.id === key); + if (foundChapter) { DownloadedChapters.push(foundChapter); } } } - for(const x of DownloadedChapters){ - await deleteDownloadedChapter(series, x, downloadsDir) + for (const x of DownloadedChapters) { + await deleteDownloadedChapter(series, x, downloadsDir); } } -} \ No newline at end of file +} diff --git a/src/services/library.ts b/src/services/library.ts index 8f906f865..2267077cc 100644 --- a/src/services/library.ts +++ b/src/services/library.ts @@ -106,14 +106,17 @@ const removeCategory = (categoryId: string): void => { }; const validURL = (str: string): boolean => { - var pattern = new RegExp('^(https?:\\/\\/)?' + // protocol - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name - '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path - '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string - '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator + const pattern = new RegExp( + '^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', + 'i' + ); // fragment locator return !!pattern.test(str); -} +}; export default { fetchSeriesList, From aeaea4a15ba3ee9b3a866bb653dae01322407b21 Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Wed, 7 Feb 2024 11:26:02 +0100 Subject: [PATCH 03/11] removed double case of autobackup in general settings --- src/components/settings/GeneralSettings.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/settings/GeneralSettings.tsx b/src/components/settings/GeneralSettings.tsx index 8ddc1678e..6b527d764 100644 --- a/src/components/settings/GeneralSettings.tsx +++ b/src/components/settings/GeneralSettings.tsx @@ -118,12 +118,6 @@ const GeneralSettings: React.FC = () => { case GeneralSetting.OnScrollingChaptersDownloadUnread: setOnScrollingChaptersDownloadUnread(value); break; - case GeneralSetting.autoBackup: - setAutoBackup(value); - break; - case GeneralSetting.autoBackupCount: - setAutoBackupCount(value); - break; default: break; } From 61d8002795faa20626971120276b926b6a45a90e Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Wed, 7 Feb 2024 11:31:51 +0100 Subject: [PATCH 04/11] Chained actions together Instead of doing each action after each other and reassigning the new data to itself, just chained the actions together on the initial assignment --- src/features/library/chapterDownloadUtils.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/features/library/chapterDownloadUtils.tsx b/src/features/library/chapterDownloadUtils.tsx index c9ac97699..cd8ecaf6c 100644 --- a/src/features/library/chapterDownloadUtils.tsx +++ b/src/features/library/chapterDownloadUtils.tsx @@ -111,10 +111,12 @@ export async function DownloadUnreadChapters( } if (series.numberUnread <= 0 || !series.id) continue; - serieChapters = library.fetchChapters(series.id); - serieChapters = serieChapters.filter((x) => !x.read); - serieChapters.sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)); - serieChapters = serieChapters.slice(0, count); + serieChapters = library + .fetchChapters(series.id) + .filter((x) => !x.read) + .sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)) + .slice(0, count); + const nonDownloadedChapters: Chapter[] = []; for (const x of serieChapters) { const result = await getChapterDownloaded(series, x, downloadsDir); @@ -155,8 +157,8 @@ export async function DeleteReadChapters( ) { for (const series of seriesList) { if (!series.id) return; - serieChapters = library.fetchChapters(series.id); - serieChapters = serieChapters.filter((x) => x.read); + serieChapters = library.fetchChapters(series.id) + .filter((x) => x.read); const DownloadedChapters: Chapter[] = []; const downloadedChapters = await getChaptersDownloaded(series, serieChapters, downloadsDir); for (const key in downloadedChapters) { From 509e9debed5410153d5cf9f8a28bd7360ce7c019 Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Wed, 7 Feb 2024 11:52:22 +0100 Subject: [PATCH 05/11] fixed more lint issues --- src/components/reader/ReaderPage.tsx | 2 +- src/features/library/chapterDownloadUtils.tsx | 100 ++++++++---------- 2 files changed, 46 insertions(+), 56 deletions(-) diff --git a/src/components/reader/ReaderPage.tsx b/src/components/reader/ReaderPage.tsx index 34595aaeb..8fb5cb13e 100644 --- a/src/components/reader/ReaderPage.tsx +++ b/src/components/reader/ReaderPage.tsx @@ -291,7 +291,7 @@ const ReaderPage: React.FC = (props: Props) => { if (newChapterId === null) return false; const desiredPage = fromPageMovement && previous ? Infinity : 1; setChapter(newChapterId, desiredPage); - seriesArr[0] != library.fetchSeries(series_id!); + seriesArr[0] = library.fetchSeries(series_id!)!; if (OnScrollingChaptersDownloadUnread) { DownloadUnreadChapters( seriesArr, diff --git a/src/features/library/chapterDownloadUtils.tsx b/src/features/library/chapterDownloadUtils.tsx index cd8ecaf6c..9a64c6b2e 100644 --- a/src/features/library/chapterDownloadUtils.tsx +++ b/src/features/library/chapterDownloadUtils.tsx @@ -102,41 +102,40 @@ export async function downloadAll( export async function DownloadUnreadChapters( seriesList: Series[], downloadsDir: string, - count = 1, - serieChapters: Chapter[] = [] + count = 1 ) { - for (const series of seriesList) { - if (!library.validURL(series.sourceId)) { - return; - } + seriesList + .filter((series) => library.validURL(series.sourceId)) + .filter((series) => series.numberUnread > 0 && series.id) + .forEach(async (series) => { + const serieChapters = library + .fetchChapters(series.id!) + .filter((x) => !x.read) + .sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)) + .slice(0, count); - if (series.numberUnread <= 0 || !series.id) continue; - serieChapters = library - .fetchChapters(series.id) - .filter((x) => !x.read) - .sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)) - .slice(0, count); + const nonDownloadedChapters = await Promise.all( + serieChapters.map(async (x) => { + const result = await getChapterDownloaded(series, x, downloadsDir); + return result !== true ? x : null; + }) + ); - const nonDownloadedChapters: Chapter[] = []; - for (const x of serieChapters) { - const result = await getChapterDownloaded(series, x, downloadsDir); - if (result !== true) { - nonDownloadedChapters.push(x); - } - } - downloaderClient.add( - nonDownloadedChapters.map( - (chapter: Chapter) => - ({ - chapter, - series, - downloadsDir, - } as DownloadTask) - ) - ); - downloaderClient.start(); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + const filteredNonDownloadedChapters = nonDownloadedChapters.filter(Boolean); + + downloaderClient.add( + filteredNonDownloadedChapters.map( + (chapter) => + ({ + chapter, + series, + downloadsDir, + } as DownloadTask) + ) + ); + downloaderClient.start(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + }); } /** @@ -150,27 +149,18 @@ export async function DownloadUnreadChapters( * of a series. * @returns The function does not have a return statement. */ -export async function DeleteReadChapters( - seriesList: Series[], - downloadsDir: string, - serieChapters?: Chapter[] -) { - for (const series of seriesList) { - if (!series.id) return; - serieChapters = library.fetchChapters(series.id) - .filter((x) => x.read); - const DownloadedChapters: Chapter[] = []; - const downloadedChapters = await getChaptersDownloaded(series, serieChapters, downloadsDir); - for (const key in downloadedChapters) { - if (downloadedChapters.hasOwnProperty(key)) { - const foundChapter = serieChapters.find((chapter) => chapter.id === key); - if (foundChapter) { - DownloadedChapters.push(foundChapter); - } - } - } - for (const x of DownloadedChapters) { - await deleteDownloadedChapter(series, x, downloadsDir); - } - } +export async function DeleteReadChapters(seriesList: Series[], downloadsDir: string) { + seriesList + .filter((series) => series.id) + .forEach(async (series) => { + const serieChapters = library.fetchChapters(series.id!).filter((x) => x.read); + + const downloadedChapters = await getChaptersDownloaded(series, serieChapters, downloadsDir); + + const DownloadedChapters = serieChapters.filter((chapter) => downloadedChapters[chapter.id!]); + + DownloadedChapters.forEach((x) => { + deleteDownloadedChapter(series, x, downloadsDir); + }); + }); } From 050f8fb1af734926fa4e65fd7d29b615729453c5 Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Wed, 7 Feb 2024 11:56:06 +0100 Subject: [PATCH 06/11] added notification variable - Added a variable to showing the download notification, so when you're reading manga and going to the next chapter it doesnt everytime show the notification. The notification variable is set to true so no other code needs to ask if it wants to show the notification and keeps working like it used to --- src/components/reader/ReaderPage.tsx | 3 ++- src/features/library/chapterDownloadUtils.tsx | 5 +++-- src/services/downloader.ts | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/reader/ReaderPage.tsx b/src/components/reader/ReaderPage.tsx index 8fb5cb13e..901f8abe5 100644 --- a/src/components/reader/ReaderPage.tsx +++ b/src/components/reader/ReaderPage.tsx @@ -296,7 +296,8 @@ const ReaderPage: React.FC = (props: Props) => { DownloadUnreadChapters( seriesArr, customDownloadsDir || String(getDefaultDownloadDir()), - OnStartUpDownloadUnreadCount + OnStartUpDownloadUnreadCount, + false ); } return true; diff --git a/src/features/library/chapterDownloadUtils.tsx b/src/features/library/chapterDownloadUtils.tsx index 9a64c6b2e..18a0d6c86 100644 --- a/src/features/library/chapterDownloadUtils.tsx +++ b/src/features/library/chapterDownloadUtils.tsx @@ -102,7 +102,8 @@ export async function downloadAll( export async function DownloadUnreadChapters( seriesList: Series[], downloadsDir: string, - count = 1 + count = 1, + notification: boolean = true ) { seriesList .filter((series) => library.validURL(series.sourceId)) @@ -133,7 +134,7 @@ export async function DownloadUnreadChapters( } as DownloadTask) ) ); - downloaderClient.start(); + downloaderClient.start(notification); await new Promise((resolve) => setTimeout(resolve, 1000)); }); } diff --git a/src/services/downloader.ts b/src/services/downloader.ts index 235391ac0..84f8da0c6 100644 --- a/src/services/downloader.ts +++ b/src/services/downloader.ts @@ -99,7 +99,7 @@ class DownloaderClient { this.setDownloadErrors([...this.downloadErrors, downloadError]); }; - start = async () => { + start = async (notification: boolean = true) => { if (this.running) return; if (this.queue.length === 0) { @@ -109,7 +109,9 @@ class DownloaderClient { const startingQueueSize = this.queue.length; const notificationId = uuidv4(); - showNotification({ id: notificationId, message: 'Starting download...', loading: true }); + if (notification) { + showNotification({ id: notificationId, message: 'Starting download...', loading: true }); + } this.setRunning(true); let tasksCompleted = 0; From dbe95050f613d71e74216c57f87ccf9a94dfa791 Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Wed, 7 Feb 2024 12:06:04 +0100 Subject: [PATCH 07/11] ran yarn lint --fix --- src/features/library/chapterDownloadUtils.tsx | 2 +- src/services/downloader.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/library/chapterDownloadUtils.tsx b/src/features/library/chapterDownloadUtils.tsx index 18a0d6c86..f6cd27b2d 100644 --- a/src/features/library/chapterDownloadUtils.tsx +++ b/src/features/library/chapterDownloadUtils.tsx @@ -103,7 +103,7 @@ export async function DownloadUnreadChapters( seriesList: Series[], downloadsDir: string, count = 1, - notification: boolean = true + notification = true ) { seriesList .filter((series) => library.validURL(series.sourceId)) diff --git a/src/services/downloader.ts b/src/services/downloader.ts index 84f8da0c6..d0fe3f4a7 100644 --- a/src/services/downloader.ts +++ b/src/services/downloader.ts @@ -99,7 +99,7 @@ class DownloaderClient { this.setDownloadErrors([...this.downloadErrors, downloadError]); }; - start = async (notification: boolean = true) => { + start = async (notification = true) => { if (this.running) return; if (this.queue.length === 0) { From c7237761fd074b27efb2b5d9d9d4913dad20da39 Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Wed, 7 Feb 2024 20:06:57 +0100 Subject: [PATCH 08/11] Fixed issues - Fixed intense lag when downloading (AFAIK) - Fixed it downloading any chapter even if there we're multiple language's, now downloads only from the language you have selected in settings. - Fixed downloading local files --- src/components/general/DashboardPage.tsx | 3 +- src/components/library/SeriesDetails.tsx | 7 ++- src/features/library/chapterDownloadUtils.tsx | 63 ++++++++++--------- src/services/library.ts | 24 +++++-- 4 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/components/general/DashboardPage.tsx b/src/components/general/DashboardPage.tsx index fd295b8cb..a9ccad2e7 100644 --- a/src/components/general/DashboardPage.tsx +++ b/src/components/general/DashboardPage.tsx @@ -94,7 +94,8 @@ const DashboardPage: React.FC = () => { DownloadUnreadChapters( library.fetchSeriesList(), customDownloadsDir || String(getDefaultDownloadDir()), - OnStartUpDownloadUnreadCount + OnStartUpDownloadUnreadCount, + chapterLanguages ); } }) diff --git a/src/components/library/SeriesDetails.tsx b/src/components/library/SeriesDetails.tsx index 75a8072c0..8da2fc708 100644 --- a/src/components/library/SeriesDetails.tsx +++ b/src/components/library/SeriesDetails.tsx @@ -33,8 +33,7 @@ import { OnSeriesDetailsDeleteReadState, OnSeriesDetailsDownloadUnreadState, OnStartDownloadUnreadCountState, - OnStartUpDeleteReadState, - OnStartUpDownloadUnreadState, + chapterLanguagesState, customDownloadsDirState, } from '../../state/settingStates'; import { @@ -67,6 +66,7 @@ const SeriesDetails: React.FC = () => { const OnStartUpDownloadUnreadCount = useRecoilValue(OnStartDownloadUnreadCountState); const OnSeriesDetailsDownloadUnread = useRecoilValue(OnSeriesDetailsDownloadUnreadState); const OnSeriesDetailsDeleteRead = useRecoilValue(OnSeriesDetailsDeleteReadState); + const chapterLanguages = useRecoilValue(chapterLanguagesState); const loadContent = async () => { log.info(`Series page is loading details from database for series ${id}`); @@ -99,7 +99,8 @@ const SeriesDetails: React.FC = () => { DownloadUnreadChapters( seriesArr, customDownloadsDir || String(getDefaultDownloadDir()), - OnStartUpDownloadUnreadCount + OnStartUpDownloadUnreadCount, + chapterLanguages ); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/features/library/chapterDownloadUtils.tsx b/src/features/library/chapterDownloadUtils.tsx index f6cd27b2d..f48533196 100644 --- a/src/features/library/chapterDownloadUtils.tsx +++ b/src/features/library/chapterDownloadUtils.tsx @@ -1,4 +1,4 @@ -import { Chapter, Series } from '@tiyo/common'; +import { Chapter, LanguageKey, Series } from '@tiyo/common'; import { downloaderClient, DownloadTask } from '../../services/downloader'; import { deleteDownloadedChapter, @@ -103,39 +103,44 @@ export async function DownloadUnreadChapters( seriesList: Series[], downloadsDir: string, count = 1, + chapterLanguages: LanguageKey[], notification = true ) { seriesList - .filter((series) => library.validURL(series.sourceId)) .filter((series) => series.numberUnread > 0 && series.id) .forEach(async (series) => { - const serieChapters = library - .fetchChapters(series.id!) - .filter((x) => !x.read) - .sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)) - .slice(0, count); - - const nonDownloadedChapters = await Promise.all( - serieChapters.map(async (x) => { - const result = await getChapterDownloaded(series, x, downloadsDir); - return result !== true ? x : null; - }) - ); - - const filteredNonDownloadedChapters = nonDownloadedChapters.filter(Boolean); - - downloaderClient.add( - filteredNonDownloadedChapters.map( - (chapter) => - ({ - chapter, - series, - downloadsDir, - } as DownloadTask) - ) - ); - downloaderClient.start(notification); - await new Promise((resolve) => setTimeout(resolve, 1000)); + library.validFilePath(series.sourceId).then(async (result) => { + if(result == false) { + const serieChapters = library + .fetchChapters(series.id!) + .filter((x) => !x.read) + .filter((x)=> chapterLanguages.includes(x.languageKey)) + .sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)) + .slice(0, count); + + const nonDownloadedChapters = await Promise.all( + serieChapters.map(async (x) => { + const result = await getChapterDownloaded(series, x, downloadsDir); + return result !== true ? x : null; + }) + ); + + const filteredNonDownloadedChapters = nonDownloadedChapters.filter(Boolean); + + downloaderClient.add( + filteredNonDownloadedChapters.map( + (chapter) => + ({ + chapter, + series, + downloadsDir, + } as DownloadTask) + ) + ); + downloaderClient.start(notification); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + }) }); } diff --git a/src/services/library.ts b/src/services/library.ts index 2267077cc..e7e994f8b 100644 --- a/src/services/library.ts +++ b/src/services/library.ts @@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid'; import persistantStore from '../util/persistantStore'; import storeKeys from '../constants/storeKeys.json'; import { Category } from '../models/types'; +import fs from 'fs'; const fetchSeriesList = (): Series[] => { const val = persistantStore.read(`${storeKeys.LIBRARY.SERIES_LIST}`); @@ -108,16 +109,28 @@ const removeCategory = (categoryId: string): void => { const validURL = (str: string): boolean => { const pattern = new RegExp( '^(https?:\\/\\/)?' + // protocol - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name - '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path - '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string - '(\\#[-a-z\\d_]*)?$', + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', 'i' ); // fragment locator return !!pattern.test(str); }; +const validFilePath = async (str: string): Promise => { + return new Promise((resolve) => { + fs.access(str, fs.constants.F_OK, (err) => { + if (err) { + resolve(false); + } else { + resolve(true); + } + }); + }); +}; + export default { fetchSeriesList, fetchSeries, @@ -131,4 +144,5 @@ export default { upsertCategory, removeCategory, validURL, + validFilePath, }; From c071aa936e3c383be267fdfb781244a73cac85f6 Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Wed, 7 Feb 2024 20:18:13 +0100 Subject: [PATCH 09/11] Fixed lint errors --- src/features/library/chapterDownloadUtils.tsx | 71 ++++++++++--------- src/services/library.ts | 12 ++-- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/src/features/library/chapterDownloadUtils.tsx b/src/features/library/chapterDownloadUtils.tsx index f48533196..d7b00307a 100644 --- a/src/features/library/chapterDownloadUtils.tsx +++ b/src/features/library/chapterDownloadUtils.tsx @@ -102,45 +102,48 @@ export async function downloadAll( export async function DownloadUnreadChapters( seriesList: Series[], downloadsDir: string, - count = 1, chapterLanguages: LanguageKey[], - notification = true + notification = true, + count = 1 ) { seriesList .filter((series) => series.numberUnread > 0 && series.id) .forEach(async (series) => { - library.validFilePath(series.sourceId).then(async (result) => { - if(result == false) { - const serieChapters = library - .fetchChapters(series.id!) - .filter((x) => !x.read) - .filter((x)=> chapterLanguages.includes(x.languageKey)) - .sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)) - .slice(0, count); - - const nonDownloadedChapters = await Promise.all( - serieChapters.map(async (x) => { - const result = await getChapterDownloaded(series, x, downloadsDir); - return result !== true ? x : null; - }) - ); - - const filteredNonDownloadedChapters = nonDownloadedChapters.filter(Boolean); - - downloaderClient.add( - filteredNonDownloadedChapters.map( - (chapter) => - ({ - chapter, - series, - downloadsDir, - } as DownloadTask) - ) - ); - downloaderClient.start(notification); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - }) + library + .validFilePath(series.sourceId) + .then(async (result) => { + if (result === false) { + const serieChapters = library + .fetchChapters(series.id!) + .filter((x) => !x.read) + .filter((x) => chapterLanguages.includes(x.languageKey)) + .sort((a, b) => parseFloat(a.chapterNumber) - parseFloat(b.chapterNumber)) + .slice(0, count); + + const nonDownloadedChapters = await Promise.all( + serieChapters.map(async (x) => { + const r = await getChapterDownloaded(series, x, downloadsDir); + return r !== true ? x : null; + }) + ); + + const filteredNonDownloadedChapters = nonDownloadedChapters.filter(Boolean); + + downloaderClient.add( + filteredNonDownloadedChapters.map( + (chapter) => + ({ + chapter, + series, + downloadsDir, + } as DownloadTask) + ) + ); + downloaderClient.start(notification); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + }) + .catch((e: Error) => console.error(e)); }); } diff --git a/src/services/library.ts b/src/services/library.ts index e7e994f8b..09a636a15 100644 --- a/src/services/library.ts +++ b/src/services/library.ts @@ -1,9 +1,9 @@ import { Chapter, Series } from '@tiyo/common'; import { v4 as uuidv4 } from 'uuid'; +import fs from 'fs'; import persistantStore from '../util/persistantStore'; import storeKeys from '../constants/storeKeys.json'; import { Category } from '../models/types'; -import fs from 'fs'; const fetchSeriesList = (): Series[] => { const val = persistantStore.read(`${storeKeys.LIBRARY.SERIES_LIST}`); @@ -109,11 +109,11 @@ const removeCategory = (categoryId: string): void => { const validURL = (str: string): boolean => { const pattern = new RegExp( '^(https?:\\/\\/)?' + // protocol - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name - '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path - '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string - '(\\#[-a-z\\d_]*)?$', + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', 'i' ); // fragment locator return !!pattern.test(str); From dace9a3d2f1fa33d9b9e79096d6f357299135f4c Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Thu, 8 Feb 2024 11:10:53 +0100 Subject: [PATCH 10/11] Fixed download function variables - Fixed variable given to the function to download since i added language filtering to the function --- src/components/general/DashboardPage.tsx | 3 ++- src/components/library/SeriesDetails.tsx | 3 ++- src/components/reader/ReaderPage.tsx | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/general/DashboardPage.tsx b/src/components/general/DashboardPage.tsx index a9ccad2e7..0f320b216 100644 --- a/src/components/general/DashboardPage.tsx +++ b/src/components/general/DashboardPage.tsx @@ -94,8 +94,9 @@ const DashboardPage: React.FC = () => { DownloadUnreadChapters( library.fetchSeriesList(), customDownloadsDir || String(getDefaultDownloadDir()), + chapterLanguages, + false, OnStartUpDownloadUnreadCount, - chapterLanguages ); } }) diff --git a/src/components/library/SeriesDetails.tsx b/src/components/library/SeriesDetails.tsx index 8da2fc708..2bf4e5b55 100644 --- a/src/components/library/SeriesDetails.tsx +++ b/src/components/library/SeriesDetails.tsx @@ -99,8 +99,9 @@ const SeriesDetails: React.FC = () => { DownloadUnreadChapters( seriesArr, customDownloadsDir || String(getDefaultDownloadDir()), + chapterLanguages, + false, OnStartUpDownloadUnreadCount, - chapterLanguages ); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/components/reader/ReaderPage.tsx b/src/components/reader/ReaderPage.tsx index 901f8abe5..3799b6056 100644 --- a/src/components/reader/ReaderPage.tsx +++ b/src/components/reader/ReaderPage.tsx @@ -296,8 +296,9 @@ const ReaderPage: React.FC = (props: Props) => { DownloadUnreadChapters( seriesArr, customDownloadsDir || String(getDefaultDownloadDir()), + chapterLanguages, + false, OnStartUpDownloadUnreadCount, - false ); } return true; From ce82c1c809526bd514399947e3701ba7462e27fe Mon Sep 17 00:00:00 2001 From: merlinmarijn Date: Thu, 8 Feb 2024 11:14:34 +0100 Subject: [PATCH 11/11] yarn lint --fix --- src/components/general/DashboardPage.tsx | 2 +- src/components/library/SeriesDetails.tsx | 2 +- src/components/reader/ReaderPage.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/general/DashboardPage.tsx b/src/components/general/DashboardPage.tsx index 0f320b216..7a701b7f5 100644 --- a/src/components/general/DashboardPage.tsx +++ b/src/components/general/DashboardPage.tsx @@ -96,7 +96,7 @@ const DashboardPage: React.FC = () => { customDownloadsDir || String(getDefaultDownloadDir()), chapterLanguages, false, - OnStartUpDownloadUnreadCount, + OnStartUpDownloadUnreadCount ); } }) diff --git a/src/components/library/SeriesDetails.tsx b/src/components/library/SeriesDetails.tsx index 2bf4e5b55..b65591b41 100644 --- a/src/components/library/SeriesDetails.tsx +++ b/src/components/library/SeriesDetails.tsx @@ -101,7 +101,7 @@ const SeriesDetails: React.FC = () => { customDownloadsDir || String(getDefaultDownloadDir()), chapterLanguages, false, - OnStartUpDownloadUnreadCount, + OnStartUpDownloadUnreadCount ); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/components/reader/ReaderPage.tsx b/src/components/reader/ReaderPage.tsx index 3799b6056..6d0b8c5e6 100644 --- a/src/components/reader/ReaderPage.tsx +++ b/src/components/reader/ReaderPage.tsx @@ -298,7 +298,7 @@ const ReaderPage: React.FC = (props: Props) => { customDownloadsDir || String(getDefaultDownloadDir()), chapterLanguages, false, - OnStartUpDownloadUnreadCount, + OnStartUpDownloadUnreadCount ); } return true;