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