/*
*openPilotLog - A FOSS Pilot Logbook Application
*Copyright (C) 2020-2021 Felix Turowsky
*
*This program is free software: you can redistribute it and/or modify
*it under the terms of the GNU General Public License as published by
*the Free Software Foundation, either version 3 of the License, or
*(at your option) any later version.
*
*This program is distributed in the hope that it will be useful,
*but WITHOUT ANY WARRANTY; without even the implied warranty of
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
*GNU General Public License for more details.
*
*You should have received a copy of the GNU General Public License
*along with this program. If not, see .
*/
#include "adatabasesetup.h"
#include "src/database/adatabase.h"
#include "src/functions/alog.h"
#include "src/functions/areadcsv.h"
#include "src/classes/astandardpaths.h"
#include "src/classes/adownload.h"
#include "src/opl.h"
#include "src/functions/adatetime.h"
#include "src/functions/alog.h"
const auto createTablePilots = QLatin1String("CREATE TABLE pilots ( "
" pilot_id INTEGER NOT NULL, "
" lastname TEXT NOT NULL, "
" firstname TEXT, "
" alias TEXT, "
" company TEXT, "
" employeeid TEXT, "
" phone TEXT, "
" email TEXT, "
" PRIMARY KEY(pilot_id AUTOINCREMENT)"
")");
const auto createTableTails = QLatin1String("CREATE TABLE tails ("
" tail_id INTEGER NOT NULL,"
" registration TEXT NOT NULL,"
" company TEXT,"
" make TEXT,"
" model TEXT,"
" variant TEXT,"
" multipilot INTEGER,"
" multiengine INTEGER,"
" engineType INTEGER,"
" weightClass INTEGER,"
" PRIMARY KEY(tail_id AUTOINCREMENT)"
")");
const auto createTableFlights = QLatin1String("CREATE TABLE flights ("
" flight_id INTEGER NOT NULL, "
" doft NUMERIC NOT NULL, "
" dept TEXT NOT NULL, "
" dest TEXT NOT NULL, "
" tofb INTEGER NOT NULL, "
" tonb INTEGER NOT NULL, "
" pic INTEGER NOT NULL, "
" acft INTEGER NOT NULL, "
" tblk INTEGER NOT NULL, "
" tSPSE INTEGER, "
" tSPME INTEGER, "
" tMP INTEGER, "
" tNIGHT INTEGER, "
" tIFR INTEGER, "
" tPIC INTEGER, "
" tPICUS INTEGER, "
" tSIC INTEGER, "
" tDUAL INTEGER, "
" tFI INTEGER, "
" tSIM INTEGER, "
" pilotFlying INTEGER, "
" toDay INTEGER, "
" toNight INTEGER, "
" ldgDay INTEGER, "
" ldgNight INTEGER, "
" autoland INTEGER, "
" secondPilot INTEGER, "
" thirdPilot INTEGER, "
" approachType TEXT, "
" flightNumber TEXT, "
" remarks TEXT, "
" FOREIGN KEY(pic) REFERENCES pilots(pilot_id) ON DELETE RESTRICT, "
" FOREIGN KEY(acft) REFERENCES tails(tail_id) ON DELETE RESTRICT, "
" PRIMARY KEY(flight_id AUTOINCREMENT) "
")");
const auto createTableAirports = QLatin1String("CREATE TABLE airports ( "
" airport_id INTEGER NOT NULL, "
" icao TEXT NOT NULL, "
" iata TEXT, "
" name TEXT, "
" lat REAL, "
" long REAL, "
" country TEXT, "
" alt INTEGER, "
" utcoffset INTEGER, "
" tzolson TEXT, "
" PRIMARY KEY(airport_id AUTOINCREMENT) "
")");
const auto createTableAircraft = QLatin1String("CREATE TABLE aircraft ("
" aircraft_id INTEGER NOT NULL,"
" make TEXT,"
" model TEXT,"
" variant TEXT,"
" name TEXT,"
" iata TEXT,"
" icao TEXT,"
" multipilot INTEGER,"
" multiengine INTEGER,"
" engineType INTEGER,"
" weightClass INTEGER,"
" PRIMARY KEY(aircraft_id AUTOINCREMENT)"
")");
const auto createTableChangelog = QLatin1String("CREATE TABLE changelog ( "
" revision INTEGER NOT NULL, "
" comment TEXT, "
" date NUMERIC, "
" PRIMARY KEY(revision) "
")");
const auto createTableCurrencies = QLatin1String("CREATE TABLE currencies ( "
" currency_id INTEGER PRIMARY KEY AUTOINCREMENT, "
" description TEXT, "
" expiryDate NUMERIC "
")"
);
// Statements for creation of views in the database
const auto createViewDefault = QLatin1String("CREATE VIEW viewDefault AS "
" SELECT flight_id, doft as 'Date', "
" dept AS 'Dept', "
" printf('%02d',(tofb/60))||':'||printf('%02d',(tofb%60)) AS 'Time', "
" dest AS 'Dest', printf('%02d',(tonb/60))||':'||printf('%02d',(tonb%60)) AS 'Time ', "
" printf('%02d',(tblk/60))||':'||printf('%02d',(tblk%60)) AS 'Total', "
" CASE "
" WHEN pilot_id = 1 THEN alias "
" ELSE lastname||', '||substr(firstname, 1, 1)||'.' "
" END "
" AS 'Name PIC', "
" CASE "
" WHEN variant IS NOT NULL THEN make||' '||model||'-'||variant "
" ELSE make||' '||model "
" END "
" AS 'Type', "
" registration AS 'Registration', "
" FlightNumber AS 'Flight #', "
" remarks AS 'Remarks'"
" FROM flights "
" INNER JOIN pilots on flights.pic = pilots.pilot_id "
" INNER JOIN tails on flights.acft = tails.tail_id "
" ORDER BY date DESC ");
const auto createViewEASA = QLatin1String("CREATE VIEW viewEASA AS "
" SELECT "
" flight_id, doft as 'Date', "
" dept AS 'Dept', "
" printf('%02d',(tofb/60))||':'||printf('%02d',(tofb%60)) AS 'Time', "
" dest AS 'Dest', printf('%02d',(tonb/60))||':'||printf('%02d',(tonb%60)) AS 'Time ', "
" CASE "
" WHEN variant IS NOT NULL THEN make||' '||model||'-'||variant "
" ELSE make||' '||model "
" END "
" AS 'Type', "
" registration AS 'Registration', "
" (SELECT printf('%02d',(tSPSE/60))||':'||printf('%02d',(tSPSE%60)) WHERE tSPSE IS NOT NULL) AS 'SP SE', "
" (SELECT printf('%02d',(tSPME/60))||':'||printf('%02d',(tSPME%60)) WHERE tSPME IS NOT NULL) AS 'SP ME', "
" (SELECT printf('%02d',(tMP/60))||':'||printf('%02d',(tMP%60)) WHERE tMP IS NOT NULL) AS 'MP', "
" printf('%02d',(tblk/60))||':'||printf('%02d',(tblk%60)) AS 'Total', "
" CASE "
" WHEN pilot_id = 1 THEN alias "
" ELSE lastname||', '||substr(firstname, 1, 1)||'.' "
" END "
" AS 'Name PIC', "
" ldgDay AS 'L/D', "
" ldgNight AS 'L/N', "
" (SELECT printf('%02d',(tNight/60))||':'||printf('%02d',(tNight%60)) WHERE tNight IS NOT NULL) AS 'Night', "
" (SELECT printf('%02d',(tIFR/60))||':'||printf('%02d',(tIFR%60)) WHERE tIFR IS NOT NULL) AS 'IFR', "
" (SELECT printf('%02d',(tPIC/60))||':'||printf('%02d',(tPIC%60)) WHERE tPIC IS NOT NULL) AS 'PIC', "
" (SELECT printf('%02d',(tSIC/60))||':'||printf('%02d',(tSIC%60)) WHERE tSIC IS NOT NULL) AS 'SIC', "
" (SELECT printf('%02d',(tDual/60))||':'||printf('%02d',(tDual%60)) WHERE tDual IS NOT NULL) AS 'Dual', "
" (SELECT printf('%02d',(tFI/60))||':'||printf('%02d',(tFI%60)) WHERE tFI IS NOT NULL) AS 'FI', "
" remarks AS 'Remarks' "
" FROM flights "
" INNER JOIN pilots on flights.pic = pilots.pilot_id "
" INNER JOIN tails on flights.acft = tails.tail_id "
" ORDER BY date DESC");
const auto createViewTails = QLatin1String("CREATE VIEW viewTails AS "
" SELECT "
" tail_id AS 'ID', "
" registration AS 'Registration', "
" make||' '||model AS 'Type', "
" company AS 'Company' "
" FROM tails WHERE model IS NOT NULL AND variant IS NULL "
" UNION "
" SELECT "
" tail_id AS 'ID', "
" registration AS 'Registration', "
" make||' '||model||'-'||variant AS 'Type', "
" company AS 'Company' "
" FROM tails WHERE variant IS NOT NULL");
const auto createViewPilots = QLatin1String("CREATE VIEW viewPilots AS "
" SELECT "
" pilot_id AS 'ID', "
" lastname AS 'Last Name', "
" firstname AS 'First Name', "
" company AS 'Company' "
" FROM pilots");
const auto createViewQCompleter = QLatin1String("CREATE VIEW viewQCompleter AS "
" SELECT airport_id, icao, iata, tail_id, registration, pilot_id, "
" lastname||', '||firstname AS 'pilot_name', alias "
" FROM airports "
" LEFT JOIN tails ON airports.airport_id = tails.tail_id "
" LEFT JOIN pilots ON airports.airport_id = pilots.pilot_id");
const auto createViewTotals = QLatin1String("CREATE VIEW viewTotals AS "
" SELECT "
" printf(\"%02d\",CAST(SUM(tblk) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tblk) AS INT)%60) AS \"TOTAL\", "
" printf(\"%02d\",CAST(SUM(tSPSE) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tSPSE) AS INT)%60) AS \"SP SE\", "
" printf(\"%02d\",CAST(SUM(tSPME) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tSPME) AS INT)%60) AS \"SP ME\", "
" printf(\"%02d\",CAST(SUM(tNIGHT) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tNIGHT) AS INT)%60) AS \"NIGHT\", "
" printf(\"%02d\",CAST(SUM(tIFR) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tIFR) AS INT)%60) AS \"IFR\", "
" printf(\"%02d\",CAST(SUM(tPIC) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tPIC) AS INT)%60) AS \"PIC\", "
" printf(\"%02d\",CAST(SUM(tPICUS) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tPICUS) AS INT)%60) AS \"PICUS\", "
" printf(\"%02d\",CAST(SUM(tSIC) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tSIC) AS INT)%60) AS \"SIC\", "
" printf(\"%02d\",CAST(SUM(tDual) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tDual) AS INT)%60) AS \"DUAL\", "
" printf(\"%02d\",CAST(SUM(tFI) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tFI) AS INT)%60) AS \"INSTRUCTOR\", "
" printf(\"%02d\",CAST(SUM(tSIM) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tSIM) AS INT)%60) AS \"SIMULATOR\", "
" printf(\"%02d\",CAST(SUM(tMP) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tMP) AS INT)%60) AS \"MultPilot\", "
" CAST(SUM(toDay) AS INT) AS \"TO Day\", CAST(SUM(toNight) AS INT) AS \"TO Night\", "
" CAST(SUM(ldgDay) AS INT) AS \"LDG Day\", CAST(SUM(ldgNight) AS INT) AS \"LDG Night\" "
" FROM flights");
const QStringList tables = {
createTablePilots,
createTableTails,
createTableFlights,
createTableAircraft,
createTableAirports,
createTableCurrencies,
createTableChangelog
};
const QStringList views = {
createViewDefault,
createViewEASA,
createViewTails,
createViewPilots,
createViewTotals,
createViewQCompleter,
};
const QStringList userTables = {
QStringLiteral("flights"),
QStringLiteral("pilots"),
QStringLiteral("tails")
};
const QStringList templateTables= {
QStringLiteral("aircraft"),
QStringLiteral("airports"),
QStringLiteral("currencies"),
QStringLiteral("changelog")
};
QT_DEPRECATED
bool ADataBaseSetup::createDatabase()
{
DEB << "Creating tables...";
if (!createSchemata(tables)) {
DEB << "Creating tables has failed.";
return false;
}
DEB << "Creating views...";
if (!createSchemata(views)) {
DEB << "Creating views failed.";
return false;
}
aDB->updateLayout();
LOG << "Database successfully created!\n";
return true;
}
QT_DEPRECATED
bool ADataBaseSetup::downloadTemplates()
{
QDir template_dir(AStandardPaths::directory(AStandardPaths::Templates));
DEB << template_dir;
for (const auto& table : templateTables) {
QEventLoop loop;
ADownload* dl = new ADownload;
QObject::connect(dl, &ADownload::done, &loop, &QEventLoop::quit );
dl->setTarget(QUrl(TEMPLATE_URL + table + QLatin1String(".csv")));
dl->setFileName(template_dir.absoluteFilePath(table + QLatin1String(".csv")));
dl->download();
dl->deleteLater();
loop.exec(); // event loop waits for download done signal before allowing loop to continue
QFileInfo downloaded_file(template_dir.filePath(table + QLatin1String(".csv")));
if (downloaded_file.size() == 0)
return false; // ssl/network error
}
return true;
}
QT_DEPRECATED
bool ADataBaseSetup::backupOldData()
{
LOG << "Backing up old database...";
QFileInfo database_file(AStandardPaths::directory(AStandardPaths::Database).
absoluteFilePath(QStringLiteral("logbook.db")));
DEB << "File Info:" << database_file;
if(!database_file.exists()) {
DEB << "No Database to backup, returning.";
return true;
}
auto date_string = ADateTime::toString(QDateTime::currentDateTime(),
Opl::Datetime::Backup);
auto backup_dir = AStandardPaths::directory(AStandardPaths::Backup);
QString backup_name = database_file.baseName() + QLatin1String("_bak_")
+ date_string + QLatin1String(".db");
QFile file(database_file.absoluteFilePath());
DEB << "File:" << file.fileName();
if (!file.rename(backup_dir.absoluteFilePath(backup_name))) {
LOG << "Unable to backup old database.\n";
return false;
}
LOG << "Backed up old database as: " << backup_name << "\n";
return true;
}
QT_DEPRECATED
bool ADataBaseSetup::importDefaultData(bool use_ressource_data)
{
QSqlQuery query;
// reset template tables
for (const auto& table_name : templateTables) {
//clear tables
query.prepare("DELETE FROM " + table_name);
if (!query.exec()) {
DEB << "Error: " << query.lastError().text();
return false;
}
// Prepare data
QVector data_to_commit;
QString error_message("Error importing data ");
if (use_ressource_data) {
data_to_commit = aReadCsv(QStringLiteral(":templates/database/templates/")
+ table_name + QLatin1String(".csv"));
error_message.append(" (ressource) ");
} else {
data_to_commit = aReadCsv(AStandardPaths::directory(
AStandardPaths::Templates).absoluteFilePath(
table_name + QLatin1String(".csv")));
error_message.append(" (downloaded) ");
}
//fill with data from csv
if (!commitData(data_to_commit, table_name)) {
LOG << error_message;
return false;
}
}
return true;
};
QT_DEPRECATED
/*!
* \brief DbSetup::resetToDefault Empties all user-generated content in the database.
* \return true on success
*/
bool ADataBaseSetup::resetToDefault()
{
QSqlQuery query;
// clear user tables
for (const auto& table : userTables) {
query.prepare("DELETE FROM " + table);
if (!query.exec()) {
DEB << "Error: " << query.lastError().text();
}
}
return true;
}
QT_DEPRECATED
/*!
* \brief dbSetup::debug prints Database Layout
*/
void ADataBaseSetup::debug()
{
DEB << "Database tables and views: ";
QSqlQuery query;
const QVector types = { "table", "view" };
for (const auto& var : types){
query.prepare("SELECT name FROM sqlite_master WHERE type=" + var);
query.exec();
while (query.next()) {
QString table = query.value(0).toString();
QSqlQuery entries("SELECT COUNT(*) FROM " + table);
entries.next();
DEB << "Element " << query.value(0).toString() << "with"
<< entries.value(0).toString() << "rows";
}
}
}
QT_DEPRECATED
/*!
* \brief dbSetup::createTables Create the required tables for the database
* \return true on success
*/
bool ADataBaseSetup::createSchemata(const QStringList &statements)
{
QSqlQuery query;
QStringList errors;
for (const auto& statement : statements) {
query.prepare(statement);
query.exec();
if(!query.isActive()) {
errors << statement.section(QLatin1Char(' '),2,2) + " ERROR - " + query.lastError().text();
DEB << "Query: " << query.lastQuery();
continue;
}
DEB << "Schema added: " << statement.section(QLatin1Char(' '), 2, 2);
}
if (!errors.isEmpty()) {
DEB << "The following errors have ocurred: ";
for (const auto& error : qAsConst(errors)) {
DEB << error;
}
return false;
}
LOG << "All database tables created successfully\n";
return true;
}
QT_DEPRECATED
/*!
* \brief DbSetup::commitData inserts the data parsed from a csv file into the
* database. The first line of the csv file has to contain the column names
* of the corresponding table in the database.
* \param fromCSV input as parsed from CSV::read()
* \param tableName as in the database
* \return
*/
bool ADataBaseSetup::commitData(QVector from_csv, const QString &table_name)
{
aDB->updateLayout();
if (!aDB->getTableNames().contains(table_name)){
DEB << table_name << "is not a table in the database. Aborting.";
DEB << "Please check input data.";
return false;
}
// create insert statement
QString statement = "INSERT INTO " + table_name + " (";
QString placeholder = ") VALUES (";
for (auto& csvColumn : from_csv) {
if(aDB->getTableColumns(table_name).contains(csvColumn.first())) {
statement += csvColumn.first() + ',';
csvColumn.removeFirst();
placeholder.append("?,");
} else {
DEB << csvColumn.first() << "is not a column of " << table_name << "Aborting.";
DEB << "Please check input data.";
return false;
}
}
statement.chop(1);
placeholder.chop(1);
placeholder.append(')');
statement.append(placeholder);
/*
* Using exclusive transaction and the loop below is MUCH faster than
* passing the QStringLists to QSqlQuery::addBindValue and using QSqlQuery::execBatch()
*/
QSqlQuery query;
query.exec("BEGIN EXCLUSIVE TRANSACTION;");
for (int i = 0; i < from_csv.first().length(); i++){
query.prepare(statement);
for(int j = 0; j < from_csv.length(); j++) {
from_csv[j][i] == QString("") ? // make sure NULL is committed for empty values
query.addBindValue(QVariant(QString()))
: query.addBindValue(from_csv[j][i]);
//query.addBindValue(fromCSV[j][i]);
}
query.exec();
}
query.exec("COMMIT;"); //commit transaction
if (query.lastError().text().length() > 3) {
DEB << "Error:" << query.lastError().text();
return false;
} else {
qDebug() << table_name << "Database successfully updated!";
return true;
}
}
QT_DEPRECATED
bool ADataBaseSetup::commitDataJson(const QJsonArray &json_arr, const QString &table_name)
{
aDB->updateLayout();
QSqlQuery q;
// create insert statement
QString statement = QLatin1String("INSERT INTO ") + table_name + QLatin1String(" (");
QString placeholder = QStringLiteral(") VALUES (");
for (const auto &column_name : aDB->getTableColumns(table_name)) {
statement += column_name + ',';
placeholder.append(QLatin1Char(':') + column_name + QLatin1Char(','));
}
statement.chop(1);
placeholder.chop(1);
placeholder.append(')');
statement.append(placeholder);
q.prepare(QStringLiteral("BEGIN EXCLUSIVE TRANSACTION"));
q.exec();
//DEB << statement;
for (const auto &entry : json_arr) {
q.prepare(statement);
auto object = entry.toObject();
const auto keys = object.keys();
for (const auto &key : keys){
object.value(key).isNull() ? q.bindValue(key, QVariant(QVariant::String)) :
q.bindValue(QLatin1Char(':') + key, object.value(key).toVariant());
}
q.exec();
}
q.prepare(QStringLiteral("COMMIT"));
if (q.exec())
return true;
else
return false;
}