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 }