Adds 2017 to copyrights
[src/app-framework-main.git] / src / afm-db.c
1 /*
2  Copyright 2015, 2016, 2017 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #include <stdlib.h>
20 #include <assert.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <dirent.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27
28 #include <json-c/json.h>
29
30 #include "utils-json.h"
31 #include "wgt-info.h"
32 #include "afm-db.h"
33
34 /*
35  * The json object recorded by application widget
36  * has the following contents:
37  *  {
38  *    id: STRING, the application identifier without version info
39  *    path: STRING, the path of the root directory for the application
40  *    content: STRING, the relative path to the entryu point of the application
41  *    type: STRING, the mime type describing the type 'content'
42  *    bindings: ARRAY, array of bindings
43  *      [
44  *        STRING, path to the binding
45  *      ]
46  *    public: OBJECT, public content describing the application widget
47  *      {  
48  *        id: STRING, the application identifier "idaver"
49  *        version: STRING, the full version of the application
50  *        width: INTEGER, the width indication or 0
51  *        height: INTEGER, the height indication or 0
52  *        name: STRING, the name of the application
53  *        description: STRING, the description of the application
54  *        shortname: STRING, the short name of the application
55  *        author: STRING, the author of the application
56  *      }
57  */
58
59 /*
60  * The structure afm_apps records the data about applications
61  * for several accesses.
62  */
63 struct afm_apps {
64         struct json_object *pubarr; /* array of the public data of apps */
65         struct json_object *direct; /* hash of applications by their "idaver" */
66         struct json_object *byapp;  /* hash of applications by their id */
67 };
68
69 /*
70  * Two types of directory are handled:
71  *  - root directories: contains subdirectories appid/version
72  *         containing the applications
73  *  - application directories: it contains an application
74  */
75 enum dir_type {
76         type_root, /* type for root directory */
77         type_app   /* type for application directory */
78 };
79
80 /*
81  * Structure for recording a path to application(s)
82  * in the list of directories.
83  */
84 struct afm_db_dir {
85         struct afm_db_dir *next; /* link to the next item of the list */
86         enum dir_type type;      /* the type of the path */
87         char path[1];            /* the path of the directory */
88 };
89
90 /*
91  * The structure afm_db records the applications
92  * for a set of directories recorded as a linked list
93  */
94 struct afm_db {
95         int refcount;               /* count of references to the structure */
96         struct afm_db_dir *dirhead; /* first directory of the set */
97         struct afm_db_dir *dirtail; /* last directory of the set */
98         struct afm_apps applications; /* the data about applications */
99 };
100
101 /*
102  * The structure enumdata records data used when enumerating
103  * application directories of a root directory.
104  */
105 struct enumdata {
106         char path[PATH_MAX];   /* "current" computed path */
107         int length;            /* length of path */
108         struct afm_apps apps;  /* */
109 };
110
111 /*
112  * Release the data of the afm_apps object 'apps'.
113  */
114 static void apps_put(struct afm_apps *apps)
115 {
116         json_object_put(apps->pubarr);
117         json_object_put(apps->direct);
118         json_object_put(apps->byapp);
119 }
120
121 /*
122  * Adds the application widget 'desc' of the directory 'path' to the
123  * afm_apps object 'apps'.
124  * Returns 0 in case of success.
125  * Returns -1 and set errno in case of error
126  */
127 static int addwgt(struct afm_apps *apps, const char *path,
128                                                 const struct wgt_desc *desc)
129 {
130         const struct wgt_desc_feature *feat;
131         struct json_object *priv, *pub, *bya, *plugs, *str;
132
133         /* create the application structure */
134         priv = json_object_new_object();
135         if (!priv)
136                 return -1;
137
138         pub = j_add_new_object(priv, "public");
139         if (!pub)
140                 goto error;
141
142         plugs = j_add_new_array(priv, "bindings");
143         if (!plugs)
144                 goto error;
145
146         if(!j_add_string(priv, "id", desc->id)
147         || !j_add_string(priv, "path", path)
148         || !j_add_string(priv, "content", desc->content_src)
149         || !j_add_string(priv, "type", desc->content_type)
150         || !j_add_string(pub, "id", desc->idaver)
151         || !j_add_string(pub, "version", desc->version)
152         || !j_add_integer(pub, "width", desc->width)
153         || !j_add_integer(pub, "height", desc->height)
154         || !j_add_string(pub, "name", desc->name)
155         || !j_add_string(pub, "description", desc->description)
156         || !j_add_string(pub, "shortname", desc->name_short)
157         || !j_add_string(pub, "author", desc->author))
158                 goto error;
159
160         /* extract bindings from features */
161         feat = desc->features;
162         while (feat) {
163                 static const char prefix[] = FWK_PREFIX_BINDING;
164                 if (!memcmp(feat->name, prefix, sizeof prefix - 1)) {
165                         str = json_object_new_string (
166                                         feat->name + sizeof prefix - 1);
167                         if (str == NULL)
168                                 goto error;
169                         if (json_object_array_add(plugs, str)) {
170                                 json_object_put(str);
171                                 goto error;
172                         }
173                 }
174                 feat = feat->next;
175         }
176
177         /* record the application structure */
178         if (!j_add(apps->direct, desc->idaver, priv))
179                 goto error;
180
181         if (json_object_array_add(apps->pubarr, pub))
182                 goto error;
183         json_object_get(pub);
184
185         if (!json_object_object_get_ex(apps->byapp, desc->id, &bya)) {
186                 bya = j_add_new_object(apps->byapp, desc->id);
187                 if (!bya)
188                         goto error;
189         }
190
191         if (!j_add(bya, desc->version, priv))
192                 goto error;
193         json_object_get(priv);
194         return 0;
195
196 error:
197         json_object_put(priv);
198         return -1;
199 }
200
201 /*
202  * Adds the application widget in the directory 'path' to the
203  * afm_apps object 'apps'.
204  * Returns 0 in case of success.
205  * Returns -1 and set errno in case of error
206  */
207 static int addapp(struct afm_apps *apps, const char *path)
208 {
209         int rc;
210         struct wgt_info *info;
211
212         /* connect to the widget */
213         info = wgt_info_createat(AT_FDCWD, path, 0, 1, 0);
214         if (info == NULL) {
215                 if (errno == ENOENT)
216                         return 0; /* silently ignore bad directories */
217                 return -1;
218         }
219         /* adds the widget */
220         rc = addwgt(apps, path, wgt_info_desc(info));
221         wgt_info_unref(info);
222         return rc;
223 }
224
225 /*
226  * Enumerate the directories designated by 'data' and call the
227  * function 'callto' for each of them.
228  */
229 static int enumentries(struct enumdata *data, int (*callto)(struct enumdata *))
230 {
231         DIR *dir;
232         int rc;
233         char *beg;
234         struct dirent entry, *e;
235         size_t len;
236
237         /* opens the directory */
238         dir = opendir(data->path);
239         if (!dir)
240                 return -1;
241
242         /* prepare appending entry names */
243         beg = data->path + data->length;
244         *beg++ = '/';
245
246         /* enumerate entries */
247         rc = readdir_r(dir, &entry, &e);
248         while (!rc && e) {
249                 if (entry.d_name[0] != '.' || (entry.d_name[1]
250                         && (entry.d_name[1] != '.' || entry.d_name[2]))) {
251                         /* prepare callto */
252                         len = strlen(entry.d_name);
253                         if (beg + len >= data->path + sizeof data->path) {
254                                 errno = ENAMETOOLONG;
255                                 return -1;
256                         }
257                         data->length = (int)(stpcpy(beg, entry.d_name)
258                                                                 - data->path);
259                         /* call the function */
260                         rc = callto(data);
261                         if (rc)
262                                 break;
263                 }       
264                 rc = readdir_r(dir, &entry, &e);
265         }
266         closedir(dir);
267         return rc;
268 }
269
270 /*
271  * called for each version directory.
272  */
273 static int recordapp(struct enumdata *data)
274 {
275         return addapp(&data->apps, data->path);
276 }
277
278 /*
279  * called for each application directory.
280  * enumerate directories of the existing versions.
281  */
282 static int enumvers(struct enumdata *data)
283 {
284         int rc = enumentries(data, recordapp);
285         return !rc || errno != ENOTDIR ? 0 : rc;
286 }
287
288 /*
289  * Adds the directory of 'path' and 'type' to the afm_db object 'afdb'.
290  * Returns 0 in case of success.
291  * Returns -1 and set errno in case of error
292  * Possible errno values: ENOMEM, ENAMETOOLONG
293  */
294 static int add_dir(struct afm_db *afdb, const char *path, enum dir_type type)
295 {
296         struct afm_db_dir *dir;
297         size_t len;
298
299         assert(afdb);
300
301         /* check size */
302         len = strlen(path);
303         if (len >= PATH_MAX) {
304                 errno = ENAMETOOLONG;
305                 return -1;
306         }
307
308         /* avoiding duplications */
309         dir = afdb->dirhead;
310         while(dir != NULL && (strcmp(dir->path, path) || dir->type != type))
311                 dir = dir ->next;
312         if (dir != NULL)
313                 return 0;
314
315         /* allocates the structure */
316         dir = malloc(strlen(path) + sizeof * dir);
317         if (dir == NULL) {
318                 errno = ENOMEM;
319                 return -1;
320         }
321
322         /* add it at tail */
323         dir->next = NULL;
324         dir->type = type;
325         strcpy(dir->path, path);
326         if (afdb->dirtail == NULL)
327                 afdb->dirhead = dir;
328         else
329                 afdb->dirtail->next = dir;
330         afdb->dirtail = dir;
331         return 0;
332 }
333
334 /*
335  * Creates an afm_db object and returns it with one reference added.
336  * Return NULL with errno = ENOMEM if memory exhausted.
337  */
338 struct afm_db *afm_db_create()
339 {
340         struct afm_db *afdb = malloc(sizeof * afdb);
341         if (afdb == NULL)
342                 errno = ENOMEM;
343         else {
344                 afdb->refcount = 1;
345                 afdb->dirhead = NULL;
346                 afdb->dirtail = NULL;
347                 afdb->applications.pubarr = NULL;
348                 afdb->applications.direct = NULL;
349                 afdb->applications.byapp = NULL;
350         }
351         return afdb;
352 }
353
354 /*
355  * Adds a reference to an existing afm_db.
356  */
357 void afm_db_addref(struct afm_db *afdb)
358 {
359         assert(afdb);
360         afdb->refcount++;
361 }
362
363 /*
364  * Removes a reference to an existing afm_db object.
365  * Removes the objet if there no more reference to it.
366  */
367 void afm_db_unref(struct afm_db *afdb)
368 {
369         struct afm_db_dir *dir;
370
371         assert(afdb);
372         if (!--afdb->refcount) {
373                 /* no more reference, clean the memory used by the object */
374                 apps_put(&afdb->applications);
375                 while (afdb->dirhead != NULL) {
376                         dir = afdb->dirhead;
377                         afdb->dirhead = dir->next;
378                         free(dir);
379                 }
380                 free(afdb);
381         }
382 }
383
384 /*
385  * Adds the root directory of 'path' to the afm_db object 'afdb'.
386  * Be aware that no check is done on the directory of 'path' that will
387  * only be used within calls to the function 'afm_db_update_applications'.
388  * Returns 0 in case of success.
389  * Returns -1 and set errno in case of error
390  * Possible errno values: ENOMEM, ENAMETOOLONG
391  */
392 int afm_db_add_root(struct afm_db *afdb, const char *path)
393 {
394         return add_dir(afdb, path, type_root);
395 }
396
397 /*
398  * Adds the application directory of 'path' to the afm_db object 'afdb'.
399  * Be aware that no check is done on the directory of 'path' that will
400  * only be used within calls to the function 'afm_db_update_applications'.
401  * Returns 0 in case of success.
402  * Returns -1 and set errno in case of error
403  * Possible errno values: ENOMEM, ENAMETOOLONG
404  */
405 int afm_db_add_application(struct afm_db *afdb, const char *path)
406 {
407         return add_dir(afdb, path, type_app);
408 }
409
410 /*
411  * Regenerate the list of applications of the afm_bd object 'afdb'.
412  * Returns 0 in case of success.
413  * Returns -1 and set errno in case of error
414  */
415 int afm_db_update_applications(struct afm_db *afdb)
416 {
417         int rc;
418         struct enumdata edata;
419         struct afm_apps oldapps;
420         struct afm_db_dir *dir;
421
422         /* create the result */
423         edata.apps.pubarr = json_object_new_array();
424         edata.apps.direct = json_object_new_object();
425         edata.apps.byapp = json_object_new_object();
426         if (edata.apps.pubarr == NULL || edata.apps.direct == NULL
427                                                 || edata.apps.byapp == NULL) {
428                 errno = ENOMEM;
429                 goto error;
430         }
431         /* for each directory of afdb */
432         for (dir = afdb->dirhead ; dir != NULL ; dir = dir->next) {
433                 if (dir->type == type_root) {
434                         edata.length = (int)(stpcpy(edata.path, dir->path)
435                                                                 - edata.path);
436                         assert(edata.length < (int)sizeof edata.path);
437                         /* enumerate the applications */
438                         rc = enumentries(&edata, enumvers);
439                         if (rc)
440                                 goto error;
441                 } else {
442                         rc = addapp(&edata.apps, dir->path);
443                 }
444         }
445         /* commit the result */
446         oldapps = afdb->applications;
447         afdb->applications = edata.apps;
448         apps_put(&oldapps);
449         return 0;
450
451 error:
452         apps_put(&edata.apps);
453         return -1;
454 }
455
456 /*
457  * Ensure that applications of the afm_bd object 'afdb' are listed.
458  * Returns 0 in case of success.
459  * Returns -1 and set errno in case of error
460  */
461 int afm_db_ensure_applications(struct afm_db *afdb)
462 {
463         return afdb->applications.pubarr ? 0 : afm_db_update_applications(afdb);
464 }
465
466 /*
467  * Get the list of the applications public data of the afm_db object 'afdb'.
468  * The list is returned as a JSON-array that must be released using
469  * 'json_object_put'.
470  * Returns NULL in case of error.
471  */
472 struct json_object *afm_db_application_list(struct afm_db *afdb)
473 {
474         return afm_db_ensure_applications(afdb) ? NULL
475                         : json_object_get(afdb->applications.pubarr);
476 }
477
478 /*
479  * Get the private data of the applications of 'id' in the afm_db object 'afdb'.
480  * It returns a JSON-object that must be released using 'json_object_put'.
481  * Returns NULL in case of error.
482  */
483 struct json_object *afm_db_get_application(struct afm_db *afdb, const char *id)
484 {
485         int i;
486         struct json_object *result;
487
488         if (afm_db_ensure_applications(afdb))
489                 return NULL;
490
491         /* search case sensitively */
492         if (json_object_object_get_ex( afdb->applications.direct, id, &result))
493                 return json_object_get(result);
494
495         /* fallback to a case insensitive search */
496         i = json_object_array_length(afdb->applications.pubarr);
497         while (i) {
498                 result = json_object_array_get_idx(afdb->applications.pubarr, --i);
499                 if (result
500                   && json_object_object_get_ex(result, "id", &result)
501                   && !strcasecmp(id, json_object_get_string(result))) {
502                         if (json_object_object_get_ex( afdb->applications.direct, 
503                                                        json_object_get_string(result),
504                                                        &result))
505                                 return json_object_get(result);
506                         else
507                                 return NULL;
508                 }
509         }
510         return NULL;
511 }
512
513 /*
514  * Get the public data of the applications of 'id' in the afm_db object 'afdb'.
515  * It returns a JSON-object that must be released using 'json_object_put'.
516  * Returns NULL in case of error.
517  */
518 struct json_object *afm_db_get_application_public(struct afm_db *afdb,
519                                                         const char *id)
520 {
521         struct json_object *result;
522         struct json_object *priv = afm_db_get_application(afdb, id);
523         if (priv == NULL)
524                 return NULL;
525         if (json_object_object_get_ex(priv, "public", &result))
526                 json_object_get(result);
527         else
528                 result = NULL;
529         json_object_put(priv);
530         return result;
531 }
532
533
534
535
536 #if defined(TESTAPPFWK)
537 #include <stdio.h>
538 int main()
539 {
540 struct afm_db *afdb = afm_db_create();
541 afm_db_add_root(afdb,FWK_APP_DIR);
542 afm_db_update_applications(afdb);
543 printf("array = %s\n", json_object_to_json_string_ext(afdb->applications.pubarr, 3));
544 printf("direct = %s\n", json_object_to_json_string_ext(afdb->applications.direct, 3));
545 printf("byapp = %s\n", json_object_to_json_string_ext(afdb->applications.byapp, 3));
546 return 0;
547 }
548 #endif
549