Обновлен контекст
This commit is contained in:
1 parent
ab4953ffd1
commit
a51a7e1a76
1 file changed
+175
-142
+175
-142
@@ -1,142 +1,175 @@
|
|||||||
# Выжимка из контекста: Домашняя библиотека
|
# Выжимка из контекста: Домашняя библиотека
|
||||||
|
|
||||||
## Что построено
|
## Что построено
|
||||||
|
|
||||||
**Локальный OPDS-каталог + веб-интерфейс** для копии библиотеки lib.rus.ec (~579 000 книг) на TV-приставке с Armbian.
|
**Локальный OPDS-каталог + веб-интерфейс** для копии библиотеки lib.rus.ec (~579 000 книг) на TV-приставке с Armbian.
|
||||||
|
|
||||||
## Железо и окружение
|
## Железо и окружение
|
||||||
|
|
||||||
- **Устройство**: TV-приставка Vontar, Armbian, ARM
|
- **Устройство**: TV-приставка Vontar, Armbian, ARM
|
||||||
- **Диски**: `/mnt/hdd` — основной
|
- **Диски**: `/mnt/hdd` — основной
|
||||||
- **Docker**: контейнеры Immich (фото), Home Assistant, qBittorrent, Adminer
|
- **Docker**: контейнеры Immich (фото), Home Assistant, qBittorrent, Adminer
|
||||||
- **PostgreSQL**: используется общий с Immich (`immich_postgres`), база `books`
|
- **PostgreSQL**: используется общий с Immich (`immich_postgres`), база `books`
|
||||||
- Пароль: `Karabas@Barabas`, пользователь: `postgres`
|
- Пароль: `Karabas@Barabas`, пользователь: `postgres`
|
||||||
- **Фиксированный IP**: `172.20.0.4` (сеть `immich_immich_net`, подсеть `172.20.0.0/16`)
|
- **Фиксированный IP**: `172.20.0.4` (сеть `immich_immich_net`, подсеть `172.20.0.0/16`)
|
||||||
- **OPDS-сервер**: контейнер `opds_server` на порту **8085** (порт 8080 занят qBittorrent)
|
- **OPDS-сервер**: контейнер `opds_server` на порту **8085** (порт 8080 занят qBittorrent)
|
||||||
- **Адрес библиотеки**: `http://192.168.30.2:8085/`
|
- **Адрес библиотеки**: `http://192.168.30.2:8085/`
|
||||||
|
|
||||||
## Расположение файлов
|
## Расположение файлов
|
||||||
|
|
||||||
```
|
```
|
||||||
/mnt/hdd/opds/ # проект OPDS
|
/mnt/hdd/opds/ # проект OPDS
|
||||||
├── opds_server.py # главный Flask-сервер
|
├── opds_server.py # главный Flask-сервер
|
||||||
├── db_utils.py # подключение к БД
|
├── db_utils.py # подключение к БД
|
||||||
├── author_utils.py # работа с авторами
|
├── author_utils.py # работа с авторами
|
||||||
├── book_utils.py # извлечение fb2, обложки, конвертация
|
├── book_utils.py # извлечение fb2, обложки, конвертация
|
||||||
├── parse_inpx.py # импорт книг
|
├── parse_inpx.py # импорт книг
|
||||||
├── parse_genres.py # загрузка жанров
|
├── parse_genres.py # загрузка жанров
|
||||||
├── genres.xml # справочник жанров fb2
|
├── genres.xml # справочник жанров fb2
|
||||||
├── Dockerfile
|
├── Dockerfile
|
||||||
└── docker-compose.yml
|
└── docker-compose.yml
|
||||||
|
|
||||||
/mnt/hdd/share/Torrents/Downloaded/Libruks/
|
/mnt/hdd/share/Torrents/Downloaded/Libruks/
|
||||||
├── Библиотека Либрусек для архивов 17-08-2026.inpx
|
├── Библиотека Либрусек для архивов 17-08-2026.inpx
|
||||||
├── Библиотека Либрусек для архивов 18-09-2026.inpx
|
├── Библиотека Либрусек для архивов 18-09-2026.inpx
|
||||||
├── Libruks1086.zip
|
├── Libruks1086.zip
|
||||||
└── Архивы Либрусек/ # 3726 zip-архивов с fb2
|
└── Архивы Либрусек/ # 3726 zip-архивов с fb2
|
||||||
├── fb2-000065-005477.zip
|
├── fb2-000065-005477.zip
|
||||||
├── fb2-069327-070122.zip
|
├── fb2-069327-070122.zip
|
||||||
└── ... (по диапазонам ID)
|
└── ... (по диапазонам ID)
|
||||||
|
|
||||||
/root/docker-compose.yml # Immich (с фиксированным IP postgres)
|
/root/docker-compose.yml # Immich (с фиксированным IP postgres)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Структура базы данных
|
## Структура базы данных
|
||||||
|
|
||||||
| Таблица | Назначение |
|
| Таблица | Назначение |
|
||||||
|---------|-----------|
|
|---------|-----------|
|
||||||
| `authors` | Авторы (нормализованные: full_name, last_name, first_name, middle_name, name_hash, first_letter) |
|
| `authors` | Авторы (нормализованные: full_name, last_name, first_name, middle_name, name_hash, first_letter) |
|
||||||
| `series` | Серии (name, name_hash, first_letter) |
|
| `series` | Серии (name, name_hash, first_letter) |
|
||||||
| `genres` | Жанры (code, title_ru, title_en, parent_code, root_code, is_root, is_custom, genre_alt[]) |
|
| `genres` | Жанры (code, title_ru, title_en, parent_code, root_code, is_root, is_custom, genre_alt[]) |
|
||||||
| `books` | Книги (title, internal_id, internal_id2, file_size, format, added_date, language, source, series_id, series_number, fts) |
|
| `books` | Книги (title, internal_id, internal_id2, file_size, format, added_date, language, source, series_id, series_number, fts) |
|
||||||
| `book_authors` | Связь книг↔авторов (book_id, author_id, position) |
|
| `book_authors` | Связь книг↔авторов (book_id, author_id, position) |
|
||||||
| `book_genres` | Связь книг↔жанров (book_id, genre_id) |
|
| `book_genres` | Связь книг↔жанров (book_id, genre_id) |
|
||||||
|
|
||||||
**Особенности:**
|
**Особенности:**
|
||||||
- Полнотекстовый поиск через `fts tsvector` (русский язык)
|
- Полнотекстовый поиск через `fts tsvector` (русский язык)
|
||||||
- Хеши (MD5) для быстрого поиска
|
- Хеши (MD5) для быстрого поиска
|
||||||
- Поле `source` для поддержки нескольких источников
|
- Поле `source` для поддержки нескольких источников
|
||||||
- Нормализация: авторы/серии/жанры вынесены в отдельные таблицы
|
- Нормализация: авторы/серии/жанры вынесены в отдельные таблицы
|
||||||
|
|
||||||
## Формат inpx (lib.rus.ec)
|
## Формат inpx (lib.rus.ec)
|
||||||
|
|
||||||
Текстовый файл с разделителем `\x04`:
|
Текстовый файл с разделителем `\x04`:
|
||||||
```
|
```
|
||||||
AUTHOR \x04 GENRE \x04 TITLE \x04 SERIES \x04 SERNO \x04 FILE \x04 SIZE \x04 LIBID \x04 DEL \x04 EXT \x04 DATE \x04 LANG \x04 KEYWORDS
|
AUTHOR \x04 GENRE \x04 TITLE \x04 SERIES \x04 SERNO \x04 FILE \x04 SIZE \x04 LIBID \x04 DEL \x04 EXT \x04 DATE \x04 LANG \x04 KEYWORDS
|
||||||
```
|
```
|
||||||
|
|
||||||
- Авторы: `Фамилия,Имя,Отчество` через `:` для нескольких
|
- Авторы: `Фамилия,Имя,Отчество` через `:` для нескольких
|
||||||
- Жанры: коды fb2 через `:` (например, `sf_history:network_literature`)
|
- Жанры: коды fb2 через `:` (например, `sf_history:network_literature`)
|
||||||
- Особенность архивов: **fb2 внутри zip-архивов может быть вложенным zip** — нужно распаковывать дважды
|
- Особенность архивов: **fb2 внутри zip-архивов может быть вложенным zip** — нужно распаковывать дважды
|
||||||
|
|
||||||
## Что реализовано в веб-интерфейсе
|
## Что реализовано в веб-интерфейсе
|
||||||
|
|
||||||
- ✅ Главная с поиском и навигацией
|
- ✅ Главная с поиском и навигацией
|
||||||
- ✅ Поиск (полнотекстовый + по автору)
|
- ✅ Поиск (полнотекстовый + по автору)
|
||||||
- ✅ Авторы с группировкой по алфавиту (умная навигация: Па → Пел → Пелевин)
|
- ✅ Авторы с группировкой по алфавиту (умная навигация: Па → Пел → Пелевин)
|
||||||
- ✅ Страница автора (серии, книги, сборники с пометкой соавторов)
|
- ✅ Страница автора (серии, книги, сборники с пометкой соавторов)
|
||||||
- ✅ Страница книги (обложка, аннотация, оглавление)
|
- ✅ Страница книги (обложка, аннотация, оглавление)
|
||||||
- ✅ Чтение глав (конвертация fb2→HTML на лету, встроенные картинки, навигация ← пред/оглавление↑/след →)
|
- ✅ Чтение глав (конвертация fb2→HTML на лету, встроенные картинки, навигация ← пред/оглавление↑/след →)
|
||||||
- ✅ Скачивание fb2 (с обработкой вложенных zip)
|
- ✅ Скачивание fb2 (с обработкой вложенных zip)
|
||||||
- ✅ OPDS 1.2 для читалок (FBReader)
|
- ✅ OPDS 1.2 для читалок (FBReader)
|
||||||
|
|
||||||
## Что НЕ реализовано / в планах
|
## Что НЕ реализовано / в планах
|
||||||
|
|
||||||
- ❌ Страница серий (список + страница серии с книгами)
|
- ❌ Страница серий (список + страница серии с книгами)
|
||||||
- ❌ Страница жанров (дерево + фильтрация)
|
- ❌ Страница жанров (дерево + фильтрация)
|
||||||
- ❌ Обложки с ленивой загрузкой (обсуждали: заглушка → загрузка в фоне → кэш в `/mnt/hdd/opds/covers/`)
|
- ❌ Обложки с ленивой загрузкой (обсуждали: заглушка → загрузка в фоне → кэш в `/mnt/hdd/opds/covers/`)
|
||||||
- ❌ OPDS 2.0 (обсуждали как второй эндпоинт `/opds2`)
|
- ❌ OPDS 2.0 (обсуждали как второй эндпоинт `/opds2`)
|
||||||
- ❌ Редактирование жанров (кастомные жанры с `is_custom=TRUE` ждут ручного заполнения)
|
- ❌ Редактирование жанров (кастомные жанры с `is_custom=TRUE` ждут ручного заполнения)
|
||||||
- ❌ Языковой фильтр (56 языков в базе, но основные — ru)
|
- ❌ Языковой фильтр (56 языков в базе, но основные — ru)
|
||||||
|
|
||||||
## Ключевые решения
|
## Новый импорт: сканирование файловой системы
|
||||||
|
|
||||||
- **Язык в скобках**: для не-русских книг показывать `(en)`, `(uk)` и т.д.
|
**Проблема INPX:** файл содержит ссылки на книги, которых нет на диске (удалены, не скачаны).
|
||||||
- **Сборники**: если авторов >1 — пометка `👥 соавторы: X, Y` (до 3) или `👥 N авторов` (больше)
|
|
||||||
- **Порядок книг автора**: от новых к старым (`added_date DESC NULLS LAST`)
|
**Решение:** скрипт-сканер каталога — альтернатива INPX.
|
||||||
- **Серии на странице автора**: все, где автор участвует, по алфавиту
|
|
||||||
- **Название**: "Домашняя библиотека" (не "Моя библиотека")
|
### Логика работы
|
||||||
- **Модульная структура**: разбить на модули (db, author, book, series, genre utils)
|
1. Принимает каталог как параметр
|
||||||
- **Git**: хранить всю информацию в репозитории
|
2. Обходит все файлы в каталоге и подкаталогах
|
||||||
|
3. Определяет новые/изменённые файлы через таблицу `file_scan_log`
|
||||||
## Текущие проблемы / нюансы
|
4. Новые/изменённые файлы → распаковка → парсинг FB2 → добавление в БД
|
||||||
|
5. Исчезнувшие файлы → удаление связанных книг из БД
|
||||||
1. **IP PostgreSQL менялся** после перезагрузки → исправлено фиксированным IP `172.20.0.4`
|
|
||||||
2. **Сеть Immich** конфликтовала с adminer → adminer отключён, сеть пересоздана как `immich_immich_net`
|
### Поддержка форматов
|
||||||
3. **Вложенные zip в архивах** → `get_book_fb2` распаковывает дважды
|
- `.zip` → внутри может быть `.zip` → внутри может быть `.fb2`
|
||||||
4. **VACUUM** нельзя в транзакции → `autocommit=True` перед VACUUM
|
- Вложенность: до 3 уровней
|
||||||
5. **Дубликаты авторов** (`Лукьяненко,Сергей,` и `Лукьяненко,Сергей`) → исправлено нормализацией при импорте
|
- RAR: бонусная поддержка
|
||||||
6. **string_agg(DISTINCT ... ORDER BY ...)** — ошибка в PostgreSQL → переписано на подзапросы
|
|
||||||
|
### Таблица `file_scan_log`
|
||||||
## Полезные команды
|
| Поле | Тип | Назначение |
|
||||||
|
|------|-----|-----------|
|
||||||
```bash
|
| file_path | TEXT | Полный путь к файлу |
|
||||||
# Обновление библиотеки новым inpx
|
| file_size | INTEGER | Размер файла |
|
||||||
docker compose exec opds python parse_inpx.py \
|
| file_hash | TEXT | MD5/SHA256 хеш содержимого |
|
||||||
"/path/to/новый.inpx" \
|
| updated_at | TIMESTAMP | Последнее обновление |
|
||||||
"dbname=books user=postgres password=Karabas@Barabas host=172.20.0.4" \
|
|
||||||
"lib.rus.ec"
|
### Удаление книг
|
||||||
|
- Файл исчез с диска → книга удаляется из БД безвозвратно
|
||||||
# Проверка количества книг
|
- Файл изменён → старые книги удаляются, парсится заново
|
||||||
docker exec -it immich_postgres psql -U postgres -d books -c "SELECT COUNT(*) FROM books;"
|
|
||||||
|
### Источник
|
||||||
# Кастомные жанры
|
- Отдельная строка в `books.source` (например, `"file_scan"`)
|
||||||
docker exec -it immich_postgres psql -U postgres -d books -c "
|
|
||||||
SELECT g.code, COUNT(bg.book_id) as cnt
|
## Ключевые решения
|
||||||
FROM genres g JOIN book_genres bg ON bg.genre_id = g.id
|
|
||||||
WHERE g.is_custom = TRUE GROUP BY g.code ORDER BY cnt DESC;"
|
- **Язык в скобках**: для не-русских книг показывать `(en)`, `(uk)` и т.д.
|
||||||
|
- **Сборники**: если авторов >1 — пометка `👥 соавторы: X, Y` (до 3) или `👥 N авторов` (больше)
|
||||||
# Пересборка OPDS
|
- **Порядок книг автора**: от новых к старым (`added_date DESC NULLS LAST`)
|
||||||
cd /mnt/hdd/opds && docker compose up -d --build
|
- **Серии на странице автора**: все, где автор участвует, по алфавиту
|
||||||
```
|
- **Название**: "Домашняя библиотека" (не "Моя библиотека")
|
||||||
|
- **Модульная структура**: разбить на модули (db, author, book, series, genre utils)
|
||||||
## Следующий шаг
|
- **Git**: хранить всю информацию в репозитории
|
||||||
|
|
||||||
Создание Git-репозитория `opds-library` со структурой:
|
## Текущие проблемы / нюансы
|
||||||
- `sql/` — схема БД
|
|
||||||
- `scripts/` — парсеры
|
1. **IP PostgreSQL менялся** после перезагрузки → исправлено фиксированным IP `172.20.0.4`
|
||||||
- `app/` — Flask-сервер (модули)
|
2. **Сеть Immich** конфликтовала с adminer → adminer отключён, сеть пересоздана как `immich_immich_net`
|
||||||
- `data/` — genres.xml
|
3. **Вложенные zip в архивах** → `get_book_fb2` распаковывает дважды
|
||||||
- `docker-compose.yml`, `Dockerfile`, `README.md`
|
4. **VACUUM** нельзя в транзакции → `autocommit=True` перед VACUUM
|
||||||
|
5. **Дубликаты авторов** (`Лукьяненко,Сергей,` и `Лукьяненко,Сергей`) → исправлено нормализацией при импорте
|
||||||
С этого места продолжаем.
|
6. **string_agg(DISTINCT ... ORDER BY ...)** — ошибка в PostgreSQL → переписано на подзапросы
|
||||||
|
|
||||||
|
## Полезные команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Обновление библиотеки новым inpx
|
||||||
|
docker compose exec opds python parse_inpx.py \
|
||||||
|
"/path/to/новый.inpx" \
|
||||||
|
"dbname=books user=postgres password=Karabas@Barabas host=172.20.0.4" \
|
||||||
|
"lib.rus.ec"
|
||||||
|
|
||||||
|
# Проверка количества книг
|
||||||
|
docker exec -it immich_postgres psql -U postgres -d books -c "SELECT COUNT(*) FROM books;"
|
||||||
|
|
||||||
|
# Кастомные жанры
|
||||||
|
docker exec -it immich_postgres psql -U postgres -d books -c "
|
||||||
|
SELECT g.code, COUNT(bg.book_id) as cnt
|
||||||
|
FROM genres g JOIN book_genres bg ON bg.genre_id = g.id
|
||||||
|
WHERE g.is_custom = TRUE GROUP BY g.code ORDER BY cnt DESC;"
|
||||||
|
|
||||||
|
# Пересборка OPDS
|
||||||
|
cd /mnt/hdd/opds && docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Следующий шаг
|
||||||
|
|
||||||
|
Создание Git-репозитория `opds-library` со структурой:
|
||||||
|
- `sql/` — схема БД
|
||||||
|
- `scripts/` — парсеры
|
||||||
|
- `app/` — Flask-сервер (модули)
|
||||||
|
- `data/` — genres.xml
|
||||||
|
- `docker-compose.yml`, `Dockerfile`, `README.md`
|
||||||
|
|
||||||
|
С этого места продолжаем.
|
||||||
Reference in new issue
Block a user