swift
backgrounddataupdater.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2017 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QElapsedTimer>
7 
8 #include "core/application.h"
10 #include "core/db/databasewriter.h"
11 #include "core/webdataservices.h"
12 #include "misc/eventloop.h"
13 #include "misc/logmessage.h"
15 #include "misc/threadutils.h"
16 
17 using namespace swift::misc;
18 using namespace swift::misc::network;
19 using namespace swift::misc::simulation;
20 using namespace swift::misc::simulation::data;
21 using namespace swift::core;
22 using namespace swift::core::db;
23 
24 namespace swift::core::db
25 {
26  const QStringList &CBackgroundDataUpdater::getLogCategories()
27  {
28  static const QStringList cats(
30  return cats;
31  }
32 
33  CBackgroundDataUpdater::CBackgroundDataUpdater(QObject *owner) : CContinuousWorker(owner, "Background data updater")
34  {
35  connect(&m_updateTimer, &QTimer::timeout, this, &CBackgroundDataUpdater::doWork);
36  m_updateTimer.setInterval(60 * 1000);
37  if (sApp && sApp->hasWebDataServices())
38  {
40  &CBackgroundDataUpdater::onModelsPublished, Qt::QueuedConnection);
41  }
42  }
43 
45  {
46  QReadLocker l(&m_lockMsg);
47  return m_messageHistory;
48  }
49 
50  void CBackgroundDataUpdater::doWork()
51  {
52  if (!this->doWorkCheck()) { return; }
53  if (m_inWork) { return; }
54  m_inWork = true;
55 
56  emit this->consolidating(true);
57  int cycle = m_cycle;
58  this->addHistory(CLogMessage(this).info(u"Background consolidation cycle %1") << cycle);
59 
60  switch (cycle)
61  {
62  case 0:
63  // normally redundant, will be read in other places as well
64  // new metadata for next comparison
65  this->addHistory(CLogMessage(this).info(u"Triggered info reads from DB"));
66  this->triggerInfoReads();
67  break;
68  case 1:
69  this->addHistory(CLogMessage(this).info(u"Synchronize DB entities"));
70  this->syncDbEntity(CEntityFlags::AirlineIcaoEntity);
71  this->syncDbEntity(CEntityFlags::LiveryEntity);
72  this->syncDbEntity(CEntityFlags::ModelEntity);
73  this->syncDbEntity(CEntityFlags::DistributorEntity);
74  this->syncDbEntity(CEntityFlags::AircraftIcaoEntity);
75  break;
76  case 2:
77  this->addHistory(CLogMessage(this).info(u"Synchronize %1") << this->modelCaches(true).getDescription());
78  this->syncModelOrModelSetCacheWithDbData(true); // set
79  break;
80  case 3:
81  this->addHistory(CLogMessage(this).info(u"Synchronize %1") << this->modelCaches(false).getDescription());
82  this->syncModelOrModelSetCacheWithDbData(false);
83  break;
84  default: break;
85  }
86 
87  ++cycle %= 4;
88  m_cycle = cycle;
89  m_inWork = false;
90  emit this->consolidating(false);
91  }
92 
93  void CBackgroundDataUpdater::triggerInfoReads()
94  {
95  if (!this->doWorkCheck()) { return; }
98  }
99 
100  void CBackgroundDataUpdater::syncModelOrModelSetCacheWithDbData(bool modelSetFlag,
101  const CAircraftModelList &dbModelsConsidered)
102  {
103  if (!this->doWorkCheck()) { return; }
104  IMultiSimulatorModelCaches &modelCaches = this->modelCaches(modelSetFlag);
105  const QDateTime latestDbModelsTs =
106  dbModelsConsidered.isEmpty() ? sApp->getWebDataServices()->getCacheTimestamp(CEntityFlags::ModelEntity) :
107  dbModelsConsidered.latestTimestamp();
108  if (!latestDbModelsTs.isValid()) { return; }
109 
110  // newer DB models as cache
111  const QDateTime dbModelsLatestSync = m_syncedModelsLatestChange.value(modelCaches.getDescription());
112  if (dbModelsLatestSync.isValid() && latestDbModelsTs <= dbModelsLatestSync) { return; }
113 
114  const QString description = modelCaches.getDescription();
115  m_syncedModelsLatestChange[description] = latestDbModelsTs;
116  const CSimulatorInfo simulators = modelCaches.simulatorsWithInitializedCache(); // simulators ever used
117  if (simulators.isNoSimulator()) { return; }
118 
119  const CAircraftModelList dbModels =
120  dbModelsConsidered.isEmpty() ? sApp->getWebDataServices()->getModels() : dbModelsConsidered;
121  if (dbModels.isEmpty()) { return; }
122  const QSet<CSimulatorInfo> simulatorsSet = simulators.asSingleSimulatorSet();
123  QElapsedTimer time;
124  for (const CSimulatorInfo &singleSimulator : simulatorsSet)
125  {
126  if (!this->doWorkCheck()) { return; }
127  CAircraftModelList simulatorModels = modelCaches.getSynchronizedCachedModels(singleSimulator);
128  if (simulatorModels.isEmpty()) { continue; }
129  time.restart();
130  const CAircraftModelList dbModelsForSimulator = dbModels.matchesSimulator(singleSimulator);
131  if (dbModelsForSimulator.isEmpty()) { continue; }
132 
133  // time consuming part
134  const int c = CDatabaseUtils::consolidateModelsWithDbData(dbModelsForSimulator, simulatorModels, true);
135  if (c > 0)
136  {
137  const CStatusMessage m = modelCaches.setCachedModels(simulatorModels, singleSimulator);
138  const int msElapsed = time.elapsed();
139  this->addHistory(CLogMessage(this).info(u"Consolidated %1 models (%2) for '%3' in %4ms")
140  << c << description << singleSimulator.convertToQString() << msElapsed);
142  this->addHistory(m);
143  }
144  else
145  {
146  this->addHistory(CLogMessage(this).info(u"Synchronized, no changes for '%1'")
147  << singleSimulator.convertToQString());
148  }
149 
150  if (simulatorsSet.size() > 1)
151  {
152  // just give the system some time to relax, consolidation is time consuming
153  if (!this->doWorkCheck()) { return; }
154  CEventLoop eventLoop(this);
155  eventLoop.exec(1000);
156  }
157  }
158  }
159 
160  void CBackgroundDataUpdater::syncDbEntity(CEntityFlags::Entity entity)
161  {
162  if (!this->doWorkCheck()) { return; }
163  const QDateTime latestCacheTs = sApp->getWebDataServices()->getCacheTimestamp(entity);
164  if (!latestCacheTs.isValid()) { return; }
165 
166  const QDateTime latestDbTs = sApp->getWebDataServices()->getLatestDbEntityTimestamp(entity);
167  const QString entityStr = CEntityFlags::entitiesToString(entity);
168  const QString latestCacheTsStr = latestCacheTs.toString(Qt::ISODate);
169 
170  if (!latestDbTs.isValid()) { return; }
171  if (latestDbTs <= latestCacheTs)
172  {
173  this->addHistory(
174  CLogMessage(this).info(
175  u"Background updater (%1), no auto synchronization with DB, entity '%2', DB ts: %3 cache ts: %4")
176  << CThreadUtils::currentThreadInfo() << entityStr << latestDbTs.toString(Qt::ISODate)
177  << latestCacheTsStr);
178  return;
179  }
180 
181  this->addHistory(CLogMessage(this).info(u"Background updater (%1) triggering read of '%2' since '%3'")
182  << CThreadUtils::currentThreadInfo() << entityStr << latestCacheTsStr);
183  sApp->getWebDataServices()->triggerLoadingDirectlyFromDb(entity, latestCacheTs);
184  }
185 
186  bool CBackgroundDataUpdater::doWorkCheck() const
187  {
188  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return false; }
189  if (!this->isEnabled()) { return false; }
190  return true;
191  }
192 
193  void CBackgroundDataUpdater::onModelsPublished(const CAircraftModelList &modelsPublished, bool directWrite)
194  {
195  if (!this->doWorkCheck()) { return; }
196  if (modelsPublished.isEmpty()) { return; }
197  if (!m_updatePublishedModels) { return; }
198  if (!directWrite) { return; } // those models are CRs and have to be released first
199  if (m_inWork) { return; } // no 2 updates at the same time
200 
201  emit this->consolidating(true);
202  this->syncModelOrModelSetCacheWithDbData(true, modelsPublished);
203  this->syncModelOrModelSetCacheWithDbData(false, modelsPublished);
204  emit this->consolidating(false);
205  }
206 
207  IMultiSimulatorModelCaches &CBackgroundDataUpdater::modelCaches(bool modelSetFlag)
208  {
209  if (modelSetFlag) { return m_modelSets; }
210  return m_modelCaches;
211  }
212 
213  void CBackgroundDataUpdater::addHistory(const CStatusMessage &msg)
214  {
215  QWriteLocker l(&m_lockMsg);
216  m_messageHistory.push_frontMaxElements(msg, 100); // latest
217  }
218 } // namespace swift::core::db
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
bool hasWebDataServices() const
Web data services available?
bool isShuttingDown() const
Is application shutting down?
CWebDataServices * getWebDataServices() const
Get the web data services.
db::CDatabaseWriter * getDatabaseWriter() const
DB writer class.
QDateTime getLatestDbEntityTimestamp(swift::misc::network::CEntityFlags::Entity entity) const
Corresponding DB timestamp if applicable.
swift::misc::simulation::CAircraftModelList getModels() const
Models.
QDateTime getCacheTimestamp(swift::misc::network::CEntityFlags::Entity entity) const
Corresponding cache timestamp if applicable.
swift::misc::network::CEntityFlags::Entity triggerLoadingDirectlyFromDb(swift::misc::network::CEntityFlags::Entity whatToRead, const QDateTime &newerThan=QDateTime())
Trigger reload from DB, loads the DB data and bypasses the caches checks and info objects.
void triggerReadOfSharedInfoObjects()
Trigger read of shared info objects.
void triggerReadOfDbInfoObjects()
Trigger read of DB info objects.
void consolidating(bool running)
Consolidation.
swift::misc::CStatusMessageList getMessageHistory() const
The message history.
static int consolidateModelsWithDbData(swift::misc::simulation::CAircraftModelList &models, bool force)
Consolidate models with DB data.
void publishedModelsSimplified(const swift::misc::simulation::CAircraftModelList &modelsPublished, bool directWrite)
Published models, simplified version of publishedModels.
Base class for a long-lived worker object which lives in its own thread.
Definition: worker.h:275
bool isEnabled() const
Enabled (running)?
Definition: worker.h:300
QTimer m_updateTimer
timer which can be used by implementing classes
Definition: worker.h:333
Utility class which blocks until a signal is emitted or timeout reached.
Definition: eventloop.h:20
static const QString & worker()
Background task.
static const QString & modelSetCache()
Model set cache.
static const QString & modelCache()
Model cache.
Class for emitting a log message.
Definition: logmessage.h:27
static void preformatted(const CStatusMessage &statusMessage)
Sends a verbatim, preformatted message to the log.
void push_frontMaxElements(const T &value, int maxElements)
Insert as first element by keep maxElements.
Definition: sequence.h:314
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Streamable status message, e.g.
Status messages, e.g. from Core -> GUI.
static QString currentThreadInfo()
Info about current thread, for debug messages.
Definition: threadutils.cpp:23
QDateTime latestTimestamp() const
Latest timestamp.
Value object encapsulating a list of aircraft models.
CAircraftModelList matchesSimulator(const CSimulatorInfo &simulator) const
Find for given simulator.
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
QSet< CSimulatorInfo > asSingleSimulatorSet() const
As a set of single simulator info objects.
bool isNoSimulator() const
No simulator?
Cache for multiple simulators specified by CSimulatorInfo.
Definition: modelcaches.h:189
CSimulatorInfo simulatorsWithInitializedCache() const
Initialized caches for which simulator?
virtual QString getDescription() const =0
Descriptive text.
CAircraftModelList getSynchronizedCachedModels(const CSimulatorInfo &simulator)
Models.
virtual CStatusMessage setCachedModels(const CAircraftModelList &models, const CSimulatorInfo &simulator)=0
Set cached models.
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.