1 /*
2 * tvheadend, HTSP interface
3 * Copyright (C) 2007 Andreas Öman
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "tvheadend.h"
20 #include "atomic.h"
21 #include "config.h"
22 #include "api.h"
23 #include "channels.h"
24 #include "subscriptions.h"
25 #include "tcp.h"
26 #include "packet.h"
27 #include "access.h"
28 #include "htsp_server.h"
29 #include "streaming.h"
30 #include "htsmsg_binary.h"
31 #include "epg.h"
32 #include "plumbing/tsfix.h"
33 #include "imagecache.h"
34 #include "descrambler.h"
35 #include "descrambler/caid.h"
36 #include "notify.h"
37 #include "htsmsg_json.h"
38 #include "lang_codes.h"
39 #if ENABLE_TIMESHIFT
40 #include "timeshift.h"
41 #endif
42
43 #include <pthread.h>
44 #include <assert.h>
45 #include <stdio.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <stdarg.h>
50 #include <fcntl.h>
51 #include <errno.h>
52 #include <signal.h>
53 #include <netinet/in.h>
54 #include <netinet/tcp.h>
55 #include <arpa/inet.h>
56 #include <sys/types.h>
57 #include <sys/socket.h>
58 #include <sys/stat.h>
59 #include <sys/time.h>
60 #include <limits.h>
61 #include "settings.h"
62
63 /* **************************************************************************
64 * Datatypes and variables
65 * *************************************************************************/
66
67 static void *htsp_server, *htsp_server_2;
68
69 #define HTSP_PROTO_VERSION 27
70
71 #define HTSP_ASYNC_OFF 0x00
72 #define HTSP_ASYNC_ON 0x01
73 #define HTSP_ASYNC_EPG 0x02
74
75 #define HTSP_ASYNC_AUX_CH 0x01
76 #define HTSP_ASYNC_AUX_CHTAG 0x02
77 #define HTSP_ASYNC_AUX_CHTAG_DEL 0x03
78 #define HTSP_ASYNC_AUX_DVR 0x04
79 #define HTSP_ASYNC_AUX_AUTOREC 0x05
80 #define HTSP_ASYNC_AUX_TIMEREC 0x06
81 #define HTSP_ASYNC_AUX_EPG 0x07
82
83 #define HTSP_ASYNC_EPG_INTERVAL 30
84
85 #define HTSP_PRIV_MASK (ACCESS_HTSP_STREAMING)
86
87 extern char *dvr_storage;
88
89 LIST_HEAD(htsp_connection_list, htsp_connection);
90 LIST_HEAD(htsp_subscription_list, htsp_subscription);
91 LIST_HEAD(htsp_file_list, htsp_file);
92
93 TAILQ_HEAD(htsp_msg_queue, htsp_msg);
94 TAILQ_HEAD(htsp_msg_q_queue, htsp_msg_q);
95
96 static struct htsp_connection_list htsp_async_connections;
97 static struct htsp_connection_list htsp_connections;
98
99 static void htsp_streaming_input(void *opaque, streaming_message_t *sm);
100 static htsmsg_t *htsp_streaming_input_info(void *opaque, htsmsg_t *list);
101 const char * _htsp_get_subscription_status(int smcode);
102 static void htsp_epg_send_waiting(struct htsp_connection *, int64_t mintime);
103
104 static streaming_ops_t htsp_streaming_input_ops = {
105 .st_cb = htsp_streaming_input,
106 .st_info = htsp_streaming_input_info
107 };
108
109 /**
110 *
111 */
112 typedef struct htsp_msg {
113 TAILQ_ENTRY(htsp_msg) hm_link;
114
115 htsmsg_t *hm_msg;
116 int hm_payloadsize; /* For maintaining stats about streaming
117 buffer depth */
118
119 pktbuf_t *hm_pb; /* For keeping reference to packet payload.
120 hm_msg can contain messages that points
121 to packet payload so to avoid copy we
122 keep a reference here */
123 } htsp_msg_t;
124
125
126 /**
127 *
128 */
129 typedef struct htsp_msg_q {
130 struct htsp_msg_queue hmq_q;
131
132 TAILQ_ENTRY(htsp_msg_q) hmq_link;
133 int hmq_strict_prio; /* Serve this queue 'til it's empty */
134 int hmq_length;
135 int hmq_payload; /* Bytes of streaming payload that's enqueued */
136 int hmq_dead;
137 } htsp_msg_q_t;
138
139 /**
140 *
141 */
142 typedef struct htsp_connection {
143 LIST_ENTRY(htsp_connection) htsp_link;
144
145 int htsp_fd;
146 struct sockaddr_storage *htsp_peer;
147
148 uint32_t htsp_version;
149
150 char *htsp_logname;
151 char *htsp_peername;
152 char *htsp_username;
153 char *htsp_clientname;
154 char *htsp_language; // for async updates
155
156 int64_t htsp_epg_window; // only send async epg updates within this window (seconds)
157 int64_t htsp_epg_lastupdate; // last update time for async epg events
158 mtimer_t htsp_epg_timer; // timer for async epg updates
159
160 /**
161 * Async mode
162 */
163 int htsp_async_mode;
164 LIST_ENTRY(htsp_connection) htsp_async_link;
165
166 /**
167 * Writer thread
168 */
169 pthread_t htsp_writer_thread;
170
171 int htsp_writer_run;
172
173 struct htsp_msg_q_queue htsp_active_output_queues;
174
175 pthread_mutex_t htsp_out_mutex;
176 tvh_cond_t htsp_out_cond;
177
178 htsp_msg_q_t htsp_hmq_ctrl;
179 htsp_msg_q_t htsp_hmq_epg;
180 htsp_msg_q_t htsp_hmq_qstatus;
181
182 struct htsp_subscription_list htsp_subscriptions;
183 struct htsp_subscription_list htsp_dead_subscriptions;
184 struct htsp_file_list htsp_files;
185 int htsp_file_id;
186
187 access_t *htsp_granted_access;
188
189 uint8_t htsp_challenge[32];
190
191 } htsp_connection_t;
192
193
194 /**
195 *
196 */
197 typedef struct htsp_subscription {
198 htsp_connection_t *hs_htsp;
199
200 LIST_ENTRY(htsp_subscription) hs_link;
201
202 int hs_sid; /* Subscription ID (set by client) */
203
204 th_subscription_t *hs_s; // Temporary
205 int hs_s_bytes_out;
206 mtimer_t hs_s_bytes_out_timer;
207
208 streaming_target_t hs_input;
209 profile_chain_t hs_prch;
210
211 htsp_msg_q_t hs_q;
212
213 int64_t hs_last_report; /* Last queue status report sent */
214
215 int hs_dropstats[PKT_NTYPES];
216
217 int hs_wait_for_video;
218
219 int hs_90khz;
220
221 int hs_queue_depth;
222
223 #define NUM_FILTERED_STREAMS (64*8)
224
225 uint64_t hs_filtered_streams[8]; // one bit per stream
226
227 int hs_first;
228
229 uint32_t hs_data_errors;
230
231 } htsp_subscription_t;
232
233
234 /**
235 *
236 */
237 typedef struct htsp_file {
238 LIST_ENTRY(htsp_file) hf_link;
239 int hf_id; // ID sent to client
240 int hf_fd; // Our file descriptor
241 char *hf_path; // For logging
242 uint32_t hf_de_id; // Associated dvr entry
243 th_subscription_t *hf_subscription;
244 } htsp_file_t;
245
246 #define HTSP_DEFAULT_QUEUE_DEPTH 500000
247
248 /* **************************************************************************
249 * Support routines
250 * *************************************************************************/
251
252 static void
htsp_trace(htsp_connection_t * htsp,int subsystem,const char * prefix,htsmsg_t * m)253 htsp_trace(htsp_connection_t *htsp, int subsystem,
254 const char *prefix, htsmsg_t *m)
255 {
256 htsbuf_queue_t q;
257 char *s;
258 htsbuf_queue_init(&q, 0);
259 htsmsg_json_serialize(m, &q, 0);
260 s = htsbuf_to_string(&q);
261 htsbuf_queue_flush(&q);
262 tvhtrace(subsystem, "%s - %s '%s'", htsp->htsp_logname, prefix, s);
263 free(s);
264 }
265
266 static void
htsp_disable_stream(htsp_subscription_t * hs,unsigned int id)267 htsp_disable_stream(htsp_subscription_t *hs, unsigned int id)
268 {
269 if(id < NUM_FILTERED_STREAMS)
270 hs->hs_filtered_streams[id / 64] |= 1 << (id & 63);
271 }
272
273
274 static void
htsp_enable_stream(htsp_subscription_t * hs,unsigned int id)275 htsp_enable_stream(htsp_subscription_t *hs, unsigned int id)
276 {
277 if(id < NUM_FILTERED_STREAMS)
278 hs->hs_filtered_streams[id / 64] &= ~(1 << (id & 63));
279 }
280
281
282 static inline int
htsp_is_stream_enabled(htsp_subscription_t * hs,unsigned int id)283 htsp_is_stream_enabled(htsp_subscription_t *hs, unsigned int id)
284 {
285 if(id < NUM_FILTERED_STREAMS)
286 return !(hs->hs_filtered_streams[id / 64] & (1 << (id & 63)));
287 return 1;
288 }
289
290 static inline int
htsp_anonymize(htsp_connection_t * htsp)291 htsp_anonymize(htsp_connection_t *htsp)
292 {
293 return (htsp->htsp_granted_access->aa_rights & ACCESS_HTSP_ANONYMIZE) != 0;
294 }
295
296 /**
297 *
298 */
299 static void
htsp_update_logname(htsp_connection_t * htsp)300 htsp_update_logname(htsp_connection_t *htsp)
301 {
302 char buf[100];
303
304 snprintf(buf, sizeof(buf), "%s%s%s%s%s%s",
305 htsp->htsp_peername,
306 htsp->htsp_username || htsp->htsp_clientname ? " [ " : "",
307 htsp->htsp_username ?: "",
308 htsp->htsp_username && htsp->htsp_clientname ? " | " : "",
309 htsp->htsp_clientname ?: "",
310 htsp->htsp_username || htsp->htsp_clientname ? " ]" : "");
311
312 tvh_str_update(&htsp->htsp_logname, buf);
313 }
314
315 /**
316 *
317 */
318 static void
htsp_msg_destroy(htsp_msg_t * hm)319 htsp_msg_destroy(htsp_msg_t *hm)
320 {
321 htsmsg_destroy(hm->hm_msg);
322 if(hm->hm_pb != NULL)
323 pktbuf_ref_dec(hm->hm_pb);
324 free(hm);
325 }
326
327 /**
328 *
329 */
330 static void
htsp_init_queue(htsp_msg_q_t * hmq,int strict_prio)331 htsp_init_queue(htsp_msg_q_t *hmq, int strict_prio)
332 {
333 TAILQ_INIT(&hmq->hmq_q);
334 hmq->hmq_length = 0;
335 hmq->hmq_strict_prio = strict_prio;
336 }
337
338 /**
339 *
340 */
341 static void
htsp_flush_queue(htsp_connection_t * htsp,htsp_msg_q_t * hmq,int dead)342 htsp_flush_queue(htsp_connection_t *htsp, htsp_msg_q_t *hmq, int dead)
343 {
344 htsp_msg_t *hm;
345
346 pthread_mutex_lock(&htsp->htsp_out_mutex);
347
348 if(hmq->hmq_length)
349 TAILQ_REMOVE(&htsp->htsp_active_output_queues, hmq, hmq_link);
350
351 while((hm = TAILQ_FIRST(&hmq->hmq_q)) != NULL) {
352 TAILQ_REMOVE(&hmq->hmq_q, hm, hm_link);
353 htsp_msg_destroy(hm);
354 }
355
356 // reset
357 hmq->hmq_length = 0;
358 hmq->hmq_payload = 0;
359 hmq->hmq_dead = dead;
360 pthread_mutex_unlock(&htsp->htsp_out_mutex);
361 }
362
363 /**
364 *
365 */
366 static void
htsp_subscription_destroy(htsp_connection_t * htsp,htsp_subscription_t * hs)367 htsp_subscription_destroy(htsp_connection_t *htsp, htsp_subscription_t *hs)
368 {
369 th_subscription_t *ts = hs->hs_s;
370
371 hs->hs_s = NULL;
372 mtimer_disarm(&hs->hs_s_bytes_out_timer);
373
374 LIST_REMOVE(hs, hs_link);
375 LIST_INSERT_HEAD(&htsp->htsp_dead_subscriptions, hs, hs_link);
376
377 subscription_unsubscribe(ts, UNSUBSCRIBE_FINAL);
378
379 if(hs->hs_prch.prch_st != NULL)
380 profile_chain_close(&hs->hs_prch);
381
382 htsp_flush_queue(htsp, &hs->hs_q, 1);
383 }
384
385 /**
386 *
387 */
388 static void
htsp_subscription_free(htsp_connection_t * htsp,htsp_subscription_t * hs)389 htsp_subscription_free(htsp_connection_t *htsp, htsp_subscription_t *hs)
390 {
391 LIST_REMOVE(hs, hs_link);
392 htsp_flush_queue(htsp, &hs->hs_q, 1);
393 free(hs);
394 }
395
396 /**
397 *
398 */
399 static void
htsp_send(htsp_connection_t * htsp,htsmsg_t * m,pktbuf_t * pb,htsp_msg_q_t * hmq,int payloadsize)400 htsp_send(htsp_connection_t *htsp, htsmsg_t *m, pktbuf_t *pb,
401 htsp_msg_q_t *hmq, int payloadsize)
402 {
403 htsp_msg_t *hm = malloc(sizeof(htsp_msg_t));
404
405 hm->hm_msg = m;
406 hm->hm_pb = pb;
407 if(pb != NULL)
408 pktbuf_ref_inc(pb);
409 hm->hm_payloadsize = payloadsize;
410
411 pthread_mutex_lock(&htsp->htsp_out_mutex);
412
413 assert(!hmq->hmq_dead);
414
415 TAILQ_INSERT_TAIL(&hmq->hmq_q, hm, hm_link);
416
417 if(hmq->hmq_length == 0) {
418 /* Activate queue */
419
420 if(hmq->hmq_strict_prio) {
421 TAILQ_INSERT_HEAD(&htsp->htsp_active_output_queues, hmq, hmq_link);
422 } else {
423 TAILQ_INSERT_TAIL(&htsp->htsp_active_output_queues, hmq, hmq_link);
424 }
425 }
426
427 hmq->hmq_length++;
428 hmq->hmq_payload += payloadsize;
429 tvh_cond_signal(&htsp->htsp_out_cond, 0);
430 pthread_mutex_unlock(&htsp->htsp_out_mutex);
431 }
432
433 /**
434 *
435 */
436 static void
htsp_send_subscription(htsp_connection_t * htsp,htsmsg_t * m,pktbuf_t * pb,htsp_subscription_t * hs,int payloadsize)437 htsp_send_subscription(htsp_connection_t *htsp, htsmsg_t *m, pktbuf_t *pb,
438 htsp_subscription_t *hs, int payloadsize)
439 {
440 if (tvhtrace_enabled()) {
441 char buf[64];
442 size_t l = 0;
443 tvh_strlcatf(buf, sizeof(buf), l, "subscription %i", hs->hs_sid);
444 if (payloadsize)
445 tvh_strlcatf(buf, sizeof(buf), l, " (payload %d)", payloadsize);
446 htsp_trace(htsp, LS_HTSP_SUB, buf, m);
447 }
448
449 htsp_send(htsp, m, pb, &hs->hs_q, payloadsize);
450 }
451
452 /**
453 *
454 */
455 static void
htsp_send_message(htsp_connection_t * htsp,htsmsg_t * m,htsp_msg_q_t * hmq)456 htsp_send_message(htsp_connection_t *htsp, htsmsg_t *m, htsp_msg_q_t *hmq)
457 {
458 if (tvhtrace_enabled()) {
459 const char *qname = "answer";
460 if (hmq == &htsp->htsp_hmq_qstatus)
461 qname = "status";
462 htsp_trace(htsp, LS_HTSP_ANS, qname, m);
463 }
464
465 htsp_send(htsp, m, NULL, hmq ?: &htsp->htsp_hmq_ctrl, 0);
466 }
467
468 /**
469 * Simple function to respond with an error
470 */
471 static htsmsg_t *
htsp_error(htsp_connection_t * htsp,const char * errstr)472 htsp_error(htsp_connection_t *htsp, const char *errstr)
473 {
474 htsmsg_t *r = htsmsg_create_map();
475 htsmsg_add_str(r, "error", tvh_gettext_lang(htsp->htsp_language, errstr));
476 return r;
477 }
478
479 /**
480 * Simple function to respond with an success
481 */
482 static htsmsg_t *
htsp_success(void)483 htsp_success(void)
484 {
485 htsmsg_t *r = htsmsg_create_map();
486 htsmsg_add_u32(r, "success", 1);
487 return r;
488 }
489
490 /**
491 *
492 */
493 static void
htsp_reply(htsp_connection_t * htsp,htsmsg_t * in,htsmsg_t * out)494 htsp_reply(htsp_connection_t *htsp, htsmsg_t *in, htsmsg_t *out)
495 {
496 uint32_t seq;
497
498 if(!htsmsg_get_u32(in, "seq", &seq))
499 htsmsg_add_u32(out, "seq", seq);
500
501 htsp_send_message(htsp, out, NULL);
502 }
503
504 /**
505 * Update challenge
506 */
507 static int
htsp_generate_challenge(htsp_connection_t * htsp)508 htsp_generate_challenge(htsp_connection_t *htsp)
509 {
510 int fd, n;
511
512 if((fd = tvh_open("/dev/urandom", O_RDONLY, 0)) < 0)
513 return -1;
514
515 n = read(fd, &htsp->htsp_challenge, 32);
516 close(fd);
517 return n != 32;
518 }
519
520 /**
521 * Check if user can access the channel
522 */
523 static inline int
htsp_user_access_channel(htsp_connection_t * htsp,channel_t * ch)524 htsp_user_access_channel(htsp_connection_t *htsp, channel_t *ch)
525 {
526 if (!ch || !ch->ch_enabled || LIST_FIRST(&ch->ch_services) == NULL) /* Don't pass unplayable channels to clients */
527 return 0;
528 if (!htsp)
529 return 1;
530 return channel_access(ch, htsp->htsp_granted_access, 0);
531 }
532
533 static const char *
htsp_dvr_config_name(htsp_connection_t * htsp,const char * config_name)534 htsp_dvr_config_name( htsp_connection_t *htsp, const char *config_name )
535 {
536 dvr_config_t *cfg = NULL, *cfg2;
537 access_t *perm = htsp->htsp_granted_access;
538 htsmsg_field_t *f;
539 const char *uuid;
540 static char ubuf[UUID_HEX_SIZE];
541
542 lock_assert(&global_lock);
543
544 config_name = config_name ?: "";
545
546 if (perm->aa_dvrcfgs == NULL)
547 return config_name; /* no change */
548
549 HTSMSG_FOREACH(f, perm->aa_dvrcfgs) {
550 uuid = htsmsg_field_get_str(f) ?: "";
551 if (strcmp(uuid, config_name) == 0)
552 return config_name;
553 cfg2 = dvr_config_find_by_uuid(uuid);
554 if (cfg2 && strcmp(cfg2->dvr_config_name, config_name) == 0)
555 return uuid;
556 if (!cfg)
557 cfg = cfg2;
558 }
559
560 if (!cfg && perm->aa_username)
561 tvhinfo(LS_HTSP, "User '%s' has no valid dvr config in ACL, using default...", perm->aa_username);
562
563 return cfg ? idnode_uuid_as_str(&cfg->dvr_id, ubuf) : NULL;
564 }
565
566 /**
567 * Converts htsp input to internal API
568 * @param htsp, current htsp connection
569 * @param in, the htsp message input from client
570 * @param autorec, true for autorecs, false for timerecs
571 * @param add, true for new instances, false for update calls
572 * @return the htsmsg_t config to be added or updated with idnode
573 */
574 static htsmsg_t *
htsp_serierec_convert(htsp_connection_t * htsp,htsmsg_t * in,channel_t * ch,int autorec,int add)575 htsp_serierec_convert(htsp_connection_t *htsp, htsmsg_t *in, channel_t *ch, int autorec, int add)
576 {
577 htsmsg_t *conf,*days;
578 uint32_t u32;
579 int64_t s64;
580 int32_t approx_time, start, start_window, s32;
581 int retval;
582 const char *str;
583 char ubuf[UUID_HEX_SIZE];
584
585 conf = htsmsg_create_map();
586 days = htsmsg_create_list();
587
588 if (autorec) { // autorec specific
589 if (!(retval = htsmsg_get_u32(in, "minduration", &u32)) || add)
590 htsmsg_add_u32(conf, "minduration", !retval ? u32 : 0); // 0 = any
591 if (!(retval = htsmsg_get_u32(in, "maxduration", &u32)) || add)
592 htsmsg_add_u32(conf, "maxduration", !retval ? u32 : 0); // 0 = any
593 if (!(retval = htsmsg_get_u32(in, "fulltext", &u32)) || add)
594 htsmsg_add_u32(conf, "fulltext", !retval ? u32 : 0); // 0 = off
595 if (!(retval = htsmsg_get_u32(in, "dupDetect", &u32)) || add)
596 htsmsg_add_u32(conf, "record", !retval ? u32 : DVR_AUTOREC_RECORD_ALL);
597 if (!(retval = htsmsg_get_u32(in, "maxCount", &u32)) || add)
598 htsmsg_add_u32(conf, "maxcount", !retval ? u32 : 0); // 0 = unlimited
599 if (!(retval = htsmsg_get_s64(in, "startExtra", &s64)) || add)
600 htsmsg_add_s64(conf, "start_extra", !retval ? (s64 < 0 ? 0 : s64) : 0); // 0 = dvr config
601 if (!(retval = htsmsg_get_s64(in, "stopExtra", &s64)) || add)
602 htsmsg_add_s64(conf, "stop_extra", !retval ? (s64 < 0 ? 0 : s64) : 0); // 0 = dvr config
603
604 if (add) { // for add, stay compatible with older "approxTime
605 if(htsmsg_get_s32(in, "approxTime", &approx_time))
606 approx_time = -1;
607 if(htsmsg_get_s32(in, "start", &start))
608 start = -1;
609 if(htsmsg_get_s32(in, "startWindow", &start_window))
610 start_window = -1;
611 if (start < 0 || start_window < 0)
612 start = start_window = -1;
613 if (start < 0 && approx_time >= 0) {
614 start = approx_time - 15;
615 if (start < 0)
616 start += 24 * 60;
617 start_window = start + 30;
618 if (start_window >= 24 * 60)
619 start_window -= 24 * 60;
620 }
621 htsmsg_add_s32(conf, "start", start >= 0 ? start : -1); // -1 = any time
622 htsmsg_add_s32(conf, "start_window", start_window >= 0 ? start_window : -1); // -1 = any duration
623 }
624 else { // for update, we don't care about "approxTime"
625 if(!htsmsg_get_s32(in, "start", &s32))
626 htsmsg_add_s32(conf, "start", s32 >= 0 ? s32 : -1); // -1 = any time
627 if(!htsmsg_get_s32(in, "startWindow", &s32))
628 htsmsg_add_s32(conf, "start_window", s32 >= 0 ? s32 : -1); // -1 = any duration
629 }
630 }
631 else { //timerec specific
632 if (!(retval = htsmsg_get_u32(in, "start", &u32)) || add)
633 htsmsg_add_u32(conf, "start", !retval ? u32 : 0);
634 if (!(retval = htsmsg_get_u32(in, "stop", &u32)) || add)
635 htsmsg_add_u32(conf, "stop", !retval ? u32 : 0);
636 }
637
638 if (!(retval = htsmsg_get_u32(in, "enabled", &u32)) || add)
639 htsmsg_add_u32(conf, "enabled", !retval ? (u32 > 0 ? 1 : 0) : 1); // default on
640 if (!(retval = htsmsg_get_u32(in, "retention", &u32)) || add)
641 htsmsg_add_u32(conf, "retention", !retval ? u32 : DVR_RET_REM_DVRCONFIG);
642 if (!(retval = htsmsg_get_u32(in, "removal", &u32)) || add)
643 htsmsg_add_u32(conf, "removal", !retval ? u32 : DVR_RET_REM_DVRCONFIG);
644 if(!(retval = htsmsg_get_u32(in, "priority", &u32)) || add)
645 htsmsg_add_u32(conf, "pri", !retval ? u32 : DVR_PRIO_NORMAL);
646 if ((str = htsmsg_get_str(in, "name")) || add)
647 htsmsg_add_str(conf, "name", str ?: "");
648 if ((str = htsmsg_get_str(in, "comment")) || add)
649 htsmsg_add_str(conf, "comment", str ?: "");
650 if ((str = htsmsg_get_str(in, "directory")) || add)
651 htsmsg_add_str(conf, "directory", str ?: "");
652 if((str = htsmsg_get_str(in, "title")) || add)
653 htsmsg_add_str(conf, "title", str ?: "");
654
655 /* Only on creation */
656 if (add) {
657 str = htsp_dvr_config_name(htsp, htsmsg_get_str(in, "configName"));
658 htsmsg_add_str(conf, "config_name", str ?: "");
659 htsmsg_add_str(conf, "owner", htsp->htsp_granted_access->aa_username ?: "");
660 htsmsg_add_str(conf, "creator", htsp->htsp_granted_access->aa_representative ?: "");
661 } else {
662 str = htsmsg_get_str(in, "configName");
663 if (str) {
664 str = htsp_dvr_config_name(htsp, str);
665 htsmsg_add_str(conf, "config_name", str ?: "");
666 }
667 }
668
669 /* Weekdays only if present */
670 if(!(retval = htsmsg_get_u32(in, "daysOfWeek", &u32))) {
671 int i;
672 for (i = 0; i < 7; i++)
673 if (u32 & (1 << i))
674 htsmsg_add_u32(days, NULL, i + 1);
675 htsmsg_add_msg(conf, "weekdays", days); // not set = all days
676 }
677
678 /* Allow channel to be cleared on update -> any channel */
679 if (ch || !add) {
680 htsmsg_add_str(conf, "channel", ch ? idnode_uuid_as_str(&ch->ch_id, ubuf) : "");
681 }
682 return conf;
683 }
684
685 /* **************************************************************************
686 * File helpers
687 * *************************************************************************/
688
689 static uint32_t
htsp_channel_tag_get_identifier(channel_tag_t * ct)690 htsp_channel_tag_get_identifier(channel_tag_t *ct)
691 {
692 static int prev = 0;
693 if (ct->ct_htsp_id == 0)
694 ct->ct_htsp_id = ++prev;
695 return ct->ct_htsp_id;
696 }
697
698 static channel_tag_t *
htsp_channel_tag_find_by_identifier(htsp_connection_t * htsp,uint32_t id)699 htsp_channel_tag_find_by_identifier(htsp_connection_t *htsp, uint32_t id)
700 {
701 channel_tag_t *ct;
702
703 TAILQ_FOREACH(ct, &channel_tags, ct_link) {
704 if (!channel_tag_access(ct, htsp->htsp_granted_access, 0))
705 continue;
706 if (id == ct->ct_htsp_id)
707 return ct;
708 }
709 return NULL;
710 }
711
712 /**
713 *
714 */
715 static htsmsg_t *
htsp_file_open(htsp_connection_t * htsp,const char * path,int fd,dvr_entry_t * de)716 htsp_file_open(htsp_connection_t *htsp, const char *path, int fd, dvr_entry_t *de)
717 {
718 struct stat st;
719
720 if (fd <= 0) {
721 pthread_mutex_unlock(&global_lock);
722 fd = tvh_open(path, O_RDONLY, 0);
723 tvhdebug(LS_HTSP, "Opening file %s -- %s", path, fd < 0 ? strerror(errno) : "OK");
724 pthread_mutex_lock(&global_lock);
725 if(fd == -1)
726 return htsp_error(htsp, N_("Unable to open file"));
727 }
728
729 htsp_file_t *hf = calloc(1, sizeof(htsp_file_t));
730 hf->hf_fd = fd;
731 hf->hf_id = ++htsp->htsp_file_id;
732 hf->hf_path = strdup(path);
733 hf->hf_de_id = de ? idnode_get_short_uuid(&de->de_id) : 0;
734
735 if (de) {
736 const char *charset = de->de_config ? de->de_config->dvr_charset_id : NULL;
737
738 hf->hf_subscription =
739 subscription_create_from_file("HTSP", charset, path,
740 htsp->htsp_peername,
741 htsp->htsp_granted_access->aa_representative,
742 htsp->htsp_clientname);
743 }
744
745 LIST_INSERT_HEAD(&htsp->htsp_files, hf, hf_link);
746
747 htsmsg_t *rep = htsmsg_create_map();
748 htsmsg_add_u32(rep, "id", hf->hf_id);
749
750 if(!fstat(hf->hf_fd, &st)) {
751 htsmsg_add_s64(rep, "size", st.st_size);
752 htsmsg_add_s64(rep, "mtime", st.st_mtime);
753 }
754
755 return rep;
756 }
757
758 /**
759 *
760 */
761 static htsp_file_t *
htsp_file_find(const htsp_connection_t * htsp,htsmsg_t * in)762 htsp_file_find(const htsp_connection_t *htsp, htsmsg_t *in)
763 {
764 htsp_file_t *hf;
765
766 int id = htsmsg_get_u32_or_default(in, "id", 0);
767
768 LIST_FOREACH(hf, &htsp->htsp_files, hf_link) {
769 if(hf->hf_id == id)
770 return hf;
771 }
772 return NULL;
773 }
774
775 /**
776 *
777 */
778 static void
htsp_file_destroy(htsp_file_t * hf)779 htsp_file_destroy(htsp_file_t *hf)
780 {
781 tvhdebug(LS_HTSP, "Closed opened file %s", hf->hf_path);
782 LIST_REMOVE(hf, hf_link);
783 if (hf->hf_subscription) {
784 pthread_mutex_lock(&global_lock);
785 subscription_unsubscribe(hf->hf_subscription, UNSUBSCRIBE_FINAL);
786 pthread_mutex_unlock(&global_lock);
787 }
788 free(hf->hf_path);
789 close(hf->hf_fd);
790 free(hf);
791 }
792
793 static void
htsp_file_update_stats(htsp_file_t * hf,size_t len)794 htsp_file_update_stats(htsp_file_t *hf, size_t len)
795 {
796 th_subscription_t *ts = hf->hf_subscription;
797 if (ts) {
798 subscription_add_bytes_in(ts, len);
799 subscription_add_bytes_out(ts, len);
800 }
801 }
802
803 /* **************************************************************************
804 * Output message generators
805 * *************************************************************************/
806
807 /**
808 *
809 */
810 static htsmsg_t *
htsp_build_channel(channel_t * ch,const char * method,htsp_connection_t * htsp)811 htsp_build_channel(channel_t *ch, const char *method, htsp_connection_t *htsp)
812 {
813 idnode_list_mapping_t *ilm;
814 channel_tag_t *ct;
815 service_t *t;
816 epg_broadcast_t *now, *next = NULL;
817 int64_t chnum = channel_get_number(ch);
818 const char *icon;
819 char buf[64];
820
821 htsmsg_t *out = htsmsg_create_map();
822 htsmsg_t *tags = htsmsg_create_list();
823 htsmsg_t *services = htsmsg_create_list();
824
825 htsmsg_add_u32(out, "channelId", channel_get_id(ch));
826 htsmsg_add_u32(out, "channelNumber", channel_get_major(chnum));
827 if (channel_get_minor(chnum))
828 htsmsg_add_u32(out, "channelNumberMinor", channel_get_minor(chnum));
829
830 htsmsg_add_str(out, "channelName", channel_get_name(ch));
831 if ((icon = channel_get_icon(ch))) {
832
833 /* Handle older clients */
834 if ((strstr(icon, "imagecache") == icon) && htsp->htsp_version < 8) {
835 struct sockaddr_storage addr;
836 socklen_t addrlen;
837 char url[256];
838 char buf[50];
839 addrlen = sizeof(addr);
840 getsockname(htsp->htsp_fd, (struct sockaddr*)&addr, &addrlen);
841 tcp_get_str_from_ip(&addr, buf, 50);
842 snprintf(url, sizeof(url), "http://%s%s%s:%d%s/%s",
843 (addr.ss_family == AF_INET6)?"[":"",
844 buf,
845 (addr.ss_family == AF_INET6)?"]":"",
846 tvheadend_webui_port,
847 tvheadend_webroot ?: "",
848 icon);
849 htsmsg_add_str(out, "channelIcon", url);
850 } else {
851 if (htsp->htsp_version < 15) {
852 /* older clients expects '/imagecache/' */
853 if (strncmp(icon, "imagecache/", 11) == 0) {
854 snprintf(buf, sizeof(buf), "/%s", icon);
855 icon = buf;
856 }
857 }
858 htsmsg_add_str(out, "channelIcon", icon);
859 }
860 }
861
862 now = ch->ch_epg_now;
863 next = ch->ch_epg_next;
864 htsmsg_add_u32(out, "eventId", now ? now->id : 0);
865 htsmsg_add_u32(out, "nextEventId", next ? next->id : 0);
866
867 LIST_FOREACH(ilm, &ch->ch_ctms, ilm_in2_link) {
868 ct = (channel_tag_t *)ilm->ilm_in1;
869 if(channel_tag_access(ct, htsp->htsp_granted_access, 0))
870 htsmsg_add_u32(tags, NULL, htsp_channel_tag_get_identifier(ct));
871 }
872
873 LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link) {
874 t = (service_t *)ilm->ilm_in1;
875 htsmsg_t *svcmsg = htsmsg_create_map();
876 htsmsg_add_str(svcmsg, "name", service_nicename(t));
877
878 /* Service type string, i.e. UHD, HD, Radio,... */
879 htsmsg_add_str(svcmsg, "type", service_servicetype_txt(t));
880
881 /* Service content, other = 0x00, tv = 0x01, radio = 0x02 */
882 htsmsg_add_u32(svcmsg, "content", service_is_tv(t) ? 0x01 : (service_is_radio(t) ? 0x02 : 0x00));
883
884 if (service_is_encrypted(t)) {
885 htsmsg_add_u32(svcmsg, "caid", 65535);
886 htsmsg_add_str(svcmsg, "caname", tvh_gettext_lang(htsp->htsp_language, N_("Encrypted service")));
887 }
888 htsmsg_add_msg(services, NULL, svcmsg);
889 }
890
891 htsmsg_add_msg(out, "services", services);
892 htsmsg_add_msg(out, "tags", tags);
893 if (method)
894 htsmsg_add_str(out, "method", method);
895 return out;
896 }
897
898 /**
899 *
900 */
901 static htsmsg_t *
htsp_build_tag(channel_tag_t * ct,const char * method,int include_channels)902 htsp_build_tag(channel_tag_t *ct, const char *method, int include_channels)
903 {
904 idnode_list_mapping_t *ilm;
905 htsmsg_t *out = htsmsg_create_map();
906 htsmsg_t *members = include_channels ? htsmsg_create_list() : NULL;
907
908 htsmsg_add_u32(out, "tagId", htsp_channel_tag_get_identifier(ct));
909 htsmsg_add_u32(out, "tagIndex", ct->ct_index);
910
911 htsmsg_add_str(out, "tagName", ct->ct_name);
912 htsmsg_add_str(out, "tagIcon", channel_tag_get_icon(ct));
913 htsmsg_add_u32(out, "tagTitledIcon", ct->ct_titled_icon);
914
915 if(members != NULL) {
916 LIST_FOREACH(ilm, &ct->ct_ctms, ilm_in1_link)
917 htsmsg_add_u32(members, NULL, channel_get_id((channel_t *)ilm->ilm_in2));
918 htsmsg_add_msg(out, "members", members);
919 }
920
921 htsmsg_add_str(out, "method", method);
922 return out;
923 }
924
925 /**
926 *
927 */
928 static htsmsg_t *
htsp_build_dvrentry(htsp_connection_t * htsp,dvr_entry_t * de,const char * method,const char * lang,int statsonly)929 htsp_build_dvrentry(htsp_connection_t *htsp, dvr_entry_t *de, const char *method, const char *lang, int statsonly)
930 {
931 htsmsg_t *out = htsmsg_create_map(), *l, *m, *e, *info;
932 htsmsg_field_t *f;
933 const char *s = NULL, *error = NULL, *subscriptionError = NULL;
934 const char *p, *last;
935 int64_t fsize = -1, start, stop;
936 uint32_t u32;
937 char ubuf[UUID_HEX_SIZE];
938
939 htsmsg_add_u32(out, "id", idnode_get_short_uuid(&de->de_id));
940
941 if (!statsonly) {
942 htsmsg_add_u32(out, "enabled", de->de_enabled >= 1 ? 1 : 0);
943 if (de->de_channel)
944 htsmsg_add_u32(out, "channel", channel_get_id(de->de_channel));
945 if (de->de_channel_name) /* stays valid after channel deletion */
946 htsmsg_add_str(out, "channelName", de->de_channel_name);
947
948 if (de->de_bcast)
949 htsmsg_add_u32(out, "eventId", de->de_bcast->id);
950
951 if (de->de_autorec)
952 htsmsg_add_str(out, "autorecId", idnode_uuid_as_str(&de->de_autorec->dae_id, ubuf));
953
954 if (de->de_timerec)
955 htsmsg_add_str(out, "timerecId", idnode_uuid_as_str(&de->de_timerec->dte_id, ubuf));
956
957 htsmsg_add_s64(out, "start", de->de_start);
958 htsmsg_add_s64(out, "stop", de->de_stop);
959 htsmsg_add_s64(out, "startExtra", dvr_entry_get_extra_time_pre(de)/60);
960 htsmsg_add_s64(out, "stopExtra", dvr_entry_get_extra_time_post(de)/60);
961
962 if (htsp->htsp_version > 24)
963 htsmsg_add_u32(out, "retention", dvr_entry_get_retention_days(de));
964 else
965 htsmsg_add_u32(out, "retention", dvr_entry_get_retention_days(de) == DVR_RET_ONREMOVE ?
966 dvr_entry_get_removal_days(de) : dvr_entry_get_retention_days(de));
967
968 htsmsg_add_u32(out, "removal", dvr_entry_get_removal_days(de));
969 u32 = de->de_pri;
970 if (u32 >= DVR_PRIO_NOTSET)
971 u32 = DVR_PRIO_NORMAL;
972 htsmsg_add_u32(out, "priority", u32);
973 htsmsg_add_u32(out, "contentType", de->de_content_type);
974
975 if (de->de_sched_state == DVR_RECORDING || de->de_sched_state == DVR_COMPLETED) {
976 htsmsg_add_u32(out, "playcount", de->de_playcount);
977 htsmsg_add_u32(out, "playposition", de->de_playposition);
978 }
979
980 if(de->de_title && (s = lang_str_get(de->de_title, lang)))
981 htsmsg_add_str(out, "title", s);
982 if(de->de_subtitle && (s = lang_str_get(de->de_subtitle, lang)))
983 htsmsg_add_str(out, "subtitle", s);
984 if(de->de_desc && (s = lang_str_get(de->de_desc, lang)))
985 htsmsg_add_str(out, "description", s);
986 if(de->de_episode)
987 htsmsg_add_str(out, "episode", de->de_episode);
988 if(de->de_owner)
989 htsmsg_add_str(out, "owner", de->de_owner);
990 if(de->de_creator)
991 htsmsg_add_str(out, "creator", de->de_creator);
992 if(de->de_comment)
993 htsmsg_add_str(out, "comment", de->de_comment);
994
995 last = NULL;
996 if (!htsmsg_is_empty(de->de_files) && de->de_config) {
997 l = htsmsg_create_list();
998 HTSMSG_FOREACH(f, de->de_files) {
999 m = htsmsg_field_get_map(f);
1000 if (m == NULL) continue;
1001 s = last = htsmsg_get_str(m, "filename");
1002 if (s && (p = tvh_strbegins(s, de->de_config->dvr_storage)) != NULL) {
1003 e = htsmsg_copy(m);
1004 htsmsg_set_str(e, "filename", p);
1005 info = htsmsg_get_list(m, "info");
1006 if (info)
1007 htsmsg_set_msg(e, "info", htsmsg_copy(info));
1008 if (!htsmsg_get_s64(m, "start", &start))
1009 htsmsg_set_s64(e, "start", start);
1010 if (!htsmsg_get_s64(m, "stop", &stop))
1011 htsmsg_set_s64(e, "stop", stop);
1012
1013 htsmsg_add_msg(l, NULL, e);
1014 }
1015 }
1016 htsmsg_add_msg(out, "files", l);
1017 }
1018
1019 if(last && de->de_config)
1020 if ((p = tvh_strbegins(last, de->de_config->dvr_storage)))
1021 htsmsg_add_str(out, "path", p);
1022 }
1023
1024 switch(de->de_sched_state) {
1025 case DVR_SCHEDULED:
1026 s = "scheduled";
1027 break;
1028 case DVR_RECORDING:
1029 s = "recording";
1030 fsize = dvr_get_filesize(de, DVR_FILESIZE_UPDATE);
1031 if (de->de_rec_state == DVR_RS_ERROR ||
1032 (de->de_rec_state == DVR_RS_PENDING && de->de_last_error != SM_CODE_OK))
1033 {
1034 error = streaming_code2txt(de->de_last_error);
1035 subscriptionError = _htsp_get_subscription_status(de->de_last_error);
1036 }
1037 break;
1038 case DVR_COMPLETED:
1039 s = "completed";
1040 fsize = dvr_get_filesize(de, DVR_FILESIZE_UPDATE);
1041 if (fsize < 0)
1042 error = "File missing";
1043 else if(de->de_last_error != SM_CODE_OK &&
1044 de->de_last_error != SM_CODE_FORCE_OK)
1045 error = streaming_code2txt(de->de_last_error);
1046 break;
1047 case DVR_MISSED_TIME:
1048 s = "missed";
1049 break;
1050 case DVR_NOSTATE:
1051 s = "invalid";
1052 break;
1053 }
1054
1055 htsmsg_add_str(out, "state", s);
1056 if(error)
1057 htsmsg_add_str(out, "error", error);
1058 if (subscriptionError)
1059 htsmsg_add_str(out, "subscriptionError", subscriptionError);
1060 if (de->de_errors)
1061 htsmsg_add_u32(out, "streamErrors", de->de_errors);
1062 if (de->de_data_errors)
1063 htsmsg_add_u32(out, "dataErrors", de->de_data_errors);
1064 if (fsize >= 0)
1065 htsmsg_add_s64(out, "dataSize", fsize);
1066 htsmsg_add_str(out, "method", method);
1067 return out;
1068 }
1069
1070 /**
1071 *
1072 */
1073 static htsmsg_t *
htsp_build_autorecentry(htsp_connection_t * htsp,dvr_autorec_entry_t * dae,const char * method)1074 htsp_build_autorecentry(htsp_connection_t *htsp, dvr_autorec_entry_t *dae, const char *method)
1075 {
1076 htsmsg_t *out = htsmsg_create_map();
1077 char ubuf[UUID_HEX_SIZE];
1078 int tad;
1079
1080 htsmsg_add_str(out, "id", idnode_uuid_as_str(&dae->dae_id, ubuf));
1081 htsmsg_add_u32(out, "enabled", dae->dae_enabled >= 1 ? 1 : 0);
1082 htsmsg_add_u32(out, "maxDuration", dae->dae_maxduration);
1083 htsmsg_add_u32(out, "minDuration", dae->dae_minduration);
1084
1085 if (htsp->htsp_version > 24)
1086 htsmsg_add_u32(out, "retention", dvr_autorec_get_retention_days(dae));
1087 else
1088 htsmsg_add_u32(out, "retention", dvr_autorec_get_retention_days(dae) == DVR_RET_ONREMOVE ?
1089 dvr_autorec_get_removal_days(dae) : dvr_autorec_get_retention_days(dae));
1090
1091 htsmsg_add_u32(out, "removal", dvr_autorec_get_removal_days(dae));
1092 htsmsg_add_u32(out, "daysOfWeek", dae->dae_weekdays);
1093 if (dae->dae_start >= 0 && dae->dae_start_window >= 0) {
1094 if (dae->dae_start > dae->dae_start_window)
1095 tad = 24 * 60 - dae->dae_start + dae->dae_start_window;
1096 else
1097 tad = dae->dae_start_window - dae->dae_start;
1098 } else {
1099 tad = -1;
1100 }
1101 htsmsg_add_s32(out, "approxTime",
1102 dae->dae_start >= 0 && tad >= 0 ?
1103 ((dae->dae_start + tad / 2) % (24 * 60)) : -1);
1104 htsmsg_add_s32(out, "start", dae->dae_start >= 0 ? dae->dae_start : -1);
1105 htsmsg_add_s32(out, "startWindow", dae->dae_start_window >= 0 ? dae->dae_start_window : -1);
1106 htsmsg_add_u32(out, "priority", dae->dae_pri);
1107 htsmsg_add_s64(out, "startExtra", dvr_autorec_get_extra_time_pre(dae));
1108 htsmsg_add_s64(out, "stopExtra", dvr_autorec_get_extra_time_post(dae));
1109 htsmsg_add_u32(out, "dupDetect", dae->dae_record);
1110 htsmsg_add_u32(out, "maxCount", dae->dae_max_count);
1111
1112 if(dae->dae_title) {
1113 htsmsg_add_str(out, "title", dae->dae_title);
1114 htsmsg_add_u32(out, "fulltext", dae->dae_fulltext >= 1 ? 1 : 0);
1115 }
1116 if(dae->dae_name)
1117 htsmsg_add_str(out, "name", dae->dae_name);
1118 if(dae->dae_directory)
1119 htsmsg_add_str(out, "directory", dae->dae_directory);
1120 if(dae->dae_owner)
1121 htsmsg_add_str(out, "owner", dae->dae_owner);
1122 if(dae->dae_creator)
1123 htsmsg_add_str(out, "creator", dae->dae_creator);
1124 if(dae->dae_channel)
1125 htsmsg_add_u32(out, "channel", channel_get_id(dae->dae_channel));
1126
1127 htsmsg_add_str(out, "method", method);
1128
1129 return out;
1130 }
1131
1132 /**
1133 *
1134 */
1135 static htsmsg_t *
htsp_build_timerecentry(htsp_connection_t * htsp,dvr_timerec_entry_t * dte,const char * method)1136 htsp_build_timerecentry(htsp_connection_t *htsp, dvr_timerec_entry_t *dte, const char *method)
1137 {
1138 htsmsg_t *out = htsmsg_create_map();
1139 char ubuf[UUID_HEX_SIZE];
1140
1141 htsmsg_add_str(out, "id", idnode_uuid_as_str(&dte->dte_id, ubuf));
1142 htsmsg_add_u32(out, "enabled", dte->dte_enabled >= 1 ? 1 : 0);
1143 htsmsg_add_u32(out, "daysOfWeek", dte->dte_weekdays);
1144
1145 if (htsp->htsp_version > 24)
1146 htsmsg_add_u32(out, "retention", dvr_timerec_get_retention_days(dte));
1147 else
1148 htsmsg_add_u32(out, "retention", dvr_timerec_get_retention_days(dte) == DVR_RET_ONREMOVE ?
1149 dvr_timerec_get_removal_days(dte) : dvr_timerec_get_retention_days(dte));
1150
1151 htsmsg_add_u32(out, "removal", dvr_timerec_get_removal_days(dte));
1152 htsmsg_add_u32(out, "priority", dte->dte_pri);
1153 htsmsg_add_s32(out, "start", dte->dte_start);
1154 htsmsg_add_s32(out, "stop", dte->dte_stop);
1155
1156 if(dte->dte_title)
1157 htsmsg_add_str(out, "title", dte->dte_title);
1158 if(dte->dte_name)
1159 htsmsg_add_str(out, "name", dte->dte_name);
1160 if(dte->dte_directory)
1161 htsmsg_add_str(out, "directory", dte->dte_directory);
1162 if(dte->dte_owner)
1163 htsmsg_add_str(out, "owner", dte->dte_owner);
1164 if(dte->dte_creator)
1165 htsmsg_add_str(out, "creator", dte->dte_creator);
1166 if(dte->dte_channel)
1167 htsmsg_add_u32(out, "channel", channel_get_id(dte->dte_channel));
1168
1169 htsmsg_add_str(out, "method", method);
1170
1171 return out;
1172 }
1173
1174 /**
1175 *
1176 */
1177 static htsmsg_t *
htsp_build_event(epg_broadcast_t * e,const char * method,const char * lang,time_t update,htsp_connection_t * htsp)1178 htsp_build_event
1179 (epg_broadcast_t *e, const char *method, const char *lang, time_t update,
1180 htsp_connection_t *htsp )
1181 {
1182 htsmsg_t *out;
1183 epg_broadcast_t *n;
1184 dvr_entry_t *de;
1185 epg_genre_t *g;
1186 epg_episode_num_t epnum;
1187 const char *str;
1188 epg_episode_t *ee = e->episode;
1189
1190 /* Ignore? */
1191 if (update) {
1192 int ignore = 1;
1193 if (e->updated > update) ignore = 0;
1194 else if (e->serieslink && e->serieslink->updated > update) ignore = 0;
1195 else if (ee) {
1196 if (ee->updated > update) ignore = 0;
1197 else if (ee->brand && ee->brand->updated > update) ignore = 0;
1198 else if (ee->season && ee->season->updated > update) ignore = 0;
1199 }
1200 if (ignore) return NULL;
1201 }
1202
1203 out = htsmsg_create_map();
1204
1205 if (method)
1206 htsmsg_add_str(out, "method", method);
1207
1208 htsmsg_add_u32(out, "eventId", e->id);
1209 htsmsg_add_u32(out, "channelId", channel_get_id(e->channel));
1210 htsmsg_add_s64(out, "start", e->start);
1211 htsmsg_add_s64(out, "stop", e->stop);
1212 if ((str = epg_broadcast_get_title(e, lang)))
1213 htsmsg_add_str(out, "title", str);
1214 if ((str = epg_broadcast_get_subtitle(e, lang)))
1215 htsmsg_add_str(out, "subtitle", str);
1216 if ((str = epg_broadcast_get_description(e, lang))) {
1217 htsmsg_add_str(out, "description", str);
1218 if ((str = epg_broadcast_get_summary(e, lang)))
1219 htsmsg_add_str(out, "summary", str);
1220 } else if((str = epg_broadcast_get_summary(e, lang)))
1221 htsmsg_add_str(out, "description", str);
1222 if (e->serieslink) {
1223 htsmsg_add_u32(out, "serieslinkId", e->serieslink->id);
1224 if (e->serieslink->uri)
1225 htsmsg_add_str(out, "serieslinkUri", e->serieslink->uri);
1226 }
1227
1228 if (ee) {
1229 htsmsg_add_u32(out, "episodeId", ee->id);
1230 if (ee->uri && strncasecmp(ee->uri,"tvh://",6)) /* tvh:// uris are internal */
1231 htsmsg_add_str(out, "episodeUri", ee->uri);
1232 if (ee->brand)
1233 htsmsg_add_u32(out, "brandId", ee->brand->id);
1234 if (ee->season)
1235 htsmsg_add_u32(out, "seasonId", ee->season->id);
1236 if((g = LIST_FIRST(&ee->genre))) {
1237 uint32_t code = g->code;
1238 if (htsp->htsp_version < 6) code = (code >> 4) & 0xF;
1239 htsmsg_add_u32(out, "contentType", code);
1240 }
1241 if (ee->age_rating)
1242 htsmsg_add_u32(out, "ageRating", ee->age_rating);
1243 if (ee->star_rating)
1244 htsmsg_add_u32(out, "starRating", ee->star_rating);
1245 if (ee->first_aired)
1246 htsmsg_add_s64(out, "firstAired", ee->first_aired);
1247 epg_episode_get_epnum(ee, &epnum);
1248 if (epnum.s_num) {
1249 htsmsg_add_u32(out, "seasonNumber", epnum.s_num);
1250 if (epnum.s_cnt)
1251 htsmsg_add_u32(out, "seasonCount", epnum.s_cnt);
1252 }
1253 if (epnum.e_num) {
1254 htsmsg_add_u32(out, "episodeNumber", epnum.e_num);
1255 if (epnum.e_cnt)
1256 htsmsg_add_u32(out, "episodeCount", epnum.e_cnt);
1257 }
1258 if (epnum.p_num) {
1259 htsmsg_add_u32(out, "partNumber", epnum.p_num);
1260 if (epnum.p_cnt)
1261 htsmsg_add_u32(out, "partCount", epnum.p_cnt);
1262 }
1263 if (epnum.text)
1264 htsmsg_add_str(out, "episodeOnscreen", epnum.text);
1265 if (ee->image)
1266 htsmsg_add_str(out, "image", ee->image);
1267 }
1268
1269 if (e->channel) {
1270 LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link) {
1271 if (de->de_bcast != e)
1272 continue;
1273 if (dvr_entry_verify(de, htsp->htsp_granted_access, 1))
1274 continue;
1275 htsmsg_add_u32(out, "dvrId", idnode_get_short_uuid(&de->de_id));
1276 break;
1277 }
1278 }
1279
1280 if ((n = epg_broadcast_get_next(e)))
1281 htsmsg_add_u32(out, "nextEventId", n->id);
1282
1283 return out;
1284 }
1285
1286 /* **************************************************************************
1287 * Message handlers
1288 * *************************************************************************/
1289
1290 /**
1291 * Hello, for protocol version negotiation
1292 */
1293 static htsmsg_t *
htsp_method_hello(htsp_connection_t * htsp,htsmsg_t * in)1294 htsp_method_hello(htsp_connection_t *htsp, htsmsg_t *in)
1295 {
1296 htsmsg_t *r;
1297 uint32_t v;
1298 const char *name, *lang;
1299
1300 if(htsmsg_get_u32(in, "htspversion", &v))
1301 return htsp_error(htsp, N_("Invalid arguments"));
1302
1303 if((name = htsmsg_get_str(in, "clientname")) == NULL)
1304 return htsp_error(htsp, N_("Invalid arguments"));
1305
1306 r = htsmsg_create_map();
1307
1308 tvh_str_update(&htsp->htsp_clientname, htsmsg_get_str(in, "clientname"));
1309
1310 tvhinfo(LS_HTSP, "%s: Welcomed client software: %s (HTSPv%d)",
1311 htsp->htsp_logname, name, v);
1312
1313 htsmsg_add_u32(r, "htspversion", HTSP_PROTO_VERSION);
1314 htsmsg_add_str(r, "servername", config_get_server_name());
1315 htsmsg_add_str(r, "serverversion", tvheadend_version);
1316 htsmsg_add_bin(r, "challenge", htsp->htsp_challenge, 32);
1317 if (tvheadend_webroot)
1318 htsmsg_add_str(r, "webroot", tvheadend_webroot);
1319 lang = config_get_language();
1320 if (lang)
1321 htsmsg_add_str(r, "language", lang);
1322
1323 /* Capabilities */
1324 htsmsg_add_msg(r, "servercapability", tvheadend_capabilities_list(1));
1325 htsmsg_add_u32(r, "api_version", TVH_API_VERSION);
1326
1327 /* Set version to lowest num */
1328 htsp->htsp_version = MIN(HTSP_PROTO_VERSION, v);
1329
1330 htsp_update_logname(htsp);
1331 return r;
1332 }
1333
1334 /**
1335 * Try to authenticate
1336 */
1337 static htsmsg_t *
htsp_method_authenticate(htsp_connection_t * htsp,htsmsg_t * in)1338 htsp_method_authenticate(htsp_connection_t *htsp, htsmsg_t *in)
1339 {
1340 htsmsg_t *r = htsmsg_create_map();
1341
1342 if(!(htsp->htsp_granted_access->aa_rights & HTSP_PRIV_MASK))
1343 htsmsg_add_u32(r, "noaccess", 1);
1344 else if (htsp->htsp_version > 25) {
1345 htsmsg_add_u32(r, "admin", htsp->htsp_granted_access->aa_rights & ACCESS_ADMIN ? 1 : 0);
1346 htsmsg_add_u32(r, "streaming", htsp->htsp_granted_access->aa_rights & ACCESS_HTSP_STREAMING ? 1 : 0);
1347 htsmsg_add_u32(r, "dvr", htsp->htsp_granted_access->aa_rights & ACCESS_HTSP_RECORDER ? 1 : 0);
1348 htsmsg_add_u32(r, "faileddvr", htsp->htsp_granted_access->aa_rights & ACCESS_FAILED_RECORDER ? 1 : 0);
1349 htsmsg_add_u32(r, "anonymous", htsp->htsp_granted_access->aa_rights & ACCESS_HTSP_ANONYMIZE ? 1 : 0);
1350 htsmsg_add_u32(r, "limitall", htsp->htsp_granted_access->aa_conn_limit);
1351 htsmsg_add_u32(r, "limitdvr", htsp->htsp_granted_access->aa_conn_limit_dvr);
1352 htsmsg_add_u32(r, "limitstreaming", htsp->htsp_granted_access->aa_conn_limit_streaming);
1353 htsmsg_add_u32(r, "uilevel", htsp->htsp_granted_access->aa_uilevel == UILEVEL_DEFAULT ?
1354 config.uilevel : htsp->htsp_granted_access->aa_uilevel);
1355 htsmsg_add_str(r, "uilanguage", htsp->htsp_granted_access->aa_lang_ui ?
1356 htsp->htsp_granted_access->aa_lang_ui : (config.language_ui ? config.language_ui : ""));
1357 }
1358
1359 return r;
1360 }
1361
1362 /**
1363 * Try to authenticate
1364 */
1365 static htsmsg_t *
htsp_method_api(htsp_connection_t * htsp,htsmsg_t * in)1366 htsp_method_api(htsp_connection_t *htsp, htsmsg_t *in)
1367 {
1368 htsmsg_t *resp = NULL, *ret = htsmsg_create_map();
1369 htsmsg_t *args, *args2 = NULL;
1370 const char *remain;
1371 int r;
1372
1373 pthread_mutex_unlock(&global_lock);
1374
1375 args = htsmsg_get_map(in, "args");
1376 remain = htsmsg_get_str(in, "path");
1377
1378 if (args == NULL)
1379 args = args2 = htsmsg_create_map();
1380
1381 /* Call */
1382 r = api_exec(htsp->htsp_granted_access, remain, args, &resp);
1383
1384 /* Convert error */
1385 if (r) {
1386 switch (r) {
1387 case EPERM:
1388 case EACCES:
1389 htsmsg_add_u32(ret, "noaccess", 1);
1390 break;
1391 case ENOENT:
1392 case ENOSYS:
1393 break;
1394 default:
1395 htsmsg_destroy(args2);
1396 htsmsg_destroy(ret);
1397 return htsp_error(htsp, N_("Bad request"));
1398 }
1399 } else if (resp) {
1400 /* Output response */
1401 htsmsg_add_msg(ret, "response", resp);
1402 }
1403
1404 htsmsg_destroy(args2);
1405
1406 pthread_mutex_lock(&global_lock);
1407 return ret;
1408 }
1409
1410 /**
1411 * Get total and free disk space on configured path
1412 */
1413 static htsmsg_t *
htsp_method_getDiskSpace(htsp_connection_t * htsp,htsmsg_t * in)1414 htsp_method_getDiskSpace(htsp_connection_t *htsp, htsmsg_t *in)
1415 {
1416 htsmsg_t *out;
1417 int64_t bfree, bused, btotal;
1418
1419 if (dvr_get_disk_space(&bfree, &bused, &btotal))
1420 return htsp_error(htsp, N_("Unable to stat path"));
1421
1422 out = htsmsg_create_map();
1423 htsmsg_add_s64(out, "freediskspace", bfree);
1424 htsmsg_add_s64(out, "useddiskspace", bused);
1425 htsmsg_add_s64(out, "totaldiskspace", btotal);
1426 return out;
1427 }
1428
1429 /**
1430 * Get system time and diff to GMT
1431 */
1432 static htsmsg_t *
htsp_method_getSysTime(htsp_connection_t * htsp,htsmsg_t * in)1433 htsp_method_getSysTime(htsp_connection_t *htsp, htsmsg_t *in)
1434 {
1435 htsmsg_t *out;
1436 struct timeval tv;
1437 struct timezone tz;
1438 int tz_offset;
1439 struct tm serverLocalTime;
1440
1441 if(gettimeofday(&tv, &tz) == -1)
1442 return htsp_error(htsp, N_("Unable to get system time"));
1443
1444 if (!localtime_r(&tv.tv_sec, &serverLocalTime))
1445 return htsp_error(htsp, N_("Unable to get system local time"));
1446 #if defined(HAS_GMTOFF)
1447 tz_offset = - serverLocalTime.tm_gmtoff / (60);
1448 #else
1449 // NB: This will be a day out when GMT offsets >= 13hrs or <11 hrs apply
1450 struct tm serverGmTime;
1451 if (!gmtime_r(&tv.tv_sec, &serverGmTime))
1452 return htsp_error(htsp, N_("Unable to get system UTC time"));
1453 tz_offset = (serverGmTime.tm_hour - serverLocalTime.tm_hour) * 60;
1454 tz_offset += serverGmTime.tm_min - serverLocalTime.tm_min;
1455 if (tz_offset > 11 * 60)
1456 tz_offset -= 24 * 60;
1457 if (tz_offset <= -13 * 60)
1458 tz_offset += 24 * 60;
1459 #endif
1460
1461 out = htsmsg_create_map();
1462 htsmsg_add_s32(out, "time", tv.tv_sec);
1463 htsmsg_add_s32(out, "timezone", tz_offset/60);
1464 htsmsg_add_s32(out, "gmtoffset", -tz_offset);
1465 return out;
1466 }
1467
1468 /**
1469 * Switch the HTSP connection into async mode
1470 */
1471 static htsmsg_t *
htsp_method_async(htsp_connection_t * htsp,htsmsg_t * in)1472 htsp_method_async(htsp_connection_t *htsp, htsmsg_t *in)
1473 {
1474 channel_t *ch;
1475 channel_tag_t *ct;
1476 dvr_entry_t *de;
1477 dvr_autorec_entry_t *dae;
1478 dvr_timerec_entry_t *dte;
1479 htsmsg_t *m;
1480 uint32_t epg = 0;
1481 int64_t lastUpdate = -1;
1482 int64_t epgMaxTime = 0;
1483 const char *lang;
1484
1485 /* Get optional flags, allow updating them if already in async mode */
1486 if (htsmsg_get_u32(in, "epg", &epg))
1487 epg = (htsp->htsp_async_mode & HTSP_ASYNC_EPG) ? 1 : 0;
1488 if (!htsmsg_get_s64(in, "lastUpdate", &lastUpdate)) // 0 = never
1489 htsp->htsp_epg_lastupdate = lastUpdate;
1490 else if (htsp->htsp_async_mode & HTSP_ASYNC_EPG)
1491 lastUpdate = htsp->htsp_epg_lastupdate;
1492 if (!htsmsg_get_s64(in, "epgMaxTime", &epgMaxTime)) { // 0 = unlimited window
1493 if (htsp->htsp_async_mode & HTSP_ASYNC_EPG) {
1494 /* Only allow to change the window in the correct range */
1495 if (htsp->htsp_epg_window && epgMaxTime > htsp->htsp_epg_lastupdate)
1496 htsp->htsp_epg_window = epgMaxTime-gclk();
1497 } else if (epgMaxTime > gclk()) {
1498 htsp->htsp_epg_window = epgMaxTime-gclk();
1499 } else {
1500 htsp->htsp_epg_window = 0;
1501 }
1502 if (htsp->htsp_epg_window)
1503 htsp->htsp_epg_window = MAX(htsp->htsp_epg_window, 600);
1504 }
1505 if ((lang = htsmsg_get_str(in, "language")) != NULL) {
1506 if (lang[0]) {
1507 htsp->htsp_language = strdup(lang);
1508 } else {
1509 free(htsp->htsp_language);
1510 htsp->htsp_language = NULL;
1511 }
1512 }
1513
1514 /* First, just OK the async request */
1515 htsp_reply(htsp, in, htsmsg_create_map());
1516
1517 /* Set epg */
1518 if(epg)
1519 htsp->htsp_async_mode |= HTSP_ASYNC_EPG;
1520 else
1521 htsp->htsp_async_mode &= ~HTSP_ASYNC_EPG;
1522
1523 if(htsp->htsp_async_mode & HTSP_ASYNC_ON) {
1524 /* Sync epg on demand */
1525 if (epg)
1526 htsp_epg_send_waiting(htsp, lastUpdate);
1527 return NULL;
1528 }
1529
1530 htsp->htsp_async_mode |= HTSP_ASYNC_ON;
1531
1532 /* Send all enabled and external tags */
1533 TAILQ_FOREACH(ct, &channel_tags, ct_link)
1534 if(channel_tag_access(ct, htsp->htsp_granted_access, 0))
1535 htsp_send_message(htsp, htsp_build_tag(ct, "tagAdd", 0), NULL);
1536
1537 /* Send all channels */
1538 CHANNEL_FOREACH(ch)
1539 if (htsp_user_access_channel(htsp,ch))
1540 htsp_send_message(htsp, htsp_build_channel(ch, "channelAdd", htsp), NULL);
1541
1542 /* Send all enabled and external tags (now with channel mappings) */
1543 TAILQ_FOREACH(ct, &channel_tags, ct_link)
1544 if(channel_tag_access(ct, htsp->htsp_granted_access, 0))
1545 htsp_send_message(htsp, htsp_build_tag(ct, "tagUpdate", 1), NULL);
1546
1547 /* Send all autorecs */
1548 TAILQ_FOREACH(dae, &autorec_entries, dae_link)
1549 if (!dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 1))
1550 htsp_send_message(htsp, htsp_build_autorecentry(htsp, dae, "autorecEntryAdd"), NULL);
1551
1552 /* Send all timerecs */
1553 TAILQ_FOREACH(dte, &timerec_entries, dte_link)
1554 if (!dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 1))
1555 htsp_send_message(htsp, htsp_build_timerecentry(htsp, dte, "timerecEntryAdd"), NULL);
1556
1557 /* Send all DVR entries */
1558 LIST_FOREACH(de, &dvrentries, de_global_link)
1559 if (!dvr_entry_verify(de, htsp->htsp_granted_access, 1))
1560 htsp_send_message(htsp, htsp_build_dvrentry(htsp, de, "dvrEntryAdd", htsp->htsp_language, 0), NULL);
1561
1562 /* Send EPG updates */
1563 if (epg)
1564 htsp_epg_send_waiting(htsp, -1);
1565
1566 /* Notify that initial sync has been completed */
1567 m = htsmsg_create_map();
1568 htsmsg_add_str(m, "method", "initialSyncCompleted");
1569 htsp_send_message(htsp, m, NULL);
1570
1571 /* Insert in list so it will get all updates */
1572 LIST_INSERT_HEAD(&htsp_async_connections, htsp, htsp_async_link);
1573
1574 return NULL;
1575 }
1576
1577 /**
1578 * Get information about the given event
1579 */
1580 static htsmsg_t *
htsp_method_getChannel(htsp_connection_t * htsp,htsmsg_t * in)1581 htsp_method_getChannel(htsp_connection_t *htsp, htsmsg_t *in)
1582 {
1583 uint32_t channelId;
1584 channel_t *ch = NULL;
1585
1586 if (htsmsg_get_u32(in, "channelId", &channelId))
1587 return htsp_error(htsp, N_("Invalid arguments"));
1588 if (!(ch = channel_find_by_id(channelId)))
1589 return htsp_error(htsp, N_("Channel does not exist"));
1590
1591 return htsp_build_channel(ch, NULL, htsp);
1592 }
1593
1594 /**
1595 * Get information about the given event
1596 */
1597 static htsmsg_t *
htsp_method_getEvent(htsp_connection_t * htsp,htsmsg_t * in)1598 htsp_method_getEvent(htsp_connection_t *htsp, htsmsg_t *in)
1599 {
1600 uint32_t eventId;
1601 epg_broadcast_t *e;
1602 const char *lang;
1603
1604 if(htsmsg_get_u32(in, "eventId", &eventId))
1605 return htsp_error(htsp, N_("Invalid arguments"));
1606 lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
1607
1608 if((e = epg_broadcast_find_by_id(eventId)) == NULL)
1609 return htsp_error(htsp, N_("Event does not exist"));
1610
1611 return htsp_build_event(e, NULL, lang, 0, htsp);
1612 }
1613
1614 /**
1615 * Get information about the given event +
1616 * n following events
1617 */
1618 static htsmsg_t *
htsp_method_getEvents(htsp_connection_t * htsp,htsmsg_t * in)1619 htsp_method_getEvents(htsp_connection_t *htsp, htsmsg_t *in)
1620 {
1621 uint32_t u32, numFollowing;
1622 int64_t maxTime = 0;
1623 htsmsg_t *out, *events;
1624 epg_broadcast_t *e = NULL;
1625 channel_t *ch = NULL;
1626 const char *lang;
1627
1628 /* Optional fields */
1629 if (!htsmsg_get_u32(in, "channelId", &u32))
1630 if (!(ch = channel_find_by_id(u32)))
1631 return htsp_error(htsp, N_("Channel does not exist"));
1632 if (!htsmsg_get_u32(in, "eventId", &u32))
1633 if (!(e = epg_broadcast_find_by_id(u32)))
1634 return htsp_error(htsp, N_("Event does not exist"));
1635
1636 /* Check access */
1637
1638 numFollowing = htsmsg_get_u32_or_default(in, "numFollowing", 0);
1639 maxTime = htsmsg_get_s64_or_default(in, "maxTime", 0);
1640 lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
1641
1642 /* Use event as starting point */
1643 if (e || ch) {
1644
1645 if (!e) e = ch->ch_epg_now ?: ch->ch_epg_next;
1646
1647 if (e && !htsp_user_access_channel(htsp, e->channel))
1648 return htsp_error(htsp, N_("User does not have access"));
1649
1650 /* Output */
1651 events = htsmsg_create_list();
1652 while (e) {
1653 if (maxTime && e->start > maxTime) break;
1654 htsmsg_add_msg(events, NULL, htsp_build_event(e, NULL, lang, 0, htsp));
1655 if (numFollowing == 1) break;
1656 if (numFollowing) numFollowing--;
1657 e = epg_broadcast_get_next(e);
1658 }
1659
1660 /* All channels */
1661 } else {
1662
1663 events = htsmsg_create_list();
1664 CHANNEL_FOREACH(ch) {
1665 int num = numFollowing;
1666 if (!htsp_user_access_channel(htsp, ch))
1667 continue;
1668 RB_FOREACH(e, &ch->ch_epg_schedule, sched_link) {
1669 if (maxTime && e->start > maxTime) break;
1670 htsmsg_add_msg(events, NULL, htsp_build_event(e, NULL, lang, 0, htsp));
1671 if (num == 1) break;
1672 if (num) num--;
1673 }
1674 }
1675
1676 }
1677
1678 /* Send */
1679 out = htsmsg_create_map();
1680 htsmsg_add_msg(out, "events", events);
1681 return out;
1682 }
1683
1684 /**
1685 *
1686 * do an epg query
1687 */
1688 static htsmsg_t *
htsp_method_epgQuery(htsp_connection_t * htsp,htsmsg_t * in)1689 htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
1690 {
1691 htsmsg_t *out, *array;
1692 const char *query;
1693 int i;
1694 uint32_t u32, full;
1695 channel_t *ch = NULL;
1696 channel_tag_t *ct = NULL;
1697 epg_query_t eq;
1698 const char *lang;
1699 int min_duration;
1700 int max_duration;
1701 char ubuf[UUID_HEX_SIZE];
1702
1703 /* Required */
1704 if( (query = htsmsg_get_str(in, "query")) == NULL )
1705 return htsp_error(htsp, N_("Invalid arguments"));
1706
1707 memset(&eq, 0, sizeof(eq));
1708
1709 if(htsmsg_get_bool_or_default(in, "fulltext", 0))
1710 eq.fulltext = 1;
1711 eq.stitle = strdup(query);
1712
1713 /* Optional */
1714 if(!(htsmsg_get_u32(in, "channelId", &u32))) {
1715 if (!(ch = channel_find_by_id(u32)))
1716 return htsp_error(htsp, N_("Channel does not exist"));
1717 else
1718 eq.channel = strdup(idnode_uuid_as_str(&ch->ch_id, ubuf));
1719 }
1720 if(!(htsmsg_get_u32(in, "tagId", &u32))) {
1721 if (!(ct = htsp_channel_tag_find_by_identifier(htsp, u32)))
1722 return htsp_error(htsp, N_("Channel tag does not exist"));
1723 else
1724 eq.channel_tag = strdup(idnode_uuid_as_str(&ct->ct_id, ubuf));
1725 }
1726 if (!htsmsg_get_u32(in, "contentType", &u32)) {
1727 if(htsp->htsp_version < 6) u32 <<= 4;
1728 eq.genre_count = 1;
1729 eq.genre = eq.genre_static;
1730 eq.genre[0] = u32;
1731 }
1732 lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
1733 eq.lang = lang ? strdup(lang) : NULL;
1734 full = htsmsg_get_u32_or_default(in, "full", 0);
1735
1736 min_duration = htsmsg_get_u32_or_default(in, "minduration", 0);
1737 max_duration = htsmsg_get_u32_or_default(in, "maxduration", INT_MAX);
1738 eq.duration.comp = EC_RG;
1739 eq.duration.val1 = min_duration;
1740 eq.duration.val2 = max_duration;
1741 tvhtrace(LS_HTSP, "min_duration %d and max_duration %d", min_duration, max_duration);
1742
1743 /* Check access */
1744 if (ch && !htsp_user_access_channel(htsp, ch))
1745 return htsp_error(htsp, N_("User does not have access"));
1746
1747 /* Query */
1748 epg_query(&eq, htsp->htsp_granted_access);
1749
1750 /* Create Reply */
1751 out = htsmsg_create_map();
1752 if( eq.entries ) {
1753 array = htsmsg_create_list();
1754 for(i = 0; i < eq.entries; ++i) {
1755 if (full)
1756 htsmsg_add_msg(array, NULL,
1757 htsp_build_event(eq.result[i], NULL, lang, 0, htsp));
1758 else
1759 htsmsg_add_u32(array, NULL, eq.result[i]->id);
1760 }
1761 htsmsg_add_msg(out, full ? "events" : "eventIds", array);
1762 }
1763
1764 epg_query_free(&eq);
1765
1766 return out;
1767 }
1768
1769 static htsmsg_t *
htsp_method_getEpgObject(htsp_connection_t * htsp,htsmsg_t * in)1770 htsp_method_getEpgObject(htsp_connection_t *htsp, htsmsg_t *in)
1771 {
1772 uint32_t id, u32;
1773 epg_object_type_t type;
1774 epg_object_t *eo;
1775 htsmsg_t *out;
1776
1777 // TODO: should really block access based on channel, but actually
1778 // that's really hard to do here!
1779
1780 /* Required fields */
1781 if (htsmsg_get_u32(in, "id", &id))
1782 return htsp_error(htsp, N_("Invalid arguments"));
1783
1784 /* Optional fields */
1785 if (!htsmsg_get_u32(in, "type", &u32) && (u32 <= EPG_TYPEMAX))
1786 type = u32;
1787 else
1788 type = EPG_UNDEF;
1789
1790 /* Get object */
1791 if (!(eo = epg_object_find_by_id(id, type)))
1792 return htsp_error(htsp, N_("Invalid EPG object request"));
1793
1794 /* Serialize */
1795 if (!(out = epg_object_serialize(eo)))
1796 return htsp_error(htsp, N_("Internal error"));
1797
1798 return out;
1799 }
1800
1801 /**
1802 *
1803 */
1804 static htsmsg_t *
htsp_method_getDvrConfigs(htsp_connection_t * htsp,htsmsg_t * in)1805 htsp_method_getDvrConfigs(htsp_connection_t *htsp, htsmsg_t *in)
1806 {
1807 htsmsg_t *out, *l, *c;
1808 htsmsg_field_t *f;
1809 dvr_config_t *cfg;
1810 const char *uuid, *s;
1811 char ubuf[UUID_HEX_SIZE];
1812
1813 l = htsmsg_create_list();
1814
1815 LIST_FOREACH(cfg, &dvrconfigs, config_link)
1816 if (cfg->dvr_enabled) {
1817 uuid = idnode_uuid_as_str(&cfg->dvr_id, ubuf);
1818 if (htsp->htsp_granted_access->aa_dvrcfgs) {
1819 HTSMSG_FOREACH(f, htsp->htsp_granted_access->aa_dvrcfgs) {
1820 if (!(s = htsmsg_field_get_str(f)))
1821 continue;
1822 if (strcmp(s, uuid) == 0)
1823 break;
1824 }
1825 if (f == NULL)
1826 continue;
1827 }
1828 c = htsmsg_create_map();
1829 htsmsg_add_str(c, "uuid", uuid);
1830 htsmsg_add_str(c, "name", cfg->dvr_config_name ?: "");
1831 htsmsg_add_str(c, "comment", cfg->dvr_comment ?: "");
1832 htsmsg_add_msg(l, NULL, c);
1833 }
1834
1835 out = htsmsg_create_map();
1836
1837 htsmsg_add_msg(out, "dvrconfigs", l);
1838
1839 return out;
1840 }
1841
1842 /**
1843 * add a Dvrentry
1844 */
1845 static htsmsg_t *
htsp_method_addDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)1846 htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
1847 {
1848 htsmsg_t *out;
1849 uint32_t eventid;
1850 epg_broadcast_t *e = NULL;
1851 dvr_entry_t *de;
1852 dvr_entry_sched_state_t dvr_status;
1853 const char *dvr_config_name, *title, *desc, *subtitle, *lang, *comment;
1854 int64_t start, stop, start_extra, stop_extra;
1855 uint32_t u32, priority, retention, removal;
1856 channel_t *ch = NULL;
1857 int enabled;
1858
1859 /* Options */
1860 enabled = htsmsg_get_u32_or_default(in, "enabled", 1);
1861 dvr_config_name = htsp_dvr_config_name(htsp, htsmsg_get_str(in, "configName"));
1862 if(htsmsg_get_s64(in, "startExtra", &start_extra))
1863 start_extra = 0;
1864 if(htsmsg_get_s64(in, "stopExtra", &stop_extra))
1865 stop_extra = 0;
1866 if(!htsmsg_get_u32(in, "channelId", &u32))
1867 ch = channel_find_by_id(u32);
1868 if(!htsmsg_get_u32(in, "eventId", &eventid)) {
1869 e = epg_broadcast_find_by_id(eventid);
1870 ch = e ? e->channel : ch;
1871 }
1872 if(htsmsg_get_u32(in, "priority", &priority))
1873 priority = DVR_PRIO_NORMAL;
1874 if(htsmsg_get_u32(in, "retention", &retention))
1875 retention = DVR_RET_REM_DVRCONFIG;
1876 if(htsmsg_get_u32(in, "removal", &removal))
1877 removal = DVR_RET_REM_DVRCONFIG;
1878 comment = htsmsg_get_str(in, "comment");
1879 if (!(lang = htsmsg_get_str(in, "language")))
1880 lang = htsp->htsp_language;
1881
1882 /* Check access */
1883 if (!htsp_user_access_channel(htsp, ch))
1884 return htsp_error(htsp, N_("User does not have access"));
1885 if (!ch)
1886 return htsp_error(htsp, N_("Channel does not exist"));
1887
1888 /* Manual timer */
1889 if (!e) {
1890
1891 /* Required attributes */
1892 if (htsmsg_get_s64(in, "start", &start) ||
1893 htsmsg_get_s64(in, "stop", &stop) ||
1894 !(title = htsmsg_get_str(in, "title")))
1895 return htsp_error(htsp, N_("Invalid arguments"));
1896
1897 /* Optional attributes */
1898 if (!(subtitle = htsmsg_get_str(in, "subtitle")))
1899 subtitle = "";
1900
1901 /* Optional attributes */
1902 if (!(desc = htsmsg_get_str(in, "description")))
1903 desc = "";
1904
1905 /* Create the dvr entry */
1906 de = dvr_entry_create_htsp(enabled, dvr_config_name, ch, start, stop,
1907 start_extra, stop_extra,
1908 title, subtitle, desc, lang, 0,
1909 htsp->htsp_granted_access->aa_username,
1910 htsp->htsp_granted_access->aa_representative,
1911 NULL, priority, retention, removal, comment);
1912
1913 /* Event timer */
1914 } else {
1915
1916 de = dvr_entry_create_by_event(enabled, dvr_config_name, e,
1917 start_extra, stop_extra,
1918 htsp->htsp_granted_access->aa_username,
1919 htsp->htsp_granted_access->aa_representative,
1920 NULL, priority, retention, removal, comment);
1921
1922 }
1923
1924 dvr_status = de != NULL ? de->de_sched_state : DVR_NOSTATE;
1925
1926 /* Create response */
1927 out = htsmsg_create_map();
1928
1929 switch(dvr_status) {
1930 case DVR_SCHEDULED:
1931 case DVR_RECORDING:
1932 case DVR_MISSED_TIME:
1933 case DVR_COMPLETED:
1934 htsmsg_add_u32(out, "id", idnode_get_short_uuid(&de->de_id));
1935 htsmsg_add_u32(out, "success", 1);
1936 break;
1937 case DVR_NOSTATE:
1938 htsmsg_add_str(out, "error", "Could not add dvrEntry");
1939 htsmsg_add_u32(out, "success", 0);
1940 break;
1941 }
1942 return out;
1943 }
1944
1945 /**
1946 * Find DVR entry
1947 */
1948 static dvr_entry_t *
htsp_findDvrEntry(htsp_connection_t * htsp,htsmsg_t * in,htsmsg_t ** out,int readonly)1949 htsp_findDvrEntry(htsp_connection_t *htsp, htsmsg_t *in, htsmsg_t **out, int readonly)
1950 {
1951 uint32_t dvrEntryId;
1952 dvr_entry_t *de;
1953
1954 if(htsmsg_get_u32(in, "id", &dvrEntryId)) {
1955 *out = htsp_error(htsp, N_("Invalid arguments"));
1956 return NULL;
1957 }
1958
1959 if((de = dvr_entry_find_by_id(dvrEntryId)) == NULL) {
1960 *out = htsp_error(htsp, N_("DVR entry not found"));
1961 return NULL;
1962 }
1963
1964 if(dvr_entry_verify(de, htsp->htsp_granted_access, readonly)) {
1965 *out = htsp_error(htsp, N_("User does not have access"));
1966 return NULL;
1967 }
1968
1969 return de;
1970 }
1971
1972
1973 /**
1974 * update a Dvrentry
1975 */
1976 static htsmsg_t *
htsp_method_updateDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)1977 htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
1978 {
1979 htsmsg_t *out = NULL;
1980 uint32_t u32;
1981 dvr_entry_t *de;
1982 time_t start, stop, start_extra, stop_extra, priority;
1983 const char *dvr_config_name, *title, *subtitle, *desc, *lang;
1984 channel_t *channel = NULL;
1985 int enabled, retention, removal, playcount = -1, playposition = -1;
1986
1987 de = htsp_findDvrEntry(htsp, in, &out, 0);
1988 if (de == NULL)
1989 return out;
1990
1991 if(!htsmsg_get_u32(in, "channelId", &u32))
1992 channel = channel_find_by_id(u32);
1993 if (!channel)
1994 channel = de->de_channel;
1995
1996 /* Check access new channel */
1997 if (channel && !htsp_user_access_channel(htsp, channel))
1998 return htsp_error(htsp, N_("User does not have access to channel"));
1999
2000 enabled = htsmsg_get_s64_or_default(in, "enabled", -1);
2001 dvr_config_name = htsp_dvr_config_name(htsp, htsmsg_get_str(in, "configName"));
2002 start = htsmsg_get_s64_or_default(in, "start", 0);
2003 stop = htsmsg_get_s64_or_default(in, "stop", 0);
2004 start_extra = htsmsg_get_s64_or_default(in, "startExtra", 0);
2005 stop_extra = htsmsg_get_s64_or_default(in, "stopExtra", 0);
2006 retention = htsmsg_get_u32_or_default(in, "retention", DVR_RET_REM_DVRCONFIG);
2007 removal = htsmsg_get_u32_or_default(in, "removal", DVR_RET_REM_DVRCONFIG);
2008 priority = htsmsg_get_u32_or_default(in, "priority", DVR_PRIO_NOTSET);
2009 title = htsmsg_get_str(in, "title");
2010 subtitle = htsmsg_get_str(in, "subtitle");
2011 desc = htsmsg_get_str(in, "description");
2012 lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
2013
2014 if(!htsmsg_get_u32(in, "playcount", &u32)) {
2015 if (u32 > INT_MAX)
2016 u32 = HTSP_DVR_PLAYCOUNT_INCR;
2017 switch (u32) {
2018 case HTSP_DVR_PLAYCOUNT_INCR:
2019 playcount = de->de_playcount + 1;
2020 break;
2021 case HTSP_DVR_PLAYCOUNT_SET:
2022 if (!de->de_playcount)
2023 playcount = 1;
2024 break;
2025 case HTSP_DVR_PLAYCOUNT_RESET:
2026 playcount = 0;
2027 break;
2028 case HTSP_DVR_PLAYCOUNT_KEEP:
2029 break;
2030 default:
2031 playcount = u32;
2032 break;
2033 }
2034 }
2035 if(!htsmsg_get_u32(in, "playposition", &u32))
2036 playposition = u32 > INT_MAX ? INT_MAX : u32;
2037
2038 de = dvr_entry_update(de, enabled, dvr_config_name, channel, title, subtitle,
2039 desc, lang, start, stop, start_extra, stop_extra,
2040 priority, retention, removal, playcount, playposition);
2041
2042 return htsp_success();
2043 }
2044
2045 /**
2046 * stop a Dvrentry
2047 */
2048 static htsmsg_t *
htsp_method_stopDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)2049 htsp_method_stopDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
2050 {
2051 htsmsg_t *out = NULL;
2052 dvr_entry_t *de;
2053
2054 de = htsp_findDvrEntry(htsp, in, &out, 0);
2055 if (de == NULL)
2056 return out;
2057
2058 dvr_entry_stop(de);
2059
2060 return htsp_success();
2061 }
2062
2063 /**
2064 * cancel a Dvrentry
2065 */
2066 static htsmsg_t *
htsp_method_cancelDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)2067 htsp_method_cancelDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
2068 {
2069 htsmsg_t *out = NULL;
2070 dvr_entry_t *de;
2071
2072 de = htsp_findDvrEntry(htsp, in, &out, 0);
2073 if (de == NULL)
2074 return out;
2075
2076 dvr_entry_cancel(de, 0);
2077
2078 return htsp_success();
2079 }
2080
2081 /**
2082 * delete a Dvrentry
2083 */
2084 static htsmsg_t *
htsp_method_deleteDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)2085 htsp_method_deleteDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
2086 {
2087 htsmsg_t *out;
2088 dvr_entry_t *de;
2089
2090 de = htsp_findDvrEntry(htsp, in, &out, 0);
2091 if (de == NULL)
2092 return out;
2093
2094 dvr_entry_cancel_remove(de, 0);
2095
2096 return htsp_success();
2097 }
2098
2099 /**
2100 * add a Dvr autorec entry
2101 */
2102 static htsmsg_t *
htsp_method_addAutorecEntry(htsp_connection_t * htsp,htsmsg_t * in)2103 htsp_method_addAutorecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2104 {
2105 htsmsg_t *out;
2106 dvr_autorec_entry_t *dae;
2107 const char *str;
2108 uint32_t u32;
2109 int64_t s64;
2110 channel_t *ch = NULL;
2111 char ubuf[UUID_HEX_SIZE];
2112
2113 /* Options */
2114 if(!(str = htsmsg_get_str(in, "title")))
2115 return htsp_error(htsp, N_("Invalid arguments"));
2116
2117 if (htsp->htsp_version > 24) {
2118 if (!htsmsg_get_s64(in, "channelId", &s64)) { // not sending or -1 = any channel
2119 if (s64 >= 0)
2120 ch = channel_find_by_id((uint32_t)s64);
2121 }
2122 }
2123 else {
2124 if (!htsmsg_get_u32(in, "channelId", &u32)) // not sending = any channel
2125 ch = channel_find_by_id(u32);
2126 }
2127
2128 /* Check access channel */
2129 if (ch && !htsp_user_access_channel(htsp, ch))
2130 return htsp_error(htsp, N_("User does not have access"));
2131
2132 /* Create autorec config from htsp and add */
2133 dae = dvr_autorec_create_htsp(htsp_serierec_convert(htsp, in, ch, 1, 1));
2134
2135 /* create response */
2136 out = htsmsg_create_map();
2137
2138 if (dae) {
2139 htsmsg_add_str(out, "id", idnode_uuid_as_str(&dae->dae_id, ubuf));
2140 htsmsg_add_u32(out, "success", 1);
2141 }
2142 else {
2143 htsmsg_add_str(out, "error", "Could not add autorec entry");
2144 htsmsg_add_u32(out, "success", 0);
2145 }
2146 return out;
2147 }
2148
2149
2150 /**
2151 * update a Dvr autorec entry
2152 */
2153 static htsmsg_t *
htsp_method_updateAutorecEntry(htsp_connection_t * htsp,htsmsg_t * in)2154 htsp_method_updateAutorecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2155 {
2156 const char *daeId;
2157 dvr_autorec_entry_t *dae;
2158 int64_t s64;
2159 channel_t *ch = NULL;
2160
2161 if (!(daeId = htsmsg_get_str(in, "id")))
2162 return htsp_error(htsp, N_("Invalid arguments"));
2163
2164 if((dae = dvr_autorec_find_by_uuid(daeId)) == NULL)
2165 return htsp_error(htsp, N_("Automatic schedule entry not found"));
2166
2167 if(dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 0))
2168 return htsp_error(htsp, N_("User does not have access"));
2169
2170 /* Do we have a channel? No = keep old one */
2171 if (!htsmsg_get_s64(in, "channelId", &s64)) //s64 -> -1 = any channel
2172 {
2173 if (s64 >= 0)
2174 ch = channel_find_by_id((uint32_t)s64);
2175
2176 /* Check access new channel */
2177 if (ch && !htsp_user_access_channel(htsp, ch))
2178 return htsp_error(htsp, N_("User does not have access"));
2179 }
2180
2181 /* Update autorec config from htsp and save */
2182 dvr_autorec_update_htsp(dae, htsp_serierec_convert(htsp, in, ch, 1, 0));
2183
2184 return htsp_success();
2185 }
2186
2187
2188 /**
2189 * delete a Dvr autorec entry
2190 */
2191 static htsmsg_t *
htsp_method_deleteAutorecEntry(htsp_connection_t * htsp,htsmsg_t * in)2192 htsp_method_deleteAutorecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2193 {
2194 const char *daeId;
2195 dvr_autorec_entry_t *dae;
2196
2197 if (!(daeId = htsmsg_get_str(in, "id")))
2198 return htsp_error(htsp, N_("Invalid arguments"));
2199
2200 if((dae = dvr_autorec_find_by_uuid(daeId)) == NULL)
2201 return htsp_error(htsp, N_("Automatic schedule entry not found"));
2202
2203 if(dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 0))
2204 return htsp_error(htsp, N_("User does not have access"));
2205
2206 autorec_destroy_by_id(daeId, 1);
2207
2208 return htsp_success();
2209 }
2210
2211 /**
2212 * add a Dvr timerec entry
2213 */
2214 static htsmsg_t *
htsp_method_addTimerecEntry(htsp_connection_t * htsp,htsmsg_t * in)2215 htsp_method_addTimerecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2216 {
2217 htsmsg_t *out;
2218 dvr_timerec_entry_t *dte;
2219 const char *str;
2220 channel_t *ch = NULL;
2221 uint32_t u32;
2222 int64_t s64;
2223 char ubuf[UUID_HEX_SIZE];
2224
2225 /* Options */
2226 if(!(str = htsmsg_get_str(in, "title")))
2227 return htsp_error(htsp, N_("Invalid arguments"));
2228
2229 if (htsp->htsp_version > 24) {
2230 if (!htsmsg_get_s64(in, "channelId", &s64)) { // not sending or -1 = any channel
2231 if (s64 >= 0)
2232 ch = channel_find_by_id((uint32_t)s64);
2233 }
2234 }
2235 else {
2236 if (!htsmsg_get_u32(in, "channelId", &u32)) // not sending = any channel
2237 ch = channel_find_by_id(u32);
2238 }
2239
2240 /* Check access channel */
2241 if (ch && !htsp_user_access_channel(htsp, ch))
2242 return htsp_error(htsp, N_("User does not have access"));
2243
2244 /* Create timerec config from htsp and add */
2245 dte = dvr_timerec_create_htsp(htsp_serierec_convert(htsp, in, ch, 0, 1));
2246
2247 /* create response */
2248 out = htsmsg_create_map();
2249
2250 if (dte) {
2251 htsmsg_add_str(out, "id", idnode_uuid_as_str(&dte->dte_id, ubuf));
2252 htsmsg_add_u32(out, "success", 1);
2253 }
2254 else {
2255 htsmsg_add_str(out, "error", "Could not add timerec entry");
2256 htsmsg_add_u32(out, "success", 0);
2257 }
2258 return out;
2259 }
2260
2261 /**
2262 * update a Dvr timerec entry
2263 */
2264 static htsmsg_t *
htsp_method_updateTimerecEntry(htsp_connection_t * htsp,htsmsg_t * in)2265 htsp_method_updateTimerecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2266 {
2267 const char *dteId;
2268 dvr_timerec_entry_t *dte;
2269 int64_t s64;
2270 channel_t *ch = NULL;
2271
2272 if (!(dteId = htsmsg_get_str(in, "id")))
2273 return htsp_error(htsp, N_("Invalid arguments"));
2274
2275 if((dte = dvr_timerec_find_by_uuid(dteId)) == NULL)
2276 return htsp_error(htsp, N_("Automatic time scheduler entry not found"));
2277
2278 if(dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 0))
2279 return htsp_error(htsp, N_("User does not have access"));
2280
2281 /* Do we have a channel? No = keep old one */
2282 if (!htsmsg_get_s64(in, "channelId", &s64)) //s64 -> -1 = any channel
2283 {
2284 if (s64 >= 0)
2285 ch = channel_find_by_id((uint32_t)s64);
2286
2287 /* Check access new channel */
2288 if (ch && !htsp_user_access_channel(htsp, ch))
2289 return htsp_error(htsp, N_("User does not have access"));
2290 }
2291
2292 /* Update timerec config from htsp and save */
2293 dvr_timerec_update_htsp(dte, htsp_serierec_convert(htsp, in, ch, 0, 0));
2294
2295 return htsp_success();
2296 }
2297
2298 /**
2299 * delete a Dvr timerec entry
2300 */
2301 static htsmsg_t *
htsp_method_deleteTimerecEntry(htsp_connection_t * htsp,htsmsg_t * in)2302 htsp_method_deleteTimerecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2303 {
2304 const char *dteId;
2305 dvr_timerec_entry_t *dte;
2306
2307 if (!(dteId = htsmsg_get_str(in, "id")))
2308 return htsp_error(htsp, N_("Invalid arguments"));
2309
2310 if((dte = dvr_timerec_find_by_uuid(dteId)) == NULL)
2311 return htsp_error(htsp, N_("Automatic time scheduler entry not found"));
2312
2313 if(dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 0))
2314 return htsp_error(htsp, N_("User does not have access"));
2315
2316 timerec_destroy_by_id(dteId, 1);
2317
2318 return htsp_success();
2319 }
2320
2321 /**
2322 * Return cutpoint data for a recording (if present).
2323 *
2324 * Request message fields:
2325 * id u32 required DVR entry id
2326 *
2327 * Result message fields:
2328 * cutpoints msg[] optional List of cutpoint entries, if a file is
2329 * found and has some valid data.
2330 *
2331 * Cutpoint fields:
2332 * start u32 required Cut start time in ms.
2333 * end u32 required Cut end time in ms.
2334 * type u32 required Action type:
2335 * 0=Cut, 1=Mute, 2=Scene,
2336 * 3=Commercial break.
2337 **/
2338 static htsmsg_t *
htsp_method_getDvrCutpoints(htsp_connection_t * htsp,htsmsg_t * in)2339 htsp_method_getDvrCutpoints(htsp_connection_t *htsp, htsmsg_t *in)
2340 {
2341 uint32_t dvrEntryId;
2342 dvr_entry_t *de;
2343 if (htsmsg_get_u32(in, "id", &dvrEntryId))
2344 return htsp_error(htsp, N_("Invalid arguments"));
2345
2346 if((de = dvr_entry_find_by_id(dvrEntryId)) == NULL)
2347 return htsp_error(htsp, N_("DVR schedule not found"));
2348
2349 if(dvr_entry_verify(de, htsp->htsp_granted_access, 1))
2350 return htsp_error(htsp, N_("User does not have access"));
2351
2352 htsmsg_t *msg = htsmsg_create_map();
2353
2354 dvr_cutpoint_list_t *list = dvr_get_cutpoint_list(de);
2355
2356 if (list != NULL) {
2357 htsmsg_t *cutpoint_list = htsmsg_create_list();
2358 dvr_cutpoint_t *cp;
2359 TAILQ_FOREACH(cp, list, dc_link) {
2360 htsmsg_t *cutpoint = htsmsg_create_map();
2361 htsmsg_add_u32(cutpoint, "start", cp->dc_start_ms);
2362 htsmsg_add_u32(cutpoint, "end", cp->dc_end_ms);
2363 htsmsg_add_u32(cutpoint, "type", cp->dc_type);
2364
2365 htsmsg_add_msg(cutpoint_list, NULL, cutpoint);
2366 }
2367 htsmsg_add_msg(msg, "cutpoints", cutpoint_list);
2368 }
2369
2370 // Cleanup...
2371 dvr_cutpoint_list_destroy(list);
2372
2373 return msg;
2374 }
2375
2376 /**
2377 * Request a ticket for a http url pointing to a channel or dvr
2378 */
2379 static htsmsg_t *
htsp_method_getTicket(htsp_connection_t * htsp,htsmsg_t * in)2380 htsp_method_getTicket(htsp_connection_t *htsp, htsmsg_t *in)
2381 {
2382 htsmsg_t *out;
2383 uint32_t id;
2384 char path[255];
2385 const char *ticket = NULL;
2386 channel_t *ch;
2387 dvr_entry_t *de;
2388
2389 if(!htsmsg_get_u32(in, "channelId", &id)) {
2390 if (!(ch = channel_find_by_id(id)))
2391 return htsp_error(htsp, N_("Channel not found"));
2392 if (!htsp_user_access_channel(htsp, ch))
2393 return htsp_error(htsp, N_("User does not have access"));
2394
2395 snprintf(path, sizeof(path), "/stream/channelid/%d", id);
2396 ticket = access_ticket_create(path, htsp->htsp_granted_access);
2397 } else if(!htsmsg_get_u32(in, "dvrId", &id)) {
2398 if (!(de = dvr_entry_find_by_id(id)))
2399 return htsp_error(htsp, N_("DVR schedule does not exist"));
2400 if (!htsp_user_access_channel(htsp, de->de_channel))
2401 return htsp_error(htsp, N_("User does not have access"));
2402
2403 snprintf(path, sizeof(path), "/dvrfile/%d", id);
2404 ticket = access_ticket_create(path, htsp->htsp_granted_access);
2405 } else {
2406 return htsp_error(htsp, N_("Invalid arguments"));
2407 }
2408
2409 out = htsmsg_create_map();
2410
2411 htsmsg_add_str(out, "path", path);
2412 htsmsg_add_str(out, "ticket", ticket);
2413
2414 return out;
2415 }
2416
2417 /*
2418 *
2419 */
_bytes_out_cb(void * aux)2420 static void _bytes_out_cb(void *aux)
2421 {
2422 htsp_subscription_t *hs = aux;
2423 if (hs->hs_s) {
2424 subscription_add_bytes_out(hs->hs_s, atomic_exchange(&hs->hs_s_bytes_out, 0));
2425 mtimer_arm_rel(&hs->hs_s_bytes_out_timer, _bytes_out_cb, hs, ms2mono(200));
2426 }
2427 }
2428
2429 /**
2430 * Request subscription for a channel
2431 */
2432 static htsmsg_t *
htsp_method_subscribe(htsp_connection_t * htsp,htsmsg_t * in)2433 htsp_method_subscribe(htsp_connection_t *htsp, htsmsg_t *in)
2434 {
2435 uint32_t chid, sid, weight, req90khz, timeshiftPeriod = 0;
2436 const char *str, *profile_id;
2437 channel_t *ch;
2438 htsp_subscription_t *hs;
2439 profile_t *pro;
2440
2441 if(htsmsg_get_u32(in, "subscriptionId", &sid))
2442 return htsp_error(htsp, N_("Invalid arguments"));
2443
2444 if(!htsmsg_get_u32(in, "channelId", &chid)) {
2445 if((ch = channel_find_by_id(chid)) == NULL)
2446 return htsp_error(htsp, N_("Channel does not exist"));
2447 } else if((str = htsmsg_get_str(in, "channelName")) != NULL) {
2448 if((ch = channel_find_by_name(str)) == NULL)
2449 return htsp_error(htsp, N_("Channel does not exist"));
2450 } else {
2451 return htsp_error(htsp, N_("Invalid arguments"));
2452 }
2453 if (!htsp_user_access_channel(htsp, ch))
2454 return htsp_error(htsp, N_("User does not have access"));
2455
2456 weight = htsmsg_get_u32_or_default(in, "weight", 0);
2457 req90khz = htsmsg_get_u32_or_default(in, "90khz", 0);
2458
2459 profile_id = htsmsg_get_str(in, "profile");
2460
2461 #if ENABLE_TIMESHIFT
2462 if (timeshift_conf.enabled) {
2463 timeshiftPeriod = htsmsg_get_u32_or_default(in, "timeshiftPeriod", 0);
2464 if (!timeshift_conf.unlimited_period)
2465 timeshiftPeriod = MIN(timeshiftPeriod, timeshift_conf.max_period * 60);
2466 }
2467 #endif
2468
2469 /* Initialize the HTSP subscription structure */
2470
2471 hs = calloc(1, sizeof(htsp_subscription_t));
2472
2473 hs->hs_htsp = htsp;
2474 hs->hs_90khz = req90khz;
2475 hs->hs_queue_depth = htsmsg_get_u32_or_default(in, "queueDepth",
2476 HTSP_DEFAULT_QUEUE_DEPTH);
2477 htsp_init_queue(&hs->hs_q, 0);
2478
2479 hs->hs_sid = sid;
2480 streaming_target_init(&hs->hs_input, &htsp_streaming_input_ops, hs, 0);
2481
2482 #if ENABLE_TIMESHIFT
2483 if (timeshiftPeriod != 0) {
2484 if (timeshiftPeriod == ~0)
2485 tvhdebug(LS_HTSP, "using timeshift buffer (unlimited)");
2486 else
2487 tvhdebug(LS_HTSP, "using timeshift buffer (%u mins)", timeshiftPeriod / 60);
2488 }
2489 #endif
2490
2491 pro = profile_find_by_list(htsp->htsp_granted_access->aa_profiles, profile_id,
2492 "htsp", SUBSCRIPTION_PACKET | SUBSCRIPTION_HTSP);
2493 profile_chain_init(&hs->hs_prch, pro, ch, 1);
2494 if (profile_chain_work(&hs->hs_prch, &hs->hs_input, timeshiftPeriod, 0)) {
2495 tvherror(LS_HTSP, "unable to create profile chain '%s'", profile_get_name(pro));
2496 profile_chain_close(&hs->hs_prch);
2497 free(hs);
2498 return htsp_error(htsp, N_("Stream setup error"));
2499 }
2500
2501 /*
2502 * We send the reply now to avoid the user getting the 'subscriptionStart'
2503 * async message before the reply to 'subscribe'.
2504 *
2505 * Send some optional boolean flags back to the subscriber so it can infer
2506 * if we support those
2507 *
2508 */
2509 htsmsg_t *rep = htsmsg_create_map();
2510 if(req90khz)
2511 htsmsg_add_u32(rep, "90khz", 1);
2512 if(hs->hs_prch.prch_sharer->prsh_tsfix)
2513 htsmsg_add_u32(rep, "normts", 1);
2514 if(hs->hs_s)
2515 htsmsg_add_u32(rep, "weight", hs->hs_s->ths_weight >= 0 ? hs->hs_s->ths_weight : 0);
2516
2517 #if ENABLE_TIMESHIFT
2518 if(timeshiftPeriod)
2519 htsmsg_add_u32(rep, "timeshiftPeriod", timeshiftPeriod);
2520 #endif
2521
2522 htsp_reply(htsp, in, rep);
2523
2524 /*
2525 * subscribe now...
2526 */
2527 LIST_INSERT_HEAD(&htsp->htsp_subscriptions, hs, hs_link);
2528
2529 tvhdebug(LS_HTSP, "%s - subscribe to %s using profile %s",
2530 htsp->htsp_logname, channel_get_name(ch), profile_get_name(pro));
2531 hs->hs_s = subscription_create_from_channel(&hs->hs_prch, NULL, weight,
2532 htsp->htsp_logname,
2533 SUBSCRIPTION_PACKET |
2534 SUBSCRIPTION_STREAMING,
2535 htsp->htsp_peername,
2536 htsp->htsp_granted_access->aa_representative,
2537 htsp->htsp_clientname,
2538 NULL);
2539 if (hs->hs_s)
2540 mtimer_arm_rel(&hs->hs_s_bytes_out_timer, _bytes_out_cb, hs, ms2mono(200));
2541 return NULL;
2542 }
2543
2544 /**
2545 * Request unsubscription for a channel
2546 */
2547 static htsmsg_t *
htsp_method_unsubscribe(htsp_connection_t * htsp,htsmsg_t * in)2548 htsp_method_unsubscribe(htsp_connection_t *htsp, htsmsg_t *in)
2549 {
2550 htsp_subscription_t *s;
2551 uint32_t sid;
2552
2553 if(htsmsg_get_u32(in, "subscriptionId", &sid))
2554 return htsp_error(htsp, N_("Invalid arguments"));
2555
2556 LIST_FOREACH(s, &htsp->htsp_subscriptions, hs_link)
2557 if(s->hs_sid == sid)
2558 break;
2559
2560 /*
2561 * We send the reply here or the user will get the 'subscriptionStop'
2562 * async message before the reply to 'unsubscribe'.
2563 */
2564 htsp_reply(htsp, in, htsmsg_create_map());
2565
2566 if(s == NULL)
2567 return NULL; /* Subscription did not exist, but we don't really care */
2568
2569 htsp_subscription_destroy(htsp, s);
2570 return NULL;
2571 }
2572
2573 /**
2574 * Change weight for a subscription
2575 */
2576 static htsmsg_t *
htsp_method_change_weight(htsp_connection_t * htsp,htsmsg_t * in)2577 htsp_method_change_weight(htsp_connection_t *htsp, htsmsg_t *in)
2578 {
2579 htsp_subscription_t *hs;
2580 uint32_t sid, weight;
2581
2582 if(htsmsg_get_u32(in, "subscriptionId", &sid))
2583 return htsp_error(htsp, N_("Invalid arguments"));
2584
2585 weight = htsmsg_get_u32_or_default(in, "weight", 0);
2586
2587 LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2588 if(hs->hs_sid == sid)
2589 break;
2590
2591 if(hs == NULL)
2592 return htsp_error(htsp, N_("Subscription does not exist"));
2593
2594 htsp_reply(htsp, in, htsmsg_create_map());
2595
2596 subscription_change_weight(hs->hs_s, weight);
2597 return NULL;
2598 }
2599
2600 /**
2601 * Skip stream
2602 */
2603 static htsmsg_t *
htsp_method_skip(htsp_connection_t * htsp,htsmsg_t * in)2604 htsp_method_skip(htsp_connection_t *htsp, htsmsg_t *in)
2605 {
2606 htsp_subscription_t *hs;
2607 uint32_t sid, abs;
2608 int64_t s64;
2609 streaming_skip_t skip;
2610
2611 if(htsmsg_get_u32(in, "subscriptionId", &sid))
2612 return htsp_error(htsp, N_("Invalid arguments"));
2613
2614 LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2615 if(hs->hs_sid == sid)
2616 break;
2617
2618 if(hs == NULL)
2619 return htsp_error(htsp, N_("Subscription does not exist"));
2620
2621 abs = htsmsg_get_u32_or_default(in, "absolute", 0);
2622
2623 memset(&skip, 0, sizeof(skip));
2624 if(!htsmsg_get_s64(in, "time", &s64)) {
2625 skip.type = abs ? SMT_SKIP_ABS_TIME : SMT_SKIP_REL_TIME;
2626 skip.time = hs->hs_90khz ? s64 : ts_rescale_inv(s64, 1000000);
2627 tvhtrace(LS_HTSP_SUB, "skip: %s %"PRId64" (%s)", abs ? "abs" : "rel",
2628 skip.time, hs->hs_90khz ? "90kHz" : "1MHz");
2629 } else if (!htsmsg_get_s64(in, "size", &s64)) {
2630 skip.type = abs ? SMT_SKIP_ABS_SIZE : SMT_SKIP_REL_SIZE;
2631 skip.size = s64;
2632 tvhtrace(LS_HTSP_SUB, "skip: %s by size %"PRId64, abs ? "abs" : "rel", s64);
2633 } else {
2634 return htsp_error(htsp, N_("Invalid arguments"));
2635 }
2636
2637 subscription_set_skip(hs->hs_s, &skip);
2638
2639 htsp_reply(htsp, in, htsmsg_create_map());
2640 return NULL;
2641 }
2642
2643 /*
2644 * Set stream speed
2645 */
2646 static htsmsg_t *
htsp_method_speed(htsp_connection_t * htsp,htsmsg_t * in)2647 htsp_method_speed(htsp_connection_t *htsp, htsmsg_t *in)
2648 {
2649 htsp_subscription_t *hs;
2650 uint32_t sid;
2651 int32_t speed;
2652
2653 if(htsmsg_get_u32(in, "subscriptionId", &sid))
2654 return htsp_error(htsp, N_("Invalid arguments"));
2655 if(htsmsg_get_s32(in, "speed", &speed))
2656 return htsp_error(htsp, N_("Invalid arguments"));
2657
2658 LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2659 if(hs->hs_sid == sid)
2660 break;
2661
2662 if(hs == NULL)
2663 return htsp_error(htsp, N_("Subscription does not exist"));
2664
2665 tvhtrace(LS_HTSP_SUB, "speed: %d", speed);
2666 subscription_set_speed(hs->hs_s, speed);
2667
2668 htsp_reply(htsp, in, htsmsg_create_map());
2669 return NULL;
2670 }
2671
2672 /*
2673 * Revert to live
2674 */
2675 static htsmsg_t *
htsp_method_live(htsp_connection_t * htsp,htsmsg_t * in)2676 htsp_method_live(htsp_connection_t *htsp, htsmsg_t *in)
2677 {
2678 htsp_subscription_t *hs;
2679 uint32_t sid;
2680 streaming_skip_t skip;
2681
2682 if(htsmsg_get_u32(in, "subscriptionId", &sid))
2683 return htsp_error(htsp, N_("Invalid arguments"));
2684
2685 LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2686 if(hs->hs_sid == sid)
2687 break;
2688
2689 if(hs == NULL)
2690 return htsp_error(htsp, N_("Subscription does not exist"));
2691
2692 memset(&skip, 0, sizeof(skip));
2693 skip.type = SMT_SKIP_LIVE;
2694 tvhtrace(LS_HTSP_SUB, "live");
2695 subscription_set_skip(hs->hs_s, &skip);
2696
2697 htsp_reply(htsp, in, htsmsg_create_map());
2698 return NULL;
2699 }
2700
2701 /**
2702 * Change filters for a subscription
2703 */
2704 static htsmsg_t *
htsp_method_filter_stream(htsp_connection_t * htsp,htsmsg_t * in)2705 htsp_method_filter_stream(htsp_connection_t *htsp, htsmsg_t *in)
2706 {
2707 htsp_subscription_t *hs;
2708 uint32_t sid;
2709 htsmsg_t *l;
2710 if(htsmsg_get_u32(in, "subscriptionId", &sid))
2711 return htsp_error(htsp, N_("Invalid arguments"));
2712
2713 LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2714 if(hs->hs_sid == sid)
2715 break;
2716
2717 if(hs == NULL)
2718 return htsp_error(htsp, N_("Subscription does not exist"));
2719
2720 if((l = htsmsg_get_list(in, "enable")) != NULL) {
2721 htsmsg_field_t *f;
2722 HTSMSG_FOREACH(f, l) {
2723 if(f->hmf_type == HMF_S64)
2724 htsp_enable_stream(hs, f->hmf_s64);
2725 }
2726 }
2727
2728 if((l = htsmsg_get_list(in, "disable")) != NULL) {
2729 htsmsg_field_t *f;
2730 HTSMSG_FOREACH(f, l) {
2731 if(f->hmf_type == HMF_S64)
2732 htsp_disable_stream(hs, f->hmf_s64);
2733 }
2734 }
2735 return htsmsg_create_map();
2736 }
2737
2738
2739 /**
2740 * Open file
2741 */
2742 static htsmsg_t *
htsp_method_file_open(htsp_connection_t * htsp,htsmsg_t * in)2743 htsp_method_file_open(htsp_connection_t *htsp, htsmsg_t *in)
2744 {
2745 const char *str, *s2;
2746 const char *filename = NULL;
2747 char buf[PATH_MAX];
2748
2749 if((str = htsmsg_get_str(in, "file")) == NULL)
2750 return htsp_error(htsp, N_("Invalid arguments"));
2751
2752 // optional leading slash
2753 if (*str == '/')
2754 str++;
2755
2756 if((s2 = tvh_strbegins(str, "dvr/")) != NULL ||
2757 (s2 = tvh_strbegins(str, "dvrfile/")) != NULL) {
2758 dvr_entry_t *de = dvr_entry_find_by_id(atoi(s2));
2759 if(de == NULL)
2760 return htsp_error(htsp, N_("DVR schedule does not exist"));
2761
2762 if (dvr_entry_verify(de, htsp->htsp_granted_access, 1))
2763 return htsp_error(htsp, N_("User does not have access"));
2764
2765 filename = dvr_get_filename(de);
2766
2767 if (filename == NULL)
2768 return htsp_error(htsp, N_("DVR schedule does not have a file yet"));
2769
2770 return htsp_file_open(htsp, filename, 0, de);
2771
2772 } else if ((s2 = tvh_strbegins(str, "imagecache/")) != NULL) {
2773 int fd = -1;
2774 if (!imagecache_filename(atoi(s2), buf, sizeof(buf)))
2775 fd = tvh_open(buf, O_RDONLY, 0);
2776 if (fd < 0)
2777 return htsp_error(htsp, N_("Failed to open image"));
2778 return htsp_file_open(htsp, str, fd, NULL);
2779
2780 } else {
2781 return htsp_error(htsp, N_("Unknown file"));
2782 }
2783 }
2784
2785 /**
2786 *
2787 */
2788 static htsmsg_t *
htsp_method_file_read(htsp_connection_t * htsp,htsmsg_t * in)2789 htsp_method_file_read(htsp_connection_t *htsp, htsmsg_t *in)
2790 {
2791 htsp_file_t *hf = htsp_file_find(htsp, in);
2792 htsmsg_t *rep = NULL;
2793 const char *e = NULL;
2794 int64_t off;
2795 int64_t size;
2796 int fd;
2797
2798 if(hf == NULL)
2799 return htsp_error(htsp, N_("Invalid file"));
2800
2801 if(htsmsg_get_s64(in, "size", &size))
2802 return htsp_error(htsp, N_("Invalid parameters"));
2803
2804 fd = hf->hf_fd;
2805
2806 pthread_mutex_unlock(&global_lock);
2807
2808 /* Seek (optional) */
2809 if (!htsmsg_get_s64(in, "offset", &off))
2810 if(lseek(fd, off, SEEK_SET) != off) {
2811 e = "Seek error";
2812 goto error;
2813 }
2814
2815 /* Read */
2816 void *m = malloc(size);
2817 if(m == NULL) {
2818 e = N_("Not enough memory");
2819 goto error;
2820 }
2821
2822 int r = read(fd, m, size);
2823 if(r < 0) {
2824 free(m);
2825 e = N_("Read error");
2826 goto error;
2827 }
2828
2829 htsp_file_update_stats(hf, r);
2830
2831 rep = htsmsg_create_map();
2832 htsmsg_add_bin(rep, "data", m, r);
2833 free(m);
2834
2835 error:
2836 pthread_mutex_lock(&global_lock);
2837 return e ? htsp_error(htsp, e) : rep;
2838 }
2839
2840 /**
2841 *
2842 */
2843 static htsmsg_t *
htsp_method_file_close(htsp_connection_t * htsp,htsmsg_t * in)2844 htsp_method_file_close(htsp_connection_t *htsp, htsmsg_t *in)
2845 {
2846 htsp_file_t *hf = htsp_file_find(htsp, in);
2847 uint32_t u32;
2848 dvr_entry_t *de;
2849
2850 if(hf == NULL)
2851 return htsp_error(htsp, N_("Invalid file"));
2852
2853 if (hf->hf_de_id > 0 && (de = dvr_entry_find_by_id(hf->hf_de_id)))
2854 {
2855 int save = 0;
2856 /* Only allow incrementing playcount on file close, the rest can be done with "updateDvrEntry" */
2857 if (htsp->htsp_version < 27 || htsmsg_get_u32_or_default(in, "playcount", HTSP_DVR_PLAYCOUNT_INCR) == HTSP_DVR_PLAYCOUNT_INCR) {
2858 de->de_playcount++;
2859 save = 1;
2860 }
2861 if(htsp->htsp_version >= 27 && !htsmsg_get_u32(in, "playposition", &u32)) {
2862 de->de_playposition = u32;
2863 save = 1;
2864 }
2865 if (save)
2866 dvr_entry_changed_notify(de);
2867 }
2868
2869 pthread_mutex_unlock(&global_lock);
2870 htsp_file_destroy(hf);
2871 pthread_mutex_lock(&global_lock);
2872 return htsmsg_create_map();
2873 }
2874
2875 /**
2876 *
2877 */
2878 static htsmsg_t *
htsp_method_file_stat(htsp_connection_t * htsp,htsmsg_t * in)2879 htsp_method_file_stat(htsp_connection_t *htsp, htsmsg_t *in)
2880 {
2881 htsp_file_t *hf = htsp_file_find(htsp, in);
2882 htsmsg_t *rep;
2883 struct stat st;
2884 int fd;
2885
2886 if(hf == NULL)
2887 return htsp_error(htsp, N_("Invalid file"));
2888
2889 fd = hf->hf_fd;
2890
2891 pthread_mutex_unlock(&global_lock);
2892 rep = htsmsg_create_map();
2893 if(!fstat(fd, &st)) {
2894 htsmsg_add_s64(rep, "size", st.st_size);
2895 htsmsg_add_s64(rep, "mtime", st.st_mtime);
2896 }
2897 pthread_mutex_lock(&global_lock);
2898
2899 return rep;
2900 }
2901
2902 /**
2903 *
2904 */
2905 static htsmsg_t *
htsp_method_file_seek(htsp_connection_t * htsp,htsmsg_t * in)2906 htsp_method_file_seek(htsp_connection_t *htsp, htsmsg_t *in)
2907 {
2908 htsp_file_t *hf = htsp_file_find(htsp, in);
2909 htsmsg_t *rep;
2910 const char *str;
2911 int64_t off;
2912 int fd, whence;
2913
2914 if(hf == NULL)
2915 return htsp_error(htsp, N_("Invalid file"));
2916
2917 if (htsmsg_get_s64(in, "offset", &off))
2918 return htsp_error(htsp, N_("Invalid parameters"));
2919
2920 if ((str = htsmsg_get_str(in, "whence"))) {
2921 if (!strcmp(str, "SEEK_SET"))
2922 whence = SEEK_SET;
2923 else if (!strcmp(str, "SEEK_CUR"))
2924 whence = SEEK_CUR;
2925 else if (!strcmp(str, "SEEK_END"))
2926 whence = SEEK_END;
2927 else
2928 return htsp_error(htsp, N_("Invalid parameters"));
2929 } else {
2930 whence = SEEK_SET;
2931 }
2932
2933 fd = hf->hf_fd;
2934 pthread_mutex_unlock(&global_lock);
2935
2936 if ((off = lseek(fd, off, whence)) < 0) {
2937 pthread_mutex_lock(&global_lock);
2938 return htsp_error(htsp, N_("Seek error"));
2939 }
2940
2941 rep = htsmsg_create_map();
2942 htsmsg_add_s64(rep, "offset", off);
2943
2944 pthread_mutex_lock(&global_lock);
2945 return rep;
2946 }
2947
2948 /**
2949 *
2950 */
2951 static htsmsg_t *
htsp_method_getProfiles(htsp_connection_t * htsp,htsmsg_t * in)2952 htsp_method_getProfiles(htsp_connection_t *htsp, htsmsg_t *in)
2953 {
2954 htsmsg_t *out, *l;
2955
2956 l = htsmsg_create_list();
2957 profile_get_htsp_list(l, htsp->htsp_granted_access->aa_profiles);
2958
2959 out = htsmsg_create_map();
2960
2961 htsmsg_add_msg(out, "profiles", l);
2962
2963 return out;
2964 }
2965
2966 /**
2967 * HTSP methods
2968 */
2969 struct {
2970 const char *name;
2971 htsmsg_t *(*fn)(htsp_connection_t *htsp, htsmsg_t *in);
2972 int privmask;
2973 } htsp_methods[] = {
2974 { "hello", htsp_method_hello, ACCESS_ANONYMOUS},
2975 { "authenticate", htsp_method_authenticate, ACCESS_ANONYMOUS},
2976 { "api", htsp_method_api, ACCESS_ANONYMOUS},
2977 { "getDiskSpace", htsp_method_getDiskSpace, ACCESS_HTSP_STREAMING},
2978 { "getSysTime", htsp_method_getSysTime, ACCESS_HTSP_STREAMING},
2979 { "enableAsyncMetadata", htsp_method_async, ACCESS_HTSP_STREAMING},
2980 { "getChannel", htsp_method_getChannel, ACCESS_HTSP_STREAMING},
2981 { "getEvent", htsp_method_getEvent, ACCESS_HTSP_STREAMING},
2982 { "getEvents", htsp_method_getEvents, ACCESS_HTSP_STREAMING},
2983 { "epgQuery", htsp_method_epgQuery, ACCESS_HTSP_STREAMING},
2984 { "getEpgObject", htsp_method_getEpgObject, ACCESS_HTSP_STREAMING},
2985 { "getDvrConfigs", htsp_method_getDvrConfigs, ACCESS_HTSP_RECORDER},
2986 { "addDvrEntry", htsp_method_addDvrEntry, ACCESS_HTSP_RECORDER},
2987 { "updateDvrEntry", htsp_method_updateDvrEntry, ACCESS_HTSP_RECORDER},
2988 { "stopDvrEntry", htsp_method_stopDvrEntry, ACCESS_HTSP_RECORDER},
2989 { "cancelDvrEntry", htsp_method_cancelDvrEntry, ACCESS_HTSP_RECORDER},
2990 { "deleteDvrEntry", htsp_method_deleteDvrEntry, ACCESS_HTSP_RECORDER},
2991 { "addAutorecEntry", htsp_method_addAutorecEntry, ACCESS_HTSP_RECORDER},
2992 { "updateAutorecEntry", htsp_method_updateAutorecEntry, ACCESS_HTSP_RECORDER},
2993 { "deleteAutorecEntry", htsp_method_deleteAutorecEntry, ACCESS_HTSP_RECORDER},
2994 { "addTimerecEntry", htsp_method_addTimerecEntry, ACCESS_HTSP_RECORDER},
2995 { "updateTimerecEntry", htsp_method_updateTimerecEntry, ACCESS_HTSP_RECORDER},
2996 { "deleteTimerecEntry", htsp_method_deleteTimerecEntry, ACCESS_HTSP_RECORDER},
2997 { "getDvrCutpoints", htsp_method_getDvrCutpoints, ACCESS_HTSP_RECORDER},
2998 { "getTicket", htsp_method_getTicket, ACCESS_HTSP_STREAMING},
2999 { "subscribe", htsp_method_subscribe, ACCESS_HTSP_STREAMING},
3000 { "unsubscribe", htsp_method_unsubscribe, ACCESS_HTSP_STREAMING},
3001 { "subscriptionChangeWeight", htsp_method_change_weight, ACCESS_HTSP_STREAMING},
3002 { "subscriptionSeek", htsp_method_skip, ACCESS_HTSP_STREAMING},
3003 { "subscriptionSkip", htsp_method_skip, ACCESS_HTSP_STREAMING},
3004 { "subscriptionSpeed", htsp_method_speed, ACCESS_HTSP_STREAMING},
3005 { "subscriptionLive", htsp_method_live, ACCESS_HTSP_STREAMING},
3006 { "subscriptionFilterStream", htsp_method_filter_stream, ACCESS_HTSP_STREAMING},
3007 { "getProfiles", htsp_method_getProfiles, ACCESS_HTSP_STREAMING},
3008 { "fileOpen", htsp_method_file_open, ACCESS_HTSP_RECORDER},
3009 { "fileRead", htsp_method_file_read, ACCESS_HTSP_RECORDER},
3010 { "fileClose", htsp_method_file_close, ACCESS_HTSP_RECORDER},
3011 { "fileStat", htsp_method_file_stat, ACCESS_HTSP_RECORDER},
3012 { "fileSeek", htsp_method_file_seek, ACCESS_HTSP_RECORDER},
3013 };
3014
3015 #define NUM_METHODS (sizeof(htsp_methods) / sizeof(htsp_methods[0]))
3016
3017 /* **************************************************************************
3018 * Message processing
3019 * *************************************************************************/
3020
3021 /**
3022 *
3023 */
3024 struct htsp_verify_struct {
3025 const uint8_t *digest;
3026 const uint8_t *challenge;
3027 };
3028
3029 static int
htsp_verify_callback(void * aux,const char * passwd)3030 htsp_verify_callback(void *aux, const char *passwd)
3031 {
3032 struct htsp_verify_struct *v = aux;
3033 uint8_t d[20];
3034
3035 if (v->digest == NULL || v->challenge == NULL) return 0;
3036 sha1_calc(d, (uint8_t *)passwd, strlen(passwd), v->challenge, 32);
3037 return memcmp(d, v->digest, 20) == 0;
3038 }
3039
3040 /**
3041 * Raise privs by field in message
3042 */
3043 static int
htsp_authenticate(htsp_connection_t * htsp,htsmsg_t * m)3044 htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
3045 {
3046 struct htsp_verify_struct vs;
3047 const char *username;
3048 const void *digest;
3049 size_t digestlen;
3050 access_t *rights;
3051 int privgain = 0;
3052
3053 if((username = htsmsg_get_str(m, "username")) == NULL)
3054 return 0;
3055
3056 if(!htsmsg_get_bin(m, "digest", &digest, &digestlen)) {
3057
3058 vs.digest = digest;
3059 vs.challenge = htsp->htsp_challenge;
3060 rights = access_get(htsp->htsp_peer, username,
3061 htsp_verify_callback, &vs);
3062
3063 if (rights->aa_rights == 0) {
3064 tvhinfo(LS_HTSP, "%s: Unauthorized access", htsp->htsp_logname);
3065 access_destroy(rights);
3066 return 0;
3067 }
3068
3069 rights->aa_rights |= ACCESS_HTSP_INTERFACE;
3070 privgain = (rights->aa_rights |
3071 htsp->htsp_granted_access->aa_rights) !=
3072 htsp->htsp_granted_access->aa_rights;
3073
3074 tvhinfo(LS_HTSP, "%s: Identified as user '%s'",
3075 htsp->htsp_logname, username);
3076 tvh_str_update(&htsp->htsp_username, username);
3077 htsp_update_logname(htsp);
3078 if(privgain)
3079 tvhinfo(LS_HTSP, "%s: Privileges updated", htsp->htsp_logname);
3080
3081 access_destroy(htsp->htsp_granted_access);
3082 htsp->htsp_granted_access = rights;
3083
3084 if (htsp->htsp_language == NULL && rights->aa_lang)
3085 htsp->htsp_language = strdup(rights->aa_lang);
3086
3087 } else {
3088
3089 tvhinfo(LS_HTSP, "%s: Identified as user '%s' (unverified)",
3090 htsp->htsp_logname, username);
3091 tvh_str_update(&htsp->htsp_username, username);
3092 htsp_update_logname(htsp);
3093
3094 }
3095
3096 notify_reload("connections");
3097
3098 return privgain;
3099 }
3100
3101 /**
3102 * timeout is in ms, 0 means infinite timeout
3103 */
3104 static int
htsp_read_message(htsp_connection_t * htsp,htsmsg_t ** mp,int timeout)3105 htsp_read_message(htsp_connection_t *htsp, htsmsg_t **mp, int timeout)
3106 {
3107 int v;
3108 uint32_t len;
3109 uint8_t data[4];
3110 void *buf;
3111
3112 v = timeout ? tcp_read_timeout(htsp->htsp_fd, data, 4, timeout) :
3113 tcp_read(htsp->htsp_fd, data, 4);
3114
3115 if(v != 0)
3116 return v;
3117
3118 len = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
3119 if(len > 1024 * 1024)
3120 return EMSGSIZE;
3121 if((buf = malloc(len)) == NULL)
3122 return ENOMEM;
3123
3124 v = timeout ? tcp_read_timeout(htsp->htsp_fd, buf, len, timeout) :
3125 tcp_read(htsp->htsp_fd, buf, len);
3126
3127 if(v != 0) {
3128 free(buf);
3129 return v;
3130 }
3131
3132 /* buf will be tied to the message (on success) */
3133 /* bellow fcn calls free(buf) (on failure) */
3134 *mp = htsmsg_binary_deserialize(buf, len, buf);
3135 if(*mp == NULL)
3136 return EBADMSG;
3137
3138 return 0;
3139 }
3140
3141 /*
3142 * Status callback
3143 */
3144 static void
htsp_server_status(void * opaque,htsmsg_t * m)3145 htsp_server_status ( void *opaque, htsmsg_t *m )
3146 {
3147 htsp_connection_t *htsp = opaque;
3148 access_t *aa;
3149 char buf[128];
3150 htsmsg_add_str(m, "type", "HTSP");
3151 if (htsp->htsp_username) {
3152 aa = htsp->htsp_granted_access;
3153 if (!strcmp(htsp->htsp_username, aa->aa_username ?: ""))
3154 snprintf(buf, sizeof(buf), "%s", htsp->htsp_username);
3155 else
3156 snprintf(buf, sizeof(buf), "[%s]", htsp->htsp_username);
3157 htsmsg_add_str(m, "user", buf);
3158 }
3159 }
3160
3161 /**
3162 *
3163 */
3164 static int
htsp_read_loop(htsp_connection_t * htsp)3165 htsp_read_loop(htsp_connection_t *htsp)
3166 {
3167 htsmsg_t *m = NULL, *reply;
3168 int r = 0, i;
3169 const char *method;
3170 void *tcp_id = NULL;;
3171
3172 if(htsp_generate_challenge(htsp)) {
3173 tvherror(LS_HTSP, "%s: Unable to generate challenge",
3174 htsp->htsp_logname);
3175 return 1;
3176 }
3177
3178 pthread_mutex_lock(&global_lock);
3179
3180 htsp->htsp_granted_access = access_get_by_addr(htsp->htsp_peer);
3181 htsp->htsp_granted_access->aa_rights |= ACCESS_HTSP_INTERFACE;
3182
3183 tcp_id = tcp_connection_launch(htsp->htsp_fd, htsp_server_status,
3184 htsp->htsp_granted_access);
3185
3186 pthread_mutex_unlock(&global_lock);
3187
3188 if (tcp_id == NULL)
3189 return 0;
3190
3191 tvhinfo(LS_HTSP, "Got connection from %s", htsp->htsp_logname);
3192
3193 /* Session main loop */
3194
3195 while(tvheadend_is_running()) {
3196 readmsg:
3197 reply = NULL;
3198
3199 if((r = htsp_read_message(htsp, &m, 0)) != 0)
3200 break;
3201
3202 pthread_mutex_lock(&global_lock);
3203 if (htsp_authenticate(htsp, m)) {
3204 tcp_connection_land(tcp_id);
3205 tcp_id = tcp_connection_launch(htsp->htsp_fd, htsp_server_status,
3206 htsp->htsp_granted_access);
3207 if (tcp_id == NULL) {
3208 htsmsg_destroy(m);
3209 pthread_mutex_unlock(&global_lock);
3210 return 1;
3211 }
3212 }
3213
3214 if((method = htsmsg_get_str(m, "method")) != NULL) {
3215 tvhtrace(LS_HTSP, "%s - method %s", htsp->htsp_logname, method);
3216 if (tvhtrace_enabled())
3217 htsp_trace(htsp, LS_HTSP_REQ, "request", m);
3218 for(i = 0; i < NUM_METHODS; i++) {
3219 if(!strcmp(method, htsp_methods[i].name)) {
3220
3221 if((htsp->htsp_granted_access->aa_rights &
3222 htsp_methods[i].privmask) !=
3223 htsp_methods[i].privmask) {
3224
3225 pthread_mutex_unlock(&global_lock);
3226 /* Classic authentication failed delay */
3227 tvh_safe_usleep(250000);
3228
3229 reply = htsmsg_create_map();
3230 htsmsg_add_u32(reply, "noaccess", 1);
3231 htsp_reply(htsp, m, reply);
3232
3233 htsmsg_destroy(m);
3234 goto readmsg;
3235
3236 } else {
3237 reply = htsp_methods[i].fn(htsp, m);
3238 }
3239 break;
3240 }
3241 }
3242
3243 if(i == NUM_METHODS) {
3244 reply = htsp_error(htsp, N_("Method not found"));
3245 }
3246
3247 } else {
3248 reply = htsp_error(htsp, N_("Invalid arguments"));
3249 }
3250
3251 pthread_mutex_unlock(&global_lock);
3252
3253 if(reply != NULL) /* Methods can do all the replying inline */
3254 htsp_reply(htsp, m, reply);
3255
3256 htsmsg_destroy(m);
3257 }
3258
3259 pthread_mutex_lock(&global_lock);
3260 tcp_connection_land(tcp_id);
3261 pthread_mutex_unlock(&global_lock);
3262 return tvheadend_is_running() ? r : 0;
3263 }
3264
3265 /**
3266 *
3267 */
3268 static void *
htsp_write_scheduler(void * aux)3269 htsp_write_scheduler(void *aux)
3270 {
3271 htsp_connection_t *htsp = aux;
3272 htsp_msg_q_t *hmq;
3273 htsp_msg_t *hm;
3274 void *dptr;
3275 size_t dlen;
3276 int r;
3277
3278 pthread_mutex_lock(&htsp->htsp_out_mutex);
3279
3280 while(htsp->htsp_writer_run) {
3281
3282 if((hmq = TAILQ_FIRST(&htsp->htsp_active_output_queues)) == NULL) {
3283 /* Nothing to be done, go to sleep */
3284 tvh_cond_wait(&htsp->htsp_out_cond, &htsp->htsp_out_mutex);
3285 continue;
3286 }
3287
3288 hm = TAILQ_FIRST(&hmq->hmq_q);
3289 TAILQ_REMOVE(&hmq->hmq_q, hm, hm_link);
3290 hmq->hmq_length--;
3291 hmq->hmq_payload -= hm->hm_payloadsize;
3292
3293 TAILQ_REMOVE(&htsp->htsp_active_output_queues, hmq, hmq_link);
3294 if(hmq->hmq_length) {
3295 /* Still messages to be sent, put back in active queues */
3296 if(hmq->hmq_strict_prio) {
3297 TAILQ_INSERT_HEAD(&htsp->htsp_active_output_queues, hmq, hmq_link);
3298 } else {
3299 TAILQ_INSERT_TAIL(&htsp->htsp_active_output_queues, hmq, hmq_link);
3300 }
3301 }
3302
3303 pthread_mutex_unlock(&htsp->htsp_out_mutex);
3304
3305 if (htsmsg_binary_serialize(hm->hm_msg, &dptr, &dlen, INT32_MAX) != 0) {
3306 tvhwarn(LS_HTSP, "%s: failed to serialize data", htsp->htsp_logname);
3307 htsp_msg_destroy(hm);
3308 pthread_mutex_lock(&htsp->htsp_out_mutex);
3309 continue;
3310 }
3311
3312 htsp_msg_destroy(hm);
3313
3314 r = tvh_write(htsp->htsp_fd, dptr, dlen);
3315 free(dptr);
3316 pthread_mutex_lock(&htsp->htsp_out_mutex);
3317
3318 if (r) {
3319 tvhinfo(LS_HTSP, "%s: Write error -- %s",
3320 htsp->htsp_logname, strerror(errno));
3321 break;
3322 }
3323 }
3324 // Shutdown socket to make receive thread terminate entire HTSP connection
3325
3326 shutdown(htsp->htsp_fd, SHUT_RDWR);
3327 pthread_mutex_unlock(&htsp->htsp_out_mutex);
3328 return NULL;
3329 }
3330
3331 /**
3332 *
3333 */
3334 static void
htsp_serve(int fd,void ** opaque,struct sockaddr_storage * source,struct sockaddr_storage * self)3335 htsp_serve(int fd, void **opaque, struct sockaddr_storage *source,
3336 struct sockaddr_storage *self)
3337 {
3338 htsp_connection_t htsp;
3339 char buf[50];
3340 htsp_subscription_t *s;
3341
3342 // Note: global_lock held on entry
3343
3344 if (config.dscp >= 0)
3345 socket_set_dscp(fd, config.dscp, NULL, 0);
3346
3347 tcp_get_str_from_ip(source, buf, 50);
3348
3349 memset(&htsp, 0, sizeof(htsp_connection_t));
3350 *opaque = &htsp;
3351
3352 TAILQ_INIT(&htsp.htsp_active_output_queues);
3353
3354 htsp_init_queue(&htsp.htsp_hmq_ctrl, 0);
3355 htsp_init_queue(&htsp.htsp_hmq_qstatus, 1);
3356 htsp_init_queue(&htsp.htsp_hmq_epg, 0);
3357
3358 htsp.htsp_peername = strdup(buf);
3359 htsp_update_logname(&htsp);
3360
3361 htsp.htsp_fd = fd;
3362 htsp.htsp_peer = source;
3363 htsp.htsp_writer_run = 1;
3364
3365 pthread_mutex_init(&htsp.htsp_out_mutex, NULL);
3366
3367 LIST_INSERT_HEAD(&htsp_connections, &htsp, htsp_link);
3368 pthread_mutex_unlock(&global_lock);
3369
3370 tvhthread_create(&htsp.htsp_writer_thread, NULL,
3371 htsp_write_scheduler, &htsp, "htsp-write");
3372
3373 /**
3374 * Reader loop
3375 */
3376
3377 htsp_read_loop(&htsp);
3378
3379 tvhinfo(LS_HTSP, "%s: Disconnected", htsp.htsp_logname);
3380
3381 /**
3382 * Ok, we're back, other end disconnected. Clean up stuff.
3383 */
3384
3385 pthread_mutex_lock(&global_lock);
3386
3387 /* no async notifications from now */
3388 if(htsp.htsp_async_mode)
3389 LIST_REMOVE(&htsp, htsp_async_link);
3390
3391 mtimer_disarm(&htsp.htsp_epg_timer);
3392
3393 /* deregister this client */
3394 LIST_REMOVE(&htsp, htsp_link);
3395
3396 /* Beware! Closing subscriptions will invoke a lot of callbacks
3397 down in the streaming code. So we do this as early as possible
3398 to avoid any weird lockups */
3399 while((s = LIST_FIRST(&htsp.htsp_subscriptions)) != NULL)
3400 htsp_subscription_destroy(&htsp, s);
3401
3402 pthread_mutex_unlock(&global_lock);
3403
3404 pthread_mutex_lock(&htsp.htsp_out_mutex);
3405 htsp.htsp_writer_run = 0;
3406 tvh_cond_signal(&htsp.htsp_out_cond, 0);
3407 pthread_mutex_unlock(&htsp.htsp_out_mutex);
3408
3409 pthread_join(htsp.htsp_writer_thread, NULL);
3410
3411 while((s = LIST_FIRST(&htsp.htsp_dead_subscriptions)) != NULL)
3412 htsp_subscription_free(&htsp, s);
3413
3414 htsp_msg_q_t *hmq;
3415
3416 TAILQ_FOREACH(hmq, &htsp.htsp_active_output_queues, hmq_link) {
3417 htsp_msg_t *hm;
3418 while((hm = TAILQ_FIRST(&hmq->hmq_q)) != NULL) {
3419 TAILQ_REMOVE(&hmq->hmq_q, hm, hm_link);
3420 htsp_msg_destroy(hm);
3421 }
3422 }
3423
3424 htsp_file_t *hf;
3425 while((hf = LIST_FIRST(&htsp.htsp_files)) != NULL)
3426 htsp_file_destroy(hf);
3427
3428 close(fd);
3429
3430 /* Free memory (leave lock in place, for parent method) */
3431 pthread_mutex_lock(&global_lock);
3432 free(htsp.htsp_logname);
3433 free(htsp.htsp_peername);
3434 free(htsp.htsp_username);
3435 free(htsp.htsp_clientname);
3436 free(htsp.htsp_language);
3437 access_destroy(htsp.htsp_granted_access);
3438 *opaque = NULL;
3439 }
3440
3441 /*
3442 * Cancel callback
3443 */
3444 static void
htsp_server_cancel(void * opaque)3445 htsp_server_cancel ( void *opaque )
3446 {
3447 htsp_connection_t *htsp = opaque;
3448
3449 if (htsp)
3450 shutdown(htsp->htsp_fd, SHUT_RDWR);
3451 }
3452
3453 /**
3454 * Fire up HTSP server
3455 */
3456 void
htsp_init(const char * bindaddr)3457 htsp_init(const char *bindaddr)
3458 {
3459 extern int tvheadend_htsp_port_extra;
3460 static tcp_server_ops_t ops = {
3461 .start = htsp_serve,
3462 .stop = NULL,
3463 .cancel = htsp_server_cancel
3464 };
3465 htsp_server = tcp_server_create(LS_HTSP, "HTSP", bindaddr, tvheadend_htsp_port, &ops, NULL);
3466 if(tvheadend_htsp_port_extra)
3467 htsp_server_2 = tcp_server_create(LS_HTSP, "HTSP2", bindaddr, tvheadend_htsp_port_extra, &ops, NULL);
3468 }
3469
3470 /*
3471 *
3472 */
3473 void
htsp_register(void)3474 htsp_register(void)
3475 {
3476 tcp_server_register(htsp_server);
3477 if (htsp_server_2)
3478 tcp_server_register(htsp_server_2);
3479 }
3480
3481 /**
3482 * Fire down HTSP server
3483 */
3484 void
htsp_done(void)3485 htsp_done(void)
3486 {
3487 if (htsp_server_2)
3488 tcp_server_delete(htsp_server_2);
3489 if (htsp_server)
3490 tcp_server_delete(htsp_server);
3491 }
3492
3493 /* **************************************************************************
3494 * Asynchronous updates
3495 * *************************************************************************/
3496
3497 /**
3498 *
3499 */
3500 static void
htsp_async_send(htsmsg_t * m,int mode,int aux_type,void * aux)3501 htsp_async_send(htsmsg_t *m, int mode, int aux_type, void *aux)
3502 {
3503 htsp_connection_t *htsp;
3504
3505 lock_assert(&global_lock);
3506 LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link)
3507 if (htsp->htsp_async_mode & mode) {
3508 if (aux_type == HTSP_ASYNC_AUX_CHTAG &&
3509 !channel_tag_access(aux, htsp->htsp_granted_access, 0))
3510 continue;
3511 htsp_send_message(htsp, htsmsg_copy(m), NULL);
3512 }
3513 htsmsg_destroy(m);
3514 }
3515
3516 /**
3517 * Called from channel.c when a new channel is created
3518 */
3519 static void
_htsp_channel_update(channel_t * ch,const char * method,htsmsg_t * msg)3520 _htsp_channel_update(channel_t *ch, const char *method, htsmsg_t *msg)
3521 {
3522 htsp_connection_t *htsp;
3523 LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3524 if (htsp->htsp_async_mode & HTSP_ASYNC_ON)
3525 if (htsp_user_access_channel(htsp,ch)) {
3526 htsmsg_t *m = msg ? htsmsg_copy(msg)
3527 : htsp_build_channel(ch, method, htsp);
3528 htsp_send_message(htsp, m, NULL);
3529 }
3530 }
3531 htsmsg_destroy(msg);
3532 }
3533
3534 /**
3535 * EPG subsystem calls this function when the current/next event
3536 * changes for a channel, e may be NULL if there is no current event.
3537 *
3538 * global_lock is held
3539 */
3540 void
htsp_channel_update_nownext(channel_t * ch)3541 htsp_channel_update_nownext(channel_t *ch)
3542 {
3543 epg_broadcast_t *now, *next;
3544 htsmsg_t *m;
3545
3546 m = htsmsg_create_map();
3547 htsmsg_add_str(m, "method", "channelUpdate");
3548 htsmsg_add_u32(m, "channelId", channel_get_id(ch));
3549
3550 now = ch->ch_epg_now;
3551 next = ch->ch_epg_next;
3552 htsmsg_add_u32(m, "eventId", now ? now->id : 0);
3553 htsmsg_add_u32(m, "nextEventId", next ? next->id : 0);
3554 _htsp_channel_update(ch, NULL, m);
3555 }
3556
3557 void
htsp_channel_add(channel_t * ch)3558 htsp_channel_add(channel_t *ch)
3559 {
3560 _htsp_channel_update(ch, "channelAdd", NULL);
3561 }
3562
3563 /**
3564 * Called from channel.c when a channel is updated
3565 */
3566 void
htsp_channel_update(channel_t * ch)3567 htsp_channel_update(channel_t *ch)
3568 {
3569 if (htsp_user_access_channel(NULL, ch))
3570 _htsp_channel_update(ch, "channelUpdate", NULL);
3571 else // in case the channel was ever sent to the client
3572 htsp_channel_delete(ch);
3573 }
3574
3575 /**
3576 * Called from channel.c when a channel is deleted
3577 */
3578 void
htsp_channel_delete(channel_t * ch)3579 htsp_channel_delete(channel_t *ch)
3580 {
3581 htsmsg_t *m = htsmsg_create_map();
3582 htsmsg_add_u32(m, "channelId", channel_get_id(ch));
3583 htsmsg_add_str(m, "method", "channelDelete");
3584 htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_CH, ch);
3585 }
3586
3587
3588 /**
3589 * Called from channel.c when a tag is exported
3590 */
3591 void
htsp_tag_add(channel_tag_t * ct)3592 htsp_tag_add(channel_tag_t *ct)
3593 {
3594 htsp_async_send(htsp_build_tag(ct, "tagAdd", 1), HTSP_ASYNC_ON,
3595 HTSP_ASYNC_AUX_CHTAG, ct);
3596 }
3597
3598
3599 /**
3600 * Called from channel.c when an exported tag is changed
3601 */
3602 void
htsp_tag_update(channel_tag_t * ct)3603 htsp_tag_update(channel_tag_t *ct)
3604 {
3605 if (ct->ct_enabled && !ct->ct_internal) {
3606 htsp_async_send(htsp_build_tag(ct, "tagUpdate", 1), HTSP_ASYNC_ON,
3607 HTSP_ASYNC_AUX_CHTAG, ct);
3608 }
3609 else // in case the tag was ever sent to the client
3610 htsp_tag_delete(ct);
3611 }
3612
3613
3614 /**
3615 * Called from channel.c when an exported tag is deleted
3616 */
3617 void
htsp_tag_delete(channel_tag_t * ct)3618 htsp_tag_delete(channel_tag_t *ct)
3619 {
3620 htsmsg_t *m = htsmsg_create_map();
3621 htsmsg_add_u32(m, "tagId", htsp_channel_tag_get_identifier(ct));
3622 htsmsg_add_str(m, "method", "tagDelete");
3623 htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_CHTAG_DEL, ct);
3624 }
3625
3626 /**
3627 * Called when a DVR entry is updated/added
3628 */
3629 static void
_htsp_dvr_entry_update(dvr_entry_t * de,const char * method,htsmsg_t * msg)3630 _htsp_dvr_entry_update(dvr_entry_t *de, const char *method, htsmsg_t *msg)
3631 {
3632 htsp_connection_t *htsp;
3633 LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3634 if (htsp->htsp_async_mode & HTSP_ASYNC_ON)
3635 if (!dvr_entry_verify(de, htsp->htsp_granted_access, 1)) {
3636 htsmsg_t *m = msg ? htsmsg_copy(msg)
3637 : htsp_build_dvrentry(htsp, de, method, htsp->htsp_language, 0);
3638 htsp_send_message(htsp, m, NULL);
3639 }
3640 }
3641 htsmsg_destroy(msg);
3642 }
3643
3644 /**
3645 * Called from dvr_db.c when a DVR entry is created
3646 */
3647 void
htsp_dvr_entry_add(dvr_entry_t * de)3648 htsp_dvr_entry_add(dvr_entry_t *de)
3649 {
3650 _htsp_dvr_entry_update(de, "dvrEntryAdd", NULL);
3651 }
3652
3653
3654 /**
3655 * Called from dvr_db.c when a DVR entry is updated
3656 */
3657 void
htsp_dvr_entry_update(dvr_entry_t * de)3658 htsp_dvr_entry_update(dvr_entry_t *de)
3659 {
3660 _htsp_dvr_entry_update(de, "dvrEntryUpdate", NULL);
3661 }
3662
3663 /**
3664 * Called from dvr_rec.c when a DVR entry is recording
3665 */
3666 void
htsp_dvr_entry_update_stats(dvr_entry_t * de)3667 htsp_dvr_entry_update_stats(dvr_entry_t *de)
3668 {
3669 htsp_connection_t *htsp;
3670 LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3671 if (htsp->htsp_async_mode & HTSP_ASYNC_ON){
3672 if (!dvr_entry_verify(de, htsp->htsp_granted_access, 1)) {
3673 htsmsg_t *m = htsp_build_dvrentry(htsp, de, "dvrEntryUpdate", htsp->htsp_language, htsp->htsp_version <= 25 ? 0 : 1);
3674 htsp_send_message(htsp, m, NULL);
3675 }
3676 }
3677 }
3678 }
3679
3680
3681 /**
3682 * Called from dvr_db.c when a DVR entry is deleted
3683 */
3684 void
htsp_dvr_entry_delete(dvr_entry_t * de)3685 htsp_dvr_entry_delete(dvr_entry_t *de)
3686 {
3687 htsmsg_t *m = htsmsg_create_map();
3688 htsmsg_add_u32(m, "id", idnode_get_short_uuid(&de->de_id));
3689 htsmsg_add_str(m, "method", "dvrEntryDelete");
3690 htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_DVR, de);
3691 }
3692
3693 /**
3694 * Called when a autorec entry is updated/added
3695 */
3696 static void
_htsp_autorec_entry_update(dvr_autorec_entry_t * dae,const char * method,htsmsg_t * msg)3697 _htsp_autorec_entry_update(dvr_autorec_entry_t *dae, const char *method, htsmsg_t *msg)
3698 {
3699 htsp_connection_t *htsp;
3700 LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3701 if (htsp->htsp_async_mode & HTSP_ASYNC_ON) {
3702 if (!dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 1)) {
3703 htsmsg_t *m = msg ? htsmsg_copy(msg)
3704 : htsp_build_autorecentry(htsp, dae, method);
3705 htsp_send_message(htsp, m, NULL);
3706 }
3707 }
3708 }
3709 htsmsg_destroy(msg);
3710 }
3711
3712 /**
3713 * Called from dvr_autorec.c when a autorec entry is added
3714 */
3715 void
htsp_autorec_entry_add(dvr_autorec_entry_t * dae)3716 htsp_autorec_entry_add(dvr_autorec_entry_t *dae)
3717 {
3718 _htsp_autorec_entry_update(dae, "autorecEntryAdd", NULL);
3719 }
3720
3721 /**
3722 * Called from dvr_autorec.c when a autorec entry is updated
3723 */
3724 void
htsp_autorec_entry_update(dvr_autorec_entry_t * dae)3725 htsp_autorec_entry_update(dvr_autorec_entry_t *dae)
3726 {
3727 _htsp_autorec_entry_update(dae, "autorecEntryUpdate", NULL);
3728 }
3729
3730 /**
3731 * Called from dvr_autorec.c when a autorec entry is deleted
3732 */
3733 void
htsp_autorec_entry_delete(dvr_autorec_entry_t * dae)3734 htsp_autorec_entry_delete(dvr_autorec_entry_t *dae)
3735 {
3736 char ubuf[UUID_HEX_SIZE];
3737
3738 if(dae == NULL)
3739 return;
3740
3741 htsmsg_t *m = htsmsg_create_map();
3742 htsmsg_add_str(m, "id", idnode_uuid_as_str(&dae->dae_id, ubuf));
3743 htsmsg_add_str(m, "method", "autorecEntryDelete");
3744 htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_AUTOREC, dae);
3745 }
3746
3747 /**
3748 * Called when a timerec entry is updated/added
3749 */
3750 static void
_htsp_timerec_entry_update(dvr_timerec_entry_t * dte,const char * method,htsmsg_t * msg)3751 _htsp_timerec_entry_update(dvr_timerec_entry_t *dte, const char *method, htsmsg_t *msg)
3752 {
3753 htsp_connection_t *htsp;
3754 LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3755 if (htsp->htsp_async_mode & HTSP_ASYNC_ON) {
3756 if (!dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 1)) {
3757 htsmsg_t *m = msg ? htsmsg_copy(msg)
3758 : htsp_build_timerecentry(htsp, dte, method);
3759 htsp_send_message(htsp, m, NULL);
3760 }
3761 }
3762 }
3763 htsmsg_destroy(msg);
3764 }
3765
3766 /**
3767 * Called from dvr_timerec.c when a timerec entry is added
3768 */
3769 void
htsp_timerec_entry_add(dvr_timerec_entry_t * dte)3770 htsp_timerec_entry_add(dvr_timerec_entry_t *dte)
3771 {
3772 _htsp_timerec_entry_update(dte, "timerecEntryAdd", NULL);
3773 }
3774
3775 /**
3776 * Called from dvr_timerec.c when a timerec entry is updated
3777 */
3778 void
htsp_timerec_entry_update(dvr_timerec_entry_t * dte)3779 htsp_timerec_entry_update(dvr_timerec_entry_t *dte)
3780 {
3781 _htsp_timerec_entry_update(dte, "timerecEntryUpdate", NULL);
3782 }
3783
3784 /**
3785 * Called from dvr_timerec.c when a timerec entry is deleted
3786 */
3787 void
htsp_timerec_entry_delete(dvr_timerec_entry_t * dte)3788 htsp_timerec_entry_delete(dvr_timerec_entry_t *dte)
3789 {
3790 char ubuf[UUID_HEX_SIZE];
3791
3792 if(dte == NULL)
3793 return;
3794
3795 htsmsg_t *m = htsmsg_create_map();
3796 htsmsg_add_str(m, "id", idnode_uuid_as_str(&dte->dte_id, ubuf));
3797 htsmsg_add_str(m, "method", "timerecEntryDelete");
3798 htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_TIMEREC, dte);
3799 }
3800
3801 /**
3802 * Called every "HTSP_ASYNC_EPG_INTERVAL" seconds
3803 * Keep the async epg window up to date
3804 */
3805 static void
htsp_epg_window_cb(void * aux)3806 htsp_epg_window_cb(void *aux)
3807 {
3808 htsp_connection_t *htsp = aux;
3809 htsp_epg_send_waiting(htsp, htsp->htsp_epg_lastupdate);
3810 }
3811
3812 /**
3813 * Send all waiting EPG events
3814 */
3815 static void
htsp_epg_send_waiting(htsp_connection_t * htsp,int64_t mintime)3816 htsp_epg_send_waiting(htsp_connection_t *htsp, int64_t mintime)
3817 {
3818 epg_broadcast_t *ebc;
3819 channel_t *ch;
3820 int64_t maxtime;
3821
3822 maxtime = gclk() + htsp->htsp_epg_window;
3823 htsp->htsp_epg_lastupdate = maxtime;
3824
3825 /* Push new events */
3826 CHANNEL_FOREACH(ch) {
3827 if (!htsp_user_access_channel(htsp, ch)) continue;
3828 RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) {
3829 if (ebc->start <= mintime) continue;
3830 if (htsp->htsp_epg_window && ebc->start > maxtime) break;
3831 htsmsg_t *e = htsp_build_event(ebc, "eventAdd", htsp->htsp_language, 0, htsp);
3832 if (e) htsp_send_message(htsp, e, NULL);
3833 }
3834 }
3835
3836 /* Keep the epg window up to date */
3837 if (htsp->htsp_epg_window)
3838 mtimer_arm_rel(&htsp->htsp_epg_timer, htsp_epg_window_cb,
3839 htsp, sec2mono(HTSP_ASYNC_EPG_INTERVAL));
3840 }
3841
3842 /**
3843 * Called when a event entry is updated/added
3844 */
3845 static void
_htsp_event_update(epg_broadcast_t * ebc,const char * method,htsmsg_t * msg)3846 _htsp_event_update(epg_broadcast_t *ebc, const char *method, htsmsg_t *msg)
3847 {
3848 htsp_connection_t *htsp;
3849 LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3850 if (htsp->htsp_async_mode & HTSP_ASYNC_EPG) {
3851 /* Use last update instead of window time as we do not want to push an update
3852 * for an event we still have to send with "htsp_epg_window_cb" */
3853 if (!htsp->htsp_epg_window || ebc->start <= htsp->htsp_epg_lastupdate) {
3854 if (htsp_user_access_channel(htsp,ebc->channel)) {
3855 htsmsg_t *m = msg ? htsmsg_copy(msg)
3856 : htsp_build_event(ebc, method, htsp->htsp_language, 0, htsp);
3857 htsp_send_message(htsp, m, NULL);
3858 }
3859 }
3860 }
3861 }
3862 htsmsg_destroy(msg);
3863 }
3864
3865 /**
3866 * Event added
3867 */
3868 void
htsp_event_add(epg_broadcast_t * ebc)3869 htsp_event_add(epg_broadcast_t *ebc)
3870 {
3871 _htsp_event_update(ebc, "eventAdd", NULL);
3872 }
3873
3874 /**
3875 * Event updated
3876 */
3877 void
htsp_event_update(epg_broadcast_t * ebc)3878 htsp_event_update(epg_broadcast_t *ebc)
3879 {
3880 _htsp_event_update(ebc, "eventUpdate", NULL);
3881 }
3882
3883 /**
3884 * Event deleted
3885 */
3886 void
htsp_event_delete(epg_broadcast_t * ebc)3887 htsp_event_delete(epg_broadcast_t *ebc)
3888 {
3889 htsmsg_t *m = htsmsg_create_map();
3890 htsmsg_add_str(m, "method", "eventDelete");
3891 htsmsg_add_u32(m, "eventId", ebc->id);
3892 htsp_async_send(m, HTSP_ASYNC_EPG, HTSP_ASYNC_AUX_EPG, ebc);
3893 }
3894
3895 static const char frametypearray[PKT_NTYPES] = {
3896 [PKT_I_FRAME] = 'I',
3897 [PKT_P_FRAME] = 'P',
3898 [PKT_B_FRAME] = 'B',
3899 };
3900
3901 /**
3902 * Build a htsmsg from a th_pkt and enqueue it on our HTSP service
3903 */
3904 static void
htsp_stream_deliver(htsp_subscription_t * hs,th_pkt_t * pkt)3905 htsp_stream_deliver(htsp_subscription_t *hs, th_pkt_t *pkt)
3906 {
3907 htsmsg_t *m;
3908 htsp_msg_t *hm;
3909 htsp_connection_t *htsp = hs->hs_htsp;
3910 int64_t ts;
3911 int qlen = hs->hs_q.hmq_payload;
3912 int video = SCT_ISVIDEO(pkt->pkt_type);
3913 size_t payloadlen;
3914
3915 if (pkt->pkt_err)
3916 hs->hs_data_errors += pkt->pkt_err;
3917 if(pkt->pkt_payload == NULL) {
3918 return;
3919 }
3920
3921 if(!htsp_is_stream_enabled(hs, pkt->pkt_componentindex)) {
3922 pkt_ref_dec(pkt);
3923 return;
3924 }
3925
3926 if(video &&
3927 ((qlen > hs->hs_queue_depth && pkt->v.pkt_frametype == PKT_B_FRAME) ||
3928 (qlen > hs->hs_queue_depth * 2 && pkt->v.pkt_frametype == PKT_P_FRAME) ||
3929 (qlen > hs->hs_queue_depth * 3))) {
3930
3931 hs->hs_dropstats[pkt->v.pkt_frametype]++;
3932
3933 /* Queue size protection */
3934 pkt_ref_dec(pkt);
3935 return;
3936 }
3937
3938 m = htsmsg_create_map();
3939
3940 htsmsg_add_str(m, "method", "muxpkt");
3941 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
3942 if (video)
3943 htsmsg_add_u32(m, "frametype", frametypearray[pkt->v.pkt_frametype]);
3944
3945 htsmsg_add_u32(m, "stream", pkt->pkt_componentindex);
3946 htsmsg_add_u32(m, "com", pkt->pkt_commercial);
3947
3948 if(pkt->pkt_pts != PTS_UNSET) {
3949 int64_t pts = hs->hs_90khz ? pkt->pkt_pts : ts_rescale(pkt->pkt_pts, 1000000);
3950 htsmsg_add_s64(m, "pts", pts);
3951 }
3952
3953 if(pkt->pkt_dts != PTS_UNSET) {
3954 int64_t dts = hs->hs_90khz ? pkt->pkt_dts : ts_rescale(pkt->pkt_dts, 1000000);
3955 htsmsg_add_s64(m, "dts", dts);
3956 }
3957
3958 uint32_t dur = hs->hs_90khz ? pkt->pkt_duration : ts_rescale(pkt->pkt_duration, 1000000);
3959 htsmsg_add_u32(m, "duration", dur);
3960
3961 /**
3962 * Since we will serialize directly we use 'binptr' which is a binary
3963 * object that just points to data, thus avoiding a copy.
3964 */
3965 payloadlen = pktbuf_len(pkt->pkt_payload);
3966 htsmsg_add_binptr(m, "payload", pktbuf_ptr(pkt->pkt_payload), payloadlen);
3967 htsp_send_subscription(htsp, m, pkt->pkt_payload, hs, payloadlen);
3968 atomic_add(&hs->hs_s_bytes_out, payloadlen);
3969
3970 if(mono2sec(hs->hs_last_report) != mono2sec(mclk())) {
3971
3972 /* Send a queue and signal status report every second */
3973
3974 hs->hs_last_report = mclk();
3975
3976 m = htsmsg_create_map();
3977 htsmsg_add_str(m, "method", "queueStatus");
3978 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
3979 htsmsg_add_u32(m, "packets", hs->hs_q.hmq_length);
3980 htsmsg_add_u32(m, "bytes", hs->hs_q.hmq_payload);
3981 if (hs->hs_data_errors)
3982 htsmsg_add_u32(m, "errors", hs->hs_data_errors);
3983
3984 /**
3985 * Figure out real time queue delay
3986 */
3987
3988 pthread_mutex_lock(&htsp->htsp_out_mutex);
3989
3990 int64_t min_dts = PTS_UNSET;
3991 int64_t max_dts = PTS_UNSET;
3992 TAILQ_FOREACH(hm, &hs->hs_q.hmq_q, hm_link) {
3993 if(!hm->hm_msg)
3994 continue;
3995 if(htsmsg_get_s64(hm->hm_msg, "dts", &ts))
3996 continue;
3997 if(ts == PTS_UNSET)
3998 continue;
3999
4000 if(min_dts == PTS_UNSET)
4001 min_dts = ts;
4002 else
4003 min_dts = MIN(ts, min_dts);
4004
4005 if(max_dts == PTS_UNSET)
4006 max_dts = ts;
4007 else
4008 max_dts = MAX(ts, max_dts);
4009 }
4010
4011 htsmsg_add_s64(m, "delay", max_dts - min_dts);
4012
4013 pthread_mutex_unlock(&htsp->htsp_out_mutex);
4014
4015 htsmsg_add_u32(m, "Bdrops", hs->hs_dropstats[PKT_B_FRAME]);
4016 htsmsg_add_u32(m, "Pdrops", hs->hs_dropstats[PKT_P_FRAME]);
4017 htsmsg_add_u32(m, "Idrops", hs->hs_dropstats[PKT_I_FRAME]);
4018
4019 /* We use a special queue for queue status message so they're not
4020 blocked by anything else */
4021 htsp_send_message(hs->hs_htsp, m, &hs->hs_htsp->htsp_hmq_qstatus);
4022 }
4023 pkt_ref_dec(pkt);
4024 }
4025
4026 /**
4027 * Send a 'subscriptionStart' message to client informing about
4028 * delivery start and all components.
4029 */
4030 static void
htsp_subscription_start(htsp_subscription_t * hs,const streaming_start_t * ss)4031 htsp_subscription_start(htsp_subscription_t *hs, const streaming_start_t *ss)
4032 {
4033 htsmsg_t *m,*streams, *c, *sourceinfo;
4034 const char *type;
4035 tvh_uuid_t hex;
4036 int i;
4037 const source_info_t *si;
4038
4039 tvhdebug(LS_HTSP, "%s - subscription start", hs->hs_htsp->htsp_logname);
4040
4041 for(i = 0; i < ss->ss_num_components; i++) {
4042 const streaming_start_component_t *ssc = &ss->ss_components[i];
4043 if (ssc->ssc_disabled) continue;
4044 if (SCT_ISVIDEO(ssc->ssc_type)) {
4045 if (ssc->ssc_width == 0 || ssc->ssc_height == 0) {
4046 hs->hs_wait_for_video = 1;
4047 return;
4048 }
4049 break;
4050 }
4051 }
4052 hs->hs_wait_for_video = 0;
4053
4054 m = htsmsg_create_map();
4055 streams = htsmsg_create_list();
4056 sourceinfo = htsmsg_create_map();
4057 for(i = 0; i < ss->ss_num_components; i++) {
4058 const streaming_start_component_t *ssc = &ss->ss_components[i];
4059 if(ssc->ssc_disabled) continue;
4060
4061 c = htsmsg_create_map();
4062 htsmsg_add_u32(c, "index", ssc->ssc_index);
4063 if (ssc->ssc_type == SCT_MP4A)
4064 type = "AAC"; /* override */
4065 else
4066 type = streaming_component_type2txt(ssc->ssc_type);
4067 htsmsg_add_str(c, "type", type);
4068 if(ssc->ssc_lang[0])
4069 htsmsg_add_str(c, "language", ssc->ssc_lang);
4070
4071 if(ssc->ssc_type == SCT_DVBSUB) {
4072 htsmsg_add_u32(c, "composition_id", ssc->ssc_composition_id);
4073 htsmsg_add_u32(c, "ancillary_id", ssc->ssc_ancillary_id);
4074 }
4075
4076 if(SCT_ISVIDEO(ssc->ssc_type)) {
4077 if(ssc->ssc_width)
4078 htsmsg_add_u32(c, "width", ssc->ssc_width);
4079 if(ssc->ssc_height)
4080 htsmsg_add_u32(c, "height", ssc->ssc_height);
4081 if(ssc->ssc_frameduration)
4082 htsmsg_add_u32(c, "duration", hs->hs_90khz ? ssc->ssc_frameduration :
4083 ts_rescale(ssc->ssc_frameduration, 1000000));
4084 if (ssc->ssc_aspect_num)
4085 htsmsg_add_u32(c, "aspect_num", ssc->ssc_aspect_num);
4086 if (ssc->ssc_aspect_den)
4087 htsmsg_add_u32(c, "aspect_den", ssc->ssc_aspect_den);
4088 }
4089
4090 if (SCT_ISAUDIO(ssc->ssc_type))
4091 {
4092 htsmsg_add_u32(c, "audio_type", ssc->ssc_audio_type);
4093 if (ssc->ssc_audio_version)
4094 htsmsg_add_u32(c, "audio_version", ssc->ssc_audio_version);
4095 if (ssc->ssc_channels)
4096 htsmsg_add_u32(c, "channels", ssc->ssc_channels);
4097 if (ssc->ssc_sri)
4098 htsmsg_add_u32(c, "rate", ssc->ssc_sri);
4099 }
4100
4101 if (ssc->ssc_gh)
4102 htsmsg_add_binptr(m, "meta", pktbuf_ptr(ssc->ssc_gh),
4103 pktbuf_len(ssc->ssc_gh));
4104
4105 htsmsg_add_msg(streams, NULL, c);
4106 }
4107
4108 htsmsg_add_msg(m, "streams", streams);
4109
4110 si = &ss->ss_si;
4111 if(!uuid_empty(&si->si_adapter_uuid)) {
4112 uuid_bin2hex(&si->si_adapter_uuid, &hex);
4113 htsmsg_add_str(sourceinfo, "adapter_uuid", hex.hex);
4114 }
4115 if(!uuid_empty(&si->si_mux_uuid)) {
4116 uuid_bin2hex(&si->si_mux_uuid, &hex);
4117 htsmsg_add_str(sourceinfo, "mux_uuid", hex.hex);
4118 }
4119 if(!uuid_empty(&si->si_network_uuid)) {
4120 uuid_bin2hex(&si->si_network_uuid, &hex);
4121 htsmsg_add_str(sourceinfo, "network_uuid", hex.hex);
4122 }
4123 if (!htsp_anonymize(hs->hs_htsp)) {
4124 htsmsg_add_str2(sourceinfo, "adapter", si->si_adapter );
4125 htsmsg_add_str2(sourceinfo, "mux", si->si_mux );
4126 htsmsg_add_str2(sourceinfo, "network", si->si_network );
4127 htsmsg_add_str2(sourceinfo, "network_type", si->si_network_type);
4128 htsmsg_add_str2(sourceinfo, "provider", si->si_provider );
4129 htsmsg_add_str2(sourceinfo, "service", si->si_service );
4130 htsmsg_add_str2(sourceinfo, "satpos", si->si_satpos );
4131 }
4132
4133 htsmsg_add_msg(m, "sourceinfo", sourceinfo);
4134
4135 htsmsg_add_str(m, "method", "subscriptionStart");
4136 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4137 htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4138 }
4139
4140 /**
4141 * Send a 'subscriptionStop' stop
4142 */
4143 static void
htsp_subscription_stop(htsp_subscription_t * hs,const char * err,const char * subscriptionErr)4144 htsp_subscription_stop(htsp_subscription_t *hs, const char *err, const char *subscriptionErr)
4145 {
4146 htsmsg_t *m = htsmsg_create_map();
4147 htsmsg_add_str(m, "method", "subscriptionStop");
4148 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4149 tvhdebug(LS_HTSP, "%s - subscription stop", hs->hs_htsp->htsp_logname);
4150
4151 if(err != NULL)
4152 htsmsg_add_str(m, "status", err);
4153
4154 if(subscriptionErr != NULL)
4155 htsmsg_add_str(m, "subscriptionError", subscriptionErr);
4156
4157 htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4158 }
4159
4160 /**
4161 * Send a 'subscriptionGrace' message
4162 */
4163 static void
htsp_subscription_grace(htsp_subscription_t * hs,int grace)4164 htsp_subscription_grace(htsp_subscription_t *hs, int grace)
4165 {
4166 htsmsg_t *m = htsmsg_create_map();
4167 htsmsg_add_str(m, "method", "subscriptionGrace");
4168 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4169 htsmsg_add_u32(m, "graceTimeout", grace);
4170 tvhdebug(LS_HTSP, "%s - subscription grace %i seconds", hs->hs_htsp->htsp_logname, grace);
4171
4172 htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4173 }
4174
4175 /**
4176 * Send a 'subscriptionStatus' message
4177 */
4178 static void
htsp_subscription_status(htsp_subscription_t * hs,const char * err,const char * subscriptionErr)4179 htsp_subscription_status(htsp_subscription_t *hs, const char *err, const char *subscriptionErr)
4180 {
4181 htsmsg_t *m = htsmsg_create_map();
4182 htsmsg_add_str(m, "method", "subscriptionStatus");
4183 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4184
4185 if(err != NULL)
4186 htsmsg_add_str(m, "status", err);
4187
4188 if(subscriptionErr != NULL)
4189 htsmsg_add_str(m, "subscriptionError", subscriptionErr);
4190
4191 htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4192 }
4193
4194 /**
4195 * Convert the SM_CODE to an understandable string
4196 */
4197 const char *
_htsp_get_subscription_status(int smcode)4198 _htsp_get_subscription_status(int smcode)
4199 {
4200 switch (smcode)
4201 {
4202 case SM_CODE_NOT_FREE:
4203 case SM_CODE_NO_FREE_ADAPTER:
4204 case SM_CODE_NO_ADAPTERS:
4205 return "noFreeAdapter";
4206 case SM_CODE_NO_ACCESS:
4207 case SM_CODE_NO_DESCRAMBLER:
4208 return "scrambled";
4209 case SM_CODE_NO_INPUT:
4210 case SM_CODE_BAD_SIGNAL:
4211 return "badSignal";
4212 case SM_CODE_TUNING_FAILED:
4213 return "tuningFailed";
4214 case SM_CODE_SUBSCRIPTION_OVERRIDDEN:
4215 return "subscriptionOverridden";
4216 case SM_CODE_MUX_NOT_ENABLED:
4217 return "muxNotEnabled";
4218 case SM_CODE_INVALID_TARGET:
4219 return "invalidTarget";
4220 case SM_CODE_USER_ACCESS:
4221 return "userAccess";
4222 case SM_CODE_USER_LIMIT:
4223 return "userLimit";
4224 case SM_CODE_WEAK_STREAM:
4225 return "weakStream";
4226 case SM_CODE_NO_SPACE:
4227 return "noDiskSpace";
4228 default:
4229 return streaming_code2txt(smcode);
4230 }
4231 }
4232
4233 /**
4234 *
4235 */
4236 static void
htsp_subscription_service_status(htsp_subscription_t * hs,int status)4237 htsp_subscription_service_status(htsp_subscription_t *hs, int status)
4238 {
4239 if(status & TSS_PACKETS) {
4240 htsp_subscription_status(hs, NULL, NULL);
4241 } else if(status & TSS_ERRORS) {
4242 htsp_subscription_status(hs, service_tss2text(status), NULL);
4243 }
4244 }
4245
4246 /**
4247 *
4248 */
4249 static void
htsp_subscription_signal_status(htsp_subscription_t * hs,signal_status_t * sig)4250 htsp_subscription_signal_status(htsp_subscription_t *hs, signal_status_t *sig)
4251 {
4252 htsmsg_t *m = htsmsg_create_map();
4253 htsmsg_add_str(m, "method", "signalStatus");
4254 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4255 if (!htsp_anonymize(hs->hs_htsp)) {
4256 htsmsg_add_str(m, "feStatus", sig->status_text);
4257 if((sig->snr != -2) && (sig->snr_scale == SIGNAL_STATUS_SCALE_RELATIVE))
4258 htsmsg_add_u32(m, "feSNR", sig->snr);
4259 if((sig->signal != -2) && (sig->signal_scale == SIGNAL_STATUS_SCALE_RELATIVE))
4260 htsmsg_add_u32(m, "feSignal", sig->signal);
4261 if(sig->ber != -2)
4262 htsmsg_add_u32(m, "feBER", sig->ber);
4263 if(sig->unc != -2)
4264 htsmsg_add_u32(m, "feUNC", sig->unc);
4265 } else {
4266 htsmsg_add_str(m, "feStatus", "");
4267 }
4268 htsp_send_message(hs->hs_htsp, m, &hs->hs_htsp->htsp_hmq_qstatus);
4269 }
4270
4271 /**
4272 *
4273 */
4274 static void
htsp_subscription_descramble_info(htsp_subscription_t * hs,descramble_info_t * di)4275 htsp_subscription_descramble_info(htsp_subscription_t *hs, descramble_info_t *di)
4276 {
4277 /* don't bother old clients */
4278 if (hs->hs_htsp->htsp_version < 24)
4279 return;
4280 if (htsp_anonymize(hs->hs_htsp))
4281 return;
4282
4283 htsmsg_t *m = htsmsg_create_map();
4284 htsmsg_add_str(m, "method", "descrambleInfo");
4285 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4286 htsmsg_add_u32(m, "pid", di->pid);
4287 htsmsg_add_u32(m, "caid", di->caid);
4288 htsmsg_add_u32(m, "provid", di->provid);
4289 htsmsg_add_u32(m, "ecmtime", di->ecmtime);
4290 htsmsg_add_u32(m, "hops", di->hops);
4291 if (di->cardsystem[0])
4292 htsmsg_add_str(m, "cardsystem", di->cardsystem);
4293 if (di->reader[0])
4294 htsmsg_add_str(m, "reader", di->reader);
4295 if (di->from[0])
4296 htsmsg_add_str(m, "from", di->from);
4297 if (di->protocol[0])
4298 htsmsg_add_str(m, "protocol", di->protocol);
4299 htsp_send_message(hs->hs_htsp, m, &hs->hs_htsp->htsp_hmq_qstatus);
4300 }
4301
4302 /**
4303 *
4304 */
4305 static void
htsp_subscription_speed(htsp_subscription_t * hs,int speed)4306 htsp_subscription_speed(htsp_subscription_t *hs, int speed)
4307 {
4308 htsmsg_t *m = htsmsg_create_map();
4309 tvhdebug(LS_HTSP, "%s - subscription speed", hs->hs_htsp->htsp_logname);
4310 htsmsg_add_str(m, "method", "subscriptionSpeed");
4311 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4312 htsmsg_add_s32(m, "speed", speed);
4313 htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4314 }
4315
4316 /**
4317 *
4318 */
4319 #if ENABLE_TIMESHIFT
4320 static void
htsp_subscription_timeshift_status(htsp_subscription_t * hs,timeshift_status_t * status)4321 htsp_subscription_timeshift_status(htsp_subscription_t *hs, timeshift_status_t *status)
4322 {
4323 htsmsg_t *m = htsmsg_create_map();
4324 htsmsg_add_str(m, "method", "timeshiftStatus");
4325 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4326 htsmsg_add_u32(m, "full", status->full);
4327 htsmsg_add_s64(m, "shift", hs->hs_90khz ? status->shift : ts_rescale(status->shift, 1000000));
4328 if (status->pts_start != PTS_UNSET)
4329 htsmsg_add_s64(m, "start", hs->hs_90khz ? status->pts_start : ts_rescale(status->pts_start, 1000000)) ;
4330 if (status->pts_end != PTS_UNSET)
4331 htsmsg_add_s64(m, "end", hs->hs_90khz ? status->pts_end : ts_rescale(status->pts_end, 1000000)) ;
4332 htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4333 }
4334 #endif
4335
4336 /**
4337 *
4338 */
4339 static void
htsp_subscription_skip(htsp_subscription_t * hs,streaming_skip_t * skip)4340 htsp_subscription_skip(htsp_subscription_t *hs, streaming_skip_t *skip)
4341 {
4342 htsmsg_t *m = htsmsg_create_map();
4343 tvhdebug(LS_HTSP, "%s - subscription skip", hs->hs_htsp->htsp_logname);
4344 htsmsg_add_str(m, "method", "subscriptionSkip");
4345 htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4346
4347 /* Flush pkt buffers */
4348 #if ENABLE_TIMESHIFT
4349 if (skip->type != SMT_SKIP_ERROR && timeshift_conf.enabled) {
4350 htsp_flush_queue(hs->hs_htsp, &hs->hs_q, 0);
4351 htsp_subscription_timeshift_status(hs, &skip->timeshift);
4352 }
4353 #endif
4354
4355 if (skip->type == SMT_SKIP_ABS_TIME || skip->type == SMT_SKIP_ABS_SIZE)
4356 htsmsg_add_u32(m, "absolute", 1);
4357 if (skip->type == SMT_SKIP_ERROR)
4358 htsmsg_add_u32(m, "error", 1);
4359 else if (skip->type == SMT_SKIP_ABS_TIME || skip->type == SMT_SKIP_REL_TIME)
4360 htsmsg_add_s64(m, "time", hs->hs_90khz ? skip->time : ts_rescale(skip->time, 1000000));
4361 else if (skip->type == SMT_SKIP_ABS_SIZE || skip->type == SMT_SKIP_REL_SIZE)
4362 htsmsg_add_s64(m, "size", skip->size);
4363 htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4364 }
4365
4366 /**
4367 *
4368 */
4369 static void
htsp_streaming_input(void * opaque,streaming_message_t * sm)4370 htsp_streaming_input(void *opaque, streaming_message_t *sm)
4371 {
4372 htsp_subscription_t *hs = opaque;
4373
4374 switch(sm->sm_type) {
4375 case SMT_PACKET:
4376 if (hs->hs_wait_for_video)
4377 break;
4378 if (!hs->hs_first)
4379 tvhdebug(LS_HTSP, "%s - first packet", hs->hs_htsp->htsp_logname);
4380 hs->hs_first = 1;
4381 htsp_stream_deliver(hs, sm->sm_data);
4382 // reference is transfered
4383 sm->sm_data = NULL;
4384 break;
4385
4386 case SMT_START:
4387 htsp_subscription_start(hs, sm->sm_data);
4388 break;
4389
4390 case SMT_STOP:
4391 htsp_subscription_stop(hs, streaming_code2txt(sm->sm_code),
4392 sm->sm_code ? _htsp_get_subscription_status(sm->sm_code) : NULL);
4393 break;
4394
4395 case SMT_GRACE:
4396 htsp_subscription_grace(hs, sm->sm_code);
4397 break;
4398
4399 case SMT_SERVICE_STATUS:
4400 htsp_subscription_service_status(hs, sm->sm_code);
4401 break;
4402
4403 case SMT_SIGNAL_STATUS:
4404 htsp_subscription_signal_status(hs, sm->sm_data);
4405 break;
4406
4407 case SMT_DESCRAMBLE_INFO:
4408 htsp_subscription_descramble_info(hs, sm->sm_data);
4409 break;
4410
4411 case SMT_NOSTART:
4412 case SMT_NOSTART_WARN:
4413 htsp_subscription_status(hs, streaming_code2txt(sm->sm_code),
4414 sm->sm_code ? _htsp_get_subscription_status(sm->sm_code) : NULL);
4415 break;
4416
4417 case SMT_MPEGTS:
4418 break;
4419
4420 case SMT_EXIT:
4421 abort();
4422
4423 case SMT_SKIP:
4424 htsp_subscription_skip(hs, sm->sm_data);
4425 break;
4426
4427 case SMT_SPEED:
4428 htsp_subscription_speed(hs, sm->sm_code);
4429 break;
4430
4431 case SMT_TIMESHIFT_STATUS:
4432 #if ENABLE_TIMESHIFT
4433 htsp_subscription_timeshift_status(hs, sm->sm_data);
4434 #endif
4435 break;
4436 }
4437 streaming_msg_free(sm);
4438 }
4439
4440 static htsmsg_t *
htsp_streaming_input_info(void * opaque,htsmsg_t * list)4441 htsp_streaming_input_info(void *opaque, htsmsg_t *list)
4442 {
4443 char buf[512];
4444 htsp_subscription_t *hs = opaque;
4445 snprintf(buf, sizeof(buf), "htsp input: %s", hs->hs_htsp->htsp_logname);
4446 htsmsg_add_str(list, NULL, buf);
4447 return list;
4448 }
4449