123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- const InpxParser = require('./InpxParser');
- const utils = require('./utils');
- class DbCreator {
- constructor(config) {
- this.config = config;
- }
- async run(db, callback) {
- const config = this.config;
- //book
- await db.create({
- table: 'book'
- });
- callback({job: 'load inpx', jobMessage: 'Загрузка INPX'});
- const readFileCallback = async(readState) => {
- callback(readState);
- };
- //поисковые таблицы, ниже сохраним в БД
- let authorMap = new Map();//авторы
- let authorArr = [];
- let seriesMap = new Map();//серии
- let seriesArr = [];
- let titleMap = new Map();//названия
- let titleArr = [];
- let genreMap = new Map();//жанры
- let genreArr = [];
- let langMap = new Map();//языки
- let langArr = [];
- //stats
- let authorCount = 0;
- let bookCount = 0;
- let noAuthorBookCount = 0;
- let bookDelCount = 0;
- //stuff
- let recsLoaded = 0;
- let id = 0;
- let chunkNum = 0;
- const splitAuthor = (author) => {
- if (!author) {
- author = 'Автор не указан';
- }
- const result = author.split(',');
- if (result.length > 1)
- result.push(author);
- return result;
- }
- const parsedCallback = async(chunk) => {
- for (const rec of chunk) {
- rec.id = ++id;
- if (!rec.del) {
- bookCount++;
- if (!rec.author)
- noAuthorBookCount++;
- } else {
- bookDelCount++;
- }
- //авторы
- const author = splitAuthor(rec.author);
- for (let i = 0; i < author.length; i++) {
- const a = author[i];
- const value = a.toLowerCase();
- let authorRec;
- if (authorMap.has(value)) {
- const authorTmpId = authorMap.get(value);
- authorRec = authorArr[authorTmpId];
- } else {
- authorRec = {tmpId: authorArr.length, author: a, value, bookId: []};
- authorArr.push(authorRec);
- authorMap.set(value, authorRec.tmpId);
- if (author.length == 1 || i < author.length - 1) //без соавторов
- authorCount++;
- }
- authorRec.bookId.push(id);
- }
- }
- await db.insert({table: 'book', rows: chunk});
-
- recsLoaded += chunk.length;
- callback({recsLoaded});
- if (chunkNum++ % 10 == 0)
- utils.freeMemory();
- };
- //парсинг 1
- const parser = new InpxParser();
- await parser.parse(config.inpxFile, readFileCallback, parsedCallback);
- utils.freeMemory();
- //отсортируем авторов и выдадим им правильные id
- //порядок id соответствует ASC-сортировке по author.toLowerCase
- callback({job: 'author sort', jobMessage: 'Сортировка'});
- authorArr.sort((a, b) => a.value.localeCompare(b.value));
- id = 0;
- authorMap = new Map();
- for (const authorRec of authorArr) {
- authorRec.id = ++id;
- authorMap.set(authorRec.author, id);
- delete authorRec.tmpId;
- }
- utils.freeMemory();
- //теперь можно создавать остальные поисковые таблицы
- const parseField = (fieldValue, fieldMap, fieldArr, authorIds) => {
- if (fieldValue) {
- const value = fieldValue.toLowerCase();
- let fieldRec;
- if (fieldMap.has(value)) {
- const fieldId = fieldMap.get(value);
- fieldRec = fieldArr[fieldId];
- } else {
- fieldRec = {id: fieldArr.length, value, authorId: new Set()};
- fieldArr.push(fieldRec);
- fieldMap.set(value, fieldRec.id);
- }
- for (const id of authorIds) {
- fieldRec.authorId.add(id);
- }
- }
- };
- const parseBookRec = (rec) => {
- //авторы
- const author = splitAuthor(rec.author);
- const authorIds = [];
- for (const a of author) {
- const authorId = authorMap.get(a);
- if (!authorId) //подстраховка
- continue;
- authorIds.push(authorId);
- }
- //серии
- parseField(rec.series, seriesMap, seriesArr, authorIds);
- //названия
- parseField(rec.title, titleMap, titleArr, authorIds);
- //жанры
- if (rec.genre) {
- const genre = rec.genre.split(',');
- for (const g of genre) {
- let genreRec;
- if (genreMap.has(g)) {
- const genreId = genreMap.get(g);
- genreRec = genreArr[genreId];
- } else {
- genreRec = {id: genreArr.length, value: g, authorId: new Set()};
- genreArr.push(genreRec);
- genreMap.set(g, genreRec.id);
- }
- for (const id of authorIds) {
- genreRec.authorId.add(id);
- }
- }
- }
- //языки
- parseField(rec.lang, langMap, langArr, authorIds);
- }
- callback({job: 'search tables create', jobMessage: 'Создание поисковых таблиц'});
- //парсинг 2
- while (1) {// eslint-disable-line
- //пробегаемся по сохраненным книгам
- const rows = await db.select({
- table: 'book',
- where: `
- let iter = @getItem('book_parsing');
- if (!iter) {
- iter = @all();
- @setItem('book_parsing', iter);
- }
- const ids = new Set();
- let id = iter.next();
- while (!id.done && ids.size < 10000) {
- ids.add(id.value);
- id = iter.next();
- }
- return ids;
- `
- });
- if (rows.length) {
- for (const rec of rows)
- parseBookRec(rec);
- } else {
- break;
- }
- }
- //чистка памяти, ибо жрет как не в себя
- authorMap = null;
- seriesMap = null;
- titleMap = null;
- genreMap = null;
- for (let i = 0; i < 3; i++) {
- utils.freeMemory();
- await utils.sleep(1000);
- }
- //config
- callback({job: 'config save', jobMessage: 'Сохранение конфигурации'});
- await db.create({
- table: 'config'
- });
- const stats = {
- recsLoaded,
- authorCount,
- authorCountAll: authorArr.length,
- bookCount,
- bookCountAll: bookCount + bookDelCount,
- bookDelCount,
- noAuthorBookCount,
- titleCount: titleArr.length,
- seriesCount: seriesArr.length,
- genreCount: genreArr.length,
- langCount: langArr.length,
- };
- //console.log(stats);
- const inpxHash = await utils.getFileHash(config.inpxFile, 'sha256', 'hex');
- await db.insert({table: 'config', rows: [
- {id: 'inpxInfo', value: parser.info},
- {id: 'stats', value: stats},
- {id: 'inpxHash', value: inpxHash},
- ]});
- //сохраним поисковые таблицы
- const chunkSize = 10000;
- const saveTable = async(table, arr, nullArr, authorIdToArray = true) => {
-
- arr.sort((a, b) => a.value.localeCompare(b.value));
- await db.create({
- table,
- index: {field: 'value', unique: true, depth: 1000000},
- });
- //вставка в БД по кусочкам, экономим память
- for (let i = 0; i < arr.length; i += chunkSize) {
- const chunk = arr.slice(i, i + chunkSize);
-
- if (authorIdToArray) {
- for (const rec of chunk)
- rec.authorId = Array.from(rec.authorId);
- }
- await db.insert({table, rows: chunk});
- await utils.sleep(100);
- }
- nullArr();
- await db.close({table});
- utils.freeMemory();
- };
- //author
- callback({job: 'author save', jobMessage: 'Сохранение авторов книг'});
- await saveTable('author', authorArr, () => {authorArr = null}, false);
- //series
- callback({job: 'series save', jobMessage: 'Сохранение серий книг'});
- await saveTable('series', seriesArr, () => {seriesArr = null});
- //title
- callback({job: 'title save', jobMessage: 'Сохранение названий книг'});
- await saveTable('title', titleArr, () => {titleArr = null});
- //genre
- callback({job: 'genre save', jobMessage: 'Сохранение жанров'});
- await saveTable('genre', genreArr, () => {genreArr = null});
- //lang
- callback({job: 'lang save', jobMessage: 'Сохранение языков'});
- await saveTable('lang', langArr, () => {langArr = null});
- //кэш-таблицы запросов
- await db.create({table: 'query_cache'});
- await db.create({table: 'query_time'});
- callback({job: 'done', jobMessage: ''});
- }
- }
- module.exports = DbCreator;
|