Files correlati : tutti Ricompilazione Demo : [ ] Commento : Corretta gestione focus indesiderato sulla prima riga di uno sheet con una sola riga nuova git-svn-id: svn://10.65.10.50/trunk@18397 c028cbd2-c16b-5b4b-a496-9718f37d4682
		
			
				
	
	
		
			1171 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1171 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
|    Attenzione!. Per costruire la DLL accertarsi che siano attivati i seguenti switch:
 | |
|    1) S4DLL_BUILD definito da command line del compilatore
 | |
|    2) Uno solo tra S4FOX S4CLIPPER S4NDX e S4MDX, definito in d4all.h
 | |
| 
 | |
|    Gli altri switch di configurazione generale sono a piacere (S4DEBUG, S4ERROR_HOOK, ecc.)
 | |
| 
 | |
|    Per costruire WINUNO.LIB Accertarsi che d4all.h non abbia definito S4DLL o S4UNIX,
 | |
|    e tanto meno S4DLL_BUILD, altrimenti per costruire la library di UNIX e' necessario
 | |
|    cambiare ogni volta. La definizione di tali simboli avviene qui dentro.
 | |
|    Inoltre deve essere attivo uno solo degli switch di selezione formato database
 | |
|    (S4FOX, S4MDX ecc.); nel caso si faccia uso della DLL non importa quale si e' definito.
 | |
|    E' importante solo in caso di utilizzo di una static Library.
 | |
| 
 | |
| */
 | |
| 
 | |
| #define XVT_INCL_NATIVE
 | |
| #include <xvt.h> 
 | |
| #define S4OFF_REPORT
 | |
| 
 | |
| #if XVT_OS == XVT_OS_WIN32
 | |
|   #define S4DLL
 | |
|   #define S4WIN32
 | |
| #endif
 | |
| 
 | |
| #include <d4all.h>
 | |
| #include <codeb.h>
 | |
| #include <rectypes.h>
 | |
| #include <progind.h>
 | |
| 
 | |
| /*--------------------------------------------------------------------------
 | |
|   numero massimo di database aperti contemporaneamente
 | |
|   --------------------------------------------------------------------------*/
 | |
| #define CB4FILES 64
 | |
| 
 | |
| #define  MAXLEN 137     /* Lunghezza massima chiave  */
 | |
| 
 | |
| static CODE4 code_base;
 | |
| static DATA4* dbdata[CB4FILES];
 | |
| static bool dbfast[CB4FILES];
 | |
| 
 | |
| #define HANDLE2DATA(handle, data)    DATA4* data = dbdata[handle]; if (handle < 0 || handle >= CB4FILES || data == NULL || data->clientId < 0) return -1
 | |
| #define HANDLE2DATASTR(handle, data) DATA4* data = dbdata[handle]; if (handle < 0 || handle >= CB4FILES || data == NULL || data->clientId < 0) return NULL
 | |
| 
 | |
| static const char* find_slash_backslash(const char* s)
 | |
| {
 | |
|   const char* slash = NULL;
 | |
|   for ( ; *s; s++)
 | |
|   {
 | |
|     if (*s == '\\' || *s == '/')
 | |
|       slash = s;
 | |
|   }
 | |
|   return slash;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*--------------------------------------------------------------------------
 | |
|   inizializzazione di CodeBase e delle variabili necessarie
 | |
|   questa funzione deve essere chiamata SOLO una volta all'interno
 | |
|   di ciascun eseguibile
 | |
|   --------------------------------------------------------------------------*/
 | |
| void DB_init(void)
 | |
| {
 | |
|   memset(dbdata, 0, sizeof(dbdata));
 | |
| 
 | |
|   code4init(&code_base);
 | |
|   // Nella 3.0 il default LOCK4DATA e' stato messo nella DLL per non ridistribuire tutto.
 | |
|   // Nelle versioni succesive la DLL e' "intonsa" ed ha un default diverso deciso da Codebase.
 | |
|   // Per cui ora forziano LOCK4DATA (anche se considerato deprecated)
 | |
|   // in quanto non funziona bene LOCK4RECORD (che sarebbe quello giusto)
 | |
|   code4unlockAutoSet(&code_base, LOCK4DATA);
 | |
|   code_base.readLock=0;
 | |
|   code_base.errDefaultUnique=e4unique;
 | |
|   code_base.safety=0;
 | |
|   code_base.lockAttempts=1;
 | |
|   code4dateFormatSet(&code_base, "CCYYMMDD");
 | |
| 
 | |
|   code_base.optimize      = OPT4EXCLUSIVE;  // Is the default?
 | |
|   code_base.optimizeWrite = OPT4EXCLUSIVE;  // Is the default?
 | |
| }
 | |
| 
 | |
| /*--------------------------------------------------------------------------
 | |
|   reset di CodeBase
 | |
|   questa funzione dovrebbe essere chiamata prima di uscire da un eseguibile
 | |
|   per una questione di correttezza formale. Non usandola comunque non acca-
 | |
|   de niente di drammatico
 | |
|   --------------------------------------------------------------------------*/
 | |
| void DB_exit(void)
 | |
| {
 | |
|   int i;
 | |
|   for (i = 0; i < CB4FILES; i++) 
 | |
|   {
 | |
|     if (dbdata[i] != NULL)
 | |
|       DB_close(i);
 | |
|   }
 | |
|   code4initUndo(&code_base);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   apertura del file 'filename'. Il parametro mode consente se != 0 l'apertura
 | |
|   esclusiva. Il parametro index consente se == 0 l'apertura senza indici
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_open(const char *filename,int mode,int index)
 | |
| {
 | |
|   int i,found;
 | |
|   /* cerca il primo posto libero nel vettore dbdata */
 | |
|   found=-1;
 | |
|   for(i=0;i<CB4FILES;i++) 
 | |
|   {
 | |
|     if(dbdata[i]==(DATA4*)NULL) 
 | |
|     {
 | |
|       found=i;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   if (found >= 0) 
 | |
|   {
 | |
|     if (mode) 
 | |
|       code_base.accessMode=1;  // Exclusive mode
 | |
|     
 | |
|     code_base.errorCode=0;
 | |
|     if (!index) 
 | |
|     {
 | |
|       code_base.autoOpen = 0; // Se e' stata richiesta l'apertura senza indici, resetta il flag autoOpen
 | |
|       dbdata[found]=d4open(&code_base, filename);
 | |
|       code_base.autoOpen = 1;
 | |
|     }
 | |
|     else
 | |
|       dbdata[found]=d4open(&code_base, filename);
 | |
|     
 | |
|     if (mode)
 | |
|       code_base.accessMode=0; // Not-Exclusive mode
 | |
| 
 | |
|     if(dbdata[found]!=NULL) 
 | |
|     {
 | |
|       HANDLE2DATA(found, data);
 | |
|       
 | |
|       // Guy: Non capisco bene perche' seleziono comunque un indice
 | |
|       d4tagSelect(data, d4tagDefault(data));
 | |
|       if (d4recCount(data) > 0)
 | |
|         d4top(data);
 | |
| 
 | |
|       dbfast[found] = mode!=0; // Ottimizabile se esclusivo
 | |
|     }
 | |
|     else
 | |
|       return code_base.errorCode;
 | |
|   }
 | |
|   return found;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   chiusura del database inviduato da handle
 | |
|   torna -1 se il database non puo essere chiuso
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_close(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
| 
 | |
|   d4close(data);
 | |
|   dbdata[handle] = NULL;
 | |
|   dbfast[handle] = FALSE;
 | |
|   code_base.errorCode=0;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   torna il puntatore al buffer del record del database individuato da
 | |
|   handle. In caso di errore torna (char *) 0
 | |
|   --------------------------------------------------------------------------*/
 | |
| char* DB_getrecord(int handle)
 | |
| {
 | |
|   HANDLE2DATASTR(handle, data);
 | |
|   return d4record(data);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   torna la lunghezza del record
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_reclen(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return (int)d4recWidth(data);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   torna la lunghezza della chiave corrente
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_keylen(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return a4tagKeyLen(data);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   torna il numero del record attuale
 | |
|   --------------------------------------------------------------------------*/
 | |
| long int DB_recno(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4recNo(data);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   torna il numero complessivo dei records presenti nell'archivio
 | |
|   --------------------------------------------------------------------------*/
 | |
| long int DB_reccount(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4recCount(data);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   seleziona un indice sul database specificato
 | |
|   torna -1 se errore, altrimenti 0
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_tagselect(int handle,int index_no)
 | |
| {
 | |
|   TAG4 *tt;
 | |
|   int i;
 | |
| 
 | |
|   HANDLE2DATA(handle, data);
 | |
|   /* si posiziona sul primo indice */
 | |
|   tt=d4tagNext(data, NULL);
 | |
|   if (tt==NULL)
 | |
|     return -1;
 | |
|   for(i=1; i<index_no; i++) 
 | |
|   {
 | |
|     tt=d4tagNext(data, tt);
 | |
|     if(tt==NULL) 
 | |
|       return(-1);
 | |
|   }
 | |
|   d4tagSelect(data, tt);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   torna il numero dell'indice selezionato
 | |
|   torna -1 se errore
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_tagget(int handle)
 | |
| {
 | |
|   TAG4 *tt,*tt1;
 | |
|   int i;
 | |
|   HANDLE2DATA(handle, data);
 | |
|   
 | |
|   /* si posiziona sul primo indice */
 | |
|   tt=d4tagDefault(data);
 | |
|   if(tt==NULL) 
 | |
|     return(-1);
 | |
| 
 | |
|   tt1=d4tagNext(data,NULL);
 | |
|   i=1;
 | |
|   while(tt!=tt1 && tt1!=NULL) 
 | |
|   {
 | |
|     tt1=d4tagNext(data,tt1);
 | |
|     i++;
 | |
|   }
 | |
|   return(i);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   si posiziona sul primo record
 | |
|   torna -1 se errore
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_first(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4top(data);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   si posiziona sull'ultimorecord
 | |
|   torna -1 se errore
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_last(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4bottom(data);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   skip avanti di un record
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_next(int handle)
 | |
| {
 | |
|   return DB_skip(handle, +1);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   skip indietro di un record
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_prev(int handle)
 | |
| {
 | |
|   return DB_skip(handle, -1);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   skip di n records
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_skip(int handle, long recno)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4skip(data, recno);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   locka il record attuale
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_lock(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4lock(data, d4recNo(data));
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   slocka il record attuale
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_unlock(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4unlock(data);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   cerca la chiave, torna r4after se not found ma si e' posizionato sul record
 | |
|   successivo, se torna r4eof e' su eof
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_seek(int handle,char *key)
 | |
| {
 | |
|   int rc, len;
 | |
|   const char * k;
 | |
| 
 | |
|   HANDLE2DATA(handle, data);
 | |
|   rc = d4seek(data,key);
 | |
|   if (rc)
 | |
|     return rc;
 | |
|   len = a4tagKeyLen(data);
 | |
|   k = a4tagKey(data);
 | |
|   while (len > 0 && k[len-1] == ' ') len--;
 | |
|   rc = strncmp(key, k, len);
 | |
|   if (rc == 0)
 | |
|     return 0;
 | |
|   else
 | |
|     if (rc < 0)
 | |
|       return r4after;
 | |
|     else
 | |
|       return r4eof;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   torna 1 se eof, 0 altrimenti
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_eof(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4eof(data);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   torna 1 se bof, 0 altrimenti
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_bof(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4bof(data);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   legge un record per numero record
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_go(int handle,long recno)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4go(data,recno);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   cancella il record attuale. Non cancella le chiavi.
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_delete(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   d4delete(data);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   ripristina il record attuale
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_recall(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   d4recall(data);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_flush(int handle)
 | |
| {
 | |
|   int rt = 0;
 | |
|   HANDLE2DATA(handle, data);
 | |
| 
 | |
|   if (dbfast[handle])
 | |
|   {
 | |
|     if (data->dataFile->nFieldsMemo > 0)
 | |
|       rt = d4write(data, -1);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     while ((rt = d4flush(data)) == r4locked)
 | |
|       u4delaySec();
 | |
|   }
 | |
|   return rt;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   riscrive il record attuale
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_rewrite(int handle)
 | |
| {
 | |
|   int rt = -1;
 | |
|   HANDLE2DATA(handle, data);
 | |
|   while ((rt=d4write(data,d4recNo(data))) == r4locked)
 | |
|     u4delaySec();
 | |
| 
 | |
|   if (rt == e4unique)
 | |
|   {
 | |
|     char msg[256];
 | |
|     DB_get_error();
 | |
|     sprintf(msg, "Errore in DB_rewrite(): chiave  duplicata nel record %ld, file %s",
 | |
|             d4recNo(data) + 1, d4fileName(data));
 | |
|     xvt_dm_post_fatal_exit(msg);
 | |
|   }
 | |
| 
 | |
|   if (rt == 0)
 | |
|     rt = DB_flush(handle);
 | |
|   rt = DB_unlock(handle);
 | |
|   return (rt);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   appende il record attuale
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_add(int handle)
 | |
| {
 | |
|   int rt = -1;
 | |
|   HANDLE2DATA(handle, data);
 | |
| 
 | |
|   while ((rt = d4appendStart(data,0)) == r4locked)
 | |
|     u4delaySec();
 | |
| 
 | |
|   if (rt == 0)
 | |
|   {
 | |
|     d4recall(data);
 | |
|     while ((rt = d4append(data)) == r4locked)
 | |
|       u4delaySec();
 | |
|     if (rt == e4unique)
 | |
|     {
 | |
|       const long rec_num = d4recNo(data);
 | |
|       DB_get_error();
 | |
|       if (rec_num > 0)
 | |
|       {
 | |
|         char msg[256];
 | |
|         sprintf(msg, "Errore in DB_add(): chiave duplicata nell' indice %ld, file %s",
 | |
|                      rec_num + 1, d4fileName(data));
 | |
|         xvt_dm_post_fatal_exit(msg);
 | |
|       }
 | |
|       else
 | |
|         rt = _isreinsert;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (rt == 0)
 | |
|         rt = DB_flush(handle);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return rt;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Blocca in modo esclusivo il file dati ed indice
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_lockfile(int handle)
 | |
| {
 | |
|   int rt = -1;
 | |
|   HANDLE2DATA(handle, data);
 | |
|   rt = d4lockFile(data);
 | |
|   if (rt==0) 
 | |
|     rt = d4lockIndex(data);
 | |
|   return rt;
 | |
| }
 | |
| 
 | |
| HIDDEN void do_key(const char* fname, const RecDes* r, TAG4INFO* tag_info, int ntags)
 | |
| {
 | |
|   const int nkeys = min(r->NKeys, ntags);
 | |
|   int i,j;
 | |
|   char tiname[_MAX_FNAME];      /* Tag name */
 | |
|   xvt_fsys_parse_pathname(fname, NULL, NULL, tiname, NULL, NULL);
 | |
|   xvt_str_make_upper(tiname);
 | |
| 
 | |
|   for (i=0; i < nkeys; i++)
 | |
|   {
 | |
|     tag_info[i].name=(char *)u4alloc(_MAX_FNAME);
 | |
|     tag_info[i].expression=(char *)u4alloc(256);
 | |
|     tag_info[i].filter=(char*)u4alloc(32);
 | |
|     tag_info[i].descending=0;
 | |
|     if (r->Ky[i].DupKeys)
 | |
|       tag_info[i].unique=0;
 | |
|     else
 | |
|       tag_info[i].unique= i == 0 ? e4unique : r4unique_continue;
 | |
|     strcpy((char *)tag_info[i].filter,".NOT. DELETED()"); /* Not available for DBIII and CLIPPER */
 | |
|     strcpy((char *)tag_info[i].name,tiname) ;
 | |
|     strcat((char *)tag_info[i].name," ");
 | |
|     tag_info[i].name[strlen(tag_info[i].name)-1] = '0' + i + 1;
 | |
|     for (j=0; j < r->Ky[i].NkFields; j++)
 | |
|     {
 | |
|       int nf= r->Ky[i].FieldSeq[j];
 | |
|       if (nf > MaxFields)                /* When Upper field is specified */
 | |
|       {
 | |
|         nf -= MaxFields;
 | |
|         strcat((char *) tag_info[i].expression,"UPPER(");
 | |
|       }
 | |
|       if (r->Ky[i].FromCh[j] != 255)     /* When partial field is specified   */
 | |
|         strcat((char *)tag_info[i].expression,"SUBSTR(");
 | |
| 
 | |
|       switch (r->Fd[nf].TypeF)           /* When numeric field in key is specified */
 | |
|       {
 | |
|       case _intfld:
 | |
|       case _longfld:
 | |
|       case _realfld:
 | |
|       case _wordfld:
 | |
|       case _intzerofld:
 | |
|       case _longzerofld:
 | |
|         strcat((char *)tag_info[i].expression,"STR(");
 | |
|         break;
 | |
|       case _datefld:
 | |
|         strcat((char *)tag_info[i].expression,"DTOS(");
 | |
|         break;
 | |
|       case _boolfld:
 | |
|         strcat((char *)tag_info[i].expression,"IIF(");  /* Logical fields are in key too.. */
 | |
|         break;
 | |
|       default:                        /* It's a non sense to keep _realfld in key... */
 | |
|         break;                        /* however it's possible to have it... */
 | |
|       }                               /* Le chiavi composte da campi data non necessitano di funzioni di traduzione. */
 | |
| 
 | |
|       strcat((char *)tag_info[i].expression,r->Fd[nf].Name); /* Append field name */
 | |
| 
 | |
|       switch (r->Fd[nf].TypeF)                 /* If numeric field was specified  */
 | |
|       {                                        /* add parameters to STR */
 | |
|       case _intfld:
 | |
|       case _longfld:
 | |
|       case _realfld:                           /* Questo tipo di campo(real) non ha senso in un a chiave... */
 | |
|       case _wordfld:
 | |
|       case _intzerofld:
 | |
|       case _longzerofld:
 | |
|       {
 | |
|         char ts[8];
 | |
|         sprintf(ts,"%d",r->Fd[nf].Len);
 | |
|         strcat((char *)tag_info[i].expression,",");
 | |
|         strcat((char *)tag_info[i].expression,ts);
 | |
|         strcat((char *)tag_info[i].expression,",0)");
 | |
|       }
 | |
|       break;
 | |
|       case _boolfld:
 | |
|         strcat((char *)tag_info[i].expression,",\"T\",\"F\")");
 | |
|         break;
 | |
|       default:
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       /* Close parentheses if UPPER or DTOS operators were used: */
 | |
|       if (r->Ky[i].FieldSeq[j] > MaxFields || (r->Fd[nf].TypeF == _datefld))
 | |
|         strcat((char *)tag_info[i].expression,")");
 | |
|       if (r->Ky[i].FromCh[j] != 255)                 /* If partial field was specified */
 | |
|       {                                              /* add parameters to SUBSTR */
 | |
|         char ts[8];
 | |
| 
 | |
|         strcat((char *)tag_info[i].expression,",");
 | |
|         sprintf(ts,"%d",r->Ky[i].FromCh[j] + 1);
 | |
|         strcat((char *)tag_info[i].expression,ts);
 | |
|         strcat((char *)tag_info[i].expression,",");
 | |
|         sprintf(ts,"%d",r->Ky[i].ToCh[j] - r->Ky[i].FromCh[j] + 1);
 | |
|         strcat((char *)tag_info[i].expression,ts);
 | |
|         strcat((char *)tag_info[i].expression,")");
 | |
|       }
 | |
|       /* If there's another field in key adds "+" operator: */
 | |
|       if ((j < (r->Ky[i].NkFields-1)) && (strlen(r->Fd[nf].Name) > 0))
 | |
|         strcat((char *)tag_info[i].expression,"+");
 | |
|     }
 | |
|   }
 | |
|   tag_info[i].name=NULL;
 | |
|   tag_info[i].expression=NULL;
 | |
|   tag_info[i].filter=NULL;
 | |
|   tag_info[i].unique=0;
 | |
|   tag_info[i].descending=0;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Compatta il file dati
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_packfile(short vis, const char* filename, long eod)
 | |
| {
 | |
|   int rt=0,handle;
 | |
| 
 | |
|   code_base.autoOpen = 0;
 | |
|   handle=DB_open(filename,1,0); /* Exclusive mode open! */
 | |
|   if (handle >= 0)
 | |
|   {
 | |
|     long rc = 0L;
 | |
|     if (vis)
 | |
|     {
 | |
|       char s[_MAX_PATH];
 | |
|       strcpy(s,"Compattamento ");
 | |
|       strcat(s, filename);
 | |
|       progind_create(1,s,0,0,60);
 | |
|     }
 | |
|     rc = d4recCount(dbdata[handle]);
 | |
|     if (eod < rc)
 | |
|       rt=d4zap(dbdata[handle],++eod,rc);
 | |
|     else
 | |
|       rt=d4pack(dbdata[handle]);
 | |
|     if (vis)
 | |
|       progind_destroy();
 | |
|     DB_close(handle);
 | |
|   }
 | |
|   else
 | |
|     rt=code_base.errorCode;
 | |
|   code_base.autoOpen = 1;
 | |
|   return rt;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Compatta il file dati
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_packmemo(short vis, const char * filename)
 | |
| {
 | |
|   int rt=0,handle;
 | |
| 
 | |
|   code_base.autoOpen = 0;
 | |
|   handle=DB_open(filename,1,0); /* Exclusive mode open! */
 | |
|   if (handle > -1)
 | |
|   {
 | |
|     if (vis)
 | |
|     {
 | |
|       char s[256];
 | |
|       strcpy(s, "Compattamento memo file : ");
 | |
|       strcat(s, filename);
 | |
|       progind_create(100L,s,0,0,60);
 | |
|     }
 | |
|     rt=d4memoCompress(dbdata[handle]);
 | |
|     if (vis)
 | |
|       progind_destroy();
 | |
|     DB_close(handle);
 | |
|   }
 | |
|   else
 | |
|     rt=code_base.errorCode;
 | |
|   code_base.autoOpen = 1;
 | |
|   return rt;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Elimina i record duplicati
 | |
|   --------------------------------------------------------------------------*/
 | |
| static int DB_clean_file(int handle, const char* filename, 
 | |
|                          const char* tagname, const RecDes* r, short vis)
 | |
| {
 | |
|   TAG4INFO tags[2];
 | |
|   TAG4 * t;
 | |
|   char s[256], s0[256];
 | |
|   int l = 0, lt = 0, rt = 0;
 | |
|   long cnt = 0;
 | |
|   INDEX4 *w = NULL;
 | |
|   long items = DB_reccount(handle);
 | |
| 
 | |
|   if (items == 0)
 | |
|     return 0;
 | |
| 
 | |
|   s[0] = '\0';
 | |
|   memset(tags, 0, sizeof(tags));
 | |
|   do_key(tagname, r, tags, 1);
 | |
|   strcat((char *) tags[0].expression, "+STR(RECNO(),9)");
 | |
|   w = i4create(dbdata[handle],(char*)filename,tags);
 | |
|   u4free((char *) tags[0].name);
 | |
|   u4free((char *) tags[0].expression);
 | |
|   u4free((char *) tags[0].filter);
 | |
|   if (w == NULL) return code_base.errorCode;
 | |
|   t = d4tagDefault(dbdata[handle]);
 | |
|   lt = expr4len(t->tagFile->expr);
 | |
|   l = lt - 9;
 | |
|   if (vis)
 | |
|     progind_create(items,"Ricerca record duplicati",0,1,60);
 | |
| 
 | |
|   rt = tfile4bottom(t->tagFile);
 | |
| 
 | |
|   while (code_base.errorCode == 0)
 | |
|   {
 | |
|     strncpy(s0, a4tagKey(dbdata[handle]), lt);
 | |
| 
 | |
|     if (vis)
 | |
|       progind_set_status(++cnt);
 | |
| 
 | |
|     if (!strncmp(s, s0, l))
 | |
|     {
 | |
|       d4go(dbdata[handle],tfile4recNo(t->tagFile));
 | |
|       d4delete(dbdata[handle]);
 | |
|       tfile4seek(t->tagFile, s0, lt);
 | |
|     }
 | |
|     strncpy(s, s0, lt);
 | |
|     if (tfile4skip(t->tagFile, -1L) >= 0)
 | |
|       break;
 | |
| 
 | |
|   } // while
 | |
|   rt = code_base.errorCode;
 | |
|   if (vis)
 | |
|     progind_destroy();
 | |
| 
 | |
|   i4close(w);
 | |
|   return rt;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Compatta gli indici
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| static int DB_yesnobox(const char* msg)
 | |
| {
 | |
|   return xvt_dm_post_ask("Si", "No", NULL, msg) == RESP_DEFAULT;
 | |
| }
 | |
| 
 | |
| int DB_packindex(short vis, const char* filename, const RecDes *r, long *peod, bool ask)
 | |
| {
 | |
|   int handle = 0, rt = 0;
 | |
|   char s[256];
 | |
|   strcpy(s,"Ricostruzione indici file : ");
 | |
|   strcat(s,filename);
 | |
| 
 | |
|   code_base.autoOpen=0 ;
 | |
|   handle=DB_open(filename,1,0); /* Exclusive mode open */
 | |
|   if (handle >= 0)
 | |
|   {
 | |
|     TAG4INFO tags[MaxKeys+1];
 | |
|     INDEX4 * w = NULL;
 | |
|     int i = 0;
 | |
|     const char *ff = find_slash_backslash(filename);
 | |
|     if (vis)
 | |
|       progind_create(1,s,0,1,60);
 | |
|     if ((ff == NULL) || *ff == '\0')
 | |
|       ff = (const char*)filename;
 | |
|     else
 | |
|       ff++;
 | |
|     memset(tags, 0, sizeof(tags));
 | |
|     do_key(ff, r, tags, r->NKeys);
 | |
| 
 | |
|     w = i4create(dbdata[handle],NULL,tags);
 | |
|     
 | |
|     if (vis)
 | |
|       progind_destroy();
 | |
|     
 | |
|     if (w == NULL) 
 | |
|       rt = code_base.errorCode;
 | |
|     if (rt == e4unique || rt == r4unique)
 | |
|     {
 | |
|       rt = 0;
 | |
|       if (!ask || DB_yesnobox("Sono stati rilevati alcuni records duplicati:\nsi desidera eliminarli?")) 
 | |
|         rt = DB_clean_file(handle, filename, ff, r, vis);
 | |
|       else
 | |
|         tags[0].unique = r4unique_continue;
 | |
|       if (rt == 0)
 | |
|       {
 | |
|         if (vis)
 | |
|           progind_create((long)r->NKeys,s,0,1,60);
 | |
|         w = i4create(dbdata[handle],filename,tags);
 | |
|         if (w == NULL) rt = code_base.errorCode;
 | |
|         if (vis)
 | |
|         {
 | |
|           progind_set_status((long)r->NKeys);
 | |
|           progind_destroy();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     for (i=0; i < r->NKeys && tags[i].name; i++)
 | |
|     {
 | |
|       u4free((char *) tags[i].name);
 | |
|       u4free((char *) tags[i].expression);
 | |
|       u4free((char *) tags[i].filter);
 | |
|     }
 | |
|     *peod=DB_reccount(handle);
 | |
|     DB_close(handle);
 | |
|   }
 | |
|   code_base.autoOpen = 1;
 | |
| 
 | |
| #ifdef DBG
 | |
|   strcpy(s, filename);
 | |
|   strcat(s, ".cdx");
 | |
|   if (xvt_fsys_file_attr(s, XVT_FILE_ATTR_SIZE) == 0)
 | |
|   {
 | |
|     strcat(s, " e' appena stato sputtanato!");
 | |
|     xvt_dm_post_error(s);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return(rt);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   costruisce il file filename
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_build(const char* filename, const RecDes* r)
 | |
| {
 | |
|   FIELD4INFO field_info[MaxFields+1]; /* Numero di campi in un record */
 | |
|   TAG4INFO   tag_info[MaxKeys+1];     /* Numero di chiavi in un file  */
 | |
|   DATA4      *dbuilded = NULL;
 | |
|   int rt=0,i;
 | |
|   const char *ff = find_slash_backslash(filename);
 | |
| 
 | |
|   memset(field_info, 0, sizeof(field_info));
 | |
|   memset(tag_info,   0, sizeof(tag_info));
 | |
| 
 | |
|   for (i=0; ((i<r->NFields) && (i<MaxFields)); i++) /* Construct field_info */
 | |
|   {
 | |
|     field_info[i].name = (char*)r->Fd[i].Name;
 | |
|     field_info[i].len  = r->Fd[i].Len;
 | |
|     field_info[i].dec  = r->Fd[i].Dec;
 | |
|     switch (r->Fd[i].TypeF)
 | |
|     {
 | |
|     case _intfld:
 | |
|     case _longfld:
 | |
|     case _realfld:     /* It's a non sense to keep this in key! */
 | |
|     case _wordfld:
 | |
|     case _intzerofld:
 | |
|     case _longzerofld:
 | |
|       field_info[i].type=r4num;
 | |
|       break;
 | |
|     case _boolfld:
 | |
|       field_info[i].type=r4log;
 | |
|       break;
 | |
|     case _datefld:
 | |
|       field_info[i].type=r4date;
 | |
|       break;
 | |
|     case _memofld:
 | |
|       field_info[i].type=r4memo;
 | |
|       field_info[i].len = 10;
 | |
|       field_info[i].dec = 0;
 | |
|       break;
 | |
|     case _charfld:
 | |
|     default:
 | |
|       field_info[i].type=r4str;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   field_info[i].name=NULL;
 | |
|   field_info[i].type=0;
 | |
|   field_info[i].len=0;
 | |
|   field_info[i].dec=0;
 | |
| 
 | |
|   if (ff == NULL || *ff == '\0')
 | |
|     ff = filename;
 | |
|   else
 | |
|     ff++;
 | |
| 
 | |
|   do_key(ff,r,tag_info, MaxKeys);
 | |
| 
 | |
|   if ((dbuilded=d4create(&code_base, filename, field_info,  tag_info))==0) /* deve solo creare il file dati vuoto */
 | |
|     rt=code_base.errorCode;
 | |
|   else
 | |
|     rt=d4close(dbuilded);
 | |
| 
 | |
|   if (rt!=0)
 | |
|     rt=code_base.errorCode;
 | |
| 
 | |
|   for (i=0; ((i < MaxKeys) && (i < r->NKeys)); i++)
 | |
|   {
 | |
|     u4free((char *) tag_info[i].name);
 | |
|     u4free((char *) tag_info[i].expression);
 | |
|     u4free((char *) tag_info[i].filter);
 | |
|   }
 | |
|   return(rt) ;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   reperisce il tracciato record e la stringa di definizione delle chiavi
 | |
|   Stringa di definizione chiavi:
 | |
|   expression1|unique1$expression2|unique2$expression3|unique3
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_recinfo(const char * filename, FileDes *d, RecDes *r, char* keys) 
 | |
| {
 | |
|   /* filename must not have extension since it's used to search index name too*/
 | |
|   FIELD4INFO *field_info; /* Definizione del tracciato record */
 | |
|   TAG4INFO   *tag_info;   /* Definizione delle chiavi  */
 | |
|   INDEX4     *index_file; 
 | |
|   DATA4      *data_file;
 | |
|   int        rt=0,num_fields,i;
 | |
|   
 | |
|   data_file = d4open(&code_base, (char*)filename);
 | |
|   if (data_file != NULL)
 | |
|   {
 | |
|     field_info = d4fieldInfo(data_file);
 | |
|     index_file = d4index(data_file,"");
 | |
|     if (index_file == NULL)
 | |
|     {
 | |
|       char msg[256];
 | |
|       sprintf(msg, "Il file %s e' senza indici.",filename);
 | |
|       xvt_dm_post_fatal_exit(msg);
 | |
|     }
 | |
|     tag_info   = i4tagInfo(index_file);
 | |
|     d->EOD = d->EOX = d4recCount(data_file);
 | |
|     d->LenR = (word)d4recWidth(data_file);
 | |
|     d->Flags = 0L;
 | |
|     strcpy(d->Des,"File esterno");
 | |
|     strcpy(d->FCalc,"");
 | |
|     strcpy(d->GenPrompt,"");
 | |
|     if (field_info != NULL && tag_info != NULL)
 | |
|     {
 | |
|       /* Compile field information */
 | |
|       num_fields = d4numFields(data_file);
 | |
|       r->NFields = num_fields;
 | |
|       for (i=0; ((i<num_fields) && (i<MaxFields)); i++)
 | |
|       {                                                
 | |
|         strcpy(r->Fd[i].Name,field_info[i].name);
 | |
|         xvt_str_make_upper(r->Fd[i].Name); 
 | |
|         r->Fd[i].Len = (unsigned char)field_info[i].len;
 | |
|         r->Fd[i].Dec = (unsigned char)field_info[i].dec;
 | |
|         switch(field_info[i].type)
 | |
|         {
 | |
|         case r4str : r->Fd[i].TypeF = r->Fd[i].Len > 1 ? _alfafld : _charfld; break;
 | |
|         case r4log : r->Fd[i].TypeF = _boolfld; break;
 | |
|         case r4date: r->Fd[i].TypeF = _datefld; break;
 | |
|         case r4memo: r->Fd[i].TypeF = _memofld; break;
 | |
|         case r4num : 
 | |
|           if (r->Fd[i].Dec > 0)
 | |
|             r->Fd[i].TypeF = _realfld;
 | |
|           else
 | |
|             r->Fd[i].TypeF = r->Fd[i].Len < 6 ? _intfld : _longfld;
 | |
|           break;
 | |
|         default: r->Fd[i].TypeF = _alfafld; break; // Considero stringhe tutte le cose sconosciute
 | |
|         }
 | |
|       }
 | |
|       
 | |
|       strcpy(keys,"");
 | |
|       /* Compile key definition */
 | |
|       for (i=0; i < MaxKeys; i++)    /* Browse all tags */
 | |
|       {
 | |
|         if (tag_info[i].name == NULL)
 | |
|           break;
 | |
|         strcat(keys,tag_info[i].expression);
 | |
|         /* Tell me if you're unique my Boy... */
 | |
|         strcat(keys,"|");
 | |
|         strcat(keys,tag_info[i].unique == 0 ? "X" : " ");
 | |
|         strcat(keys,"$");
 | |
|       }
 | |
|       r->NKeys = i;
 | |
|       u4free(field_info);
 | |
|       u4free(tag_info);
 | |
|     }
 | |
|     else
 | |
|       rt = code_base.errorCode;
 | |
|     d4close(data_file);
 | |
|   }
 | |
|   else 
 | |
|     rt = code_base.errorCode;
 | |
|   return (rt);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   ritorna l'ultimo errore
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_get_error(void)
 | |
| {
 | |
|   int rt = code_base.errorCode;
 | |
|   code_base.errorCode=0;
 | |
|   return (rt);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Azzera la viarabile globale d'errore
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| void DB_zero_error(void)
 | |
| {
 | |
|   code_base.errorCode=0;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Si posiziona sull'indice attivo alla chiave indicata, restituisce
 | |
|   la prima chiave che trova >= a from. (TCusrsor)
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_index_seek(int handle, const char* from)
 | |
| {
 | |
|   TAG4* t = NULL;
 | |
|   HANDLE2DATA(handle, data);
 | |
|   t = d4tagDefault(data);
 | |
|   if (t==NULL) 
 | |
|     return -1;
 | |
|   if (tfile4seek(t->tagFile, from, strlen(from)) < 0) 
 | |
|     return DB_get_error();
 | |
|   return 0;
 | |
| 
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Ritorna il numero di record corrispondente alla chiave corrente
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| long DB_index_recno(int handle)
 | |
| {
 | |
|   TAG4 *t;
 | |
|   HANDLE2DATA(handle, data);
 | |
|   t=d4tagDefault(data);
 | |
|   if (t==NULL) 
 | |
|     return(-1);
 | |
| 
 | |
|   return tfile4recNo(t->tagFile);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Si posiziona sulla chiave successiva dell'indice corrente
 | |
|   --------------------------------------------------------------------------*/
 | |
| long DB_index_next(int handle)
 | |
| {
 | |
|   TAG4 *t;
 | |
|   HANDLE2DATA(handle, data);
 | |
|   t = d4tagDefault(data);
 | |
|   if (t==NULL) 
 | |
|     return -1;
 | |
|   return tfile4skip(t->tagFile,1L);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Restituisce la chiave corrente
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| char* DB_index_getkey(int handle)
 | |
| {
 | |
|   static char key[MAXLEN];
 | |
|   TAG4 *t;
 | |
|   int klen;
 | |
|   const char* src;
 | |
| 
 | |
|   HANDLE2DATASTR(handle, data);
 | |
|   
 | |
|   if ((t=d4tagDefault(data))==NULL) 
 | |
|     return(NULL);
 | |
|   klen=a4tagKeyLen(data);
 | |
|   if (klen > (MAXLEN-1)) klen=MAXLEN-1;
 | |
|   src = a4tagKey(data);
 | |
|   if (src != NULL)
 | |
|   {
 | |
|     memcpy(key,src,klen); /* tfile4key non restituisce una null terminated string */
 | |
|     key[klen]='\0';
 | |
|   }
 | |
|   else
 | |
|     key[0] = '\0';
 | |
|   return key;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Si posiziona sulla chiave ed il record indicati
 | |
|   --------------------------------------------------------------------------*/
 | |
| int DB_index_go(int handle, const char* key, long recno)
 | |
| {
 | |
|   TAG4* t = NULL;
 | |
|   HANDLE2DATA(handle, data);
 | |
|   t = d4tagDefault(data);
 | |
|   if (t==NULL) 
 | |
|     return -1;
 | |
|   return tfile4go(t->tagFile, key, recno, FALSE);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Restituisce vero se l'indice e' alla fine
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_index_eof(int handle)
 | |
| {
 | |
|   TAG4* t = NULL;
 | |
|   HANDLE2DATA(handle, data);
 | |
|   t = d4tagDefault(data);
 | |
|   if (t==NULL) 
 | |
|     return -1;
 | |
|   return tfile4eof(t->tagFile);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Blocca il record indicato
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_lock_rec(int handle, long nrec)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4lock(data, nrec)==r4locked ? -1 : 0;
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Ritorna vero(non zero) se il file dati od indice sono stati bloccati
 | |
|   in modo esclusivo dalla presente applicazione, non da altri programmi!!!
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_file_locked(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return a4lockTest(data);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Ritorna vero se il record nrec e' bloccato dalla presente applicazione,
 | |
|   non da altri programmi!!!
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| int DB_rec_locked(int handle, long nrec)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return d4lockTest(data,nrec);
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   restituisce la configurazione della libreria
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| long DB_getconf()
 | |
| {
 | |
|   return u4switch();
 | |
| }
 | |
| 
 | |
| /*-------------------------------------------------------------------------
 | |
|   Restituisce il numero di versione scritto sull'header dell'indice
 | |
|   --------------------------------------------------------------------------*/
 | |
| 
 | |
| long DB_changed(int handle)
 | |
| {
 | |
|   HANDLE2DATA(handle, data);
 | |
|   return a4indexVersion(data);
 | |
| }
 | |
| 
 | |
| char* DB_memoptr( const int handle, const char * fieldname )
 | |
| {
 | |
|   FIELD4* f = NULL;
 | |
|   HANDLE2DATASTR(handle, data);
 | |
|   f = d4field(data, fieldname);
 | |
|   return f4memoPtr(f);
 | |
| }
 | |
| 
 | |
| int DB_memowrite( const int handle, const char* fieldname, const char* memo )
 | |
| {
 | |
|   FIELD4* f = NULL;
 | |
|   HANDLE2DATA(handle, data);
 | |
|   f = d4field(data, fieldname);
 | |
|   return f4memoAssign(f, memo);
 | |
| }
 | |
| 
 | |
| long DB_version(char* str, int maxstr)
 | |
| {
 | |
|   if (str != NULL && maxstr >= 16)
 | |
|     sprintf(str, "Codebase %d.%03d", S4VERSION/1000, S4VERSION%1000);
 | |
|   return S4VERSION;
 | |
| }
 |