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