File indexing completed on 2024-04-28 16:30:31
0001 /*************************************************************************** 0002 * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr 0003 * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 ***************************************************************************/ 0006 /** @file 0007 * This file implements classes SKGDocument. 0008 * 0009 * @author Stephane MANKOWSKI / Guillaume DE BURE 0010 */ 0011 #include "skgdocument.h" 0012 0013 #include <kcolorscheme.h> 0014 0015 #include <qapplication.h> 0016 #ifdef SKG_DBUS 0017 #include <qdbusconnection.h> 0018 #endif 0019 #include <qdir.h> 0020 #include <qfile.h> 0021 #include <qhash.h> 0022 #include <qicon.h> 0023 #include <qjsondocument.h> 0024 #include <qprocess.h> 0025 #include <qregularexpression.h> 0026 #include <qsqldatabase.h> 0027 #include <qsqldriver.h> 0028 #include <qsqlerror.h> 0029 #include <qsqlquery.h> 0030 #include <qtconcurrentrun.h> 0031 #include <qthread.h> 0032 #include <qurl.h> 0033 #include <quuid.h> 0034 #include <qvariant.h> 0035 0036 #include <sqlite3.h> 0037 #include <cmath> 0038 0039 #include "skgdocumentprivate.h" 0040 #include "skgerror.h" 0041 #include "skgpropertyobject.h" 0042 #include "skgreport.h" 0043 #include "skgservices.h" 0044 #include "skgtraces.h" 0045 #include "skgtransactionmng.h" 0046 0047 #define SQLDRIVERNAME QStringLiteral("SKGSQLCIPHER") 0048 0049 /** 0050 * Custom sqlite function. 0051 */ 0052 static void sleepFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0053 { 0054 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0055 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0056 0057 if (Q_LIKELY(data1)) { 0058 auto s = SKGServices::stringToInt(QString(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar))); 0059 QThread::sleep(s); 0060 0061 sqlite3_result_text(context, "OK", 2, SQLITE_TRANSIENT); 0062 } 0063 } 0064 0065 /** 0066 * Custom sqlite function. 0067 */ 0068 static void periodFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0069 { 0070 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0071 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0072 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0073 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0074 0075 if (Q_LIKELY(data1 && data2)) { 0076 QDate date = SKGServices::stringToTime(QString(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar))).date(); 0077 QString format = QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar)).toUpper(); 0078 QString period = SKGServices::dateToPeriod(date, format); 0079 QByteArray output = period.toUtf8(); 0080 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0081 } 0082 } 0083 0084 /** 0085 * Custom sqlite function. 0086 */ 0087 static void formattedDateFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0088 { 0089 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0090 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0091 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0092 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0093 0094 if (Q_LIKELY(data1 && data2)) { 0095 QString string(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)); 0096 QString format = QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar)); 0097 0098 QString date = QDate::fromString(string, QStringLiteral("yyyy-MM-dd")).toString(format); 0099 0100 QByteArray output = date.toUtf8(); 0101 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0102 } 0103 } 0104 0105 /** 0106 * Custom sqlite function. 0107 */ 0108 static void dateFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0109 { 0110 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0111 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0112 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0113 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0114 0115 if (Q_LIKELY(data1 && data2)) { 0116 QString string(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)); 0117 QString format = QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar)); 0118 0119 QString date = SKGServices::dateToSqlString(string, format).trimmed(); 0120 if (date.isEmpty()) { 0121 date = QDate::currentDate().toString(QStringLiteral("yyyy-MM-dd")); 0122 } 0123 0124 QByteArray output = date.toUtf8(); 0125 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0126 } 0127 } 0128 0129 /** 0130 * Custom sqlite function. 0131 */ 0132 static void weekYearFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0133 { 0134 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0135 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0136 0137 if (Q_LIKELY(data1)) { 0138 QString string(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)); 0139 0140 QString date = SKGServices::dateToPeriod(SKGServices::stringToTime(string).date(), QStringLiteral("W")); 0141 0142 QByteArray output = date.toUtf8(); 0143 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0144 } 0145 } 0146 0147 /** 0148 * Custom sqlite function. 0149 */ 0150 static void currencyFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0151 { 0152 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0153 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0154 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0155 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0156 0157 if (Q_LIKELY(data1 && data2)) { 0158 double string = SKGServices::stringToDouble(QString::fromRawData(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar))); 0159 QString symbol = QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar)); 0160 0161 QString currency = SKGServices::toCurrencyString(string, symbol); 0162 0163 QByteArray output = currency.toUtf8(); 0164 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0165 } 0166 } 0167 0168 /** 0169 * Custom sqlite function. 0170 */ 0171 static void xorFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0172 { 0173 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0174 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0175 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0176 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0177 0178 if (Q_LIKELY(data1 && data2)) { 0179 auto string = QString::fromRawData(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)).toUtf8(); 0180 auto key = QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar)).toUtf8(); 0181 0182 if (string.startsWith(QByteArray("# "))) { 0183 // Decrypt 0184 string = QByteArray::fromHex(string.right(string.length() - 2)); 0185 QByteArray estring; 0186 for (int i = 0; i < string.size(); ++i) { 0187 estring += static_cast<char>(string[i] ^ key[i % key.size()]); 0188 } 0189 QByteArray output = estring; 0190 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0191 } else { 0192 // Encrypt 0193 QByteArray estring; 0194 for (int i = 0; i < string.size(); ++i) { 0195 estring += static_cast<char>(string[i] ^ key[i % key.size()]); 0196 } 0197 QByteArray output = "# " + estring.toHex(); 0198 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0199 } 0200 } 0201 } 0202 0203 static void xordoubleFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0204 { 0205 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0206 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0207 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0208 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0209 0210 if (Q_LIKELY(data1 && data2)) { 0211 auto d = SKGServices::stringToDouble(QString::fromRawData(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)).toUtf8()); 0212 auto key = QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar)).toUtf8(); 0213 int kk = 0; 0214 for (int i = 0; i < key.size(); ++i) { 0215 kk += key[i]; 0216 } 0217 QByteArray output = SKGServices::doubleToString(static_cast<double>(kk) - d).toUtf8(); 0218 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0219 } 0220 } 0221 0222 /** 0223 * Custom sqlite function. 0224 */ 0225 static void wordFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0226 { 0227 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0228 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0229 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0230 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0231 0232 if (Q_LIKELY(data1 && data2)) { 0233 QString string1(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)); 0234 string1 = string1.simplified(); 0235 QRegularExpression re(QStringLiteral("(\\w+)")); 0236 QRegularExpressionMatchIterator i = re.globalMatch(string1); 0237 QStringList list; 0238 while (i.hasNext()) { 0239 QRegularExpressionMatch match = i.next(); 0240 QString word = match.captured(1); 0241 list << word; 0242 } 0243 0244 int pos = SKGServices::stringToInt(QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar))); 0245 if (pos == 0) { 0246 pos = 1; 0247 } else if (pos > list.count()) { 0248 pos = list.count(); 0249 } else if (pos < -list.count()) { 0250 pos = 1; 0251 } else if (pos < 0) { 0252 pos = list.count() + pos + 1; 0253 } 0254 0255 QByteArray output = list[pos - 1].toUtf8(); 0256 0257 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0258 } 0259 } 0260 0261 /** 0262 * Custom sqlite function. 0263 */ 0264 static void wildcardFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0265 { 0266 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0267 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0268 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0269 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0270 0271 if (Q_LIKELY(data1 && data2)) { 0272 QString string1(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)); 0273 QString string2 = QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar)); 0274 0275 QRegularExpression pattern(QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(string1)), QRegularExpression::CaseInsensitiveOption); 0276 if (pattern.isValid()) { 0277 sqlite3_result_int(context, static_cast<int>(pattern.match(string2).hasMatch())); 0278 } else { 0279 sqlite3_result_error(context, pattern.errorString().toUtf8().constData(), -1); 0280 } 0281 } 0282 } 0283 0284 /** 0285 * Custom sqlite function. 0286 */ 0287 static void regexpFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0288 { 0289 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0290 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0291 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0292 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0293 0294 if (Q_LIKELY(data1 && data2)) { 0295 QString string1(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)); 0296 QString string2 = QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar)); 0297 0298 QRegularExpression pattern(QRegularExpression::anchoredPattern(string1), QRegularExpression::CaseInsensitiveOption); 0299 if (pattern.isValid()) { 0300 sqlite3_result_int(context, static_cast<int>(pattern.match(string2).hasMatch())); 0301 } else { 0302 sqlite3_result_error(context, pattern.errorString().toUtf8().constData(), -1); 0303 } 0304 } 0305 } 0306 0307 /** 0308 * Custom sqlite function. 0309 */ 0310 static void regexpCaptureFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0311 { 0312 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0313 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0314 int len2 = sqlite3_value_bytes16(argv[ 1 ]); 0315 const void* data2 = sqlite3_value_text16(argv[ 1 ]); 0316 int len3 = sqlite3_value_bytes16(argv[ 2 ]); 0317 const void* data3 = sqlite3_value_text16(argv[ 2 ]); 0318 if (Q_LIKELY(data1 && data2 && data3)) { 0319 int pos = SKGServices::stringToInt(QString::fromRawData(reinterpret_cast<const QChar*>(data3), len3 / sizeof(QChar))); 0320 0321 QString string1(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)); 0322 QString string2 = QString::fromRawData(reinterpret_cast<const QChar*>(data2), len2 / sizeof(QChar)); 0323 0324 QRegularExpression pattern(string1, QRegularExpression::CaseInsensitiveOption); 0325 if (pattern.isValid()) { 0326 auto match = pattern.match(string2); 0327 QByteArray output = match.capturedTexts().value(pos).toUtf8(); 0328 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0329 } else { 0330 sqlite3_result_error(context, pattern.errorString().toUtf8().constData(), -1); 0331 } 0332 } 0333 } 0334 0335 /** 0336 * Custom sqlite function. 0337 */ 0338 static void upperFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0339 { 0340 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0341 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0342 0343 if (Q_LIKELY(data1)) { 0344 QByteArray output = QString::fromRawData(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)).toUpper().toUtf8(); 0345 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0346 } 0347 } 0348 0349 /** 0350 * Custom sqlite function. 0351 */ 0352 static void nextFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0353 { 0354 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0355 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0356 0357 if (Q_LIKELY(data1)) { 0358 QByteArray output = SKGServices::getNextString(QString::fromRawData(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar))).toUtf8(); 0359 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0360 } 0361 } 0362 0363 /** 0364 * Custom sqlite function. 0365 */ 0366 static void lowerFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0367 { 0368 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0369 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0370 0371 if (Q_LIKELY(data1)) { 0372 QByteArray output = QString::fromRawData(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)).toLower().toUtf8(); 0373 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0374 } 0375 } 0376 0377 /** 0378 * Custom sqlite function. 0379 */ 0380 static void capitalizeFunction(sqlite3_context* context, int /*argc*/, sqlite3_value** argv) 0381 { 0382 int len1 = sqlite3_value_bytes16(argv[ 0 ]); 0383 const void* data1 = sqlite3_value_text16(argv[ 0 ]); 0384 0385 if (Q_LIKELY(data1)) { 0386 QString str = QString::fromRawData(reinterpret_cast<const QChar*>(data1), len1 / sizeof(QChar)); 0387 QByteArray output = (str.at(0).toUpper() + str.mid(1).toLower()).toUtf8(); 0388 sqlite3_result_text(context, output.constData(), output.size(), SQLITE_TRANSIENT); 0389 } 0390 } 0391 0392 static SKGError addSqliteAddon(QSqlDatabase* iDb) 0393 { 0394 SKGError err; 0395 auto* sqlite_handle = iDb->driver()->handle().value<sqlite3*>(); 0396 if (sqlite_handle != nullptr) { 0397 sqlite3_create_function(sqlite_handle, "REGEXP", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, ®expFunction, nullptr, nullptr); 0398 sqlite3_create_function(sqlite_handle, "REGEXPCAPTURE", 3, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, ®expCaptureFunction, nullptr, nullptr); 0399 sqlite3_create_function(sqlite_handle, "WILDCARD", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &wildcardFunction, nullptr, nullptr); 0400 sqlite3_create_function(sqlite_handle, "WORD", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &wordFunction, nullptr, nullptr); 0401 sqlite3_create_function(sqlite_handle, "TODATE", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &dateFunction, nullptr, nullptr); 0402 sqlite3_create_function(sqlite_handle, "TOWEEKYEAR", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &weekYearFunction, nullptr, nullptr); 0403 sqlite3_create_function(sqlite_handle, "TOFORMATTEDDATE", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &formattedDateFunction, nullptr, nullptr); 0404 sqlite3_create_function(sqlite_handle, "PERIOD", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &periodFunction, nullptr, nullptr); 0405 sqlite3_create_function(sqlite_handle, "SLEEP", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &sleepFunction, nullptr, nullptr); 0406 sqlite3_create_function(sqlite_handle, "TOCURRENCY", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, ¤cyFunction, nullptr, nullptr); 0407 sqlite3_create_function(sqlite_handle, "XOR", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &xorFunction, nullptr, nullptr); 0408 sqlite3_create_function(sqlite_handle, "XORD", 2, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &xordoubleFunction, nullptr, nullptr); 0409 sqlite3_create_function(sqlite_handle, "UPPER", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &upperFunction, nullptr, nullptr); 0410 sqlite3_create_function(sqlite_handle, "LOWER", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &lowerFunction, nullptr, nullptr); 0411 sqlite3_create_function(sqlite_handle, "NEXT", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &nextFunction, nullptr, nullptr); 0412 int rc = sqlite3_create_function(sqlite_handle, "CAPITALIZE", 1, SQLITE_UTF16 | SQLITE_DETERMINISTIC, nullptr, &capitalizeFunction, nullptr, nullptr); 0413 if (rc != SQLITE_OK) { 0414 err = SKGError(SQLLITEERROR + rc, QStringLiteral("sqlite3_create_function failed")); 0415 } 0416 } else { 0417 SKGTRACE << "WARNING: Custom sqlite functions not added" << SKGENDL; 0418 } 0419 return err; 0420 } 0421 0422 SKGDocument::SKGDocument() 0423 : d(new SKGDocumentPrivate()) 0424 { 0425 SKGTRACEINFUNC(10) 0426 // Set the QThreadPool 0427 // QThreadPool::globalInstance()->setMaxThreadCount(3*QThread::idealThreadCount()); 0428 0429 // DBUS registration 0430 #ifdef SKG_DBUS 0431 QDBusConnection dbus = QDBusConnection::sessionBus(); 0432 dbus.registerObject(QStringLiteral("/skg/skgdocument"), this, QDBusConnection::ExportAllContents); 0433 dbus.registerService(QStringLiteral("org.skg")); 0434 #endif 0435 0436 // Initialisation of undoable tables 0437 SKGListNotUndoable.push_back(QStringLiteral("T.doctransaction")); 0438 SKGListNotUndoable.push_back(QStringLiteral("T.doctransactionitem")); 0439 SKGListNotUndoable.push_back(QStringLiteral("T.doctransactionmsg")); 0440 0441 // Database unique identifier 0442 ++SKGDocumentPrivate::m_databaseUniqueIdentifier; 0443 d->m_databaseIdentifier = "SKGDATABASE_" % SKGServices::intToString(SKGDocumentPrivate::m_databaseUniqueIdentifier); 0444 0445 // Initialisation of backup file parameters 0446 setBackupParameters(QLatin1String(""), QStringLiteral(".old")); 0447 0448 // 320157 vvvv 0449 // Disable OS lock 0450 sqlite3_vfs* vfs = sqlite3_vfs_find("unix-none"); 0451 if (Q_LIKELY(vfs)) { 0452 sqlite3_vfs_register(vfs, 1); 0453 } else { 0454 SKGTRACE << "WARNING: Impossible to use the 'unix-none' vfs mode of sqlite3. Use:'" << sqlite3_vfs_find(nullptr)->zName << "'" << SKGENDL; 0455 } 0456 // 320157 ^^^^ 0457 } 0458 0459 SKGDocument::~SKGDocument() 0460 { 0461 SKGTRACEINFUNC(10) 0462 close(); 0463 d->m_progressFunction = nullptr; 0464 d->m_progressData = nullptr; 0465 d->m_checkFunctions.clear(); 0466 0467 for (auto w : qAsConst(d->m_watchers)) { 0468 delete w; 0469 } 0470 d->m_watchers.clear(); 0471 0472 delete d->m_cacheSql; 0473 d->m_cacheSql = nullptr; 0474 0475 delete d; 0476 d = nullptr; 0477 } 0478 0479 QString SKGDocument::getUniqueIdentifier() const 0480 { 0481 return d->m_uniqueIdentifier; 0482 } 0483 0484 QString SKGDocument::getDatabaseIdentifier() const 0485 { 0486 return d->m_databaseIdentifier; 0487 } 0488 0489 SKGError SKGDocument::setProgressCallback(FuncProgress iProgressFunction, void* iData) 0490 { 0491 d->m_progressFunction = iProgressFunction; 0492 d->m_progressData = iData; 0493 return SKGError(); 0494 } 0495 0496 SKGError SKGDocument::addEndOfTransactionCheck(SKGError(*iCheckFunction)(SKGDocument*)) 0497 { 0498 d->m_checkFunctions.append(iCheckFunction); 0499 return SKGError(); 0500 } 0501 0502 SKGError SKGDocument::stepForward(int iPosition, const QString& iText) 0503 { 0504 SKGError err; 0505 0506 // Increase the step for the last transaction 0507 if (Q_LIKELY(getDepthTransaction())) { 0508 d->m_posStepForTransaction.pop_back(); 0509 d->m_posStepForTransaction.push_back(iPosition); 0510 } 0511 0512 // Check if a callback function exists 0513 if (Q_LIKELY(d->m_progressFunction)) { 0514 // YES ==> compute 0515 double min = 0; 0516 double max = 100; 0517 0518 bool emitevent = true; 0519 auto nbIt = d->m_nbStepForTransaction.constBegin(); 0520 auto posIt = d->m_posStepForTransaction.constBegin(); 0521 for (; emitevent && nbIt != d->m_nbStepForTransaction.constEnd(); ++nbIt) { 0522 int p = *posIt; 0523 int n = *nbIt; 0524 if (Q_UNLIKELY(p < 0 || p > n)) { 0525 p = n; 0526 } 0527 0528 if (Q_LIKELY(n != 0)) { 0529 double pmin = min; 0530 double pmax = max; 0531 min = pmin + (pmax - pmin) * (static_cast<double>(p) / static_cast<double>(n)); 0532 max = pmin + (pmax - pmin) * (static_cast<double>(p + 1) / static_cast<double>(n)); 0533 if (Q_UNLIKELY(max > 100)) { 0534 max = 100; 0535 } 0536 } else { 0537 emitevent = false; 0538 } 0539 0540 ++posIt; 0541 } 0542 0543 int posPercent = rint(min); 0544 0545 // Call the call back 0546 if (emitevent) { 0547 d->m_inProgress = true; 0548 QString text; 0549 qint64 time = QDateTime::currentMSecsSinceEpoch() - d->m_timeBeginTransaction; 0550 if (Q_UNLIKELY(time > 3000)) { 0551 text = iText; 0552 if (text.isEmpty()) { 0553 text = d->m_nameForTransaction.at(d->m_nameForTransaction.count() - 1); 0554 } 0555 } 0556 if (Q_LIKELY(d->m_progressFunction(posPercent, time, text, d->m_progressData) != 0)) { 0557 err.setReturnCode(ERR_ABORT).setMessage(i18nc("User interrupted something that Skrooge was performing", "The current transaction has been interrupted")); 0558 0559 // Remove all untransactionnal messaged 0560 m_unTransactionnalMessages.clear(); 0561 } 0562 d->m_inProgress = false; 0563 } 0564 } 0565 return err; 0566 } 0567 0568 SKGError SKGDocument::beginTransaction(const QString& iName, int iNbStep, const QDateTime& iDate, bool iRefreshViews) 0569 { 0570 SKGError err; 0571 SKGTRACEINFUNCRC(5, err) 0572 SKGTRACEL(10) << "Input parameter [name]=[" << iName << "] [nb step]=[" << iNbStep << "] [refresh]=[" << (iRefreshViews ? QStringLiteral("Y") : QStringLiteral("N")) << ']' << SKGENDL; 0573 bool overrideCursor = false; 0574 if (d->m_nbStepForTransaction.isEmpty()) { 0575 // Open SQLtransaction 0576 err = executeSqliteOrder(QStringLiteral("BEGIN;")); 0577 IFOK(err) { 0578 overrideCursor = true; 0579 0580 // Create undo redo transaction 0581 err = executeSqliteOrder(QStringLiteral("insert into doctransaction (d_date, t_name, i_parent") % 0582 (!iRefreshViews ? ", t_refreshviews" : "") % 0583 ") values " 0584 "('" % SKGServices::timeToString(iDate) % 0585 "','" % SKGServices::stringToSqlString(iName) % 0586 "', " % SKGServices::intToString(getTransactionToProcess(SKGDocument::UNDO)) % 0587 (!iRefreshViews ? ",'N'" : "") % 0588 ");"); 0589 addValueInCache(QStringLiteral("SKG_REFRESH_VIEW"), (iRefreshViews ? QStringLiteral("Y") : QStringLiteral("N"))); 0590 d->m_currentTransaction = getTransactionToProcess(SKGDocument::UNDO); 0591 d->m_timeBeginTransaction = QDateTime::currentMSecsSinceEpoch(); 0592 } 0593 } else { 0594 // A transaction already exists 0595 // Check if the child transaction is a opened in the progress callback 0596 if (d->m_inProgress) { 0597 err.setReturnCode(ERR_FAIL).setMessage(i18nc("Something went wrong with SQL transactions", "A transaction cannot be started during execution of another one")); 0598 } 0599 } 0600 IFOK(err) { 0601 d->m_nbStepForTransaction.push_back(iNbStep); 0602 d->m_posStepForTransaction.push_back(iNbStep); 0603 QString n = iName; 0604 n = n.remove(QStringLiteral("#INTERNAL#")); 0605 if (n.isEmpty() && !d->m_nameForTransaction.isEmpty()) { 0606 n = d->m_nameForTransaction.at(d->m_nameForTransaction.count() - 1); 0607 } 0608 d->m_nameForTransaction.push_back(n); 0609 0610 if (iNbStep > 0) { 0611 err = stepForward(0); 0612 } 0613 } else { 0614 executeSqliteOrder(QStringLiteral("ROLLBACK;")); 0615 } 0616 0617 if (Q_LIKELY(overrideCursor && !err && qobject_cast<QGuiApplication*>(qApp) != nullptr)) { // clazy:excludeall=unneeded-cast 0618 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); 0619 } 0620 0621 return err; 0622 } 0623 0624 SKGError SKGDocument::checkExistingTransaction() const 0625 { 0626 SKGError err; 0627 if (d->m_nbStepForTransaction.isEmpty()) { 0628 err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "A transaction must be opened to do this action")); 0629 } 0630 return err; 0631 } 0632 0633 SKGError SKGDocument::endTransaction(bool succeedded) 0634 { 0635 SKGError err; 0636 SKGError errOverwritten; 0637 SKGTRACEINFUNCRC(5, err) 0638 if (Q_UNLIKELY(d->m_nbStepForTransaction.empty())) { 0639 err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Closing transaction failed because too many transactions ended")); 0640 } else { 0641 stepForward(d->m_nbStepForTransaction.at(d->m_nbStepForTransaction.count() - 1)); // =100% 0642 if (Q_LIKELY(d->m_nbStepForTransaction.size())) { // This test is needed. It is a security in some cases. 0643 d->m_nbStepForTransaction.pop_back(); 0644 d->m_posStepForTransaction.pop_back(); 0645 d->m_nameForTransaction.pop_back(); 0646 } 0647 QString currentTransactionString = SKGServices::intToString(getCurrentTransaction()); 0648 0649 if (d->m_nbStepForTransaction.empty()) { 0650 QStringList listModifiedTables; 0651 0652 // Check 0653 if (succeedded) { 0654 auto cachepointer = d->m_cache; 0655 d->m_cache = QHash<QString, QString>(); 0656 0657 for (auto check : qAsConst(d->m_checkFunctions)) { 0658 errOverwritten = check(this); 0659 IFKO(errOverwritten) { 0660 succeedded = false; 0661 SKGTRACEL(5) << "Transaction cancelled by a check" << SKGENDL; 0662 break; 0663 } 0664 } 0665 0666 d->m_cache = cachepointer; 0667 } 0668 0669 if (succeedded) { 0670 // Link items on current transaction 0671 IFOK(err) { 0672 err = executeSqliteOrder("UPDATE doctransactionitem set rd_doctransaction_id=" % currentTransactionString % " WHERE rd_doctransaction_id=0;"); 0673 } 0674 0675 // Optimization of the current transaction 0676 IFOK(err) { 0677 SKGStringListList listTmp; 0678 err = executeSelectSqliteOrder("SELECT count(1) FROM doctransactionitem where rd_doctransaction_id=" % currentTransactionString, listTmp); 0679 IFOK(err) { 0680 int nbItem = SKGServices::stringToInt(listTmp.at(1).at(0)); 0681 if (nbItem == 0) { 0682 // Optimization is needed 0683 // Get non hidden messages 0684 SKGMessageList messages; 0685 getMessages(getCurrentTransaction(), messages, false); 0686 0687 // Delete current transaction 0688 err = executeSqliteOrder("DELETE FROM doctransaction WHERE id=" % currentTransactionString); 0689 0690 int nb = messages.count(); 0691 for (int i = 0; i < nb; ++i) { 0692 m_unTransactionnalMessages.push_back(messages.at(i)); 0693 } 0694 } 0695 } 0696 } 0697 0698 // Optimization 2: remove duplicate orders 0699 IFOK(err) { 0700 QString wc = "DELETE FROM doctransactionitem WHERE id IN " 0701 "(SELECT a.id FROM doctransactionitem a INDEXED BY idx_doctransactionitem_optimization, doctransactionitem b INDEXED BY idx_doctransactionitem_optimization " 0702 "WHERE a.rd_doctransaction_id=" % currentTransactionString % " AND b.rd_doctransaction_id=" % currentTransactionString % 0703 " AND a.i_object_id=b.i_object_id AND a.t_object_table=b.t_object_table AND b.t_action=a.t_action AND b.t_sqlorder=a.t_sqlorder AND a.id>b.id );"; 0704 err = executeSqliteOrder(wc); 0705 } 0706 0707 // Get current transaction information to be able to emit envent in case of SKG_UNDO_MAX_DEPTH=0 0708 IFOK(err) { 0709 err = this->getDistinctValues(QStringLiteral("doctransactionitem"), 0710 QStringLiteral("t_object_table"), 0711 "rd_doctransaction_id=" % currentTransactionString, 0712 listModifiedTables); 0713 } 0714 0715 // Remove oldest transaction 0716 IFOK(err) { 0717 QString maxdepthstring = getParameter(QStringLiteral("SKG_UNDO_MAX_DEPTH")); 0718 if (maxdepthstring.isEmpty()) { 0719 maxdepthstring = QStringLiteral("-1"); 0720 } 0721 int maxdepth = SKGServices::stringToInt(maxdepthstring); 0722 if (maxdepth >= 0) { 0723 err = executeSqliteOrder("delete from doctransaction where id in (select id from doctransaction limit max(0,((select count(1) from doctransaction)-(" % maxdepthstring % "))))"); 0724 } 0725 } 0726 0727 // Remove SKGDocument::REDO transactions if we are not in a undo / redo transaction 0728 if (d->m_inundoRedoTransaction == 0) { 0729 int i = 0; 0730 while (((i = getTransactionToProcess(SKGDocument::REDO)) != 0) && !err) { 0731 err = executeSqliteOrder("delete from doctransaction where id=" % SKGServices::intToString(i)); 0732 } 0733 } 0734 0735 // Commit the transaction 0736 IFOK(err) { 0737 err = executeSqliteOrder(QStringLiteral("COMMIT;")); 0738 } 0739 } 0740 0741 // clean cache sql (must be done before event emit) 0742 d->m_cacheSql->clear(); 0743 0744 if (!succeedded || err) { 0745 // Rollback the transaction 0746 SKGError err2 = executeSqliteOrder(QStringLiteral("ROLLBACK;")); 0747 // delete the transaction 0748 IFOKDO(err2, executeSqliteOrder("delete from doctransaction where id=" % currentTransactionString)) 0749 0750 if (err2) { 0751 err.addError(err2.getReturnCode(), err2.getMessage()); 0752 } 0753 } else { 0754 // For better performance, events are submitted only for the first recursive undo 0755 if (Q_UNLIKELY(d->m_inundoRedoTransaction <= 1)) { 0756 // Is it a light transaction? 0757 bool lightTransaction = (getCachedValue(QStringLiteral("SKG_REFRESH_VIEW")) != QStringLiteral("Y")); 0758 0759 // Emit modification events 0760 QStringList tablesRefreshed; 0761 tablesRefreshed.reserve(listModifiedTables.count()); 0762 for (const auto& table : qAsConst(listModifiedTables)) { 0763 Q_EMIT tableModified(table, getCurrentTransaction(), lightTransaction); 0764 tablesRefreshed.push_back(table); 0765 } 0766 0767 // Remove temporary transaction if needed 0768 IFOKDO(err, executeSqliteOrder(QStringLiteral("delete from doctransaction where t_name LIKE '#INTERNAL#%';"))) 0769 0770 Q_EMIT tableModified(QStringLiteral("doctransaction"), getCurrentTransaction(), lightTransaction); 0771 Q_EMIT tableModified(QStringLiteral("doctransactionitem"), getCurrentTransaction(), lightTransaction); 0772 0773 // WARNING: list is modified during treatement 0774 for (int i = 0; !err && i < listModifiedTables.count(); ++i) { 0775 QString table = listModifiedTables.at(i); 0776 QStringList toAdd = getImpactedViews(table); 0777 int nbToAdd = toAdd.count(); 0778 for (int j = 0; !err && j < nbToAdd; ++j) { 0779 const QString& toAddTable = toAdd.at(j); 0780 if (!listModifiedTables.contains(toAddTable)) { 0781 // Compute materialized view of modified table 0782 if (!lightTransaction) { 0783 err = computeMaterializedViews(toAddTable); 0784 } 0785 listModifiedTables.push_back(toAddTable); 0786 } 0787 } 0788 } 0789 0790 // Emit events 0791 for (int i = tablesRefreshed.count(); i < listModifiedTables.count(); ++i) { 0792 Q_EMIT tableModified(listModifiedTables.at(i), 0, lightTransaction); 0793 } 0794 0795 Q_EMIT transactionSuccessfullyEnded(getCurrentTransaction()); 0796 } 0797 } 0798 0799 // clean cache 0800 d->m_cache.clear(); 0801 0802 d->m_currentTransaction = 0; 0803 0804 if (Q_LIKELY(qobject_cast<QGuiApplication*>(qApp) != nullptr)) { // clazy:excludeall=unneeded-cast 0805 QApplication::restoreOverrideCursor(); 0806 } 0807 } 0808 } 0809 0810 IFOK(err) { 0811 err = errOverwritten; 0812 } 0813 return err; 0814 } 0815 0816 SKGError SKGDocument::removeAllTransactions() 0817 { 0818 SKGError err; 0819 SKGTRACEINFUNCRC(10, err) 0820 // Check if a transaction is still opened 0821 err = checkExistingTransaction(); 0822 IFOK(err) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Remove of transactions is forbidden inside a transaction")); 0823 else { 0824 err = SKGDocument::beginTransaction(QStringLiteral("#INTERNAL#")); 0825 IFOKDO(err, executeSqliteOrder(QStringLiteral("delete from doctransaction"))) 0826 SKGENDTRANSACTION(this, err) 0827 0828 // Force the save 0829 d->m_lastSavedTransaction = -1; 0830 } 0831 return err; 0832 } 0833 0834 SKGError SKGDocument::computeMaterializedViews(const QString& iTable) const 0835 { 0836 SKGError err; 0837 SKGTRACEINFUNCRC(5, err) 0838 0839 // Compute additional where clause 0840 QStringList tables; 0841 if (d->m_MaterializedViews.contains(iTable)) { 0842 tables = d->m_MaterializedViews[iTable]; 0843 } else { 0844 QString wc; 0845 if (!iTable.isEmpty()) { 0846 QString t = iTable; 0847 if (t.startsWith(QLatin1String("v_"))) { 0848 t.replace(0, 2, QStringLiteral("vm_")); 0849 } 0850 wc = " AND name='" % t % '\''; 0851 } 0852 0853 // Get list of materialized table 0854 err = getDistinctValues(QStringLiteral("sqlite_master"), QStringLiteral("name"), "type='table' AND name LIKE 'vm_%' " % wc, tables); 0855 d->m_MaterializedViews[iTable] = tables; 0856 } 0857 0858 // Refresh tables 0859 int nb = tables.count(); 0860 for (int i = 0; !err && i < nb; ++i) { 0861 const QString& table = tables.at(i); 0862 QString view = table; 0863 view.replace(0, 3, QStringLiteral("v_")); 0864 0865 // Remove previous table 0866 { 0867 SKGTRACEINRC(5, "SKGDocument::computeMaterializedViews-drop-" % table, err) 0868 err = executeSqliteOrder("DROP TABLE IF EXISTS " % table); 0869 } 0870 { 0871 // Recreate table 0872 SKGTRACEINRC(5, "SKGDocument::computeMaterializedViews-create-" % table, err) 0873 IFOKDO(err, executeSqliteOrder("CREATE TABLE " % table % " AS SELECT * FROM " % view)) 0874 } 0875 } 0876 0877 return err; 0878 } 0879 0880 SKGError SKGDocument::sendMessage(const QString& iMessage, MessageType iMessageType, const QString& iAction) 0881 { 0882 SKGError err; 0883 SKGTRACEINFUNCRC(10, err) 0884 // Associate message with transaction 0885 if (!checkExistingTransaction()) { 0886 SKGObjectBase msg(this, QStringLiteral("doctransactionmsg")); 0887 err = msg.setAttribute(QStringLiteral("rd_doctransaction_id"), SKGServices::intToString(getCurrentTransaction())); 0888 IFOKDO(err, msg.setAttribute(QStringLiteral("t_message"), iMessage)) 0889 IFOKDO(err, msg.setAttribute(QStringLiteral("t_type"), iMessageType == SKGDocument::Positive ? QStringLiteral("P") : 0890 iMessageType == SKGDocument::Information ? QStringLiteral("I") : 0891 iMessageType == SKGDocument::Warning ? QStringLiteral("W") : 0892 iMessageType == SKGDocument::Error ? QStringLiteral("E") : QStringLiteral("H"))) 0893 IFOKDO(err, msg.save()) 0894 } 0895 0896 if (checkExistingTransaction() || !iAction.isEmpty()) { 0897 // Addition message in global variable in case of no transaction opened 0898 bool found = false; 0899 for (const auto& m : qAsConst(m_unTransactionnalMessages)) { 0900 if (m.Text == iMessage) { 0901 found = true; 0902 } 0903 } 0904 if (iMessageType != SKGDocument::Hidden && !found) { 0905 SKGMessage m; 0906 m.Text = iMessage; 0907 m.Type = iMessageType; 0908 m.Action = iAction; 0909 m_unTransactionnalMessages.push_back(m); 0910 } 0911 } 0912 return err; 0913 } 0914 0915 SKGError SKGDocument::removeMessages(int iIdTransaction) 0916 { 0917 SKGError err; 0918 SKGTRACEINFUNCRC(10, err) 0919 0920 if (!checkExistingTransaction()) { 0921 err = executeSqliteOrder("DELETE FROM doctransactionmsg WHERE rd_doctransaction_id=" % SKGServices::intToString(iIdTransaction)); 0922 } 0923 0924 m_unTransactionnalMessages.clear(); 0925 return err; 0926 } 0927 0928 SKGError SKGDocument::getMessages(int iIdTransaction, SKGMessageList& oMessages, bool iAll) 0929 { 0930 SKGError err; 0931 SKGTRACEINFUNCRC(10, err) 0932 oMessages = m_unTransactionnalMessages; 0933 0934 SKGStringListList listTmp; 0935 err = executeSelectSqliteOrder( 0936 QStringLiteral("SELECT t_message, t_type FROM doctransactionmsg WHERE ") % 0937 (iAll ? "" : "t_type<>'H' AND ") % 0938 "rd_doctransaction_id=" % 0939 SKGServices::intToString(iIdTransaction) % 0940 " ORDER BY id ASC", 0941 listTmp); 0942 0943 int nb = listTmp.count(); 0944 for (int i = 1; !err && i < nb ; ++i) { 0945 QString msg = listTmp.at(i).at(0); 0946 QString type = listTmp.at(i).at(1); 0947 bool found = false; 0948 for (const auto& m : qAsConst(m_unTransactionnalMessages)) { 0949 if (m.Text == msg) { 0950 found = true; 0951 } 0952 } 0953 if (!found) { 0954 SKGMessage m; 0955 m.Text = msg; 0956 m.Type = type == QStringLiteral("P") ? SKGDocument::Positive : type == QStringLiteral("I") ? SKGDocument::Information : type == QStringLiteral("W") ? SKGDocument::Warning : type == QStringLiteral("E") ? SKGDocument::Error : SKGDocument::Hidden; 0957 oMessages.push_back(m); 0958 } 0959 } 0960 0961 m_unTransactionnalMessages.clear(); 0962 return err; 0963 } 0964 0965 SKGError SKGDocument::getModifications(int iIdTransaction, SKGObjectModificationList& oModifications) const 0966 { 0967 SKGError err; 0968 SKGTRACEINFUNCRC(10, err) 0969 oModifications.clear(); 0970 0971 SKGStringListList listTmp; 0972 err = executeSelectSqliteOrder( 0973 "SELECT i_object_id,t_object_table,t_action FROM doctransactionitem WHERE rd_doctransaction_id=" % 0974 SKGServices::intToString(iIdTransaction) % 0975 " ORDER BY id ASC", 0976 listTmp); 0977 int nb = listTmp.count(); 0978 for (int i = 1; !err && i < nb ; ++i) { 0979 SKGObjectModification mod; 0980 mod.id = SKGServices::stringToInt(listTmp.at(i).at(0)); 0981 mod.table = listTmp.at(i).at(1); 0982 QString type = listTmp.at(i).at(2); 0983 mod.type = (type == QStringLiteral("D") ? I : (type == QStringLiteral("I") ? D : U)); // Normal because in database we have to sql order to go back. 0984 mod.uuid = listTmp.at(i).at(0) % '-' % mod.table; 0985 0986 oModifications.push_back(mod); 0987 } 0988 return err; 0989 } 0990 0991 QStringList SKGDocument::getImpactedViews(const QString& iTable) const 0992 { 0993 SKGTRACEINFUNC(10) 0994 if (Q_UNLIKELY(d->m_ImpactedViews.isEmpty())) { 0995 // Get list of tables and views 0996 QStringList tables; 0997 SKGStringListList result; 0998 executeSelectSqliteOrder(QStringLiteral("SELECT tbl_name FROM sqlite_master WHERE tbl_name NOT LIKE '%_delete' AND type IN ('table', 'view')"), result); 0999 int nb = result.count(); 1000 tables.reserve(nb); 1001 for (int i = 1; i < nb; ++i) { 1002 tables.push_back(result.at(i).at(0)); 1003 } 1004 1005 // First computation 1006 executeSelectSqliteOrder(QStringLiteral("SELECT tbl_name, sql FROM sqlite_master WHERE tbl_name NOT LIKE '%_delete' AND type='view'"), result); 1007 nb = result.count(); 1008 for (int i = 1; i < nb; ++i) { 1009 const QStringList& line = result.at(i); 1010 const QString& name = line.at(0); 1011 const QString& sql = line.at(1); 1012 1013 QStringList words = SKGServices::splitCSVLine(sql, ' ', false); 1014 words.push_back(QStringLiteral("parameters")); 1015 int nbWords = words.count(); 1016 for (int j = 0; j < nbWords; ++j) { 1017 QString word = words.at(j); 1018 word = word.remove(','); 1019 if (word.startsWith(QLatin1String("vm_"))) { 1020 word.replace(0, 3, QStringLiteral("v_")); 1021 } 1022 if (word != name && tables.contains(word, Qt::CaseInsensitive)) { 1023 QStringList l = d->m_ImpactedViews.value(word); 1024 if (!l.contains(name)) { 1025 l.push_back(name); 1026 } 1027 d->m_ImpactedViews[word] = l; 1028 } 1029 } 1030 } 1031 1032 // Now, we have some thing like this 1033 // d->m_ImpactedViews[A]={ B, C, D} 1034 // d->m_ImpactedViews[B]={ E, F} 1035 // We must build d->m_ImpactedViews[A]={ B, C, D, E, F} 1036 QStringList keys = d->m_ImpactedViews.keys(); 1037 for (const auto& k : qAsConst(keys)) { 1038 QStringList l = d->m_ImpactedViews.value(k); 1039 for (int i = 0; i < l.count(); ++i) { // Warning: the size of l will change in the loop 1040 QString item = l.at(i); 1041 if (d->m_ImpactedViews.contains(item)) { 1042 // No qAsConst here, item is already const 1043 for (const auto& name : d->m_ImpactedViews.value(item)) { 1044 if (!l.contains(name)) { 1045 l.push_back(name); 1046 } 1047 } 1048 } 1049 } 1050 d->m_ImpactedViews[k] = l; 1051 } 1052 } 1053 return d->m_ImpactedViews.value(iTable); 1054 } 1055 1056 SKGError SKGDocument::groupTransactions(int iFrom, int iTo) 1057 { 1058 SKGError err; 1059 SKGTRACEINFUNCRC(5, err) 1060 1061 ++d->m_inundoRedoTransaction; // It is a kind of undo redo 1062 1063 // Check if a transaction is still opened 1064 err = checkExistingTransaction(); 1065 IFOK(err) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Creation of a group of transactions is forbidden inside a transaction")); 1066 else { 1067 int iidMaster = qMax(iFrom, iTo); 1068 QString smin = SKGServices::intToString(qMin(iFrom, iTo)); 1069 QString smax = SKGServices::intToString(iidMaster); 1070 1071 // Get transaction 1072 SKGStringListList transactions; 1073 err = executeSelectSqliteOrder( 1074 QStringLiteral("SELECT id, t_name, t_mode, i_parent FROM doctransaction WHERE id BETWEEN ") % 1075 smin % " AND " % 1076 smax % " ORDER BY id ASC", 1077 transactions); 1078 1079 // Check and get main parameter for the group 1080 int nb = transactions.count(); 1081 QString transactionMode; 1082 QString communParent; 1083 QString name; 1084 for (int i = 1; !err && i < nb; ++i) { // We forget header 1085 const QStringList& transaction = transactions.at(i); 1086 const QString& mode = transaction.at(2); 1087 if (!name.isEmpty()) { 1088 name += ','; 1089 } 1090 name += transaction.at(1); 1091 1092 if (!transactionMode.isEmpty() && mode != transactionMode) { 1093 err = SKGError(ERR_INVALIDARG, QStringLiteral("Undo and Redo transactions cannot be grouped")); 1094 } else { 1095 transactionMode = mode; 1096 } 1097 1098 if (i == 1) { 1099 communParent = transaction.at(3); 1100 } 1101 } 1102 1103 // Group 1104 IFOK(err) { 1105 err = SKGDocument::beginTransaction(QStringLiteral("#INTERNAL#")); 1106 // Group items 1107 IFOKDO(err, executeSqliteOrder( 1108 QStringLiteral("UPDATE doctransactionitem set rd_doctransaction_id=") % 1109 smax % 1110 " where rd_doctransaction_id BETWEEN " % 1111 smin % " AND " % smax)) 1112 IFOKDO(err, executeSqliteOrder( 1113 QStringLiteral("UPDATE doctransaction set i_parent=") % 1114 communParent % 1115 ", t_name='" % SKGServices::stringToSqlString(name) % 1116 "' where id=" % smax)) 1117 1118 IFOKDO(err, executeSqliteOrder( 1119 QStringLiteral("DELETE FROM doctransaction WHERE id BETWEEN ") % 1120 smin % " AND " % SKGServices::intToString(qMax(iFrom, iTo) - 1))) 1121 1122 SKGENDTRANSACTION(this, err) 1123 } 1124 } 1125 1126 --d->m_inundoRedoTransaction; 1127 return err; 1128 } 1129 1130 SKGError SKGDocument::undoRedoTransaction(UndoRedoMode iMode) 1131 { 1132 SKGError err; 1133 SKGTRACEINFUNCRC(5, err) 1134 // Check if a transaction is still opened 1135 err = checkExistingTransaction(); 1136 IFOK(err) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Undo / Redo is forbidden inside a transaction")); 1137 else { 1138 if (iMode == SKGDocument::UNDOLASTSAVE) { 1139 // Create group 1140 SKGStringListList transactions; 1141 err = executeSelectSqliteOrder( 1142 QStringLiteral("SELECT id, t_savestep FROM doctransaction WHERE t_mode='U' ORDER BY id DESC"), 1143 transactions); 1144 int nb = transactions.count(); 1145 int min = 0; 1146 int max = 0; 1147 for (int i = 1; !err && i < nb; ++i) { 1148 const QStringList& transaction = transactions.at(i); 1149 if (i == 1) { 1150 max = SKGServices::stringToInt(transaction.at(0)); 1151 } 1152 if (i != 1 && transaction.at(1) == QStringLiteral("Y")) { 1153 break; 1154 } 1155 min = SKGServices::stringToInt(transaction.at(0)); 1156 } 1157 if (min == 0) { 1158 min = max; 1159 } 1160 if (!err && min != max && min != 0) { 1161 err = groupTransactions(min, max); 1162 } 1163 } else { 1164 err = SKGError(); // To ignore error generated by checkExistingTransaction. 1165 } 1166 1167 // Get ID of the transaction to undo 1168 IFOK(err) { 1169 QString name; 1170 bool saveStep = false; 1171 QDateTime date; 1172 bool refreshViews; 1173 int id = getTransactionToProcess(iMode, &name, &saveStep, &date, &refreshViews); 1174 if (id == 0) { 1175 // No transaction found ==> generate an error 1176 err = SKGError(ERR_INVALIDARG, QStringLiteral("No transaction found. Undo / Redo impossible.")); 1177 } else { 1178 // Undo transaction 1179 SKGTRACEL(5) << "Undoing transaction [" << id << "]- [" << name << "]…" << SKGENDL; 1180 SKGStringListList listSqlOrder; 1181 err = executeSelectSqliteOrder( 1182 "SELECT t_sqlorder FROM doctransactionitem WHERE rd_doctransaction_id=" % 1183 SKGServices::intToString(id) % 1184 " ORDER BY id DESC", 1185 listSqlOrder); 1186 IFOK(err) { 1187 int nb = listSqlOrder.count(); 1188 err = SKGDocument::beginTransaction(name, nb + 3, date, refreshViews); 1189 IFOK(err) { 1190 ++d->m_inundoRedoTransaction; // Because we will be in a undo/redo transaction 1191 // Normal the first element is ignored because it is the header 1192 for (int i = 1; !err && i < nb ; ++i) { 1193 err = executeSqliteOrder(listSqlOrder.at(i).at(0)); 1194 1195 IFOKDO(err, stepForward(i)) 1196 } 1197 1198 IFOK(err) { 1199 // Set the NEW transaction in redo mode 1200 int lastredo = getTransactionToProcess((iMode == SKGDocument::UNDO || iMode == SKGDocument::UNDOLASTSAVE ? SKGDocument::REDO : SKGDocument::UNDO)); 1201 int newredo = getTransactionToProcess(iMode); 1202 IFOKDO(err, executeSqliteOrder( 1203 QStringLiteral("UPDATE doctransaction set t_mode=") % 1204 (iMode == SKGDocument::UNDO || iMode == SKGDocument::UNDOLASTSAVE ? QStringLiteral("'R'") : QStringLiteral("'U'")) % 1205 ", i_parent=" % 1206 SKGServices::intToString(lastredo) % 1207 " where id=" % SKGServices::intToString(newredo))) 1208 IFOKDO(err, stepForward(nb)) 1209 1210 // Move messages from previous transaction to new one 1211 IFOKDO(err, executeSqliteOrder( 1212 "UPDATE doctransactionmsg set rd_doctransaction_id=" % 1213 SKGServices::intToString(getCurrentTransaction()) % 1214 " where rd_doctransaction_id=" % 1215 SKGServices::intToString(id))) 1216 IFOKDO(err, stepForward(nb + 1)) 1217 1218 // delete treated transaction 1219 IFOKDO(err, executeSqliteOrder( 1220 "DELETE from doctransaction where id=" 1221 % SKGServices::intToString(id))) 1222 IFOKDO(err, stepForward(nb + 2)) 1223 1224 // Check that new transaction has exactly the same number of item 1225 /* IFOK (err) { 1226 SKGStringListList listSqlOrder; 1227 err=executeSelectSqliteOrder( 1228 "SELECT count(1) FROM doctransactionitem WHERE rd_doctransaction_id=" % 1229 SKGServices::intToString(getCurrentTransaction()), 1230 listSqlOrder); 1231 if (!err && SKGServices::stringToInt(listSqlOrder.at(1).at(0))!=nb-1) { 1232 err=SKGError(ERR_ABORT, i18nc("Error message", "Invalid number of item after undo/redo. Expected (%1) != Result (%2)",nb-1,listSqlOrder.at(1).at(0))); 1233 } 1234 }*/ 1235 1236 IFOKDO(err, stepForward(nb + 3)) 1237 } 1238 1239 SKGENDTRANSACTION(this, err) 1240 --d->m_inundoRedoTransaction; // We left the undo / redo transaction 1241 } 1242 } 1243 } 1244 } 1245 } 1246 1247 return err; 1248 } 1249 1250 int SKGDocument::getDepthTransaction() const 1251 { 1252 return d->m_nbStepForTransaction.size(); 1253 } 1254 1255 int SKGDocument::getNbTransaction(UndoRedoMode iMode) const 1256 { 1257 SKGTRACEINFUNC(10) 1258 int output = 0; 1259 if (Q_LIKELY(getMainDatabase())) { 1260 QString sqlorder = QStringLiteral("select count(1) from doctransaction where t_mode='"); 1261 sqlorder += (iMode == SKGDocument::UNDO || iMode == SKGDocument::UNDOLASTSAVE ? QStringLiteral("U") : QStringLiteral("R")); 1262 sqlorder += '\''; 1263 QSqlQuery query = getMainDatabase()->exec(sqlorder); 1264 if (query.next()) { 1265 output = query.value(0).toInt(); 1266 } 1267 } 1268 return output; 1269 } 1270 1271 int SKGDocument::getTransactionToProcess(UndoRedoMode iMode, QString* oName, bool* oSaveStep, QDateTime* oDate, bool* oRefreshViews) const 1272 { 1273 SKGTRACEINFUNC(10) 1274 // initialisation 1275 int output = 0; 1276 if (oName != nullptr) { 1277 *oName = QLatin1String(""); 1278 } 1279 if (Q_LIKELY(getMainDatabase())) { 1280 QString sqlorder = QStringLiteral("select A.id , A.t_name, A.t_savestep, A.d_date, A.t_refreshviews from doctransaction A where " 1281 "NOT EXISTS(select 1 from doctransaction B where B.i_parent=A.id) " 1282 "and A.t_mode='"); 1283 sqlorder += (iMode == SKGDocument::UNDO || iMode == SKGDocument::UNDOLASTSAVE ? QStringLiteral("U") : QStringLiteral("R")); 1284 sqlorder += '\''; 1285 QSqlQuery query = getMainDatabase()->exec(sqlorder); 1286 if (query.next()) { 1287 output = query.value(0).toInt(); 1288 if (oName != nullptr) { 1289 *oName = query.value(1).toString(); 1290 } 1291 if (oSaveStep != nullptr) { 1292 *oSaveStep = (query.value(2).toString() == QStringLiteral("Y")); 1293 } 1294 if (oDate != nullptr) { 1295 *oDate = SKGServices::stringToTime(query.value(3).toString()); 1296 } 1297 if (oRefreshViews != nullptr) { 1298 *oRefreshViews = (query.value(4).toString() == QStringLiteral("Y")); 1299 } 1300 } 1301 } 1302 return output; 1303 } 1304 1305 int SKGDocument::getCurrentTransaction() const 1306 { 1307 SKGTRACEINFUNC(10) 1308 return d->m_currentTransaction; 1309 } 1310 1311 QString SKGDocument::getPassword() const 1312 { 1313 if (!d->m_password_got) { 1314 d->m_password = getParameter(QStringLiteral("SKG_PASSWORD")); 1315 d->m_password_got = true; 1316 } 1317 return d->m_password; 1318 } 1319 1320 SKGError SKGDocument::changePassword(const QString& iNewPassword) 1321 { 1322 SKGError err; 1323 SKGTRACEINFUNCRC(10, err) 1324 IFOK(checkExistingTransaction()) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Something went wrong with SQL transactions", "Change password is forbidden inside a transaction")); 1325 else { 1326 IFOKDO(err, executeSqliteOrder("PRAGMA REKEY = '" % SKGServices::stringToSqlString(iNewPassword.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : iNewPassword) % "'")) 1327 IFOKDO(err, beginTransaction(QStringLiteral("#INTERNAL#"), 0, QDateTime::currentDateTime(), false)) 1328 IFOKDO(err, setParameter(QStringLiteral("SKG_PASSWORD"), iNewPassword)) 1329 IFOKDO(err, setParameter(QStringLiteral("SKG_PASSWORD_LASTUPDATE"), SKGServices::dateToSqlString(QDate::currentDate()))) 1330 IFOKDO(err, sendMessage(iNewPassword.isEmpty() ? i18nc("Inform the user that the password protection was removed", "The document password has been removed.") : 1331 i18nc("Inform the user that the password was successfully changed", "The document password has been modified."), SKGDocument::Positive)) 1332 SKGENDTRANSACTION(this, err) 1333 1334 // Force the save 1335 IFOK(err) { 1336 d->m_lastSavedTransaction = -1; 1337 1338 d->m_password = iNewPassword; 1339 d->m_password_got = true; 1340 1341 // Close all thread connection 1342 auto conNameMainConnection = getMainDatabase()->connectionName(); 1343 const auto conNames = QSqlDatabase::connectionNames(); 1344 for (const auto& conName : conNames) { 1345 if (conName.startsWith(conNameMainConnection % "_")) { 1346 /* NO NEED 1347 { 1348 auto con = QSqlDatabase::database(conName, false); 1349 con.close(); 1350 }*/ 1351 QSqlDatabase::removeDatabase(conName); 1352 } 1353 } 1354 } 1355 } 1356 return err; 1357 } 1358 1359 SKGError SKGDocument::setLanguage(const QString& iLanguage) 1360 { 1361 SKGError err; 1362 SKGTRACEINFUNCRC(5, err) 1363 QString previousLanguage = getParameter(QStringLiteral("SKG_LANGUAGE")); 1364 if (previousLanguage != iLanguage) { 1365 // Save language into the document 1366 IFOKDO(err, beginTransaction(QStringLiteral("#INTERNAL#"), 0, QDateTime::currentDateTime(), false)) 1367 IFOKDO(err, setParameter(QStringLiteral("SKG_LANGUAGE"), iLanguage)) 1368 1369 // Migrate view for new language 1370 IFOKDO(err, refreshViewsIndexesAndTriggers()) 1371 1372 // close temporary transaction 1373 SKGENDTRANSACTION(this, err) 1374 } 1375 return err; 1376 } 1377 1378 SKGError SKGDocument::initialize() 1379 { 1380 SKGError err; 1381 SKGTRACEINFUNCRC(5, err) 1382 err = load(QLatin1String(""), QLatin1String("")); 1383 return err; 1384 } 1385 1386 SKGError SKGDocument::recover(const QString& iName, const QString& iPassword, QString& oRecoveredFile) 1387 { 1388 SKGError err; 1389 SKGTRACEINFUNCRC(5, err) 1390 SKGTRACEL(10) << "Input parameter [name]=[" << iName << ']' << SKGENDL; 1391 1392 QString sqliteFile = QString(iName % "_recovered.sqlite").replace(QStringLiteral(".skg_"), QStringLiteral("_")); 1393 oRecoveredFile = QString(iName % "_recovered.skg").replace(QStringLiteral(".skg_"), QStringLiteral("_")); 1394 bool mode; 1395 err = SKGServices::cryptFile(iName, sqliteFile, iPassword, false, getDocumentHeader(), mode); 1396 IFOK(err) { 1397 QFile(oRecoveredFile).remove(); 1398 QString cmd = "echo .dump | sqlcipher \"" % sqliteFile % "\" | sed -e 's/ROLLBACK; -- due to errors/COMMIT;/g' | sqlcipher \"" % oRecoveredFile % '"'; 1399 QProcess p; 1400 p.start(QStringLiteral("sh"), QStringList() << QStringLiteral("-c") << cmd); 1401 if (!p.waitForFinished(1000 * 60 * 2) || p.exitCode() != 0) { 1402 err.setReturnCode(ERR_FAIL).setMessage(i18nc("Error message", "The following command line failed with code %2:\n'%1'", cmd, p.exitCode())); 1403 } 1404 1405 // Try to load the recovered file 1406 IFOKDO(err, load(oRecoveredFile, QLatin1String(""))) 1407 IFOK(err) { 1408 SKGBEGINTRANSACTION(*this, i18nc("Noun", "Recovery"), err) 1409 IFOKDO(err, refreshViewsIndexesAndTriggers(true)) 1410 } 1411 IFOKDO(err, save()) 1412 1413 // Reset the current document 1414 initialize(); 1415 1416 // Clean useless file 1417 IFOK(err) { 1418 // We keep only the recovered 1419 QFile(sqliteFile).remove(); 1420 } else { 1421 // We keep the sqlite file in case of 1422 QFile(oRecoveredFile).remove(); 1423 err.addError(ERR_FAIL, i18nc("Error message", "Impossible to recover this file")); 1424 } 1425 } 1426 1427 return err; 1428 } 1429 1430 SKGError SKGDocument::load(const QString& iName, const QString& iPassword, bool iRestoreTmpFile, bool iForceReadOnly) 1431 { 1432 // Close previous document 1433 SKGError err; 1434 SKGTRACEINFUNCRC(5, err) 1435 SKGTRACEL(10) << "Input parameter [name]=[" << iName << ']' << SKGENDL; 1436 SKGTRACEL(10) << "Input parameter [iRestoreTmpFile]=[" << (iRestoreTmpFile ? "TRUE" : "FALSE") << ']' << SKGENDL; 1437 SKGTRACEL(10) << "Input parameter [iForceReadOnly]=[" << (iForceReadOnly ? "TRUE" : "FALSE") << ']' << SKGENDL; 1438 1439 d->m_lastSavedTransaction = -1; // To avoid double event emission 1440 d->m_modeSQLCipher = true; 1441 d->m_blockEmits = true; 1442 err = close(); 1443 d->m_blockEmits = false; 1444 IFOK(err) { 1445 if (!iName.isEmpty()) { 1446 // File exist 1447 QFileInfo fi(iName); 1448 d->m_modeReadOnly = iForceReadOnly || !fi.permission(QFile::WriteUser); 1449 1450 // Temporary file 1451 d->m_temporaryFile = SKGDocument::getTemporaryFile(iName, d->m_modeReadOnly); 1452 bool temporaryFileExisting = QFile(d->m_temporaryFile).exists(); 1453 SKGTRACEL(10) << "Temporary file: [" << d->m_temporaryFile << ']' << SKGENDL; 1454 SKGTRACEL(10) << "Temporary file existing: [" << (temporaryFileExisting ? "TRUE" : "FALSE") << ']' << SKGENDL; 1455 if (!iRestoreTmpFile || !temporaryFileExisting) { 1456 SKGTRACEL(10) << "Create the temporary file" << SKGENDL; 1457 QFile::remove(d->m_temporaryFile); // Must remove it to be able to copy 1458 err = SKGServices::cryptFile(iName, d->m_temporaryFile, iPassword, false, getDocumentHeader(), d->m_modeSQLCipher); 1459 } else { 1460 SKGTRACEL(10) << "The temporary file is existing, try a restore but we must check if the file is password protected first" << SKGENDL; 1461 // 249955: Check if password protected vvv 1462 // Temporary file will be loaded but first we must check if original document is password protected 1463 QString temporaryFile2 = d->m_temporaryFile % '2'; 1464 err = SKGServices::cryptFile(iName, temporaryFile2, iPassword, false, getDocumentHeader(), d->m_modeSQLCipher); 1465 1466 // Try an open to check if well descrypted 1467 IFOK(err) { 1468 QSqlDatabase tryOpen(QSqlDatabase::addDatabase(SQLDRIVERNAME, QStringLiteral("tryOpen"))); 1469 tryOpen.setDatabaseName(temporaryFile2); 1470 if (!tryOpen.open()) { 1471 // Set error message 1472 QSqlError sqlErr = tryOpen.lastError(); 1473 err = SKGError(SQLLITEERROR + sqlErr.nativeErrorCode().toInt(), sqlErr.text()); 1474 } 1475 if (d->m_modeSQLCipher) { 1476 IFOKDO(err, SKGServices::executeSqliteOrder(tryOpen, "PRAGMA KEY = '" % SKGServices::stringToSqlString(iPassword.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : iPassword) % "'")) 1477 IFKO(err) { 1478 SKGTRACEL(10) << "Wrong installation of sqlcipher (doesn't support encryption)" << SKGENDL; 1479 err = SKGError(ERR_ENCRYPTION, i18nc("Error message", "Wrong installation")); 1480 } 1481 1482 // Migrate to the last version of SQLCipher 1483 IFOKDO(err, SKGServices::executeSqliteOrder(tryOpen, QStringLiteral("PRAGMA cipher_migrate"))) 1484 1485 // Test the password 1486 IFOKDO(err, SKGServices::executeSqliteOrder(tryOpen, QStringLiteral("SELECT count(*) FROM sqlite_master"))) 1487 IFKO(err) { 1488 SKGTRACEL(10) << "Wrong password in restore mode" << SKGENDL; 1489 err = SKGError(ERR_ENCRYPTION, i18nc("Error message", "Wrong password")); 1490 } 1491 } 1492 IFOKDO(err, SKGServices::executeSqliteOrder(tryOpen, QStringLiteral("PRAGMA synchronous = OFF"))) 1493 } 1494 QSqlDatabase::removeDatabase(QStringLiteral("tryOpen")); 1495 QFile::remove(temporaryFile2); 1496 1497 // To avoid deletion of temporary file during next try 1498 IFKO(err) d->m_temporaryFile = QLatin1String(""); 1499 // 249955: Check if password protected ^^^ 1500 } 1501 1502 // Create file database 1503 IFOK(err) { 1504 d->m_currentDatabase = QSqlDatabase::addDatabase(SQLDRIVERNAME, d->m_databaseIdentifier); 1505 d->m_currentDatabase.setDatabaseName(d->m_temporaryFile); 1506 if (!d->m_currentDatabase.open()) { 1507 // Set error message 1508 QSqlError sqlErr = d->m_currentDatabase.lastError(); 1509 err = SKGError(SQLLITEERROR + sqlErr.nativeErrorCode().toInt(), sqlErr.text()); 1510 } 1511 1512 d->m_directAccessDb = true; 1513 if (QUrl::fromUserInput(iName).isLocalFile()) { 1514 d->m_currentFileName = iName; 1515 } 1516 } 1517 } else { 1518 // Temporary file 1519 d->m_temporaryFile = QDir::tempPath() % "/skg_" % QUuid::createUuid().toString() % ".skg"; 1520 1521 // Create memory database 1522 d->m_currentDatabase = QSqlDatabase::addDatabase(SQLDRIVERNAME, d->m_databaseIdentifier); 1523 d->m_currentDatabase.setConnectOptions(QStringLiteral("QSQLITE_OPEN_URI")); 1524 d->m_currentDatabase.setDatabaseName(QStringLiteral("file:") + d->m_databaseIdentifier + QStringLiteral("?mode=memory&cache=shared")); 1525 if (!d->m_currentDatabase.open()) { 1526 // Set error message 1527 QSqlError sqlErr = d->m_currentDatabase.lastError(); 1528 err = SKGError(SQLLITEERROR + sqlErr.nativeErrorCode().toInt(), sqlErr.text()); 1529 } 1530 1531 d->m_directAccessDb = false; 1532 } 1533 if (d->m_modeSQLCipher) { 1534 // This is an encrypted data base 1535 IFOKDO(err, executeSqliteOrder("PRAGMA KEY = '" % SKGServices::stringToSqlString(iPassword.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : iPassword) % "'")) 1536 IFKO(err) { 1537 SKGTRACEL(10) << "Wrong installation of sqlcipher (doesn't support encryption)" << SKGENDL; 1538 err = SKGError(ERR_ENCRYPTION, i18nc("Error message", "Wrong installation")); 1539 } 1540 1541 // Migrate to the last version of SQLCipher 1542 IFOKDO(err, executeSqliteOrder(QStringLiteral("PRAGMA cipher_migrate"))) 1543 1544 // Test the password 1545 IFOKDO(err, executeSqliteOrder(QStringLiteral("SELECT count(*) FROM sqlite_master"))) 1546 IFKO(err) { 1547 SKGTRACEL(10) << "Wrong password on temporary file" << SKGENDL; 1548 err = SKGError(ERR_ENCRYPTION, i18nc("Error message", "Wrong password")); 1549 } 1550 } 1551 1552 // Check if the database is correct 1553 IFOK(err) { 1554 IFOKDO(err, executeSqliteOrder(QStringLiteral("PRAGMA journal_mode=MEMORY"))) 1555 IFKO(err) { 1556 err.addError(ERR_CORRUPTION, i18nc("Error message", "Oups, this file seems to be corrupted")); 1557 } 1558 } 1559 1560 // Optimization 1561 QStringList optimization; 1562 optimization << QStringLiteral("PRAGMA case_sensitive_like=true") 1563 << QStringLiteral("PRAGMA journal_mode=MEMORY") 1564 << QStringLiteral("PRAGMA temp_store=MEMORY") 1565 // << QStringLiteral("PRAGMA locking_mode=EXCLUSIVE") 1566 << QStringLiteral("PRAGMA synchronous = OFF") 1567 << QStringLiteral("PRAGMA legacy_alter_table=ON") // For migration on sqlite >=3.25 (see https://sqlite.org/lang_altertable.html) 1568 << QStringLiteral("PRAGMA recursive_triggers=true"); 1569 IFOKDO(err, executeSqliteOrders(optimization)) 1570 1571 // Add custom sqlite functions 1572 IFOKDO(err, addSqliteAddon(getMainDatabase())) 1573 1574 if (!d->m_directAccessDb) { 1575 // Create parameter and undo redo table 1576 /** 1577 * This constant is used to initialized the data model (table creation) 1578 */ 1579 QStringList InitialDataModel; 1580 1581 // ================================================================== 1582 // Table parameters 1583 InitialDataModel << QStringLiteral("CREATE TABLE parameters " 1584 "(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," 1585 "t_uuid_parent TEXT NOT NULL DEFAULT ''," 1586 "t_name TEXT NOT NULL," 1587 "t_value TEXT NOT NULL DEFAULT ''," 1588 "b_blob BLOB," 1589 "d_lastmodifdate DATE NOT NULL DEFAULT CURRENT_TIMESTAMP," 1590 "i_tmp INTEGER NOT NULL DEFAULT 0" 1591 ")") 1592 1593 // ================================================================== 1594 // Table node 1595 << "CREATE TABLE node (" 1596 "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," 1597 "t_name TEXT NOT NULL DEFAULT '' CHECK (t_name NOT LIKE '%" % OBJECTSEPARATOR % "%')," 1598 "t_fullname TEXT," 1599 "t_icon TEXT DEFAULT ''," 1600 "f_sortorder FLOAT," 1601 "t_autostart VARCHAR(1) DEFAULT 'N' CHECK (t_autostart IN ('Y', 'N'))," 1602 "t_data TEXT," 1603 "rd_node_id INT CONSTRAINT fk_id REFERENCES node(id) ON DELETE CASCADE)" 1604 1605 // ================================================================== 1606 // Table doctransaction 1607 << QStringLiteral("CREATE TABLE doctransaction (" 1608 "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," 1609 "t_name TEXT NOT NULL," 1610 "t_mode VARCHAR(1) DEFAULT 'U' CHECK (t_mode IN ('U', 'R'))," 1611 "d_date DATE NOT NULL," 1612 "t_savestep VARCHAR(1) DEFAULT 'N' CHECK (t_savestep IN ('Y', 'N'))," 1613 "t_refreshviews VARCHAR(1) DEFAULT 'Y' CHECK (t_refreshviews IN ('Y', 'N'))," 1614 "i_parent INTEGER)") 1615 1616 // ================================================================== 1617 // Table doctransactionitem 1618 << QStringLiteral("CREATE TABLE doctransactionitem (" 1619 "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " 1620 "rd_doctransaction_id INTEGER NOT NULL," 1621 "i_object_id INTEGER NOT NULL," 1622 "t_object_table TEXT NOT NULL," 1623 "t_action VARCHAR(1) DEFAULT 'I' CHECK (t_action IN ('I', 'U', 'D'))," 1624 "t_sqlorder TEXT NOT NULL DEFAULT '')") 1625 1626 << QStringLiteral("CREATE TABLE doctransactionmsg (" 1627 "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " 1628 "rd_doctransaction_id INTEGER NOT NULL," 1629 "t_message TEXT NOT NULL DEFAULT ''," 1630 "t_type VARCHAR(1) DEFAULT 'I' CHECK (t_type IN ('P', 'I', 'W', 'E', 'H')))"); // Positive, Information, Warning, Error, Hidden 1631 1632 IFOKDO(err, executeSqliteOrders(InitialDataModel)) 1633 IFOKDO(err, SKGDocument::refreshViewsIndexesAndTriggers()) 1634 } 1635 } 1636 1637 // migrate 1638 IFOK(err) { 1639 bool mig = false; 1640 err = migrate(mig); 1641 1642 if (!err && getParameter(QStringLiteral("SKG_DATABASE_TYPE")) != SQLDRIVERNAME && !getPassword().isEmpty()) { 1643 err = sendMessage(i18nc("Information message", "This document is protected by a password but the database is still in SQLite mode.\nDo you know that the SQLCipher mode is more secured because even the temporary file is encrypted?"), SKGDocument::Warning, QStringLiteral("skg://migrate_sqlcipher")); 1644 } 1645 if (!err && getParameter(QStringLiteral("SKG_PASSWORD_LASTUPDATE")) == QLatin1String("") && !getPassword().isEmpty()) { 1646 err = sendMessage(i18nc("Information message", "A security hole has been detected and corrected on this version of the application. We strongly encourage you to change your password."), SKGDocument::Warning, QStringLiteral("skg://file_change_password")); 1647 } 1648 1649 // To authorize manual repair of document in case of error during migration 1650 // the error is not caught if traces are activated 1651 if (err && (SKGTraces::SKGLevelTrace != 0)) { 1652 err = sendMessage(i18nc("Popup message", "The migration failed but the document has been loaded without error because debug mode is activated"), SKGDocument::Warning); 1653 } 1654 1655 if (!err && mig && !iName.isEmpty()) { 1656 err = sendMessage(i18nc("The document has been upgraded to the latest Skrooge version format", "The document has been migrated"), SKGDocument::Positive); 1657 } 1658 } 1659 1660 // Optimization 1661 IFOK(err) { 1662 d->m_lastSavedTransaction = getTransactionToProcess(SKGDocument::UNDO); 1663 executeSqliteOrder(QStringLiteral("ANALYZE")); 1664 } 1665 1666 // Creation undo/redo triggers 1667 IFOKDO(err, createUndoRedoTemporaryTriggers()) 1668 1669 IFOK(err) { 1670 QString sqliteQtVersion = getParameter(QStringLiteral("SKG_SQLITE_LAST_VERSION")); 1671 QString sqliteSystemVersion(sqlite3_libversion()); 1672 QProcess sqlite3Process; 1673 QString mode; 1674 sqlite3Process.start(QStringLiteral("sqlcipher"), QStringList() << QStringLiteral("-version")); 1675 mode = QStringLiteral("SQLCipher"); 1676 if (sqlite3Process.waitForFinished()) { 1677 sqliteSystemVersion = SKGServices::splitCSVLine(sqlite3Process.readAll(), ' ').value(0); 1678 } 1679 SKGTRACEL(5) << "SQLite version of Qt :" << sqliteQtVersion << SKGENDL; 1680 SKGTRACEL(5) << "SQLite version of the system:" << sqliteSystemVersion << SKGENDL; 1681 if (!sqliteQtVersion.isEmpty() && !sqliteSystemVersion.isEmpty() && sqliteQtVersion != sqliteSystemVersion) { 1682 QString message = i18nc("Error message", "This application can not run correctly because the %3 version of the system (%1) is not aligned with the %4 version embedded in Qt (%2). You should rebuild Qt with the option -system-sqlite.", sqliteSystemVersion, sqliteQtVersion, mode, mode); 1683 err = sendMessage(message, Warning); 1684 SKGTRACE << "WARNING:" << message << SKGENDL; 1685 } 1686 } 1687 1688 if (err && !iName.isEmpty()) { 1689 close(); 1690 } else { 1691 // Send event 1692 d->m_uniqueIdentifier = QUuid::createUuid().toString(); 1693 d->m_password = iPassword; 1694 d->m_password_got = true; 1695 Q_EMIT tableModified(QLatin1String(""), 0, false); 1696 Q_EMIT modified(); 1697 } 1698 1699 return err; 1700 } 1701 1702 bool SKGDocument::isReadOnly() const 1703 { 1704 return d->m_modeReadOnly; 1705 } 1706 1707 bool SKGDocument::isFileModified() const 1708 { 1709 // Get last executed transaction 1710 int last = getTransactionToProcess(SKGDocument::UNDO); 1711 // if (nbStepForTransaction.size()) --last; 1712 return (d->m_lastSavedTransaction != last); 1713 } 1714 1715 void SKGDocument::setFileNotModified() const 1716 { 1717 d->m_lastSavedTransaction = getTransactionToProcess(SKGDocument::UNDO); 1718 } 1719 1720 QString SKGDocument::getCurrentFileName() const 1721 { 1722 return d->m_currentFileName; 1723 } 1724 1725 SKGError SKGDocument::save() 1726 { 1727 SKGError err; 1728 SKGTRACEINFUNCRC(5, err) 1729 if (d->m_currentFileName.isEmpty()) { 1730 err = SKGError(ERR_INVALIDARG, i18nc("Error message: Can not save a file if it has no name yet", "Save not authorized because the file name is not yet defined")); 1731 } else { 1732 // save 1733 err = saveAs(d->m_currentFileName, true); 1734 } 1735 return err; 1736 } 1737 1738 SKGError SKGDocument::saveAs(const QString& iName, bool iOverwrite) 1739 { 1740 SKGError err; 1741 SKGTRACEINFUNCRC(5, err) 1742 SKGTRACEL(10) << "Input parameter [name]=[" << iName << ']' << SKGENDL; 1743 1744 bool simulateFileSystemFull = !SKGServices::getEnvVariable(QStringLiteral("SKGFILESYSTEMFULL")).isEmpty(); 1745 1746 // Check if a transaction is still opened 1747 err = checkExistingTransaction(); 1748 IFOK(err) err.setReturnCode(ERR_ABORT).setMessage(i18nc("Cannot save the file while the application is still performing an SQL transaction", "Save is forbidden if a transaction is still opened")); 1749 else { 1750 err = SKGError(); 1751 if (getParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE")) == QStringLiteral("Y")) { 1752 err = executeSqliteOrder(QStringLiteral("delete from doctransaction")); 1753 } 1754 1755 // No transaction opened ==> it is ok 1756 // We mark the last transaction as a save point 1757 IFOKDO(err, executeSqliteOrder(QStringLiteral("update doctransaction set t_savestep='Y' where id in (select A.id from doctransaction A where " 1758 "NOT EXISTS(select 1 from doctransaction B where B.i_parent=A.id) " 1759 "and A.t_mode='U')"))) 1760 Q_EMIT tableModified(QStringLiteral("doctransaction"), 0, false); 1761 1762 // Optimization 1763 IFOK(err) { 1764 err = executeSqliteOrder(QStringLiteral("VACUUM;")); 1765 IFOK(err) { 1766 // Check if file already exist 1767 if (!iOverwrite && QFile(iName).exists()) { 1768 err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("There is already a file with the same name", "File '%1' already exist", iName)); 1769 } else { 1770 // Get backup file name 1771 bool backupFileMustBeRemoved = false; 1772 QString backupFileName = getBackupFile(iName); 1773 if (backupFileName.isEmpty()) { 1774 backupFileName = iName % ".tmp"; 1775 backupFileMustBeRemoved = true; 1776 } 1777 1778 // Create backup file 1779 QFile::remove(backupFileName % '~'); 1780 QFile::rename(backupFileName, backupFileName % '~'); 1781 if (QFile(iName).exists() && (simulateFileSystemFull || !QFile(iName).copy(backupFileName))) { 1782 this->sendMessage(i18nc("Error message: Could not create a backup file", "Creation of backup file %1 failed", backupFileName), Warning); 1783 } 1784 1785 // Save database 1786 IFOK(err) { 1787 QFile::remove(iName % '~'); 1788 QFile::rename(iName, iName % '~'); 1789 1790 // To be sure that db is flushed 1791 IFOKDO(err, executeSqliteOrder(QStringLiteral("PRAGMA synchronous = FULL"))) 1792 QString pwd = getPassword(); 1793 1794 // Copy memory to tmp db 1795 if (!d->m_directAccessDb && !err) { 1796 QFile::remove(d->m_temporaryFile); 1797 auto fileDb = QSqlDatabase::addDatabase(SQLDRIVERNAME, d->m_databaseIdentifier % "_tmp"); 1798 fileDb.setDatabaseName(d->m_temporaryFile); 1799 if (!fileDb.open()) { 1800 // Set error message 1801 QSqlError sqlErr = fileDb.lastError(); 1802 err = SKGError(SQLLITEERROR + sqlErr.nativeErrorCode().toInt(), sqlErr.text()); 1803 } else { 1804 IFOKDO(err, SKGServices::executeSqliteOrder(fileDb, "PRAGMA KEY = '" % SKGServices::stringToSqlString(pwd.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : pwd) % "'")) 1805 addSqliteAddon(&fileDb); 1806 IFOKDO(err, SKGServices::copySqliteDatabase(fileDb, d->m_currentDatabase, false, pwd.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : pwd)) 1807 } 1808 1809 fileDb.close(); 1810 QSqlDatabase::removeDatabase(d->m_databaseIdentifier % "_tmp"); 1811 } 1812 1813 // To simulate a file system full 1814 if (!err && simulateFileSystemFull) { 1815 err = SKGError(ERR_WRITEACCESS, i18nc("Error message: writing a file failed", "Write file '%1' failed", iName)); 1816 } 1817 1818 // Crypt the file 1819 if (!err) { 1820 bool mode; 1821 err = SKGServices::cryptFile(d->m_temporaryFile, iName, pwd, true, getDocumentHeader(), mode); 1822 } 1823 if (!d->m_directAccessDb && !err) { 1824 QFile(d->m_temporaryFile).remove(); 1825 } 1826 1827 // For performances 1828 IFOKDO(err, executeSqliteOrder(QStringLiteral("PRAGMA synchronous = OFF"))) 1829 } 1830 1831 if (backupFileMustBeRemoved) { 1832 QFile::remove(backupFileName); 1833 } 1834 1835 IFOK(err) { 1836 // The document is not modified 1837 QString oldtemporaryFile = d->m_temporaryFile; 1838 d->m_currentFileName = iName; 1839 d->m_modeReadOnly = false; 1840 d->m_temporaryFile = getTemporaryFile(d->m_currentFileName); 1841 if (oldtemporaryFile != d->m_temporaryFile) { 1842 QFile(oldtemporaryFile).rename(d->m_temporaryFile); 1843 } 1844 d->m_lastSavedTransaction = getTransactionToProcess(SKGDocument::UNDO); 1845 1846 // Commit save 1847 QFile::remove(backupFileName % '~'); 1848 QFile::remove(iName % '~'); 1849 } else { 1850 // Rollback file 1851 QFile::remove(backupFileName); 1852 QFile::rename(backupFileName % '~', backupFileName); 1853 1854 QFile::remove(iName); 1855 QFile::rename(iName % '~', iName); 1856 } 1857 } 1858 } 1859 } 1860 1861 Q_EMIT transactionSuccessfullyEnded(0); 1862 } 1863 return err; 1864 } 1865 1866 SKGError SKGDocument::close() 1867 { 1868 SKGTRACEINFUNC(5) 1869 if (getMainDatabase() != nullptr) { 1870 QString conNameMainConnection = getMainDatabase()->connectionName(); 1871 const auto& conNames = QSqlDatabase::connectionNames(); 1872 for (const auto& conName : conNames) { 1873 if (conName.startsWith(conNameMainConnection % "_")) { 1874 /* NO NEED 1875 { 1876 auto con = QSqlDatabase::database(conName, false); 1877 con.close(); 1878 }*/ 1879 QSqlDatabase::removeDatabase(conName); 1880 } 1881 } 1882 getMainDatabase()->close(); 1883 d->m_currentDatabase = QSqlDatabase(); // To avoid warning on remove 1884 QSqlDatabase::removeDatabase(d->m_databaseIdentifier); 1885 } 1886 1887 if (!d->m_temporaryFile.isEmpty()) { 1888 QFile(d->m_temporaryFile).remove(); 1889 d->m_temporaryFile = QLatin1String(""); 1890 } 1891 1892 // Emit events ? 1893 bool emitEvent = (d->m_lastSavedTransaction != -1); 1894 1895 // Init fields 1896 d->m_currentFileName = QLatin1String(""); 1897 d->m_lastSavedTransaction = 0; 1898 d->m_nbStepForTransaction.clear(); 1899 d->m_posStepForTransaction.clear(); 1900 d->m_nameForTransaction.clear(); 1901 d->m_password.clear(); 1902 d->m_password_got = false; 1903 1904 // Send event 1905 if (!d->m_blockEmits && emitEvent && qApp && !qApp->closingDown()) { 1906 Q_EMIT tableModified(QLatin1String(""), 0, false); 1907 Q_EMIT transactionSuccessfullyEnded(0); 1908 Q_EMIT modified(); 1909 } 1910 1911 return SKGError(); 1912 } 1913 1914 SKGError SKGDocument::dropViewsAndIndexes(const QStringList& iTables) const 1915 { 1916 SKGError err; 1917 // Drop all views 1918 SKGStringListList list; 1919 err = executeSelectSqliteOrder(QStringLiteral("SELECT tbl_name, name, type FROM sqlite_master WHERE type IN ('view','index')"), list); 1920 int nb = list.count(); 1921 for (int i = 1; !err && i < nb; ++i) { 1922 QString name = list.at(i).at(1); 1923 QString table = SKGServices::getRealTable(list.at(i).at(0)); 1924 QString type = list.at(i).at(2); 1925 if (iTables.contains(table)) { 1926 QString sql = "DROP " % type % " IF EXISTS " % name; 1927 err = this->executeSqliteOrder(sql); 1928 } 1929 } 1930 return err; 1931 } 1932 1933 #include "skgdocument2.cpp" 1934 1935 SKGError SKGDocument::migrate(bool& oMigrationDone) 1936 { 1937 SKGError err; 1938 SKGTRACEINFUNCRC(5, err) 1939 oMigrationDone = false; 1940 1941 { 1942 SKGBEGINPROGRESSTRANSACTION(*this, "#INTERNAL#" % i18nc("Progression step", "Migrate document"), err, 3) 1943 if (getParameter(QStringLiteral("SKG_UNDO_MAX_DEPTH")).isEmpty()) { 1944 IFOKDO(err, setParameter(QStringLiteral("SKG_UNDO_MAX_DEPTH"), SKGServices::intToString(SKG_UNDO_MAX_DEPTH))) 1945 } 1946 1947 if (getParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE")).isEmpty()) { 1948 IFOKDO(err, setParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE"), QStringLiteral("N"))) 1949 } 1950 1951 if (!err && getParameter(QStringLiteral("SKG_DATABASE_TYPE")) != (d->m_modeSQLCipher ? SQLDRIVERNAME : QStringLiteral("QSQLITE"))) { 1952 IFOKDO(err, setParameter(QStringLiteral("SKG_DATABASE_TYPE"), d->m_modeSQLCipher ? SQLDRIVERNAME : QStringLiteral("QSQLITE"))) 1953 } 1954 1955 QString version = getParameter(QStringLiteral("SKG_DB_VERSION")); 1956 QString initialversion = version; 1957 QString lastversion = QStringLiteral("1.6"); 1958 1959 if (!err && version.isEmpty()) { 1960 // First creation 1961 SKGTRACEL(10) << "Migration from 0 to " << lastversion << SKGENDL; 1962 1963 // Set new version 1964 version = lastversion; 1965 IFOKDO(err, setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 1966 1967 // Set sqlite creation version 1968 SKGStringListList listTmp; 1969 IFOKDO(err, executeSelectSqliteOrder(QStringLiteral("select sqlite_version()"), listTmp)) 1970 if (!err && listTmp.count() == 2) { 1971 err = setParameter(QStringLiteral("SKG_SQLITE_CREATION_VERSION"), listTmp.at(1).at(0)); 1972 } 1973 oMigrationDone = true; 1974 } 1975 1976 if (!err && SKGServices::stringToDouble(version) > SKGServices::stringToDouble(lastversion)) { 1977 err = SKGError(ERR_ABORT, i18nc("Error message", "Impossible to load a document generated by a more recent version")); 1978 } 1979 1980 { 1981 // Migration steps 1982 if (!err && version == QStringLiteral("0.1")) { 1983 // Migration from version 0.1 to 0.2 1984 SKGTRACEL(10) << "Migration from 0.1 to 0.2" << SKGENDL; 1985 1986 // ================================================================== 1987 // Table doctransactionmsg 1988 QStringList sqlOrders; 1989 sqlOrders << QStringLiteral("DROP TABLE IF EXISTS doctransactionmsg") 1990 << QStringLiteral("CREATE TABLE doctransactionmsg (" 1991 "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " 1992 "rd_doctransaction_id INTEGER NOT NULL," 1993 "t_message TEXT NOT NULL DEFAULT '')"); 1994 err = executeSqliteOrders(sqlOrders); 1995 1996 // Set new version 1997 version = QStringLiteral("0.2"); 1998 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 1999 oMigrationDone = true; 2000 } 2001 if (!err && version == QStringLiteral("0.2")) { 2002 // Migration from version 0.2 to 0.3 2003 SKGTRACEL(10) << "Migration from 0.2 to 0.3" << SKGENDL; 2004 2005 err = executeSqliteOrder(QStringLiteral("UPDATE node set f_sortorder=id")); 2006 2007 // Set new version 2008 version = QStringLiteral("0.3"); 2009 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2010 oMigrationDone = true; 2011 } 2012 if (!err && version == QStringLiteral("0.3")) { 2013 // Migration from version 0.3 to 0.4 2014 SKGTRACEL(10) << "Migration from 0.3 to 0.4" << SKGENDL; 2015 2016 err = executeSqliteOrder(QStringLiteral("ALTER TABLE node ADD COLUMN t_autostart VARCHAR(1) DEFAULT 'N' CHECK (t_autostart IN ('Y', 'N'))")); 2017 IFOKDO(err, executeSqliteOrder("UPDATE node set t_autostart='Y' where t_name='" % i18nc("Verb, automatically load when the application is started", "autostart") % '\'')) 2018 2019 // Set new version 2020 version = QStringLiteral("0.4"); 2021 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2022 oMigrationDone = true; 2023 } 2024 if (!err && version == QStringLiteral("0.4")) { 2025 // Migration from version 0.4 to 0.5 2026 SKGTRACEL(10) << "Migration from 0.4 to 0.5" << SKGENDL; 2027 2028 err = executeSqliteOrder(QStringLiteral("ALTER TABLE doctransactionmsg ADD COLUMN t_popup VARCHAR(1) DEFAULT 'Y' CHECK (t_popup IN ('Y', 'N'))")); 2029 2030 // Set new version 2031 version = QStringLiteral("0.5"); 2032 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2033 oMigrationDone = true; 2034 } 2035 if (!err && version == QStringLiteral("0.5")) { 2036 // Migration from version 0.5 to 0.6 2037 SKGTRACEL(10) << "Migration from 0.5 to 0.6" << SKGENDL; 2038 2039 err = executeSqliteOrder(QStringLiteral("UPDATE node set t_autostart='N' where t_autostart NOT IN ('Y', 'N')")); 2040 2041 // Set new version 2042 version = QStringLiteral("0.6"); 2043 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2044 oMigrationDone = true; 2045 } 2046 if (!err && version == QStringLiteral("0.6")) { 2047 // Migration from version 0.6 to 0.7 2048 SKGTRACEL(10) << "Migration from 0.6 to 0.7" << SKGENDL; 2049 2050 err = executeSqliteOrder(QStringLiteral("ALTER TABLE parameters ADD COLUMN b_blob BLOB")); 2051 2052 // Set new version 2053 version = QStringLiteral("0.7"); 2054 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2055 oMigrationDone = true; 2056 } 2057 if (!err && version == QStringLiteral("0.7")) { 2058 // Migration from version 0.7 to 0.8 2059 SKGTRACEL(10) << "Migration from 0.7 to 0.8" << SKGENDL; 2060 2061 err = executeSqliteOrder(QStringLiteral("UPDATE parameters set t_name='SKG_LANGUAGE' where t_name='SKGLANGUAGE'")); 2062 2063 // Set new version 2064 version = QStringLiteral("0.8"); 2065 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2066 oMigrationDone = true; 2067 } 2068 if (!err && version == QStringLiteral("0.8")) { 2069 SKGTRACEL(10) << "Migration from 0.8 to 0.9" << SKGENDL; 2070 2071 QStringList sql; 2072 sql << QStringLiteral("ALTER TABLE parameters ADD COLUMN i_tmp INTEGER NOT NULL DEFAULT 0") 2073 << QStringLiteral("UPDATE parameters set i_tmp=0"); 2074 2075 err = executeSqliteOrders(sql); 2076 2077 // Set new version 2078 version = QStringLiteral("0.9"); 2079 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2080 oMigrationDone = true; 2081 } 2082 if (!err && version == QStringLiteral("0.9")) { 2083 SKGTRACEL(10) << "Migration from 0.9 to 1.0" << SKGENDL; 2084 2085 err = SKGDocument::setParameter(QStringLiteral("SKG_UNIQUE_ID"), QLatin1String("")); 2086 2087 // Set new version 2088 version = QStringLiteral("1.0"); 2089 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2090 oMigrationDone = true; 2091 } 2092 if (!err && version == QStringLiteral("1.0")) { 2093 // Migration from version 1.0 to 1.1 2094 SKGTRACEL(10) << "Migration from 1.0 to 1.1" << SKGENDL; 2095 2096 err = executeSqliteOrder(QStringLiteral("ALTER TABLE node ADD COLUMN t_icon TEXT DEFAULT ''")); 2097 IFOK(err) { 2098 SKGStringListList result; 2099 err = executeSelectSqliteOrder(QStringLiteral("SELECT id,t_data from node"), result); 2100 int nb = result.count(); 2101 for (int i = 1; !err && i < nb; ++i) { 2102 const QStringList& line = result.at(i); 2103 QString icon = QStringLiteral("folder-bookmark"); 2104 QStringList data = SKGServices::splitCSVLine(line.at(1)); 2105 if (data.count() > 2) { 2106 icon = data.at(2); 2107 } 2108 data.removeAt(2); 2109 err = executeSqliteOrder("UPDATE node set t_icon='" % SKGServices::stringToSqlString(icon) % 2110 "', t_data='" % SKGServices::stringToSqlString(SKGServices::stringsToCsv(data)) % "' where id=" % line.at(0)); 2111 } 2112 } 2113 2114 // Set new version 2115 version = QStringLiteral("1.1"); 2116 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2117 oMigrationDone = true; 2118 } 2119 if (!err && version == QStringLiteral("1.1")) { 2120 // Migration from version 1.1 to 1.2 2121 SKGTRACEL(10) << "Migration from 1.1 to 1.2" << SKGENDL; 2122 2123 QStringList sql; 2124 sql << QStringLiteral("ALTER TABLE doctransaction ADD COLUMN t_refreshviews VARCHAR(1) DEFAULT 'Y' CHECK (t_refreshviews IN ('Y', 'N'))") 2125 << QStringLiteral("UPDATE doctransaction set t_refreshviews='Y'"); 2126 2127 err = executeSqliteOrders(sql); 2128 2129 // Set new version 2130 version = QStringLiteral("1.2"); 2131 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2132 oMigrationDone = true; 2133 } 2134 if (!err && version == QStringLiteral("1.2")) { 2135 // Migration from version 1.2 to 1.3 2136 SKGTRACEL(10) << "Migration from 1.2 to 1.3" << SKGENDL; 2137 2138 err = SKGDocument::refreshViewsIndexesAndTriggers(); 2139 2140 QStringList sql; 2141 sql << QStringLiteral("DELETE FROM node WHERE (r_node_id IS NULL OR r_node_id='') AND EXISTS (SELECT 1 FROM node n WHERE n.t_name=node.t_name AND r_node_id=0)") 2142 << QStringLiteral("UPDATE node SET t_name=t_name"); 2143 IFOKDO(err, executeSqliteOrders(sql)) 2144 2145 // Set new version 2146 version = QStringLiteral("1.3"); 2147 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2148 oMigrationDone = true; 2149 } 2150 if (!err && version == QStringLiteral("1.3")) { 2151 // Migration from version 1.3 to 1.4 2152 SKGTRACEL(10) << "Migration from 1.3 to 1.4" << SKGENDL; 2153 2154 QStringList sql; 2155 sql << "CREATE TABLE node2 (" 2156 "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," 2157 "t_name TEXT NOT NULL DEFAULT '' CHECK (t_name NOT LIKE '%" % OBJECTSEPARATOR % "%')," 2158 "t_fullname TEXT," 2159 "t_icon TEXT DEFAULT ''," 2160 "f_sortorder FLOAT," 2161 "t_autostart VARCHAR(1) DEFAULT 'N' CHECK (t_autostart IN ('Y', 'N'))," 2162 "t_data TEXT," 2163 "rd_node_id INT CONSTRAINT fk_id REFERENCES node(id) ON DELETE CASCADE)" 2164 2165 << QStringLiteral("INSERT INTO node2 (id, t_name, t_fullname, t_icon, f_sortorder, t_autostart, t_data, rd_node_id) " 2166 "SELECT id, t_name, t_fullname, t_icon, f_sortorder, t_autostart, t_data, r_node_id FROM node") 2167 2168 << QStringLiteral("DROP TABLE IF EXISTS node") 2169 << QStringLiteral("ALTER TABLE node2 RENAME TO node"); 2170 2171 err = executeSqliteOrders(sql); 2172 2173 // Set new version 2174 version = QStringLiteral("1.4"); 2175 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2176 oMigrationDone = true; 2177 } 2178 if (!err && version == QStringLiteral("1.4")) { 2179 // Migration from version 1.4 to 1.5 2180 SKGTRACEL(10) << "Migration from 1.4 to 1.5" << SKGENDL; 2181 2182 err = SKGDocument::refreshViewsIndexesAndTriggers(); 2183 2184 QStringList sql; 2185 sql << QStringLiteral("UPDATE parameters SET t_uuid_parent='advice' WHERE t_uuid_parent='advices'"); 2186 IFOKDO(err, executeSqliteOrders(sql)) 2187 2188 // Set new version 2189 version = QStringLiteral("1.5"); 2190 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2191 oMigrationDone = true; 2192 } 2193 if (!err && version == QStringLiteral("1.5")) { 2194 // Migration from version 1.5 to 1.6 2195 SKGTRACEL(10) << "Migration from 1.5 to 1.6" << SKGENDL; 2196 2197 err = SKGDocument::refreshViewsIndexesAndTriggers(); 2198 2199 QStringList sql; 2200 sql << QStringLiteral("DROP TABLE IF EXISTS doctransactionmsg2") 2201 << QStringLiteral("CREATE TABLE doctransactionmsg2 (" 2202 "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " 2203 "rd_doctransaction_id INTEGER NOT NULL," 2204 "t_message TEXT NOT NULL DEFAULT ''," 2205 "t_type VARCHAR(1) DEFAULT 'I' CHECK (t_type IN ('P', 'I', 'W', 'E', 'H')))") // Positive, Information, Warning, Error, Hidden 2206 << QStringLiteral("INSERT INTO doctransactionmsg2 (id, rd_doctransaction_id, t_message, t_type) SELECT id, rd_doctransaction_id, t_message, (CASE WHEN t_popup='Y' THEN 'I' ELSE 'H' END) FROM doctransactionmsg") 2207 2208 << QStringLiteral("DROP TABLE IF EXISTS doctransactionmsg") 2209 << QStringLiteral("ALTER TABLE doctransactionmsg2 RENAME TO doctransactionmsg"); 2210 IFOKDO(err, executeSqliteOrders(sql)) 2211 2212 // Set new version 2213 version = QStringLiteral("1.6"); 2214 IFOKDO(err, SKGDocument::setParameter(QStringLiteral("SKG_DB_VERSION"), version)) 2215 oMigrationDone = true; 2216 } 2217 } 2218 IFOKDO(err, stepForward(1, i18nc("Progression step", "Refresh views"))) 2219 2220 // Set sqlite last version 2221 SKGStringListList listTmp; 2222 IFOKDO(err, executeSelectSqliteOrder(QStringLiteral("select sqlite_version()"), listTmp)) 2223 if (!err && listTmp.count() == 2) { 2224 err = setParameter(QStringLiteral("SKG_SQLITE_LAST_VERSION"), listTmp.at(1).at(0)); 2225 } 2226 2227 // Refresh views 2228 IFOKDO(err, refreshViewsIndexesAndTriggers()) 2229 IFOKDO(err, stepForward(2, i18nc("Progression step", "Update materialized views"))) 2230 2231 // Refresh materialized views 2232 if (!err && oMigrationDone) { 2233 err = computeMaterializedViews(); 2234 } 2235 IFOKDO(err, stepForward(3)) 2236 2237 IFKO(err) err.addError(ERR_FAIL, i18nc("Error message: Could not perform database migration", "Database migration from version %1 to version %2 failed", initialversion, version)); 2238 } 2239 2240 return err; 2241 } 2242 2243 SKGError SKGDocument::createUndoRedoTemporaryTriggers() const 2244 { 2245 SKGError err; 2246 SKGTRACEINFUNCRC(10, err) 2247 2248 // Create triggers 2249 QStringList tables; 2250 err = this->getTablesList(tables); 2251 int nbTables = tables.count(); 2252 for (int i = 0; !err && i < nbTables; ++i) { 2253 // Get table name 2254 const QString& table = tables.at(i); 2255 2256 // Do we have to treat this table 2257 if (!SKGListNotUndoable.contains("T." % table) && !table.startsWith(QLatin1String("vm_"))) { 2258 // YES 2259 // Get attributes name 2260 QStringList attributes; 2261 err = getAttributesList(table, attributes); 2262 2263 // Build sqlorder for update and insert 2264 QString sqlorderForUpdate2; 2265 QString sqlorderForInsert1; 2266 QString sqlorderForInsert2; 2267 int nbAttributes = attributes.count(); 2268 for (int j = 0; !err && j < nbAttributes; ++j) { 2269 // Get attribute 2270 const QString& att = attributes.at(j); 2271 2272 // Do we have to treat this attribute 2273 if (!SKGListNotUndoable.contains("A." % table % '.' % att)) { 2274 // Build for update 2275 if (!sqlorderForUpdate2.isEmpty()) { 2276 sqlorderForUpdate2 += ','; 2277 } 2278 sqlorderForUpdate2 += att % "='||quote(old." % att % ")||'"; 2279 2280 // Build for insert part 1 2281 if (!sqlorderForInsert1.isEmpty()) { 2282 sqlorderForInsert1 += ','; 2283 } 2284 sqlorderForInsert1 += att; 2285 2286 // Build for insert part 2 2287 if (!sqlorderForInsert2.isEmpty()) { 2288 sqlorderForInsert2 += ','; 2289 } 2290 sqlorderForInsert2 += "'||quote(old." % att % ")||'"; 2291 } 2292 } 2293 2294 // Create specific triggers for the current transaction 2295 QStringList sqlOrders; 2296 // DROP DELETE trigger 2297 sqlOrders << "DROP TRIGGER IF EXISTS UR_" % table % "_IN" 2298 2299 // Create DELETE trigger 2300 << "CREATE TEMP TRIGGER UR_" % table % "_IN " 2301 "AFTER INSERT ON " % table % " BEGIN " 2302 "INSERT INTO doctransactionitem (rd_doctransaction_id, t_sqlorder,i_object_id,t_object_table,t_action) VALUES(0,'DELETE FROM " % table % 2303 " WHERE id='||new.id,new.id,'" % table % "','D');END" 2304 2305 // DROP UPDATE trigger 2306 << "DROP TRIGGER IF EXISTS UR_" % table % "_UP" 2307 2308 // Create UPDATE trigger 2309 << "CREATE TEMP TRIGGER UR_" % table % "_UP " 2310 "AFTER UPDATE ON " % table % " BEGIN " 2311 "INSERT INTO doctransactionitem (rd_doctransaction_id, t_sqlorder,i_object_id,t_object_table,t_action) VALUES(0,'UPDATE " % table % 2312 " SET " % sqlorderForUpdate2 % 2313 " WHERE id='||new.id,new.id,'" % table % "','U');END" 2314 2315 // DROP INSERT trigger 2316 << "DROP TRIGGER IF EXISTS UR_" % table % "_DE" 2317 2318 // Create INSERT trigger 2319 << "CREATE TEMP TRIGGER UR_" % table % "_DE " 2320 "AFTER DELETE ON " % table % 2321 " BEGIN " 2322 "INSERT INTO doctransactionitem (rd_doctransaction_id, t_sqlorder,i_object_id,t_object_table,t_action) VALUES(0,'INSERT INTO " % table % 2323 '(' % sqlorderForInsert1 % ") VALUES(" % sqlorderForInsert2 % ")',old.id,'" % table % "','I'); END"; 2324 err = executeSqliteOrders(sqlOrders); 2325 } 2326 } 2327 return err; 2328 } 2329 2330 QStringList SKGDocument::getParameters(const QString& iParentUUID, const QString& iWhereClause) 2331 { 2332 SKGTRACEINFUNC(10) 2333 QStringList output; 2334 QString wc = "t_uuid_parent='" % SKGServices::stringToSqlString(iParentUUID) % '\''; 2335 if (!iWhereClause.isEmpty()) { 2336 wc += " AND (" % iWhereClause % ')'; 2337 } 2338 this->getDistinctValues(QStringLiteral("parameters"), QStringLiteral("t_name"), wc, output); 2339 return output; 2340 } 2341 2342 QString SKGDocument::getParameter(const QString& iName, const QString& iParentUUID) const 2343 { 2344 SKGTRACEINFUNC(10) 2345 SKGTRACEL(10) << "Input parameter [iName]=[" << iName << ']' << SKGENDL; 2346 QString output; 2347 2348 // Get parameter 2349 SKGObjectBase param; 2350 SKGError err = getObject(QStringLiteral("parameters"), "t_name='" % SKGServices::stringToSqlString(iName) % 2351 "' AND t_uuid_parent='" % SKGServices::stringToSqlString(iParentUUID) % '\'', param); 2352 IFOK(err) { 2353 output = param.getAttribute(QStringLiteral("t_value")); 2354 } 2355 return output; 2356 } 2357 2358 QVariant SKGDocument::getParameterBlob(const QString& iName, const QString& iParentUUID) const 2359 { 2360 SKGTRACEINFUNC(10) 2361 SKGTRACEL(10) << "Input parameter [iName]=[" << iName << ']' << SKGENDL; 2362 2363 QVariant output; 2364 if (getMainDatabase() != nullptr) { 2365 QString sqlQuery = QStringLiteral("SELECT b_blob FROM parameters WHERE t_name=? AND t_uuid_parent=?"); 2366 QSqlQuery query(*getMainDatabase()); 2367 query.prepare(sqlQuery); 2368 query.addBindValue(iName); 2369 query.addBindValue(iParentUUID); 2370 if (Q_LIKELY(!query.exec())) { 2371 QSqlError sqlError = query.lastError(); 2372 SKGTRACE << "WARNING: " << sqlQuery << SKGENDL; 2373 SKGTRACE << " returns :" << sqlError.text() << SKGENDL; 2374 } else { 2375 if (query.next()) { 2376 output = query.value(0); 2377 } 2378 } 2379 } 2380 2381 return output; 2382 } 2383 2384 SKGError SKGDocument::setParameter(const QString& iName, const QString& iValue, const QString& iFileName, const QString& iParentUUID, SKGPropertyObject* oObjectCreated) const 2385 { 2386 SKGError err; 2387 SKGTRACEINFUNCRC(10, err) 2388 SKGTRACEL(10) << "Input parameter [iName] =[" << iName << ']' << SKGENDL; 2389 SKGTRACEL(10) << "Input parameter [iValue] =[" << iValue << ']' << SKGENDL; 2390 SKGTRACEL(10) << "Input parameter [iFileName]=[" << iFileName << ']' << SKGENDL; 2391 QVariant blob; 2392 QString value = iValue; 2393 QFile file(iFileName); 2394 if (file.exists()) { 2395 QFileInfo fileInfo(iFileName); 2396 if (fileInfo.isDir()) { 2397 value = "file://" % iFileName; 2398 } else { 2399 // Open file 2400 if (Q_UNLIKELY(!file.open(QIODevice::ReadOnly))) { 2401 err = SKGError(ERR_INVALIDARG, i18nc("Error message: Could not open a file", "Open file '%1' failed", iFileName)); 2402 } else { 2403 QByteArray blob_bytes = file.readAll(); 2404 if (blob_bytes.isEmpty()) { 2405 err = SKGError(ERR_INVALIDARG, i18nc("Error message: Could not open a file", "Open file '%1' failed", iFileName)); 2406 } else { 2407 blob = blob_bytes; 2408 value = fileInfo.fileName(); 2409 } 2410 2411 // close file 2412 file.close(); 2413 } 2414 } 2415 } 2416 2417 IFOKDO(err, setParameter(iName, value, blob, iParentUUID, oObjectCreated)) 2418 return err; 2419 } 2420 2421 SKGError SKGDocument::setParameter(const QString& iName, const QString& iValue, const QVariant& iBlob, const QString& iParentUUID, SKGPropertyObject* oObjectCreated) const 2422 { 2423 SKGError err; 2424 SKGTRACEINFUNCRC(10, err) 2425 SKGTRACEL(10) << "Input parameter [iName] =[" << iName << ']' << SKGENDL; 2426 SKGTRACEL(10) << "Input parameter [iValue] =[" << iValue << ']' << SKGENDL; 2427 if (getMainDatabase() == nullptr) { 2428 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 2429 } else { 2430 SKGPropertyObject param(const_cast<SKGDocument*>(this)); 2431 IFOKDO(err, param.setName(iName)) 2432 IFOKDO(err, param.setValue(iValue)) 2433 IFOKDO(err, param.setParentId(iParentUUID)) 2434 IFOKDO(err, param.save(true, oObjectCreated != nullptr)) 2435 2436 if (!err && !iBlob.isNull()) { 2437 err = param.load(); 2438 IFOK(err) { 2439 // Set blob 2440 QString sqlQuery = QStringLiteral("UPDATE parameters SET b_blob=? WHERE id=?"); 2441 QSqlQuery query(*getMainDatabase()); 2442 query.prepare(sqlQuery); 2443 query.addBindValue(iBlob); 2444 query.addBindValue(param.getID()); 2445 if (Q_LIKELY(!query.exec())) { 2446 QSqlError sqlError = query.lastError(); 2447 QString msg = sqlQuery % ':' % sqlError.text(); 2448 err = SKGError(SQLLITEERROR + sqlError.nativeErrorCode().toInt(), msg); 2449 } 2450 } 2451 } 2452 if (!err && oObjectCreated != nullptr) { 2453 *oObjectCreated = param; 2454 } 2455 } 2456 return err; 2457 } 2458 2459 SKGError SKGDocument::dump(int iMode) const 2460 { 2461 SKGError err; 2462 // dump parameters 2463 SKGTRACE << "=== START DUMP ===" << SKGENDL; 2464 if ((iMode & DUMPSQLITE) != 0) { 2465 SKGTRACE << "=== DUMPSQLITE ===" << SKGENDL; 2466 err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM sqlite_master order by type"))); 2467 2468 SKGTRACE << "=== DUMPSQLITE (TEMPORARY) ===" << SKGENDL; 2469 err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM sqlite_temp_master order by type"))); 2470 } 2471 2472 if ((iMode & DUMPPARAMETERS) != 0) { 2473 SKGTRACE << "=== DUMPPARAMETERS ===" << SKGENDL; 2474 err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM parameters order by id"))); 2475 } 2476 2477 if ((iMode & DUMPNODES) != 0) { 2478 SKGTRACE << "=== DUMPNODES ===" << SKGENDL; 2479 err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM node order by id"))); 2480 } 2481 2482 if ((iMode & DUMPTRANSACTIONS) != 0) { 2483 // dump transaction 2484 SKGTRACE << "=== DUMPTRANSACTIONS ===" << SKGENDL; 2485 err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM doctransaction order by id"))); 2486 2487 // dump transaction 2488 SKGTRACE << "=== DUMPTRANSACTIONS (ITEMS) ===" << SKGENDL; 2489 err.addError(dumpSelectSqliteOrder(QStringLiteral("SELECT * FROM doctransactionitem order by rd_doctransaction_id, id"))); 2490 } 2491 SKGTRACE << "=== END DUMP ===" << SKGENDL; 2492 return err; 2493 } 2494 2495 QSqlDatabase* SKGDocument::getMainDatabase() const 2496 { 2497 if (!d->m_currentDatabase.isOpen()) { 2498 return nullptr; 2499 } 2500 return const_cast<QSqlDatabase*>(&d->m_currentDatabase); 2501 } 2502 2503 QSqlDatabase SKGDocument::getThreadDatabase() const 2504 { 2505 if (qApp->thread() != QThread::currentThread()) { 2506 d->m_mutex.lock(); 2507 QString pwd = getPassword(); 2508 QString dbName = getMainDatabase()->databaseName(); 2509 QString conName = getMainDatabase()->connectionName(); 2510 2511 QString id = conName % "_" % QString::number((quint64)QThread::currentThread(), 16); 2512 d->m_mutex.unlock(); 2513 2514 auto tmpDatabase = QSqlDatabase::database(id); 2515 if (!tmpDatabase.isValid()) { 2516 tmpDatabase = QSqlDatabase::addDatabase(SQLDRIVERNAME, id); 2517 } 2518 if (tmpDatabase.databaseName() != dbName) { 2519 tmpDatabase.setConnectOptions(QStringLiteral("QSQLITE_OPEN_URI")); 2520 tmpDatabase.setDatabaseName(dbName); 2521 if (tmpDatabase.open()) { 2522 addSqliteAddon(&tmpDatabase); 2523 if (d->m_modeSQLCipher) { 2524 SKGServices::executeSqliteOrder(tmpDatabase, "PRAGMA KEY = '" % SKGServices::stringToSqlString(pwd.isEmpty() ? QStringLiteral("DEFAULTPASSWORD") : pwd) % "'"); 2525 } 2526 } 2527 } 2528 return tmpDatabase; 2529 } 2530 return d->m_currentDatabase; 2531 } 2532 2533 SKGError SKGDocument::getConsolidatedView(const QString& iTable, 2534 const QString& iAsColumn, 2535 const QString& iAsRow, 2536 const QString& iAttribute, 2537 const QString& iOpAtt, 2538 const QString& iWhereClause, 2539 SKGStringListList& oTable, 2540 const QString& iMissingValue) const 2541 { 2542 SKGError err; 2543 SKGTRACEINFUNCRC(10, err) 2544 SKGTRACEL(10) << "Input parameter [iTable]=[" << iTable << ']' << SKGENDL; 2545 SKGTRACEL(10) << "Input parameter [iAsColumn]=[" << iAsColumn << ']' << SKGENDL; 2546 SKGTRACEL(10) << "Input parameter [iAsRow]=[" << iAsRow << ']' << SKGENDL; 2547 SKGTRACEL(10) << "Input parameter [iAttribute]=[" << iAttribute << ']' << SKGENDL; 2548 SKGTRACEL(10) << "Input parameter [iOpAtt]=[" << iOpAtt << ']' << SKGENDL; 2549 SKGTRACEL(10) << "Input parameter [iWhereClause]=[" << iWhereClause << ']' << SKGENDL; 2550 SKGTRACEL(10) << "Input parameter [iMissingValue]=[" << iMissingValue << ']' << SKGENDL; 2551 2552 // Mode 2553 int mode = 0; 2554 if (!iAsColumn.isEmpty()) { 2555 mode += 1; 2556 } 2557 if (!iAsRow.isEmpty()) { 2558 mode += 2; 2559 } 2560 2561 oTable.clear(); 2562 oTable.push_back(QStringList()); 2563 2564 2565 QStringList titles = oTable.at(0); 2566 2567 if (mode == 3) { 2568 titles.push_back(iAsRow % '/' % iAsColumn); 2569 } else { 2570 if (mode == 1) { 2571 titles.push_back(iAsColumn); 2572 2573 QStringList sums; 2574 sums.push_back(i18nc("Noun, the numerical sum of a list of values", "Sum")); 2575 oTable.push_back(sums); 2576 } else { 2577 if (mode == 2) { 2578 titles.push_back(iAsRow); 2579 titles.push_back(i18nc("Noun, the numerical sum of a list of values", "Sum")); 2580 } 2581 } 2582 } 2583 oTable.removeAt(0); 2584 oTable.insert(0, titles); 2585 2586 // Create sqlorder 2587 QString asColumn = iAsColumn; 2588 if (asColumn.startsWith(QLatin1String("p_"))) { 2589 QString propertyName = asColumn.right(asColumn.length() - 2); 2590 asColumn = "(SELECT t_value FROM parameters WHERE t_uuid_parent=" % iTable % ".id||'-" % SKGServices::getRealTable(iTable) % "' AND t_name='" % propertyName % "')"; 2591 } 2592 QString asRow = iAsRow; 2593 if (asRow.startsWith(QLatin1String("p_"))) { 2594 QString propertyName = asRow.right(asRow.length() - 2); 2595 asRow = "(SELECT t_value FROM parameters WHERE t_uuid_parent=" % iTable % ".id||'-" % SKGServices::getRealTable(iTable) % "' AND t_name='" % propertyName % "')"; 2596 } 2597 2598 QString att = asColumn; 2599 if (!att.isEmpty() && !asRow.isEmpty()) { 2600 att += ','; 2601 } 2602 att += asRow; 2603 2604 QString sort = asRow; 2605 if (!sort.isEmpty() && !asColumn.isEmpty()) { 2606 sort += ','; 2607 } 2608 sort += asColumn; 2609 2610 if (!att.isEmpty()) { 2611 QString sql = "SELECT " % att % ',' % iOpAtt % '(' % iAttribute % ") FROM " % iTable; 2612 if (!iWhereClause.isEmpty()) { 2613 sql += " WHERE " % iWhereClause; 2614 } 2615 if (!iOpAtt.isEmpty()) { 2616 sql += " GROUP BY " % att; 2617 } 2618 sql += " ORDER BY " % sort; 2619 2620 QHash<QString, int> cols; 2621 QHash<QString, int> rows; 2622 2623 SKGTRACEL(10) << "sqlorder=[" << sql << ']' << SKGENDL; 2624 SKGStringListList listTmp; 2625 err = executeSelectSqliteOrder(sql, listTmp); 2626 int nb = listTmp.count(); 2627 for (int i = 1; !err && i < nb; ++i) { // Title is ignored 2628 const QStringList& line = listTmp.at(i); 2629 int rowindex = -1; 2630 int colindex = -1; 2631 if (mode >= 2) { 2632 const QString& rowname = line.at(mode == 3 ? 1 : 0); 2633 2634 if (!rows.contains(rowname)) { 2635 QStringList r; 2636 int nbx = oTable.at(0).count(); 2637 r.reserve(nbx); 2638 r.push_back(rowname); 2639 for (int j = 1; j < nbx; ++j) { 2640 r.push_back(iMissingValue); 2641 } 2642 2643 oTable.push_back(r); 2644 2645 rowindex = oTable.count() - 1; 2646 rows.insert(rowname, rowindex); 2647 } else { 2648 rowindex = rows[rowname]; 2649 } 2650 } else { 2651 rowindex = 1; 2652 } 2653 2654 if (mode == 1 || mode == 3) { 2655 const QString& colname = line.at(0); 2656 2657 if (!cols.contains(colname)) { 2658 // Search better position of this column 2659 colindex = -1; 2660 { 2661 QHashIterator<QString, int> cols_i(cols); 2662 while (cols_i.hasNext()) { 2663 cols_i.next(); 2664 if (colname > cols_i.key() && cols_i.value() > colindex) { 2665 colindex = cols_i.value(); 2666 } 2667 } 2668 } 2669 if (colindex == -1) { 2670 colindex = 1; 2671 } else { 2672 ++colindex; 2673 } 2674 2675 int nbx = oTable.count(); 2676 for (int j = 0; j < nbx; ++j) { 2677 if (j == 0) { 2678 oTable[j].insert(colindex, colname); 2679 } else { 2680 oTable[j].insert(colindex, iMissingValue); 2681 } 2682 } 2683 2684 { 2685 QHash<QString, int> tmp; 2686 QHashIterator<QString, int> cols_i(cols); 2687 while (cols_i.hasNext()) { 2688 cols_i.next(); 2689 tmp.insert(cols_i.key(), cols_i.value() + (cols_i.value() >= colindex ? 1 : 0)); 2690 } 2691 2692 cols = tmp; 2693 } 2694 cols.insert(colname, colindex); 2695 2696 } else { 2697 colindex = cols[colname]; 2698 } 2699 } else { 2700 colindex = 1; 2701 } 2702 2703 const QString& sum = line.at(mode == 3 ? 2 : 1); 2704 2705 oTable[rowindex][colindex] = sum; 2706 } 2707 2708 IFSKGTRACEL(10) { 2709 QStringList dump2 = SKGServices::tableToDump(oTable, SKGServices::DUMP_TEXT); 2710 int nbl = dump2.count(); 2711 for (int i = 0; i < nbl; ++i) { 2712 SKGTRACE << dump2.at(i) << SKGENDL; 2713 } 2714 } 2715 2716 // Correction bug 205466 vvv 2717 // If some months or years are missing, we must add them. 2718 if (asColumn.startsWith(QLatin1String("d_"))) { 2719 for (int c = 1; c < oTable[0].count() - 1; ++c) { // Dynamic size 2720 bool forecast = false; 2721 QString title = oTable.at(0).at(c); 2722 if (title.isEmpty()) { 2723 title = QStringLiteral("0000"); 2724 } 2725 2726 if (title.endsWith(QLatin1String("999"))) { 2727 title = title.left(title.count() - 3); 2728 forecast = true; 2729 } 2730 QString nextTitle = oTable.at(0).at(c + 1); 2731 if (nextTitle.endsWith(QLatin1String("999"))) { 2732 nextTitle = nextTitle.left(nextTitle.count() - 3); 2733 forecast = true; 2734 } 2735 2736 QString dateFormat = (asColumn == QStringLiteral("d_date") ? QStringLiteral("yyyy-MM-dd") : 2737 (asColumn == QStringLiteral("d_DATEMONTH") ? QStringLiteral("yyyy-MM") : 2738 (asColumn == QStringLiteral("d_DATEQUARTER") ? QStringLiteral("yyyy-QM") : 2739 (asColumn == QStringLiteral("d_DATESEMESTER") ? QStringLiteral("yyyy-SM") : 2740 (asColumn == QStringLiteral("d_DATEWEEK") ? QStringLiteral("yyyy-WM") : QStringLiteral("yyyy")))))); 2741 QDate nextExpected = QDate::fromString(title, dateFormat); 2742 QString nextExpectedString; 2743 if (asColumn == QStringLiteral("d_DATEWEEK")) { 2744 QStringList items = SKGServices::splitCSVLine(oTable.at(0).at(c), '-'); 2745 auto y = SKGServices::stringToInt(items.at(0)); 2746 QString ws = items.value(1); 2747 ws.remove('W'); 2748 auto w = SKGServices::stringToInt(ws); 2749 if (w == 0) { 2750 w = 1; 2751 } 2752 2753 nextExpected = QDate(y, 1, 1).addDays(7 * w); 2754 nextExpectedString = SKGServices::dateToPeriod(nextExpected, "W"); 2755 if (nextExpectedString == title) { 2756 nextExpected = nextExpected.addDays(7); 2757 nextExpectedString = SKGServices::dateToPeriod(nextExpected, "W"); 2758 } 2759 } else if (asColumn == QStringLiteral("d_DATEMONTH")) { 2760 nextExpected = nextExpected.addMonths(1); 2761 nextExpectedString = nextExpected.toString(dateFormat); 2762 } else if (asColumn == QStringLiteral("d_DATEQUARTER")) { 2763 nextExpected = nextExpected.addMonths(nextExpected.month() * 3 - nextExpected.month()); // convert quarter in month 2764 nextExpected = nextExpected.addMonths(3); 2765 nextExpectedString = nextExpected.toString(QStringLiteral("yyyy-Q")) % (nextExpected.month() <= 3 ? '1' : (nextExpected.month() <= 6 ? '2' : (nextExpected.month() <= 9 ? '3' : '4'))); 2766 } else if (asColumn == QStringLiteral("d_DATESEMESTER")) { 2767 nextExpected = nextExpected.addMonths(nextExpected.month() * 6 - nextExpected.month()); // convert semester in month 2768 nextExpected = nextExpected.addMonths(6); 2769 nextExpectedString = nextExpected.toString(QStringLiteral("yyyy-S")) % (nextExpected.month() <= 6 ? '1' : '2'); 2770 } else if (asColumn == QStringLiteral("d_DATEYEAR")) { 2771 nextExpected = nextExpected.addYears(1); 2772 nextExpectedString = nextExpected.toString(dateFormat); 2773 } else { 2774 nextExpected = nextExpected.addDays(1); 2775 nextExpectedString = nextExpected.toString(dateFormat); 2776 } 2777 if (title != QStringLiteral("0000") && nextTitle != nextExpectedString && nextTitle != title) { 2778 int colindex = c + 1; 2779 if (forecast) { 2780 nextExpectedString += QStringLiteral("999"); 2781 } 2782 2783 int nbx = oTable.count(); 2784 oTable[0].insert(colindex, nextExpectedString); 2785 for (int j = 1; j < nbx; ++j) { 2786 oTable[j].insert(colindex, iMissingValue); 2787 } 2788 } 2789 } 2790 } 2791 // Correction bug 205466 ^^^ 2792 } 2793 2794 return err; 2795 } 2796 2797 SKGDocument::SKGModelTemplateList SKGDocument::getDisplaySchemas(const QString& iRealTable) const 2798 { 2799 SKGDocument::SKGModelTemplateList listSchema; 2800 2801 // Build schemas 2802 if (iRealTable == QStringLiteral("doctransaction")) { 2803 SKGModelTemplate def; 2804 def.id = QStringLiteral("default"); 2805 def.name = i18nc("Noun, the default value of an item", "Default"); 2806 def.icon = QStringLiteral("edit-undo"); 2807 def.schema = QStringLiteral("t_name;t_value;d_lastmodifdate;t_savestep"); 2808 listSchema.push_back(def); 2809 2810 SKGModelTemplate minimum; 2811 minimum.id = QStringLiteral("minimum"); 2812 minimum.name = i18nc("Noun, the minimum value of an item", "Minimum"); 2813 minimum.icon = QLatin1String(""); 2814 minimum.schema = QStringLiteral("t_name;t_value;d_lastmodifdate|N;t_savestep|N"); 2815 listSchema.push_back(minimum); 2816 } else if (iRealTable == QStringLiteral("parameters")) { 2817 SKGModelTemplate def; 2818 def.id = QStringLiteral("default"); 2819 def.name = i18nc("Noun, the default value of an item", "Default"); 2820 def.icon = QStringLiteral("edit-undo"); 2821 def.schema = QStringLiteral("t_name;t_value"); 2822 listSchema.push_back(def); 2823 } else if (iRealTable == QStringLiteral("node")) { 2824 SKGModelTemplate def; 2825 def.id = QStringLiteral("default"); 2826 def.name = i18nc("Noun, the default value of an item", "Default"); 2827 def.icon = QStringLiteral("edit-undo"); 2828 def.schema = QStringLiteral("t_name"); 2829 listSchema.push_back(def); 2830 } else { 2831 SKGModelTemplate def; 2832 def.id = QStringLiteral("default"); 2833 def.name = i18nc("Noun, the default value of an item", "Default"); 2834 def.icon = QStringLiteral("edit-undo"); 2835 def.schema = QLatin1String(""); 2836 SKGStringListList lines; 2837 executeSelectSqliteOrder("PRAGMA table_info(" % iRealTable % ");", lines); 2838 for (const auto& line : qAsConst(lines)) { 2839 if (!def.schema.isEmpty()) { 2840 def.schema += ';'; 2841 } 2842 def.schema += line[1]; 2843 } 2844 listSchema.push_back(def); 2845 } 2846 2847 return listSchema; 2848 } 2849 2850 QString SKGDocument::getDisplay(const QString& iString) const 2851 { 2852 QString output = iString.toLower(); 2853 2854 if (output.endsWith(QLatin1String("t_name"))) { 2855 output = i18nc("Noun, the name of an item", "Name"); 2856 } else if (output.endsWith(QLatin1String("d_date"))) { 2857 output = i18nc("Noun, the date of an item", "Date"); 2858 } else if (output.endsWith(QLatin1String("t_savestep"))) { 2859 output = i18nc("Verb, save a document", "Save"); 2860 } else if (output.endsWith(QLatin1String("t_value"))) { 2861 output = i18nc("Noun, the value of an item", "Value"); 2862 } else if (output.endsWith(QLatin1String("d_lastmodifdate"))) { 2863 output = i18nc("Noun, date of last modification", "Last modification"); 2864 } else if (output.startsWith(QLatin1String("p_"))) { 2865 output = iString; 2866 output = output.right(output.count() - 2); 2867 } else if (output.contains(QStringLiteral(".p_"))) { 2868 output = iString; 2869 output = output.replace(QStringLiteral(".p_"), QStringLiteral(".")); 2870 } else { 2871 output = iString; 2872 } 2873 return output; 2874 } 2875 2876 QString SKGDocument::getIconName(const QString& iString) const 2877 { 2878 QString output = iString.toLower(); 2879 if (output.startsWith(QLatin1String("p_")) || output.contains(QStringLiteral("p_"))) { 2880 return QStringLiteral("tag"); 2881 } 2882 return QLatin1String(""); 2883 } 2884 2885 QIcon SKGDocument::getIcon(const QString& iString) const 2886 { 2887 return SKGServices::fromTheme(getIconName(iString)); 2888 } 2889 2890 QString SKGDocument::getRealAttribute(const QString& iString) const 2891 { 2892 if (iString == iString.toLower()) { 2893 return iString; 2894 } 2895 return QLatin1String(""); 2896 } 2897 2898 SKGServices::AttributeType SKGDocument::getAttributeType(const QString& iAttributeName) const 2899 { 2900 SKGServices::AttributeType output = SKGServices::TEXT; 2901 if (iAttributeName.startsWith(QLatin1String("d_"))) { 2902 output = SKGServices::DATE; 2903 } else if (iAttributeName.startsWith(QLatin1String("i_"))) { 2904 output = SKGServices::INTEGER; 2905 } else if (iAttributeName.startsWith(QLatin1String("rd_")) || iAttributeName.startsWith(QLatin1String("rc_")) || iAttributeName.startsWith(QLatin1String("r_")) || iAttributeName.startsWith(QLatin1String("id_"))) { 2906 output = SKGServices::LINK; 2907 } else if (iAttributeName.startsWith(QLatin1String("f_"))) { 2908 output = SKGServices::FLOAT; 2909 } else if (iAttributeName.startsWith(QLatin1String("b_"))) { 2910 output = SKGServices::BLOB; 2911 } else if (iAttributeName == QStringLiteral("id")) { 2912 output = SKGServices::ID; 2913 } else if (iAttributeName == QStringLiteral("t_savestep") || iAttributeName == QStringLiteral("t_refreshviews")) { 2914 output = SKGServices::BOOL; 2915 } 2916 2917 return output; 2918 } 2919 2920 SKGServices::SKGUnitInfo SKGDocument::getUnit(const QString& iPrefixInCache) const 2921 { 2922 SKGServices::SKGUnitInfo output; 2923 output.Name = getCachedValue(iPrefixInCache % "UnitCache"); 2924 if (output.Name.isEmpty()) { 2925 refreshCache(QStringLiteral("unit")); 2926 output.Name = getCachedValue(iPrefixInCache % "UnitCache"); 2927 } 2928 output.Symbol = getCachedValue(iPrefixInCache % "UnitSymbolCache"); 2929 QString val = getCachedValue(iPrefixInCache % "UnitValueCache"); 2930 if (!val.isEmpty()) { 2931 output.Value = SKGServices::stringToDouble(val); 2932 } else { 2933 output.Value = 1; 2934 } 2935 val = getCachedValue(iPrefixInCache % "UnitDecimalCache"); 2936 if (!val.isEmpty()) { 2937 output.NbDecimal = SKGServices::stringToInt(val); 2938 } else { 2939 output.NbDecimal = 2; 2940 } 2941 2942 return output; 2943 } 2944 2945 QString SKGDocument::formatMoney(double iValue, const SKGServices::SKGUnitInfo& iUnit, bool iHtml) const 2946 { 2947 QString val = SKGServices::toCurrencyString(iValue / iUnit.Value, iUnit.Symbol, iUnit.NbDecimal); 2948 if (iHtml) { 2949 // Return value 2950 if (iValue < 0) { 2951 // Get std colors 2952 KColorScheme scheme(QPalette::Normal); 2953 return QStringLiteral("<font color=\"") % scheme.foreground(KColorScheme::NegativeText).color().name() % "\">" % SKGServices::stringToHtml(val) % "</font>"; 2954 } 2955 return SKGServices::stringToHtml(val); 2956 } 2957 return val; 2958 } 2959 2960 QString SKGDocument::formatPrimaryMoney(double iValue, int iForcedNbOfDigit) const 2961 { 2962 Q_UNUSED(iForcedNbOfDigit) 2963 return SKGServices::doubleToString(iValue); 2964 } 2965 2966 QString SKGDocument::formatSecondaryMoney(double iValue, int iForcedNbOfDigit)const 2967 { 2968 Q_UNUSED(iForcedNbOfDigit) 2969 return SKGServices::doubleToString(iValue); 2970 } 2971 2972 QString SKGDocument::formatPercentage(double iValue, bool iInvertColors) const 2973 { 2974 // Get std colors 2975 KColorScheme scheme(QPalette::Normal); 2976 QString negative = scheme.foreground(KColorScheme::NegativeText).color().name(); 2977 QString positive = scheme.foreground(KColorScheme::PositiveText).color().name(); 2978 2979 // Return value 2980 QString p = SKGServices::toPercentageString(iValue); 2981 if (iValue > 0) { 2982 p = '+' % p; 2983 } 2984 if (p.count() > 20 || std::isnan(iValue) || std::isinf(iValue)) { 2985 p = QChar(8734); 2986 } 2987 return "<font color=\"" % 2988 QString((iValue < 0 && !iInvertColors) || (iValue >= 0 && iInvertColors) ? negative : positive) % 2989 "\">" % SKGServices::stringToHtml(p) % 2990 "</font>"; 2991 } 2992 2993 QString SKGDocument::getFileExtension() const 2994 { 2995 return QStringLiteral("skgc"); 2996 } 2997 2998 QString SKGDocument::getDocumentHeader() const 2999 { 3000 return QStringLiteral("SKG"); 3001 } 3002 3003 void SKGDocument::addValueInCache(const QString& iKey, const QString& iValue) const 3004 { 3005 d->m_cache[iKey] = iValue; 3006 } 3007 3008 void SKGDocument::addSqlResultInCache(const QString& iKey, const SKGStringListList& iValue) const 3009 { 3010 d->m_mutex.lock(); 3011 (*d->m_cacheSql)[iKey] = iValue; 3012 d->m_mutex.unlock(); 3013 } 3014 3015 QString SKGDocument::getCachedValue(const QString& iKey) const 3016 { 3017 return d->m_cache.value(iKey); 3018 } 3019 3020 SKGStringListList SKGDocument::getCachedSqlResult(const QString& iKey) const 3021 { 3022 return d->m_cacheSql->value(iKey); 3023 } 3024 3025 void SKGDocument::refreshCache(const QString& iTable) const 3026 { 3027 Q_UNUSED(iTable) 3028 } 3029 3030 void SKGDocument::setBackupParameters(const QString& iPrefix, const QString& iSuffix) const 3031 { 3032 d->m_backupPrefix = iPrefix; 3033 d->m_backupSuffix = iSuffix; 3034 } 3035 3036 QString SKGDocument::getCurrentTemporaryFile() const 3037 { 3038 return d->m_temporaryFile; 3039 } 3040 3041 QString SKGDocument::getTemporaryFile(const QString& iFileName, bool iForceReadOnly) 3042 { 3043 QString output; 3044 QFileInfo fi(iFileName); 3045 QFileInfo di(fi.dir().path()); 3046 if (iForceReadOnly || !QUrl::fromUserInput(iFileName).isLocalFile() || !di.permission(QFile::WriteUser)) { 3047 output = QDir::tempPath(); 3048 } else { 3049 output = fi.absolutePath(); 3050 } 3051 return output += "/." % fi.fileName() % ".wrk"; 3052 } 3053 3054 QString SKGDocument::getBackupFile(const QString& iFileName) const 3055 { 3056 QString output; 3057 if (!d->m_backupPrefix.isEmpty() || !d->m_backupSuffix.isEmpty()) { 3058 QFileInfo fi(iFileName); 3059 if (d->m_backupSuffix.endsWith(QLatin1String(".skg"))) { 3060 output = d->m_backupPrefix % fi.baseName() % d->m_backupSuffix; 3061 } else { 3062 output = d->m_backupPrefix % fi.fileName() % d->m_backupSuffix; 3063 } 3064 output = output.replace(QStringLiteral("<DATE>"), SKGServices::dateToSqlString(QDateTime::currentDateTime().date())); 3065 output = output.replace(QStringLiteral("<TIME>"), SKGServices::timeToString(QDateTime::currentDateTime())); 3066 if (!QFileInfo(output).isAbsolute()) { 3067 output = fi.absolutePath() % '/' % output; 3068 } 3069 } 3070 3071 return output; 3072 } 3073 3074 SKGError SKGDocument::getObjects(const QString& iTable, const QString& iWhereClause, SKGObjectBase::SKGListSKGObjectBase& oListObject) const 3075 { 3076 SKGError err; 3077 3078 // Initialisation 3079 oListObject.clear(); 3080 3081 // Execute sqlorder 3082 SKGStringListList result; 3083 err = executeSelectSqliteOrder( 3084 QString("SELECT * FROM " % iTable % 3085 (!iWhereClause.isEmpty() ? QString(" WHERE " % iWhereClause) : QString())), 3086 result); 3087 3088 // Create output 3089 IFOK(err) { 3090 SKGStringListList::const_iterator itrow = result.constBegin(); 3091 QStringList columns = *(itrow); 3092 ++itrow; 3093 for (; !err && itrow != result.constEnd(); ++itrow) { 3094 QStringList values = *(itrow); 3095 SKGObjectBase tmp(const_cast<SKGDocument*>(this), iTable); 3096 err = tmp.setAttributes(columns, values); 3097 oListObject.push_back(tmp); 3098 } 3099 } 3100 return err; 3101 } 3102 3103 SKGError SKGDocument::existObjects(const QString& iTable, const QString& iWhereClause, bool& oExist) const 3104 { 3105 SKGError err; 3106 3107 // Initialisation 3108 oExist = false; 3109 3110 // Execute sqlorder 3111 SKGStringListList result; 3112 err = executeSelectSqliteOrder( 3113 "SELECT EXISTS(SELECT 1 FROM " % iTable % " WHERE " % 3114 (!iWhereClause.isEmpty() ? iWhereClause : QStringLiteral("1=1")) % ')', 3115 result); 3116 3117 // Create output 3118 IFOK(err) oExist = (result.at(1).at(0) == QStringLiteral("1")); 3119 return err; 3120 } 3121 3122 SKGError SKGDocument::getNbObjects(const QString& iTable, const QString& iWhereClause, int& oNbObjects) const 3123 { 3124 SKGError err; 3125 3126 // Initialisation 3127 oNbObjects = 0; 3128 3129 // Execute sqlorder 3130 SKGStringListList result; 3131 err = executeSelectSqliteOrder( 3132 QString("SELECT count(1) FROM " % iTable % 3133 (!iWhereClause.isEmpty() ? QString(" WHERE " % iWhereClause) : QString())), 3134 result); 3135 3136 // Create output 3137 IFOK(err) oNbObjects = SKGServices::stringToInt(result.at(1).at(0)); 3138 return err; 3139 } 3140 3141 SKGError SKGDocument::getObject(const QString& iTable, const QString& iWhereClause, SKGObjectBase& oObject) const 3142 { 3143 SKGObjectBase::SKGListSKGObjectBase temporaryResult; 3144 oObject.resetID(); 3145 SKGError err = SKGDocument::getObjects(iTable, iWhereClause, temporaryResult); 3146 IFOK(err) { 3147 int size = temporaryResult.size(); 3148 if (Q_UNLIKELY(size > 1)) { 3149 err = SKGError(ERR_INVALIDARG, i18nc("Error message: We expected only one object in the result, but got more", "More than one object returned in '%1' for '%2'", iTable, iWhereClause)); 3150 } else { 3151 if (Q_UNLIKELY(size == 0)) { 3152 err = SKGError(ERR_INVALIDARG, i18nc("Error message: We expected at least one object in the result, but got none", "No object returned in '%1' for '%2'", iTable, iWhereClause)); 3153 } else { 3154 oObject = *(temporaryResult.begin()); 3155 } 3156 } 3157 } 3158 return err; 3159 } 3160 3161 SKGError SKGDocument::getObject(const QString& iTable, int iId, SKGObjectBase& oObject) const 3162 { 3163 return getObject(iTable, "id=" % SKGServices::intToString(iId), oObject); 3164 } 3165 3166 SKGError SKGDocument::getTablesList(QStringList& oResult) const 3167 { 3168 return getDistinctValues(QStringLiteral("sqlite_master"), QStringLiteral("name"), 3169 QStringLiteral("type='table' AND name NOT LIKE 'sqlite_%'"), 3170 oResult); 3171 } 3172 3173 SKGError SKGDocument::getDistinctValues(const QString& iTable, const QString& iAttribute, const QString& iWhereClause, QStringList& oResult) const 3174 { 3175 SKGError err; 3176 _SKGTRACEINFUNCRC(10, err) 3177 // initialisation 3178 oResult.clear(); 3179 3180 // Search 3181 SKGStringListList temporaryResult; 3182 err = executeSelectSqliteOrder( 3183 "SELECT DISTINCT " % iAttribute % 3184 " FROM " % iTable % " WHERE (" % 3185 (!iWhereClause.isEmpty() ? iWhereClause : QStringLiteral("1=1")) % 3186 ") ORDER BY " % iAttribute 3187 // Correction bug 202167 vvv 3188 % " COLLATE NOCASE" 3189 // Correction bug 202167 ^^^ 3190 , temporaryResult); 3191 IFOK(err) { 3192 SKGStringListList::const_iterator it = temporaryResult.constBegin(); 3193 ++it; // to forget column name 3194 for (; it != temporaryResult.constEnd(); ++it) { 3195 oResult.push_back(*(it->constBegin())); 3196 } 3197 } 3198 3199 return err; 3200 } 3201 3202 SKGError SKGDocument::getDistinctValues(const QString& iTable, const QString& iAttribute, QStringList& oResult) const 3203 { 3204 return getDistinctValues(iTable, iAttribute, 3205 iAttribute % " IS NOT NULL AND " % iAttribute % "!=''", 3206 oResult); 3207 } 3208 3209 SKGError SKGDocument::executeSqliteOrder(const QString& iSqlOrder, int* iLastId) const 3210 { 3211 SKGError err; 3212 _SKGTRACEINFUNCRC(10, err) 3213 if (getMainDatabase() == nullptr) { 3214 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 3215 } else { 3216 err = SKGServices::executeSqliteOrder(*getMainDatabase(), iSqlOrder, iLastId); 3217 } 3218 return err; 3219 } 3220 3221 SKGError SKGDocument::executeSqliteOrder(const QString& iSqlOrder) const 3222 { 3223 return SKGDocument::executeSqliteOrder(iSqlOrder, nullptr); 3224 } 3225 3226 SKGError SKGDocument::executeSqliteOrders(const QStringList& iSqlOrders) const 3227 { 3228 SKGError err; 3229 _SKGTRACEINFUNCRC(10, err) 3230 if (getMainDatabase() == nullptr) { 3231 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 3232 } else { 3233 err = SKGServices::executeSqliteOrders(*getMainDatabase(), iSqlOrders); 3234 } 3235 return err; 3236 } 3237 3238 SKGError SKGDocument::executeSqliteOrder(const QString& iSqlOrder, const QMap< QString, QVariant >& iBind, int* iLastId) const 3239 { 3240 SKGError err; 3241 _SKGTRACEINFUNCRC(10, err) 3242 if (getMainDatabase() == nullptr) { 3243 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 3244 } else { 3245 err = SKGServices::executeSqliteOrder(*getMainDatabase(), iSqlOrder, iBind, iLastId); 3246 } 3247 return err; 3248 } 3249 3250 SKGError SKGDocument::dumpSelectSqliteOrder(const QString& iSqlOrder, QTextStream* oStream, SKGServices::DumpMode iMode) const 3251 { 3252 SKGError err; 3253 _SKGTRACEINFUNCRC(10, err) 3254 if (getMainDatabase() == nullptr) { 3255 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 3256 } else { 3257 err = SKGServices::dumpSelectSqliteOrder(*getMainDatabase(), iSqlOrder, oStream, iMode); 3258 } 3259 return err; 3260 } 3261 3262 SKGError SKGDocument::dumpSelectSqliteOrder(const QString& iSqlOrder, QString& oResult, SKGServices::DumpMode iMode) const 3263 { 3264 SKGError err; 3265 _SKGTRACEINFUNCRC(10, err) 3266 if (getMainDatabase() == nullptr) { 3267 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 3268 } else { 3269 err = SKGServices::dumpSelectSqliteOrder(*getMainDatabase(), iSqlOrder, oResult, iMode); 3270 } 3271 return err; 3272 } 3273 3274 SKGError SKGDocument::dumpSelectSqliteOrder(const QString& iSqlOrder, QStringList& oResult, SKGServices::DumpMode iMode) const 3275 { 3276 SKGError err; 3277 _SKGTRACEINFUNCRC(10, err) 3278 if (getMainDatabase() == nullptr) { 3279 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 3280 } else { 3281 err = SKGServices::dumpSelectSqliteOrder(*getMainDatabase(), iSqlOrder, oResult, iMode); 3282 } 3283 return err; 3284 } 3285 3286 SKGError SKGDocument::executeSingleSelectSqliteOrder(const QString& iSqlOrder, QString& oResult, bool iUseCache) const 3287 { 3288 SKGError err; 3289 _SKGTRACEINFUNCRC(10, err) 3290 // Is the select in cache? 3291 if (iUseCache && d->m_cache.contains(iSqlOrder)) { 3292 // Yes => get the value 3293 oResult = getCachedValue(iSqlOrder); 3294 SKGTRACEL(10) << "Result retrieved from cache for:" << iSqlOrder << SKGENDL; 3295 3296 } else { 3297 // No => Run the select 3298 oResult.clear(); 3299 double elapse = SKGServices::getMicroTime(); 3300 if (getMainDatabase() == nullptr) { 3301 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 3302 } else { 3303 err = SKGServices::executeSingleSelectSqliteOrder(*getMainDatabase(), iSqlOrder, oResult); 3304 } 3305 3306 // Is the execution time too long 3307 if (SKGServices::getMicroTime() - elapse > 50) { 3308 // Yes => put the result in cache 3309 addValueInCache(iSqlOrder, oResult); 3310 } 3311 } 3312 return err; 3313 } 3314 3315 SKGError SKGDocument::executeSelectSqliteOrder(const QString& iSqlOrder, SKGStringListList& oResult, bool iUseCache) const 3316 { 3317 SKGError err; 3318 _SKGTRACEINFUNCRC(10, err) 3319 // Is the select in cache? 3320 if (iUseCache && d->m_cacheSql->contains(iSqlOrder)) { 3321 // Yes => get the value 3322 oResult = getCachedSqlResult(iSqlOrder); 3323 SKGTRACEL(10) << "Result retrieved from cache for:" << iSqlOrder << SKGENDL; 3324 3325 } else { 3326 // No => Run the select 3327 oResult.clear(); 3328 double elapse = SKGServices::getMicroTime(); 3329 if (getMainDatabase() == nullptr) { 3330 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 3331 } else { 3332 err = SKGServices::executeSelectSqliteOrder(*getMainDatabase(), iSqlOrder, oResult); 3333 } 3334 3335 // Is the execution time too long 3336 if (SKGServices::getMicroTime() - elapse > 50) { 3337 // Yes => put the result in cache 3338 addSqlResultInCache(iSqlOrder, oResult); 3339 } 3340 } 3341 return err; 3342 } 3343 3344 void SKGDocument::concurrentExecuteSelectSqliteOrder(const QString& iSqlOrder, const FuncSelect& iFunction, bool iExecuteInMainThread) const 3345 { 3346 // Is the select in cache? 3347 if (d->m_cacheSql->contains(iSqlOrder)) { 3348 // Yes => get the value and call the function 3349 iFunction(getCachedSqlResult(iSqlOrder)); 3350 SKGTRACEL(10) << "Result retrieved from cache for:" << iSqlOrder << SKGENDL; 3351 3352 } else { 3353 // No => Run the select asynchronously 3354 // Search a watcher free 3355 QFutureWatcher<SKGStringListList>* watcher = nullptr; 3356 if (iExecuteInMainThread) { 3357 for (auto w : qAsConst(d->m_watchers)) { 3358 if (w->isFinished()) { 3359 watcher = w; 3360 break; 3361 } 3362 } 3363 if (watcher == nullptr) { 3364 d->m_watchers.push_back(new QFutureWatcher<SKGStringListList>()); 3365 watcher = d->m_watchers.at(d->m_watchers.count() - 1); 3366 } 3367 3368 watcher->disconnect(); 3369 connect(watcher, &QFutureWatcherBase::finished, this, [ = ] { 3370 auto w = dynamic_cast< QFutureWatcher<SKGStringListList>* >(sender()); 3371 iFunction(w->result()); 3372 }); 3373 } 3374 3375 // Launch in another thread the select 3376 auto future = QtConcurrent::run([ = ] { 3377 auto tmpDatabase = this->getThreadDatabase(); 3378 double elapse = SKGServices::getMicroTime(); 3379 SKGStringListList listTmp; 3380 SKGServices::executeSelectSqliteOrder(tmpDatabase, iSqlOrder, listTmp); 3381 3382 // Is the execution time too long 3383 if (SKGServices::getMicroTime() - elapse > 50) 3384 { 3385 // Yes => put the result in cache 3386 addSqlResultInCache(iSqlOrder, listTmp); 3387 } 3388 if (!iExecuteInMainThread) 3389 { 3390 iFunction(listTmp); 3391 } 3392 return listTmp; 3393 }); 3394 if (watcher != nullptr) { 3395 watcher->setFuture(future); 3396 } 3397 } 3398 } 3399 3400 void SKGDocument::concurrentExistObjects(const QString& iTable, const QString& iWhereClause, const FuncExist& iFunction, bool iExecuteInMainThread) 3401 { 3402 // Execute sqlorder 3403 concurrentExecuteSelectSqliteOrder("SELECT EXISTS(SELECT 1 FROM " % iTable % " WHERE " % (!iWhereClause.isEmpty() ? iWhereClause : QStringLiteral("1=1")) % ')', 3404 [ = ](const SKGStringListList & iResult) { 3405 iFunction(iResult.count() > 0 && (iResult.at(1).at(0) == QStringLiteral("1"))); 3406 }, iExecuteInMainThread); 3407 } 3408 3409 SKGError SKGDocument::getAttributesDescription(const QString& iTable, SKGServices::SKGAttributesList& oResult) const 3410 { 3411 SKGError err; 3412 _SKGTRACEINFUNCRC(10, err) 3413 // initialisation 3414 oResult.clear(); 3415 3416 // Search 3417 SKGStringListList temporaryResult; 3418 err = this->executeSelectSqliteOrder("PRAGMA table_info( " % iTable % " );", temporaryResult); 3419 IFOK(err) { 3420 int nblines = temporaryResult.count(); 3421 QString realTable = SKGServices::getRealTable(iTable); 3422 3423 for (int i = 1; i < nblines; ++i) { // the first one is ignored because it is the headers 3424 QStringList line = temporaryResult.at(i); 3425 3426 SKGServices::SKGAttributeInfo attribute; 3427 attribute.name = line[1]; 3428 3429 QString attname = realTable % '.' % attribute.name; 3430 attribute.display = getDisplay(attname); 3431 if (attribute.display == attname) { 3432 attribute.display = QLatin1String(""); 3433 } 3434 attribute.icon = getIcon(attname); 3435 attribute.type = getAttributeType(attribute.name); 3436 attribute.notnull = (line[3] == QStringLiteral("0")); 3437 attribute.defaultvalue = line[4]; 3438 oResult.push_back(attribute); 3439 } 3440 } 3441 3442 return err; 3443 } 3444 3445 SKGError SKGDocument::getAttributesList(const QString& iTable, QStringList& oResult) const 3446 { 3447 SKGError err; 3448 _SKGTRACEINFUNCRC(10, err) 3449 oResult.clear(); 3450 SKGServices::SKGAttributesList attDesc; 3451 err = getAttributesDescription(iTable, attDesc); 3452 int nblines = attDesc.count(); 3453 for (int i = 0; !err && i < nblines; ++i) { 3454 oResult.push_back(attDesc.at(i).name); 3455 } 3456 return err; 3457 } 3458 3459 SKGReport* SKGDocument::getReport() const 3460 { 3461 return new SKGReport(const_cast<SKGDocument*>(this)); 3462 } 3463 3464 SKGError SKGDocument::copyToJson(QString& oDocument) const 3465 { 3466 SKGError err; 3467 SKGTRACEINFUNCRC(10, err) 3468 QVariantMap doc; 3469 3470 // Copy the tables 3471 QVariantList list; 3472 QStringList listTables = getMainDatabase()->tables(); 3473 int nb = listTables.count(); 3474 for (int i = 0; !err && i < nb; ++i) { 3475 const QString& tableName = listTables.at(i); 3476 if (Q_UNLIKELY(!tableName.startsWith(QLatin1String("sqlite_")) && !tableName.startsWith(QLatin1String("vm_")))) { 3477 list.clear(); 3478 3479 SKGStringListList listRows; 3480 if (getMainDatabase() == nullptr) { 3481 err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined")); 3482 } else { 3483 err = SKGServices::executeSelectSqliteOrder(*getMainDatabase(), "SELECT * FROM " % tableName, listRows); 3484 } 3485 int nbRows = listRows.count(); 3486 if (Q_LIKELY(nbRows)) { 3487 QVariantMap item; 3488 const QStringList& titles = listRows.at(0); 3489 for (int j = 1; !err && j < nbRows; ++j) { // Forget title 3490 const QStringList& values = listRows.at(j); 3491 3492 int nbVals = values.count(); 3493 for (int k = 0; k < nbVals; ++k) { 3494 const QString& t = titles.at(k); 3495 SKGServices::AttributeType type = getAttributeType(t); 3496 if (type == SKGServices::ID || type == SKGServices::INTEGER || type == SKGServices::LINK) { 3497 item.insert(t, SKGServices::stringToInt(values.at(k))); 3498 } else if (type == SKGServices::FLOAT) { 3499 item.insert(t, SKGServices::stringToDouble(values.at(k))); 3500 } else if (type == SKGServices::BOOL) { 3501 item.insert(t, values.at(k) == QStringLiteral("Y")); 3502 } else { 3503 item.insert(t, values.at(k)); 3504 } 3505 } 3506 3507 list << item; 3508 } 3509 } 3510 doc.insert(tableName, list); 3511 } 3512 } 3513 3514 QJsonDocument serializer = QJsonDocument::fromVariant(doc); 3515 oDocument = serializer.toJson(QJsonDocument::Compact); 3516 return err; 3517 } 3518 3519 Q_DECLARE_METATYPE(sqlite3*) // NOLINT(readability/casting) 3520 Q_DECLARE_OPAQUE_POINTER(sqlite3*) // NOLINT(readability/casting)