Recently Written · git

scan-subread

PDF reader that follows an audiobook with the SubRead suite

git clone https://github.com/equwal/scan-subread

Log | Files | Refs


src/book-open.ts (4409 bytes)

1 // The open of a book: the open attempts, the owner of subtitles that load
2 // during an open, the restore of the last book, the error of a failed
3 // attempt, and the meta data that comes after the open. Pure: no DOM.
4 
5 import type { BookMeta } from './token-cache';
6 
7 /**
8  * The open attempts of books. Each attempt gets the next number: 1, 2, 3 ...
9  * A large PDF can load for many seconds, so the user can pick another book
10  * while one loads. The book of the last attempt that loads shows.
11  */
12 export interface Opens {
13   /** The number of the last attempt. */
14   tried: number;
15   /** The attempt of the book that shows, or 0. */
16   shown: number;
17   /** The count of attempts that the user started: a file of the file picker. */
18   byUser: number;
19   /** The attempts that still load. */
20   loading: Set<number>;
21 }
22 
23 export function createOpens(): Opens {
24   return { tried: 0, shown: 0, byUser: 0, loading: new Set() };
25 }
26 
27 /** Starts an attempt, and gives its number. `byUser` is true for a file that the user picked. */
28 export function beginOpen(o: Opens, byUser: boolean): number {
29   const attempt = ++o.tried;
30   if (byUser) o.byUser++;
31   o.loading.add(attempt);
32   return attempt;
33 }
34 
35 /**
36  * Ends an attempt. `loaded` is true when its file loaded as a PDF. Gives
37  * true when the book of the attempt shows now: it loaded, and no later
38  * attempt shows a book.
39  */
40 export function endOpen(o: Opens, attempt: number, loaded: boolean): boolean {
41   o.loading.delete(attempt);
42   if (!loaded || attempt < o.shown) return false;
43   o.shown = attempt;
44   return true;
45 }
46 
47 /** True while an attempt loads that can replace the book that shows: an attempt after it. */
48 export function opening(o: Opens): boolean {
49   for (const attempt of o.loading) if (attempt > o.shown) return true;
50   return false;
51 }
52 
53 /**
54  * The book of subtitles that the user loads now, when `book` shows. While an
55  * open loads that can replace `book`, the subtitles belong to no book yet:
56  * they are for the book that shows when the opens end, the new book or, when
57  * its open fails, `book`. Before, a subtitle file for the new book went into
58  * the meta data of the book before it, over its own subtitles, and the new
59  * book did not get it.
60  */
61 export function subtitlesOwner(o: Opens, book: string | null): string | null {
62   return opening(o) ? null : book;
63 }
64 
65 /**
66  * True when the user started an open after `since`, the value of o.byUser
67  * when the restore of the last book started. The restore waits for the
68  * database, and the first open after an upgrade can take many seconds. A
69  * book that the user picks meanwhile wins: the restore does not open the
70  * last book over it, also while it still loads, and does not forget the copy
71  * of the last book, because the copy can be of the user's book now.
72  */
73 export function userOpened(o: Opens, since: number): boolean {
74   return o.byUser !== since;
75 }
76 
77 /**
78  * True when the strip may tell why the attempt `attempt` failed: no later
79  * attempt shows a book. For the restore of the last book, `since` is
80  * o.byUser when the restore started, and the error shows only when the user
81  * started no open after that: the user's book tells its own state. Before,
82  * "This file is not a PDF, or it is damaged." of an older pick showed over
83  * the good book that the user picked after it.
84  */
85 export function failureShows(o: Opens, attempt: number, since?: number): boolean {
86   return attempt > o.shown && (since === undefined || !userOpened(o, since));
87 }
88 
89 /** What changed after the open of a book, before its meta data came. */
90 export interface Changes {
91   /** A page turn: by the user, or by the follow of the audio. */
92   page: boolean;
93   /** A change of "Force OCR" by the user. */
94   forceOcr: boolean;
95 }
96 
97 /**
98  * The fields of the meta data of a book that apply when the meta data comes
99  * after the open: each field that did not change after the open. A field
100  * that is not in the result does not apply. Without a saved setting, "Force
101  * OCR" is off. The upgrade of the database can hold the meta data for many
102  * seconds, and before, it set "Force OCR" back after the user ticked it.
103  */
104 export function lateMeta(meta: BookMeta, changed: Changes): { page?: number; forceOcr?: boolean } {
105   const late: { page?: number; forceOcr?: boolean } = {};
106   if (!changed.page && meta.page !== undefined) late.page = meta.page;
107   if (!changed.forceOcr) late.forceOcr = meta.forceOcr ?? false;
108   return late;
109 }