File indexing completed on 2024-04-14 04:00:10

0001 //
0002 // C++ Implementation: cProfileManager
0003 //
0004 // Description: profile manager
0005 //
0006 /*
0007 Copyright 2008-2011 Tomas Mecir <kmuddy@kmuddy.com>
0008 
0009 This program is free software; you can redistribute it and/or
0010 modify it under the terms of the GNU General Public License as
0011 published by the Free Software Foundation; either version 2 of 
0012 the License, or (at your option) any later version.
0013 
0014 This program is distributed in the hope that it will be useful,
0015 but WITHOUT ANY WARRANTY; without even the implied warranty of
0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017 GNU General Public License for more details.
0018 
0019 You should have received a copy of the GNU General Public License
0020 along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "cprofilemanager.h"
0024 
0025 #include <QAbstractTableModel>
0026 #include <QDebug>
0027 #include <QDir>
0028 #include <QFile>
0029 #include <QXmlStreamReader>
0030 #include <QXmlStreamWriter>
0031 
0032 #include <KLocalizedString>
0033 
0034 #include <map>
0035 
0036 #include "cprofilesettings.h"
0037 
0038 #include "cglobalsettings.h"
0039 
0040 using namespace std;
0041 
0042 // cProfileModel - a model showing all the profiles.
0043 class cProfileModel : public QAbstractTableModel {
0044  public:
0045   cProfileModel (cProfileManager *manager)
0046       : QAbstractTableModel (nullptr), mgr (manager)
0047   {
0048   }
0049 
0050   ~cProfileModel () override
0051   {
0052   }
0053 
0054   int columnCount (const QModelIndex &parent = QModelIndex()) const override
0055   {
0056     if (parent.isValid()) return 0;  // because Qt docs say so
0057     return 4;  // we have 4 columns
0058   }
0059 
0060   int rowCount (const QModelIndex &parent = QModelIndex()) const override
0061   {
0062     if (parent.isValid()) return 0;  // because Qt docs say so
0063     return mgr->profileList().size();
0064   }
0065 
0066   QVariant headerData ( int section, Qt::Orientation /*orientation*/,
0067       int role = Qt::DisplayRole ) const override
0068   {
0069     if (role != Qt::DisplayRole) return QVariant();
0070     switch (section) {
0071       case 0: return i18n ("Profile name");
0072       case 1: return i18n ("Server");
0073       case 2: return i18n ("Port");
0074       case 3: return i18n ("Login name");
0075       default: return QVariant();
0076     }
0077   }
0078 
0079   QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const override
0080   {
0081     // display role only
0082     if (role != Qt::DisplayRole) return QVariant();
0083 
0084     if (index.parent().isValid()) return QVariant();
0085     int row = index.row();
0086     int col = index.column();
0087     if ((col < 0) || (col > 3)) return QVariant();
0088 
0089     if ((row < 0) || (row > mgr->profileList().size())) return QVariant();
0090     QString profile = mgr->profileList()[row];
0091     // okay, we have the profile
0092     cProfileSettings *sett = mgr->settings (profile);
0093     // now fetch the requested information and return it
0094     switch (col) {
0095       case 0: return mgr->visibleProfileName (profile);
0096       case 1: return sett->getString ("server");
0097       case 2: return sett->getInt ("port");
0098       case 3: return sett->getString ("login");
0099       default: return QVariant();
0100     };
0101   }
0102 
0103   void rowChanged (int row) {
0104     emit dataChanged (index (row, 0), index(row, 3));
0105   }
0106 
0107   void addRow (int row) {
0108     beginInsertRows (QModelIndex(), row, row);
0109   }
0110 
0111   void rowAdded () {
0112     endInsertRows ();
0113   }
0114 
0115   void removeRow (int row) {
0116     beginRemoveRows (QModelIndex(), row, row);
0117   }
0118 
0119   void rowRemoved () {
0120     endRemoveRows ();
0121   }
0122 
0123  private:
0124    cProfileManager *mgr;
0125 };
0126 
0127 struct cProfileManager::Private {
0128   QStringList profiles;
0129   map<QString, QString> profileNames;
0130   map<int, QString> sessions;
0131   map<QString, cProfileSettings *> profileSettings;
0132 
0133   cProfileModel *model;
0134 };
0135 
0136 cProfileManager *cProfileManager::_self = nullptr;
0137 
0138 cProfileManager *cProfileManager::self ()
0139 {
0140   if (!_self) {
0141     _self = new cProfileManager;
0142     // we can't load in the constructor, because we need the self() pointer there
0143     _self->load ();
0144     /** Initialize the model. */
0145     _self->init ();
0146   }
0147   return _self;
0148 }
0149 
0150 cProfileManager::cProfileManager ()
0151 {
0152   d = new Private;
0153 }
0154 
0155 cProfileManager::~cProfileManager ()
0156 {
0157   map<QString, cProfileSettings *>::iterator it;
0158   for (it = d->profileSettings.begin(); it != d->profileSettings.end(); ++it)
0159     delete it->second;
0160   delete d->model;
0161   delete d;
0162 }
0163 
0164 QAbstractTableModel *cProfileManager::model () const
0165 {
0166   return d->model;
0167 }
0168 
0169 QStringList cProfileManager::profileList () const
0170 {
0171   return d->profiles;
0172 }
0173 
0174 bool cProfileManager::profileExists (const QString &name)
0175 {
0176   return (d->profileNames.count (name) != 0);
0177 }
0178 
0179 QString cProfileManager::profilePath (const QString &profileName) const
0180 {
0181   QString path = cGlobalSettings::self()->profilePath();
0182   return path + "/" + profileName + "/";
0183 }
0184 
0185 void cProfileManager::assignSession (int sess, const QString &profileName)
0186 {
0187   if (d->profileNames.count (profileName))
0188     d->sessions[sess] = profileName;
0189 }
0190 
0191 bool cProfileManager::sessionAssigned (int sess)
0192 {
0193   return (d->sessions.count (sess));
0194 }
0195 
0196 QString cProfileManager::profileName (int sess)
0197 {
0198   if (!sessionAssigned (sess)) return QString();
0199   return d->sessions[sess];
0200 }
0201 
0202 bool cProfileManager::hasSessionAssigned (const QString &profile)
0203 {
0204   map<int, QString>::iterator it;
0205   for (it = d->sessions.begin(); it != d->sessions.end(); ++it)
0206     if (it->second == profile)
0207       return true;
0208   return false;
0209 }
0210 
0211 void cProfileManager::unassignSession (int sess)
0212 {
0213   // save session settings
0214   if (sessionAssigned (sess)) {
0215     cProfileSettings *s = settings (sess);
0216     if (s) s->save();
0217   }
0218   // and remove it
0219   d->sessions.erase (sess);
0220 }
0221 
0222 QString cProfileManager::profilePath (int sess)
0223 {
0224   if (d->sessions.count (sess))
0225     return profilePath (d->sessions[sess]);
0226 //  return QString();   // not found
0227   return cGlobalSettings::self()->profilePath() + "/";
0228 }
0229 
0230 QString cProfileManager::visibleProfileName (QString name)
0231 {
0232   if (d->profileNames.count (name))
0233     return d->profileNames[name];
0234   return QString();
0235 }
0236 
0237 cProfileSettings *cProfileManager::settings (const QString &profileName)
0238 {
0239   if (d->profileSettings.count (profileName))
0240     return d->profileSettings[profileName];
0241   return nullptr;
0242 }
0243 
0244 cProfileSettings *cProfileManager::settings (int sess)
0245 {
0246   QString name;
0247   if (!d->sessions.count (sess)) return nullptr;
0248   name = d->sessions[sess];
0249   if (d->profileSettings.count (name))
0250     return d->profileSettings[name];
0251   return nullptr;
0252 }
0253 
0254 QString cProfileManager::newProfile (const QString &name)
0255 {
0256   // find first available directory name
0257 
0258   QDir mainDir (cGlobalSettings::self()->profilePath());
0259   int num = 0;
0260   QString dirname;
0261   bool found = false;
0262   while (num < 1000000) {  // nobody will have more than a million profiles :)
0263     ++num;
0264     dirname = "profile" + QString::number (num);
0265     // check if the profile and/or directory name exists
0266     if (d->profileNames.count (dirname)) continue;
0267     if (mainDir.exists (dirname)) continue;
0268     // they do not - we're good to go
0269     found = true;
0270     break;
0271   }
0272   if (!found) return QString();
0273 
0274   // notify the model
0275   d->model->addRow (d->profiles.size());
0276   
0277   // create the directory
0278   mainDir.mkdir (dirname);
0279 
0280   // adjust the structures
0281   d->profiles.push_back (dirname);
0282   d->profileNames[dirname] = name;
0283   cProfileSettings *s = new cProfileSettings (dirname);
0284   d->profileSettings[dirname] = s;
0285 
0286   // we are done - notify the model
0287   d->model->rowAdded ();
0288 
0289   save ();
0290   return dirname;
0291 }
0292 
0293 bool cProfileManager::renameProfile (const QString &name, const QString &newName)
0294 {
0295   d->profileNames[name] = newName;
0296   profileInfoChanged (name);
0297   save ();
0298   return true;
0299 }
0300 
0301 bool cProfileManager::deleteProfile (const QString &name, bool deleteFiles)
0302 {
0303   // check if any open connection uses the profile
0304   if (hasSessionAssigned (name)) return false;
0305   if (!profileExists (name)) return false;
0306 
0307   int idx = d->profiles.indexOf (name);
0308   d->model->removeRow (idx);
0309 
0310   // remove the profile
0311   d->profiles.removeAll (name);
0312   d->profileNames.erase (name);
0313   if (d->profileSettings.count (name))
0314     delete d->profileSettings[name];
0315   d->profileSettings.erase (name);
0316 
0317   // delete files, if requested
0318   if (deleteFiles) {
0319     QString path = profilePath (name);
0320     QDir dir (path);
0321     QFileInfoList list = dir.entryInfoList();
0322     for (int i = 0; i < list.size(); ++i) {
0323       QFileInfo fileInfo = list.at(i);
0324       dir.remove (fileInfo.fileName());
0325     }
0326     QDir mainDir (cGlobalSettings::self()->profilePath());
0327     mainDir.rmdir (name);
0328   }
0329 
0330   d->model->rowRemoved ();
0331   save ();
0332   return true;
0333 }
0334 
0335 bool cProfileManager::duplicateProfile (const QString &name, const QString &newName)
0336 {
0337   // create a new profile
0338   QString profile = newProfile (newName);
0339   if (profile.isEmpty()) return false;
0340 
0341   // and copy over everything to it
0342   QString path = profilePath (name);
0343   QString path2 = profilePath (profile);
0344   QDir dir (path);
0345   QFileInfoList list = dir.entryInfoList();
0346   for (int i = 0; i < list.size(); ++i) {
0347     QFileInfo fileInfo = list.at(i);
0348     QFile(path + "/" + fileInfo.fileName()).copy (path2 + "/" + fileInfo.fileName());
0349   }
0350 
0351   // and reload the settings
0352   cProfileSettings *sett = new cProfileSettings (profile);
0353   if (d->profileSettings.count (profile))
0354     delete d->profileSettings[profile];
0355   d->profileSettings[profile] = sett;
0356   sett->save ();  // just in case the old settings have overwritten the XML
0357 
0358   profileInfoChanged (profile);
0359   save ();
0360   return true;
0361 }
0362 
0363 void cProfileManager::profileInfoChanged (const QString &name)
0364 {
0365   int idx = d->profiles.indexOf (name);
0366   if (idx >= 0)
0367     d->model->rowChanged (idx);
0368 }
0369 
0370 // TODO: error reporting !!!
0371 void cProfileManager::load ()
0372 {
0373   // create the profile directory if it does not exist
0374   QString path = cGlobalSettings::self()->profilePath();
0375   QDir dir = QDir (path);
0376   if (!dir.exists()) QDir::root().mkpath (dir.absolutePath());
0377 
0378   // create the XML loader
0379   QFile f (path + "/profiles.xml");
0380   if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
0381     qDebug() << "No profiles file - nothing to do.";
0382     return;  // no profiles - nothing to do
0383   }
0384   QXmlStreamReader *reader = new QXmlStreamReader (&f);
0385 
0386   // load the profiles
0387   reader->readNext ();  // read the document start
0388   reader->readNext ();
0389   if (reader->isStartElement ())
0390     if (reader->name() == "profiles")
0391       if (reader->attributes().value ("version") == "1.0") {
0392         // okay, read the list
0393         while (!reader->atEnd()) {
0394           reader->readNext ();
0395           if (reader->isStartElement () && (reader->name() == "profile")) {
0396             // found another profile
0397             QString id = reader->attributes().value ("id").toString();
0398             QString name = reader->attributes().value ("name").toString();
0399             if (!id.isEmpty() && (!d->profileNames.count (id))) {
0400               // add the profile
0401               d->profiles.push_back (id);
0402               d->profileNames[id] = name;
0403               cProfileSettings *sett = new cProfileSettings (id);
0404               d->profileSettings[id] = sett;
0405             }
0406           }
0407         }
0408       } else reader->raiseError ("Unknown profile file version.");
0409     else reader->raiseError ("This is not a valid profile list file.");
0410   else reader->raiseError ("This file is corrupted.");
0411 
0412   if (reader->hasError()) {
0413     qDebug() << ("Error in profiles.xml at line " + QString::number (reader->lineNumber()) + ", column " + QString::number (reader->columnNumber()) + QString (": ") + reader->errorString());
0414   }
0415 
0416   // close the file
0417   f.close ();
0418   delete reader;
0419 }
0420 
0421 void cProfileManager::init ()
0422 {
0423   d->model = new cProfileModel (this);
0424 }
0425 
0426 void cProfileManager::save ()
0427 {
0428   // create the profile directory if it does not exist
0429   QString path = cGlobalSettings::self()->profilePath();
0430   QDir dir = QDir (path);
0431   if (!dir.exists()) QDir::root().mkpath (dir.absolutePath());
0432 
0433   // backup the old profile file, if any
0434   dir.remove ("profiles.backup");
0435   if (!QFile(path + "/profiles.xml").copy (path + "/profiles.backup")) {
0436     qDebug() << "Unable to backup profiles.xml.";  // not fatal, may simply not exist
0437   }
0438 
0439   QFile f (path + "/profiles.xml");
0440   if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
0441   {
0442     qDebug() << "Unable to open profile.xml for writing.";
0443     return;
0444   }
0445   // save the profile file
0446   QXmlStreamWriter *writer = new QXmlStreamWriter (&f);
0447 
0448   writer->setAutoFormatting (true);  // make the generated XML more readable
0449   writer->writeStartDocument ();
0450 
0451   writer->writeStartElement ("profiles");
0452   writer->writeAttribute ("version", "1.0");
0453  
0454   map<QString, QString>::iterator it;
0455   for (it = d->profileNames.begin(); it != d->profileNames.end(); ++it) {
0456     writer->writeStartElement ("profile");
0457     writer->writeAttribute ("id", it->first);
0458     writer->writeAttribute ("name", it->second);
0459     writer->writeEndElement ();
0460   }
0461   
0462   writer->writeEndElement ();
0463   writer->writeEndDocument ();
0464 
0465   f.close ();
0466   delete writer;
0467 }
0468