Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: a reading window acquires the lock when multiple windows are opened with the same publication ID #2706

Merged
merged 3 commits into from
Dec 17, 2024
Merged
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/common/models/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ export interface WithSender {
sender: WindowSender;
}

export interface WindowReaderDestination {
identifier: string;
}
export interface WithDestination {
destination: WindowReaderDestination;
}

// tslint:disable-next-line: max-line-length
export interface ActionWithSender<Type extends string = string, Payload = undefined, Meta = undefined> extends Action<Type, Payload, Meta>, WithSender {
}

export interface ActionWithDestination<Type extends string = string, Payload = undefined, Meta = undefined> extends Action<Type, Payload, Meta>, WithDestination {
}
2 changes: 2 additions & 0 deletions src/common/redux/actions/reader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as setReduxState from "./setReduxState";
import * as disableRTLFlip from "./rtlFlip";
import * as bookmark from "./bookmarks";
import * as annotation from "./annotations";
import * as setTheLock from "./setTheLock";

export {
openRequest,
Expand All @@ -39,4 +40,5 @@ export {
disableRTLFlip,
bookmark,
annotation,
setTheLock,
};
28 changes: 28 additions & 0 deletions src/common/redux/actions/reader/setTheLock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import { ActionWithDestination } from "readium-desktop/common/models/sync";

export const ID = "READER_SET_THE_LOCK_INSTANCE";

export interface Payload {
}

export function build(readerWindowIdentifier: string):
ActionWithDestination<typeof ID, Payload> {

return {
type: ID,
payload: {
},
destination: {
identifier: readerWindowIdentifier,
},
};
}
build.toString = () => ID; // Redux StringableActionCreator
export type TAction = ReturnType<typeof build>;
7 changes: 7 additions & 0 deletions src/common/redux/states/renderer/readerRootState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,11 @@ export interface IReaderStateReader {
mediaOverlay: IMediaOverlayState;
allowCustomConfig: IAllowCustomConfigState;
transientConfig: ReaderConfigPublisher;


// got the lock
// acquired on first reader opened with the same publication UUID instance
// allow to do computation for the publication on one reader and not across reader
// it is a kind of Mutex in multi-threading concept
lock: boolean;
}
6 changes: 5 additions & 1 deletion src/main/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,13 @@ const getLibraryWindowFromDi =

const readerWinMap = new Map<string, BrowserWindow>();

// todo: infinite growing cache! must implement opposite function to saveReaderWindowInDi()

const saveReaderWindowInDi =
(readerWin: BrowserWindow, id: string) => (readerWinMap.set(id, readerWin), readerWin);

const deleteReaderWindowInDi =
(id: string) => readerWinMap.delete(id);

const getReaderWindowFromDi =
(id: string) => readerWinMap.get(id); // we could filter out based on win.isDestroyed() && win.webContents.isDestroyed() but this would change the null/undefined contract of the return value in consumer code, so let's leave it for now (strictNullChecks and stricter typeof id)

Expand Down Expand Up @@ -346,6 +349,7 @@ export {
diMainGet,
getLibraryWindowFromDi,
getReaderWindowFromDi,
deleteReaderWindowInDi,
saveLibraryWindowInDi,
saveReaderWindowInDi,
getAllReaderWindowFromDi,
Expand Down
1 change: 1 addition & 0 deletions src/main/redux/actions/win/session/registerReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export function build(
publicationView,
navigator: undefined,
},
lock: false,
},
};

Expand Down
8 changes: 7 additions & 1 deletion src/main/redux/middleware/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import * as debug_ from "debug";
import { syncIpc } from "readium-desktop/common/ipc";
import { ActionWithSender, SenderType } from "readium-desktop/common/models/sync";
import { ActionWithDestination, ActionWithSender, SenderType } from "readium-desktop/common/models/sync";
import {
apiActions, authActions, catalogActions, dialogActions, downloadActions, historyActions, i18nActions, keyboardActions, lcpActions,
publicationActions, themeActions,
Expand Down Expand Up @@ -87,6 +87,7 @@ const SYNCHRONIZABLE_ACTIONS: string[] = [
annotationActions.importTriggerModal.ID,
// annotationActions.importConfirmOrAbort.ID,

readerActions.setTheLock.ID,
];

export const reduxSyncMiddleware: Middleware
Expand Down Expand Up @@ -148,6 +149,11 @@ export const reduxSyncMiddleware: Middleware
)
) {

if ((action as ActionWithDestination)?.destination?.identifier && (action as ActionWithDestination)?.destination?.identifier !== id) {
// if the action has a reader destination, do not send to other browserWin Instance
return ;
}

debug("send to", id);
const a = ActionSerializer.serialize(action as ActionWithSender);
// debug(a);
Expand Down
38 changes: 35 additions & 3 deletions src/main/redux/sagas/win/reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { readerIpc } from "readium-desktop/common/ipc";
import { ReaderMode } from "readium-desktop/common/models/reader";
import { normalizeRectangle } from "readium-desktop/common/rectangle/window";
import { takeSpawnEvery } from "readium-desktop/common/redux/sagas/takeSpawnEvery";
import { diMainGet, getLibraryWindowFromDi, getReaderWindowFromDi } from "readium-desktop/main/di";
import { deleteReaderWindowInDi, diMainGet, getLibraryWindowFromDi, getReaderWindowFromDi } from "readium-desktop/main/di";
import { error } from "readium-desktop/main/tools/error";
import { streamerActions, winActions } from "readium-desktop/main/redux/actions";
import { RootState } from "readium-desktop/main/redux/states";
Expand All @@ -22,12 +22,15 @@ import { call as callTyped, select as selectTyped } from "typed-redux-saga/macro
import { createReaderWindow } from "./browserWindow/createReaderWindow";
import { readerConfigInitialState } from "readium-desktop/common/redux/states/reader";
import { comparePublisherReaderConfig } from "readium-desktop/common/publisherConfig";
import { readerActions } from "readium-desktop/common/redux/actions";

// Logger
const filename_ = "readium-desktop:main:redux:sagas:win:reader";
const debug = debug_(filename_);
debug("_");

const __readerWithSamePubIdGotTheLockMap = new Map<string, string>(); // K: publicationIdentifier V: windowIdentifier

function* winOpen(action: winActions.reader.openSucess.TAction) {

const identifier = action.payload.identifier;
Expand All @@ -53,6 +56,18 @@ function* winOpen(action: winActions.reader.openSucess.TAction) {
// ignore
}


let gotTheLock = false;
const winIdGotTheLock = __readerWithSamePubIdGotTheLockMap.get(reader.publicationIdentifier);
if (winIdGotTheLock) {
gotTheLock = false;
debug(`reader ${identifier} did not get the lock`);
} else {
__readerWithSamePubIdGotTheLockMap.set(reader.publicationIdentifier, identifier);
gotTheLock = true;
debug(`reader ${identifier} got the lock !!!`);
}

webContents.send(readerIpc.CHANNEL, {
type: readerIpc.EventType.request,
payload: {
Expand Down Expand Up @@ -82,6 +97,7 @@ function* winOpen(action: winActions.reader.openSucess.TAction) {
state: !comparePublisherReaderConfig(config, readerConfigInitialState),
},
config,
lock: gotTheLock,
},
keyboard,
mode,
Expand All @@ -97,23 +113,31 @@ function* winOpen(action: winActions.reader.openSucess.TAction) {
function* winClose(action: winActions.reader.closed.TAction) {

const identifier = action.payload.identifier;
let publicationIdentifier = "";
debug(`reader ${identifier} -> winClose`);
deleteReaderWindowInDi(identifier);

{
const readers = yield* selectTyped((state: RootState) => state.win.session.reader);
const reader = readers[identifier];

if (reader) {

publicationIdentifier = reader.publicationIdentifier;
const winIdGotTheLock = __readerWithSamePubIdGotTheLockMap.get(publicationIdentifier);
if (identifier === winIdGotTheLock) {
__readerWithSamePubIdGotTheLockMap.delete(publicationIdentifier);
}

yield put(winActions.session.unregisterReader.build(identifier));

yield put(winActions.registry.registerReaderPublication.build(
reader.publicationIdentifier,
publicationIdentifier,
reader.windowBound,
reader.reduxState),
);

yield put(streamerActions.publicationCloseRequest.build(reader.publicationIdentifier));
yield put(streamerActions.publicationCloseRequest.build(publicationIdentifier));
}
}

Expand All @@ -122,6 +146,14 @@ function* winClose(action: winActions.reader.closed.TAction) {
const readers = yield* selectTyped((state: RootState) => state.win.session.reader);
const readersArray = ObjectValues(readers);

const readersWithSamePubId = readersArray.filter(({publicationIdentifier: pubIdFromOtherReader}) => publicationIdentifier === pubIdFromOtherReader);
const readerSamePubIdFirstWinId = readersWithSamePubId[0]?.identifier;
if (readerSamePubIdFirstWinId) {
__readerWithSamePubIdGotTheLockMap.set(publicationIdentifier, readerSamePubIdFirstWinId);
yield put(readerActions.setTheLock.build(readerSamePubIdFirstWinId));
debug(`reader ${readerSamePubIdFirstWinId} got the lock !!!`);
}

try {
const libraryWin = yield* callTyped(() => getLibraryWindowFromDi());

Expand Down
7 changes: 7 additions & 0 deletions src/renderer/reader/components/Reader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,9 @@ class Reader extends React.Component<IProps, IState> {
this.props.readerConfig.theme === "paper" ? stylesReader.paperMode :
"",
)}>
{/* Reader Lock DEMO !!! */}
{/* <h1 style={{zIndex: 999999, backgroundColor: "red", position: "absolute"}}>{this.props.lock ? "lock" : "no-lock"}</h1> */}
{/* Reader Lock DEMO !!! */}
<a
role="heading"
className={stylesReader.anchor_link}
Expand Down Expand Up @@ -2920,6 +2923,10 @@ const mapStateToProps = (state: IReaderRootState, _props: IBaseProps) => {
ttsVoice: state.reader.config.ttsVoice,
mediaOverlaysPlaybackRate: state.reader.config.mediaOverlaysPlaybackRate,
ttsPlaybackRate: state.reader.config.ttsPlaybackRate,

// Reader Lock Demo
// lock: state.reader.lock,
// Reader Lock Demo
};
};

Expand Down
2 changes: 2 additions & 0 deletions src/renderer/reader/redux/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { annotationTagsIndexReducer } from "./annotationTagsIndex";
import { creatorReducer } from "readium-desktop/common/redux/reducers/creator";
import { importAnnotationReducer } from "readium-desktop/renderer/common/redux/reducers/importAnnotation";
import { tagReducer } from "readium-desktop/common/redux/reducers/tag";
import { readerLockReducer } from "./lock";

export const rootReducer = () => {

Expand Down Expand Up @@ -179,6 +180,7 @@ export const rootReducer = () => {
disableRTLFlip: readerRTLFlipReducer,
mediaOverlay: readerMediaOverlayReducer,
tts: readerTTSReducer,
lock: readerLockReducer,
}),
search: searchReducer,
annotation: annotationModeEnableReducer,
Expand Down
28 changes: 28 additions & 0 deletions src/renderer/reader/redux/reducers/lock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import { type Reducer } from "redux";

import { readerActions } from "readium-desktop/common/redux/actions";

function readerLockReducer_(
state: boolean = false, // hydrated from the main
action: readerActions.setTheLock.TAction,
): boolean {

switch (action.type) {
case readerActions.setTheLock.ID:
console.log("This reader Window has acquired the lock!");

return true;

default:
return state;
}
}

export const readerLockReducer = readerLockReducer_ as Reducer<ReturnType<typeof readerLockReducer_>>;
Loading