//OpenSCADA module UI.VCAEngine file: session.cpp /*************************************************************************** * Copyright (C) 2007-2024 by Roman Savochenko, * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; version 2 of the License. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "vcaengine.h" #include "session.h" using namespace VCA; //************************************************ //* Session: Project's session * //************************************************ Session::Session( const string &iid, const string &iproj ) : mAlrmRes(true), mCalcRes(true), mDataRes(true), mId(iid), mPrjnm(iproj), mOwner("root"), mGrp("UI"), mUser(dataResSes()), mReqUser(dataResSes()), mReqLang(dataResSes()), mPer(100), mPerReal(0), mPermit(RWRWR_), mEnable(false), mStart(false), endrunReq(false), mBackgrnd(false), mConnects(0), mCalcClk(CLK_START), mReqTm(0), mUserActTm(0), mStyleIdW(Project::StlDisabled) { mUser = "root"; mPage = grpAdd("pg_"); sec = SYS->security(); mReqTm = time(NULL); setUserActTm(); } Session::~Session( ) { modifClr(); for(map::iterator iN = mNotify.begin(); iN != mNotify.end(); ++iN) delete iN->second; } void Session::postEnable( int flag ) { if(flag&TCntrNode::NodeRestore) setEnable(true); } void Session::preDisable( int flag ) { if(enable()) setEnable(false); } void Session::setUser( const string &it ) { mUser = it; if(!enable()) mOwner = it; } void Session::setEnable( bool val ) { int64_t d_tm = 0; MtxAlloc res(mCalcRes, true); if(val == enable()) return; vector pg_ls; if(val) { mess_debug(nodePath().c_str(),_("Enabling the session.")); try { if(mess_lev() == TMess::Debug) d_tm = TSYS::curTime(); //Connecting and registering to the project mParent = mod->prjAt(mPrjnm); mParent.at().heritReg(this); //Getting data from the project mOwner = parent().at().owner(); mGrp = parent().at().grp(); mPermit = parent().at().permit(); setPeriod(parent().at().period()); //Loading the previous style stlCurentSet(); if(mess_lev() == TMess::Debug) { mess_debug(nodePath().c_str(), _("Time of the previous style loading: %f ms."), 1e-3*(TSYS::curTime()-d_tm)); d_tm = TSYS::curTime(); } //Creation the root pages parent().at().list(pg_ls); for(unsigned iP = 0; iP < pg_ls.size(); iP++) if(!present(pg_ls[iP])) add(pg_ls[iP],parent().at().at(pg_ls[iP]).at().addr()); if(mess_lev() == TMess::Debug) { mess_debug(nodePath().c_str(), _("Time of the root pages creating: %f ms."), 1e-3*(TSYS::curTime()-d_tm)); d_tm = TSYS::curTime(); } //Enabling the pages list(pg_ls); for(unsigned iLs = 0; iLs < pg_ls.size(); iLs++) try{ at(pg_ls[iLs]).at().setEnable(true); } catch(TError &err) { mess_err(err.cat.c_str(), "%s", err.mess.c_str()); } if(mess_lev() == TMess::Debug) mess_debug(nodePath().c_str(), _("Time of the root pages enabling: %f ms."), 1e-3*(TSYS::curTime()-d_tm)); modifGClr(); } catch(...) { mParent.at().heritUnreg(this); mParent.free(); } } else { if(start()) setStart(false); mess_debug(nodePath().c_str(),_("Disabling the session.")); //Pages disable list(pg_ls); for(unsigned iLs = 0; iLs < pg_ls.size(); iLs++) at(pg_ls[iLs]).at().setEnable(false); //Delete pages for(unsigned iLs = 0; iLs < pg_ls.size(); iLs++) del(pg_ls[iLs]); //Unregistering and disconnecting for the project mParent.at().heritUnreg(this); mParent.free(); } mEnable = val; } void Session::setStart( bool val ) { int64_t d_tm = 0; MtxAlloc res(mCalcRes, true); vector pg_ls; if(val) { //Enable session if it disabled if(!enable()) setEnable(true); if(mess_lev() == TMess::Debug) d_tm = TSYS::curTime(); mess_debug(nodePath().c_str(),_("Starting the session.")); //Load Styles from the project mStProp.clear(); if(stlCurent() >= 0) { parent().at().stlPropList(pg_ls); for(unsigned iSP = 0; iSP < pg_ls.size(); iSP++) mStProp[pg_ls[iSP]] = parent().at().stlPropGet(pg_ls[iSP], "", stlCurent()); } if(mess_lev() == TMess::Debug) { mess_debug(nodePath().c_str(), _("Time of the styles loading from the project: %f ms."), 1e-3*(TSYS::curTime()-d_tm)); d_tm = TSYS::curTime(); } //Process all pages to on list(pg_ls); for(unsigned iLs = 0; iLs < pg_ls.size(); iLs++) at(pg_ls[iLs]).at().setProcess(true); if(mess_lev() == TMess::Debug) { mess_debug(nodePath().c_str(), _("Time of the processing all the root pages: %f ms."), 1e-3*(TSYS::curTime()-d_tm)); d_tm = TSYS::curTime(); } //VCA server's force off MtxAlloc resAl(mAlrmRes, true); for(map::iterator iN = mNotify.begin(); iN != mNotify.end(); ++iN) iN->second->ntf(0); resAl.unlock(); //Start process task if(!mStart) SYS->taskCreate(nodePath('.',true), 0, Session::Task, this); if(mess_lev() == TMess::Debug) mess_debug(nodePath().c_str(), _("Time of the processing task starting: %f ms."), 1e-3*(TSYS::curTime()-d_tm)); } else { mess_debug(nodePath().c_str(),_("Stopping the session.")); //Stop process task if(mStart) SYS->taskDestroy(nodePath('.',true), &endrunReq); //VCA server's force off MtxAlloc resAl(mAlrmRes, true); for(map::iterator iN = mNotify.begin(); iN != mNotify.end(); ++iN) iN->second->ntf(0); resAl.unlock(); //Process all pages to off list(pg_ls); for(unsigned iLs = 0; iLs < pg_ls.size(); iLs++) at(pg_ls[iLs]).at().setProcess(false); } } int Session::connect( bool recon ) { dataResSes().lock(); if(!recon) mConnects++; int rez; do { rez = (SYS->sysTm()%10000000)*10 + (int)(10*(float)rand()/(float)RAND_MAX); } while(mCons.find(rez) != mCons.end()); mCons[rez] = true; dataResSes().unlock(); setUserActTm(); return rez; } void Session::disconnect( int conId ) { dataResSes().lock(); if(mConnects > 0) mConnects--; map::iterator mC = mCons.find(conId); if(mC != mCons.end()) mCons.erase(mC); dataResSes().unlock(); } bool Session::clkChkModif( unsigned clkFrom, unsigned clkCh ) { if(clkFrom == CLK_NO_ALL) return true; if(clkCh == CLK_NO_ALL) return false; //Checking the counter overrun unsigned clkCur = calcClk(); if(clkCur < clkFrom) { if(clkCur > clkCh) clkCh += UINT16_MAX+1-CLK_START; clkCur += UINT16_MAX+1-CLK_START; } return (clkCh > clkFrom && clkCh < clkCur); } uint16_t Session::clkPairPrc( uint32_t &pair, bool set ) { //Getting the pair parts uint16_t clkCur = mCalcClk, clkMdfForm = (pair>>16)&0xFFFF, clkMdfFix = pair&0xFFFF; unsigned clkCurCor = clkCur, clkMdfFormCor = clkMdfForm; //Checking the counter overload if((clkMdfFix != CLK_NO_ALL && clkMdfFix > clkCur) || (clkMdfForm != CLK_NO_ALL && clkMdfForm > clkCur)) clkCurCor += UINT16_MAX+1-CLK_START; if(clkMdfFix != CLK_NO_ALL && clkMdfForm != CLK_NO_ALL && clkMdfFix > clkMdfForm) clkMdfFormCor += UINT16_MAX+1-CLK_START; //Clearing old values if(clkMdfFix != CLK_NO_ALL && (clkCurCor-clkMdfFix) > CLK_OLD) clkMdfFix = CLK_NO_ALL; if(clkMdfFormCor != CLK_NO_ALL && (clkCurCor-clkMdfFormCor) > CLK_OLD) clkMdfFormCor = clkMdfForm = CLK_NO_ALL; //Same processing if(clkMdfFormCor < clkCurCor && clkMdfFormCor > clkMdfFix) clkMdfFix = clkMdfForm; if(set) clkMdfForm = clkCur; pair = (clkMdfForm<<16)|clkMdfFix; return clkMdfFix; } string Session::ico( ) const { return (!parent().freeStat()) ? parent().at().ico() : ""; } int Session::period( bool isReal ) { return (isReal && mPerReal) ? mPerReal : vmax(1, mPer); } AutoHD Session::parent( ) const{ return mParent; } void Session::add( const string &iid, const string &iparent ) { if(present(iid)) return; chldAdd(mPage, new SessPage(iid,iparent,this)); } vector Session::openList( ) { dataResSes().lock(); vector rez = mOpen; dataResSes().unlock(); return rez; } bool Session::openCheck( const string &iid ) { bool rez = false; dataResSes().lock(); for(unsigned iOp = 0; iOp < mOpen.size() && !rez; iOp++) rez = (iid == mOpen[iOp]); dataResSes().unlock(); return rez; } void Session::openReg( const string &iid ) { dataResSes().lock(); //!!!! The new open page will always appear on the top for(int iOp = 0; iOp < (int)mOpen.size(); ++iOp) if(iid == mOpen[iOp]) mOpen.erase(mOpen.begin()+(iOp--)); mOpen.push_back(iid); dataResSes().unlock(); mess_debug(nodePath().c_str(), _("Registering/opening the page '%s'."), iid.c_str()); //Check for notifiers register for(unsigned iNtf = 0; iNtf < 7; iNtf++) { string aNtf = TSYS::strMess("notify%d", iNtf); AutoHD pgO = nodeAt(iid, 1); if(pgO.at().attrPresent(aNtf)) ntfReg(iNtf, pgO.at().attrAt(aNtf).at().getS(), iid); } } bool Session::openUnreg( const string &iid ) { bool rez = false; dataResSes().lock(); for(unsigned iOp = 0; iOp < mOpen.size(); iOp++) if(iid == mOpen[iOp]) { mOpen.erase(mOpen.begin()+iOp); rez = true; } dataResSes().unlock(); mess_debug(nodePath().c_str(), _("Unregistering/closing the page '%s'."), iid.c_str()); //Freeing the notificators configuration for the page ntfReg(-1, "", iid); return rez; } AutoHD Session::at( const string &id ) const { return chldAt(mPage, id); } void Session::uiCmd( const string &com, const string &prm, SessWdg *src ) { //Find of pattern adequancy for opened page string oppg, pBase; //Opened page according of pattern vector opLs = openList(); //for(unsigned iOp = 0; iOp < opLs.size(); iOp++) { for(int iOp = opLs.size()-1; iOp >= 0; iOp--) { string curPtEl, curEl; for(int iEl = 0; (curPtEl=TSYS::pathLev(prm,iEl++)).size(); ) if((curEl=TSYS::pathLev(opLs[iOp],iEl)).empty() || (curPtEl.find("pg_") == 0 && curPtEl != curEl)) break; if(curPtEl.empty()) { oppg = opLs[iOp]; break; } } pBase = oppg; if(pBase.empty() && src) pBase = src->addr(); //Individual commands process try { // Go to the destination page string curPtEl, cpgAddr = "/ses_"+id(); AutoHD cpg, cpg_; for(unsigned iEl = 0; (curPtEl=TSYS::pathLev(prm,iEl++)).size(); ) { string opPg; if(curPtEl.find("pg_") == 0) opPg = curPtEl.substr(3); else if(curPtEl == "*" || (curPtEl == "$" && (com == "next" || com == "prev"))) { vector pls; int iL; if(cpg.freeStat()) list(pls); else cpg.at().pageList(pls); if(pls.empty()) return; string curEl = TSYS::pathLev(pBase, iEl); if(curEl.empty()) { if(curPtEl == "$") return; for(iL = 0; iL < (int)pls.size(); iL++) { opPg = pls[iL]; cpg_ = cpg.freeStat() ? at(opPg) : cpg.at().pageAt(opPg); if(SYS->security().at().access(reqUser(),SEC_RD, TSYS::strParse(cpg_.at().attrAt("owner").at().getS(),0,":"),TSYS::strParse(cpg_.at().attrAt("owner").at().getS(),1,":"), cpg_.at().attrAt("perm").at().getI())) break; } if(iL >= (int)pls.size()) opPg = pls[0]; } else { curEl = curEl.substr(3); for(iL = 0; iL < (int)pls.size(); iL++) if(curEl == pls[iL]) break; if(iL < (int)pls.size()) { if(curPtEl == "$") { while(true) { if(com == "next") iL++; if(com == "prev") iL--; iL = (iL < 0) ? (int)pls.size()-1 : (iL >= (int)pls.size()) ? 0 : iL; opPg = pls[iL]; if(opPg == curEl) return; cpg_ = cpg.freeStat() ? at(opPg) : cpg.at().pageAt(opPg); if(SYS->security().at().access(reqUser(),SEC_RD, TSYS::strParse(cpg_.at().attrAt("owner").at().getS(),0,":"),TSYS::strParse(cpg_.at().attrAt("owner").at().getS(),1,":"), cpg_.at().attrAt("perm").at().getI())) break; }; } else opPg = curEl; } else { if(curPtEl == "$") return; opPg = pls[0]; } } } else opPg = curPtEl; // Go to the next page cpg = cpg.freeStat() ? at(opPg) : cpg.at().pageAt(opPg); cpgAddr += "/pg_"+opPg; } //Open founded page if(!cpg.freeStat()) { //!!!! here mostly wrong for multiple container pages //if(!oppg.empty() && ((AutoHD)mod->nodeAt(oppg)).at().addr() != cpg.at().addr()) // ((AutoHD)mod->nodeAt(oppg)).at().attrAt("pgOpenSrc").at().setS(""); cpg.at().setPathAsOpen(cpgAddr); //To descry links if(src) cpg.at().attrAt("pgOpenSrc").at().setS(src->addr(), true); else cpg.at().attrAt("pgOpen").at().setB(true, true); } } catch(TError &er) { //throw TError(nodePath().c_str(), _("Error command '%s' for the parameters '%s': %s"), com.c_str(), prm.c_str(), er.mess.c_str()); } } string Session::sessAttr( const string &idw, const string &id, bool onlyAllow ) { TConfig cEl(&mod->elPrjSes()); cEl.cfg("IDW").setS(idw); cEl.cfg("ID").setS(id); cEl.cfg("IO_VAL").setView(!onlyAllow); string db = parent().at().DB(); string tbl = parent().at().tbl()+"_ses"; return TBDS::dataGet(db+"."+tbl,mod->nodePath()+tbl,cEl,TBDS::NoException) ? (onlyAllow?"1":cEl.cfg("IO_VAL").getS()) : ""; } void Session::sessAttrSet( const string &idw, const string &id, const string &val ) { TConfig cEl(&mod->elPrjSes()); cEl.cfg("IDW").setS(idw); cEl.cfg("ID").setS(id); cEl.cfg("IO_VAL").setS(val); string db = parent().at().DB(); string tbl = parent().at().tbl()+"_ses"; TBDS::dataSet(db+"."+tbl, mod->nodePath()+tbl, cEl, TBDS::NoException); } void Session::alarmSet( const string &wpath, const string &alrm ) { if(wpath.empty()) return; //Notifications queue update MtxAlloc resAl(mAlrmRes, true); for(map::iterator iN = mNotify.begin(); iN != mNotify.end(); ++iN) iN->second->queueSet(wpath, alrm); } int Session::alarmStat( ) { uint8_t alev = 0, atp = 0, aqtp = 0; vector ls; list(ls); for(unsigned iP = 0; iP < ls.size(); iP++) { int ast = at(ls[iP]).at().attrAt("alarmSt").at().getI(); alev = vmax(alev,ast&0xFF); atp |= (ast>>8)&0xFF; aqtp |= (ast>>16)&0xFF; } return (aqtp<<16)|(atp<<8)|alev; } void Session::alarmQuietance( const string &wpath, uint8_t quit_tmpl, bool ret ) { string tStr; if(!wpath.empty()) for(int off = 0; (tStr=TSYS::strParse(wpath,0,";",&off)).size(); ) ((AutoHD)mod->nodeAt(tStr)).at().alarmQuietance(quit_tmpl, true, ret); else { vector ls; list(ls); for(unsigned iP = 0; iP < ls.size(); iP++) at(ls[iP]).at().alarmQuietance(quit_tmpl, true, ret); } //The notifications queue quietance MtxAlloc resAl(mAlrmRes, true); for(map::iterator iN = mNotify.begin(); iN != mNotify.end(); ++iN) iN->second->queueQuietance(wpath, quit_tmpl, ret); } void Session::ntfReg( int8_t tp, const string &props, const string &pgCrtor ) { if(tp < 0) { for(unsigned iNtf = 0; iNtf < 7; iNtf++) ntfReg(iNtf, props, pgCrtor); return; } vector pgPropsQ; MtxAlloc res(mAlrmRes, true); //Find for presented notification type map::iterator iN = mNotify.find(tp); if(iN != mNotify.end()) { if(pgCrtor == iN->second->pgCrtor() && props == iN->second->props()) return; pgPropsQ = iN->second->pgPropsQ; if(pgCrtor != iN->second->pgCrtor()) { // Check the queue for pointed page already here for(vector::iterator iQ = iN->second->pgPropsQ.begin(); iQ != iN->second->pgPropsQ.end(); ++iQ) if(TSYS::strLine(*iQ,0) == pgCrtor) { if(props.empty()) iN->second->pgPropsQ.erase(iQ); else *iQ = pgCrtor + "\n" + props; return; } if(props.empty()) return; pgPropsQ.push_back(iN->second->pgProps); } delete iN->second; mNotify.erase(iN); } //New or replaced creation if(props.size()) mNotify[tp] = new Notify(tp, pgCrtor+"\n"+props, this); //Take and place a notificator from the queue else if(pgPropsQ.size()) { mNotify[tp] = new Notify(tp, pgPropsQ.back(), this); pgPropsQ.pop_back(); } else return; mNotify[tp]->pgPropsQ = pgPropsQ; } void *Session::Task( void *icontr ) { const TSYS::STask &tsk = TSYS::taskDescr(); vector pls; Session &ses = *(Session *)icontr; ses.endrunReq = false; ses.mStart = true; ses.list(pls); while(!ses.endrunReq) { ses.mPerReal = 1e-6*tsk.period(); //Calc session pages and all other items at recursion for(unsigned iL = 0; iL < pls.size(); iL++) try { ses.at(pls[iL]).at().calc(false, false, iL); } catch(TError &err) { mess_err(err.cat.c_str(),"%s",err.mess.c_str()); mess_err(ses.nodePath().c_str(),_("Error calculating the session '%s'."),pls[iL].c_str()); } //VCA server's notifications processing MtxAlloc resAl(ses.mAlrmRes, true); int aSt = ses.alarmStat(); for(map::iterator iN = ses.mNotify.begin(); iN != ses.mNotify.end(); ++iN) iN->second->ntf(aSt); resAl.unlock(); //Sleep to next cycle TSYS::taskSleep((int64_t)ses.period()*1000000); if((ses.mCalcClk++) == CLK_NO_ALL) ses.mCalcClk = CLK_START; } ses.mStart = false; return NULL; } void Session::stlCurentSet( int sid ) { if(sid == Project::StlMaximum) { string stVl = sessAttr("