swift
databasereader.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QByteArray>
7 #include <QFileInfo>
8 #include <QJsonDocument>
9 #include <QJsonObject>
10 #include <QJsonValueRef>
11 #include <QMetaObject>
12 #include <QNetworkReply>
13 #include <QPointer>
14 #include <QReadLocker>
15 #include <QStringBuilder>
16 #include <QUrl>
17 #include <QWriteLocker>
18 
19 #include "core/application.h"
20 #include "core/db/databaseutils.h"
21 #include "core/db/infodatareader.h"
22 #include "core/webdataservices.h"
24 #include "misc/directoryutils.h"
25 #include "misc/logcategories.h"
26 #include "misc/logmessage.h"
29 #include "misc/swiftdirectories.h"
30 #include "misc/verify.h"
31 
32 using namespace swift::misc;
33 using namespace swift::misc::db;
34 using namespace swift::misc::network;
35 using namespace swift::core;
36 using namespace swift::core::data;
37 using namespace swift::core::db;
38 
39 namespace swift::core::db
40 {
41  CDatabaseReader::CDatabaseReader(QObject *owner, const CDatabaseReaderConfigList &config, const QString &name)
42  : CThreadedReader(owner, name), m_config(config)
43  {}
44 
45  void CDatabaseReader::readInBackgroundThread(CEntityFlags::Entity entities, const QDateTime &newerThan)
46  {
47  if (!this->doWorkCheck()) { return; }
48  Q_ASSERT_X(hasStarted(), Q_FUNC_INFO, "Thread was not started yet!");
49 
50  // we accept cached data
51  Q_ASSERT_X(!entities.testFlag(CEntityFlags::DbInfoObjectEntity), Q_FUNC_INFO, "Read info objects directly");
52  const bool hasDbInfoObjects =
53  this->hasDbInfoObjects(); // no info objects is not necessarily an error, but indicates a) either data not
54  // available (DB down) or b) only caches are used
55  // const bool hasSharedInfoObjects = this->hasSharedInfoObjects();
56  CEntityFlags::Entity allEntities = entities;
57  CEntityFlags::Entity cachedEntities = CEntityFlags::NoEntity;
58  CEntityFlags::Entity dbEntities = CEntityFlags::NoEntity;
59  CEntityFlags::Entity sharedEntities = CEntityFlags::NoEntity;
60  CEntityFlags::Entity currentEntity =
61  CEntityFlags::iterateDbEntities(allEntities); // CEntityFlags::InfoObjectEntity will be ignored
62  while (currentEntity)
63  {
64  const CDatabaseReaderConfig config(this->getConfigForEntity(currentEntity));
65  const QString currentEntityName = CEntityFlags::entitiesToString(currentEntity);
66 
67  // retrieval mode
68  const CDbFlags::DataRetrievalMode rm = config.getRetrievalMode(); // DB reading, cache, shared
69  Q_ASSERT_X(!rm.testFlag(CDbFlags::Unspecified), Q_FUNC_INFO, "Missing retrieval mode");
70  const QString rmString = CDbFlags::flagToString(rm);
71  const CDbFlags::DataRetrievalModeFlag rmDbOrSharedFlag =
72  CDbFlags::modeToModeFlag(rm & CDbFlags::DbReadingOrShared);
73  const QString rmDbOrSharedFlagString = CDbFlags::flagToString(rmDbOrSharedFlag);
74  const bool rmDbReadingOrShared =
75  (rmDbOrSharedFlag == CDbFlags::DbReading || rmDbOrSharedFlag == CDbFlags::Shared);
76  const int currentEntityCount = this->getCacheCount(currentEntity);
77 
78  if (rm.testFlag(CDbFlags::Ignore) || rm.testFlag(CDbFlags::Canceled))
79  {
80  // do not load
81  }
82  else if (rm.testFlag(CDbFlags::Cached))
83  {
84  //
85  // !!! info object comparisons only for: cache + shared or cache + DB data
86  //
87  if (hasDbInfoObjects && rmDbReadingOrShared)
88  {
89  // check mode here for consistency
90  Q_ASSERT_X(!getBaseUrl(rmDbOrSharedFlag).isEmpty(), Q_FUNC_INFO, "Wrong retrieval mode");
91 
92  CUrl oldUrlInfo;
93  CUrl newUrlInfo;
94  const bool changedUrl = this->hasChangedUrl(currentEntity, oldUrlInfo, newUrlInfo);
95  const QDateTime cacheTs(this->getCacheTimestamp(currentEntity));
96  const QDateTime latestEntityTs(this->getLatestEntityTimestampFromDbInfoObjects(currentEntity));
97  const qint64 cacheTimestamp = cacheTs.isValid() ? cacheTs.toMSecsSinceEpoch() : -1;
98  const qint64 latestEntityTimestamp =
99  latestEntityTs.isValid() ? latestEntityTs.toMSecsSinceEpoch() : -1;
100  Q_ASSERT_X(latestEntityTimestamp >= 0, Q_FUNC_INFO, "Missing timestamp");
101  if (!changedUrl && cacheTimestamp >= latestEntityTimestamp && cacheTimestamp >= 0 &&
102  latestEntityTimestamp >= 0)
103  {
104  this->admitCaches(currentEntity);
105  cachedEntities |= currentEntity; // read from cache
106  CLogMessage(this).info(u"Using cache for '%1' (%2, %3)")
107  << currentEntityName << cacheTs.toString() << cacheTimestamp;
108  }
109  else
110  {
111  Q_ASSERT_X(rmDbReadingOrShared, Q_FUNC_INFO, "Wrong retrieval mode");
112  if (rmDbOrSharedFlag == CDbFlags::DbReading) { dbEntities |= currentEntity; }
113  else if (rmDbOrSharedFlag == CDbFlags::Shared) { sharedEntities |= currentEntity; }
114 
115  if (changedUrl)
116  {
117  CLogMessage(this).info(
118  u"Data location for '%1' changed ('%2'->'%3'), will override cache for reading '%4'")
119  << currentEntityName << oldUrlInfo.toQString() << newUrlInfo.toQString()
120  << rmDbOrSharedFlagString;
121  }
122  else
123  {
124  CLogMessage(this).info(u"Cache for '%1' outdated, latest entity (%2, %3), reading '%4'")
125  << currentEntityName << latestEntityTs.toString() << latestEntityTimestamp
126  << rmDbOrSharedFlagString;
127  }
128  }
129  }
130  else
131  {
132  if (!rmDbReadingOrShared)
133  {
134  CLogMessage(this).info(u"No DB or shared reading for '%1', read mode is: '%2'")
135  << currentEntityName << rmString;
136  }
137  if (!hasDbInfoObjects)
138  {
139  CLogMessage(this).info(u"No DB info objects for '%1', read mode is: '%2'")
140  << currentEntityName << rmString;
141  }
142  if (currentEntityCount > 0)
143  {
144  CLogMessage(this).info(u"Cache for '%1' already read, %2 entries")
145  << currentEntityName << currentEntityCount;
146  }
147  else
148  {
149  // no info objects, server down or no shared/db read mode
150  this->admitCaches(currentEntity);
151  if (!rmDbReadingOrShared)
152  {
153  // intentionally we do not want to read from DB/shared
154  CLogMessage(this).info(u"Triggered reading cache for '%1', read mode: %2")
155  << currentEntityName << rmString;
156  }
157  else
158  {
159  // we want to read from DB/shared, but have no info object
160  CLogMessage(this).info(u"No info object for '%1', triggered reading cache, read mode: %2")
161  << currentEntityName << rmString;
162  }
163  }
164  cachedEntities |= currentEntity; // read from cache
165  }
166  }
167  else
168  {
169  // cache ignored
170  Q_ASSERT_X(rmDbReadingOrShared, Q_FUNC_INFO, "Wrong retrieval mode");
171  if (rmDbOrSharedFlag == CDbFlags::DbReading) { dbEntities |= currentEntity; }
172  else if (rmDbOrSharedFlag == CDbFlags::Shared) { sharedEntities |= currentEntity; }
173  }
174  currentEntity = CEntityFlags::iterateDbEntities(allEntities);
175  }
176 
177  // signals for the cached entities
178  if (cachedEntities != CEntityFlags::NoEntity)
179  {
180  this->emitReadSignalPerSingleCachedEntity(cachedEntities, true);
181  }
182 
183  // Real read from DB
184  if (dbEntities != CEntityFlags::NoEntity)
185  {
186  CLogMessage(this).info(u"Start reading DB entities: %1") << CEntityFlags::entitiesToString(dbEntities);
187  this->startReadFromBackendInBackgroundThread(dbEntities, CDbFlags::DbReading, newerThan);
188  }
189 
190  // Real read from shared
191  if (sharedEntities != CEntityFlags::NoEntity)
192  {
193  CLogMessage(this).info(u"Start reading shared entities: %1")
194  << CEntityFlags::entitiesToString(sharedEntities);
195  this->startReadFromBackendInBackgroundThread(dbEntities, CDbFlags::Shared, newerThan);
196  }
197  }
198 
199  CEntityFlags::Entity CDatabaseReader::triggerLoadingDirectlyFromDb(CEntityFlags::Entity entities,
200  const QDateTime &newerThan)
201  {
202  this->startReadFromBackendInBackgroundThread(entities, CDbFlags::DbReading, newerThan);
203  return entities;
204  }
205 
206  CEntityFlags::Entity CDatabaseReader::triggerLoadingDirectlyFromSharedFiles(CEntityFlags::Entity entities,
207  bool checkCacheTsUpfront)
208  {
209  if (entities == CEntityFlags::NoEntity) { return CEntityFlags::NoEntity; }
210  if (checkCacheTsUpfront)
211  {
212  CEntityFlags::Entity newerHeaderEntities = this->getEntitesWithNewerSharedInfoObject(entities);
213  if (newerHeaderEntities != entities)
214  {
215  const CEntityFlags::Entity validInCacheEntities = (entities ^ newerHeaderEntities) & entities;
216  CLogMessage(this).info(u"Reduced '%1' to '%2' before triggering load of shared files (still in cache)")
217  << CEntityFlags::entitiesToString(entities) << CEntityFlags::entitiesToString(newerHeaderEntities);
218 
219  // In case we have difference entities we treat them as they were loaded, they result from still being
220  // in the cache Using timer to first finish this function, then the resulting signal
221  if (validInCacheEntities != CEntityFlags::NoEntity)
222  {
223  QPointer<CDatabaseReader> myself(this);
224  QTimer::singleShot(0, this, [=] {
225  if (!myself) { return; }
226  if (!sApp || sApp->isShuttingDown()) { return; }
227  emit this->dataRead(validInCacheEntities, CEntityFlags::ReadFinished, 0, {});
228  });
229  }
230  if (newerHeaderEntities == CEntityFlags::NoEntity) { return CEntityFlags::NoEntity; }
231  entities = newerHeaderEntities;
232  }
233  }
234  this->startReadFromBackendInBackgroundThread(entities, CDbFlags::Shared);
235  return entities;
236  }
237 
240  const QDateTime &newerThan)
241  {
242  Q_ASSERT_X(mode == CDbFlags::DbReading || mode == CDbFlags::Shared, Q_FUNC_INFO, "Wrong mode");
243 
244  // ps_read is implemented in the derived classes
245  if (entities == CEntityFlags::NoEntity) { return; }
246 
247  QPointer<CDatabaseReader> myself(this);
248  QTimer::singleShot(0, this, [=] {
249  if (!sApp || sApp->isShuttingDown() || !myself) { return; }
250  this->read(entities, mode, newerThan);
251  });
252  }
253 
256  {
257  Q_ASSERT_X(nwReply, Q_FUNC_INFO, "missing reply");
258  JsonDatastoreResponse datastoreResponse;
259  const bool ok = this->setHeaderInfoPart(datastoreResponse, nwReply);
260  if (ok)
261  {
262  const QString dataFileData = nwReply->readAll();
263  nwReply->close(); // close asap
264  datastoreResponse.setStringSize(dataFileData.size());
265  if (dataFileData.isEmpty())
266  {
267  datastoreResponse.setMessage(
268  CStatusMessage(this, CStatusMessage::SeverityError, u"Empty response, no data"));
269  }
270  else { CDatabaseReader::stringToDatastoreResponse(dataFileData, datastoreResponse); }
271  }
272  return datastoreResponse;
273  }
274 
276  {
277  HeaderResponse headerResponse;
278  const bool success = this->setHeaderInfoPart(headerResponse, nwReply);
279  Q_UNUSED(success);
280  return headerResponse;
281  }
282 
284  QNetworkReply *nwReply) const
285  {
286  Q_ASSERT_X(nwReply, Q_FUNC_INFO, "Missing reply");
287  this->threadAssertCheck();
288  if (!this->doWorkCheck())
289  {
290  nwReply->abort();
291  headerResponse.setMessage(
292  CStatusMessage(this, CStatusMessage::SeverityError, u"Terminated data parsing process"));
293  return false; // stop, terminate straight away, ending thread
294  }
295 
296  headerResponse.setValues(nwReply);
297  if (nwReply->error() == QNetworkReply::NoError)
298  {
299  // do not close because of obtaining data
300  return true;
301  }
302  else
303  {
304  // no valid response
305  const QString error(nwReply->errorString());
306  const QString url(nwReply->url().toString());
307  nwReply->abort();
308  headerResponse.setMessage(
309  CStatusMessage(this, CStatusMessage::SeverityError, u"Reading data failed: '" % error % u"' " % url));
310  return false;
311  }
312  }
313 
316  {
317  this->setReplyStatus(nwReply);
319  if (dsr.isSharedFile()) { this->receivedSharedFileHeaderNonClosing(nwReply); }
320  else
321  {
322  if (dsr.isLoadedFromDb())
323  {
324  const bool s = !dsr.hasErrorMessage();
325  emit this->swiftDbDataRead(s);
326  }
327  }
328  return dsr;
329  }
330 
332  {
333  static const CDbInfoList e;
334  if (!sApp->hasWebDataServices()) { return e; }
335  if (!sApp->getWebDataServices()->getDbInfoDataReader()) { return e; }
337  }
338 
340  {
341  static const CDbInfoList e;
342  if (!sApp->hasWebDataServices()) { return e; }
343  if (!sApp->getWebDataServices()->getSharedInfoDataReader()) { return e; }
345  }
346 
348 
350 
351  bool CDatabaseReader::hasSharedFileHeader(const CEntityFlags::Entity entity) const
352  {
353  Q_ASSERT_X(CEntityFlags::isSingleEntity(entity), Q_FUNC_INFO, "need single entity");
354  return m_sharedFileResponses.contains(entity);
355  }
356 
357  bool CDatabaseReader::hasSharedFileHeaders(const CEntityFlags::Entity entities) const
358  {
359  CEntityFlags::Entity myEntities = maskBySupportedEntities(entities);
360  CEntityFlags::Entity currentEntity = CEntityFlags::iterateDbEntities(myEntities);
361  while (currentEntity != CEntityFlags::NoEntity)
362  {
363  if (!hasSharedFileHeader(currentEntity)) { return false; }
364  currentEntity = CEntityFlags::iterateDbEntities(myEntities);
365  }
366  return true;
367  }
368 
369  QDateTime CDatabaseReader::getLatestEntityTimestampFromDbInfoObjects(CEntityFlags::Entity entity) const
370  {
371  Q_ASSERT_X(CEntityFlags::isSingleEntity(entity), Q_FUNC_INFO, "need single entity");
372  static const QDateTime e;
373  const CDbInfoList il(getDbInfoObjects());
374  if (il.isEmpty() || entity == CEntityFlags::NoEntity) { return e; }
375 
376  // for some entities there can be more than one entry because of the
377  // raw tables (see DB view last updates)
378  const CDbInfo info = il.findFirstByEntityOrDefault(entity);
379  if (!info.isValid()) { return e; }
380  return info.getUtcTimestamp();
381  }
382 
383  QDateTime CDatabaseReader::getLatestEntityTimestampFromSharedInfoObjects(CEntityFlags::Entity entity) const
384  {
385  Q_ASSERT_X(CEntityFlags::isSingleEntity(entity), Q_FUNC_INFO, "need single entity");
386  static const QDateTime e;
387  const CDbInfoList il(this->getSharedInfoObjects());
388  if (il.isEmpty() || entity == CEntityFlags::NoEntity) { return e; }
389 
390  const CDbInfo info = il.findFirstByEntityOrDefault(entity);
391  if (!info.isValid()) { return e; }
392  return info.getUtcTimestamp();
393  }
394 
395  QDateTime CDatabaseReader::getLatestSharedFileHeaderTimestamp(CEntityFlags::Entity entity) const
396  {
397  Q_ASSERT_X(CEntityFlags::isSingleEntity(entity), Q_FUNC_INFO, "need single entity");
398  static const QDateTime e;
399  if (!this->hasSharedFileHeader(entity)) { return e; }
400  return m_sharedFileResponses[entity].getLastModifiedTimestamp();
401  }
402  bool CDatabaseReader::isSharedHeaderNewerThanCacheTimestamp(CEntityFlags::Entity entity) const
403  {
404  Q_ASSERT_X(CEntityFlags::isSingleEntity(entity), Q_FUNC_INFO, "need single entity");
405  const QDateTime cacheTs(this->getCacheTimestamp(entity));
406  if (!cacheTs.isValid()) { return true; } // we have no cache ts
407 
408  const QDateTime headerTimestamp(this->getLatestSharedFileHeaderTimestamp(entity));
409  if (!headerTimestamp.isValid()) { return false; }
410  return headerTimestamp > cacheTs;
411  }
412 
413  bool CDatabaseReader::isSharedInfoObjectNewerThanCacheTimestamp(CEntityFlags::Entity entity) const
414  {
415  Q_ASSERT_X(CEntityFlags::isSingleEntity(entity), Q_FUNC_INFO, "need single entity");
416  const QDateTime cacheTs(this->getCacheTimestamp(entity));
417  if (!cacheTs.isValid()) { return true; } // we have no cache ts
418 
419  const QDateTime sharedInfoTimestamp(this->getLatestEntityTimestampFromSharedInfoObjects(entity));
420  if (!sharedInfoTimestamp.isValid()) { return false; }
421  return sharedInfoTimestamp > cacheTs;
422  }
423 
424  CEntityFlags::Entity CDatabaseReader::getEntitesWithNewerHeaderTimestamp(CEntityFlags::Entity entities) const
425  {
426  entities &= CEntityFlags::AllDbEntitiesNoInfoObjects;
427  CEntityFlags::Entity currentEntity = CEntityFlags::iterateDbEntities(entities);
428  CEntityFlags::Entity newerEntities = CEntityFlags::NoEntity;
429  while (currentEntity != CEntityFlags::NoEntity)
430  {
431  if (this->isSharedHeaderNewerThanCacheTimestamp(currentEntity)) { newerEntities |= currentEntity; }
432  currentEntity = CEntityFlags::iterateDbEntities(entities);
433  }
434  return newerEntities;
435  }
436 
437  CEntityFlags::Entity CDatabaseReader::getEntitesWithNewerSharedInfoObject(CEntityFlags::Entity entities) const
438  {
439  entities &= CEntityFlags::AllDbEntitiesNoInfoObjects;
440  CEntityFlags::Entity currentEntity = CEntityFlags::iterateDbEntities(entities);
441  CEntityFlags::Entity newerEntities = CEntityFlags::NoEntity;
442  while (currentEntity != CEntityFlags::NoEntity)
443  {
444  if (this->isSharedInfoObjectNewerThanCacheTimestamp(currentEntity)) { newerEntities |= currentEntity; }
445  currentEntity = CEntityFlags::iterateDbEntities(entities);
446  }
447  return newerEntities;
448  }
449 
451  {
452  return m_config.findFirstOrDefaultForEntity(entity);
453  }
454 
455  CEntityFlags::Entity CDatabaseReader::emitReadSignalPerSingleCachedEntity(CEntityFlags::Entity cachedEntities,
456  bool onlyIfHasData)
457  {
458  if (cachedEntities == CEntityFlags::NoEntity) { return CEntityFlags::NoEntity; }
459  CEntityFlags::Entity emitted = CEntityFlags::NoEntity;
460  CEntityFlags::Entity cachedEntitiesToEmit = cachedEntities;
461  CEntityFlags::Entity currentCachedEntity = CEntityFlags::iterateDbEntities(cachedEntitiesToEmit);
462  while (currentCachedEntity)
463  {
464  const int c = this->getCacheCount(currentCachedEntity);
465  if (!onlyIfHasData || c > 0)
466  {
467  emit this->dataRead(currentCachedEntity, CEntityFlags::ReadFinished, c, {});
468  emitted |= currentCachedEntity;
469  }
470  currentCachedEntity = CEntityFlags::iterateDbEntities(cachedEntitiesToEmit);
471  }
472  return emitted;
473  }
474 
475  void CDatabaseReader::emitAndLogDataRead(CEntityFlags::Entity entity, int number, const JsonDatastoreResponse &res)
476  {
477  // never emit when lock is held, deadlock
478  Q_ASSERT_X(CEntityFlags::isSingleEntity(entity), Q_FUNC_INFO, "Expect single entity");
479  CLogMessage(this).info(u"Read %1 entities of '%2' from '%3' (%4)")
480  << number << CEntityFlags::entitiesToString(entity) << res.getUrlString()
482  emit this->dataRead(entity,
483  res.isRestricted() ? CEntityFlags::ReadFinishedRestricted : CEntityFlags::ReadFinished,
484  number, res.getUrl());
485  }
486 
487  void CDatabaseReader::logNoWorkingUrl(CEntityFlags::Entity entity)
488  {
489  const CStatusMessage msg = CStatusMessage(this, m_severityNoWorkingUrl, u"No working URL for '%1'")
490  << CEntityFlags::entitiesToString(entity);
492  }
493 
495  {
496  if (!sApp || sApp->isShuttingDown()) { return CUrl(); }
497  switch (mode)
498  {
499  case CDbFlags::DbReading: return this->getDbServiceBaseUrl().withAppendedPath("/service");
500  case CDbFlags::SharedInfoOnly:
501  case CDbFlags::Shared: return sApp->getGlobalSetup().getSharedDbDataDirectoryUrl();
502  default: qFatal("Wrong mode"); break;
503  }
504  return CUrl();
505  }
506 
507  bool CDatabaseReader::isChangedUrl(const CUrl &oldUrl, const CUrl &currentUrl)
508  {
509  if (oldUrl.isEmpty()) { return true; }
510  Q_ASSERT_X(!currentUrl.isEmpty(), Q_FUNC_INFO, "No base URL");
511 
512  const QString old(oldUrl.getFullUrl(false));
513  const QString current(currentUrl.getFullUrl(false));
514  return old != current;
515  }
516 
517  void CDatabaseReader::receivedSharedFileHeader(QNetworkReply *nwReplyPtr)
518  {
519  // wrap pointer, make sure any exit cleans up reply
520  // required to use delete later as object is created in a different thread
521  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
522  if (!this->doWorkCheck()) { return; }
523  this->receivedSharedFileHeaderNonClosing(nwReplyPtr);
524  nwReply->close();
525  }
526 
528  {
529  if (this->isAbandoned()) { return; }
530 
531  const HeaderResponse headerResponse = this->transformReplyIntoHeaderResponse(nwReplyPtr);
532  const QString fileName = nwReplyPtr->url().fileName();
533  const CEntityFlags::Entity entity = CEntityFlags::singleEntityByName(fileName);
534  m_sharedFileResponses[entity] = headerResponse;
535 
536  CLogMessage(this).info(u"Received header for shared file of '%1' from '%2'")
537  << fileName << headerResponse.getUrl().toQString();
538  emit this->sharedFileHeaderRead(entity, fileName, !headerResponse.hasWarningOrAboveMessage());
539  }
540 
542  {
543  QReadLocker rl(&m_statusLock);
544  return m_1stReplyReceived && m_1stReplyStatus == QNetworkReply::NoError;
545  }
546 
547  bool CDatabaseReader::hasReceivedOkReply(QString &message) const
548  {
549  QReadLocker rl(&m_statusLock);
550  message = m_statusMessage;
551  return m_1stReplyReceived && m_1stReplyStatus == QNetworkReply::NoError;
552  }
553 
555  {
556  QReadLocker rl(&m_statusLock);
557  return m_1stReplyReceived;
558  }
559 
561  {
562  return CEntityFlags::entitiesToString(this->getSupportedEntities());
563  }
564 
565  CEntityFlags::Entity CDatabaseReader::maskBySupportedEntities(CEntityFlags::Entity entities) const
566  {
567  return entities & this->getSupportedEntities();
568  }
569 
570  bool CDatabaseReader::supportsAnyOfEntities(CEntityFlags::Entity entities) const
571  {
572  return static_cast<int>(this->maskBySupportedEntities(entities)) > 0;
573  }
574 
575  bool CDatabaseReader::hasCacheTimestampNewerThan(CEntityFlags::Entity entity, const QDateTime &threshold) const
576  {
577  const QDateTime ts = this->getCacheTimestamp(entity);
578  if (!ts.isValid()) { return false; }
579  return ts > threshold;
580  }
581 
582  const QString &CDatabaseReader::getStatusMessage() const { return m_statusMessage; }
583 
585  {
586  return this->initFromLocalResourceFiles(this->getSupportedEntities(), inBackground);
587  }
588 
590  CStatusMessageList CDatabaseReader::initFromLocalResourceFiles(CEntityFlags::Entity entities, bool inBackground)
591  {
592  const bool overrideNewerOnly = true;
593  entities = this->maskBySupportedEntities(entities);
594 
595  if (inBackground || !CThreadUtils::isInThisThread(this))
596  {
598  overrideNewerOnly);
599  return s ? CStatusMessage(this).info(u"Started reading in background from '%1' of entities: '%2'")
600  << CSwiftDirectories::staticDbFilesDirectory() << CEntityFlags::entitiesToString(entities) :
601  CStatusMessage(this).error(u"Starting reading in background from '%1' of entities: '%2' failed")
602  << CSwiftDirectories::staticDbFilesDirectory() << CEntityFlags::entitiesToString(entities);
603  }
604  else
605  {
606  return this->readFromJsonFiles(CSwiftDirectories::staticDbFilesDirectory(), entities, overrideNewerOnly);
607  }
608  }
610 
611  void CDatabaseReader::setReplyStatus(QNetworkReply::NetworkError status, const QString &message)
612  {
613  QWriteLocker wl(&m_statusLock);
614  m_statusMessage = message;
615  m_1stReplyStatus = status;
616  m_1stReplyReceived = true;
617  }
618 
619  void CDatabaseReader::setReplyStatus(QNetworkReply *nwReply)
620  {
621  Q_ASSERT_X(nwReply, Q_FUNC_INFO, "Missing network reply");
622  if (nwReply && nwReply->isFinished())
623  {
624  this->logNetworkReplyReceived(nwReply);
625  this->setReplyStatus(nwReply->error(), nwReply->errorString());
626  }
627  }
628 
629  bool CDatabaseReader::overrideCacheFromFile(bool overrideNewerOnly, const QFileInfo &fileInfo,
630  CEntityFlags::Entity entity, CStatusMessageList &msgs) const
631  {
632  if (!fileInfo.birthTime().isValid()) { return false; }
633  if (!overrideNewerOnly) { return true; }
634 
635  const qint64 fileTs = fileInfo.birthTime().toUTC().toMSecsSinceEpoch();
636  const QDateTime cacheDateTime(this->getCacheTimestamp(entity));
637  if (!cacheDateTime.isValid()) { return true; } // no cache
638  const qint64 cacheTs = cacheDateTime.toUTC().toMSecsSinceEpoch();
639  if (fileTs > cacheTs)
640  {
641  msgs.push_back(CStatusMessage(this).info(u"File '%1' is newer than cache (%2)")
642  << fileInfo.absoluteFilePath() << cacheDateTime.toUTC().toString());
643  return true;
644  }
645  else
646  {
647  msgs.push_back(CStatusMessage(this).info(u"File '%1' is not newer than cache (%2)")
648  << fileInfo.absoluteFilePath() << cacheDateTime.toUTC().toString());
649  return false;
650  }
651  }
652 
653  void CDatabaseReader::logParseMessage(const QString &entity, int size, int msElapsed,
654  const CDatabaseReader::JsonDatastoreResponse &response) const
655  {
656  CLogMessage(this).info(u"Parsed %1 %2 in %3ms, thread %4 | '%5'")
657  << size << entity << msElapsed << CThreadUtils::currentThreadInfo() << response.toQString();
658  }
659 
660  void CDatabaseReader::networkReplyProgress(int logId, qint64 current, qint64 max, const QUrl &url)
661  {
662  CThreadedReader::networkReplyProgress(logId, current, max, url);
663  const QString fileName = url.fileName();
664  const CEntityFlags::Entity entity = CEntityFlags::singleEntityByName(fileName);
665  if (CEntityFlags::isSingleEntity(entity))
666  {
668  m_networkReplyMax, url);
669  }
670  }
671 
672  QString CDatabaseReader::fileNameForMode(CEntityFlags::Entity entity, CDbFlags::DataRetrievalModeFlag mode)
673  {
674  Q_ASSERT_X(CEntityFlags::isSingleEntity(entity), Q_FUNC_INFO, "needs single entity");
675  switch (mode)
676  {
677  case CDbFlags::Shared: return CDbInfo::entityToSharedName(entity);
678  case CDbFlags::SharedInfoOnly: return CDbInfo::sharedInfoFileName();
679  default:
680  case CDbFlags::DbReading: return CDbInfo::entityToServiceName(entity);
681  }
682  }
683 
684  QString CDatabaseReader::dateTimeToDbLatestTs(const QDateTime &ts)
685  {
686  if (!ts.isValid()) { return {}; }
687  return ts.toUTC().toString(Qt::ISODate);
688  }
689 
691  {
692  static const QStringList cats =
695  return cats;
696  }
697 
699  {
700  static const QString p("latestTimestamp");
701  return p;
702  }
703 
704  QString CDatabaseReader::queryLatestTimestamp(const QDateTime &ts)
705  {
706  if (!ts.isValid()) return {};
707  const QString q = parameterLatestTimestamp() % u"=" % dateTimeToDbLatestTs(ts);
708  return q;
709  }
710 
712  {
713  static const CUrl dbUrl(sApp->getGlobalSetup().getDbRootDirectoryUrl());
714  return dbUrl;
715  }
716 
717  void CDatabaseReader::cacheHasChanged(CEntityFlags::Entity entities)
718  {
719  this->emitReadSignalPerSingleCachedEntity(entities, false);
720  }
721 
722  void CDatabaseReader::stringToDatastoreResponse(const QString &jsonContent,
723  JsonDatastoreResponse &datastoreResponse)
724  {
725  const int status = datastoreResponse.getHttpStatusCode();
726  if (jsonContent.isEmpty())
727  {
728  static const QString errorMsg = "Empty JSON string, status: %1, URL: '%2', load time: %3";
729  datastoreResponse.setMessage(
731  errorMsg.arg(status).arg(datastoreResponse.getUrlString(),
732  datastoreResponse.getLoadTimeStringWithStartedHint())));
733  return;
734  }
735 
736  const QJsonDocument jsonResponse = CDatabaseUtils::databaseJsonToQJsonDocument(jsonContent);
737  if (jsonResponse.isEmpty())
738  {
739  if (CNetworkUtils::looksLikePhpErrorMessage(jsonContent))
740  {
741  static const QString errorMsg = "Looks like PHP errror, status %1, URL: '%2', msg: %3";
742  const QString phpErrorMessage = CNetworkUtils::removeHtmlPartsFromPhpErrorMessage(jsonContent);
743  datastoreResponse.setMessage(
745  errorMsg.arg(status).arg(datastoreResponse.getUrlString(), phpErrorMessage)));
746  }
747  else
748  {
749  static const QString errorMsg = "Empty JSON document, URL: '%1', load time: %2";
750  datastoreResponse.setMessage(
752  errorMsg.arg(datastoreResponse.getUrlString(),
753  datastoreResponse.getLoadTimeStringWithStartedHint())));
754  }
755  return;
756  }
757 
758  if (jsonResponse.isArray())
759  {
760  // directly an array, no further info
761  datastoreResponse.setJsonArray(jsonResponse.array());
762  datastoreResponse.setLastModifiedTimestamp(QDateTime::currentDateTimeUtc());
763  }
764  else
765  {
766  const QJsonObject responseObject(jsonResponse.object());
767  datastoreResponse.setJsonArray(responseObject["data"].toArray());
768  const QString ts(responseObject["latest"].toString());
769  datastoreResponse.setLastModifiedTimestamp(ts.isEmpty() ? QDateTime::currentDateTimeUtc() :
770  CDatastoreUtility::parseTimestamp(ts));
771  datastoreResponse.setRestricted(responseObject["restricted"].toBool());
772  }
773  }
774 
776  {
777  return this->getUrl().getHost() == getDbUrl().getHost();
778  }
779 
781  {
782  m_jsonArray = value;
783  m_arraySize = value.size();
784  }
785 
787  {
788  static const QString s("DB: %1 | restricted: %2 | array: %3 | string size: %4 | content: %5");
789  return s.arg(boolToYesNo(this->isLoadedFromDb()), boolToYesNo(this->isRestricted()))
790  .arg(this->getArraySize())
791  .arg(m_stringSize)
792  .arg(this->getContentLengthHeader());
793  }
794 
796  {
797  const QString fn(getUrl().getFileName());
798  return CDbInfo::sharedFileNames().contains(fn, Qt::CaseInsensitive);
799  }
800 
802  {
803  return QStringLiteral("%1ms").arg(getLoadTimeMs());
804  }
805 
807  {
808  if (m_requestStarted < 0) { return this->getLoadTimeString(); }
809  const qint64 diff = QDateTime::currentMSecsSinceEpoch() - m_requestStarted;
810  static const QString s("%1 load time, started %2ms before now");
811  return s.arg(this->getLoadTimeString()).arg(diff);
812  }
813 
814  void CDatabaseReader::HeaderResponse::setValues(const QNetworkReply *nwReply)
815  {
816  Q_ASSERT_X(nwReply, Q_FUNC_INFO, "Need valid reply");
817  this->setUrl(nwReply->url());
818  const QVariant started = nwReply->property("started");
819  if (started.isValid() && started.canConvert<qint64>())
820  {
821  const qint64 now = QDateTime::currentMSecsSinceEpoch();
822  const qint64 start = started.value<qint64>();
823  this->setLoadTimeMs(now - start);
824  m_requestStarted = start;
825  m_responseReceived = now;
826  }
827 
828  const QVariant qvStatusCode = nwReply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
829  if (qvStatusCode.isValid() && qvStatusCode.canConvert<int>()) { m_httpStatusCode = qvStatusCode.toInt(); }
830 
831  const QDateTime lastModified = nwReply->header(QNetworkRequest::LastModifiedHeader).toDateTime();
832  const qulonglong size = nwReply->header(QNetworkRequest::ContentLengthHeader).toULongLong();
833  this->setLastModifiedTimestamp(lastModified);
834  this->setContentLengthHeader(size);
835  }
836 } // namespace swift::core::db
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
data::CGlobalSetup getGlobalSetup() const
Global setup.
bool hasWebDataServices() const
Web data services available?
bool isShuttingDown() const
Is application shutting down?
CWebDataServices * getWebDataServices() const
Get the web data services.
Support for threaded based reading and parsing tasks such as data files via http, or file system and ...
std::atomic_llong m_networkReplyCurrent
current bytes
static const QStringList & getLogCategories()
Log categories.
virtual void networkReplyProgress(int logId, qint64 current, qint64 max, const QUrl &url)
Network request progress.
std::atomic_int m_networkReplyProgress
Progress percentage 0...100.
void threadAssertCheck() const
Make sure everything runs correctly in own thread.
void logNetworkReplyReceived(QNetworkReply *reply)
Network reply received, mark in m_urlReadLog.
bool doWorkCheck() const
Still enabled etc.?
std::atomic_llong m_networkReplyMax
max bytes
swift::core::db::CInfoDataReader * getDbInfoDataReader() const
DB info data reader.
swift::core::db::CInfoDataReader * getSharedInfoDataReader() const
Shared info data reader.
const swift::misc::network::CUrl & getDbRootDirectoryUrl() const
Root directory of DB.
Definition: globalsetup.h:65
swift::misc::network::CUrl getSharedDbDataDirectoryUrl()
Get shared DB data directory URL.
Definition: globalsetup.cpp:72
Details how to read a certain entity.
swift::misc::db::CDbFlags::DataRetrievalMode getRetrievalMode() const
Supported modes.
Value object encapsulating a list of reader configs.
CDatabaseReaderConfig findFirstOrDefaultForEntity(const swift::misc::network::CEntityFlags::Entity entities) const
Find first one matching given.
Specialized version of threaded reader for DB data.
virtual QDateTime getCacheTimestamp(swift::misc::network::CEntityFlags::Entity entity) const =0
Get cache timestamp.
virtual bool hasChangedUrl(swift::misc::network::CEntityFlags::Entity entity, swift::misc::network::CUrl &oldUrlInfo, swift::misc::network::CUrl &newUrlInfo) const =0
Changed URL, means the cache values have been read from elsewhere.
virtual void admitCaches(swift::misc::network::CEntityFlags::Entity entities)=0
Admit caches for given entities.
QDateTime getLatestSharedFileHeaderTimestamp(swift::misc::network::CEntityFlags::Entity entity) const
Header timestamp (last-modified) for shared file.
void emitAndLogDataRead(swift::misc::network::CEntityFlags::Entity entity, int number, const JsonDatastoreResponse &res)
Emit signal and log when data have been read.
swift::misc::CStatusMessageList initFromLocalResourceFiles(bool inBackground)
Init from local resource file.
swift::misc::network::CEntityFlags::Entity emitReadSignalPerSingleCachedEntity(swift::misc::network::CEntityFlags::Entity cachedEntities, bool onlyIfHasData)
Split into single entity and send dataRead signal.
HeaderResponse transformReplyIntoHeaderResponse(QNetworkReply *nwReply) const
Check if terminated or error, otherwise set header information.
static QString queryLatestTimestamp(const QDateTime &ts)
Latest timestamp query for DB.
QNetworkReply::NetworkError m_1stReplyStatus
Successful connection?
bool isSharedHeaderNewerThanCacheTimestamp(swift::misc::network::CEntityFlags::Entity entity) const
Is the file timestamp newer than cache timestamp?
swift::misc::db::CDbInfoList getSharedInfoObjects() const
Shared info list (latest data timestamps from DB web service)
virtual void cacheHasChanged(swift::misc::network::CEntityFlags::Entity entities)
Cache for given entity has changed.
virtual void networkReplyProgress(int logId, qint64 current, qint64 max, const QUrl &url)
Network request progress.
bool hasSharedInfoObjects() const
Shared info objects available?
swift::misc::CStatusMessage::StatusSeverity m_severityNoWorkingUrl
severity of message if there is no working URL
void sharedFileHeaderRead(swift::misc::network::CEntityFlags::Entity entity, const QString &fileName, bool success)
Header of shared file read.
bool hasReceivedOkReply() const
Has received Ok response from server at least once?
static QString dateTimeToDbLatestTs(const QDateTime &ts)
A newer than value understood by swift DB.
void setReplyStatus(QNetworkReply::NetworkError status, const QString &message="")
Feedback about connection status.
static const QStringList & getLogCategories()
Log categories.
virtual int getCacheCount(swift::misc::network::CEntityFlags::Entity entity) const =0
Cache`s number of entities.
void entityDownloadProgress(swift::misc::network::CEntityFlags::Entity entity, int logId, int progress, qint64 current, qint64 max, const QUrl &url)
Download progress for an entity.
QDateTime getLatestEntityTimestampFromSharedInfoObjects(swift::misc::network::CEntityFlags::Entity entity) const
Obtain latest object timestamp from shared info objects.
QReadWriteLock m_statusLock
Lock.
JsonDatastoreResponse transformReplyIntoDatastoreResponse(QNetworkReply *nwReply) const
Check if terminated or error, otherwise split into array of objects.
bool hasSharedFileHeader(const swift::misc::network::CEntityFlags::Entity entity) const
Header of shared file read (for single entity)?
QDateTime getLatestEntityTimestampFromDbInfoObjects(swift::misc::network::CEntityFlags::Entity entity) const
Obtain latest object timestamp from DB info objects.
void logNoWorkingUrl(swift::misc::network::CEntityFlags::Entity entity)
Log if no working URL exists, using m_noWorkingUrlSeverity.
bool supportsAnyOfEntities(swift::misc::network::CEntityFlags::Entity entities) const
Is any of the given entities supported here by this reader.
QString getSupportedEntitiesAsString() const
Supported entities as string.
void swiftDbDataRead(bool success)
DB have been read.
bool hasDbInfoObjects() const
DB info objects available?
swift::misc::network::CEntityFlags::Entity getEntitesWithNewerHeaderTimestamp(swift::misc::network::CEntityFlags::Entity entities) const
Those entities where the timestamp of the header is newer than the cache timestamp.
swift::misc::network::CEntityFlags::Entity getEntitesWithNewerSharedInfoObject(swift::misc::network::CEntityFlags::Entity entities) const
Those entities where the timestamp of a shared info object is newer than the cache timestamp.
CDatabaseReaderConfig getConfigForEntity(swift::misc::network::CEntityFlags::Entity entity) const
Config for given entity.
QMap< swift::misc::network::CEntityFlags::Entity, HeaderResponse > m_sharedFileResponses
file responses of the shared files
void receivedSharedFileHeader(QNetworkReply *nwReplyPtr)
Received a reply of a header for a shared file.
swift::misc::network::CEntityFlags::Entity maskBySupportedEntities(swift::misc::network::CEntityFlags::Entity entities) const
Mask by supported entities.
swift::misc::network::CEntityFlags::Entity triggerLoadingDirectlyFromSharedFiles(swift::misc::network::CEntityFlags::Entity entities, bool checkCacheTsUpfront)
Start loading from shared files in own thread.
bool hasSharedFileHeaders(const swift::misc::network::CEntityFlags::Entity entities) const
Headers of shared file read (for single entity)?
bool hasReceivedFirstReply() const
Has received 1st reply?
bool overrideCacheFromFile(bool overrideNewerOnly, const QFileInfo &fileInfo, swift::misc::network::CEntityFlags::Entity entity, swift::misc::CStatusMessageList &msgs) const
Override cache from file.
virtual swift::misc::network::CUrl getDbServiceBaseUrl() const =0
Get the service URL, individual for each reader.
static const QString & parameterLatestTimestamp()
Name of latest timestamp.
static const swift::misc::network::CUrl & getDbUrl()
DB base URL.
swift::misc::db::CDbInfoList getDbInfoObjects() const
DB Info list (latest data timestamps from DB web service)
QString m_statusMessage
Returned status message from watchdog.
void startReadFromBackendInBackgroundThread(swift::misc::network::CEntityFlags::Entity entities, swift::misc::db::CDbFlags::DataRetrievalModeFlag mode, const QDateTime &newerThan=QDateTime())
Start reading in own thread (without config/caching)
virtual bool readFromJsonFilesInBackground(const QString &dir, swift::misc::network::CEntityFlags::Entity whatToRead, bool overrideNewer)=0
Data read from local data.
virtual swift::misc::network::CEntityFlags::Entity getSupportedEntities() const =0
Supported entities by this reader.
swift::misc::network::CUrl getBaseUrl(swift::misc::db::CDbFlags::DataRetrievalModeFlag mode) const
Base URL for mode (either a shared or DB URL)
void dataRead(swift::misc::network::CEntityFlags::Entity entities, swift::misc::network::CEntityFlags::ReadState state, int number, const QUrl &url)
Combined read signal.
virtual swift::misc::CStatusMessageList readFromJsonFiles(const QString &dir, swift::misc::network::CEntityFlags::Entity whatToRead, bool overrideNewer)=0
Data read from local data.
CDatabaseReaderConfigList m_config
DB reder configuration.
bool setHeaderInfoPart(HeaderResponse &headerResponse, QNetworkReply *nwReply) const
Set the header part.
static bool isChangedUrl(const swift::misc::network::CUrl &oldUrl, const swift::misc::network::CUrl &currentUrl)
Has URL been changed? Means we load from a different server.
static QString fileNameForMode(swift::misc::network::CEntityFlags::Entity entity, swift::misc::db::CDbFlags::DataRetrievalModeFlag mode)
File name for given mode, either php service or shared file name.
CDatabaseReader::JsonDatastoreResponse setStatusAndTransformReplyIntoDatastoreResponse(QNetworkReply *nwReply)
Check if terminated or error, otherwise split into array of objects.
void readInBackgroundThread(swift::misc::network::CEntityFlags::Entity entities, const QDateTime &newerThan)
Start reading in own thread.
bool m_1stReplyReceived
Successful connection? Does not mean data / authorizations are correct.
swift::misc::network::CEntityFlags::Entity triggerLoadingDirectlyFromDb(swift::misc::network::CEntityFlags::Entity entities, const QDateTime &newerThan)
Start loading from DB in own thread.
const QString & getStatusMessage() const
Status message (error message)
void logParseMessage(const QString &entity, int size, int msElapsed, const JsonDatastoreResponse &response) const
Parsing info message.
bool isSharedInfoObjectNewerThanCacheTimestamp(swift::misc::network::CEntityFlags::Entity entity) const
Is the shared info timestamp newer than cache timestamp?
bool hasCacheTimestampNewerThan(swift::misc::network::CEntityFlags::Entity entity, const QDateTime &threshold) const
Has entity a valid and newer timestamp.
void receivedSharedFileHeaderNonClosing(QNetworkReply *nwReplyPtr)
Received a reply of a header for a shared file.
static QJsonDocument databaseJsonToQJsonDocument(const QString &content)
Database JSON from content string, which can be compressed.
swift::misc::db::CDbInfoList getInfoObjects() const
Get info list (either shared or from DB)
void start(QThread::Priority priority=QThread::InheritPriority)
Starts a thread and moves the worker into it.
Definition: worker.cpp:165
static const QString & webservice()
Webservice.
static const QString & swiftDbWebservice()
Webservice with swift DB.
Class for emitting a log message.
Definition: logmessage.h:27
static void preformatted(const CStatusMessage &statusMessage)
Sends a verbatim, preformatted message to the log.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Streamable status message, e.g.
constexpr static auto SeverityError
Status severities.
Status messages, e.g. from Core -> GUI.
Locations of important directories for swift files.
static const QString & staticDbFilesDirectory()
Where static DB files are located.
static QString currentThreadInfo()
Info about current thread, for debug messages.
Definition: threadutils.cpp:23
static bool isInThisThread(const QObject *toBeTested)
Is the current thread the object's thread?
Definition: threadutils.cpp:16
bool isAbandoned() const
For the task to check whether it can finish early.
Definition: worker.cpp:153
bool hasStarted() const
True if the worker has started.
Definition: worker.h:182
QDateTime getUtcTimestamp() const
Get timestamp.
DataRetrievalModeFlag
Which data to read, requires corresponding readers.
Definition: dbflags.h:25
Info about the latest models.
Definition: dbinfo.h:22
bool isValid() const
Valid?
Definition: dbinfo.cpp:23
Value object encapsulating a list of info objects.
Definition: dbinfolist.h:27
CDbInfo findFirstByEntityOrDefault(swift::misc::network::CEntityFlags::Entity entity) const
Find by entity.
Definition: dbinfolist.cpp:16
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:74
What and state of reading from web services.
Definition: entityflags.h:22
Value object encapsulating information of a location, kind of simplified CValueObject compliant versi...
Definition: url.h:27
bool isEmpty() const
Empty.
Definition: url.cpp:54
CUrl withAppendedPath(const QString &path) const
Append path.
Definition: url.cpp:115
const QString & getHost() const
Get host.
Definition: url.h:55
QString getFullUrl(bool withQuery=true) const
Qualified name.
Definition: url.cpp:84
Core data traits (aka cached values) and classes.
Classes interacting with the swift database (aka "datastore").
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
Free functions in swift::misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
const swift::misc::network::CUrl & getUrl() const
URL loaded.
QString getUrlString() const
URL loaded as string.
void setValues(const QNetworkReply *nwReply)
Set reply values.
bool hasWarningOrAboveMessage() const
Warning or error message?
QString getLoadTimeString() const
Load time as string.
QString getLoadTimeStringWithStartedHint() const
Load time as string.
void setMessage(const swift::misc::CStatusMessage &lastErrorOrWarning)
Set the error/warning message.
Response from our database (depending on JSON DB backend generates)
bool isRestricted() const
Incremental data, restricted by query?
void setJsonArray(const QJsonArray &value)
Set the JSON array.
bool isLoadedFromDb() const
Is loaded from database.