~ubuntu-branches/debian/sid/kexi/sid

« back to all changes in this revision

Viewing changes to src/core/kexiprojectdata.cpp

  • Committer: Package Import Robot
  • Author(s): Pino Toscano
  • Date: 2017-06-24 20:10:10 UTC
  • Revision ID: package-import@ubuntu.com-20170624201010-5lrzd5r2vwthwifp
Tags: upstream-3.0.1.1
ImportĀ upstreamĀ versionĀ 3.0.1.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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>
 
4
 
 
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.
 
9
 
 
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.
 
14
 
 
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.
 
19
*/
 
20
 
 
21
#include "kexiprojectdata.h"
 
22
#include <kexiutils/utils.h>
 
23
#include <kexi_global.h>
 
24
 
 
25
#include <KDbUtils>
 
26
#include <KDbDriverManager>
 
27
#include <KDbConnectionData>
 
28
#include <KDbDriverMetaData>
 
29
 
 
30
#include <KConfig>
 
31
#include <KConfigGroup>
 
32
 
 
33
#include <QDir>
 
34
#include <QStringList>
 
35
#include <QDebug>
 
36
 
 
37
#include <sys/types.h>
 
38
#include <unistd.h>
 
39
 
 
40
//! Version of the KexiProjectData format.
 
41
#define KEXIPROJECTDATA_FORMAT 3
 
42
/* CHANGELOG:
 
43
 v1: initial version
 
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).
 
52
*/
 
53
 
 
54
//! @internal
 
55
class KexiProjectDataPrivate
 
56
{
 
57
public:
 
58
    KexiProjectDataPrivate()
 
59
            : userMode(false)
 
60
            , readOnly(false) {}
 
61
 
 
62
    KDbConnectionData connData;
 
63
    QDateTime lastOpened;
 
64
    bool userMode;
 
65
    bool readOnly;
 
66
};
 
67
 
 
68
//---------------------------------------
 
69
 
 
70
KexiProjectData::AutoOpenObjects::AutoOpenObjects()
 
71
        : QList<ObjectInfo*>()
 
72
{
 
73
}
 
74
 
 
75
KexiProjectData::AutoOpenObjects::AutoOpenObjects(const KexiProjectData::AutoOpenObjects& other)
 
76
        : QList<ObjectInfo*>()
 
77
{
 
78
    *this = other;
 
79
}
 
80
 
 
81
KexiProjectData::AutoOpenObjects::~AutoOpenObjects()
 
82
{
 
83
    qDeleteAll(*this);
 
84
}
 
85
 
 
86
KexiProjectData::AutoOpenObjects& KexiProjectData::AutoOpenObjects::operator=(
 
87
    const KexiProjectData::AutoOpenObjects & other)
 
88
{
 
89
    clear();
 
90
    for (QListIterator<ObjectInfo*> it(other);it.hasNext();) //deep copy
 
91
        append(new ObjectInfo(*it.next()));
 
92
    return *this;
 
93
}
 
94
 
 
95
//---------------------------------------
 
96
 
 
97
KexiProjectData::KexiProjectData()
 
98
        : QObject(0)
 
99
        , KDbObject()
 
100
        , formatVersion(0)
 
101
        , d(new KexiProjectDataPrivate())
 
102
{
 
103
    setObjectName("KexiProjectData");
 
104
}
 
105
 
 
106
KexiProjectData::KexiProjectData(
 
107
    const KDbConnectionData &cdata, const QString& dbname, const QString& caption)
 
108
        : QObject(0)
 
109
        , KDbObject()
 
110
        , formatVersion(0)
 
111
        , d(new KexiProjectDataPrivate())
 
112
{
 
113
    setObjectName("KexiProjectData");
 
114
    d->connData = cdata;
 
115
    setDatabaseName(cdata.databaseName().isEmpty() ? dbname : cdata.databaseName());
 
116
    setCaption(caption);
 
117
}
 
118
 
 
119
KexiProjectData::KexiProjectData(const KexiProjectData& pdata)
 
120
        : QObject(0)
 
121
        , KDbObject()
 
122
        , KDbResultable(pdata)
 
123
        , d(new KexiProjectDataPrivate())
 
124
{
 
125
    setObjectName("KexiProjectData");
 
126
    *this = pdata;
 
127
    autoopenObjects = pdata.autoopenObjects;
 
128
}
 
129
 
 
130
KexiProjectData::~KexiProjectData()
 
131
{
 
132
    delete d;
 
133
}
 
134
 
 
135
KexiProjectData& KexiProjectData::operator=(const KexiProjectData & pdata)
 
136
{
 
137
    static_cast<KDbObject&>(*this) = static_cast<const KDbObject&>(pdata);
 
138
    //deep copy
 
139
    autoopenObjects = pdata.autoopenObjects;
 
140
    formatVersion = pdata.formatVersion;
 
141
    *d = *pdata.d;
 
142
    return *this;
 
143
}
 
144
 
 
145
KDbConnectionData* KexiProjectData::connectionData()
 
146
{
 
147
    return &d->connData;
 
148
}
 
149
 
 
150
const KDbConnectionData* KexiProjectData::connectionData() const
 
151
{
 
152
    return &d->connData;
 
153
}
 
154
 
 
155
QString KexiProjectData::databaseName() const
 
156
{
 
157
    return KDbObject::name();
 
158
}
 
159
 
 
160
void KexiProjectData::setDatabaseName(const QString& dbName)
 
161
{
 
162
    //qDebug() << dbName;
 
163
    //qDebug() << *this;
 
164
    KDbObject::setName(dbName);
 
165
}
 
166
 
 
167
bool KexiProjectData::userMode() const
 
168
{
 
169
    return d->userMode;
 
170
}
 
171
 
 
172
QDateTime KexiProjectData::lastOpened() const
 
173
{
 
174
    return d->lastOpened;
 
175
}
 
176
 
 
177
void KexiProjectData::setLastOpened(const QDateTime& lastOpened)
 
178
{
 
179
    d->lastOpened = lastOpened;
 
180
 
 
181
}
 
182
QString KexiProjectData::description() const
 
183
{
 
184
    return KDbObject::description();
 
185
}
 
186
 
 
187
void KexiProjectData::setDescription(const QString& desc)
 
188
{
 
189
    return KDbObject::setDescription(desc);
 
190
}
 
191
 
 
192
// static
 
193
KLocalizedString KexiProjectData::infoString(const QString &databaseName,
 
194
                                    const KDbConnectionData &data)
 
195
{
 
196
    if (data.databaseName().isEmpty()) {
 
197
        //server-based
 
198
        return kxi18nc("@info database connection",
 
199
                       "<resource>%1</resource> (connection <resource>%2</resource>)")
 
200
                .subs(databaseName).subs(data.toUserVisibleString());
 
201
    }
 
202
    //file-based
 
203
    return kxi18nc("@info database name",
 
204
                   "<resource>%1</resource>").subs(data.databaseName());
 
205
}
 
206
 
 
207
KLocalizedString KexiProjectData::infoString() const
 
208
{
 
209
    return infoString(databaseName(), d->connData);
 
210
}
 
211
 
 
212
void KexiProjectData::setReadOnly(bool set)
 
213
{
 
214
    d->readOnly = set;
 
215
}
 
216
 
 
217
bool KexiProjectData::isReadOnly() const
 
218
{
 
219
    return d->readOnly;
 
220
}
 
221
 
 
222
bool KexiProjectData::load(const QString& fileName, QString* _groupKey)
 
223
{
 
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);
 
228
 
 
229
    QString groupKey;
 
230
    if (!_groupKey || _groupKey->isEmpty()) {
 
231
        QStringList groups(config.groupList());
 
232
        foreach(const QString &s, groups) {
 
233
            if (s.toLower() != "file information") {
 
234
                groupKey = s;
 
235
                break;
 
236
            }
 
237
        }
 
238
        if (groupKey.isEmpty()) {
 
239
            m_result = KDbResult(xi18n("File <filename>%1</filename> contains no connection information.",
 
240
                                       fileName));
 
241
            return false;
 
242
        }
 
243
        if (_groupKey)
 
244
            *_groupKey = groupKey;
 
245
    } else {
 
246
        if (!config.hasGroup(*_groupKey)) {
 
247
            m_result = KDbResult(xi18n("File <filename>%1</filename> does not contain group <resource>%2</resource>.",
 
248
                                       fileName, *_groupKey));
 
249
            return false;
 
250
        }
 
251
        groupKey = *_groupKey;
 
252
    }
 
253
 
 
254
    cg = config.group(groupKey);
 
255
    QString type(cg.readEntry("type", "database").toLower());
 
256
 
 
257
    bool isDatabaseShortcut;
 
258
    if (type == "database") {
 
259
        isDatabaseShortcut = true;
 
260
    } else if (type == "connection") {
 
261
        isDatabaseShortcut = false;
 
262
    } else {
 
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));
 
266
        return false;
 
267
    }
 
268
 
 
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));
 
273
        return false;
 
274
    }
 
275
 
 
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();
 
286
            return false;
 
287
        }
 
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());
 
294
        if (fileBased) {
 
295
            QString fn(cg.readEntry("name"));
 
296
            if (!fn.isEmpty()) {
 
297
                const QString homeVar("$HOME");
 
298
                if (fn.startsWith(homeVar)) {
 
299
                    QString home(QDir::homePath());
 
300
                    if (home.endsWith('/')) {
 
301
                        home.chop(1);
 
302
                    }
 
303
                    fn = home + fn.mid(homeVar.length());
 
304
                }
 
305
                d->connData.setDatabaseName(fn);
 
306
                setDatabaseName(d->connData.databaseName());
 
307
            }
 
308
        }
 
309
        else {
 
310
            setDatabaseName(cg.readEntry("name"));
 
311
        }
 
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... */
 
319
    }
 
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.";
 
329
            password.clear();
 
330
        }
 
331
        d->connData.setPassword(password);
 
332
    }
 
333
    if (!cg.hasKey("encryptedPassword")) {//no "encryptedPassword", for compatibility
 
334
        //UNSAFE
 
335
        d->connData.setPassword(cg.readEntry("password"));
 
336
    }
 
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);
 
343
        }
 
344
    }
 
345
    /*! @todo add "options=", eg. as string list? */
 
346
    return true;
 
347
}
 
348
 
 
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)
 
353
{
 
354
    QString prefix = "org.kde.kdb.";
 
355
    if (!driverId.startsWith(prefix)) {
 
356
        return QString();
 
357
    }
 
358
    QString suffix = driverId.mid(prefix.length());
 
359
    if (suffix == "sqlite"
 
360
        || suffix == "mysql"
 
361
        || suffix == "postgresql"
 
362
        || suffix == "xbase"
 
363
        || suffix == "sybase")
 
364
    {
 
365
        return suffix;
 
366
    }
 
367
    return QString();
 
368
}
 
369
 
 
370
bool KexiProjectData::save(const QString& fileName, bool savePassword,
 
371
                           QString* _groupKey, bool overwriteFirstGroup)
 
372
{
 
373
    //! @todo how about readOnly arg?
 
374
    KConfig config(fileName, KConfig::SimpleConfig);
 
375
    KConfigGroup cg = config.group("File Information");
 
376
 
 
377
    int realFormatVersion = formatVersion;
 
378
    if (realFormatVersion == 0) /* 0 means "default version"*/
 
379
        realFormatVersion = KEXIPROJECTDATA_FORMAT;
 
380
    cg.writeEntry("version", realFormatVersion);
 
381
 
 
382
    const bool thisIsConnectionData = databaseName().isEmpty();
 
383
 
 
384
    //use or find a nonempty group key
 
385
    QString groupKey;
 
386
    if (_groupKey && !_groupKey->isEmpty()) {
 
387
        groupKey = *_groupKey;
 
388
    } else {
 
389
        QString groupPrefix;
 
390
        const QStringList groups(config.groupList());
 
391
        if (overwriteFirstGroup && !groups.isEmpty()) {
 
392
            foreach(const QString &s, groups) {
 
393
                if (s.toLower() != "file information") {
 
394
                    groupKey = s;
 
395
                    break;
 
396
                }
 
397
            }
 
398
        }
 
399
 
 
400
        if (groupKey.isEmpty()) {
 
401
            //find a new unique name
 
402
            if (thisIsConnectionData)
 
403
                groupPrefix = "Connection%1"; //do not i18n!
 
404
            else
 
405
                groupPrefix = "Database%1"; //do not i18n!
 
406
 
 
407
            int number = 1;
 
408
            while (config.hasGroup(groupPrefix.arg(number))) //a new group key couldn't exist
 
409
                number++;
 
410
            groupKey = groupPrefix.arg(number);
 
411
        }
 
412
        if (_groupKey) //return this one (generated or found)
 
413
            *_groupKey = groupKey;
 
414
    }
 
415
 
 
416
    config.group(groupKey).deleteGroup();
 
417
    cg = config.group(groupKey);
 
418
    KDbDriverManager manager;
 
419
    const KDbDriverMetaData *metaData = manager.driverMetaData(d->connData.driverId());
 
420
    if (!metaData) {
 
421
        m_result = manager.result();
 
422
        return false;
 
423
    }
 
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());
 
431
    } else { //database
 
432
        cg.writeEntry("type", "database");
 
433
        if (!caption().isEmpty())
 
434
            cg.writeEntry("caption", caption());
 
435
        if (fileBased) {
 
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('/'))
 
441
                    fn.prepend('/');
 
442
                fn.prepend(QLatin1String("$HOME"));
 
443
            }
 
444
            cg.writeEntry("name", fn);
 
445
        }
 
446
        else { // server-based
 
447
            cg.writeEntry("name", databaseName());
 
448
        }
 
449
        if (!description().isEmpty())
 
450
            cg.writeEntry("comment", description());
 
451
    }
 
452
 
 
453
    QString engine = driverIdToKexi2DriverName(d->connData.driverId());
 
454
    if (engine.isEmpty()) {
 
455
        engine = d->connData.driverId();
 
456
    }
 
457
    cg.writeEntry("engine", engine);
 
458
    if (!fileBased) {
 
459
        if (!d->connData.hostName().isEmpty())
 
460
            cg.writeEntry("server", d->connData.hostName());
 
461
 
 
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());
 
469
    }
 
470
 
 
471
    if (savePassword || d->connData.savePassword()) {
 
472
        if (realFormatVersion < 2) {
 
473
            cg.writeEntry("password", d->connData.password());
 
474
        } else {
 
475
            QString encryptedPassword = d->connData.password();
 
476
            KDbUtils::simpleCrypt(&encryptedPassword);
 
477
            cg.writeEntry("encryptedPassword", encryptedPassword);
 
478
            encryptedPassword.fill(' '); //for security
 
479
        }
 
480
    }
 
481
 
 
482
    if (lastOpened().isValid()) {
 
483
        cg.writeEntry("lastOpened", lastOpened().toString(Qt::ISODate));
 
484
    }
 
485
 
 
486
    /*! @todo add "options=", eg. as string list? */
 
487
    if (!cg.sync()) {
 
488
        m_result = KDbResult("Could not write project file.");
 
489
        return false;
 
490
    }
 
491
    return true;
 
492
}
 
493
 
 
494
KEXICORE_EXPORT QDebug operator<<(QDebug dbg, const KexiProjectData& data)
 
495
{
 
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();
 
501
    }
 
502
    dbg.nospace() << ")";
 
503
    return dbg.space();
 
504
}