/*  DISKUSE ADD, SUBSTRACT EVERYWHERE **** */
/* dbnew should subtract the whole of the old file*/

/* Copyright (C) 1995 NetWin World, New Zealand, All rights reserved. */

#include <stdlib.h>
#include <stdio.h>
#ifdef WIN32
#include <io.h>
#endif
#include <ctype.h>
#include <string.h>
#include <assert.h>
#ifdef VMS
#include <unixio.h>
#endif

#ifndef NO_FCNTL
#include <fcntl.h>
#else
#include <file.h>
#endif
#include "emsg.h"
#include "sary.h"
#include "str.h"
#include "lib.h"
#include "group.h"
#include "hist.h"
#include "suck.h"
#include "feed.h"
#include "expire.h"
#include "control.h"
#include "item.h"
#include "db.h"

/*
States we can be in:
	Appending an article to a file block.
	Re-writing a file block.

Main operations:
	Normal adding - reading  append.
	Expire			 rewrite individual blocks 
	Purge Lost		 same
	Rebuild History 	 same	
	Update			 (purge lost will do this)

can't allow write to aold formatin new format!!!

*/


enum {DB_CLOSED,DB_APPEND, DB_READ, DB_NEW};
static char *mode_name[]={"DB_CLOSED","DB_APPEND","DB_READ","DB_NEW"};
#define MAX_OPEN 1000
#ifdef WIN32
static int max_open=10;	    /* Could probably work upto 40 or so. */
#else
static int max_open=10; 	/* Could probably be 100 */
#endif
static int dbgrp[MAX_OPEN];
static int dbfg[MAX_OPEN];
static int dbf[MAX_OPEN];
static int dbmode[MAX_OPEN];
static int dbsent[MAX_OPEN];
static int ndb_open;
#define HEAD_SIZE 102
#define dbg if (1==2)

/* Each pointer is 1.2K   1MB for 1000, 4mb for 4000 */
#define MAX_CACHE 30000
static int max_cache = 1000;
Hrec *head_cache[MAX_CACHE];
static int head_item[MAX_CACHE];
static int head_fg[MAX_CACHE];
static int head_grp[MAX_CACHE];
static int head_len[MAX_CACHE];
static int head_changed[MAX_CACHE];
static int nchanged,new_grp,new_fg;


#define SIZEOFHEAD (sizeof(Hrec)*HEAD_SIZE)

#define QUELEN 10
int quelen=5;
static Hrec *que_head[QUELEN];
static int que_num[QUELEN];
static int que_grp[QUELEN];
static String *que_item[QUELEN];
static int que_state[QUELEN];
static int que_fidx[QUELEN];
static int que_fitm[QUELEN];

enum {Q_INIT,Q_OPEN,Q_WRITE,Q_CLOSE};

/*
100.idx
	[0-99] = Items
	[100].pnt = version (1),  .len = Compressed.  
100.itm
	~(len):article^
	~(len):article^
	If loose sync, search for  ^~nnn:  
*/

static int lastgrp= -1, lastfg, lastmode, lastp;
void db_init(void)
{
 	int i;
	static int done=FALSE;
	if (done) return;
	done = TRUE;
	i = atoi(config_get(C_DB_MAX));
	if (i>0) max_open = i;
	i = atoi(config_get(C_DB_INDEX));
	if (i>0) max_cache = i;
	imsg("db: db_init ok cache files %d indexes %d\n",max_open,max_cache);
	for (i=0; i<MAX_CACHE; i++) head_grp[i] = -1;
	for (i=0; i<MAX_OPEN; i++) { dbgrp[i] = -1; dbf[i] = -1;}
	new_grp = -1;
	lastgrp = -1;
}

int db_add(int grp, int num, String *xitem, int islink)
{
	char bf[BFSZ];
	int f;
	char *eoh = "\015\012\015\012";
	Hrec *h;
	char *s;
	int fg,fi,p,hp;
	static String *tmp;

      	dbg dmsg("db: dbadd %d %d\n",grp,num);

	cvt_numfile2(num,&fg, &fi);
	p = db_open(grp,fg,DB_APPEND);
	if (p<0) {emsg("db: db_open failed in add, %d %d \n",grp,fg); return FALSE;}

	sprintf(bf,"~%d:%d:",str_len(xitem),num);
	if (tmp==NULL) tmp = str_new();
	str_clearbig(tmp);

	str_copysn(tmp,bf);
	str_add(tmp,xitem);
	str_addsn(tmp,"^");
	
	hp = db_find_head_write(grp,fg);
	if (hp<0) {emsg("db: db_find_head failed in add, %d %d \n",grp,fg); return FALSE;}
	h = head_cache[hp];
	if (h==NULL) {emsg("db: head_cache is null %d %d \n",grp,fg); return FALSE;}
	if (islink) {
		h[fi].hlen = -1;
	} else {
		s = strstr(str_s(tmp),eoh);
		if (s==NULL) {emsg("item: Item has no body, will not save \n"); return FALSE;}
		h[fi].hlen = s - str_s(tmp) + 2;
	}
	h[fi].pnt = head_len[hp];
	h[fi].len = str_len(tmp);
	head_len[hp] += h[fi].len;
	head_changed[hp] = TRUE;
	nchanged++;
	if (dbmode[p]==DB_NEW) {
		db_write(max_open,tmp);
	} else {
		db_write(p,tmp);
	}
	dbsent[p]++;
	diskuse_add(str_len(tmp)); /* not counting size of index */
	str_clearbig(tmp);
	return TRUE;
}

int db_find(int grp, int fg, int isread)
{
	int i;
	static int upto;

	if (!isread) if ((grp==new_grp) && (fg==new_fg)) return max_open;

 	for (i=0; i<max_open; i++) {
		if (dbgrp[i]==grp) if (dbfg[i]==fg) return i;
	}
	upto++;
	if (upto>=max_open) upto=0;
	return upto;
} 
int db_findnum(int grp, int *xfirst, int *xlast) /* Not used, but would be faster */
{
	int i;
	int first = *xfirst;
	int last = *xlast;
	int fg,fi;
	int foundone=FALSE;
	int num = 0;
	int lastfg= -2;
	int hp;
	Hrec *h;

	*xfirst = last+1;
	for (i=first; i<=last; i++) {
		cvt_numfile2(i,&fg, &fi);
		if (fg != lastfg) {
			hp = db_find_head_read(grp,fg);
			if (hp<0) break;
	 		h = head_cache[hp];
		}
		lastfg = fg;
		if (h[fi].pnt>=0) {
			if (!foundone) *xfirst = i;
			foundone = TRUE;
			num++;
		}
	}
	return num;
}


int db_exists(int grp, int num)
{
	int fg,fi;
	int hp;
	Hrec *h;
	cvt_numfile2(num,&fg, &fi);
 	hp = db_find_head_read(grp,fg);
	if (hp<0) {imsg("hp less than zero\n"); return FALSE;}
	h = head_cache[hp];
	if (h[fi].pnt>=0) {return TRUE; }
	return FALSE;
}
static int inupdate;
int db_update(int grp,int fg)
{
	String *shead,*sbody,*tmp;
	int islink,islink2;
	int i,ffg,ffi;

	inupdate = TRUE;
	shead = str_new();
	if (db_open(grp,fg,DB_NEW)<0) return FALSE;

	for (i=fg;i<fg+100;i++) {
		cvt_numfile(i,&ffg, &ffi);
		tmp = db_get_raw(grp,i,TRUE,&islink);
		if (tmp!=NULL) str_copy(shead,tmp);
		sbody = db_get_raw(grp,i,FALSE,&islink2);
		if ((shead!=NULL) && (tmp!=NULL)) {
			item_purgewrite(grp,i,shead,sbody,islink);
		}
	}

	db_close_new(grp,fg);
	inupdate = FALSE;
	str_free(shead);
	return TRUE;
}
int db_open_new(int grp,int num)
{
	dmsg("db: db_open_new called %d %d \n",grp,num);
 	return db_open(grp,num,DB_NEW);
}
int db_open(int grp, int num, int mode)
{
	int p;
	static int failedfg,failedgrp= -1;
	int hp;
	int fg,fi;
	int f,isread;
	static char lastfail[BFSZ];
      
      	cvt_numfile2(num,&fg, &fi);

	dmsg("dbx: dbopen grp=%d=%d fg=%d=%d mode=%d=%d num=%d {%s} {%s} lastp=%d\n",grp,lastgrp
		,fg,lastfg,mode,lastmode,num,sys_gname(grp,num),mode_name[mode],lastp);

	if (mode==DB_READ) isread = TRUE; else isread = FALSE;
	
	if ((failedgrp==grp) && (failedfg==fg) && (isread)) {
		return -1;
	}
	failedgrp = -1;
	/* if (!isread) */
	if ((lastgrp==grp) && (lastfg==fg) && (lastmode==mode)) {
		p = lastp; 
		if (dbgrp[p]==grp && dbfg[p]==fg) {
			if (dbmode[p]==mode) return p;
		}
	}
	hp = db_find_head_read(grp,fg);
	if (hp<0) {dmsg("dbx: Can't open header %d %d \n",grp,fg); return -1;} /* Couldn't open index file */
	p = db_find(grp,fg,isread);
	
	lastgrp = grp; lastfg = fg; lastmode = mode; lastp = p;
	if (dbgrp[p]==grp && dbfg[p]==fg) {
		dmsg("dbx: Success, cached [%d] \n",lastp);
		if (dbmode[p]==mode) return p;
	}
	if (dbf[p]>=0) db_close(p);

	dmsg("dbx: Realopen [%d] {%s} %s \n",p,sys_gname(grp,fg),mode_name[mode]);
	if (mode==DB_APPEND) {
		f = xopen(sys_gname(grp,fg),O_WRONLY | O_APPEND | O_CREAT | O_BINARY,U_RW);
		if (f<0) {
			lib_createdir(sys_gname(grp,fg));
			f = xopen(sys_gname(grp,fg),O_WRONLY | O_APPEND | O_CREAT | O_BINARY,U_RW);
			if (f<0) {
			  filemsg("Couldn't open to write {%s} \n",sys_gname(grp,fg));
			  return -1;
			}
		}
		head_len[hp] = file_length(f);
		dbsent[p] = 0;
	} else if (mode==DB_NEW) {
		dbmode[p] = DB_CLOSED;
		dbmode[max_open] = DB_CLOSED;
		f = xopen(sys_gname(grp,fg),O_RDONLY | O_BINARY,0);
		if (f<0) { fimsg("Couldn't open to read {%s} \n",sys_gname(grp,fg)); return -1; }
		diskuse_add(-file_length(f));
		dbf[p] = f;
		f = xopen(sys_nname(grp,fg),O_WRONLY | O_APPEND | O_CREAT | O_BINARY,U_RW);
		if (f<0) { filemsg("Couldn't open to write {%s} \n",sys_nname(grp,fg)); return -1; }
		dbf[max_open] = f;
		if (head_cache[max_cache]==NULL) head_cache[max_cache] = malloc(SIZEOFHEAD);
		db_clear_head(head_cache[max_cache]);
		head_len[max_cache] = 0;
		dbsent[p] = 0;
		lastmode = -1;
		new_grp = grp;
		new_fg = fg;
		mode = DB_READ;
		dbmode[max_open] = DB_APPEND;
		dbgrp[max_open] = grp;
		dbfg[max_open] = fg;
		f = dbf[p];
    	} else if (mode==DB_READ) {
		f = xopen(sys_gname(grp,fg),O_RDONLY | O_BINARY,0);
		dbsent[p] = 0;
		if (f<0) {
			failedgrp = grp;
			failedfg = fg;
			if (strcmp(lastfail,sys_gname(grp,fg))!=0) {
				fimsg("Couldn't open to read {%s} \n",sys_gname(grp,fg));
			}
			ncpy(lastfail,sys_gname(grp,fg),BFSZ-1);
			lastp = -1;
			return -1;
		}
	}
	
	ndb_open++;
	dbmode[p] = mode;
	dbgrp[p] = grp;
	dbfg[p] = fg;
	dbf[p] = f;
	dmsg("dbx: New open OK! %d %d %s  \n",grp,fg,mode_name[mode]);
	return p;
}
int db_write(int p, String *s)
{
	int r;
	if (dbf[p]<0) {emsg("File is not open %d \n",p); return 0;}
	r = write(dbf[p],str_s(s),str_len(s));
	if (r<str_len(s)) {filemsg("Error writing article file\n"); return FALSE;}
	return TRUE;
}
int db_hash(int grp, int fg)
{
	int r =  (grp*123+fg) % max_cache;
	if (r<0) return -r;
	return r;
}
int db_close(int p)
{
	if (dbmode[p]!=DB_CLOSED) {
		if (p<0) {emsg("db file was never open \n"); return FALSE;}
		if (dbf[p]<0) {emsg("db file is already closed %d \n",p); return FALSE;}
		dmsg("db: db_close %d f=%d \n",p,dbf[p]);
		xclose(dbf[p]);
		ndb_open--;
	} else imsg("db: not open %d, why close it? \n",p);
	dbf[p] = -1;
	dbmode[p] = DB_CLOSED;
}
int db_close_new(int grp, int num)
{
	int p,hp;
	Hrec *h;
	int fg,fi;
	cvt_numfile2(num,&fg, &fi);

      	dmsg("db: close_new %d %d\n",grp,num);

	p = db_find(grp,fg,TRUE);
	hp = db_find_head_read(grp,fg);
	if (hp<0) return FALSE; /* Couldn't open index file */
	h = head_cache[hp];
	if (h==NULL) {emsg("Null header pointer\n"); return FALSE;}
	db_close(p); db_close(max_open);
	
	new_grp = -1; new_fg = -1;

	if (remove(sys_gname(grp,fg))!=0) fimsg("remove failed %s\n",sys_gname(grp,fg));
	if (head_len[max_cache]==0) {
		imsg("item: item file empty, deleting it %s\n",sys_nname(grp,fg));
		remove(sys_nname(grp,fg));
		remove(sys_iname(grp,fg));
		db_clear_head(h);
		return TRUE;
	}
	if ( rename(sys_nname(grp,fg),sys_gname(grp,fg))<0) {filemsg("db: purgeclose rename: {%s} {%s}\n",sys_nname(grp,fg),sys_gname(grp,fg));  return FALSE;}
	memcpy(h,head_cache[max_cache],SIZEOFHEAD);

	db_write_head(hp);
	return TRUE;
}
String *db_old_raw(int f, int fg, int fi, int wanthead, int *islink, Hrec *h);
String *db_old_raw(int f, int fg, int fi, int wanthead, int *islink, Hrec *h)
{
	static String *rslt;
	int offset,len,nread;
	char bf1[100],bf2[100];
	char *s;


	*islink = FALSE;
	if (rslt==NULL) rslt = str_new();
	str_clear(rslt);

   	if (h==NULL) {emsg("db: header null panic \n"); dnews_ex("Null header"); }
	if (h[fi].pnt== -1) {dmsg("db: get_raw= -1 \n"); return NULL;}
	if (wanthead) {
		offset = h[fi].pnt;
		len = h[fi].hlen;
	} else {
		/* -2, skips the blank line between header and body */
		offset = h[fi].pnt+h[fi].hlen+2;
		len = h[fi].len-h[fi].hlen-2;
	}
	if (len<0) {
		emsg("db: old-read len < 0,  %d:%d  offset %d:%d \n",fg,fi,offset,len);
		return NULL;
	}
	if ((len>10000000) || (offset<0)) {
		emsg("db: old-read len > 10mb,  %d:%d  offset %d:%d \n",fg,fi,offset,len);
		return NULL;
	}

   	if (lseek(dbf[f],offset,SEEK_SET)<0) {
   	   	filemsg("db: seek failed %d %d, offset %d \n",fg,fi,offset);
		return NULL;
	}

  	str_setlen(rslt,len+2);	
	dmsg("db: read2[%d] len %d \n",f,len);
  	nread = read(dbf[f],str_s(rslt), len);
  	str_setlen(rslt,len);	
  	if (nread<len) {
   	   	filemsg("db: old read failed %d %d, offset %d \n",fg,fi,offset);
		return NULL;
	}
	if ((len<200) && (str_s(rslt)[0] == '@')) *islink = TRUE;
	return rslt;
}

String *db_get_raw(int grp, int num,int wanthead, int *islink)
{
	static String *rslt,*rslt2;
	int fg,fi,offset,len,nread;
	char abit[BFSZ];
	int hp;
	Hrec *h;
	int f;
	char *s;
	
	cvt_numfile2(num,&fg, &fi);

	hp = db_find_head_read(grp,fg);	if (hp<0) {dmsg("db: no db_findhead\n"); return NULL;}
	f = db_open(grp,num,DB_READ); if (f<0)  {dmsg("db: no db_open\n"); return NULL;}
	h = head_cache[hp]; if (h==NULL) {emsg("head null?? \n"); return NULL;}


	dmsg("db: db_get_raw grp=%d num=%d wanthead=%d \n",grp,num,wanthead);
	*islink = FALSE;
	if (rslt==NULL) rslt = str_new();
	str_clearbig(rslt);
	if (rslt2==NULL) rslt2 = str_new();
	str_clearbig(rslt2);


	if (h[100].len!= -2) return db_old_raw(f,fg,fi,wanthead,islink,h);   /* Version 2.0 of database format */	

	dbg dmsg("item: db_get_raw2 f=%d hp=%d \n",f,hp);
   	if (h==NULL) {emsg("db: header null panic \n"); dnews_ex("Null header2"); }
	if (h[fi].pnt== -1) {dmsg("db: get_raw=-1 \n"); return NULL;}
	if (h[fi].hlen<0) {
		*islink = TRUE;
		offset = h[fi].pnt;
		len = h[fi].len;
	} else {
		if (*islink)  {
			offset = h[fi].pnt;
			len = h[fi].len;
		} else if (wanthead) {
			offset = h[fi].pnt;
			len = h[fi].hlen;
		} else {
			/* -2, skips the blank line between header and body */
			offset = h[fi].pnt+h[fi].hlen+2;
			len = h[fi].len-h[fi].hlen-2;
		}
   	}
   	if (lseek(dbf[f],offset,SEEK_SET)<0) {
   	   	filemsg("db: seek failed %d %d, offset %d \n",grp,num,offset);
		return NULL;
	}

  	if (len>100000) imsg("db: large item %s:%d size=%d \n",group_name(grp),num,len);
  	str_setlen(rslt,len+2);	
  	str_setlen(rslt2,len+2);
	dmsg("db: read[%d] len %d \n",f,len);
  	nread = read(dbf[f],str_s(rslt), len);
  	if (nread<len) {
		filemsg("Read error on group %d:%d, offset %d  wanted %d got %d \n",grp,num,offset,len,nread);
		return NULL;
	}
	if (!(*islink)) {
	  if (wanthead) {
		if (str_s(rslt)[0] != '~') {
			ncpy(abit,str_s(rslt),20);
			emsg("Starting ~ missing %s:%d, offset %d %d {%s}\n",group_name(grp),num,offset,len,abit);
			return NULL;
		}
	  } else if (str_s(rslt)[len-1] != '^') {
		ncpy(abit,str_s(rslt)+len-15,20);
		emsg("End ^ of item missing %s:%d, offset %d %d {%s} \n",group_name(grp),num,offset,len,abit);
		return NULL;
	  }
	}

	/* Strip off extra info, also deal with link a bit */
	/* ~(len):article^ */

	str_lock(rslt);
	if (*islink) {		 /* Only safe because rslt2 is already allocated !!!*/
		dmsg("db: islink {%s} \n",str_s(rslt));
		s = strchr(str_s(rslt),':'); if (s==NULL) goto return_null;
		s = strchr(s+1,':'); if (s==NULL) goto return_null;
		str_copys(rslt2,s+1,len-(s-str_s(rslt)+1));
		s = strstr(str_s(rslt2),"\015\012"); 
		if (s==NULL) {emsg("Dud link \n"); goto return_null;}
		str_setlen(rslt2,s-str_s(rslt2)+2);
	} else if (wanthead) { /* Step past length and item number */
		s = strchr(str_s(rslt),':'); if (s==NULL) goto return_null;
		s = strchr(s+1,':'); if (s==NULL) goto return_null;
		str_copys(rslt2,s+1,len-(s-str_s(rslt)+1));
	} else {
		str_copys(rslt2,str_s(rslt),len-1);
	}
	str_unlock(rslt);
	str_clearbig(rslt);

	return rslt2;
return_null: 
	str_unlock(rslt);
	return NULL;
}

String *db_get(int grp, int num,int wanthead)
{
 	static String *tmp;
	int islink;
	char bf[BFSZ];
	char *s;
	char abit[BFSZ];

	dmsg("db: db_get %d %d \n",grp,num);
	if (grp<0) return NULL; /* Just incase :-) */ 
try_again:
	tmp = db_get_raw(grp,num,wanthead,&islink);
	if (tmp==NULL) return tmp;
	if (!islink) return tmp;
	s = str_s(tmp);
	if (*s!='@') goto link_fail;
	ncpy(bf,s+1,100);
	s = strtok(bf,":");  if (s==NULL) goto link_fail;
	grp = group_find(s);
	if (grp<0) {emsg("Link points to noexistant group {%s} \n",s); return NULL;}
	s = strtok(NULL,":");  if (s==NULL) goto link_fail;
	num = atoi(s);
	dmsg("item: db_get try_again %d:%d \n",group_name(grp),num);
	goto try_again;

link_fail:
	ncpy(abit,str_s(tmp),10);
	emsg("db: @ sign missing in symbolic link %s:%d {%s}\n",group_name(grp),num,abit); 
	return NULL;  /* was return tmp and that caused a crash ??? */
}

int db_find_head_read(int grp, int fg)
{
 	return db_find_head(grp,fg,TRUE);
}
int db_find_head_write(int grp, int fg)
{
	return db_find_head(grp,fg,FALSE);
}
int db_find_head(int grp, int fg, int isread)
{
	int r;
	if (!isread) if ((grp==new_grp) && (fg==new_fg)) return max_cache;
	r = db_hash(grp,fg);
	if (head_fg[r]==fg) if (head_grp[r]==grp) return r;
	if (head_changed[r]) db_write_head(r);
	if (!db_read_head(r,grp,fg)) return -1;
	return r; 
}
int db_write_head(int r)
{
	int n;
	int idx;
	char iname[BFSZ];

    	dmsg("db: write_head  %d %s:%d \n",r,group_name(head_grp[r]),head_fg[r]);
	if (r<0) {emsg("Write head bad value %d \n",r); return FALSE;}
	strcpy(iname,sys_iname(head_grp[r],head_fg[r]));
	idx = open_log(iname,O_WRONLY | O_CREAT | O_BINARY,U_RW);
	if (idx<0) {filemsg("fatal: Can't write index file {%s}\n",iname); return FALSE;}
	n = write(idx,head_cache[r],SIZEOFHEAD); 
	if (n<SIZEOFHEAD) {filemsg("Can't write whole index file {%s}\n",iname); xclose(idx); return FALSE;}
	xclose(idx);
	free(head_cache[r]); 	head_cache[r] = NULL;
	head_grp[r] = -1;
	head_changed[r] = FALSE;
	return TRUE;
}
int db_clear_head(Hrec *h)
{
	int i;
	if (h==NULL) {emsg("Panic, clear head to null header??\n"); return FALSE;}
	for (i=0; i<HEAD_SIZE; i++) {
		h[i].len = -1;
		h[i].pnt = -1;
		h[i].hlen = -1;
	}
	h[100].len = -2;  /* Version 2.0 of database format */	
}
int db_read_head(int r, int grp, int fg)
{
	int idx;
	char iname[BFSZ];
	Hrec *h;
	int n;

    	dmsg("db: Actual read_head[%d] %d %d\n",r,grp,fg);

	head_grp[r] = grp;
	head_fg[r] = fg;
	head_changed[r] = FALSE;
	strcpy(iname,sys_iname(head_grp[r],head_fg[r]));
	if (head_cache[r]!=NULL) {free(head_cache[r]); 	head_cache[r] = NULL;}

	head_cache[r] = malloc(SIZEOFHEAD);
	if (head_cache[r]==NULL) {emsg("Mem excceeded in read_head\n"); return FALSE;}
	h = head_cache[r];
	db_clear_head(h);

	idx = open_log(iname,O_RDONLY | O_BINARY,U_RW);
	if (idx<0) { /* Doesn't exist yet probably */
		return TRUE;
	}
	dmsg("db: read_head  %d %s:%d size=%d\n",r,group_name(head_grp[r]),head_fg[r],SIZEOFHEAD);
	n = read(idx,h,SIZEOFHEAD);
	if (n<SIZEOFHEAD) {
		xclose(idx);
		filemsg("Error reading index file {%s} \n",iname); return FALSE;
	}

	xclose(idx);
	if (!inupdate && (h[100].len!= -2)) {
		imsg("Update %s %d\n",group_name(grp),fg);
		db_update(grp,fg);
		return db_read_head(r,grp,fg);
	}
	return TRUE;
}

int db_flush_all(void) /* Call flush, but don't close */
{
	int i;
	time_show();
	
    	dbg dmsg("db: flush2\n");

	for (i=0; i<max_open; i++) {
		if (dbmode[i]==DB_APPEND) {
			db_close(i);
		}
	}
	for (i=0; i<max_cache; i++) {
	 	if (head_changed[i]) db_write_head(i);
	}
	return TRUE;
}
