1 /* This file is part of the KDE project
2 Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
3 Copyright (C) 2003-20198 Jarosław Staniek <staniek@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include "kexiproject.h"
22 #include "kexiprojectdata.h"
23 #include "kexipartmanager.h"
24 #include "kexipartitem.h"
25 #include "kexipartinfo.h"
26 #include "kexipart.h"
27 #include "KexiWindow.h"
28 #include "KexiWindowData.h"
29 #include "kexi.h"
30 #include "kexiblobbuffer.h"
31 #include "kexiguimsghandler.h"
32 #include <kexiutils/utils.h>
33 #include <KexiIcon.h>
34
35 #include <QFile>
36 #include <QFileInfo>
37 #include <QDir>
38 #include <QDebug>
39
40 #include <KLocalizedString>
41 #include <KStandardGuiItem>
42 #include <KMessageBox>
43
44 #include <KDbConnection>
45 #include <KDbConnectionOptions>
46 #include <KDbCursor>
47 #include <KDbDriverManager>
48 #include <KDbParser>
49 #include <KDbMessageHandler>
50 #include <KDbProperties>
51 #include <KDbTransactionGuard>
52
53 #include <assert.h>
54
55 //! @return a real plugin ID for @a pluginId and @a partMime
56 //! for compatibility with Kexi 1.x
realPluginId(const QString & pluginId,const QString & partMime)57 static QString realPluginId(const QString &pluginId, const QString &partMime)
58 {
59 if (pluginId.startsWith(QLatin1String("http://"))) {
60 // for compatibility with Kexi 1.x
61 // part mime was used at the time
62 return QLatin1String("org.kexi-project.")
63 + QString(partMime).remove("kexi/");
64 }
65 return pluginId;
66 }
67
68 class Q_DECL_HIDDEN KexiProject::Private
69 {
70 public:
Private(KexiProject * qq)71 explicit Private(KexiProject *qq)
72 : q(qq)
73 , connection(0)
74 , data(0)
75 , tempPartItemID_Counter(-1)
76 , sqlParser(0)
77 , versionMajor(0)
78 , versionMinor(0)
79 , privateIDCounter(0)
80 , itemsRetrieved(false)
81 {
82 }
~Private()83 ~Private() {
84 delete data;
85 data = 0;
86 delete sqlParser;
87 foreach(KexiPart::ItemDict* dict, itemDicts) {
88 qDeleteAll(*dict);
89 dict->clear();
90 }
91 qDeleteAll(itemDicts);
92 qDeleteAll(unstoredItems);
93 unstoredItems.clear();
94 }
95
savePluginId(const QString & pluginId,int typeId)96 void savePluginId(const QString& pluginId, int typeId)
97 {
98 if (!typeIds.contains(pluginId) && !pluginIdsForTypeIds.contains(typeId)) {
99 typeIds.insert(pluginId, typeId);
100 pluginIdsForTypeIds.insert(typeId, pluginId);
101 }
102 //! @todo what to do with extra plugin IDs for the same type ID or extra type ID name for the plugin ID?
103 }
104
105 //! @return user name for the current project
106 //! @todo the name is taken from connection but it also can be specified otherwise
107 //! if the same connection data is shared by multiple users. This will be especially
108 //! true for 3-tier architectures.
userName() const109 QString userName() const
110 {
111 QString name = connection->data().userName();
112 return name.isNull() ? "" : name;
113 }
114
setNameOrCaption(KexiPart::Item * item,const QString * _newName,const QString * _newCaption)115 bool setNameOrCaption(KexiPart::Item* item,
116 const QString* _newName,
117 const QString* _newCaption)
118 {
119 q->clearResult();
120 if (data->userMode()) {
121 return false;
122 }
123
124 KexiUtils::WaitCursor wait;
125 QString newName;
126 if (_newName) {
127 newName = _newName->trimmed();
128 KDbMessageTitleSetter ts(q);
129 if (newName.isEmpty()) {
130 q->m_result = KDbResult(xi18n("Could not set empty name for this object."));
131 return false;
132 }
133 if (q->itemForPluginId(item->pluginId(), newName) != 0) {
134 q->m_result = KDbResult(
135 xi18nc("@info",
136 "Could not use this name. Object <resource>%1</resource> already exists.",
137 newName));
138 return false;
139 }
140 }
141 QString newCaption;
142 if (_newCaption) {
143 newCaption = _newCaption->trimmed();
144 }
145
146 KDbMessageTitleSetter et(q,
147 xi18nc("@info",
148 "Could not rename object <resource>%1</resource>.", item->name()));
149 if (!q->checkWritable())
150 return false;
151 KexiPart::Part *part = q->findPartFor(*item);
152 if (!part)
153 return false;
154 KDbTransactionGuard tg(connection);
155 if (!tg.transaction().isActive()) {
156 q->m_result = connection->result();
157 return false;
158 }
159 if (_newName) {
160 if (!part->rename(item, newName)) {
161 q->m_result = KDbResult(part->lastOperationStatus().description);
162 q->m_result.setMessageTitle(part->lastOperationStatus().message);
163 return false;
164 }
165 if (!connection->executeSql(KDbEscapedString("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2")
166 .arg(connection->escapeString(newName))
167 .arg(connection->driver()->valueToSql(KDbField::Integer, item->identifier()))))
168 {
169 q->m_result = connection->result();
170 return false;
171 }
172 }
173 if (_newCaption) {
174 if (!connection->executeSql(KDbEscapedString("UPDATE kexi__objects SET o_caption=%1 WHERE o_id=%2")
175 .arg(connection->escapeString(newCaption))
176 .arg(connection->driver()->valueToSql(KDbField::Integer, item->identifier()))))
177 {
178 q->m_result = connection->result();
179 return false;
180 }
181 }
182 if (!tg.commit()) {
183 q->m_result = connection->result();
184 return false;
185 }
186 QString oldName(item->name());
187 if (_newName) {
188 item->setName(newName);
189 emit q->itemRenamed(*item, oldName);
190 }
191 QString oldCaption(item->caption());
192 if (_newCaption) {
193 item->setCaption(newCaption);
194 emit q->itemCaptionChanged(*item, oldCaption);
195 }
196 return true;
197 }
198
199 KexiProject *q;
200 //! @todo KEXI3 use equivalent of QPointer<KDbConnection>
201 KDbConnection* connection;
202 //! @todo KEXI3 use equivalent of QPointer<KexiProjectData> or make KexiProjectData implicitly shared like KDbConnectionData
203 KexiProjectData *data;
204 QString error_title;
205 KexiPart::MissingPartsList missingParts;
206
207 QHash<QString, int> typeIds;
208 QHash<int, QString> pluginIdsForTypeIds;
209 //! a cache for item() method, indexed by plugin IDs
210 QHash<QString, KexiPart::ItemDict*> itemDicts;
211 QSet<KexiPart::Item*> unstoredItems;
212 //! helper for getting unique
213 //! temporary identifiers for unstored items
214 int tempPartItemID_Counter;
215 KDbParser* sqlParser;
216 int versionMajor;
217 int versionMinor;
218 int privateIDCounter; //!< counter: ID for private "document" like Relations window
219 bool itemsRetrieved;
220 };
221
222 //---------------------------
223
KexiProject(const KexiProjectData & pdata,KDbMessageHandler * handler)224 KexiProject::KexiProject(const KexiProjectData& pdata, KDbMessageHandler* handler)
225 : QObject(), KDbObject(), KDbResultable()
226 , d(new Private(this))
227 {
228 d->data = new KexiProjectData(pdata);
229 setMessageHandler(handler);
230 }
231
KexiProject(const KexiProjectData & pdata,KDbMessageHandler * handler,KDbConnection * conn)232 KexiProject::KexiProject(const KexiProjectData& pdata, KDbMessageHandler* handler,
233 KDbConnection* conn)
234 : QObject(), KDbObject(), KDbResultable()
235 , d(new Private(this))
236 {
237 d->data = new KexiProjectData(pdata);
238 setMessageHandler(handler);
239 if (*d->data->connectionData() == d->connection->data())
240 d->connection = conn;
241 else
242 qWarning() << "passed connection's data ("
243 << conn->data().toUserVisibleString() << ") is not compatible with project's conn. data ("
244 << d->data->connectionData()->toUserVisibleString() << ")";
245 }
246
~KexiProject()247 KexiProject::~KexiProject()
248 {
249 closeConnection();
250 delete d;
251 }
252
dbConnection() const253 KDbConnection *KexiProject::dbConnection() const
254 {
255 return d->connection;
256 }
257
data() const258 KexiProjectData* KexiProject::data() const
259 {
260 return d->data;
261 }
262
versionMajor() const263 int KexiProject::versionMajor() const
264 {
265 return d->versionMajor;
266 }
267
versionMinor() const268 int KexiProject::versionMinor() const
269 {
270 return d->versionMinor;
271 }
272
273 tristate
open(bool * incompatibleWithKexi)274 KexiProject::open(bool *incompatibleWithKexi)
275 {
276 Q_ASSERT(incompatibleWithKexi);
277 KDbMessageGuard mg(this);
278 return openInternal(incompatibleWithKexi);
279 }
280
281 tristate
open()282 KexiProject::open()
283 {
284 KDbMessageGuard mg(this);
285 return openInternal(0);
286 }
287
288 tristate
openInternal(bool * incompatibleWithKexi)289 KexiProject::openInternal(bool *incompatibleWithKexi)
290 {
291 if (!Kexi::partManager().infoList()) {
292 m_result = Kexi::partManager().result();
293 return cancelled;
294 }
295 if (incompatibleWithKexi)
296 *incompatibleWithKexi = false;
297 //qDebug() << d->data->databaseName() << d->data->connectionData()->driverId();
298 KDbMessageTitleSetter et(this,
299 xi18nc("@info",
300 "Could not open project <resource>%1</resource>.",
301 d->data->databaseName()));
302
303 if (!d->data->connectionData()->databaseName().isEmpty()) {
304 QFileInfo finfo(d->data->connectionData()->databaseName());
305 if (!finfo.exists()) {
306 KMessageBox::sorry(0, xi18nc("@info", "Could not open project. "
307 "The project file <filename>%1</filename> does not exist.",
308 QDir::toNativeSeparators(finfo.absoluteFilePath())),
309 xi18nc("@title:window", "Could Not Open File"));
310 return cancelled;
311 }
312 if (!d->data->isReadOnly() && !finfo.isWritable()) {
313 if (KexiProject::askForOpeningNonWritableFileAsReadOnly(0, finfo)) {
314 d->data->setReadOnly(true);
315 }
316 else {
317 return cancelled;
318 }
319 }
320 }
321
322 if (!createConnection()) {
323 qWarning() << "!createConnection()";
324 return false;
325 }
326 bool cancel = false;
327 if (!d->connection->useDatabase(d->data->databaseName(), true, &cancel)) {
328 m_result = d->connection->result();
329 if (cancel) {
330 return cancelled;
331 }
332 qWarning() << "!d->connection->useDatabase() "
333 << d->data->databaseName() << " " << d->data->connectionData()->driverId();
334
335 if (d->connection->result().code() == ERR_NO_DB_PROPERTY) {
336 //<temp>
337 //! @todo this is temporary workaround as we have no import driver for SQLite
338 if (/*supported?*/ !d->data->connectionData()->driverId().contains("sqlite")) {
339 //</temp>
340 if (incompatibleWithKexi)
341 *incompatibleWithKexi = true;
342 } else {
343 KDbMessageTitleSetter et(this,
344 xi18nc("@info (don't add tags around %1, it's done already)",
345 "Database project %1 does not "
346 "appear to have been created using Kexi and cannot be opened. "
347 "It is an SQLite file created using other tools.",
348 KexiUtils::localizedStringToHtmlSubstring(d->data->infoString())));
349 m_result = d->connection->result();
350 }
351 closeConnectionInternal();
352 return false;
353 }
354
355 m_result = d->connection->result();
356 closeConnectionInternal();
357 return false;
358 }
359
360 if (!initProject())
361 return false;
362
363 return createInternalStructures(/*insideTransaction*/true);
364 }
365
366 tristate
create(bool forceOverwrite)367 KexiProject::create(bool forceOverwrite)
368 {
369 KDbMessageGuard mg(this);
370 KDbMessageTitleSetter et(this,
371 xi18nc("@info",
372 "Could not create project <resource>%1</resource>.",
373 d->data->databaseName()));
374
375 if (!createConnection())
376 return false;
377 if (!checkWritable())
378 return false;
379 if (d->connection->databaseExists(d->data->databaseName())) {
380 if (!forceOverwrite)
381 return cancelled;
382 if (!d->connection->dropDatabase(d->data->databaseName())) {
383 m_result = d->connection->result();
384 closeConnectionInternal();
385 return false;
386 }
387 //qDebug() << "--- DB '" << d->data->databaseName() << "' dropped ---";
388 }
389 if (!d->connection->createDatabase(d->data->databaseName())) {
390 m_result = d->connection->result();
391 closeConnectionInternal();
392 return false;
393 }
394 //qDebug() << "--- DB '" << d->data->databaseName() << "' created ---";
395 // and now: open
396 if (!d->connection->useDatabase(d->data->databaseName())) {
397 qWarning() << "--- DB '" << d->data->databaseName() << "' USE ERROR ---";
398 m_result = d->connection->result();
399 closeConnectionInternal();
400 return false;
401 }
402 //qDebug() << "--- DB '" << d->data->databaseName() << "' used ---";
403
404 //<add some data>
405 KDbTransaction trans = d->connection->beginTransaction();
406 if (trans.isNull())
407 return false;
408
409 if (!createInternalStructures(/*!insideTransaction*/false))
410 return false;
411
412 //add some metadata
413 //! @todo put more props. todo - creator, created date, etc. (also to KexiProjectData)
414 KDbProperties props = d->connection->databaseProperties();
415 if (!props.setValue("kexiproject_major_ver", d->versionMajor)
416 || !props.setCaption("kexiproject_major_ver", xi18n("Project major version"))
417 || !props.setValue("kexiproject_minor_ver", d->versionMinor)
418 || !props.setCaption("kexiproject_minor_ver", xi18n("Project minor version"))
419 || !props.setValue("project_caption", d->data->caption())
420 || !props.setCaption("project_caption", xi18n("Project caption"))
421 || !props.setValue("project_desc", d->data->description())
422 || !props.setCaption("project_desc", xi18n("Project description")))
423 {
424 m_result = props.result();
425 return false;
426 }
427
428 if (trans.isActive() && !d->connection->commitTransaction(trans))
429 return false;
430 //</add some metadata>
431
432 if (!Kexi::partManager().infoList()) {
433 m_result = Kexi::partManager().result();
434 return cancelled;
435 }
436 return initProject();
437 }
438
createInternalStructures(bool insideTransaction)439 bool KexiProject::createInternalStructures(bool insideTransaction)
440 {
441 KDbTransactionGuard tg;
442 if (insideTransaction) {
443 tg.setTransaction(d->connection->beginTransaction());
444 if (tg.transaction().isNull())
445 return false;
446 }
447
448 //Get information about kexiproject version.
449 //kexiproject version is a version of data layer above kexidb layer.
450 KDbProperties props = d->connection->databaseProperties();
451 bool ok;
452 int storedMajorVersion = props.value("kexiproject_major_ver").toInt(&ok);
453 if (!ok)
454 storedMajorVersion = 0;
455 int storedMinorVersion = props.value("kexiproject_minor_ver").toInt(&ok);
456 if (!ok)
457 storedMinorVersion = 1;
458
459 const tristate containsKexi__blobsTable = d->connection->containsTable("kexi__blobs");
460 if (~containsKexi__blobsTable) {
461 return false;
462 }
463 int dummy;
464 bool contains_o_folder_id = false;
465 if (true == containsKexi__blobsTable) {
466 const tristate res = d->connection->querySingleNumber(
467 KDbEscapedString("SELECT COUNT(o_folder_id) FROM kexi__blobs"), &dummy, 0,
468 KDbConnection::QueryRecordOptions(KDbConnection::QueryRecordOption::Default)
469 & ~KDbConnection::QueryRecordOptions(KDbConnection::QueryRecordOption::AddLimitTo1));
470 if (res == false) {
471 m_result = d->connection->result();
472 }
473 else if (res == true) {
474 contains_o_folder_id = true;
475 }
476 }
477 bool add_folder_id_column = false;
478
479 //! @todo what about read-only db access?
480 if (storedMajorVersion <= 0) {
481 d->versionMajor = KEXIPROJECT_VERSION_MAJOR;
482 d->versionMinor = KEXIPROJECT_VERSION_MINOR;
483 //For compatibility for projects created before Kexi 1.0 beta 1:
484 //1. no kexiproject_major_ver and kexiproject_minor_ver -> add them
485 if (!d->connection->options()->isReadOnly()) {
486 if (!props.setValue("kexiproject_major_ver", d->versionMajor)
487 || !props.setCaption("kexiproject_major_ver", xi18n("Project major version"))
488 || !props.setValue("kexiproject_minor_ver", d->versionMinor)
489 || !props.setCaption("kexiproject_minor_ver", xi18n("Project minor version"))) {
490 return false;
491 }
492 }
493
494 if (true == containsKexi__blobsTable) {
495 //! @todo what to do for readonly connections? Should we alter kexi__blobs in memory?
496 if (!d->connection->options()->isReadOnly()) {
497 if (!contains_o_folder_id) {
498 add_folder_id_column = true;
499 }
500 }
501 }
502 }
503 if (storedMajorVersion != d->versionMajor || storedMajorVersion != d->versionMinor) {
504 //! @todo version differs: should we change something?
505 d->versionMajor = storedMajorVersion;
506 d->versionMinor = storedMinorVersion;
507 }
508
509 QScopedPointer<KDbInternalTableSchema> t_blobs(new KDbInternalTableSchema("kexi__blobs"));
510 t_blobs->addField(new KDbField("o_id", KDbField::Integer,
511 KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned));
512 t_blobs->addField(new KDbField("o_data", KDbField::BLOB));
513 t_blobs->addField(new KDbField("o_name", KDbField::Text));
514 t_blobs->addField(new KDbField("o_caption", KDbField::Text));
515 t_blobs->addField(new KDbField("o_mime", KDbField::Text, KDbField::NotNull));
516 t_blobs->addField(new KDbField("o_folder_id",
517 KDbField::Integer, 0, KDbField::Unsigned) //references kexi__gallery_folders.f_id
518 //If null, the BLOB only points to virtual "All" folder
519 //WILL BE USED in Kexi >=2.0
520 );
521
522 //*** create global BLOB container, if not present
523 if (true == containsKexi__blobsTable) {
524 if (add_folder_id_column && !d->connection->options()->isReadOnly()) {
525 // 2. "kexi__blobs" table contains no "o_folder_id" column -> add it
526 // (by copying table to avoid data loss)
527 QScopedPointer<KDbInternalTableSchema> kexi__blobsCopy(
528 new KDbInternalTableSchema(*t_blobs)); //won't be not needed - will be physically renamed to kexi_blobs
529
530 kexi__blobsCopy->setName("kexi__blobs__copy");
531 if (!d->connection->createTable(kexi__blobsCopy.data(),
532 KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination))
533 {
534
535 m_result = d->connection->result();
536 return false;
537 }
538 KDbInternalTableSchema *ts = kexi__blobsCopy.take(); // createTable() took ownerhip of kexi__blobsCopy
539 // 2.1 copy data (insert 0's into o_folder_id column)
540 if (!d->connection->executeSql(KDbEscapedString(
541 "INSERT INTO kexi__blobs (o_data, o_name, o_caption, o_mime, o_folder_id) "
542 "SELECT o_data, o_name, o_caption, o_mime, 0 FROM kexi__blobs"))
543 // 2.2 remove the original kexi__blobs
544 || !d->connection->executeSql(
545 KDbEscapedString("DROP TABLE kexi__blobs")) // lowlevel
546 // 2.3 rename the copy back into kexi__blobs
547 || !d->connection->alterTableName(
548 ts, "kexi__blobs",
549 KDbConnection::AlterTableNameOptions(KDbConnection::AlterTableNameOption::Default)
550 & ~KDbConnection::AlterTableNameOptions(KDbConnection::AlterTableNameOption::DropDestination)))
551 {
552 //(no need to drop the copy, ROLLBACK will drop it)
553 m_result = d->connection->result();
554 return false;
555 }
556 }
557 //! just insert this schema, proper table exists
558 d->connection->createTable(t_blobs.take());
559 } else {
560 if (!d->connection->options()->isReadOnly()) {
561 if (!d->connection->createTable(t_blobs.data(),
562 KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination))
563 {
564 m_result = d->connection->result();
565 return false;
566 }
567 (void)t_blobs.take(); // createTable() took ownerhip of t_blobs
568 }
569 }
570
571 //Store default part information.
572 //Information for other parts (forms, reports...) are created on demand in KexiWindow::storeNewData()
573 const tristate containsKexi__partsTable = d->connection->containsTable("kexi__parts");
574 if (~containsKexi__partsTable) {
575 return false;
576 }
577 QScopedPointer<KDbInternalTableSchema> t_parts(new KDbInternalTableSchema("kexi__parts"));
578 t_parts->addField(
579 new KDbField("p_id", KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned)
580 );
581 t_parts->addField(new KDbField("p_name", KDbField::Text));
582 t_parts->addField(new KDbField("p_mime", KDbField::Text));
583 t_parts->addField(new KDbField("p_url", KDbField::Text));
584
585 if (true == containsKexi__partsTable) {
586 //! just insert this schema
587 d->connection->createTable(t_parts.take());
588 } else {
589 if (!d->connection->options()->isReadOnly()) {
590 bool partsTableOk = d->connection->createTable(t_parts.data(),
591 KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination);
592 if (!partsTableOk) {
593 m_result = d->connection->result();
594 return false;
595 }
596 KDbInternalTableSchema *ts = t_parts.take(); // createTable() took ownerhip of t_parts
597 QScopedPointer<KDbFieldList> fl(ts->subList("p_id", "p_name", "p_mime", "p_url"));
598 #define INSERT_RECORD(typeId, groupName, name) \
599 if (partsTableOk) { \
600 partsTableOk = d->connection->insertRecord(fl.data(), QVariant(int(KexiPart::typeId)), \
601 QVariant(groupName), \
602 QVariant("kexi/" name), QVariant("org.kexi-project." name)); \
603 if (partsTableOk) { \
604 d->savePluginId("org.kexi-project." name, int(KexiPart::typeId)); \
605 } \
606 }
607
608 INSERT_RECORD(TableObjectType, "Tables", "table")
609 INSERT_RECORD(QueryObjectType, "Queries", "query")
610 INSERT_RECORD(FormObjectType, "Forms", "form")
611 INSERT_RECORD(ReportObjectType, "Reports", "report")
612 INSERT_RECORD(ScriptObjectType, "Scripts", "script")
613 INSERT_RECORD(WebObjectType, "Web pages", "web")
614 INSERT_RECORD(MacroObjectType, "Macros", "macro")
615 #undef INSERT_RECORD
616 if (!partsTableOk) {
617 m_result = d->connection->result();
618 // note: kexi__parts object still exists because createTable() succeeded
619 return false;
620 }
621 }
622 }
623
624 // User data storage
625 const tristate containsKexi__userdataTable = d->connection->containsTable("kexi__userdata");
626 if (~containsKexi__userdataTable) {
627 return false;
628 }
629 QScopedPointer<KDbInternalTableSchema> t_userdata(new KDbInternalTableSchema("kexi__userdata"));
630 t_userdata->addField(new KDbField("d_user", KDbField::Text, KDbField::NotNull));
631 t_userdata->addField(new KDbField("o_id", KDbField::Integer, KDbField::NotNull, KDbField::Unsigned));
632 t_userdata->addField(new KDbField("d_sub_id", KDbField::Text, KDbField::NotNull | KDbField::NotEmpty));
633 t_userdata->addField(new KDbField("d_data", KDbField::LongText));
634
635 if (true == containsKexi__userdataTable) {
636 d->connection->createTable(t_userdata.take());
637 }
638 else if (!d->connection->options()->isReadOnly()) {
639 if (!d->connection->createTable(t_userdata.data(),
640 KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination))
641 {
642 m_result = d->connection->result();
643 return false;
644 }
645 (void)t_userdata.take(); // createTable() took ownerhip of t_userdata
646 }
647
648 if (insideTransaction) {
649 if (tg.transaction().isActive() && !tg.commit()) {
650 m_result = d->connection->result();
651 return false;
652 }
653 }
654 return true;
655 }
656
657 bool
createConnection()658 KexiProject::createConnection()
659 {
660 clearResult();
661 KDbMessageGuard mg(this);
662 if (d->connection) {
663 return true;
664 }
665
666 KDbMessageTitleSetter et(this);
667 KDbDriver *driver = Kexi::driverManager().driver(d->data->connectionData()->driverId());
668 if (!driver) {
669 m_result = Kexi::driverManager().result();
670 return false;
671 }
672
673 KDbConnectionOptions connectionOptions;
674 if (d->data->isReadOnly()) {
675 connectionOptions.setReadOnly(true);
676 }
677 d->connection = driver->createConnection(*d->data->connectionData(), connectionOptions);
678 if (!d->connection) {
679 m_result = driver->result();
680 qWarning() << "error create connection: " << m_result;
681 return false;
682 }
683
684 if (!d->connection->connect()) {
685 m_result = d->connection->result();
686 qWarning() << "error connecting: " << m_result;
687 delete d->connection; //this will also clear connection for BLOB buffer
688 d->connection = 0;
689 return false;
690 }
691
692 //re-init BLOB buffer
693 //! @todo won't work for subsequent connection
694 KexiBLOBBuffer::setConnection(d->connection);
695 return true;
696 }
697
closeConnectionInternal()698 bool KexiProject::closeConnectionInternal()
699 {
700 if (!m_result.isError()) {
701 clearResult();
702 }
703 if (!d->connection) {
704 return true;
705 }
706 if (!d->connection->disconnect()) {
707 if (!m_result.isError()) {
708 m_result = d->connection->result();
709 }
710 return false;
711 }
712
713 delete d->connection; //this will also clear connection for BLOB buffer
714 d->connection = 0;
715 return true;
716 }
717
closeConnection()718 bool KexiProject::closeConnection()
719 {
720 clearResult();
721 KDbMessageGuard mg(this);
722 if (!d->connection)
723 return true;
724
725 if (!d->connection->disconnect()) {
726 m_result = d->connection->result();
727 return false;
728 }
729
730 delete d->connection; //this will also clear connection for BLOB buffer
731 d->connection = 0;
732 return true;
733 }
734
735 bool
initProject()736 KexiProject::initProject()
737 {
738 //qDebug() << "checking project parts...";
739 if (!checkProject()) {
740 return false;
741 }
742
743 // !@todo put more props. todo - creator, created date, etc. (also to KexiProjectData)
744 KDbProperties props = d->connection->databaseProperties();
745 QString str(props.value("project_caption").toString());
746 if (!str.isEmpty())
747 d->data->setCaption(str);
748 str = props.value("project_desc").toString();
749 if (!str.isEmpty())
750 d->data->setDescription(str);
751
752 return true;
753 }
754
755 bool
isConnected()756 KexiProject::isConnected()
757 {
758 if (d->connection && d->connection->isDatabaseUsed())
759 return true;
760
761 return false;
762 }
763
764 KexiPart::ItemDict*
items(KexiPart::Info * i)765 KexiProject::items(KexiPart::Info *i)
766 {
767 clearResult();
768 KDbMessageGuard mg(this);
769 if (!i || !isConnected())
770 return 0;
771
772 //trying in cache...
773 KexiPart::ItemDict *dict = d->itemDicts.value(i->id());
774 if (dict)
775 return dict;
776 if (d->itemsRetrieved)
777 return 0;
778 if (!retrieveItems())
779 return 0;
780 return items(i); // try again
781 }
782
retrieveItems()783 bool KexiProject::retrieveItems()
784 {
785 d->itemsRetrieved = true;
786 KDbCursor *cursor = d->connection->executeQuery(
787 KDbEscapedString("SELECT o_id, o_name, o_caption, o_type FROM kexi__objects ORDER BY o_type"));
788 if (!cursor) {
789 m_result = d->connection->result();
790 return 0;
791 }
792
793 int recentTypeId = -1000;
794 QString pluginId;
795 KexiPart::ItemDict *dict = 0;
796 QSet<QString> tableNamesSet;
797 for (cursor->moveFirst(); !cursor->eof(); cursor->moveNext()) {
798 bool ok;
799 const int typeId = cursor->value(3).toInt(&ok);
800 if (!ok || typeId <= 0) {
801 qInfo() << "object of unknown type id" << cursor->value(3) << "id=" << cursor->value(0)
802 << "name=" << cursor->value(1);
803 continue;
804 }
805 if (recentTypeId == typeId) {
806 if (pluginId.isEmpty()) { // still the same unknown plugin ID
807 continue;
808 }
809 }
810 else {
811 // a new type ID: create another plugin items dict if it's an ID for a known type
812 recentTypeId = typeId;
813 pluginId = pluginIdForTypeId(typeId);
814 if (pluginId.isEmpty())
815 continue;
816 dict = new KexiPart::ItemDict();
817 d->itemDicts.insert(pluginId, dict);
818 if (typeId == KDb::TableObjectType) {
819 // Starting to load table names: initialize.
820 // This list since 3.2 does not contain names without physical tables so we can
821 // catch these cases below.
822 const QStringList tableNames(d->connection->tableNames(false /*public*/, &ok));
823 if (!ok) {
824 m_result = KDbResult(ERR_OBJECT_NOT_FOUND, xi18n("Could not load list of tables."));
825 qDeleteAll(d->itemDicts);
826 return false;
827 }
828 for (const QString &name : tableNames) {
829 tableNamesSet.insert(name.toLower());
830 }
831 }
832 }
833 const int ident = cursor->value(0).toInt(&ok);
834 const QString objName(cursor->value(1).toString());
835 if (!ok || ident <= 0 || !KDb::isIdentifier(objName))
836 {
837 continue; // invalid ID or invalid name
838 }
839 if (typeId == KDb::TableObjectType) {
840 if (d->connection->isInternalTableSchema(objName)) {
841 qInfo() << "table" << objName << "id=" << ident << "is internal, skipping";
842 continue;
843 }
844 if (!tableNamesSet.contains(objName.toLower())) {
845 qInfo() << "table" << objName << "id=" << ident
846 << "does not correspondent with physical table";
847 continue;
848 }
849 }
850 KexiPart::Item *it = new KexiPart::Item();
851 it->setIdentifier(ident);
852 it->setPluginId(pluginId);
853 it->setName(objName);
854 it->setCaption(cursor->value(2).toString());
855 dict->insert(it->identifier(), it);
856 }
857
858 d->connection->deleteCursor(cursor);
859 return true;
860 }
861
typeIdForPluginId(const QString & pluginId) const862 int KexiProject::typeIdForPluginId(const QString &pluginId) const
863 {
864 return d->typeIds.value(pluginId, -1);
865 }
866
pluginIdForTypeId(int typeId) const867 QString KexiProject::pluginIdForTypeId(int typeId) const
868 {
869 return d->pluginIdsForTypeIds.value(typeId);
870 }
871
872 //static
pluginIdToTableOrQueryType(const QString & pluginId,bool * ok)873 KDbTableOrQuerySchema::Type KexiProject::pluginIdToTableOrQueryType(
874 const QString &pluginId, bool *ok)
875 {
876 Q_ASSERT(ok);
877 KDbTableOrQuerySchema::Type result;
878 if (pluginId == QStringLiteral("org.kexi-project.table")) {
879 result = KDbTableOrQuerySchema::Type::Table;
880 *ok = true;
881 } else if (pluginId == QStringLiteral("org.kexi-project.query")) {
882 result = KDbTableOrQuerySchema::Type::Query;
883 *ok = true;
884 } else {
885 result = KDbTableOrQuerySchema::Type::Table;
886 *ok = false;
887 }
888 return result;
889 }
890
891 KexiPart::ItemDict*
itemsForPluginId(const QString & pluginId)892 KexiProject::itemsForPluginId(const QString &pluginId)
893 {
894 KDbMessageGuard mg(this);
895 KexiPart::Info *info = Kexi::partManager().infoForPluginId(pluginId);
896 if (!info) {
897 m_result = Kexi::partManager().result();
898 return 0;
899 }
900 return items(info);
901 }
902
903 void
getSortedItems(KexiPart::ItemList * list,KexiPart::Info * i)904 KexiProject::getSortedItems(KexiPart::ItemList* list, KexiPart::Info *i)
905 {
906 Q_ASSERT(list);
907 list->clear();
908 KexiPart::ItemDict* dict = items(i);
909 if (!dict)
910 return;
911 foreach(KexiPart::Item *item, *dict) {
912 list->append(item);
913 }
914 }
915
916 void
getSortedItemsForPluginId(KexiPart::ItemList * list,const QString & pluginId)917 KexiProject::getSortedItemsForPluginId(KexiPart::ItemList *list, const QString &pluginId)
918 {
919 Q_ASSERT(list);
920 KexiPart::Info *info = Kexi::partManager().infoForPluginId(pluginId);
921 if (!info) {
922 m_result = Kexi::partManager().result();
923 return;
924 }
925 getSortedItems(list, info);
926 }
927
928 void
addStoredItem(KexiPart::Info * info,KexiPart::Item * item)929 KexiProject::addStoredItem(KexiPart::Info *info, KexiPart::Item *item)
930 {
931 if (!info || !item)
932 return;
933 KexiPart::ItemDict *dict = items(info);
934 item->setNeverSaved(false);
935 d->unstoredItems.remove(item); //no longer unstored
936
937 // are we replacing previous item?
938 KexiPart::Item *prevItem = dict->take(item->identifier());
939 if (prevItem) {
940 emit itemRemoved(*prevItem);
941 }
942
943 dict->insert(item->identifier(), item);
944 //let's update e.g. navigator
945 emit newItemStored(item);
946 }
947
948 KexiPart::Item*
itemForPluginId(const QString & pluginId,const QString & name)949 KexiProject::itemForPluginId(const QString &pluginId, const QString &name)
950 {
951 KexiPart::ItemDict *dict = itemsForPluginId(pluginId);
952 if (!dict) {
953 qWarning() << "no part class=" << pluginId;
954 return 0;
955 }
956 foreach(KexiPart::Item *item, *dict) {
957 if (item->name() == name)
958 return item;
959 }
960 return 0;
961 }
962
963 KexiPart::Item*
item(KexiPart::Info * i,const QString & name)964 KexiProject::item(KexiPart::Info *i, const QString &name)
965 {
966 KexiPart::ItemDict *dict = items(i);
967 if (!dict)
968 return 0;
969 foreach(KexiPart::Item* item, *dict) {
970 if (item->name() == name)
971 return item;
972 }
973 return 0;
974 }
975
976 KexiPart::Item*
item(int identifier)977 KexiProject::item(int identifier)
978 {
979 foreach(KexiPart::ItemDict *dict, d->itemDicts) {
980 KexiPart::Item *item = dict->value(identifier);
981 if (item)
982 return item;
983 }
984 return 0;
985 }
986
findPartFor(const KexiPart::Item & item)987 KexiPart::Part *KexiProject::findPartFor(const KexiPart::Item& item)
988 {
989 clearResult();
990 KDbMessageGuard mg(this);
991 KDbMessageTitleSetter et(this);
992 KexiPart::Part *part = Kexi::partManager().partForPluginId(item.pluginId());
993 if (!part) {
994 qWarning() << "!part: " << item.pluginId();
995 m_result = Kexi::partManager().result();
996 }
997 return part;
998 }
999
openObject(QWidget * parent,KexiPart::Item * item,Kexi::ViewMode viewMode,QMap<QString,QVariant> * staticObjectArgs)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
openObject(QWidget * parent,const QString & pluginId,const QString & name,Kexi::ViewMode viewMode)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
checkWritable()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
removeObject(KexiPart::Item * item)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
renameObject(KexiPart::Item * item,const QString & newName)1089 bool KexiProject::renameObject(KexiPart::Item *item, const QString& newName)
1090 {
1091 KDbMessageGuard mg(this);
1092 return d->setNameOrCaption(item, &newName, 0);
1093 }
1094
setObjectCaption(KexiPart::Item * item,const QString & newCaption)1095 bool KexiProject::setObjectCaption(KexiPart::Item *item, const QString& newCaption)
1096 {
1097 KDbMessageGuard mg(this);
1098 return d->setNameOrCaption(item, 0, &newCaption);
1099 }
1100
createPartItem(KexiPart::Info * info,const QString & suggestedCaption)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
createPartItem(KexiPart::Part * part,const QString & suggestedCaption)1173 KexiPart::Item* KexiProject::createPartItem(KexiPart::Part *part, const QString& suggestedCaption)
1174 {
1175 Q_ASSERT(part);
1176 return createPartItem(part->info(), suggestedCaption);
1177 }
1178
deleteUnstoredItem(KexiPart::Item * item)1179 void KexiProject::deleteUnstoredItem(KexiPart::Item *item)
1180 {
1181 if (!item)
1182 return;
1183 d->unstoredItems.remove(item);
1184 delete item;
1185 }
1186
sqlParser()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*
createBlankProject(bool * cancelled,const KexiProjectData & data,KDbMessageHandler * handler)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*/
dropProject(const KexiProjectData & data,KDbMessageHandler * handler,bool dontAsk)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
checkProject(const QString & singlePluginId)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
generatePrivateID()1327 int KexiProject::generatePrivateID()
1328 {
1329 return --d->privateIDCounter;
1330 }
1331
createIdForPart(const KexiPart::Info & info)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
missingParts() const1391 KexiPart::MissingPartsList KexiProject::missingParts() const
1392 {
1393 return d->missingParts;
1394 }
1395
checkObjectId(const char * method,int objectID)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
loadUserDataBlock(int objectID,const QString & dataID,QString * dataString)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
storeUserDataBlock(int objectID,const QString & dataID,const QString & dataString)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
copyUserDataBlock(int sourceObjectID,int destObjectID,const QString & dataID)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
removeUserDataBlock(int objectID,const QString & dataID)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
askForOpeningNonWritableFileAsReadOnly(QWidget * parent,const QFileInfo & finfo)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 }
1535