1 /*
2  *  Multi-language String support
3  *  Copyright (C) 2012 Adam Sutton
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <string.h>
20 #include <stdlib.h>
21 
22 #include "redblack.h"
23 #include "lang_codes.h"
24 #include "lang_str.h"
25 #include "tvheadend.h"
26 
27 SKEL_DECLARE(lang_str_ele_skel, lang_str_ele_t);
28 
29 /* ************************************************************************
30  * Support
31  * ***********************************************************************/
32 
33 /* Compare language codes */
_lang_cmp(void * a,void * b)34 static int _lang_cmp ( void *a, void *b )
35 {
36   return strcmp(((lang_str_ele_t*)a)->lang, ((lang_str_ele_t*)b)->lang);
37 }
38 
39 /* ************************************************************************
40  * Language String
41  * ***********************************************************************/
42 
43 /* Create new instance */
lang_str_create(void)44 lang_str_t *lang_str_create ( void )
45 {
46   return calloc(1, sizeof(lang_str_t));
47 }
48 
lang_str_create2(const char * s,const char * lang)49 lang_str_t *lang_str_create2 ( const char *s, const char *lang )
50 {
51   lang_str_t *ls = lang_str_create();
52   if (ls)
53     lang_str_add(ls, s, lang, 0);
54   return ls;
55 }
56 
57 /* Destroy (free memory) */
lang_str_destroy(lang_str_t * ls)58 void lang_str_destroy ( lang_str_t *ls )
59 {
60   lang_str_ele_t *e;
61   if (ls == NULL)
62     return;
63   while ((e = RB_FIRST(ls))) {
64     if (e->str)  free(e->str);
65     RB_REMOVE(ls, e, link);
66     free(e);
67   }
68   free(ls);
69 }
70 
71 /* Copy the lang_str instance */
lang_str_copy(const lang_str_t * ls)72 lang_str_t *lang_str_copy ( const lang_str_t *ls )
73 {
74   lang_str_t *ret = lang_str_create();
75   lang_str_ele_t *e;
76   RB_FOREACH(e, ls, link)
77     lang_str_add(ret, e->str, e->lang, 0);
78   return ret;
79 }
80 
81 /* Get language element */
lang_str_get2(const lang_str_t * ls,const char * lang)82 lang_str_ele_t *lang_str_get2
83   ( const lang_str_t *ls, const char *lang )
84 {
85   int i;
86   const char **langs;
87   lang_str_ele_t skel, *e = NULL;
88 
89   if (!ls) return NULL;
90 
91   /* Check config/requested langs */
92   if ((langs = lang_code_split(lang))) {
93     i = 0;
94     while (langs[i]) {
95       skel.lang = langs[i];
96       if ((e = RB_FIND(ls, &skel, link, _lang_cmp)))
97         break;
98       i++;
99     }
100     free(langs);
101   }
102 
103   /* Use first available */
104   if (!e) e = RB_FIRST(ls);
105 
106   /* Return */
107   return e;
108 }
109 
110 /* Get string */
lang_str_get(const lang_str_t * ls,const char * lang)111 const char *lang_str_get
112   ( const lang_str_t *ls, const char *lang )
113 {
114   lang_str_ele_t *e = lang_str_get2(ls, lang);
115   return e ? e->str : NULL;
116 }
117 
118 /* Internal insertion routine */
_lang_str_add(lang_str_t * ls,const char * str,const char * lang,int update,int append)119 static int _lang_str_add
120   ( lang_str_t *ls, const char *str, const char *lang, int update, int append )
121 {
122   int save = 0;
123   lang_str_ele_t *e;
124 
125   if (!str) return 0;
126 
127   /* Get proper code */
128   if (!lang) lang = lang_code_preferred();
129   if (!(lang = lang_code_get(lang))) return 0;
130 
131   /* Create skel */
132   SKEL_ALLOC(lang_str_ele_skel);
133   lang_str_ele_skel->lang = lang;
134 
135   /* Create */
136   e = RB_INSERT_SORTED(ls, lang_str_ele_skel, link, _lang_cmp);
137   if (!e) {
138     lang_str_ele_skel->str = strdup(str);
139     SKEL_USED(lang_str_ele_skel);
140     save = 1;
141 
142   /* Append */
143   } else if (append) {
144     e->str = realloc(e->str, strlen(e->str) + strlen(str) + 1);
145     strcat(e->str, str);
146     save = 1;
147 
148   /* Update */
149   } else if (update && strcmp(str, e->str)) {
150     free(e->str);
151     e->str = strdup(str);
152     save = 1;
153   }
154 
155   return save;
156 }
157 
158 /* Add new string (or replace existing one) */
lang_str_add(lang_str_t * ls,const char * str,const char * lang,int update)159 int lang_str_add
160   ( lang_str_t *ls, const char *str, const char *lang, int update )
161 {
162   return _lang_str_add(ls, str, lang, update, 0);
163 }
164 
165 /* Append to existing string (or add new one) */
lang_str_append(lang_str_t * ls,const char * str,const char * lang)166 int lang_str_append
167   ( lang_str_t *ls, const char *str, const char *lang )
168 {
169   return _lang_str_add(ls, str, lang, 0, 1);
170 }
171 
172 /* Set new string with update check */
lang_str_set(lang_str_t ** dst,const char * str,const char * lang)173 int lang_str_set
174   ( lang_str_t **dst, const char *str, const char *lang )
175 {
176   lang_str_ele_t *e;
177   int found;
178   if (*dst == NULL) goto change1;
179   if (!lang) lang = lang_code_preferred();
180   if (!(lang = lang_code_get(lang))) return 0;
181   if (*dst) {
182     found = 0;
183     RB_FOREACH(e, *dst, link) {
184       if (found)
185         goto change;
186       found = strcmp(e->lang, lang) == 0 &&
187               strcmp(e->str, str) == 0;
188       if (!found)
189         goto change;
190     }
191     if (found)
192       return 0;
193   }
194 change:
195   lang_str_destroy(*dst);
196 change1:
197   *dst = lang_str_create();
198   lang_str_add(*dst, str, lang, 1);
199   return 1;
200 }
201 
202 /* Set new strings with update check */
lang_str_set2(lang_str_t ** dst,lang_str_t * src)203 int lang_str_set2
204   ( lang_str_t **dst, lang_str_t *src )
205 {
206   if (*dst) {
207     if (!lang_str_compare(*dst, src))
208       return 0;
209     lang_str_destroy(*dst);
210   }
211   *dst = lang_str_copy(src);
212   return 1;
213 }
214 
215 /* Serialize  map */
lang_str_serialize_map(lang_str_t * ls)216 htsmsg_t *lang_str_serialize_map ( lang_str_t *ls )
217 {
218   lang_str_ele_t *e;
219   if (!ls) return NULL;
220   htsmsg_t *a = htsmsg_create_map();
221   RB_FOREACH(e, ls, link) {
222     htsmsg_add_str(a, e->lang, e->str);
223   }
224   return a;
225 }
226 
227 /* Serialize */
lang_str_serialize(lang_str_t * ls,htsmsg_t * m,const char * f)228 void lang_str_serialize ( lang_str_t *ls, htsmsg_t *m, const char *f )
229 {
230   if (!ls) return;
231   htsmsg_add_msg(m, f, lang_str_serialize_map(ls));
232 }
233 
234 /* De-serialize map */
lang_str_deserialize_map(htsmsg_t * map)235 lang_str_t *lang_str_deserialize_map ( htsmsg_t *map )
236 {
237   lang_str_t *ret = lang_str_create();
238   htsmsg_field_t *f;
239   const char *str;
240 
241   HTSMSG_FOREACH(f, map) {
242     if ((str = htsmsg_field_get_string(f))) {
243       lang_str_add(ret, str, f->hmf_name, 0);
244     }
245   }
246   return ret;
247 }
248 
249 /* De-serialize */
lang_str_deserialize(htsmsg_t * m,const char * n)250 lang_str_t *lang_str_deserialize ( htsmsg_t *m, const char *n )
251 {
252   htsmsg_t *a;
253   const char *str;
254 
255   if ((a = htsmsg_get_map(m, n))) {
256     return lang_str_deserialize_map(a);
257   } else if ((str = htsmsg_get_str(m, n))) {
258     lang_str_t *ret = lang_str_create();
259     lang_str_add(ret, str, NULL, 0);
260     return ret;
261   }
262   return NULL;
263 }
264 
265 /* Compare */
lang_str_compare(const lang_str_t * ls1,const lang_str_t * ls2)266 int lang_str_compare( const lang_str_t *ls1, const lang_str_t *ls2 )
267 {
268   lang_str_ele_t *e;
269   const char *s1, *s2;
270   int r;
271 
272   if (ls1 == NULL && ls2)
273     return -1;
274   if (ls2 == NULL && ls1)
275     return 1;
276   if (ls1 == ls2)
277     return 0;
278   /* Note: may be optimized to not check languages twice */
279   RB_FOREACH(e, ls1, link) {
280     s1 = lang_str_get(ls1, e->lang);
281     s2 = lang_str_get(ls2, e->lang);
282     if (s1 == NULL && s2 != NULL)
283       return -1;
284     if (s2 == NULL && s1 != NULL)
285       return 1;
286     if (s1 == NULL || s2 == NULL)
287       continue;
288     r = strcmp(s1, s2);
289     if (r) return r;
290   }
291   RB_FOREACH(e, ls2, link) {
292     s1 = lang_str_get(ls1, e->lang);
293     s2 = lang_str_get(ls2, e->lang);
294     if (s1 == NULL && s2 != NULL)
295       return -1;
296     if (s2 == NULL && s1 != NULL)
297       return 1;
298     if (s1 == NULL || s2 == NULL)
299       continue;
300     r = strcmp(s1, s2);
301     if (r) return r;
302   }
303   return 0;
304 }
305 
lang_str_size(const lang_str_t * ls)306 size_t lang_str_size(const lang_str_t *ls)
307 {
308   lang_str_ele_t *e;
309   size_t size;
310   if (!ls) return 0;
311   size = sizeof(*ls);
312   RB_FOREACH(e, ls, link) {
313     size += sizeof(*e);
314     size += tvh_strlen(e->str);
315     size += tvh_strlen(e->lang);
316   }
317   return size;
318 }
319 
lang_str_done(void)320 void lang_str_done( void )
321 {
322   SKEL_FREE(lang_str_ele_skel);
323 }
324