summaryrefslogtreecommitdiff
path: root/sqlitebackend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sqlitebackend.cpp')
-rw-r--r--sqlitebackend.cpp322
1 files changed, 322 insertions, 0 deletions
diff --git a/sqlitebackend.cpp b/sqlitebackend.cpp
new file mode 100644
index 0000000..b95e18d
--- /dev/null
+++ b/sqlitebackend.cpp
@@ -0,0 +1,322 @@
+#include <QMessageBox>
+
+#include<sqlite3.h>
+
+#include "sqlitebackend.h"
+
+SQLiteSaveFile::SQLiteSaveFile(QObject *parent, QString filename) :
+ QObject(parent)
+ , lastError(NoError)
+ , lastErrorString(QString())
+ , db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()))
+ , filename(filename)
+ , m_isOpen(false)
+{
+ connect();
+}
+
+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;
+}
+
+bool SQLiteSaveFile::connect()
+{
+ QMutexLocker l(&dbMut);
+ resetError();
+ m_isOpen = false;
+ imageData = QByteArray();
+
+ bool newlyCreated = QFile(filename).exists();
+
+ db.setDatabaseName(filename);
+ if (!db.open()) {
+ setDatabaseError(db);
+ db.close();
+ return false;
+ }
+
+ if (!initDb(newlyCreated)) {
+ db.close();
+ return false;
+ }
+
+ m_isOpen = true;
+
+ /* Try to load image, ignore if image is unset */
+ QSqlQuery q("SELECT data FROM blobs WHERE name = 'image'", db);
+ if (!q.next())
+ return setDatabaseError(q);
+
+ imageData = q.value(0).toByteArray();
+ 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)
+{
+ QMutexLocker l(&dbMut);
+ QFile f(filename);
+ QSqlDatabase old_db = db;
+ 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;
+ }
+
+ sqlite3 *old_handle = getSqliteHandle(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;
+
+ db = new_db;
+ old_db.close();
+ return true;
+
+err_cleanup:
+ setDatabaseError(new_db);
+ new_db.close();
+ return false;
+}
+
+bool SQLiteSaveFile::clearNew()
+{
+ QMutexLocker l(&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;
+ }
+
+ if (!initDb()) {
+ db.close();
+ return false;
+ }
+
+ imageData = QByteArray();
+ return true;
+}
+
+QList<Tag> SQLiteSaveFile::getAllTags()
+{
+ QMutexLocker l(&dbMut);
+ resetError();
+ QList<Tag> rv;
+ QSqlQuery q("SELECT (id, name, anchor_x, anchor_y, meta) FROM tags", db);
+
+ 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(&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;
+
+ tagChange(TagChange::CHANGED, tag);
+ return true;
+}
+
+bool SQLiteSaveFile::deleteTag(Tag tag)
+{
+ QMutexLocker l(&dbMut);
+ if (!runSql("DELETE FROM tags WHERE id=?", {tag.id}))
+ return false;
+
+ tagChange(TagChange::DELETED, tag);
+ return true;
+}
+
+bool SQLiteSaveFile::createTag(Tag tag)
+{
+ QMutexLocker l(&dbMut);
+ resetError();
+ QSqlQuery q("INSERT INTO tags(name, anchor_x, anchor_y, meta) VALUES (?, ?, ?, ?)", 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);
+ 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(&dbMut);
+ return setMetaLocked(key, value);
+}
+
+bool SQLiteSaveFile::setMeta(std::initializer_list<QPair<QString, QVariant>> metas) {
+ QMutexLocker l(&dbMut);
+ return setMetaLocked(metas);
+}
+
+QVariant SQLiteSaveFile::getMeta(const QString &key) {
+ QMutexLocker l(&dbMut);
+ return getMetaLocked(key);
+}
+
+QVariant SQLiteSaveFile::getMetaLocked(const QString &key)
+{
+ resetError();
+ QSqlQuery q("SELECT value FROM metadata WHERE key=?", db);
+ q.addBindValue(key);
+ if (!q.next()) {
+ setDatabaseError(q);
+ return QVariant();
+ }
+
+ return q.value(0);
+}
+
+bool SQLiteSaveFile::runSql(QString query, std::initializer_list<QVariant> bindings)
+{
+ resetError();
+ QSqlQuery q(query, db);
+ for (const QVariant &v : bindings)
+ q.addBindValue(v);
+
+ q.exec();
+ return setDatabaseError(q);
+}
+
+bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
+{
+ QMutexLocker l(&dbMut);
+ QFile f(filename);
+ resetError();
+
+ if (!f.open(QIODevice::ReadOnly)) {
+ setError(ImageOpenError, QString("Failed to open image: %1").arg(f.errorString()));
+ return false;
+ }
+
+ imageData = 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;
+
+ return runSql("INSERT OR REPLACE INTO blobs(name, data) VALUES ('image', ?)", {imageData});
+}
+
+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())
+{
+}
+
+Tag::Tag()
+ : id(-1)
+{
+}
+
+Tag::Tag(long long id, const Tag &other)
+ : id(id)
+ , name(other.name)
+ , anchor(other.anchor)
+ , metadata(other.metadata)
+{
+}
+
+bool SQLiteSaveFile::setDatabaseError(const QSqlQuery &q)
+{
+ if (!q.lastError().isValid())
+ return true;
+
+ setError(SQLiteError, QString("Project file database error: %1").arg(q.lastError().text()));
+ return false;
+}
+
+bool SQLiteSaveFile::setDatabaseError(const QSqlDatabase &db)
+{
+ if (!db.lastError().isValid())
+ return true;
+
+ setError(SQLiteError, QString("Project file database error: %1").arg(db.lastError().text()));
+ return false;
+}