QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCTileCacheWorker.cpp
Go to the documentation of this file.
3
4#include <QtCore/QCoreApplication>
5#include <QtCore/QSettings>
6
7#include "QGCCacheTile.h"
8#include "QGCCachedTileSet.h"
10#include "QGCMapTasks.h"
11#include "QGCMapUrlEngine.h"
12
13#ifdef QGC_UNITTEST_BUILD
14#include "AppMessages.h"
15#endif
16
17QGC_LOGGING_CATEGORY(QGCTileCacheWorkerLog, "QtLocationPlugin.QGCTileCacheWorker")
18
19#ifdef QGC_UNITTEST_BUILD
20std::function<QGCCacheTile*(const QString&)> QGCCacheWorker::_unitTestTileGenerator;
21
22void QGCCacheWorker::setUnitTestTileGenerator(std::function<QGCCacheTile*(const QString &hash)> generator)
23{
24 _unitTestTileGenerator = std::move(generator);
25}
26#endif
27
29 : QThread(parent)
30{
31 qCDebug(QGCTileCacheWorkerLog) << this;
32}
33
35{
36 stop();
37 wait();
38 qCDebug(QGCTileCacheWorkerLog) << this;
39}
40
42{
43 _stopRequested = true;
44 QMutexLocker lock(&_taskQueueMutex);
45 qDeleteAll(_taskQueue);
46 _taskQueue.clear();
47 lock.unlock();
48
49 if (isRunning()) {
50 _waitc.wakeAll();
51 }
52}
53
55{
56 if (!_dbValid && !isRunning() && (task->type() != QGCMapTask::TaskType::taskInit)) {
57 task->setError(tr("Database Not Initialized"));
58 task->deleteLater();
59 return false;
60 }
61
62 QMutexLocker lock(&_taskQueueMutex);
63 _taskQueue.enqueue(task);
64 lock.unlock();
65
66 if (isRunning()) {
67 _waitc.wakeAll();
68 } else {
69 start(QThread::NormalPriority);
70 }
71
72 return true;
73}
74
76{
77 _stopRequested = false;
78 _database = std::make_unique<QGCTileCacheDatabase>(_databasePath);
79
80 if (!_database->init()) {
81 qCWarning(QGCTileCacheWorkerLog) << "Failed To Init Database";
82 _database.reset();
83
84 QMutexLocker lock(&_taskQueueMutex);
85 for (QGCMapTask *orphan : _taskQueue) {
86 orphan->setError(tr("Database Init Failed"));
87 orphan->deleteLater();
88 }
89 _taskQueue.clear();
90 return;
91 }
92
93 if (_database->isValid()) {
94 if (_database->connectDB()) {
95 _database->deleteBingNoTileTiles();
96 }
97 }
98
99 _dbValid = _database->isValid();
100
101 _updateTimer.start();
102
103 QMutexLocker lock(&_taskQueueMutex);
104 while (!_stopRequested) {
105 if (!_taskQueue.isEmpty()) {
106 QGCMapTask* const task = _taskQueue.dequeue();
107 lock.unlock();
108 _runTask(task);
109 lock.relock();
110 task->deleteLater();
111
112 const qsizetype count = _taskQueue.count();
113 if (count > 100) {
114 _updateTimeout = kLongTimeoutMs;
115 } else if (count < 25) {
116 _updateTimeout = kShortTimeoutMs;
117 }
118
119 if ((count == 0) || _updateTimer.hasExpired(_updateTimeout)) {
120 if (_database && _database->isValid()) {
121 lock.unlock();
122 _emitTotals();
123 lock.relock();
124 }
125 }
126 } else {
127 (void) _waitc.wait(lock.mutex(), 5000);
128 }
129 }
130
131 for (QGCMapTask *orphan : _taskQueue) {
132 orphan->setError(tr("Worker shutting down"));
133 orphan->deleteLater();
134 }
135 _taskQueue.clear();
136 lock.unlock();
137
138 _dbValid = false;
139 if (_database) {
140 _database->disconnectDB();
141 _database.reset();
142 }
143}
144
145void QGCCacheWorker::_runTask(QGCMapTask *task)
146{
147 switch (task->type()) {
149 break;
151 _saveTile(task);
152 break;
154 _getTile(task);
155 break;
157 _getTileSets(task);
158 break;
160 _createTileSet(task);
161 break;
163 _getTileDownloadList(task);
164 break;
166 _updateTileDownloadState(task);
167 break;
169 _deleteTileSet(task);
170 break;
172 _renameTileSet(task);
173 break;
175 _pruneCache(task);
176 break;
178 _resetCacheDatabase(task);
179 break;
181 _exportSets(task);
182 break;
184 _importSets(task);
185 break;
186 default:
187 qCWarning(QGCTileCacheWorkerLog) << "given unhandled task type" << task->type();
188 break;
189 }
190}
191
192bool QGCCacheWorker::_testTask(QGCMapTask *mtask)
193{
194 if (!_database || !_database->isValid()) {
195 mtask->setError("No Cache Database");
196 return false;
197 }
198
199 return true;
200}
201
202void QGCCacheWorker::_emitTotals()
203{
204 TotalsResult t = _database->computeTotals();
206 _updateTimer.restart();
207}
208
209void QGCCacheWorker::_saveTile(QGCMapTask *mtask)
210{
211 if (!_testTask(mtask)) {
212 return;
213 }
214
215 QGCSaveTileTask *task = static_cast<QGCSaveTileTask*>(mtask);
216 if (!_database->saveTile(task->tile()->hash, task->tile()->format,
217 task->tile()->img, task->tile()->type, task->tile()->tileSet)) {
218 mtask->setError("Error saving tile to cache");
219 }
220}
221
222void QGCCacheWorker::_getTile(QGCMapTask *mtask)
223{
224 if (!_testTask(mtask)) {
225 return;
226 }
227
228 QGCFetchTileTask *task = static_cast<QGCFetchTileTask*>(mtask);
229 auto tile = _database->getTile(task->hash());
230 if (tile) {
231 task->setTileFetched(tile.release());
232 return;
233 }
234
235 // Under unit tests a cache miss consults the synthetic tile generator instead of
236 // erroring, so tile consumers never fall back to real network fetches (late
237 // replies race UI teardown and crash). See UnitTestTileGenerator.
238#ifdef QGC_UNITTEST_BUILD
239 if (QGC::runningUnitTests() && _unitTestTileGenerator) {
240 QGCCacheTile* const generated = _unitTestTileGenerator(task->hash());
241 if (generated) {
242 task->setTileFetched(generated);
243 return;
244 }
245 }
246#endif
247
248 task->setError("Tile not in cache database");
249}
250
251void QGCCacheWorker::_getTileSets(QGCMapTask *mtask)
252{
253 if (!_testTask(mtask)) {
254 return;
255 }
256
257 QGCFetchTileSetTask *task = static_cast<QGCFetchTileSetTask*>(mtask);
258 const QList<TileSetRecord> records = _database->getTileSets();
259
260 for (const auto &rec : records) {
261 QGCCachedTileSet *set = new QGCCachedTileSet(rec.name);
262 set->blockSignals(true);
263
264 set->setId(rec.setID);
265 set->setMapTypeStr(rec.mapTypeStr);
266 set->setTopleftLat(rec.topleftLat);
267 set->setTopleftLon(rec.topleftLon);
268 set->setBottomRightLat(rec.bottomRightLat);
269 set->setBottomRightLon(rec.bottomRightLon);
270 set->setMinZoom(rec.minZoom);
271 set->setMaxZoom(rec.maxZoom);
273 set->setTotalTileCount(rec.numTiles);
274 set->setDefaultSet(rec.defaultSet);
275 set->setCreationDate(QDateTime::fromSecsSinceEpoch(rec.date));
276
277 const SetTotalsResult totals = _database->computeSetTotals(rec.setID, rec.defaultSet, rec.numTiles, set->type());
279 set->setSavedTileSize(totals.savedTileSize);
280 set->setTotalTileSize(totals.totalTileSize);
283
284 set->blockSignals(false);
285 (void) set->moveToThread(QCoreApplication::instance()->thread());
286 task->setTileSetFetched(set);
287 }
288}
289
290void QGCCacheWorker::_createTileSet(QGCMapTask *mtask)
291{
292 if (!_testTask(mtask)) {
293 return;
294 }
295
296 QGCCreateTileSetTask *task = static_cast<QGCCreateTileSetTask*>(mtask);
297 const auto setID = _database->createTileSet(
298 task->tileSet()->name(), task->tileSet()->mapTypeStr(),
299 task->tileSet()->topleftLat(), task->tileSet()->topleftLon(),
300 task->tileSet()->bottomRightLat(), task->tileSet()->bottomRightLon(),
301 task->tileSet()->minZoom(), task->tileSet()->maxZoom(),
302 task->tileSet()->type(), task->tileSet()->totalTileCount());
303
304 if (!setID.has_value()) {
305 mtask->setError("Error saving tile set");
306 return;
307 }
308
309 task->tileSet()->blockSignals(true);
310 task->tileSet()->setId(setID.value());
311
312 const SetTotalsResult totals = _database->computeSetTotals(
313 setID.value(), task->tileSet()->defaultSet(),
314 task->tileSet()->totalTileCount(), task->tileSet()->type());
316 task->tileSet()->setSavedTileSize(totals.savedTileSize);
317 task->tileSet()->setTotalTileSize(totals.totalTileSize);
320 task->tileSet()->blockSignals(false);
321
322 task->setTileSetSaved();
323}
324
325void QGCCacheWorker::_getTileDownloadList(QGCMapTask *mtask)
326{
327 if (!_testTask(mtask)) {
328 return;
329 }
330
331 QGCGetTileDownloadListTask *task = static_cast<QGCGetTileDownloadListTask*>(mtask);
332 const QList<QGCTile> tileValues = _database->getTileDownloadList(task->setID(), task->count());
333 QQueue<QGCTile*> tiles;
334 for (const auto &t : tileValues) {
335 tiles.enqueue(new QGCTile(t));
336 }
337 task->setTileListFetched(tiles);
338}
339
340void QGCCacheWorker::_updateTileDownloadState(QGCMapTask *mtask)
341{
342 if (!_testTask(mtask)) {
343 return;
344 }
345
347 bool ok;
348 if (task->hash() == QStringLiteral("*")) {
349 ok = _database->updateAllTileDownloadStates(task->setID(), static_cast<int>(task->state()));
350 } else {
351 ok = _database->updateTileDownloadState(task->setID(), static_cast<int>(task->state()), task->hash());
352 }
353 if (!ok) {
354 mtask->setError("Error updating tile download state");
355 }
356}
357
358void QGCCacheWorker::_pruneCache(QGCMapTask *mtask)
359{
360 if (!_testTask(mtask)) {
361 return;
362 }
363
364 QGCPruneCacheTask *task = static_cast<QGCPruneCacheTask*>(mtask);
365 if (!_database->pruneCache(task->amount())) {
366 mtask->setError("Error pruning cache");
367 return;
368 }
369 task->setPruned();
370}
371
372void QGCCacheWorker::_deleteTileSet(QGCMapTask *mtask)
373{
374 if (!_testTask(mtask)) {
375 return;
376 }
377
378 QGCDeleteTileSetTask *task = static_cast<QGCDeleteTileSetTask*>(mtask);
379 if (!_database->deleteTileSet(task->setID())) {
380 mtask->setError("Error deleting tile set");
381 return;
382 }
383 _emitTotals();
384 task->setTileSetDeleted();
385}
386
387void QGCCacheWorker::_renameTileSet(QGCMapTask *mtask)
388{
389 if (!_testTask(mtask)) {
390 return;
391 }
392
393 QGCRenameTileSetTask *task = static_cast<QGCRenameTileSetTask*>(mtask);
394 if (!_database->renameTileSet(task->setID(), task->newName())) {
395 task->setError("Error renaming tile set");
396 }
397}
398
399void QGCCacheWorker::_resetCacheDatabase(QGCMapTask *mtask)
400{
401 if (!_testTask(mtask)) {
402 return;
403 }
404
405 QGCResetTask *task = static_cast<QGCResetTask*>(mtask);
406 if (!_database->resetDatabase()) {
407 mtask->setError("Error resetting cache database");
408 return;
409 }
410 _dbValid = _database->isValid();
411 task->setResetCompleted();
412}
413
414void QGCCacheWorker::_importSets(QGCMapTask *mtask)
415{
416 if (!_testTask(mtask)) {
417 return;
418 }
419
420 QGCImportTileTask *task = static_cast<QGCImportTileTask*>(mtask);
421 auto progress = [task](int pct) { task->setProgress(pct); };
422
423 DatabaseResult result;
424 if (task->replace()) {
425 result = _database->importSetsReplace(task->path(), progress);
426 } else {
427 result = _database->importSetsMerge(task->path(), progress);
428 }
429
430 _dbValid = _database->isValid();
431
432 if (!result.success) {
433 task->setError(result.errorString);
434 return;
435 }
436
437 if (task->replace() && _database->isValid()) {
438 QSettings settings;
439 settings.remove(QLatin1String(QGCTileCacheDatabase::kBingNoTileDoneKey));
440 _database->deleteBingNoTileTiles();
441 }
442
443 task->setImportCompleted();
444}
445
446void QGCCacheWorker::_exportSets(QGCMapTask *mtask)
447{
448 if (!_testTask(mtask)) {
449 return;
450 }
451
452 QGCExportTileTask *task = static_cast<QGCExportTileTask*>(mtask);
453
454 auto progress = [task](int pct) { task->setProgress(pct); };
455 DatabaseResult result = _database->exportSets(task->sets(), task->path(), progress);
456
457 if (!result.success) {
458 task->setError(result.errorString);
459 return;
460 }
461
462 task->setExportCompleted();
463}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
bool enqueueTask(QGCMapTask *task)
void updateTotals(quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize)
QGCCacheWorker(QObject *parent=nullptr)
void setTotalTileCount(quint32 num)
double bottomRightLon() const
double topleftLat() const
void setUniqueTileCount(quint32 num)
void setTotalTileSize(quint64 size)
void setSavedTileCount(quint32 num)
void setMinZoom(int zoom)
void setSavedTileSize(quint64 size)
void setMaxZoom(int zoom)
void setTopleftLat(double lat)
const QString & type() const
double topleftLon() const
void setDefaultSet(bool def)
quint32 totalTileCount() const
const QString & name() const
void setBottomRightLon(double lon)
void setType(const QString &type)
void setId(quint64 id)
const QString & mapTypeStr() const
void setBottomRightLat(double lat)
bool defaultSet() const
void setCreationDate(const QDateTime &date)
void setMapTypeStr(const QString &typeStr)
double bottomRightLat() const
void setUniqueTileSize(quint64 size)
void setTopleftLon(double lon)
QGCCachedTileSet * tileSet()
Definition QGCMapTasks.h:49
quint64 setID() const
void setProgress(int percentage)
QString path() const
void setExportCompleted()
const QList< TileSetRecord > & sets() const
void setTileSetFetched(QGCCachedTileSet *tileSet)
Definition QGCMapTasks.h:26
QString hash() const
Definition QGCMapTasks.h:83
void setTileFetched(QGCCacheTile *tile)
Definition QGCMapTasks.h:78
void setTileListFetched(const QQueue< QGCTile * > &tiles)
void setImportCompleted()
bool replace() const
void setProgress(int percentage)
QString path() const
void setError(const QString &errorString=QString())
TaskType type() const
quint64 amount() const
QString newName() const
quint64 setID() const
void setResetCompleted()
const QGCCacheTile * tile() const
static constexpr const char * kBingNoTileDoneKey
QGCTile::TileState state() const
static QString getProviderTypeFromQtMapId(int qtMapId)
bool runningUnitTests()
quint64 tileSet
QByteArray img
QString format