From: Jaroslav Kysela Date: Wed, 6 Apr 2016 19:18:53 +0000 (+0200) Subject: mdhelp: allow to override (extend) property docs - example - DVR config X-Git-Tag: v4.2.1~711 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=957b835de449f57e7b0aa71a482342e22b4b1f2b;p=thirdparty%2Ftvheadend.git mdhelp: allow to override (extend) property docs - example - DVR config --- diff --git a/Makefile b/Makefile index 209699c49..cd45cb9a7 100644 --- a/Makefile +++ b/Makefile @@ -547,9 +547,11 @@ SRCS-yes += src/docs.c I18N-C += src/docs_inc.c I18N-DOCS = $(wildcard docs/markdown/*.md) I18N-DOCS += $(wildcard docs/class/*.md) +I18N-DOCS += $(wildcard docs/property/*.md) I18N-DOCS += $(wildcard docs/wizard/*.md) MD-ROOT = $(patsubst docs/markdown/%.md,%,$(wildcard docs/markdown/*.md)) MD-CLASS = $(patsubst docs/class/%.md,%,$(wildcard docs/class/*.md)) +MD-PROP = $(patsubst docs/property/%.md,%,$(wildcard docs/property/*.md)) MD-WIZARD = $(patsubst docs/wizard/%.md,%,$(wildcard docs/wizard/*.md)) # @@ -681,6 +683,11 @@ $(BUILDDIR)/docs-timestamp: $(I18N-DOCS) support/doc/md_to_c.py $(MD-TO-C) --in="docs/class/$${i}.md" \ --name="tvh_doc_$${i}_class" >> src/docs_inc.c || exit 1; \ done + @for i in $(MD-PROP); do \ + echo "Markdown: docs/property/$${i}.md"; \ + $(MD-TO-C) --in="docs/property/$${i}.md" \ + --name="tvh_doc_$${i}_property" >> src/docs_inc.c || exit 1; \ + done @for i in $(MD-WIZARD); do \ echo "Markdown: docs/wizard/$${i}.md"; \ $(MD-TO-C) --in="docs/wizard/$${i}.md" \ diff --git a/docs/property/postprocessor.md b/docs/property/postprocessor.md new file mode 100644 index 000000000..12f65a54b --- /dev/null +++ b/docs/property/postprocessor.md @@ -0,0 +1,32 @@ +: Command to run after finishing a recording. The command will be run in + background and is executed even if a recording is aborted or an error + occurred. Use the %e error formatting string to check for errors, the + error string is “OK” if recording finished successfully. + + Supported format strings: + + +Format | Description | Example value +:-----:| ----------------------------------------- | ------------- +`%f` | Full path to recording | /home/user/Videos/News.mkv +`%b` | Basename of recording | News.mkv +`%c` | Channel name | BBC world +`%O` | Owner of this recording | user +`%C` | Who created this recording | user +`%t` | Program title | News +`%s` | Program subtitle | Afternoon +`%p` | Program episode | S02.E07 +`%d` | Program description | News and stories… +`%e` | Error message | Aborted by user +`%S` | Start time stamp of recording, UNIX epoch | 1224421200 +`%E` | Stop time stamp of recording, UNIX epoch | 1224426600 +`%r` | Number of errors during recording | 0 +`%R` | Number of data errors during recording | 6 + +*Example usage* + +To use special characters (e.g. spaces), either put the string in quotes or +escape the individual characters. + +```/path/to/ffmpeg -i "%f" -vcodec libx264 -acodec copy "/path/with white space/%b"``` + diff --git a/src/dvr/dvr_config.c b/src/dvr/dvr_config.c index 245ebdbb2..256b6cd67 100644 --- a/src/dvr/dvr_config.c +++ b/src/dvr/dvr_config.c @@ -791,6 +791,13 @@ dvr_config_class_pathname_set(void *o, const void *v) return 0; } +static char * +dvr_config_prop_pathname_doc(const struct property *p, const char *lang) +{ + extern const char *tvh_doc_postprocessor_property[]; + return prop_md_doc(tvh_doc_postprocessor_property, lang); +} + extern const char *tvh_doc_dvrconfig_class[]; const idclass_t dvr_config_class = { @@ -897,7 +904,7 @@ const idclass_t dvr_config_class = { .off = offsetof(dvr_config_t, dvr_retention_days), .def.u32 = DVR_RET_ONREMOVE, .list = dvr_config_class_retention_list, - .opts = PO_EXPERT, + .opts = PO_EXPERT | PO_DOC_NLIST, .group = 1, }, { @@ -908,6 +915,7 @@ const idclass_t dvr_config_class = { .off = offsetof(dvr_config_t, dvr_removal_days), .def.u32 = DVR_RET_FOREVER, .list = dvr_config_class_removal_list, + .opts = PO_DOC_NLIST, .group = 1, }, { @@ -958,7 +966,7 @@ const idclass_t dvr_config_class = { "in the channel or DVR entry will be used."), .off = offsetof(dvr_config_t, dvr_extra_time_pre), .list = dvr_config_class_extra_list, - .opts = PO_ADVANCED, + .opts = PO_ADVANCED | PO_DOC_NLIST, .group = 1, }, { @@ -969,7 +977,7 @@ const idclass_t dvr_config_class = { "stop time."), .off = offsetof(dvr_config_t, dvr_extra_time_post), .list = dvr_config_class_extra_list, - .opts = PO_ADVANCED, + .opts = PO_ADVANCED | PO_DOC_NLIST, .group = 1, }, { @@ -981,7 +989,7 @@ const idclass_t dvr_config_class = { .off = offsetof(dvr_config_t, dvr_update_window), .list = dvr_config_entry_class_update_window_list, .def.u32 = 24*3600, - .opts = PO_EXPERT, + .opts = PO_EXPERT | PO_DOC_NLIST, .group = 1, }, { @@ -1125,6 +1133,7 @@ const idclass_t dvr_config_class = { .desc = N_("The string allows you to manually specify the " "full path generation using predefined " "modifiers. See Help for full details."), + .doc = dvr_config_prop_pathname_doc, .set = dvr_config_class_pathname_set, .off = offsetof(dvr_config_t, dvr_pathname), .opts = PO_EXPERT, diff --git a/src/idnode.h b/src/idnode.h index 70a83cadd..d0481e3cf 100644 --- a/src/idnode.h +++ b/src/idnode.h @@ -231,8 +231,9 @@ void idnode_read0 (idnode_t *self, htsmsg_t *m, htsmsg_t *list, int optmas int idnode_write0 (idnode_t *self, htsmsg_t *m, int optmask, int dosave); void idnode_save_check (idnode_t *self, int weak); -#define idclass_serialize(idc, lang) idclass_serialize0(idc, NULL, 0, lang) -#define idnode_serialize(in, lang) idnode_serialize0(in, NULL, 0, lang) +#define idclass_serialize(idc, lang) idclass_serialize0(idc, NULL, 0, lang) +#define idclass_serializedoc(idc, lang) idclass_serialize0(idc, NULL, PO_DOC, lang) +#define idnode_serialize(in, lang) idnode_serialize0(in, NULL, 0, lang) #define idnode_load(in, m) idnode_write0(in, m, PO_NOSAVE, 0) #define idnode_save(in, m) idnode_read0(in, m, NULL, PO_NOSAVE | PO_USERAW, NULL) #define idnode_update(in, m) idnode_write0(in, m, PO_RDONLY | PO_WRONCE, 1) diff --git a/src/prop.c b/src/prop.c index 75bb3ca38..484a753dd 100644 --- a/src/prop.c +++ b/src/prop.c @@ -422,6 +422,13 @@ prop_serialize_value /* Metadata */ htsmsg_add_str(m, "caption", tvh_gettext_lang(lang, pl->name)); + if ((optmask & PO_DOC) && pl->doc) { + char *s = pl->doc(pl, lang); + if (s) { + htsmsg_add_str(m, "doc", s); + free(s); + } + } if (pl->desc) htsmsg_add_str(m, "description", tvh_gettext_lang(lang, pl->desc)); if (pl->islist) { @@ -498,6 +505,8 @@ prop_serialize_value htsmsg_add_bool(m, "multiline", 1); if (opts & PO_PERSIST) htsmsg_add_bool(m, "persistent", 1); + if ((optmask & PO_DOC) && (opts & PO_DOC_NLIST)) + htsmsg_add_bool(m, "doc_nlist", 1); /* Enum list */ if (pl->list) { @@ -567,6 +576,35 @@ prop_serialize } } +/** + * + */ +char * +prop_md_doc(const char **doc, const char *lang) +{ + const char *s; + char *r = NULL; + size_t l = 0; + + for (; *doc; doc++) { + if (*doc[0] == '\xff') { + s = tvh_gettext_lang(lang, *doc + 1); + } else { + s = *doc; + } + if (r == NULL) { + r = strdup(s); + l = strlen(s); + } else { + l += strlen(s) + 1; + r = realloc(r, l); + strcat(r, s); + } + } + return r; +} + + /****************************************************************************** * Editor Configuration * diff --git a/src/prop.h b/src/prop.h index 86b601cfe..1bed0fc4b 100644 --- a/src/prop.h +++ b/src/prop.h @@ -63,6 +63,8 @@ typedef enum { #define PO_LORDER (1<<15) // Manage order in lists #define PO_MULTILINE (1<<16) // Multiline string #define PO_PERSIST (1<<17) // Persistent value (return back on save) +#define PO_DOC (1<<18) // Use doc callback instead description if exists +#define PO_DOC_NLIST (1<<19) // Do not show list in doc /* * min/max/step helpers @@ -114,6 +116,9 @@ typedef struct property { /* Extended options */ uint32_t (*get_opts) (void *ptr); + /* Documentation callback */ + char *(*doc) ( const struct property *prop, const char *lang ); + /* Notification callback */ void (*notify) (void *ptr, const char *lang); @@ -144,6 +149,9 @@ static inline int64_t prop_intsplit_from_str(const char *s, int64_t intsplit) return s64; } +char * +prop_md_doc(const char **md, const char *lang); + #endif /* __TVH_PROP_H__ */ /****************************************************************************** diff --git a/src/webui/doc_md.c b/src/webui/doc_md.c index 4c4b14dfc..add004842 100644 --- a/src/webui/doc_md.c +++ b/src/webui/doc_md.c @@ -140,7 +140,7 @@ http_markdown_class(http_connection_t *hc, const char *clazz) return HTTP_STATUS_NOT_FOUND; } doc = ic->ic_doc; - m = idclass_serialize(ic, lang); + m = idclass_serializedoc(ic, lang); pthread_mutex_unlock(&global_lock); s = htsmsg_get_str(m, "caption"); if (s) { @@ -186,22 +186,29 @@ http_markdown_class(http_connection_t *hc, const char *clazz) md_text(hq, ": ", " ", s); md_nl(hq, 1); } - e = htsmsg_get_list(n, "enum"); - if (e) { - HTSMSG_FOREACH(f2, e) { - x = htsmsg_field_get_map(f2); - if (x) { - s = htsmsg_get_str(x, "val"); - } else { - s = htsmsg_field_get_string(f2); - } - if (s) { - md_nl(hq, 1); - htsbuf_append(hq, " * ", 4); - md_style(hq, "**", s); + s = htsmsg_get_str(n, "doc"); + if (s) { + htsbuf_append_str(hq, s); + md_nl(hq, 1); + } + if (!htsmsg_get_bool_or_default(n, "doc_nlist", 0)) { + e = htsmsg_get_list(n, "enum"); + if (e) { + HTSMSG_FOREACH(f2, e) { + x = htsmsg_field_get_map(f2); + if (x) { + s = htsmsg_get_str(x, "val"); + } else { + s = htsmsg_field_get_string(f2); + } + if (s) { + md_nl(hq, 1); + htsbuf_append(hq, " * ", 4); + md_style(hq, "**", s); + } } + md_nl(hq, 1); } - md_nl(hq, 1); } } htsmsg_destroy(m); diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index 0cf4bc804..341e861ba 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -320,7 +320,7 @@ tvheadend.dvr_upcoming = function(panel, index) { selected: selected, beforeedit: beforeedit, help: function() { - new tvheadend.help(_('DVR - Upcoming/Current Recordings'), 'dvr_upcoming.html'); + new tvheadend.mdhelp('class/dvrentry'); } }); @@ -461,7 +461,7 @@ tvheadend.dvr_finished = function(panel, index) { tbar: [downloadButton, rerecordButton, moveButton], selected: selected, help: function() { - new tvheadend.help(_('DVR - Finished Recordings'), 'dvr_finished.html'); + new tvheadend.mdhelp('class/dvrentry'); } }); @@ -602,7 +602,7 @@ tvheadend.dvr_failed = function(panel, index) { tbar: [downloadButton, rerecordButton, moveButton], selected: selected, help: function() { - new tvheadend.help(_('DVR - Failed Recordings'), 'dvr_failed.html'); + new tvheadend.mdhelp('class/dvrentry'); } }); @@ -628,7 +628,7 @@ tvheadend.dvr_settings = function(panel, index) { }, del: true, help: function() { - new tvheadend.help(_('DVR'), 'config_dvr.html'); + new tvheadend.mdhelp('class/dvrconfig'); } }); @@ -696,7 +696,7 @@ tvheadend.autorec_editor = function(panel, index) { direction: 'ASC' }, help: function() { - new tvheadend.help(_('DVR Autorec'), 'dvr_autorec.html'); + new tvheadend.mdhelp('class/dvrautorec'); } }); @@ -749,7 +749,7 @@ tvheadend.timerec_editor = function(panel, index) { direction: 'ASC' }, help: function() { - new tvheadend.help(_('DVR Timers'), 'dvr_timerec.html'); + new tvheadend.mdhelp('class/dvrtimerec'); } });