File indexing completed on 2024-04-14 14:32:25

0001 /***************************************************************************
0002                           cvariablelist.cpp  -  list of variables
0003                              -------------------
0004     begin                : Po sep 8 2003
0005     copyright            : (C) 2003-2008 by Tomas Mecir
0006     email                : kmuddy@kmuddy.com
0007  ***************************************************************************/
0008 
0009 /***************************************************************************
0010  *                                                                         *
0011  *   This program is free software; you can redistribute it and/or modify  *
0012  *   it under the terms of the GNU General Public License as published by  *
0013  *   the Free Software Foundation; either version 2 of the License, or     *
0014  *   (at your option) any later version.                                   *
0015  *                                                                         *
0016  ***************************************************************************/
0017 
0018 #include "cvariablelist.h"
0019 
0020 #include "ccmdqueue.h"
0021 #include "cprofilemanager.h"
0022 #include "cvalue.h"
0023 #include "cvariable.h"
0024 
0025 #include <KLocalizedString>
0026 
0027 #include <QDebug>
0028 #include <QDir>
0029 #include <QFile>
0030 #include <QXmlStreamReader>
0031 #include <QXmlStreamWriter>
0032 
0033 using namespace std;
0034 
0035 //maximum number of passes when doing recursive variable expansion
0036 
0037 #define MAXEXPANDPASSES 10
0038 
0039 cVariableList::cVariableList (int sess)
0040       : cActionBase ("variables", sess)
0041 {
0042   load ();
0043 }
0044 
0045 cVariableList::~cVariableList ()
0046 {
0047   save ();
0048   vars.clear ();
0049 }
0050 
0051 QString cVariableList::getValue (const QString &varname)
0052 {
0053   cValue *val = value (varname);
0054   //no such variable -> nothing to do
0055   if (!val) return QString();
0056 
0057   return val->asString();
0058 }
0059 
0060 int cVariableList::getIntValue (const QString &varname)
0061 {
0062   cValue *val = value (varname);
0063   //no such variable -> nothing to do
0064   if (!val) return 0;
0065 
0066   return val->asInteger();
0067 }
0068 
0069 cValue *cVariableList::value (const QString &varname, cCmdQueue *queue)
0070 {
0071   if (varname.isEmpty()) return nullptr;
0072   QString vn = varname;
0073   if (varname[0] == '$')
0074     vn = vn.mid(1);
0075 
0076   if (queue) {
0077     cValue *value = queue->value (vn);
0078     if (value)
0079       return value;
0080   }
0081 
0082   if (!exists (vn))
0083     return nullptr;
0084   
0085   return vars[vn]->getValue();
0086 }
0087 
0088 cValue *cVariableList::valueNotEmpty (const QString &varname, cCmdQueue *queue)
0089 {
0090   cValue *val = value (varname, queue);
0091   if (val) return val;
0092   // doesn't exist - create empty !
0093   set (varname, QString());
0094   return value (varname, queue);
0095 }
0096   
0097 bool cVariableList::exists (const QString &varname)
0098 {
0099   if (varname.isEmpty()) return false;
0100   QString vn = varname;
0101   if (varname[0] == '$')
0102     vn = vn.mid(1);
0103   return (vars.count (vn) != 0);
0104 }
0105 
0106 void cVariableList::set (const QString &varname, const QString &value)
0107 {
0108   cValue v (value);
0109   set (varname, &v);
0110 }
0111 
0112 void cVariableList::set (const QString &varname, cValue *value)
0113 {
0114   if (varname.isEmpty()) return;
0115   QString vn = varname;
0116   if (varname[0] == '$')
0117     vn = vn.mid(1);
0118 
0119   QString oldval = QString();
0120   QString newval = value ? value->asString() : QString();
0121   bool changed = (oldval != newval);
0122   if (exists (vn))
0123   {
0124     //variable already exists - we just update its value
0125     oldval = vars[vn]->value ();
0126     vars[vn]->setValue (value);
0127   }
0128   else
0129   {
0130     //good, add new variable to the list
0131     cVariable *var = new cVariable;
0132     var->setName (vn);
0133     var->setValue (value);
0134     vars[vn] = var;
0135     changed = true;   // if we're adding a new value, always report the change
0136   }
0137 
0138   //inform everyone about the change, if needed
0139   if (!changed) return;
0140   invokeEvent ("old-var-value", sess(), vn, oldval);
0141   invokeEvent ("var-changed", sess(), vn, value ? value->asString() : QString());
0142 }
0143 
0144 void cVariableList::unset (const QString &varname)
0145 {
0146   if (varname.isEmpty()) return;
0147   QString vn = varname;
0148   if (varname[0] == '$')
0149     vn = vn.mid(1);
0150 
0151   //no such variable -> nothing to do
0152   if (!exists (vn))
0153     return;
0154 
0155   //get the variable
0156   cVariable *var = vars[vn];
0157   QString oldval = var->value();
0158   //and remove it
0159   vars.erase (vn);
0160   delete var;
0161 
0162   invokeEvent ("old-var-value", sess(), vn, oldval);
0163   invokeEvent ("var-changed", sess(), vn, QString());
0164 }
0165 
0166 void cVariableList::inc (const QString &varname, double delta)
0167 {
0168   if (!exists (varname)) {
0169     cValue val (delta);
0170     set (varname, &val);
0171   } else {
0172     QString vn = varname;
0173     if (varname[0] == '$')
0174       vn = vn.mid(1);
0175     cValue *val = vars[vn]->getValue();
0176     if (val) {
0177       QString oldval = val->asString();
0178       val->setValue (val->asDouble() + delta);
0179       // we are bypassing cVariableList::set here, hence we need to invoke
0180       // this event ourselves ...
0181       invokeEvent ("old-var-value", sess(), vn, oldval);
0182       invokeEvent ("var-changed", sess(), vn, val->asString());
0183     }
0184     else {
0185       cValue d (delta);
0186       set (varname, &d);
0187     }
0188   }
0189 }
0190 
0191 void cVariableList::dec (const QString &varname, double delta)
0192 {
0193   inc (varname, -1.0 * delta);
0194 }
0195 
0196 void cVariableList::provideResource (const QString &varname)
0197 {
0198   //returns a resource to available ones
0199   inc (varname, 1);
0200 }
0201 
0202 bool cVariableList::requestResource (const QString &varname)
0203 {
0204   //is a resource available?
0205   if (getIntValue (varname) <= 0)
0206     //no :(
0207     return false;
0208   //yes, "allocate" one
0209   dec (varname, 1);
0210   return true;
0211 }
0212 
0213 QStringList cVariableList::getList ()
0214 {
0215   map<QString, cVariable *>::iterator it;
0216   QStringList list;
0217   for (it = vars.begin(); it != vars.end(); ++it)
0218     list.push_back (it->first);
0219   return list;
0220 }
0221 
0222 QString cVariableList::expandVariables (const QString &string, bool recursive, cCmdQueue *queue)
0223 {
0224   return doExpandVariables (string, recursive ? MAXEXPANDPASSES : 1, queue);
0225 }
0226 
0227 QString cVariableList::doExpandVariables (const QString &string, int recursionCounter,
0228     cCmdQueue *queue)
0229 {
0230   QString newstring = "";
0231   int len = string.length ();
0232   bool hadvar = false; //whether we had some variable - used to prevent
0233                        //unneeded recursion
0234   bool invar = false;
0235   bool inpar = false; //variable name is in parentheses '(' and ')'
0236   QString varname;
0237   for (int i = 0; i < len; i++)
0238   {
0239     QChar ch = string[i];
0240 
0241     //support things like $$$a correctly
0242     if (invar && (!inpar) && (ch == '$') && (varname.isEmpty()))
0243     {
0244       invar = false;
0245       newstring += ch;
0246     }
0247 
0248     if (!invar)
0249     {
0250       if (ch == '$')
0251       {
0252         invar = true;
0253         inpar = false;
0254         varname = "";
0255       }
0256       else
0257         newstring += ch;
0258     }
0259     else
0260     {
0261       if ((varname == "") && (ch == '(') && (!inpar))
0262         inpar = true;
0263       else
0264       if ((!ch.isLetterOrNumber())&&
0265           (ch != '_')               )  //end of variable name
0266       {
0267         invar = false;
0268 
0269         if (inpar)
0270         {
0271           inpar = false;
0272           if (ch == ')')
0273           {
0274             QString newstr = processVariable (varname, queue);
0275             if (newstr == QString())
0276               newstring += "$(" + varname + ")";
0277             else
0278             {
0279               newstring += newstr;
0280               hadvar = true;
0281             }
0282           }
0283           else
0284             newstring += "$(" + varname + ch;
0285         }
0286         else
0287         {
0288           QString newstr = processVariable (varname, queue);
0289           if (newstr.isEmpty())
0290             newstring += "$" + varname;
0291           else
0292           {
0293             newstring += newstr;
0294             hadvar = true;  
0295           }
0296           newstring += ch;
0297         }
0298       }
0299       else
0300         varname += ch;
0301     }
0302   }
0303 
0304   if (invar) {    //if a variable ends the string
0305     if (inpar)
0306       newstring += "$(" + varname;  //no ending parenthesis - no variable
0307     else
0308     {
0309       QString newstr = processVariable (varname, queue);
0310       if (newstr.isEmpty())
0311         newstring += "$" + varname;
0312       else
0313       {
0314         newstring += newstr;
0315         hadvar = true;
0316       }
0317     }
0318  }
0319 
0320   //another pass if needed, then return the result
0321   recursionCounter--;
0322   if (hadvar && (recursionCounter > 0))
0323     return doExpandVariables (newstring, recursionCounter, queue);
0324   return newstring;
0325 }
0326 
0327 QString cVariableList::processVariable (const QString &varname, cCmdQueue *queue)
0328 {
0329   if (varname.isEmpty()) return varname;  // nothing if the varname is empty
0330   QString vn = varname;
0331   if (varname[0] == '$')
0332     vn = vn.mid(1);
0333 
0334   if (queue && queue->varExists (vn))
0335     return queue->getValue (vn);
0336   return getValue (vn);
0337 }
0338 
0339 void cVariableList::load ()
0340 {
0341   // load the list contents
0342   cProfileManager *pm = cProfileManager::self();
0343 
0344   QString path = pm->profilePath (sess());
0345   QDir dir = QDir (path);
0346   if (!dir.exists()) QDir::root().mkpath (dir.absolutePath());
0347 
0348   QString fName = "variables.xml";
0349 
0350   QFile f (path + "/" + fName);
0351   if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
0352     qDebug() << "No " << fName << " file - nothing to do.";
0353     return;  // no profiles - nothing to do
0354   }
0355 
0356   QXmlStreamReader *reader = new QXmlStreamReader (&f);
0357 
0358   // we have the reader, now we actually load the list
0359   reader->readNext ();  // read the document start
0360   reader->readNext ();
0361   if (reader->isStartElement ())
0362     if (reader->name() == "variables")
0363       if (reader->attributes().value ("version") == "1.0") {
0364         // we're inside the root element, now we load the list
0365         while (!reader->atEnd()) {
0366           cValue val;
0367           QString name = val.load (reader);
0368           if (!name.isEmpty())
0369             set (name, &val);
0370         }
0371       }
0372       else
0373         reader->raiseError (i18n ("This file was created by a newer version of KMuddy, and this version is unable to open it."));
0374     else
0375       reader->raiseError (i18n ("This is not a KMuddy variable list file."));
0376   else if (!reader->hasError())
0377     reader->raiseError (i18n ("This file is corrupted."));
0378 
0379   if (reader->hasError()) {
0380     // TODO: present the error to the user
0381     QString lastError = i18n ("Error at line %1, column %2: %3",
0382                                 QString::number (reader->lineNumber()),
0383                                 QString::number (reader->columnNumber()),
0384                                 reader->errorString());
0385     qWarning() << lastError;
0386   }
0387   delete reader;
0388   f.close ();
0389 }
0390 
0391 void cVariableList::save ()
0392 {
0393   // save the list contents
0394   cProfileManager *pm = cProfileManager::self();
0395 
0396   QString path = pm->profilePath (sess());
0397   QDir dir = QDir (path);
0398   if (!dir.exists()) QDir::root().mkpath (dir.absolutePath());
0399 
0400   dir.remove ("variables.backup");
0401   QString fName = "variables.xml";
0402   if (!QFile(path + "/" + fName).copy (path + "/variables.backup")) {
0403     qDebug() << "Unable to backup " << fName;  // not fatal, may simply not exist
0404   }
0405 
0406   QFile f (path + "/" + fName);
0407   if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
0408     qDebug() << "Unable to open " << (path + "/" + fName) << " for writing.";
0409     return;  // problem
0410   }
0411 
0412   QXmlStreamWriter *writer = new QXmlStreamWriter (&f);
0413 
0414   writer->setAutoFormatting (true);  // make the generated XML more readable
0415   writer->writeStartDocument ();
0416 
0417   writer->writeStartElement ("variables");
0418   writer->writeAttribute ("version", "1.0");
0419 
0420   map<QString, cVariable *>::iterator it;
0421   for (it = vars.begin(); it != vars.end(); ++it)
0422     it->second->getValue()->save (writer, it->first);
0423 
0424   writer->writeEndElement ();
0425   writer->writeEndDocument ();
0426 
0427   f.close ();
0428   delete writer;
0429 }
0430