Initial revision
This commit is contained in:
668
sicvar.c
Normal file
668
sicvar.c
Normal file
@@ -0,0 +1,668 @@
|
||||
/*----------------------------------------------------------------------
|
||||
Implementation file for the Sics variables module.
|
||||
|
||||
|
||||
|
||||
Mark Koennecke, November 1996
|
||||
revised: Mark Koennecke, June 1997
|
||||
|
||||
Copyright:
|
||||
|
||||
Labor fuer Neutronenstreuung
|
||||
Paul Scherrer Institut
|
||||
CH-5423 Villigen-PSI
|
||||
|
||||
|
||||
The authors hereby grant permission to use, copy, modify, distribute,
|
||||
and license this software and its documentation for any purpose, provided
|
||||
that existing copyright notices are retained in all copies and that this
|
||||
notice is included verbatim in any distributions. No written agreement,
|
||||
license, or royalty fee is required for any of the authorized uses.
|
||||
Modifications to this software may be copyrighted by their authors
|
||||
and need not follow the licensing terms described here, provided that
|
||||
the new terms are clearly indicated on the first page of each file where
|
||||
they apply.
|
||||
|
||||
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
|
||||
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
|
||||
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
|
||||
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
|
||||
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
|
||||
MODIFICATIONS.
|
||||
---------------------------------------------------------------------------*/
|
||||
#include "fortify.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "Scommon.h"
|
||||
#include <string.h>
|
||||
#include "SCinter.h"
|
||||
#include "conman.h"
|
||||
#include "splitter.h"
|
||||
#include "status.h"
|
||||
#include "interface.h"
|
||||
#include "event.h"
|
||||
#include "sicsvar.h"
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
static int VarSave(void *pData, char *name, FILE *fd)
|
||||
{
|
||||
char pBueffel[512];
|
||||
pSicsVariable pVar = NULL;
|
||||
|
||||
assert(pData);
|
||||
assert(fd);
|
||||
pVar = (pSicsVariable)pData;
|
||||
if(pVar->iLock)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
sprintf(pBueffel,"# Variable %s\n",name);
|
||||
switch(pVar->eType)
|
||||
{
|
||||
case veText:
|
||||
sprintf(pBueffel,"%s %s\n",name, pVar->text);
|
||||
break;
|
||||
case veInt:
|
||||
sprintf(pBueffel,"%s %d\n",name, pVar->iVal);
|
||||
break;
|
||||
case veFloat:
|
||||
sprintf(pBueffel,"%s %f\n",name,pVar->fVal);
|
||||
break;
|
||||
}
|
||||
fputs(pBueffel,fd);
|
||||
sprintf(pBueffel,"%s setAccess %d\n",name,pVar->iAccessCode);
|
||||
fputs(pBueffel,fd);
|
||||
return 1;
|
||||
}
|
||||
/*------------------------------------------------------------------------*/
|
||||
static void *VarInterface(void *pData, int iInter)
|
||||
{
|
||||
pSicsVariable self = NULL;
|
||||
|
||||
self = (pSicsVariable)pData;
|
||||
assert(self);
|
||||
|
||||
if(iInter == CALLBACKINTERFACE)
|
||||
{
|
||||
return self->pCall;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*/
|
||||
pSicsVariable VarCreate(int iAccessCode, VarType eTyp, char *name)
|
||||
{
|
||||
pSicsVariable pRes = NULL;
|
||||
|
||||
pRes = (SicsVariable *)malloc(sizeof(SicsVariable));
|
||||
if(!pRes)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pRes->pDescriptor = CreateDescriptor("SicsVariable");
|
||||
if(!pRes->pDescriptor)
|
||||
{
|
||||
free(pRes);
|
||||
return NULL;
|
||||
}
|
||||
pRes->pDescriptor->GetInterface = VarInterface;
|
||||
pRes->pDescriptor->SaveStatus = VarSave;
|
||||
pRes->eType = eTyp;
|
||||
pRes->iAccessCode = iAccessCode;
|
||||
pRes->fVal = .0;
|
||||
pRes->iVal = 0;
|
||||
pRes->iLock = 0;
|
||||
pRes->text = strdup("UNKNOWN");
|
||||
pRes->name = strdup(name);
|
||||
|
||||
pRes->pCall = CreateCallBackInterface();
|
||||
return pRes;
|
||||
}
|
||||
/*--------------------------------------------------------------------------
|
||||
VarFactory will be used in the initialisation phase to configure a new
|
||||
variable.
|
||||
|
||||
Syntax: VarMake name type access
|
||||
|
||||
type can be one of: Text, Int, Float
|
||||
access can be one of: Internal, Mugger, User, Spy
|
||||
-------------------------------------------------------------------------*/
|
||||
static char *cType[] = {
|
||||
"text",
|
||||
"int",
|
||||
"float",
|
||||
NULL
|
||||
};
|
||||
static char *cAccess[] = {
|
||||
"internal",
|
||||
"mugger",
|
||||
"user",
|
||||
"spy",
|
||||
NULL
|
||||
};
|
||||
|
||||
int VarFactory(SConnection *pCon, SicsInterp *pSics, void *pData,
|
||||
int argc, char *argv[])
|
||||
{
|
||||
pSicsVariable pRes = NULL;
|
||||
char pBueffel[512];
|
||||
VarType eType;
|
||||
int i;
|
||||
int iCode, iRet;
|
||||
|
||||
assert(pCon);
|
||||
assert(pSics);
|
||||
|
||||
/* check if enough commands */
|
||||
argtolower(argc,argv);
|
||||
if(argc < 4)
|
||||
{
|
||||
sprintf(pBueffel,"Insufficient no of args to %s, Usage: %s name type accescode",
|
||||
argv[0],argv[0]);
|
||||
SCWrite(pCon,pBueffel,eError);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* argv[1] is expected to be the name of the var, argv[2] the type*/
|
||||
/* interpret the type */
|
||||
i = 0;
|
||||
while(cType[i] != NULL)
|
||||
{
|
||||
if(strcmp(cType[i],argv[2]) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
switch(i)
|
||||
{
|
||||
case 0:
|
||||
eType = veText;
|
||||
break;
|
||||
case 1:
|
||||
eType = veInt;
|
||||
break;
|
||||
case 2:
|
||||
eType = veFloat;
|
||||
break;
|
||||
default:
|
||||
sprintf(pBueffel,"Var %s Type --> %s <-- not recognized",
|
||||
argv[1], argv[2]);
|
||||
SCWrite(pCon,pBueffel,eError);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* argv[3] must be the access code, check that now */
|
||||
i = 0;
|
||||
while(cAccess[i] != NULL)
|
||||
{
|
||||
if(strcmp(argv[3],cAccess[i]) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
switch(i)
|
||||
{
|
||||
case 0:
|
||||
iCode = usInternal;
|
||||
break;
|
||||
case 1:
|
||||
iCode = usMugger;
|
||||
break;
|
||||
case 2:
|
||||
iCode = usUser;
|
||||
break;
|
||||
case 3:
|
||||
iCode = usSpy;
|
||||
break;
|
||||
default:
|
||||
sprintf(pBueffel," %s access code %s not recognized",
|
||||
argv[1], argv[3]);
|
||||
SCWrite(pCon,pBueffel,eError);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* now we can actually install the variable */
|
||||
pRes = VarCreate(iCode,eType,argv[1]);
|
||||
if(!pRes)
|
||||
{
|
||||
sprintf(pBueffel,"Memory Error creating variable %s", argv[1]);
|
||||
SCWrite(pCon,pBueffel,eError);
|
||||
return 0;
|
||||
}
|
||||
iRet = AddCommand(pSics,argv[1],VarWrapper,(KillFunc)VarKill,pRes);
|
||||
if(!iRet)
|
||||
{
|
||||
sprintf(pBueffel,"ERROR: duplicate command %s not created",argv[1]);
|
||||
SCWrite(pCon,pBueffel,eError);
|
||||
VarKill(pRes);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
int VarKill(pSicsVariable self)
|
||||
{
|
||||
|
||||
assert(self);
|
||||
|
||||
if(self->text)
|
||||
{
|
||||
free(self->text);
|
||||
}
|
||||
if(self->pDescriptor)
|
||||
{
|
||||
DeleteDescriptor(self->pDescriptor);
|
||||
}
|
||||
if(self->pCall)
|
||||
{
|
||||
DeleteCallBackInterface(self->pCall);
|
||||
}
|
||||
if(self->name)
|
||||
{
|
||||
free(self->name);
|
||||
}
|
||||
|
||||
free(self);
|
||||
return 1;
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
int VarSetFloat(pSicsVariable self, float fNew, int iUserRights)
|
||||
{
|
||||
assert(self);
|
||||
assert(self->eType == veFloat);
|
||||
|
||||
if(self->iAccessCode < iUserRights)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->fVal = fNew;
|
||||
InvokeCallBack(self->pCall, VALUECHANGE, self);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*/
|
||||
int VarSetInt(pSicsVariable self, int iNew, int iUserRights)
|
||||
{
|
||||
assert(self);
|
||||
assert(self->eType == veInt);
|
||||
|
||||
if(self->iAccessCode < iUserRights)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->iVal = iNew;
|
||||
InvokeCallBack(self->pCall, VALUECHANGE, self);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*/
|
||||
int VarSetText(pSicsVariable self, char *pNew, int iUserRights)
|
||||
{
|
||||
assert(self);
|
||||
assert(self->eType == veText);
|
||||
|
||||
if(self->iAccessCode < iUserRights)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(self->text)
|
||||
{
|
||||
free(self->text);
|
||||
}
|
||||
self->text = strdup(pNew);
|
||||
InvokeCallBack(self->pCall, VALUECHANGE, self);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*/
|
||||
int VarGetFloat(pSicsVariable self, float *fNew)
|
||||
{
|
||||
assert(self);
|
||||
assert(self->eType == veFloat);
|
||||
|
||||
*fNew = self->fVal;
|
||||
return 1;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*/
|
||||
int VarGetInt(pSicsVariable self, int *iNew)
|
||||
{
|
||||
assert(self);
|
||||
assert(self->eType == veInt);
|
||||
|
||||
*iNew = self->iVal;
|
||||
return 1;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*/
|
||||
int VarGetText(pSicsVariable self, char **pNew)
|
||||
{
|
||||
assert(self);
|
||||
assert(self->eType == veText);
|
||||
|
||||
*pNew = strdup(self->text);
|
||||
return 1;
|
||||
}
|
||||
/*------------------------------------------------------------------------*/
|
||||
VarType GetVarType(pSicsVariable self)
|
||||
{
|
||||
assert(self);
|
||||
return self->eType;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*/
|
||||
int VarSetRights(pSicsVariable self, int iNewRights, int iYourRights)
|
||||
{
|
||||
assert(self);
|
||||
|
||||
if( iYourRights > 1) /* only muggers allowed here */
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->iAccessCode = iNewRights;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/*--------------------------------------------------------------------*/
|
||||
static int VarInterestCallback(int iEvent, void *pEvent, void *pUser)
|
||||
{
|
||||
SConnection *pCon;
|
||||
char pBueffel[512];
|
||||
pSicsVariable pVar = NULL;
|
||||
int iVal;
|
||||
float fVal;
|
||||
char *pText;
|
||||
|
||||
assert(pEvent);
|
||||
assert(pUser);
|
||||
|
||||
pVar = (pSicsVariable)pEvent;
|
||||
pCon = (SConnection *)pUser;
|
||||
switch(pVar->eType)
|
||||
{
|
||||
case veInt:
|
||||
VarGetInt(pVar,&iVal);
|
||||
sprintf(pBueffel,"%s = %d",pVar->name,iVal);
|
||||
SCWrite(pCon,pBueffel,eValue);
|
||||
return 1;
|
||||
case veFloat:
|
||||
VarGetFloat(pVar,&fVal);
|
||||
sprintf(pBueffel,"%s = %f",pVar->name,fVal);
|
||||
SCWrite(pCon,pBueffel,eValue);
|
||||
return 1;
|
||||
case veText:
|
||||
VarGetText(pVar,&pText);
|
||||
sprintf(pBueffel,"%s = %s", pVar->name,pText);
|
||||
SCWrite(pCon,pBueffel,eValue);
|
||||
if(pText)
|
||||
{
|
||||
free(pText);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
Variables understands some commands:
|
||||
setrights : for setting uer rights
|
||||
lock : for locking the variable
|
||||
interest : for notifictaion on value change
|
||||
uninterest : delete notification
|
||||
*/
|
||||
int VarWrapper(SConnection *pCon, SicsInterp *pInterp, void *pData,
|
||||
int argc, char *argv[])
|
||||
{
|
||||
float fVal;
|
||||
int iVal;
|
||||
char *pText = NULL;
|
||||
pSicsVariable pVar = NULL;
|
||||
VarType eTyp;
|
||||
TokenList *pList = NULL;
|
||||
TokenList *pCurrent;
|
||||
char pBueffel[256];
|
||||
int iRet;
|
||||
Status eStat;
|
||||
long lID;
|
||||
|
||||
assert(pCon);
|
||||
assert(pInterp);
|
||||
assert(pData);
|
||||
|
||||
/* get Variable pointer */
|
||||
pVar = ( pSicsVariable ) pData;
|
||||
eTyp = GetVarType(pVar);
|
||||
|
||||
/* tokenize arguments */
|
||||
pList = SplitArguments(argc, argv);
|
||||
if(!pList)
|
||||
{
|
||||
SCWrite(pCon,"ERROR: cannot parse arguments",eError);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pCurrent = pList->pNext; /* if only one arg: print the value */
|
||||
if(!pCurrent)
|
||||
{
|
||||
switch(eTyp)
|
||||
{
|
||||
case veInt:
|
||||
VarGetInt(pVar,&iVal);
|
||||
sprintf(pBueffel,"%s = %d",argv[0],iVal);
|
||||
SCWrite(pCon,pBueffel,eValue);
|
||||
DeleteTokenList(pList);
|
||||
return 1;
|
||||
case veFloat:
|
||||
VarGetFloat(pVar,&fVal);
|
||||
sprintf(pBueffel,"%s = %f",argv[0],fVal);
|
||||
SCWrite(pCon,pBueffel,eValue);
|
||||
DeleteTokenList(pList);
|
||||
return 1;
|
||||
case veText:
|
||||
VarGetText(pVar,&pText);
|
||||
sprintf(pBueffel,"%s = %s", argv[0],pText);
|
||||
SCWrite(pCon,pBueffel,eValue);
|
||||
if(pText)
|
||||
{
|
||||
free(pText);
|
||||
}
|
||||
DeleteTokenList(pList);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* now either new value, lock or setAccess */
|
||||
strtolower(pCurrent->text);
|
||||
if (strcmp(pCurrent->text,"setaccess") == 0)
|
||||
{
|
||||
pCurrent = pCurrent->pNext;
|
||||
if(pCurrent)
|
||||
{
|
||||
if(pCurrent->Type != eInt)
|
||||
{
|
||||
SCWrite(pCon,"Wrong argument for setAccess, expect Integer",eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* is control grabbed ? */
|
||||
if(SCGetGrab(pCon) != 0)
|
||||
{
|
||||
SCWrite(pCon,"ERROR: somebody else has grabbed control, Request REJECTED",eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
}
|
||||
/* finaly do it */
|
||||
iRet = VarSetRights(pVar,pCurrent->iVal,SCGetRights(pCon));
|
||||
if(!iRet)
|
||||
{
|
||||
SCWrite(pCon,
|
||||
"You have no privilege to change AccessCodes",eError);
|
||||
}
|
||||
DeleteTokenList(pList);
|
||||
SCSendOK(pCon);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SCWrite(pCon,
|
||||
"Missing argument to setAccess",eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if(strcmp(pCurrent->text,"interest") == 0) /* interest */
|
||||
{
|
||||
lID = RegisterCallback(pVar->pCall, VALUECHANGE, VarInterestCallback,
|
||||
pCon, NULL);
|
||||
SCRegister(pCon,pInterp, pVar->pCall,lID);
|
||||
DeleteTokenList(pList);
|
||||
SCSendOK(pCon);
|
||||
return 1;
|
||||
}
|
||||
else if(strcmp(pCurrent->text,"uninterest") == 0)
|
||||
{
|
||||
RemoveCallback2(pVar->pCall,pCon);
|
||||
DeleteTokenList(pList);
|
||||
SCSendOK(pCon);
|
||||
return 1;
|
||||
}
|
||||
else if(strcmp(pCurrent->text,"lock") == 0)
|
||||
{
|
||||
pVar->iLock = 1;
|
||||
DeleteTokenList(pList);
|
||||
SCSendOK(pCon);
|
||||
return 1;
|
||||
}
|
||||
/* now, only a new value is still possible */
|
||||
eStat = GetStatus();
|
||||
if( (eStat != eEager) && (eStat != eBatch) )
|
||||
{
|
||||
SCWrite(pCon,
|
||||
"You cannot set variables while a scan is running",eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
|
||||
}
|
||||
iRet = 0;
|
||||
if(pCurrent)
|
||||
{
|
||||
/* is it locked ? */
|
||||
if(pVar->iLock)
|
||||
{
|
||||
sprintf(pBueffel,"ERROR: variable %s is configured locked!",
|
||||
argv[0]);
|
||||
SCWrite(pCon,pBueffel,eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
}
|
||||
/* is control grabbed ? */
|
||||
if(SCGetGrab(pCon) != 0)
|
||||
{
|
||||
SCWrite(pCon,"ERROR: somebody else has grabbed control, Request REJECTED",eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
}
|
||||
/* do not care for typechecks if text */
|
||||
if(eTyp == veText)
|
||||
{
|
||||
Arg2Text(argc-1,&argv[1],pBueffel,255);
|
||||
iRet = VarSetText(pVar,pBueffel,SCGetRights(pCon));
|
||||
}
|
||||
else if(eTyp == veInt)
|
||||
{
|
||||
if(pCurrent->Type == eFloat)
|
||||
{
|
||||
iVal = (int)pCurrent->fVal;
|
||||
}
|
||||
else if(pCurrent->Type == eInt)
|
||||
{
|
||||
iVal = pCurrent->iVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(pBueffel,"Wrong Type: cannot assign %s to %s",
|
||||
pCurrent->text, argv[0]);
|
||||
SCWrite(pCon,pBueffel,eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
}
|
||||
iRet = VarSetInt(pVar,iVal,SCGetRights(pCon));
|
||||
}
|
||||
else if(eTyp == veFloat)
|
||||
{
|
||||
if(pCurrent->Type == eFloat)
|
||||
{
|
||||
fVal = pCurrent->fVal;
|
||||
}
|
||||
else if(pCurrent->Type == eInt)
|
||||
{
|
||||
fVal = (float)pCurrent->iVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(pBueffel,"Wrong Type: cannot assign %s to %s",
|
||||
pCurrent->text, argv[0]);
|
||||
SCWrite(pCon,pBueffel,eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
}
|
||||
iRet = VarSetFloat(pVar,fVal,SCGetRights(pCon));
|
||||
}
|
||||
if(!iRet)
|
||||
{
|
||||
sprintf(pBueffel,"Insufficient Privilege to change %s",
|
||||
argv[0]);
|
||||
SCWrite(pCon,pBueffel,eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
}
|
||||
SCSendOK(pCon);
|
||||
DeleteTokenList(pList);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* if we are here, no valid command was found */
|
||||
SCWrite(pCon,
|
||||
"No valid syntax found in Variable",eError);
|
||||
DeleteTokenList(pList);
|
||||
return 0;
|
||||
}
|
||||
/*-------------------------------------------------------------------------*/
|
||||
pSicsVariable FindVariable(SicsInterp *pSics, char *name)
|
||||
{
|
||||
CommandList *pC;
|
||||
pSicsVariable pVar;
|
||||
|
||||
pC = FindCommand(pSics,name);
|
||||
if(!pC)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
pVar = (pSicsVariable)pC->pData;
|
||||
if(!pVar)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if(strcmp(pVar->pDescriptor->name,"SicsVariable") != 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return pVar;
|
||||
}
|
||||
Reference in New Issue
Block a user