File indexing completed on 2024-04-28 16:30:32
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 defines classes SKGObjectBase. 0008 * 0009 * @author Stephane MANKOWSKI / Guillaume DE BURE 0010 */ 0011 #include "skgobjectbase.h" 0012 0013 #include <qsqldatabase.h> 0014 #include "qregularexpression.h" 0015 0016 #include <klocalizedstring.h> 0017 #include <kstringhandler.h> 0018 0019 #include <algorithm> 0020 0021 #include "skgdocument.h" 0022 #include "skgnamedobject.h" 0023 #include "skgservices.h" 0024 #include "skgtraces.h" 0025 0026 /** 0027 * This private class of SKGObjectBase 0028 */ 0029 class SKGObjectBasePrivate 0030 { 0031 public: 0032 /** 0033 * internal id 0034 */ 0035 int id = 0; 0036 0037 /** 0038 * internal table 0039 */ 0040 QString table; 0041 0042 /** 0043 * internal document 0044 */ 0045 SKGDocument* document = nullptr; 0046 0047 /** 0048 * internal attributes 0049 */ 0050 SKGQStringQStringMap attributes; 0051 0052 /** 0053 * objects to save 0054 */ 0055 SKGObjectBase::SKGListSKGObjectBase objects; 0056 }; 0057 0058 SKGObjectBase::SKGObjectBase() : SKGObjectBase(nullptr) 0059 {} 0060 0061 SKGObjectBase::SKGObjectBase(SKGDocument* iDocument, const QString& iTable, int iID) 0062 : d(new SKGObjectBasePrivate) 0063 { 0064 d->id = iID; 0065 d->table = iTable; 0066 d->document = iDocument; 0067 if (Q_LIKELY(d->id != 0)) { 0068 load(); 0069 } 0070 } 0071 0072 SKGObjectBase::~SKGObjectBase() 0073 { 0074 delete d; 0075 } 0076 0077 SKGObjectBase::SKGObjectBase(const SKGObjectBase& iObject) 0078 : d(new SKGObjectBasePrivate) 0079 { 0080 copyFrom(iObject); 0081 } 0082 0083 SKGObjectBase::SKGObjectBase(SKGObjectBase&& iObject) noexcept 0084 : d(iObject.d) 0085 { 0086 iObject.d = nullptr; 0087 } 0088 0089 SKGObjectBase& SKGObjectBase::operator= (const SKGObjectBase& iObject) 0090 { 0091 copyFrom(iObject); 0092 return *this; 0093 } 0094 0095 bool SKGObjectBase::operator==(const SKGObjectBase& iObject) const 0096 { 0097 return (getUniqueID() == iObject.getUniqueID()); 0098 } 0099 0100 bool SKGObjectBase::operator!=(const SKGObjectBase& iObject) const 0101 { 0102 return !(*this == iObject); 0103 } 0104 0105 bool SKGObjectBase::operator<(const SKGObjectBase& iObject) const 0106 { 0107 double d1 = SKGServices::stringToDouble(getAttribute(QStringLiteral("f_sortorder"))); 0108 double d2 = SKGServices::stringToDouble(iObject.getAttribute(QStringLiteral("f_sortorder"))); 0109 return (d1 < d2); 0110 } 0111 0112 bool SKGObjectBase::operator>(const SKGObjectBase& iObject) const 0113 { 0114 double d1 = SKGServices::stringToDouble(getAttribute(QStringLiteral("f_sortorder"))); 0115 double d2 = SKGServices::stringToDouble(iObject.getAttribute(QStringLiteral("f_sortorder"))); 0116 return (d1 > d2); 0117 } 0118 0119 QString SKGObjectBase::getUniqueID() const 0120 { 0121 return SKGServices::intToString(d->id) % '-' % getRealTable(); 0122 } 0123 0124 int SKGObjectBase::getID() const 0125 { 0126 return d->id; 0127 } 0128 0129 QString SKGObjectBase::getAttributeFromView(const QString& iView, const QString& iName) const 0130 { 0131 QString output; 0132 0133 SKGStringListList result; 0134 QString wc = getWhereclauseId(); 0135 if (wc.isEmpty()) { 0136 wc = "id=" % SKGServices::intToString(d->id); 0137 } 0138 QString sql = "SELECT " % iName % " FROM " % iView % " WHERE " % wc; 0139 if (getDocument() != nullptr) { 0140 getDocument()->executeSelectSqliteOrder(sql, result, false); 0141 } 0142 if (result.count() == 2) { 0143 output = result.at(1).at(0); 0144 } 0145 0146 return output; 0147 } 0148 0149 QString SKGObjectBase::getDisplayName() const 0150 { 0151 return getAttributeFromView("v_" % getRealTable() % "_displayname", QStringLiteral("t_displayname")); 0152 } 0153 0154 void SKGObjectBase::copyFrom(const SKGObjectBase& iObject) 0155 { 0156 if (d == nullptr) { 0157 d = new SKGObjectBasePrivate; 0158 } 0159 d->id = iObject.d->id; 0160 d->table = iObject.d->table; 0161 d->document = iObject.d->document; 0162 d->attributes = iObject.d->attributes; 0163 } 0164 0165 SKGObjectBase SKGObjectBase::cloneInto() 0166 { 0167 return cloneInto(nullptr); 0168 } 0169 0170 SKGObjectBase SKGObjectBase::cloneInto(SKGDocument* iDocument) 0171 { 0172 SKGDocument* targetDocument = iDocument; 0173 if (targetDocument == nullptr) { 0174 targetDocument = d->document; 0175 } 0176 0177 SKGObjectBase output; 0178 output.copyFrom(*this); 0179 output.d->id = 0; 0180 output.d->document = targetDocument; 0181 return output; 0182 } 0183 0184 SKGError SKGObjectBase::resetID() 0185 { 0186 d->id = 0; 0187 return SKGError(); 0188 } 0189 0190 QString SKGObjectBase::getTable() const 0191 { 0192 return d->table; 0193 } 0194 0195 QString SKGObjectBase::getRealTable() const 0196 { 0197 return SKGServices::getRealTable(d->table); 0198 } 0199 0200 SKGDocument* SKGObjectBase::getDocument() const 0201 { 0202 return d->document; 0203 } 0204 0205 SKGQStringQStringMap SKGObjectBase::getAttributes() const 0206 { 0207 return d->attributes; 0208 } 0209 0210 int SKGObjectBase::getNbAttributes() const 0211 { 0212 return d->attributes.count(); 0213 } 0214 0215 SKGError SKGObjectBase::setAttributes(const QStringList& iNames, const QStringList& iValues) 0216 { 0217 SKGError err; 0218 int nb = iNames.size(); 0219 for (int i = 0; !err && i < nb; ++i) { 0220 const auto& att = iNames.at(i); 0221 const auto& val = iValues.at(i); 0222 0223 if (Q_LIKELY(att != QStringLiteral("id"))) { 0224 err = setAttribute(att, val); 0225 } else { 0226 d->id = SKGServices::stringToInt(val); 0227 } 0228 } 0229 return err; 0230 } 0231 0232 SKGError SKGObjectBase::setAttribute(const QString& iName, const QString& iValue) 0233 { 0234 SKGError err; 0235 if (Q_LIKELY(iValue != NOUPDATE)) { 0236 QString val = iValue; 0237 0238 // Case modification on marked document 0239 if (this->getRealTable() == this->getTable() && iName != iName.toLower()) { 0240 QString realAttribute = d->document->getRealAttribute(iName); 0241 if (!realAttribute.isEmpty()) { 0242 QStringList l = realAttribute.split('.'); 0243 if (l.size() == 3) { 0244 SKGObjectBase obj; 0245 QString refId = getAttribute(l.at(1)); 0246 if (!refId.isEmpty()) { 0247 err = getDocument()->getObject("v_" % l.at(0), "id=" % refId, obj); 0248 IFOK(err) { 0249 err = obj.setAttribute(l.at(2), iValue); 0250 d->objects.push_back(obj); 0251 } 0252 } 0253 } 0254 } 0255 } else if (iValue.startsWith(QLatin1String("="))) { 0256 // Case modificator 0257 QString op = iValue.right(iValue.length() - 1).toLower(); 0258 val = d->attributes[iName]; 0259 if (op == i18nc("Key word to modify a string into a field", "lower")) { 0260 val = val.toLower(); 0261 } else if (op == i18nc("Key word to modify a string into a field", "upper")) { 0262 val = val.toUpper(); 0263 } else if (op == i18nc("Key word to modify a string into a field", "capwords")) { 0264 val = KStringHandler::capwords(val.toLower()); 0265 } else if (op == i18nc("Key word to modify a string into a field", "capitalize")) { 0266 val = val.at(0).toUpper() % val.right(val.length() - 1).toLower(); 0267 } else if (op == i18nc("Key word to modify a string into a field", "trim")) { 0268 val = val.trimmed(); 0269 } else { 0270 val = iValue; 0271 } 0272 } 0273 d->attributes[iName] = val; 0274 } 0275 return err; 0276 } 0277 0278 QString SKGObjectBase::getAttribute(const QString& iName) const 0279 { 0280 QString output; 0281 if (Q_LIKELY(d->attributes.contains(iName))) { 0282 output = d->attributes[iName]; 0283 } else if (iName == QStringLiteral("id")) { 0284 output = SKGServices::intToString(getID()); 0285 } if (iName.startsWith(QLatin1String("p_"))) { 0286 output = getProperty(iName.right(iName.length() - 2)); 0287 } else { 0288 // Is the iName a number ? 0289 bool ok; 0290 int pos = iName.toInt(&ok); 0291 if (ok) { 0292 // What is the key corresponding to this name ? 0293 QStringList keys = d->attributes.keys(); 0294 std::sort(keys.begin(), keys.end()); 0295 if (pos >= 0 && pos < keys.count()) { 0296 output = d->attributes[keys.at(pos)]; 0297 } 0298 } else { 0299 // Case modification on marked document iName1.(iTable2)iName2 or iName1.iName2 0300 int pos = iName.indexOf('.'); 0301 if (pos != -1) { 0302 QString attributeref = iName.left(pos); 0303 QString refId = getAttribute(attributeref); 0304 if (!refId.isEmpty()) { 0305 QString rest = iName.right(iName.length() - pos - 1); 0306 0307 QString table; 0308 QString att; 0309 auto rx = QRegularExpression(QStringLiteral("^\\(([^\\)]*)\\)(.*)$")).match(rest); 0310 if (rx.hasMatch()) { 0311 // Case iName1.(iTable2)iName2 0312 table = rx.captured(1); 0313 att = rx.captured(2); 0314 } else { 0315 auto rx2 = QRegularExpression(QStringLiteral("^.*_(.*)_.*$")).match(attributeref); 0316 if (rx2.hasMatch()) { 0317 // Case iName1.iName2 0318 table = "v_" % rx2.captured(1); 0319 att = rest; 0320 } 0321 } 0322 0323 SKGObjectBase obj; 0324 SKGError err = getDocument()->getObject(table, "id=" % refId, obj); 0325 IFOK(err) { 0326 output = obj.getAttribute(att); 0327 } 0328 } 0329 } 0330 } 0331 } 0332 0333 return output; 0334 } 0335 0336 bool SKGObjectBase::exist() const 0337 { 0338 SKGTRACEINFUNC(20) 0339 0340 SKGStringListList result; 0341 QString wc = getWhereclauseId(); 0342 if (wc.isEmpty() && d->id != 0) { 0343 wc = "id=" % SKGServices::intToString(d->id); 0344 } 0345 if (wc.isEmpty()) { 0346 return false; 0347 } 0348 0349 QString sql = "SELECT count(1) FROM " % d->table % " WHERE " % wc; 0350 if (getDocument() != nullptr) { 0351 getDocument()->executeSelectSqliteOrder(sql, result, false); 0352 } 0353 return (result.size() >= 2 && result.at(1).at(0) != QStringLiteral("0")); 0354 } 0355 0356 SKGError SKGObjectBase::load() 0357 { 0358 SKGError err; 0359 SKGTRACEINFUNCRC(20, err) 0360 0361 if (Q_LIKELY(getDocument() && !getTable().isEmpty())) { 0362 // Prepare where clause 0363 QString wc = getWhereclauseId(); 0364 if (wc.isEmpty()) { 0365 wc = "id=" % SKGServices::intToString(d->id); 0366 } 0367 0368 // Execute sql order 0369 SKGStringListList result; 0370 err = getDocument()->executeSelectSqliteOrder("SELECT * FROM " % d->table % " WHERE " % wc, result, false); 0371 IFOK(err) { 0372 int size = result.size(); 0373 if (Q_UNLIKELY(size == 1)) { 0374 err = SKGError(ERR_INVALIDARG, i18nc("Error message: Could not load something because it is not in the database", "Load of '%1' with '%2' failed because it was not found in the database", d->table, wc)); 0375 } else if (Q_UNLIKELY(size != 2)) { 0376 err = SKGError(ERR_INVALIDARG, i18np("Load of '%2' with '%3' failed because of bad size of result (found one object)", 0377 "Load of '%2' with '%3' failed because of bad size of result (found %1 objects)", 0378 size - 1, d->table, wc)); 0379 } else { 0380 SKGStringListList::const_iterator itrow = result.constBegin(); 0381 QStringList columns = *(itrow); 0382 ++itrow; 0383 QStringList values = *(itrow); 0384 err = setAttributes(columns, values); 0385 } 0386 } 0387 } 0388 return err; 0389 } 0390 0391 QString SKGObjectBase::getWhereclauseId() const 0392 { 0393 int id = getID(); 0394 if (id != 0) { 0395 return "id=" % SKGServices::intToString(id); 0396 } 0397 return QLatin1String(""); 0398 } 0399 0400 SKGError SKGObjectBase::save(bool iInsertOrUpdate, bool iReloadAfterSave) 0401 { 0402 SKGError err; 0403 SKGTRACEINFUNCRC(20, err) 0404 0405 if (Q_UNLIKELY(!d->document)) { 0406 err = SKGError(ERR_POINTER, i18nc("Error message", "Transaction impossible because the document is missing")); 0407 } else { 0408 // Save linking objects 0409 int nb = d->objects.count(); 0410 for (int i = 0; !err && i < nb; ++i) { 0411 SKGObjectBase ref = d->objects.at(i); 0412 err = ref.save(iInsertOrUpdate, iReloadAfterSave); 0413 } 0414 0415 // Check if we are in a transaction 0416 IFOKDO(err, d->document->checkExistingTransaction()) 0417 IFOK(err) { 0418 // Table to use 0419 QString tablename = getRealTable(); 0420 0421 // Build order 0422 QString part1Insert; 0423 QString part2Insert; 0424 QString partUpdate; 0425 0426 SKGQStringQStringMap::const_iterator it; 0427 for (it = d->attributes.constBegin() ; it != d->attributes.constEnd(); ++it) { 0428 QString att = SKGServices::stringToSqlString(it.key()); 0429 QString attlower = att.toLower(); 0430 if (att.length() > 2 && att == attlower) { // We must ignore attributes coming from views 0431 QString value = '\'' % SKGServices::stringToSqlString(it.value()) % '\''; 0432 0433 if (!part1Insert.isEmpty()) { 0434 part1Insert.append(','); 0435 part2Insert.append(','); 0436 partUpdate.append(','); 0437 } 0438 // Attribute 0439 part1Insert.append('\'' % att % '\''); 0440 0441 // Value 0442 part2Insert.append(value); 0443 0444 // Attribute=Value for update 0445 partUpdate.append(att % '=' % value); 0446 } 0447 } 0448 0449 // We try an Insert 0450 if (d->id == 0) { 0451 // We have to try un insert 0452 err = getDocument()->executeSqliteOrder("INSERT INTO " % tablename % " (" % part1Insert % ") VALUES (" % part2Insert % ')', &(d->id)); 0453 } else { 0454 // We must try an update 0455 err = SKGError(ERR_ABORT, QLatin1String("")); // Just to go in UPDATE code 0456 } 0457 0458 if (err && iInsertOrUpdate) { 0459 // INSERT failed, could we try an update ? 0460 QString wc = this->getWhereclauseId(); 0461 if (!wc.isEmpty()) { 0462 // Yes ==> Update 0463 err = getDocument()->executeSqliteOrder("UPDATE " % tablename % " SET " % partUpdate % " WHERE " % wc); 0464 } 0465 } 0466 } 0467 } 0468 0469 // Reload object is updated 0470 if (!err && iReloadAfterSave) { 0471 // The object has been updated ==>load 0472 err = load(); 0473 } 0474 0475 return err; 0476 } 0477 0478 SKGError SKGObjectBase::remove(bool iSendMessage, bool iForce) const 0479 { 0480 SKGError err; 0481 SKGTRACEINFUNCRC(20, err) 0482 if (Q_UNLIKELY(!d->document)) { 0483 err = SKGError(ERR_POINTER, i18nc("Error message", "Transaction impossible because the document is missing")); 0484 } else { 0485 // Check if we are in a transaction 0486 err = d->document->checkExistingTransaction(); 0487 0488 // delete order 0489 QString viewForDelete = QStringLiteral("v_") % getRealTable() % "_delete"; 0490 0491 // Check if the delete view exist 0492 SKGStringListList temporaryResult; 0493 d->document->executeSelectSqliteOrder("PRAGMA table_info( " % viewForDelete % " );", temporaryResult); 0494 if (!iForce && temporaryResult.count() > 1) { // At least one attribute 0495 // Delete view exists, check if the delete is authorized 0496 err = d->document->executeSelectSqliteOrder("SELECT t_delete_message FROM " % viewForDelete % " WHERE id=" % SKGServices::intToString(d->id), temporaryResult, false); 0497 IFOK(err) { 0498 QString msg; 0499 if (temporaryResult.count() > 1) { 0500 msg = temporaryResult.at(1).at(0); 0501 } 0502 // Should the string below be translated ??? It contains no word 0503 if (!msg.isEmpty()) { 0504 err = SKGError(ERR_FORCEABLE, i18nc("Error message for an object", "'%1': %2", getDisplayName(), msg)); 0505 } 0506 } 0507 } 0508 0509 QString displayname = getDisplayName(); // Must be done before the delete order 0510 IFOKDO(err, d->document->executeSqliteOrder("DELETE FROM " % getRealTable() % " WHERE id=" % SKGServices::intToString(d->id))) 0511 if (iSendMessage && !err && !displayname.isEmpty()) { 0512 err = d->document->sendMessage(i18nc("An information to the user that something was deleted", "'%1' has been deleted", displayname), SKGDocument::Hidden); 0513 } 0514 } 0515 0516 return err; 0517 } 0518 0519 SKGError SKGObjectBase::dump() const 0520 { 0521 // dump 0522 SKGTRACE << "=== START DUMP [" << getUniqueID() << "]===" << SKGENDL; 0523 SKGQStringQStringMap::const_iterator it; 0524 for (it = d->attributes.constBegin() ; it != d->attributes.constEnd(); ++it) { 0525 SKGTRACE << it.key() << "=[" << it.value() << ']' << SKGENDL; 0526 } 0527 SKGTRACE << "=== END DUMP [" << getUniqueID() << "]===" << SKGENDL; 0528 return SKGError(); 0529 } 0530 0531 QStringList SKGObjectBase::getProperties() const 0532 { 0533 return Q_UNLIKELY(!getDocument()) ? QStringList() : getDocument()->getParameters(getUniqueID()); 0534 } 0535 0536 QString SKGObjectBase::getProperty(const QString& iName) const 0537 { 0538 return Q_UNLIKELY(!getDocument()) ? QString() : getDocument()->getParameter(iName, getUniqueID()); 0539 } 0540 0541 SKGObjectBase SKGObjectBase::getPropertyObject(const QString& iName) const 0542 { 0543 SKGObjectBase property; 0544 if (getDocument() != nullptr) { 0545 getDocument()->getObject(QStringLiteral("parameters"), "t_name='" % SKGServices::stringToSqlString(iName) % 0546 "' AND t_uuid_parent='" % SKGServices::stringToSqlString(getUniqueID()) % '\'', property); 0547 } 0548 return property; 0549 } 0550 0551 QVariant SKGObjectBase::getPropertyBlob(const QString& iName) const 0552 { 0553 return Q_UNLIKELY(!getDocument()) ? QVariant() : getDocument()->getParameterBlob(iName, getUniqueID()); 0554 } 0555 0556 SKGError SKGObjectBase::setProperty(const QString& iName, const QString& iValue, const QString& iFileName, SKGPropertyObject* oObjectCreated) const 0557 { 0558 SKGError err = Q_UNLIKELY(!getDocument()) ? SKGError() : getDocument()->setParameter(iName, iValue, iFileName, getUniqueID(), oObjectCreated); 0559 0560 // Send message 0561 IFOKDO(err, Q_UNLIKELY(!getDocument()) ? SKGError() : getDocument()->sendMessage(i18nc("An information to the user", "The property '%1=%2' has been added on '%3'", iName, iValue, getDisplayName()), SKGDocument::Hidden)) 0562 0563 return err; 0564 } 0565 0566 SKGError SKGObjectBase::setProperty(const QString& iName, const QString& iValue, const QVariant& iBlob, SKGPropertyObject* oObjectCreated) const 0567 { 0568 SKGError err = Q_UNLIKELY(!getDocument()) ? SKGError() : getDocument()->setParameter(iName, iValue, iBlob, getUniqueID(), oObjectCreated); 0569 0570 // Send message 0571 IFOKDO(err, Q_UNLIKELY(!getDocument()) ? SKGError() : getDocument()->sendMessage(i18nc("An information to the user", "The property '%1=%2' has been added on '%3'", iName, iValue, getDisplayName()), SKGDocument::Hidden)) 0572 0573 return err; 0574 }