( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
uint32_t id, entries = 0;
- htsmsg_t *l = htsmsg_create_list();
- epg_broadcast_t *e;
- epg_episode_t *ep, *ep2;
- char *lang;
+ htsmsg_t *l = htsmsg_create_list(), *m;
+ epg_broadcast_t *e, *ebc;
+ channel_t *ch;
+ char *lang, *uri;
if (htsmsg_get_u32(args, "eventId", &id))
return -EINVAL;
lang = access_get_lang(perm, htsmsg_get_str(args, "lang"));
pthread_mutex_lock(&global_lock);
e = epg_broadcast_find_by_id(id);
- ep = e ? e->episode : NULL;
- if (ep && ep->season) {
- LIST_FOREACH(ep2, &ep->season->episodes, slink) {
- if (ep2 == ep) continue;
- if (!ep2->title) continue;
- api_epg_episode_broadcasts(perm, l, lang, ep2, &entries, e);
- }
+ uri = e->serieslink_uri;
+ if (uri && uri[0]) {
+ CHANNEL_FOREACH(ch)
+ if (channel_access(ch, perm, 0))
+ RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link)
+ if (ebc != e && ebc->serieslink_uri &&
+ strcmp(ebc->serieslink_uri, uri) == 0) {
+ m = api_epg_entry(ebc, lang, perm, NULL);
+ htsmsg_add_msg(l, NULL, m);
+ entries++;
+ }
}
pthread_mutex_unlock(&global_lock);
free(lang);
struct dvr_entry_list dae_spawns;
- epg_season_t *dae_season;
const char *dae_serieslink_uri;
epg_episode_num_t dae_epnum;
const char *dvr_entry_class_image_url_get(const dvr_entry_t *o);
void dvr_autorec_check_event(epg_broadcast_t *e);
-void dvr_autorec_check_season(epg_season_t *s);
void autorec_destroy_by_config(dvr_config_t *cfg, int delconf);
(dae->dae_cat1 == NULL || *dae->dae_cat1 == 0) &&
(dae->dae_cat2 == NULL || *dae->dae_cat2 == 0) &&
(dae->dae_cat3 == NULL || *dae->dae_cat3 == 0) &&
- dae->dae_season == NULL &&
dae->dae_minduration <= 0 &&
(dae->dae_maxduration <= 0 || dae->dae_maxduration > 24 * 3600) &&
dae->dae_serieslink_uri == NULL)
return 0; // Avoid super wildcard match
- // Note: we always test season first, though it will only be set
- // if configured
if(dae->dae_serieslink_uri) {
if (!e->serieslink_uri ||
strcmp(dae->dae_serieslink_uri ?: "", e->serieslink_uri)) return 0;
- } else {
- if(dae->dae_season)
- if (!e->episode->season || dae->dae_season != e->episode->season) return 0;
}
if(dae->dae_btype != DVR_AUTOREC_BTYPE_ALL) {
if(dae->dae_channel_tag != NULL)
LIST_REMOVE(dae, dae_channel_tag_link);
- if(dae->dae_season)
- dae->dae_season->ops->putref(dae->dae_season);
-
free(dae);
}
return dvr_autorec_entry_class_weekdays_rend(dae->dae_weekdays, lang);
}
-static int
-dvr_autorec_entry_class_season_set(void *o, const void *v)
-{
- dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
- int save;
- epg_season_t *season;
-
- v = tvh_str_default(v, 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->ops->putref((epg_object_t*)dae->dae_season);
- season->ops->getref((epg_object_t*)season);
- dae->dae_season = season;
- return 1;
- } else if (season == NULL && dae->dae_season) {
- dae->dae_season->ops->putref((epg_object_t*)dae->dae_season);
- dae->dae_season = NULL;
- return 1;
- }
- return 0;
-}
-
-static const void *
-dvr_autorec_entry_class_season_get(void *o)
-{
- dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
- prop_ptr = dae->dae_season ? dae->dae_season->uri : NULL;
- if (prop_ptr == NULL)
- prop_ptr = "";
- return &prop_ptr;
-}
-
/** Validate star rating is in range */
static int
dvr_autorec_entry_class_star_rating_set(void *o, const void *v)
.list = dvr_entry_class_config_name_list,
.opts = PO_ADVANCED
},
- {
- .type = PT_STR,
- .id = "season",
- .name = N_("Season"),
- .desc = N_("Season information (if available)."),
- .set = dvr_autorec_entry_class_season_set,
- .get = dvr_autorec_entry_class_season_get,
- .opts = PO_RDONLY | PO_ADVANCED,
- },
{
.type = PT_STR,
.id = "serieslink",
// anyway
}
-void dvr_autorec_check_season(epg_season_t *s)
-{
-// Note: I guess new episodes might have been added, but again its likely
-// this will already have been picked up by the check_event call
-}
-
/**
*
*/
epg_object_tree_t epg_objects[EPG_HASH_WIDTH];
/* URI lists */
-epg_object_tree_t epg_seasons;
epg_object_tree_t epg_episodes;
/* Other special case lists */
return ((epg_broadcast_t*)a)->start - ((epg_broadcast_t*)b)->start;
}
-// Note: this will do nothing with text episode numbering
-static int _episode_order ( const void *_a, const void *_b )
-{
- int r, as, bs;
- const epg_episode_t *a = (const epg_episode_t*)_a;
- const epg_episode_t *b = (const epg_episode_t*)_b;
- if (!a) return -1;
- if (!b) return 1;
- if (a->season) as = a->season->number;
- else as = a->epnum.s_num;
- if (b->season) bs = b->season->number;
- else bs = b->epnum.s_num;
- r = as - bs;
- if (r) return r;
- r = a->epnum.e_num - b->epnum.e_num;
- if (r) return r;
- return a->epnum.p_num - b->epnum.p_num;
-}
-
void epg_updated ( void )
{
epg_object_t *eo;
eo->ops->destroy(eo);
}
// Note: we do things this way around since unref'd objects are not likely
- // to be useful to DVR since they will relate to episode/seasons
+ // to be useful to DVR since they will relate to episodes
// with no valid broadcasts etc..
/* Update updated */
{
if (!eo) return NULL;
switch (eo->type) {
- case EPG_SEASON:
- return epg_season_serialize((epg_season_t*)eo);
case EPG_EPISODE:
return epg_episode_serialize((epg_episode_t*)eo);
case EPG_BROADCAST:
if (!msg) return NULL;
type = htsmsg_get_u32_or_default(msg, "type", 0);
switch (type) {
- case EPG_SEASON:
- return (epg_object_t*)epg_season_deserialize(msg, create, save);
case EPG_EPISODE:
return (epg_object_t*)epg_episode_deserialize(msg, create, save);
case EPG_BROADCAST:
return NULL;
}
-/* **************************************************************************
- * Season
- * *************************************************************************/
-
-static void _epg_season_destroy ( void *eo )
-{
- epg_season_t *es = (epg_season_t*)eo;
- if (LIST_FIRST(&es->episodes)) {
- tvhlog(LOG_CRIT, LS_EPG, "attempt to destory season with episodes");
- assert(0);
- }
- if (es->summary) lang_str_destroy(es->summary);
- if (es->image) free(es->image);
- _epg_object_destroy(eo, &epg_seasons);
- free(es);
-}
-
-static void _epg_season_updated ( void *eo )
-{
- dvr_autorec_check_season((epg_season_t*)eo);
-}
-
-static epg_object_ops_t _epg_season_ops = {
- .getref = _epg_object_getref,
- .putref = _epg_object_putref,
- .destroy = _epg_season_destroy,
- .update = _epg_season_updated,
-};
-
-static epg_object_t **_epg_season_skel ( void )
-{
- static epg_object_t *skel = NULL;
- if (!skel) {
- skel = calloc(1, sizeof(epg_season_t));
- skel->type = EPG_SEASON;
- skel->ops = &_epg_season_ops;
- }
- return &skel;
-}
-
-epg_season_t* epg_season_find_by_uri
- ( const char *uri, epggrab_module_t *src,
- int create, int *save, uint32_t *changed )
-{
- return (epg_season_t*)
- _epg_object_find_by_uri(uri, src, create, save, changed,
- &epg_seasons,
- _epg_season_skel());
-}
-
-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);
- return save;
-}
-
-int epg_season_set_summary
- ( 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,
- changed, EPG_CHANGED_SUMMARY);
-}
-
-int epg_season_set_image
- ( 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,
- 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, uint32_t *changed )
-{
- if (!season) return 0;
- 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, uint32_t *changed )
-{
- if (!season) return 0;
- return _epg_object_set_u16(season, &season->number, number,
- changed, EPG_CHANGED_SEASON_NUMBER);
-}
-
-static void _epg_season_add_episode
- ( epg_season_t *season, epg_episode_t *episode )
-{
- _epg_object_getref(season);
- _epg_object_set_updated(season);
- LIST_INSERT_SORTED(&season->episodes, episode, slink, _episode_order);
-}
-
-static void _epg_season_rem_episode
- ( epg_season_t *season, epg_episode_t *episode )
-{
- LIST_REMOVE(episode, slink);
- _epg_object_set_updated(season);
- _epg_object_putref(season);
-}
-
-htsmsg_t *epg_season_serialize ( epg_season_t *season )
-{
- htsmsg_t *m;
- if (!season || !season->uri) return NULL;
- if (!(m = _epg_object_serialize((epg_object_t*)season))) return NULL;
- if (season->summary)
- lang_str_serialize(season->summary, m, "summary");
- if (season->number)
- htsmsg_add_u32(m, "number", season->number);
- if (season->episode_count)
- htsmsg_add_u32(m, "episode-count", season->episode_count);
- if (season->image)
- htsmsg_add_str(m, "image", season->image);
- return m;
-}
-
-epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save )
-{
- epg_object_t **skel = _epg_season_skel();
- epg_season_t *es;
- uint32_t u32, changes = 0;
- const char *str;
- lang_str_t *ls;
-
- if (!_epg_object_deserialize(m, *skel)) 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"))) {
- *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, &changes);
- if (!htsmsg_get_u32(m, "episode-count", &u32))
- *save |= epg_season_set_episode_count(es, u32, &changes);
-
- if ((str = htsmsg_get_str(m, "image")))
- *save |= epg_season_set_image(es, str, &changes);
-
- *save |= epg_season_change_finish(es, changes, 0);
-
- return es;
-}
-
-const char *epg_season_get_summary
- ( const epg_season_t *s, const char *lang )
-{
- if (!s || !s->summary) return NULL;
- return lang_str_get(s->summary, lang);
-}
-
/* **************************************************************************
* Episode
* *************************************************************************/
tvhlog(LOG_CRIT, LS_EPG, "attempt to destroy episode with broadcasts");
assert(0);
}
- if (ee->season) _epg_season_rem_episode(ee->season, ee);
if (ee->title) lang_str_destroy(ee->title);
if (ee->subtitle) lang_str_destroy(ee->subtitle);
if (ee->summary) lang_str_destroy(ee->summary);
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_SEASON))
- save |= epg_episode_set_season(episode, NULL, NULL);
if (!(changes & EPG_CHANGED_GENRE))
save |= epg_episode_set_genre(episode, NULL, NULL);
if (!(changes & EPG_CHANGED_IS_BW))
return save;
}
-int epg_episode_set_season
- ( epg_episode_t *episode, epg_season_t *season, uint32_t *changed )
-{
- int save = 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);
- }
- _epg_object_set_updated(episode);
- save |= 1;
- }
- return save;
-}
-
int epg_episode_set_genre
( epg_episode_t *ee, epg_genre_list_t *genre, uint32_t *changed )
{
return;
}
*num = ee->epnum;
- if (ee->season) {
- num->e_cnt = ee->season->episode_count;
- num->s_num = ee->season->number;
- }
}
int epg_episode_number_cmp ( const epg_episode_num_t *a, const epg_episode_num_t *b )
htsmsg_add_u32(a, NULL, eg->code);
}
if (a) htsmsg_add_msg(m, "genre", a);
- if (episode->season)
- htsmsg_add_str(m, "season", episode->season->uri);
if (episode->is_bw)
htsmsg_add_u32(m, "is_bw", 1);
if (episode->star_rating)
{
epg_object_t **skel = _epg_episode_skel();
epg_episode_t *ee;
- epg_season_t *es;
const char *str;
epg_episode_num_t num;
htsmsg_t *sub;
epg_genre_list_destroy(egl);
}
- if ((str = htsmsg_get_str(m, "season")))
- if ((es = epg_season_find_by_uri(str, ee->grabber, 0, NULL, &changes)))
- *save |= epg_episode_set_season(ee, es, NULL);
-
if (!htsmsg_get_u32(m, "is_bw", &u32))
*save |= epg_episode_set_is_bw(ee, u32, &changes);
epg_object_t **skel;
epg_broadcast_t **broad;
- skel = _epg_season_skel();
- free(*skel); *skel = NULL;
skel = _epg_episode_skel();
free(*skel); *skel = NULL;
broad = _epg_broadcast_skel();
*/
typedef LIST_HEAD(,epg_object) epg_object_list_t;
typedef RB_HEAD (,epg_object) epg_object_tree_t;
-typedef LIST_HEAD(,epg_season) epg_season_list_t;
typedef LIST_HEAD(,epg_episode) epg_episode_list_t;
typedef LIST_HEAD(,epg_broadcast) epg_broadcast_list_t;
typedef RB_HEAD (,epg_broadcast) epg_broadcast_tree_t;
*/
typedef struct epg_genre epg_genre_t;
typedef struct epg_object epg_object_t;
-typedef struct epg_season epg_season_t;
typedef struct epg_episode epg_episode_t;
typedef struct epg_broadcast epg_broadcast_t;
typedef enum epg_object_type
{
EPG_UNDEF,
- EPG_SEASON,
EPG_EPISODE,
EPG_BROADCAST,
- EPG_SERIESLINK
} epg_object_type_t;
-#define EPG_TYPEMAX EPG_SERIESLINK
+#define EPG_TYPEMAX EPG_BROADCAST
/* Change flags - shared */
#define EPG_CHANGED_CREATE (1<<0)
htsmsg_t *epg_object_serialize ( epg_object_t *eo );
epg_object_t *epg_object_deserialize ( htsmsg_t *msg, int create, int *save );
-/* ************************************************************************
- * 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
-{
- epg_object_t; ///< Parent object
-
- lang_str_t *summary; ///< Season summary
- uint16_t number; ///< The season number
- uint16_t episode_count; ///< Total number of episodes
- char *image; ///< Season image
-
- LIST_ENTRY(epg_season) blink; ///< Brand list link
- epg_episode_list_t episodes; ///< Episode list
-
-};
-
-/* Lookup */
-epg_season_t *epg_season_find_by_uri
- ( 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 lang_str_t *summary, uint32_t *changed )
- __attribute__((warn_unused_result));
-int epg_season_set_number
- ( 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, uint32_t *changed )
- __attribute__((warn_unused_result));
-int epg_season_set_image
- ( epg_season_t *s, const char *image, uint32_t *changed )
- __attribute__((warn_unused_result));
-
-/* Serialization */
-htsmsg_t *epg_season_serialize ( epg_season_t *b );
-epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save );
-
/* ************************************************************************
* Episode
* ***********************************************************************/
#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_SEASON (1<<(EPG_CHANGED_SLAST+13))
-#define EPG_CHANGED_COPYRIGHT_YEAR (1<<(EPG_CHANGED_SLAST+14))
+#define EPG_CHANGED_COPYRIGHT_YEAR (1<<(EPG_CHANGED_SLAST+13))
/* Episode numbering object - this is for some back-compat and also
* to allow episode information to be "collated" into easy to use object
///< year since we only get year not month and day.
LIST_ENTRY(epg_episode) blink; ///< Brand link
LIST_ENTRY(epg_episode) slink; ///< Season link
- epg_season_t *season; ///< Parent season
epg_broadcast_list_t broadcasts; ///< Broadcast list
};
int epg_episode_set_epnum
( epg_episode_t *e, epg_episode_num_t *num, uint32_t *changed )
__attribute__((warn_unused_result));
-int epg_episode_set_season
- ( 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, uint32_t *changed )
__attribute__((warn_unused_result));
#define EPG_DB_VERSION 2
#define EPG_DB_ALLOC_STEP (1024*1024)
-extern epg_object_tree_t epg_seasons;
extern epg_object_tree_t epg_episodes;
/* **************************************************************************
/* Season */
} else if ( !strcmp(*sect, "seasons") ) {
- if (epg_season_deserialize(m, 1, &save)) stats->seasons.total++;
+ /* skip */
/* Episode */
} else if ( !strcmp(*sect, "episodes") ) {
* Memoryinfo
*/
-static void epg_memoryinfo_seasons_update(memoryinfo_t *my)
-{
- epg_object_t *eo;
- epg_season_t *es;
- int64_t size = 0, count = 0;
-
- RB_FOREACH(eo, &epg_seasons, uri_link) {
- es = (epg_season_t *)eo;
- size += sizeof(*es);
- size += tvh_strlen(es->uri);
- size += lang_str_size(es->summary);
- size += tvh_strlen(es->image);
- count++;
- }
- memoryinfo_update(my, size, count);
-}
-
-static memoryinfo_t epg_memoryinfo_seasons = {
- .my_name = "EPG Seasons",
- .my_update = epg_memoryinfo_seasons_update
-};
-
static void epg_memoryinfo_episodes_update(memoryinfo_t *my)
{
epg_object_t *eo;
struct sigaction act, oldact;
char *sect = NULL;
- memoryinfo_register(&epg_memoryinfo_seasons);
memoryinfo_register(&epg_memoryinfo_episodes);
memoryinfo_register(&epg_memoryinfo_broadcasts);
/* Stats */
tvhinfo(LS_EPGDB, "loaded v%d", ver);
tvhinfo(LS_EPGDB, " config %d", stats.config.total);
- tvhinfo(LS_EPGDB, " seasons %d", stats.seasons.total);
tvhinfo(LS_EPGDB, " episodes %d", stats.episodes.total);
tvhinfo(LS_EPGDB, " broadcasts %d", stats.broadcasts.total);
CHANNEL_FOREACH(ch)
epg_channel_unlink(ch);
epg_skel_done();
- memoryinfo_unregister(&epg_memoryinfo_seasons);
memoryinfo_unregister(&epg_memoryinfo_episodes);
memoryinfo_unregister(&epg_memoryinfo_broadcasts);
pthread_mutex_unlock(&global_lock);
memset(&stats, 0, sizeof(stats));
if ( _epg_write_sect(sb, "config") ) goto error;
if (_epg_write(sb, epg_config_serialize())) goto error;
- if ( _epg_write_sect(sb, "seasons") ) goto error;
- RB_FOREACH(eo, &epg_seasons, uri_link) {
- if (_epg_write(sb, epg_season_serialize((epg_season_t*)eo))) goto error;
- stats.seasons.total++;
- }
if ( _epg_write_sect(sb, "episodes") ) goto error;
RB_FOREACH(eo, &epg_episodes, uri_link) {
if (_epg_write(sb, epg_episode_serialize((epg_episode_t*)eo))) goto error;
/* Stats */
tvhinfo(LS_EPGDB, "queued to save (size %d)", sb->sb_ptr);
- tvhinfo(LS_EPGDB, " seasons %d", stats.seasons.total);
tvhinfo(LS_EPGDB, " episodes %d", stats.episodes.total);
tvhinfo(LS_EPGDB, " broadcasts %d", stats.broadcasts.total);
/* Ignore? */
if (update) {
int ignore = 1;
- if (e->updated > update) ignore = 0;
- else if (ee) {
- if (ee->updated > update) ignore = 0;
- else if (ee->season && ee->season->updated > update) ignore = 0;
- }
+ if (e->updated > update) ignore = 0;
+ else if (ee && ee->updated > update) ignore = 0;
if (ignore) return NULL;
}
htsmsg_add_u32(out, "episodeId", ee->id);
if (ee->uri && strncasecmp(ee->uri,"tvh://",6)) /* tvh:// uris are internal */
htsmsg_add_str(out, "episodeUri", ee->uri);
- if (ee->season)
- htsmsg_add_u32(out, "seasonId", ee->season->id);
if((g = LIST_FIRST(&ee->genre))) {
uint32_t code = g->code;
if (htsp->htsp_version < 6) code = (code >> 4) & 0xF;