DbCreator.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. const InpxParser = require('./InpxParser');
  2. const utils = require('./utils');
  3. class DbCreator {
  4. constructor(config) {
  5. this.config = config;
  6. }
  7. async run(db, callback) {
  8. const config = this.config;
  9. //book
  10. await db.create({
  11. table: 'book'
  12. });
  13. callback({job: 'load inpx', jobMessage: 'Загрузка INPX'});
  14. const readFileCallback = async(readState) => {
  15. callback(readState);
  16. };
  17. //поисковые таблицы, ниже сохраним в БД
  18. let authorMap = new Map();//авторы
  19. let authorArr = [];
  20. let seriesMap = new Map();//серии
  21. let seriesArr = [];
  22. let titleMap = new Map();//названия
  23. let titleArr = [];
  24. let genreMap = new Map();//жанры
  25. let genreArr = [];
  26. let langMap = new Map();//языки
  27. let langArr = [];
  28. //stats
  29. let authorCount = 0;
  30. let bookCount = 0;
  31. let noAuthorBookCount = 0;
  32. let bookDelCount = 0;
  33. //stuff
  34. let recsLoaded = 0;
  35. let id = 0;
  36. let chunkNum = 0;
  37. const parsedCallback = async(chunk) => {
  38. for (const rec of chunk) {
  39. rec.id = ++id;
  40. if (!rec.del)
  41. bookCount++;
  42. else
  43. bookDelCount++;
  44. if (!rec.author) {
  45. if (!rec.del)
  46. noAuthorBookCount++;
  47. rec.author = 'Автор не указан';
  48. }
  49. //авторы
  50. const author = rec.author.split(',');
  51. if (author.length > 1)
  52. author.push(rec.author);
  53. const authorIds = [];
  54. for (let i = 0; i < author.length; i++) {
  55. const a = author[i];
  56. let authorRec;
  57. if (authorMap.has(a)) {
  58. const authorId = authorMap.get(a);
  59. authorRec = authorArr[authorId];
  60. } else {
  61. authorRec = {id: authorArr.length, author: a, value: a.toLowerCase(), bookId: []};
  62. authorArr.push(authorRec);
  63. authorMap.set(a, authorRec.id);
  64. if (author.length == 1 || i < author.length - 1) //без соавторов
  65. authorCount++;
  66. }
  67. authorRec.bookId.push(id);
  68. authorIds.push(authorRec.id);
  69. }
  70. //серии
  71. if (rec.series) {
  72. const series = rec.series;
  73. let seriesRec;
  74. if (seriesMap.has(series)) {
  75. const seriesId = seriesMap.get(series);
  76. seriesRec = seriesArr[seriesId];
  77. } else {
  78. seriesRec = {id: seriesArr.length, value: series.toLowerCase(), authorId: new Set()};
  79. seriesArr.push(seriesRec);
  80. seriesMap.set(series, seriesRec.id);
  81. }
  82. for (const id of authorIds) {
  83. seriesRec.authorId.add(id);
  84. }
  85. }
  86. //названия
  87. if (rec.title) {
  88. const title = rec.title;
  89. let titleRec;
  90. if (titleMap.has(title)) {
  91. const titleId = titleMap.get(title);
  92. titleRec = titleArr[titleId];
  93. } else {
  94. titleRec = {id: titleArr.length, value: title.toLowerCase(), authorId: new Set()};
  95. titleArr.push(titleRec);
  96. titleMap.set(title, titleRec.id);
  97. }
  98. for (const id of authorIds) {
  99. titleRec.authorId.add(id);
  100. }
  101. }
  102. //жанры
  103. if (rec.genre) {
  104. const genre = rec.genre.split(',');
  105. for (const g of genre) {
  106. let genreRec;
  107. if (genreMap.has(g)) {
  108. const genreId = genreMap.get(g);
  109. genreRec = genreArr[genreId];
  110. } else {
  111. genreRec = {id: genreArr.length, value: g, authorId: new Set()};
  112. genreArr.push(genreRec);
  113. genreMap.set(g, genreRec.id);
  114. }
  115. for (const id of authorIds) {
  116. genreRec.authorId.add(id);
  117. }
  118. }
  119. }
  120. //языки
  121. if (rec.lang) {
  122. const lang = rec.lang;
  123. let langRec;
  124. if (langMap.has(lang)) {
  125. const langId = langMap.get(lang);
  126. langRec = langArr[langId];
  127. } else {
  128. langRec = {id: langArr.length, value: lang, authorId: new Set()};
  129. langArr.push(langRec);
  130. langMap.set(lang, langRec.id);
  131. }
  132. for (const id of authorIds) {
  133. langRec.authorId.add(id);
  134. }
  135. }
  136. }
  137. await db.insert({table: 'book', rows: chunk});
  138. recsLoaded += chunk.length;
  139. callback({recsLoaded});
  140. if (chunkNum++ % 10 == 0)
  141. utils.freeMemory();
  142. };
  143. //парсинг
  144. const parser = new InpxParser();
  145. await parser.parse(config.inpxFile, readFileCallback, parsedCallback);
  146. //чистка памяти, ибо жрет как не в себя
  147. authorMap = null;
  148. seriesMap = null;
  149. titleMap = null;
  150. genreMap = null;
  151. utils.freeMemory();
  152. //config
  153. callback({job: 'config save', jobMessage: 'Сохранение конфигурации'});
  154. await db.create({
  155. table: 'config'
  156. });
  157. const stats = {
  158. recsLoaded,
  159. authorCount,
  160. authorCountAll: authorArr.length,
  161. bookCount,
  162. bookCountAll: bookCount + bookDelCount,
  163. bookDelCount,
  164. noAuthorBookCount,
  165. titleCount: titleArr.length,
  166. seriesCount: seriesArr.length,
  167. genreCount: genreArr.length,
  168. langCount: langArr.length,
  169. };
  170. //console.log(stats);
  171. const inpxHash = await utils.getFileHash(config.inpxFile, 'sha256', 'hex');
  172. await db.insert({table: 'config', rows: [
  173. {id: 'inpxInfo', value: parser.info},
  174. {id: 'stats', value: stats},
  175. {id: 'inpxHash', value: inpxHash},
  176. ]});
  177. //сохраним поисковые таблицы
  178. const chunkSize = 10000;
  179. //author
  180. callback({job: 'author save', jobMessage: 'Сохранение авторов книг'});
  181. await db.create({
  182. table: 'author',
  183. index: {field: 'value', depth: config.indexDepth},
  184. });
  185. //вставка в БД по кусочкам, экономим память
  186. for (let i = 0; i < authorArr.length; i += chunkSize) {
  187. const chunk = authorArr.slice(i, i + chunkSize);
  188. await db.insert({table: 'author', rows: chunk});
  189. }
  190. authorArr = null;
  191. await db.close({table: 'author'});
  192. utils.freeMemory();
  193. //series
  194. callback({job: 'series save', jobMessage: 'Сохранение серий книг'});
  195. await db.create({
  196. table: 'series',
  197. index: {field: 'value', depth: config.indexDepth},
  198. });
  199. //вставка в БД по кусочкам, экономим память
  200. for (let i = 0; i < seriesArr.length; i += chunkSize) {
  201. const chunk = seriesArr.slice(i, i + chunkSize);
  202. for (const rec of chunk)
  203. rec.authorId = Array.from(rec.authorId);
  204. await db.insert({table: 'series', rows: chunk});
  205. }
  206. seriesArr = null;
  207. await db.close({table: 'series'});
  208. utils.freeMemory();
  209. //title
  210. callback({job: 'title save', jobMessage: 'Сохранение названий книг'});
  211. await db.create({
  212. table: 'title',
  213. index: {field: 'value', depth: config.indexDepth},
  214. });
  215. //вставка в БД по кусочкам, экономим память
  216. let j = 0;
  217. for (let i = 0; i < titleArr.length; i += chunkSize) {
  218. const chunk = titleArr.slice(i, i + chunkSize);
  219. for (const rec of chunk)
  220. rec.authorId = Array.from(rec.authorId);
  221. await db.insert({table: 'title', rows: chunk});
  222. if (j++ % 10 == 0)
  223. utils.freeMemory();
  224. await utils.sleep(100);
  225. }
  226. titleArr = null;
  227. await db.close({table: 'title'});
  228. utils.freeMemory();
  229. //genre
  230. callback({job: 'genre save', jobMessage: 'Сохранение жанров'});
  231. await db.create({
  232. table: 'genre',
  233. index: {field: 'value', depth: config.indexDepth},
  234. });
  235. await db.insert({table: 'genre', rows: genreArr});
  236. genreArr = null;
  237. await db.close({table: 'genre'});
  238. utils.freeMemory();
  239. //lang
  240. callback({job: 'lang save', jobMessage: 'Сохранение языков'});
  241. await db.create({
  242. table: 'lang',
  243. index: {field: 'value', depth: config.indexDepth},
  244. });
  245. await db.insert({table: 'lang', rows: langArr});
  246. langArr = null;
  247. await db.close({table: 'lang'});
  248. utils.freeMemory();
  249. callback({job: 'done', jobMessage: ''});
  250. }
  251. }
  252. module.exports = DbCreator;