DbCreator.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. const fs = require('fs-extra');
  2. const InpxParser = require('./InpxParser');
  3. const InpxHashCreator = require('./InpxHashCreator');
  4. const utils = require('./utils');
  5. const emptyFieldValue = '?';
  6. class DbCreator {
  7. constructor(config) {
  8. this.config = config;
  9. }
  10. async loadInpxFilter() {
  11. const inpxFilterFile = this.config.inpxFilterFile;
  12. if (await fs.pathExists(inpxFilterFile)) {
  13. let filter = await fs.readFile(inpxFilterFile, 'utf8');
  14. filter = JSON.parse(filter);
  15. if (filter.includeAuthors) {
  16. filter.includeAuthors = filter.includeAuthors.map(a => a.toLowerCase());
  17. filter.includeSet = new Set(filter.includeAuthors);
  18. }
  19. if (filter.excludeAuthors) {
  20. filter.excludeAuthors = filter.excludeAuthors.map(a => a.toLowerCase());
  21. filter.excludeSet = new Set(filter.excludeAuthors);
  22. }
  23. return filter;
  24. } else {
  25. return false;
  26. }
  27. }
  28. //процедура формировани БД несколько усложнена, в целях экономии памяти
  29. async run(db, callback) {
  30. const config = this.config;
  31. callback({jobStepCount: 5});
  32. callback({job: 'load inpx', jobMessage: 'Загрузка INPX', jobStep: 1, progress: 0});
  33. //временная таблица
  34. await db.create({
  35. table: 'book',
  36. cacheSize: (config.lowMemoryMode ? 5 : 500),
  37. });
  38. //поисковые таблицы, позже сохраним в БД
  39. let authorMap = new Map();//авторы
  40. let authorArr = [];
  41. let seriesMap = new Map();//серии
  42. let seriesArr = [];
  43. let titleMap = new Map();//названия
  44. let titleArr = [];
  45. let genreMap = new Map();//жанры
  46. let genreArr = [];
  47. let langMap = new Map();//языки
  48. let langArr = [];
  49. let delMap = new Map();//удаленные
  50. let delArr = [];
  51. let dateMap = new Map();//дата поступления
  52. let dateArr = [];
  53. let librateMap = new Map();//оценка
  54. let librateArr = [];
  55. //stats
  56. let authorCount = 0;
  57. let bookCount = 0;
  58. let noAuthorBookCount = 0;
  59. let bookDelCount = 0;
  60. //stuff
  61. let recsLoaded = 0;
  62. callback({recsLoaded});
  63. let chunkNum = 0;
  64. //фильтр
  65. const inpxFilter = await this.loadInpxFilter();
  66. let filter = () => true;
  67. if (inpxFilter) {
  68. let recFilter = () => true;
  69. if (inpxFilter.filter) {
  70. if (config.allowUnsafeFilter)
  71. recFilter = new Function(`'use strict'; return ${inpxFilter.filter}`)();
  72. else
  73. throw new Error(`Unsafe property 'filter' detected in ${this.config.inpxFilterFile}. Please specify '--unsafe-filter' param if you know what you're doing.`);
  74. }
  75. filter = (rec) => {
  76. let author = rec.author;
  77. if (!author)
  78. author = emptyFieldValue;
  79. author = author.toLowerCase();
  80. let excluded = false;
  81. if (inpxFilter.excludeSet) {
  82. const authors = author.split(',');
  83. for (const a of authors) {
  84. if (inpxFilter.excludeSet.has(a)) {
  85. excluded = true;
  86. break;
  87. }
  88. }
  89. }
  90. return recFilter(rec)
  91. && (!inpxFilter.includeSet || inpxFilter.includeSet.has(author))
  92. && !excluded
  93. ;
  94. };
  95. }
  96. //вспомогательные функции
  97. const splitAuthor = (author) => {
  98. if (!author)
  99. author = emptyFieldValue;
  100. const result = author.split(',');
  101. if (result.length > 1)
  102. result.push(author);
  103. return result;
  104. }
  105. let totalFiles = 0;
  106. const readFileCallback = async(readState) => {
  107. callback(readState);
  108. if (readState.totalFiles)
  109. totalFiles = readState.totalFiles;
  110. if (totalFiles)
  111. callback({progress: (readState.current || 0)/totalFiles});
  112. };
  113. let id = 0;
  114. const parsedCallback = async(chunk) => {
  115. let filtered = false;
  116. for (const rec of chunk) {
  117. //сначала фильтр
  118. if (!filter(rec)) {
  119. rec.id = 0;
  120. filtered = true;
  121. continue;
  122. }
  123. rec.id = ++id;
  124. if (!rec.del) {
  125. bookCount++;
  126. if (!rec.author)
  127. noAuthorBookCount++;
  128. } else {
  129. bookDelCount++;
  130. }
  131. //авторы
  132. const author = splitAuthor(rec.author);
  133. for (let i = 0; i < author.length; i++) {
  134. const a = author[i];
  135. const value = a.toLowerCase();
  136. let authorRec;
  137. if (authorMap.has(value)) {
  138. const authorTmpId = authorMap.get(value);
  139. authorRec = authorArr[authorTmpId];
  140. } else {
  141. authorRec = {tmpId: authorArr.length, author: a, value, bookCount: 0, bookDelCount: 0, bookId: []};
  142. authorArr.push(authorRec);
  143. authorMap.set(value, authorRec.tmpId);
  144. if (author.length == 1 || i < author.length - 1) //без соавторов
  145. authorCount++;
  146. }
  147. //это нужно для того, чтобы имя автора начиналось с заглавной
  148. if (a[0].toUpperCase() === a[0])
  149. authorRec.author = a;
  150. //счетчики
  151. if (!rec.del) {
  152. authorRec.bookCount++;
  153. } else {
  154. authorRec.bookDelCount++;
  155. }
  156. //ссылки на книги
  157. authorRec.bookId.push(id);
  158. }
  159. }
  160. let saveChunk = [];
  161. if (filtered) {
  162. saveChunk = chunk.filter(r => r.id);
  163. } else {
  164. saveChunk = chunk;
  165. }
  166. await db.insert({table: 'book', rows: saveChunk});
  167. recsLoaded += chunk.length;
  168. callback({recsLoaded});
  169. if (chunkNum++ % 10 == 0 && config.lowMemoryMode)
  170. utils.freeMemory();
  171. };
  172. //парсинг 1
  173. const parser = new InpxParser();
  174. await parser.parse(config.inpxFile, readFileCallback, parsedCallback);
  175. utils.freeMemory();
  176. //отсортируем авторов и выдадим им правильные id
  177. //порядок id соответствует ASC-сортировке по author.toLowerCase
  178. callback({job: 'author sort', jobMessage: 'Сортировка авторов', jobStep: 2, progress: 0});
  179. await utils.sleep(100);
  180. authorArr.sort((a, b) => a.value.localeCompare(b.value));
  181. id = 0;
  182. authorMap = new Map();
  183. for (const authorRec of authorArr) {
  184. authorRec.id = ++id;
  185. authorMap.set(authorRec.author, id);
  186. delete authorRec.tmpId;
  187. }
  188. utils.freeMemory();
  189. //подготовка к сохранению author_book
  190. const saveBookChunk = async(authorChunk, callback) => {
  191. callback(0);
  192. const ids = [];
  193. for (const a of authorChunk) {
  194. for (const id of a.bookId) {
  195. ids.push(id);
  196. }
  197. }
  198. ids.sort((a, b) => a - b);// обязательно, иначе будет тормозить - особенности JembaDb
  199. callback(0.1);
  200. const rows = await db.select({table: 'book', where: `@@id(${db.esc(ids)})`});
  201. callback(0.6);
  202. await utils.sleep(100);
  203. const bookArr = new Map();
  204. for (const row of rows)
  205. bookArr.set(row.id, row);
  206. const abRows = [];
  207. for (const a of authorChunk) {
  208. const aBooks = [];
  209. for (const id of a.bookId) {
  210. const rec = bookArr.get(id);
  211. aBooks.push(rec);
  212. }
  213. abRows.push({id: a.id, author: a.author, books: JSON.stringify(aBooks)});
  214. delete a.bookId;//в дальнейшем не понадобится, authorArr сохраняем без него
  215. }
  216. callback(0.7);
  217. await db.insert({
  218. table: 'author_book',
  219. rows: abRows,
  220. });
  221. callback(1);
  222. };
  223. callback({job: 'book sort', jobMessage: 'Сортировка книг', jobStep: 3, progress: 0});
  224. //сохранение author_book
  225. await db.create({
  226. table: 'author_book',
  227. });
  228. let idsLen = 0;
  229. let aChunk = [];
  230. let prevI = 0;
  231. for (let i = 0; i < authorArr.length; i++) {// eslint-disable-line
  232. const author = authorArr[i];
  233. aChunk.push(author);
  234. idsLen += author.bookId.length;
  235. if (idsLen > 50000) {//константа выяснена эмпирическим путем "память/скорость"
  236. await saveBookChunk(aChunk, (p) => {
  237. callback({progress: (prevI + (i - prevI)*p)/authorArr.length});
  238. });
  239. prevI = i;
  240. idsLen = 0;
  241. aChunk = [];
  242. await utils.sleep(100);
  243. utils.freeMemory();
  244. await db.freeMemory();
  245. }
  246. }
  247. if (aChunk.length) {
  248. await saveBookChunk(aChunk, () => {});
  249. aChunk = null;
  250. }
  251. callback({progress: 1});
  252. //чистка памяти, ибо жрет как не в себя
  253. await db.close({table: 'book'});
  254. await db.freeMemory();
  255. utils.freeMemory();
  256. //парсинг 2, подготовка
  257. const parseField = (fieldValue, fieldMap, fieldArr, authorIds, bookId) => {
  258. let addBookId = bookId;
  259. let value = fieldValue;
  260. if (typeof(fieldValue) == 'string') {
  261. if (!fieldValue) {
  262. fieldValue = emptyFieldValue;
  263. addBookId = 0;//!!!
  264. }
  265. value = fieldValue.toLowerCase();
  266. }
  267. let fieldRec;
  268. if (fieldMap.has(value)) {
  269. const fieldId = fieldMap.get(value);
  270. fieldRec = fieldArr[fieldId];
  271. } else {
  272. fieldRec = {id: fieldArr.length, value, authorId: new Set()};
  273. if (bookId)
  274. fieldRec.bookId = new Set();
  275. fieldArr.push(fieldRec);
  276. fieldMap.set(value, fieldRec.id);
  277. }
  278. for (const id of authorIds) {
  279. fieldRec.authorId.add(id);
  280. }
  281. if (addBookId)
  282. fieldRec.bookId.add(addBookId);
  283. };
  284. const parseBookRec = (rec) => {
  285. //авторы
  286. const author = splitAuthor(rec.author);
  287. const authorIds = [];
  288. for (const a of author) {
  289. const authorId = authorMap.get(a);
  290. if (!authorId) //подстраховка
  291. continue;
  292. authorIds.push(authorId);
  293. }
  294. //серии
  295. parseField(rec.series, seriesMap, seriesArr, authorIds, rec.id);
  296. //названия
  297. parseField(rec.title, titleMap, titleArr, authorIds, rec.id);
  298. //жанры
  299. let genre = rec.genre || emptyFieldValue;
  300. genre = rec.genre.split(',');
  301. for (let g of genre) {
  302. if (!g)
  303. g = emptyFieldValue;
  304. let genreRec;
  305. if (genreMap.has(g)) {
  306. const genreId = genreMap.get(g);
  307. genreRec = genreArr[genreId];
  308. } else {
  309. genreRec = {id: genreArr.length, value: g, authorId: new Set()};
  310. genreArr.push(genreRec);
  311. genreMap.set(g, genreRec.id);
  312. }
  313. for (const id of authorIds) {
  314. genreRec.authorId.add(id);
  315. }
  316. }
  317. //языки
  318. parseField(rec.lang, langMap, langArr, authorIds);
  319. //удаленные
  320. parseField(rec.del, delMap, delArr, authorIds);
  321. //дата поступления
  322. parseField(rec.date, dateMap, dateArr, authorIds);
  323. //оценка
  324. parseField(rec.librate, librateMap, librateArr, authorIds);
  325. };
  326. callback({job: 'search tables create', jobMessage: 'Создание поисковых таблиц', jobStep: 4, progress: 0});
  327. //парсинг 2, теперь можно создавать остальные поисковые таблицы
  328. let proc = 0;
  329. while (1) {// eslint-disable-line
  330. const rows = await db.select({
  331. table: 'author_book',
  332. where: `
  333. let iter = @getItem('parse_book');
  334. if (!iter) {
  335. iter = @all();
  336. @setItem('parse_book', iter);
  337. }
  338. const ids = new Set();
  339. let id = iter.next();
  340. while (!id.done) {
  341. ids.add(id.value);
  342. if (ids.size >= 10000)
  343. break;
  344. id = iter.next();
  345. }
  346. return ids;
  347. `
  348. });
  349. if (rows.length) {
  350. for (const row of rows) {
  351. const books = JSON.parse(row.books);
  352. for (const rec of books)
  353. parseBookRec(rec);
  354. }
  355. proc += rows.length;
  356. callback({progress: proc/authorArr.length});
  357. } else
  358. break;
  359. if (config.lowMemoryMode) {
  360. await utils.sleep(100);
  361. utils.freeMemory();
  362. await db.freeMemory();
  363. }
  364. }
  365. //чистка памяти, ибо жрет как не в себя
  366. authorMap = null;
  367. seriesMap = null;
  368. titleMap = null;
  369. genreMap = null;
  370. langMap = null;
  371. delMap = null;
  372. dateMap = null;
  373. librateMap = null;
  374. utils.freeMemory();
  375. //сортировка серий
  376. callback({job: 'sort', jobMessage: 'Сортировка', jobStep: 5, progress: 0});
  377. await utils.sleep(100);
  378. seriesArr.sort((a, b) => a.value.localeCompare(b.value));
  379. await utils.sleep(100);
  380. callback({progress: 0.3});
  381. id = 0;
  382. for (const seriesRec of seriesArr) {
  383. seriesRec.id = ++id;
  384. }
  385. await utils.sleep(100);
  386. callback({progress: 0.5});
  387. //заодно и названия
  388. titleArr.sort((a, b) => a.value.localeCompare(b.value));
  389. await utils.sleep(100);
  390. callback({progress: 0.7});
  391. id = 0;
  392. for (const titleRec of titleArr) {
  393. titleRec.id = ++id;
  394. }
  395. //stats
  396. const stats = {
  397. filesCount: 0,//вычислим позднее
  398. filesCountAll: 0,//вычислим позднее
  399. filesDelCount: 0,//вычислим позднее
  400. recsLoaded,
  401. authorCount,
  402. authorCountAll: authorArr.length,
  403. bookCount,
  404. bookCountAll: bookCount + bookDelCount,
  405. bookDelCount,
  406. noAuthorBookCount,
  407. titleCount: titleArr.length,
  408. seriesCount: seriesArr.length,
  409. genreCount: genreArr.length,
  410. langCount: langArr.length,
  411. };
  412. //console.log(stats);
  413. //сохраним поисковые таблицы
  414. const chunkSize = 10000;
  415. const saveTable = async(table, arr, nullArr, authorIdToArray = false, bookIdToArray = false, indexType = 'string') => {
  416. if (indexType == 'string')
  417. arr.sort((a, b) => a.value.localeCompare(b.value));
  418. else
  419. arr.sort((a, b) => a.value - b.value);
  420. await db.create({
  421. table,
  422. index: {field: 'value', unique: true, type: indexType, depth: 1000000},
  423. });
  424. //вставка в БД по кусочкам, экономим память
  425. for (let i = 0; i < arr.length; i += chunkSize) {
  426. const chunk = arr.slice(i, i + chunkSize);
  427. if (authorIdToArray) {
  428. for (const rec of chunk)
  429. rec.authorId = Array.from(rec.authorId);
  430. }
  431. if (bookIdToArray) {
  432. for (const rec of chunk)
  433. rec.bookId = Array.from(rec.bookId);
  434. }
  435. await db.insert({table, rows: chunk});
  436. if (i % 5 == 0) {
  437. await db.freeMemory();
  438. await utils.sleep(100);
  439. }
  440. callback({progress: i/arr.length});
  441. }
  442. nullArr();
  443. await db.close({table});
  444. utils.freeMemory();
  445. await db.freeMemory();
  446. };
  447. //author
  448. callback({job: 'author save', jobMessage: 'Сохранение индекса авторов', jobStep: 6, progress: 0});
  449. await saveTable('author', authorArr, () => {authorArr = null});
  450. //series
  451. callback({job: 'series save', jobMessage: 'Сохранение индекса серий', jobStep: 7, progress: 0});
  452. await saveTable('series', seriesArr, () => {seriesArr = null}, true, true);
  453. //title
  454. callback({job: 'title save', jobMessage: 'Сохранение индекса названий', jobStep: 8, progress: 0});
  455. await saveTable('title', titleArr, () => {titleArr = null}, true, true);
  456. //genre
  457. callback({job: 'genre save', jobMessage: 'Сохранение индекса жанров', jobStep: 9, progress: 0});
  458. await saveTable('genre', genreArr, () => {genreArr = null}, true);
  459. callback({job: 'others save', jobMessage: 'Сохранение остальных индексов', jobStep: 10, progress: 0});
  460. //lang
  461. await saveTable('lang', langArr, () => {langArr = null}, true);
  462. //del
  463. await saveTable('del', delArr, () => {delArr = null}, true, false, 'number');
  464. //date
  465. await saveTable('date', dateArr, () => {dateArr = null}, true);
  466. //librate
  467. await saveTable('librate', librateArr, () => {librateArr = null}, true, false, 'number');
  468. //кэш-таблицы запросов
  469. await db.create({table: 'query_cache'});
  470. await db.create({table: 'query_time'});
  471. //кэш-таблица имен файлов и их хешей
  472. await db.create({table: 'file_hash'});
  473. //-- завершающие шаги --------------------------------
  474. await db.open({
  475. table: 'book',
  476. cacheSize: (config.lowMemoryMode ? 5 : 500),
  477. });
  478. callback({job: 'optimization', jobMessage: 'Оптимизация', jobStep: 11, progress: 0});
  479. await this.optimizeTable('series', 'series_book', 'series', db, (p) => {
  480. if (p.progress)
  481. p.progress = 0.2*p.progress;
  482. callback(p);
  483. });
  484. await this.optimizeTable('title', 'title_book', 'title', db, (p) => {
  485. if (p.progress)
  486. p.progress = 0.2 + 0.8*p.progress;
  487. callback(p);
  488. });
  489. callback({job: 'stats count', jobMessage: 'Подсчет статистики', jobStep: 12, progress: 0});
  490. await this.countStats(db, callback, stats);
  491. //чистка памяти, ибо жрет как не в себя
  492. await db.drop({table: 'book'});//больше не понадобится
  493. await db.freeMemory();
  494. utils.freeMemory();
  495. //config сохраняем в самом конце, нет конфига - с базой что-то не так
  496. const inpxHashCreator = new InpxHashCreator(config);
  497. await db.create({
  498. table: 'config'
  499. });
  500. await db.insert({table: 'config', rows: [
  501. {id: 'inpxInfo', value: (inpxFilter && inpxFilter.info ? inpxFilter.info : parser.info)},
  502. {id: 'stats', value: stats},
  503. {id: 'inpxHash', value: await inpxHashCreator.getHash()},
  504. ]});
  505. callback({job: 'done', jobMessage: ''});
  506. }
  507. async optimizeTable(from, to, restoreProp, db, callback) {
  508. //оптимизация таблицы from, превращаем массив bookId в books, кладем все в таблицу to
  509. await db.open({table: from});
  510. await db.create({
  511. table: to,
  512. flag: {name: 'toDel', check: 'r => r.toDel'},
  513. });
  514. const saveChunk = async(chunk) => {
  515. const ids = [];
  516. for (const s of chunk) {
  517. for (const id of s.bookId) {
  518. ids.push(id);
  519. }
  520. }
  521. ids.sort((a, b) => a - b);// обязательно, иначе будет тормозить - особенности JembaDb
  522. const rows = await db.select({table: 'book', where: `@@id(${db.esc(ids)})`});
  523. const bookArr = new Map();
  524. for (const row of rows)
  525. bookArr.set(row.id, row);
  526. for (const s of chunk) {
  527. s.books = [];
  528. s.bookCount = 0;
  529. s.bookDelCount = 0;
  530. for (const id of s.bookId) {
  531. const rec = bookArr.get(id);
  532. if (rec) {//на всякий случай
  533. s.books.push(rec);
  534. if (!rec.del)
  535. s.bookCount++;
  536. else
  537. s.bookDelCount++;
  538. }
  539. }
  540. if (s.books.length) {
  541. s[restoreProp] = s.books[0][restoreProp];
  542. } else {
  543. s.toDel = 1;
  544. }
  545. delete s.value;
  546. delete s.authorId;
  547. delete s.bookId;
  548. }
  549. await db.insert({
  550. table: to,
  551. rows: chunk,
  552. });
  553. };
  554. const rows = await db.select({table: from, count: true});
  555. const fromLength = rows[0].count;
  556. let processed = 0;
  557. while (1) {// eslint-disable-line
  558. const chunk = await db.select({
  559. table: from,
  560. where: `
  561. let iter = @getItem('optimize');
  562. if (!iter) {
  563. iter = @all();
  564. @setItem('optimize', iter);
  565. }
  566. const ids = new Set();
  567. let id = iter.next();
  568. while (!id.done) {
  569. ids.add(id.value);
  570. if (ids.size >= 20000)
  571. break;
  572. id = iter.next();
  573. }
  574. return ids;
  575. `
  576. });
  577. if (chunk.length) {
  578. await saveChunk(chunk);
  579. processed += chunk.length;
  580. callback({progress: processed/fromLength});
  581. } else
  582. break;
  583. if (this.config.lowMemoryMode) {
  584. await utils.sleep(10);
  585. utils.freeMemory();
  586. await db.freeMemory();
  587. }
  588. }
  589. await db.delete({table: to, where: `@@flag('toDel')`});
  590. await db.close({table: to});
  591. await db.close({table: from});
  592. }
  593. async countStats(db, callback, stats) {
  594. //статистика по количеству файлов
  595. //эмуляция прогресса
  596. let countDone = false;
  597. (async() => {
  598. let i = 0;
  599. while (!countDone) {
  600. callback({progress: i/100});
  601. i = (i < 100 ? i + 5 : 100);
  602. await utils.sleep(1000);
  603. }
  604. })();
  605. //подчсет
  606. const countRes = await db.select({table: 'book', rawResult: true, where: `
  607. const files = new Set();
  608. const filesDel = new Set();
  609. for (const id of @all()) {
  610. const r = @row(id);
  611. const file = ${"`${r.folder}/${r.file}.${r.ext}`"};
  612. if (!r.del) {
  613. files.add(file);
  614. } else {
  615. filesDel.add(file);
  616. }
  617. }
  618. for (const file of filesDel)
  619. if (files.has(file))
  620. filesDel.delete(file);
  621. return {filesCount: files.size, filesDelCount: filesDel.size};
  622. `});
  623. if (countRes.length) {
  624. const res = countRes[0].rawResult;
  625. stats.filesCount = res.filesCount;
  626. stats.filesCountAll = res.filesCount + res.filesDelCount;
  627. stats.filesDelCount = res.filesDelCount;
  628. }
  629. countDone = true;
  630. }
  631. }
  632. module.exports = DbCreator;