337 lines
11 KiB
C++
337 lines
11 KiB
C++
#include "database.h"
|
|
#include <QStandardPaths>
|
|
#include <QDir>
|
|
#include <QSqlError>
|
|
#include <QDebug>
|
|
#include <QDateTime>
|
|
#include "loadrunable.h"
|
|
|
|
Database::Database(QObject *parent) : QObject(parent)
|
|
{
|
|
}
|
|
|
|
bool Database::init(const QLatin1String &connectionName)
|
|
{
|
|
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
|
QDir dir(path);
|
|
|
|
QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
|
|
|
|
if(!dir.mkpath("."))
|
|
return false;
|
|
|
|
if(database.isValid())
|
|
{
|
|
database.setDatabaseName(dir.absoluteFilePath("database2.db"));
|
|
if(database.open())
|
|
{
|
|
QSqlQuery query(database);
|
|
query.exec("PRAGMA foreign_keys = ON");
|
|
int version = checkVersion();
|
|
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)");
|
|
}
|
|
else if(version > 1)
|
|
{
|
|
qDebug() << "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;
|
|
}
|
|
qDebug() << error.text();
|
|
}
|
|
}
|
|
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
|
|
{
|
|
qDebug() << error.text();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int Database::checkVersion()
|
|
{
|
|
QSqlDatabase db = QSqlDatabase::database();
|
|
QSqlQuery query("PRAGMA user_version");
|
|
if(query.next())
|
|
return query.value(0).toInt();
|
|
return -1;
|
|
}
|
|
|
|
static QStringList nameFilters = {"*.fit", "*.fits", "*.fz", "*.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);
|
|
QSqlDatabase database = QSqlDatabase::database();
|
|
database.transaction();
|
|
|
|
scannedDirs.clear();
|
|
if(indexDir2(dir, progress, scannedDirs))
|
|
{
|
|
database.commit();
|
|
emit databaseChanged();
|
|
}
|
|
else
|
|
{
|
|
database.rollback();
|
|
}
|
|
}
|
|
|
|
void Database::reindex(QProgressDialog *progress)
|
|
{
|
|
QVariantList deleteids;
|
|
QSqlDatabase database = QSqlDatabase::database();
|
|
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;
|
|
}
|
|
|
|
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;
|
|
if(filePath.endsWith(".xisf", Qt::CaseInsensitive))
|
|
ok = readXISFHeader(filePath, info);
|
|
else
|
|
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())
|
|
{
|
|
qDebug() << "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())
|
|
{
|
|
qDebug() << "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)
|
|
{
|
|
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())
|
|
{
|
|
qDebug() << "Database error" << m_insertFitsHeader.lastError();
|
|
return false;
|
|
}
|
|
}
|
|
qDebug() << "Indexed" << filePath << last_id;
|
|
return true;
|
|
}
|