diff --git a/package.json b/package.json index 7da7b44d..641592be 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.67.2", + "version": "3.68.0", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/src/database.js b/src/database.js index a61fa131..76afc9ae 100644 --- a/src/database.js +++ b/src/database.js @@ -3,6 +3,7 @@ var tables = require("./database/tables"); import * as Metrics from './metrics/metrics'; import knex from 'knex'; import { GlobalBanDB } from './db/globalban'; +import { MetadataCacheDB } from './database/metadata_cache'; import { Summary, Counter } from 'prom-client'; const LOGGER = require('@calzoneman/jsli')('database'); @@ -84,6 +85,9 @@ module.exports.init = function (newDB) { .then(() => { require('./database/update').checkVersion(); module.exports.loadAnnouncement(); + require('cytube-mediaquery/lib/provider/youtube').setCache( + new MetadataCacheDB(db) + ); }).catch(error => { LOGGER.error(error.stack); process.exit(1); diff --git a/src/database/metadata_cache.js b/src/database/metadata_cache.js new file mode 100644 index 00000000..9c52d3e3 --- /dev/null +++ b/src/database/metadata_cache.js @@ -0,0 +1,69 @@ +import { createMySQLDuplicateKeyUpdate } from '../util/on-duplicate-key-update'; +const Switches = require('../switches'); + +const Media = require('cytube-mediaquery/lib/media'); + +// TODO: these fullname-vs-shortcode hacks really need to be abolished +function mediaquery2cytube(type) { + switch (type) { + case 'youtube': + return 'yt'; + default: + throw new Error(`mediaquery2cytube: no mapping for ${type}`); + } +} + +function cytube2mediaquery(type) { + switch (type) { + case 'yt': + return 'youtube'; + default: + throw new Error(`cytube2mediaquery: no mapping for ${type}`); + } +} + +class MetadataCacheDB { + constructor(db) { + this.db = db; + } + + async put(media) { + if (!Switches.isActive('ytCache')) return; + + media = new Media(media); + media.type = mediaquery2cytube(media.type); + return this.db.runTransaction(async tx => { + let insert = tx.table('media_metadata_cache') + .insert({ + id: media.id, + type: media.type, + metadata: JSON.stringify(media) + }); + let update = tx.raw(createMySQLDuplicateKeyUpdate( + ['metadata'] + )); + + return tx.raw(insert.toString() + update.toString()); + }); + } + + async get(id, type) { + if (!Switches.isActive('ytCache')) return null; + + return this.db.runTransaction(async tx => { + let row = await tx.table('media_metadata_cache') + .where({ id, type }) + .first(); + + if (row === undefined || row === null) { + return null; + } + + let metadata = JSON.parse(row.metadata); + metadata.type = cytube2mediaquery(metadata.type); + return new Media(metadata); + }); + } +} + +export { MetadataCacheDB }; diff --git a/src/database/tables.js b/src/database/tables.js index 320e283e..cc389dca 100644 --- a/src/database/tables.js +++ b/src/database/tables.js @@ -142,4 +142,17 @@ export async function initTables() { t.timestamps(/* useTimestamps */ true, /* defaultToNow */ true); t.index('created_at'); }); + + await ensureTable('media_metadata_cache', t => { + // The types of id and type are chosen for compatibility + // with the existing channel_libraries table. + // TODO in the future schema, revisit the ID layout for different media types. + t.string('id', 255).notNullable(); + t.string('type', 2).notNullable(); + t.text('metadata').notNullable(); + t.timestamps(/* useTimestamps */ true, /* defaultToNow */ true); + + t.primary(['type', 'id']); + t.index('updated_at'); + }); } diff --git a/src/switches.js b/src/switches.js index 8725e572..5b76c10b 100644 --- a/src/switches.js +++ b/src/switches.js @@ -1,5 +1,6 @@ const switches = { - plDirtyCheck: true + plDirtyCheck: true, + ytCache: true }; export function isActive(switchName) {