![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Про флибусту
Скачавши очередное (декабрьское) обновление флибусты, решил подумать о том, как бы себе сделать правильную искалку по ее метаинформации. На основании того, что я знаю о XML-схеме fb2-файлов и о том как устроены индексные (inpx) файлы флибусты. (ну и о том что флибустьеры не правят метаинфомрацию в файлах, поэтому корректная метаинформация содержится (если содержится) только в inp-файлах.
Ну то есть я знаю что у MyHomeLib схема базы кривая, ее какой-то девопс дизайнил, который не то что Дейта - Граббера не читал.
Ну для начала взял pgmodeller и нарисовал ER-диаграмму каталога библиотеки fb2-файлов. Из чисто флибустовских сущностей добавил туда только понятие архива. В смысле того zip-файла в котором в скачанном из торрентов мирроре лежит нужный f2.
Получилось 10 таблиц. Сущностей в принципе не так уж много
- book
- author (он же translator)
- sequence
- lang (классификатор для полей lang и srclang в таблице book)
- genre
Но везде же отношения m:n и нужны развязывающие таблички. А в табличке устанавливающей соответствие между книгой и серией еще и содержательное поле number лежит. И ведь на него даже NOT NULL не повесишь, я же знаю что полно есть в архиве книг у которых серия без номера.
Вообще я чувствую, что над жанрами еще табличка транзитивного замыкания нужна чтобы из них дерево выстроить. Но поскольку fb2-шной концепции жанров я никогда не понимал (и подозреваю что большая часть оцифровщиков тоже и жанры они в fb2-файлах ставили от балды) занимтаься этим мне лень.
Вот теперь думаю - писать парсилку inp-файлов в эту схему в sqlite, или уж сразу постгрес использовать. (и импортить туда весь текст fb2 на предмет организации полнотекстового поиска).
Для интересующихся сдизайненную схему положу под кат:
CREATE TABLE lang ( lang_code char(3) NOT NULL, lang_name text NOT NULL, CONSTRAINT lang_pk PRIMARY KEY (lang_code) ); CREATE TABLE archive ( archive_id serial NOT NULL, archive_name text NOT NULL, CONSTRAINT archive_pk PRIMARY KEY (archive_id) ); CREATE TABLE book ( book_id int8 NOT NULL, title text NOT NULL, file text NOT NULL, deleted boolean NOT NULL DEFAULT false, writedate datetime, lang char(3), srclang char(3), archive smallint NOT NULL, CONSTRAINT book_pk PRIMARY KEY (book_id), FOREIGN KEY (lang) REFERENCES lang(lang_code), FOREIGN KEY (srclang) REFERENCES lang(lang_code), FOREIGN KEY (archive) REFERENCES archive(archive_id) ); CREATE TABLE author ( author_id int4 NOT NULL, first_name text, middle_name text, last_name text, nickname text, CONSTRAINT author_pk PRIMARY KEY (author_id) ); CREATE TABLE seq ( sequence_id int4 NOT NULL, name text NOT NULL, CONSTRAINT seq_pk PRIMARY KEY (sequence_id) ); CREATE TABLE replaces ( old_book_id int8 NOT NULL, new_book_id int8 NOT NULL, CONSTRAINT replaces_pk PRIMARY KEY (old_book_id,new_book_id), FOREIGN KEY (old_book_id) REFERENCES book(book_id), FOREIGN KEY (new_book_id) REFERENCES book(book_id) ); CREATE TABLE book_author ( book_id int8 NOT NULL, author_id int4 NOT NULL, CONSTRAINT book_author_pk PRIMARY KEY (book_id,author_id), FOREIGN KEY (book_id) REFERENCES book(book_id), FOREIGN KEY (author_id) REFERENCES author(author_id) ); CREATE TABLE book_translator ( book_id int8 NOT NULL, translator_id int4 NOT NULL, CONSTRAINT book_translator_pk PRIMARY KEY (book_id,translator_id), FOREIGN KEY (book_id) REFERENCES book(book_id), FOREIGN KEY (translator_id) REFERENCES author(author_id) ); CREATE TABLE book_seq ( book_id int8 NOT NULL, sequence_id int4 NOT NULL, sequence_number int4, CONSTRAINT book_seq_pk PRIMARY KEY (book_id,sequence_id), FOREIGN KEY (book_id) REFERENCES book(book_id), FOREIGN KEY (sequence_id) REFERENCES seq(sequence_id) ); CREATE TABLE genre ( genre_id varchar(20) NOT NULL, genre_name text NOT NULL, CONSTRAINT genre_pk PRIMARY KEY (genre_id) ); CREATE TABLE book_genre ( book_id int8 NOT NULL, genre_id varchar(20) NOT NULL, CONSTRAINT book_genre_pk PRIMARY KEY (book_id,genre_id), FOREIGN KEY (book_id) REFERENCES book(book_id), FOREIGN KEY (genre_id) REFERENCES genre(genre_id) );
Что характерно, пока я причесывал этот SQL скрипт так, чтобы его сожрал sqlite, его объем сократился в 2.5 раза, а из содержательной информации потерялись только имена констрейнтов внешних ключей (и то можно было бы сохранить).
no subject
У меня вот полнотекстовый поиск для себя есть через спецплагин к calibre и recoll (штатный поиск в Calibre 6 - неудобный) но у меня объемы базы Calibre целиком - около 100 Gb и там не только fb2 а объем fb2 насколько помню где то 300-400 Gb архивов.
(no subject)
(no subject)
(no subject)
no subject
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
no subject
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
no subject
Переделал на создание индексов в виде файлов (json и jsonl) и тупое чтение без сложных разборов, чтобы не грузить процессор. Правда, пришлось ещё делать многопроходное создание индексов по авторам, чтобы не упереться в память до oom, но это уже детали.
У меня парсится каждый fb2 (точнее, первые десятки кб, а не весь), а .inp использовался только для замены всякого, ибо рядом может лежать архив с файлами из samlib, к примеру. Плюс есть файлы замены метаданных (скажем, для автора "Луиза-Франсуаза" они понадобились, так как ник я ещё могу вспомнить, а фио - уже нет). В новом варианте индексация занимает примерно ночь, в старом - около суток (sqlite не самая быстрая штука на запись).
Надо будет выложить в доступное место, а то сейчас только в моём личном гите.
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
Зачем inp-файлы?
Re: Зачем inp-файлы?