1
/* This file is part of the KDE project
2
Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at>
3
Copyright (C) 2003-2016 JarosÅaw Staniek <staniek@kde.org>
5
This library is free software; you can redistribute it and/or
6
modify it under the terms of the GNU Library General Public
7
License as published by the Free Software Foundation; either
8
version 2 of the License, or (at your option) any later version.
10
This library is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
Library General Public License for more details.
15
You should have received a copy of the GNU Library General Public License
16
along with this library; see the file COPYING.LIB. If not, write to
17
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18
* Boston, MA 02110-1301, USA.
21
#include "kexiprojectdata.h"
22
#include <kexiutils/utils.h>
23
#include <kexi_global.h>
26
#include <KDbDriverManager>
27
#include <KDbConnectionData>
28
#include <KDbDriverMetaData>
31
#include <KConfigGroup>
34
#include <QStringList>
37
#include <sys/types.h>
40
//! Version of the KexiProjectData format.
41
#define KEXIPROJECTDATA_FORMAT 3
44
v2: "encryptedPassword" field added.
45
For backward compatibility, it is not used if the connection data has been loaded from
46
a file saved with version 1. In such cases unencrypted "password" field is used.
47
v3: "name" for shortcuts to file-based databases is a full file path.
48
If the file is within the user's home directory, the dir is replaced with $HOME,
49
e.g. name=$HOME/mydb.kexi. Not compatible with earlier versions but in these
50
versions only filename was stored so the file was generally inaccessible anyway.
51
"lastOpened" field added of type date/time (ISO format).
55
class KexiProjectDataPrivate
58
KexiProjectDataPrivate()
62
KDbConnectionData connData;
68
//---------------------------------------
70
KexiProjectData::AutoOpenObjects::AutoOpenObjects()
71
: QList<ObjectInfo*>()
75
KexiProjectData::AutoOpenObjects::AutoOpenObjects(const KexiProjectData::AutoOpenObjects& other)
76
: QList<ObjectInfo*>()
81
KexiProjectData::AutoOpenObjects::~AutoOpenObjects()
86
KexiProjectData::AutoOpenObjects& KexiProjectData::AutoOpenObjects::operator=(
87
const KexiProjectData::AutoOpenObjects & other)
90
for (QListIterator<ObjectInfo*> it(other);it.hasNext();) //deep copy
91
append(new ObjectInfo(*it.next()));
95
//---------------------------------------
97
KexiProjectData::KexiProjectData()
101
, d(new KexiProjectDataPrivate())
103
setObjectName("KexiProjectData");
106
KexiProjectData::KexiProjectData(
107
const KDbConnectionData &cdata, const QString& dbname, const QString& caption)
111
, d(new KexiProjectDataPrivate())
113
setObjectName("KexiProjectData");
115
setDatabaseName(cdata.databaseName().isEmpty() ? dbname : cdata.databaseName());
119
KexiProjectData::KexiProjectData(const KexiProjectData& pdata)
122
, KDbResultable(pdata)
123
, d(new KexiProjectDataPrivate())
125
setObjectName("KexiProjectData");
127
autoopenObjects = pdata.autoopenObjects;
130
KexiProjectData::~KexiProjectData()
135
KexiProjectData& KexiProjectData::operator=(const KexiProjectData & pdata)
137
static_cast<KDbObject&>(*this) = static_cast<const KDbObject&>(pdata);
139
autoopenObjects = pdata.autoopenObjects;
140
formatVersion = pdata.formatVersion;
145
KDbConnectionData* KexiProjectData::connectionData()
150
const KDbConnectionData* KexiProjectData::connectionData() const
155
QString KexiProjectData::databaseName() const
157
return KDbObject::name();
160
void KexiProjectData::setDatabaseName(const QString& dbName)
162
//qDebug() << dbName;
164
KDbObject::setName(dbName);
167
bool KexiProjectData::userMode() const
172
QDateTime KexiProjectData::lastOpened() const
174
return d->lastOpened;
177
void KexiProjectData::setLastOpened(const QDateTime& lastOpened)
179
d->lastOpened = lastOpened;
182
QString KexiProjectData::description() const
184
return KDbObject::description();
187
void KexiProjectData::setDescription(const QString& desc)
189
return KDbObject::setDescription(desc);
193
KLocalizedString KexiProjectData::infoString(const QString &databaseName,
194
const KDbConnectionData &data)
196
if (data.databaseName().isEmpty()) {
198
return kxi18nc("@info database connection",
199
"<resource>%1</resource> (connection <resource>%2</resource>)")
200
.subs(databaseName).subs(data.toUserVisibleString());
203
return kxi18nc("@info database name",
204
"<resource>%1</resource>").subs(data.databaseName());
207
KLocalizedString KexiProjectData::infoString() const
209
return infoString(databaseName(), d->connData);
212
void KexiProjectData::setReadOnly(bool set)
217
bool KexiProjectData::isReadOnly() const
222
bool KexiProjectData::load(const QString& fileName, QString* _groupKey)
224
//! @todo how about readOnly arg?
225
KConfig config(fileName, KConfig::SimpleConfig);
226
KConfigGroup cg = config.group("File Information");
227
int _formatVersion = cg.readEntry("version", KEXIPROJECTDATA_FORMAT);
230
if (!_groupKey || _groupKey->isEmpty()) {
231
QStringList groups(config.groupList());
232
foreach(const QString &s, groups) {
233
if (s.toLower() != "file information") {
238
if (groupKey.isEmpty()) {
239
m_result = KDbResult(xi18n("File <filename>%1</filename> contains no connection information.",
244
*_groupKey = groupKey;
246
if (!config.hasGroup(*_groupKey)) {
247
m_result = KDbResult(xi18n("File <filename>%1</filename> does not contain group <resource>%2</resource>.",
248
fileName, *_groupKey));
251
groupKey = *_groupKey;
254
cg = config.group(groupKey);
255
QString type(cg.readEntry("type", "database").toLower());
257
bool isDatabaseShortcut;
258
if (type == "database") {
259
isDatabaseShortcut = true;
260
} else if (type == "connection") {
261
isDatabaseShortcut = false;
263
m_result = KDbResult(xi18n("Invalid value <resource>%1</resource> type specified in group "
264
"<resource>%2</resource> of file <filename>%3</filename>.",
265
type, groupKey, fileName));
269
const QString driverName = cg.readEntry("engine").toLower();
270
if (driverName.isEmpty()) {
271
m_result = KDbResult(xi18n("No valid \"engine\" field specified in group <resource>%1</resource> "
272
"of file <filename>%2</filename>.", groupKey, fileName));
276
// verification OK, now applying the values:
277
// -- "engine" is backward compatible simple name, not a driver ID
278
d->connData.setDriverId(QString::fromLatin1("org.kde.kdb.") + driverName);
279
formatVersion = _formatVersion;
280
d->connData.setHostName(cg.readEntry("server")); //empty allowed, means localhost
281
if (isDatabaseShortcut) {
282
KDbDriverManager driverManager;
283
const KDbDriverMetaData *driverMetaData = driverManager.driverMetaData(d->connData.driverId());
284
if (!driverMetaData) {
285
m_result = driverManager.result();
288
const bool fileBased = driverMetaData->isFileBased()
289
&& driverMetaData->id() == d->connData.driverId();
290
setCaption(cg.readEntry("caption"));
291
setDescription(cg.readEntry("comment"));
292
d->connData.setCaption(QString()); /* connection name is not specified... */
293
d->connData.setDescription(QString());
295
QString fn(cg.readEntry("name"));
297
const QString homeVar("$HOME");
298
if (fn.startsWith(homeVar)) {
299
QString home(QDir::homePath());
300
if (home.endsWith('/')) {
303
fn = home + fn.mid(homeVar.length());
305
d->connData.setDatabaseName(fn);
306
setDatabaseName(d->connData.databaseName());
310
setDatabaseName(cg.readEntry("name"));
312
} else { // connection
313
d->connData.setDatabaseName(QString());
314
setCaption(QString());
315
d->connData.setCaption(cg.readEntry("caption"));
316
setDescription(QString());
317
d->connData.setDescription(cg.readEntry("comment"));
318
setDatabaseName(QString()); /* db name is not specified... */
320
d->connData.setPort(cg.readEntry("port", 0));
321
d->connData.setUseLocalSocketFile(cg.readEntry("useLocalSocketFile", true));
322
d->connData.setLocalSocketFileName(cg.readEntry("localSocketFile"));
323
d->connData.setSavePassword(cg.hasKey("password") || cg.hasKey("encryptedPassword"));
324
if (formatVersion >= 2) {
325
QString password = cg.readEntry("encryptedPassword");
326
if (!KDbUtils::simpleDecrypt(&password)) {
327
qWarning() << "Encrypted password in connection file" << fileName << "cannot be decrypted so won't be used. "
328
"Remove \"encryptedPassword\" line or correct it to fix this issue.";
331
d->connData.setPassword(password);
333
if (!cg.hasKey("encryptedPassword")) {//no "encryptedPassword", for compatibility
335
d->connData.setPassword(cg.readEntry("password"));
337
d->connData.setUserName(cg.readEntry("user"));
338
QString lastOpenedStr(cg.readEntry("lastOpened"));
339
if (!lastOpenedStr.isEmpty()) {
340
QDateTime lastOpened(QDateTime::fromString(lastOpenedStr, Qt::ISODate));
341
if (lastOpened.isValid()) {
342
setLastOpened(lastOpened);
345
/*! @todo add "options=", eg. as string list? */
349
//! @return a simple driver name (as used by Kexi <= 2.x) for KDb's driver ID
350
//! or empty string if matching name not found.
351
//! Example: "sqlite" name is returned for "org.kde.kdb.sqlite" ID.
352
static QString driverIdToKexi2DriverName(const QString &driverId)
354
QString prefix = "org.kde.kdb.";
355
if (!driverId.startsWith(prefix)) {
358
QString suffix = driverId.mid(prefix.length());
359
if (suffix == "sqlite"
361
|| suffix == "postgresql"
363
|| suffix == "sybase")
370
bool KexiProjectData::save(const QString& fileName, bool savePassword,
371
QString* _groupKey, bool overwriteFirstGroup)
373
//! @todo how about readOnly arg?
374
KConfig config(fileName, KConfig::SimpleConfig);
375
KConfigGroup cg = config.group("File Information");
377
int realFormatVersion = formatVersion;
378
if (realFormatVersion == 0) /* 0 means "default version"*/
379
realFormatVersion = KEXIPROJECTDATA_FORMAT;
380
cg.writeEntry("version", realFormatVersion);
382
const bool thisIsConnectionData = databaseName().isEmpty();
384
//use or find a nonempty group key
386
if (_groupKey && !_groupKey->isEmpty()) {
387
groupKey = *_groupKey;
390
const QStringList groups(config.groupList());
391
if (overwriteFirstGroup && !groups.isEmpty()) {
392
foreach(const QString &s, groups) {
393
if (s.toLower() != "file information") {
400
if (groupKey.isEmpty()) {
401
//find a new unique name
402
if (thisIsConnectionData)
403
groupPrefix = "Connection%1"; //do not i18n!
405
groupPrefix = "Database%1"; //do not i18n!
408
while (config.hasGroup(groupPrefix.arg(number))) //a new group key couldn't exist
410
groupKey = groupPrefix.arg(number);
412
if (_groupKey) //return this one (generated or found)
413
*_groupKey = groupKey;
416
config.group(groupKey).deleteGroup();
417
cg = config.group(groupKey);
418
KDbDriverManager manager;
419
const KDbDriverMetaData *metaData = manager.driverMetaData(d->connData.driverId());
421
m_result = manager.result();
424
const bool fileBased = metaData->isFileBased();
425
if (thisIsConnectionData) {
426
cg.writeEntry("type", "connection");
427
if (!d->connData.caption().isEmpty())
428
cg.writeEntry("caption", d->connData.caption());
429
if (!d->connData.description().isEmpty())
430
cg.writeEntry("comment", d->connData.description());
432
cg.writeEntry("type", "database");
433
if (!caption().isEmpty())
434
cg.writeEntry("caption", caption());
436
QString fn(d->connData.databaseName());
437
if (!QDir::homePath().isEmpty() && fn.startsWith(QDir::homePath())) {
438
// replace prefix if == $HOME
439
fn = fn.mid(QDir::homePath().length());
440
if (!fn.startsWith('/'))
442
fn.prepend(QLatin1String("$HOME"));
444
cg.writeEntry("name", fn);
446
else { // server-based
447
cg.writeEntry("name", databaseName());
449
if (!description().isEmpty())
450
cg.writeEntry("comment", description());
453
QString engine = driverIdToKexi2DriverName(d->connData.driverId());
454
if (engine.isEmpty()) {
455
engine = d->connData.driverId();
457
cg.writeEntry("engine", engine);
459
if (!d->connData.hostName().isEmpty())
460
cg.writeEntry("server", d->connData.hostName());
462
if (d->connData.port() != 0)
463
cg.writeEntry("port", int(d->connData.port()));
464
cg.writeEntry("useLocalSocketFile", d->connData.useLocalSocketFile());
465
if (!d->connData.localSocketFileName().isEmpty())
466
cg.writeEntry("localSocketFile", d->connData.localSocketFileName());
467
if (!d->connData.userName().isEmpty())
468
cg.writeEntry("user", d->connData.userName());
471
if (savePassword || d->connData.savePassword()) {
472
if (realFormatVersion < 2) {
473
cg.writeEntry("password", d->connData.password());
475
QString encryptedPassword = d->connData.password();
476
KDbUtils::simpleCrypt(&encryptedPassword);
477
cg.writeEntry("encryptedPassword", encryptedPassword);
478
encryptedPassword.fill(' '); //for security
482
if (lastOpened().isValid()) {
483
cg.writeEntry("lastOpened", lastOpened().toString(Qt::ISODate));
486
/*! @todo add "options=", eg. as string list? */
488
m_result = KDbResult("Could not write project file.");
494
KEXICORE_EXPORT QDebug operator<<(QDebug dbg, const KexiProjectData& data)
496
dbg.space() << "KexiProjectData" << "databaseName=" << data.databaseName()
497
<< "lastOpened=" << data.lastOpened() << "description=" << data.description()
498
<< "connectionData=(";
499
if (data.connectionData()) {
500
dbg.nospace() << *data.connectionData();
502
dbg.nospace() << ")";