[PR-174] - Icon caching support to reduce overhead on upstream providers.

This commit is contained in:
andyb2000 2012-10-07 12:32:51 +01:00 committed by Adam Sutton
parent 2b0e495b7b
commit acdc094fe7
15 changed files with 545 additions and 21 deletions

View file

@ -32,7 +32,7 @@ CFLAGS += -Wmissing-prototypes -fms-extensions
CFLAGS += -g -funsigned-char -O2
CFLAGS += -D_FILE_OFFSET_BITS=64
CFLAGS += -I${BUILDDIR} -I${CURDIR}/src -I${CURDIR}
LDFLAGS += -lrt -ldl -lpthread -lm
LDFLAGS += -lrt -ldl -lpthread -lm -lcurl
#
# Other config
@ -140,6 +140,8 @@ SRCS += src/muxer.c \
src/muxer_pass.c \
src/muxer_tvh.c \
SRCS += src/iconserve.c \
#
# Optional code
#

View file

@ -20,5 +20,19 @@
dvb-apps stores these in /usr/share/dvb/. Leave blank to use TVH's internal
file set.
<dt>Cache channel icons:
<dd>
Enable the caching of channel icons. This will cause TVH to download channel
icons locally, and then when requested via HTSP clients the URL for the icon
(or logo) will be retrieved from the TVH web server rather than fetched each
time by the HTSP client.
This REQUIRES the TVH server IP address field to also be populated
<dt>TVH Server IP Address:
<dd>
Enter the IP address of the TVH server used for HTSP clients. This permits the
above cache channel icons to work (TVH Clients are directed to the IP address
entered here to retrieve channel icons from)
</dl>
</div>

View file

@ -74,3 +74,50 @@ int config_set_muxconfpath ( const char *path )
}
return 0;
}
const char *config_get_iconserve ( void )
{
return htsmsg_get_str(config, "iconserve");
}
int config_set_iconserve ( const char *setting )
{
const char *c = config_get_iconserve();
if (!c || strcmp(c, setting)) {
if (c) htsmsg_delete_field(config, "iconserve");
htsmsg_add_str(config, "iconserve", setting);
return 1;
}
return 0;
}
const char *config_get_iconserve_periodicdownload ( void )
{
return htsmsg_get_str(config, "iconserve_periodicdownload");
}
int config_set_iconserve_periodicdownload ( const char *setting )
{
const char *c = config_get_iconserve_periodicdownload();
if (!c || strcmp(c, setting)) {
if (c) htsmsg_delete_field(config, "iconserve_periodicdownload");
htsmsg_add_str(config, "iconserve_periodicdownload", setting);
return 1;
}
return 0;
}
const char *config_get_serverip ( void )
{
return htsmsg_get_str(config, "serverip");
};
int config_set_serverip ( const char *setting )
{
const char *c = config_get_serverip();
if (!c || strcmp(c, setting)) {
if (c) htsmsg_delete_field(config, "serverip");
htsmsg_add_str(config, "serverip", setting);
return 1;
}
return 0;
};

View file

@ -32,6 +32,18 @@ const char *config_get_muxconfpath ( void );
int config_set_muxconfpath ( const char *str )
__attribute__((warn_unused_result));
const char *config_get_iconserve ( void );
int config_set_iconserve ( const char *str )
__attribute__((warn_unused_result));
const char *config_get_iconserve_periodicdownload ( void );
int config_set_iconserve_periodicdownload ( const char *str )
__attribute__((warn_unused_result));
const char *config_get_serverip ( void );
int config_set_serverip ( const char *str )
__attribute__((warn_unused_result));
const char *config_get_language ( void );
int config_set_language ( const char *str )
__attribute__((warn_unused_result));

View file

@ -42,6 +42,8 @@
#include "htsmsg_binary.h"
#include "epg.h"
#include "plumbing/tsfix.h"
#include "iconserve.h"
#include "config2.h"
#include <sys/statvfs.h>
#include "settings.h"
@ -447,8 +449,10 @@ htsp_build_channel(channel_t *ch, const char *method)
htsmsg_add_u32(out, "channelNumber", ch->ch_number);
htsmsg_add_str(out, "channelName", ch->ch_name);
if(ch->ch_icon != NULL)
htsmsg_add_str(out, "channelIcon", ch->ch_icon);
if(ch->ch_icon != NULL) {
htsmsg_add_str(out, "channelIcon", logo_query(ch->ch_id, ch->ch_icon));
};
now = ch->ch_epg_now;
next = ch->ch_epg_next;

View file

@ -173,18 +173,18 @@ http_send_header(http_connection_t *hc, int rc, const char *content,
tm = gmtime_r(&t, &tm0);
htsbuf_qprintf(&hdrs,
"Last-Modified: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
cachedays[tm->tm_wday], tm->tm_year + 1900,
cachemonths[tm->tm_mon], tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
"Last-Modified: %s, %d %s %02d %02d:%02d:%02d GMT\r\n",
cachedays[tm->tm_wday], tm->tm_mday,
cachemonths[tm->tm_mon], tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec);
t += maxage;
tm = gmtime_r(&t, &tm0);
htsbuf_qprintf(&hdrs,
"Expires: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
cachedays[tm->tm_wday], tm->tm_year + 1900,
cachemonths[tm->tm_mon], tm->tm_mday,
"Expires: %s, %d %s %02d %02d:%02d:%02d GMT\r\n",
cachedays[tm->tm_wday], tm->tm_mday,
cachemonths[tm->tm_mon], tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec);
htsbuf_qprintf(&hdrs, "Cache-Control: max-age=%d\r\n", maxage);

358
src/iconserve.c Normal file
View file

@ -0,0 +1,358 @@
/*
* Icon file server operations
* Copyright (C) 2012 Andy Brown
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define CURL_STATICLIB
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#include <curl/easy.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>
#include "settings.h"
#include "tvheadend.h"
#include "channels.h"
#include "http.h"
#include "webui/webui.h"
#include "filebundle.h"
#include "iconserve.h"
#include "config2.h"
#include "queue.h"
#include "spawn.h"
/* Queue, Cond to signal data and Mutex to protect it */
static TAILQ_HEAD(,iconserve_grab_queue) iconserve_queue;
static pthread_mutex_t iconserve_mutex;
static pthread_cond_t iconserve_cond;
/**
* https://github.com/andyb2000 Function to provide local icon files
*/
int
page_logo(http_connection_t *hc, const char *remain, void *opaque)
{
const char *homedir = hts_settings_get_root();
channel_t *ch = NULL;
char *inpath, *inpath2;
const char *outpath = "none";
char homepath[254];
char iconpath[100];
pthread_mutex_lock(&global_lock);
fb_file *fp;
ssize_t size;
char buf[4096];
char *mimetest_outbuf;
if(remain == NULL) {
pthread_mutex_unlock(&global_lock);
return 404;
};
if(strstr(remain, "..")) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
};
ch = channel_find_by_identifier(atoi(remain));
if (ch == NULL || ch->ch_icon == NULL) {
pthread_mutex_unlock(&global_lock);
return 404;
};
snprintf(homepath, sizeof(homepath), "%s/icons", homedir);
inpath = NULL;
inpath2 = NULL;
outpath = NULL;
/* split icon to last component */
inpath = strdup(ch->ch_icon);
inpath2 = strtok(inpath, "/");
while (inpath2 != NULL) {
inpath2 = strtok(NULL, "/");
if (inpath2 != NULL) {
outpath = strdup(inpath2);
};
};
snprintf(iconpath, sizeof(iconpath), "%s/%s", homepath, outpath);
fp = fb_open(iconpath, 1, 0);
if (!fp) {
tvhlog(LOG_DEBUG, "page_logo",
"failed to open %s redirecting to http link for icon (%s)",
iconpath, ch->ch_icon);
http_redirect(hc, ch->ch_icon);
iconserve_queue_add ( ch->ch_id, ch->ch_icon );
} else {
tvhlog(LOG_DEBUG, "page_logo", "File %s opened", iconpath);
size = fb_size(fp);
mimetest_outbuf = strdup("image/jpeg");
http_send_header(hc, 200, mimetest_outbuf, size, NULL, NULL, 300, 0, NULL);
while (!fb_eof(fp)) {
ssize_t c = fb_read(fp, buf, sizeof(buf));
if (c < 0) {
break;
};
if (write(hc->hc_fd, buf, c) != c) {
break;
};
};
fb_close(fp);
};
pthread_mutex_unlock(&global_lock);
return 0;
}
/*
* Logo loader functions, called from http htsp
* Will return local cache url instead of icon stored
*/
const char
*logo_query(int ch_id, const char *ch_icon)
{
const char *setting = config_get_iconserve();
const char *serverip = config_get_serverip();
char outiconpath[255];
char *return_icon = strdup(ch_icon);
if (!setting || !*setting || (strcmp(setting, "off") == 0)) {
return return_icon;
};
if (!serverip || !*serverip) {
return return_icon;
};
snprintf(outiconpath, sizeof(outiconpath),
"http://%s:%d/channellogo/%d", serverip, webui_port, ch_id);
return_icon = strdup(outiconpath);
return return_icon;
};
/*
* Icon grabber queue thread
*/
void *iconserve_thread ( void *aux )
{
iconserve_grab_queue_t *qe;
pthread_mutex_lock(&iconserve_mutex);
char *inpath, *inpath2;
const char *header_parse = NULL, *header_maxage = NULL;
const char *outpath = "none";
CURL *curl;
FILE *curl_fp, *curl_fp_header;
CURLcode res;
fb_file *fp;
char iconpath[100], iconpath_header[100];
char homepath[254];
const char *homedir = hts_settings_get_root();
struct stat fileStat;
int trigger_download = 0;
char buf[256];
int file = 0;
time_t seconds;
int dif, compare_seconds, rc;
const char *periodicdownload = config_get_iconserve_periodicdownload();
struct timespec timertrigger;
channel_t *ch;
tvhlog(LOG_INFO, "iconserve_thread", "Thread startup");
curl = curl_easy_init();
snprintf(homepath, sizeof(homepath), "%s/icons", homedir);
if(stat(homepath, &fileStat) == 0 || mkdir(homepath, 0700) == 0) {
if (curl) {
while (1) {
/* Get entry from queue */
qe = TAILQ_FIRST(&iconserve_queue);
/* Check for queue data */
if (!qe) { /* Queue Empty */
periodicdownload = config_get_iconserve_periodicdownload();
if (!periodicdownload || !*periodicdownload ||
(strcmp(periodicdownload, "off") == 0)) {
tvhlog(LOG_DEBUG, "iconserve_thread", "Non-timer wakeup");
rc = pthread_cond_wait(&iconserve_cond, &iconserve_mutex);
} else {
tvhlog(LOG_DEBUG, "iconserve_thread", "Timer wakeup set");
timertrigger.tv_sec = time(NULL) + 86400;
timertrigger.tv_nsec = 0;
rc = pthread_cond_timedwait(&iconserve_cond,
&iconserve_mutex, &timertrigger);
};
if (rc == ETIMEDOUT) {
tvhlog(LOG_INFO, "iconserve_thread", "Thread wakeup by timer");
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
if (ch->ch_icon != NULL) {
iconserve_grab_queue_t *qe = calloc(1, sizeof(iconserve_grab_queue_t));
qe->chan_number = ch->ch_id;
qe->icon_url = ch->ch_icon;
TAILQ_INSERT_TAIL(&iconserve_queue, qe, iconserve_link);
};
};
};
continue;
}
TAILQ_REMOVE(&iconserve_queue, qe, iconserve_link);
pthread_mutex_unlock(&iconserve_mutex);
inpath = NULL;
inpath2 = NULL;
outpath = NULL;
curl_fp = NULL;
/* split icon to last component */
inpath = strdup(qe->icon_url);
inpath2 = strtok(inpath, "/");
while (inpath2 != NULL) {
inpath2 = strtok(NULL, "/");
if (inpath2 != NULL)
outpath = strdup(inpath2);
};
if (outpath != NULL) {
snprintf(iconpath, sizeof(iconpath), "%s/%s", homepath, outpath);
snprintf(iconpath_header, sizeof(iconpath_header), "%s/%s.head",
homepath, outpath);
fp = fb_open(iconpath, 0, 1);
if (!fp) {
/* No file exists so grab immediately */
tvhlog(LOG_INFO, "logo_loader", "No logo, downloading file %s", outpath);
trigger_download = 1;
} else {
/* File exists so compare expiry times to re-grab */
fb_close(fp);
fp = fb_open(iconpath_header, 0, 0);
while (!fb_eof(fp)) {
memset(buf, 0, sizeof(buf));
if (!fb_gets(fp, buf, sizeof(buf) - 1)) break;
if (buf[strlen(buf) - 1] == '\n') {
buf[strlen(buf) - 1] = '\0';
};
if(strstr(buf, "Cache-Control: ")) {
header_parse = strtok(buf, "=");
header_parse = strtok ( NULL, "=");
header_maxage = strdup(header_parse);
};
};
fb_close(fp);
file=open(iconpath, O_RDONLY);
fstat(file,&fileStat);
seconds = time (NULL);
dif = difftime (seconds,fileStat.st_mtime);
compare_seconds=atoi(header_maxage);
if (dif > compare_seconds) {
tvhlog(LOG_DEBUG, "logo_loader", "Logo expired, downloading %s", outpath);
trigger_download = 1;
} else {
tvhlog(LOG_INFO, "logo_loader", "Logo not expired %s", outpath);
};
close(file);
};
if (trigger_download == 1) {
curl_fp=fopen(iconpath,"wb");
curl_fp_header=fopen(iconpath_header,"w");
curl_easy_setopt(curl, CURLOPT_URL, qe->icon_url);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_fp);
curl_easy_setopt(curl, CURLOPT_WRITEHEADER, curl_fp_header);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "TVHeadend");
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
res = curl_easy_perform(curl);
if (res == 0) {
tvhlog(LOG_INFO, "logo_loader", "Downloaded icon via curl (%s)",
qe->icon_url);
} else {
tvhlog(LOG_WARNING, "logo_loader", "Error with curl download (%s)",
qe->icon_url);
};
fclose(curl_fp);
fclose(curl_fp_header);
trigger_download = 0;
};
};
}; /* while loop */
curl_easy_cleanup(curl);
} else {
tvhlog(LOG_WARNING, "iconserve", "CURL cannot initialise");
};
};
return NULL;
};
/*
* Add data to the queue
*/
void iconserve_queue_add ( int chan_number, char *icon_url )
{
/* Create entry */
tvhlog(LOG_DEBUG, "iconserve_queue_add", "Adding chan_number to queue: %i",
chan_number);
iconserve_grab_queue_t *qe = calloc(1, sizeof(iconserve_grab_queue_t));
qe->chan_number = chan_number;
qe->icon_url = strdup(icon_url);
pthread_mutex_lock(&iconserve_mutex);
TAILQ_INSERT_TAIL(&iconserve_queue, qe, iconserve_link);
pthread_cond_signal(&iconserve_cond);
pthread_mutex_unlock(&iconserve_mutex);
}
/**
* Loader for icons, check config params and pull them in one go
*/
void
logo_loader(void)
{
channel_t *ch;
const char *setting = config_get_iconserve();
const char *serverip = config_get_serverip();
if (!setting || !*setting || (strcmp(setting, "off") == 0)) {
tvhlog(LOG_DEBUG, "logo_loader", "Disabled by config, skipping");
return;
};
if (!serverip || !*serverip) {
tvhlog(LOG_ALERT, "logo_loader", "No server IP, skipping icon cache");
return;
};
pthread_t tid;
pthread_mutex_init(&iconserve_mutex, NULL);
pthread_cond_init(&iconserve_cond, NULL);
TAILQ_INIT(&iconserve_queue);
/* Start thread - presumably permanently active */
pthread_create(&tid, NULL, iconserve_thread, NULL); // last param is passed as aux
// as this is single global
// you can probably use global
// vars
tvhlog(LOG_INFO, "logo_loader", "Caching logos locally");
/* loop through channels and load logo files */
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
if (ch->ch_icon != NULL) {
iconserve_queue_add ( ch->ch_id, ch->ch_icon );
};
};
pthread_mutex_lock(&iconserve_mutex);
pthread_cond_signal(&iconserve_cond); // tell thread data is available
pthread_mutex_unlock(&iconserve_mutex);
};

47
src/iconserve.h Normal file
View file

@ -0,0 +1,47 @@
/*
* Icon file serve operations
* Copyright (C) 2012 Andy Brown
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ICONSERVE_H
#define ICONSERVE_H
#include "http.h"
/* Struct of entries for icon grabbing
* FIELD: chan_number
* FIELD: icon url
*/
typedef struct iconserve_grab_queue
{
TAILQ_ENTRY(iconserve_grab_queue) iconserve_link;
int chan_number;
char *icon_url;
} iconserve_grab_queue_t;
int page_logo(http_connection_t *hc, const char *remain, void *opaque);
size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream);
void *iconserve_thread ( void *aux );
const char *logo_query(int ch_id, const char *ch_icon);
void iconserve_queue_add ( int chan_number, char *icon_url );
void logo_loader(void);
#endif /* ICONSERVE_H */

View file

@ -60,6 +60,7 @@
#include "ffdecsa/FFdecsa.h"
#include "muxes.h"
#include "config2.h"
#include "iconserve.h"
int running;
time_t dispatch_clock;
@ -488,6 +489,8 @@ main(int argc, char **argv)
http_server_init();
webui_init();
logo_loader();
serviceprobe_init();
#if ENABLE_CWC

View file

@ -47,6 +47,7 @@
#include "config2.h"
#include "lang_codes.h"
#include "subscriptions.h"
#include "iconserve.h"
/**
*
@ -858,7 +859,7 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
htsmsg_add_str(m, "channel", ch->ch_name);
htsmsg_add_u32(m, "channelid", ch->ch_id);
if(ch->ch_icon != NULL)
htsmsg_add_str(m, "chicon", ch->ch_icon);
htsmsg_add_str(m, "chicon", logo_query(ch->ch_id, ch->ch_icon));
if((s = epg_episode_get_title(ee, lang)))
htsmsg_add_str(m, "title", s);
@ -940,7 +941,7 @@ extjs_epgrelated(http_connection_t *hc, const char *remain, void *opaque)
m = htsmsg_create_map();
htsmsg_add_u32(m, "id", ebc->id);
if ( ch->ch_name ) htsmsg_add_str(m, "channel", ch->ch_name);
if ( ch->ch_icon ) htsmsg_add_str(m, "chicon", ch->ch_icon);
if ( ch->ch_icon ) htsmsg_add_str(m, "chicon", logo_query(ch->ch_id, ch->ch_icon));
htsmsg_add_u32(m, "start", ebc->start);
htsmsg_add_msg(array, NULL, m);
}
@ -1340,7 +1341,7 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque,
if(de->de_channel != NULL) {
htsmsg_add_str(m, "channel", de->de_channel->ch_name);
if(de->de_channel->ch_icon != NULL)
htsmsg_add_str(m, "chicon", de->de_channel->ch_icon);
htsmsg_add_str(m, "chicon", logo_query(de->de_channel->ch_id, de->de_channel->ch_icon));
}
htsmsg_add_str(m, "config_name", de->de_config_name);
@ -1930,7 +1931,23 @@ extjs_config(http_connection_t *hc, const char *remain, void *opaque)
save |= config_set_muxconfpath(str);
if ((str = http_arg_get(&hc->hc_req_args, "language")))
save |= config_set_language(str);
str = http_arg_get(&hc->hc_req_args, "iconserve");
if (str != NULL) {
save |= config_set_iconserve(str);
} else {
save |= config_set_iconserve("off");
};
str = http_arg_get(&hc->hc_req_args, "iconserve_periodicdownload");
if (str != NULL) {
save |= config_set_iconserve_periodicdownload(str);
} else {
save |= config_set_iconserve_periodicdownload("off");
};
if ((str = http_arg_get(&hc->hc_req_args, "serverip")))
save |= config_set_serverip(str);
if (save) config_save();
/* trigger the iconserve init routine */
logo_loader();
pthread_mutex_unlock(&global_lock);
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);

View file

@ -482,7 +482,5 @@ simpleui_start(void)
http_path_add("/simple.html", NULL, page_simple, ACCESS_SIMPLE);
http_path_add("/eventinfo", NULL, page_einfo, ACCESS_SIMPLE);
http_path_add("/pvrinfo", NULL, page_pvrinfo, ACCESS_SIMPLE);
http_path_add("/status.xml", NULL, page_status, ACCESS_SIMPLE);
http_path_add("/status.xml", NULL, page_status, ACCESS_SIMPLE);
}

View file

@ -30,6 +30,7 @@
#include "epg.h"
#include "psi.h"
#include "channels.h"
#include "iconserve.h"
#if ENABLE_LINUXDVB
#include "dvr/dvr.h"
#include "dvb/dvb.h"
@ -72,7 +73,7 @@ dumpchannels(htsbuf_queue_t *hq)
ch->ch_refcount,
ch->ch_zombie,
ch->ch_number,
ch->ch_icon ?: "<none set>");
logo_query(ch->ch_id, ch->ch_icon) ?: "<none set>");
}
}

View file

@ -37,7 +37,7 @@ tvheadend.miscconf = function() {
*/
var confreader = new Ext.data.JsonReader({
root : 'config'
}, [ 'muxconfpath', 'language' ]);
}, [ 'muxconfpath', 'language', 'iconserve', 'serverip' ]);
/* ****************************************************************
* Form Fields
@ -50,6 +50,22 @@ tvheadend.miscconf = function() {
width: 400
});
var iconServeConfig = new Ext.form.Checkbox({
name : 'iconserve',
fieldLabel : 'Cache channel icons'
});
var iconPeriodicDownload = new Ext.form.Checkbox({
name : 'iconserve_periodicdownload',
fieldLabel : 'Periodically check for updated icons'
});
var serveripConfig = new Ext.form.TextField({
fieldLabel : 'TVH Server IP address',
name : 'serverip',
allowBlank : true,
width: 150
});
var language = new Ext.ux.ItemSelector({
name: 'language',
fromStore: tvheadend.languages,
@ -95,7 +111,7 @@ tvheadend.miscconf = function() {
layout : 'form',
defaultType : 'textfield',
autoHeight : true,
items : [ language, dvbscanPath ],
items : [ language, dvbscanPath, iconServeConfig, iconPeriodicDownload, serveripConfig ],
tbar : [ saveButton, '->', helpButton ]
});

View file

@ -44,6 +44,7 @@
#include "muxer.h"
#include "dvb/dvb.h"
#include "dvb/dvb_support.h"
#include "iconserve.h"
/**
*
@ -95,7 +96,7 @@ page_root2(http_connection_t *hc, const char *remain, void *opaque)
/**
* Static download of a file from the filesystem
*/
static int
int
page_static_file(http_connection_t *hc, const char *remain, void *opaque)
{
int ret = 0;
@ -909,6 +910,8 @@ webui_init(void)
http_path_add("/stream", NULL, http_stream, ACCESS_STREAMING);
http_path_add("/channellogo", NULL, page_logo, ACCESS_ANONYMOUS);
webui_static_content("/static", "src/webui/static");
webui_static_content("/docs", "docs/html");
webui_static_content("/docresources", "docs/docresources");

View file

@ -20,6 +20,7 @@
#define WEBUI_H_
#include "htsmsg.h"
#include "http.h"
void webui_init(void);
@ -30,6 +31,8 @@ void extjs_start(void);
size_t html_escaped_len(const char *src);
const char* html_escape(char *dst, const char *src, size_t len);
int page_static_file(http_connection_t *hc, const char *remain, void *opaque);
#if ENABLE_LINUXDVB
void extjs_list_dvb_adapters(htsmsg_t *array);
void extjs_start_dvb(void);
@ -54,5 +57,4 @@ void comet_mailbox_add_message(htsmsg_t *m, int isdebug);
void comet_flush(void);
#endif /* WEBUI_H_ */