150fc27a9f68509d5661da83065adccbe6cfc59b
[idzebra-moved-to-github.git] / index / trav.c
1 /*
2  * Copyright (C) 1994-1999, Index Data
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: trav.c,v $
7  * Revision 1.36  1999-05-15 14:36:38  adam
8  * Updated dictionary. Implemented "compression" of dictionary.
9  *
10  * Revision 1.35  1999/02/02 14:51:09  adam
11  * Updated WIN32 code specific sections. Changed header.
12  *
13  * Revision 1.34  1998/06/08 14:43:14  adam
14  * Added suport for EXPLAIN Proxy servers - added settings databasePath
15  * and explainDatabase to facilitate this. Increased maximum number
16  * of databases and attributes in one register.
17  *
18  * Revision 1.33  1998/01/12 15:04:08  adam
19  * The test option (-s) only uses read-lock (and not write lock).
20  *
21  * Revision 1.32  1997/09/25 14:56:51  adam
22  * Windows NT interface code to the stat call.
23  *
24  * Revision 1.31  1997/09/17 12:19:17  adam
25  * Zebra version corresponds to YAZ version 1.4.
26  * Changed Zebra server so that it doesn't depend on global common_resource.
27  *
28  * Revision 1.30  1997/09/09 13:38:09  adam
29  * Partial port to WIN95/NT.
30  *
31  * Revision 1.29  1997/02/12 20:39:47  adam
32  * Implemented options -f <n> that limits the log to the first <n>
33  * records.
34  * Changed some log messages also.
35  *
36  * Revision 1.28  1996/11/01 08:58:44  adam
37  * Interface to isamc system now includes update and delete.
38  *
39  * Revision 1.27  1996/10/29 14:06:56  adam
40  * Include zebrautl.h instead of alexutil.h.
41  *
42  * Revision 1.26  1996/06/04 10:19:01  adam
43  * Minor changes - removed include of ctype.h.
44  *
45  * Revision 1.25  1996/05/01  13:46:37  adam
46  * First work on multiple records in one file.
47  * New option, -offset, to the "unread" command in the filter module.
48  *
49  * Revision 1.24  1996/04/26  10:00:23  adam
50  * Added option -V to zebraidx to display version information.
51  * Removed stupid warnings from file update.
52  *
53  * Revision 1.23  1996/04/12  07:02:25  adam
54  * File update of single files.
55  *
56  * Revision 1.22  1996/04/09 06:50:50  adam
57  * Bug fix: bad reference in function fileUpdateR.
58  *
59  * Revision 1.21  1996/03/22 15:34:18  quinn
60  * Fixed bad reference
61  *
62  * Revision 1.20  1996/03/21  14:50:10  adam
63  * File update uses modify-time instead of change-time.
64  *
65  * Revision 1.19  1996/03/20  16:16:55  quinn
66  * Added diagnostic output
67  *
68  * Revision 1.18  1996/03/19  12:43:27  adam
69  * Bug fix: File update traversal didn't handle trailing slashes correctly.
70  * Bug fix: Update of sub directory groups wasn't handled correctly.
71  *
72  * Revision 1.17  1996/02/12  18:45:17  adam
73  * Changed naming of some functions.
74  *
75  * Revision 1.16  1996/02/05  12:30:02  adam
76  * Logging reduced a bit.
77  * The remaining running time is estimated during register merge.
78  *
79  * Revision 1.15  1995/12/07  17:38:48  adam
80  * Work locking mechanisms for concurrent updates/commit.
81  *
82  * Revision 1.14  1995/12/06  12:41:26  adam
83  * New command 'stat' for the index program.
84  * Filenames can be read from stdin by specifying '-'.
85  * Bug fix/enhancement of the transformation from terms to regular
86  * expressons in the search engine.
87  *
88  * Revision 1.13  1995/11/28  09:09:46  adam
89  * Zebra config renamed.
90  * Use setting 'recordId' to identify record now.
91  * Bug fix in recindex.c: rec_release_blocks was invokeded even
92  * though the blocks were already released.
93  * File traversal properly deletes records when needed.
94  *
95  * Revision 1.12  1995/11/24  11:31:37  adam
96  * Commands add & del read filenames from stdin if source directory is
97  * empty.
98  * Match criteria supports 'constant' strings.
99  *
100  * Revision 1.11  1995/11/22  17:19:19  adam
101  * Record management uses the bfile system.
102  *
103  * Revision 1.10  1995/11/21  15:01:16  adam
104  * New general match criteria implemented.
105  * New feature: document groups.
106  *
107  * Revision 1.9  1995/11/21  09:20:32  adam
108  * Yet more work on record match.
109  *
110  * Revision 1.8  1995/11/20  16:59:46  adam
111  * New update method: the 'old' keys are saved for each records.
112  *
113  * Revision 1.7  1995/11/20  11:56:28  adam
114  * Work on new traversal.
115  *
116  * Revision 1.6  1995/11/17  15:54:42  adam
117  * Started work on virtual directory structure.
118  *
119  * Revision 1.5  1995/10/17  18:02:09  adam
120  * New feature: databases. Implemented as prefix to words in dictionary.
121  *
122  * Revision 1.4  1995/09/28  09:19:46  adam
123  * xfree/xmalloc used everywhere.
124  * Extract/retrieve method seems to work for text records.
125  *
126  * Revision 1.3  1995/09/06  16:11:18  adam
127  * Option: only one word key per file.
128  *
129  * Revision 1.2  1995/09/04  12:33:43  adam
130  * Various cleanup. YAZ util used instead.
131  *
132  * Revision 1.1  1995/09/01  14:06:36  adam
133  * Split of work into more files.
134  *
135  */
136
137
138 #include <stdio.h>
139 #include <assert.h>
140 #include <sys/types.h>
141 #include <sys/stat.h>
142 #ifdef WIN32
143 #include <io.h>
144 #define S_ISREG(x) (x & _S_IFREG)
145 #define S_ISDIR(x) (x & _S_IFDIR)
146 #else
147 #include <unistd.h>
148 #endif
149 #include <direntz.h>
150 #include <fcntl.h>
151 #include <time.h>
152
153 #include "index.h"
154
155 static int repComp (const char *a, const char *b, size_t len)
156 {
157     if (!len)
158         return 0;
159     return memcmp (a, b, len);
160 }
161
162 static void repositoryExtractR (int deleteFlag, char *rep,
163                                 struct recordGroup *rGroup,
164                                 int level)
165 {
166     struct dir_entry *e;
167     int i;
168     size_t rep_len = strlen (rep);
169
170     e = dir_open (rep);
171     if (!e)
172         return;
173     logf (LOG_LOG, "dir %s", rep);
174     if (rep[rep_len-1] != '/')
175         rep[rep_len] = '/';
176     else
177         --rep_len;
178     
179     for (i=0; e[i].name; i++)
180     {
181         char *ecp;
182         strcpy (rep +rep_len+1, e[i].name);
183         if ((ecp = strrchr (e[i].name, '/')))
184             *ecp = '\0';
185         if (level == 0 && rGroup->databaseNamePath)
186             rGroup->databaseName = e[i].name;
187
188         switch (e[i].kind)
189         {
190         case dirs_file:
191             fileExtract (NULL, rep, rGroup, deleteFlag);
192             break;
193         case dirs_dir:
194             repositoryExtractR (deleteFlag, rep, rGroup, level+1);
195             break;
196         }
197     }
198     dir_free (&e);
199
200 }
201
202 static void fileDeleteR (struct dirs_info *di, struct dirs_entry *dst,
203                                const char *base, char *src,
204                                struct recordGroup *rGroup)
205 {
206     char tmppath[1024];
207     size_t src_len = strlen (src);
208
209     while (dst && !repComp (dst->path, src, src_len+1))
210     {
211         switch (dst->kind)
212         {
213         case dirs_file:
214             sprintf (tmppath, "%s%s", base, dst->path);
215             fileExtract (&dst->sysno, tmppath, rGroup, 1);
216              
217             strcpy (tmppath, dst->path);
218             dst = dirs_read (di); 
219             dirs_del (di, tmppath);
220             break;
221         case dirs_dir:
222             strcpy (tmppath, dst->path);
223             dst = dirs_read (di);
224             dirs_rmdir (di, tmppath);
225             break;
226         default:
227             dst = dirs_read (di);
228         }
229     }
230 }
231
232 static void fileUpdateR (struct dirs_info *di, struct dirs_entry *dst,
233                          const char *base, char *src, 
234                          struct recordGroup *rGroup,
235                          int level)
236 {
237     struct dir_entry *e_src;
238     int i_src = 0;
239     static char tmppath[1024];
240     size_t src_len = strlen (src);
241
242     sprintf (tmppath, "%s%s", base, src);
243     e_src = dir_open (tmppath);
244     logf (LOG_LOG, "dir %s", tmppath);
245
246 #if 0
247     if (!dst || repComp (dst->path, src, src_len))
248 #else
249     if (!dst || strcmp (dst->path, src))
250 #endif
251     {
252         if (!e_src)
253             return;
254
255         if (src_len && src[src_len-1] != '/')
256         {
257             src[src_len] = '/';
258             src[++src_len] = '\0';
259         }
260         dirs_mkdir (di, src, 0);
261         if (dst && repComp (dst->path, src, src_len))
262             dst = NULL;
263     }
264     else if (!e_src)
265     {
266         strcpy (src, dst->path);
267         fileDeleteR (di, dst, base, src, rGroup);
268         return;
269     }
270     else
271     {
272         if (src_len && src[src_len-1] != '/')
273         {
274             src[src_len] = '/';
275             src[++src_len] = '\0';
276         }
277         dst = dirs_read (di); 
278     }
279     dir_sort (e_src);
280
281     while (1)
282     {
283         int sd;
284
285         if (dst && !repComp (dst->path, src, src_len))
286         {
287             if (e_src[i_src].name)
288             {
289                 logf (LOG_DEBUG, "dst=%s src=%s", dst->path + src_len,
290                       e_src[i_src].name);
291                 sd = strcmp (dst->path + src_len, e_src[i_src].name);
292             }
293             else
294                 sd = -1;
295         }
296         else if (e_src[i_src].name)
297             sd = 1;
298         else
299             break;
300         logf (LOG_DEBUG, "trav sd=%d", sd);
301
302         if (level == 0 && rGroup->databaseNamePath)
303             rGroup->databaseName = e_src[i_src].name;
304         if (sd == 0)
305         {
306             strcpy (src + src_len, e_src[i_src].name);
307             sprintf (tmppath, "%s%s", base, src);
308             
309             switch (e_src[i_src].kind)
310             {
311             case dirs_file:
312                 if (e_src[i_src].mtime > dst->mtime)
313                 {
314                     if (fileExtract (&dst->sysno, tmppath, rGroup, 0))
315                     {
316                         dirs_add (di, src, dst->sysno, e_src[i_src].mtime);
317                     }
318                     logf (LOG_DEBUG, "old: %s", ctime (&dst->mtime));
319                     logf (LOG_DEBUG, "new: %s", ctime (&e_src[i_src].mtime));
320                 }
321                 dst = dirs_read (di);
322                 break;
323             case dirs_dir:
324                 fileUpdateR (di, dst, base, src, rGroup, level+1);
325                 dst = dirs_last (di);
326                 logf (LOG_DEBUG, "last is %s", dst ? dst->path : "null");
327                 break;
328             default:
329                 dst = dirs_read (di); 
330             }
331             i_src++;
332         }
333         else if (sd > 0)
334         {
335             SYSNO sysno = 0;
336             strcpy (src + src_len, e_src[i_src].name);
337             sprintf (tmppath, "%s%s", base, src);
338
339             switch (e_src[i_src].kind)
340             {
341             case dirs_file:
342                 if (fileExtract (&sysno, tmppath, rGroup, 0))
343                     dirs_add (di, src, sysno, e_src[i_src].mtime);            
344                 break;
345             case dirs_dir:
346                 fileUpdateR (di, dst, base, src, rGroup, level+1);
347                 if (dst)
348                     dst = dirs_last (di);
349                 break;
350             }
351             i_src++;
352         }
353         else  /* sd < 0 */
354         {
355             strcpy (src, dst->path);
356             sprintf (tmppath, "%s%s", base, dst->path);
357
358             switch (dst->kind)
359             {
360             case dirs_file:
361                 fileExtract (&dst->sysno, tmppath, rGroup, 1);
362                 dirs_del (di, dst->path);
363                 dst = dirs_read (di);
364                 break;
365             case dirs_dir:
366                 fileDeleteR (di, dst, base, src, rGroup);
367                 dst = dirs_last (di);
368             }
369         }
370     }
371     dir_free (&e_src);
372 }
373
374 static void groupRes (struct recordGroup *rGroup)
375 {
376     char resStr[256];
377     char gPrefix[256];
378
379     if (!rGroup->groupName || !*rGroup->groupName)
380         *gPrefix = '\0';
381     else
382         sprintf (gPrefix, "%s.", rGroup->groupName);
383
384     sprintf (resStr, "%srecordId", gPrefix);
385     rGroup->recordId = res_get (common_resource, resStr);
386     sprintf (resStr, "%sdatabasePath", gPrefix);
387     rGroup->databaseNamePath =
388         atoi (res_get_def (common_resource, resStr, "0"));
389 }
390
391 void repositoryShow (struct recordGroup *rGroup)
392 {
393     char src[1024];
394     int src_len;
395     struct dirs_entry *dst;
396     Dict dict;
397     struct dirs_info *di;
398     
399     if (!(dict = dict_open (rGroup->bfs, FMATCH_DICT, 50, 0, 0)))
400     {
401         logf (LOG_FATAL, "dict_open fail of %s", FMATCH_DICT);
402         return;
403     }
404     
405     assert (rGroup->path);    
406     strcpy (src, rGroup->path);
407     src_len = strlen (src);
408     
409     if (src_len && src[src_len-1] != '/')
410     {
411         src[src_len] = '/';
412         src[++src_len] = '\0';
413     }
414     
415     di = dirs_open (dict, src, rGroup->flagRw);
416     
417     while ( (dst = dirs_read (di)) )
418         logf (LOG_LOG, "%s", dst->path);
419     dirs_free (&di);
420     dict_close (dict);
421 }
422
423 static void fileUpdate (Dict dict, struct recordGroup *rGroup,
424                         const char *path)
425 {
426     struct dirs_info *di;
427     struct stat sbuf;
428     char src[1024];
429     char dst[1024];
430     int src_len;
431
432     assert (path);
433     strcpy (src, path);
434     src_len = strlen (src);
435
436     stat (src, &sbuf);
437     if (S_ISREG(sbuf.st_mode))
438     {
439         struct dirs_entry *e_dst;
440         di = dirs_fopen (dict, src);
441
442         e_dst = dirs_read (di);
443         if (e_dst)
444         {
445             if (sbuf.st_mtime > e_dst->mtime)
446                 if (fileExtract (&e_dst->sysno, src, rGroup, 0))
447                     dirs_add (di, src, e_dst->sysno, sbuf.st_mtime);
448         }
449         else
450         {
451             SYSNO sysno = 0;
452             if (fileExtract (&sysno, src, rGroup, 0))
453                  dirs_add (di, src, sysno, sbuf.st_mtime);
454         }
455         dirs_free (&di);
456     }
457     else if (S_ISDIR(sbuf.st_mode))
458     {
459         if (src_len && src[src_len-1] != '/')
460         {
461             src[src_len] = '/';
462             src[++src_len] = '\0';
463         }
464         di = dirs_open (dict, src, rGroup->flagRw);
465         *dst = '\0';
466         fileUpdateR (di, dirs_read (di), src, dst, rGroup, 0);
467         dirs_free (&di);
468     }
469     else
470     {
471         logf (LOG_WARN, "Ignoring path %s", src);
472     }
473 }
474
475
476 static void repositoryExtract (int deleteFlag, struct recordGroup *rGroup,
477                                const char *path)
478 {
479     struct stat sbuf;
480     char src[1024];
481
482     assert (path);
483     strcpy (src, path);
484
485     stat (src, &sbuf);
486     if (S_ISREG(sbuf.st_mode))
487         fileExtract (NULL, src, rGroup, deleteFlag);
488     else if (S_ISDIR(sbuf.st_mode))
489         repositoryExtractR (deleteFlag, src, rGroup, 0);
490     else
491         logf (LOG_WARN, "Ignoring path %s", src);
492 }
493
494 static void repositoryExtractG (int deleteFlag, struct recordGroup *rGroup)
495 {
496     if (*rGroup->path == '\0' || !strcmp(rGroup->path, "-"))
497     {
498         char src[1024];
499
500         while (scanf ("%s", src) == 1)
501             repositoryExtract (deleteFlag, rGroup, src);
502     }
503     else
504         repositoryExtract (deleteFlag, rGroup, rGroup->path);
505 }
506
507 void repositoryUpdate (struct recordGroup *rGroup)
508 {
509     groupRes (rGroup);
510     assert (rGroup->path);
511     if (rGroup->recordId && !strcmp (rGroup->recordId, "file"))
512     {
513         Dict dict;
514         if (!(dict = dict_open (rGroup->bfs, FMATCH_DICT, 50,
515                                 rGroup->flagRw, 0)))
516         {
517             logf (LOG_FATAL, "dict_open fail of %s", FMATCH_DICT);
518             return ;
519         }
520         if (*rGroup->path == '\0' || !strcmp(rGroup->path, "-"))
521         {
522             char src[1024];
523             while (scanf ("%s", src) == 1)
524                 fileUpdate (dict, rGroup, src);
525         }
526         else
527             fileUpdate (dict, rGroup, rGroup->path);
528         dict_close (dict);
529     }
530     else 
531         repositoryExtractG (0, rGroup);
532 }
533
534 void repositoryDelete (struct recordGroup *rGroup)
535 {
536     repositoryExtractG (1, rGroup);
537 }
538