Skip to content
Draft
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
10 changes: 10 additions & 0 deletions src/main/converter/publication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,16 @@ export class PublicationViewConverter {
// could be refactored when the publications documents will be in the state
const store = diMainGet("store");
const state = store.getState();


// Reads locators from disk.
// When batching `convertDocumentToView` across the full publication DB list:
//
// - Worst case: a spinning disk handles ~128 open/read ops per second (~1KB chunks).
// - A library with ~1000 publications may take ~8 seconds to load via `getPublicationView`
// when initializing the catalog in the library window.
// - This introduces a startup penalty (blocking delay) before the UI becomes responsive.
// This delay blocks sending the Redux state hydration to libraryWindow.
const readerStateLocator = await diMainGet("publication-data").readJsonObj(document.identifier, "locator") as MiniLocatorExtended | undefined; // TODO: type object

const duration = typeof r2Publication.Metadata.Duration === "number" ? r2Publication.Metadata.Duration : undefined;
Expand Down
11 changes: 11 additions & 0 deletions src/main/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,16 @@ interface IGet {
// the type any for container.get is overloaded by IGet
const diMainGet: IGet = (symbol: keyof typeof diSymbolTable) => container.get<any>(diSymbolTable[symbol], { autobind: true });

let __splashScreenBrowserWindow: BrowserWindow | undefined;
const splashScreen = {
get browserWindow() {
return __splashScreenBrowserWindow;
},
set browserWindow(v) {
__splashScreenBrowserWindow = v;
},
};

export {
closeProcessLock,
diMainGet,
Expand All @@ -402,4 +412,5 @@ export {
saveReaderWindowInDi,
getAllReaderWindowFromDi,
createStoreFromDi,
splashScreen,
};
12 changes: 12 additions & 0 deletions src/main/redux/sagas/api/publication/findAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ const convertDocs = async (docs: PublicationDocument[], publicationViewConverter
const pubs = [];
for (const doc of docs) {
try {

// TODO: Optimize publication view conversion during imports.
//
// Current behavior:
// - Each new publication import triggers a full reload of all converted publication views.
// - During batch imports, this repeats until the entire catalog is processed (no memoization).
// - After every import, the full catalog is rebuilt and sent to the libraryWindow.
//
// Impact:
// - Significant performance overhead during batch imports.
// - Repeated disk I/O (reading locators, scanning directories for publication paths).
// - Unnecessary recomputation and redundant UI updates.
const pub = await publicationViewConverter.convertDocumentToView(doc);
pubs.push(pub);
} catch (e) {
Expand Down
8 changes: 8 additions & 0 deletions src/main/redux/sagas/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ function* getPublicationView() {
debug("Start converting the last added documents to publicationView ");
for (const doc of lastAddedPublicationsDocument) {
try {
// for test delay purpose, DO NOT FORGET TO COMMENT IT
// yield* delayTyped(100);
//////

const pub = yield* callTyped(() => publicationViewConverter.convertDocumentToView(doc));
lastAddedPublicationsView.push(pub);
} catch (e) {
Expand All @@ -145,6 +149,10 @@ function* getPublicationView() {
debug("Start converting the last read documents to publicationView ");
for (const doc of lastReadedPublicationDocument) {
try {
// for test delay purpose, DO NOT FORGET TO COMMENT IT
// yield* delayTyped(100);
//////

const pub = yield* callTyped(() => publicationViewConverter.convertDocumentToView(doc));
lastReadPublicationsView.push(pub);
} catch (e) {
Expand Down
5 changes: 5 additions & 0 deletions src/main/redux/sagas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { sagaCustomizationProfileProvisioning } from "./customization";
import isURL from "validator/lib/isURL";
import { publicationIntegrityChecker } from "./publication/checker";
import { error } from "readium-desktop/main/tools/error";
import { splashScreen } from "readium-desktop/main/di";

// Logger
const filename_ = "readium-desktop:main:saga:app";
Expand Down Expand Up @@ -171,6 +172,10 @@ export function* rootSaga() {
// app initialized
yield put(appActions.initSuccess.build());

if (splashScreen.browserWindow && !splashScreen.browserWindow.isDestroyed()) {
splashScreen.browserWindow.destroy();
}

// wait library window fully opened before to throw events
yield take(winActions.library.openSucess.ID);

Expand Down
115 changes: 76 additions & 39 deletions src/main/redux/sagas/persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ import { IWinSessionLibraryState } from "../states/win/session/library";
import { JsonStringifySortedKeys } from "readium-desktop/common/utils/json";
import { rmrf } from "readium-desktop/utils/fs";

const DEBOUNCE_TIME = 3 * 60 * 1000; // 3 min
const PUBLICATION_STORAGE_DEBOUNCE_TIME = 10 * 1000; // 10 secs
// Persist state diffs regularly now that win.registry is disabled.
// Only publication.db and opds remain unbounded (arrays with N elements).
const PATCH_DEBOUNCE_TIME = 1000; // 1 second before dumping to disk
// const PATCH_DEBOUNCE_TIME = 3 * 60 * 1000; // 3 min

// not used for the 340 for the moment, let's wait the 350
// const PUBLICATION_STORAGE_DEBOUNCE_TIME = 10 * 1000; // 10 secs

// Logger
const filename_ = "readium-desktop:main:saga:persist";
Expand All @@ -36,34 +41,65 @@ debug("_");

export const persistStateToFs = async (nextState: Partial<PersistRootState>) => {

debug("start of persist reduxState in disk");
debug("START of persist reduxState in disk");

// Part of code to reconstruct the entire state.json for 3.3 and earlier, not needed for the 3.4 and beyond
let reader: IDictWinRegistryReaderState | undefined = undefined;
if (nextState?.win?.registry?.reader) {

debug("START reconstruction of the registry reader state for backward compatibilty with 3.3");
reader = {};
const pubData = diMainGet("publication-data");
const readers = await pubData.listPublication();

// TODO: need to parallelize with Promise.allSettled
for (const pubId of readers) {
// const _reader = nextState.win.registry.reader[pubId];
// const _readerReduxState = _reader.reduxState;

reader[pubId] = {
reduxState: {
// "config" | "locator" | "divina" | "disableRTLFlip" | "allowCustomConfig" | "noteTotalCount" | "pdfConfig"
config: await pubData.readJsonObj(pubId, "config") as any, // TODO: type object
locator: await pubData.readJsonObj(pubId, "locator") as any, // TODO: type object
divina: await pubData.readJsonObj(pubId, "divina") as any, // TODO: type object
disableRTLFlip: await pubData.readJsonObj(pubId, "disableRTLFlip") as any, // TODO: type object
allowCustomConfig: await pubData.readJsonObj(pubId, "allowCustomConfig") as any, // TODO: type object
noteTotalCount: await pubData.readJsonObj(pubId, "noteTotalCount") as any, // TODO: type object
pdfConfig: await pubData.readJsonObj(pubId, "pdfConfig") as any, // TODO: type object
},
windowBound: await pubData.readJsonObj(pubId, "bound") as any, // TODO: type object
};
const publicationData = diMainGet("publication-data");
const publicationIdentifierFromPublicationDataBase = Object.keys(nextState?.publication?.db || {});

for (const pubId of publicationIdentifierFromPublicationDataBase) {
if (publicationData.visited.has(pubId)) {

debug(`${pubId} has been visited`);

const promisesSettledResult = await Promise.allSettled([
publicationData.readJsonObj(pubId, "config") as any, // TODO: type object
publicationData.readJsonObj(pubId, "locator") as any, // TODO: type object
publicationData.readJsonObj(pubId, "divina") as any, // TODO: type object
publicationData.readJsonObj(pubId, "disableRTLFlip") as any, // TODO: type object
publicationData.readJsonObj(pubId, "allowCustomConfig") as any, // TODO: type object
publicationData.readJsonObj(pubId, "noteTotalCount") as any, // TODO: type object
publicationData.readJsonObj(pubId, "pdfConfig") as any, // TODO: type object
publicationData.readJsonObj(pubId, "bound") as any, // TODO: type object
]);
await publicationData.close(pubId);

reader[pubId] = {
reduxState: {
config: promisesSettledResult[0].status === "fulfilled" ? promisesSettledResult[0].value : undefined,
locator: promisesSettledResult[1].status === "fulfilled" ? promisesSettledResult[1].value : undefined,
divina: promisesSettledResult[2].status === "fulfilled" ? promisesSettledResult[2].value : undefined,
disableRTLFlip: promisesSettledResult[3].status === "fulfilled" ? promisesSettledResult[3].value : undefined,
allowCustomConfig: promisesSettledResult[4].status === "fulfilled" ? promisesSettledResult[4].value : undefined,
noteTotalCount: promisesSettledResult[5].status === "fulfilled" ? promisesSettledResult[5].value : undefined,
pdfConfig: promisesSettledResult[6].status === "fulfilled" ? promisesSettledResult[6].value : undefined,
},
windowBound: promisesSettledResult[7].status === "fulfilled" ? promisesSettledResult[7].value : undefined,
};

debug(`SAVED reader[${pubId}]: ${JSON.stringify({
reduxState: {
config: typeof reader[pubId].reduxState.config,
locator: typeof reader[pubId].reduxState.locator,
divina: typeof reader[pubId].reduxState.divina,
disableRTLFlip: typeof reader[pubId].reduxState.disableRTLFlip,
allowCustomConfig: typeof reader[pubId].reduxState.allowCustomConfig,
noteTotalCount: typeof reader[pubId].reduxState.noteTotalCount,
pdfConfig: typeof reader[pubId].reduxState.pdfConfig,
},
windowBound: typeof reader[pubId].windowBound,
}, null, 4)}`);
} else {
reader[pubId] = nextState.win.registry.reader[pubId];
}
}

debug("END of the reconstruction of the registry reader state for backward compatibilty");
}

const value: PersistRootState & { __t: string, __v: string } = {
Expand Down Expand Up @@ -207,7 +243,7 @@ export function* needToPersistPatch() {
export function saga() {
return all([
debounce(
DEBOUNCE_TIME,
PATCH_DEBOUNCE_TIME,
winActions.persistRequest.ID,
needToPersistPatch,
),
Expand Down Expand Up @@ -397,21 +433,22 @@ export function saga() {
},
(e) => debug(e),
),
debounce(
PUBLICATION_STORAGE_DEBOUNCE_TIME,
readerActions.setLocator.ID,
function* (action: readerActions.setLocator.TAction) {
const jsonObj = action.payload as unknown as object;
const sender = action.sender as EventPayload["sender"];
// disabled for the 340 release, let's wait 350
// debounce(
// PUBLICATION_STORAGE_DEBOUNCE_TIME,
// readerActions.setLocator.ID,
// function* (action: readerActions.setLocator.TAction) {
// const jsonObj = action.payload as unknown as object;
// const sender = action.sender as EventPayload["sender"];

if (sender?.type !== SenderType.Renderer) {
debug("sender is not renderer !!!");
return;
}
const pubId = sender.reader_pubId; // see syncFactory
yield* callTyped(() => diMainGet("publication-storage").writeJsonObj(pubId, "locator", jsonObj));
},
),
// if (sender?.type !== SenderType.Renderer) {
// debug("sender is not renderer !!!");
// return;
// }
// const pubId = sender.reader_pubId; // see syncFactory
// yield* callTyped(() => diMainGet("publication-storage").writeJsonObj(pubId, "locator", jsonObj));
// },
// ),

// TODO: enable publication-storage debounce persistence
// debounce(
Expand Down
3 changes: 3 additions & 0 deletions src/main/redux/sagas/win/browserWindow/createLibraryWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export function* createLibraryWindow(_action: winActions.library.openRequest.TAc
},
icon: path.join(__dirname, "assets/icons/icon.png"),
});
debug("LibraryWindow new BrowserWindow instancied");

if (ENABLE_DEV_TOOLS) {
const wc = libWindow.webContents;
Expand Down Expand Up @@ -142,6 +143,8 @@ export function* createLibraryWindow(_action: winActions.library.openRequest.TAc

if (!libWindow.isDestroyed()) {
try {

debug("LibraryWindow load url to the webview");
await libWindow.loadURL(rendererBaseUrl /*, {baseURLForDataURL, httpReferrer} */);
} catch (e) {
debug("Load url rejected", e);
Expand Down
2 changes: 2 additions & 0 deletions src/main/redux/sagas/win/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,10 @@ function* winOpen(action: winActions.library.openSucess.TAction) {
customization: state.customization,
};
try {
debug("START REQUEST CATALOG FROM DATABASE");
const publication = yield* callTyped(getCatalog);
payload.publication = publication;
debug("END REQUEST CATALOG FROM DATABASE; DONE");
} catch (e) {
error(filename_, e);
}
Expand Down
Loading
Loading