mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-14 15:37:07 +03:00
Add caching for raw videoID fetching
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import { Service, VideoID, VideoIDHash } from "../types/segments.model";
|
import { Service, VideoID, VideoIDHash } from "../types/segments.model";
|
||||||
import { Logger } from "../utils/logger";
|
import { Logger } from "../utils/logger";
|
||||||
|
|
||||||
export function skipSegmentsKey(videoID: VideoID): string {
|
export function skipSegmentsKey(videoID: VideoID, service: Service): string {
|
||||||
return "segments-" + videoID;
|
return "segments." + service + ".videoID." + videoID;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function skipSegmentsHashKey(hashedVideoIDPrefix: VideoIDHash, service: Service): string {
|
export function skipSegmentsHashKey(hashedVideoIDPrefix: VideoIDHash, service: Service): string {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { getHash } from '../utils/getHash';
|
|||||||
import { getIP } from '../utils/getIP';
|
import { getIP } from '../utils/getIP';
|
||||||
import { Logger } from '../utils/logger';
|
import { Logger } from '../utils/logger';
|
||||||
import redis from '../utils/redis';
|
import redis from '../utils/redis';
|
||||||
|
import { getSkipSegmentsByHash } from './getSkipSegmentsByHash';
|
||||||
|
|
||||||
|
|
||||||
async function prepareCategorySegments(req: Request, videoID: VideoID, category: Category, segments: DBSegment[], cache: SegmentCache = {shadowHiddenSegmentIPs: {}}): Promise<Segment[]> {
|
async function prepareCategorySegments(req: Request, videoID: VideoID, category: Category, segments: DBSegment[], cache: SegmentCache = {shadowHiddenSegmentIPs: {}}): Promise<Segment[]> {
|
||||||
@@ -50,7 +51,7 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, category:
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSegmentsByVideoID(req: Request, videoID: string, categories: Category[], service: Service): Promise<Segment[]> {
|
async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories: Category[], service: Service): Promise<Segment[]> {
|
||||||
const cache: SegmentCache = {shadowHiddenSegmentIPs: {}};
|
const cache: SegmentCache = {shadowHiddenSegmentIPs: {}};
|
||||||
const segments: Segment[] = [];
|
const segments: Segment[] = [];
|
||||||
|
|
||||||
@@ -58,13 +59,9 @@ async function getSegmentsByVideoID(req: Request, videoID: string, categories: C
|
|||||||
categories = categories.filter((category) => !/[^a-z|_|-]/.test(category));
|
categories = categories.filter((category) => !/[^a-z|_|-]/.test(category));
|
||||||
if (categories.length === 0) return null;
|
if (categories.length === 0) return null;
|
||||||
|
|
||||||
const segmentsByCategory: SBRecord<Category, DBSegment[]> = (await db
|
const segmentsByCategory: SBRecord<Category, DBSegment[]> = (await getSegmentsFromDBByVideoID(videoID, service))
|
||||||
.prepare(
|
.filter((segment: DBSegment) => categories.includes(segment?.category))
|
||||||
'all',
|
.reduce((acc: SBRecord<Category, DBSegment[]>, segment: DBSegment) => {
|
||||||
`SELECT "startTime", "endTime", "votes", "locked", "UUID", "category", "videoDuration", "shadowHidden" FROM "sponsorTimes"
|
|
||||||
WHERE "videoID" = ? AND "category" IN (${categories.map((c) => "'" + c + "'")}) AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`,
|
|
||||||
[videoID, service]
|
|
||||||
)).reduce((acc: SBRecord<Category, DBSegment[]>, segment: DBSegment) => {
|
|
||||||
acc[segment.category] = acc[segment.category] || [];
|
acc[segment.category] = acc[segment.category] || [];
|
||||||
acc[segment.category].push(segment);
|
acc[segment.category].push(segment);
|
||||||
|
|
||||||
@@ -72,7 +69,7 @@ async function getSegmentsByVideoID(req: Request, videoID: string, categories: C
|
|||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
for (const [category, categorySegments] of Object.entries(segmentsByCategory)) {
|
for (const [category, categorySegments] of Object.entries(segmentsByCategory)) {
|
||||||
segments.push(...(await prepareCategorySegments(req, videoID as VideoID, category as Category, categorySegments, cache)));
|
segments.push(...(await prepareCategorySegments(req, videoID, category as Category, categorySegments, cache)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return segments;
|
return segments;
|
||||||
@@ -94,7 +91,7 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash,
|
|||||||
categories = categories.filter((category) => !(/[^a-z|_|-]/.test(category)));
|
categories = categories.filter((category) => !(/[^a-z|_|-]/.test(category)));
|
||||||
if (categories.length === 0) return null;
|
if (categories.length === 0) return null;
|
||||||
|
|
||||||
const segmentPerVideoID: SegmentWithHashPerVideoID = (await getSegmentsFromDB(hashedVideoIDPrefix, service))
|
const segmentPerVideoID: SegmentWithHashPerVideoID = (await getSegmentsFromDBByHash(hashedVideoIDPrefix, service))
|
||||||
.filter((segment: DBSegment) => categories.includes(segment?.category))
|
.filter((segment: DBSegment) => categories.includes(segment?.category))
|
||||||
.reduce((acc: SegmentWithHashPerVideoID, segment: DBSegment) => {
|
.reduce((acc: SegmentWithHashPerVideoID, segment: DBSegment) => {
|
||||||
acc[segment.videoID] = acc[segment.videoID] || {
|
acc[segment.videoID] = acc[segment.videoID] || {
|
||||||
@@ -129,7 +126,7 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSegmentsFromDB(hashedVideoIDPrefix: VideoIDHash, service: Service): Promise<DBSegment[]> {
|
async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service: Service): Promise<DBSegment[]> {
|
||||||
const fetchFromDB = () => db
|
const fetchFromDB = () => db
|
||||||
.prepare(
|
.prepare(
|
||||||
'all',
|
'all',
|
||||||
@@ -139,27 +136,42 @@ async function getSegmentsFromDB(hashedVideoIDPrefix: VideoIDHash, service: Serv
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (hashedVideoIDPrefix.length === 4) {
|
if (hashedVideoIDPrefix.length === 4) {
|
||||||
const key = skipSegmentsHashKey(hashedVideoIDPrefix, service);
|
return await getSegmentsFromDB(fetchFromDB, skipSegmentsHashKey(hashedVideoIDPrefix, service))
|
||||||
const {err, reply} = await redis.getAsync(key);
|
|
||||||
|
|
||||||
if (!err && reply) {
|
|
||||||
try {
|
|
||||||
Logger.debug("Got data from redis: " + reply);
|
|
||||||
return JSON.parse(reply);
|
|
||||||
} catch (e) {
|
|
||||||
// If all else, continue on to fetching from the database
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await fetchFromDB();
|
|
||||||
|
|
||||||
redis.setAsync(key, JSON.stringify(data));
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await fetchFromDB();
|
return await fetchFromDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): Promise<DBSegment[]> {
|
||||||
|
const fetchFromDB = () => db
|
||||||
|
.prepare(
|
||||||
|
'all',
|
||||||
|
`SELECT "startTime", "endTime", "votes", "locked", "UUID", "category", "videoDuration", "shadowHidden" FROM "sponsorTimes"
|
||||||
|
WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`,
|
||||||
|
[videoID, service]
|
||||||
|
);
|
||||||
|
|
||||||
|
return await getSegmentsFromDB(fetchFromDB, skipSegmentsKey(videoID, service))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSegmentsFromDB(fetchFromDB: () => Promise<DBSegment[]>, key: string): Promise<DBSegment[]> {
|
||||||
|
const {err, reply} = await redis.getAsync(key);
|
||||||
|
|
||||||
|
if (!err && reply) {
|
||||||
|
try {
|
||||||
|
Logger.debug("Got data from redis: " + reply);
|
||||||
|
return JSON.parse(reply);
|
||||||
|
} catch (e) {
|
||||||
|
// If all else, continue on to fetching from the database
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetchFromDB();
|
||||||
|
|
||||||
|
redis.setAsync(key, JSON.stringify(data));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
//gets a weighted random choice from the choices array based on their `votes` property.
|
//gets a weighted random choice from the choices array based on their `votes` property.
|
||||||
//amountOfChoices specifies the maximum amount of choices to return, 1 or more.
|
//amountOfChoices specifies the maximum amount of choices to return, 1 or more.
|
||||||
//choices are unique
|
//choices are unique
|
||||||
@@ -278,18 +290,6 @@ async function handleGetSegments(req: Request, res: Response): Promise<Segment[]
|
|||||||
service = Service.YouTube;
|
service = Service.YouTube;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only 404s are cached at the moment
|
|
||||||
const redisResult = await redis.getAsync(skipSegmentsKey(videoID));
|
|
||||||
|
|
||||||
if (redisResult.reply) {
|
|
||||||
const redisSegments = JSON.parse(redisResult.reply);
|
|
||||||
if (redisSegments?.length === 0) {
|
|
||||||
res.sendStatus(404);
|
|
||||||
Logger.debug("Using segments from cache for " + videoID);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const segments = await getSegmentsByVideoID(req, videoID, categories, service);
|
const segments = await getSegmentsByVideoID(req, videoID, categories, service);
|
||||||
|
|
||||||
if (segments === null || segments === undefined) {
|
if (segments === null || segments === undefined) {
|
||||||
@@ -300,9 +300,6 @@ async function handleGetSegments(req: Request, res: Response): Promise<Segment[]
|
|||||||
if (segments.length === 0) {
|
if (segments.length === 0) {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
|
|
||||||
// Save in cache
|
|
||||||
if (categories.length == 7) redis.setAsync(skipSegmentsKey(videoID), JSON.stringify(segments));
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ export async function postSkipSegments(req: Request, res: Response) {
|
|||||||
await privateDB.prepare('run', `INSERT INTO "sponsorTimes" VALUES(?, ?, ?)`, [videoID, hashedIP, timeSubmitted]);
|
await privateDB.prepare('run', `INSERT INTO "sponsorTimes" VALUES(?, ?, ?)`, [videoID, hashedIP, timeSubmitted]);
|
||||||
|
|
||||||
// Clear redis cache for this video
|
// Clear redis cache for this video
|
||||||
redis.delAsync(skipSegmentsKey(videoID));
|
redis.delAsync(skipSegmentsKey(videoID, service));
|
||||||
redis.delAsync(skipSegmentsHashKey(hashedVideoID, service));
|
redis.delAsync(skipSegmentsHashKey(hashedVideoID, service));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
//a DB change probably occurred
|
//a DB change probably occurred
|
||||||
|
|||||||
@@ -469,7 +469,7 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
|
|||||||
|
|
||||||
function clearRedisCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoIDHash; service: Service; }) {
|
function clearRedisCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoIDHash; service: Service; }) {
|
||||||
if (videoInfo) {
|
if (videoInfo) {
|
||||||
redis.delAsync(skipSegmentsKey(videoInfo.videoID));
|
redis.delAsync(skipSegmentsKey(videoInfo.videoID, videoInfo.service));
|
||||||
redis.delAsync(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service));
|
redis.delAsync(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user