File indexing completed on 2024-05-12 16:39:39

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
0003    Copyright (C) 2003-20198 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 "kexiproject.h"
0022 #include "kexiprojectdata.h"
0023 #include "kexipartmanager.h"
0024 #include "kexipartitem.h"
0025 #include "kexipartinfo.h"
0026 #include "kexipart.h"
0027 #include "KexiWindow.h"
0028 #include "KexiWindowData.h"
0029 #include "kexi.h"
0030 #include "kexiblobbuffer.h"
0031 #include "kexiguimsghandler.h"
0032 #include <kexiutils/utils.h>
0033 #include <KexiIcon.h>
0034 
0035 #include <QFile>
0036 #include <QFileInfo>
0037 #include <QDir>
0038 #include <QDebug>
0039 
0040 #include <KLocalizedString>
0041 #include <KStandardGuiItem>
0042 #include <KMessageBox>
0043 
0044 #include <KDbConnection>
0045 #include <KDbConnectionOptions>
0046 #include <KDbCursor>
0047 #include <KDbDriverManager>
0048 #include <KDbParser>
0049 #include <KDbMessageHandler>
0050 #include <KDbProperties>
0051 #include <KDbTransactionGuard>
0052 
0053 #include <assert.h>
0054 
0055 //! @return a real plugin ID for @a pluginId and @a partMime
0056 //! for compatibility with Kexi 1.x
0057 static QString realPluginId(const QString &pluginId, const QString &partMime)
0058 {
0059     if (pluginId.startsWith(QLatin1String("http://"))) {
0060         // for compatibility with Kexi 1.x
0061         // part mime was used at the time
0062         return QLatin1String("org.kexi-project.")
0063                + QString(partMime).remove("kexi/");
0064     }
0065     return pluginId;
0066 }
0067 
0068 class Q_DECL_HIDDEN KexiProject::Private
0069 {
0070 public:
0071     explicit Private(KexiProject *qq)
0072             : q(qq)
0073             , connection(0)
0074             , data(0)
0075             , tempPartItemID_Counter(-1)
0076             , sqlParser(0)
0077             , versionMajor(0)
0078             , versionMinor(0)
0079             , privateIDCounter(0)
0080             , itemsRetrieved(false)
0081     {
0082     }
0083     ~Private() {
0084         delete data;
0085         data = 0;
0086         delete sqlParser;
0087         foreach(KexiPart::ItemDict* dict, itemDicts) {
0088             qDeleteAll(*dict);
0089             dict->clear();
0090         }
0091         qDeleteAll(itemDicts);
0092         qDeleteAll(unstoredItems);
0093         unstoredItems.clear();
0094     }
0095 
0096     void savePluginId(const QString& pluginId, int typeId)
0097     {
0098         if (!typeIds.contains(pluginId) && !pluginIdsForTypeIds.contains(typeId)) {
0099             typeIds.insert(pluginId, typeId);
0100             pluginIdsForTypeIds.insert(typeId, pluginId);
0101         }
0102 //! @todo what to do with extra plugin IDs for the same type ID or extra type ID name for the plugin ID?
0103     }
0104 
0105     //! @return user name for the current project
0106     //! @todo the name is taken from connection but it also can be specified otherwise
0107     //!       if the same connection data is shared by multiple users. This will be especially
0108     //!       true for 3-tier architectures.
0109     QString userName() const
0110     {
0111         QString name = connection->data().userName();
0112         return name.isNull() ? "" : name;
0113     }
0114 
0115     bool setNameOrCaption(KexiPart::Item* item,
0116                           const QString* _newName,
0117                           const QString* _newCaption)
0118     {
0119         q->clearResult();
0120         if (data->userMode()) {
0121             return false;
0122         }
0123 
0124         KexiUtils::WaitCursor wait;
0125         QString newName;
0126         if (_newName) {
0127             newName = _newName->trimmed();
0128             KDbMessageTitleSetter ts(q);
0129             if (newName.isEmpty()) {
0130                 q->m_result = KDbResult(xi18n("Could not set empty name for this object."));
0131                 return false;
0132             }
0133             if (q->itemForPluginId(item->pluginId(), newName) != 0) {
0134                 q->m_result = KDbResult(
0135                     xi18nc("@info",
0136                            "Could not use this name. Object <resource>%1</resource> already exists.",
0137                            newName));
0138                 return false;
0139             }
0140         }
0141         QString newCaption;
0142         if (_newCaption) {
0143             newCaption = _newCaption->trimmed();
0144         }
0145 
0146         KDbMessageTitleSetter et(q,
0147                                 xi18nc("@info",
0148                                        "Could not rename object <resource>%1</resource>.", item->name()));
0149         if (!q->checkWritable())
0150             return false;
0151         KexiPart::Part *part = q->findPartFor(*item);
0152         if (!part)
0153             return false;
0154         KDbTransactionGuard tg(connection);
0155         if (!tg.transaction().isActive()) {
0156             q->m_result = connection->result();
0157             return false;
0158         }
0159         if (_newName) {
0160             if (!part->rename(item, newName)) {
0161                 q->m_result = KDbResult(part->lastOperationStatus().description);
0162                 q->m_result.setMessageTitle(part->lastOperationStatus().message);
0163                 return false;
0164             }
0165             if (!connection->executeSql(KDbEscapedString("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2")
0166                     .arg(connection->escapeString(newName))
0167                     .arg(connection->driver()->valueToSql(KDbField::Integer, item->identifier()))))
0168             {
0169                 q->m_result = connection->result();
0170                 return false;
0171             }
0172         }
0173         if (_newCaption) {
0174             if (!connection->executeSql(KDbEscapedString("UPDATE kexi__objects SET o_caption=%1 WHERE o_id=%2")
0175                     .arg(connection->escapeString(newCaption))
0176                     .arg(connection->driver()->valueToSql(KDbField::Integer, item->identifier()))))
0177             {
0178                 q->m_result = connection->result();
0179                 return false;
0180             }
0181         }
0182         if (!tg.commit()) {
0183             q->m_result = connection->result();
0184             return false;
0185         }
0186         QString oldName(item->name());
0187         if (_newName) {
0188             item->setName(newName);
0189             emit q->itemRenamed(*item, oldName);
0190         }
0191         QString oldCaption(item->caption());
0192         if (_newCaption) {
0193             item->setCaption(newCaption);
0194             emit q->itemCaptionChanged(*item, oldCaption);
0195         }
0196         return true;
0197     }
0198 
0199     KexiProject *q;
0200     //! @todo KEXI3 use equivalent of QPointer<KDbConnection>
0201     KDbConnection* connection;
0202     //! @todo KEXI3 use equivalent of QPointer<KexiProjectData> or make KexiProjectData implicitly shared like KDbConnectionData
0203     KexiProjectData *data;
0204     QString error_title;
0205     KexiPart::MissingPartsList missingParts;
0206 
0207     QHash<QString, int> typeIds;
0208     QHash<int, QString> pluginIdsForTypeIds;
0209     //! a cache for item() method, indexed by plugin IDs
0210     QHash<QString, KexiPart::ItemDict*> itemDicts;
0211     QSet<KexiPart::Item*> unstoredItems;
0212     //! helper for getting unique
0213     //! temporary identifiers for unstored items
0214     int tempPartItemID_Counter;
0215     KDbParser* sqlParser;
0216     int versionMajor;
0217     int versionMinor;
0218     int privateIDCounter; //!< counter: ID for private "document" like Relations window
0219     bool itemsRetrieved;
0220 };
0221 
0222 //---------------------------
0223 
0224 KexiProject::KexiProject(const KexiProjectData& pdata, KDbMessageHandler* handler)
0225         : QObject(), KDbObject(), KDbResultable()
0226         , d(new Private(this))
0227 {
0228     d->data = new KexiProjectData(pdata);
0229     setMessageHandler(handler);
0230 }
0231 
0232 KexiProject::KexiProject(const KexiProjectData& pdata, KDbMessageHandler* handler,
0233                          KDbConnection* conn)
0234         : QObject(), KDbObject(), KDbResultable()
0235         , d(new Private(this))
0236 {
0237     d->data = new KexiProjectData(pdata);
0238     setMessageHandler(handler);
0239     if (*d->data->connectionData() == d->connection->data())
0240         d->connection = conn;
0241     else
0242         qWarning() << "passed connection's data ("
0243             << conn->data().toUserVisibleString() << ") is not compatible with project's conn. data ("
0244             << d->data->connectionData()->toUserVisibleString() << ")";
0245 }
0246 
0247 KexiProject::~KexiProject()
0248 {
0249     closeConnection();
0250     delete d;
0251 }
0252 
0253 KDbConnection *KexiProject::dbConnection() const
0254 {
0255     return d->connection;
0256 }
0257 
0258 KexiProjectData* KexiProject::data() const
0259 {
0260     return d->data;
0261 }
0262 
0263 int KexiProject::versionMajor() const
0264 {
0265     return d->versionMajor;
0266 }
0267 
0268 int KexiProject::versionMinor() const
0269 {
0270     return d->versionMinor;
0271 }
0272 
0273 tristate
0274 KexiProject::open(bool *incompatibleWithKexi)
0275 {
0276     Q_ASSERT(incompatibleWithKexi);
0277     KDbMessageGuard mg(this);
0278     return openInternal(incompatibleWithKexi);
0279 }
0280 
0281 tristate
0282 KexiProject::open()
0283 {
0284     KDbMessageGuard mg(this);
0285     return openInternal(0);
0286 }
0287 
0288 tristate
0289 KexiProject::openInternal(bool *incompatibleWithKexi)
0290 {
0291     if (!Kexi::partManager().infoList()) {
0292         m_result = Kexi::partManager().result();
0293         return cancelled;
0294     }
0295     if (incompatibleWithKexi)
0296         *incompatibleWithKexi = false;
0297     //qDebug() << d->data->databaseName() << d->data->connectionData()->driverId();
0298     KDbMessageTitleSetter et(this,
0299                              xi18nc("@info",
0300                                     "Could not open project <resource>%1</resource>.",
0301                                     d->data->databaseName()));
0302 
0303     if (!d->data->connectionData()->databaseName().isEmpty()) {
0304         QFileInfo finfo(d->data->connectionData()->databaseName());
0305         if (!finfo.exists()) {
0306             KMessageBox::sorry(0, xi18nc("@info", "Could not open project. "
0307                                          "The project file <filename>%1</filename> does not exist.",
0308                                          QDir::toNativeSeparators(finfo.absoluteFilePath())),
0309                                          xi18nc("@title:window", "Could Not Open File"));
0310             return cancelled;
0311         }
0312         if (!d->data->isReadOnly() && !finfo.isWritable()) {
0313             if (KexiProject::askForOpeningNonWritableFileAsReadOnly(0, finfo)) {
0314                 d->data->setReadOnly(true);
0315             }
0316             else {
0317                 return cancelled;
0318             }
0319         }
0320     }
0321 
0322     if (!createConnection()) {
0323         qWarning() << "!createConnection()";
0324         return false;
0325     }
0326     bool cancel = false;
0327     if (!d->connection->useDatabase(d->data->databaseName(), true, &cancel)) {
0328         m_result = d->connection->result();
0329         if (cancel) {
0330             return cancelled;
0331         }
0332         qWarning() << "!d->connection->useDatabase() "
0333                    << d->data->databaseName() << " " << d->data->connectionData()->driverId();
0334 
0335         if (d->connection->result().code() == ERR_NO_DB_PROPERTY) {
0336 //<temp>
0337 //! @todo this is temporary workaround as we have no import driver for SQLite
0338             if (/*supported?*/ !d->data->connectionData()->driverId().contains("sqlite")) {
0339 //</temp>
0340                 if (incompatibleWithKexi)
0341                     *incompatibleWithKexi = true;
0342             } else {
0343                 KDbMessageTitleSetter et(this,
0344                     xi18nc("@info (don't add tags around %1, it's done already)",
0345                            "Database project %1 does not "
0346                            "appear to have been created using Kexi and cannot be opened. "
0347                            "It is an SQLite file created using other tools.",
0348                            KexiUtils::localizedStringToHtmlSubstring(d->data->infoString())));
0349                 m_result = d->connection->result();
0350             }
0351             closeConnectionInternal();
0352             return false;
0353         }
0354 
0355         m_result = d->connection->result();
0356         closeConnectionInternal();
0357         return false;
0358     }
0359 
0360     if (!initProject())
0361         return false;
0362 
0363     return createInternalStructures(/*insideTransaction*/true);
0364 }
0365 
0366 tristate
0367 KexiProject::create(bool forceOverwrite)
0368 {
0369     KDbMessageGuard mg(this);
0370     KDbMessageTitleSetter et(this,
0371                              xi18nc("@info",
0372                                     "Could not create project <resource>%1</resource>.",
0373                                     d->data->databaseName()));
0374 
0375     if (!createConnection())
0376         return false;
0377     if (!checkWritable())
0378         return false;
0379     if (d->connection->databaseExists(d->data->databaseName())) {
0380         if (!forceOverwrite)
0381             return cancelled;
0382         if (!d->connection->dropDatabase(d->data->databaseName())) {
0383             m_result = d->connection->result();
0384             closeConnectionInternal();
0385             return false;
0386         }
0387         //qDebug() << "--- DB '" << d->data->databaseName() << "' dropped ---";
0388     }
0389     if (!d->connection->createDatabase(d->data->databaseName())) {
0390         m_result = d->connection->result();
0391         closeConnectionInternal();
0392         return false;
0393     }
0394     //qDebug() << "--- DB '" << d->data->databaseName() << "' created ---";
0395     // and now: open
0396     if (!d->connection->useDatabase(d->data->databaseName())) {
0397         qWarning() << "--- DB '" << d->data->databaseName() << "' USE ERROR ---";
0398         m_result = d->connection->result();
0399         closeConnectionInternal();
0400         return false;
0401     }
0402     //qDebug() << "--- DB '" << d->data->databaseName() << "' used ---";
0403 
0404     //<add some data>
0405     KDbTransaction trans = d->connection->beginTransaction();
0406     if (trans.isNull())
0407         return false;
0408 
0409     if (!createInternalStructures(/*!insideTransaction*/false))
0410         return false;
0411 
0412     //add some metadata
0413 //! @todo put more props. todo - creator, created date, etc. (also to KexiProjectData)
0414     KDbProperties props = d->connection->databaseProperties();
0415     if (!props.setValue("kexiproject_major_ver", d->versionMajor)
0416             || !props.setCaption("kexiproject_major_ver", xi18n("Project major version"))
0417             || !props.setValue("kexiproject_minor_ver", d->versionMinor)
0418             || !props.setCaption("kexiproject_minor_ver", xi18n("Project minor version"))
0419             || !props.setValue("project_caption", d->data->caption())
0420             || !props.setCaption("project_caption", xi18n("Project caption"))
0421             || !props.setValue("project_desc", d->data->description())
0422             || !props.setCaption("project_desc", xi18n("Project description")))
0423     {
0424         m_result = props.result();
0425         return false;
0426     }
0427 
0428     if (trans.isActive() && !d->connection->commitTransaction(trans))
0429         return false;
0430     //</add some metadata>
0431 
0432     if (!Kexi::partManager().infoList()) {
0433         m_result = Kexi::partManager().result();
0434         return cancelled;
0435     }
0436     return initProject();
0437 }
0438 
0439 bool KexiProject::createInternalStructures(bool insideTransaction)
0440 {
0441     KDbTransactionGuard tg;
0442     if (insideTransaction) {
0443         tg.setTransaction(d->connection->beginTransaction());
0444         if (tg.transaction().isNull())
0445             return false;
0446     }
0447 
0448     //Get information about kexiproject version.
0449     //kexiproject version is a version of data layer above kexidb layer.
0450     KDbProperties props = d->connection->databaseProperties();
0451     bool ok;
0452     int storedMajorVersion = props.value("kexiproject_major_ver").toInt(&ok);
0453     if (!ok)
0454         storedMajorVersion = 0;
0455     int storedMinorVersion = props.value("kexiproject_minor_ver").toInt(&ok);
0456     if (!ok)
0457         storedMinorVersion = 1;
0458 
0459     const tristate containsKexi__blobsTable = d->connection->containsTable("kexi__blobs");
0460     if (~containsKexi__blobsTable) {
0461         return false;
0462     }
0463     int dummy;
0464     bool contains_o_folder_id = false;
0465     if (true == containsKexi__blobsTable) {
0466         const tristate res = d->connection->querySingleNumber(
0467                 KDbEscapedString("SELECT COUNT(o_folder_id) FROM kexi__blobs"), &dummy, 0,
0468                 KDbConnection::QueryRecordOptions(KDbConnection::QueryRecordOption::Default)
0469                     & ~KDbConnection::QueryRecordOptions(KDbConnection::QueryRecordOption::AddLimitTo1));
0470         if (res == false) {
0471             m_result = d->connection->result();
0472         }
0473         else if (res == true) {
0474             contains_o_folder_id = true;
0475         }
0476     }
0477     bool add_folder_id_column = false;
0478 
0479 //! @todo what about read-only db access?
0480     if (storedMajorVersion <= 0) {
0481         d->versionMajor = KEXIPROJECT_VERSION_MAJOR;
0482         d->versionMinor = KEXIPROJECT_VERSION_MINOR;
0483         //For compatibility for projects created before Kexi 1.0 beta 1:
0484         //1. no kexiproject_major_ver and kexiproject_minor_ver -> add them
0485         if (!d->connection->options()->isReadOnly()) {
0486             if (!props.setValue("kexiproject_major_ver", d->versionMajor)
0487                     || !props.setCaption("kexiproject_major_ver", xi18n("Project major version"))
0488                     || !props.setValue("kexiproject_minor_ver", d->versionMinor)
0489                     || !props.setCaption("kexiproject_minor_ver", xi18n("Project minor version"))) {
0490                 return false;
0491             }
0492         }
0493 
0494         if (true == containsKexi__blobsTable) {
0495 //! @todo what to do for readonly connections? Should we alter kexi__blobs in memory?
0496             if (!d->connection->options()->isReadOnly()) {
0497                 if (!contains_o_folder_id) {
0498                     add_folder_id_column = true;
0499                 }
0500             }
0501         }
0502     }
0503     if (storedMajorVersion != d->versionMajor || storedMajorVersion != d->versionMinor) {
0504         //! @todo version differs: should we change something?
0505         d->versionMajor = storedMajorVersion;
0506         d->versionMinor = storedMinorVersion;
0507     }
0508 
0509     QScopedPointer<KDbInternalTableSchema> t_blobs(new KDbInternalTableSchema("kexi__blobs"));
0510     t_blobs->addField(new KDbField("o_id", KDbField::Integer,
0511                                         KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned));
0512     t_blobs->addField(new KDbField("o_data", KDbField::BLOB));
0513     t_blobs->addField(new KDbField("o_name", KDbField::Text));
0514     t_blobs->addField(new KDbField("o_caption", KDbField::Text));
0515     t_blobs->addField(new KDbField("o_mime", KDbField::Text, KDbField::NotNull));
0516     t_blobs->addField(new KDbField("o_folder_id",
0517                                 KDbField::Integer, 0, KDbField::Unsigned) //references kexi__gallery_folders.f_id
0518               //If null, the BLOB only points to virtual "All" folder
0519               //WILL BE USED in Kexi >=2.0
0520              );
0521 
0522     //*** create global BLOB container, if not present
0523     if (true == containsKexi__blobsTable) {
0524         if (add_folder_id_column && !d->connection->options()->isReadOnly()) {
0525             // 2. "kexi__blobs" table contains no "o_folder_id" column -> add it
0526             //    (by copying table to avoid data loss)
0527             QScopedPointer<KDbInternalTableSchema> kexi__blobsCopy(
0528                 new KDbInternalTableSchema(*t_blobs)); //won't be not needed - will be physically renamed to kexi_blobs
0529 
0530             kexi__blobsCopy->setName("kexi__blobs__copy");
0531             if (!d->connection->createTable(kexi__blobsCopy.data(),
0532                     KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination))
0533             {
0534 
0535                 m_result = d->connection->result();
0536                 return false;
0537             }
0538             KDbInternalTableSchema *ts = kexi__blobsCopy.take(); // createTable() took ownerhip of kexi__blobsCopy
0539             // 2.1 copy data (insert 0's into o_folder_id column)
0540             if (!d->connection->executeSql(KDbEscapedString(
0541                     "INSERT INTO kexi__blobs (o_data, o_name, o_caption, o_mime, o_folder_id) "
0542                     "SELECT o_data, o_name, o_caption, o_mime, 0 FROM kexi__blobs"))
0543                 // 2.2 remove the original kexi__blobs
0544                 || !d->connection->executeSql(
0545                        KDbEscapedString("DROP TABLE kexi__blobs")) // lowlevel
0546                 // 2.3 rename the copy back into kexi__blobs
0547                 || !d->connection->alterTableName(
0548                        ts, "kexi__blobs",
0549                        KDbConnection::AlterTableNameOptions(KDbConnection::AlterTableNameOption::Default)
0550                            & ~KDbConnection::AlterTableNameOptions(KDbConnection::AlterTableNameOption::DropDestination)))
0551             {
0552                 //(no need to drop the copy, ROLLBACK will drop it)
0553                 m_result = d->connection->result();
0554                 return false;
0555             }
0556         }
0557         //! just insert this schema, proper table exists
0558         d->connection->createTable(t_blobs.take());
0559     } else {
0560         if (!d->connection->options()->isReadOnly()) {
0561             if (!d->connection->createTable(t_blobs.data(),
0562                 KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination))
0563             {
0564                 m_result = d->connection->result();
0565                 return false;
0566             }
0567             (void)t_blobs.take(); // createTable() took ownerhip of t_blobs
0568         }
0569     }
0570 
0571     //Store default part information.
0572     //Information for other parts (forms, reports...) are created on demand in KexiWindow::storeNewData()
0573     const tristate containsKexi__partsTable = d->connection->containsTable("kexi__parts");
0574     if (~containsKexi__partsTable) {
0575         return false;
0576     }
0577     QScopedPointer<KDbInternalTableSchema> t_parts(new KDbInternalTableSchema("kexi__parts"));
0578     t_parts->addField(
0579         new KDbField("p_id", KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned)
0580     );
0581     t_parts->addField(new KDbField("p_name", KDbField::Text));
0582     t_parts->addField(new KDbField("p_mime", KDbField::Text));
0583     t_parts->addField(new KDbField("p_url", KDbField::Text));
0584 
0585     if (true == containsKexi__partsTable) {
0586         //! just insert this schema
0587         d->connection->createTable(t_parts.take());
0588     } else {
0589         if (!d->connection->options()->isReadOnly()) {
0590             bool partsTableOk = d->connection->createTable(t_parts.data(),
0591                 KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination);
0592             if (!partsTableOk) {
0593                 m_result = d->connection->result();
0594                 return false;
0595             }
0596             KDbInternalTableSchema *ts = t_parts.take(); // createTable() took ownerhip of t_parts
0597             QScopedPointer<KDbFieldList> fl(ts->subList("p_id", "p_name", "p_mime", "p_url"));
0598 #define INSERT_RECORD(typeId, groupName, name) \
0599             if (partsTableOk) { \
0600                 partsTableOk = d->connection->insertRecord(fl.data(), QVariant(int(KexiPart::typeId)), \
0601                     QVariant(groupName), \
0602                     QVariant("kexi/" name), QVariant("org.kexi-project." name)); \
0603                 if (partsTableOk) { \
0604                     d->savePluginId("org.kexi-project." name, int(KexiPart::typeId)); \
0605                 } \
0606             }
0607 
0608             INSERT_RECORD(TableObjectType, "Tables", "table")
0609             INSERT_RECORD(QueryObjectType, "Queries", "query")
0610             INSERT_RECORD(FormObjectType, "Forms", "form")
0611             INSERT_RECORD(ReportObjectType, "Reports", "report")
0612             INSERT_RECORD(ScriptObjectType, "Scripts", "script")
0613             INSERT_RECORD(WebObjectType, "Web pages", "web")
0614             INSERT_RECORD(MacroObjectType, "Macros", "macro")
0615 #undef INSERT_RECORD
0616             if (!partsTableOk) {
0617                 m_result = d->connection->result();
0618                 // note: kexi__parts object still exists because createTable() succeeded
0619                 return false;
0620             }
0621         }
0622     }
0623 
0624     // User data storage
0625     const tristate containsKexi__userdataTable = d->connection->containsTable("kexi__userdata");
0626     if (~containsKexi__userdataTable) {
0627         return false;
0628     }
0629     QScopedPointer<KDbInternalTableSchema> t_userdata(new KDbInternalTableSchema("kexi__userdata"));
0630     t_userdata->addField(new KDbField("d_user", KDbField::Text, KDbField::NotNull));
0631     t_userdata->addField(new KDbField("o_id", KDbField::Integer, KDbField::NotNull, KDbField::Unsigned));
0632     t_userdata->addField(new KDbField("d_sub_id", KDbField::Text, KDbField::NotNull | KDbField::NotEmpty));
0633     t_userdata->addField(new KDbField("d_data", KDbField::LongText));
0634 
0635     if (true == containsKexi__userdataTable) {
0636         d->connection->createTable(t_userdata.take());
0637     }
0638     else if (!d->connection->options()->isReadOnly()) {
0639         if (!d->connection->createTable(t_userdata.data(),
0640             KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination))
0641         {
0642             m_result = d->connection->result();
0643             return false;
0644         }
0645         (void)t_userdata.take(); // createTable() took ownerhip of t_userdata
0646     }
0647 
0648     if (insideTransaction) {
0649         if (tg.transaction().isActive() && !tg.commit()) {
0650             m_result = d->connection->result();
0651             return false;
0652         }
0653     }
0654     return true;
0655 }
0656 
0657 bool
0658 KexiProject::createConnection()
0659 {
0660     clearResult();
0661     KDbMessageGuard mg(this);
0662     if (d->connection) {
0663         return true;
0664     }
0665 
0666     KDbMessageTitleSetter et(this);
0667     KDbDriver *driver = Kexi::driverManager().driver(d->data->connectionData()->driverId());
0668     if (!driver) {
0669         m_result = Kexi::driverManager().result();
0670         return false;
0671     }
0672 
0673     KDbConnectionOptions connectionOptions;
0674     if (d->data->isReadOnly()) {
0675         connectionOptions.setReadOnly(true);
0676     }
0677     d->connection = driver->createConnection(*d->data->connectionData(), connectionOptions);
0678     if (!d->connection) {
0679         m_result = driver->result();
0680         qWarning() << "error create connection: " << m_result;
0681         return false;
0682     }
0683 
0684     if (!d->connection->connect()) {
0685         m_result = d->connection->result();
0686         qWarning() << "error connecting: " << m_result;
0687         delete d->connection; //this will also clear connection for BLOB buffer
0688         d->connection = 0;
0689         return false;
0690     }
0691 
0692     //re-init BLOB buffer
0693 //! @todo won't work for subsequent connection
0694     KexiBLOBBuffer::setConnection(d->connection);
0695     return true;
0696 }
0697 
0698 bool KexiProject::closeConnectionInternal()
0699 {
0700     if (!m_result.isError()) {
0701         clearResult();
0702     }
0703     if (!d->connection) {
0704         return true;
0705     }
0706     if (!d->connection->disconnect()) {
0707         if (!m_result.isError()) {
0708             m_result = d->connection->result();
0709         }
0710         return false;
0711     }
0712 
0713     delete d->connection; //this will also clear connection for BLOB buffer
0714     d->connection = 0;
0715     return true;
0716 }
0717 
0718 bool KexiProject::closeConnection()
0719 {
0720     clearResult();
0721     KDbMessageGuard mg(this);
0722     if (!d->connection)
0723         return true;
0724 
0725     if (!d->connection->disconnect()) {
0726         m_result = d->connection->result();
0727         return false;
0728     }
0729 
0730     delete d->connection; //this will also clear connection for BLOB buffer
0731     d->connection = 0;
0732     return true;
0733 }
0734 
0735 bool
0736 KexiProject::initProject()
0737 {
0738     //qDebug() << "checking project parts...";
0739     if (!checkProject()) {
0740         return false;
0741     }
0742 
0743 // !@todo put more props. todo - creator, created date, etc. (also to KexiProjectData)
0744     KDbProperties props = d->connection->databaseProperties();
0745     QString str(props.value("project_caption").toString());
0746     if (!str.isEmpty())
0747         d->data->setCaption(str);
0748     str = props.value("project_desc").toString();
0749     if (!str.isEmpty())
0750         d->data->setDescription(str);
0751 
0752     return true;
0753 }
0754 
0755 bool
0756 KexiProject::isConnected()
0757 {
0758     if (d->connection && d->connection->isDatabaseUsed())
0759         return true;
0760 
0761     return false;
0762 }
0763 
0764 KexiPart::ItemDict*
0765 KexiProject::items(KexiPart::Info *i)
0766 {
0767     clearResult();
0768     KDbMessageGuard mg(this);
0769     if (!i || !isConnected())
0770         return 0;
0771 
0772     //trying in cache...
0773     KexiPart::ItemDict *dict = d->itemDicts.value(i->id());
0774     if (dict)
0775         return dict;
0776     if (d->itemsRetrieved)
0777         return 0;
0778     if (!retrieveItems())
0779         return 0;
0780     return items(i); // try again
0781 }
0782 
0783 bool KexiProject::retrieveItems()
0784 {
0785     d->itemsRetrieved = true;
0786     KDbCursor *cursor = d->connection->executeQuery(
0787         KDbEscapedString("SELECT o_id, o_name, o_caption, o_type FROM kexi__objects ORDER BY o_type"));
0788     if (!cursor) {
0789         m_result = d->connection->result();
0790         return 0;
0791     }
0792 
0793     int recentTypeId = -1000;
0794     QString pluginId;
0795     KexiPart::ItemDict *dict = 0;
0796     QSet<QString> tableNamesSet;
0797     for (cursor->moveFirst(); !cursor->eof(); cursor->moveNext()) {
0798         bool ok;
0799         const int typeId = cursor->value(3).toInt(&ok);
0800         if (!ok || typeId <= 0) {
0801             qInfo() << "object of unknown type id" << cursor->value(3) << "id=" << cursor->value(0)
0802                     << "name=" <<  cursor->value(1);
0803             continue;
0804         }
0805         if (recentTypeId == typeId) {
0806             if (pluginId.isEmpty()) { // still the same unknown plugin ID
0807                 continue;
0808             }
0809         }
0810         else {
0811             // a new type ID: create another plugin items dict if it's an ID for a known type
0812             recentTypeId = typeId;
0813             pluginId = pluginIdForTypeId(typeId);
0814             if (pluginId.isEmpty())
0815                 continue;
0816             dict = new KexiPart::ItemDict();
0817             d->itemDicts.insert(pluginId, dict);
0818             if (typeId == KDb::TableObjectType) {
0819                 // Starting to load table names: initialize.
0820                 // This list since 3.2 does not contain names without physical tables so we can
0821                 // catch these cases below.
0822                 const QStringList tableNames(d->connection->tableNames(false /*public*/, &ok));
0823                 if (!ok) {
0824                     m_result = KDbResult(ERR_OBJECT_NOT_FOUND, xi18n("Could not load list of tables."));
0825                     qDeleteAll(d->itemDicts);
0826                     return false;
0827                 }
0828                 for (const QString &name : tableNames) {
0829                     tableNamesSet.insert(name.toLower());
0830                 }
0831             }
0832         }
0833         const int ident = cursor->value(0).toInt(&ok);
0834         const QString objName(cursor->value(1).toString());
0835         if (!ok || ident <= 0 || !KDb::isIdentifier(objName))
0836         {
0837             continue; // invalid ID or invalid name
0838         }
0839         if (typeId == KDb::TableObjectType) {
0840             if (d->connection->isInternalTableSchema(objName)) {
0841                 qInfo() << "table" << objName << "id=" << ident << "is internal, skipping";
0842                 continue;
0843             }
0844             if (!tableNamesSet.contains(objName.toLower())) {
0845                 qInfo() << "table" << objName << "id=" << ident
0846                         << "does not correspondent with physical table";
0847                 continue;
0848             }
0849         }
0850         KexiPart::Item *it = new KexiPart::Item();
0851         it->setIdentifier(ident);
0852         it->setPluginId(pluginId);
0853         it->setName(objName);
0854         it->setCaption(cursor->value(2).toString());
0855         dict->insert(it->identifier(), it);
0856     }
0857 
0858     d->connection->deleteCursor(cursor);
0859     return true;
0860 }
0861 
0862 int KexiProject::typeIdForPluginId(const QString &pluginId) const
0863 {
0864     return d->typeIds.value(pluginId, -1);
0865 }
0866 
0867 QString KexiProject::pluginIdForTypeId(int typeId) const
0868 {
0869     return d->pluginIdsForTypeIds.value(typeId);
0870 }
0871 
0872 //static
0873 KDbTableOrQuerySchema::Type KexiProject::pluginIdToTableOrQueryType(
0874         const QString &pluginId, bool *ok)
0875 {
0876     Q_ASSERT(ok);
0877     KDbTableOrQuerySchema::Type result;
0878     if (pluginId == QStringLiteral("org.kexi-project.table")) {
0879         result = KDbTableOrQuerySchema::Type::Table;
0880         *ok = true;
0881     } else if (pluginId == QStringLiteral("org.kexi-project.query")) {
0882         result = KDbTableOrQuerySchema::Type::Query;
0883         *ok = true;
0884     } else {
0885         result = KDbTableOrQuerySchema::Type::Table;
0886         *ok = false;
0887     }
0888     return result;
0889 }
0890 
0891 KexiPart::ItemDict*
0892 KexiProject::itemsForPluginId(const QString &pluginId)
0893 {
0894     KDbMessageGuard mg(this);
0895     KexiPart::Info *info = Kexi::partManager().infoForPluginId(pluginId);
0896     if (!info) {
0897         m_result = Kexi::partManager().result();
0898         return 0;
0899     }
0900     return items(info);
0901 }
0902 
0903 void
0904 KexiProject::getSortedItems(KexiPart::ItemList* list, KexiPart::Info *i)
0905 {
0906     Q_ASSERT(list);
0907     list->clear();
0908     KexiPart::ItemDict* dict = items(i);
0909     if (!dict)
0910         return;
0911     foreach(KexiPart::Item *item, *dict) {
0912         list->append(item);
0913     }
0914 }
0915 
0916 void
0917 KexiProject::getSortedItemsForPluginId(KexiPart::ItemList *list, const QString &pluginId)
0918 {
0919     Q_ASSERT(list);
0920     KexiPart::Info *info = Kexi::partManager().infoForPluginId(pluginId);
0921     if (!info) {
0922         m_result = Kexi::partManager().result();
0923         return;
0924     }
0925     getSortedItems(list, info);
0926 }
0927 
0928 void
0929 KexiProject::addStoredItem(KexiPart::Info *info, KexiPart::Item *item)
0930 {
0931     if (!info || !item)
0932         return;
0933     KexiPart::ItemDict *dict = items(info);
0934     item->setNeverSaved(false);
0935     d->unstoredItems.remove(item); //no longer unstored
0936 
0937     // are we replacing previous item?
0938     KexiPart::Item *prevItem = dict->take(item->identifier());
0939     if (prevItem) {
0940         emit itemRemoved(*prevItem);
0941     }
0942 
0943     dict->insert(item->identifier(), item);
0944     //let's update e.g. navigator
0945     emit newItemStored(item);
0946 }
0947 
0948 KexiPart::Item*
0949 KexiProject::itemForPluginId(const QString &pluginId, const QString &name)
0950 {
0951     KexiPart::ItemDict *dict = itemsForPluginId(pluginId);
0952     if (!dict) {
0953         qWarning() << "no part class=" << pluginId;
0954         return 0;
0955     }
0956     foreach(KexiPart::Item *item, *dict) {
0957         if (item->name() == name)
0958             return item;
0959     }
0960     return 0;
0961 }
0962 
0963 KexiPart::Item*
0964 KexiProject::item(KexiPart::Info *i, const QString &name)
0965 {
0966     KexiPart::ItemDict *dict = items(i);
0967     if (!dict)
0968         return 0;
0969     foreach(KexiPart::Item* item, *dict) {
0970         if (item->name() == name)
0971             return item;
0972     }
0973     return 0;
0974 }
0975 
0976 KexiPart::Item*
0977 KexiProject::item(int identifier)
0978 {
0979     foreach(KexiPart::ItemDict *dict, d->itemDicts) {
0980         KexiPart::Item *item = dict->value(identifier);
0981         if (item)
0982             return item;
0983     }
0984     return 0;
0985 }
0986 
0987 KexiPart::Part *KexiProject::findPartFor(const KexiPart::Item& item)
0988 {
0989     clearResult();
0990     KDbMessageGuard mg(this);
0991     KDbMessageTitleSetter et(this);
0992     KexiPart::Part *part = Kexi::partManager().partForPluginId(item.pluginId());
0993     if (!part) {
0994         qWarning() << "!part: " << item.pluginId();
0995         m_result = Kexi::partManager().result();
0996     }
0997     return part;
0998 }
0999 
1000 KexiWindow* KexiProject::openObject(QWidget* parent, KexiPart::Item *item,
1001                                     Kexi::ViewMode viewMode, QMap<QString, QVariant>* staticObjectArgs)
1002 {
1003     clearResult();
1004     KDbMessageGuard mg(this);
1005     if (viewMode != Kexi::DataViewMode && data()->userMode())
1006         return 0;
1007 
1008     KDbMessageTitleSetter et(this);
1009     KexiPart::Part *part = findPartFor(*item);
1010     if (!part)
1011         return 0;
1012     KexiWindow *window  = part->openInstance(parent, item, viewMode, staticObjectArgs);
1013     if (!window) {
1014         if (part->lastOperationStatus().error()) {
1015             m_result = KDbResult(xi18nc("@info",
1016                                         "Opening object <resource>%1</resource> failed.\n%2 %3", item->name())
1017                                  .arg(part->lastOperationStatus().message)
1018                                  .arg(part->lastOperationStatus().description)
1019                                  .replace("(I18N_ARGUMENT_MISSING)", " ")); // a hack until there's other solution
1020         }
1021         return 0;
1022     }
1023     return window;
1024 }
1025 
1026 KexiWindow* KexiProject::openObject(QWidget* parent, const QString &pluginId,
1027                                     const QString& name, Kexi::ViewMode viewMode)
1028 {
1029     KexiPart::Item *it = itemForPluginId(pluginId, name);
1030     return it ? openObject(parent, it, viewMode) : 0;
1031 }
1032 
1033 bool KexiProject::checkWritable()
1034 {
1035     if (!d->connection->options()->isReadOnly())
1036         return true;
1037     m_result = KDbResult(xi18n("This project is opened as read only."));
1038     return false;
1039 }
1040 
1041 bool KexiProject::removeObject(KexiPart::Item *item)
1042 {
1043     Q_ASSERT(item);
1044     clearResult();
1045     if (data()->userMode())
1046         return false;
1047 
1048     KDbMessageTitleSetter et(this);
1049     if (!checkWritable())
1050         return false;
1051     KexiPart::Part *part = findPartFor(*item);
1052     if (!part)
1053         return false;
1054     if (!item->neverSaved() && !part->remove(item)) {
1055         //! @todo check for errors
1056         return false;
1057     }
1058     if (!item->neverSaved()) {
1059         KDbTransactionGuard tg(d->connection);
1060         if (!tg.transaction().isActive()) {
1061             m_result = d->connection->result();
1062             return false;
1063         }
1064         if (!d->connection->removeObject(item->identifier())) {
1065             m_result = d->connection->result();
1066             return false;
1067         }
1068         if (!removeUserDataBlock(item->identifier())) {
1069             m_result = KDbResult(ERR_DELETE_SERVER_ERROR, xi18n("Could not delete object's user data."));
1070             return false;
1071         }
1072         if (!tg.commit()) {
1073             m_result = d->connection->result();
1074             return false;
1075         }
1076     }
1077     emit itemRemoved(*item);
1078 
1079     //now: remove this item from cache
1080     if (part->info()) {
1081         KexiPart::ItemDict *dict = d->itemDicts.value(part->info()->pluginId());
1082         if (!(dict && dict->remove(item->identifier())))
1083             d->unstoredItems.remove(item);//remove temp.
1084     }
1085     return true;
1086 }
1087 
1088 
1089 bool KexiProject::renameObject(KexiPart::Item *item, const QString& newName)
1090 {
1091     KDbMessageGuard mg(this);
1092     return d->setNameOrCaption(item, &newName, 0);
1093 }
1094 
1095 bool KexiProject::setObjectCaption(KexiPart::Item *item, const QString& newCaption)
1096 {
1097     KDbMessageGuard mg(this);
1098     return d->setNameOrCaption(item, 0, &newCaption);
1099 }
1100 
1101 KexiPart::Item* KexiProject::createPartItem(KexiPart::Info *info, const QString& suggestedCaption)
1102 {
1103     clearResult();
1104     KDbMessageGuard mg(this);
1105     if (data()->userMode())
1106         return 0;
1107 
1108     KDbMessageTitleSetter et(this);
1109     KexiPart::Part *part = Kexi::partManager().part(info);
1110     if (!part) {
1111         m_result = Kexi::partManager().result();
1112         return 0;
1113     }
1114 
1115     KexiPart::ItemDict *dict = items(info);
1116     if (!dict) {
1117         dict = new KexiPart::ItemDict();
1118         d->itemDicts.insert(info->pluginId(), dict);
1119     }
1120     QSet<QString> storedItemNames;
1121     foreach(KexiPart::Item* item, *dict) {
1122         storedItemNames.insert(item->name());
1123     }
1124 
1125     QSet<QString> unstoredItemNames;
1126     foreach(KexiPart::Item* item, d->unstoredItems) {
1127         unstoredItemNames.insert(item->name());
1128     }
1129 
1130     //find new, unique default name for this item
1131     int n;
1132     QString new_name;
1133     QString base_name;
1134     if (suggestedCaption.isEmpty()) {
1135         n = 1;
1136         base_name = part->instanceName();
1137     } else {
1138         n = 0; //means: try not to add 'n'
1139         base_name = KDb::stringToIdentifier(suggestedCaption).toLower();
1140     }
1141     base_name = KDb::stringToIdentifier(base_name).toLower();
1142     do {
1143         new_name = base_name;
1144         if (n >= 1)
1145             new_name += QString::number(n);
1146         if (storedItemNames.contains(new_name)) {
1147             n++;
1148             continue; //stored exists!
1149         }
1150         if (!unstoredItemNames.contains(new_name))
1151             break; //unstored doesn't exist
1152         n++;
1153     } while (n < 1000/*sanity*/);
1154 
1155     if (n >= 1000)
1156         return 0;
1157 
1158     QString new_caption(suggestedCaption.isEmpty()
1159         ? part->info()->name() : suggestedCaption);
1160     if (n >= 1)
1161         new_caption += QString::number(n);
1162 
1163     KexiPart::Item *item = new KexiPart::Item();
1164     item->setIdentifier(--d->tempPartItemID_Counter);  //temporary
1165     item->setPluginId(info->pluginId());
1166     item->setName(new_name);
1167     item->setCaption(new_caption);
1168     item->setNeverSaved(true);
1169     d->unstoredItems.insert(item);
1170     return item;
1171 }
1172 
1173 KexiPart::Item* KexiProject::createPartItem(KexiPart::Part *part, const QString& suggestedCaption)
1174 {
1175     Q_ASSERT(part);
1176     return createPartItem(part->info(), suggestedCaption);
1177 }
1178 
1179 void KexiProject::deleteUnstoredItem(KexiPart::Item *item)
1180 {
1181     if (!item)
1182         return;
1183     d->unstoredItems.remove(item);
1184     delete item;
1185 }
1186 
1187 KDbParser* KexiProject::sqlParser()
1188 {
1189     if (!d->sqlParser) {
1190         if (!d->connection)
1191             return 0;
1192         d->sqlParser = new KDbParser(d->connection);
1193     }
1194     return d->sqlParser;
1195 }
1196 
1197 const char warningNoUndo[] = I18N_NOOP2("warning", "Entire project's data and design will be deleted.");
1198 
1199 /*static*/
1200 KexiProject*
1201 KexiProject::createBlankProject(bool *cancelled, const KexiProjectData& data,
1202                                 KDbMessageHandler* handler)
1203 {
1204     Q_ASSERT(cancelled);
1205     *cancelled = false;
1206     KexiProject *prj = new KexiProject(data, handler);
1207 
1208     tristate res = prj->create(false);
1209     if (~res) {
1210 //! @todo move to KexiMessageHandler
1211         if (KMessageBox::Yes != KMessageBox::warningYesNo(0,
1212             xi18nc("@info (don't add tags around %1, it's done already)",
1213                    "<para>The project %1 already exists.</para>"
1214                    "<para>Do you want to replace it with a new, blank one?</para>"
1215                    "<para><warning>%2</warning></para>",
1216                    KexiUtils::localizedStringToHtmlSubstring(prj->data()->infoString()),
1217                    xi18n(warningNoUndo)),
1218                 QString(), KGuiItem(xi18nc("@action:button", "Replace")), KStandardGuiItem::cancel()))
1219 //! @todo add toUserVisibleString() for server-based prj
1220         {
1221             delete prj;
1222             *cancelled = true;
1223             return 0;
1224         }
1225         res = prj->create(true/*overwrite*/);
1226     }
1227     if (res != true) {
1228         delete prj;
1229         return 0;
1230     }
1231     //qDebug() << "new project created --- ";
1232 //! @todo Kexi::recentProjects().addProjectData( data );
1233 
1234     return prj;
1235 }
1236 
1237 /*static*/
1238 tristate KexiProject::dropProject(const KexiProjectData& data,
1239                                   KDbMessageHandler* handler, bool dontAsk)
1240 {
1241     if (!dontAsk && KMessageBox::Yes != KMessageBox::questionYesNo(0,
1242             xi18nc("@info",
1243                    "<para>Do you want to delete the project <resource>%1</resource>?</para>"
1244                    "<para><warning>%2</warning></para>",
1245                    static_cast<const KDbObject*>(&data)->name(),
1246                    i18n(warningNoUndo)),
1247                  QString(), KGuiItem(xi18nc("@action:button", "Delete Project"), koIconName("edit-delete")),
1248                  KStandardGuiItem::no(), QString(),
1249                  KMessageBox::Notify | KMessageBox::Dangerous))
1250     {
1251         return cancelled;
1252     }
1253 
1254     KexiProject prj(data, handler);
1255     if (!prj.open())
1256         return false;
1257 
1258     if (prj.dbConnection()->options()->isReadOnly()) {
1259         handler->showErrorMessage(
1260             KDbMessageHandler::Error,
1261             xi18n("Could not delete this project. Database connection for this project has been opened as read only."));
1262         return false;
1263     }
1264 
1265     KDbMessageGuard mg(prj.dbConnection()->result(), handler);
1266     return prj.dbConnection()->dropDatabase();
1267 }
1268 
1269 bool KexiProject::checkProject(const QString& singlePluginId)
1270 {
1271     clearResult();
1272 
1273 //! @todo catch errors!
1274     if (!d->connection->isDatabaseUsed()) {
1275         m_result = d->connection->result();
1276         return false;
1277     }
1278     const tristate containsKexi__partsTable = d->connection->containsTable("kexi__parts");
1279     if (~containsKexi__partsTable) {
1280         return false;
1281     }
1282     if (true == containsKexi__partsTable) { // check if kexi__parts exists, if missing, createInternalStructures() will create it
1283         KDbEscapedString sql = KDbEscapedString("SELECT p_id, p_name, p_mime, p_url FROM kexi__parts ORDER BY p_id");
1284         if (!singlePluginId.isEmpty()) {
1285             sql.append(KDbEscapedString(" WHERE p_url=%1").arg(d->connection->escapeString(singlePluginId)));
1286         }
1287         KDbCursor *cursor = d->connection->executeQuery(sql);
1288         if (!cursor) {
1289             m_result = d->connection->result();
1290             return false;
1291         }
1292 
1293         bool saved = false;
1294         for (cursor->moveFirst(); !cursor->eof(); cursor->moveNext()) {
1295             const QString partMime(cursor->value(2).toString());
1296             QString pluginId(cursor->value(3).toString());
1297             pluginId = realPluginId(pluginId, partMime);
1298             if (pluginId == QLatin1String("uk.co.piggz.report")) { // compatibility
1299                 pluginId = QLatin1String("org.kexi-project.report");
1300             }
1301             KexiPart::Info *info = Kexi::partManager().infoForPluginId(pluginId);
1302             bool ok;
1303             const int typeId = cursor->value(0).toInt(&ok);
1304             if (!ok || typeId <= 0) {
1305                 qWarning() << "Invalid type ID" << typeId << "; part with ID" << pluginId << "will not be used";
1306             }
1307             if (info && ok && typeId > 0) {
1308                 d->savePluginId(pluginId, typeId);
1309                 saved = true;
1310             }
1311             else {
1312                 KexiPart::MissingPart m;
1313                 m.name = cursor->value(1).toString();
1314                 m.id = pluginId;
1315                 d->missingParts.append(m);
1316             }
1317         }
1318 
1319         d->connection->deleteCursor(cursor);
1320         if (!saved && !singlePluginId.isEmpty()) {
1321             return false; // failure is single part class was not found
1322         }
1323     }
1324     return true;
1325 }
1326 
1327 int KexiProject::generatePrivateID()
1328 {
1329     return --d->privateIDCounter;
1330 }
1331 
1332 bool KexiProject::createIdForPart(const KexiPart::Info& info)
1333 {
1334     KDbMessageGuard mg(this);
1335     int typeId = typeIdForPluginId(info.pluginId());
1336     if (typeId > 0) {
1337         return true;
1338     }
1339     // try again, perhaps the id is already created
1340     if (checkProject(info.pluginId())) {
1341         return true;
1342     }
1343 
1344     // Find first available custom part ID by taking the greatest
1345     // existing custom ID (if it exists) and adding 1.
1346     typeId = int(KexiPart::UserObjectType);
1347     tristate success = d->connection->querySingleNumber(KDbEscapedString("SELECT max(p_id) FROM kexi__parts"), &typeId);
1348     if (!success) {
1349         // Couldn't read part id's from the kexi__parts table
1350         m_result = d->connection->result();
1351         return false;
1352     } else {
1353         // Got a maximum part ID, or there were no parts
1354         typeId = typeId + 1;
1355         typeId = qMax(typeId, (int)KexiPart::UserObjectType);
1356     }
1357 
1358     //this part's ID is not stored within kexi__parts:
1359     KDbTableSchema *ts =
1360         d->connection->tableSchema("kexi__parts");
1361     if (!ts) {
1362         m_result = d->connection->result();
1363         return false;
1364     }
1365     QScopedPointer<KDbFieldList> fl(ts->subList("p_id", "p_name", "p_mime", "p_url"));
1366     //qDebug() << "fieldlist: " << (fl ? *fl : QString());
1367     if (!fl)
1368         return false;
1369 
1370     //qDebug() << info.ptr()->untranslatedGenericName();
1371 //  QStringList sl = part()->info()->ptr()->propertyNames();
1372 //  for (QStringList::ConstIterator it=sl.constBegin();it!=sl.constEnd();++it)
1373    //qDebug() << *it << " " << part()->info()->ptr()->property(*it).toString();
1374     if (!d->connection->insertRecord(
1375                 fl.data(),
1376                 QVariant(typeId),
1377                 QVariant(info.untranslatedGroupName()),
1378                 QVariant(QString::fromLatin1("kexi/") + info.typeName()/*ok?*/),
1379                 QVariant(info.id() /*always ok?*/)))
1380     {
1381         m_result = d->connection->result();
1382         return false;
1383     }
1384 
1385     //qDebug() << "insert success!";
1386     d->savePluginId(info.id(), typeId);
1387     //qDebug() << "new id is: " << p_id;
1388     return true;
1389 }
1390 
1391 KexiPart::MissingPartsList KexiProject::missingParts() const
1392 {
1393     return d->missingParts;
1394 }
1395 
1396 static bool checkObjectId(const char* method, int objectID)
1397 {
1398     if (objectID <= 0) {
1399         qWarning() << method <<  ": Invalid objectID" << objectID;
1400         return false;
1401     }
1402     return true;
1403 }
1404 
1405 tristate KexiProject::loadUserDataBlock(int objectID, const QString& dataID, QString *dataString)
1406 {
1407     KDbMessageGuard mg(this);
1408     if (!checkObjectId("loadUserDataBlock", objectID)) {
1409         return false;
1410     }
1411     if (!d->connection->querySingleString(
1412                KDbEscapedString("SELECT d_data FROM kexi__userdata WHERE o_id=%1 AND ")
1413                 .arg(d->connection->driver()->valueToSql(KDbField::Integer, objectID))
1414                 + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_user", d->userName())
1415                 + " AND " + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_sub_id", dataID),
1416                dataString))
1417     {
1418         m_result = d->connection->result();
1419         return false;
1420     }
1421     return true;
1422 }
1423 
1424 bool KexiProject::storeUserDataBlock(int objectID, const QString& dataID, const QString &dataString)
1425 {
1426     KDbMessageGuard mg(this);
1427     if (!checkObjectId("storeUserDataBlock", objectID)) {
1428         return false;
1429     }
1430     KDbEscapedString sql
1431             = KDbEscapedString("SELECT kexi__userdata.o_id FROM kexi__userdata WHERE o_id=%1").arg(objectID);
1432     KDbEscapedString sql_sub
1433             = KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_user", d->userName())
1434               + " AND " + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_sub_id", dataID);
1435 
1436     const tristate result = d->connection->resultExists(sql + " AND " + sql_sub);
1437     if (~result) {
1438         m_result = d->connection->result();
1439         return false;
1440     }
1441     if (result == true) {
1442         if (!d->connection->executeSql(
1443             KDbEscapedString("UPDATE kexi__userdata SET d_data="
1444                 + d->connection->driver()->valueToSql(KDbField::LongText, dataString)
1445                 + " WHERE o_id=" + QString::number(objectID) + " AND " + sql_sub)))
1446         {
1447             m_result = d->connection->result();
1448             return false;
1449         }
1450         return true;
1451     }
1452     if (!d->connection->executeSql(
1453                KDbEscapedString("INSERT INTO kexi__userdata (d_user, o_id, d_sub_id, d_data) VALUES (")
1454                + d->connection->driver()->valueToSql(KDbField::Text, d->userName())
1455                + ", " + QString::number(objectID)
1456                + ", " + d->connection->driver()->valueToSql(KDbField::Text, dataID)
1457                + ", " + d->connection->driver()->valueToSql(KDbField::LongText, dataString)
1458                + ")"))
1459     {
1460         m_result = d->connection->result();
1461         return false;
1462     }
1463     return true;
1464 }
1465 
1466 bool KexiProject::copyUserDataBlock(int sourceObjectID, int destObjectID, const QString &dataID)
1467 {
1468     KDbMessageGuard mg(this);
1469     if (!checkObjectId("storeUserDataBlock(sourceObjectID)", sourceObjectID)) {
1470         return false;
1471     }
1472     if (!checkObjectId("storeUserDataBlock(destObjectID)", destObjectID)) {
1473         return false;
1474     }
1475     if (sourceObjectID == destObjectID)
1476         return true;
1477     if (!removeUserDataBlock(destObjectID, dataID)) // remove before copying
1478         return false;
1479     KDbEscapedString sql
1480         = KDbEscapedString("INSERT INTO kexi__userdata SELECT t.d_user, %2, t.d_sub_id, t.d_data "
1481                            "FROM kexi__userdata AS t WHERE d_user=%1 AND o_id=%3")
1482                          .arg(d->connection->escapeString(d->userName()))
1483                          .arg(d->connection->driver()->valueToSql(KDbField::Integer, destObjectID))
1484                          .arg(d->connection->driver()->valueToSql(KDbField::Integer, sourceObjectID));
1485     if (!dataID.isEmpty()) {
1486         sql += " AND " + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_sub_id", dataID);
1487     }
1488     if (!d->connection->executeSql(sql)) {
1489         m_result = d->connection->result();
1490         return false;
1491     }
1492     return true;
1493 }
1494 
1495 bool KexiProject::removeUserDataBlock(int objectID, const QString& dataID)
1496 {
1497     KDbMessageGuard mg(this);
1498     if (!checkObjectId("removeUserDataBlock", objectID)) {
1499         return false;
1500     }
1501     if (dataID.isEmpty()) {
1502         if (!KDb::deleteRecords(d->connection, "kexi__userdata",
1503                                "o_id", KDbField::Integer, objectID,
1504                                "d_user", KDbField::Text, d->userName()))
1505         {
1506             m_result = d->connection->result();
1507             return false;
1508         }
1509     else
1510         if (!KDb::deleteRecords(d->connection, "kexi__userdata",
1511                                "o_id", KDbField::Integer, objectID,
1512                                "d_user", KDbField::Text, d->userName(),
1513                                "d_sub_id", KDbField::Text, dataID))
1514         {
1515             m_result = d->connection->result();
1516             return false;
1517         }
1518     }
1519     return true;
1520 }
1521 
1522 // static
1523 bool KexiProject::askForOpeningNonWritableFileAsReadOnly(QWidget *parent, const QFileInfo &finfo)
1524 {
1525     KGuiItem openItem(KStandardGuiItem::open());
1526     openItem.setText(xi18n("Open As Read Only"));
1527     return KMessageBox::Yes == KMessageBox::questionYesNo(
1528             parent, xi18nc("@info",
1529                           "<para>Could not open file <filename>%1</filename> for reading and writing.</para>"
1530                           "<para>Do you want to open the file as read only?</para>",
1531                           QDir::toNativeSeparators(finfo.filePath())),
1532                     xi18nc("@title:window", "Could Not Open File" ),
1533                     openItem, KStandardGuiItem::cancel(), QString());
1534 }