diff --git a/src/QtLocationPlugin/CMakeLists.txt b/src/QtLocationPlugin/CMakeLists.txt index 421c79a822e..3a92a0d4987 100644 --- a/src/QtLocationPlugin/CMakeLists.txt +++ b/src/QtLocationPlugin/CMakeLists.txt @@ -26,6 +26,8 @@ qt_add_plugin(QGCLocation STATIC QGCMapUrlEngine.cpp QGCMapUrlEngine.h QGCTile.h + QGCTileCacheDatabase.cpp + QGCTileCacheDatabase.h QGCTileCacheWorker.cpp QGCTileCacheWorker.h QGCTileSet.h @@ -53,6 +55,7 @@ endif() target_link_libraries(QGCLocation PRIVATE Qt6::Positioning + Qt6::Sql QGC Settings Utilities @@ -61,7 +64,6 @@ target_link_libraries(QGCLocation Qt6::Location Qt6::LocationPrivate Qt6::Network - Qt6::Sql QmlControls ) diff --git a/src/QtLocationPlugin/QGCMapEngine.cpp b/src/QtLocationPlugin/QGCMapEngine.cpp index 19b83aed225..88c735b5f48 100644 --- a/src/QtLocationPlugin/QGCMapEngine.cpp +++ b/src/QtLocationPlugin/QGCMapEngine.cpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #define CACHE_PATH_VERSION "300" @@ -51,23 +53,27 @@ QGCMapEngine* QGCMapEngine::instance() QGCMapEngine::QGCMapEngine(QObject *parent) : QObject(parent) , m_worker(new QGCCacheWorker(this)) + // , m_thread(new QThread(this)) { // qCDebug(QGCMapEngineLog) << Q_FUNC_INFO << this; - (void) qRegisterMetaType(); - (void) qRegisterMetaType(); - (void) qRegisterMetaType>(); - (void) qRegisterMetaType(); - (void) qRegisterMetaType(); + (void) qRegisterMetaType("GCMapTask::TaskType"); + (void) qRegisterMetaType("QGCTile"); + (void) qRegisterMetaType>("QList"); + (void) qRegisterMetaType("QGCTileSet"); + (void) qRegisterMetaType("QGCCacheTile"); - (void) connect(m_worker, &QGCCacheWorker::updateTotals, this, &QGCMapEngine::_updateTotals); + // m_worker->moveToThread(m_thread); + (void) connect(m_worker, &QGCCacheWorker::updateTotals, this, &QGCMapEngine::_updateTotals, Qt::AutoConnection); + // m_thread->start(); } QGCMapEngine::~QGCMapEngine() { (void) disconnect(m_worker); - m_worker->quit(); - m_worker->wait(); + (void) QMetaObject::invokeMethod(m_worker, "stop", Qt::AutoConnection); + m_worker->stop(); + (void) m_worker->wait(); // qCDebug(QGCMapEngineLog) << Q_FUNC_INFO << this; } @@ -96,7 +102,7 @@ void QGCMapEngine::init() m_cachePath = cacheDir; if (!m_cachePath.isEmpty()) { const QString databaseFilePath(m_cachePath + "/" + QGeoFileTileCacheQGC::getCacheFilename()); - m_worker->setDatabaseFile(databaseFilePath); + m_worker->setDatabaseFilePath(databaseFilePath); qCDebug(QGCMapEngineLog) << "Map Cache in:" << databaseFilePath; } else { @@ -115,7 +121,9 @@ void QGCMapEngine::init() bool QGCMapEngine::addTask(QGCMapTask *task) { - return m_worker->enqueueTask(task); + bool result; + (void) QMetaObject::invokeMethod(m_worker, "enqueueTask", Qt::AutoConnection, qReturnArg(result), task); + return result; } bool QGCMapEngine::_wipeDirectory(const QString &dirPath) diff --git a/src/QtLocationPlugin/QGCMapEngine.h b/src/QtLocationPlugin/QGCMapEngine.h index 315b5a356d3..41943c709c3 100644 --- a/src/QtLocationPlugin/QGCMapEngine.h +++ b/src/QtLocationPlugin/QGCMapEngine.h @@ -26,6 +26,7 @@ Q_DECLARE_LOGGING_CATEGORY(QGCMapEngineLog) class QGCMapTask; class QGCCacheWorker; +class QThread; class QGCMapEngine : public QObject { @@ -57,6 +58,7 @@ private slots: bool m_prunning = false; bool m_cacheWasReset = false; QString m_cachePath; + QThread *m_thread = nullptr; }; QGCMapEngine* getQGCMapEngine(); diff --git a/src/QtLocationPlugin/QGCTileCacheDatabase.cpp b/src/QtLocationPlugin/QGCTileCacheDatabase.cpp new file mode 100644 index 00000000000..757111e7bad --- /dev/null +++ b/src/QtLocationPlugin/QGCTileCacheDatabase.cpp @@ -0,0 +1,766 @@ +#include "QGCTileCacheDatabase.h" +#include "QGCCacheTile.h" +#include "QGCCachedTileSet.h" +#include "QGCMapUrlEngine.h" +#include "QGCLoggingCategory.h" + +#include +#include +#include +#include + +QGC_LOGGING_CATEGORY(QGCTileCacheDBLog, "qgc.qtlocationplugin.qgctilecachedb") + +/*===========================================================================*/ + +SetTilesTableModel::SetTilesTableModel(QObject *parent, const QSqlDatabase &db) + : QSqlTableModel(parent, db) +{ + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; + + setTable("setTiles"); +} + +SetTilesTableModel::~SetTilesTableModel() +{ + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; +} + +bool SetTilesTableModel::create() +{ + QSqlQuery query; + query.prepare( + "CREATE TABLE IF NOT EXISTS SetTiles (" + "setID INTEGER, " + "tileID INTEGER)" + ); + return query.exec(); +} + +bool SetTilesTableModel::selectFromSetID(quint64 setID) +{ + QSqlQuery query; + query.prepare("SELECT * FROM SetTiles WHERE setID = ?"); + query.addBindValue(setID); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool SetTilesTableModel::insertSetTiles(quint64 setID, quint64 tileID) +{ + QSqlQuery query; + query.prepare("INSERT INTO SetTiles(tileID, setID) VALUES(?, ?)"); // INSERT OR IGNORE? + query.addBindValue(tileID); + query.addBindValue(setID); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool SetTilesTableModel::deleteTileSet(quint64 setID) +{ + QSqlQuery query; + (void) query.prepare("DELETE FROM SetTiles WHERE setID = ?"); + query.addBindValue(setID); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool SetTilesTableModel::drop() +{ + QSqlQuery query; + query.prepare("DROP TABLE SetTiles"); + return query.exec(); +} + +/*===========================================================================*/ + +quint64 TileSetsTableModel::_defaultSet = UINT64_MAX; + +TileSetsTableModel::TileSetsTableModel(QObject *parent, const QSqlDatabase &db) + : QSqlTableModel(parent, db) +{ + setTable("TileSets"); + + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; +} + +TileSetsTableModel::~TileSetsTableModel() +{ + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; +} + +bool TileSetsTableModel::create() +{ + QSqlQuery query; + query.prepare( + "CREATE TABLE IF NOT EXISTS TileSets (" + "setID INTEGER PRIMARY KEY NOT NULL, " + "name TEXT NOT NULL UNIQUE, " + "typeStr TEXT, " + "topleftLat REAL DEFAULT 0.0, " + "topleftLon REAL DEFAULT 0.0, " + "bottomRightLat REAL DEFAULT 0.0, " + "bottomRightLon REAL DEFAULT 0.0, " + "minZoom INTEGER DEFAULT 3, " + "maxZoom INTEGER DEFAULT 3, " + "type INTEGER DEFAULT -1, " + "numTiles INTEGER DEFAULT 0, " + "defaultSet INTEGER DEFAULT 0, " + "date INTEGER DEFAULT 0)" + ); + return query.exec(); +} + +bool TileSetsTableModel::insertTileSet(const QGCCachedTileSet &tileSet) +{ + QSqlQuery query; + (void) query.prepare( + "INSERT INTO TileSets" + "(name, typeStr, topleftLat, topleftLon, bottomRightLat, bottomRightLon, minZoom, maxZoom, type, numTiles, date) " + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + ); + query.addBindValue(tileSet.name()); + query.addBindValue(tileSet.mapTypeStr()); + query.addBindValue(tileSet.topleftLat()); + query.addBindValue(tileSet.topleftLon()); + query.addBindValue(tileSet.bottomRightLat()); + query.addBindValue(tileSet.bottomRightLon()); + query.addBindValue(tileSet.minZoom()); + query.addBindValue(tileSet.maxZoom()); + query.addBindValue(UrlFactory::getQtMapIdFromProviderType(tileSet.type())); + query.addBindValue(tileSet.totalTileCount()); + query.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); + + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool TileSetsTableModel::getTileSets(QList &tileSets) +{ + QSqlQuery query; + query.prepare("SELECT * FROM TileSets ORDER BY defaultSet DESC, name ASC"); + if (!query.exec()) { + qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + while (query.next()) { + const QString name = query.value("name").toString(); + QGCCachedTileSet* const set = new QGCCachedTileSet(name); + set->setId(query.value("setID").toULongLong()); + set->setMapTypeStr(query.value("typeStr").toString()); + set->setTopleftLat(query.value("topleftLat").toDouble()); + set->setTopleftLon(query.value("topleftLon").toDouble()); + set->setBottomRightLat(query.value("bottomRightLat").toDouble()); + set->setBottomRightLon(query.value("bottomRightLon").toDouble()); + set->setMinZoom(query.value("minZoom").toInt()); + set->setMaxZoom(query.value("maxZoom").toInt()); + set->setType(UrlFactory::getProviderTypeFromQtMapId(query.value("type").toInt())); + set->setTotalTileCount(query.value("numTiles").toUInt()); + set->setDefaultSet(query.value("defaultSet").toInt() != 0); + set->setCreationDate(QDateTime::fromSecsSinceEpoch(query.value("date").toUInt())); + (void) tileSets.append(set); + } + + return true; +} + +bool TileSetsTableModel::setName(quint64 setID, const QString &newName) +{ + QSqlQuery query; + query.prepare("UPDATE TileSets SET name = ? WHERE setID = ?"); + query.addBindValue(newName); + query.addBindValue(setID); + return query.exec(); +} + +bool TileSetsTableModel::setNumTiles(quint64 setID, quint64 numTiles) +{ + QSqlQuery query; + query.prepare("UPDATE TileSets SET numTiles = ? WHERE setID = ?"); + query.addBindValue(numTiles); + query.addBindValue(setID); + return query.exec(); +} + +bool TileSetsTableModel::getTileSetID(quint64 &setID, const QString &name) +{ + QSqlQuery query; + query.prepare("SELECT setID FROM TileSets WHERE name = ?"); + query.addBindValue(name); + if (query.exec() && query.next()) { + setID = query.value(0).toULongLong(); + return true; + } + + return false; +} + +bool TileSetsTableModel::getDefaultTileSet(quint64 &setID) +{ + if (_defaultSet != UINT64_MAX) { + setID = _defaultSet; + return true; + } + + QSqlQuery query; + query.prepare("SELECT setID FROM TileSets WHERE defaultSet = 1"); + if (query.exec() && query.next()) { + _defaultSet = query.value(0).toULongLong(); + setID = _defaultSet; + return true; + } + + setID = 1; + return false; +} + +bool TileSetsTableModel::deleteTileSet(quint64 setID) +{ + QSqlQuery query; + (void) query.prepare("DELETE FROM TileSets WHERE setID = ?"); + query.addBindValue(setID); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool TileSetsTableModel::drop() +{ + QSqlQuery query; + query.prepare("DROP TABLE TileSets"); + return query.exec(); +} + +bool TileSetsTableModel::createDefaultTileSet() +{ + static const QString kDefaultSet = QStringLiteral("Default Tile Set"); + + QSqlQuery query; + (void) query.prepare("SELECT name FROM TileSets WHERE name = ?"); + query.addBindValue(kDefaultSet); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + if (query.next()) { + return true; + } + + (void) query.prepare("INSERT INTO TileSets(name, defaultSet, date) VALUES(?, ?, ?)"); + query.addBindValue(kDefaultSet); + query.addBindValue(1); + query.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + + +/*===========================================================================*/ + +QByteArray TilesTableModel::_bingNoTileImage; + +TilesTableModel::TilesTableModel(QObject *parent, const QSqlDatabase &db) + : QSqlTableModel(parent, db) +{ + setTable("Tiles"); + + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; +} + +TilesTableModel::~TilesTableModel() +{ + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; +} + +bool TilesTableModel::create() +{ + QSqlQuery query; + query.prepare( + "CREATE TABLE IF NOT EXISTS Tiles (" + "tileID INTEGER PRIMARY KEY NOT NULL, " + "hash TEXT NOT NULL UNIQUE, " + "format TEXT NOT NULL, " + "tile BLOB NULL, " + "size INTEGER, " + "type INTEGER, " + "date INTEGER DEFAULT 0)" + ); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + query.prepare( + "CREATE INDEX IF NOT EXISTS hash ON Tiles (" + "hash, " + "size, " + "type)" + ); + + return query.exec(); +} + +bool TilesTableModel::getTile(QGCCacheTile *tile, const QString &hash) +{ + QSqlQuery query; + (void) query.prepare("SELECT tile, format, type FROM Tiles WHERE hash = ?"); + query.addBindValue(hash); + if (!query.exec() || !query.next()) { + qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text();; + return false; + } + + const QByteArray array = query.value(0).toByteArray(); + const QString format = query.value(1).toString(); + const QString type = query.value(2).toString(); + tile = new QGCCacheTile(hash, array, format, type); + qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text();; + + return true; +} + +bool TilesTableModel::getTileID(quint64 &tileID, const QString &hash) +{ + QSqlQuery query; + (void) query.prepare("SELECT tileID FROM Tiles WHERE hash = ?"); + query.addBindValue(hash); + if (query.exec() && query.next()) { + tileID = query.value(0).toULongLong(); + return true; + } + + qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text();; + return false; +} + +bool TilesTableModel::selectFromSetID(quint64 tileID) +{ + QSqlQuery query; + query.prepare("SELECT * FROM Tiles WHERE tileID = ?"); + query.addBindValue(tileID); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool TilesTableModel::insertTile(const QGCCacheTile &tile) +{ + QSqlQuery query; + (void) query.prepare("INSERT INTO Tiles(hash, format, tile, size, type, date) VALUES(?, ?, ?, ?, ?, ?)"); + query.addBindValue(tile.hash()); + query.addBindValue(tile.format()); + query.addBindValue(tile.img()); + query.addBindValue(tile.img().size()); + query.addBindValue(tile.type()); + query.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool TilesTableModel::deleteTileSet(quint64 setID) +{ + QSqlQuery query; + (void) query.prepare("DELETE FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A JOIN SetTiles B ON A.tileID = B.tileID WHERE B.setID = ? GROUP BY A.tileID HAVING COUNT(A.tileID) = 1)"); + query.addBindValue(setID); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool TilesTableModel::updateSetTotals(QGCCachedTileSet &set, quint64 setID) +{ + QSqlQuery query; + (void) query.prepare("SELECT COUNT(size), SUM(size) FROM Tiles A INNER JOIN SetTiles B on A.tileID = B.tileID WHERE B.setID = ?"); + query.addBindValue(setID); + if (!query.exec() || !query.next()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << "query failed"; + return false; + } + + set.setSavedTileCount(query.value(0).toUInt()); + set.setSavedTileSize(query.value(1).toULongLong()); + qCDebug(QGCTileCacheDBLog) << "Set" << set.id() << "Totals:" << set.savedTileCount() << set.savedTileSize() << "Expected:" << set.totalTileCount() << set.totalTilesSize(); + + quint64 avg = UrlFactory::averageSizeForType(set.type()); + if (set.totalTileCount() <= set.savedTileCount()) { + set.setTotalTileSize(set.savedTileSize()); + } else { + if ((set.savedTileCount() > 10) && set.savedTileSize()) { + avg = (set.savedTileSize() / set.savedTileCount()); + } + set.setTotalTileSize(avg * set.totalTileCount()); + } + + quint32 ucount = 0; + quint64 usize = 0; + (void) query.prepare("SELECT COUNT(size), SUM(size) FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A join SetTiles B on A.tileID = B.tileID WHERE B.setID = ? GROUP by A.tileID HAVING COUNT(A.tileID) = 1)"); + query.addBindValue(setID); + if (query.exec() && query.next()) { + ucount = query.value(0).toUInt(); + usize = query.value(1).toULongLong(); + } + + quint32 expectedUcount = set.totalTileCount() - set.savedTileCount(); + if (ucount == 0) { + usize = expectedUcount * avg; + } else { + expectedUcount = ucount; + } + set.setUniqueTileCount(expectedUcount); + set.setUniqueTileSize(usize); + + return true; +} + +bool TilesTableModel::updateTotals(quint32 &totalCount, quint64 &totalSize, quint32 &defaultCount, quint64 &defaultSize, quint64 defaultTileSetID) +{ + QSqlQuery query; + (void) query.prepare("SELECT COUNT(size), SUM(size) FROM Tiles"); + if (!query.exec() || !query.next()) { + return false; + } + totalCount = query.value(0).toUInt(); + totalSize = query.value(1).toULongLong(); + + (void) query.prepare("SELECT COUNT(size), SUM(size) FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A join SetTiles B on A.tileID = B.tileID WHERE B.setID = ? GROUP by A.tileID HAVING COUNT(A.tileID) = 1)"); + query.addBindValue(defaultTileSetID); + if (!query.exec() || !query.next()) { + return false; + } + defaultCount = query.value(0).toUInt(); + defaultSize = query.value(1).toULongLong(); + + return true; +} + +bool TilesTableModel::prune(quint64 defaultTileSetID, qint64 amount) +{ + QSqlQuery query; + (void) query.prepare("SELECT tileID, size, hash FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A join SetTiles B on A.tileID = B.tileID WHERE B.setID = ? GROUP by A.tileID HAVING COUNT(A.tileID) = 1) ORDER BY DATE ASC LIMIT 128"); + query.addBindValue(defaultTileSetID); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + QQueue tilelist; + while (query.next() && (amount >= 0)) { + (void) tilelist.enqueue(query.value(0).toULongLong()); + amount -= query.value(1).toULongLong(); + } + + while (!tilelist.isEmpty()) { + const quint64 tileID = tilelist.dequeue(); + query.prepare("DELETE FROM Tiles WHERE tileID = ?"); + query.addBindValue(tileID); + if (!query.exec()) { + break; + } + } + + return true; +} + +bool TilesTableModel::deleteBingNoTileImageTiles() +{ + static const QString alreadyDoneKey = QStringLiteral("_deleteBingNoTileTilesDone"); + + QSettings settings; + if (settings.value(alreadyDoneKey, false).toBool()) { + return true; + } + settings.setValue(alreadyDoneKey, true); + + if (_bingNoTileImage.isEmpty()) { + QFile file("://res/BingNoTileBytes.dat"); + if (file.open(QFile::ReadOnly)) { + _bingNoTileImage = file.readAll(); + file.close(); + if (_bingNoTileImage.isEmpty()) { + qCWarning(QGCTileCacheDBLog) << "Unable to read BingNoTileBytes"; + return false; + } + } else { + qCWarning(QGCTileCacheDBLog) << "Open File Failed"; + return false; + } + } + + QSqlQuery query; + query.prepare("SELECT tileID, tile, hash FROM Tiles WHERE LENGTH(tile) = ?"); + query.addBindValue(_bingNoTileImage.length()); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + QList idsToDelete; + while (query.next()) { + if (query.value(1).toByteArray() == _bingNoTileImage) { + (void) idsToDelete.append(query.value(0).toULongLong()); + } + } + + for (const quint64 tileId: idsToDelete) { + query.prepare("DELETE FROM Tiles WHERE tileID = ?"); + query.addBindValue(tileId); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + } + } + + return true; +} + +bool TilesTableModel::drop() +{ + QSqlQuery query; + query.prepare("DROP TABLE Tiles"); + return query.exec(); +} + +/*===========================================================================*/ + +TilesDownloadTableModel::TilesDownloadTableModel(QObject *parent, const QSqlDatabase &db) + : QSqlTableModel(parent, db) +{ + setTable("TilesDownload"); + + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; +} + +TilesDownloadTableModel::~TilesDownloadTableModel() +{ + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; +} + +bool TilesDownloadTableModel::create() +{ + QSqlQuery query; + query.prepare( + "CREATE TABLE IF NOT EXISTS TilesDownload (" + "setID INTEGER, " + "hash TEXT NOT NULL UNIQUE, " + "type INTEGER, " + "x INTEGER, " + "y INTEGER, " + "z INTEGER, " + "state INTEGER DEFAULT 0)" + ); + return query.exec(); +} + +bool TilesDownloadTableModel::insertTilesDownload(QGCCachedTileSet* tileSet) +{ + QSqlQuery query; + const quint64 setID = query.lastInsertId().toULongLong(); + tileSet->setId(setID); + (void) database().transaction(); + + for (int z = tileSet->minZoom(); z <= tileSet->maxZoom(); z++) { + const QGCTileSet set = UrlFactory::getTileCount( + z, + tileSet->topleftLon(), + tileSet->topleftLat(), + tileSet->bottomRightLon(), + tileSet->bottomRightLat(), + tileSet->type() + ); + const QString type = tileSet->type(); + for (int x = set.tileX0; x <= set.tileX1; x++) { + for (int y = set.tileY0; y <= set.tileY1; y++) { + const QString hash = UrlFactory::getTileHash(type, x, y, z); + const quint64 tileID = 0; // TODO: _findTile(hash); + // _tilesTable->getTileID(tileID, hash); + if (!tileID) { + (void) query.prepare(QStringLiteral("INSERT OR IGNORE INTO TilesDownload(setID, hash, type, x, y, z, state) VALUES(?, ?, ?, ?, ? ,? ,?)")); + query.addBindValue(setID); + query.addBindValue(hash); + query.addBindValue(UrlFactory::getQtMapIdFromProviderType(type)); + query.addBindValue(x); + query.addBindValue(y); + query.addBindValue(z); + query.addBindValue(0); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << "Map Cache SQL error (add tile into TilesDownload):" << query.lastError().text(); + return false; + } + } else { + const QString s = QStringLiteral("INSERT OR IGNORE INTO SetTiles(tileID, setID) VALUES(%1, %2)").arg(tileID).arg(setID); + if (!query.exec(s)) { + qCWarning(QGCTileCacheDBLog) << "Map Cache SQL error (add tile into SetTiles):" << query.lastError().text(); + } + qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << "Already Cached HASH:" << hash; + } + } + } + } + + (void) database().commit(); + + return true; +} + +bool TilesDownloadTableModel::setState(quint64 setID, const QString &hash, int state) +{ + QSqlQuery query; + query.prepare("UPDATE TilesDownload SET state = ? WHERE setID = ? AND hash = ?"); + query.addBindValue(state); + query.addBindValue(setID); + query.addBindValue(hash); + return query.exec(); +} + +bool TilesDownloadTableModel::setState(quint64 setID, int state) +{ + QSqlQuery query; + query.prepare("UPDATE TilesDownload SET state = ? WHERE setID = ?"); + query.addBindValue(state); + query.addBindValue(setID); + return query.exec(); +} + +bool TilesDownloadTableModel::deleteTileSet(quint64 setID) +{ + QSqlQuery query; + (void) query.prepare("DELETE FROM TilesDownload WHERE setID = ?"); + query.addBindValue(setID); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool TilesDownloadTableModel::getTileDownloadList(QQueue &tiles, quint64 setID, int count) +{ + QSqlQuery query; + query.prepare("SELECT hash, type, x, y, z FROM TilesDownload WHERE setID = ? AND state = 0 LIMIT ?"); + query.addBindValue(setID); + query.addBindValue(count); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + while (query.next()) { + QGCTile* const tile = new QGCTile(); + // tile->setTileSet(task->setID()); + tile->setHash(query.value("hash").toString()); + tile->setType(UrlFactory::getProviderTypeFromQtMapId(query.value("type").toInt())); + tile->setX(query.value("x").toInt()); + tile->setY(query.value("y").toInt()); + tile->setZ(query.value("z").toInt()); + (void) tiles.enqueue(tile); + } + + for (qsizetype i = 0; i < tiles.size(); i++) { + query.prepare("UPDATE TilesDownload SET state = ? WHERE setID = ? AND hash = ?"); + query.addBindValue(static_cast(QGCTile::StateDownloading)); + query.addBindValue(setID); + query.addBindValue(tiles[i]->hash()); + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + } + } + + return true; +} + +bool TilesDownloadTableModel::updateTilesDownloadSet(QGCTile::TileState state, quint64 setID, const QString &hash) +{ + QSqlQuery query; + if (state == QGCTile::StateComplete) { + query.prepare("DELETE FROM TilesDownload WHERE setID = ? AND hash = ?"); + query.addBindValue(setID); + query.addBindValue(hash); + } else if (hash == "*") { + query.prepare("UPDATE TilesDownload SET state = ? WHERE setID = ?"); + query.addBindValue(state); + query.addBindValue(setID); + } else { + query.prepare("UPDATE TilesDownload SET state = ? WHERE setID = ? AND hash = ?"); + query.addBindValue(state); + query.addBindValue(setID); + query.addBindValue(hash); + } + + if (!query.exec()) { + qCWarning(QGCTileCacheDBLog) << Q_FUNC_INFO << query.lastError().text(); + return false; + } + + return true; +} + +bool TilesDownloadTableModel::drop() +{ + QSqlQuery query; + query.prepare("DROP TABLE TilesDownload"); + return query.exec(); +} + +/*===========================================================================*/ + +TilesSetTilesRelationalTableModel::TilesSetTilesRelationalTableModel(QObject *parent) + : QObject(parent) +{ + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; +} + +TilesSetTilesRelationalTableModel::~TilesSetTilesRelationalTableModel() +{ + // qCDebug(QGCTileCacheDBLog) << Q_FUNC_INFO << this; +} + +void TilesSetTilesRelationalTableModel::select(quint64 tilesSaved, quint64 insertSetID) +{ + if (tilesSaved) { + QSqlQuery query; + query.prepare("SELECT COUNT(size) FROM Tiles A INNER JOIN SetTiles B on A.tileID = B.tileID WHERE B.setID = ?"); + query.addBindValue(insertSetID); + if (query.exec() && query.next()) { + const quint64 count = cQuery.value(0).toULongLong(); + query.prepare("UPDATE TileSets SET numTiles = ? WHERE setID = ?"); + query.addBindValue(count); + query.addBindValue(insertSetID); + (void) query.exec(); + } + } +} diff --git a/src/QtLocationPlugin/QGCTileCacheDatabase.h b/src/QtLocationPlugin/QGCTileCacheDatabase.h new file mode 100644 index 00000000000..85ff45ca357 --- /dev/null +++ b/src/QtLocationPlugin/QGCTileCacheDatabase.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "QGCTile.h" + +class QGCCacheTile; +class QGCCachedTileSet; + +Q_DECLARE_LOGGING_CATEGORY(QGCTileCacheDBLog) + +/*===========================================================================*/ + +class SetTilesTableModel: public QSqlTableModel +{ + Q_OBJECT + +public: + explicit SetTilesTableModel(QObject *parent = nullptr, const QSqlDatabase &db = QSqlDatabase()); + ~SetTilesTableModel(); + + bool create(); + bool selectFromSetID(quint64 setID); + bool insertSetTiles(quint64 setID, quint64 tileID); + bool deleteTileSet(quint64 setID); + bool drop(); +}; + +/*===========================================================================*/ + +class TileSetsTableModel: public QSqlTableModel +{ + Q_OBJECT + +public: + explicit TileSetsTableModel(QObject *parent = nullptr, const QSqlDatabase &db = QSqlDatabase()); + ~TileSetsTableModel(); + + bool create(); + bool insertTileSet(const QGCCachedTileSet &tileSet); + bool getTileSets(QList &tileSets); + bool setName(quint64 setID, const QString &newName); + bool setNumTiles(quint64 setID, quint64 numTiles); + bool getTileSetID(quint64 &setID, const QString &name); + bool getDefaultTileSet(quint64 &setID); + bool deleteTileSet(quint64 setID); + bool drop(); + bool createDefaultTileSet(); + +private: + static quint64 _defaultSet; +}; + +/*===========================================================================*/ + +class TilesTableModel: public QSqlTableModel +{ + Q_OBJECT + +public: + explicit TilesTableModel(QObject *parent = nullptr, const QSqlDatabase &db = QSqlDatabase()); + ~TilesTableModel(); + + bool create(); + bool getTile(QGCCacheTile *tile, const QString &hash); + bool getTileID(quint64 &tileID, const QString &hash); + bool selectFromSetID(quint64 tileID); + bool insertTile(const QGCCacheTile &tile); + bool deleteTileSet(quint64 setID); + bool updateSetTotals(QGCCachedTileSet &set, quint64 setID); + bool updateTotals(quint32 &totalCount, quint64 &totalSize, quint32 &defaultCount, quint64 &defaultSize, quint64 defaultTileSetID); + bool prune(quint64 defaultTileSetID, qint64 amount); + bool deleteBingNoTileImageTiles(); + bool drop(); + +private: + static QByteArray _bingNoTileImage; +}; + +/*===========================================================================*/ + +class TilesDownloadTableModel: public QSqlTableModel +{ + Q_OBJECT + +public: + explicit TilesDownloadTableModel(QObject *parent = nullptr, const QSqlDatabase &db = QSqlDatabase()); + ~TilesDownloadTableModel(); + + bool create(); + bool insertTilesDownload(QGCCachedTileSet* tileSet); + bool setState(quint64 setID, const QString &hash, int state); + bool setState(quint64 setID, int state); + bool deleteTileSet(quint64 setID); + bool getTileDownloadList(QQueue &tiles, quint64 setID, int count); + bool updateTilesDownloadSet(QGCTile::TileState state, quint64 setID, const QString &hash); + bool drop(); +}; + +/*===========================================================================*/ + +class TilesSetTilesRelationalTableModel : public QSqlRelationalTableModel +{ + Q_OBJECT + +public: + explicit TilesSetTilesRelationalTableModel(QObject *parent = nullptr, const QSqlDatabase &db = QSqlDatabase()); + ~TilesSetTilesRelationalTableModel(); + + void select(quint64 tilesSaved, quint64 insertSetID); +} diff --git a/src/QtLocationPlugin/QGCTileCacheWorker.cpp b/src/QtLocationPlugin/QGCTileCacheWorker.cpp index cea6459ecbb..bf1e4400958 100644 --- a/src/QtLocationPlugin/QGCTileCacheWorker.cpp +++ b/src/QtLocationPlugin/QGCTileCacheWorker.cpp @@ -17,373 +17,259 @@ */ #include "QGCTileCacheWorker.h" -#include "QGCMapEngine.h" +#include "QGCTileCacheDatabase.h" #include "QGCCachedTileSet.h" -#include "QGCMapUrlEngine.h" #include "QGCMapTasks.h" +#include "QGCMapUrlEngine.h" #include "QGCLoggingCategory.h" -#include -#include #include #include #include #include -#ifndef Q_OS_ANDROID -#include -#endif - -#include "time.h" - -static const QString kSession = QStringLiteral("QGeoTileWorkerSession"); -static const QString kExportSession = QStringLiteral("QGeoTileExportSession"); - -QGC_LOGGING_CATEGORY(QGCTileCacheLog, "QGCTileCacheLog") +#include +#include +#include -//-- Update intervals +const QString QGCCacheWorker::kSession = QStringLiteral("QGeoTileWorkerSession"); +QByteArray QGCCacheWorker::_bingNoTileImage; -#define LONG_TIMEOUT 5 -#define SHORT_TIMEOUT 2 +QGC_LOGGING_CATEGORY(QGCTileCacheWorkerLog, "qgc.qtlocationplugin.qgctilecacheworker") -//----------------------------------------------------------------------------- -QGCCacheWorker::QGCCacheWorker(QObject* parent) +QGCCacheWorker::QGCCacheWorker(QObject *parent) : QThread(parent) - , _db(nullptr) - , _valid(false) - , _failed(false) - , _defaultSet(UINT64_MAX) - , _totalSize(0) - , _totalCount(0) - , _defaultSize(0) - , _defaultCount(0) - , _lastUpdate(0) - , _updateTimeout(SHORT_TIMEOUT) { + // qCDebug(QGCTileCacheWorkerLog) << Q_FUNC_INFO << this; + + const QSqlDatabase db = QSqlDatabase::database(kSession); + + _setTilesTable = new SetTilesTableModel(this, db); + _tileSetsTable = new TileSetsTableModel(this, db); + _tilesTable = new TilesTableModel(this, db); + _tilesDownloadTable = new TilesDownloadTableModel(this, db); + _tilesSetTilesRelationalTable = new TilesSetTilesRelationalTableModel(this, db); } -//----------------------------------------------------------------------------- QGCCacheWorker::~QGCCacheWorker() { -} + stop(); -//----------------------------------------------------------------------------- -void -QGCCacheWorker::setDatabaseFile(const QString& path) -{ - _databasePath = path; + // qCDebug(QGCTileCacheWorkerLog) << Q_FUNC_INFO << this; } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::quit() +void QGCCacheWorker::stop() { + // TODO: Send Stop Task Instead? QMutexLocker lock(&_taskQueueMutex); - while(_taskQueue.count()) { - QGCMapTask* task = _taskQueue.dequeue(); - delete task; - } - lock.unlock(); // don't need the lock any more - if(this->isRunning()) { + qDeleteAll(_taskQueue); + lock.unlock(); + + if (isRunning()) { _waitc.wakeAll(); } } -//----------------------------------------------------------------------------- -bool -QGCCacheWorker::enqueueTask(QGCMapTask* task) +bool QGCCacheWorker::enqueueTask(QGCMapTask *task) { - //-- If not initialized, the only allowed task is Init - if(!_valid && task->type() != QGCMapTask::taskInit) { + if (!_valid && (task->type() != QGCMapTask::taskInit)) { task->setError("Database Not Initialized"); task->deleteLater(); return false; } + + // TODO: Prepend Stop Task Instead? QMutexLocker lock(&_taskQueueMutex); _taskQueue.enqueue(task); - lock.unlock(); // don't need to hold the mutex any more - if(this->isRunning()) { + lock.unlock(); + + if (isRunning()) { _waitc.wakeAll(); } else { - this->start(QThread::HighPriority); + start(QThread::HighPriority); } + return true; } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::run() +void QGCCacheWorker::run() { - if(!_valid && !_failed) { - _init(); + if (!_valid && !_failed) { + (void) _init(); } - if(_valid) { - _connectDB(); + + if (_valid) { + if (_connectDB()) { + _deleteBingNoTileTiles(); + } } - _deleteBingNoTileTiles(); - QMutexLocker lock(&_taskQueueMutex); - while(true) { - QGCMapTask* task; - if(_taskQueue.count()) { - task = _taskQueue.dequeue(); - // Don't need the lock while running the task. + QMutexLocker lock(&_taskQueueMutex); + while (true) { + if (!_taskQueue.isEmpty()) { + QGCMapTask* const task = _taskQueue.dequeue(); lock.unlock(); _runTask(task); lock.relock(); task->deleteLater(); - //-- Check for update timeout - size_t count = static_cast(_taskQueue.count()); - if(count > 100) { + + const qsizetype count = _taskQueue.count(); + if (count > 100) { _updateTimeout = LONG_TIMEOUT; - } else if(count < 25) { + } else if (count < 25) { _updateTimeout = SHORT_TIMEOUT; } - if(!count || (time(nullptr) - _lastUpdate > _updateTimeout)) { - if(_valid) { - // _updateTotals() will emit a signal. Don't keep the lock - // while any slots process the signal. + + if ((count == 0) || _updateTimer.hasExpired(_updateTimeout)) { + if (_valid) { lock.unlock(); _updateTotals(); lock.relock(); } } } else { - //-- Wait a bit before shutting things down - unsigned long timeoutMilliseconds = 5000; - _waitc.wait(lock.mutex(), timeoutMilliseconds); - //-- If nothing to do, close db and leave thread - if(!_taskQueue.count()) { + (void) _waitc.wait(lock.mutex(), 5000); + if (_taskQueue.isEmpty()) { break; } } } lock.unlock(); - _disconnectDB(); + + for (const QString &connection: QSqlDatabase::connectionNames()) { + QSqlDatabase db = QSqlDatabase::database(connection, false); + if (db.isOpen()) { + db.close(); + } + QSqlDatabase::removeDatabase(connection); + } } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_runTask(QGCMapTask *task) +void QGCCacheWorker::_runTask(QGCMapTask *task) { - switch(task->type()) { - case QGCMapTask::taskInit: - return; - case QGCMapTask::taskCacheTile: - _saveTile(task); - return; - case QGCMapTask::taskFetchTile: - _getTile(task); - return; - case QGCMapTask::taskFetchTileSets: - _getTileSets(task); - return; - case QGCMapTask::taskCreateTileSet: - _createTileSet(task); - return; - case QGCMapTask::taskGetTileDownloadList: - _getTileDownloadList(task); - return; - case QGCMapTask::taskUpdateTileDownloadState: - _updateTileDownloadState(task); - return; - case QGCMapTask::taskDeleteTileSet: - _deleteTileSet(task); - return; - case QGCMapTask::taskRenameTileSet: - _renameTileSet(task); - return; - case QGCMapTask::taskPruneCache: - _pruneCache(task); - return; - case QGCMapTask::taskReset: - _resetCacheDatabase(task); - return; - case QGCMapTask::taskExport: - _exportSets(task); - return; - case QGCMapTask::taskImport: - _importSets(task); - return; + switch (task->type()) { + case QGCMapTask::taskInit: + break; + case QGCMapTask::taskCacheTile: + _saveTile(task); + break; + case QGCMapTask::taskFetchTile: + _getTile(task); + break; + case QGCMapTask::taskFetchTileSets: + _getTileSets(task); + break; + case QGCMapTask::taskCreateTileSet: + _createTileSet(task); + break; + case QGCMapTask::taskGetTileDownloadList: + _getTileDownloadList(task); + break; + case QGCMapTask::taskUpdateTileDownloadState: + _updateTileDownloadState(task); + break; + case QGCMapTask::taskDeleteTileSet: + _deleteTileSet(task); + break; + case QGCMapTask::taskRenameTileSet: + _renameTileSet(task); + break; + case QGCMapTask::taskPruneCache: + _pruneCache(task); + break; + case QGCMapTask::taskReset: + _resetCacheDatabase(task); + break; + case QGCMapTask::taskExport: + _exportSets(task); + break; + case QGCMapTask::taskImport: + _importSets(task); + break; + default: + qCWarning(QGCTileCacheWorkerLog) << Q_FUNC_INFO << "given unhandled task type" << task->type(); + break; } - qCWarning(QGCTileCacheLog) << "_runTask given unhandled task type" << task->type(); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_deleteBingNoTileTiles() +void QGCCacheWorker::_deleteBingNoTileTiles() { - QSettings settings; - static const char* alreadyDoneKey = "_deleteBingNoTileTilesDone"; - - if (settings.value(alreadyDoneKey, false).toBool()) { - return; - } - settings.setValue(alreadyDoneKey, true); - - // Previously we would store these empty tile graphics in the cache. This prevented the ability to zoom beyong the level - // of available tiles. So we need to remove only of these still hanging around to make higher zoom levels work. - QFile file(":/res/BingNoTileBytes.dat"); - file.open(QFile::ReadOnly); - QByteArray noTileBytes = file.readAll(); - file.close(); - - QSqlQuery query(*_db); - QString s; - //-- Select tiles in default set only, sorted by oldest. - s = QString("SELECT tileID, tile, hash FROM Tiles WHERE LENGTH(tile) = %1").arg(noTileBytes.length()); - QList idsToDelete; - if (query.exec(s)) { - while(query.next()) { - if (query.value(1).toByteArray() == noTileBytes) { - idsToDelete.append(query.value(0).toULongLong()); - qCDebug(QGCTileCacheLog) << "_deleteBingNoTileTiles HASH:" << query.value(2).toString(); - } - } - for (const quint64 tileId: idsToDelete) { - s = QString("DELETE FROM Tiles WHERE tileID = %1").arg(tileId); - if (!query.exec(s)) { - qCWarning(QGCTileCacheLog) << "Delete failed"; - } - } - } else { - qCWarning(QGCTileCacheLog) << "_deleteBingNoTileTiles query failed"; - } + (void) _tilesTable->deleteBingNoTileImageTiles(); } -//----------------------------------------------------------------------------- -bool -QGCCacheWorker::_findTileSetID(const QString name, quint64& setID) +bool QGCCacheWorker::_findTileSetID(const QString &name, quint64 &setID) { - QSqlQuery query(*_db); - QString s = QString("SELECT setID FROM TileSets WHERE name = \"%1\"").arg(name); - if(query.exec(s)) { - if(query.next()) { - setID = query.value(0).toULongLong(); - return true; - } - } - return false; + return _tileSetsTable->getTileSetID(setID, name); } -//----------------------------------------------------------------------------- -quint64 -QGCCacheWorker::_getDefaultTileSet() +quint64 QGCCacheWorker::_getDefaultTileSet() { - if(_defaultSet != UINT64_MAX) - return _defaultSet; - QSqlQuery query(*_db); - QString s = QString("SELECT setID FROM TileSets WHERE defaultSet = 1"); - if(query.exec(s)) { - if(query.next()) { - _defaultSet = query.value(0).toULongLong(); - return _defaultSet; - } - } - return 1L; + quint64 setID; + (void) _tileSetsTable->getDefaultTileSet(setID); + return setID; } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_saveTile(QGCMapTask *mtask) +void QGCCacheWorker::_saveTile(QGCMapTask *mtask) { - if(_valid) { - QGCSaveTileTask* task = static_cast(mtask); - QSqlQuery query(*_db); - query.prepare("INSERT INTO Tiles(hash, format, tile, size, type, date) VALUES(?, ?, ?, ?, ?, ?)"); - query.addBindValue(task->tile()->hash()); - query.addBindValue(task->tile()->format()); - query.addBindValue(task->tile()->img()); - query.addBindValue(task->tile()->img().size()); - query.addBindValue(task->tile()->type()); - query.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); - if(query.exec()) { - quint64 tileID = query.lastInsertId().toULongLong(); - quint64 setID = task->tile()->tileSet() == UINT64_MAX ? _getDefaultTileSet() : task->tile()->tileSet(); - QString s = QString("INSERT INTO SetTiles(tileID, setID) VALUES(%1, %2)").arg(tileID).arg(setID); - query.prepare(s); - if(!query.exec()) { - qWarning() << "Map Cache SQL error (add tile into SetTiles):" << query.lastError().text(); - } - qCDebug(QGCTileCacheLog) << "_saveTile() HASH:" << task->tile()->hash(); - } else { - //-- Tile was already there. - // QtLocation some times requests the same tile twice in a row. The first is saved, the second is already there. - } - } else { - qWarning() << "Map Cache SQL error (saveTile() open db):" << _db->lastError(); + if (!_testTask(mtask)) { + return; + } + + if (!_valid) { + qCWarning(QGCTileCacheWorkerLog) << "Map Cache SQL error (saveTile() open db): Not Valid"; + return; + } + + QGCSaveTileTask* const task = static_cast(mtask); + + if (!_tilesTable->insertTile(*task->tile())) { + qCWarning(QGCTileCacheWorkerLog) << "Map Cache SQL error (add tile into SetTiles)"; } } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_getTile(QGCMapTask* mtask) +void QGCCacheWorker::_getTile(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } - bool found = false; - QGCFetchTileTask* task = static_cast(mtask); - QSqlQuery query(*_db); - QString s = QString("SELECT tile, format, type FROM Tiles WHERE hash = \"%1\"").arg(task->hash()); - if(query.exec(s)) { - if(query.next()) { - const QByteArray& arrray = query.value(0).toByteArray(); - const QString& format = query.value(1).toString(); - const QString& type = query.value(2).toString(); - qCDebug(QGCTileCacheLog) << "_getTile() (Found in DB) HASH:" << task->hash(); - QGCCacheTile* tile = new QGCCacheTile(task->hash(), arrray, format, type); - task->setTileFetched(tile); - found = true; - } - } - if(!found) { - qCDebug(QGCTileCacheLog) << "_getTile() (NOT in DB) HASH:" << task->hash(); + + QGCFetchTileTask* const task = static_cast(mtask); + + QGCCacheTile *tile = nullptr; + if (!_tilesTable->getTile(tile, task->hash())) { + qCDebug(QGCTileCacheWorkerLog) << Q_FUNC_INFO << "(NOT in DB) HASH:" << task->hash(); task->setError("Tile not in cache database"); + return; } + + task->setTileFetched(tile); + qCDebug(QGCTileCacheWorkerLog) << Q_FUNC_INFO << "(Found in DB) HASH:" << task->hash(); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_getTileSets(QGCMapTask* mtask) +void QGCCacheWorker::_getTileSets(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } - QGCFetchTileSetTask* task = static_cast(mtask); - QSqlQuery query(*_db); - QString s = QString("SELECT * FROM TileSets ORDER BY defaultSet DESC, name ASC"); - qCDebug(QGCTileCacheLog) << "_getTileSets(): " << s; - if(query.exec(s)) { - while(query.next()) { - QString name = query.value("name").toString(); - QGCCachedTileSet* set = new QGCCachedTileSet(name); - set->setId(query.value("setID").toULongLong()); - set->setMapTypeStr(query.value("typeStr").toString()); - set->setTopleftLat(query.value("topleftLat").toDouble()); - set->setTopleftLon(query.value("topleftLon").toDouble()); - set->setBottomRightLat(query.value("bottomRightLat").toDouble()); - set->setBottomRightLon(query.value("bottomRightLon").toDouble()); - set->setMinZoom(query.value("minZoom").toInt()); - set->setMaxZoom(query.value("maxZoom").toInt()); - set->setType(UrlFactory::getProviderTypeFromQtMapId(query.value("type").toInt())); - set->setTotalTileCount(query.value("numTiles").toUInt()); - set->setDefaultSet(query.value("defaultSet").toInt() != 0); - set->setCreationDate(QDateTime::fromSecsSinceEpoch(query.value("date").toUInt())); - _updateSetTotals(set); - //-- Object created here must be moved to app thread to be used there - set->moveToThread(QCoreApplication::instance()->thread()); - task->tileSetFetched(set); - } - } else { + + QGCFetchTileSetTask* const task = static_cast(mtask); + + QList tileSets; + if (!_tileSetsTable->getTileSets(tileSets)) { + qCDebug(QGCTileCacheWorkerLog) << Q_FUNC_INFO << "No tile set in database"; task->setError("No tile set in database"); + return; + } + + for (QGCCachedTileSet* tileSet : tileSets) { + _updateSetTotals(tileSet); + tileSet->moveToThread(QCoreApplication::instance()->thread()); + task->setTileSetFetched(tileSet); } } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_updateSetTotals(QGCCachedTileSet* set) +void QGCCacheWorker::_updateSetTotals(QGCCachedTileSet *set) { - if(set->defaultSet()) { + if (set->defaultSet()) { _updateTotals(); set->setSavedTileCount(_totalCount); set->setSavedTileSize(_totalSize); @@ -391,542 +277,358 @@ QGCCacheWorker::_updateSetTotals(QGCCachedTileSet* set) set->setTotalTileSize(_defaultSize); return; } - QSqlQuery subquery(*_db); - QString sq = QString("SELECT COUNT(size), SUM(size) FROM Tiles A INNER JOIN SetTiles B on A.tileID = B.tileID WHERE B.setID = %1").arg(set->id()); - qCDebug(QGCTileCacheLog) << "_updateSetTotals(): " << sq; - if(subquery.exec(sq)) { - if(subquery.next()) { - set->setSavedTileCount(subquery.value(0).toUInt()); - set->setSavedTileSize(subquery.value(1).toULongLong()); - qCDebug(QGCTileCacheLog) << "Set" << set->id() << "Totals:" << set->savedTileCount() << " " << set->savedTileSize() << "Expected: " << set->totalTileCount() << " " << set->totalTilesSize(); - //-- Update (estimated) size - quint64 avg = UrlFactory::averageSizeForType(set->type()); - if(set->totalTileCount() <= set->savedTileCount()) { - //-- We're done so the saved size is the total size - set->setTotalTileSize(set->savedTileSize()); - } else { - //-- Otherwise we need to estimate it. - if(set->savedTileCount() > 10 && set->savedTileSize()) { - avg = set->savedTileSize() / set->savedTileCount(); - } - set->setTotalTileSize(avg * set->totalTileCount()); - } - //-- Now figure out the count for tiles unique to this set - quint32 ucount = 0; - quint64 usize = 0; - sq = QString("SELECT COUNT(size), SUM(size) FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A join SetTiles B on A.tileID = B.tileID WHERE B.setID = %1 GROUP by A.tileID HAVING COUNT(A.tileID) = 1)").arg(set->id()); - if(subquery.exec(sq)) { - if(subquery.next()) { - //-- This is only accurate when all tiles are downloaded - ucount = subquery.value(0).toUInt(); - usize = subquery.value(1).toULongLong(); - } - } - //-- If we haven't downloaded it all, estimate size of unique tiles - quint32 expectedUcount = set->totalTileCount() - set->savedTileCount(); - if(!ucount) { - usize = expectedUcount * avg; - } else { - expectedUcount = ucount; - } - set->setUniqueTileCount(expectedUcount); - set->setUniqueTileSize(usize); - } - } + + (void) _tilesTable->updateSetTotals(*set, set->id()); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_updateTotals() +void QGCCacheWorker::_updateTotals() { - QSqlQuery query(*_db); - QString s; - s = QString("SELECT COUNT(size), SUM(size) FROM Tiles"); - qCDebug(QGCTileCacheLog) << "_updateTotals(): " << s; - if(query.exec(s)) { - if(query.next()) { - _totalCount = query.value(0).toUInt(); - _totalSize = query.value(1).toULongLong(); - } - } - s = QString("SELECT COUNT(size), SUM(size) FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A join SetTiles B on A.tileID = B.tileID WHERE B.setID = %1 GROUP by A.tileID HAVING COUNT(A.tileID) = 1)").arg(_getDefaultTileSet()); - qCDebug(QGCTileCacheLog) << "_updateTotals(): " << s; - if(query.exec(s)) { - if(query.next()) { - _defaultCount = query.value(0).toUInt(); - _defaultSize = query.value(1).toULongLong(); - } - } + (void) _tilesTable->updateTotals(_totalCount, _totalSize, _defaultCount, _defaultSize, _getDefaultTileSet()); + emit updateTotals(_totalCount, _totalSize, _defaultCount, _defaultSize); - _lastUpdate = time(nullptr); + if (!_updateTimer.isValid()) { + _updateTimer.start(); + } else { + (void) _updateTimer.restart(); + } } -//----------------------------------------------------------------------------- -quint64 QGCCacheWorker::_findTile(const QString hash) +quint64 QGCCacheWorker::_findTile(const QString &hash) { quint64 tileID = 0; - QSqlQuery query(*_db); - QString s = QString("SELECT tileID FROM Tiles WHERE hash = \"%1\"").arg(hash); - if(query.exec(s)) { - if(query.next()) { - tileID = query.value(0).toULongLong(); - } - } + (void) _tilesTable->getTileID(tileID, hash); return tileID; } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_createTileSet(QGCMapTask *mtask) +void QGCCacheWorker::_createTileSet(QGCMapTask *mtask) { - if(_valid) { - //-- Create Tile Set - quint32 actual_count = 0; - QGCCreateTileSetTask* task = static_cast(mtask); - QSqlQuery query(*_db); - query.prepare("INSERT INTO TileSets(" - "name, typeStr, topleftLat, topleftLon, bottomRightLat, bottomRightLon, minZoom, maxZoom, type, numTiles, date" - ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - query.addBindValue(task->tileSet()->name()); - query.addBindValue(task->tileSet()->mapTypeStr()); - query.addBindValue(task->tileSet()->topleftLat()); - query.addBindValue(task->tileSet()->topleftLon()); - query.addBindValue(task->tileSet()->bottomRightLat()); - query.addBindValue(task->tileSet()->bottomRightLon()); - query.addBindValue(task->tileSet()->minZoom()); - query.addBindValue(task->tileSet()->maxZoom()); - query.addBindValue(UrlFactory::getQtMapIdFromProviderType(task->tileSet()->type())); - query.addBindValue(task->tileSet()->totalTileCount()); - query.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); - if(!query.exec()) { - qWarning() << "Map Cache SQL error (add tileSet into TileSets):" << query.lastError().text(); - } else { - //-- Get just created (auto-incremented) setID - quint64 setID = query.lastInsertId().toULongLong(); - task->tileSet()->setId(setID); - //-- Prepare Download List - _db->transaction(); - for(int z = task->tileSet()->minZoom(); z <= task->tileSet()->maxZoom(); z++) { - QGCTileSet set = UrlFactory::getTileCount(z, - task->tileSet()->topleftLon(), task->tileSet()->topleftLat(), - task->tileSet()->bottomRightLon(), task->tileSet()->bottomRightLat(), task->tileSet()->type()); - QString type = task->tileSet()->type(); - for(int x = set.tileX0; x <= set.tileX1; x++) { - for(int y = set.tileY0; y <= set.tileY1; y++) { - //-- See if tile is already downloaded - QString hash = UrlFactory::getTileHash(type, x, y, z); - quint64 tileID = _findTile(hash); - if(!tileID) { - //-- Set to download - query.prepare("INSERT OR IGNORE INTO TilesDownload(setID, hash, type, x, y, z, state) VALUES(?, ?, ?, ?, ? ,? ,?)"); - query.addBindValue(setID); - query.addBindValue(hash); - query.addBindValue(UrlFactory::getQtMapIdFromProviderType(type)); - query.addBindValue(x); - query.addBindValue(y); - query.addBindValue(z); - query.addBindValue(0); - if(!query.exec()) { - qWarning() << "Map Cache SQL error (add tile into TilesDownload):" << query.lastError().text(); - mtask->setError("Error creating tile set download list"); - return; - } else - actual_count++; - } else { - //-- Tile already in the database. No need to dowload. - QString s = QString("INSERT OR IGNORE INTO SetTiles(tileID, setID) VALUES(%1, %2)").arg(tileID).arg(setID); - query.prepare(s); - if(!query.exec()) { - qWarning() << "Map Cache SQL error (add tile into SetTiles):" << query.lastError().text(); - } - qCDebug(QGCTileCacheLog) << "_createTileSet() Already Cached HASH:" << hash; - } - } - } - } - _db->commit(); - //-- Done - _updateSetTotals(task->tileSet()); - task->setTileSetSaved(); - return; - } + QGCCreateTileSetTask* const task = static_cast(mtask); + + if (!_valid) { + task->setError("Error saving tile set"); + return; + } + + QGCCachedTileSet* const tileSet = task->tileSet(); + + if (!_tileSetsTable->insertTileSet(*tileSet)) { + task->setError("Error saving tile set"); + return; + } + + if (!_tilesDownloadTable->insertTilesDownload(tileSet)) { + mtask->setError("Error creating tile set download list"); + return; } - mtask->setError("Error saving tile set"); + + _updateSetTotals(tileSet); + task->setTileSetSaved(); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_getTileDownloadList(QGCMapTask* mtask) +void QGCCacheWorker::_getTileDownloadList(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } + + QGCGetTileDownloadListTask* const task = static_cast(mtask); + QQueue tiles; - QGCGetTileDownloadListTask* task = static_cast(mtask); - QSqlQuery query(*_db); - QString s = QString("SELECT hash, type, x, y, z FROM TilesDownload WHERE setID = %1 AND state = 0 LIMIT %2").arg(task->setID()).arg(task->count()); - if(query.exec(s)) { - while(query.next()) { - QGCTile* tile = new QGCTile; - // tile->setTileSet(task->setID()); - tile->setHash(query.value("hash").toString()); - tile->setType(UrlFactory::getProviderTypeFromQtMapId(query.value("type").toInt())); - tile->setX(query.value("x").toInt()); - tile->setY(query.value("y").toInt()); - tile->setZ(query.value("z").toInt()); - tiles.enqueue(tile); - } - for(int i = 0; i < tiles.size(); i++) { - s = QString("UPDATE TilesDownload SET state = %1 WHERE setID = %2 and hash = \"%3\"").arg(static_cast(QGCTile::StateDownloading)).arg(task->setID()).arg(tiles[i]->hash()); - if(!query.exec(s)) { - qWarning() << "Map Cache SQL error (set TilesDownload state):" << query.lastError().text(); - } - } - } + + (void) _tilesDownloadTable->getTileDownloadList(tiles, task->setID(), task->count()); + task->setTileListFetched(tiles); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_updateTileDownloadState(QGCMapTask* mtask) +void QGCCacheWorker::_updateTileDownloadState(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } - QGCUpdateTileDownloadStateTask* task = static_cast(mtask); - QSqlQuery query(*_db); - QString s; - if(task->state() == QGCTile::StateComplete) { - s = QString("DELETE FROM TilesDownload WHERE setID = %1 AND hash = \"%2\"").arg(task->setID()).arg(task->hash()); - } else { - if(task->hash() == "*") { - s = QString("UPDATE TilesDownload SET state = %1 WHERE setID = %2").arg(static_cast(task->state())).arg(task->setID()); - } else { - s = QString("UPDATE TilesDownload SET state = %1 WHERE setID = %2 AND hash = \"%3\"").arg(static_cast(task->state())).arg(task->setID()).arg(task->hash()); - } - } - if(!query.exec(s)) { - qWarning() << "QGCCacheWorker::_updateTileDownloadState() Error:" << query.lastError().text(); - } + + QGCUpdateTileDownloadStateTask* const task = static_cast(mtask); + + (void) _tilesDownloadTable->updateTilesDownloadSet(task->state(), task->setID(), task->hash()); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_pruneCache(QGCMapTask* mtask) +void QGCCacheWorker::_pruneCache(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } - QGCPruneCacheTask* task = static_cast(mtask); - QSqlQuery query(*_db); - QString s; - //-- Select tiles in default set only, sorted by oldest. - s = QString("SELECT tileID, size, hash FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A join SetTiles B on A.tileID = B.tileID WHERE B.setID = %1 GROUP by A.tileID HAVING COUNT(A.tileID) = 1) ORDER BY DATE ASC LIMIT 128").arg(_getDefaultTileSet()); - qint64 amount = (qint64)task->amount(); - QList tlist; - if(query.exec(s)) { - while(query.next() && amount >= 0) { - tlist << query.value(0).toULongLong(); - amount -= query.value(1).toULongLong(); - qCDebug(QGCTileCacheLog) << "_pruneCache() HASH:" << query.value(2).toString(); - } - while(tlist.count()) { - s = QString("DELETE FROM Tiles WHERE tileID = %1").arg(tlist[0]); - tlist.removeFirst(); - if(!query.exec(s)) - break; - } - task->setPruned(); + + QGCPruneCacheTask* const task = static_cast(mtask); + + if (!_tilesTable->prune(_getDefaultTileSet(), static_cast(task->amount()))) { + return; } + + task->setPruned(); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_deleteTileSet(QGCMapTask* mtask) +void QGCCacheWorker::_deleteTileSet(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } - QGCDeleteTileSetTask* task = static_cast(mtask); + + QGCDeleteTileSetTask* const task = static_cast(mtask); _deleteTileSet(task->setID()); task->setTileSetDeleted(); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_deleteTileSet(qulonglong id) +void QGCCacheWorker::_deleteTileSet(quint64 id) { - QSqlQuery query(*_db); - QString s; - //-- Only delete tiles unique to this set - s = QString("DELETE FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A JOIN SetTiles B ON A.tileID = B.tileID WHERE B.setID = %1 GROUP BY A.tileID HAVING COUNT(A.tileID) = 1)").arg(id); - query.exec(s); - s = QString("DELETE FROM TilesDownload WHERE setID = %1").arg(id); - query.exec(s); - s = QString("DELETE FROM TileSets WHERE setID = %1").arg(id); - query.exec(s); - s = QString("DELETE FROM SetTiles WHERE setID = %1").arg(id); - query.exec(s); + (void) _tilesTable->deleteTileSet(id); + _updateTotals(); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_renameTileSet(QGCMapTask* mtask) +void QGCCacheWorker::_renameTileSet(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } - QGCRenameTileSetTask* task = static_cast(mtask); - QSqlQuery query(*_db); - QString s; - s = QString("UPDATE TileSets SET name = \"%1\" WHERE setID = %2").arg(task->newName()).arg(task->setID()); - if(!query.exec(s)) { + + QGCRenameTileSetTask* const task = static_cast(mtask); + + if (!_tileSetsTable->setName(task->setID(), task->newName())) { task->setError("Error renaming tile set"); } } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_resetCacheDatabase(QGCMapTask* mtask) + +void QGCCacheWorker::_resetCacheDatabase(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } - QGCResetTask* task = static_cast(mtask); - QSqlQuery query(*_db); - QString s; - s = QString("DROP TABLE Tiles"); - query.exec(s); - s = QString("DROP TABLE TileSets"); - query.exec(s); - s = QString("DROP TABLE SetTiles"); - query.exec(s); - s = QString("DROP TABLE TilesDownload"); - query.exec(s); - _valid = _createDB(*_db); + + QGCResetTask* const task = static_cast(mtask); + + (void) _tilesTable->drop(); + (void) _tileSetsTable->drop(); + (void) _setTilesTable->drop(); + (void) _tilesDownloadTable->drop(); + + // TODO: Where to get DB instance from? + QSqlDatabase db = QSqlDatabase::database(kSession); + _valid = _createDB(db); + task->setResetCompleted(); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_importSets(QGCMapTask* mtask) +void QGCCacheWorker::_importSets(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } - QGCImportTileTask* task = static_cast(mtask); - //-- If replacing, simply copy over it - if(task->replace()) { - //-- Close and delete old database - _disconnectDB(); + + QGCImportTileTask* const task = static_cast(mtask); + + QSqlDatabase db = QSqlDatabase::database(kSession); + + if (task->replace()) { + db.close(); + QSqlDatabase::removeDatabase(kSession); QFile file(_databasePath); - file.remove(); - //-- Copy given database - QFile::copy(task->path(), _databasePath); + (void) file.remove(); + (void) QFile::copy(task->path(), _databasePath); task->setProgress(25); - _init(); - if(_valid) { + (void) _init(); + if (_valid) { task->setProgress(50); - _connectDB(); + (void) _connectDB(); } task->setProgress(100); - } else { - //-- Open imported set - QSqlDatabase* dbImport = new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", kExportSession)); - dbImport->setDatabaseName(task->path()); - dbImport->setConnectOptions("QSQLITE_ENABLE_SHARED_CACHE"); - if (dbImport->open()) { - QSqlQuery query(*dbImport); - //-- Prepare progress report - quint64 tileCount = 0; - quint64 currentCount = 0; - int lastProgress = -1; - QString s; - s = QString("SELECT COUNT(tileID) FROM Tiles"); - if(query.exec(s)) { - if(query.next()) { - //-- Total number of tiles in imported database - tileCount = query.value(0).toULongLong(); - } - } - if(tileCount) { - //-- Iterate Tile Sets - s = QString("SELECT * FROM TileSets ORDER BY defaultSet DESC, name ASC"); - if(query.exec(s)) { - while(query.next()) { - QString name = query.value("name").toString(); - quint64 setID = query.value("setID").toULongLong(); - QString mapType = query.value("typeStr").toString(); - double topleftLat = query.value("topleftLat").toDouble(); - double topleftLon = query.value("topleftLon").toDouble(); - double bottomRightLat = query.value("bottomRightLat").toDouble(); - double bottomRightLon = query.value("bottomRightLon").toDouble(); - int minZoom = query.value("minZoom").toInt(); - int maxZoom = query.value("maxZoom").toInt(); - int type = query.value("type").toInt(); - quint32 numTiles = query.value("numTiles").toUInt(); - int defaultSet = query.value("defaultSet").toInt(); - quint64 insertSetID = _getDefaultTileSet(); - //-- If not default set, create new one - if(!defaultSet) { - //-- Check if we have this tile set already - if(_findTileSetID(name, insertSetID)) { - int testCount = 0; - //-- Set with this name already exists. Make name unique. - while (true) { - auto testName = QString::asprintf("%s %02d", name.toLatin1().data(), ++testCount); - if(!_findTileSetID(testName, insertSetID) || testCount > 99) { - name = testName; - break; - } + task->setImportCompleted(); + return; + } + + QSqlDatabase dbImport = QSqlDatabase::addDatabase("QSQLITE", QStringLiteral("QGeoTileImportSession")); + dbImport.setDatabaseName(task->path()); + dbImport.setConnectOptions(QStringLiteral("QSQLITE_ENABLE_SHARED_CACHE")); + if (dbImport.open()) { + quint64 tileCount = 0; + QSqlQuery query(dbImport); + query.prepare("SELECT COUNT(tileID) FROM Tiles"); + if (query.exec() && query.next()) { + tileCount = query.value(0).toULongLong(); + } + + if (tileCount) { + query.prepare("SELECT * FROM TileSets ORDER BY defaultSet DESC, name ASC"); + if (query.exec()) { + quint64 currentCount = 0; + int lastProgress = -1; + while (query.next()) { + QString name = query.value("name").toString(); + const quint64 setID = query.value("setID").toULongLong(); + const QString mapType = query.value("typeStr").toString(); + const double topleftLat = query.value("topleftLat").toDouble(); + const double topleftLon = query.value("topleftLon").toDouble(); + const double bottomRightLat = query.value("bottomRightLat").toDouble(); + const double bottomRightLon = query.value("bottomRightLon").toDouble(); + const int minZoom = query.value("minZoom").toInt(); + const int maxZoom = query.value("maxZoom").toInt(); + const int type = query.value("type").toInt(); + const quint32 numTiles = query.value("numTiles").toUInt(); + const int defaultSet = query.value("defaultSet").toInt(); + quint64 insertSetID = _getDefaultTileSet(); + if (defaultSet == 0) { + if (_findTileSetID(name, insertSetID)) { + int testCount = 0; + while (true) { + const QString testName = QString::asprintf("%s %02d", name.toLatin1().data(), ++testCount); + if (!_findTileSetID(testName, insertSetID) || (testCount > 99)) { + name = testName; + break; } } - //-- Create new set - QSqlQuery cQuery(*_db); - cQuery.prepare("INSERT INTO TileSets(" - "name, typeStr, topleftLat, topleftLon, bottomRightLat, bottomRightLon, minZoom, maxZoom, type, numTiles, defaultSet, date" - ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - cQuery.addBindValue(name); - cQuery.addBindValue(mapType); - cQuery.addBindValue(topleftLat); - cQuery.addBindValue(topleftLon); - cQuery.addBindValue(bottomRightLat); - cQuery.addBindValue(bottomRightLon); - cQuery.addBindValue(minZoom); - cQuery.addBindValue(maxZoom); + } + + _tileSetsTable->insertTileSet() + + QSqlQuery cQuery(db); + (void) cQuery.prepare("INSERT INTO TileSets(" + "name, typeStr, topleftLat, topleftLon, bottomRightLat, bottomRightLon, minZoom, maxZoom, type, numTiles, defaultSet, date" + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + ); + cQuery.addBindValue(name); + cQuery.addBindValue(mapType); + cQuery.addBindValue(topleftLat); + cQuery.addBindValue(topleftLon); + cQuery.addBindValue(bottomRightLat); + cQuery.addBindValue(bottomRightLon); + cQuery.addBindValue(minZoom); + cQuery.addBindValue(maxZoom); + cQuery.addBindValue(type); + cQuery.addBindValue(numTiles); + cQuery.addBindValue(defaultSet); + cQuery.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); + if (!cQuery.exec()) { + task->setError("Error adding imported tile set to database"); + break; + } else { + insertSetID = cQuery.lastInsertId().toULongLong(); + } + } + + QSqlQuery cQuery(db); + QSqlQuery subQuery(dbImport); + subQuery.prepare("SELECT * FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A JOIN SetTiles B ON A.tileID = B.tileID WHERE B.setID = ? GROUP BY A.tileID HAVING COUNT(A.tileID) = 1)"); + subQuery.addBindValue(setID); + if (subQuery.exec()) { + quint64 tilesFound = 0; + quint64 tilesSaved = 0; + (void) db.transaction(); + while (subQuery.next()) { + tilesFound++; + const QString hash = subQuery.value("hash").toString(); + const QString format = subQuery.value("format").toString(); + const QByteArray img = subQuery.value("tile").toByteArray(); + const int type = subQuery.value("type").toInt(); + (void) cQuery.prepare("INSERT INTO Tiles(hash, format, tile, size, type, date) VALUES(?, ?, ?, ?, ?, ?)"); + cQuery.addBindValue(hash); + cQuery.addBindValue(format); + cQuery.addBindValue(img); + cQuery.addBindValue(img.size()); cQuery.addBindValue(type); - cQuery.addBindValue(numTiles); - cQuery.addBindValue(defaultSet); cQuery.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); - if(!cQuery.exec()) { - task->setError("Error adding imported tile set to database"); - break; - } else { - //-- Get just created (auto-incremented) setID - insertSetID = cQuery.lastInsertId().toULongLong(); - } - } - //-- Find set tiles - QSqlQuery cQuery(*_db); - QSqlQuery subQuery(*dbImport); - QString sb = QString("SELECT * FROM Tiles WHERE tileID IN (SELECT A.tileID FROM SetTiles A JOIN SetTiles B ON A.tileID = B.tileID WHERE B.setID = %1 GROUP BY A.tileID HAVING COUNT(A.tileID) = 1)").arg(setID); - if(subQuery.exec(sb)) { - quint64 tilesFound = 0; - quint64 tilesSaved = 0; - _db->transaction(); - while(subQuery.next()) { - tilesFound++; - QString hash = subQuery.value("hash").toString(); - QString format = subQuery.value("format").toString(); - QByteArray img = subQuery.value("tile").toByteArray(); - int type = subQuery.value("type").toInt(); - //-- Save tile - cQuery.prepare("INSERT INTO Tiles(hash, format, tile, size, type, date) VALUES(?, ?, ?, ?, ?, ?)"); - cQuery.addBindValue(hash); - cQuery.addBindValue(format); - cQuery.addBindValue(img); - cQuery.addBindValue(img.size()); - cQuery.addBindValue(type); - cQuery.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); - if(cQuery.exec()) { - tilesSaved++; - quint64 importTileID = cQuery.lastInsertId().toULongLong(); - QString s = QString("INSERT INTO SetTiles(tileID, setID) VALUES(%1, %2)").arg(importTileID).arg(insertSetID); - cQuery.prepare(s); - cQuery.exec(); - currentCount++; - if(tileCount) { - int progress = (int)((double)currentCount / (double)tileCount * 100.0); - //-- Avoid calling this if (int) progress hasn't changed. - if(lastProgress != progress) { - lastProgress = progress; - task->setProgress(progress); - } + if (cQuery.exec()) { + tilesSaved++; + const quint64 importTileID = cQuery.lastInsertId().toULongLong(); + cQuery.prepare("INSERT INTO SetTiles(tileID, setID) VALUES(?, ?)"); + cQuery.addBindValue(importTileID); + cQuery.addBindValue(insertSetID); + (void) cQuery.exec(); + currentCount++; + if (tileCount) { + const int progress = static_cast((static_cast(currentCount) / static_cast(tileCount)) * 100.0); + if (lastProgress != progress) { + lastProgress = progress; + task->setProgress(progress); } } } - _db->commit(); - if(tilesSaved) { - //-- Update tile count (if any added) - s = QString("SELECT COUNT(size) FROM Tiles A INNER JOIN SetTiles B on A.tileID = B.tileID WHERE B.setID = %1").arg(insertSetID); - if(cQuery.exec(s)) { - if(cQuery.next()) { - quint64 count = cQuery.value(0).toULongLong(); - s = QString("UPDATE TileSets SET numTiles = %1 WHERE setID = %2").arg(count).arg(insertSetID); - cQuery.exec(s); - } - } - } - qint64 uniqueTiles = tilesFound - tilesSaved; - if((quint64)uniqueTiles < tileCount) { - tileCount -= uniqueTiles; - } else { - tileCount = 0; - } - //-- If there was nothing new in this set, remove it. - if(!tilesSaved && !defaultSet) { - qCDebug(QGCTileCacheLog) << "No unique tiles in" << name << "Removing it."; - _deleteTileSet(insertSetID); - } + } + + (void) db.commit(); + + _tilesSetTilesRelationalTable->select(tilesSaved, insertSetID); + + const qint64 uniqueTiles = tilesFound - tilesSaved; + if (static_cast(uniqueTiles) < tileCount) { + tileCount -= uniqueTiles; + } else { + tileCount = 0; + } + + if (!tilesSaved && !defaultSet) { + _deleteTileSet(insertSetID); + qCDebug(QGCTileCacheWorkerLog) << "No unique tiles in" << name << "Removing it."; } } - } else { - task->setError("No tile set in database"); } + } else { + task->setError("No tile set in database"); } - delete dbImport; - QSqlDatabase::removeDatabase(kExportSession); - if(!tileCount) { - task->setError("No unique tiles in imported database"); - } - } else { - task->setError("Error opening import database"); } + + if (!tileCount) { + task->setError("No unique tiles in imported database"); + } + } else { + task->setError("Error opening import database"); } + + dbImport.close(); + QSqlDatabase::removeDatabase(dbImport.connectionName()); + task->setImportCompleted(); } -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_exportSets(QGCMapTask* mtask) +void QGCCacheWorker::_exportSets(QGCMapTask *mtask) { - if(!_testTask(mtask)) { + if (!_testTask(mtask)) { return; } - QGCExportTileTask* task = static_cast(mtask); - //-- Delete target if it exists + + QGCExportTileTask* const task = static_cast(mtask); + + QSqlDatabase db = QSqlDatabase::database(kSession); + QFile file(task->path()); - file.remove(); - //-- Create exported database - QScopedPointer dbExport(new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", kExportSession))); - dbExport->setDatabaseName(task->path()); - dbExport->setConnectOptions("QSQLITE_ENABLE_SHARED_CACHE"); - if (dbExport->open()) { - if(_createDB(*dbExport, false)) { - //-- Prepare progress report + (void) file.remove(); + + QSqlDatabase dbExport = QSqlDatabase::addDatabase("QSQLITE", QStringLiteral("QGeoTileExportSession")); + dbExport.setDatabaseName(task->path()); + dbExport.setConnectOptions(QStringLiteral("QSQLITE_ENABLE_SHARED_CACHE")); + if (dbExport.open()) { + if (_createDB(dbExport, false)) { quint64 tileCount = 0; quint64 currentCount = 0; - for(int i = 0; i < task->sets().count(); i++) { - QGCCachedTileSet* set = task->sets()[i]; - //-- Default set has no unique tiles - if(set->defaultSet()) { + for (qsizetype i = 0; i < task->sets().count(); i++) { + const QGCCachedTileSet* const set = task->sets().at(i); + if (set->defaultSet()) { tileCount += set->totalTileCount(); } else { tileCount += set->uniqueTileCount(); } } - if(!tileCount) { + + if (tileCount == 0) { tileCount = 1; } - //-- Iterate sets to save - for(int i = 0; i < task->sets().count(); i++) { - QGCCachedTileSet* set = task->sets()[i]; - //-- Create Tile Exported Set - QSqlQuery exportQuery(*dbExport); - exportQuery.prepare("INSERT INTO TileSets(" + + for (qsizetype i = 0; i < task->sets().count(); i++) { + const QGCCachedTileSet* const set = task->sets().at(i); + + QSqlQuery exportQuery(dbExport); + (void) exportQuery.prepare("INSERT INTO TileSets(" "name, typeStr, topleftLat, topleftLon, bottomRightLat, bottomRightLon, minZoom, maxZoom, type, numTiles, defaultSet, date" - ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + ); exportQuery.addBindValue(set->name()); exportQuery.addBindValue(set->mapTypeStr()); exportQuery.addBindValue(set->topleftLat()); @@ -939,203 +641,149 @@ QGCCacheWorker::_exportSets(QGCMapTask* mtask) exportQuery.addBindValue(set->totalTileCount()); exportQuery.addBindValue(set->defaultSet()); exportQuery.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); - if(!exportQuery.exec()) { + if (!exportQuery.exec()) { task->setError("Error adding tile set to exported database"); break; - } else { - //-- Get just created (auto-incremented) setID - quint64 exportSetID = exportQuery.lastInsertId().toULongLong(); - //-- Find set tiles - QString s = QString("SELECT * FROM SetTiles WHERE setID = %1").arg(set->id()); - QSqlQuery query(*_db); - if(query.exec(s)) { - dbExport->transaction(); - while(query.next()) { - quint64 tileID = query.value("tileID").toULongLong(); - //-- Get tile - QString s = QString("SELECT * FROM Tiles WHERE tileID = \"%1\"").arg(tileID); - QSqlQuery subQuery(*_db); - if(subQuery.exec(s)) { - if(subQuery.next()) { - QString hash = subQuery.value("hash").toString(); - QString format = subQuery.value("format").toString(); - QByteArray img = subQuery.value("tile").toByteArray(); - int type = subQuery.value("type").toInt(); - //-- Save tile - exportQuery.prepare("INSERT INTO Tiles(hash, format, tile, size, type, date) VALUES(?, ?, ?, ?, ?, ?)"); - exportQuery.addBindValue(hash); - exportQuery.addBindValue(format); - exportQuery.addBindValue(img); - exportQuery.addBindValue(img.size()); - exportQuery.addBindValue(type); - exportQuery.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); - if(exportQuery.exec()) { - quint64 exportTileID = exportQuery.lastInsertId().toULongLong(); - QString s = QString("INSERT INTO SetTiles(tileID, setID) VALUES(%1, %2)").arg(exportTileID).arg(exportSetID); - exportQuery.prepare(s); - exportQuery.exec(); - currentCount++; - task->setProgress((int)((double)currentCount / (double)tileCount * 100.0)); - } - } + } + + const quint64 exportSetID = exportQuery.lastInsertId().toULongLong(); + + QSqlQuery query(db); + query.prepare("SELECT * FROM SetTiles WHERE setID = ?"); + query.addBindValue(set->id()); + if (query.exec()) { // _setTilesTable->selectFromSetID(set->id()) + dbExport.transaction(); + while (query.next()) { + const quint64 tileID = query.value("tileID").toULongLong(); + QSqlQuery subQuery(db); + subQuery.prepare("SELECT * FROM Tiles WHERE tileID = ?"); + subQuery.addBindValue(tileID); + if (subQuery.exec() && subQuery.next()) { + const QString hash = subQuery.value("hash").toString(); + const QString format = subQuery.value("format").toString(); + const QByteArray img = subQuery.value("tile").toByteArray(); + const int type = subQuery.value("type").toInt(); + + (void) exportQuery.prepare("INSERT INTO Tiles(hash, format, tile, size, type, date) VALUES(?, ?, ?, ?, ?, ?)"); + exportQuery.addBindValue(hash); + exportQuery.addBindValue(format); + exportQuery.addBindValue(img); + exportQuery.addBindValue(img.size()); + exportQuery.addBindValue(type); + exportQuery.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); + if (exportQuery.exec()) { // _tileTable->insertTile(QGCCacheTile(hash, img, format, type)) + const quint64 exportTileID = exportQuery.lastInsertId().toULongLong(); + // (void) _setTilesTable->insertSetTiles(exportSetID, exportTileID); + exportQuery.prepare("INSERT INTO SetTiles(tileID, setID) VALUES(?, ?)"); + exportQuery.addBindValue(exportTileID); + exportQuery.addBindValue(exportSetID); + (void) exportQuery.exec(); + currentCount++; + task->setProgress(static_cast(static_cast(currentCount) / static_cast(tileCount) * 100.0)); } } } - dbExport->commit(); + (void) dbExport.commit(); } } } else { task->setError("Error creating export database"); } } else { - qCritical() << "Map Cache SQL error (create export database):" << dbExport->lastError(); + qCCritical(QGCTileCacheWorkerLog) << "Map Cache SQL error (create export database):" << dbExport.lastError(); task->setError("Error opening export database"); } - dbExport.reset(); - QSqlDatabase::removeDatabase(kExportSession); + + dbExport.close(); + QSqlDatabase::removeDatabase(dbExport.connectionName()); + task->setExportCompleted(); } -//----------------------------------------------------------------------------- -bool QGCCacheWorker::_testTask(QGCMapTask* mtask) +bool QGCCacheWorker::_testTask(QGCMapTask *mtask) { - if(!_valid) { + if (!_valid) { mtask->setError("No Cache Database"); return false; } + return true; } -//----------------------------------------------------------------------------- -bool -QGCCacheWorker::_init() +bool QGCCacheWorker::_init() { _failed = false; - if(!_databasePath.isEmpty()) { - qCDebug(QGCTileCacheLog) << "Mapping cache directory:" << _databasePath; - //-- Initialize Database - if (_connectDB()) { - _valid = _createDB(*_db); - if(!_valid) { - _failed = true; - } - } else { - qCritical() << "Map Cache SQL error (init() open db):" << _db->lastError(); - _failed = true; - } - _disconnectDB(); - } else { - qCritical() << "Could not find suitable cache directory."; + + if (_databasePath.isEmpty()) { + qCCritical(QGCTileCacheWorkerLog) << "Could not find suitable cache directory."; _failed = true; + return _failed; } + + qCDebug(QGCTileCacheWorkerLog) << "Mapping cache directory:" << _databasePath; + if (!_connectDB()) { + qCCritical(QGCTileCacheWorkerLog) << "Map Cache SQL error (init() open db)"; + _failed = true; + return _failed; + } + + QSqlDatabase db = QSqlDatabase::database(kSession); + _valid = _createDB(db); + if (!_valid) { + _failed = true; + } + + db.close(); + QSqlDatabase::removeDatabase(kSession); + return _failed; } -//----------------------------------------------------------------------------- -bool -QGCCacheWorker::_connectDB() +bool QGCCacheWorker::_connectDB() { - _db.reset(new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", kSession))); - _db->setDatabaseName(_databasePath); - _db->setConnectOptions("QSQLITE_ENABLE_SHARED_CACHE"); - _valid = _db->open(); + if (!QSqlDatabase::contains(kSession)) { + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", kSession); + db.setDatabaseName(_databasePath); + db.setConnectOptions(QStringLiteral("QSQLITE_ENABLE_SHARED_CACHE")); + _valid = db.open(); + } else { + QSqlDatabase db = QSqlDatabase::database(kSession); + if (!db.isOpen()) { + _valid = db.open(); + } else { + _valid = true; + } + } + return _valid; } -//----------------------------------------------------------------------------- -bool -QGCCacheWorker::_createDB(QSqlDatabase& db, bool createDefault) +bool QGCCacheWorker::_createDB(QSqlDatabase &db, bool createDefault) { bool res = false; - QSqlQuery query(db); - if(!query.exec( - "CREATE TABLE IF NOT EXISTS Tiles (" - "tileID INTEGER PRIMARY KEY NOT NULL, " - "hash TEXT NOT NULL UNIQUE, " - "format TEXT NOT NULL, " - "tile BLOB NULL, " - "size INTEGER, " - "type INTEGER, " - "date INTEGER DEFAULT 0)")) - { - qWarning() << "Map Cache SQL error (create Tiles db):" << query.lastError().text(); + + if (!_tilesTable->create()) { + qCWarning(QGCTileCacheWorkerLog) << "Map Cache SQL error (create Tiles db)"; } else { - query.exec("CREATE INDEX IF NOT EXISTS hash ON Tiles ( hash, size, type ) "); - - if(!query.exec( - "CREATE TABLE IF NOT EXISTS TileSets (" - "setID INTEGER PRIMARY KEY NOT NULL, " - "name TEXT NOT NULL UNIQUE, " - "typeStr TEXT, " - "topleftLat REAL DEFAULT 0.0, " - "topleftLon REAL DEFAULT 0.0, " - "bottomRightLat REAL DEFAULT 0.0, " - "bottomRightLon REAL DEFAULT 0.0, " - "minZoom INTEGER DEFAULT 3, " - "maxZoom INTEGER DEFAULT 3, " - "type INTEGER DEFAULT -1, " - "numTiles INTEGER DEFAULT 0, " - "defaultSet INTEGER DEFAULT 0, " - "date INTEGER DEFAULT 0)")) - { - qWarning() << "Map Cache SQL error (create TileSets db):" << query.lastError().text(); + if (!_tileSetsTable->create()) { + qCWarning(QGCTileCacheWorkerLog) << "Map Cache SQL error (create TileSets db)"; + } else if (!_setTilesTable->create()) { + qCWarning(QGCTileCacheWorkerLog) << "Map Cache SQL error (create SetTiles db)"; + } else if (!_tilesDownloadTable->create()) { + qCWarning(QGCTileCacheWorkerLog) << "Map Cache SQL error (create TilesDownload db)"; } else { - if(!query.exec( - "CREATE TABLE IF NOT EXISTS SetTiles (" - "setID INTEGER, " - "tileID INTEGER)")) - { - qWarning() << "Map Cache SQL error (create SetTiles db):" << query.lastError().text(); - } else { - if(!query.exec( - "CREATE TABLE IF NOT EXISTS TilesDownload (" - "setID INTEGER, " - "hash TEXT NOT NULL UNIQUE, " - "type INTEGER, " - "x INTEGER, " - "y INTEGER, " - "z INTEGER, " - "state INTEGER DEFAULT 0)")) - { - qWarning() << "Map Cache SQL error (create TilesDownload db):" << query.lastError().text(); - } else { - //-- Database it ready for use - res = true; - } - } + res = true; } } - //-- Create default tile set - if(res && createDefault) { - QString s = QString("SELECT name FROM TileSets WHERE name = \"%1\"").arg(kDefaultSet); - if(query.exec(s)) { - if(!query.next()) { - query.prepare("INSERT INTO TileSets(name, defaultSet, date) VALUES(?, ?, ?)"); - query.addBindValue(kDefaultSet); - query.addBindValue(1); - query.addBindValue(QDateTime::currentDateTime().toSecsSinceEpoch()); - if(!query.exec()) { - qWarning() << "Map Cache SQL error (Creating default tile set):" << db.lastError(); - res = false; - } - } - } else { - qWarning() << "Map Cache SQL error (Looking for default tile set):" << db.lastError(); - } + + if (res && createDefault) { + res = _tileSetsTable->createDefaultTileSet(); } - if(!res) { + + if (!res) { QFile file(_databasePath); - file.remove(); + (void) file.remove(); } - return res; -} -//----------------------------------------------------------------------------- -void -QGCCacheWorker::_disconnectDB() -{ - if (_db) { - _db.reset(); - QSqlDatabase::removeDatabase(kSession); - } + return res; } diff --git a/src/QtLocationPlugin/QGCTileCacheWorker.h b/src/QtLocationPlugin/QGCTileCacheWorker.h index ebbc5f272e9..5a7f2f918b4 100644 --- a/src/QtLocationPlugin/QGCTileCacheWorker.h +++ b/src/QtLocationPlugin/QGCTileCacheWorker.h @@ -18,81 +18,93 @@ #pragma once +#include +#include +#include #include #include -#include -#include #include -#include -#include -Q_DECLARE_LOGGING_CATEGORY(QGCTileCacheLog) +#define LONG_TIMEOUT 5 +#define SHORT_TIMEOUT 2 + +Q_DECLARE_LOGGING_CATEGORY(QGCTileCacheWorkerLog) class QGCMapTask; class QGCCachedTileSet; +class QSqlDatabase; +class SetTilesTableModel; +class TileSetsTableModel; +class TilesTableModel; +class TilesDownloadTableModel; -//----------------------------------------------------------------------------- class QGCCacheWorker : public QThread { Q_OBJECT + public: - QGCCacheWorker (QObject* parent = nullptr); - ~QGCCacheWorker (); + explicit QGCCacheWorker(QObject *parent = nullptr); + ~QGCCacheWorker(); - void quit (); - bool enqueueTask (QGCMapTask* task); - void setDatabaseFile (const QString& path); + void setDatabaseFilePath(const QString &path) { _databasePath = path; } + +public slots: + bool enqueueTask(QGCMapTask *task); + void stop(); + +signals: + void updateTotals(quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize); protected: - void run (); + void run() final; private: - void _runTask (QGCMapTask* task); - - void _saveTile (QGCMapTask* mtask); - void _getTile (QGCMapTask* mtask); - void _getTileSets (QGCMapTask* mtask); - void _createTileSet (QGCMapTask* mtask); - void _getTileDownloadList (QGCMapTask* mtask); - void _updateTileDownloadState(QGCMapTask* mtask); - void _deleteTileSet (QGCMapTask* mtask); - void _renameTileSet (QGCMapTask* mtask); - void _resetCacheDatabase (QGCMapTask* mtask); - void _pruneCache (QGCMapTask* mtask); - void _exportSets (QGCMapTask* mtask); - void _importSets (QGCMapTask* mtask); - bool _testTask (QGCMapTask* mtask); - void _deleteBingNoTileTiles (); - - quint64 _findTile (const QString hash); - bool _findTileSetID (const QString name, quint64& setID); - void _updateSetTotals (QGCCachedTileSet* set); - bool _init (); - bool _connectDB (); - bool _createDB (QSqlDatabase& db, bool createDefault = true); - void _disconnectDB (); - quint64 _getDefaultTileSet (); - void _updateTotals (); - void _deleteTileSet (qulonglong id); + void _runTask(QGCMapTask *task); -signals: - void updateTotals (quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize); + void _saveTile(QGCMapTask *task); + void _getTile(QGCMapTask *task); + void _getTileSets(QGCMapTask *task); + void _createTileSet(QGCMapTask *task); + void _getTileDownloadList(QGCMapTask *task); + void _updateTileDownloadState(QGCMapTask *task); + void _pruneCache(QGCMapTask *task); + void _deleteTileSet(QGCMapTask *task); + void _renameTileSet(QGCMapTask *task); + void _resetCacheDatabase(QGCMapTask *task); + void _importSets(QGCMapTask *task); + void _exportSets(QGCMapTask *task); + bool _testTask(QGCMapTask *task); -private: - QQueue _taskQueue; - QMutex _taskQueueMutex; - QWaitCondition _waitc; - QString _databasePath; - QScopedPointer _db; - std::atomic_bool _valid; - bool _failed; - quint64 _defaultSet; - quint64 _totalSize; - quint32 _totalCount; - quint64 _defaultSize; - quint32 _defaultCount; - time_t _lastUpdate; - int _updateTimeout; - - static constexpr const char* kDefaultSet = "Default Tile Set"; + bool _connectDB(); + bool _createDB(QSqlDatabase &db, bool createDefault = true); + bool _findTileSetID(const QString &name, quint64 &setID); + bool _init(); + quint64 _findTile(const QString &hash); + quint64 _getDefaultTileSet(); + void _deleteBingNoTileTiles(); + void _deleteTileSet(quint64 id); + void _updateSetTotals(QGCCachedTileSet *set); + void _updateTotals(); + + SetTilesTableModel *_setTilesTable = nullptr; + TileSetsTableModel *_tileSetsTable = nullptr; + TilesTableModel *_tilesTable = nullptr; + TilesDownloadTableModel *_tilesDownloadTable = nullptr; + TilesSetTilesRelationalTableModel *_tilesSetTilesRelationalTable = nullptr; + QMutex _taskQueueMutex; + QQueue _taskQueue; + QWaitCondition _waitc; + QString _databasePath; + quint32 _defaultCount = 0; + quint32 _totalCount = 0; + quint64 _defaultSet = UINT64_MAX; + quint64 _defaultSize = 0; + quint64 _totalSize = 0; + QElapsedTimer _updateTimer; + int _updateTimeout = SHORT_TIMEOUT; + std::atomic_bool _failed = false; + std::atomic_bool _valid = false; + + static QByteArray _bingNoTileImage; + static const QString kSession; };