فهرست منبع

Merge branch 'release/1.2.1'

Book Pauk 11 ماه پیش
والد
کامیت
8858d6d1f2

+ 9 - 0
client/components/Reader/TextPage/DrawHelper.js

@@ -39,6 +39,7 @@ export default class DrawHelper {
         let center = false;
         let center = false;
         let space = 0;
         let space = 0;
         let j = 0;
         let j = 0;
+        const pad = this.fontSize/2;
         //формируем строку
         //формируем строку
         for (const part of line.parts) {
         for (const part of line.parts) {
             let tOpen = '';
             let tOpen = '';
@@ -46,7 +47,12 @@ export default class DrawHelper {
             tOpen += (part.style.italic ? '<i>' : '');
             tOpen += (part.style.italic ? '<i>' : '');
             tOpen += (part.style.sup ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: -0.3em">' : '');
             tOpen += (part.style.sup ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: -0.3em">' : '');
             tOpen += (part.style.sub ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: 0.3em">' : '');
             tOpen += (part.style.sub ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: 0.3em">' : '');
+            tOpen += (part.style.note ? `<span style="position: relative;">` +
+                `<span style="position: absolute; background-color: ${this.textColor}; opacity: 0.1; cursor: pointer; pointer-events: auto; ` +
+                `height: ${this.fontSize + pad*2}px; padding: ${pad}px; left: -${pad}px; top: -${pad*0.9}px; border-radius: ${this.fontSize}px;" ` +
+                `onclick="onNoteClickLiberama('${part.style.note.id}', ${part.style.note.orig ? 1 : 0})"><span style="visibility: hidden;">__TEXT</span></span>` : '');
             let tClose = '';
             let tClose = '';
+            tClose += (part.style.note ? '</span>' : '');
             tClose += (part.style.sub ? '</span>' : '');
             tClose += (part.style.sub ? '</span>' : '');
             tClose += (part.style.sup ? '</span>' : '');
             tClose += (part.style.sup ? '</span>' : '');
             tClose += (part.style.italic ? '</i>' : '');
             tClose += (part.style.italic ? '</i>' : '');
@@ -64,6 +70,9 @@ export default class DrawHelper {
             if (text && text.trim() == '')
             if (text && text.trim() == '')
                 text = `<span style="white-space: pre">${text}</span>`;
                 text = `<span style="white-space: pre">${text}</span>`;
 
 
+            if (part.style.note)
+                tOpen = tOpen.replace('__TEXT', text);
+
             lineText += `${tOpen}${text}${tClose}`;
             lineText += `${tOpen}${text}${tClose}`;
 
 
             center = center || part.style.center;
             center = center || part.style.center;

+ 95 - 9
client/components/Reader/TextPage/TextPage.vue

@@ -4,12 +4,12 @@
             <div class="absolute" v-html="background"></div>
             <div class="absolute" v-html="background"></div>
             <div class="absolute" v-html="pageDivider"></div>
             <div class="absolute" v-html="pageDivider"></div>
         </div>
         </div>
-        <div ref="scrollBox1" class="layout over-hidden" @wheel.prevent.stop="onMouseWheel">
+        <div ref="scrollBox1" class="scroll-box layout over-hidden" @wheel.prevent.stop="onMouseWheel">
             <div ref="scrollingPage1" class="layout over-hidden" @transitionend="onPage1TransitionEnd" @animationend="onPage1AnimationEnd">
             <div ref="scrollingPage1" class="layout over-hidden" @transitionend="onPage1TransitionEnd" @animationend="onPage1AnimationEnd">
                 <div @copy.prevent="copyText" v-html="page1"></div>
                 <div @copy.prevent="copyText" v-html="page1"></div>
             </div>
             </div>
         </div>
         </div>
-        <div ref="scrollBox2" class="layout over-hidden" @wheel.prevent.stop="onMouseWheel">
+        <div ref="scrollBox2" class="scroll-box layout over-hidden" @wheel.prevent.stop="onMouseWheel">
             <div ref="scrollingPage2" class="layout over-hidden" @transitionend="onPage2TransitionEnd" @animationend="onPage2AnimationEnd">
             <div ref="scrollingPage2" class="layout over-hidden" @transitionend="onPage2TransitionEnd" @animationend="onPage2AnimationEnd">
                 <div @copy.prevent="copyText" v-html="page2"></div>
                 <div @copy.prevent="copyText" v-html="page2"></div>
             </div>
             </div>
@@ -24,14 +24,9 @@
             @wheel.prevent.stop="onMouseWheel"
             @wheel.prevent.stop="onMouseWheel"
             @touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"            
             @touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"            
         >
         >
-            <div
-                v-show="showStatusBar && statusBarClickOpen" @mousedown.prevent.stop @touchstart.stop
-                @click.prevent.stop="onStatusBarClick"
-                v-html="statusBarClickable"
-            ></div>
         </div>
         </div>
         <div
         <div
-            v-show="!clickControl && showStatusBar && statusBarClickOpen" class="layout" 
+            v-show="showStatusBar && statusBarClickOpen" class="layout" 
             @mousedown.prevent.stop @touchstart.stop
             @mousedown.prevent.stop @touchstart.stop
             @click.prevent.stop="onStatusBarClick"
             @click.prevent.stop="onStatusBarClick"
             v-html="statusBarClickable"
             v-html="statusBarClickable"
@@ -40,6 +35,29 @@
         <!-- невидимым делать нельзя (display: none), вовремя не подгружаютя шрифты -->
         <!-- невидимым делать нельзя (display: none), вовремя не подгружаютя шрифты -->
         <canvas ref="offscreenCanvas" class="layout" style="visibility: hidden"></canvas>
         <canvas ref="offscreenCanvas" class="layout" style="visibility: hidden"></canvas>
         <div ref="measureWidth" style="position: absolute; visibility: hidden"></div>
         <div ref="measureWidth" style="position: absolute; visibility: hidden"></div>
+
+        <!-- Примечание -->
+        <Dialog ref="dialog1" v-model="noteDialogVisible">
+            <!--template #header>
+                Примечание
+            </template-->
+
+            <div class="column col" style="line-height: 20px; max-width: 400px; max-height: 200px; overflow-x: hidden; overflow-y: auto">
+                <div v-html="noteHtml"></div>
+            </div>
+
+            <template #footer>
+                <div class="row col">
+                    <q-btn class="q-px-md q-mr-md" color="btn2" text-color="app" dense no-caps @click="goToNotes">
+                        В примечаниях
+                    </q-btn>
+                </div>
+
+                <q-btn class="q-px-md" color="btn2" text-color="app" dense no-caps @click="noteDialogVisible = false">
+                    OK
+                </q-btn>
+            </template>
+        </Dialog>
     </div>
     </div>
 </template>
 </template>
 
 
@@ -51,6 +69,7 @@ import {loadCSS} from 'fg-loadcss';
 import _ from 'lodash';
 import _ from 'lodash';
 import he from 'he';
 import he from 'he';
 
 
+import Dialog from '../../share/Dialog.vue';
 import './TextPage.css';
 import './TextPage.css';
 
 
 import * as utils from '../../../share/utils';
 import * as utils from '../../../share/utils';
@@ -62,7 +81,19 @@ import {clickMap} from '../share/clickMap';
 
 
 const minLayoutWidth = 100;
 const minLayoutWidth = 100;
 
 
+//обработчик кликов по примечаниям, см. DrawHelper
+//коряво, но иначе придется сильно усложнять рендеринг страниц (через Vue)
+window.onNoteClickLiberama = (noteId, orig) => {
+    const textPage = window.textPageLiberama;
+    if (textPage) {
+        textPage.showNote(noteId, orig);
+    }
+}
+
 const componentOptions = {
 const componentOptions = {
+    components: {
+        Dialog
+    },
     watch: {
     watch: {
         bookPos: function() {
         bookPos: function() {
             this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen});
             this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen});
@@ -90,6 +121,7 @@ class TextPage {
     _options = componentOptions;
     _options = componentOptions;
 
 
     showStatusBar = false;
     showStatusBar = false;
+    statusBarClickOpen = false;
     clickControl = true;
     clickControl = true;
 
 
     background = null;
     background = null;
@@ -114,6 +146,10 @@ class TextPage {
 
 
     meta = null;
     meta = null;
 
 
+    noteDialogVisible = false;
+    noteId = '';
+    noteHtml = '';
+
     created() {
     created() {
         this.drawHelper = new DrawHelper();
         this.drawHelper = new DrawHelper();
 
 
@@ -153,6 +189,8 @@ class TextPage {
             await utils.sleep(200);
             await utils.sleep(200);
             this.$nextTick(this.onResize);
             this.$nextTick(this.onResize);
         });
         });
+
+        window.textPageLiberama = this;
     }
     }
 
 
     mounted() {
     mounted() {
@@ -297,6 +335,8 @@ class TextPage {
             top += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
             top += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
         let page1 = this.$refs.scrollBox1.style;
         let page1 = this.$refs.scrollBox1.style;
         let page2 = this.$refs.scrollBox2.style;
         let page2 = this.$refs.scrollBox2.style;
+
+        page1.pointerEvents = page2.pointerEvents = (this.clickControl ? 'none' : 'auto'); 
         
         
         page1.perspective = page2.perspective = '3072px';
         page1.perspective = page2.perspective = '3072px';
 
 
@@ -913,6 +953,22 @@ class TextPage {
         }
         }
     }
     }
 
 
+    doPara(paraIndex) {
+        const para = this.parsed.para[paraIndex];
+
+        if (para && this.pageLineCount > 0) {
+            const lines = this.parsed.getLines(para.offset, this.pageLineCount);
+
+            if (lines.length >= this.pageLineCount) {
+                this.currentAnimation = this.pageChangeAnimation;
+                this.pageChangeDirectionDown = true;
+                this.userBookPosChange = true;
+                this.bookPos = lines[0].begin;
+            } else 
+                this.doEnd();
+        }
+    }
+
     doToolBarToggle(event) {
     doToolBarToggle(event) {
         this.$emit('do-action', {action: 'switchToolbar', event});
         this.$emit('do-action', {action: 'switchToolbar', event});
     }
     }
@@ -1209,6 +1265,36 @@ class TextPage {
 
 
         event.clipboardData.setData('text/plain', filtered);
         event.clipboardData.setData('text/plain', filtered);
     }
     }
+
+    showNote(noteId, orig) {
+        const note = this.parsed.notes[noteId];
+        if (note) {
+            if (orig) {//show dialog
+                this.noteId = noteId;
+                const pad = (note.para.length > 1 ? 20 : 0);
+                this.noteHtml = note.para.map(p => `<p style="margin: 0; padding-left: ${pad}px">${p}</p>`).join('');
+                this.noteDialogVisible = true;
+            } else {//go to orig
+                this.goToOrigNote(noteId);
+            }
+        }
+    }
+
+    goToNotes() {
+        const note = this.parsed.notes[this.noteId];
+        if (note && note.noteParaIndex >= 0) {
+            this.doPara(note.noteParaIndex);
+            this.noteDialogVisible = false;
+        }
+    }
+
+    goToOrigNote(noteId) {
+        const note = this.parsed.notes[noteId];
+        if (note && note.linkParaIndex >= 0) {
+            this.doPara(note.linkParaIndex);
+            this.noteDialogVisible = false;
+        }
+    }
 }
 }
 
 
 export default vueComponent(TextPage);
 export default vueComponent(TextPage);
@@ -1244,7 +1330,7 @@ export default vueComponent(TextPage);
 }
 }
 
 
 .events {
 .events {
-    z-index: 20;
+    z-index: 9;
     background-color: rgba(0,0,0,0);
     background-color: rgba(0,0,0,0);
 }
 }
 
 

+ 89 - 7
client/components/Reader/share/BookParser.js

@@ -86,17 +86,23 @@ export default class BookParser {
         let binaryType = '';
         let binaryType = '';
         let dimPromises = [];
         let dimPromises = [];
         this.coverPageId = '';
         this.coverPageId = '';
+        this.images = [];
+        let imageNum = 0;
+
+        //примечания
+        this.notes = {};
+        let inNote = false;
+        let noteId = '';
+        let inNotesBody = false;
 
 
         //оглавление
         //оглавление
         this.contents = [];
         this.contents = [];
-        this.images = [];
         let curTitle = {paraIndex: -1, title: '', subtitles: []};
         let curTitle = {paraIndex: -1, title: '', subtitles: []};
         let curSubtitle = {paraIndex: -1, title: ''};
         let curSubtitle = {paraIndex: -1, title: ''};
         let inTitle = false;
         let inTitle = false;
         let inSubtitle = false;
         let inSubtitle = false;
         let sectionLevel = 0;
         let sectionLevel = 0;
         let bodyIndex = 0;
         let bodyIndex = 0;
-        let imageNum = 0;
 
 
         let paraIndex = -1;
         let paraIndex = -1;
         let paraOffset = 0;
         let paraOffset = 0;
@@ -289,7 +295,7 @@ export default class BookParser {
                 if (attrs.href && attrs.href.value) {
                 if (attrs.href && attrs.href.value) {
                     const href = attrs.href.value;
                     const href = attrs.href.value;
                     const alt = (attrs.alt && attrs.alt.value ? attrs.alt.value : '');
                     const alt = (attrs.alt && attrs.alt.value ? attrs.alt.value : '');
-                    const {id, local} = this.imageHrefToId(href);
+                    const {id, local} = this.hrefToId(href);
                     if (local) {//local
                     if (local) {//local
                         imageNum++;
                         imageNum++;
 
 
@@ -322,6 +328,23 @@ export default class BookParser {
                 }
                 }
             }
             }
 
 
+            if (tag == 'a') {
+                let attrs = sax.getAttrsSync(tail);
+                if (attrs.href && attrs.href.value && attrs.type && attrs.type.value === 'note') {//note
+                    const href = attrs.href.value;
+                    const {id, local} = this.hrefToId(href);
+
+                    if (local) {
+                        inNote = true;
+                        growParagraph(`<note href="${id}" orig="1">`, 0);
+
+                        if (!this.notes[id]) {
+                            this.notes[id] = {id, linkParaIndex: paraIndex};
+                        }
+                    }
+                }
+            }
+
             if (path == '/fictionbook/description/title-info/author') {
             if (path == '/fictionbook/description/title-info/author') {
                 if (!fb2.author)
                 if (!fb2.author)
                     fb2.author = [];
                     fb2.author = [];
@@ -350,6 +373,11 @@ export default class BookParser {
 
 
             if (path.indexOf('/fictionbook/body') == 0) {
             if (path.indexOf('/fictionbook/body') == 0) {
                 if (tag == 'body') {
                 if (tag == 'body') {
+                    let attrs = sax.getAttrsSync(tail);
+                    if (attrs.name && attrs.name.value === 'notes') {//notes
+                        inNotesBody = true;
+                    }
+
                     if (isFirstBody && fb2.annotation) {
                     if (isFirstBody && fb2.annotation) {
                         const ann = fb2.annotation.split('<p>').filter(v => v).map(v => utils.removeHtmlTags(v));
                         const ann = fb2.annotation.split('<p>').filter(v => v).map(v => utils.removeHtmlTags(v));
                         ann.forEach(a => {
                         ann.forEach(a => {
@@ -389,6 +417,23 @@ export default class BookParser {
                         newParagraph();
                         newParagraph();
                     isFirstSection = false;
                     isFirstSection = false;
                     sectionLevel++;
                     sectionLevel++;
+
+                    if (inNotesBody) {
+                        let attrs = sax.getAttrsSync(tail);
+                        if (attrs.id && attrs.id.value) {//notes
+                            const id = attrs.id.value;
+                            let note = this.notes[id];
+                            if (!note) {
+                                note = {id};
+                                this.notes[id] = note;
+                            }
+
+                            note.noteParaIndex = paraIndex;
+                            note.para = [];
+                            noteId = id;
+                        }
+
+                    }
                 }
                 }
 
 
                 if (tag == 'emphasis' || tag == 'strong' || tag == 'sup' || tag == 'sub') {
                 if (tag == 'emphasis' || tag == 'strong' || tag == 'sup' || tag == 'sub') {
@@ -401,6 +446,14 @@ export default class BookParser {
                     if (tag == 'p') {
                     if (tag == 'p') {
                         inPara = true;
                         inPara = true;
                         isFirstTitlePara = false;
                         isFirstTitlePara = false;
+
+                        if (inNotesBody && noteId) {
+                            if (!inTitle) {
+                                this.notes[noteId].para.push('');
+                            } else {
+                                growParagraph(`<note href="${noteId}">`, 0);
+                            }
+                        }
                     }
                     }
                 }
                 }
 
 
@@ -440,11 +493,20 @@ export default class BookParser {
         const onEndNode = (elemName) => {// eslint-disable-line no-unused-vars
         const onEndNode = (elemName) => {// eslint-disable-line no-unused-vars
             tag = elemName;
             tag = elemName;
 
 
+            if (tag == 'a' && inNote) {
+                growParagraph('</note>', 0);
+                inNote = false;
+            }
+
             if (tag == 'binary') {
             if (tag == 'binary') {
                 binaryId = '';
                 binaryId = '';
             }
             }
         
         
             if (path.indexOf('/fictionbook/body') == 0) {
             if (path.indexOf('/fictionbook/body') == 0) {
+                if (tag == 'body') {
+                    inNotesBody = false;
+                }
+
                 if (tag == 'title') {
                 if (tag == 'title') {
                     isFirstTitlePara = false;
                     isFirstTitlePara = false;
                     bold = false;
                     bold = false;
@@ -462,6 +524,10 @@ export default class BookParser {
 
 
                 if (tag == 'p') {
                 if (tag == 'p') {
                     inPara = false;
                     inPara = false;
+
+                    if (inTitle && inNotesBody && noteId) {
+                        growParagraph('</note>', 0);
+                    }
                 }
                 }
 
 
                 if (tag == 'subtitle') {
                 if (tag == 'subtitle') {
@@ -570,6 +636,12 @@ export default class BookParser {
                     growParagraph(`${tOpen}${text}${tClose}`, text.length, text);
                     growParagraph(`${tOpen}${text}${tClose}`, text.length, text);
                 else
                 else
                     growParagraph(' ', 1);
                     growParagraph(' ', 1);
+
+                if (!inTitle && inPara && inNotesBody && noteId) {
+                    const p = this.notes[noteId].para;
+                    if (p.length)
+                        p[p.length - 1] = p[p.length - 1] + text;
+                }
             }
             }
         };
         };
 
 
@@ -602,7 +674,7 @@ export default class BookParser {
         return {fb2};
         return {fb2};
     }
     }
 
 
-    imageHrefToId(id) {
+    hrefToId(id) {
         let local = false;
         let local = false;
         if (id[0] == '#') {
         if (id[0] == '#') {
             id = id.substr(1);
             id = id.substr(1);
@@ -635,7 +707,7 @@ export default class BookParser {
 
 
     splitToStyle(s) {
     splitToStyle(s) {
         let result = [];/*array of {
         let result = [];/*array of {
-            style: {bold: Boolean, italic: Boolean, sup: Boolean, sub: Boolean, center: Boolean, space: Number},
+            style: {bold: Boolean, italic: Boolean, sup: Boolean, sub: Boolean, center: Boolean, space: Number, note: Object},
             image: {local: Boolean, inline: Boolean, id: String},
             image: {local: Boolean, inline: Boolean, id: String},
             text: String,
             text: String,
         }*/
         }*/
@@ -686,7 +758,7 @@ export default class BookParser {
                 case 'image': {
                 case 'image': {
                     let attrs = sax.getAttrsSync(tail);
                     let attrs = sax.getAttrsSync(tail);
                     if (attrs.href && attrs.href.value) {
                     if (attrs.href && attrs.href.value) {
-                        image = this.imageHrefToId(attrs.href.value);
+                        image = this.hrefToId(attrs.href.value);
                         image.inline = false;
                         image.inline = false;
                         image.num = (attrs.num && attrs.num.value ? attrs.num.value : 0);
                         image.num = (attrs.num && attrs.num.value ? attrs.num.value : 0);
                     }
                     }
@@ -695,7 +767,7 @@ export default class BookParser {
                 case 'image-inline': {
                 case 'image-inline': {
                     let attrs = sax.getAttrsSync(tail);
                     let attrs = sax.getAttrsSync(tail);
                     if (attrs.href && attrs.href.value) {
                     if (attrs.href && attrs.href.value) {
-                        const img = this.imageHrefToId(attrs.href.value);
+                        const img = this.hrefToId(attrs.href.value);
                         img.inline = true;
                         img.inline = true;
                         img.num = (attrs.num && attrs.num.value ? attrs.num.value : 0);
                         img.num = (attrs.num && attrs.num.value ? attrs.num.value : 0);
                         result.push({
                         result.push({
@@ -706,6 +778,13 @@ export default class BookParser {
                     }
                     }
                     break;
                     break;
                 }
                 }
+                case 'note': {
+                    let attrs = sax.getAttrsSync(tail);
+                    if (attrs.href && attrs.href.value) {
+                        style.note = {id: attrs.href.value, orig: attrs.orig?.value};
+                    }
+                    break;
+                }
             }
             }
         };
         };
 
 
@@ -734,6 +813,9 @@ export default class BookParser {
                     break;
                     break;
                 case 'image-inline':
                 case 'image-inline':
                     break;
                     break;
+                case 'note':
+                    style.note = false;
+                    break;
             }
             }
         };
         };
 
 

+ 15 - 1
client/components/Reader/versionHistory.js

@@ -1,4 +1,18 @@
 export const versionHistory = [
 export const versionHistory = [
+{
+    version: '1.2.1',
+    releaseDate: '2024-07-28',
+    showUntil: '2024-08-04',
+    content:
+`
+<ul>
+    <li>добавлено отображение примечаний на месте, по клику на сноске (#50)</li>
+    <li>исправление багов</li>
+</ul>
+
+`
+},
+
 {
 {
     version: '1.2.0',
     version: '1.2.0',
     releaseDate: '2024-03-25',
     releaseDate: '2024-03-25',
@@ -7,7 +21,7 @@ export const versionHistory = [
 `
 `
 <ul>
 <ul>
     <li>в списке загруженных, книга в архив (из архива) переносится теперь со всей группой своих версий</li>
     <li>в списке загруженных, книга в архив (из архива) переносится теперь со всей группой своих версий</li>
-    <li>добавлена возможность задавать в конфиге любую ссылку для кнопки "Сетевая библиотека", параматр networkLibraryLink (#47)</li>
+    <li>добавлена возможность задавать в конфиге любую ссылку для кнопки "Сетевая библиотека", параметр networkLibraryLink (#47)</li>
 </ul>
 </ul>
 
 
 `
 `

+ 8 - 8
package-lock.json

@@ -1,12 +1,12 @@
 {
 {
   "name": "liberama",
   "name": "liberama",
-  "version": "1.1.3",
+  "version": "1.2.0",
   "lockfileVersion": 2,
   "lockfileVersion": 2,
   "requires": true,
   "requires": true,
   "packages": {
   "packages": {
     "": {
     "": {
       "name": "liberama",
       "name": "liberama",
-      "version": "1.1.3",
+      "version": "1.2.0",
       "hasInstallScript": true,
       "hasInstallScript": true,
       "license": "CC0-1.0",
       "license": "CC0-1.0",
       "dependencies": {
       "dependencies": {
@@ -3364,9 +3364,9 @@
       }
       }
     },
     },
     "node_modules/caniuse-lite": {
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001566",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz",
-      "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==",
+      "version": "1.0.30001643",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz",
+      "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==",
       "dev": true,
       "dev": true,
       "funding": [
       "funding": [
         {
         {
@@ -13709,9 +13709,9 @@
       }
       }
     },
     },
     "caniuse-lite": {
     "caniuse-lite": {
-      "version": "1.0.30001566",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz",
-      "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==",
+      "version": "1.0.30001643",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz",
+      "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==",
       "dev": true
       "dev": true
     },
     },
     "chalk": {
     "chalk": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "liberama",
   "name": "liberama",
-  "version": "1.2.0",
+  "version": "1.2.1",
   "author": "Book Pauk <bookpauk@gmail.com>",
   "author": "Book Pauk <bookpauk@gmail.com>",
   "license": "CC0-1.0",
   "license": "CC0-1.0",
   "repository": "bookpauk/liberama",
   "repository": "bookpauk/liberama",

+ 3 - 0
server/config/base.js

@@ -56,6 +56,9 @@ module.exports = {
             ip: '0.0.0.0',
             ip: '0.0.0.0',
             port: '33443',
             port: '33443',
             accessToken: '',
             accessToken: '',
+            shciForHost: {
+                'samlib.ru': 300000
+            },
         }*/
         }*/
     ],
     ],
 
 

+ 12 - 6
server/core/BookUpdateChecker/BUCServer.js

@@ -27,8 +27,8 @@ class BUCServer {
 
 
                 this.cleanQueryInterval = 300*dayMs;//интервал очистки устаревших
                 this.cleanQueryInterval = 300*dayMs;//интервал очистки устаревших
                 this.oldQueryInterval = 14*dayMs;//интервал устаревания запроса на обновление
                 this.oldQueryInterval = 14*dayMs;//интервал устаревания запроса на обновление
-                this.checkingInterval = 5*hourMs;//интервал проверки обновления одного и того же файла
-                this.sameHostCheckInterval = 1000;//интервал проверки файла на том же сайте, не менее
+                this.checkingInterval = 1*dayMs;//интервал проверки обновления одного и того же файла
+                this.sameHostCheckInterval = 10*1000;//интервал проверки файла на том же сайте, не менее
             } else {
             } else {
                 this.maxCheckQueueLength = 10;//максимальная длина checkQueue
                 this.maxCheckQueueLength = 10;//максимальная длина checkQueue
                 this.fillCheckQueuePeriod = 10*1000;//период пополнения очереди
                 this.fillCheckQueuePeriod = 10*1000;//период пополнения очереди
@@ -51,6 +51,7 @@ class BUCServer {
             
             
             this.checkQueue = [];
             this.checkQueue = [];
             this.hostChecking = {};
             this.hostChecking = {};
+            this.shciForHost = this.config.shciForHost || {};//sameHostCheckInterval for host
 
 
             this.main(); //no await
             this.main(); //no await
 
 
@@ -262,7 +263,7 @@ class BUCServer {
                         let unchanged = true;
                         let unchanged = true;
                         let hash = '';
                         let hash = '';
 
 
-                        const headers = await this.down.head(row.id);
+                        const headers = await this.down.head(row.id, {timeout: 10*1000});
 
 
                         const etag = headers['etag'] || '';
                         const etag = headers['etag'] || '';
                         const modTime = headers['last-modified'] || '';
                         const modTime = headers['last-modified'] || '';
@@ -276,7 +277,7 @@ class BUCServer {
                             && (!size || !row.size || (size !== row.size))
                             && (!size || !row.size || (size !== row.size))
                             ) {
                             ) {
 
 
-                            const downdata = await this.down.load(row.id);
+                            const downdata = await this.down.load(row.id, {timeout: 10*1000});
 
 
                             size = downdata.length;
                             size = downdata.length;
                             hash = await utils.getBufHash(downdata, 'sha256', 'hex');
                             hash = await utils.getBufHash(downdata, 'sha256', 'hex');
@@ -316,7 +317,12 @@ class BUCServer {
                         log(LM_ERR, `error ${row.id} > ${e.stack ? e.stack : e.message}`);
                         log(LM_ERR, `error ${row.id} > ${e.stack ? e.stack : e.message}`);
                     } finally {
                     } finally {
                         (async() => {
                         (async() => {
-                            await utils.sleep(this.sameHostCheckInterval);
+                            let sameHostCheckInterval = this.shciForHost[url.hostname] || this.sameHostCheckInterval;
+                            sameHostCheckInterval = Math.round((Math.random() - 0.5)*(sameHostCheckInterval*0.2) + sameHostCheckInterval);
+
+                            log(`delay ${sameHostCheckInterval}ms for host '${url.hostname}'`);
+                            await utils.sleep(sameHostCheckInterval);
+
                             this.hostChecking[url.hostname] = false;
                             this.hostChecking[url.hostname] = false;
                         })();
                         })();
                     }
                     }
@@ -327,7 +333,7 @@ class BUCServer {
                 log(LM_ERR, e.stack);
                 log(LM_ERR, e.stack);
             }
             }
 
 
-            await utils.sleep(10);
+            await utils.sleep(100);
         }
         }
     }
     }
 
 

+ 5 - 3
server/core/FileDownloader.js

@@ -2,7 +2,7 @@ const https = require('https');
 const axios = require('axios');
 const axios = require('axios');
 const utils = require('./utils');
 const utils = require('./utils');
 
 
-const userAgent = 'Mozilla/5.0 (X11; HasCodingOs 1.0; Linux x64) AppleWebKit/637.36 (KHTML, like Gecko) Chrome/70.0.3112.101 Safari/637.36 HasBrowser/5.0';
+const userAgent = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0';
 
 
 class FileDownloader {
 class FileDownloader {
     constructor(limitDownloadSize = 0) {
     constructor(limitDownloadSize = 0) {
@@ -16,7 +16,6 @@ class FileDownloader {
             headers: {
             headers: {
                 'accept-encoding': 'gzip, compress, deflate',
                 'accept-encoding': 'gzip, compress, deflate',
                 'user-agent': userAgent,
                 'user-agent': userAgent,
-                timeout: 300*1000,
             },
             },
             httpsAgent: new https.Agent({
             httpsAgent: new https.Agent({
                 rejectUnauthorized: false // решение проблемы 'unable to verify the first certificate' для некоторых сайтов с валидным сертификатом
                 rejectUnauthorized: false // решение проблемы 'unable to verify the first certificate' для некоторых сайтов с валидным сертификатом
@@ -26,6 +25,9 @@ class FileDownloader {
         if (opts)
         if (opts)
             options = Object.assign({}, opts, options);
             options = Object.assign({}, opts, options);
 
 
+        if (!options.timeout)
+            options.timeout = 300*1000;//5 min
+
         try {
         try {
             const res = await axios.get(url, options);
             const res = await axios.get(url, options);
 
 
@@ -77,8 +79,8 @@ class FileDownloader {
         const options = {
         const options = {
             headers: {
             headers: {
                 'user-agent': userAgent,
                 'user-agent': userAgent,
-                timeout: 10*1000,
             },
             },
+            timeout: 10*1000,
         };
         };
 
 
         const res = await axios.head(url, options);
         const res = await axios.head(url, options);

+ 1 - 0
server/index.js

@@ -1,4 +1,5 @@
 require('tls').DEFAULT_MIN_VERSION = 'TLSv1';
 require('tls').DEFAULT_MIN_VERSION = 'TLSv1';
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
 
 
 const fs = require('fs-extra');
 const fs = require('fs-extra');
 const express = require('express');
 const express = require('express');