numberator/sqlitebackend.cpp
2020-08-09 17:12:14 +02:00

362 lines
9.8 KiB
C++

#include <QMessageBox>
#include<sqlite3.h>
#include "sqlitebackend.h"
SQLiteSaveFile::SQLiteSaveFile(QObject *parent) :
QObject(parent)
, m_lastError(NoError)
, m_lastErrorString(QString())
, m_open(false)
, m_dirty(false)
{
clearNew();
}
bool SQLiteSaveFile::open(const QString &filename)
{
qDebug() << "open";
{
QMutexLocker l(&m_dbMut);
QFile f(filename);
if (!f.exists()) {
setError(FileNotFoundError, QString("File \"%1\"does not exist.").arg(filename));
return false;
}
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
new_db.setDatabaseName(f.fileName());
if (!new_db.open()) {
setDatabaseError(new_db);
new_db.close();
return false;
}
m_db.close();
m_db = new_db;
/* Try to load image, ignore if image is unset */
QSqlQuery q("SELECT data FROM blobs WHERE name = 'image'", m_db);
if (!q.exec())
return setDatabaseError(q);
if (!q.next())
return setDatabaseError(q);
m_memory = false;
m_dirty = false;
m_open = true;
m_image = q.value(0).toByteArray();
}
fileReload(); /* Call after unlocking mutex to allow accesses by receivers */
return true;
}
bool SQLiteSaveFile::clearNew()
{
qDebug() << "clearNew";
{
QMutexLocker l(&m_dbMut);
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
new_db.setDatabaseName(":memory:");
if (!new_db.open()) {
setDatabaseError(new_db);
new_db.close();
return false;
}
QSqlDatabase old_db = m_db;
m_db = new_db;
if (!initDb(true)) {
m_db.close();
m_db = old_db;
return false;
}
old_db.close();
m_memory = true;
m_dirty = false;
m_open = true;
m_image = QByteArray();
}
fileReload(); /* Call after unlocking mutex to allow accesses by receivers */
return true;
}
bool SQLiteSaveFile::initDb(bool setCreationDate)
{
for (auto const &q: {
"CREATE TABLE IF NOT EXISTS metadata (key TEXT, value TEXT)",
"CREATE TABLE IF NOT EXISTS tags (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, anchor_x REAL, anchor_y REAL, meta TEXT)",
"CREATE TABLE IF NOT EXISTS blobs (name TEXT, data BLOB)"}) {
if (!runSql(q))
return false;
}
if (setCreationDate) {
if (!setMetaLocked("creationTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()))
return false;
}
return true;
}
static sqlite3 *getSqliteHandle(QSqlDatabase &db) {
QVariant v = db.driver()->handle();
assert (v.isValid());
assert (!qstrcmp(v.typeName(), "sqlite3*"));
return *static_cast<sqlite3 **>(v.data());
}
bool SQLiteSaveFile::saveAs(const QString &filename)
{
qDebug() << "saveAs" << filename;
QMutexLocker l(&m_dbMut);
QFile f(filename);
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
{
new_db.setDatabaseName(f.fileName());
if (!new_db.open())
goto err_cleanup;
sqlite3 *old_handle = getSqliteHandle(m_db);
sqlite3 *new_handle = getSqliteHandle(new_db);
sqlite3_backup *bck = sqlite3_backup_init(new_handle, "main", old_handle, "main");
if (!bck)
goto err_cleanup;
if (sqlite3_backup_step(bck, -1) != SQLITE_DONE)
goto err_cleanup;
if (sqlite3_backup_finish(bck) != SQLITE_DONE)
goto err_cleanup;
m_db.close();
m_db = new_db;
m_memory = false;
return true;
}
err_cleanup:
setDatabaseError(new_db);
new_db.close();
return false;
}
QList<Tag> SQLiteSaveFile::getAllTags()
{
qDebug() << "getAllTags";
QMutexLocker l(&m_dbMut);
resetError();
QList<Tag> rv;
QSqlQuery q("SELECT id, name, anchor_x, anchor_y, meta FROM tags", m_db);
if (!q.exec()) {
setDatabaseError(q);
return QList<Tag>();
}
while (q.next()) {
rv << Tag {
q.value(0).toLongLong(),
q.value(1).toString(),
q.value(2).toFloat(),
q.value(3).toFloat(),
q.value(4).toByteArray()
};
}
if (!setDatabaseError(q))
return QList<Tag>();
return rv;
}
bool SQLiteSaveFile::updateTag(Tag tag)
{
QMutexLocker l(&m_dbMut);
if (!runSql("UPDATE tags SET name=?, anchor_x=?, anchor_y=?, meta=? WHERE id=?", {
tag.name, tag.anchor.x(), tag.anchor.y(), QJsonDocument::fromVariant(tag.metadata).toJson(), tag.id
}))
return false;
m_dirty = true;
tagChange(TagChange::CHANGED, tag);
return true;
}
bool SQLiteSaveFile::deleteTag(Tag tag)
{
QMutexLocker l(&m_dbMut);
if (!runSql("DELETE FROM tags WHERE id=?", {tag.id}))
return false;
m_dirty = true;
tagChange(TagChange::DELETED, tag);
return true;
}
bool SQLiteSaveFile::createTag(Tag tag)
{
qDebug() << "createTag";
QMutexLocker l(&m_dbMut);
resetError();
QSqlQuery q("INSERT INTO tags(name, anchor_x, anchor_y, meta) VALUES (?, ?, ?, ?)", m_db);
q.addBindValue(tag.name);
q.addBindValue(tag.anchor.x());
q.addBindValue(tag.anchor.y());
q.addBindValue(QJsonDocument::fromVariant(tag.metadata).toJson());
if (!q.exec())
return setDatabaseError(q);
Tag created_tag(q.lastInsertId().toLongLong(), tag);
m_dirty = true;
tagChange(TagChange::CREATED, created_tag);
return true;
}
bool SQLiteSaveFile::setMetaLocked(const QString &key, const QVariant &value)
{
return runSql("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)", {key, value});
}
bool SQLiteSaveFile::setMetaLocked(std::initializer_list<QPair<QString, QVariant>> metas)
{
for (const auto &meta : metas) {
if (!setMetaLocked(meta.first, meta.second))
return false;
}
return true;
}
bool SQLiteSaveFile::setMeta(const QString &key, const QVariant &value) {
QMutexLocker l(&m_dbMut);
m_dirty = true;
return setMetaLocked(key, value);
}
bool SQLiteSaveFile::setMeta(std::initializer_list<QPair<QString, QVariant>> metas) {
QMutexLocker l(&m_dbMut);
m_dirty = true;
return setMetaLocked(metas);
}
const QVariant SQLiteSaveFile::getMeta(const QString &key) const {
QMutexLocker l(&m_dbMut);
return getMetaLocked(key);
}
const QVariant SQLiteSaveFile::getMetaLocked(const QString &key) const
{
qDebug() << "getMeta " << key;
resetError();
QSqlQuery q("SELECT value FROM metadata WHERE key=?", m_db);
q.addBindValue(key);
if (!q.exec()) {
setDatabaseError(q);
return QVariant();
}
if (!q.next()) {
setDatabaseError(q);
return QVariant();
}
return q.value(0);
}
bool SQLiteSaveFile::runSql(QString query, std::initializer_list<QVariant> bindings)
{
qDebug() << "runSql " << query;
resetError();
QSqlQuery q(query, m_db);
for (const QVariant &v : bindings) {
q.addBindValue(v);
}
if (!q.exec()) {
return setDatabaseError(q);
}
return true;
}
bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
{
QMutexLocker l(&m_dbMut);
QFile f(filename);
resetError();
if (!f.open(QIODevice::ReadOnly)) {
setError(ImageOpenError, QString("Failed to open image: %1").arg(f.errorString()));
return false;
}
m_image = f.readAll();
if (f.error() != QFileDevice::NoError) {
setError(ImageReadError, QString("Failed to read image: %1").arg(f.errorString()));
return false;
}
if (!setMetaLocked({
{"imagePathOriginal", f.fileName()},
{"imagePathAbsolute", QFileInfo(f).absoluteFilePath()},
{"imageLoadedTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()}}))
return false;
m_dirty = true;
return runSql("INSERT OR REPLACE INTO blobs(name, data) VALUES ('image', ?)", {m_image});
}
bool SQLiteSaveFile::reloadImageFromDisk()
{
const QString &p = getMeta("imagePathOriginal").toString();
if (QFile(p).exists())
return loadImageFromDisk(p);
const QString &q = getMeta("imagePathAbsolute").toString();
if (QFile(q).exists())
return loadImageFromDisk(q);
return false;
}
Tag::Tag(long long id, QString name, qreal anchor_x, qreal anchor_y, QByteArray metadata)
: id(id)
, name(name)
, anchor(QPointF(anchor_x, anchor_y))
, metadata(QJsonDocument::fromJson(metadata).object().toVariantMap())
, valid(true)
{
}
Tag::Tag(long long id, const Tag &other)
: id(id)
, name(other.name)
, anchor(other.anchor)
, metadata(other.metadata)
, valid(true)
{
}
bool SQLiteSaveFile::setDatabaseError(const QSqlQuery &q) const
{
if (!q.lastError().isValid())
return true;
qDebug() << "Query error: " << q.lastError().text();
setError(SQLiteError, QString("Project file database error executing %1: %2").arg(q.executedQuery()).arg(q.lastError().text()));
return false;
}
bool SQLiteSaveFile::setDatabaseError(const QSqlDatabase &db) const
{
if (!db.lastError().isValid())
return true;
qDebug() << "Database error: " << db.lastError().text();
setError(SQLiteError, QString("Project file database error: %1").arg(db.lastError().text()));
return false;
}