Spaces:
Sleeping
Sleeping
Upload database.js
Browse files- database.js +55 -40
database.js
CHANGED
@@ -17,9 +17,8 @@ export async function initializeDatabase() {
|
|
17 |
console.log('Подключение к SQLite (Main API) установлено.');
|
18 |
|
19 |
await db.exec('PRAGMA journal_mode = WAL;');
|
20 |
-
await db.exec('PRAGMA synchronous = NORMAL;');
|
21 |
|
22 |
-
// Создаем основные таблицы
|
23 |
await db.exec(`
|
24 |
CREATE TABLE IF NOT EXISTS movies (
|
25 |
id INTEGER PRIMARY KEY,
|
@@ -52,35 +51,38 @@ export async function initializeDatabase() {
|
|
52 |
}
|
53 |
|
54 |
/**
|
55 |
-
*
|
|
|
56 |
*/
|
57 |
export async function refreshData() {
|
58 |
console.log('Начинается высокопроизводительная перезагрузка данных...');
|
59 |
try {
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
const
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
const moviesData = [];
|
70 |
const allGenres = new Set();
|
71 |
const allCountries = new Set();
|
72 |
-
|
|
|
73 |
if (line.trim()) {
|
|
|
74 |
const movie = JSON.parse(line);
|
75 |
-
moviesData.push(movie);
|
76 |
movie.genres?.forEach(g => allGenres.add(g.name));
|
77 |
movie.countries?.forEach(c => allCountries.add(c.name));
|
78 |
}
|
79 |
}
|
80 |
-
console.log(`Шаг 1 завершен. Найдено ${
|
81 |
|
|
|
82 |
await db.exec('BEGIN TRANSACTION;');
|
83 |
-
|
|
|
84 |
await db.exec('DELETE FROM movie_genres;');
|
85 |
await db.exec('DELETE FROM movie_countries;');
|
86 |
await db.exec('DELETE FROM genres;');
|
@@ -102,7 +104,7 @@ export async function refreshData() {
|
|
102 |
await countryStmt.finalize();
|
103 |
console.log('Шаг 2 завершен.');
|
104 |
|
105 |
-
// --- ШАГ 3: Кэширование ID жанров и стран ---
|
106 |
console.log('Шаг 3: Кэширование ID в память...');
|
107 |
const genreCache = new Map();
|
108 |
const countryCache = new Map();
|
@@ -112,34 +114,44 @@ export async function refreshData() {
|
|
112 |
countriesFromDb.forEach(c => countryCache.set(c.name, c.id));
|
113 |
console.log('Шаг 3 завершен.');
|
114 |
|
115 |
-
// --- ШАГ 4:
|
116 |
-
console.log('Шаг 4:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
const movieStmt = await db.prepare('INSERT INTO movies (id, year, type, rating_kp, rating_imdb, age_rating, movie_length, is_series, data) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
118 |
-
// ИСПРАВЛЕНИЕ: Используем INSERT OR IGNORE для избежания сбоя при дубликатах
|
119 |
const movieGenreStmt = await db.prepare('INSERT OR IGNORE INTO movie_genres (movie_id, genre_id) VALUES (?, ?)');
|
120 |
const movieCountryStmt = await db.prepare('INSERT OR IGNORE INTO movie_countries (movie_id, country_id) VALUES (?, ?)');
|
121 |
|
122 |
-
|
123 |
-
|
124 |
-
if (
|
125 |
-
|
126 |
-
|
127 |
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
|
|
|
|
|
|
132 |
}
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
}
|
139 |
-
}
|
140 |
|
141 |
-
|
142 |
-
|
|
|
|
|
143 |
}
|
144 |
}
|
145 |
|
@@ -148,28 +160,31 @@ export async function refreshData() {
|
|
148 |
await movieCountryStmt.finalize();
|
149 |
|
150 |
await db.exec('COMMIT;');
|
151 |
-
console.log(`Шаг 4 завершен. Перезагрузка данных успешно
|
152 |
|
153 |
} catch (error) {
|
154 |
console.error('Критическая ошибка во время обновления данных:', error);
|
155 |
try {
|
156 |
await db.exec('ROLLBACK;');
|
|
|
157 |
} catch (rollbackError) {
|
158 |
console.error('Ошибка при откате транзакции:', rollbackError);
|
159 |
}
|
160 |
}
|
161 |
}
|
162 |
|
163 |
-
|
164 |
// --- Функции для API остаются без изменений ---
|
|
|
165 |
export async function getMovieById(id) {
|
166 |
const row = await db.get('SELECT data FROM movies WHERE id = ?', id);
|
167 |
return row ? JSON.parse(row.data) : null;
|
168 |
}
|
|
|
169 |
export async function getRandomMovie() {
|
170 |
const row = await db.get('SELECT data FROM movies ORDER BY RANDOM() LIMIT 1');
|
171 |
return row ? JSON.parse(row.data) : null;
|
172 |
}
|
|
|
173 |
export async function getMovies(options) {
|
174 |
// Эта функция остается без изменений, так как она уже была достаточно оптимизирована для чтения
|
175 |
const { filters, pagination, sorting } = options;
|
|
|
17 |
console.log('Подключение к SQLite (Main API) установлено.');
|
18 |
|
19 |
await db.exec('PRAGMA journal_mode = WAL;');
|
20 |
+
await db.exec('PRAGMA synchronous = NORMAL;');
|
21 |
|
|
|
22 |
await db.exec(`
|
23 |
CREATE TABLE IF NOT EXISTS movies (
|
24 |
id INTEGER PRIMARY KEY,
|
|
|
51 |
}
|
52 |
|
53 |
/**
|
54 |
+
* Переработанная функция загрузки данных, использующая потоковую обработку
|
55 |
+
* для предотвращения переполнения памяти.
|
56 |
*/
|
57 |
export async function refreshData() {
|
58 |
console.log('Начинается высокопроизводительная перезагрузка данных...');
|
59 |
try {
|
60 |
+
// --- ШАГ 1: Сканирование файла для сбора метаданных (жанры, страны) ---
|
61 |
+
console.log('Шаг 1: Сканирование файла для сбора жанров и стран...');
|
62 |
+
const response1 = await fetch(DATA_URL);
|
63 |
+
if (!response1.ok) throw new Error(`Ошибка загрузки на первом проходе: ${response1.statusText}`);
|
64 |
+
|
65 |
+
const gunzip1 = createGunzip();
|
66 |
+
response1.body.pipe(gunzip1);
|
67 |
+
const rl1 = createInterface({ input: gunzip1, crlfDelay: Infinity });
|
68 |
+
|
|
|
69 |
const allGenres = new Set();
|
70 |
const allCountries = new Set();
|
71 |
+
|
72 |
+
for await (const line of rl1) {
|
73 |
if (line.trim()) {
|
74 |
+
// Мы не храним весь объект, только извлекаем нужные данные
|
75 |
const movie = JSON.parse(line);
|
|
|
76 |
movie.genres?.forEach(g => allGenres.add(g.name));
|
77 |
movie.countries?.forEach(c => allCountries.add(c.name));
|
78 |
}
|
79 |
}
|
80 |
+
console.log(`Шаг 1 завершен. Найдено ${allGenres.size} уникальных жанров, ${allCountries.size} уникальных стран.`);
|
81 |
|
82 |
+
// --- Начало транзакции для массовой вставки ---
|
83 |
await db.exec('BEGIN TRANSACTION;');
|
84 |
+
|
85 |
+
// Очищаем таблицы перед новой загрузкой
|
86 |
await db.exec('DELETE FROM movie_genres;');
|
87 |
await db.exec('DELETE FROM movie_countries;');
|
88 |
await db.exec('DELETE FROM genres;');
|
|
|
104 |
await countryStmt.finalize();
|
105 |
console.log('Шаг 2 завершен.');
|
106 |
|
107 |
+
// --- ШАГ 3: Кэширование ID жанров и стран для быстрой связи ---
|
108 |
console.log('Шаг 3: Кэширование ID в память...');
|
109 |
const genreCache = new Map();
|
110 |
const countryCache = new Map();
|
|
|
114 |
countriesFromDb.forEach(c => countryCache.set(c.name, c.id));
|
115 |
console.log('Шаг 3 завершен.');
|
116 |
|
117 |
+
// --- ШАГ 4: Потоковая вставка фильмов и их связей (второй проход) ---
|
118 |
+
console.log('Шаг 4: Потоковая запись фильмов и их связей...');
|
119 |
+
const response2 = await fetch(DATA_URL);
|
120 |
+
if (!response2.ok) throw new Error(`Ошибка загрузки на втором проходе: ${response2.statusText}`);
|
121 |
+
|
122 |
+
const gunzip2 = createGunzip();
|
123 |
+
response2.body.pipe(gunzip2);
|
124 |
+
const rl2 = createInterface({ input: gunzip2, crlfDelay: Infinity });
|
125 |
+
|
126 |
const movieStmt = await db.prepare('INSERT INTO movies (id, year, type, rating_kp, rating_imdb, age_rating, movie_length, is_series, data) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
|
|
127 |
const movieGenreStmt = await db.prepare('INSERT OR IGNORE INTO movie_genres (movie_id, genre_id) VALUES (?, ?)');
|
128 |
const movieCountryStmt = await db.prepare('INSERT OR IGNORE INTO movie_countries (movie_id, country_id) VALUES (?, ?)');
|
129 |
|
130 |
+
let processedCount = 0;
|
131 |
+
for await (const line of rl2) {
|
132 |
+
if (line.trim()) {
|
133 |
+
const movie = JSON.parse(line);
|
134 |
+
if (!movie.id) continue;
|
135 |
|
136 |
+
await movieStmt.run(movie.id, movie.year, movie.type, movie.rating?.kp, movie.rating?.imdb, movie.ageRating, movie.movieLength, movie.isSeries, JSON.stringify(movie));
|
137 |
+
|
138 |
+
if (movie.genres) {
|
139 |
+
for (const genre of movie.genres) {
|
140 |
+
const genreId = genreCache.get(genre.name);
|
141 |
+
if (genreId) await movieGenreStmt.run(movie.id, genreId);
|
142 |
+
}
|
143 |
}
|
144 |
+
if (movie.countries) {
|
145 |
+
for (const country of movie.countries) {
|
146 |
+
const countryId = countryCache.get(country.name);
|
147 |
+
if (countryId) await movieCountryStmt.run(movie.id, countryId);
|
148 |
+
}
|
149 |
}
|
|
|
150 |
|
151 |
+
processedCount++;
|
152 |
+
if (processedCount % 10000 === 0) {
|
153 |
+
console.log(`Обработано ${processedCount} фильмов...`);
|
154 |
+
}
|
155 |
}
|
156 |
}
|
157 |
|
|
|
160 |
await movieCountryStmt.finalize();
|
161 |
|
162 |
await db.exec('COMMIT;');
|
163 |
+
console.log(`Шаг 4 завершен. Перезагрузка данных успешно окончена. Всего обработано ${processedCount} фильмов.`);
|
164 |
|
165 |
} catch (error) {
|
166 |
console.error('Критическая ошибка во время обновления данных:', error);
|
167 |
try {
|
168 |
await db.exec('ROLLBACK;');
|
169 |
+
console.log('Транзакция была отменена.');
|
170 |
} catch (rollbackError) {
|
171 |
console.error('Ошибка при откате транзакции:', rollbackError);
|
172 |
}
|
173 |
}
|
174 |
}
|
175 |
|
|
|
176 |
// --- Функции для API остаются без изменений ---
|
177 |
+
|
178 |
export async function getMovieById(id) {
|
179 |
const row = await db.get('SELECT data FROM movies WHERE id = ?', id);
|
180 |
return row ? JSON.parse(row.data) : null;
|
181 |
}
|
182 |
+
|
183 |
export async function getRandomMovie() {
|
184 |
const row = await db.get('SELECT data FROM movies ORDER BY RANDOM() LIMIT 1');
|
185 |
return row ? JSON.parse(row.data) : null;
|
186 |
}
|
187 |
+
|
188 |
export async function getMovies(options) {
|
189 |
// Эта функция остается без изменений, так как она уже была достаточно оптимизирована для чтения
|
190 |
const { filters, pagination, sorting } = options;
|