#include "database.h" #include #include #include #include #include #include "loadimage.h" Database::Database(QObject *parent) : QObject(parent) { } bool Database::init(const QLatin1String &connectionName) { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QDir dir(path); database = QSqlDatabase::addDatabase("QSQLITE", connectionName); ngc = QSqlDatabase::addDatabase("QSQLITE", connectionName + "ngc"); if(!dir.mkpath(".")) return false; if(ngc.isValid()) { QString ngcDb = dir.absoluteFilePath("ngc.db"); if(!QFile::exists(ngcDb)) QFile::copy(":/ngc.db", ngcDb); ngc.setDatabaseName(ngcDb); if(ngc.open()) { m_getNgc = QSqlQuery(ngc); m_getNgc.prepare("SELECT *,IIF(V_Mag IS NULL, B_Mag, V_Mag) AS mag FROM ngc WHERE RA_deg BETWEEN ? AND ? AND DEC_deg BETWEEN ? AND ?"); } else { qWarning() << "Could not open NGC database"; } } if(database.isValid()) { database.setDatabaseName(dir.absoluteFilePath("database2.db")); if(database.open()) { QSqlQuery query(database); query.exec("PRAGMA foreign_keys = ON"); int version = checkVersion(database); if(version == 0) { query.exec("PRAGMA user_version = 1"); query.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)"); query.exec("CREATE TABLE IF NOT EXISTS fits_files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE, mtime DATETIME," " minRa REAL, maxRa REAL, minDec REAL, maxDec REAL, crVal1 REAL, crVal2 REAL)"); query.exec("CREATE TABLE IF NOT EXISTS fits_headers (id INTEGER PRIMARY KEY AUTOINCREMENT, id_file INTEGER," "key VARCHAR(81), value VARCHAR(81), comment VARCHAR(81), FOREIGN KEY(id_file) REFERENCES fits_files(id) ON DELETE CASCADE)"); query.exec("CREATE INDEX IF NOT EXISTS key_value ON fits_headers(key, value)"); query.exec("CREATE INDEX IF NOT EXISTS id_file ON fits_headers(id_file)"); query.exec("CREATE INDEX IF NOT EXISTS minRa_idx ON fits_files(minRa)"); query.exec("CREATE INDEX IF NOT EXISTS maxRa_idx ON fits_files(maxRa)"); query.exec("CREATE INDEX IF NOT EXISTS minDec_idx ON fits_files(minDec)"); query.exec("CREATE INDEX IF NOT EXISTS maxDec_idx ON fits_files(maxDec)"); version = 1; } if(version == 1) { query.exec("CREATE INDEX IF NOT EXISTS id_file_key ON fits_headers(id_file, key)"); query.exec("PRAGMA user_version = 2"); version = 2; } if(version > 2) { qWarning() << "Database version is too new"; return false; } QSqlError error = database.lastError(); if(error.type() == QSqlError::NoError) { m_markQuery = QSqlQuery(database); m_markQuery.prepare("INSERT INTO files (file) VALUES (?)"); m_unmarkQuery = QSqlQuery(database); m_unmarkQuery.prepare("DELETE FROM files WHERE file = (?)"); m_isMarkedQuery = QSqlQuery(database); m_isMarkedQuery.prepare("SELECT * FROM files WHERE file = (:name)"); m_insertFile = QSqlQuery(database); m_insertFile.prepare("INSERT INTO fits_files (file, mtime) VALUES (?, ?)"); m_insertFileWcs = QSqlQuery(database); m_insertFileWcs.prepare("INSERT INTO fits_files (file, mtime, minRa, maxRa, minDec, maxDec, crVal1, crVal2) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); m_insertFitsHeader = QSqlQuery(database); m_insertFitsHeader.prepare("INSERT INTO fits_headers (id_file, key, value, comment) VALUES (?, ?, ?, ?)"); m_checkFile = QSqlQuery(database); m_checkFile.prepare("SELECT id,mtime FROM fits_files WHERE file=?"); m_headerKeywords = QSqlQuery(database); m_headerKeywords.prepare("SELECT DISTINCT key FROM fits_headers ORDER BY key"); m_deleteFile = QSqlQuery(database); m_deleteFile.prepare("DELETE FROM fits_files WHERE id=?"); return true; } qWarning() << error.text(); } else { qWarning() << "Failed to open database" << connectionName; } } else { qWarning() << "Database is invalid"; } return false; } bool Database::mark(const QString &filename) { m_markQuery.bindValue(0, filename); m_markQuery.exec(); return checkError(m_markQuery); } bool Database::unmark(const QString &filename) { m_unmarkQuery.bindValue(0, filename); m_unmarkQuery.exec(); return checkError(m_unmarkQuery); } bool Database::mark(const QStringList &filenames) { m_markQuery.bindValue(0, filenames); m_markQuery.execBatch(); return checkError(m_markQuery); } bool Database::unmark(const QStringList &filenames) { m_unmarkQuery.bindValue(0, filenames); m_unmarkQuery.execBatch(); return checkError(m_unmarkQuery); } bool Database::isMarked(const QString &filename) { m_isMarkedQuery.bindValue(":name", filename); m_isMarkedQuery.exec(); checkError(m_isMarkedQuery); return m_isMarkedQuery.next(); } QStringList Database::getMarkedFiles() { QSqlQuery markedFiles("SELECT * from files"); QStringList files; while(markedFiles.next()) { files << markedFiles.value("file").toString(); } return files; } void Database::clearMarkedFiles() { QSqlQuery query("DELETE FROM files"); } bool Database::checkError(QSqlQuery &query) { QSqlError error = query.lastError(); if(error.type() == QSqlError::NoError) return true; else { qWarning() << error.text(); return false; } } int Database::checkVersion(QSqlDatabase &db) { QSqlQuery query("PRAGMA user_version", db); if(query.next()) return query.value(0).toInt(); return -1; } static QStringList nameFilters = {"*.fit", "*.fits", "*.fz", "*.fts", "*.xisf"}; static int countFiles(const QDir &dir, QStringList &scannedDirs) { if(scannedDirs.contains(dir.canonicalPath()))return 0; scannedDirs.append(dir.canonicalPath()); int count = dir.entryList(nameFilters, QDir::Files).size(); QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for(const QString &d : dirs) count += countFiles(dir.filePath(d), scannedDirs); return count; } void Database::indexDir(const QDir &dir, QProgressDialog *progress) { m_progress = 0; QStringList scannedDirs; int count = countFiles(dir, scannedDirs); progress->setMaximum(count); database.transaction(); scannedDirs.clear(); if(indexDir2(dir, progress, scannedDirs)) { database.commit(); emit databaseChanged(); } else { database.rollback(); } } void Database::reindex(QProgressDialog *progress) { QVariantList deleteids; database.transaction(); QSqlQuery size("SELECT COUNT(*) FROM fits_files", database); size.next(); progress->setMaximum(size.value(0).toInt()); QSqlQuery files("SELECT id,file,mtime FROM fits_files", database); int i = 0; while(files.next()) { QString path = files.value(1).toString(); QFileInfo file(path); if(file.exists() && file.fileTime(QFileDevice::FileModificationTime).toUTC().toString(Qt::ISODate) != files.value(2).toString()) indexFile(file); if(!file.exists()) deleteids.append(files.value(0)); progress->setValue(i++); if(progress->wasCanceled()) { database.rollback(); return; } } QSqlQuery deleteFiles("DELETE FROM fits_files WHERE id = ?", database); deleteFiles.bindValue(0, deleteids); deleteFiles.execBatch(); database.commit(); } QStringList Database::getFitsKeywords() { m_headerKeywords.exec(); QStringList keywords; while(m_headerKeywords.next()) { keywords << m_headerKeywords.value(0).toString(); } return keywords; } QVector Database::getObjects(double minRa, double maxRa, double minDec, double maxDec) { QVector objects; if(!ngc.isOpen())return objects; m_getNgc.bindValue(0, minRa); m_getNgc.bindValue(1, maxRa); m_getNgc.bindValue(2, minDec); m_getNgc.bindValue(3, maxDec); if(m_getNgc.exec()) { while(m_getNgc.next()) { QString name; QString name2; QString m = m_getNgc.value("M").toString(); QString ic = m_getNgc.value("IC").toString(); if(!m.isEmpty()) { name = "M" + m; m.clear(); } else if(!ic.isEmpty()) { name = "IC" + ic; ic.clear(); } else { name = m_getNgc.value("Name").toString(); } if(!ic.isEmpty())name2 += "IC" + ic + " "; name2 += m_getNgc.value("Common names").toString(); objects.append({ name, name2, {m_getNgc.value("RA_deg").toDouble(), m_getNgc.value("DEC_deg").toDouble()}, m_getNgc.value("MajAx").toDouble(), m_getNgc.value("MinAx").toDouble(), m_getNgc.value("PosAng").toDouble(), m_getNgc.value("mag").isNull() ? NAN : m_getNgc.value("mag").toDouble(), {0, 0}, }); } } return objects; } const QSqlDatabase &Database::db() const { return database; } bool Database::indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs) { if(scannedDirs.contains(dir.canonicalPath()))return true; scannedDirs.append(dir.canonicalPath()); QFileInfoList files = dir.entryInfoList(nameFilters, QDir::Files); QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for(const QString &d : dirs) { if(!indexDir2(dir.filePath(d), progress, scannedDirs)) return false; } for(const QFileInfo &file : files) { progress->setValue(m_progress++); if(progress->wasCanceled())return false; if(!indexFile(file))return false; } return true; } bool Database::indexFile(const QFileInfo &file) { ImageInfoData info; QString filePath = file.absoluteFilePath(); QString mtime = file.fileTime(QFileDevice::FileModificationTime).toUTC().toString(Qt::ISODate); m_checkFile.bindValue(0, filePath); m_checkFile.exec(); if(m_checkFile.next()) { if(m_checkFile.value(1).toString() == mtime) return true; else { m_deleteFile.bindValue(0, m_checkFile.value(0).toLongLong()); m_deleteFile.exec(); } } bool ok = false; if(isXISF(file.suffix())) ok = readXISFHeader(filePath, info); else if(isFITS(file.suffix())) ok = readFITSHeader(filePath, info); qlonglong last_id = -1; if(ok) { if(info.wcs) { double minRa, maxRa, minDec, maxDec, crVal1, crVal2; info.wcs->calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2); qDebug() << "bounds" << minRa << maxRa << minDec << maxDec; m_insertFileWcs.bindValue(0, filePath); m_insertFileWcs.bindValue(1, mtime); m_insertFileWcs.bindValue(2, minRa); m_insertFileWcs.bindValue(3, maxRa); m_insertFileWcs.bindValue(4, minDec); m_insertFileWcs.bindValue(5, maxDec); m_insertFileWcs.bindValue(6, crVal1); m_insertFileWcs.bindValue(7, crVal2); if(!m_insertFileWcs.exec()) { qWarning() << "Database error" << m_insertFileWcs.lastError(); return false; } last_id = m_insertFileWcs.lastInsertId().toLongLong(); } else { m_insertFile.bindValue(0, filePath); m_insertFile.bindValue(1, mtime); if(!m_insertFile.exec()) { qWarning() << "Database error" << m_insertFile.lastError(); return false; } last_id = m_insertFile.lastInsertId().toLongLong(); } QVariantList file_id, keys, values, comments; for(const auto &record : info.fitsHeader) { if(record.xisf && record.key.startsWith("PCL:"))continue; file_id << last_id; keys << QString(record.key); values << record.value.toString(); comments << QString(record.comment); } m_insertFitsHeader.bindValue(0, file_id); m_insertFitsHeader.bindValue(1, keys); m_insertFitsHeader.bindValue(2, values); m_insertFitsHeader.bindValue(3, comments); if(!m_insertFitsHeader.execBatch()) { qWarning() << "Database error" << m_insertFitsHeader.lastError(); return false; } } qDebug() << "Indexed" << filePath << last_id; return true; }