]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
epg: move from 'merge' to 'complete update' behaviour
authorJaroslav Kysela <perex@perex.cz>
Mon, 1 Feb 2016 20:39:50 +0000 (21:39 +0100)
committerJaroslav Kysela <perex@perex.cz>
Wed, 3 Feb 2016 09:42:33 +0000 (10:42 +0100)
src/dvr/dvr_autorec.c
src/epg.c
src/epg.h
src/epggrab/module/eit.c
src/epggrab/module/opentv.c
src/epggrab/module/psip.c
src/epggrab/module/pyepg.c
src/epggrab/module/xmltv.c
src/epggrab/private.h
src/lang_str.c
src/lang_str.h

index 2e222f27d7b019e2cec2f231ad24655e4f2870a2..368a7477ecbbb27f945dac9f8a07d12214c2682d 100644 (file)
@@ -813,7 +813,7 @@ dvr_autorec_entry_class_brand_set(void *o, const void *v)
 
   if (v && *(char *)v == '\0')
     v = NULL;
-  brand = v ? epg_brand_find_by_uri(v, 1, &save) : NULL;
+  brand = v ? epg_brand_find_by_uri(v, NULL, 1, &save, NULL) : NULL;
   if (brand && dae->dae_brand != brand) {
     if (dae->dae_brand)
       dae->dae_brand->putref((epg_object_t*)dae->dae_brand);
@@ -849,7 +849,7 @@ dvr_autorec_entry_class_season_set(void *o, const void *v)
 
   if (v && *(char *)v == '\0')
     v = NULL;
-  season = v ? epg_season_find_by_uri(v, 1, &save) : NULL;
+  season = v ? epg_season_find_by_uri(v, NULL, 1, &save, NULL) : NULL;
   if (season && dae->dae_season != season) {
     if (dae->dae_season)
       dae->dae_season->putref((epg_object_t*)dae->dae_season);
@@ -885,7 +885,7 @@ dvr_autorec_entry_class_series_link_set(void *o, const void *v)
 
   if (v && *(char *)v == '\0')
     v = NULL;
-  sl = v ? epg_serieslink_find_by_uri(v, 1, &save) : NULL;
+  sl = v ? epg_serieslink_find_by_uri(v, NULL, 1, &save, NULL) : NULL;
   if (sl && dae->dae_serieslink != sl) {
     if (dae->dae_serieslink)
       dae->dae_serieslink->putref((epg_object_t*)dae->dae_season);
index 9f6a57e4f218ff171986268415567b29bf3aa92b..de01e7e625e1dab29e098bd2ae214c113386594f 100644 (file)
--- a/src/epg.c
+++ b/src/epg.c
@@ -186,6 +186,18 @@ static void _epg_object_set_updated ( void *o )
   }
 }
 
+static int _epg_object_set_grabber ( void *o, epggrab_module_t *grab )
+{
+  epg_object_t *eo = o;
+  if (!grab) return 1; // grab=NULL is override
+  if (!eo->grabber ||
+      ((eo->grabber != grab) && (grab->priority > eo->grabber->priority))) {
+    eo->grabber = grab;
+    return 1;
+  }
+  return grab == eo->grabber;
+}
+
 static void _epg_object_create ( void *o )
 {
   epg_object_t *eo = o;
@@ -211,10 +223,11 @@ static void _epg_object_create ( void *o )
 }
 
 static epg_object_t *_epg_object_find_by_uri
-  ( const char *uri, int create, int *save,
-    epg_object_tree_t *tree, epg_object_t **skel )
+  ( const char *uri, epggrab_module_t *src, int create, int *save,
+    uint32_t *changes, epg_object_tree_t *tree, epg_object_t **skel )
 {
   epg_object_t *eo;
+  int _save;
 
   assert(skel != NULL);
   lock_assert(&global_lock);
@@ -229,6 +242,7 @@ static epg_object_t *_epg_object_find_by_uri
   } else {
     eo = RB_INSERT_SORTED(tree, *skel, uri_link, _uri_cmp);
     if (!eo) {
+      if (changes) *changes |= EPG_CHANGED_CREATE;
       *save        = 1;
       eo           = *skel;
       *skel        = NULL;
@@ -236,6 +250,10 @@ static epg_object_t *_epg_object_find_by_uri
       _epg_object_create(eo);
     }
   }
+  if (eo) {
+    _save = _epg_object_set_grabber(eo, src);
+    if (save) *save |= _save;
+  }
   return eo;
 }
 
@@ -287,27 +305,17 @@ static epg_object_t *_epg_object_deserialize ( htsmsg_t *m, epg_object_t *eo )
   return eo;
 }
 
-static int _epg_object_set_grabber ( void *o, epggrab_module_t *grab  )
-{
-  epg_object_t *eo = o;
-  if (!grab) return 1; // grab=NULL is override
-  if (!eo->grabber ||
-      ((eo->grabber != grab) && (grab->priority > eo->grabber->priority))) {
-    eo->grabber = grab;
-    return 2;
-  }
-  return grab == eo->grabber;
-}
-
 static int _epg_object_set_str
-  ( void *o, char **old, const char *newstr, epggrab_module_t *src )
+  ( void *o, char **old, const char *newstr,
+    uint32_t *changed, uint32_t cflag )
 {
   int save = 0;
   epg_object_t *eo = o;
-  if (!eo || !_epg_object_set_grabber(eo, src)) return 0;
+  if (!eo) return 0;
+  if (changed) *changed |= cflag;
   if (!*old && !newstr) return 0;
   if (!*old || !newstr || strcmp(*old, newstr)) {
-    if (*old) free(*old);
+    free(*old);
     *old = newstr ? strdup(newstr) : NULL;
     _epg_object_set_updated(eo);
     save = 1;
@@ -316,43 +324,35 @@ static int _epg_object_set_str
 }
 
 static int _epg_object_set_lang_str
-  ( void *o, lang_str_t **old, const char *newstr, const char *newlang,
-    epggrab_module_t *src )
+  ( void *o, lang_str_t **old, const lang_str_t *str,
+    uint32_t *changed, uint32_t cflag )
 {
-  int save, nflag;
-  epg_object_t *eo = o;
-  nflag = _epg_object_set_grabber(eo, src);
-  if (!eo || !nflag) return 0;
-  if (nflag == 2) { /* changed grabber */
+  if (!o) return 0;
+  if (changed) *changed |= cflag;
+  if (!*old) {
+    if (!str)
+      return 0;
+  }
+  if (!str) {
     lang_str_destroy(*old);
     *old = NULL;
+    return 1;
   }
-  if (!*old) {
-    if (!newstr)
-      return 0;
-    *old = lang_str_create();
+  if (lang_str_compare(*old, str)) {
+    lang_str_destroy(*old);
+    *old = lang_str_copy(str);
+    return 1;
   }
-  save = lang_str_add(*old, newstr, newlang, 1);
-  if (save)
-    _epg_object_set_updated(eo);
-  return save;
-}
-
-static int _epg_object_set_lang_str2
-  ( void *o, lang_str_t **old, const lang_str_t *str, epggrab_module_t *src )
-{
-  int save = 0;
-  lang_str_ele_t *ls;
-  RB_FOREACH(ls, str, link)
-    save |= _epg_object_set_lang_str(o, old, ls->str, ls->lang, src);
-  return save;
+  return 0;
 }
 
 static int _epg_object_set_u8
-  ( void *o, uint8_t *old, const uint8_t nval, epggrab_module_t *src )
+  ( void *o, uint8_t *old, const uint8_t nval,
+    uint32_t *changed, uint32_t cflag )
 {
   int save;
-  if (!o || !_epg_object_set_grabber(o, src)) return 0;
+  if (!o) return 0;
+  if (changed) *changed |= cflag;
   if ((save = (*old != nval)) != 0) {
     *old = nval;
     _epg_object_set_updated(o);
@@ -361,10 +361,12 @@ static int _epg_object_set_u8
 }
 
 static int _epg_object_set_u16
-  ( void *o, uint16_t *old, const uint16_t nval, epggrab_module_t *src )
+  ( void *o, uint16_t *old, const uint16_t nval,
+    uint32_t *changed, uint32_t cflag )
 {
   int save = 0;
-  if (!o || !_epg_object_set_grabber(o, src)) return 0;
+  if (!o) return 0;
+  if (changed) *changed |= cflag;
   if ((save = (*old != nval)) != 0) {
     *old = nval;
     _epg_object_set_updated(o);
@@ -452,10 +454,11 @@ static epg_object_t **_epg_brand_skel ( void )
 }
 
 epg_brand_t* epg_brand_find_by_uri 
-  ( const char *uri, int create, int *save )
+  ( const char *uri, epggrab_module_t *src,
+    int create, int *save, uint32_t *changed )
 {
   return (epg_brand_t*)
-    _epg_object_find_by_uri(uri, create, save,
+    _epg_object_find_by_uri(uri, src, create, save, changed,
                             &epg_brands,
                             _epg_brand_skel());
 }
@@ -465,38 +468,57 @@ epg_brand_t *epg_brand_find_by_id ( uint32_t id )
   return (epg_brand_t*)epg_object_find_by_id(id, EPG_BRAND);
 }
 
+int epg_brand_change_finish
+  ( epg_brand_t *brand, uint32_t changes, int merge )
+{
+  int save = 0;
+  if (merge) return 0;
+  if (changes & EPG_CHANGED_CREATE) return 0;
+  if (!(changes & EPG_CHANGED_TITLE))
+    save |= epg_brand_set_title(brand, NULL, NULL);
+  if (!(changes & EPG_CHANGED_SUMMARY))
+    save |= epg_brand_set_summary(brand, NULL, NULL);
+  if (!(changes & EPG_CHANGED_IMAGE))
+    save |= epg_brand_set_image(brand, NULL, NULL);
+  if (!(changes & EPG_CHANGED_SEASON_COUNT))
+    save |= epg_brand_set_season_count(brand, 0, NULL);
+  return save;
+}
+
 int epg_brand_set_title
-  ( epg_brand_t *brand, const char *title, const char *lang,
-    epggrab_module_t *src )
+  ( epg_brand_t *brand, const lang_str_t *title, uint32_t *changed )
 {
   if (!brand) return 0;
-  return _epg_object_set_lang_str(brand, &brand->title, title, lang, src);
+  return _epg_object_set_lang_str(brand, &brand->title, title,
+                                  changed, EPG_CHANGED_TITLE);
 }
 
 int epg_brand_set_summary
-  ( epg_brand_t *brand, const char *summary, const char *lang,
-    epggrab_module_t *src )
+  ( epg_brand_t *brand, const lang_str_t *summary, uint32_t *changed )
 {
   if (!brand) return 0;
-  return _epg_object_set_lang_str(brand, &brand->summary, summary, lang, src);
+  return _epg_object_set_lang_str(brand, &brand->summary, summary,
+                                  changed, EPG_CHANGED_SUMMARY);
 }
 
 int epg_brand_set_image
-  ( epg_brand_t *brand, const char *image, epggrab_module_t *src )
+  ( epg_brand_t *brand, const char *image, uint32_t *changed )
 {
   int save;
   if (!brand) return 0;
-  save = _epg_object_set_str(brand, &brand->image, image, src);
+  save = _epg_object_set_str(brand, &brand->image, image,
+                             changed, EPG_CHANGED_IMAGE);
   if (save)
     imagecache_get_id(image);
   return save;
 }
 
 int epg_brand_set_season_count
-  ( epg_brand_t *brand, uint16_t count, epggrab_module_t *src )
+  ( epg_brand_t *brand, uint16_t count, uint32_t *changed )
 {
   if (!brand) return 0;
-  return _epg_object_set_u16(brand, &brand->season_count, count, src);
+  return _epg_object_set_u16(brand, &brand->season_count, count,
+                             changed, EPG_CHANGED_SEASON_COUNT);
 }
 
 static void _epg_brand_add_season 
@@ -551,29 +573,29 @@ epg_brand_t *epg_brand_deserialize ( htsmsg_t *m, int create, int *save )
 {
   epg_object_t **skel = _epg_brand_skel();
   epg_brand_t *eb;
-  uint32_t u32;
+  uint32_t u32, changes = 0;
   const char *str;
   lang_str_t *ls;
-  lang_str_ele_t *e;
 
   if (!_epg_object_deserialize(m, *skel)) return NULL;
-  if (!(eb = epg_brand_find_by_uri((*skel)->uri, create, save))) return NULL;
+  if (!(eb = epg_brand_find_by_uri((*skel)->uri, (*skel)->grabber,
+                                   create, save, &changes)))
+    return NULL;
   
   if ((ls = lang_str_deserialize(m, "title"))) {
-    RB_FOREACH(e, ls, link)
-      *save |= epg_brand_set_title(eb, e->str, e->lang, NULL);
+    *save |= epg_brand_set_title(eb, ls, &changes);
     lang_str_destroy(ls);
   }
   if ((ls = lang_str_deserialize(m, "summary"))) {
-    RB_FOREACH(e, ls, link)
-      *save |= epg_brand_set_summary(eb, e->str, e->lang, NULL);
+    *save |= epg_brand_set_summary(eb, ls, &changes);
     lang_str_destroy(ls);
   }
-  if ( !htsmsg_get_u32(m, "season-count", &u32) )
+  if (!htsmsg_get_u32(m, "season-count", &u32))
     *save |= epg_brand_set_season_count(eb, u32, NULL);
+  if ((str = htsmsg_get_str(m, "image")))
+    *save |= epg_brand_set_image(eb, str, &changes);
 
-  if ( (str = htsmsg_get_str(m, "image")) )
-    *save |= epg_brand_set_image(eb, str, NULL);
+  *save |= epg_brand_change_finish(eb, changes, 0);
 
   return eb;
 }
@@ -639,10 +661,11 @@ static epg_object_t **_epg_season_skel ( void )
 }
 
 epg_season_t* epg_season_find_by_uri 
-  ( const char *uri, int create, int *save )
+  ( const char *uri, epggrab_module_t *src,
+    int create, int *save, uint32_t *changed )
 {
   return (epg_season_t*)
-    _epg_object_find_by_uri(uri, create, save,
+    _epg_object_find_by_uri(uri, src, create, save, changed,
                             &epg_seasons,
                             _epg_season_skel());
 }
@@ -652,44 +675,67 @@ epg_season_t *epg_season_find_by_id ( uint32_t id )
   return (epg_season_t*)epg_object_find_by_id(id, EPG_SEASON);
 }
 
+int epg_season_change_finish
+  ( epg_season_t *season, uint32_t changes, int merge )
+{
+  int save = 0;
+  if (merge) return 0;
+  if (changes & EPG_CHANGED_CREATE) return 0;
+  if (!(changes & EPG_CHANGED_SUMMARY))
+    save |= epg_season_set_summary(season, NULL, NULL);
+  if (!(changes & EPG_CHANGED_IMAGE))
+    save |= epg_season_set_image(season, NULL, NULL);
+  if (!(changes & EPG_CHANGED_EPISODE_COUNT))
+    save |= epg_season_set_episode_count(season, 0, NULL);
+  if (!(changes & EPG_CHANGED_SEASON_NUMBER))
+    save |= epg_season_set_number(season, 0, NULL);
+  if (!(changes & EPG_CHANGED_BRAND))
+    save |= epg_season_set_brand(season, 0, NULL);
+  return save;
+}
+
 int epg_season_set_summary
-  ( epg_season_t *season, const char *summary, const char *lang,
-    epggrab_module_t *src )
+  ( epg_season_t *season, const lang_str_t *summary, uint32_t *changed )
 {
   if (!season) return 0;
-  return _epg_object_set_lang_str(season, &season->summary, summary, lang, src);
+  return _epg_object_set_lang_str(season, &season->summary, summary,
+                                  changed, EPG_CHANGED_SUMMARY);
 }
 
 int epg_season_set_image
-  ( epg_season_t *season, const char *image, epggrab_module_t *src )
+  ( epg_season_t *season, const char *image, uint32_t *changed )
 {
   int save;
   if (!season) return 0;
-  save = _epg_object_set_str(season, &season->image, image, src);
+  save = _epg_object_set_str(season, &season->image, image,
+                             changed, EPG_CHANGED_IMAGE);
   if (save)
     imagecache_get_id(image);
   return save;
 }
 
 int epg_season_set_episode_count
-  ( epg_season_t *season, uint16_t count, epggrab_module_t *src )
+  ( epg_season_t *season, uint16_t count, uint32_t *changed )
 {
   if (!season) return 0;
-  return _epg_object_set_u16(season, &season->episode_count, count, src);
+  return _epg_object_set_u16(season, &season->episode_count, count,
+                             changed, EPG_CHANGED_EPISODE_COUNT);
 }
 
 int epg_season_set_number
-  ( epg_season_t *season, uint16_t number, epggrab_module_t *src )
+  ( epg_season_t *season, uint16_t number, uint32_t *changed )
 {
   if (!season) return 0;
-  return _epg_object_set_u16(season, &season->number, number, src);
+  return _epg_object_set_u16(season, &season->number, number,
+                             changed, EPG_CHANGED_SEASON_NUMBER);
 }
 
 int epg_season_set_brand
-  ( epg_season_t *season, epg_brand_t *brand, epggrab_module_t *src )
+  ( epg_season_t *season, epg_brand_t *brand, uint32_t *changed )
 {
   int save = 0;
-  if (!season || !_epg_object_set_grabber(season, src)) return 0;
+  if (!season) return 0;
+  if (changed) *changed |= EPG_CHANGED_BRAND;
   if (season->brand != brand) {
     if (season->brand) _epg_brand_rem_season(season->brand, season);
     season->brand = brand;
@@ -739,30 +785,33 @@ epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save )
   epg_object_t **skel = _epg_season_skel();
   epg_season_t *es;
   epg_brand_t *eb;
-  uint32_t u32;
+  uint32_t u32, changes = 0;
   const char *str;
   lang_str_t *ls;
-  lang_str_ele_t *e;
 
   if (!_epg_object_deserialize(m, *skel)) return NULL;
-  if (!(es = epg_season_find_by_uri((*skel)->uri, create, save))) return NULL;
+  if (!(es = epg_season_find_by_uri((*skel)->uri, (*skel)->grabber,
+                                    create, save, &changes)))
+    return NULL;
   
   if ((ls = lang_str_deserialize(m, "summary"))) {
-    RB_FOREACH(e, ls, link)
-      *save |= epg_season_set_summary(es, e->str, e->lang, NULL);
+    *save |= epg_season_set_summary(es, ls, &changes);
     lang_str_destroy(ls);
   }
+
   if (!htsmsg_get_u32(m, "number", &u32))
-    *save |= epg_season_set_number(es, u32, NULL);
+    *save |= epg_season_set_number(es, u32, &changes);
   if (!htsmsg_get_u32(m, "episode-count", &u32))
-    *save |= epg_season_set_episode_count(es, u32, NULL);
+    *save |= epg_season_set_episode_count(es, u32, &changes);
   
   if ((str = htsmsg_get_str(m, "brand")))
-    if ( (eb = epg_brand_find_by_uri(str, 0, NULL)))
-      *save |= epg_season_set_brand(es, eb, NULL);
+    if ((eb = epg_brand_find_by_uri(str, es->grabber, 0, NULL, NULL)))
+      *save |= epg_season_set_brand(es, eb, &changes);
 
   if ((str = htsmsg_get_str(m, "image")))
-    *save |= epg_season_set_image(es, str, NULL);
+    *save |= epg_season_set_image(es, str, &changes);
+
+  *save |= epg_season_change_finish(es, changes, 0);
 
   return es;
 }
@@ -866,10 +915,11 @@ static epg_object_t **_epg_episode_skel ( void )
 }
 
 epg_episode_t* epg_episode_find_by_uri
-  ( const char *uri, int create, int *save )
+  ( const char *uri, epggrab_module_t *src, int create,
+    int *save, uint32_t *changed )
 {
   return (epg_episode_t*)
-    _epg_object_find_by_uri(uri, create, save,
+    _epg_object_find_by_uri(uri, src, create, save, changed,
                             &epg_episodes,
                             _epg_episode_skel());
 }
@@ -879,86 +929,136 @@ epg_episode_t *epg_episode_find_by_id ( uint32_t id )
   return (epg_episode_t*)epg_object_find_by_id(id, EPG_EPISODE);
 }
 
-int epg_episode_set_title
-  ( epg_episode_t *episode, const char *title, const char *lang,
-    epggrab_module_t *src )
+epg_episode_t *epg_episode_find_by_broadcast
+  ( epg_broadcast_t *ebc, epggrab_module_t *src,
+    int create, int *save, uint32_t *changed )
 {
-  if (!episode) return 0;
-  return _epg_object_set_lang_str(episode, &episode->title, title, lang, src);
+  char uri[UUID_HEX_SIZE+50], ubuf[UUID_HEX_SIZE];
+  if (!ebc) return NULL;
+  if (ebc->episode) {
+    _epg_object_set_grabber(ebc->episode, src);
+    return ebc->episode;
+  }
+  if (!create) return NULL;
+  snprintf(uri, sizeof(uri)-1, "tvh://channel-%s/bcast-%u/episode",
+           idnode_uuid_as_str(&ebc->channel->ch_id, ubuf), ebc->id);
+  return epg_episode_find_by_uri(uri, src, 1, save, changed);
 }
 
-int epg_episode_set_title2
-  ( epg_episode_t *episode, const lang_str_t *str, epggrab_module_t *src )
+int epg_episode_change_finish
+  ( epg_episode_t *episode, uint32_t changes, int merge )
 {
-  if (!episode) return 0;
-  return _epg_object_set_lang_str2(episode, &episode->title, str, src);
+  int save = 0;
+  if (merge) return 0;
+  if (changes & EPG_CHANGED_CREATE) return 0;
+  if (!(changes & EPG_CHANGED_TITLE))
+    save |= epg_episode_set_title(episode, NULL, NULL);
+  if (!(changes & EPG_CHANGED_SUBTITLE))
+    save |= epg_episode_set_subtitle(episode, NULL, NULL);
+  if (!(changes & EPG_CHANGED_SUMMARY))
+    save |= epg_episode_set_summary(episode, NULL, NULL);
+  if (!(changes & EPG_CHANGED_DESCRIPTION))
+    save |= epg_episode_set_description(episode, NULL, NULL);
+  if (!(changes & EPG_CHANGED_IMAGE))
+    save |= epg_episode_set_image(episode, NULL, NULL);
+  if (!(changes & EPG_CHANGED_EPSER_NUM))
+    save |= _epg_object_set_u16(episode, &episode->epnum.s_num, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPSER_CNT))
+    save |= _epg_object_set_u16(episode, &episode->epnum.s_cnt, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPNUM_NUM))
+    save |= _epg_object_set_u16(episode, &episode->epnum.e_num, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPNUM_CNT))
+    save |= _epg_object_set_u16(episode, &episode->epnum.e_cnt, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPPAR_NUM))
+    save |= _epg_object_set_u16(episode, &episode->epnum.p_num, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPPAR_CNT))
+    save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPTEXT))
+    save |= _epg_object_set_str(episode, &episode->epnum.text, NULL, NULL, 0);
+  if (!(changes & EPG_CHANGED_BRAND))
+    save |= epg_episode_set_brand(episode, NULL, NULL);
+  if (!(changes & EPG_CHANGED_SEASON))
+    save |= epg_episode_set_brand(episode, NULL, NULL);
+  if (!(changes & EPG_CHANGED_GENRE))
+    save |= epg_episode_set_genre(episode, NULL, NULL);
+  if (!(changes & EPG_CHANGED_IS_BW))
+    save |= epg_episode_set_is_bw(episode, 0, NULL);
+  if (!(changes & EPG_CHANGED_STAR_RATING))
+    save |= epg_episode_set_star_rating(episode, 0, NULL);
+  if (!(changes & EPG_CHANGED_AGE_RATING))
+    save |= epg_episode_set_age_rating(episode, 0, NULL);
+  if (!(changes & EPG_CHANGED_FIRST_AIRED))
+    save |= epg_episode_set_first_aired(episode, 0, NULL);
+  return save;
 }
 
-int epg_episode_set_subtitle
-  ( epg_episode_t *episode, const char *subtitle, const char *lang,
-    epggrab_module_t *src )
+int epg_episode_set_title
+  ( epg_episode_t *episode, const lang_str_t *title, uint32_t *changed )
 {
   if (!episode) return 0;
-  return _epg_object_set_lang_str(episode, &episode->subtitle,
-                                  subtitle, lang, src);
+  return _epg_object_set_lang_str(episode, &episode->title, title,
+                                  changed, EPG_CHANGED_TITLE);
 }
 
-int epg_episode_set_subtitle2
-  ( epg_episode_t *episode, const lang_str_t *str, epggrab_module_t *src )
+int epg_episode_set_subtitle
+  ( epg_episode_t *episode, const lang_str_t *subtitle, uint32_t *changed )
 {
   if (!episode) return 0;
-  return _epg_object_set_lang_str2(episode, &episode->subtitle, str, src);
+  return _epg_object_set_lang_str(episode, &episode->subtitle,
+                                  subtitle, changed, EPG_CHANGED_SUBTITLE);
 }
 
 int epg_episode_set_summary
-  ( epg_episode_t *episode, const char *summary, const char *lang,
-    epggrab_module_t *src )
+  ( epg_episode_t *episode, const lang_str_t *summary, uint32_t *changed )
 {
   if (!episode) return 0;
   return _epg_object_set_lang_str(episode, &episode->summary,
-                                  summary, lang, src);
+                                  summary, changed, EPG_CHANGED_SUMMARY);
 }
 
 int epg_episode_set_description
-  ( epg_episode_t *episode, const char *desc, const char *lang,
-    epggrab_module_t *src )
+  ( epg_episode_t *episode, const lang_str_t *desc, uint32_t *changed )
 {
   if (!episode) return 0;
   return _epg_object_set_lang_str(episode, &episode->description,
-                                  desc, lang, src);
+                                  desc, changed, EPG_CHANGED_DESCRIPTION);
 }
 
 int epg_episode_set_image
-  ( epg_episode_t *episode, const char *image, epggrab_module_t *src )
+  ( epg_episode_t *episode, const char *image, uint32_t *changed )
 {
   int save;
   if (!episode) return 0;
-  save = _epg_object_set_str(episode, &episode->image, image, src);
+  save = _epg_object_set_str(episode, &episode->image, image,
+                             changed, EPG_CHANGED_IMAGE);
   if (save)
     imagecache_get_id(image);
   return save;
 }
 
 int epg_episode_set_number
-  ( epg_episode_t *episode, uint16_t number, epggrab_module_t *src )
+  ( epg_episode_t *episode, uint16_t number, uint32_t *changed )
 {
   if (!episode) return 0;
-  return _epg_object_set_u16(episode, &episode->epnum.e_num, number, src);
+  return _epg_object_set_u16(episode, &episode->epnum.e_num, number,
+                             changed, EPG_CHANGED_EPNUM_NUM);
 }
 
 int epg_episode_set_part
   ( epg_episode_t *episode, uint16_t part, uint16_t count,
-    epggrab_module_t *src )
+    uint32_t *changed )
 {
   int save = 0;
   if (!episode) return 0;
-  save |= _epg_object_set_u16(episode, &episode->epnum.p_num, part, src);
-  save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt, count, src);
+  save |= _epg_object_set_u16(episode, &episode->epnum.p_num, part,
+                              changed, EPG_CHANGED_EPPAR_NUM);
+  save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt, count,
+                              changed, EPG_CHANGED_EPPAR_CNT);
   return save;
 }
 
 int epg_episode_set_epnum
-  ( epg_episode_t *episode, epg_episode_num_t *num, epggrab_module_t *src )
+  ( epg_episode_t *episode, epg_episode_num_t *num, uint32_t *changed )
 {
   int save = 0;
   static epg_episode_num_t _zero = { 0 };
@@ -968,33 +1068,34 @@ int epg_episode_set_epnum
     num = &_zero;
   if (num->s_num)
     save |= _epg_object_set_u16(episode, &episode->epnum.s_num,
-                                num->s_num, src);
+                                num->s_num, changed, EPG_CHANGED_EPSER_NUM);
   if (num->s_cnt)
     save |= _epg_object_set_u16(episode, &episode->epnum.s_cnt,
-                                num->s_cnt, src);
+                                num->s_cnt, changed, EPG_CHANGED_EPSER_CNT);
   if (num->e_num)
     save |= _epg_object_set_u16(episode, &episode->epnum.e_num,
-                                num->e_num, src);
+                                num->e_num, changed, EPG_CHANGED_EPNUM_NUM);
   if (num->e_cnt)
     save |= _epg_object_set_u16(episode, &episode->epnum.e_cnt,
-                                num->e_cnt, src);
+                                num->e_cnt, changed, EPG_CHANGED_EPNUM_CNT);
   if (num->p_num)
     save |= _epg_object_set_u16(episode, &episode->epnum.p_num,
-                                num->p_num, src);
+                                num->p_num, changed, EPG_CHANGED_EPPAR_NUM);
   if (num->p_cnt)
     save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt,
-                                num->p_cnt, src);
+                                num->p_cnt, changed, EPG_CHANGED_EPPAR_CNT);
   if (num->text)
     save |= _epg_object_set_str(episode, &episode->epnum.text,
-                                num->text, src);
+                                num->text, changed, EPG_CHANGED_EPTEXT);
   return save;
 }
 
 int epg_episode_set_brand
-  ( epg_episode_t *episode, epg_brand_t *brand, epggrab_module_t *src )
+  ( epg_episode_t *episode, epg_brand_t *brand, uint32_t *changed )
 {
   int save = 0;
-  if (!episode && !_epg_object_set_grabber(episode, src)) return 0;
+  if (!episode) return 0;
+  if (changed) *changed |= EPG_CHANGED_BRAND;
   if (episode->brand != brand) {
     if (episode->brand) _epg_brand_rem_episode(episode->brand, episode);
     episode->brand = brand;
@@ -1006,18 +1107,19 @@ int epg_episode_set_brand
 }
 
 int epg_episode_set_season 
-  ( epg_episode_t *episode, epg_season_t *season, epggrab_module_t *src )
+  ( epg_episode_t *episode, epg_season_t *season, uint32_t *changed )
 {
   int save = 0;
-  if (!episode || !_epg_object_set_grabber(episode, src)) return 0;
+  if (!episode) return 0;
+  if (changed) *changed |= EPG_CHANGED_SEASON;
   if (episode->season != season) {
     if (episode->season) _epg_season_rem_episode(episode->season, episode);
     episode->season = season;
     if (season) {
       _epg_season_add_episode(season, episode);
-      save |= epg_episode_set_brand(episode, season->brand ?: NULL, src);
+      save |= epg_episode_set_brand(episode, season->brand ?: NULL, changed);
     } else {
-      save |= epg_episode_set_brand(episode, NULL, src);
+      save |= epg_episode_set_brand(episode, NULL, changed);
     }
     _epg_object_set_updated(episode);
     save = 1;
@@ -1026,13 +1128,16 @@ int epg_episode_set_season
 }
 
 int epg_episode_set_genre
-  ( epg_episode_t *ee, epg_genre_list_t *genre, epggrab_module_t *src )
+  ( epg_episode_t *ee, epg_genre_list_t *genre, uint32_t *changed )
 {
   int save = 0;
   epg_genre_t *g1, *g2;
 
+  if (!ee) return 0;
+
+  if (changed) *changed |= EPG_CHANGED_GENRE;
+
   g1 = LIST_FIRST(&ee->genre);
-  if (!_epg_object_set_grabber(ee, src) && g1) return 0;
 
   /* Remove old */
   while (g1) {
@@ -1046,44 +1151,49 @@ int epg_episode_set_genre
   }
   
   /* Insert all entries */
-  LIST_FOREACH(g1, genre, link)
-    save |= epg_genre_list_add(&ee->genre, g1);
+  if (genre) {
+    LIST_FOREACH(g1, genre, link)
+      save |= epg_genre_list_add(&ee->genre, g1);
+  }
 
   return save;
 }
 
 int epg_episode_set_is_bw
-  ( epg_episode_t *episode, uint8_t bw, epggrab_module_t *src )
+  ( epg_episode_t *episode, uint8_t bw, uint32_t *changed )
 {
   if (!episode) return 0;
-  return _epg_object_set_u8(episode, &episode->is_bw, bw, src);
+  return _epg_object_set_u8(episode, &episode->is_bw, bw,
+                            changed, EPG_CHANGED_IS_BW);
 }
 
 int epg_episode_set_star_rating
-  ( epg_episode_t *episode, uint8_t stars, epggrab_module_t *src )
+  ( epg_episode_t *episode, uint8_t stars, uint32_t *changed )
 {
   if (!episode) return 0;
-  return _epg_object_set_u8(episode, &episode->star_rating, stars, src);
+  return _epg_object_set_u8(episode, &episode->star_rating, stars,
+                            changed, EPG_CHANGED_STAR_RATING);
 }
 
 int epg_episode_set_age_rating
-  ( epg_episode_t *episode, uint8_t age, epggrab_module_t *src )
+  ( epg_episode_t *episode, uint8_t age, uint32_t *changed )
 {
   if (!episode) return 0;
-  return _epg_object_set_u8(episode, &episode->age_rating, age, src);
+  return _epg_object_set_u8(episode, &episode->age_rating, age,
+                            changed, EPG_CHANGED_AGE_RATING);
 }
 
 int epg_episode_set_first_aired
-  ( epg_episode_t *episode, time_t aired, epggrab_module_t *src )
+  ( epg_episode_t *episode, time_t aired, uint32_t *changed )
 {
-  int save = 0;
-  if (!episode || !_epg_object_set_grabber(episode, src)) return 0;
+  if (!episode) return 0;
+  if (changed) *changed |= EPG_CHANGED_FIRST_AIRED;
   if (episode->first_aired != aired) {
     episode->first_aired = aired;
     _epg_object_set_updated(episode);
-    save = 1;
+    return 1;
   }
-  return save;
+  return 0;
 }
 
 static void _epg_episode_add_broadcast 
@@ -1218,38 +1328,34 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
   epg_episode_num_t num;
   htsmsg_t *sub;
   htsmsg_field_t *f;
-  uint32_t u32;
+  uint32_t u32, changes = 0;
   int64_t s64;
   lang_str_t *ls;
-  lang_str_ele_t *e;
   
   if (!_epg_object_deserialize(m, *skel)) return NULL;
-  if (!(ee = epg_episode_find_by_uri((*skel)->uri, create, save)))
+  if (!(ee = epg_episode_find_by_uri((*skel)->uri, (*skel)->grabber,
+                                     create, save, &changes)))
     return NULL;
   
   if ((ls = lang_str_deserialize(m, "title"))) {
-    RB_FOREACH(e, ls, link)
-      *save |= epg_episode_set_title(ee, e->str, e->lang, NULL);
+    *save |= epg_episode_set_title(ee, ls, &changes);
     lang_str_destroy(ls);
   }
   if ((ls = lang_str_deserialize(m, "subtitle"))) {
-    RB_FOREACH(e, ls, link)
-      *save |= epg_episode_set_subtitle(ee, e->str, e->lang, NULL);
+    *save |= epg_episode_set_subtitle(ee, ls, &changes);
     lang_str_destroy(ls);
   }
   if ((ls = lang_str_deserialize(m, "summary"))) {
-    RB_FOREACH(e, ls, link)
-      *save |= epg_episode_set_summary(ee, e->str, e->lang, NULL);
+    *save |= epg_episode_set_summary(ee, ls, &changes);
     lang_str_destroy(ls);
   }
   if ((ls = lang_str_deserialize(m, "description"))) {
-    RB_FOREACH(e, ls, link)
-      *save |= epg_episode_set_description(ee, e->str, e->lang, NULL);
+    *save |= epg_episode_set_description(ee, ls, &changes);
     lang_str_destroy(ls);
   }
   if ((sub = htsmsg_get_map(m, "epnum"))) {
     epg_episode_num_deserialize(sub, &num);
-    *save |= epg_episode_set_epnum(ee, &num, NULL);
+    *save |= epg_episode_set_epnum(ee, &num, &changes);
     if (num.text) free(num.text);
   }
   if ((sub = htsmsg_get_list(m, "genre"))) {
@@ -1259,31 +1365,33 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
       genre.code = (uint8_t)f->hmf_s64;
       epg_genre_list_add(egl, &genre);
     }
-    *save |= epg_episode_set_genre(ee, egl, NULL);
+    *save |= epg_episode_set_genre(ee, egl, &changes);
     epg_genre_list_destroy(egl);
   }
   
   if ((str = htsmsg_get_str(m, "season")))
-    if ( (es = epg_season_find_by_uri(str, 0, NULL)) )
+    if ((es = epg_season_find_by_uri(str, ee->grabber, 0, NULL, &changes)))
       *save |= epg_episode_set_season(ee, es, NULL);
   if ((str = htsmsg_get_str(m, "brand")))
-    if ((eb = epg_brand_find_by_uri(str, 0, NULL)))
+    if ((eb = epg_brand_find_by_uri(str, ee->grabber, 0, NULL, &changes)))
       *save |= epg_episode_set_brand(ee, eb, NULL);
   
   if (!htsmsg_get_u32(m, "is_bw", &u32))
-    *save |= epg_episode_set_is_bw(ee, u32, NULL);
+    *save |= epg_episode_set_is_bw(ee, u32, &changes);
 
   if (!htsmsg_get_u32(m, "star_rating", &u32))
-    *save |= epg_episode_set_star_rating(ee, u32, NULL);
+    *save |= epg_episode_set_star_rating(ee, u32, &changes);
 
   if (!htsmsg_get_u32(m, "age_rating", &u32))
-    *save |= epg_episode_set_age_rating(ee, u32, NULL);
+    *save |= epg_episode_set_age_rating(ee, u32, &changes);
 
   if (!htsmsg_get_s64(m, "first_aired", &s64))
-    *save |= epg_episode_set_first_aired(ee, (time_t)s64, NULL);
+    *save |= epg_episode_set_first_aired(ee, (time_t)s64, &changes);
 
   if ((str = htsmsg_get_str(m, "image")))
-    *save |= epg_episode_set_image(ee, str, NULL);
+    *save |= epg_episode_set_image(ee, str, &changes);
+
+  *save |= epg_episode_change_finish(ee, changes, 0);
 
   return ee;
 }
@@ -1349,10 +1457,11 @@ static epg_object_t **_epg_serieslink_skel ( void )
 }
 
 epg_serieslink_t* epg_serieslink_find_by_uri
-  ( const char *uri, int create, int *save )
+  ( const char *uri, epggrab_module_t *src, int create,
+    int *save, uint32_t *changed )
 {
   return (epg_serieslink_t*)
-    _epg_object_find_by_uri(uri, create, save,
+    _epg_object_find_by_uri(uri, src, create, save, changed,
                             &epg_serieslinks,
                             _epg_serieslink_skel());
 }
@@ -1362,6 +1471,12 @@ epg_serieslink_t *epg_serieslink_find_by_id ( uint32_t id )
   return (epg_serieslink_t*)epg_object_find_by_id(id, EPG_SERIESLINK);
 }
 
+int epg_serieslink_change_finish
+  ( epg_serieslink_t *esl, uint32_t changes, int merge )
+{
+  return 0;
+}
+
 static void _epg_serieslink_add_broadcast
   ( epg_serieslink_t *esl, epg_broadcast_t *ebc )
 {
@@ -1391,10 +1506,14 @@ epg_serieslink_t *epg_serieslink_deserialize
 {
   epg_object_t **skel = _epg_serieslink_skel();
   epg_serieslink_t *esl;
+  uint32_t changes = 0;
 
   if (!_epg_object_deserialize(m, *skel)) return NULL;
-  if (!(esl = epg_serieslink_find_by_uri((*skel)->uri, create, save))) 
+  if (!(esl = epg_serieslink_find_by_uri((*skel)->uri, (*skel)->grabber,
+                                         create, save, &changes)))
     return NULL;
+
+  *save |= epg_serieslink_change_finish(esl, changes, 0);
   
   return esl;
 }
@@ -1485,8 +1604,8 @@ static void _epg_channel_timer_callback ( void *p )
 }
 
 static epg_broadcast_t *_epg_channel_add_broadcast 
-  (channel_t *ch, epg_broadcast_t **bcast, epggrab_module_t *src,
-   int create, int *save)
+  ( channel_t *ch, epg_broadcast_t **bcast, epggrab_module_t *src,
+    int create, int *save, uint32_t *changed )
 {
   int timer = 0;
   epg_broadcast_t *ebc, *ret;
@@ -1504,6 +1623,7 @@ static epg_broadcast_t *_epg_channel_add_broadcast
 
     /* New */
     if (!ret) {
+      if (changed) *changed |= EPG_CHANGED_CREATE;
       *save  = 1;
       ret    = *bcast;
       *bcast = NULL;
@@ -1519,8 +1639,6 @@ static epg_broadcast_t *_epg_channel_add_broadcast
       if (!_epg_object_set_grabber(ret, src))
         return ret;
 
-      *save |= _epg_object_set_u16(ret, &ret->dvb_eid, (*bcast)->dvb_eid, NULL);
-
       /* No time change */
       if (ret->stop == (*bcast)->stop) {
         return ret;
@@ -1638,8 +1756,7 @@ static epg_broadcast_t **_epg_broadcast_skel ( void )
 
 epg_broadcast_t *epg_broadcast_find_by_time
   ( channel_t *channel, epggrab_module_t *src,
-    time_t start, time_t stop, uint16_t eid,
-    int create, int *save )
+    time_t start, time_t stop, int create, int *save, uint32_t *changed )
 {
   epg_broadcast_t **ebc;
   if (!channel || !start || !stop) return NULL;
@@ -1649,36 +1766,74 @@ epg_broadcast_t *epg_broadcast_find_by_time
   ebc = _epg_broadcast_skel();
   (*ebc)->start   = start;
   (*ebc)->stop    = stop;
-  (*ebc)->dvb_eid = eid;
 
-  return _epg_channel_add_broadcast(channel, ebc, src, create, save);
+  return _epg_channel_add_broadcast(channel, ebc, src, create, save, changed);
+}
+
+int epg_broadcast_change_finish
+  ( epg_broadcast_t *broadcast, uint32_t changes, int merge )
+{
+  int save = 0;
+  if (merge) return 0;
+  if (changes & EPG_CHANGED_CREATE) return 0;
+  if (!(changes & EPG_CHANGED_EPISODE))
+    save |= epg_broadcast_set_episode(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_SERIESLINK))
+    save |= epg_broadcast_set_serieslink(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_DVB_EID))
+    save |= epg_broadcast_set_dvb_eid(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_IS_WIDESCREEN))
+    save |= epg_broadcast_set_is_widescreen(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_IS_HD))
+    save |= epg_broadcast_set_is_hd(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_LINES))
+    save |= epg_broadcast_set_lines(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_ASPECT))
+    save |= epg_broadcast_set_aspect(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_DEAFSIGNED))
+    save |= epg_broadcast_set_is_deafsigned(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_SUBTITLED))
+    save |= epg_broadcast_set_is_subtitled(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_AUDIO_DESC))
+    save |= epg_broadcast_set_is_audio_desc(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_IS_NEW))
+    save |= epg_broadcast_set_is_new(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_IS_REPEAT))
+    save |= epg_broadcast_set_is_repeat(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_SUMMARY))
+    save |= epg_broadcast_set_summary(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_DESCRIPTION))
+    save |= epg_broadcast_set_description(broadcast, NULL, NULL);
+  return save;
 }
 
 epg_broadcast_t *epg_broadcast_clone
   ( channel_t *channel, epg_broadcast_t *src, int *save )
 {
   epg_broadcast_t *ebc;
+  uint32_t changes = 0;
 
   if (!src) return NULL;
   ebc = epg_broadcast_find_by_time(channel, src->grabber,
                                    src->start, src->stop,
-                                   src->dvb_eid, 1, save);
+                                   1, save, &changes);
   if (ebc) {
     /* Copy metadata */
-    *save |= epg_broadcast_set_is_widescreen(ebc, src->is_widescreen, NULL);
-    *save |= epg_broadcast_set_is_hd(ebc, src->is_hd, NULL);
-    *save |= epg_broadcast_set_lines(ebc, src->lines, NULL);
-    *save |= epg_broadcast_set_aspect(ebc, src->aspect, NULL);
-    *save |= epg_broadcast_set_is_deafsigned(ebc, src->is_deafsigned, NULL);
-    *save |= epg_broadcast_set_is_subtitled(ebc, src->is_subtitled, NULL);
-    *save |= epg_broadcast_set_is_audio_desc(ebc, src->is_audio_desc, NULL);
-    *save |= epg_broadcast_set_is_new(ebc, src->is_new, NULL);
-    *save |= epg_broadcast_set_is_repeat(ebc, src->is_repeat, NULL);
-    *save |= epg_broadcast_set_summary2(ebc, src->summary, NULL);
-    *save |= epg_broadcast_set_description2(ebc, src->description, NULL);
-    *save |= epg_broadcast_set_serieslink(ebc, src->serieslink, NULL);
-    *save |= epg_broadcast_set_episode(ebc, src->episode, NULL);
+    *save |= epg_broadcast_set_is_widescreen(ebc, src->is_widescreen, &changes);
+    *save |= epg_broadcast_set_is_hd(ebc, src->is_hd, &changes);
+    *save |= epg_broadcast_set_lines(ebc, src->lines, &changes);
+    *save |= epg_broadcast_set_aspect(ebc, src->aspect, &changes);
+    *save |= epg_broadcast_set_is_deafsigned(ebc, src->is_deafsigned, &changes);
+    *save |= epg_broadcast_set_is_subtitled(ebc, src->is_subtitled, &changes);
+    *save |= epg_broadcast_set_is_audio_desc(ebc, src->is_audio_desc, &changes);
+    *save |= epg_broadcast_set_is_new(ebc, src->is_new, &changes);
+    *save |= epg_broadcast_set_is_repeat(ebc, src->is_repeat, &changes);
+    *save |= epg_broadcast_set_summary(ebc, src->summary, &changes);
+    *save |= epg_broadcast_set_description(ebc, src->description, &changes);
+    *save |= epg_broadcast_set_serieslink(ebc, src->serieslink, &changes);
+    *save |= epg_broadcast_set_episode(ebc, src->episode, &changes);
     _epg_object_set_grabber(ebc, src->grabber);
+    *save |= epg_broadcast_change_finish(ebc, changes, 0);
   }
   return ebc;
 }
@@ -1720,10 +1875,11 @@ void epg_broadcast_notify_running
 }
 
 int epg_broadcast_set_episode 
-  ( epg_broadcast_t *broadcast, epg_episode_t *episode, epggrab_module_t *src )
+  ( epg_broadcast_t *broadcast, epg_episode_t *episode, uint32_t *changed )
 {
   int save = 0;
-  if (!broadcast || !_epg_object_set_grabber(broadcast, src)) return 0;
+  if (!broadcast) return 0;
+  if (changed) *changed |= EPG_CHANGED_EPISODE;
   if (broadcast->episode != episode) {
     if (broadcast->episode)
       _epg_episode_rem_broadcast(broadcast->episode, broadcast);
@@ -1736,10 +1892,11 @@ int epg_broadcast_set_episode
 }
 
 int epg_broadcast_set_serieslink
-  ( epg_broadcast_t *ebc, epg_serieslink_t *esl, epggrab_module_t *src )
+  ( epg_broadcast_t *ebc, epg_serieslink_t *esl, uint32_t *changed )
 {
   int save = 0;
-  if (!ebc || !_epg_object_set_grabber(ebc, src)) return 0;
+  if (!ebc) return 0;
+  if (changed) *changed |= EPG_CHANGED_SERIESLINK;
   if (ebc->serieslink != esl) {
     if (ebc->serieslink) _epg_serieslink_rem_broadcast(ebc->serieslink, ebc);
     ebc->serieslink = esl;
@@ -1749,97 +1906,100 @@ int epg_broadcast_set_serieslink
   return save;
 }
 
+int epg_broadcast_set_dvb_eid
+  ( epg_broadcast_t *b, uint16_t dvb_eid, uint32_t *changed )
+{
+  if (!b) return 0;
+  return _epg_object_set_u16(b, &b->dvb_eid, dvb_eid,
+                             changed, EPG_CHANGED_DVB_EID);
+}
+
 int epg_broadcast_set_is_widescreen
-  ( epg_broadcast_t *b, uint8_t ws, epggrab_module_t *src )
+  ( epg_broadcast_t *b, uint8_t ws, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_u8(b, &b->is_widescreen, ws, src);
+  return _epg_object_set_u8(b, &b->is_widescreen, ws,
+                            changed, EPG_CHANGED_IS_WIDESCREEN);
 }
 
 int epg_broadcast_set_is_hd
-  ( epg_broadcast_t *b, uint8_t hd, epggrab_module_t *src )
+  ( epg_broadcast_t *b, uint8_t hd, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_u8(b, &b->is_hd, hd, src);
+  return _epg_object_set_u8(b, &b->is_hd, hd,
+                            changed, EPG_CHANGED_IS_HD);
 }
 
 int epg_broadcast_set_lines
-  ( epg_broadcast_t *b, uint16_t lines, epggrab_module_t *src )
+  ( epg_broadcast_t *b, uint16_t lines, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_u16(b, &b->lines, lines, src);
+  return _epg_object_set_u16(b, &b->lines, lines,
+                             changed, EPG_CHANGED_LINES);
 }
 
 int epg_broadcast_set_aspect
-  ( epg_broadcast_t *b, uint16_t aspect, epggrab_module_t *src )
+  ( epg_broadcast_t *b, uint16_t aspect, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_u16(b, &b->aspect, aspect, src);
+  return _epg_object_set_u16(b, &b->aspect, aspect,
+                             changed, EPG_CHANGED_ASPECT);
 }
 
 int epg_broadcast_set_is_deafsigned
-  ( epg_broadcast_t *b, uint8_t ds, epggrab_module_t *src )
+  ( epg_broadcast_t *b, uint8_t ds, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_u8(b, &b->is_deafsigned, ds, src);
+  return _epg_object_set_u8(b, &b->is_deafsigned, ds,
+                            changed, EPG_CHANGED_DEAFSIGNED);
 }
 
 int epg_broadcast_set_is_subtitled
-  ( epg_broadcast_t *b, uint8_t st, epggrab_module_t *src )
+  ( epg_broadcast_t *b, uint8_t st, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_u8(b, &b->is_subtitled, st, src);
+  return _epg_object_set_u8(b, &b->is_subtitled, st,
+                            changed, EPG_CHANGED_SUBTITLED);
 }
 
 int epg_broadcast_set_is_audio_desc
-  ( epg_broadcast_t *b, uint8_t ad, epggrab_module_t *src )
+  ( epg_broadcast_t *b, uint8_t ad, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_u8(b, &b->is_audio_desc, ad, src);
+  return _epg_object_set_u8(b, &b->is_audio_desc, ad,
+                            changed, EPG_CHANGED_AUDIO_DESC);
 }
 
 int epg_broadcast_set_is_new
-  ( epg_broadcast_t *b, uint8_t n, epggrab_module_t *src )
+  ( epg_broadcast_t *b, uint8_t n, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_u8(b, &b->is_new, n, src);
+  return _epg_object_set_u8(b, &b->is_new, n,
+                            changed, EPG_CHANGED_IS_NEW);
 }
 
 int epg_broadcast_set_is_repeat
-  ( epg_broadcast_t *b, uint8_t r, epggrab_module_t *src )
+  ( epg_broadcast_t *b, uint8_t r, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_u8(b, &b->is_repeat, r, src);
+  return _epg_object_set_u8(b, &b->is_repeat, r,
+                            changed, EPG_CHANGED_IS_REPEAT);
 }
 
 int epg_broadcast_set_summary
-  ( epg_broadcast_t *b, const char *str, const char *lang,
-    epggrab_module_t *src )
+  ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_lang_str(b, &b->summary, str, lang, src);
+  return _epg_object_set_lang_str(b, &b->summary, str,
+                                  changed, EPG_CHANGED_SUMMARY);
 }
 
 int epg_broadcast_set_description
-  ( epg_broadcast_t *b, const char *str, const char *lang,
-    epggrab_module_t *src )
+  ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
 {
   if (!b) return 0;
-  return _epg_object_set_lang_str(b, &b->description, str, lang, src);
-}
-
-int epg_broadcast_set_summary2
-  ( epg_broadcast_t *b, const lang_str_t *str, epggrab_module_t *src )
-{
-  if (!b || !str) return 0;
-  return _epg_object_set_lang_str2(b, &b->summary, str, src);
-}
-
-int epg_broadcast_set_description2
-  ( epg_broadcast_t *b, const lang_str_t *str, epggrab_module_t *src )
-{
-  if (!b || !str) return 0;
-  return _epg_object_set_lang_str2(b, &b->description, str, src);
+  return _epg_object_set_lang_str(b, &b->description, str,
+                                  changed, EPG_CHANGED_DESCRIPTION);
 }
 
 epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *broadcast )
@@ -1848,21 +2008,6 @@ epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *broadcast )
   return RB_NEXT(broadcast, sched_link);
 }
 
-epg_episode_t *epg_broadcast_get_episode
-  ( epg_broadcast_t *ebc, int create, int *save )
-{
-  char uri[256], ubuf[UUID_HEX_SIZE];
-  epg_episode_t *ee;
-  if (!ebc) return NULL;
-  if (ebc->episode) return ebc->episode;
-  if (!create) return NULL;
-  snprintf(uri, sizeof(uri)-1, "tvh://channel-%s/bcast-%u/episode",
-           idnode_uuid_as_str(&ebc->channel->ch_id, ubuf), ebc->id);
-  if ((ee = epg_episode_find_by_uri(uri, 1, save)))
-    *save |= epg_broadcast_set_episode(ebc, ee, ebc->grabber);
-  return ee;
-}
-
 const char *epg_broadcast_get_title ( epg_broadcast_t *b, const char *lang )
 {
   if (!b || !b->episode) return NULL;
@@ -1938,7 +2083,7 @@ epg_broadcast_t *epg_broadcast_deserialize
   epg_serieslink_t *esl;
   lang_str_t *ls;
   const char *str;
-  uint32_t eid, u32;
+  uint32_t eid, u32, changes = 0, changes2 = 0;
   int64_t start, stop;
 
   if (htsmsg_get_s64(m, "start", &start)) return NULL;
@@ -1947,63 +2092,68 @@ epg_broadcast_t *epg_broadcast_deserialize
   if (stop <= start) return NULL;
   if (stop <= dispatch_clock) return NULL;
   if (!(str = htsmsg_get_str(m, "episode"))) return NULL;
-  if (!(ee  = epg_episode_find_by_uri(str, 0, NULL))) return NULL;
 
-  /* Set properties */
   _epg_object_deserialize(m, (epg_object_t*)*skel);
+
+  if (!(ee  = epg_episode_find_by_uri(str, (*skel)->grabber, 0, NULL, NULL)))
+    return NULL;
+
+  /* Set properties */
   (*skel)->start   = start;
   (*skel)->stop    = stop;
 
-  /* Get DVB id */
-  if (!htsmsg_get_u32(m, "dvb_eid", &eid))
-    (*skel)->dvb_eid = eid;
-
   /* Get channel */
   if ((str = htsmsg_get_str(m, "channel")))
     ch = channel_find(str);
   if (!ch) return NULL;
 
   /* Create */
-  ebc = _epg_channel_add_broadcast(ch, skel, NULL, create, save);
+  ebc = _epg_channel_add_broadcast(ch, skel, (*skel)->grabber, create, save, &changes);
   if (!ebc) return NULL;
 
   /* Get metadata */
+  if (!htsmsg_get_u32(m, "dvb_eid", &eid))
+    *save |= epg_broadcast_set_dvb_eid(ebc, eid, &changes);
   if (!htsmsg_get_u32(m, "is_widescreen", &u32))
-    *save |= epg_broadcast_set_is_widescreen(ebc, u32, NULL);
+    *save |= epg_broadcast_set_is_widescreen(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "is_hd", &u32))
-    *save |= epg_broadcast_set_is_hd(ebc, u32, NULL);
+    *save |= epg_broadcast_set_is_hd(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "lines", &u32))
-    *save |= epg_broadcast_set_lines(ebc, u32, NULL);
+    *save |= epg_broadcast_set_lines(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "aspect", &u32))
-    *save |= epg_broadcast_set_aspect(ebc, u32, NULL);
+    *save |= epg_broadcast_set_aspect(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "is_deafsigned", &u32))
-    *save |= epg_broadcast_set_is_deafsigned(ebc, u32, NULL);
+    *save |= epg_broadcast_set_is_deafsigned(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "is_subtitled", &u32))
-    *save |= epg_broadcast_set_is_subtitled(ebc, u32, NULL);
+    *save |= epg_broadcast_set_is_subtitled(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "is_audio_desc", &u32))
-    *save |= epg_broadcast_set_is_audio_desc(ebc, u32, NULL);
+    *save |= epg_broadcast_set_is_audio_desc(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "is_new", &u32))
-    *save |= epg_broadcast_set_is_new(ebc, u32, NULL);
+    *save |= epg_broadcast_set_is_new(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "is_repeat", &u32))
-    *save |= epg_broadcast_set_is_repeat(ebc, u32, NULL);
+    *save |= epg_broadcast_set_is_repeat(ebc, u32, &changes);
 
   if ((ls = lang_str_deserialize(m, "summary"))) {
-    *save |= epg_broadcast_set_summary2(ebc, ls, NULL);
+    *save |= epg_broadcast_set_summary(ebc, ls, &changes);
     lang_str_destroy(ls);
   }
 
   if ((ls = lang_str_deserialize(m, "description"))) {
-    *save |= epg_broadcast_set_description2(ebc, ls, NULL);
+    *save |= epg_broadcast_set_description(ebc, ls, &changes);
     lang_str_destroy(ls);
   }
 
   /* Series link */
   if ((str = htsmsg_get_str(m, "serieslink")))
-    if ((esl = epg_serieslink_find_by_uri(str, 1, save)))
-      *save |= epg_broadcast_set_serieslink(ebc, esl, NULL);
+    if ((esl = epg_serieslink_find_by_uri(str, ebc->grabber, 1, save, &changes2))) {
+      *save |= epg_broadcast_set_serieslink(ebc, esl, &changes);
+      *save |= epg_serieslink_change_finish(esl, changes2, 0);
+    }
 
   /* Set the episode */
-  *save |= epg_broadcast_set_episode(ebc, ee, NULL);
+  *save |= epg_broadcast_set_episode(ebc, ee, &changes);
+
+  *save |= epg_broadcast_change_finish(ebc, changes, 0);
 
   return ebc;
 }
index b89d86ec6b83146766164c7674dd5054e4ec3a19..d720f3f6a22f81b118c8098dd2488dd5aa119962 100644 (file)
--- a/src/epg.h
+++ b/src/epg.h
@@ -119,6 +119,15 @@ typedef enum epg_object_type
 } epg_object_type_t;
 #define EPG_TYPEMAX EPG_SERIESLINK
 
+/* Change flags - shared */
+#define EPG_CHANGED_CREATE        (1<<0)
+#define EPG_CHANGED_TITLE         (1<<1)
+#define EPG_CHANGED_SUBTITLE      (1<<2)
+#define EPG_CHANGED_SUMMARY       (1<<3)
+#define EPG_CHANGED_DESCRIPTION   (1<<4)
+#define EPG_CHANGED_IMAGE         (1<<5)
+#define EPG_CHANGED_SLAST         2
+
 /* Object */
 struct epg_object
 {
@@ -155,6 +164,11 @@ epg_object_t *epg_object_deserialize ( htsmsg_t *msg, int create, int *save );
  * e.g. The Simpsons, 24, Eastenders, etc...
  * ***********************************************************************/
 
+/* Change flags */
+#define EPG_CHANGED_SEASON_COUNT (1<<(EPG_CHANGED_SLAST+1))
+#define EPG_CHANGED_SEASONS      (1<<(EPG_CHANGED_SLAST+2))
+#define EPG_CHANGED_EPISODES     (1<<(EPG_CHANGED_SLAST+3))
+
 /* Object */
 struct epg_brand
 {
@@ -171,9 +185,13 @@ struct epg_brand
 
 /* Lookup */
 epg_brand_t *epg_brand_find_by_uri
-  ( const char *uri, int create, int *save );
+  ( const char *uri, struct epggrab_module *src, int create, int *save, uint32_t *changes );
 epg_brand_t *epg_brand_find_by_id ( uint32_t id );
 
+/* Post-modify */
+int epg_brand_change_finish( epg_brand_t *b, uint32_t changed, int merge )
+  __attribute__((warn_unused_result));
+
 /* Accessors */
 const char *epg_brand_get_title
   ( const epg_brand_t *b, const char *lang );
@@ -182,18 +200,16 @@ const char *epg_brand_get_summary
 
 /* Mutators */
 int epg_brand_set_title        
-  ( epg_brand_t *b, const char *title, const char *lang,
-    struct epggrab_module *src )
+  ( epg_brand_t *b, const lang_str_t *title, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_brand_set_summary
-  ( epg_brand_t *b, const char *summary, const char *lang,
-    struct epggrab_module *src )
+  ( epg_brand_t *b, const lang_str_t *summary, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_brand_set_season_count
-  ( epg_brand_t *b, uint16_t season_count, struct epggrab_module *src )
+  ( epg_brand_t *b, uint16_t season_count, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_brand_set_image
-  ( epg_brand_t *b, const char *i, struct epggrab_module *src )
+  ( epg_brand_t *b, const char *i, uint32_t *changed )
   __attribute__((warn_unused_result));
 
 /* Serialization */
@@ -207,6 +223,10 @@ htsmsg_t    *epg_brand_list ( void );
  * Season
  * ***********************************************************************/
 
+/* Change flags */
+#define EPG_CHANGED_SEASON_NUMBER (1<<(EPG_CHANGED_SLAST+1))
+#define EPG_CHANGED_EPISODE_COUNT (1<<(EPG_CHANGED_SLAST+2))
+
 /* Object */
 struct epg_season
 {
@@ -225,29 +245,32 @@ struct epg_season
 
 /* Lookup */
 epg_season_t *epg_season_find_by_uri
-  ( const char *uri, int create, int *save );
+  ( const char *uri, struct epggrab_module *src, int create, int *save, uint32_t *changes );
 epg_season_t *epg_season_find_by_id ( uint32_t id );
 
+/* Post-modify */
+int epg_season_change_finish( epg_season_t *s, uint32_t changed, int merge )
+  __attribute__((warn_unused_result));
+
 /* Accessors */
 const char *epg_season_get_summary
   ( const epg_season_t *s, const char *lang );
 
 /* Mutators */
 int epg_season_set_summary
-  ( epg_season_t *s, const char *summary, const char *lang,
-    struct epggrab_module *src )
+  ( epg_season_t *s, const lang_str_t *summary, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_season_set_number
-  ( epg_season_t *s, uint16_t number, struct epggrab_module *src )
+  ( epg_season_t *s, uint16_t number, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_season_set_episode_count
-  ( epg_season_t *s, uint16_t episode_count, struct epggrab_module *src )
+  ( epg_season_t *s, uint16_t episode_count, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_season_set_brand
-  ( epg_season_t *s, epg_brand_t *b, struct epggrab_module *src )
+  ( epg_season_t *s, epg_brand_t *b, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_season_set_image
-  ( epg_season_t *s, const char *image, struct epggrab_module *src )
+  ( epg_season_t *s, const char *image, uint32_t *changed )
   __attribute__((warn_unused_result));
 
 /* Serialization */
@@ -258,6 +281,22 @@ epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save );
  * Episode
  * ***********************************************************************/
 
+/* Change flags */
+#define EPG_CHANGED_GENRE        (1<<(EPG_CHANGED_SLAST+1))
+#define EPG_CHANGED_EPNUM_NUM    (1<<(EPG_CHANGED_SLAST+2))
+#define EPG_CHANGED_EPNUM_CNT    (1<<(EPG_CHANGED_SLAST+3))
+#define EPG_CHANGED_EPPAR_NUM    (1<<(EPG_CHANGED_SLAST+4))
+#define EPG_CHANGED_EPPAR_CNT    (1<<(EPG_CHANGED_SLAST+5))
+#define EPG_CHANGED_EPSER_NUM    (1<<(EPG_CHANGED_SLAST+6))
+#define EPG_CHANGED_EPSER_CNT    (1<<(EPG_CHANGED_SLAST+7))
+#define EPG_CHANGED_EPTEXT       (1<<(EPG_CHANGED_SLAST+8))
+#define EPG_CHANGED_IS_BW        (1<<(EPG_CHANGED_SLAST+9))
+#define EPG_CHANGED_STAR_RATING  (1<<(EPG_CHANGED_SLAST+10))
+#define EPG_CHANGED_AGE_RATING   (1<<(EPG_CHANGED_SLAST+11))
+#define EPG_CHANGED_FIRST_AIRED  (1<<(EPG_CHANGED_SLAST+12))
+#define EPG_CHANGED_BRAND        (1<<(EPG_CHANGED_SLAST+13))
+#define EPG_CHANGED_SEASON       (1<<(EPG_CHANGED_SLAST+14))
+
 /* Episode numbering object - this is for some back-compat and also
  * to allow episode information to be "collated" into easy to use object
  */
@@ -300,8 +339,14 @@ struct epg_episode
 
 /* Lookup */
 epg_episode_t *epg_episode_find_by_uri
-  ( const char *uri, int create, int *save );
+  ( const char *uri, struct epggrab_module *src, int create, int *save, uint32_t *changes );
 epg_episode_t *epg_episode_find_by_id ( uint32_t id );
+epg_episode_t *epg_episode_find_by_broadcast
+  ( epg_broadcast_t *b, struct epggrab_module *src, int create, int *save, uint32_t *changes );
+
+/* Post-modify */
+int epg_episode_change_finish( epg_episode_t *s, uint32_t changed, int merge )
+  __attribute__((warn_unused_result));
 
 /* Accessors */
 const char *epg_episode_get_title
@@ -315,60 +360,49 @@ const char *epg_episode_get_description
 
 /* Mutators */
 int epg_episode_set_title
-  ( epg_episode_t *e, const char *title, const char *lang,
-    struct epggrab_module *src )
+  ( epg_episode_t *e, const lang_str_t *title, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_subtitle
-  ( epg_episode_t *e, const char *subtitle, const char *lang,
-    struct epggrab_module *src )
+  ( epg_episode_t *e, const lang_str_t *subtitle, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_summary
-  ( epg_episode_t *e, const char *summary, const char *lang,
-    struct epggrab_module *src )
+  ( epg_episode_t *e, const lang_str_t *summary, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_description
-  ( epg_episode_t *e, const char *description, const char *lang,
-    struct epggrab_module *src )
+  ( epg_episode_t *e, const lang_str_t *description, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_number
-  ( epg_episode_t *e, uint16_t number, struct epggrab_module *src )
+  ( epg_episode_t *e, uint16_t number, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_part
-  ( epg_episode_t *e, uint16_t number, uint16_t count,
-    struct epggrab_module *src )
+  ( epg_episode_t *e, uint16_t number, uint16_t count, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_epnum
-  ( epg_episode_t *e, epg_episode_num_t *num, struct epggrab_module *src )
+  ( epg_episode_t *e, epg_episode_num_t *num, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_brand
-  ( epg_episode_t *e, epg_brand_t *b, struct epggrab_module *src )
+  ( epg_episode_t *e, epg_brand_t *b, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_season
-  ( epg_episode_t *e, epg_season_t *s, struct epggrab_module *src )
+  ( epg_episode_t *e, epg_season_t *s, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_genre
-  ( epg_episode_t *e, epg_genre_list_t *g, struct epggrab_module *src )
+  ( epg_episode_t *e, epg_genre_list_t *g, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_image
-  ( epg_episode_t *e, const char *i, struct epggrab_module *src )
+  ( epg_episode_t *e, const char *i, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_is_bw
-  ( epg_episode_t *e, uint8_t bw, struct epggrab_module *src )
-  __attribute__((warn_unused_result));
-int epg_episode_set_title2
-  ( epg_episode_t *e, const lang_str_t *str, struct epggrab_module *src )
-  __attribute__((warn_unused_result));
-int epg_episode_set_subtitle2
-  ( epg_episode_t *e, const lang_str_t *str, struct epggrab_module *src )
+  ( epg_episode_t *e, uint8_t bw, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_first_aired
-  ( epg_episode_t *e, time_t aired, struct epggrab_module *src )
+  ( epg_episode_t *e, time_t aired, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_star_rating
-  ( epg_episode_t *e, uint8_t stars, struct epggrab_module *src )
+  ( epg_episode_t *e, uint8_t stars, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_episode_set_age_rating
-  ( epg_episode_t *e, uint8_t age, struct epggrab_module *src )
+  ( epg_episode_t *e, uint8_t age, uint32_t *changed )
   __attribute__((warn_unused_result));
 
 // Note: this does NOT strdup the text field
@@ -415,10 +449,14 @@ struct epg_serieslink
 
 /* Lookup */
 epg_serieslink_t *epg_serieslink_find_by_uri
-  ( const char *uri, int create, int *save );
+  ( const char *uri, struct epggrab_module *src, int create, int *save, uint32_t *changes );
 epg_serieslink_t *epg_serieslink_find_by_id
   ( uint32_t id );
 
+/* Post-modify */
+int epg_serieslink_change_finish( epg_serieslink_t *s, uint32_t changed, int merge )
+  __attribute__((warn_unused_result));
+
 /* Serialization */
 htsmsg_t         *epg_serieslink_serialize   ( epg_serieslink_t *s );
 epg_serieslink_t *epg_serieslink_deserialize 
@@ -428,6 +466,19 @@ epg_serieslink_t *epg_serieslink_deserialize
  * Broadcast - specific airing (channel & time) of an episode
  * ***********************************************************************/
 
+#define EPG_CHANGED_DVB_EID      (1<<(EPG_CHANGED_SLAST+1))
+#define EPG_CHANGED_IS_WIDESCREEN (1<<(EPG_CHANGED_SLAST+2))
+#define EPG_CHANGED_IS_HD        (1<<(EPG_CHANGED_SLAST+3))
+#define EPG_CHANGED_LINES        (1<<(EPG_CHANGED_SLAST+4))
+#define EPG_CHANGED_ASPECT       (1<<(EPG_CHANGED_SLAST+5))
+#define EPG_CHANGED_DEAFSIGNED   (1<<(EPG_CHANGED_SLAST+6))
+#define EPG_CHANGED_SUBTITLED    (1<<(EPG_CHANGED_SLAST+7))
+#define EPG_CHANGED_AUDIO_DESC   (1<<(EPG_CHANGED_SLAST+8))
+#define EPG_CHANGED_IS_NEW       (1<<(EPG_CHANGED_SLAST+9))
+#define EPG_CHANGED_IS_REPEAT    (1<<(EPG_CHANGED_SLAST+10))
+#define EPG_CHANGED_EPISODE      (1<<(EPG_CHANGED_SLAST+11))
+#define EPG_CHANGED_SERIESLINK   (1<<(EPG_CHANGED_SLAST+12))
+
 /* Object */
 struct epg_broadcast
 {
@@ -469,10 +520,14 @@ struct epg_broadcast
 /* Lookup */
 epg_broadcast_t *epg_broadcast_find_by_time 
   ( struct channel *ch, struct epggrab_module *src,
-    time_t start, time_t stop, uint16_t eid, int create, int *save );
+    time_t start, time_t stop, int create, int *save, uint32_t *changes );
 epg_broadcast_t *epg_broadcast_find_by_eid ( struct channel *ch, uint16_t eid );
 epg_broadcast_t *epg_broadcast_find_by_id  ( uint32_t id );
 
+/* Post-modify */
+int epg_broadcast_change_finish( epg_broadcast_t *b, uint32_t changed, int merge )
+  __attribute__((warn_unused_result));
+
 /* Special */
 epg_broadcast_t *epg_broadcast_clone
   ( struct channel *channel, epg_broadcast_t *src, int *save );
@@ -480,58 +535,51 @@ void epg_broadcast_notify_running
   ( epg_broadcast_t *b, epg_source_t esrc, epg_running_t running );
 
 /* Mutators */
+int epg_broadcast_set_dvb_eid
+  ( epg_broadcast_t *b, uint16_t dvb_eid, uint32_t *changed )
+  __attribute__((warn_unused_result));
 int epg_broadcast_set_episode
-  ( epg_broadcast_t *b, epg_episode_t *e, struct epggrab_module *src )
+  ( epg_broadcast_t *b, epg_episode_t *e, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_widescreen
-  ( epg_broadcast_t *b, uint8_t ws, struct epggrab_module *src )
+  ( epg_broadcast_t *b, uint8_t ws, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_hd
-  ( epg_broadcast_t *b, uint8_t hd, struct epggrab_module *src )
+  ( epg_broadcast_t *b, uint8_t hd, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_lines 
-  ( epg_broadcast_t *b, uint16_t lines, struct epggrab_module *src )
+  ( epg_broadcast_t *b, uint16_t lines, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_aspect
-  ( epg_broadcast_t *b, uint16_t aspect, struct epggrab_module *src )
+  ( epg_broadcast_t *b, uint16_t aspect, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_deafsigned
-  ( epg_broadcast_t *b, uint8_t ds, struct epggrab_module *src )
+  ( epg_broadcast_t *b, uint8_t ds, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_subtitled
-  ( epg_broadcast_t *b, uint8_t st, struct epggrab_module *src )
+  ( epg_broadcast_t *b, uint8_t st, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_audio_desc
-  ( epg_broadcast_t *b, uint8_t ad, struct epggrab_module *src )
+  ( epg_broadcast_t *b, uint8_t ad, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_new
-  ( epg_broadcast_t *b, uint8_t n, struct epggrab_module *src )
+  ( epg_broadcast_t *b, uint8_t n, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_repeat
-  ( epg_broadcast_t *b, uint8_t r, struct epggrab_module *src )
+  ( epg_broadcast_t *b, uint8_t r, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_summary
-  ( epg_broadcast_t *b, const char *str, const char *lang, 
-    struct epggrab_module *src )
+  ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_description
-  ( epg_broadcast_t *b, const char *str, const char *lang, 
-    struct epggrab_module *src )
-  __attribute__((warn_unused_result));
-int epg_broadcast_set_summary2
-  ( epg_broadcast_t *b, const lang_str_t *str, struct epggrab_module *src )
-  __attribute__((warn_unused_result));
-int epg_broadcast_set_description2
-  ( epg_broadcast_t *b, const lang_str_t *str, struct epggrab_module *src )
+  ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_serieslink
-  ( epg_broadcast_t *b, epg_serieslink_t *sl, struct epggrab_module *src )
+  ( epg_broadcast_t *b, epg_serieslink_t *sl, uint32_t *changed )
   __attribute__((warn_unused_result));
 
 /* Accessors */
 epg_broadcast_t *epg_broadcast_get_next    ( epg_broadcast_t *b );
-epg_episode_t   *epg_broadcast_get_episode 
-  ( epg_broadcast_t *b, int create, int *save );
 const char *epg_broadcast_get_title 
   ( epg_broadcast_t *b, const char *lang );
 const char *epg_broadcast_get_subtitle
index 09e35e628f37fea56a62c63c9170076c4b3a4048..24e257535e06e9ec0b68ca4fc1c3cf4d5898af77 100644 (file)
@@ -415,10 +415,11 @@ static int _eit_process_event_one
   uint16_t eid;
   uint8_t dtag, dlen, running;
   epg_broadcast_t *ebc;
-  epg_episode_t *ee;
+  epg_episode_t *ee = NULL;
   epg_serieslink_t *es;
   epg_running_t run;
   eit_event_t ev;
+  uint32_t changes2 = 0, changes3 = 0, changes4 = 0;
 
   /* Core fields */
   eid   = ptr[0] << 8 | ptr[1];
@@ -434,7 +435,7 @@ static int _eit_process_event_one
   if ( len < dllen ) return -1;
 
   /* Find broadcast */
-  ebc  = epg_broadcast_find_by_time(ch, mod, start, stop, eid, 1, &save2);
+  ebc  = epg_broadcast_find_by_time(ch, mod, start, stop, 1, &save2, &changes2);
   tvhtrace("eit", "svc='%s', ch='%s', eid=%5d, start=%"PRItime_t","
                   " stop=%"PRItime_t", ebc=%p",
            svc->s_dvb_svcname ?: "(null)", ch ? channel_get_name(ch) : "(null)",
@@ -495,26 +496,30 @@ static int _eit_process_event_one
    * Broadcast
    */
 
+  *save |= epg_broadcast_set_dvb_eid(ebc, eid, &changes2);
+
   /* Summary/Description */
-  if ( ev.summary )
-    *save |= epg_broadcast_set_summary2(ebc, ev.summary, mod);
-  if ( ev.desc )
-    *save |= epg_broadcast_set_description2(ebc, ev.desc, mod);
+  if (ev.summary)
+    *save |= epg_broadcast_set_summary(ebc, ev.summary, &changes2);
+  if (ev.desc)
+    *save |= epg_broadcast_set_description(ebc, ev.desc, &changes2);
 
   /* Broadcast Metadata */
-  *save |= epg_broadcast_set_is_hd(ebc, ev.hd, mod);
-  *save |= epg_broadcast_set_is_widescreen(ebc, ev.ws, mod);
-  *save |= epg_broadcast_set_is_audio_desc(ebc, ev.ad, mod);
-  *save |= epg_broadcast_set_is_subtitled(ebc, ev.st, mod);
-  *save |= epg_broadcast_set_is_deafsigned(ebc, ev.ds, mod);
+  *save |= epg_broadcast_set_is_hd(ebc, ev.hd, &changes2);
+  *save |= epg_broadcast_set_is_widescreen(ebc, ev.ws, &changes2);
+  *save |= epg_broadcast_set_is_audio_desc(ebc, ev.ad, &changes2);
+  *save |= epg_broadcast_set_is_subtitled(ebc, ev.st, &changes2);
+  *save |= epg_broadcast_set_is_deafsigned(ebc, ev.ds, &changes2);
 
   /*
    * Series link
    */
 
   if (*ev.suri) {
-    if ((es = epg_serieslink_find_by_uri(ev.suri, 1, save)))
-      *save |= epg_broadcast_set_serieslink(ebc, es, mod);
+    if ((es = epg_serieslink_find_by_uri(ev.suri, mod, 1, save, &changes3))) {
+      *save |= epg_broadcast_set_serieslink(ebc, es, &changes2);
+      *save |= epg_serieslink_change_finish(es, changes3, 0);
+    }
   }
 
   /*
@@ -523,30 +528,32 @@ static int _eit_process_event_one
 
   /* Find episode */
   if (*ev.uri) {
-    if ((ee = epg_episode_find_by_uri(ev.uri, 1, save)))
-      *save |= epg_broadcast_set_episode(ebc, ee, mod);
-
-  /* Existing/Artificial */
-  } else
-    ee = epg_broadcast_get_episode(ebc, 1, save);
+    ee = epg_episode_find_by_uri(ev.uri, mod, 1, save, &changes4);
+  } else {
+    ee = epg_episode_find_by_broadcast(ebc, mod, 1, save, &changes4);
+  }
 
   /* Update Episode */
   if (ee) {
-    *save |= epg_episode_set_is_bw(ee, ev.bw, mod);
-    if ( ev.title )
-      *save |= epg_episode_set_title2(ee, ev.title, mod);
-    if ( ev.genre )
-      *save |= epg_episode_set_genre(ee, ev.genre, mod);
-    if ( ev.parental )
-      *save |= epg_episode_set_age_rating(ee, ev.parental, mod);
-    if ( ev.summary )
-      *save |= epg_episode_set_subtitle2(ee, ev.summary, mod);
+    *save |= epg_broadcast_set_episode(ebc, ee, &changes2);
+    *save |= epg_episode_set_is_bw(ee, ev.bw, &changes4);
+    if (ev.title)
+      *save |= epg_episode_set_title(ee, ev.title, &changes4);
+    if (ev.genre)
+      *save |= epg_episode_set_genre(ee, ev.genre, &changes4);
+    if (ev.parental)
+      *save |= epg_episode_set_age_rating(ee, ev.parental, &changes4);
+    if (ev.summary)
+      *save |= epg_episode_set_subtitle(ee, ev.summary, &changes4);
 #if TODO_ADD_EXTRA
-    if ( ev.extra )
-      *save |= epg_episode_set_extra(ee, extra, mod);
+    if (ev.extra)
+      *save |= epg_episode_set_extra(ee, extra, &changes4);
 #endif
+    *save |= epg_episode_change_finish(ee, changes4, 0);
   }
 
+  *save |= epg_broadcast_change_finish(ebc, changes2, 0);
+
   /* Tidy up */
 #if TODO_ADD_EXTRA
   if (ev.extra)   htsmsg_destroy(ev.extra);
index 4013aa363c7ce278a15a818f0ec1ab3585e71ce3..e7129a9a675827b8727a1e48bd40290ee50ab6e7 100644 (file)
@@ -310,7 +310,7 @@ opentv_parse_event_section_one
     channel_t *ch, const char *lang,
     const uint8_t *buf, int len )
 {
-  int i, r, save = 0;
+  int i, r, save = 0, merge;
   opentv_module_t  *mod = sta->os_mod;
   epggrab_module_t *src = (epggrab_module_t*)mod;
   epg_broadcast_t *ebc;
@@ -318,6 +318,8 @@ opentv_parse_event_section_one
   epg_serieslink_t *es;
   opentv_event_t ev;
   char buffer[2048];
+  lang_str_t *ls;
+  uint32_t changes, changes2, changes3;
 
   /* Loop around event entries */
   i = 7;
@@ -331,16 +333,22 @@ opentv_parse_event_section_one
      * Broadcast
      */
 
+    merge = changes = changes2 = changes3 = 0;
+
     /* Find broadcast */
     if (ev.start && ev.stop) {
       ebc = epg_broadcast_find_by_time(ch, src, ev.start, ev.stop,
-                                       ev.eid, 1, &save);
+                                       1, &save, &changes);
       tvhdebug("opentv", "find by time start %"PRItime_t " stop "
                "%"PRItime_t " eid %d = %p",
                ev.start, ev.stop, ev.eid, ebc);
+      save |= epg_broadcast_set_dvb_eid(ebc, ev.eid, &changes);
     } else {
       ebc = epg_broadcast_find_by_eid(ch, ev.eid);
       tvhdebug("opentv", "find by eid %d = %p", ev.eid, ebc);
+      if (ebc && ebc->grabber != src)
+        goto done;
+      merge = 1;
     }
     if (!ebc)
       goto done;
@@ -348,11 +356,15 @@ opentv_parse_event_section_one
     /* Summary / Description */
     if (ev.summary) {
       tvhdebug("opentv", "  summary '%s'", ev.summary);
-      save |= epg_broadcast_set_summary(ebc, ev.summary, lang, src);
+      ls = lang_str_create2(ev.summary, lang);
+      save |= epg_broadcast_set_summary(ebc, ls, &changes);
+      lang_str_destroy(ls);
     }
     if (ev.desc) {
       tvhdebug("opentv", "  desc '%s'", ev.desc);
-      save |= epg_broadcast_set_description(ebc, ev.desc, lang, src);
+      ls = lang_str_create2(ev.desc, lang);
+      save |= epg_broadcast_set_description(ebc, ls, &changes);
+      lang_str_destroy(ls);
     }
 
     /*
@@ -363,15 +375,18 @@ opentv_parse_event_section_one
       char suri[257], ubuf[UUID_HEX_SIZE];
       snprintf(suri, 256, "opentv://channel-%s/series-%d",
                channel_get_uuid(ch, ubuf), ev.serieslink);
-      if ((es = epg_serieslink_find_by_uri(suri, 1, &save)))
-        save |= epg_broadcast_set_serieslink(ebc, es, src);
+      if ((es = epg_serieslink_find_by_uri(suri, src, 1, &save, &changes2))) {
+        save |= epg_broadcast_set_serieslink(ebc, es, &changes);
+        save |= epg_serieslink_change_finish(es, changes2, merge);
+      }
     }
 
     /*
      * Episode
      */
 
-    if ((ee = epg_broadcast_get_episode(ebc, 1, &save))) {
+    if ((ee = epg_episode_find_by_broadcast(ebc, src, 1, &save, &changes3))) {
+      save |= epg_broadcast_set_episode(ebc, ee, &changes);
       tvhdebug("opentv", "  find episode %p", ee);
       if (ev.title) {
         tvhdebug("opentv", "    title '%s'", ev.title);
@@ -379,14 +394,19 @@ opentv_parse_event_section_one
         /* try to cleanup the title */
         if (_opentv_apply_pattern_list(buffer, sizeof(buffer), ev.title, &mod->p_cleanup_title)) {
           tvhtrace("opentv", "  clean title '%s'", buffer);
-          save |= epg_episode_set_title(ee, buffer, lang, src);
-        } else
-          save |= epg_episode_set_title(ee, ev.title, lang, src);
+          ls = lang_str_create2(buffer, lang);
+          save |= epg_episode_set_title(ee, ls, &changes3);
+          lang_str_destroy(ls);
+        } else {
+          ls = lang_str_create2(buffer, lang);
+          save |= epg_episode_set_title(ee, ls, &changes3);
+          lang_str_destroy(ls);
+        }
       }
       if (ev.cat) {
         epg_genre_list_t *egl = calloc(1, sizeof(epg_genre_list_t));
         epg_genre_list_add_by_eit(egl, ev.cat);
-        save |= epg_episode_set_genre(ee, egl, src);
+        save |= epg_episode_set_genre(ee, egl, &changes3);
         epg_genre_list_destroy(egl);
       }
       if (ev.summary) {
@@ -413,16 +433,21 @@ opentv_parse_event_section_one
         }
         /* save any found number */
         if (en.s_num || en.e_num || en.p_num)
-          save |= epg_episode_set_epnum(ee, &en, src);
+          save |= epg_episode_set_epnum(ee, &en, &changes3);
 
         /* ...for subtitle */
         if (_opentv_apply_pattern_list(buffer, sizeof(buffer), ev.summary, &mod->p_subt)) {
           tvhtrace("opentv", "  extract subtitle '%s'", buffer);
-          save |= epg_episode_set_subtitle(ee, buffer, lang, src);
+          ls = lang_str_create2(buffer, lang);
+          save |= epg_episode_set_subtitle(ee, ls, &changes3);
+          lang_str_destroy(ls);
         }
       }
+      save |= epg_broadcast_change_finish(ebc, changes, merge);
     }
 
+    save |= epg_broadcast_change_finish(ebc, changes, merge);
+
     /* Cleanup */
 done:
     if (ev.title)   free(ev.title);
index ef73c6d76e880a7dd197fb02d7efbffb9fe23d14..60276af89bef1b1ae744af569671c0326baa5a6c 100644 (file)
@@ -341,13 +341,14 @@ _psip_eit_callback_channel
   uint16_t eventid;
   uint32_t starttime, length;
   time_t start, stop;
-  int save = 0, save2, i, size;
+  int save = 0, save2, save3, i, size;
   uint8_t titlelen;
   unsigned int dlen;
   epg_broadcast_t *ebc;
   epg_episode_t *ee;
   lang_str_t *title, *description;
   psip_desc_t *pd;
+  uint32_t changes2, changes3;
   epggrab_module_t *mod = (epggrab_module_t *)ps->ps_mod;
 
   for (i = 0; len >= 12 && i < count; len -= size, ptr += size, i++) {
@@ -384,23 +385,34 @@ _psip_eit_callback_channel
              i, ch ? channel_get_name(ch) : "(null)", eventid, start, length,
              lang_str_get(title, NULL), titlelen);
 
-    ebc = epg_broadcast_find_by_time(ch, mod, start, stop, eventid, 1, &save2);
+    save2 = save3 = changes2 = changes3 = 0;
+
+    ebc = epg_broadcast_find_by_time(ch, mod, start, stop, 1, &save2, &changes2);
     tvhtrace("psip", "  eid=%5d, start=%"PRItime_t", stop=%"PRItime_t", ebc=%p",
              eventid, start, stop, ebc);
     if (!ebc) goto next;
-    save |= save2;
+
+    save2 |= epg_broadcast_set_dvb_eid(ebc, eventid, &changes2);
 
     pd = psip_find_desc(ps, eventid);
     if (pd) {
       description = atsc_get_string(pd->pd_data, pd->pd_datalen);
       if (description) {
-        save |= epg_broadcast_set_description2(ebc, description, mod);
+        save2 |= epg_broadcast_set_description(ebc, description, &changes2);
         lang_str_destroy(description);
       }
     }
 
-    ee = epg_broadcast_get_episode(ebc, 1, &save2);
-    save |= epg_episode_set_title2(ee, title, mod);
+    ee = epg_episode_find_by_broadcast(ebc, mod, 1, &save3, &changes3);
+    if (ee) {
+      save2 |= epg_broadcast_set_episode(ebc, ee, &changes2);
+      save3 |= epg_episode_set_title(ee, title, &changes3);
+      save3 |= epg_episode_change_finish(ee, changes3, 0);
+    }
+
+    save |= epg_broadcast_change_finish(ebc, changes2, 0);
+
+    save |= save2 | save3;
 
 next:
     lang_str_destroy(title);
@@ -538,6 +550,7 @@ _psip_ett_callback
   lang_str_t *description;
   idnode_list_mapping_t *ilm;
   channel_t            *ch;
+  uint32_t              changes;
 
   /* Validate */
   if (tableid != 0xcc) return -1;
@@ -585,9 +598,11 @@ _psip_ett_callback
     LIST_FOREACH(ilm, &svc->s_channels, ilm_in1_link) {
       ch = (channel_t *)ilm->ilm_in2;
       epg_broadcast_t *ebc;
+      changes = 0;
       ebc = epg_broadcast_find_by_eid(ch, eventid);
-      if (ebc) {
-        save |= epg_broadcast_set_description2(ebc, description, mod);
+      if (ebc && ebc->grabber == mod) {
+        save |= epg_broadcast_set_description(ebc, description, &changes);
+        save |= epg_broadcast_change_finish(ebc, changes, 1);
         tvhtrace("psip", "0x%04x: ETT tableid 0x%04X [%s], eventid 0x%04X (%d) ['%s'], ver %d",
                  mt->mt_pid, tsid, svc->s_dvb_svcname, eventid, eventid,
                  lang_str_get(ebc->episode->title, "eng"), ver);
index 3f3ad0686d1584954f84cb8a7eec7e7cf9c75960..54cc37f4f2c73440801b7b7809918c3f210b7e8d 100644 (file)
@@ -100,7 +100,8 @@ static int _pyepg_parse_brand
   htsmsg_t *attr, *tags;
   epg_brand_t *brand;
   const char *str;
-  uint32_t u32;
+  lang_str_t *ls;
+  uint32_t u32, changes = 0;
 
   if ( data == NULL ) return 0;
 
@@ -109,32 +110,42 @@ static int _pyepg_parse_brand
   if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0;
   
   /* Find brand */
-  if ((brand = epg_brand_find_by_uri(str, 1, &save)) == NULL) return 0;
+  if ((brand = epg_brand_find_by_uri(str, mod, 1, &save, &changes)) == NULL)
+    return 0;
   stats->brands.total++;
   if (save) stats->brands.created++;
 
   /* Set title */
   if ((str = htsmsg_xml_get_cdata_str(tags, "title"))) {
-    save |= epg_brand_set_title(brand, str, NULL, mod);
+    ls = lang_str_create2(str, NULL);
+    save |= epg_brand_set_title(brand, ls, &changes);
+    lang_str_destroy(ls);
   }
 
   /* Set summary */
   if ((str = htsmsg_xml_get_cdata_str(tags, "summary"))) {
-    save |= epg_brand_set_summary(brand, str, NULL, mod);
+    ls = lang_str_create2(str, NULL);
+    save |= epg_brand_set_summary(brand, ls, &changes);
+    lang_str_destroy(ls);
   }
   
   /* Set image */
   if ((str = htsmsg_xml_get_cdata_str(tags, "image"))) {
-    save |= epg_brand_set_image(brand, str, mod);
+    ls = lang_str_create2(str, NULL);
+    save |= epg_brand_set_image(brand, str, &changes);
+    lang_str_destroy(ls);
   } else if ((str = htsmsg_xml_get_cdata_str(tags, "thumb"))) {
-    save |= epg_brand_set_image(brand, str, mod);
+    ls = lang_str_create2(str, NULL);
+    save |= epg_brand_set_image(brand, str, &changes);
+    lang_str_destroy(ls);
   }
 
   /* Set season count */
   if (htsmsg_xml_get_cdata_u32(tags, "series-count", &u32) == 0) {
-    save |= epg_brand_set_season_count(brand, u32, mod);
+    save |= epg_brand_set_season_count(brand, u32, &changes);
   }
 
+  save |= epg_brand_change_finish(brand, changes, 0);
   if (save) stats->brands.modified++;
 
   return save;
@@ -148,7 +159,8 @@ static int _pyepg_parse_season
   epg_season_t *season;
   epg_brand_t *brand;
   const char *str;
-  uint32_t u32;
+  lang_str_t *ls;
+  uint32_t u32, changes = 0;
 
   if ( data == NULL ) return 0;
 
@@ -157,40 +169,43 @@ static int _pyepg_parse_season
   if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0;
 
   /* Find series */
-  if ((season = epg_season_find_by_uri(str, 1, &save)) == NULL) return 0;
+  if ((season = epg_season_find_by_uri(str, mod, 1, &save, &changes)) == NULL)
+    return 0;
   stats->seasons.total++;
   if (save) stats->seasons.created++;
   
   /* Set brand */
   if ((str = htsmsg_get_str(attr, "brand"))) {
-    if ((brand = epg_brand_find_by_uri(str, 0, NULL))) {
-      save |= epg_season_set_brand(season, brand, mod);
+    if ((brand = epg_brand_find_by_uri(str, mod, 0, NULL, NULL))) {
+      save |= epg_season_set_brand(season, brand, &changes);
     }
   }
 
   /* Set summary */
   if ((str = htsmsg_xml_get_cdata_str(tags, "summary"))) {
-    save |= epg_season_set_summary(season, str, NULL, mod);
+    ls = lang_str_create2(str, NULL);
+    save |= epg_season_set_summary(season, ls, &changes);
+    lang_str_destroy(ls);
   }
   
   /* Set image */
-  if ((str = htsmsg_xml_get_cdata_str(tags, "image"))) {
-    save |= epg_season_set_image(season, str, mod);
-  } else if ((str = htsmsg_xml_get_cdata_str(tags, "thumb"))) {
-    save |= epg_season_set_image(season, str, mod);
+  if ((str = htsmsg_xml_get_cdata_str(tags, "image")) ||
+      (str = htsmsg_xml_get_cdata_str(tags, "thumb"))) {
+    save |= epg_season_set_image(season, str, &changes);
   }
 
   /* Set season number */
   if (htsmsg_xml_get_cdata_u32(tags, "number", &u32) == 0) {
-    save |= epg_season_set_number(season, u32, mod);
+    save |= epg_season_set_number(season, u32, &changes);
   }
 
   /* Set episode count */
   if (htsmsg_xml_get_cdata_u32(tags, "episode-count", &u32) == 0) {
-    save |= epg_season_set_episode_count(season, u32, mod);
+    save |= epg_season_set_episode_count(season, u32, &changes);
   }
 
-  if(save) stats->seasons.modified++;
+  save |= epg_season_change_finish(season, changes, 0);
+  if (save) stats->seasons.modified++;
 
   return save;
 }
@@ -204,7 +219,8 @@ static int _pyepg_parse_episode
   epg_season_t *season;
   epg_brand_t *brand;
   const char *str;
-  uint32_t u32, pc, pn;
+  lang_str_t *ls;
+  uint32_t u32, pc, pn, changes = 0;
   epg_genre_list_t *egl;
 
   if ( data == NULL ) return 0;
@@ -214,64 +230,70 @@ static int _pyepg_parse_episode
   if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0;
 
   /* Find episode */
-  if ((episode = epg_episode_find_by_uri(str, 1, &save)) == NULL) return 0;
+  if ((episode = epg_episode_find_by_uri(str, mod, 1, &save, &changes)) == NULL) return 0;
   stats->episodes.total++;
   if (save) stats->episodes.created++;
   
   /* Set season */
   if ((str = htsmsg_get_str(attr, "series"))) {
-    if ((season = epg_season_find_by_uri(str, 0, NULL))) {
-      save |= epg_episode_set_season(episode, season, mod);
+    if ((season = epg_season_find_by_uri(str, mod, 0, NULL, NULL))) {
+      save |= epg_episode_set_season(episode, season, &changes);
     }
   }
 
   /* Set brand */
   if ((str = htsmsg_get_str(attr, "brand"))) {
-    if ((brand = epg_brand_find_by_uri(str, 0, NULL))) {
-      save |= epg_episode_set_brand(episode, brand, mod);
+    if ((brand = epg_brand_find_by_uri(str, mod, 0, NULL, NULL))) {
+      save |= epg_episode_set_brand(episode, brand, &changes);
     }
   }
 
   /* Set title/subtitle */
   if ((str = htsmsg_xml_get_cdata_str(tags, "title"))) {
-    save |= epg_episode_set_title(episode, str, NULL, mod);
+    ls = lang_str_create2(str, NULL);
+    save |= epg_episode_set_title(episode, ls, &changes);
+    lang_str_destroy(ls);
   } 
   if ((str = htsmsg_xml_get_cdata_str(tags, "subtitle"))) {
-    save |= epg_episode_set_subtitle(episode, str, NULL, mod);
+    ls = lang_str_create2(str, NULL);
+    save |= epg_episode_set_subtitle(episode, ls, &changes);
+    lang_str_destroy(ls);
   } 
 
   /* Set summary */
   if ((str = htsmsg_xml_get_cdata_str(tags, "summary"))) {
-    save |= epg_episode_set_summary(episode, str, NULL, mod);
+    ls = lang_str_create2(str, NULL);
+    save |= epg_episode_set_summary(episode, ls, &changes);
+    lang_str_destroy(ls);
   }
 
   /* Number */
   if (htsmsg_xml_get_cdata_u32(tags, "number", &u32) == 0) {
-    save |= epg_episode_set_number(episode, u32, mod);
+    save |= epg_episode_set_number(episode, u32, &changes);
   }
   if (!htsmsg_xml_get_cdata_u32(tags, "part-number", &pn)) {
     pc = 0;
     htsmsg_xml_get_cdata_u32(tags, "part-count", &pc);
-    save |= epg_episode_set_part(episode, pn, pc, mod);
+    save |= epg_episode_set_part(episode, pn, pc, &changes);
   }
 
   /* Set image */
-  if ((str = htsmsg_xml_get_cdata_str(tags, "image"))) {
-    save |= epg_episode_set_image(episode, str, mod);
-  } else if ((str = htsmsg_xml_get_cdata_str(tags, "thumb"))) {
-    save |= epg_episode_set_image(episode, str, mod);
+  if ((str = htsmsg_xml_get_cdata_str(tags, "image")) ||
+      (str = htsmsg_xml_get_cdata_str(tags, "thumb"))) {
+    save |= epg_episode_set_image(episode, str, &changes);
   }
 
   /* Genre */
   if ((egl = _pyepg_parse_genre(tags))) {
-    save |= epg_episode_set_genre(episode, egl, mod);
+    save |= epg_episode_set_genre(episode, egl, &changes);
     epg_genre_list_destroy(egl);
   }
 
   /* Content */
   if ((htsmsg_get_map(tags, "blackandwhite")))
-    save |= epg_episode_set_is_bw(episode, 1, mod);
+    save |= epg_episode_set_is_bw(episode, 1, &changes);
 
+  save |= epg_episode_change_finish(episode, changes, 0);
   if (save) stats->episodes.modified++;
 
   return save;
@@ -287,7 +309,7 @@ static int _pyepg_parse_broadcast
   epg_broadcast_t *broadcast;
   const char *id, *start, *stop;
   time_t tm_start, tm_stop;
-  uint32_t u32;
+  uint32_t u32, changes = 0;
 
   if ( data == NULL || channel == NULL ) return 0;
 
@@ -302,33 +324,34 @@ static int _pyepg_parse_broadcast
   if (!_pyepg_parse_time(stop, &tm_stop)) return 0;
 
   /* Find broadcast */
-  broadcast 
-    = epg_broadcast_find_by_time(channel, mod, tm_start, tm_stop, 0, 1, &save);
+  broadcast = epg_broadcast_find_by_time(channel, mod, tm_start, tm_stop,
+                                         1, &save, &changes);
   if ( broadcast == NULL ) return 0;
   stats->broadcasts.total++;
   if ( save ) stats->broadcasts.created++;
 
   /* Quality */
   u32 = htsmsg_get_map(tags, "hd") ? 1 : 0;
-  save |= epg_broadcast_set_is_hd(broadcast, u32, mod);
+  save |= epg_broadcast_set_is_hd(broadcast, u32, &changes);
   u32 = htsmsg_get_map(tags, "widescreen") ? 1 : 0;
-  save |= epg_broadcast_set_is_widescreen(broadcast, u32, mod);
+  save |= epg_broadcast_set_is_widescreen(broadcast, u32, &changes);
   // TODO: lines, aspect
 
   /* Accessibility */
   // Note: reuse XMLTV parse code as this is the same
-  xmltv_parse_accessibility(mod, broadcast, tags);
+  xmltv_parse_accessibility(broadcast, tags, &changes);
 
   /* New/Repeat */
   u32 = htsmsg_get_map(tags, "new") || htsmsg_get_map(tags, "premiere");
-  save |= epg_broadcast_set_is_new(broadcast, u32, mod);
+  save |= epg_broadcast_set_is_new(broadcast, u32, &changes);
   u32 = htsmsg_get_map(tags, "repeat") ? 1 : 0;
-  save |= epg_broadcast_set_is_repeat(broadcast, u32, mod);
+  save |= epg_broadcast_set_is_repeat(broadcast, u32, &changes);
 
   /* Set episode */
-  if ((episode = epg_episode_find_by_uri(id, 1, &save)) == NULL) return 0;
-  save |= epg_broadcast_set_episode(broadcast, episode, mod);
+  if ((episode = epg_episode_find_by_uri(id, mod, 1, &save, &changes)) == NULL) return 0;
+  save |= epg_broadcast_set_episode(broadcast, episode, &changes);
 
+  save |= epg_broadcast_change_finish(broadcast, changes, 0);
   if (save) stats->broadcasts.modified++;  
 
   return save;
index 98fa37c5532b658bffdbe23b59f0ccb24d3b8775..d1da228d436bcbcb309e29577b44a063480f3bf2 100644 (file)
@@ -217,8 +217,9 @@ static void parse_xmltv_dd_progid
  *
  */
 static void get_episode_info
-  (epggrab_module_t *mod,
-   htsmsg_t *tags, char **uri, char **suri, epg_episode_num_t *epnum )
+  ( epggrab_module_t *mod,
+    htsmsg_t *tags, char **uri, char **suri,
+    epg_episode_num_t *epnum )
 {
   htsmsg_field_t *f;
   htsmsg_t *c, *a;
@@ -249,7 +250,7 @@ static void get_episode_info
  */
 static int
 xmltv_parse_vid_quality
-  ( epggrab_module_t *mod, epg_broadcast_t *ebc, htsmsg_t *m, int8_t *bw )
+  ( epg_broadcast_t *ebc, htsmsg_t *m, int8_t *bw, uint32_t *changes )
 {
   int save = 0;
   int hd = 0, lines = 0, aspect = 0;
@@ -293,13 +294,13 @@ xmltv_parse_vid_quality
       aspect = (100 * w) / h;
     }
   }
-  save |= epg_broadcast_set_is_hd(ebc, hd, mod);
+  save |= epg_broadcast_set_is_hd(ebc, hd, changes);
   if (aspect) {
-    save |= epg_broadcast_set_is_widescreen(ebc, hd || aspect > 137, mod);
-    save |= epg_broadcast_set_aspect(ebc, aspect, mod);
+    save |= epg_broadcast_set_is_widescreen(ebc, hd || aspect > 137, changes);
+    save |= epg_broadcast_set_aspect(ebc, aspect, changes);
   }
   if (lines)
-    save |= epg_broadcast_set_lines(ebc, lines, mod);
+    save |= epg_broadcast_set_lines(ebc, lines, changes);
   
   return save;
 }
@@ -309,7 +310,7 @@ xmltv_parse_vid_quality
  */
 int
 xmltv_parse_accessibility 
-  ( epggrab_module_t *mod, epg_broadcast_t *ebc, htsmsg_t *m )
+  ( epg_broadcast_t *ebc, htsmsg_t *m, uint32_t *changes )
 {
   int save = 0;
   htsmsg_t *tag;
@@ -321,12 +322,12 @@ xmltv_parse_accessibility
       if ((tag = htsmsg_get_map_by_field(f))) {
         str = htsmsg_xml_get_attr_str(tag, "type");
         if (str && !strcmp(str, "teletext"))
-          save |= epg_broadcast_set_is_subtitled(ebc, 1, mod);
+          save |= epg_broadcast_set_is_subtitled(ebc, 1, changes);
         else if (str && !strcmp(str, "deaf-signed"))
-          save |= epg_broadcast_set_is_deafsigned(ebc, 1, mod);
+          save |= epg_broadcast_set_is_deafsigned(ebc, 1, changes);
       }
     } else if (!strcmp(f->hmf_name, "audio-described")) {
-      save |= epg_broadcast_set_is_audio_desc(ebc, 1, mod);
+      save |= epg_broadcast_set_is_audio_desc(ebc, 1, changes);
     }
   }
   return save;
@@ -336,13 +337,13 @@ xmltv_parse_accessibility
  * Previously shown
  */
 static int _xmltv_parse_previously_shown
-  ( epggrab_module_t *mod, epg_broadcast_t *ebc, htsmsg_t *tag,
-    time_t *first_aired )
+  ( epg_broadcast_t *ebc, time_t *first_aired,
+    htsmsg_t *tag, uint32_t *changes )
 {
   int ret;
   const char *start;
-  if (!mod || !ebc || !tag) return 0;
-  ret = epg_broadcast_set_is_repeat(ebc, 1, mod);
+  if (!ebc || !tag) return 0;
+  ret = epg_broadcast_set_is_repeat(ebc, 1, changes);
   if ((start = htsmsg_xml_get_attr_str(tag, "start")))
     *first_aired = _xmltv_str2time(start);
   return ret;
@@ -355,14 +356,14 @@ static int _xmltv_parse_previously_shown
  *   </star-rating>
  */
 static int _xmltv_parse_star_rating
-  ( epggrab_module_t *mod, epg_episode_t *ee, htsmsg_t *body )
+  ( epg_episode_t *ee, htsmsg_t *body, uint32_t *changes )
 {
   double a, b;
   htsmsg_t *stars, *tags;
   const char *s1, *s2;
   char *s1end, *s2end;
 
-  if (!mod || !ee || !body) return 0;
+  if (!ee || !body) return 0;
   if (!(stars = htsmsg_get_map(body, "star-rating"))) return 0;
   if (!(tags  = htsmsg_get_map(stars, "tags"))) return 0;
   if (!(s1 = htsmsg_xml_get_cdata_str(tags, "value"))) return 0;
@@ -372,7 +373,7 @@ static int _xmltv_parse_star_rating
   b = strtod(s2 + 1, &s2end);
   if ( a == 0.0f || b == 0.0f) return 0;
 
-  return epg_episode_set_star_rating(ee, (100 * a) / b, mod);
+  return epg_episode_set_star_rating(ee, (100 * a) / b, changes);
 }
 
 /*
@@ -397,20 +398,20 @@ static int _xmltv_parse_star_rating
  * [rating system=advisory] values "strong sexual content","Language", etc
  */
 static int _xmltv_parse_age_rating
-  ( epggrab_module_t *mod, epg_episode_t *ee, htsmsg_t *body )
+  ( epg_episode_t *ee, htsmsg_t *body, uint32_t *changes )
 {
   uint8_t age;
   htsmsg_t *rating, *tags;
   const char *s1;
 
-  if (!mod || !ee || !body) return 0;
+  if (!ee || !body) return 0;
   if (!(rating = htsmsg_get_map(body, "rating"))) return 0;
   if (!(tags  = htsmsg_get_map(rating, "tags"))) return 0;
   if (!(s1 = htsmsg_xml_get_cdata_str(tags, "value"))) return 0;
 
   age = atoi(s1);
 
-  return epg_episode_set_age_rating(ee, age, mod);
+  return epg_episode_set_age_rating(ee, age, changes);
 }
 
 /*
@@ -473,11 +474,12 @@ static int _xmltv_parse_programme_tags
   lang_str_t *subtitle = NULL;
   time_t first_aired = 0;
   int8_t bw = -1;
+  uint32_t changes = 0, changes2 = 0, changes3 = 0;
 
   /*
    * Broadcast
    */
-  if (!(ebc = epg_broadcast_find_by_time(ch, mod, start, stop, 0, 1, &save)))
+  if (!(ebc = epg_broadcast_find_by_time(ch, mod, start, stop, 1, &save, &changes)))
     return 0;
   stats->broadcasts.total++;
   if (save) stats->broadcasts.created++;
@@ -485,21 +487,21 @@ static int _xmltv_parse_programme_tags
   /* Description (wait for episode first) */
   _xmltv_parse_lang_str(&desc, tags, "desc");
   if (desc)
-    save3 |= epg_broadcast_set_description2(ebc, desc, mod);
+    save3 |= epg_broadcast_set_description(ebc, desc, &changes);
 
   /* Quality metadata */
-  save |= xmltv_parse_vid_quality(mod, ebc, htsmsg_get_map(tags, "video"), &bw);
+  save |= xmltv_parse_vid_quality(ebc, htsmsg_get_map(tags, "video"), &bw, &changes);
 
   /* Accessibility */
-  save |= xmltv_parse_accessibility(mod, ebc, tags);
+  save |= xmltv_parse_accessibility(ebc, tags, &changes);
 
   /* Misc */
-  save |= _xmltv_parse_previously_shown(mod, ebc,
+  save |= _xmltv_parse_previously_shown(ebc, &first_aired,
                                         htsmsg_get_map(tags, "previously-shown"),
-                                        &first_aired);
+                                        &changes);
   if (htsmsg_get_map(tags, "premiere") ||
       htsmsg_get_map(tags, "new"))
-    save |= epg_broadcast_set_is_new(ebc, 1, mod);
+    save |= epg_broadcast_set_is_new(ebc, 1, &changes);
 
   /*
    * Episode/Series info
@@ -510,25 +512,24 @@ static int _xmltv_parse_programme_tags
    * Series Link
    */
   if (suri) {
-    es = epg_serieslink_find_by_uri(suri, 1, &save2);
+    if ((es = epg_serieslink_find_by_uri(suri, mod, 1, &save2, &changes2))) {
+      save |= epg_broadcast_set_serieslink(ebc, es, &changes);
+      save |= epg_serieslink_change_finish(es, changes2, 0);
+    }
     free(suri);
     if (es) stats->seasons.total++;
     if (save2) stats->seasons.created++;
-
-    if (es)
-      save |= epg_broadcast_set_serieslink(ebc, es, mod);
   }
 
   /*
    * Episode
    */
   if (uri) {
-    if ((ee = epg_episode_find_by_uri(uri, 1, &save3)))
-      save |= epg_broadcast_set_episode(ebc, ee, mod);
-    free(uri);
+    ee = epg_episode_find_by_uri(uri, mod, 1, &save3, &changes3);
   } else {
-    ee = epg_broadcast_get_episode(ebc, 1, &save3);
+    ee = epg_episode_find_by_broadcast(ebc, mod, 1, &save3, &changes3);
   }
+  save |= epg_broadcast_set_episode(ebc, ee, &changes);
   if (ee)    stats->episodes.total++;
   if (save3) stats->episodes.created++;
 
@@ -537,28 +538,34 @@ static int _xmltv_parse_programme_tags
     _xmltv_parse_lang_str(&subtitle, tags, "sub-title");
 
     if (title) 
-      save3 |= epg_episode_set_title2(ee, title, mod);
+      save3 |= epg_episode_set_title(ee, title, &changes3);
     if (subtitle)
-      save3 |= epg_episode_set_subtitle2(ee, subtitle, mod);
+      save3 |= epg_episode_set_subtitle(ee, subtitle, &changes3);
 
     if ((egl = _xmltv_parse_categories(tags))) {
-      save3 |= epg_episode_set_genre(ee, egl, mod);
+      save3 |= epg_episode_set_genre(ee, egl, &changes3);
       epg_genre_list_destroy(egl);
     }
 
     if (bw != -1)
-      save3 |= epg_episode_set_is_bw(ee, (uint8_t)bw, mod);
+      save3 |= epg_episode_set_is_bw(ee, (uint8_t)bw, &changes3);
 
-    save3 |= epg_episode_set_epnum(ee, &epnum, mod);
+    save3 |= epg_episode_set_epnum(ee, &epnum, &changes3);
 
-    save3 |= _xmltv_parse_star_rating(mod, ee, tags);
+    save3 |= _xmltv_parse_star_rating(ee, tags, &changes3);
 
-    save3 |= _xmltv_parse_age_rating(mod, ee, tags);
+    save3 |= _xmltv_parse_age_rating(ee, tags, &changes3);
 
     if (icon)
-      save3 |= epg_episode_set_image(ee, icon, mod);
+      save3 |= epg_episode_set_image(ee, icon, &changes3);
+
+    save3 |= epg_episode_set_first_aired(ee, first_aired, &changes3);
+
+    save3 |= epg_episode_change_finish(ee, changes3, 0);
   }
 
+  save |= epg_broadcast_change_finish(ebc, changes, 0);
+
   /* Stats */
   if (save)  stats->broadcasts.modified++;
   if (save2) stats->seasons.modified++;
index 2625c1b8a95411e4215d9b692091afbb990e628f..5221801e7d0487a1692ebb80529312bee26b3559 100644 (file)
@@ -176,11 +176,11 @@ epggrab_ota_service_del
 
 /* Note: this is reused by pyepg since they share a common format */
 int  xmltv_parse_accessibility
-  ( epggrab_module_t *mod, epg_broadcast_t *ebc, htsmsg_t *m );
+  ( epg_broadcast_t *ebc, htsmsg_t *m, uint32_t *changes );
 
 /* Freesat huffman decoder */
 size_t freesat_huffman_decode
-  (char *dst, size_t* dstlen, const uint8_t *src, size_t srclen);
+  ( char *dst, size_t* dstlen, const uint8_t *src, size_t srclen );
 
 /* **************************************************************************
  * Module setup(s)
index 24a85fd20da64d34b84e5c1d31e29252bc2ea6f8..eb040de50f527f81db31cf8cc65a7e27755bc904 100644 (file)
@@ -46,6 +46,14 @@ lang_str_t *lang_str_create ( void )
   return calloc(1, sizeof(lang_str_t));
 }
 
+lang_str_t *lang_str_create2 ( const char *s, const char *lang )
+{
+  lang_str_t *ls = lang_str_create();
+  if (ls)
+    lang_str_add(ls, s, lang, 0);
+  return ls;
+}
+
 /* Destroy (free memory) */
 void lang_str_destroy ( lang_str_t *ls )
 { 
@@ -72,7 +80,7 @@ lang_str_t *lang_str_copy ( const lang_str_t *ls )
 
 /* Get language element */
 lang_str_ele_t *lang_str_get2
-  ( lang_str_t *ls, const char *lang )
+  ( const lang_str_t *ls, const char *lang )
 {
   int i;
   const char **langs;
@@ -101,7 +109,7 @@ lang_str_ele_t *lang_str_get2
 
 /* Get string */
 const char *lang_str_get
-  ( lang_str_t *ls, const char *lang )
+  ( const lang_str_t *ls, const char *lang )
 {
   lang_str_ele_t *e = lang_str_get2(ls, lang);
   return e ? e->str : NULL;
@@ -255,7 +263,7 @@ lang_str_t *lang_str_deserialize ( htsmsg_t *m, const char *n )
 }
 
 /* Compare */
-int lang_str_compare( lang_str_t *ls1, lang_str_t *ls2 )
+int lang_str_compare( const lang_str_t *ls1, const lang_str_t *ls2 )
 {
   lang_str_ele_t *e;
   const char *s1, *s2;
index 206fe3000c17087111a763524c171bab4e8d409e..d8e3b5f6c96d5a44d0909fd1d9235ef6d5613224 100644 (file)
@@ -34,11 +34,12 @@ typedef RB_HEAD(lang_str, lang_str_ele) lang_str_t;
 /* Create/Destroy */
 void            lang_str_destroy ( lang_str_t *ls );
 lang_str_t     *lang_str_create  ( void );
+lang_str_t     *lang_str_create2 ( const char *str, const char *lang );
 lang_str_t     *lang_str_copy    ( const lang_str_t *ls );
 
 /* Get elements */
-const char     *lang_str_get     ( lang_str_t *ls, const char *lang );
-lang_str_ele_t *lang_str_get2    ( lang_str_t *ls, const char *lang );
+const char     *lang_str_get     ( const lang_str_t *ls, const char *lang );
+lang_str_ele_t *lang_str_get2    ( const lang_str_t *ls, const char *lang );
 
 /* Add/Update elements */
 int             lang_str_add      
@@ -61,7 +62,7 @@ lang_str_t     *lang_str_deserialize
   ( htsmsg_t *m, const char *f );
 
 /* Compare */
-int             lang_str_compare ( lang_str_t *ls1, lang_str_t *ls2 );
+int             lang_str_compare ( const lang_str_t *ls1, const lang_str_t *ls2 );
 
 /* Empty */
 int             strempty(const char* c);