File indexing completed on 2024-05-12 16:39:40
0001 /* This file is part of the KDE project 0002 Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> 0003 Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Library General Public 0007 License as published by the Free Software Foundation; either 0008 version 2 of the License, or (at your option) any later version. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this library; see the file COPYING.LIB. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 * Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "kexiprojectdata.h" 0022 #include <kexiutils/utils.h> 0023 #include <kexi_global.h> 0024 0025 #include <KDbUtils> 0026 #include <KDbDriverManager> 0027 #include <KDbConnectionData> 0028 #include <KDbDriverMetaData> 0029 0030 #include <KConfig> 0031 #include <KConfigGroup> 0032 0033 #include <QDir> 0034 #include <QStringList> 0035 #include <QDebug> 0036 0037 //! Version of the KexiProjectData format. 0038 #define KEXIPROJECTDATA_FORMAT 3 0039 /* CHANGELOG: 0040 v1: initial version 0041 v2: "encryptedPassword" field added. 0042 For backward compatibility, it is not used if the connection data has been loaded from 0043 a file saved with version 1. In such cases unencrypted "password" field is used. 0044 v3: "name" for shortcuts to file-based databases is a full file path. 0045 If the file is within the user's home directory, the dir is replaced with $HOME, 0046 e.g. name=$HOME/mydb.kexi. Not compatible with earlier versions but in these 0047 versions only filename was stored so the file was generally inaccessible anyway. 0048 "lastOpened" field added of type date/time (ISO format). 0049 */ 0050 0051 //! @internal 0052 class KexiProjectDataPrivate 0053 { 0054 public: 0055 KexiProjectDataPrivate() 0056 : userMode(false) 0057 , readOnly(false) {} 0058 0059 KDbConnectionData connData; 0060 QDateTime lastOpened; 0061 bool userMode; 0062 bool readOnly; 0063 }; 0064 0065 //--------------------------------------- 0066 0067 KexiProjectData::KexiProjectData() 0068 : QObject(0) 0069 , KDbObject() 0070 , formatVersion(0) 0071 , d(new KexiProjectDataPrivate()) 0072 { 0073 setObjectName("KexiProjectData"); 0074 } 0075 0076 KexiProjectData::KexiProjectData( 0077 const KDbConnectionData &cdata, const QString& dbname, const QString& caption) 0078 : QObject(0) 0079 , KDbObject() 0080 , formatVersion(0) 0081 , d(new KexiProjectDataPrivate()) 0082 { 0083 setObjectName("KexiProjectData"); 0084 d->connData = cdata; 0085 setDatabaseName(cdata.databaseName().isEmpty() ? dbname : cdata.databaseName()); 0086 setCaption(caption); 0087 } 0088 0089 KexiProjectData::KexiProjectData(const KexiProjectData& pdata) 0090 : QObject(0) 0091 , KDbObject() 0092 , KDbResultable(pdata) 0093 , d(new KexiProjectDataPrivate()) 0094 { 0095 setObjectName("KexiProjectData"); 0096 *this = pdata; 0097 autoopenObjects = pdata.autoopenObjects; 0098 } 0099 0100 KexiProjectData::~KexiProjectData() 0101 { 0102 delete d; 0103 } 0104 0105 KexiProjectData& KexiProjectData::operator=(const KexiProjectData & pdata) 0106 { 0107 static_cast<KDbObject&>(*this) = static_cast<const KDbObject&>(pdata); 0108 //deep copy 0109 autoopenObjects = pdata.autoopenObjects; 0110 formatVersion = pdata.formatVersion; 0111 *d = *pdata.d; 0112 return *this; 0113 } 0114 0115 KDbConnectionData* KexiProjectData::connectionData() 0116 { 0117 return &d->connData; 0118 } 0119 0120 const KDbConnectionData* KexiProjectData::connectionData() const 0121 { 0122 return &d->connData; 0123 } 0124 0125 QString KexiProjectData::databaseName() const 0126 { 0127 return KDbObject::name(); 0128 } 0129 0130 void KexiProjectData::setDatabaseName(const QString& dbName) 0131 { 0132 //qDebug() << dbName; 0133 //qDebug() << *this; 0134 KDbObject::setName(dbName); 0135 } 0136 0137 bool KexiProjectData::userMode() const 0138 { 0139 return d->userMode; 0140 } 0141 0142 QDateTime KexiProjectData::lastOpened() const 0143 { 0144 return d->lastOpened; 0145 } 0146 0147 void KexiProjectData::setLastOpened(const QDateTime& lastOpened) 0148 { 0149 d->lastOpened = lastOpened; 0150 0151 } 0152 QString KexiProjectData::description() const 0153 { 0154 return KDbObject::description(); 0155 } 0156 0157 void KexiProjectData::setDescription(const QString& desc) 0158 { 0159 return KDbObject::setDescription(desc); 0160 } 0161 0162 // static 0163 KLocalizedString KexiProjectData::infoString(const QString &databaseName, 0164 const KDbConnectionData &data) 0165 { 0166 if (data.databaseName().isEmpty()) { 0167 //server-based 0168 return kxi18nc("@info database connection", 0169 "<resource>%1</resource> (connection <resource>%2</resource>)") 0170 .subs(databaseName).subs(data.toUserVisibleString()); 0171 } 0172 //file-based 0173 return kxi18nc("@info database name", 0174 "<resource>%1</resource>").subs(data.databaseName()); 0175 } 0176 0177 KLocalizedString KexiProjectData::infoString() const 0178 { 0179 return infoString(databaseName(), d->connData); 0180 } 0181 0182 void KexiProjectData::setReadOnly(bool set) 0183 { 0184 d->readOnly = set; 0185 } 0186 0187 bool KexiProjectData::isReadOnly() const 0188 { 0189 return d->readOnly; 0190 } 0191 0192 bool KexiProjectData::load(const QString& fileName, QString* _groupKey) 0193 { 0194 //! @todo how about readOnly arg? 0195 KConfig config(fileName, KConfig::SimpleConfig); 0196 KConfigGroup cg = config.group("File Information"); 0197 int _formatVersion = cg.readEntry("version", KEXIPROJECTDATA_FORMAT); 0198 0199 QString groupKey; 0200 if (!_groupKey || _groupKey->isEmpty()) { 0201 QStringList groups(config.groupList()); 0202 foreach(const QString &s, groups) { 0203 if (s.toLower() != "file information") { 0204 groupKey = s; 0205 break; 0206 } 0207 } 0208 if (groupKey.isEmpty()) { 0209 m_result = KDbResult(xi18n("File <filename>%1</filename> contains no connection information.", 0210 fileName)); 0211 return false; 0212 } 0213 if (_groupKey) 0214 *_groupKey = groupKey; 0215 } else { 0216 if (!config.hasGroup(*_groupKey)) { 0217 m_result = KDbResult(xi18n("File <filename>%1</filename> does not contain group <resource>%2</resource>.", 0218 fileName, *_groupKey)); 0219 return false; 0220 } 0221 groupKey = *_groupKey; 0222 } 0223 0224 cg = config.group(groupKey); 0225 QString type(cg.readEntry("type", "database").toLower()); 0226 0227 bool isDatabaseShortcut; 0228 if (type == "database") { 0229 isDatabaseShortcut = true; 0230 } else if (type == "connection") { 0231 isDatabaseShortcut = false; 0232 } else { 0233 m_result = KDbResult(xi18n("Invalid value <resource>%1</resource> type specified in group " 0234 "<resource>%2</resource> of file <filename>%3</filename>.", 0235 type, groupKey, fileName)); 0236 return false; 0237 } 0238 0239 const QString driverName = cg.readEntry("engine").toLower(); 0240 if (driverName.isEmpty()) { 0241 m_result = KDbResult(xi18n("No valid \"engine\" field specified in group <resource>%1</resource> " 0242 "of file <filename>%2</filename>.", groupKey, fileName)); 0243 return false; 0244 } 0245 0246 // verification OK, now applying the values: 0247 // -- "engine" is backward compatible simple name, not a driver ID 0248 d->connData.setDriverId(QString::fromLatin1("org.kde.kdb.") + driverName); 0249 formatVersion = _formatVersion; 0250 d->connData.setHostName(cg.readEntry("server")); //empty allowed, means localhost 0251 if (isDatabaseShortcut) { 0252 KDbDriverManager driverManager; 0253 const KDbDriverMetaData *driverMetaData = driverManager.driverMetaData(d->connData.driverId()); 0254 if (!driverMetaData) { 0255 m_result = driverManager.result(); 0256 return false; 0257 } 0258 const bool fileBased = driverMetaData->isFileBased() 0259 && driverMetaData->id() == d->connData.driverId(); 0260 setCaption(cg.readEntry("caption")); 0261 setDescription(cg.readEntry("comment")); 0262 d->connData.setCaption(QString()); /* connection name is not specified... */ 0263 d->connData.setDescription(QString()); 0264 if (fileBased) { 0265 QString fn(cg.readEntry("name")); 0266 if (!fn.isEmpty()) { 0267 const QString homeVar("$HOME"); 0268 if (fn.startsWith(homeVar)) { 0269 QString home(QDir::homePath()); 0270 if (home.endsWith('/')) { 0271 home.chop(1); 0272 } 0273 fn = home + fn.mid(homeVar.length()); 0274 } 0275 d->connData.setDatabaseName(fn); 0276 setDatabaseName(d->connData.databaseName()); 0277 } 0278 } 0279 else { 0280 setDatabaseName(cg.readEntry("name")); 0281 } 0282 } else { // connection 0283 d->connData.setDatabaseName(QString()); 0284 setCaption(QString()); 0285 d->connData.setCaption(cg.readEntry("caption")); 0286 setDescription(QString()); 0287 d->connData.setDescription(cg.readEntry("comment")); 0288 setDatabaseName(QString()); /* db name is not specified... */ 0289 } 0290 d->connData.setPort(cg.readEntry("port", 0)); 0291 d->connData.setUseLocalSocketFile(cg.readEntry("useLocalSocketFile", true)); 0292 d->connData.setLocalSocketFileName(cg.readEntry("localSocketFile")); 0293 d->connData.setSavePassword(cg.hasKey("password") || cg.hasKey("encryptedPassword")); 0294 if (formatVersion >= 2) { 0295 QString password = cg.readEntry("encryptedPassword"); 0296 if (!KDbUtils::simpleDecrypt(&password)) { 0297 qWarning() << "Encrypted password in connection file" << fileName << "cannot be decrypted so won't be used. " 0298 "Delete \"encryptedPassword\" line or correct it to fix this issue."; 0299 password.clear(); 0300 } 0301 d->connData.setPassword(password); 0302 } 0303 if (!cg.hasKey("encryptedPassword")) {//no "encryptedPassword", for compatibility 0304 //UNSAFE 0305 d->connData.setPassword(cg.readEntry("password")); 0306 } 0307 d->connData.setUserName(cg.readEntry("user")); 0308 QString lastOpenedStr(cg.readEntry("lastOpened")); 0309 if (!lastOpenedStr.isEmpty()) { 0310 QDateTime lastOpened(QDateTime::fromString(lastOpenedStr, Qt::ISODate)); 0311 if (lastOpened.isValid()) { 0312 setLastOpened(lastOpened); 0313 } 0314 } 0315 /*! @todo add "options=", eg. as string list? */ 0316 return true; 0317 } 0318 0319 //! @return a simple driver name (as used by Kexi <= 2.x) for KDb's driver ID 0320 //! or empty string if matching name not found. 0321 //! Example: "sqlite" name is returned for "org.kde.kdb.sqlite" ID. 0322 static QString driverIdToKexi2DriverName(const QString &driverId) 0323 { 0324 QString prefix = "org.kde.kdb."; 0325 if (!driverId.startsWith(prefix)) { 0326 return QString(); 0327 } 0328 QString suffix = driverId.mid(prefix.length()); 0329 if (suffix == "sqlite" 0330 || suffix == "mysql" 0331 || suffix == "postgresql" 0332 || suffix == "xbase" 0333 || suffix == "sybase") 0334 { 0335 return suffix; 0336 } 0337 return QString(); 0338 } 0339 0340 bool KexiProjectData::save(const QString& fileName, bool savePassword, 0341 QString* _groupKey, bool overwriteFirstGroup) 0342 { 0343 //! @todo how about readOnly arg? 0344 KConfig config(fileName, KConfig::SimpleConfig); 0345 KConfigGroup cg = config.group("File Information"); 0346 0347 int realFormatVersion = formatVersion; 0348 if (realFormatVersion == 0) /* 0 means "default version"*/ 0349 realFormatVersion = KEXIPROJECTDATA_FORMAT; 0350 cg.writeEntry("version", realFormatVersion); 0351 0352 const bool thisIsConnectionData = databaseName().isEmpty(); 0353 0354 //use or find a nonempty group key 0355 QString groupKey; 0356 if (_groupKey && !_groupKey->isEmpty()) { 0357 groupKey = *_groupKey; 0358 } else { 0359 QString groupPrefix; 0360 const QStringList groups(config.groupList()); 0361 if (overwriteFirstGroup && !groups.isEmpty()) { 0362 foreach(const QString &s, groups) { 0363 if (s.toLower() != "file information") { 0364 groupKey = s; 0365 break; 0366 } 0367 } 0368 } 0369 0370 if (groupKey.isEmpty()) { 0371 //find a new unique name 0372 if (thisIsConnectionData) 0373 groupPrefix = "Connection%1"; //do not i18n! 0374 else 0375 groupPrefix = "Database%1"; //do not i18n! 0376 0377 int number = 1; 0378 while (config.hasGroup(groupPrefix.arg(number))) //a new group key couldn't exist 0379 number++; 0380 groupKey = groupPrefix.arg(number); 0381 } 0382 if (_groupKey) //return this one (generated or found) 0383 *_groupKey = groupKey; 0384 } 0385 0386 config.group(groupKey).deleteGroup(); 0387 cg = config.group(groupKey); 0388 KDbDriverManager manager; 0389 const KDbDriverMetaData *metaData = manager.driverMetaData(d->connData.driverId()); 0390 if (!metaData) { 0391 m_result = manager.result(); 0392 return false; 0393 } 0394 const bool fileBased = metaData->isFileBased(); 0395 if (thisIsConnectionData) { 0396 cg.writeEntry("type", "connection"); 0397 if (!d->connData.caption().isEmpty()) 0398 cg.writeEntry("caption", d->connData.caption()); 0399 if (!d->connData.description().isEmpty()) 0400 cg.writeEntry("comment", d->connData.description()); 0401 } else { //database 0402 cg.writeEntry("type", "database"); 0403 if (!caption().isEmpty()) 0404 cg.writeEntry("caption", caption()); 0405 if (fileBased) { 0406 QString fn(d->connData.databaseName()); 0407 if (!QDir::homePath().isEmpty() && fn.startsWith(QDir::homePath())) { 0408 // replace prefix if == $HOME 0409 fn = fn.mid(QDir::homePath().length()); 0410 if (!fn.startsWith('/')) 0411 fn.prepend('/'); 0412 fn.prepend(QLatin1String("$HOME")); 0413 } 0414 cg.writeEntry("name", fn); 0415 } 0416 else { // server-based 0417 cg.writeEntry("name", databaseName()); 0418 } 0419 if (!description().isEmpty()) 0420 cg.writeEntry("comment", description()); 0421 } 0422 0423 QString engine = driverIdToKexi2DriverName(d->connData.driverId()); 0424 if (engine.isEmpty()) { 0425 engine = d->connData.driverId(); 0426 } 0427 cg.writeEntry("engine", engine); 0428 if (!fileBased) { 0429 if (!d->connData.hostName().isEmpty()) 0430 cg.writeEntry("server", d->connData.hostName()); 0431 0432 if (d->connData.port() != 0) 0433 cg.writeEntry("port", int(d->connData.port())); 0434 cg.writeEntry("useLocalSocketFile", d->connData.useLocalSocketFile()); 0435 if (!d->connData.localSocketFileName().isEmpty()) 0436 cg.writeEntry("localSocketFile", d->connData.localSocketFileName()); 0437 if (!d->connData.userName().isEmpty()) 0438 cg.writeEntry("user", d->connData.userName()); 0439 } 0440 0441 if (savePassword || d->connData.savePassword()) { 0442 if (realFormatVersion < 2) { 0443 cg.writeEntry("password", d->connData.password()); 0444 } else { 0445 QString encryptedPassword = d->connData.password(); 0446 KDbUtils::simpleCrypt(&encryptedPassword); 0447 cg.writeEntry("encryptedPassword", encryptedPassword); 0448 encryptedPassword.fill(' '); //for security 0449 } 0450 } 0451 0452 if (lastOpened().isValid()) { 0453 cg.writeEntry("lastOpened", lastOpened().toString(Qt::ISODate)); 0454 } 0455 0456 /*! @todo add "options=", eg. as string list? */ 0457 if (!cg.sync()) { 0458 m_result = KDbResult("Could not write project file."); 0459 return false; 0460 } 0461 return true; 0462 } 0463 0464 KEXICORE_EXPORT QDebug operator<<(QDebug dbg, const KexiProjectData& data) 0465 { 0466 dbg.space() << "KexiProjectData" << "databaseName=" << data.databaseName() 0467 << "lastOpened=" << data.lastOpened() << "description=" << data.description() 0468 << "connectionData=("; 0469 if (data.connectionData()) { 0470 dbg.nospace() << *data.connectionData(); 0471 } 0472 dbg.nospace() << ")"; 0473 return dbg.space(); 0474 }