diff --git a/index.js b/index.js index bbab982..ac9cb50 100644 --- a/index.js +++ b/index.js @@ -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, () => { diff --git a/src/api.js b/src/api.js index 67d937f..37673dd 100644 --- a/src/api.js +++ b/src/api.js @@ -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() diff --git a/src/cache-image-service.js b/src/cache-image-service.js new file mode 100644 index 0000000..c604796 --- /dev/null +++ b/src/cache-image-service.js @@ -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(); \ No newline at end of file diff --git a/src/cache-service.js b/src/cache-service.js new file mode 100644 index 0000000..a81e236 --- /dev/null +++ b/src/cache-service.js @@ -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(); \ No newline at end of file diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..396ea30 --- /dev/null +++ b/src/config.js @@ -0,0 +1,4 @@ +module.exports = { + DATABASE: process.env.DATABASE || './.dizquetv', + PORT: process.env.PORT || 8000, +}; \ No newline at end of file diff --git a/src/services/m3u-service.js b/src/services/m3u-service.js new file mode 100644 index 0000000..ab78064 --- /dev/null +++ b/src/services/m3u-service.js @@ -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; \ No newline at end of file diff --git a/src/services/settings-service.js b/src/services/settings-service.js new file mode 100644 index 0000000..1df45e8 --- /dev/null +++ b/src/services/settings-service.js @@ -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(); \ No newline at end of file diff --git a/src/tv-guide-service.js b/src/tv-guide-service.js index 9227827..3c40df9 100644 --- a/src/tv-guide-service.js +++ b/src/tv-guide-service.js @@ -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() { diff --git a/src/xmltv.js b/src/xmltv.js index 6d738b2..ac38e98 100644 --- a/src/xmltv.js +++ b/src/xmltv.js @@ -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 diff --git a/web/app.js b/web/app.js index af58820..18d161c 100644 --- a/web/app.js +++ b/web/app.js @@ -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')) diff --git a/web/directives/cache-settings.js b/web/directives/cache-settings.js new file mode 100644 index 0000000..d37fd99 --- /dev/null +++ b/web/directives/cache-settings.js @@ -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(); + }); + }; + } + } +} \ No newline at end of file diff --git a/web/public/templates/cache-settings.html b/web/public/templates/cache-settings.html new file mode 100644 index 0000000..376f8c9 --- /dev/null +++ b/web/public/templates/cache-settings.html @@ -0,0 +1,15 @@ +