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