Settings system is code complete... now to actually USE the darn things for
[pazpar2-moved-to-github.git] / src / settings.c
1 // $Id: settings.c,v 1.3 2007-03-29 13:44:19 quinn Exp $
2 // This module implements a generic system of settings (attribute-value) that can 
3 // be associated with search targets. The system supports both default values,
4 // per-target overrides, and per-user settings.
5 //
6
7 #include <string.h>
8 #include <stdio.h>
9 #include <sys/types.h>
10 #include <dirent.h>
11 #include <stdlib.h>
12 #include <sys/stat.h>
13
14 #include <libxml/parser.h>
15 #include <libxml/tree.h>
16
17 #include <yaz/nmem.h>
18 #include <yaz/log.h>
19
20 #include "pazpar2.h"
21 #include "database.h"
22 #include "settings.h"
23
24 static NMEM nmem = 0;
25
26 // Used for initializing setting_dictionary with pazpar2-specific settings
27 static char *hard_settings[] = {
28     "pz:piggyback",
29     "pz:elements",
30     "pz::syntax",
31     0
32 };
33
34 struct setting_dictionary
35 {
36     char **dict;
37     int size;
38     int num;
39 };
40
41 static struct setting_dictionary *dictionary = 0;
42
43 int settings_offset(const char *name)
44 {
45     int i;
46
47     for (i = 0; i < dictionary->num; i++)
48         if (!strcmp(name, dictionary->dict[i]))
49             return i;
50     return -1;
51 }
52
53 char *settings_name(int offset)
54 {
55     return dictionary->dict[offset];
56 }
57
58 static int isdir(const char *path)
59 {
60     struct stat st;
61
62     if (stat(path, &st) < 0)
63     {
64         yaz_log(YLOG_FATAL|YLOG_ERRNO, "%s", path);
65         exit(1);
66     }
67     return st.st_mode & S_IFDIR;
68 }
69
70 // Read settings from an XML file, calling handler function for each setting
71 static void read_settings_file(const char *path,
72         void (*fun)(struct setting *set))
73 {
74     xmlDoc *doc = xmlParseFile(path);
75     xmlNode *n;
76     xmlChar *namea, *targeta, *valuea, *usera, *precedencea;
77
78     if (!doc)
79     {
80         yaz_log(YLOG_FATAL, "Failed to parse %s", path);
81         exit(1);
82     }
83     n = xmlDocGetRootElement(doc);
84     namea = xmlGetProp(n, (xmlChar *) "name");
85     targeta = xmlGetProp(n, (xmlChar *) "target");
86     valuea = xmlGetProp(n, (xmlChar *) "value");
87     usera = xmlGetProp(n, (xmlChar *) "user");
88     precedencea = xmlGetProp(n, (xmlChar *) "precedence");
89     for (n = n->children; n; n = n->next)
90     {
91         if (n->type != XML_ELEMENT_NODE)
92             continue;
93         if (!strcmp(n->name, (xmlChar *) "set"))
94         {
95             char *name, *target, *value, *user, *precedence;
96
97             name = xmlGetProp(n, (xmlChar *) "name");
98             target = xmlGetProp(n, (xmlChar *) "target");
99             value = xmlGetProp(n, (xmlChar *) "value");
100             user = xmlGetProp(n, (xmlChar *) "user");
101             precedence = xmlGetProp(n, (xmlChar *) "precedence");
102
103             if ((!name && !namea) || (!value && !valuea) || (!target && !targeta))
104             {
105                 yaz_log(YLOG_FATAL, "set must specify name, value, and target");
106                 exit(1);
107             }
108             else
109             {
110                 struct setting set;
111                 char nameb[1024];
112                 char targetb[1024];
113                 char userb[1024];
114                 char valueb[1024];
115
116                 // Copy everything into a temporary buffer -- we decide
117                 // later if we are keeping it.
118                 if (precedence)
119                     set.precedence = atoi((char *) precedence);
120                 else if (precedencea)
121                     set.precedence = atoi((char *) precedencea);
122                 else
123                     set.precedence = 0;
124                 set.user = userb;
125                 if (user)
126                     strcpy(userb, user);
127                 else if (usera)
128                     strcpy(userb, usera);
129                 else
130                     set.user = "";
131                 if (target)
132                     strcpy(targetb, target);
133                 else
134                     strcpy(targetb, targeta);
135                 set.target = targetb;
136                 if (name)
137                     strcpy(nameb, name);
138                 else
139                     strcpy(nameb, namea);
140                 set.name = nameb;
141                 if (value)
142                     strcpy(valueb, value);
143                 else
144                     strcpy(valueb, valuea);
145                 set.value = valueb;
146                 set.next = 0;
147                 (*fun)(&set);
148             }
149             xmlFree(name);
150             xmlFree(precedence);
151             xmlFree(value);
152             xmlFree(user);
153             xmlFree(target);
154         }
155         else
156         {
157             yaz_log(YLOG_FATAL, "Unknown element %s in settings file", (char*) n->name);
158             exit(1);
159         }
160     }
161     xmlFree(namea);
162     xmlFree(precedencea);
163     xmlFree(valuea);
164     xmlFree(usera);
165     xmlFree(targeta);
166 }
167  
168 // Recursively read files in a directory structure, calling 
169 // callback for each one
170 static void read_settings(const char *path,
171                 void (*fun)(struct setting *set))
172 {
173     DIR *d;
174     struct dirent *de;
175
176     if (!(d = opendir(path)))
177     {
178         yaz_log(YLOG_FATAL|YLOG_ERRNO, "%s", path);
179         exit(1);
180     }
181     while ((de = readdir(d)))
182     {
183         char tmp[1024];
184         if (*de->d_name == '.' || !strcmp(de->d_name, "CVS"))
185             continue;
186         sprintf(tmp, "%s/%s", path, de->d_name);
187         if (isdir(tmp))
188             read_settings(tmp, fun);
189         else
190         {
191             char *dot;
192             if ((dot = rindex(de->d_name, '.')) && !strcmp(dot + 1, "xml"))
193                 read_settings_file(tmp, fun);
194         }
195     }
196     closedir(d);
197 }
198
199 // Callback. Adds a new entry to the dictionary if necessary
200 // This is used in pass 1 to determine layout of dictionary
201 static void prepare_dictionary(struct setting *set)
202 {
203     int i;
204
205     for (i = 0; i < dictionary->num; i++)
206         if (!strcmp(dictionary->dict[i], set->name))
207             return;
208     // Create a new dictionary entry
209     // Grow dictionary if necessary
210     if (!dictionary->size)
211         dictionary->dict = nmem_malloc(nmem, (dictionary->size = 50) * sizeof(char*));
212     else if (dictionary->num + 1 > dictionary->size)
213     {
214         char **tmp = nmem_malloc(nmem, dictionary->size * 2 * sizeof(char*));
215         memcpy(tmp, dictionary->dict, dictionary->size * sizeof(char*));
216         dictionary->dict = tmp;
217         dictionary->size *= 2;
218     }
219     dictionary->dict[dictionary->num++] = nmem_strdup(nmem, set->name);
220 }
221
222 // This is called from grep_databases -- adds/overrides setting for a target
223 // This is also where the rules for precedence of settings are implemented
224 static void update_database(void *context, struct database *db)
225 {
226     struct setting *set = (struct setting *) context;
227     struct setting *s, **sp;
228     int offset;
229
230     if (!db->settings)
231     {
232         db->settings = nmem_malloc(nmem, sizeof(struct settings*) * dictionary->num);
233         memset(db->settings, sizeof(struct settings*) * dictionary->num, 0);
234     }
235     if ((offset = settings_offset(set->name)) < 0)
236         abort(); // Should never get here
237
238     // First we determine if this setting is overriding  any existing settings
239     // with the same name.
240     for (s = db->settings[offset], sp = &db->settings[offset]; s;
241             sp = &s->next, s = s->next)
242         if (!strcmp(s->user, set->user))
243         {
244             if (s->precedence < set->precedence)
245                 // We discard the value (nmem keeps track of the space)
246                 *sp = (*sp)->next;
247             else if (s->precedence > set->precedence)
248                 // Db contains a higher-priority setting. Abort 
249                 break;
250             if (*s->target == '*' && *set->target != '*')
251                 // target-specific value trumps wildcard. Delete.
252                 *sp = (*sp)->next;
253             else if (*s->target != '*' && *set->target == '*')
254                 // Db already contains higher-priority setting. Abort
255                 break;
256         }
257     if (!s) // s will be null when there are no higher-priority settings -- we add one
258     {
259         struct setting *new = nmem_malloc(nmem, sizeof(*new));
260
261         memset(new, sizeof(*new), 0);
262         new->precedence = set->precedence;
263         new->target = nmem_strdup(nmem, set->target);
264         new->name = nmem_strdup(nmem, set->name);
265         new->value = nmem_strdup(nmem, set->value);
266         new->user = nmem_strdup(nmem, set->user);
267         new->next = db->settings[offset];
268         db->settings[offset] = new;
269     }
270 }
271
272 // Callback -- updates database records with dictionary entries as appropriate
273 // This is used in pass 2 to assign name/value pairs to databases
274 static void update_databases(struct setting *set)
275 {
276     struct database_criterion crit;
277     struct database_criterion_value val;
278
279     // Update all databases which match pattern in set->target
280     crit.name = "id";
281     crit.values = &val;
282     crit.next = 0;
283     val.value = set->target;
284     val.next = 0;
285     grep_databases(set, &crit, update_database);
286 }
287
288 // This simply copies the 'hard' (application-specific) settings
289 // to the settings dictionary.
290 static void initialize_hard_settings(struct setting_dictionary *dict)
291 {
292     dict->dict = nmem_malloc(nmem, sizeof(hard_settings) - sizeof(char*));
293     dict->size = (sizeof(hard_settings) - sizeof(char*)) / sizeof(char*);
294     memcpy(dict->dict, hard_settings, dict->size * sizeof(char*));
295     dict->num = dict->size;
296 }
297
298 // If we ever decide we need to be able to specify multiple settings directories,
299 // the two calls to read_settings must be split -- so the dictionary is prepared
300 // for the contents of every directory before the databases are updated.
301 void settings_read(const char *path)
302 {
303     struct setting_dictionary *new;
304     if (!nmem)
305         nmem = nmem_create();
306     else
307         nmem_reset(nmem);
308     new = nmem_malloc(nmem, sizeof(*new));
309     initialize_hard_settings(new);
310     memset(new, sizeof(*new), 0);
311     dictionary = new;
312     read_settings(path, prepare_dictionary);
313     read_settings(path, update_databases);
314 }
315
316 /*
317  * Local variables:
318  * c-basic-offset: 4
319  * indent-tabs-mode: nil
320  * End:
321  * vim: shiftwidth=4 tabstop=8 expandtab
322  */