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)
34  : CContinuousWorker(owner, "Background data updater"), m_updateTimer(this, "Background data updater")
35  {
36  connect(&m_updateTimer, &misc::CThreadedTimer::timeout, this, &CBackgroundDataUpdater::doWork);
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::startUpdating(std::chrono::milliseconds ms)
51  {
52  Q_ASSERT_X(this->hasStarted(), Q_FUNC_INFO, "Worker not started yet");
53  m_updateTimer.startTimer(ms);
54  }
55 
56  void CBackgroundDataUpdater::doWork()
57  {
58  if (!this->doWorkCheck()) { return; }
59  if (m_inWork) { return; }
60  m_inWork = true;
61 
62  emit this->consolidating(true);
63  int cycle = m_cycle;
64  this->addHistory(CLogMessage(this).info(u"Background consolidation cycle %1") << cycle);
65 
66  switch (cycle)
67  {
68  case 0:
69  // normally redundant, will be read in other places as well
70  // new metadata for next comparison
71  this->addHistory(CLogMessage(this).info(u"Triggered info reads from DB"));
72  this->triggerInfoReads();
73  break;
74  case 1:
75  this->addHistory(CLogMessage(this).info(u"Synchronize DB entities"));
76  this->syncDbEntity(CEntityFlags::AirlineIcaoEntity);
77  this->syncDbEntity(CEntityFlags::LiveryEntity);
78  this->syncDbEntity(CEntityFlags::ModelEntity);
79  this->syncDbEntity(CEntityFlags::DistributorEntity);
80  this->syncDbEntity(CEntityFlags::AircraftIcaoEntity);
81  break;
82  case 2:
83  this->addHistory(CLogMessage(this).info(u"Synchronize %1") << this->modelCaches(true).getDescription());
84  this->syncModelOrModelSetCacheWithDbData(true); // set
85  break;
86  case 3:
87  this->addHistory(CLogMessage(this).info(u"Synchronize %1") << this->modelCaches(false).getDescription());
88  this->syncModelOrModelSetCacheWithDbData(false);
89  break;
90  default: break;
91  }
92 
93  ++cycle %= 4;
94  m_cycle = cycle;
95  m_inWork = false;
96  emit this->consolidating(false);
97  }
98 
99  void CBackgroundDataUpdater::triggerInfoReads()
100  {
101  if (!this->doWorkCheck()) { return; }
104  }
105 
106  void CBackgroundDataUpdater::syncModelOrModelSetCacheWithDbData(bool modelSetFlag,
107  const CAircraftModelList &dbModelsConsidered)
108  {
109  if (!this->doWorkCheck()) { return; }
110  IMultiSimulatorModelCaches &modelCaches = this->modelCaches(modelSetFlag);
111  const QDateTime latestDbModelsTs =
112  dbModelsConsidered.isEmpty() ? sApp->getWebDataServices()->getCacheTimestamp(CEntityFlags::ModelEntity) :
113  dbModelsConsidered.latestTimestamp();
114  if (!latestDbModelsTs.isValid()) { return; }
115 
116  // newer DB models as cache
117  const QDateTime dbModelsLatestSync = m_syncedModelsLatestChange.value(modelCaches.getDescription());
118  if (dbModelsLatestSync.isValid() && latestDbModelsTs <= dbModelsLatestSync) { return; }
119 
120  const QString description = modelCaches.getDescription();
121  m_syncedModelsLatestChange[description] = latestDbModelsTs;
122  const CSimulatorInfo simulators = modelCaches.simulatorsWithInitializedCache(); // simulators ever used
123  if (simulators.isNoSimulator()) { return; }
124 
125  const CAircraftModelList dbModels =
126  dbModelsConsidered.isEmpty() ? sApp->getWebDataServices()->getModels() : dbModelsConsidered;
127  if (dbModels.isEmpty()) { return; }
128  const QSet<CSimulatorInfo> simulatorsSet = simulators.asSingleSimulatorSet();
129  QElapsedTimer time;
130  for (const CSimulatorInfo &singleSimulator : simulatorsSet)
131  {
132  if (!this->doWorkCheck()) { return; }
133  CAircraftModelList simulatorModels = modelCaches.getSynchronizedCachedModels(singleSimulator);
134  if (simulatorModels.isEmpty()) { continue; }
135  time.restart();
136  const CAircraftModelList dbModelsForSimulator = dbModels.matchesSimulator(singleSimulator);
137  if (dbModelsForSimulator.isEmpty()) { continue; }
138 
139  // time consuming part
140  const int c = CDatabaseUtils::consolidateModelsWithDbData(dbModelsForSimulator, simulatorModels, true);
141  if (c > 0)
142  {
143  const CStatusMessage m = modelCaches.setCachedModels(simulatorModels, singleSimulator);
144  const qint64 msElapsed = time.elapsed();
145  this->addHistory(CLogMessage(this).info(u"Consolidated %1 models (%2) for '%3' in %4ms")
146  << c << description << singleSimulator.convertToQString() << msElapsed);
148  this->addHistory(m);
149  }
150  else
151  {
152  this->addHistory(CLogMessage(this).info(u"Synchronized, no changes for '%1'")
153  << singleSimulator.convertToQString());
154  }
155 
156  if (simulatorsSet.size() > 1)
157  {
158  // just give the system some time to relax, consolidation is time consuming
159  if (!this->doWorkCheck()) { return; }
160  CEventLoop eventLoop(this);
161  eventLoop.exec(1000);
162  }
163  }
164  }
165 
166  void CBackgroundDataUpdater::syncDbEntity(CEntityFlags::Entity entity)
167  {
168  if (!this->doWorkCheck()) { return; }
169  const QDateTime latestCacheTs = sApp->getWebDataServices()->getCacheTimestamp(entity);
170  if (!latestCacheTs.isValid()) { return; }
171 
172  const QDateTime latestDbTs = sApp->getWebDataServices()->getLatestDbEntityTimestamp(entity);
173  const QString entityStr = CEntityFlags::entitiesToString(entity);
174  const QString latestCacheTsStr = latestCacheTs.toString(Qt::ISODate);
175 
176  if (!latestDbTs.isValid()) { return; }
177  if (latestDbTs <= latestCacheTs)
178  {
179  this->addHistory(
180  CLogMessage(this).info(
181  u"Background updater (%1), no auto synchronization with DB, entity '%2', DB ts: %3 cache ts: %4")
182  << CThreadUtils::currentThreadInfo() << entityStr << latestDbTs.toString(Qt::ISODate)
183  << latestCacheTsStr);
184  return;
185  }
186 
187  this->addHistory(CLogMessage(this).info(u"Background updater (%1) triggering read of '%2' since '%3'")
188  << CThreadUtils::currentThreadInfo() << entityStr << latestCacheTsStr);
189  sApp->getWebDataServices()->triggerLoadingDirectlyFromDb(entity, latestCacheTs);
190  }
191 
192  bool CBackgroundDataUpdater::doWorkCheck() const
193  {
194  if (!sApp || sApp->isShuttingDown() || !sApp->hasWebDataServices()) { return false; }
195  if (!this->isEnabled()) { return false; }
196  return true;
197  }
198 
199  void CBackgroundDataUpdater::onModelsPublished(const CAircraftModelList &modelsPublished, bool directWrite)
200  {
201  if (!this->doWorkCheck()) { return; }
202  if (modelsPublished.isEmpty()) { return; }
203  if (!m_updatePublishedModels) { return; }
204  if (!directWrite) { return; } // those models are CRs and have to be released first
205  if (m_inWork) { return; } // no 2 updates at the same time
206 
207  emit this->consolidating(true);
208  this->syncModelOrModelSetCacheWithDbData(true, modelsPublished);
209  this->syncModelOrModelSetCacheWithDbData(false, modelsPublished);
210  emit this->consolidating(false);
211  }
212 
213  IMultiSimulatorModelCaches &CBackgroundDataUpdater::modelCaches(bool modelSetFlag)
214  {
215  if (modelSetFlag) { return m_modelSets; }
216  return m_modelCaches;
217  }
218 
219  void CBackgroundDataUpdater::addHistory(const CStatusMessage &msg)
220  {
221  QWriteLocker l(&m_lockMsg);
222  m_messageHistory.push_frontMaxElements(msg, 100); // latest
223  }
224 } // 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.
void startUpdating(std::chrono::milliseconds ms)
Start the updating timer.
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:299
bool isEnabled() const
Enabled (running)?
Definition: worker.h:324
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
void startTimer(std::chrono::milliseconds ms)
Start updating (start timer)
bool hasStarted() const
True if the worker has started.
Definition: worker.h:182
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.