}
}
+static void
+config_migrate_v18 ( void )
+{
+ htsmsg_t *c, *e, *l, *m;
+ htsmsg_field_t *f;
+ const char *filename;
+
+ if ((c = hts_settings_load("dvr/log")) != NULL) {
+ HTSMSG_FOREACH(f, c) {
+ if (!(e = htsmsg_field_get_map(f))) continue;
+ if ((filename = htsmsg_get_str(e, "filename")) == NULL)
+ continue;
+ if ((l = htsmsg_get_list(e, "files")) != NULL)
+ continue;
+ l = htsmsg_create_list();
+ m = htsmsg_create_map();
+ htsmsg_add_str(m, "filename", filename);
+ htsmsg_add_msg(l, NULL, m);
+ htsmsg_delete_field(e, "filename");
+ htsmsg_add_msg(e, "files", l);
+ hts_settings_save(e, "dvr/log/%s", f->hmf_name);
+ }
+ }
+}
+
/*
* Perform backup
*/
config_migrate_v14,
config_migrate_v15,
config_migrate_v16,
- config_migrate_v17
+ config_migrate_v17,
+ config_migrate_v18,
};
/*
char *de_owner;
char *de_creator;
char *de_comment;
- char *de_filename; /* Initially null if no filename has been
- generated yet */
+ htsmsg_t *de_files; /* List of all used files */
char *de_directory; /* Can be set for autorec entries, will override any
directory setting from the configuration */
lang_str_t *de_title; /* Title in UTF-8 (from EPG) */
dvr_entry_t *dvr_entry_find_by_episode(epg_broadcast_t *e);
+const char *dvr_get_filename(dvr_entry_t *de);
+
int64_t dvr_get_filesize(dvr_entry_t *de);
dvr_entry_t *dvr_entry_cancel(dvr_entry_t *de);
{
int i;
char *path, *sptr;
+ const char *filename;
dvr_cutpoint_list_t *cuts;
/* Check this is a valid recording */
assert(de != NULL);
- if (de->de_filename == NULL)
+ filename = dvr_get_filename(de);
+ if (filename == NULL)
return NULL;
/* Allocate list space */
/* Get base filename */
// TODO: harcoded 3 for max extension
- path = alloca(strlen(de->de_filename) + 3);
- strcpy(path, de->de_filename);
+ path = alloca(strlen(filename) + 3);
+ strcpy(path, filename);
sptr = strrchr(path, '.');
if (!sptr) {
free(cuts);
if(now >= stop || de->de_dont_reschedule) {
- if(de->de_filename == NULL)
+ if(htsmsg_is_empty(de->de_files))
dvr_entry_assign_sched_state(de, DVR_MISSED_TIME);
else
_dvr_entry_completed(de);
{
dvr_entry_t *de, *de2;
int64_t start, stop;
+ htsmsg_t *m;
const char *s;
if (conf) {
idnode_load(&de->de_id, conf);
- /* special case, becaous PO_NOSAVE, load ignores it */
+ /* filenames */
+ m = htsmsg_get_list(conf, "files");
+ if (m)
+ de->de_files = htsmsg_copy(m);
+
+ /* special case, because PO_NOSAVE, load ignores it */
if (de->de_title == NULL &&
(s = htsmsg_get_str(conf, "disp_title")) != NULL)
dvr_entry_class_disp_title_set(de, s);
- /* special case, becaous PO_NOSAVE, load ignores it */
+ /* special case, because PO_NOSAVE, load ignores it */
if (de->de_subtitle == NULL &&
(s = htsmsg_get_str(conf, "disp_subtitle")) != NULL)
dvr_entry_class_disp_subtitle_set(de, s);
if(de->de_config != NULL)
LIST_REMOVE(de, de_config_link);
- free(de->de_filename);
+ htsmsg_destroy(de->de_files);
free(de->de_owner);
free(de->de_creator);
free(de->de_comment);
lock_assert(&global_lock);
idnode_save(&de->de_id, m);
+ if (de->de_files)
+ htsmsg_add_msg(m, "files", htsmsg_copy(de->de_files));
hts_settings_save(m, "dvr/log/%s", idnode_uuid_as_str(&de->de_id));
htsmsg_destroy(m);
}
{
if (de->de_rec_state == DVR_RS_PENDING ||
de->de_rec_state == DVR_RS_WAIT_PROGRAM_START ||
- de->de_filename == NULL)
+ htsmsg_is_empty(de->de_files))
dvr_entry_assign_sched_state(de, DVR_MISSED_TIME);
else
_dvr_entry_completed(de);
return NULL;
}
+static const void *
+dvr_entry_class_filename_get(void *o)
+{
+ static const char *ret;
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ const char *s = dvr_get_filename(de);
+ ret = s ?: "";
+ return &ret;
+}
+
static int
dvr_entry_class_channel_set(void *o, const void *v)
{
.type = PT_STR,
.id = "filename",
.name = "Filename",
- .off = offsetof(dvr_entry_t, de_filename),
- .opts = PO_RDONLY,
+ .get = dvr_entry_class_filename_get,
+ .opts = PO_RDONLY | PO_NOSAVE,
},
{
.type = PT_STR,
}
}
+/**
+ *
+ */
+const char *
+dvr_get_filename(dvr_entry_t *de)
+{
+ htsmsg_field_t *f;
+ htsmsg_t *m;
+ const char *s;
+ if ((f = htsmsg_field_last(de->de_files)) != NULL &&
+ (m = htsmsg_field_get_map(f)) != NULL &&
+ (s = htsmsg_get_str(m, "filename")) != NULL)
+ return s;
+ else
+ return NULL;
+}
+
/**
*
*/
int64_t
dvr_get_filesize(dvr_entry_t *de)
{
+ const char *filename;
struct stat st;
- if(de->de_filename == NULL)
+ filename = dvr_get_filename(de);
+
+ if(filename == NULL)
return -1;
- if(stat(de->de_filename, &st) != 0)
+ if(stat(filename, &st) != 0)
return -1;
return st.st_size;
}
-
-
/**
*
*/
dvr_entry_delete(dvr_entry_t *de)
{
dvr_config_t *cfg = de->de_config;
+ htsmsg_t *m;
+ htsmsg_field_t *f;
time_t t;
struct tm tm;
+ const char *filename;
char tbuf[64], *rdir;
int r;
de->de_creator ?: "",
dvr_entry_get_retention(de));
- if(de->de_filename != NULL) {
+ if(!htsmsg_is_empty(de->de_files)) {
#if ENABLE_INOTIFY
dvr_inotify_del(de);
#endif
if(cfg->dvr_title_dir || cfg->dvr_channel_dir || cfg->dvr_dir_per_day || de->de_directory)
rdir = cfg->dvr_storage;
- r = deferred_unlink(de->de_filename, rdir);
- if(r && r != -ENOENT)
- tvhlog(LOG_WARNING, "dvr", "Unable to remove file '%s' from disk -- %s",
- de->de_filename, strerror(-errno));
+ HTSMSG_FOREACH(f, de->de_files) {
+ m = htsmsg_field_get_map(f);
+ if (m == NULL) continue;
+ filename = htsmsg_get_str(m, "filename");
+ r = deferred_unlink(filename, rdir);
+ if(r && r != -ENOENT)
+ tvhlog(LOG_WARNING, "dvr", "Unable to remove file '%s' from disk -- %s",
+ filename, strerror(-errno));
+ }
}
dvr_entry_destroy(de, 1);
}
void dvr_inotify_add ( dvr_entry_t *de )
{
dvr_inotify_entry_t *e;
+ const char *filename = dvr_get_filename(de);
char *path;
if (_inot_fd < 0)
return;
- if (!de->de_filename || de->de_filename[0] == '\0')
+ if (filename == NULL)
return;
- path = strdup(de->de_filename);
+ path = strdup(filename);
SKEL_ALLOC(dvr_inotify_entry_skel);
dvr_inotify_entry_skel->path = dirname(path);
return e;
}
-/*
- * Find DVR entry
- */
-static dvr_entry_t *
-_dvr_inotify_find2
- ( dvr_inotify_entry_t *die, const char *name )
-{
- dvr_entry_t *de = NULL;
- char path[512];
-
- snprintf(path, sizeof(path), "%s/%s", die->path, name);
-
- LIST_FOREACH(de, &die->entries, de_inotify_link)
- if (de->de_filename && !strcmp(path, de->de_filename))
- break;
-
- return de;
-}
-
/*
* File moved
*/
{
dvr_inotify_entry_t *die;
dvr_entry_t *de;
+ char path[PATH_MAX];
+ const char *filename;
+ htsmsg_t *m = NULL;
+ htsmsg_field_t *f = NULL;
if (!(die = _dvr_inotify_find(fd)))
return;
- if (!(de = _dvr_inotify_find2(die, from)))
+ snprintf(path, sizeof(path), "%s/%s", die->path, from);
+
+ LIST_FOREACH(de, &die->entries, de_inotify_link) {
+ if (de->de_files == NULL)
+ continue;
+ HTSMSG_FOREACH(f, de->de_files)
+ if ((m = htsmsg_field_get_list(f)) != NULL) {
+ filename = htsmsg_get_str(m, "filename");
+ if (filename && !strcmp(path, filename))
+ break;
+ }
+ if (f)
+ break;
+ }
+
+ if (!de)
return;
- if (to) {
- char path[512];
- snprintf(path, sizeof(path), "%s/%s", die->path, to);
- tvh_str_update(&de->de_filename, path);
- dvr_entry_save(de);
- } else
- dvr_inotify_del(de);
+ if (f && m) {
+ if (to) {
+ snprintf(path, sizeof(path), "%s/%s", die->path, to);
+ htsmsg_set_str(m, "filename", path);
+ dvr_entry_save(de);
+ } else {
+ htsmsg_field_destroy(de->de_files, f);
+ if (htsmsg_is_empty(de->de_files))
+ dvr_inotify_del(de);
+ }
+ }
htsp_dvr_entry_update(de);
idnode_notify_changed(&de->de_id);
char *filename, *s;
struct tm tm;
dvr_config_t *cfg;
+ htsmsg_t *m;
if (de == NULL)
return -1;
}
free(filename);
- tvh_str_set(&de->de_filename, fullname);
+ if (de->de_files == NULL)
+ de->de_files = htsmsg_create_list();
+ m = htsmsg_create_map();
+ htsmsg_add_str(m, "filename", fullname);
+ htsmsg_add_msg(de->de_files, NULL, m);
return 0;
}
tvhlog(LOG_ERR, "dvr",
"Recording error: \"%s\": %s",
- de->de_filename ?: lang_str_get(de->de_title, NULL), msgbuf);
+ dvr_get_filename(de) ?: lang_str_get(de->de_title, NULL), msgbuf);
}
/**
return -1;
}
- if(muxer_open_file(muxer, de->de_filename)) {
+ if(muxer_open_file(muxer, dvr_get_filename(de))) {
dvr_rec_fatal_error(de, "Unable to open file");
return -1;
}
"network: \"%s\", mux: \"%s\", provider: \"%s\", "
"service: \"%s\"",
- de->de_filename ?: lang_str_get(de->de_title, NULL),
+ dvr_get_filename(de) ?: lang_str_get(de->de_title, NULL),
si->si_adapter ?: "<N/A>",
si->si_network ?: "<N/A>",
si->si_mux ?: "<N/A>",
muxer_reconfigure(prch->prch_muxer, sm->sm_data) < 0) {
tvhlog(LOG_WARNING,
"dvr", "Unable to reconfigure \"%s\"",
- de->de_filename ?: lang_str_get(de->de_title, NULL));
+ dvr_get_filename(de) ?: lang_str_get(de->de_title, NULL));
// Try to restart the recording if the muxer doesn't
// support reconfiguration of the streams.
de->de_last_error = SM_CODE_OK;
tvhlog(LOG_INFO,
"dvr", "Recording completed: \"%s\"",
- de->de_filename ?: lang_str_get(de->de_title, NULL));
+ dvr_get_filename(de) ?: lang_str_get(de->de_title, NULL));
dvr_thread_epilog(de);
started = 0;
dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code);
tvhlog(LOG_ERR,
"dvr", "Recording stopped: \"%s\": %s",
- de->de_filename ?: lang_str_get(de->de_title, NULL),
+ dvr_get_filename(de) ?: lang_str_get(de->de_title, NULL),
streaming_code2txt(sm->sm_code));
dvr_thread_epilog(de);
dvr_rec_set_state(de, DVR_RS_ERROR, code);
tvhlog(LOG_ERR,
"dvr", "Streaming error: \"%s\": %s",
- de->de_filename ?: lang_str_get(de->de_title, NULL),
+ dvr_get_filename(de) ?: lang_str_get(de->de_title, NULL),
streaming_code2txt(code));
}
}
tvhlog(LOG_ERR,
"dvr", "Recording unable to start: \"%s\": %s",
- de->de_filename ?: lang_str_get(de->de_title, NULL),
+ dvr_get_filename(de) ?: lang_str_get(de->de_title, NULL),
streaming_code2txt(sm->sm_code));
}
break;
static void
dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc)
{
- const char *fmap[256];
+ const char *fmap[256], *filename;
char **args;
char start[16];
char stop[16];
return;
}
- fbasename = tvh_strdupa(de->de_filename);
+ filename = dvr_get_filename(de);
+ if (filename == NULL)
+ return;
+
+ fbasename = tvh_strdupa(filename);
snprintf(start, sizeof(start), "%"PRItime_t, (time_t)dvr_entry_get_start_time(de));
snprintf(stop, sizeof(stop), "%"PRItime_t, (time_t)dvr_entry_get_stop_time(de));
memset(fmap, 0, sizeof(fmap));
- fmap['f'] = de->de_filename; /* full path to recoding */
+ fmap['f'] = filename; /* full path to recoding */
fmap['b'] = basename(fbasename); /* basename of recoding */
fmap['c'] = DVR_CH_NAME(de); /* channel name */
fmap['C'] = de->de_creator; /* user who created this recording */
dvr_notify(de, 1);
dvr_config_t *cfg = de->de_config;
- if(cfg && cfg->dvr_postproc && de->de_filename)
+ if(cfg && cfg->dvr_postproc)
dvr_spawn_postproc(de,cfg->dvr_postproc);
}
}
-
/*
*
*/
}
+/*
+ *
+ */
+htsmsg_field_t *
+htsmsg_field_last(htsmsg_t *msg)
+{
+ if (msg == NULL)
+ return NULL;
+ return TAILQ_LAST(&msg->hm_fields, htsmsg_field_queue);
+}
+
/**
*
}
+/**
+ *
+ */
+int
+htsmsg_is_empty(htsmsg_t *msg)
+{
+ if (msg == NULL)
+ return 1;
+
+ assert(msg->hm_data == NULL);
+ return TAILQ_EMPTY(&msg->hm_fields);
+}
+
+
/*
*
*/
f->hmf_str = strdup(str);
}
+/*
+ *
+ */
+int
+htsmsg_field_set_str(htsmsg_field_t *f, const char *str)
+{
+ if (f->hmf_type != HMF_STR)
+ return 1;
+ if (f->hmf_flags & HMF_ALLOCED)
+ free((void *)f->hmf_str);
+ f->hmf_flags |= HMF_ALLOCED;
+ f->hmf_str = strdup(str);
+ return 0;
+}
+
/*
*
*/
htsmsg_field_t *f = htsmsg_field_find(msg, name);
if (!f)
f = htsmsg_field_add(msg, name, HMF_STR, HMF_ALLOCED | HMF_NAME_ALLOCED);
- else {
- if (f->hmf_type != HMF_STR)
- return 1;
- if(f->hmf_flags & HMF_ALLOCED)
- free((void *)f->hmf_str);
- }
- f->hmf_str = strdup(str);
- return 0;
+ return htsmsg_field_set_str(f, str);
}
/*
*/
int htsmsg_set_str(htsmsg_t *msg, const char *name, const char *str);
+/**
+ * Update a string field
+ */
+int htsmsg_field_set_str(htsmsg_field_t *f, const char *str);
+
/**
* Add an field where source is a list or map message.
*/
*/
int htsmsg_delete_field(htsmsg_t *msg, const char *name);
+/**
+ * Is list/map empty
+ */
+int htsmsg_is_empty(htsmsg_t *msg);
+
/**
* Detach will remove the given field (and only if it is a list or map)
* from the message and make it a 'standalone message'. This means
*/
htsmsg_field_t *htsmsg_field_find(htsmsg_t *msg, const char *name);
+/**
+ * Get a last field, return NULL if it does not exist
+ */
+htsmsg_field_t *htsmsg_field_last(htsmsg_t *msg);
+
/**
* Clone a message.
static void *htsp_server, *htsp_server_2;
-#define HTSP_PROTO_VERSION 20
+#define HTSP_PROTO_VERSION 21
#define HTSP_ASYNC_OFF 0x00
#define HTSP_ASYNC_ON 0x01
static htsmsg_t *
htsp_build_dvrentry(dvr_entry_t *de, const char *method)
{
- htsmsg_t *out = htsmsg_create_map();
+ htsmsg_t *out = htsmsg_create_map(), *l, *m;
+ htsmsg_field_t *f;
const char *s = NULL, *error = NULL, *subscriptionError = NULL;
- const char *p;
+ const char *p, *last;
int64_t fsize = -1;
htsmsg_add_u32(out, "id", idnode_get_short_uuid(&de->de_id));
htsmsg_add_u32(out, "priority", de->de_pri);
htsmsg_add_u32(out, "contentType", de->de_content_type);
- if( de->de_title && (s = lang_str_get(de->de_title, NULL)))
+ if(de->de_title && (s = lang_str_get(de->de_title, NULL)))
htsmsg_add_str(out, "title", s);
if( de->de_desc && (s = lang_str_get(de->de_desc, NULL)))
htsmsg_add_str(out, "description", s);
if(de->de_creator)
htsmsg_add_str(out, "creator", de->de_creator);
- if( de->de_filename && de->de_config ) {
- if ((p = tvh_strbegins(de->de_filename, de->de_config->dvr_storage)))
- htsmsg_add_str(out, "path", p);
+ last = NULL;
+ if (!htsmsg_is_empty(de->de_files) && de->de_config) {
+ l = htsmsg_create_list();
+ htsmsg_add_msg(out, "files", l);
+ HTSMSG_FOREACH(f, de->de_files) {
+ m = htsmsg_field_get_list(f);
+ if (m == NULL) continue;
+ s = last = htsmsg_get_str(m, "filename");
+ if (s && (p = tvh_strbegins(s, de->de_config->dvr_storage)) != NULL)
+ htsmsg_add_msg(l, NULL, htsmsg_copy(m));
+ }
}
+
+ if(last && de->de_config)
+ if ((p = tvh_strbegins(last, de->de_config->dvr_storage)))
+ htsmsg_add_str(out, "path", p);
+
switch(de->de_sched_state) {
case DVR_SCHEDULED:
s = "scheduled";
{
const char *str, *s2;
const char *filename = NULL;
+ htsmsg_field_t *f;
+
+
if((str = htsmsg_get_str(in, "file")) == NULL)
return htsp_error("Missing argument 'file'");
if (!htsp_user_access_channel(htsp, de->de_channel))
return htsp_error("User does not have access");
- if(de->de_filename == NULL)
+ f = htsmsg_field_last(de->de_files);
+ if (f)
+ filename = htsmsg_field_get_str(f);
+
+ if (f == NULL || filename == NULL)
return htsp_error("DVR entry does not have a file yet");
- filename = de->de_filename;
return htsp_file_open(htsp, filename, 0);
} else if ((s2 = tvh_strbegins(str, "imagecache/")) != NULL) {
{
int fd, i, ret;
struct stat st;
- const char *content = NULL, *range;
+ const char *content = NULL, *range, *filename;
dvr_entry_t *de;
char *fname;
char *basename;
de = dvr_entry_find_by_uuid(remain);
if (de == NULL)
de = dvr_entry_find_by_id(atoi(remain));
- if(de == NULL || de->de_filename == NULL) {
+ if(de == NULL || (filename = dvr_get_filename(de)) == NULL) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_NOT_FOUND;
}
return HTTP_STATUS_NOT_FOUND;
}
- fname = tvh_strdupa(de->de_filename);
+ fname = tvh_strdupa(filename);
content = muxer_container_type2mime(de->de_mc, 1);
pthread_mutex_unlock(&global_lock);