File cache system (#242)

* Create a File Cache Service and Channels M3U Cache

* Create a Cache Image Service for external images

* Singleton, db configurations and repairs
This commit is contained in:
Rafael Vieira Santos 2021-01-24 12:56:36 -03:00 committed by GitHub
parent 56d6ae3bde
commit c97ff8f24e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 688 additions and 45 deletions

View File

@ -10,6 +10,8 @@ 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 xmltv = require('./src/xmltv')
const Plex = require('./src/plex');
@ -51,22 +53,28 @@ if (!fs.existsSync(process.env.DATABASE)) {
if(!fs.existsSync(path.join(process.env.DATABASE, 'images'))) {
fs.mkdirSync(path.join(process.env.DATABASE, 'images'))
}
if(!fs.existsSync(path.join(process.env.DATABASE, 'channels'))) {
fs.mkdirSync(path.join(process.env.DATABASE, 'channels'))
}
if(!fs.existsSync(path.join(process.env.DATABASE, 'filler'))) {
fs.mkdirSync(path.join(process.env.DATABASE, 'filler'))
}
if(!fs.existsSync(path.join(process.env.DATABASE, 'cache'))) {
fs.mkdirSync(path.join(process.env.DATABASE, 'cache'))
}
if(!fs.existsSync(path.join(process.env.DATABASE, 'cache/images'))) {
fs.mkdirSync(path.join(process.env.DATABASE, 'cache/images'))
}
channelDB = new ChannelDB( path.join(process.env.DATABASE, 'channels') );
fillerDB = new FillerDB( path.join(process.env.DATABASE, 'filler') , channelDB, channelCache );
db.connect(process.env.DATABASE, ['channels', 'plex-servers', 'ffmpeg-settings', 'plex-settings', 'xmltv-settings', 'hdhr-settings', 'db-version', 'client-id'])
db.connect(process.env.DATABASE, ['channels', 'plex-servers', 'ffmpeg-settings', 'plex-settings', 'xmltv-settings', 'hdhr-settings', 'db-version', 'client-id', 'cache-images', 'settings'])
initDB(db, channelDB)
const guideService = new TVGuideService(xmltv, db);
@ -156,6 +164,7 @@ app.use(fileUpload({
createParentPath: true
}));
app.use(bodyParser.json({limit: '50mb'}))
app.get('/version.js', (req, res) => {
res.writeHead(200, {
'Content-Type': 'application/javascript'
@ -176,11 +185,17 @@ 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', 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(video.router( channelDB, fillerDB, db))
app.use(hdhr.router)
app.listen(process.env.PORT, () => {

View File

@ -1,6 +1,7 @@
const express = require('express')
const path = require('path')
const fs = require('fs')
const databaseMigration = require('./database-migration');
const channelCache = require('./channel-cache')
const constants = require('./constants');
@ -9,11 +10,13 @@ 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 ) {
let router = express.Router()
let plexServerDB = new PlexServerDB(channelDB, channelCache, db);
const m3uService = new M3uService(channelDB);
const router = express.Router()
const plexServerDB = new PlexServerDB(channelDB, channelCache, db);
router.get('/api/version', async (req, res) => {
try {
@ -150,7 +153,7 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
let channel = await channelCache.getChannelConfig(channelDB, number);
if (channel.length == 1) {
channel = channel[0];
res.send( {
res.send({
number: channel.number,
icon: channel.icon,
name: channel.name,
@ -176,6 +179,7 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
})
router.post('/api/channel', async (req, res) => {
try {
await m3uService.clearCache();
cleanUpChannel(req.body);
await channelDB.saveChannel( req.body.number, req.body );
channelCache.clear();
@ -188,6 +192,7 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
})
router.put('/api/channel', async (req, res) => {
try {
await m3uService.clearCache();
cleanUpChannel(req.body);
await channelDB.saveChannel( req.body.number, req.body );
channelCache.clear();
@ -200,6 +205,7 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
})
router.delete('/api/channel', async (req, res) => {
try {
await m3uService.clearCache();
await channelDB.deleteChannel( req.body.number );
channelCache.clear();
res.send( { number: req.body.number} )
@ -554,13 +560,19 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
// XMLTV.XML Download
router.get('/api/xmltv.xml', (req, res) => {
router.get('/api/xmltv.xml', async (req, res) => {
try {
const host = `${req.protocol}://${req.get('host')}`;
res.set('Cache-Control', 'no-store')
res.type('text')
let xmltvSettings = db['xmltv-settings'].find()[0]
let f = path.resolve(xmltvSettings.file);
res.sendFile(f)
res.type('application/xml');
res.attachment('xmltv.xml');
let xmltvSettings = db['xmltv-settings'].find()[0];
const fileContent = await fs.readFileSync(xmltvSettings.file, 'utf8');
const fileFinal = fileContent.replace(/\{\{host\}\}/g, host);
res.send(fileFinal);
} catch(err) {
console.error(err);
res.status(500).send("error");
@ -571,6 +583,7 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
//tool services
router.post('/api/channel-tools/time-slots', async (req, res) => {
try {
await m3uService.clearCache();
let toolRes = await timeSlotsService(req.body.programs, req.body.schedule);
if ( typeof(toolRes.userError) !=='undefined') {
return res.status(400).send(toolRes.userError);
@ -585,22 +598,13 @@ function api(db, channelDB, fillerDB, xmltvInterval, guideService ) {
// CHANNELS.M3U Download
router.get('/api/channels.m3u', async (req, res) => {
try {
res.type('text')
let channels = await channelDB.getAllChannels();
channels.sort((a, b) => { return a.number < b.number ? -1 : 1 })
let tvg = `${req.protocol}://${req.get('host')}/api/xmltv.xml`
var 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 += `${req.protocol}://${req.get('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="https://raw.githubusercontent.com/vexorian/dizquetv/main/resources/dizquetv.png" group-title="dizqueTV",dizqueTV\n`
data += `${req.protocol}://${req.get('host')}/setup\n`
}
res.send(data)
res.type('text');
const host = `${req.protocol}://${req.get('host')}`;
const data = await m3uService.getChannelList(host);
res.send(data);
} catch(err) {
console.error(err);
res.status(500).send("error");
@ -611,28 +615,22 @@ 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')
let channels = await channelDB.getAllChannels();
channels.sort((a, b) => { return a.number < b.number ? -1 : 1 })
var 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 += `${req.protocol}://${req.get('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="https://raw.githubusercontent.com/vexorian/dizquetv/main/resources/dizquetv.png" group-title="dizqueTV",dizqueTV\n`
data += `${req.protocol}://${req.get('host')}/setup\n`
}
res.send(data)
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()
xmltvInterval.restartInterval()

164
src/cache-image-service.js Normal file
View File

@ -0,0 +1,164 @@
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.
*
* @class CacheImageService
*/
class CacheImageService {
constructor() {
this.cacheService = CacheService;
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);
}
/**
* Router interceptor to download image and update cache before pass to express.static return this cached image.
*
* GET /:hash - Hash is a full external URL encoded in base64.
* eg.: http://{host}/cache/images/aHR0cHM6Ly8xO...cXVUbmFVNDZQWS1LWQ==
*
* @returns
* @memberof CacheImageService
*/
routerInterceptor() {
const router = express.Router();
router.get('/:hash', async (req, res, next) => {
try {
const hash = req.params.hash;
const imgItem = this.db.find({url: hash})[0];
if(imgItem) {
const file = await this.getImageFromCache(imgItem.url);
if(!file.length) {
const fileMimeType = await this.requestImageAndStore(Buffer.from(imgItem.url, 'base64').toString('ascii'), imgItem);
res.set('content-type', fileMimeType);
next();
} else {
res.set('content-type', imgItem.mimeType);
next();
}
}
} catch(err) {
console.error(err);
res.status(500).send("error");
}
});
return router;
}
/**
* Routers exported to use on express.use() function.
* Use on api routers, like `{host}/api/cache/images`
*
* `DELETE /` - Clear all files on .dizquetv/cache/images
*
* @returns {Router}
* @memberof CacheImageService
*/
apiRouters() {
const router = express.Router();
router.delete('/', async (req, res, next) => {
try {
await this.clearCache();
res.status(200).send({msg: 'Cache Image are Cleared'});
} catch (error) {
console.error(error);
res.status(500).send("error");
}
});
return router;
}
/**
*
*
* @param {*} url External URL to get file/image
* @param {*} dbFile register of file from db
* @returns {promise} `Resolve` when can download imagem and store on cache folder, `Reject` when file are inaccessible over network or can't write on directory
* @memberof CacheImageService
*/
async requestImageAndStore(url, dbFile) {
return new Promise( async(resolve, reject) => {
const requestConfiguration = {
method: 'get',
url
};
request(requestConfiguration, (err, res) => {
if (err) {
reject(err);
} else {
const mimeType = res.headers['content-type'];
this.db.update({_id: dbFile._id}, {url: dbFile.url, mimeType});
request(requestConfiguration)
.pipe(fs.createWriteStream(`${this.cacheService.cachePath}/${this.imageCacheFolder}/${dbFile.url}`))
.on('close', () =>{
resolve(mimeType);
});
}
});
});
}
/**
* Get image from cache using an filename
*
* @param {*} fileName
* @returns {promise} `Resolve` with file content
* @memberof CacheImageService
*/
getImageFromCache(fileName) {
return new Promise(async(resolve, reject) => {
try {
const file = await this.cacheService.getCache(`${this.imageCacheFolder}/${fileName}`);
resolve(file);
} catch (error) {
reject(error);
}
});
}
/**
* Clear all files on .dizquetv/cache/images
*
* @returns {promise}
* @memberof CacheImageService
*/
async clearCache() {
return new Promise( async(resolve, reject) => {
const cachePath = `${this.cacheService.cachePath}/${this.imageCacheFolder}`;
fs.rmdir(cachePath, { recursive: true }, (err) => {
if(err) {
reject();
}
fs.mkdirSync(cachePath);
resolve();
});
});
}
registerImageOnDatabase(imageUrl) {
const url = Buffer.from(imageUrl).toString('base64');
const dbQuery = {url};
if(!this.db.find(dbQuery)[0]) {
this.db.save(dbQuery);
}
return url;
}
}
module.exports = new CacheImageService();

94
src/cache-service.js Normal file
View File

@ -0,0 +1,94 @@
const path = require('path');
const fs = require('fs');
const config = require('./config');
/**
* A File Cache controller for store and retrieve files from disk
*
* @class CacheService
*/
class CacheService {
constructor() {
this.cachePath = path.join(config.DATABASE, 'cache');
this.cache = {};
}
/**
* `save` a file on cache folder
*
* @param {string} fullFilePath
* @param {*} data
* @returns {promise}
* @memberof CacheService
*/
setCache(fullFilePath, data) {
return new Promise((resolve, reject) => {
try {
const file = fs.createWriteStream(path.join(this.cachePath, fullFilePath));
file.write(data, (err) => {
if(err) {
throw Error("Can't save file: ", err);
} else {
this.cache[fullFilePath] = data;
resolve(true);
}
});
} catch (err) {
reject(err);
}
});
}
/**
* `get` a File from cache folder
*
* @param {string} fullFilePath
* @returns {promise} `Resolve` with file content, `Reject` with false
* @memberof CacheService
*/
getCache(fullFilePath) {
return new Promise((resolve, reject) => {
try {
if(fullFilePath in this.cache) {
resolve(this.cache[fullFilePath]);
} else {
fs.readFile(path.join(this.cachePath, fullFilePath), 'utf8', function (err,data) {
if (err) {
resolve(false);
}
resolve(data);
});
}
} catch (error) {
resolve(false);
throw Error("Can't get file", error)
}
});
}
/**
* `delete` a File from cache folder
*
* @param {string} fullFilePath
* @returns {promise}
* @memberof CacheService
*/
deleteCache(fullFilePath) {
return new Promise((resolve, reject) => {
try {
fs.unlinkSync(path.join(this.cachePath, fullFilePath), (err) => {
if(err) {
throw Error("Can't save file: ", err);
} else {
delete this.cache[fullFilePath];
resolve(true);
}
});
} catch (err) {
reject(err);
}
});
}
}
module.exports = new CacheService();

4
src/config.js Normal file
View File

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

161
src/services/m3u-service.js Normal file
View File

@ -0,0 +1,161 @@
const CacheService = require('../cache-service');
const SettingsService = require('./settings-service');
/**
* Manager and Generate M3U content
*
* @class M3uService
*/
class M3uService {
constructor(dataBase) {
this.dataBase = dataBase;
this.cacheService = CacheService;
SettingsService.saveSetting('enabled-cache-m3u', 'Enable Cache M3U', false);
}
/**
* Get the channel list in HLS or M3U
*
* @param {string} [type='m3u'] List type
* @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);
}
}
/**
* Build M3U with cache
*
* @param {string} host
* @returns {promise} M3U file content
* @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);
}
});
}
/**
*
* "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.
*
* @param {*} host
* @param {*} data
* @returns
* @memberof M3uService
*/
replaceHostOnM3u(host, data) {
return data.replace(/\{\{host\}\}/g, host);
}
/**
* Clear channels.m3u file from cache folder.
*
* @memberof M3uService
*/
async clearCache() {
await this.cacheService.deleteCache('channels.m3u');
await this.cacheService.deleteCache('channels-hls.m3u');
}
}
module.exports = M3uService;

View File

@ -0,0 +1,114 @@
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

@ -351,7 +351,7 @@ class TVGuideService
async refreshXML() {
let xmltvSettings = this.db['xmltv-settings'].find()[0];
await this.xmltv.WriteXMLTV(this.cached, xmltvSettings, async() => await this._throttle() );
await this.xmltv.WriteXMLTV(this.cached, xmltvSettings, async() => await this._throttle(), this.db);
}
async getStatus() {

View File

@ -1,12 +1,14 @@
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) {
if (isShutdown) {
return;
}
@ -112,7 +114,14 @@ async function _writeProgramme(channel, program, xw) {
// Icon
if (typeof program.icon !== 'undefined') {
xw.startElement('icon')
xw.writeAttribute('src', program.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.endElement()
}
// Desc

View File

@ -15,6 +15,7 @@ app.directive('plexSettings', require('./directives/plex-settings'))
app.directive('ffmpegSettings', require('./directives/ffmpeg-settings'))
app.directive('xmltvSettings', require('./directives/xmltv-settings'))
app.directive('hdhrSettings', require('./directives/hdhr-settings'))
app.directive('cacheSettings', require('./directives/cache-settings'))
app.directive('plexLibrary', require('./directives/plex-library'))
app.directive('programConfig', require('./directives/program-config'))
app.directive('flexConfig', require('./directives/flex-config'))

View File

@ -0,0 +1,23 @@
module.exports = function (dizquetv) {
return {
restrict: 'E',
templateUrl: 'templates/cache-settings.html',
replace: true,
scope: {
},
link: function (scope, element, attrs) {
dizquetv.getAllSettings().then((settings) => {
console.warn(settings);
scope.settings = settings;
scope.$apply();
});
scope.updateSetting = (setting) => {
const {key, value} = setting;
dizquetv.putSetting(key, !value).then((response) => {
scope.settings = response;
scope.$apply();
});
};
}
}
}

View File

@ -0,0 +1,15 @@
<div>
<h5>Cache</h5>
<div class="options col-sm-4">
<div class="option row" ng-repeat="setting in settings track by setting.key">
<div class="option__title col-sm-6">{{setting.title}}</div>
<div class="option__action col-sm-6">
<button class="pull-right btn btn-sm" ng-class="[{'btn-danger': setting.value, 'btn-success': !setting.value}]" ng-click="updateSetting(setting)">
{{(setting.value) ? 'Disable' : 'Enable'}}
</button>
</div>
</div>
</div>
</div>

View File

@ -20,10 +20,16 @@
HDHR
</span>
</li>
<li class="nav-item">
<span class="nav-link btn btn-link {{ selected === 'cache' ? 'active' : ''}}" ng-click="selected = 'cache'">
Cache
</span>
</li>
</ul>
<br />
<plex-settings ng-if="selected == 'plex'"></plex-settings>
<ffmpeg-settings ng-if="selected == 'ffmpeg'"></ffmpeg-settings>
<xmltv-settings ng-if="selected == 'xmltv'"></xmltv-settings>
<cache-settings ng-if="selected == 'cache'"></cache-settings>
<hdhr-settings ng-if="selected == 'hdhr'"></hdhr-settings>
</div>

View File

@ -1,4 +1,4 @@
module.exports = function ($http) {
module.exports = function ($http, $q) {
return {
getVersion: () => {
return $http.get('/api/version').then((d) => { return d.data })
@ -264,8 +264,47 @@ module.exports = function ($http) {
headers: { 'Content-Type': 'application/json; charset=utf-8' },
} );
return d.data;
}
},
/*======================================================================
* Settings
*/
getAllSettings: async () => {
var deferred = $q.defer();
$http({
method: "GET",
url : "/api/settings/cache",
headers: { 'Content-Type': 'application/json; charset=utf-8' },
}).then((response) => {
if(response.status === 200) {
deferred.resolve(response.data);
} else {
deferred.reject();
}
});
return deferred.promise;
},
putSetting: async (key, value) => {
console.warn(key, value);
var deferred = $q.defer();
$http({
method: "PUT",
url : `/api/settings/cache/${key}`,
data: {
value
},
headers: { 'Content-Type': 'application/json; charset=utf-8' },
}).then((response) => {
if(response.status === 200) {
deferred.resolve(response.data);
} else {
deferred.reject();
}
});
return deferred.promise;
}
}
}