Skip to content

Commit

Permalink
feat(annotation): mutex/lock mechanism for the first publication read…
Browse files Browse the repository at this point in the history
…er window opened (PR #2706)
  • Loading branch information
panaC authored Dec 17, 2024
1 parent 7630311 commit d25a395
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 5 deletions.
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_>>;

0 comments on commit d25a395

Please sign in to comment.