Tweaks to caches

This commit is contained in:
vexorian 2021-01-24 16:23:34 -04:00
parent c97ff8f24e
commit 0fc689bc3e
12 changed files with 126 additions and 296 deletions

View File

@ -10,14 +10,15 @@ const api = require('./src/api')
const dbMigration = require('./src/database-migration');
const video = require('./src/video')
const HDHR = require('./src/hdhr')
const CacheImageService = require('./src/cache-image-service');
const SettingsService = require('./src/services/settings-service');
const FileCacheService = require('./src/services/file-cache-service');
const CacheImageService = require('./src/services/cache-image-service');
const xmltv = require('./src/xmltv')
const Plex = require('./src/plex');
const channelCache = require('./src/channel-cache');
const constants = require('./src/constants')
const ChannelDB = require("./src/dao/channel-db");
const M3uService = require("./src/services/m3u-service");
const FillerDB = require("./src/dao/filler-db");
const TVGuideService = require("./src/tv-guide-service");
const onShutdown = require("node-graceful-shutdown").onShutdown;
@ -72,10 +73,14 @@ fillerDB = new FillerDB( path.join(process.env.DATABASE, 'filler') , channelDB,
db.connect(process.env.DATABASE, ['channels', 'plex-servers', 'ffmpeg-settings', 'plex-settings', 'xmltv-settings', 'hdhr-settings', 'db-version', 'client-id', 'cache-images', 'settings'])
fileCache = new FileCacheService( path.join(process.env.DATABASE, 'cache') );
cacheImageService = new CacheImageService(db, fileCache);
m3uService = new M3uService(channelDB, fileCache, channelCache)
initDB(db, channelDB)
const guideService = new TVGuideService(xmltv, db);
const guideService = new TVGuideService(xmltv, db, cacheImageService);
@ -185,16 +190,15 @@ app.get('/version.js', (req, res) => {
app.use('/images', express.static(path.join(process.env.DATABASE, 'images')))
app.use(express.static(path.join(__dirname, 'web/public')))
app.use('/images', express.static(path.join(process.env.DATABASE, 'images')))
app.use('/cache/images', CacheImageService.routerInterceptor())
app.use('/cache/images', cacheImageService.routerInterceptor())
app.use('/cache/images', express.static(path.join(process.env.DATABASE, 'cache/images')))
app.use('/favicon.svg', express.static(
path.join(__dirname, 'resources/favicon.svg')
) );
// API Routers
app.use(api.router(db, channelDB, fillerDB, xmltvInterval, guideService ))
app.use('/api/cache/images', CacheImageService.apiRouters())
app.use('/api/settings/cache', SettingsService.apiRouters())
app.use(api.router(db, channelDB, fillerDB, xmltvInterval, guideService, m3uService ))
app.use('/api/cache/images', cacheImageService.apiRouters())
app.use(video.router( channelDB, fillerDB, db))
app.use(hdhr.router)

View File

@ -10,11 +10,10 @@ const PlexServerDB = require('./dao/plex-server-db');
const Plex = require("./plex.js");
const FillerDB = require('./dao/filler-db');
const timeSlotsService = require('./services/time-slots-service');
const M3uService = require('./services/m3u-service');
module.exports = { router: api }
function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
const m3uService = new M3uService(channelDB);
function api(db, channelDB, fillerDB, xmltvInterval, guideService, _m3uService ) {
const m3uService = _m3uService;
const router = express.Router()
const plexServerDB = new PlexServerDB(channelDB, channelCache, db);
@ -452,6 +451,7 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
_id: req.body._id,
cache: req.body.cache,
refresh: req.body.refresh,
enableImageCache: (req.body.enableImageCache === true),
file: xmltv.file,
}
);
@ -612,24 +612,6 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
})
// hls.m3u Download is not really working correctly right now
router.get('/api/hls.m3u', async (req, res) => {
try {
res.type('text');
const host = `${req.protocol}://${req.get('host')}`;
const data = await m3uService.getChannelList(host, 'hls');
res.send(data);
} catch(err) {
console.error(err);
res.status(500).send("error");
}
})
function updateXmltv() {
xmltvInterval.updateXML()

View File

@ -4,6 +4,7 @@ let cache = {};
let programPlayTimeCache = {};
let fillerPlayTimeCache = {};
let configCache = {};
let numbers = null;
async function getChannelConfig(channelDB, channelId) {
//with lazy-loading
@ -21,6 +22,22 @@ async function getChannelConfig(channelDB, channelId) {
return configCache[channelId];
}
async function getAllNumbers(channelDB) {
if (numbers === null) {
let n = channelDB.getAllChannelNumbers();
numbers = n;
}
return numbers;
}
async function getAllChannels(channelDB) {
let channelNumbers = await getAllNumbers(channelDB);
return await Promise.all( channelNumbers.map( async (x) => {
return (await getChannelConfig(channelDB, x))[0];
}) );
}
function saveChannelConfig(number, channel ) {
configCache[number] = [channel];
}
@ -127,6 +144,7 @@ function clear() {
//it's not necessary to clear the playback cache and it may be undesirable
configCache = {};
cache = {};
numbers = null;
}
module.exports = {
@ -134,6 +152,7 @@ module.exports = {
recordPlayback: recordPlayback,
clear: clear,
getProgramLastPlayTime: getProgramLastPlayTime,
getAllChannels: getAllChannels,
getChannelConfig: getChannelConfig,
saveChannelConfig: saveChannelConfig,
getFillerLastPlayTime: getFillerLastPlayTime,

View File

@ -1,4 +0,0 @@
module.exports = {
DATABASE: process.env.DATABASE || './.dizquetv',
PORT: process.env.PORT || 8000,
};

View File

@ -20,7 +20,7 @@
const path = require('path');
var fs = require('fs');
const TARGET_VERSION = 800;
const TARGET_VERSION = 801;
const STEPS = [
// [v, v2, x] : if the current version is v, call x(db), and version becomes v2
@ -40,6 +40,7 @@ const STEPS = [
// the addDeinterlaceFilter step. This 702 step no longer exists as a target
// but we have to migrate it to 800 using the reAddIcon.
[ 702, 800, (db,channels,dir) => reAddIcon(dir) ],
[ 800, 801, (db) => addImageCache(db) ],
]
const { v4: uuidv4 } = require('uuid');
@ -803,6 +804,14 @@ function addDeinterlaceFilter(db) {
fs.writeFileSync( f, JSON.stringify( [ffmpegSettings] ) );
}
function addImageCache(db) {
let xmltvSettings = db['xmltv-settings'].find()[0];
let f = path.join(process.env.DATABASE, 'xmltv-settings.json');
xmltvSettings.enableImageCache = false;
fs.writeFileSync( f, JSON.stringify( [xmltvSettings] ) );
}
module.exports = {
initDB: initDB,
defaultFFMPEG: ffmpeg,

View File

@ -1,11 +1,6 @@
const fs = require('fs');
const express = require('express');
const request = require('request');
const diskDb = require('diskdb');
const config = require('./config');
const CacheService = require('./cache-service');
const SettingsService = require('./services/settings-service');
/**
* Manager a cache in disk for external images.
@ -13,13 +8,10 @@ const SettingsService = require('./services/settings-service');
* @class CacheImageService
*/
class CacheImageService {
constructor() {
this.cacheService = CacheService;
constructor( db, fileCacheService ) {
this.cacheService = fileCacheService;
this.imageCacheFolder = 'images';
const connection = diskDb.connect(config.DATABASE, ['cache-images']);
this.db = connection['cache-images'];
SettingsService.saveSetting('enabled-cache-image', 'Enable Cache Image', false);
this.db = db['cache-images'];
}
/**
@ -161,4 +153,4 @@ class CacheImageService {
}
}
module.exports = new CacheImageService();
module.exports = CacheImageService;

View File

@ -1,15 +1,14 @@
const path = require('path');
const fs = require('fs');
const config = require('./config');
/**
* A File Cache controller for store and retrieve files from disk
* Store files in cache
*
* @class CacheService
* @class FileCacheService
*/
class CacheService {
constructor() {
this.cachePath = path.join(config.DATABASE, 'cache');
class FileCacheService {
constructor(cachePath) {
this.cachePath = cachePath;
this.cache = {};
}
@ -76,7 +75,11 @@ class CacheService {
deleteCache(fullFilePath) {
return new Promise((resolve, reject) => {
try {
fs.unlinkSync(path.join(this.cachePath, fullFilePath), (err) => {
let thePath = path.join(this.cachePath, fullFilePath);
if (! fs.existsSync(thePath)) {
return resolve(true);
}
fs.unlinkSync(thePath, (err) => {
if(err) {
throw Error("Can't save file: ", err);
} else {
@ -91,4 +94,4 @@ class CacheService {
}
}
module.exports = new CacheService();
module.exports = FileCacheService;

View File

@ -1,16 +1,14 @@
const CacheService = require('../cache-service');
const SettingsService = require('./settings-service');
/**
* Manager and Generate M3U content
*
* @class M3uService
*/
class M3uService {
constructor(dataBase) {
constructor(dataBase, fileCacheService, channelCache) {
this.dataBase = dataBase;
this.cacheService = CacheService;
SettingsService.saveSetting('enabled-cache-m3u', 'Enable Cache M3U', false);
this.cacheService = fileCacheService;
this.channelCache = channelCache;
this.cacheReady = false;
}
/**
@ -20,12 +18,8 @@ class M3uService {
* @returns {promise} Return a Promise with HLS or M3U file content
* @memberof M3uService
*/
getChannelList(host, type = 'm3u') {
if(type === 'hls') {
return this.buildHLSList(host);
} else {
return this.buildM3uList(host);
}
getChannelList(host) {
return this.buildM3uList(host);
}
/**
@ -36,105 +30,46 @@ class M3uService {
* @memberof M3uService
*/
buildM3uList(host) {
return new Promise(async (resolve, reject) => {
try {
const cache = SettingsService.getSetting('enabled-cache-m3u').value;
if(cache) {
const cacheChannels = await this.cacheService.getCache('channels.m3u');
if(cacheChannels) {
resolve(this.replaceHostOnM3u(host, cacheChannels));
}
}
let channels = await this.dataBase.getAllChannels();
channels.sort((a, b) => {
return a.number < b.number ? -1 : 1
});
const tvg = `{{host}}/api/xmltv.xml`;
let data = `#EXTM3U url-tvg="${tvg}" x-tvg-url="${tvg}"\n`;
for (var i = 0; i < channels.length; i++) {
if (channels[i].stealth!==true) {
data += `#EXTINF:0 tvg-id="${channels[i].number}" CUID="${channels[i].number}" tvg-chno="${channels[i].number}" tvg-name="${channels[i].name}" tvg-logo="${channels[i].icon}" group-title="dizqueTV",${channels[i].name}\n`
data += `{{host}}/video?channel=${channels[i].number}\n`
}
}
if (channels.length === 0) {
data += `#EXTINF:0 tvg-id="1" tvg-chno="1" tvg-name="dizqueTV" tvg-logo="{{host}}/resources/dizquetv.png" group-title="dizqueTV",dizqueTV\n`
data += `{{host}}/setup\n`
}
if(cache) {
await this.cacheService.setCache('channels.m3u', data);
}
resolve(this.replaceHostOnM3u(host, data));
} catch (error) {
reject(error);
async buildM3uList(host) {
if (this.cacheReady) {
const cachedM3U = await this.cacheService.getCache('channels.m3u');
if (cachedM3U) {
return this.replaceHostOnM3u(host, cachedM3U);
}
}
let channels = await this.channelCache.getAllChannels(this.dataBase);
channels.sort((a, b) => {
return a.number < b.number ? -1 : 1
});
const tvg = `{{host}}/api/xmltv.xml`;
let data = `#EXTM3U url-tvg="${tvg}" x-tvg-url="${tvg}"\n`;
for (var i = 0; i < channels.length; i++) {
if (channels[i].stealth !== true) {
data += `#EXTINF:0 tvg-id="${channels[i].number}" CUID="${channels[i].number}" tvg-chno="${channels[i].number}" tvg-name="${channels[i].name}" tvg-logo="${channels[i].icon}" group-title="dizqueTV",${channels[i].name}\n`
data += `{{host}}/video?channel=${channels[i].number}\n`
}
}
if (channels.length === 0) {
data += `#EXTINF:0 tvg-id="1" tvg-chno="1" tvg-name="dizqueTV" tvg-logo="{{host}}/resources/dizquetv.png" group-title="dizqueTV",dizqueTV\n`
data += `{{host}}/setup\n`
}
let saveCacheThread = async() => {
try {
await this.cacheService.setCache('channels.m3u', data);
this.cacheReady = true;
} catch(err) {
console.error(err);
}
};
saveCacheThread();
return this.replaceHostOnM3u(host, data);
}
/**
*
* "hls.m3u Download is not really working correctly right now"
*
* @param {boolean} [cache=true]
* @param {*} host
* @returns {promise} M3U file content
* @memberof M3uService
*/
buildHLSList(host, cache = true) {
return new Promise(async (resolve, reject) => {
try {
const cacheChannels = await this.cacheService.getCache('channels-hls.m3u');
if(cache && cacheChannels) {
resolve(this.replaceHostOnM3u(host, cacheChannels));
}
let channels = await this.dataBase.getAllChannels();
channels.sort((a, b) => {
return a.number < b.number ? -1 : 1
});
const tvg = `{{host}}/api/xmltv.xml`;
let data = "#EXTM3U\n"
for (var i = 0; i < channels.length; i++) {
data += `#EXTINF:0 tvg-id="${channels[i].number}" tvg-chno="${channels[i].number}" tvg-name="${channels[i].name}" tvg-logo="${channels[i].icon}" group-title="dizqueTV",${channels[i].name}\n`
data += `{{host}}/m3u8?channel=${channels[i].number}\n`
}
if (channels.length === 0) {
data += `#EXTINF:0 tvg-id="1" tvg-chno="1" tvg-name="dizqueTV" tvg-logo="{{host}}/resources/dizquetv.png" group-title="dizqueTV",dizqueTV\n`
data += `{{host}}/setup\n`
}
if(cache) {
await this.cacheService.setCache('channels-hls.m3u', data);
}
resolve(this.replaceHostOnM3u(host, data));
} catch (error) {
reject(error);
}
});
}
/**
* Replace {{host}} string with a URL on file contents.
*
@ -153,8 +88,7 @@ class M3uService {
* @memberof M3uService
*/
async clearCache() {
await this.cacheService.deleteCache('channels.m3u');
await this.cacheService.deleteCache('channels-hls.m3u');
this.cacheReady = false;
}
}

View File

@ -1,114 +0,0 @@
const diskDb = require('diskdb');
const express = require('express');
const config = require('../config');
class SettingsService {
constructor() {
const connection = diskDb.connect(config.DATABASE, ['settings']);
this.db = connection['settings'];
}
apiRouters() {
const router = express.Router();
router.get('/', async (req, res) => {
try {
const settings = await this.getAllSettings();
res.send(settings);
} catch (error) {
console.error(error);
res.status(500).send("error");
}
});
router.post('/', async (req, res) => {
try {
const {key, title, value} = req.body;
if(!key || !title || value === undefined) {
throw Error("Key, title and value are Required");
}
const settings = await this.saveSetting(key, title, value);
res.send(settings);
} catch (error) {
console.error(error);
res.status(500).send("error");
}
});
router.put('/:key', async (req, res) => {
try {
const key = req.params.key;
const {value} = req.body;
console.log(key, value);
if(!key || value === undefined) {
throw Error("Key and value are Required");
}
const settings = await this.updateSetting(key, value);
console.log(settings);
res.send(settings);
} catch (error) {
console.error(error);
res.status(500).send("error");
}
});
return router;
}
getSetting(key) {
return new Promise((resolve, reject) =>{
try {
const setting = this.db.find({key})[0];
resolve(setting);
} catch (error) {
reject(error);
}
});
}
getAllSettings() {
return new Promise((resolve, reject) =>{
try {
const settings = this.db.find();
resolve(settings);
} catch (error) {
reject(error);
}
});
}
saveSetting(key, title, value) {
return new Promise((resolve, reject) =>{
try {
const setting = this.db.find({key})[0];
if(!setting) {
this.db.save({key, title, value});
}
resolve(true);
} catch (error) {
reject(error);
}
});
}
updateSetting(key, value) {
return new Promise((resolve, reject) => {
try {
const setting = this.db.find({key})[0];
if(setting) {
const query = this.db.update({_id: setting._id}, {key, value});
if(query.updated > 0) {
const settings = this.db.find();
resolve(settings);
}
reject();
} else {
reject({error: true, msg: "Setting not found!"});
}
} catch (error) {
}
});
}
}
module.exports = new SettingsService();

View File

@ -7,7 +7,7 @@ class TVGuideService
/****
*
**/
constructor(xmltv, db) {
constructor(xmltv, db, cacheImageService) {
this.cached = null;
this.lastUpdate = 0;
this.updateTime = 0;
@ -18,6 +18,7 @@ class TVGuideService
this.doThrottle = false;
this.xmltv = xmltv;
this.db = db;
this.cacheImageService = cacheImageService;
}
async get() {
@ -351,7 +352,7 @@ class TVGuideService
async refreshXML() {
let xmltvSettings = this.db['xmltv-settings'].find()[0];
await this.xmltv.WriteXMLTV(this.cached, xmltvSettings, async() => await this._throttle(), this.db);
await this.xmltv.WriteXMLTV(this.cached, xmltvSettings, async() => await this._throttle(), this.cacheImageService);
}
async getStatus() {

View File

@ -1,14 +1,12 @@
const XMLWriter = require('xml-writer')
const fs = require('fs')
const CacheImageService = require('./cache-image-service');
const SettingsService = require('./services/settings-service');
module.exports = { WriteXMLTV: WriteXMLTV, shutdown: shutdown }
let isShutdown = false;
let isWorking = false;
async function WriteXMLTV(json, xmlSettings, throttle) {
async function WriteXMLTV(json, xmlSettings, throttle, cacheImageService) {
if (isShutdown) {
return;
}
@ -18,14 +16,14 @@ async function WriteXMLTV(json, xmlSettings, throttle) {
}
isWorking = true;
try {
await writePromise(json, xmlSettings, throttle);
await writePromise(json, xmlSettings, throttle, cacheImageService);
} catch (err) {
console.error("Error writing xmltv", err);
}
isWorking = false;
}
function writePromise(json, xmlSettings, throttle) {
function writePromise(json, xmlSettings, throttle, cacheImageService) {
return new Promise((resolve, reject) => {
let ws = fs.createWriteStream(xmlSettings.file)
let xw = new XMLWriter(true, (str, enc) => ws.write(str, enc))
@ -39,7 +37,7 @@ function writePromise(json, xmlSettings, throttle) {
_writeChannels( xw, channels );
for (let i = 0; i < channelNumbers.length; i++) {
let number = channelNumbers[i];
await _writePrograms(xw, json[number].channel, json[number].programs, throttle);
await _writePrograms(xw, json[number].channel, json[number].programs, throttle, xmlSettings, cacheImageService);
}
}
middle().then( () => {
@ -77,16 +75,16 @@ function _writeChannels(xw, channels) {
}
}
async function _writePrograms(xw, channel, programs, throttle) {
async function _writePrograms(xw, channel, programs, throttle, xmlSettings, cacheImageService) {
for (let i = 0; i < programs.length; i++) {
if (! isShutdown) {
await throttle();
}
await _writeProgramme(channel, programs[i], xw);
await _writeProgramme(channel, programs[i], xw, xmlSettings, cacheImageService);
}
}
async function _writeProgramme(channel, program, xw) {
async function _writeProgramme(channel, program, xw, xmlSettings, cacheImageService) {
// Programme
xw.startElement('programme')
xw.writeAttribute('start', _createXMLTVDate(program.start))
@ -113,16 +111,14 @@ async function _writeProgramme(channel, program, xw) {
}
// Icon
if (typeof program.icon !== 'undefined') {
xw.startElement('icon')
if(await SettingsService.getSetting('enabled-cache-image')){
const imgUrl = CacheImageService.registerImageOnDatabase(program.icon);
xw.writeAttribute('src', `{{host}}/cache/images/${imgUrl}`)
} else {
xw.writeAttribute('src', program.icon)
xw.startElement('icon');
let icon = program.icon;
if (xmlSettings.enableImageCache === true) {
const imgUrl = cacheImageService.registerImageOnDatabase(icon);
icon = `{{host}}/cache/images/${imgUrl}`;
}
xw.endElement()
xw.writeAttribute('src', icon);
xw.endElement();
}
// Desc
xw.startElement('desc')

View File

@ -14,7 +14,7 @@
<br/>
<div class="row">
<div class="col-sm-6">
<label>EPG Cache (hours)</label>
<label>EPG Hours</label>
<input type="number" class="form-control form-control-sm" ng-model="settings.cache" aria-describedby="cachehelp"/>
<small id="cachehelp" class="form-text text-muted">How many hours of programming to include in the xmltv file.</small>
</div>
@ -24,5 +24,13 @@
<small id="timerhelp" class="form-text text-muted">How often should the xmltv file be updated.</small>
</div>
</div>
<br />
<div class="form-check">
<input type="checkbox" class="form-check-input" id="imageCache" aria-describedby="imageCacheHelp" ng-model='settings.enableImageCache'>
<label class="form-check-label" for="stealth">Image Cache</label>
<div class='text-muted' id="imageCacheHelp">If enabled the pictures used for Movie and TV Show posters will be cached in dizqueTV's .dizqueTV folder and will be delivered by dizqueTV's server instead of requiring calls to Plex. Note that using fixed xmltv location in Plex (as opposed to url) will not work correctly in this case.</div>
</div>
</div>