1 /*
2 * Electronic Program Guide - Common functions
3 * Copyright (C) 2012 Adam Sutton
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 <stdio.h>
20 #include <unistd.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <regex.h>
24 #include <assert.h>
25 #include <inttypes.h>
26 #include <time.h>
27
28 #include "tvheadend.h"
29 #include "config.h"
30 #include "queue.h"
31 #include "channels.h"
32 #include "settings.h"
33 #include "epg.h"
34 #include "dvr/dvr.h"
35 #include "htsp_server.h"
36 #include "epggrab.h"
37 #include "imagecache.h"
38 #include "notify.h"
39
40 /* Broadcast hashing */
41 #define EPG_HASH_WIDTH 1024
42 #define EPG_HASH_MASK (EPG_HASH_WIDTH - 1)
43
44 /* Objects tree */
45 epg_object_tree_t epg_objects[EPG_HASH_WIDTH];
46
47 /* URI lists */
48 epg_object_tree_t epg_brands;
49 epg_object_tree_t epg_seasons;
50 epg_object_tree_t epg_episodes;
51 epg_object_tree_t epg_serieslinks;
52
53 /* Other special case lists */
54 epg_object_list_t epg_object_unref;
55 epg_object_list_t epg_object_updated;
56
57 int epg_in_load;
58
59 /* Global counter */
60 static uint32_t _epg_object_idx = 0;
61
62 /*
63 *
64 */
epg_id_tree(epg_object_t * eo)65 static inline epg_object_tree_t *epg_id_tree( epg_object_t *eo )
66 {
67 return &epg_objects[eo->id & EPG_HASH_MASK];
68 }
69
70 /* **************************************************************************
71 * Comparators / Ordering
72 * *************************************************************************/
73
_id_cmp(const void * a,const void * b)74 static int _id_cmp ( const void *a, const void *b )
75 {
76 return ((epg_object_t*)a)->id - ((epg_object_t*)b)->id;
77 }
78
_uri_cmp(const void * a,const void * b)79 static int _uri_cmp ( const void *a, const void *b )
80 {
81 return strcmp(((epg_object_t*)a)->uri, ((epg_object_t*)b)->uri);
82 }
83
_ebc_start_cmp(const void * a,const void * b)84 static int _ebc_start_cmp ( const void *a, const void *b )
85 {
86 return ((epg_broadcast_t*)a)->start - ((epg_broadcast_t*)b)->start;
87 }
88
_season_order(const void * _a,const void * _b)89 static int _season_order ( const void *_a, const void *_b )
90 {
91 const epg_season_t *a = (const epg_season_t*)_a;
92 const epg_season_t *b = (const epg_season_t*)_b;
93 if (!a || !a->number) return 1;
94 if (!b || !b->number) return -1;
95 return a->number - b->number;
96 }
97
98 // Note: this will do nothing with text episode numbering
_episode_order(const void * _a,const void * _b)99 static int _episode_order ( const void *_a, const void *_b )
100 {
101 int r, as, bs;
102 const epg_episode_t *a = (const epg_episode_t*)_a;
103 const epg_episode_t *b = (const epg_episode_t*)_b;
104 if (!a) return -1;
105 if (!b) return 1;
106 if (a->season) as = a->season->number;
107 else as = a->epnum.s_num;
108 if (b->season) bs = b->season->number;
109 else bs = b->epnum.s_num;
110 r = as - bs;
111 if (r) return r;
112 r = a->epnum.e_num - b->epnum.e_num;
113 if (r) return r;
114 return a->epnum.p_num - b->epnum.p_num;
115 }
116
epg_updated(void)117 void epg_updated ( void )
118 {
119 epg_object_t *eo;
120
121 lock_assert(&global_lock);
122
123 /* Remove unref'd */
124 while ((eo = LIST_FIRST(&epg_object_unref))) {
125 tvhtrace(LS_EPG,
126 "unref'd object %u (%s) created during update", eo->id, eo->uri);
127 LIST_REMOVE(eo, un_link);
128 eo->ops->destroy(eo);
129 }
130 // Note: we do things this way around since unref'd objects are not likely
131 // to be useful to DVR since they will relate to episode/seasons/brands
132 // with no valid broadcasts etc..
133
134 /* Update updated */
135 while ((eo = LIST_FIRST(&epg_object_updated))) {
136 eo->ops->update(eo);
137 LIST_REMOVE(eo, up_link);
138 eo->_updated = 0;
139 eo->_created = 1;
140 }
141 }
142
143 /* **************************************************************************
144 * Object (Generic routines)
145 * *************************************************************************/
146
_epg_object_destroy(epg_object_t * eo,epg_object_tree_t * tree)147 static void _epg_object_destroy
148 ( epg_object_t *eo, epg_object_tree_t *tree )
149 {
150 assert(eo->refcount == 0);
151 tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] destroy",
152 eo, eo->id, eo->type, eo->uri);
153 if (eo->uri) free(eo->uri);
154 if (tree) RB_REMOVE(tree, eo, uri_link);
155 if (eo->_updated) LIST_REMOVE(eo, up_link);
156 RB_REMOVE(epg_id_tree(eo), eo, id_link);
157 }
158
_epg_object_getref(void * o)159 static void _epg_object_getref ( void *o )
160 {
161 epg_object_t *eo = o;
162 tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] getref %d",
163 eo, eo->id, eo->type, eo->uri, eo->refcount+1);
164 if (eo->refcount == 0) LIST_REMOVE(eo, un_link);
165 eo->refcount++;
166 }
167
_epg_object_putref(void * o)168 static int _epg_object_putref ( void *o )
169 {
170 epg_object_t *eo = o;
171 tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] putref %d",
172 eo, eo->id, eo->type, eo->uri, eo->refcount-1);
173 assert(eo->refcount>0);
174 eo->refcount--;
175 if (!eo->refcount) {
176 eo->ops->destroy(eo);
177 return 1;
178 }
179 return 0;
180 }
181
_epg_object_set_updated(void * o)182 static void _epg_object_set_updated ( void *o )
183 {
184 epg_object_t *eo = o;
185 if (!eo->_updated) {
186 tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] updated",
187 eo, eo->id, eo->type, eo->uri);
188 eo->_updated = 1;
189 eo->updated = gclk();
190 LIST_INSERT_HEAD(&epg_object_updated, eo, up_link);
191 }
192 }
193
_epg_object_can_remove(void * _old,void * _new)194 static int _epg_object_can_remove ( void *_old, void *_new )
195 {
196 epggrab_module_t *ograb, *ngrab;
197 ngrab = ((epg_object_t *)_new)->grabber;
198 if (ngrab == NULL) return 0;
199 ograb = ((epg_object_t *)_old)->grabber;
200 if (ograb == NULL || ograb == ngrab) return 1;
201 if (ngrab->priority > ograb->priority) return 1;
202 return 0;
203 }
204
_epg_object_set_grabber(void * o,epggrab_module_t * ngrab)205 static int _epg_object_set_grabber ( void *o, epggrab_module_t *ngrab )
206 {
207 epggrab_module_t *ograb;
208 if (!ngrab) return 1; // grab=NULL is override
209 ograb = ((epg_object_t *)o)->grabber;
210 if (ograb == ngrab) return 1;
211 if (ograb && ograb->priority >= ngrab->priority) return 0;
212 ((epg_object_t *)o)->grabber = ngrab;
213 return 1;
214 }
215
_epg_object_create(void * o)216 static void _epg_object_create ( void *o )
217 {
218 epg_object_t *eo = o;
219 uint32_t id = eo->id;
220 if (!id) eo->id = ++_epg_object_idx;
221 if (!eo->id) eo->id = ++_epg_object_idx;
222 tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] created",
223 eo, eo->id, eo->type, eo->uri);
224 _epg_object_set_updated(eo);
225 LIST_INSERT_HEAD(&epg_object_unref, eo, un_link);
226 while (1) {
227 if (!RB_INSERT_SORTED(epg_id_tree(eo), eo, id_link, _id_cmp))
228 break;
229 if (id) {
230 tvherror(LS_EPG, "fatal error, duplicate EPG ID");
231 abort();
232 }
233 eo->id = ++_epg_object_idx;
234 if (!eo->id) eo->id = ++_epg_object_idx;
235 }
236 }
237
_epg_object_find_by_uri(const char * uri,epggrab_module_t * src,int create,int * save,uint32_t * changes,epg_object_tree_t * tree,epg_object_t ** skel)238 static epg_object_t *_epg_object_find_by_uri
239 ( const char *uri, epggrab_module_t *src, int create, int *save,
240 uint32_t *changes, epg_object_tree_t *tree, epg_object_t **skel )
241 {
242 epg_object_t *eo;
243 int _save;
244
245 assert(skel != NULL);
246 lock_assert(&global_lock);
247
248 (*skel)->uri = (char*)uri;
249
250 /* Find only */
251 if (!create) {
252 eo = RB_FIND(tree, *skel, uri_link, _uri_cmp);
253
254 /* Find/create */
255 } else {
256 eo = RB_INSERT_SORTED(tree, *skel, uri_link, _uri_cmp);
257 if (!eo) {
258 if (changes) *changes |= EPG_CHANGED_CREATE;
259 *save = 1;
260 eo = *skel;
261 *skel = NULL;
262 eo->uri = strdup(uri);
263 _epg_object_create(eo);
264 }
265 }
266 if (eo) {
267 _save = _epg_object_set_grabber(eo, src);
268 if (save) *save |= _save;
269 }
270 return eo;
271 }
272
epg_object_find_by_id(uint32_t id,epg_object_type_t type)273 epg_object_t *epg_object_find_by_id ( uint32_t id, epg_object_type_t type )
274 {
275 epg_object_t *eo, temp;
276 temp.id = id;
277 eo = RB_FIND(epg_id_tree(&temp), &temp, id_link, _id_cmp);
278 if (eo && eo->type == type)
279 return eo;
280 return NULL;
281 }
282
_epg_object_serialize(void * o)283 static htsmsg_t * _epg_object_serialize ( void *o )
284 {
285 epg_object_t *eo = o;
286 tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] serialize",
287 eo, eo->id, eo->type, eo->uri);
288 htsmsg_t *m;
289 if (!eo->id || !eo->type) return NULL;
290 m = htsmsg_create_map();
291 htsmsg_add_u32(m, "id", eo->id);
292 htsmsg_add_u32(m, "type", eo->type);
293 if (eo->uri)
294 htsmsg_add_str(m, "uri", eo->uri);
295 if (eo->grabber)
296 htsmsg_add_str(m, "grabber", eo->grabber->id);
297 htsmsg_add_s64(m, "updated", eo->updated);
298 return m;
299 }
300
_epg_object_deserialize(htsmsg_t * m,epg_object_t * eo)301 static epg_object_t *_epg_object_deserialize ( htsmsg_t *m, epg_object_t *eo )
302 {
303 int64_t s64;
304 uint32_t u32;
305 const char *s;
306 if (htsmsg_get_u32(m, "id", &eo->id)) return NULL;
307 if (htsmsg_get_u32(m, "type", &u32)) return NULL;
308 if (u32 != eo->type) return NULL;
309 eo->uri = (char*)htsmsg_get_str(m, "uri");
310 if ((s = htsmsg_get_str(m, "grabber")))
311 eo->grabber = epggrab_module_find_by_id(s);
312 if (!htsmsg_get_s64(m, "updated", &s64)) {
313 _epg_object_set_updated(eo);
314 eo->updated = s64;
315 }
316 tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] deserialize",
317 eo, eo->id, eo->type, eo->uri);
318 return eo;
319 }
320
_epg_object_set_str(void * o,char ** old,const char * newstr,uint32_t * changed,uint32_t cflag)321 static int _epg_object_set_str
322 ( void *o, char **old, const char *newstr,
323 uint32_t *changed, uint32_t cflag )
324 {
325 int save = 0;
326 epg_object_t *eo = o;
327 if (!eo) return 0;
328 if (changed) *changed |= cflag;
329 if (!*old && !newstr) return 0;
330 if (!*old || !newstr || strcmp(*old, newstr)) {
331 free(*old);
332 *old = newstr ? strdup(newstr) : NULL;
333 _epg_object_set_updated(eo);
334 save = 1;
335 }
336 return save;
337 }
338
_epg_object_set_lang_str(void * o,lang_str_t ** old,const lang_str_t * str,uint32_t * changed,uint32_t cflag)339 static int _epg_object_set_lang_str
340 ( void *o, lang_str_t **old, const lang_str_t *str,
341 uint32_t *changed, uint32_t cflag )
342 {
343 if (!o) return 0;
344 if (changed) *changed |= cflag;
345 if (!*old) {
346 if (!str)
347 return 0;
348 }
349 if (!str) {
350 lang_str_destroy(*old);
351 *old = NULL;
352 _epg_object_set_updated(o);
353 return 1;
354 }
355 if (lang_str_compare(*old, str)) {
356 lang_str_destroy(*old);
357 *old = lang_str_copy(str);
358 _epg_object_set_updated(o);
359 return 1;
360 }
361 return 0;
362 }
363
_epg_object_set_u8(void * o,uint8_t * old,const uint8_t nval,uint32_t * changed,uint32_t cflag)364 static int _epg_object_set_u8
365 ( void *o, uint8_t *old, const uint8_t nval,
366 uint32_t *changed, uint32_t cflag )
367 {
368 int save;
369 if (!o) return 0;
370 if (changed) *changed |= cflag;
371 if ((save = (*old != nval)) != 0) {
372 *old = nval;
373 _epg_object_set_updated(o);
374 }
375 return save;
376 }
377
_epg_object_set_u16(void * o,uint16_t * old,const uint16_t nval,uint32_t * changed,uint32_t cflag)378 static int _epg_object_set_u16
379 ( void *o, uint16_t *old, const uint16_t nval,
380 uint32_t *changed, uint32_t cflag )
381 {
382 int save = 0;
383 if (!o) return 0;
384 if (changed) *changed |= cflag;
385 if ((save = (*old != nval)) != 0) {
386 *old = nval;
387 _epg_object_set_updated(o);
388 save = 1;
389 }
390 return save;
391 }
392
epg_object_serialize(epg_object_t * eo)393 htsmsg_t *epg_object_serialize ( epg_object_t *eo )
394 {
395 if (!eo) return NULL;
396 switch (eo->type) {
397 case EPG_BRAND:
398 return epg_brand_serialize((epg_brand_t*)eo);
399 case EPG_SEASON:
400 return epg_season_serialize((epg_season_t*)eo);
401 case EPG_EPISODE:
402 return epg_episode_serialize((epg_episode_t*)eo);
403 case EPG_BROADCAST:
404 return epg_broadcast_serialize((epg_broadcast_t*)eo);
405 case EPG_SERIESLINK:
406 return epg_serieslink_serialize((epg_serieslink_t*)eo);
407 default:
408 return NULL;
409 }
410 }
411
epg_object_deserialize(htsmsg_t * msg,int create,int * save)412 epg_object_t *epg_object_deserialize ( htsmsg_t *msg, int create, int *save )
413 {
414 uint32_t type;
415 if (!msg) return NULL;
416 type = htsmsg_get_u32_or_default(msg, "type", 0);
417 switch (type) {
418 case EPG_BRAND:
419 return (epg_object_t*)epg_brand_deserialize(msg, create, save);
420 case EPG_SEASON:
421 return (epg_object_t*)epg_season_deserialize(msg, create, save);
422 case EPG_EPISODE:
423 return (epg_object_t*)epg_episode_deserialize(msg, create, save);
424 case EPG_BROADCAST:
425 return (epg_object_t*)epg_broadcast_deserialize(msg, create, save);
426 case EPG_SERIESLINK:
427 return (epg_object_t*)epg_serieslink_deserialize(msg, create, save);
428 }
429 return NULL;
430 }
431
432 /* **************************************************************************
433 * Brand
434 * *************************************************************************/
435
_epg_brand_destroy(void * eo)436 static void _epg_brand_destroy ( void *eo )
437 {
438 epg_brand_t *eb = (epg_brand_t*)eo;
439 if (LIST_FIRST(&eb->seasons)) {
440 tvhlog(LOG_CRIT, LS_EPG, "attempt to destroy brand with seasons");
441 assert(0);
442 }
443 if (LIST_FIRST(&eb->episodes)) {
444 tvhlog(LOG_CRIT, LS_EPG, "attempt to destroy brand with episodes");
445 assert(0);
446 }
447 if (eb->title) lang_str_destroy(eb->title);
448 if (eb->summary) lang_str_destroy(eb->summary);
449 if (eb->image) free(eb->image);
450 _epg_object_destroy(eo, &epg_brands);
451 free(eb);
452 }
453
_epg_brand_updated(void * o)454 static void _epg_brand_updated ( void *o )
455 {
456 dvr_autorec_check_brand((epg_brand_t*)o);
457 }
458
459 static epg_object_ops_t _epg_brand_ops = {
460 .getref = _epg_object_getref,
461 .putref = _epg_object_putref,
462 .destroy = _epg_brand_destroy,
463 .update = _epg_brand_updated,
464 };
465
_epg_brand_skel(void)466 static epg_object_t **_epg_brand_skel ( void )
467 {
468 static epg_object_t *skel = NULL;
469 if (!skel) {
470 skel = calloc(1, sizeof(epg_brand_t));
471 skel->type = EPG_BRAND;
472 skel->ops = &_epg_brand_ops;
473 }
474 return &skel;
475 }
476
epg_brand_find_by_uri(const char * uri,epggrab_module_t * src,int create,int * save,uint32_t * changed)477 epg_brand_t* epg_brand_find_by_uri
478 ( const char *uri, epggrab_module_t *src,
479 int create, int *save, uint32_t *changed )
480 {
481 return (epg_brand_t*)
482 _epg_object_find_by_uri(uri, src, create, save, changed,
483 &epg_brands,
484 _epg_brand_skel());
485 }
486
epg_brand_find_by_id(uint32_t id)487 epg_brand_t *epg_brand_find_by_id ( uint32_t id )
488 {
489 return (epg_brand_t*)epg_object_find_by_id(id, EPG_BRAND);
490 }
491
epg_brand_change_finish(epg_brand_t * brand,uint32_t changes,int merge)492 int epg_brand_change_finish
493 ( epg_brand_t *brand, uint32_t changes, int merge )
494 {
495 int save = 0;
496 if (merge) return 0;
497 if (changes & EPG_CHANGED_CREATE) return 0;
498 if (!(changes & EPG_CHANGED_TITLE))
499 save |= epg_brand_set_title(brand, NULL, NULL);
500 if (!(changes & EPG_CHANGED_SUMMARY))
501 save |= epg_brand_set_summary(brand, NULL, NULL);
502 if (!(changes & EPG_CHANGED_IMAGE))
503 save |= epg_brand_set_image(brand, NULL, NULL);
504 if (!(changes & EPG_CHANGED_SEASON_COUNT))
505 save |= epg_brand_set_season_count(brand, 0, NULL);
506 return save;
507 }
508
epg_brand_set_title(epg_brand_t * brand,const lang_str_t * title,uint32_t * changed)509 int epg_brand_set_title
510 ( epg_brand_t *brand, const lang_str_t *title, uint32_t *changed )
511 {
512 if (!brand) return 0;
513 return _epg_object_set_lang_str(brand, &brand->title, title,
514 changed, EPG_CHANGED_TITLE);
515 }
516
epg_brand_set_summary(epg_brand_t * brand,const lang_str_t * summary,uint32_t * changed)517 int epg_brand_set_summary
518 ( epg_brand_t *brand, const lang_str_t *summary, uint32_t *changed )
519 {
520 if (!brand) return 0;
521 return _epg_object_set_lang_str(brand, &brand->summary, summary,
522 changed, EPG_CHANGED_SUMMARY);
523 }
524
epg_brand_set_image(epg_brand_t * brand,const char * image,uint32_t * changed)525 int epg_brand_set_image
526 ( epg_brand_t *brand, const char *image, uint32_t *changed )
527 {
528 int save;
529 if (!brand) return 0;
530 save = _epg_object_set_str(brand, &brand->image, image,
531 changed, EPG_CHANGED_IMAGE);
532 if (save)
533 imagecache_get_id(image);
534 return save;
535 }
536
epg_brand_set_season_count(epg_brand_t * brand,uint16_t count,uint32_t * changed)537 int epg_brand_set_season_count
538 ( epg_brand_t *brand, uint16_t count, uint32_t *changed )
539 {
540 if (!brand) return 0;
541 return _epg_object_set_u16(brand, &brand->season_count, count,
542 changed, EPG_CHANGED_SEASON_COUNT);
543 }
544
_epg_brand_add_season(epg_brand_t * brand,epg_season_t * season)545 static void _epg_brand_add_season
546 ( epg_brand_t *brand, epg_season_t *season )
547 {
548 _epg_object_getref(brand);
549 _epg_object_set_updated(brand);
550 LIST_INSERT_SORTED(&brand->seasons, season, blink, _season_order);
551 }
552
_epg_brand_rem_season(epg_brand_t * brand,epg_season_t * season)553 static void _epg_brand_rem_season
554 ( epg_brand_t *brand, epg_season_t *season )
555 {
556 LIST_REMOVE(season, blink);
557 _epg_object_set_updated(brand);
558 _epg_object_putref(brand);
559 }
560
_epg_brand_add_episode(epg_brand_t * brand,epg_episode_t * episode)561 static void _epg_brand_add_episode
562 ( epg_brand_t *brand, epg_episode_t *episode )
563 {
564 _epg_object_getref(brand);
565 _epg_object_set_updated(brand);
566 LIST_INSERT_SORTED(&brand->episodes, episode, blink, _episode_order);
567 }
568
_epg_brand_rem_episode(epg_brand_t * brand,epg_episode_t * episode)569 static void _epg_brand_rem_episode
570 ( epg_brand_t *brand, epg_episode_t *episode )
571 {
572 LIST_REMOVE(episode, blink);
573 _epg_object_set_updated(brand);
574 _epg_object_putref(brand);
575 }
576
epg_brand_serialize(epg_brand_t * brand)577 htsmsg_t *epg_brand_serialize ( epg_brand_t *brand )
578 {
579 htsmsg_t *m;
580 if (!brand || !brand->uri) return NULL;
581 if (!(m = _epg_object_serialize(brand))) return NULL;
582 if (brand->title)
583 lang_str_serialize(brand->title, m, "title");
584 if (brand->summary)
585 lang_str_serialize(brand->summary, m, "summary");
586 if (brand->season_count)
587 htsmsg_add_u32(m, "season-count", brand->season_count);
588 if (brand->image)
589 htsmsg_add_str(m, "image", brand->image);
590 return m;
591 }
592
epg_brand_deserialize(htsmsg_t * m,int create,int * save)593 epg_brand_t *epg_brand_deserialize ( htsmsg_t *m, int create, int *save )
594 {
595 epg_object_t **skel = _epg_brand_skel();
596 epg_brand_t *eb;
597 uint32_t u32, changes = 0;
598 const char *str;
599 lang_str_t *ls;
600
601 if (!_epg_object_deserialize(m, *skel)) return NULL;
602 if (!(eb = epg_brand_find_by_uri((*skel)->uri, (*skel)->grabber,
603 create, save, &changes)))
604 return NULL;
605
606 if ((ls = lang_str_deserialize(m, "title"))) {
607 *save |= epg_brand_set_title(eb, ls, &changes);
608 lang_str_destroy(ls);
609 }
610 if ((ls = lang_str_deserialize(m, "summary"))) {
611 *save |= epg_brand_set_summary(eb, ls, &changes);
612 lang_str_destroy(ls);
613 }
614 if (!htsmsg_get_u32(m, "season-count", &u32))
615 *save |= epg_brand_set_season_count(eb, u32, NULL);
616 if ((str = htsmsg_get_str(m, "image")))
617 *save |= epg_brand_set_image(eb, str, &changes);
618
619 *save |= epg_brand_change_finish(eb, changes, 0);
620
621 return eb;
622 }
623
epg_brand_list(void)624 htsmsg_t *epg_brand_list ( void )
625 {
626 epg_object_t *eo;
627 htsmsg_t *a, *e;
628 a = htsmsg_create_list();
629 RB_FOREACH(eo, &epg_brands, uri_link) {
630 assert(eo->type == EPG_BRAND);
631 e = epg_brand_serialize((epg_brand_t*)eo);
632 htsmsg_add_msg(a, NULL, e);
633 }
634 return a;
635 }
636
epg_brand_get_title(const epg_brand_t * b,const char * lang)637 const char *epg_brand_get_title ( const epg_brand_t *b, const char *lang )
638 {
639 if (!b || !b->title) return NULL;
640 return lang_str_get(b->title, lang);
641 }
642
epg_brand_get_summary(const epg_brand_t * b,const char * lang)643 const char *epg_brand_get_summary ( const epg_brand_t *b, const char *lang )
644 {
645 if (!b || !b->summary) return NULL;
646 return lang_str_get(b->summary, lang);
647 }
648
649 /* **************************************************************************
650 * Season
651 * *************************************************************************/
652
_epg_season_destroy(void * eo)653 static void _epg_season_destroy ( void *eo )
654 {
655 epg_season_t *es = (epg_season_t*)eo;
656 if (LIST_FIRST(&es->episodes)) {
657 tvhlog(LOG_CRIT, LS_EPG, "attempt to destory season with episodes");
658 assert(0);
659 }
660 if (es->brand) _epg_brand_rem_season(es->brand, es);
661 if (es->summary) lang_str_destroy(es->summary);
662 if (es->image) free(es->image);
663 _epg_object_destroy(eo, &epg_seasons);
664 free(es);
665 }
666
_epg_season_updated(void * eo)667 static void _epg_season_updated ( void *eo )
668 {
669 dvr_autorec_check_season((epg_season_t*)eo);
670 }
671
672 static epg_object_ops_t _epg_season_ops = {
673 .getref = _epg_object_getref,
674 .putref = _epg_object_putref,
675 .destroy = _epg_season_destroy,
676 .update = _epg_season_updated,
677 };
678
_epg_season_skel(void)679 static epg_object_t **_epg_season_skel ( void )
680 {
681 static epg_object_t *skel = NULL;
682 if (!skel) {
683 skel = calloc(1, sizeof(epg_season_t));
684 skel->type = EPG_SEASON;
685 skel->ops = &_epg_season_ops;
686 }
687 return &skel;
688 }
689
epg_season_find_by_uri(const char * uri,epggrab_module_t * src,int create,int * save,uint32_t * changed)690 epg_season_t* epg_season_find_by_uri
691 ( const char *uri, epggrab_module_t *src,
692 int create, int *save, uint32_t *changed )
693 {
694 return (epg_season_t*)
695 _epg_object_find_by_uri(uri, src, create, save, changed,
696 &epg_seasons,
697 _epg_season_skel());
698 }
699
epg_season_find_by_id(uint32_t id)700 epg_season_t *epg_season_find_by_id ( uint32_t id )
701 {
702 return (epg_season_t*)epg_object_find_by_id(id, EPG_SEASON);
703 }
704
epg_season_change_finish(epg_season_t * season,uint32_t changes,int merge)705 int epg_season_change_finish
706 ( epg_season_t *season, uint32_t changes, int merge )
707 {
708 int save = 0;
709 if (merge) return 0;
710 if (changes & EPG_CHANGED_CREATE) return 0;
711 if (!(changes & EPG_CHANGED_SUMMARY))
712 save |= epg_season_set_summary(season, NULL, NULL);
713 if (!(changes & EPG_CHANGED_IMAGE))
714 save |= epg_season_set_image(season, NULL, NULL);
715 if (!(changes & EPG_CHANGED_EPISODE_COUNT))
716 save |= epg_season_set_episode_count(season, 0, NULL);
717 if (!(changes & EPG_CHANGED_SEASON_NUMBER))
718 save |= epg_season_set_number(season, 0, NULL);
719 if (!(changes & EPG_CHANGED_BRAND))
720 save |= epg_season_set_brand(season, 0, NULL);
721 return save;
722 }
723
epg_season_set_summary(epg_season_t * season,const lang_str_t * summary,uint32_t * changed)724 int epg_season_set_summary
725 ( epg_season_t *season, const lang_str_t *summary, uint32_t *changed )
726 {
727 if (!season) return 0;
728 return _epg_object_set_lang_str(season, &season->summary, summary,
729 changed, EPG_CHANGED_SUMMARY);
730 }
731
epg_season_set_image(epg_season_t * season,const char * image,uint32_t * changed)732 int epg_season_set_image
733 ( epg_season_t *season, const char *image, uint32_t *changed )
734 {
735 int save;
736 if (!season) return 0;
737 save = _epg_object_set_str(season, &season->image, image,
738 changed, EPG_CHANGED_IMAGE);
739 if (save)
740 imagecache_get_id(image);
741 return save;
742 }
743
epg_season_set_episode_count(epg_season_t * season,uint16_t count,uint32_t * changed)744 int epg_season_set_episode_count
745 ( epg_season_t *season, uint16_t count, uint32_t *changed )
746 {
747 if (!season) return 0;
748 return _epg_object_set_u16(season, &season->episode_count, count,
749 changed, EPG_CHANGED_EPISODE_COUNT);
750 }
751
epg_season_set_number(epg_season_t * season,uint16_t number,uint32_t * changed)752 int epg_season_set_number
753 ( epg_season_t *season, uint16_t number, uint32_t *changed )
754 {
755 if (!season) return 0;
756 return _epg_object_set_u16(season, &season->number, number,
757 changed, EPG_CHANGED_SEASON_NUMBER);
758 }
759
epg_season_set_brand(epg_season_t * season,epg_brand_t * brand,uint32_t * changed)760 int epg_season_set_brand
761 ( epg_season_t *season, epg_brand_t *brand, uint32_t *changed )
762 {
763 int save = 0;
764 if (!season) return 0;
765 if (changed) *changed |= EPG_CHANGED_BRAND;
766 if (season->brand != brand) {
767 if (season->brand) _epg_brand_rem_season(season->brand, season);
768 season->brand = brand;
769 if (brand) _epg_brand_add_season(brand, season);
770 _epg_object_set_updated(season);
771 save = 1;
772 }
773 return save;
774 }
775
_epg_season_add_episode(epg_season_t * season,epg_episode_t * episode)776 static void _epg_season_add_episode
777 ( epg_season_t *season, epg_episode_t *episode )
778 {
779 _epg_object_getref(season);
780 _epg_object_set_updated(season);
781 LIST_INSERT_SORTED(&season->episodes, episode, slink, _episode_order);
782 }
783
_epg_season_rem_episode(epg_season_t * season,epg_episode_t * episode)784 static void _epg_season_rem_episode
785 ( epg_season_t *season, epg_episode_t *episode )
786 {
787 LIST_REMOVE(episode, slink);
788 _epg_object_set_updated(season);
789 _epg_object_putref(season);
790 }
791
epg_season_serialize(epg_season_t * season)792 htsmsg_t *epg_season_serialize ( epg_season_t *season )
793 {
794 htsmsg_t *m;
795 if (!season || !season->uri) return NULL;
796 if (!(m = _epg_object_serialize((epg_object_t*)season))) return NULL;
797 if (season->summary)
798 lang_str_serialize(season->summary, m, "summary");
799 if (season->number)
800 htsmsg_add_u32(m, "number", season->number);
801 if (season->episode_count)
802 htsmsg_add_u32(m, "episode-count", season->episode_count);
803 if (season->brand)
804 htsmsg_add_str(m, "brand", season->brand->uri);
805 if (season->image)
806 htsmsg_add_str(m, "image", season->image);
807 return m;
808 }
809
epg_season_deserialize(htsmsg_t * m,int create,int * save)810 epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save )
811 {
812 epg_object_t **skel = _epg_season_skel();
813 epg_season_t *es;
814 epg_brand_t *eb;
815 uint32_t u32, changes = 0;
816 const char *str;
817 lang_str_t *ls;
818
819 if (!_epg_object_deserialize(m, *skel)) return NULL;
820 if (!(es = epg_season_find_by_uri((*skel)->uri, (*skel)->grabber,
821 create, save, &changes)))
822 return NULL;
823
824 if ((ls = lang_str_deserialize(m, "summary"))) {
825 *save |= epg_season_set_summary(es, ls, &changes);
826 lang_str_destroy(ls);
827 }
828
829 if (!htsmsg_get_u32(m, "number", &u32))
830 *save |= epg_season_set_number(es, u32, &changes);
831 if (!htsmsg_get_u32(m, "episode-count", &u32))
832 *save |= epg_season_set_episode_count(es, u32, &changes);
833
834 if ((str = htsmsg_get_str(m, "brand")))
835 if ((eb = epg_brand_find_by_uri(str, es->grabber, 0, NULL, NULL)))
836 *save |= epg_season_set_brand(es, eb, &changes);
837
838 if ((str = htsmsg_get_str(m, "image")))
839 *save |= epg_season_set_image(es, str, &changes);
840
841 *save |= epg_season_change_finish(es, changes, 0);
842
843 return es;
844 }
845
epg_season_get_summary(const epg_season_t * s,const char * lang)846 const char *epg_season_get_summary
847 ( const epg_season_t *s, const char *lang )
848 {
849 if (!s || !s->summary) return NULL;
850 return lang_str_get(s->summary, lang);
851 }
852
853 /* **************************************************************************
854 * Episode
855 * *************************************************************************/
856
epg_episode_num_serialize(epg_episode_num_t * num)857 static htsmsg_t *epg_episode_num_serialize ( epg_episode_num_t *num )
858 {
859 htsmsg_t *m;
860 if (!num) return NULL;
861 m = htsmsg_create_map();
862 if (num->e_num)
863 htsmsg_add_u32(m, "e_num", num->e_num);
864 if (num->e_cnt)
865 htsmsg_add_u32(m, "e_cnt", num->e_cnt);
866 if (num->s_num)
867 htsmsg_add_u32(m, "s_num", num->s_num);
868 if (num->s_cnt)
869 htsmsg_add_u32(m, "s_cnt", num->s_cnt);
870 if (num->p_num)
871 htsmsg_add_u32(m, "p_num", num->p_num);
872 if (num->p_cnt)
873 htsmsg_add_u32(m, "p_cnt", num->p_cnt);
874 if (num->text)
875 htsmsg_add_str(m, "text", num->text);
876 return m;
877 }
878
epg_episode_num_deserialize(htsmsg_t * m,epg_episode_num_t * num)879 static void epg_episode_num_deserialize
880 ( htsmsg_t *m, epg_episode_num_t *num )
881 {
882 const char *str;
883 uint32_t u32;
884 assert(m && num);
885
886 memset(num, 0, sizeof(epg_episode_num_t));
887
888 if (!htsmsg_get_u32(m, "e_num", &u32))
889 num->e_num = u32;
890 if (!htsmsg_get_u32(m, "e_cnt", &u32))
891 num->e_cnt = u32;
892 if (!htsmsg_get_u32(m, "s_num", &u32))
893 num->s_num = u32;
894 if (!htsmsg_get_u32(m, "s_cnt", &u32))
895 num->s_cnt = u32;
896 if (!htsmsg_get_u32(m, "p_num", &u32))
897 num->p_num = u32;
898 if (!htsmsg_get_u32(m, "p_cnt", &u32))
899 num->p_cnt = u32;
900 if ((str = htsmsg_get_str(m, "text")))
901 num->text = strdup(str);
902 }
903
_epg_episode_destroy(void * eo)904 static void _epg_episode_destroy ( void *eo )
905 {
906 epg_genre_t *g;
907 epg_episode_t *ee = eo;
908 if (LIST_FIRST(&ee->broadcasts)) {
909 tvhlog(LOG_CRIT, LS_EPG, "attempt to destroy episode with broadcasts");
910 assert(0);
911 }
912 if (ee->brand) _epg_brand_rem_episode(ee->brand, ee);
913 if (ee->season) _epg_season_rem_episode(ee->season, ee);
914 if (ee->title) lang_str_destroy(ee->title);
915 if (ee->subtitle) lang_str_destroy(ee->subtitle);
916 if (ee->summary) lang_str_destroy(ee->summary);
917 if (ee->description) lang_str_destroy(ee->description);
918 while ((g = LIST_FIRST(&ee->genre))) {
919 LIST_REMOVE(g, link);
920 free(g);
921 }
922 if (ee->image) free(ee->image);
923 if (ee->epnum.text) free(ee->epnum.text);
924 _epg_object_destroy(eo, &epg_episodes);
925 free(ee);
926 }
927
_epg_episode_updated(void * eo)928 static void _epg_episode_updated ( void *eo )
929 {
930 }
931
932 static epg_object_ops_t _epg_episode_ops = {
933 .getref = _epg_object_getref,
934 .putref = _epg_object_putref,
935 .destroy = _epg_episode_destroy,
936 .update = _epg_episode_updated,
937 };
938
_epg_episode_skel(void)939 static epg_object_t **_epg_episode_skel ( void )
940 {
941 static epg_object_t *skel = NULL;
942 if (!skel) {
943 skel = calloc(1, sizeof(epg_episode_t));
944 skel->type = EPG_EPISODE;
945 skel->ops = &_epg_episode_ops;
946 }
947 return &skel;
948 }
949
epg_episode_find_by_uri(const char * uri,epggrab_module_t * src,int create,int * save,uint32_t * changed)950 epg_episode_t* epg_episode_find_by_uri
951 ( const char *uri, epggrab_module_t *src, int create,
952 int *save, uint32_t *changed )
953 {
954 return (epg_episode_t*)
955 _epg_object_find_by_uri(uri, src, create, save, changed,
956 &epg_episodes,
957 _epg_episode_skel());
958 }
959
epg_episode_find_by_id(uint32_t id)960 epg_episode_t *epg_episode_find_by_id ( uint32_t id )
961 {
962 return (epg_episode_t*)epg_object_find_by_id(id, EPG_EPISODE);
963 }
964
epg_episode_find_by_broadcast(epg_broadcast_t * ebc,epggrab_module_t * src,int create,int * save,uint32_t * changed)965 epg_episode_t *epg_episode_find_by_broadcast
966 ( epg_broadcast_t *ebc, epggrab_module_t *src,
967 int create, int *save, uint32_t *changed )
968 {
969 char uri[UUID_HEX_SIZE+50], ubuf[UUID_HEX_SIZE];
970 if (!ebc) return NULL;
971 if (ebc->episode) {
972 _epg_object_set_grabber(ebc->episode, src);
973 return ebc->episode;
974 }
975 if (!create) return NULL;
976 snprintf(uri, sizeof(uri)-1, "tvh://channel-%s/bcast-%u/episode",
977 idnode_uuid_as_str(&ebc->channel->ch_id, ubuf), ebc->id);
978 return epg_episode_find_by_uri(uri, src, 1, save, changed);
979 }
980
epg_episode_change_finish(epg_episode_t * episode,uint32_t changes,int merge)981 int epg_episode_change_finish
982 ( epg_episode_t *episode, uint32_t changes, int merge )
983 {
984 int save = 0;
985 if (merge) return 0;
986 if (changes & EPG_CHANGED_CREATE) return 0;
987 if (!(changes & EPG_CHANGED_TITLE))
988 save |= epg_episode_set_title(episode, NULL, NULL);
989 if (!(changes & EPG_CHANGED_SUBTITLE))
990 save |= epg_episode_set_subtitle(episode, NULL, NULL);
991 if (!(changes & EPG_CHANGED_SUMMARY))
992 save |= epg_episode_set_summary(episode, NULL, NULL);
993 if (!(changes & EPG_CHANGED_DESCRIPTION))
994 save |= epg_episode_set_description(episode, NULL, NULL);
995 if (!(changes & EPG_CHANGED_IMAGE))
996 save |= epg_episode_set_image(episode, NULL, NULL);
997 if (!(changes & EPG_CHANGED_EPSER_NUM))
998 save |= _epg_object_set_u16(episode, &episode->epnum.s_num, 0, NULL, 0);
999 if (!(changes & EPG_CHANGED_EPSER_CNT))
1000 save |= _epg_object_set_u16(episode, &episode->epnum.s_cnt, 0, NULL, 0);
1001 if (!(changes & EPG_CHANGED_EPNUM_NUM))
1002 save |= _epg_object_set_u16(episode, &episode->epnum.e_num, 0, NULL, 0);
1003 if (!(changes & EPG_CHANGED_EPNUM_CNT))
1004 save |= _epg_object_set_u16(episode, &episode->epnum.e_cnt, 0, NULL, 0);
1005 if (!(changes & EPG_CHANGED_EPPAR_NUM))
1006 save |= _epg_object_set_u16(episode, &episode->epnum.p_num, 0, NULL, 0);
1007 if (!(changes & EPG_CHANGED_EPPAR_CNT))
1008 save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt, 0, NULL, 0);
1009 if (!(changes & EPG_CHANGED_EPTEXT))
1010 save |= _epg_object_set_str(episode, &episode->epnum.text, NULL, NULL, 0);
1011 if (!(changes & EPG_CHANGED_BRAND))
1012 save |= epg_episode_set_brand(episode, NULL, NULL);
1013 if (!(changes & EPG_CHANGED_SEASON))
1014 save |= epg_episode_set_brand(episode, NULL, NULL);
1015 if (!(changes & EPG_CHANGED_GENRE))
1016 save |= epg_episode_set_genre(episode, NULL, NULL);
1017 if (!(changes & EPG_CHANGED_IS_BW))
1018 save |= epg_episode_set_is_bw(episode, 0, NULL);
1019 if (!(changes & EPG_CHANGED_STAR_RATING))
1020 save |= epg_episode_set_star_rating(episode, 0, NULL);
1021 if (!(changes & EPG_CHANGED_AGE_RATING))
1022 save |= epg_episode_set_age_rating(episode, 0, NULL);
1023 if (!(changes & EPG_CHANGED_FIRST_AIRED))
1024 save |= epg_episode_set_first_aired(episode, 0, NULL);
1025 return save;
1026 }
1027
epg_episode_set_title(epg_episode_t * episode,const lang_str_t * title,uint32_t * changed)1028 int epg_episode_set_title
1029 ( epg_episode_t *episode, const lang_str_t *title, uint32_t *changed )
1030 {
1031 if (!episode) return 0;
1032 return _epg_object_set_lang_str(episode, &episode->title, title,
1033 changed, EPG_CHANGED_TITLE);
1034 }
1035
epg_episode_set_subtitle(epg_episode_t * episode,const lang_str_t * subtitle,uint32_t * changed)1036 int epg_episode_set_subtitle
1037 ( epg_episode_t *episode, const lang_str_t *subtitle, uint32_t *changed )
1038 {
1039 if (!episode) return 0;
1040 return _epg_object_set_lang_str(episode, &episode->subtitle,
1041 subtitle, changed, EPG_CHANGED_SUBTITLE);
1042 }
1043
epg_episode_set_summary(epg_episode_t * episode,const lang_str_t * summary,uint32_t * changed)1044 int epg_episode_set_summary
1045 ( epg_episode_t *episode, const lang_str_t *summary, uint32_t *changed )
1046 {
1047 if (!episode) return 0;
1048 return _epg_object_set_lang_str(episode, &episode->summary,
1049 summary, changed, EPG_CHANGED_SUMMARY);
1050 }
1051
epg_episode_set_description(epg_episode_t * episode,const lang_str_t * desc,uint32_t * changed)1052 int epg_episode_set_description
1053 ( epg_episode_t *episode, const lang_str_t *desc, uint32_t *changed )
1054 {
1055 if (!episode) return 0;
1056 return _epg_object_set_lang_str(episode, &episode->description,
1057 desc, changed, EPG_CHANGED_DESCRIPTION);
1058 }
1059
epg_episode_set_image(epg_episode_t * episode,const char * image,uint32_t * changed)1060 int epg_episode_set_image
1061 ( epg_episode_t *episode, const char *image, uint32_t *changed )
1062 {
1063 int save;
1064 if (!episode) return 0;
1065 save = _epg_object_set_str(episode, &episode->image, image,
1066 changed, EPG_CHANGED_IMAGE);
1067 if (save)
1068 imagecache_get_id(image);
1069 return save;
1070 }
1071
epg_episode_set_number(epg_episode_t * episode,uint16_t number,uint32_t * changed)1072 int epg_episode_set_number
1073 ( epg_episode_t *episode, uint16_t number, uint32_t *changed )
1074 {
1075 if (!episode) return 0;
1076 return _epg_object_set_u16(episode, &episode->epnum.e_num, number,
1077 changed, EPG_CHANGED_EPNUM_NUM);
1078 }
1079
epg_episode_set_part(epg_episode_t * episode,uint16_t part,uint16_t count,uint32_t * changed)1080 int epg_episode_set_part
1081 ( epg_episode_t *episode, uint16_t part, uint16_t count,
1082 uint32_t *changed )
1083 {
1084 int save = 0;
1085 if (!episode) return 0;
1086 save |= _epg_object_set_u16(episode, &episode->epnum.p_num, part,
1087 changed, EPG_CHANGED_EPPAR_NUM);
1088 save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt, count,
1089 changed, EPG_CHANGED_EPPAR_CNT);
1090 return save;
1091 }
1092
epg_episode_set_epnum(epg_episode_t * episode,epg_episode_num_t * num,uint32_t * changed)1093 int epg_episode_set_epnum
1094 ( epg_episode_t *episode, epg_episode_num_t *num, uint32_t *changed )
1095 {
1096 int save = 0;
1097 static epg_episode_num_t _zero = { 0 };
1098 if (!episode)
1099 return 0;
1100 if (!num)
1101 num = &_zero;
1102 if (num->s_num)
1103 save |= _epg_object_set_u16(episode, &episode->epnum.s_num,
1104 num->s_num, changed, EPG_CHANGED_EPSER_NUM);
1105 if (num->s_cnt)
1106 save |= _epg_object_set_u16(episode, &episode->epnum.s_cnt,
1107 num->s_cnt, changed, EPG_CHANGED_EPSER_CNT);
1108 if (num->e_num)
1109 save |= _epg_object_set_u16(episode, &episode->epnum.e_num,
1110 num->e_num, changed, EPG_CHANGED_EPNUM_NUM);
1111 if (num->e_cnt)
1112 save |= _epg_object_set_u16(episode, &episode->epnum.e_cnt,
1113 num->e_cnt, changed, EPG_CHANGED_EPNUM_CNT);
1114 if (num->p_num)
1115 save |= _epg_object_set_u16(episode, &episode->epnum.p_num,
1116 num->p_num, changed, EPG_CHANGED_EPPAR_NUM);
1117 if (num->p_cnt)
1118 save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt,
1119 num->p_cnt, changed, EPG_CHANGED_EPPAR_CNT);
1120 if (num->text)
1121 save |= _epg_object_set_str(episode, &episode->epnum.text,
1122 num->text, changed, EPG_CHANGED_EPTEXT);
1123 return save;
1124 }
1125
epg_episode_set_brand(epg_episode_t * episode,epg_brand_t * brand,uint32_t * changed)1126 int epg_episode_set_brand
1127 ( epg_episode_t *episode, epg_brand_t *brand, uint32_t *changed )
1128 {
1129 int save = 0;
1130 if (!episode) return 0;
1131 if (changed) *changed |= EPG_CHANGED_BRAND;
1132 if (episode->brand != brand) {
1133 if (episode->brand) _epg_brand_rem_episode(episode->brand, episode);
1134 episode->brand = brand;
1135 if (brand) _epg_brand_add_episode(brand, episode);
1136 _epg_object_set_updated(episode);
1137 save = 1;
1138 }
1139 return save;
1140 }
1141
epg_episode_set_season(epg_episode_t * episode,epg_season_t * season,uint32_t * changed)1142 int epg_episode_set_season
1143 ( epg_episode_t *episode, epg_season_t *season, uint32_t *changed )
1144 {
1145 int save = 0;
1146 if (!episode) return 0;
1147 if (changed) *changed |= EPG_CHANGED_SEASON;
1148 if (episode->season != season) {
1149 if (episode->season) _epg_season_rem_episode(episode->season, episode);
1150 episode->season = season;
1151 if (season) {
1152 _epg_season_add_episode(season, episode);
1153 save |= epg_episode_set_brand(episode, season->brand ?: NULL, changed);
1154 } else {
1155 save |= epg_episode_set_brand(episode, NULL, changed);
1156 }
1157 _epg_object_set_updated(episode);
1158 save = 1;
1159 }
1160 return save;
1161 }
1162
epg_episode_set_genre(epg_episode_t * ee,epg_genre_list_t * genre,uint32_t * changed)1163 int epg_episode_set_genre
1164 ( epg_episode_t *ee, epg_genre_list_t *genre, uint32_t *changed )
1165 {
1166 int save = 0;
1167 epg_genre_t *g1, *g2;
1168
1169 if (!ee) return 0;
1170
1171 if (changed) *changed |= EPG_CHANGED_GENRE;
1172
1173 g1 = LIST_FIRST(&ee->genre);
1174
1175 /* Remove old */
1176 while (g1) {
1177 g2 = LIST_NEXT(g1, link);
1178 if (!epg_genre_list_contains(genre, g1, 0)) {
1179 LIST_REMOVE(g1, link);
1180 free(g1);
1181 save = 1;
1182 }
1183 g1 = g2;
1184 }
1185
1186 /* Insert all entries */
1187 if (genre) {
1188 LIST_FOREACH(g1, genre, link)
1189 save |= epg_genre_list_add(&ee->genre, g1);
1190 }
1191
1192 return save;
1193 }
1194
epg_episode_set_is_bw(epg_episode_t * episode,uint8_t bw,uint32_t * changed)1195 int epg_episode_set_is_bw
1196 ( epg_episode_t *episode, uint8_t bw, uint32_t *changed )
1197 {
1198 if (!episode) return 0;
1199 return _epg_object_set_u8(episode, &episode->is_bw, bw,
1200 changed, EPG_CHANGED_IS_BW);
1201 }
1202
epg_episode_set_star_rating(epg_episode_t * episode,uint8_t stars,uint32_t * changed)1203 int epg_episode_set_star_rating
1204 ( epg_episode_t *episode, uint8_t stars, uint32_t *changed )
1205 {
1206 if (!episode) return 0;
1207 return _epg_object_set_u8(episode, &episode->star_rating, stars,
1208 changed, EPG_CHANGED_STAR_RATING);
1209 }
1210
epg_episode_set_age_rating(epg_episode_t * episode,uint8_t age,uint32_t * changed)1211 int epg_episode_set_age_rating
1212 ( epg_episode_t *episode, uint8_t age, uint32_t *changed )
1213 {
1214 if (!episode) return 0;
1215 return _epg_object_set_u8(episode, &episode->age_rating, age,
1216 changed, EPG_CHANGED_AGE_RATING);
1217 }
1218
epg_episode_set_first_aired(epg_episode_t * episode,time_t aired,uint32_t * changed)1219 int epg_episode_set_first_aired
1220 ( epg_episode_t *episode, time_t aired, uint32_t *changed )
1221 {
1222 if (!episode) return 0;
1223 if (changed) *changed |= EPG_CHANGED_FIRST_AIRED;
1224 if (episode->first_aired != aired) {
1225 episode->first_aired = aired;
1226 _epg_object_set_updated(episode);
1227 return 1;
1228 }
1229 return 0;
1230 }
1231
_epg_episode_add_broadcast(epg_episode_t * episode,epg_broadcast_t * broadcast)1232 static void _epg_episode_add_broadcast
1233 ( epg_episode_t *episode, epg_broadcast_t *broadcast )
1234 {
1235 _epg_object_getref(episode);
1236 _epg_object_set_updated(episode);
1237 LIST_INSERT_SORTED(&episode->broadcasts, broadcast, ep_link, _ebc_start_cmp);
1238 }
1239
_epg_episode_rem_broadcast(epg_episode_t * episode,epg_broadcast_t * broadcast)1240 static void _epg_episode_rem_broadcast
1241 ( epg_episode_t *episode, epg_broadcast_t *broadcast )
1242 {
1243 LIST_REMOVE(broadcast, ep_link);
1244 _epg_object_set_updated(episode);
1245 _epg_object_putref(episode);
1246 }
1247
epg_episode_number_format(epg_episode_t * episode,char * buf,size_t len,const char * pre,const char * sfmt,const char * sep,const char * efmt,const char * cfmt)1248 size_t epg_episode_number_format
1249 ( epg_episode_t *episode, char *buf, size_t len,
1250 const char *pre, const char *sfmt,
1251 const char *sep, const char *efmt,
1252 const char *cfmt )
1253 {
1254 size_t i = 0;
1255 if (!episode || !buf || !len) return 0;
1256 epg_episode_num_t num;
1257 epg_episode_get_epnum(episode, &num);
1258 buf[0] = '\0';
1259 if (num.e_num) {
1260 if (pre) tvh_strlcatf(buf, len, i, "%s", pre);
1261 if (sfmt && num.s_num) {
1262 tvh_strlcatf(buf, len, i, sfmt, num.s_num);
1263 if (cfmt && num.s_cnt)
1264 tvh_strlcatf(buf, len, i, cfmt, num.s_cnt);
1265 if (sep) tvh_strlcatf(buf, len, i, "%s", sep);
1266 }
1267 tvh_strlcatf(buf, len, i, efmt, num.e_num);
1268 if (cfmt && num.e_cnt)
1269 tvh_strlcatf(buf, len, i, cfmt, num.e_cnt);
1270 } else if (num.text) {
1271 if (pre) tvh_strlcatf(buf, len, i, "%s", pre);
1272 tvh_strlcatf(buf, len, i, "%s", num.text);
1273 }
1274 return i;
1275 }
1276
epg_episode_get_epnum(epg_episode_t * ee,epg_episode_num_t * num)1277 void epg_episode_get_epnum ( epg_episode_t *ee, epg_episode_num_t *num )
1278 {
1279 if (!ee || !num) return;
1280 *num = ee->epnum;
1281 if (ee->season) {
1282 num->e_cnt = ee->season->episode_count;
1283 num->s_num = ee->season->number;
1284 }
1285 if (ee->brand) {
1286 num->s_cnt = ee->brand->season_count;
1287 }
1288 }
1289
epg_episode_number_cmp(epg_episode_num_t * a,epg_episode_num_t * b)1290 int epg_episode_number_cmp ( epg_episode_num_t *a, epg_episode_num_t *b )
1291 {
1292 if (a->e_num) {
1293 if (a->s_num != b->s_num) {
1294 return a->s_num - b->s_num;
1295 } else if (a->e_num != b->e_num) {
1296 return a->e_num - b->e_num;
1297 } else {
1298 return a->p_num - b->p_num;
1299 }
1300 } else if (a->text && b->text) {
1301 return strcmp(a->text, b->text);
1302 }
1303 return 0;
1304 }
1305
1306 // WIBNI: this could do with soem proper matching, maybe some form of
1307 // fuzzy string match. I did try a few things, but none of them
1308 // were very reliable.
1309 #if TODO_FUZZY_MATCH
epg_episode_fuzzy_match(epg_episode_t * episode,const char * uri,const char * title,const char * summary,const char * description)1310 int epg_episode_fuzzy_match
1311 ( epg_episode_t *episode, const char *uri, const char *title,
1312 const char *summary, const char *description )
1313 {
1314 if (!episode) return 0;
1315 if (uri && episode->uri && !strcmp(episode->uri, uri)) return 1;
1316 if (title && episode->title && (strstr(title, episode->title) || strstr(episode->title, title))) return 1;
1317 return 0;
1318 }
1319 #endif
1320
epg_episode_serialize(epg_episode_t * episode)1321 htsmsg_t *epg_episode_serialize ( epg_episode_t *episode )
1322 {
1323 epg_genre_t *eg;
1324 htsmsg_t *m, *a = NULL;
1325 if (!episode || !episode->uri) return NULL;
1326 if (!(m = _epg_object_serialize((epg_object_t*)episode))) return NULL;
1327 if (episode->title)
1328 lang_str_serialize(episode->title, m, "title");
1329 if (episode->subtitle)
1330 lang_str_serialize(episode->subtitle, m, "subtitle");
1331 if (episode->summary)
1332 lang_str_serialize(episode->summary, m, "summary");
1333 if (episode->description)
1334 lang_str_serialize(episode->description, m, "description");
1335 htsmsg_add_msg(m, "epnum", epg_episode_num_serialize(&episode->epnum));
1336 LIST_FOREACH(eg, &episode->genre, link) {
1337 if (!a) a = htsmsg_create_list();
1338 htsmsg_add_u32(a, NULL, eg->code);
1339 }
1340 if (a) htsmsg_add_msg(m, "genre", a);
1341 if (episode->brand)
1342 htsmsg_add_str(m, "brand", episode->brand->uri);
1343 if (episode->season)
1344 htsmsg_add_str(m, "season", episode->season->uri);
1345 if (episode->is_bw)
1346 htsmsg_add_u32(m, "is_bw", 1);
1347 if (episode->star_rating)
1348 htsmsg_add_u32(m, "star_rating", episode->star_rating);
1349 if (episode->age_rating)
1350 htsmsg_add_u32(m, "age_rating", episode->age_rating);
1351 if (episode->first_aired)
1352 htsmsg_add_s64(m, "first_aired", episode->first_aired);
1353 if (episode->image)
1354 htsmsg_add_str(m, "image", episode->image);
1355
1356 return m;
1357 }
1358
epg_episode_deserialize(htsmsg_t * m,int create,int * save)1359 epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
1360 {
1361 epg_object_t **skel = _epg_episode_skel();
1362 epg_episode_t *ee;
1363 epg_season_t *es;
1364 epg_brand_t *eb;
1365 const char *str;
1366 epg_episode_num_t num;
1367 htsmsg_t *sub;
1368 htsmsg_field_t *f;
1369 uint32_t u32, changes = 0;
1370 int64_t s64;
1371 lang_str_t *ls;
1372
1373 if (!_epg_object_deserialize(m, *skel)) return NULL;
1374 if (!(ee = epg_episode_find_by_uri((*skel)->uri, (*skel)->grabber,
1375 create, save, &changes)))
1376 return NULL;
1377
1378 if ((ls = lang_str_deserialize(m, "title"))) {
1379 *save |= epg_episode_set_title(ee, ls, &changes);
1380 lang_str_destroy(ls);
1381 }
1382 if ((ls = lang_str_deserialize(m, "subtitle"))) {
1383 *save |= epg_episode_set_subtitle(ee, ls, &changes);
1384 lang_str_destroy(ls);
1385 }
1386 if ((ls = lang_str_deserialize(m, "summary"))) {
1387 *save |= epg_episode_set_summary(ee, ls, &changes);
1388 lang_str_destroy(ls);
1389 }
1390 if ((ls = lang_str_deserialize(m, "description"))) {
1391 *save |= epg_episode_set_description(ee, ls, &changes);
1392 lang_str_destroy(ls);
1393 }
1394 if ((sub = htsmsg_get_map(m, "epnum"))) {
1395 epg_episode_num_deserialize(sub, &num);
1396 *save |= epg_episode_set_epnum(ee, &num, &changes);
1397 if (num.text) free(num.text);
1398 }
1399 if ((sub = htsmsg_get_list(m, "genre"))) {
1400 epg_genre_list_t *egl = calloc(1, sizeof(epg_genre_list_t));
1401 HTSMSG_FOREACH(f, sub) {
1402 epg_genre_t genre;
1403 genre.code = (uint8_t)f->hmf_s64;
1404 epg_genre_list_add(egl, &genre);
1405 }
1406 *save |= epg_episode_set_genre(ee, egl, &changes);
1407 epg_genre_list_destroy(egl);
1408 }
1409
1410 if ((str = htsmsg_get_str(m, "season")))
1411 if ((es = epg_season_find_by_uri(str, ee->grabber, 0, NULL, &changes)))
1412 *save |= epg_episode_set_season(ee, es, NULL);
1413 if ((str = htsmsg_get_str(m, "brand")))
1414 if ((eb = epg_brand_find_by_uri(str, ee->grabber, 0, NULL, &changes)))
1415 *save |= epg_episode_set_brand(ee, eb, NULL);
1416
1417 if (!htsmsg_get_u32(m, "is_bw", &u32))
1418 *save |= epg_episode_set_is_bw(ee, u32, &changes);
1419
1420 if (!htsmsg_get_u32(m, "star_rating", &u32))
1421 *save |= epg_episode_set_star_rating(ee, u32, &changes);
1422
1423 if (!htsmsg_get_u32(m, "age_rating", &u32))
1424 *save |= epg_episode_set_age_rating(ee, u32, &changes);
1425
1426 if (!htsmsg_get_s64(m, "first_aired", &s64))
1427 *save |= epg_episode_set_first_aired(ee, (time_t)s64, &changes);
1428
1429 if ((str = htsmsg_get_str(m, "image")))
1430 *save |= epg_episode_set_image(ee, str, &changes);
1431
1432 *save |= epg_episode_change_finish(ee, changes, 0);
1433
1434 return ee;
1435 }
1436
epg_episode_get_title(const epg_episode_t * e,const char * lang)1437 const char *epg_episode_get_title
1438 ( const epg_episode_t *e, const char *lang )
1439 {
1440 if (!e || !e->title) return NULL;
1441 return lang_str_get(e->title, lang);
1442 }
1443
epg_episode_get_subtitle(const epg_episode_t * e,const char * lang)1444 const char *epg_episode_get_subtitle
1445 ( const epg_episode_t *e, const char *lang )
1446 {
1447 if (!e || !e->subtitle) return NULL;
1448 return lang_str_get(e->subtitle, lang);
1449 }
1450
epg_episode_get_summary(const epg_episode_t * e,const char * lang)1451 const char *epg_episode_get_summary
1452 ( const epg_episode_t *e, const char *lang )
1453 {
1454 if (!e || !e->summary) return NULL;
1455 return lang_str_get(e->summary, lang);
1456 }
1457
epg_episode_get_description(const epg_episode_t * e,const char * lang)1458 const char *epg_episode_get_description
1459 ( const epg_episode_t *e, const char *lang )
1460 {
1461 if (!e || !e->description) return NULL;
1462 return lang_str_get(e->description, lang);
1463 }
1464
1465 /* **************************************************************************
1466 * Series link
1467 * *************************************************************************/
1468
_epg_serieslink_destroy(void * eo)1469 static void _epg_serieslink_destroy ( void *eo )
1470 {
1471 epg_serieslink_t *es = (epg_serieslink_t*)eo;
1472 if (LIST_FIRST(&es->broadcasts)) {
1473 tvhlog(LOG_CRIT, LS_EPG, "attempt to destory series link with broadcasts");
1474 assert(0);
1475 }
1476 _epg_object_destroy(eo, &epg_serieslinks);
1477 free(es);
1478 }
1479
_epg_serieslink_updated(void * eo)1480 static void _epg_serieslink_updated ( void *eo )
1481 {
1482 dvr_autorec_check_serieslink((epg_serieslink_t*)eo);
1483 }
1484
1485 static epg_object_ops_t _epg_serieslink_ops = {
1486 .getref = _epg_object_getref,
1487 .putref = _epg_object_putref,
1488 .destroy = _epg_serieslink_destroy,
1489 .update = _epg_serieslink_updated,
1490 };
1491
_epg_serieslink_skel(void)1492 static epg_object_t **_epg_serieslink_skel ( void )
1493 {
1494 static epg_object_t *skel = NULL;
1495 if (!skel) {
1496 skel = calloc(1, sizeof(epg_serieslink_t));
1497 skel->type = EPG_SERIESLINK;
1498 skel->ops = &_epg_serieslink_ops;
1499 }
1500 return &skel;
1501 }
1502
epg_serieslink_find_by_uri(const char * uri,epggrab_module_t * src,int create,int * save,uint32_t * changed)1503 epg_serieslink_t* epg_serieslink_find_by_uri
1504 ( const char *uri, epggrab_module_t *src, int create,
1505 int *save, uint32_t *changed )
1506 {
1507 return (epg_serieslink_t*)
1508 _epg_object_find_by_uri(uri, src, create, save, changed,
1509 &epg_serieslinks,
1510 _epg_serieslink_skel());
1511 }
1512
epg_serieslink_find_by_id(uint32_t id)1513 epg_serieslink_t *epg_serieslink_find_by_id ( uint32_t id )
1514 {
1515 return (epg_serieslink_t*)epg_object_find_by_id(id, EPG_SERIESLINK);
1516 }
1517
epg_serieslink_change_finish(epg_serieslink_t * esl,uint32_t changes,int merge)1518 int epg_serieslink_change_finish
1519 ( epg_serieslink_t *esl, uint32_t changes, int merge )
1520 {
1521 return 0;
1522 }
1523
_epg_serieslink_add_broadcast(epg_serieslink_t * esl,epg_broadcast_t * ebc)1524 static void _epg_serieslink_add_broadcast
1525 ( epg_serieslink_t *esl, epg_broadcast_t *ebc )
1526 {
1527 _epg_object_getref(esl);
1528 _epg_object_set_updated(esl);
1529 LIST_INSERT_HEAD(&esl->broadcasts, ebc, sl_link);
1530 }
1531
_epg_serieslink_rem_broadcast(epg_serieslink_t * esl,epg_broadcast_t * ebc)1532 static void _epg_serieslink_rem_broadcast
1533 ( epg_serieslink_t *esl, epg_broadcast_t *ebc )
1534 {
1535 LIST_REMOVE(ebc, sl_link);
1536 _epg_object_set_updated(esl);
1537 _epg_object_putref(esl);
1538 }
1539
epg_serieslink_serialize(epg_serieslink_t * esl)1540 htsmsg_t *epg_serieslink_serialize ( epg_serieslink_t *esl )
1541 {
1542 htsmsg_t *m;
1543 if (!esl || !esl->uri) return NULL;
1544 if (!(m = _epg_object_serialize((epg_object_t*)esl))) return NULL;
1545 return m;
1546 }
1547
epg_serieslink_deserialize(htsmsg_t * m,int create,int * save)1548 epg_serieslink_t *epg_serieslink_deserialize
1549 ( htsmsg_t *m, int create, int *save )
1550 {
1551 epg_object_t **skel = _epg_serieslink_skel();
1552 epg_serieslink_t *esl;
1553 uint32_t changes = 0;
1554
1555 if (!_epg_object_deserialize(m, *skel)) return NULL;
1556 if (!(esl = epg_serieslink_find_by_uri((*skel)->uri, (*skel)->grabber,
1557 create, save, &changes)))
1558 return NULL;
1559
1560 *save |= epg_serieslink_change_finish(esl, changes, 0);
1561
1562 return esl;
1563 }
1564
1565 /* **************************************************************************
1566 * Channel
1567 * *************************************************************************/
1568
_epg_channel_rem_broadcast(channel_t * ch,epg_broadcast_t * ebc,epg_broadcast_t * ebc_new)1569 static void _epg_channel_rem_broadcast
1570 ( channel_t *ch, epg_broadcast_t *ebc, epg_broadcast_t *ebc_new )
1571 {
1572 RB_REMOVE(&ch->ch_epg_schedule, ebc, sched_link);
1573 if (ch->ch_epg_now == ebc) ch->ch_epg_now = NULL;
1574 if (ch->ch_epg_next == ebc) ch->ch_epg_next = NULL;
1575 if (ebc_new) {
1576 dvr_event_replaced(ebc, ebc_new);
1577 } else {
1578 dvr_event_removed(ebc);
1579 }
1580 _epg_object_putref(ebc);
1581 }
1582
_epg_channel_timer_callback(void * p)1583 static void _epg_channel_timer_callback ( void *p )
1584 {
1585 time_t next = 0;
1586 epg_broadcast_t *ebc, *cur, *nxt;
1587 channel_t *ch = (channel_t*)p;
1588 char tm1[32];
1589
1590 /* Clear now/next */
1591 if ((cur = ch->ch_epg_now)) {
1592 if (cur->running != EPG_RUNNING_STOP) {
1593 /* running? don't do anything */
1594 gtimer_arm_rel(&ch->ch_epg_timer, _epg_channel_timer_callback, ch, 2);
1595 return;
1596 }
1597 cur->ops->getref(cur);
1598 }
1599 if ((nxt = ch->ch_epg_next))
1600 nxt->ops->getref(nxt);
1601 ch->ch_epg_now = ch->ch_epg_next = NULL;
1602
1603 /* Check events */
1604 while ((ebc = RB_FIRST(&ch->ch_epg_schedule))) {
1605
1606 /* Expire */
1607 if ( ebc->stop <= gclk() ) {
1608 tvhdebug(LS_EPG, "expire event %u (%s) from %s",
1609 ebc->id, epg_broadcast_get_title(ebc, NULL),
1610 channel_get_name(ch));
1611 _epg_channel_rem_broadcast(ch, ebc, NULL);
1612 continue; // skip to next
1613
1614 /* No now */
1615 } else if (ebc->start > gclk()) {
1616 ch->ch_epg_next = ebc;
1617 next = ebc->start;
1618
1619 /* Now/Next */
1620 } else {
1621 ch->ch_epg_now = ebc;
1622 ch->ch_epg_next = RB_NEXT(ebc, sched_link);
1623 next = ebc->stop;
1624 }
1625 break;
1626 }
1627
1628 /* Change (update HTSP) */
1629 if (cur != ch->ch_epg_now || nxt != ch->ch_epg_next) {
1630 tvhdebug(LS_EPG, "now/next %u/%u set on %s",
1631 ch->ch_epg_now ? ch->ch_epg_now->id : 0,
1632 ch->ch_epg_next ? ch->ch_epg_next->id : 0,
1633 channel_get_name(ch));
1634 tvhdebug(LS_EPG, "inform HTSP of now event change on %s",
1635 channel_get_name(ch));
1636 htsp_channel_update_nownext(ch);
1637 }
1638
1639 /* re-arm */
1640 if (next) {
1641 tvhdebug(LS_EPG, "arm channel timer @ %s for %s",
1642 gmtime2local(next, tm1, sizeof(tm1)), channel_get_name(ch));
1643 gtimer_arm_absn(&ch->ch_epg_timer, _epg_channel_timer_callback, ch, next);
1644 }
1645
1646 /* Remove refs */
1647 if (cur) cur->ops->putref(cur);
1648 if (nxt) nxt->ops->putref(nxt);
1649 }
1650
_epg_channel_add_broadcast(channel_t * ch,epg_broadcast_t ** bcast,epggrab_module_t * src,int create,int * save,uint32_t * changed)1651 static epg_broadcast_t *_epg_channel_add_broadcast
1652 ( channel_t *ch, epg_broadcast_t **bcast, epggrab_module_t *src,
1653 int create, int *save, uint32_t *changed )
1654 {
1655 int timer = 0;
1656 epg_broadcast_t *ebc, *ret;
1657 char tm1[32], tm2[32];
1658
1659 if (!src) {
1660 tvherror(LS_EPG, "skipped event (!grabber) %u (%s) on %s @ %s to %s",
1661 (*bcast)->id, epg_broadcast_get_title(*bcast, NULL),
1662 channel_get_name(ch),
1663 gmtime2local((*bcast)->start, tm1, sizeof(tm1)),
1664 gmtime2local((*bcast)->stop, tm2, sizeof(tm2)));
1665 return NULL;
1666 }
1667
1668 /* Set channel */
1669 (*bcast)->channel = ch;
1670
1671 /* Find (only) */
1672 if ( !create ) {
1673 return RB_FIND(&ch->ch_epg_schedule, *bcast, sched_link, _ebc_start_cmp);
1674
1675 /* Find/Create */
1676 } else {
1677 ret = RB_INSERT_SORTED(&ch->ch_epg_schedule, *bcast, sched_link, _ebc_start_cmp);
1678
1679 /* New */
1680 if (!ret) {
1681 if (changed) *changed |= EPG_CHANGED_CREATE;
1682 *save = 1;
1683 ret = *bcast;
1684 *bcast = NULL;
1685 _epg_object_create(ret);
1686 // Note: sets updated
1687 _epg_object_getref(ret);
1688 ret->grabber = src;
1689 tvhtrace(LS_EPG, "added event %u (%s) on %s @ %s to %s (grabber %s)",
1690 ret->id, epg_broadcast_get_title(ret, NULL),
1691 channel_get_name(ch),
1692 gmtime2local(ret->start, tm1, sizeof(tm1)),
1693 gmtime2local(ret->stop, tm2, sizeof(tm2)),
1694 src->id);
1695
1696 /* Existing */
1697 } else {
1698 if (!_epg_object_set_grabber(ret, src))
1699 return NULL;
1700
1701 /* No time change */
1702 if (ret->stop == (*bcast)->stop) {
1703 return ret;
1704
1705 /* Extend in time */
1706 } else {
1707 ret->stop = (*bcast)->stop;
1708 _epg_object_set_updated(ret);
1709 tvhtrace(LS_EPG, "updated event %u (%s) on %s @ %s to %s (grabber %s)",
1710 ret->id, epg_broadcast_get_title(ret, NULL),
1711 channel_get_name(ch),
1712 gmtime2local(ret->start, tm1, sizeof(tm1)),
1713 gmtime2local(ret->stop, tm2, sizeof(tm2)),
1714 src->id);
1715 }
1716 }
1717 }
1718
1719 /* Changed */
1720 *save |= 1;
1721
1722 /* Remove overlapping (before) */
1723 while ((ebc = RB_PREV(ret, sched_link)) != NULL) {
1724 if (ebc->stop <= ret->start) break;
1725 if (!_epg_object_can_remove(ebc, ret)) {
1726 tvhtrace(LS_EPG, "grabber for event %u has lower priority than overlap (b), removing", ebc->id);
1727 _epg_channel_rem_broadcast(ch, ret, NULL);
1728 return NULL;
1729 }
1730 if (config.epg_cutwindow && ebc->stop - ebc->start > config.epg_cutwindow * 2 &&
1731 ebc->stop - ret->start <= config.epg_cutwindow) {
1732 tvhtrace(LS_EPG, "cut stop for overlap (b) event %u (%s) on %s @ %s to %s",
1733 ebc->id, epg_broadcast_get_title(ebc, NULL),
1734 channel_get_name(ch),
1735 gmtime2local(ebc->start, tm1, sizeof(tm1)),
1736 gmtime2local(ebc->stop, tm2, sizeof(tm2)));
1737 ebc->stop = ret->start;
1738 _epg_object_set_updated(ebc);
1739 continue;
1740 }
1741 tvhtrace(LS_EPG, "remove overlap (b) event %u (%s) on %s @ %s to %s",
1742 ebc->id, epg_broadcast_get_title(ebc, NULL),
1743 channel_get_name(ch),
1744 gmtime2local(ebc->start, tm1, sizeof(tm1)),
1745 gmtime2local(ebc->stop, tm2, sizeof(tm2)));
1746 _epg_channel_rem_broadcast(ch, ebc, ret);
1747 }
1748
1749 /* Remove overlapping (after) */
1750 while ((ebc = RB_NEXT(ret, sched_link)) != NULL) {
1751 if (ebc->start >= ret->stop) break;
1752 if (!_epg_object_can_remove(ebc, ret)) {
1753 tvhtrace(LS_EPG, "grabber for event %u has lower priority than overlap (a), removing", ebc->id);
1754 _epg_channel_rem_broadcast(ch, ret, NULL);
1755 return NULL;
1756 }
1757 if (config.epg_cutwindow && ret->stop - ret->start > config.epg_cutwindow * 2 &&
1758 ret->stop - ebc->start <= config.epg_cutwindow) {
1759 tvhtrace(LS_EPG, "cut stop for overlap (a) event %u (%s) on %s @ %s to %s",
1760 ebc->id, epg_broadcast_get_title(ebc, NULL),
1761 channel_get_name(ch),
1762 gmtime2local(ebc->start, tm1, sizeof(tm1)),
1763 gmtime2local(ebc->stop, tm2, sizeof(tm2)));
1764 ret->stop = ebc->start;
1765 _epg_object_set_updated(ebc);
1766 continue;
1767 }
1768 tvhtrace(LS_EPG, "remove overlap (a) event %u (%s) on %s @ %s to %s",
1769 ebc->id, epg_broadcast_get_title(ebc, NULL),
1770 channel_get_name(ch),
1771 gmtime2local(ebc->start, tm1, sizeof(tm1)),
1772 gmtime2local(ebc->stop, tm2, sizeof(tm2)));
1773 _epg_channel_rem_broadcast(ch, ebc, ret);
1774 }
1775
1776 /* Check now/next change */
1777 if (RB_FIRST(&ch->ch_epg_schedule) == ret) {
1778 timer = 1;
1779 } else if (ch->ch_epg_now &&
1780 RB_NEXT(ch->ch_epg_now, sched_link) == ret) {
1781 timer = 1;
1782 }
1783
1784 /* Reset timer - it might free return event! */
1785 ret->ops->getref(ret);
1786 if (timer) _epg_channel_timer_callback(ch);
1787 if (ret->ops->putref(ret)) return NULL;
1788 return ret;
1789 }
1790
epg_channel_unlink(channel_t * ch)1791 void epg_channel_unlink ( channel_t *ch )
1792 {
1793 epg_broadcast_t *ebc;
1794 while ((ebc = RB_FIRST(&ch->ch_epg_schedule)))
1795 _epg_channel_rem_broadcast(ch, ebc, NULL);
1796 gtimer_disarm(&ch->ch_epg_timer);
1797 }
1798
1799 /* **************************************************************************
1800 * Broadcast
1801 * *************************************************************************/
1802
_epg_broadcast_destroy(void * eo)1803 static void _epg_broadcast_destroy ( void *eo )
1804 {
1805 epg_broadcast_t *ebc = eo;
1806 char id[16];
1807
1808 if (ebc->_created) {
1809 htsp_event_delete(ebc);
1810 snprintf(id, sizeof(id), "%u", ebc->id);
1811 notify_delayed(id, "epg", "delete");
1812 }
1813 if (ebc->episode) _epg_episode_rem_broadcast(ebc->episode, ebc);
1814 if (ebc->serieslink) _epg_serieslink_rem_broadcast(ebc->serieslink, ebc);
1815 if (ebc->summary) lang_str_destroy(ebc->summary);
1816 if (ebc->description) lang_str_destroy(ebc->description);
1817 _epg_object_destroy(eo, NULL);
1818 free(ebc);
1819 }
1820
_epg_broadcast_updated(void * eo)1821 static void _epg_broadcast_updated ( void *eo )
1822 {
1823 epg_broadcast_t *ebc = eo;
1824 char id[16];
1825
1826 if (!epg_in_load)
1827 snprintf(id, sizeof(id), "%u", ebc->id);
1828 else
1829 id[0] = '\0';
1830
1831 if (ebc->_created) {
1832 htsp_event_update(eo);
1833 notify_delayed(id, "epg", "update");
1834 } else {
1835 htsp_event_add(eo);
1836 notify_delayed(id, "epg", "create");
1837 }
1838 if (ebc->channel) {
1839 dvr_event_updated(eo);
1840 dvr_autorec_check_event(eo);
1841 channel_event_updated(eo);
1842 }
1843 }
1844
1845 static epg_object_ops_t _epg_broadcast_ops = {
1846 .getref = _epg_object_getref,
1847 .putref = _epg_object_putref,
1848 .destroy = _epg_broadcast_destroy,
1849 .update = _epg_broadcast_updated,
1850 };
1851
_epg_broadcast_skel(void)1852 static epg_broadcast_t **_epg_broadcast_skel ( void )
1853 {
1854 static epg_broadcast_t *skel = NULL;
1855 if (!skel) {
1856 skel = calloc(1, sizeof(epg_broadcast_t));
1857 skel->type = EPG_BROADCAST;
1858 skel->ops = &_epg_broadcast_ops;
1859 }
1860 return &skel;
1861 }
1862
epg_broadcast_find_by_time(channel_t * channel,epggrab_module_t * src,time_t start,time_t stop,int create,int * save,uint32_t * changed)1863 epg_broadcast_t *epg_broadcast_find_by_time
1864 ( channel_t *channel, epggrab_module_t *src,
1865 time_t start, time_t stop, int create, int *save, uint32_t *changed )
1866 {
1867 epg_broadcast_t **ebc;
1868 if (!channel || !start || !stop) return NULL;
1869 if (stop <= start) return NULL;
1870 if (stop <= gclk()) return NULL;
1871
1872 ebc = _epg_broadcast_skel();
1873 (*ebc)->start = start;
1874 (*ebc)->stop = stop;
1875
1876 return _epg_channel_add_broadcast(channel, ebc, src, create, save, changed);
1877 }
1878
epg_broadcast_change_finish(epg_broadcast_t * broadcast,uint32_t changes,int merge)1879 int epg_broadcast_change_finish
1880 ( epg_broadcast_t *broadcast, uint32_t changes, int merge )
1881 {
1882 int save = 0;
1883 if (merge) return 0;
1884 if (changes & EPG_CHANGED_CREATE) return 0;
1885 if (!(changes & EPG_CHANGED_EPISODE))
1886 save |= epg_broadcast_set_episode(broadcast, NULL, NULL);
1887 if (!(changes & EPG_CHANGED_SERIESLINK))
1888 save |= epg_broadcast_set_serieslink(broadcast, NULL, NULL);
1889 if (!(changes & EPG_CHANGED_DVB_EID))
1890 save |= epg_broadcast_set_dvb_eid(broadcast, 0, NULL);
1891 if (!(changes & EPG_CHANGED_IS_WIDESCREEN))
1892 save |= epg_broadcast_set_is_widescreen(broadcast, 0, NULL);
1893 if (!(changes & EPG_CHANGED_IS_HD))
1894 save |= epg_broadcast_set_is_hd(broadcast, 0, NULL);
1895 if (!(changes & EPG_CHANGED_LINES))
1896 save |= epg_broadcast_set_lines(broadcast, 0, NULL);
1897 if (!(changes & EPG_CHANGED_ASPECT))
1898 save |= epg_broadcast_set_aspect(broadcast, 0, NULL);
1899 if (!(changes & EPG_CHANGED_DEAFSIGNED))
1900 save |= epg_broadcast_set_is_deafsigned(broadcast, 0, NULL);
1901 if (!(changes & EPG_CHANGED_SUBTITLED))
1902 save |= epg_broadcast_set_is_subtitled(broadcast, 0, NULL);
1903 if (!(changes & EPG_CHANGED_AUDIO_DESC))
1904 save |= epg_broadcast_set_is_audio_desc(broadcast, 0, NULL);
1905 if (!(changes & EPG_CHANGED_IS_NEW))
1906 save |= epg_broadcast_set_is_new(broadcast, 0, NULL);
1907 if (!(changes & EPG_CHANGED_IS_REPEAT))
1908 save |= epg_broadcast_set_is_repeat(broadcast, 0, NULL);
1909 if (!(changes & EPG_CHANGED_SUMMARY))
1910 save |= epg_broadcast_set_summary(broadcast, NULL, NULL);
1911 if (!(changes & EPG_CHANGED_DESCRIPTION))
1912 save |= epg_broadcast_set_description(broadcast, NULL, NULL);
1913 return save;
1914 }
1915
epg_broadcast_clone(channel_t * channel,epg_broadcast_t * src,int * save)1916 epg_broadcast_t *epg_broadcast_clone
1917 ( channel_t *channel, epg_broadcast_t *src, int *save )
1918 {
1919 epg_broadcast_t *ebc;
1920 uint32_t changes = 0;
1921
1922 if (!src) return NULL;
1923 ebc = epg_broadcast_find_by_time(channel, src->grabber,
1924 src->start, src->stop,
1925 1, save, &changes);
1926 if (ebc) {
1927 /* Copy metadata */
1928 *save |= epg_broadcast_set_is_widescreen(ebc, src->is_widescreen, &changes);
1929 *save |= epg_broadcast_set_is_hd(ebc, src->is_hd, &changes);
1930 *save |= epg_broadcast_set_lines(ebc, src->lines, &changes);
1931 *save |= epg_broadcast_set_aspect(ebc, src->aspect, &changes);
1932 *save |= epg_broadcast_set_is_deafsigned(ebc, src->is_deafsigned, &changes);
1933 *save |= epg_broadcast_set_is_subtitled(ebc, src->is_subtitled, &changes);
1934 *save |= epg_broadcast_set_is_audio_desc(ebc, src->is_audio_desc, &changes);
1935 *save |= epg_broadcast_set_is_new(ebc, src->is_new, &changes);
1936 *save |= epg_broadcast_set_is_repeat(ebc, src->is_repeat, &changes);
1937 *save |= epg_broadcast_set_summary(ebc, src->summary, &changes);
1938 *save |= epg_broadcast_set_description(ebc, src->description, &changes);
1939 *save |= epg_broadcast_set_serieslink(ebc, src->serieslink, &changes);
1940 *save |= epg_broadcast_set_episode(ebc, src->episode, &changes);
1941 _epg_object_set_grabber(ebc, src->grabber);
1942 *save |= epg_broadcast_change_finish(ebc, changes, 0);
1943 }
1944 return ebc;
1945 }
1946
epg_broadcast_find_by_id(uint32_t id)1947 epg_broadcast_t *epg_broadcast_find_by_id ( uint32_t id )
1948 {
1949 return (epg_broadcast_t*)epg_object_find_by_id(id, EPG_BROADCAST);
1950 }
1951
epg_broadcast_find_by_eid(channel_t * ch,uint16_t eid)1952 epg_broadcast_t *epg_broadcast_find_by_eid ( channel_t *ch, uint16_t eid )
1953 {
1954 epg_broadcast_t *e;
1955 RB_FOREACH(e, &ch->ch_epg_schedule, sched_link) {
1956 if (e->dvb_eid == eid) return e;
1957 }
1958 return NULL;
1959 }
1960
epg_broadcast_notify_running(epg_broadcast_t * broadcast,epg_source_t esrc,epg_running_t running)1961 void epg_broadcast_notify_running
1962 ( epg_broadcast_t *broadcast, epg_source_t esrc, epg_running_t running )
1963 {
1964 channel_t *ch;
1965 epg_broadcast_t *now;
1966 int orunning = broadcast->running;
1967
1968 broadcast->running = running;
1969 ch = broadcast->channel;
1970 now = ch ? ch->ch_epg_now : NULL;
1971 if (running == EPG_RUNNING_STOP) {
1972 if (now == broadcast && orunning == broadcast->running)
1973 broadcast->stop = gclk() - 1;
1974 } else {
1975 if (broadcast != now && now) {
1976 now->running = EPG_RUNNING_STOP;
1977 dvr_event_running(now, esrc, EPG_RUNNING_STOP);
1978 }
1979 }
1980 dvr_event_running(broadcast, esrc, running);
1981 }
1982
epg_broadcast_set_episode(epg_broadcast_t * broadcast,epg_episode_t * episode,uint32_t * changed)1983 int epg_broadcast_set_episode
1984 ( epg_broadcast_t *broadcast, epg_episode_t *episode, uint32_t *changed )
1985 {
1986 int save = 0;
1987 if (!broadcast) return 0;
1988 if (changed) *changed |= EPG_CHANGED_EPISODE;
1989 if (broadcast->episode != episode) {
1990 if (broadcast->episode)
1991 _epg_episode_rem_broadcast(broadcast->episode, broadcast);
1992 broadcast->episode = episode;
1993 if (episode) _epg_episode_add_broadcast(episode, broadcast);
1994 _epg_object_set_updated(broadcast);
1995 save = 1;
1996 }
1997 return save;
1998 }
1999
epg_broadcast_set_serieslink(epg_broadcast_t * ebc,epg_serieslink_t * esl,uint32_t * changed)2000 int epg_broadcast_set_serieslink
2001 ( epg_broadcast_t *ebc, epg_serieslink_t *esl, uint32_t *changed )
2002 {
2003 int save = 0;
2004 if (!ebc) return 0;
2005 if (changed) *changed |= EPG_CHANGED_SERIESLINK;
2006 if (ebc->serieslink != esl) {
2007 if (ebc->serieslink) _epg_serieslink_rem_broadcast(ebc->serieslink, ebc);
2008 ebc->serieslink = esl;
2009 if (esl) _epg_serieslink_add_broadcast(esl, ebc);
2010 save = 1;
2011 }
2012 return save;
2013 }
2014
epg_broadcast_set_dvb_eid(epg_broadcast_t * b,uint16_t dvb_eid,uint32_t * changed)2015 int epg_broadcast_set_dvb_eid
2016 ( epg_broadcast_t *b, uint16_t dvb_eid, uint32_t *changed )
2017 {
2018 if (!b) return 0;
2019 return _epg_object_set_u16(b, &b->dvb_eid, dvb_eid,
2020 changed, EPG_CHANGED_DVB_EID);
2021 }
2022
epg_broadcast_set_is_widescreen(epg_broadcast_t * b,uint8_t ws,uint32_t * changed)2023 int epg_broadcast_set_is_widescreen
2024 ( epg_broadcast_t *b, uint8_t ws, uint32_t *changed )
2025 {
2026 if (!b) return 0;
2027 return _epg_object_set_u8(b, &b->is_widescreen, ws,
2028 changed, EPG_CHANGED_IS_WIDESCREEN);
2029 }
2030
epg_broadcast_set_is_hd(epg_broadcast_t * b,uint8_t hd,uint32_t * changed)2031 int epg_broadcast_set_is_hd
2032 ( epg_broadcast_t *b, uint8_t hd, uint32_t *changed )
2033 {
2034 if (!b) return 0;
2035 return _epg_object_set_u8(b, &b->is_hd, hd,
2036 changed, EPG_CHANGED_IS_HD);
2037 }
2038
epg_broadcast_set_lines(epg_broadcast_t * b,uint16_t lines,uint32_t * changed)2039 int epg_broadcast_set_lines
2040 ( epg_broadcast_t *b, uint16_t lines, uint32_t *changed )
2041 {
2042 if (!b) return 0;
2043 return _epg_object_set_u16(b, &b->lines, lines,
2044 changed, EPG_CHANGED_LINES);
2045 }
2046
epg_broadcast_set_aspect(epg_broadcast_t * b,uint16_t aspect,uint32_t * changed)2047 int epg_broadcast_set_aspect
2048 ( epg_broadcast_t *b, uint16_t aspect, uint32_t *changed )
2049 {
2050 if (!b) return 0;
2051 return _epg_object_set_u16(b, &b->aspect, aspect,
2052 changed, EPG_CHANGED_ASPECT);
2053 }
2054
epg_broadcast_set_is_deafsigned(epg_broadcast_t * b,uint8_t ds,uint32_t * changed)2055 int epg_broadcast_set_is_deafsigned
2056 ( epg_broadcast_t *b, uint8_t ds, uint32_t *changed )
2057 {
2058 if (!b) return 0;
2059 return _epg_object_set_u8(b, &b->is_deafsigned, ds,
2060 changed, EPG_CHANGED_DEAFSIGNED);
2061 }
2062
epg_broadcast_set_is_subtitled(epg_broadcast_t * b,uint8_t st,uint32_t * changed)2063 int epg_broadcast_set_is_subtitled
2064 ( epg_broadcast_t *b, uint8_t st, uint32_t *changed )
2065 {
2066 if (!b) return 0;
2067 return _epg_object_set_u8(b, &b->is_subtitled, st,
2068 changed, EPG_CHANGED_SUBTITLED);
2069 }
2070
epg_broadcast_set_is_audio_desc(epg_broadcast_t * b,uint8_t ad,uint32_t * changed)2071 int epg_broadcast_set_is_audio_desc
2072 ( epg_broadcast_t *b, uint8_t ad, uint32_t *changed )
2073 {
2074 if (!b) return 0;
2075 return _epg_object_set_u8(b, &b->is_audio_desc, ad,
2076 changed, EPG_CHANGED_AUDIO_DESC);
2077 }
2078
epg_broadcast_set_is_new(epg_broadcast_t * b,uint8_t n,uint32_t * changed)2079 int epg_broadcast_set_is_new
2080 ( epg_broadcast_t *b, uint8_t n, uint32_t *changed )
2081 {
2082 if (!b) return 0;
2083 return _epg_object_set_u8(b, &b->is_new, n,
2084 changed, EPG_CHANGED_IS_NEW);
2085 }
2086
epg_broadcast_set_is_repeat(epg_broadcast_t * b,uint8_t r,uint32_t * changed)2087 int epg_broadcast_set_is_repeat
2088 ( epg_broadcast_t *b, uint8_t r, uint32_t *changed )
2089 {
2090 if (!b) return 0;
2091 return _epg_object_set_u8(b, &b->is_repeat, r,
2092 changed, EPG_CHANGED_IS_REPEAT);
2093 }
2094
epg_broadcast_set_summary(epg_broadcast_t * b,const lang_str_t * str,uint32_t * changed)2095 int epg_broadcast_set_summary
2096 ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
2097 {
2098 if (!b) return 0;
2099 return _epg_object_set_lang_str(b, &b->summary, str,
2100 changed, EPG_CHANGED_SUMMARY);
2101 }
2102
epg_broadcast_set_description(epg_broadcast_t * b,const lang_str_t * str,uint32_t * changed)2103 int epg_broadcast_set_description
2104 ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
2105 {
2106 if (!b) return 0;
2107 return _epg_object_set_lang_str(b, &b->description, str,
2108 changed, EPG_CHANGED_DESCRIPTION);
2109 }
2110
epg_broadcast_get_prev(epg_broadcast_t * b)2111 epg_broadcast_t *epg_broadcast_get_prev ( epg_broadcast_t *b )
2112 {
2113 if (!b) return NULL;
2114 return RB_PREV(b, sched_link);
2115 }
2116
epg_broadcast_get_next(epg_broadcast_t * b)2117 epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *b )
2118 {
2119 if (!b) return NULL;
2120 return RB_NEXT(b, sched_link);
2121 }
2122
epg_broadcast_get_title(epg_broadcast_t * b,const char * lang)2123 const char *epg_broadcast_get_title ( epg_broadcast_t *b, const char *lang )
2124 {
2125 if (!b || !b->episode) return NULL;
2126 return epg_episode_get_title(b->episode, lang);
2127 }
2128
epg_broadcast_get_subtitle(epg_broadcast_t * b,const char * lang)2129 const char *epg_broadcast_get_subtitle ( epg_broadcast_t *b, const char *lang )
2130 {
2131 if (!b || !b->episode) return NULL;
2132 return epg_episode_get_subtitle(b->episode, lang);
2133 }
2134
epg_broadcast_get_summary(epg_broadcast_t * b,const char * lang)2135 const char *epg_broadcast_get_summary ( epg_broadcast_t *b, const char *lang )
2136 {
2137 if (!b || !b->summary) return NULL;
2138 return lang_str_get(b->summary, lang);
2139 }
2140
epg_broadcast_get_description(epg_broadcast_t * b,const char * lang)2141 const char *epg_broadcast_get_description ( epg_broadcast_t *b, const char *lang )
2142 {
2143 if (!b || !b->description) return NULL;
2144 return lang_str_get(b->description, lang);
2145 }
2146
epg_broadcast_serialize(epg_broadcast_t * broadcast)2147 htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
2148 {
2149 htsmsg_t *m;
2150 char ubuf[UUID_HEX_SIZE];
2151 if (!broadcast) return NULL;
2152 if (!broadcast->episode || !broadcast->episode->uri) return NULL;
2153 if (!(m = _epg_object_serialize((epg_object_t*)broadcast))) return NULL;
2154 htsmsg_add_s64(m, "start", broadcast->start);
2155 htsmsg_add_s64(m, "stop", broadcast->stop);
2156 htsmsg_add_str(m, "episode", broadcast->episode->uri);
2157 if (broadcast->channel)
2158 htsmsg_add_str(m, "channel", channel_get_uuid(broadcast->channel, ubuf));
2159 if (broadcast->dvb_eid)
2160 htsmsg_add_u32(m, "dvb_eid", broadcast->dvb_eid);
2161 if (broadcast->is_widescreen)
2162 htsmsg_add_u32(m, "is_widescreen", 1);
2163 if (broadcast->is_hd)
2164 htsmsg_add_u32(m, "is_hd", 1);
2165 if (broadcast->lines)
2166 htsmsg_add_u32(m, "lines", broadcast->lines);
2167 if (broadcast->aspect)
2168 htsmsg_add_u32(m, "aspect", broadcast->aspect);
2169 if (broadcast->is_deafsigned)
2170 htsmsg_add_u32(m, "is_deafsigned", 1);
2171 if (broadcast->is_subtitled)
2172 htsmsg_add_u32(m, "is_subtitled", 1);
2173 if (broadcast->is_audio_desc)
2174 htsmsg_add_u32(m, "is_audio_desc", 1);
2175 if (broadcast->is_new)
2176 htsmsg_add_u32(m, "is_new", 1);
2177 if (broadcast->is_repeat)
2178 htsmsg_add_u32(m, "is_repeat", 1);
2179 if (broadcast->summary)
2180 lang_str_serialize(broadcast->summary, m, "summary");
2181 if (broadcast->description)
2182 lang_str_serialize(broadcast->description, m, "description");
2183 if (broadcast->serieslink)
2184 htsmsg_add_str(m, "serieslink", broadcast->serieslink->uri);
2185
2186 return m;
2187 }
2188
epg_broadcast_deserialize(htsmsg_t * m,int create,int * save)2189 epg_broadcast_t *epg_broadcast_deserialize
2190 ( htsmsg_t *m, int create, int *save )
2191 {
2192 channel_t *ch = NULL;
2193 epg_broadcast_t *ebc, **skel = _epg_broadcast_skel();
2194 epg_episode_t *ee;
2195 epg_serieslink_t *esl;
2196 lang_str_t *ls;
2197 const char *str;
2198 uint32_t eid, u32, changes = 0, changes2 = 0;
2199 int64_t start, stop;
2200
2201 if (htsmsg_get_s64(m, "start", &start)) return NULL;
2202 if (htsmsg_get_s64(m, "stop", &stop)) return NULL;
2203 if (!start || !stop) return NULL;
2204 if (stop <= start) return NULL;
2205 if (stop <= gclk()) return NULL;
2206 if (!(str = htsmsg_get_str(m, "episode"))) return NULL;
2207
2208 _epg_object_deserialize(m, (epg_object_t*)*skel);
2209
2210 if (!(ee = epg_episode_find_by_uri(str, (*skel)->grabber, 0, NULL, NULL)))
2211 return NULL;
2212
2213 /* Set properties */
2214 (*skel)->start = start;
2215 (*skel)->stop = stop;
2216
2217 /* Get channel */
2218 if ((str = htsmsg_get_str(m, "channel")))
2219 ch = channel_find(str);
2220 if (!ch) return NULL;
2221
2222 /* Create */
2223 ebc = _epg_channel_add_broadcast(ch, skel, (*skel)->grabber, create, save, &changes);
2224 if (!ebc) return NULL;
2225
2226 /* Get metadata */
2227 if (!htsmsg_get_u32(m, "dvb_eid", &eid))
2228 *save |= epg_broadcast_set_dvb_eid(ebc, eid, &changes);
2229 if (!htsmsg_get_u32(m, "is_widescreen", &u32))
2230 *save |= epg_broadcast_set_is_widescreen(ebc, u32, &changes);
2231 if (!htsmsg_get_u32(m, "is_hd", &u32))
2232 *save |= epg_broadcast_set_is_hd(ebc, u32, &changes);
2233 if (!htsmsg_get_u32(m, "lines", &u32))
2234 *save |= epg_broadcast_set_lines(ebc, u32, &changes);
2235 if (!htsmsg_get_u32(m, "aspect", &u32))
2236 *save |= epg_broadcast_set_aspect(ebc, u32, &changes);
2237 if (!htsmsg_get_u32(m, "is_deafsigned", &u32))
2238 *save |= epg_broadcast_set_is_deafsigned(ebc, u32, &changes);
2239 if (!htsmsg_get_u32(m, "is_subtitled", &u32))
2240 *save |= epg_broadcast_set_is_subtitled(ebc, u32, &changes);
2241 if (!htsmsg_get_u32(m, "is_audio_desc", &u32))
2242 *save |= epg_broadcast_set_is_audio_desc(ebc, u32, &changes);
2243 if (!htsmsg_get_u32(m, "is_new", &u32))
2244 *save |= epg_broadcast_set_is_new(ebc, u32, &changes);
2245 if (!htsmsg_get_u32(m, "is_repeat", &u32))
2246 *save |= epg_broadcast_set_is_repeat(ebc, u32, &changes);
2247
2248 if ((ls = lang_str_deserialize(m, "summary"))) {
2249 *save |= epg_broadcast_set_summary(ebc, ls, &changes);
2250 lang_str_destroy(ls);
2251 }
2252
2253 if ((ls = lang_str_deserialize(m, "description"))) {
2254 *save |= epg_broadcast_set_description(ebc, ls, &changes);
2255 lang_str_destroy(ls);
2256 }
2257
2258 /* Series link */
2259 if ((str = htsmsg_get_str(m, "serieslink")))
2260 if ((esl = epg_serieslink_find_by_uri(str, ebc->grabber, 1, save, &changes2))) {
2261 *save |= epg_broadcast_set_serieslink(ebc, esl, &changes);
2262 *save |= epg_serieslink_change_finish(esl, changes2, 0);
2263 }
2264
2265 /* Set the episode */
2266 *save |= epg_broadcast_set_episode(ebc, ee, &changes);
2267
2268 *save |= epg_broadcast_change_finish(ebc, changes, 0);
2269
2270 return ebc;
2271 }
2272
2273 /* **************************************************************************
2274 * Genre
2275 * *************************************************************************/
2276
2277 // TODO: make this configurable
2278 // FULL(ish) list from EN 300 468, I've excluded the last category
2279 // that relates more to broadcast content than what I call a "genre"
2280 // these will be handled elsewhere as broadcast metadata
2281
2282 // Reference (Sept 2016):
2283 // http://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.11.01_60/en_300468v011101p.pdf
2284
2285 #define C_ (const char *[])
2286 static const char **_epg_genre_names[16][16] = {
2287 { /* 00 */
2288 C_{ "", NULL }
2289 },
2290 { /* 01 */
2291 C_{ N_("Movie"), N_("Drama"), NULL },
2292 C_{ N_("Detective"), N_("Thriller"), NULL },
2293 C_{ N_("Adventure"), N_("Western"), N_("War"), NULL },
2294 C_{ N_("Science fiction"), N_("Fantasy"), N_("Horror"), NULL },
2295 C_{ N_("Comedy"), NULL },
2296 C_{ N_("Soap"), N_("Melodrama"), N_("Folkloric"), NULL },
2297 C_{ N_("Romance"), NULL },
2298 C_{ N_("Serious"), N_("Classical"), N_("Religious"), N_("Historical movie"), N_("Drama"), NULL },
2299 C_{ N_("Adult movie"), N_("Drama"), NULL },
2300 C_{ N_("Movie / drama"), NULL },
2301 C_{ N_("Movie / drama"), NULL },
2302 C_{ N_("Movie / drama"), NULL },
2303 C_{ N_("Movie / drama"), NULL },
2304 C_{ N_("Movie / drama"), NULL },
2305 C_{ N_("Movie / drama"), NULL },
2306 C_{ N_("Movie / drama"), NULL },
2307 },
2308 { /* 02 */
2309 C_{ N_("News"), N_("Current affairs"), NULL },
2310 C_{ N_("News"), N_("Weather report"), NULL },
2311 C_{ N_("News magazine"), NULL },
2312 C_{ N_("Documentary"), NULL },
2313 C_{ N_("Discussion"), N_("Interview"), N_("Debate"), NULL },
2314 C_{ N_("News / Current Affairs"), NULL },
2315 C_{ N_("News / Current Affairs"), NULL },
2316 C_{ N_("News / Current Affairs"), NULL },
2317 C_{ N_("News / Current Affairs"), NULL },
2318 C_{ N_("News / Current Affairs"), NULL },
2319 C_{ N_("News / Current Affairs"), NULL },
2320 C_{ N_("News / Current Affairs"), NULL },
2321 C_{ N_("News / Current Affairs"), NULL },
2322 C_{ N_("News / Current Affairs"), NULL },
2323 C_{ N_("News / Current Affairs"), NULL },
2324 C_{ N_("News / Current Affairs"), NULL },
2325 },
2326 { /* 03 */
2327 C_{ N_("Show"), N_("Game show"), NULL },
2328 C_{ N_("Game show"), N_("Quiz"), N_("Contest"), NULL },
2329 C_{ N_("Variety show"), NULL },
2330 C_{ N_("Talk show"), NULL },
2331 C_{ N_("Show / Game show"), NULL },
2332 C_{ N_("Show / Game show"), NULL },
2333 C_{ N_("Show / Game show"), NULL },
2334 C_{ N_("Show / Game show"), NULL },
2335 C_{ N_("Show / Game show"), NULL },
2336 C_{ N_("Show / Game show"), NULL },
2337 C_{ N_("Show / Game show"), NULL },
2338 C_{ N_("Show / Game show"), NULL },
2339 C_{ N_("Show / Game show"), NULL },
2340 C_{ N_("Show / Game show"), NULL },
2341 C_{ N_("Show / Game show"), NULL },
2342 C_{ N_("Show / Game show"), NULL },
2343 },
2344 { /* 04 */
2345 C_{ N_("Sports"), NULL },
2346 C_{ N_("Special events (Olympic Games, World Cup, etc.)"), NULL },
2347 C_{ N_("Sports magazines"), NULL },
2348 C_{ N_("Football"), N_("Soccer"), NULL },
2349 C_{ N_("Tennis"), N_("Squash"), NULL },
2350 C_{ N_("Team sports (excluding football)"), NULL },
2351 C_{ N_("Athletics"), NULL },
2352 C_{ N_("Motor sport"), NULL },
2353 C_{ N_("Water sport"), NULL },
2354 C_{ N_("Winter sports"), NULL },
2355 C_{ N_("Equestrian"), NULL },
2356 C_{ N_("Martial sports"), NULL },
2357 C_{ N_("Sports"), NULL },
2358 C_{ N_("Sports"), NULL },
2359 C_{ N_("Sports"), NULL },
2360 C_{ N_("Sports"), NULL },
2361 },
2362 { /* 05 */
2363 C_{ N_("Children's / Youth programs"), NULL },
2364 C_{ N_("Pre-school children's programs"), NULL },
2365 C_{ N_("Entertainment programs for 6 to 14"), NULL },
2366 C_{ N_("Entertainment programs for 10 to 16"), NULL },
2367 C_{ N_("Informational"), N_("Educational"), N_("School programs"), NULL },
2368 C_{ N_("Cartoons"), N_("Puppets"), NULL },
2369 C_{ N_("Children's / Youth Programs"), NULL },
2370 C_{ N_("Children's / Youth Programs"), NULL },
2371 C_{ N_("Children's / Youth Programs"), NULL },
2372 C_{ N_("Children's / Youth Programs"), NULL },
2373 C_{ N_("Children's / Youth Programs"), NULL },
2374 C_{ N_("Children's / Youth Programs"), NULL },
2375 C_{ N_("Children's / Youth Programs"), NULL },
2376 C_{ N_("Children's / Youth Programs"), NULL },
2377 C_{ N_("Children's / Youth Programs"), NULL },
2378 C_{ N_("Children's / Youth Programs"), NULL },
2379 },
2380 { /* 06 */
2381 C_{ N_("Music"), N_("Ballet"), N_("Dance"), NULL },
2382 C_{ N_("Rock"), N_("Pop"), NULL },
2383 C_{ N_("Serious music"), N_("Classical music"), NULL },
2384 C_{ N_("Folk"), N_("Traditional music"), NULL },
2385 C_{ N_("Jazz"), NULL },
2386 C_{ N_("Musical"), N_("Opera"), NULL },
2387 C_{ N_("Ballet"), NULL },
2388 C_{ N_("Music / Ballet / Dance"), NULL },
2389 C_{ N_("Music / Ballet / Dance"), NULL },
2390 C_{ N_("Music / Ballet / Dance"), NULL },
2391 C_{ N_("Music / Ballet / Dance"), NULL },
2392 C_{ N_("Music / Ballet / Dance"), NULL },
2393 C_{ N_("Music / Ballet / Dance"), NULL },
2394 C_{ N_("Music / Ballet / Dance"), NULL },
2395 C_{ N_("Music / Ballet / Dance"), NULL },
2396 C_{ N_("Music / Ballet / Dance"), NULL },
2397 },
2398 { /* 07 */
2399 C_{ N_("Arts"), N_("Culture (without music)"), NULL },
2400 C_{ N_("Performing arts"), NULL },
2401 C_{ N_("Fine arts"), NULL },
2402 C_{ N_("Religion"), NULL },
2403 C_{ N_("Popular culture"), N_("Traditional arts"), NULL },
2404 C_{ N_("Literature"), NULL },
2405 C_{ N_("Film"), N_("Cinema"), NULL },
2406 C_{ N_("Experimental film"), N_("Video"), NULL },
2407 C_{ N_("Broadcasting"), N_("Press"), NULL },
2408 C_{ N_("New media"), NULL },
2409 C_{ N_("Arts magazines"), N_("Culture magazines"), NULL },
2410 C_{ N_("Fashion"), NULL },
2411 C_{ N_("Arts / Culture (without music)"), NULL },
2412 C_{ N_("Arts / Culture (without music)"), NULL },
2413 C_{ N_("Arts / Culture (without music)"), NULL },
2414 C_{ N_("Arts / Culture (without music)"), NULL },
2415 },
2416 { /* 08 */
2417 C_{ N_("Social"), N_("Political issues"), N_("Economics"), NULL },
2418 C_{ N_("Magazines"), N_("Reports"), N_("Documentary"), NULL },
2419 C_{ N_("Economics"), N_("Social advisory"), NULL },
2420 C_{ N_("Remarkable people"), NULL },
2421 C_{ N_("Social / Political issues / Economics"), NULL },
2422 C_{ N_("Social / Political issues / Economics"), NULL },
2423 C_{ N_("Social / Political issues / Economics"), NULL },
2424 C_{ N_("Social / Political issues / Economics"), NULL },
2425 C_{ N_("Social / Political issues / Economics"), NULL },
2426 C_{ N_("Social / Political issues / Economics"), NULL },
2427 C_{ N_("Social / Political issues / Economics"), NULL },
2428 C_{ N_("Social / Political issues / Economics"), NULL },
2429 C_{ N_("Social / Political issues / Economics"), NULL },
2430 C_{ N_("Social / Political issues / Economics"), NULL },
2431 C_{ N_("Social / Political issues / Economics"), NULL },
2432 C_{ N_("Social / Political issues / Economics"), NULL },
2433 },
2434 { /* 09 */
2435 C_{ N_("Education"), N_("Science"), N_("Factual topics"), NULL },
2436 C_{ N_("Nature"), N_("Animals"), N_("Environment"), NULL },
2437 C_{ N_("Technology"), N_("Natural sciences"), NULL },
2438 C_{ N_("Medicine"), N_("Physiology"), N_("Psychology"), NULL },
2439 C_{ N_("Foreign countries"), N_("Expeditions"), NULL },
2440 C_{ N_("Social"), N_("Spiritual sciences"), NULL },
2441 C_{ N_("Further education"), NULL },
2442 C_{ N_("Languages"), NULL },
2443 C_{ N_("Education / Science / Factual topics"), NULL },
2444 C_{ N_("Education / Science / Factual topics"), NULL },
2445 C_{ N_("Education / Science / Factual topics"), NULL },
2446 C_{ N_("Education / Science / Factual topics"), NULL },
2447 C_{ N_("Education / Science / Factual topics"), NULL },
2448 C_{ N_("Education / Science / Factual topics"), NULL },
2449 C_{ N_("Education / Science / Factual topics"), NULL },
2450 C_{ N_("Education / Science / Factual topics"), NULL },
2451 },
2452 { /* 10 */
2453 C_{ N_("Leisure hobbies"), NULL },
2454 C_{ N_("Tourism / Travel"), NULL },
2455 C_{ N_("Handicraft"), NULL },
2456 C_{ N_("Motoring"), NULL },
2457 C_{ N_("Fitness and health"), NULL },
2458 C_{ N_("Cooking"), NULL },
2459 C_{ N_("Advertisement / Shopping"), NULL },
2460 C_{ N_("Gardening"), NULL },
2461 C_{ N_("Leisure hobbies"), NULL },
2462 C_{ N_("Leisure hobbies"), NULL },
2463 C_{ N_("Leisure hobbies"), NULL },
2464 C_{ N_("Leisure hobbies"), NULL },
2465 C_{ N_("Leisure hobbies"), NULL },
2466 C_{ N_("Leisure hobbies"), NULL },
2467 C_{ N_("Leisure hobbies"), NULL },
2468 C_{ N_("Leisure hobbies"), NULL },
2469 }
2470 };
2471
_genre_get_name(int a,int b,const char * lang)2472 static const char *_genre_get_name(int a, int b, const char *lang)
2473 {
2474 static __thread char name[64];
2475 size_t l = 0;
2476 const char **p = _epg_genre_names[a][b];
2477 name[0] = '\0';
2478 if (p == NULL)
2479 return NULL;
2480 for ( ; *p; p++)
2481 tvh_strlcatf(name, sizeof(name), l, "%s%s", l ? " / " : "",
2482 lang ? tvh_gettext_lang(lang, *p) : *p);
2483 return name;
2484 }
2485
2486 // match strings, ignoring case and whitespace
2487 // Note: | 0x20 is a cheats (fast) way of lowering case
_genre_str_match(const char * a,const char * b)2488 static int _genre_str_match ( const char *a, const char *b )
2489 {
2490 if (!a || !b) return 0;
2491 while (*a != '\0' || *b != '\0') {
2492 while (*a == ' ') a++;
2493 while (*b == ' ') b++;
2494 if ((*a | 0x20) != (*b | 0x20)) return 0;
2495 a++; b++;
2496 }
2497 return (*a == '\0' && *b == '\0'); // end of string(both)
2498 }
2499
_epg_genre_find_by_name(const char * name,const char * lang)2500 static uint8_t _epg_genre_find_by_name ( const char *name, const char *lang )
2501 {
2502 uint8_t a, b;
2503 const char *s;
2504 for (a = 1; a < 11; a++) {
2505 for (b = 0; b < 16; b++) {
2506 s = _genre_get_name(a, b, lang);
2507 if (_genre_str_match(name, s))
2508 return (a << 4) | b;
2509 }
2510 }
2511 return 0; // undefined
2512 }
2513
epg_genre_get_eit(const epg_genre_t * genre)2514 uint8_t epg_genre_get_eit ( const epg_genre_t *genre )
2515 {
2516 if (!genre) return 0;
2517 return genre->code;
2518 }
2519
epg_genre_get_str(const epg_genre_t * genre,int major_only,int major_prefix,char * buf,size_t len,const char * lang)2520 size_t epg_genre_get_str ( const epg_genre_t *genre, int major_only,
2521 int major_prefix, char *buf, size_t len,
2522 const char *lang )
2523 {
2524 const char *s;
2525 int maj, min;
2526 size_t ret = 0;
2527 if (!genre || !buf) return 0;
2528 maj = (genre->code >> 4) & 0xf;
2529 s = _genre_get_name(maj, 0, lang);
2530 if (s[0] == '\0') return 0;
2531 min = major_only ? 0 : (genre->code & 0xf);
2532 if (!min || major_prefix ) {
2533 tvh_strlcatf(buf, len, ret, "%s", s);
2534 }
2535 if (min) {
2536 s = _genre_get_name(maj, min, lang);
2537 if (s[0]) {
2538 tvh_strlcatf(buf, len, ret, " : ");
2539 tvh_strlcatf(buf, len, ret, "%s", s);
2540 }
2541 }
2542 return ret;
2543 }
2544
epg_genre_list_add(epg_genre_list_t * list,epg_genre_t * genre)2545 int epg_genre_list_add ( epg_genre_list_t *list, epg_genre_t *genre )
2546 {
2547 epg_genre_t *g1, *g2;
2548 if (!list || !genre || !genre->code) return 0;
2549 g1 = LIST_FIRST(list);
2550 if (!g1) {
2551 g2 = calloc(1, sizeof(epg_genre_t));
2552 g2->code = genre->code;
2553 LIST_INSERT_HEAD(list, g2, link);
2554 } else {
2555 while (g1) {
2556
2557 /* Already exists */
2558 if (g1->code == genre->code) return 0;
2559
2560 /* Update a major only entry */
2561 if (g1->code == (genre->code & 0xF0)) {
2562 g1->code = genre->code;
2563 break;
2564 }
2565
2566 /* Insert before */
2567 if (g1->code > genre->code) {
2568 g2 = calloc(1, sizeof(epg_genre_t));
2569 g2->code = genre->code;
2570 LIST_INSERT_BEFORE(g1, g2, link);
2571 break;
2572 }
2573
2574 /* Insert after (end) */
2575 if (!(g2 = LIST_NEXT(g1, link))) {
2576 g2 = calloc(1, sizeof(epg_genre_t));
2577 g2->code = genre->code;
2578 LIST_INSERT_AFTER(g1, g2, link);
2579 break;
2580 }
2581
2582 /* Next */
2583 g1 = g2;
2584 }
2585 }
2586 return 1;
2587 }
2588
epg_genre_list_add_by_eit(epg_genre_list_t * list,uint8_t eit)2589 int epg_genre_list_add_by_eit ( epg_genre_list_t *list, uint8_t eit )
2590 {
2591 epg_genre_t g;
2592 g.code = eit;
2593 return epg_genre_list_add(list, &g);
2594 }
2595
epg_genre_list_add_by_str(epg_genre_list_t * list,const char * str,const char * lang)2596 int epg_genre_list_add_by_str ( epg_genre_list_t *list, const char *str, const char *lang )
2597 {
2598 epg_genre_t g;
2599 g.code = _epg_genre_find_by_name(str, lang);
2600 return epg_genre_list_add(list, &g);
2601 }
2602
2603 // Note: if partial=1 and genre is a major only category then all minor
2604 // entries will also match
epg_genre_list_contains(epg_genre_list_t * list,epg_genre_t * genre,int partial)2605 int epg_genre_list_contains
2606 ( epg_genre_list_t *list, epg_genre_t *genre, int partial )
2607 {
2608 uint8_t mask = 0xFF;
2609 epg_genre_t *g;
2610 if (!list || !genre) return 0;
2611 if (partial && !(genre->code & 0x0F)) mask = 0xF0;
2612 LIST_FOREACH(g, list, link) {
2613 if ((g->code & mask) == genre->code) break;
2614 }
2615 return g ? 1 : 0;
2616 }
2617
epg_genre_list_destroy(epg_genre_list_t * list)2618 void epg_genre_list_destroy ( epg_genre_list_t *list )
2619 {
2620 epg_genre_t *g;
2621 while ((g = LIST_FIRST(list))) {
2622 LIST_REMOVE(g, link);
2623 free(g);
2624 }
2625 free(list);
2626 }
2627
epg_genres_list_all(int major_only,int major_prefix,const char * lang)2628 htsmsg_t *epg_genres_list_all ( int major_only, int major_prefix, const char *lang )
2629 {
2630 int i, j;
2631 htsmsg_t *e, *m;
2632 const char *s;
2633 m = htsmsg_create_list();
2634 for (i = 0; i < 16; i++ ) {
2635 for (j = 0; j < (major_only ? 1 : 16); j++) {
2636 if (_epg_genre_names[i][j]) {
2637 e = htsmsg_create_map();
2638 htsmsg_add_u32(e, "key", (i << 4) | (major_only ? 0 : j));
2639 s = _genre_get_name(i, j, lang);
2640 htsmsg_add_str(e, "val", s);
2641 // TODO: use major_prefix
2642 htsmsg_add_msg(m, NULL, e);
2643 }
2644 }
2645 }
2646 return m;
2647 }
2648
2649 /* **************************************************************************
2650 * Querying
2651 * *************************************************************************/
2652
2653 static inline int
_eq_comp_num(epg_filter_num_t * f,int64_t val)2654 _eq_comp_num ( epg_filter_num_t *f, int64_t val )
2655 {
2656 switch (f->comp) {
2657 case EC_EQ: return val != f->val1;
2658 case EC_LT: return val > f->val1;
2659 case EC_GT: return val < f->val1;
2660 case EC_RG: return val < f->val1 || val > f->val2;
2661 default: return 0;
2662 }
2663 }
2664
2665 static inline int
_eq_comp_str(epg_filter_str_t * f,const char * str)2666 _eq_comp_str ( epg_filter_str_t *f, const char *str )
2667 {
2668 switch (f->comp) {
2669 case EC_EQ: return strcmp(str, f->str);
2670 case EC_LT: return strcmp(str, f->str) > 0;
2671 case EC_GT: return strcmp(str, f->str) < 0;
2672 case EC_IN: return strstr(str, f->str) != NULL;
2673 case EC_RE: return regexec(&f->re, str, 0, NULL, 0) != 0;
2674 default: return 0;
2675 }
2676 }
2677
2678 static void
_eq_add(epg_query_t * eq,epg_broadcast_t * e)2679 _eq_add ( epg_query_t *eq, epg_broadcast_t *e )
2680 {
2681 const char *s, *lang = eq->lang;
2682 epg_episode_t *ep;
2683 int fulltext = eq->stitle && eq->fulltext;
2684
2685 /* Filtering */
2686 if (e == NULL) return;
2687 if (e->stop < gclk()) return;
2688 if (_eq_comp_num(&eq->start, e->start)) return;
2689 if (_eq_comp_num(&eq->stop, e->stop)) return;
2690 if (eq->duration.comp != EC_NO) {
2691 int64_t duration = (int64_t)e->stop - (int64_t)e->start;
2692 if (_eq_comp_num(&eq->duration, duration)) return;
2693 }
2694 ep = e->episode;
2695 if (eq->stars.comp != EC_NO)
2696 if (_eq_comp_num(&eq->stars, ep->star_rating)) return;
2697 if (eq->age.comp != EC_NO)
2698 if (_eq_comp_num(&eq->age, ep->age_rating)) return;
2699 if (eq->channel_num.comp != EC_NO)
2700 if (_eq_comp_num(&eq->channel_num, channel_get_number(e->channel))) return;
2701 if (eq->channel_name.comp != EC_NO)
2702 if (_eq_comp_str(&eq->channel_name, channel_get_name(e->channel))) return;
2703 if (eq->genre_count) {
2704 epg_genre_t genre;
2705 uint32_t i, r = 0;
2706 for (i = 0; i < eq->genre_count; i++) {
2707 genre.code = eq->genre[i];
2708 if (genre.code == 0) continue;
2709 if (epg_genre_list_contains(&e->episode->genre, &genre, 1)) r++;
2710 }
2711 if (!r) return;
2712 }
2713 if (fulltext) {
2714 if ((s = epg_episode_get_title(ep, lang)) == NULL ||
2715 regexec(&eq->stitle_re, s, 0, NULL, 0)) {
2716 if ((s = epg_episode_get_subtitle(ep, lang)) == NULL ||
2717 regexec(&eq->stitle_re, s, 0, NULL, 0)) {
2718 if ((s = epg_broadcast_get_summary(e, lang)) == NULL ||
2719 regexec(&eq->stitle_re, s, 0, NULL, 0)) {
2720 if ((s = epg_broadcast_get_description(e, lang)) == NULL ||
2721 regexec(&eq->stitle_re, s, 0, NULL, 0)) {
2722 return;
2723 }
2724 }
2725 }
2726 }
2727 }
2728 if (eq->title.comp != EC_NO || (eq->stitle && !fulltext)) {
2729 if ((s = epg_episode_get_title(ep, lang)) == NULL) return;
2730 if (eq->stitle && !fulltext && regexec(&eq->stitle_re, s, 0, NULL, 0)) return;
2731 if (eq->title.comp != EC_NO && _eq_comp_str(&eq->title, s)) return;
2732 }
2733 if (eq->subtitle.comp != EC_NO) {
2734 if ((s = epg_episode_get_subtitle(ep, lang)) == NULL) return;
2735 if (_eq_comp_str(&eq->subtitle, s)) return;
2736 }
2737 if (eq->summary.comp != EC_NO) {
2738 if ((s = epg_broadcast_get_summary(e, lang)) == NULL) return;
2739 if (_eq_comp_str(&eq->summary, s)) return;
2740 }
2741 if (eq->description.comp != EC_NO) {
2742 if ((s = epg_broadcast_get_description(e, lang)) == NULL) return;
2743 if (_eq_comp_str(&eq->description, s)) return;
2744 }
2745
2746 /* More space */
2747 if (eq->entries == eq->allocated) {
2748 eq->allocated = MAX(100, eq->allocated + 100);
2749 eq->result = realloc(eq->result, eq->allocated * sizeof(epg_broadcast_t *));
2750 }
2751
2752 /* Store */
2753 eq->result[eq->entries++] = e;
2754 }
2755
2756 static void
_eq_add_channel(epg_query_t * eq,channel_t * ch)2757 _eq_add_channel ( epg_query_t *eq, channel_t *ch )
2758 {
2759 epg_broadcast_t *ebc;
2760 RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) {
2761 if (ebc->episode)
2762 _eq_add(eq, ebc);
2763 }
2764 }
2765
2766 static int
_eq_init_str(epg_filter_str_t * f)2767 _eq_init_str( epg_filter_str_t *f )
2768 {
2769 if (f->comp != EC_RE) return 0;
2770 return regcomp(&f->re, f->str, REG_ICASE | REG_EXTENDED | REG_NOSUB);
2771 }
2772
2773 static void
_eq_done_str(epg_filter_str_t * f)2774 _eq_done_str( epg_filter_str_t *f )
2775 {
2776 if (f->comp == EC_RE)
2777 regfree(&f->re);
2778 free(f->str);
2779 f->str = NULL;
2780 }
2781
_epg_sort_start_ascending(const void * a,const void * b,void * eq)2782 static int _epg_sort_start_ascending ( const void *a, const void *b, void *eq )
2783 {
2784 return (*(epg_broadcast_t**)a)->start - (*(epg_broadcast_t**)b)->start;
2785 }
2786
_epg_sort_start_descending(const void * a,const void * b,void * eq)2787 static int _epg_sort_start_descending ( const void *a, const void *b, void *eq )
2788 {
2789 return (*(epg_broadcast_t**)b)->start - (*(epg_broadcast_t**)a)->start;
2790 }
2791
_epg_sort_stop_ascending(const void * a,const void * b,void * eq)2792 static int _epg_sort_stop_ascending ( const void *a, const void *b, void *eq )
2793 {
2794 return (*(epg_broadcast_t**)a)->stop - (*(epg_broadcast_t**)b)->stop;
2795 }
2796
_epg_sort_stop_descending(const void * a,const void * b,void * eq)2797 static int _epg_sort_stop_descending ( const void *a, const void *b, void *eq )
2798 {
2799 return (*(epg_broadcast_t**)b)->stop - (*(epg_broadcast_t**)a)->stop;
2800 }
2801
_epg_sort_duration(const epg_broadcast_t * b)2802 static inline int64_t _epg_sort_duration( const epg_broadcast_t *b )
2803 {
2804 return b->stop - b->start;
2805 }
2806
_epg_sort_duration_ascending(const void * a,const void * b,void * eq)2807 static int _epg_sort_duration_ascending ( const void *a, const void *b, void *eq )
2808 {
2809 return _epg_sort_duration(*(epg_broadcast_t**)a) - _epg_sort_duration(*(epg_broadcast_t**)b);
2810 }
2811
_epg_sort_duration_descending(const void * a,const void * b,void * eq)2812 static int _epg_sort_duration_descending ( const void *a, const void *b, void *eq )
2813 {
2814 return _epg_sort_duration(*(epg_broadcast_t**)b) - _epg_sort_duration(*(epg_broadcast_t**)a);
2815 }
2816
_epg_sort_title_ascending(const void * a,const void * b,void * eq)2817 static int _epg_sort_title_ascending ( const void *a, const void *b, void *eq )
2818 {
2819 const char *s1 = epg_broadcast_get_title(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
2820 const char *s2 = epg_broadcast_get_title(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
2821 if (s1 == NULL && s2 == NULL) return 0;
2822 if (s1 == NULL && s2) return 1;
2823 if (s1 && s2 == NULL) return -1;
2824 int r = strcmp(s1, s2);
2825 if (r == 0) {
2826 s1 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
2827 s2 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
2828 if (s1 == NULL && s2 == NULL) return 0;
2829 if (s1 == NULL && s2) return 1;
2830 if (s1 && s2 == NULL) return -1;
2831 r = strcmp(s1, s2);
2832 }
2833 return r;
2834 }
2835
_epg_sort_title_descending(const void * a,const void * b,void * eq)2836 static int _epg_sort_title_descending ( const void *a, const void *b, void *eq )
2837 {
2838 return _epg_sort_title_ascending(a, b, eq) * -1;
2839 }
2840
_epg_sort_subtitle_ascending(const void * a,const void * b,void * eq)2841 static int _epg_sort_subtitle_ascending ( const void *a, const void *b, void *eq )
2842 {
2843 const char *s1 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
2844 const char *s2 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
2845 if (s1 == NULL && s2 == NULL) return 0;
2846 if (s1 == NULL && s2) return 1;
2847 if (s1 && s2 == NULL) return -1;
2848 return strcmp(s1, s2);
2849 }
2850
_epg_sort_subtitle_descending(const void * a,const void * b,void * eq)2851 static int _epg_sort_subtitle_descending ( const void *a, const void *b, void *eq )
2852 {
2853 return _epg_sort_subtitle_ascending(a, b, eq) * -1;
2854 }
2855
_epg_sort_summary_ascending(const void * a,const void * b,void * eq)2856 static int _epg_sort_summary_ascending ( const void *a, const void *b, void *eq )
2857 {
2858 const char *s1 = epg_broadcast_get_summary(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
2859 const char *s2 = epg_broadcast_get_summary(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
2860 if (s1 == NULL && s2 == NULL) return 0;
2861 if (s1 == NULL && s2) return 1;
2862 if (s1 && s2 == NULL) return -1;
2863 return strcmp(s1, s2);
2864 }
2865
_epg_sort_summary_descending(const void * a,const void * b,void * eq)2866 static int _epg_sort_summary_descending ( const void *a, const void *b, void *eq )
2867 {
2868 return _epg_sort_summary_ascending(a, b, eq) * -1;
2869 }
2870
_epg_sort_description_ascending(const void * a,const void * b,void * eq)2871 static int _epg_sort_description_ascending ( const void *a, const void *b, void *eq )
2872 {
2873 const char *s1 = epg_broadcast_get_description(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
2874 const char *s2 = epg_broadcast_get_description(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
2875 if (s1 == NULL && s2 == NULL) return 0;
2876 if (s1 == NULL && s2) return 1;
2877 if (s1 && s2 == NULL) return -1;
2878 return strcmp(s1, s2);
2879 }
2880
_epg_sort_description_descending(const void * a,const void * b,void * eq)2881 static int _epg_sort_description_descending ( const void *a, const void *b, void *eq )
2882 {
2883 return _epg_sort_description_ascending(a, b, eq) * -1;
2884 }
2885
_epg_sort_channel_ascending(const void * a,const void * b,void * eq)2886 static int _epg_sort_channel_ascending ( const void *a, const void *b, void *eq )
2887 {
2888 char *s1 = strdup(channel_get_name((*(epg_broadcast_t**)a)->channel));
2889 char *s2 = strdup(channel_get_name((*(epg_broadcast_t**)b)->channel));
2890 int r = strcmp(s1, s2);
2891 free(s2);
2892 free(s1);
2893 return r;
2894 }
2895
_epg_sort_channel_descending(const void * a,const void * b,void * eq)2896 static int _epg_sort_channel_descending ( const void *a, const void *b, void *eq )
2897 {
2898 return _epg_sort_description_ascending(a, b, eq) * -1;
2899 }
2900
_epg_sort_channel_num_ascending(const void * a,const void * b,void * eq)2901 static int _epg_sort_channel_num_ascending ( const void *a, const void *b, void *eq )
2902 {
2903 int64_t v1 = channel_get_number((*(epg_broadcast_t**)a)->channel);
2904 int64_t v2 = channel_get_number((*(epg_broadcast_t**)b)->channel);
2905 return v1 - v2;
2906 }
2907
_epg_sort_channel_num_descending(const void * a,const void * b,void * eq)2908 static int _epg_sort_channel_num_descending ( const void *a, const void *b, void *eq )
2909 {
2910 int64_t v1 = channel_get_number((*(epg_broadcast_t**)a)->channel);
2911 int64_t v2 = channel_get_number((*(epg_broadcast_t**)b)->channel);
2912 return v2 - v1;
2913 }
2914
_epg_sort_stars_ascending(const void * a,const void * b,void * eq)2915 static int _epg_sort_stars_ascending ( const void *a, const void *b, void *eq )
2916 {
2917 return (*(epg_broadcast_t**)a)->episode->star_rating - (*(epg_broadcast_t**)b)->episode->star_rating;
2918 }
2919
_epg_sort_stars_descending(const void * a,const void * b,void * eq)2920 static int _epg_sort_stars_descending ( const void *a, const void *b, void *eq )
2921 {
2922 return (*(epg_broadcast_t**)b)->episode->star_rating - (*(epg_broadcast_t**)a)->episode->star_rating;
2923 }
2924
_epg_sort_age_ascending(const void * a,const void * b,void * eq)2925 static int _epg_sort_age_ascending ( const void *a, const void *b, void *eq )
2926 {
2927 return (*(epg_broadcast_t**)a)->episode->age_rating - (*(epg_broadcast_t**)b)->episode->age_rating;
2928 }
2929
_epg_sort_age_descending(const void * a,const void * b,void * eq)2930 static int _epg_sort_age_descending ( const void *a, const void *b, void *eq )
2931 {
2932 return (*(epg_broadcast_t**)b)->episode->age_rating - (*(epg_broadcast_t**)a)->episode->age_rating;
2933 }
2934
_epg_sort_genre_hash(epg_episode_t * ep)2935 static uint64_t _epg_sort_genre_hash( epg_episode_t *ep )
2936 {
2937 uint64_t h = 0, t;
2938 epg_genre_t *g;
2939
2940 LIST_FOREACH(g, &ep->genre, link) {
2941 t = h >> 28;
2942 h <<= 8;
2943 h += (uint64_t)g->code + t;
2944 }
2945 return h;
2946 }
2947
_epg_sort_genre_ascending(const void * a,const void * b,void * eq)2948 static int _epg_sort_genre_ascending ( const void *a, const void *b, void *eq )
2949 {
2950 uint64_t v1 = _epg_sort_genre_hash((*(epg_broadcast_t**)a)->episode);
2951 uint64_t v2 = _epg_sort_genre_hash((*(epg_broadcast_t**)b)->episode);
2952 return v1 - v2;
2953 }
2954
_epg_sort_genre_descending(const void * a,const void * b,void * eq)2955 static int _epg_sort_genre_descending ( const void *a, const void *b, void *eq )
2956 {
2957 return _epg_sort_genre_ascending(a, b, eq) * -1;
2958 }
2959
2960 epg_broadcast_t **
epg_query(epg_query_t * eq,access_t * perm)2961 epg_query ( epg_query_t *eq, access_t *perm )
2962 {
2963 channel_t *channel;
2964 channel_tag_t *tag;
2965 int (*fcn)(const void *, const void *, void *) = NULL;
2966
2967 /* Setup exp */
2968 if (_eq_init_str(&eq->title)) goto fin;
2969 if (_eq_init_str(&eq->subtitle)) goto fin;
2970 if (_eq_init_str(&eq->summary)) goto fin;
2971 if (_eq_init_str(&eq->description)) goto fin;
2972 if (_eq_init_str(&eq->channel_name)) goto fin;
2973
2974 if (eq->stitle)
2975 if (regcomp(&eq->stitle_re, eq->stitle, REG_ICASE | REG_EXTENDED | REG_NOSUB))
2976 goto fin;
2977
2978 channel = channel_find_by_uuid(eq->channel) ?:
2979 channel_find_by_name(eq->channel);
2980
2981 tag = channel_tag_find_by_uuid(eq->channel_tag) ?:
2982 channel_tag_find_by_name(eq->channel_tag, 0);
2983
2984 /* Single channel */
2985 if (channel && tag == NULL) {
2986 if (channel_access(channel, perm, 0))
2987 _eq_add_channel(eq, channel);
2988
2989 /* Tag based */
2990 } else if (tag) {
2991 idnode_list_mapping_t *ilm;
2992 channel_t *ch2;
2993 LIST_FOREACH(ilm, &tag->ct_ctms, ilm_in1_link) {
2994 ch2 = (channel_t *)ilm->ilm_in2;
2995 if(ch2 == channel || channel == NULL)
2996 if (channel_access(ch2, perm, 0))
2997 _eq_add_channel(eq, ch2);
2998 }
2999
3000 /* All channels */
3001 } else {
3002 CHANNEL_FOREACH(channel)
3003 if (channel_access(channel, perm, 0))
3004 _eq_add_channel(eq, channel);
3005 }
3006
3007 switch (eq->sort_dir) {
3008 case ES_ASC:
3009 switch (eq->sort_key) {
3010 case ESK_START: fcn = _epg_sort_start_ascending; break;
3011 case ESK_STOP: fcn = _epg_sort_stop_ascending; break;
3012 case ESK_DURATION: fcn = _epg_sort_duration_ascending; break;
3013 case ESK_TITLE: fcn = _epg_sort_title_ascending; break;
3014 case ESK_SUBTITLE: fcn = _epg_sort_subtitle_ascending; break;
3015 case ESK_SUMMARY: fcn = _epg_sort_summary_ascending; break;
3016 case ESK_DESCRIPTION: fcn = _epg_sort_description_ascending; break;
3017 case ESK_CHANNEL: fcn = _epg_sort_channel_ascending; break;
3018 case ESK_CHANNEL_NUM: fcn = _epg_sort_channel_num_ascending; break;
3019 case ESK_STARS: fcn = _epg_sort_stars_ascending; break;
3020 case ESK_AGE: fcn = _epg_sort_age_ascending; break;
3021 case ESK_GENRE: fcn = _epg_sort_genre_ascending; break;
3022 }
3023 break;
3024 case ES_DSC:
3025 switch (eq->sort_key) {
3026 case ESK_START: fcn = _epg_sort_start_descending; break;
3027 case ESK_STOP: fcn = _epg_sort_stop_descending; break;
3028 case ESK_DURATION: fcn = _epg_sort_duration_descending; break;
3029 case ESK_TITLE: fcn = _epg_sort_title_descending; break;
3030 case ESK_SUBTITLE: fcn = _epg_sort_subtitle_descending; break;
3031 case ESK_SUMMARY: fcn = _epg_sort_summary_descending; break;
3032 case ESK_DESCRIPTION: fcn = _epg_sort_description_descending; break;
3033 case ESK_CHANNEL: fcn = _epg_sort_channel_descending; break;
3034 case ESK_CHANNEL_NUM: fcn = _epg_sort_channel_num_descending; break;
3035 case ESK_STARS: fcn = _epg_sort_stars_descending; break;
3036 case ESK_AGE: fcn = _epg_sort_age_descending; break;
3037 case ESK_GENRE: fcn = _epg_sort_genre_descending; break;
3038 }
3039 break;
3040 }
3041
3042 tvh_qsort_r(eq->result, eq->entries, sizeof(epg_broadcast_t *), fcn, eq);
3043
3044 fin:
3045 _eq_done_str(&eq->title);
3046 _eq_done_str(&eq->subtitle);
3047 _eq_done_str(&eq->summary);
3048 _eq_done_str(&eq->description);
3049 _eq_done_str(&eq->channel_name);
3050
3051 if (eq->stitle)
3052 regfree(&eq->stitle_re);
3053
3054 free(eq->lang); eq->lang = NULL;
3055 free(eq->channel); eq->channel = NULL;
3056 free(eq->channel_tag); eq->channel_tag = NULL;
3057 free(eq->stitle); eq->stitle = NULL;
3058 if (eq->genre != eq->genre_static)
3059 free(eq->genre);
3060 eq->genre = NULL;
3061
3062 return eq->result;
3063 }
3064
epg_query_free(epg_query_t * eq)3065 void epg_query_free(epg_query_t *eq)
3066 {
3067 free(eq->result); eq->result = NULL;
3068 }
3069
3070
3071 /* **************************************************************************
3072 * Miscellaneous
3073 * *************************************************************************/
3074
epg_config_serialize(void)3075 htsmsg_t *epg_config_serialize( void )
3076 {
3077 htsmsg_t *m = htsmsg_create_map();
3078 htsmsg_add_u32(m, "last_id", _epg_object_idx);
3079 return m;
3080 }
3081
epg_config_deserialize(htsmsg_t * m)3082 int epg_config_deserialize( htsmsg_t *m )
3083 {
3084 if (htsmsg_get_u32(m, "last_id", &_epg_object_idx))
3085 return 0;
3086 return 1; /* ok */
3087 }
3088
epg_skel_done(void)3089 void epg_skel_done(void)
3090 {
3091 epg_object_t **skel;
3092 epg_broadcast_t **broad;
3093
3094 skel = _epg_brand_skel();
3095 free(*skel); *skel = NULL;
3096 skel = _epg_season_skel();
3097 free(*skel); *skel = NULL;
3098 skel = _epg_episode_skel();
3099 free(*skel); *skel = NULL;
3100 skel = _epg_serieslink_skel();
3101 free(*skel); *skel = NULL;
3102 broad = _epg_broadcast_skel();
3103 free(*broad); *broad = NULL;
3104 }
3105