Improve performance of segment querying endpoints

This commit is contained in:
opl-
2020-12-02 15:20:42 +01:00
parent 0002a7549d
commit 0bac7e8d90
3 changed files with 108 additions and 56 deletions

View File

@@ -8,51 +8,102 @@ var logger = require('../utils/logger.js');
var getHash = require('../utils/getHash.js'); var getHash = require('../utils/getHash.js');
var getIP = require('../utils/getIP.js'); var getIP = require('../utils/getIP.js');
function cleanGetSegments(req, videoID, categories) { function prepareCategorySegments(req, videoID, category, segments, cache = {shadowHiddenSegments: {}}) {
let userHashedIP, shadowHiddenSegments; const filteredSegments = segments.filter((segment) => {
if (segment.votes < -1) {
return false; //too untrustworthy, just ignore it
}
let segments = []; //check if shadowHidden
//this means it is hidden to everyone but the original ip that submitted it
if (segment.shadowHidden != 1) {
return true;
}
if (cache.shadowHiddenSegments[videoID] === undefined) {
cache.shadowHiddenSegments[videoID] = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]);
}
//if this isn't their ip, don't send it to them
return cache.shadowHiddenSegments[videoID].some((shadowHiddenSegment) => {
if (cache.userHashedIP === undefined) {
//hash the IP only if it's strictly necessary
cache.userHashedIP = getHash(getIP(req) + config.globalSalt);
}
return shadowHiddenSegment.hashedIP === cache.userHashedIP;
});
});
return chooseSegments(filteredSegments).map((chosenSegment) => ({
category,
segment: [chosenSegment.startTime, chosenSegment.endTime],
UUID: chosenSegment.UUID,
}));
}
function getSegmentsByVideoID(req, videoID, categories) {
const cache = {};
const segments = [];
try { try {
for (const category of categories) { const segmentsByCategory = db
const categorySegments = db .prepare(
.prepare( 'all',
'all', `SELECT startTime, endTime, votes, UUID, category, shadowHidden FROM sponsorTimes WHERE videoID = ? AND category IN (${Array(categories.length).fill('?').join()}) ORDER BY startTime`,
'SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime', [videoID, categories]
[videoID, category] ).reduce((acc, segment) => {
) acc[segment.category] = acc[segment.category] || [];
.filter(segment => { acc[segment.category].push(segment);
if (segment.votes < -1) {
return false; //too untrustworthy, just ignore it
}
//check if shadowHidden return acc;
//this means it is hidden to everyone but the original ip that submitted it }, {});
if (segment.shadowHidden != 1) {
return true;
}
if (shadowHiddenSegments === undefined) { for (const [category, categorySegments] of Object.entries(segmentsByCategory)) {
shadowHiddenSegments = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]); segments.push(...prepareCategorySegments(req, videoID, category, categorySegments, cache));
} }
//if this isn't their ip, don't send it to them return segments;
return shadowHiddenSegments.some(shadowHiddenSegment => { } catch (err) {
if (userHashedIP === undefined) { if (err) {
//hash the IP only if it's strictly necessary logger.error(err);
userHashedIP = getHash(getIP(req) + config.globalSalt); return null;
} }
return shadowHiddenSegment.hashedIP === userHashedIP; }
}); }
});
chooseSegments(categorySegments).forEach(chosenSegment => { function getSegmentsByHash(req, hashedVideoIDPrefix, categories) {
segments.push({ const cache = {};
category, const segments = {};
segment: [chosenSegment.startTime, chosenSegment.endTime],
UUID: chosenSegment.UUID, try {
}); const allSegments = db
}); .prepare(
'all',
`SELECT videoID, startTime, endTime, votes, UUID, category, shadowHidden, hashedVideoID FROM sponsorTimes WHERE hashedVideoID LIKE ? AND category IN (${Array(categories.length).fill('?').join()}) ORDER BY startTime`,
[hashedVideoIDPrefix + '%', categories]
).reduce((acc, segment) => {
acc[segment.videoID] = acc[segment.videoID] || {
hash: segment.hashedVideoID,
categories: {},
};
const videoCategories = acc[segment.videoID].categories;
videoCategories[segment.category] = videoCategories[segment.category] || [];
videoCategories[segment.category].push(segment);
return acc;
}, {});
for (const [videoID, videoData] of Object.entries(allSegments)) {
segments[videoID] = {
hash: videoData.hash,
segments: [],
};
for (const [category, categorySegments] of Object.entries(videoData.categories)) {
segments[videoID].segments.push(...prepareCategorySegments(req, videoID, category, categorySegments, cache));
}
} }
return segments; return segments;
@@ -160,14 +211,14 @@ function handleGetSegments(req, res) {
? [req.query.category] ? [req.query.category]
: ['sponsor']; : ['sponsor'];
let segments = cleanGetSegments(req, videoID, categories); const segments = getSegmentsByVideoID(req, videoID, categories);
if (segments === null || segments === undefined) { if (segments === null || segments === undefined) {
res.sendStatus(500); res.sendStatus(500);
return false; return false;
} }
if (segments.length == 0) { if (segments.length === 0) {
res.sendStatus(404); res.sendStatus(404);
return false; return false;
} }
@@ -177,7 +228,8 @@ function handleGetSegments(req, res) {
module.exports = { module.exports = {
handleGetSegments, handleGetSegments,
cleanGetSegments, getSegmentsByVideoID,
getSegmentsByHash,
endpoint: function (req, res) { endpoint: function (req, res) {
let segments = handleGetSegments(req, res); let segments = handleGetSegments(req, res);

View File

@@ -1,5 +1,5 @@
const hashPrefixTester = require('../utils/hashPrefixTester.js'); const hashPrefixTester = require('../utils/hashPrefixTester.js');
const getSegments = require('./getSkipSegments.js').cleanGetSegments; const getSegments = require('./getSkipSegments.js').getSegmentsByHash;
const databases = require('../databases/databases.js'); const databases = require('../databases/databases.js');
const logger = require('../utils/logger.js'); const logger = require('../utils/logger.js');
@@ -19,15 +19,15 @@ module.exports = async function (req, res) {
: ['sponsor']; : ['sponsor'];
// Get all video id's that match hash prefix // Get all video id's that match hash prefix
const videoIds = db.prepare('all', 'SELECT DISTINCT videoId, hashedVideoID from sponsorTimes WHERE hashedVideoID LIKE ?', [hashPrefix+'%']); const segments = getSegments(req, hashPrefix, categories);
let segments = videoIds.map((video) => { if (!segments) return res.status(404).json([]);
return {
videoID: video.videoID,
hash: video.hashedVideoID,
segments: getSegments(req, video.videoID, categories)
};
});
res.status((segments.length === 0) ? 404 : 200).json(segments); const output = Object.entries(segments).map(([videoID, data]) => ({
} videoID,
hash: data.hash,
segments: data.segments,
}));
res.status(output.length === 0 ? 404 : 200).json(output);
}

View File

@@ -24,17 +24,17 @@ describe('getSegmentsByHash', () => {
}); });
}); });
it('Should be able to get a 200 with empty segments for video but no matching categories', (done) => { it('Should return 404 if no segments are found even if a video for the given hash is known', (done) => {
request.get(utils.getbaseURL() request.get(utils.getbaseURL()
+ '/api/skipSegments/3272f?categories=["shilling"]', null, + '/api/skipSegments/3272f?categories=["shilling"]', null,
(err, res, body) => { (err, res, body) => {
if (err) done("Couldn't call endpoint"); if (err) done("Couldn't call endpoint");
else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); else if (res.statusCode !== 404) done("non 404 status code, was " + res.statusCode);
else { else {
if (JSON.parse(body) && JSON.parse(body).length > 0 && JSON.parse(body)[0].segments.length === 0) { if (body === '[]') {
done(); // pass done(); // pass
} else { } else {
done("response had segments"); done("response had videos");
} }
} }
}); });