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 }