Prechádzať zdrojové kódy

Merge pull request #71 from fiffty-50/develop-backupwidget

First draft of backup widget, moved ui specific commands to backup.ui closes #69
Felix Turowsky 3 rokov pred
rodič
commit
beae9b5083

+ 10 - 10
mainwindow.cpp

@@ -73,13 +73,13 @@ MainWindow::MainWindow(QWidget *parent)
 
 
     // check database version (Debug)
-    int db_ver = checkDbVersion();
+    int db_ver = aDB->dbVersion();
     if (db_ver != DATABASE_REVISION) {
-        DEB << "############## WARNING ####################";
+        DEB << "############## WARNING ##############";
         DEB << "Your database is out of date.";
         DEB << "Current Revision:\t" << DATABASE_REVISION;
         DEB << "You have revision:\t" << db_ver;
-        DEB << "############## WARNING ###################";
+        DEB << "############## WARNING ##############";
         QMessageBox message_box(this); //error box
         message_box.setText(tr("Database revision out of date!"));
         message_box.exec();
@@ -126,6 +126,13 @@ void MainWindow::connectWidgets()
                      pilotsWidget,   &PilotsWidget::onPilotsWidget_databaseUpdated);
     QObject::connect(settingsWidget, &SettingsWidget::settingChanged,
                      pilotsWidget,   &PilotsWidget::onPilotsWidget_settingChanged);
+
+    QObject::connect(aDB,             &ADatabase::connectionReset,
+                     logbookWidget,   &LogbookWidget::repopulateModel);
+    QObject::connect(aDB,             &ADatabase::connectionReset,
+                     pilotsWidget,    &PilotsWidget::repopulateModel);
+    QObject::connect(aDB,             &ADatabase::connectionReset,
+                     aircraftWidget,  &AircraftWidget::repopulateModel);
 }
 
 /*
@@ -180,13 +187,6 @@ void MainWindow::on_actionDebug_triggered()
 
 // Debug
 
-int MainWindow::checkDbVersion()
-{
-    QSqlQuery query("SELECT COUNT(*) FROM changelog");
-    query.next();
-    return query.value(0).toInt();
-}
-
 // not used at the moment
 
 /*void MainWindow::on_actionNewAircraft_triggered()

+ 0 - 2
mainwindow.h

@@ -100,8 +100,6 @@ private:
 
     void connectWidgets();
 
-    int checkDbVersion();
-
 protected:
     /*!
      * \brief Shows the debug widget by pressing <ctrl + t>

+ 5 - 0
src/classes/astandardpaths.cpp

@@ -40,6 +40,11 @@ const QDir& AStandardPaths::directory(Directories location)
     return directories[location];
 }
 
+const QString AStandardPaths::asChildOfDir(Directories location, const QString &filename)
+{
+    return directories[location].absoluteFilePath(filename);
+}
+
 const QMap<AStandardPaths::Directories, QDir>& AStandardPaths::allDirectories()
 {
     return directories;

+ 4 - 0
src/classes/astandardpaths.h

@@ -56,6 +56,10 @@ public:
      */
     static const QDir &directory(Directories location);
 
+    /*!
+     * \brief Returns a string of the absolute path to directory location concatenated with filename
+     */
+    static const QString asChildOfDir(Directories location, const QString& filename);
     /*!
      * \brief returns the static map of all standard directories
      * \return static const QMap<Directories, QDir>

+ 148 - 20
src/database/adatabase.cpp

@@ -33,26 +33,35 @@ QString ADatabaseError::text() const
 
 ADatabase* ADatabase::self = nullptr;
 
-/*!
- * \brief Return the names of a given table in the database.
- */
+ADatabase::ADatabase()
+    : databaseFile(QFileInfo(AStandardPaths::directory(AStandardPaths::Database).
+                             absoluteFilePath(QStringLiteral("logbook.db"))
+                             )
+                   )
+{}
+
+int ADatabase::dbVersion() const
+{
+    return databaseVersion;
+}
+
+int ADatabase::checkDbVersion() const
+{
+    QSqlQuery query("SELECT COUNT(*) FROM changelog");
+    query.next();
+    return query.value(0).toInt();
+}
+
 ColumnNames_T ADatabase::getTableColumns(TableName_T table_name) const
 {
     return tableColumns.value(table_name);
 }
 
-/*!
- * \brief Return the names of all tables in the database
- */
 TableNames_T ADatabase::getTableNames() const
 {
     return tableNames;
 }
 
-/*!
- * \brief Updates the member variables tableNames and tableColumns with up-to-date layout information
- * if the database has been altered. This function is normally only required during database setup or maintenance.
- */
 void ADatabase::updateLayout()
 {
     auto db = ADatabase::database();
@@ -73,7 +82,7 @@ void ADatabase::updateLayout()
 ADatabase* ADatabase::instance()
 {
 #ifdef __GNUC__
-    return self ?: self = new ADatabase();
+    return self ?: self = new ADatabase();  // Cheeky business...
 #else
     if(!self)
         self = new ADatabase();
@@ -81,16 +90,8 @@ ADatabase* ADatabase::instance()
 #endif
 }
 
-ADatabase::ADatabase()
-    : databaseFile(QFileInfo(AStandardPaths::directory(AStandardPaths::Database).
-                             absoluteFilePath(QStringLiteral("logbook.db"))))
-{}
 
-/*!
- * \brief ADatabase::sqliteVersion returns database sqlite version.
- * \return sqlite version string
- */
-const QString ADatabase::sqliteVersion()
+const QString ADatabase::sqliteVersion() const
 {
     QSqlQuery query;
     query.prepare(QStringLiteral("SELECT sqlite_version()"));
@@ -114,6 +115,7 @@ bool ADatabase::connect()
     // Enable foreign key restrictions
     QSqlQuery query(QStringLiteral("PRAGMA foreign_keys = ON;"));
     updateLayout();
+    databaseVersion = checkDbVersion();
     return true;
 }
 
@@ -652,3 +654,129 @@ QVector<QVariant> ADatabase::customQuery(QString statement, int return_values)
         return result;
     }
 }
+
+QMap<ADatabaseSummaryKey, QString> ADatabase::databaseSummary(const QString &db_path)
+{
+    // List layout: {"# Flights", "# Aircraft", "# Pilots", "ISODate last flight", "Total Time hh:mm"}
+    const QString connection_name = QStringLiteral("summary_connection");
+    QMap<ADatabaseSummaryKey, QString> return_values;
+    { // scope for a temporary database connection, ensures proper cleanup when removeDatabase() is called.
+        DEB << "Adding temporary connection to database:" << db_path;
+        QSqlDatabase temp_database = QSqlDatabase::addDatabase(SQLITE_DRIVER, connection_name); // Don't use default connection
+        temp_database.setDatabaseName(db_path);
+        if (!temp_database.open())
+            return {};
+
+        QSqlQuery query(temp_database);
+        ADatabaseSummaryKey key;  // Used among the queries for verbosity... and sanity
+
+        const QVector<QPair<ADatabaseSummaryKey, QString>> key_table_pairs = {
+                {ADatabaseSummaryKey::total_flights, QStringLiteral("flights")},
+                {ADatabaseSummaryKey::total_tails, QStringLiteral("tails")},
+                {ADatabaseSummaryKey::total_pilots, QStringLiteral("pilots")}
+    };
+        for (const auto & pair : key_table_pairs) {
+            query.prepare(QLatin1String("SELECT COUNT (*) FROM ") + pair.second);
+            query.exec();
+            key = pair.first;
+            if (query.first()){
+                return_values[key] = query.value(0).toString();
+            }
+            else{
+                return_values[key] = QString();
+            }
+        }
+
+        query.prepare(QStringLiteral("SELECT MAX(doft) FROM flights"));
+        query.exec();
+        key = ADatabaseSummaryKey::max_doft;
+        if (query.first()){
+            return_values[key] = query.value(0).toString();
+        }
+        else {
+            return_values[key] = QString();
+        }
+
+        query.prepare(QStringLiteral("SELECT "
+                                     "printf(\"%02d\",CAST(SUM(tblk) AS INT)/60)"
+                                     "||':'||"
+                                     "printf(\"%02d\",CAST(SUM(tblk) AS INT)%60) FROM flights"));
+        key = ADatabaseSummaryKey::total_time;
+        query.exec();
+        if (query.first()){
+            return_values[key] = query.value(0).toString();
+        }
+        else {
+            return_values[key] = QString();
+        }
+    }
+
+    QSqlDatabase::removeDatabase(connection_name); // cleanly removes temp connection without leaks since query+db are out of scope
+//    DEB << return_values;  // [G]: ADatabaseSummaryKeys cant print
+
+    return return_values;
+}
+
+/*!
+ * \brief ADatabase::createBackup copies the currently used database to an external backup location provided by the user
+ * \param dest_file This is the full path and filename of where the backup will be created, e.g. 'home/Sully/myBackups/backupFromOpl.db'
+ */
+bool ADatabase::createBackup(const QString& dest_file)
+{
+    INFO << "Backing up current database to: " << dest_file;
+    ADatabase::disconnect();
+    QFile db_file(databaseFile.absoluteFilePath());
+    DEB << "File to Overwrite:" << db_file;  // [G]: Check adebug.h got INFO WARN, ... additions and discuss convention of use.
+
+//    if(!db_file.remove()) {
+//        DEB << "Unable to delete file:" << db_file;
+//        return false;
+//    }
+
+    if (!db_file.copy(dest_file)) {
+        WARN << "Unable to backup old database:" << db_file.errorString();
+        return false;
+    }
+
+    INFO << "Backed up old database as:" << dest_file;
+    ADatabase::connect();
+    emit connectionReset();
+    return true;
+}
+
+/*!
+ * \brief ADatabase::restoreBackup restores the database from a given backup file and replaces the currently active database.
+ * \param backup_file This is the full path and filename of the backup, e.g. 'home/Sully/myBackups/backupFromOpl.db'
+ * \return
+ */
+bool ADatabase::restoreBackup(const QString& backup_file)
+{
+    INFO << "Restoring backup from file:" << backup_file;
+
+    QString default_loc = databaseFile.absoluteFilePath();
+
+    ADatabase::disconnect();
+    QFile backup(backup_file);
+    QFile current_db(default_loc);
+
+    if (!current_db.rename(default_loc + QLatin1String(".tmp"))) { // move previously used db out of the way
+        WARN << current_db.errorString() << "Unable to remove current db file";
+        return false;
+    }
+
+    if (!backup.copy(default_loc))
+    {
+        WARN << backup.errorString() << "Could not copy" << backup << "to" << databaseFile;
+        // try to restore previously used db
+        current_db.rename(default_loc);
+        return false;
+    }
+
+    // backup has been restored, clean up the previously moved file
+    current_db.remove();
+    INFO << "Backup successfully restored!";
+    ADatabase::connect();
+    emit connectionReset();
+    return true;
+}
+

+ 54 - 9
src/database/adatabase.h

@@ -68,6 +68,17 @@ enum class ADatabaseTarget
     tails
 };
 
+/*!
+ * \brief Enumerates the QMap keys used when summarising a database
+ */
+enum class ADatabaseSummaryKey {
+    total_flights,
+    total_tails,
+    total_pilots,
+    max_doft,
+    total_time,
+};
+
 // [G]: This is how we should handle custom "events" in the program.
 // In this case a custom error doesnt need to be built from scratch.
 // Find the type of error you want and extend it with a few tweaks.
@@ -88,28 +99,48 @@ public:
  */
 class ADatabase : public QObject {
     Q_OBJECT
-public:
 
 private:
-    ADatabase();
-
     static ADatabase* self;
     TableNames_T tableNames;
     TableColumns_T tableColumns;
+    int databaseVersion;
+
+    ADatabase();
+    int checkDbVersion() const;
 public:
+    ADatabaseError lastError;
+    //const QDir databaseDir;
+    const QFileInfo databaseFile;
+
     // Ensure DB is not copiable or assignable
     ADatabase(const ADatabase&) = delete;
     void operator=(const ADatabase&) = delete;
     static ADatabase* instance();
+
+    int dbVersion() const;
+
+    /*!
+     * \brief Return the names of all tables in the database
+     */
     TableNames_T getTableNames() const;
+
+    /*!
+     * \brief Return the names of a given table in the database.
+     */
     ColumnNames_T getTableColumns(TableName_T table_name) const;
-    void updateLayout();
-    const QString sqliteVersion();
 
-    ADatabaseError lastError;
-    //const QDir databaseDir;
-    const QFileInfo databaseFile;
+    /*!
+     * \brief Updates the member variables tableNames and tableColumns with up-to-date layout information
+     * if the database has been altered. This function is normally only required during database setup or maintenance.
+     */
+    void updateLayout();
 
+    /*!
+     * \brief ADatabase::sqliteVersion returns database sqlite version.
+     * \return sqlite version string
+     */
+    const QString sqliteVersion() const;
 
     /*!
      * \brief Connect to the database and populate database information.
@@ -258,8 +289,17 @@ public:
      */
     ATailEntry resolveForeignTail(RowId_T foreign_key);
 
+    /*!
+     * \brief Return the summary of the DB_PATH as a stringlist
+     * \todo Contemplate whether it should be a more generic function
+     * that may be used for different elements to summarize.
+     * and ADD DOCUMENTATION, theres some specific sql stuff going on.
+     * \return
+     */
+    QMap<ADatabaseSummaryKey, QString> databaseSummary(const QString& db_path);
 
-
+    bool restoreBackup(const QString& backup_file);
+    bool createBackup(const QString& dest_file);
 
 
 signals:
@@ -270,6 +310,11 @@ signals:
      * the user interface so that a user is always presented with up-to-date information.
      */
     void dataBaseUpdated();
+    /*!
+     * \brief connectionReset is emitted whenever the database connection is reset, for
+     * example when creating or restoring a backup.
+     */
+    void connectionReset();
 };
 
 #endif // ADATABASE_H

+ 2 - 0
src/functions/alog.h

@@ -18,6 +18,8 @@
 #ifndef ALOG_H
 #define ALOG_H
 
+#include <QDebug>
+
 /* Use QTextStream to print status messages. These messages
  * are oriented towards end-users and will persist even when
  * compiling a release without enabling qDebug. As such, LOG

+ 16 - 0
src/gui/widgets/aircraftwidget.cpp

@@ -66,6 +66,11 @@ void AircraftWidget::setupModelAndView()
     view->show();
     selection = view->selectionModel();
 
+    connectSignalsAndSlots();
+}
+
+void AircraftWidget::connectSignalsAndSlots()
+{
     QObject::connect(ui->tableView->selectionModel(),   &QItemSelectionModel::selectionChanged,
                      this,                              &AircraftWidget::tableView_selectionChanged);
     QObject::connect(ui->tableView->horizontalHeader(), &QHeaderView::sectionClicked,
@@ -237,3 +242,14 @@ void AircraftWidget::onDeleteUnsuccessful()
         message_box.exec();
     }
 }
+
+void AircraftWidget::repopulateModel()
+{
+    // unset the current model and delete it to avoid leak
+    view->setModel(nullptr);
+    delete model;
+    // create a new model and populate it
+    model = new QSqlTableModel(this);
+    setupModelAndView();
+    connectSignalsAndSlots();
+}

+ 7 - 0
src/gui/widgets/aircraftwidget.h

@@ -76,6 +76,11 @@ public slots:
      */
     void onAircraftWidget_dataBaseUpdated();
 
+    /*!
+     * \brief AircraftWidget::repopulateModel (public slot) - re-populates the model to cater for a change
+     * to the database connection (for example, when a backup is created)
+     */
+    void repopulateModel();
 private:
     Ui::AircraftWidget *ui;
 
@@ -94,6 +99,8 @@ private:
 
     void setupModelAndView();
 
+    void connectSignalsAndSlots();
+
     void onDeleteUnsuccessful();
 
     inline void refreshView(){model->select();}

+ 148 - 61
src/gui/widgets/backupwidget.cpp

@@ -1,9 +1,15 @@
 #include "backupwidget.h"
 #include "ui_backupwidget.h"
 #include "src/classes/astandardpaths.h"
+#include "src/testing/adebug.h"
+#include "src/database/adatabase.h"
+#include "src/functions/adatetime.h"
 
+#include <QListView>
 #include <QStandardItemModel>
 #include <QFileIconProvider>
+#include <QMessageBox>
+#include <QFileDialog>
 
 BackupWidget::BackupWidget(QWidget *parent) :
     QWidget(parent),
@@ -28,7 +34,12 @@ BackupWidget::BackupWidget(QWidget *parent) :
      *
      */
 
-    fillTableWithSampleData();
+
+    model = new QStandardItemModel(this);
+    model->setHorizontalHeaderLabels(QStringList{"Backup File","Total Flights", "Total Tails",
+                                                 "Total Pilots", "Max Doft", "Total Time"});  // [G]: TODO make const but where?
+    view = ui->tableView;
+    refresh();
 }
 
 BackupWidget::~BackupWidget()
@@ -36,90 +47,166 @@ BackupWidget::~BackupWidget()
     delete ui;
 }
 
-
-void BackupWidget::on_createLocalPushButton_clicked()
+void BackupWidget::refresh()
 {
-    // Copy database to backupdir
+    // First column in table, would be created by listing the files in backupdir
+    QDir backup_dir = QDir(AStandardPaths::directory(AStandardPaths::Backup));
+    const QStringList entries = backup_dir.entryList(QStringList{"*.db"}, QDir::Files, QDir::Time);
+    QFileIconProvider provider;
+
+    // Get summary of each db file and populate lists (columns) of data
+    for (const auto &entry : entries) {
+        QMap<ADatabaseSummaryKey, QString> summary = aDB->databaseSummary(backup_dir.absoluteFilePath(entry));
+        model->appendRow({new AFileStandardItem(provider.icon(QFileIconProvider::File), entry, AStandardPaths::Backup),
+                          new QStandardItem(summary[ADatabaseSummaryKey::total_flights]),
+                          new QStandardItem(summary[ADatabaseSummaryKey::total_tails]),
+                          new QStandardItem(summary[ADatabaseSummaryKey::total_pilots]),
+                          new QStandardItem(summary[ADatabaseSummaryKey::max_doft]),
+                          new QStandardItem(summary[ADatabaseSummaryKey::total_time])
+                         });
+    }
+
+    ui->tableView->setModel(model);
+    ui->tableView->resizeColumnsToContents();  // [G]: Bit hacky couldnt do it by default
 }
 
-void BackupWidget::on_restoreLocalPushButton_clicked()
+const QString BackupWidget::absoluteBackupPath()
+{
+    const QString backup_name = QLatin1String("logbook_backup_")
+            + ADateTime::toString(QDateTime::currentDateTime(), Opl::Datetime::Backup)
+            + QLatin1String(".db");
+    return AStandardPaths::asChildOfDir(AStandardPaths::Backup, backup_name);
+}
+const QString BackupWidget::backupName()
 {
-    // Restore the selected entry from the list
+    return  QLatin1String("logbook_backup_")
+            + ADateTime::toString(QDateTime::currentDateTime(), Opl::Datetime::Backup)
+            + QLatin1String(".db");
 }
 
-void BackupWidget::on_deleteSelectedPushButton_clicked()
+void BackupWidget::on_tableView_clicked(const QModelIndex &index)
 {
-    // Remove selected backup from list and delete file.
+    selectedFileInfo = static_cast<AFileStandardItem*>(model->item(index.row(), 0));
+    DEB << "Item at row:" << index.row() << "->" << selectedFileInfo->data(Qt::DisplayRole);
 }
 
-void BackupWidget::on_createExternalPushButton_clicked()
+void BackupWidget::on_createLocalPushButton_clicked()
 {
-    // Open something like a QFileDialog and let the user choose where to save the backup
+    QString filename = absoluteBackupPath();
+    DEB << filename;
+
+    if(!aDB->createBackup(filename)) {
+        WARN << "Could not create local file:" << filename;
+        return;
+    }
+
+    // [G] TODO: propably make a function out of this for future tweaks
+    QFileIconProvider provider;
+    QMap<ADatabaseSummaryKey, QString> summary = aDB->databaseSummary(filename);
+    model->appendRow({new AFileStandardItem(provider.icon(QFileIconProvider::File), QFileInfo(filename)),
+                      new QStandardItem(summary[ADatabaseSummaryKey::total_flights]),
+                      new QStandardItem(summary[ADatabaseSummaryKey::total_tails]),
+                      new QStandardItem(summary[ADatabaseSummaryKey::total_pilots]),
+                      new QStandardItem(summary[ADatabaseSummaryKey::max_doft]),
+                      new QStandardItem(summary[ADatabaseSummaryKey::total_time])
+                     });
+    model->sort(0);
 }
 
-void BackupWidget::on_restoreExternalPushButton_clicked()
+void BackupWidget::on_restoreLocalPushButton_clicked()
 {
-    // Open something like a QFileDialog and let the user choose where to load the backup from
+    if(selectedFileInfo == nullptr) {
+        INFO << "No backup selected";
+        return;
+    }
+
+    QString backup_name = AStandardPaths::asChildOfDir(
+                AStandardPaths::Backup,
+                selectedFileInfo->data(Qt::DisplayRole).toString()
+                );
+
+    if(!aDB->restoreBackup(backup_name)) {
+        WARN << "Couldnt restore" << backup_name;
+    }
 }
 
-void BackupWidget::on_aboutPushButton_clicked()
+void BackupWidget::on_deleteSelectedPushButton_clicked()
 {
-    // Shows a message box explaining a little about local and external backups
+    NOT_IMPLEMENTED("Test external deletion");
+    if(selectedFileInfo == nullptr) {
+        INFO << "No backup was selected";
+        return;
+    }
+
+    const QFileInfo& filename = selectedFileInfo->info();
+    QFile file(filename.absoluteFilePath());
+
+    if(!file.exists()) {
+        WARN << "Selected backup file doesnt exist:" << file;
+        return;
+    }
+
+    DEB << "deleting:" << filename;
+    if(!file.remove()) {
+        WARN << "Unable to remove file:" << file.errorString();
+        return;
+    }
+
+    model->removeRow(selectedFileInfo->row());
+    view->clearSelection();
+    selectedFileInfo = nullptr;
 }
 
+void BackupWidget::on_createExternalPushButton_clicked()
+{
+    QString filename = QFileDialog::getSaveFileName(
+                this,
+                "Choose destination file",
+                // [G]: Is this necessary?
+                //QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).absoluteFilePath("untitled.db"),
+                QDir::homePath() + QDir::separator() + backupName(),
+                "*.db"
+    );
+
+    if(filename.isEmpty()) { // QFileDialog has been cancelled
+        return;
+    }
 
+    if(!filename.endsWith(".db")) {
+        filename.append(".db");
+    }
 
-// ===================================================================================
+    if(!aDB->createBackup(filename)) {
+        DEB << "Unable to backup file:" << filename;
+        return;
+    }
+}
 
-// feel free to delete this as you go along, just here for demonstration purposes
-void BackupWidget::fillTableWithSampleData()
+void BackupWidget::on_restoreExternalPushButton_clicked()
 {
-    // First column in table, would be created by listing the files in backupdir
-    QList<QStandardItem *> filenames{
-    };
-    QStringList filenames_string_list {
-        "logbook_bak_2021_02_12_T_22_03.db",
-        "logbook_bak_2021_01_15_T_18_32.db",
-        "logbook_bak_2020_12_23_T_14_27.db",
-        "logbook_bak_2020_11_21_T_23_43.db",
-        "logbook_bak_2020_10_30_T_17_55.db",
-    };
-    QFileIconProvider provider;
-    for (const auto &string : filenames_string_list) {
-        auto item = new QStandardItem(string);
-        item->setIcon(provider.icon(QFileIconProvider::File));
-        filenames.append(item);
+    QString filename = QFileDialog::getOpenFileName(
+                this,
+                tr("Choose backup file"),
+                QDir::homePath(),
+                "*.db"
+    );
+
+    if(filename.isEmpty()) { // QFileDialog has been cancelled
+        return;
     }
 
-    // Second column, would be created by reading the details from each file and creating the description string
-    QList<QStandardItem *> descriptions{
-    };
-    QStringList descriptions_string_list {
-        "2020-02-12 - 512 Flights, 25 Aircraft, 44 Pilots, 1640:47 Total Time, Last Flight 2020-01-23",
-        "2020-01-15 - 476 Flights, 24 Aircraft, 42 Pilots, 1490:23 Total Time, Last Flight 2019-12-06",
-        "2020-12-23 - 452 Flights, 19 Aircraft, 39 Pilots, 1460:34 Total Time, Last Flight 2019-11-23",
-        "2020-11-21 - 435 Flights, 17 Aircraft, 33 Pilots, 1373:25 Total Time, Last Flight 2019-10-16",
-        "2020-10-30 - 419 Flights, 15 Aircraft, 12 Pilots, 1337:02 Total Time, Last Flight 2019-09-24",
-    };
-    for (const auto &string : descriptions_string_list) {
-        auto item = new QStandardItem(string);
-        descriptions.append(item);
+    // Maybe create a Message Box asking for confirmation here and displaying the summary of backup and active DB
+
+    if(!aDB->restoreBackup(filename)) {
+        DEB << "Unable to backup file:" << filename;
+        return;
     }
+}
 
-    // create and populate the model
-    model = new QStandardItemModel(this);
-    model->insertColumn(0, filenames);
-    model->insertColumn(1, descriptions);
-    model->setHorizontalHeaderLabels(QStringList{"Backup File","Summary"});
-    // prepare and show the view
-    auto view = ui->tableView;
-    view->setModel(model);
-    view->setSelectionBehavior(QAbstractItemView::SelectRows);
-    view->setSelectionMode(QAbstractItemView::SingleSelection);
-    view->setEditTriggers(QAbstractItemView::NoEditTriggers);
-    view->horizontalHeader()->setStretchLastSection(QHeaderView::Stretch);
-    view->verticalHeader()->hide();
-    view->setAlternatingRowColors(true);
-    view->resizeColumnsToContents();
-    view->setColumnWidth(0, ui->tableView->columnWidth(0) + 20);
+void BackupWidget::on_aboutPushButton_clicked() {
+    // [G]: Add message text. Could this be predefined in Opl::Assets?
+    QMessageBox msg_box(QMessageBox::Information, "About backups", "...", QMessageBox::Ok);
+    msg_box.exec();
 }
+
+

+ 49 - 2
src/gui/widgets/backupwidget.h

@@ -1,13 +1,45 @@
 #ifndef BACKUPWIDGET_H
 #define BACKUPWIDGET_H
 
+#include "src/classes/astandardpaths.h"
+
 #include <QWidget>
 #include <QStandardItemModel>
+#include <QFileSystemModel>
+#include <QFileSystemWatcher>
+#include <QTableView>
 
 namespace Ui {
 class BackupWidget;
 }
 
+/*!
+ * \brief Simple QStandardItem subclass to encapsulate necessary file info.
+ * Using only a QStandardItem would mean that the full path should be inputted
+ * as data and of course displayed by default. However this way we create
+ * the absolute path in the fileInfo attribute for further use while
+ * displaying only the base name.
+ */
+class AFileStandardItem : public QStandardItem {
+private:
+    QFileInfo fileInfo;
+public:
+    AFileStandardItem(const QIcon& icon, const QString& filename, const AStandardPaths::Directories dir)
+        : QStandardItem(icon, filename),
+          fileInfo(QFileInfo(AStandardPaths::asChildOfDir(dir, filename)))
+    {}
+    AFileStandardItem(const QIcon& icon, const QFileInfo file_info)
+        : QStandardItem(icon, file_info.baseName()),
+          fileInfo(QFileInfo(file_info))
+    {}
+
+
+    const QFileInfo& info() const
+    {
+        return fileInfo;
+    }
+};
+
 class BackupWidget : public QWidget
 {
     Q_OBJECT
@@ -17,6 +49,8 @@ public:
     ~BackupWidget();
 
 private slots:
+    void on_tableView_clicked(const QModelIndex &index);
+
     void on_createLocalPushButton_clicked();
 
     void on_restoreLocalPushButton_clicked();
@@ -32,9 +66,22 @@ private slots:
 private:
     Ui::BackupWidget *ui;
 
-    QStandardItemModel* model;
+    QStandardItemModel *model;
+    QTableView *view;
+    AFileStandardItem *selectedFileInfo = nullptr;  // Only the first column is necessary for
+                                                    // any operation and it is encapsulated in the
+                                                    // AFileStandardItem class
+    void refresh();
+
+    /*!
+     * \brief Generates a filename for creating a backup
+     */
+    const QString backupName();
 
-    void fillTableWithSampleData();
+    /*!
+     * \brief Generates the absolute path for a new backup file.
+     */
+    const QString absoluteBackupPath();
 };
 
 #endif // BACKUPWIDGET_H

+ 82 - 44
src/gui/widgets/backupwidget.ui

@@ -14,10 +14,45 @@
    <string>Form</string>
   </property>
   <layout class="QGridLayout" name="gridLayout">
-   <item row="3" column="0">
-    <widget class="QTableView" name="tableView"/>
+   <item row="4" column="0">
+    <widget class="QTableView" name="tableView">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="sizeAdjustPolicy">
+      <enum>QAbstractScrollArea::AdjustToContents</enum>
+     </property>
+     <property name="editTriggers">
+      <set>QAbstractItemView::NoEditTriggers</set>
+     </property>
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::SingleSelection</enum>
+     </property>
+     <property name="selectionBehavior">
+      <enum>QAbstractItemView::SelectRows</enum>
+     </property>
+     <attribute name="horizontalHeaderStretchLastSection">
+      <bool>true</bool>
+     </attribute>
+     <attribute name="verticalHeaderVisible">
+      <bool>false</bool>
+     </attribute>
+    </widget>
    </item>
-   <item row="5" column="0">
+   <item row="2" column="0">
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="6" column="0">
     <widget class="Line" name="line_2">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
@@ -56,31 +91,16 @@
      </item>
     </layout>
    </item>
-   <item row="9" column="0">
-    <widget class="QPushButton" name="createExternalPushButton">
-     <property name="text">
-      <string>Create External Backup</string>
-     </property>
-    </widget>
-   </item>
-   <item row="7" column="0">
-    <widget class="QPushButton" name="restoreLocalPushButton">
-     <property name="text">
-      <string>Restore Local Backup</string>
-     </property>
-    </widget>
-   </item>
-   <item row="6" column="0">
-    <widget class="QPushButton" name="createLocalPushButton">
-     <property name="text">
-      <string>Create Local Backup</string>
-     </property>
-    </widget>
-   </item>
    <item row="1" column="0">
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <widget class="QLabel" name="titleSpacerLabel1">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
        <property name="text">
         <string/>
        </property>
@@ -105,26 +125,44 @@
      </item>
     </layout>
    </item>
-   <item row="2" column="0">
-    <widget class="Line" name="line">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-    </widget>
-   </item>
-   <item row="10" column="0">
-    <widget class="QPushButton" name="restoreExternalPushButton">
-     <property name="text">
-      <string>Restore External Backup</string>
-     </property>
-    </widget>
-   </item>
-   <item row="8" column="0">
-    <widget class="QPushButton" name="deleteSelectedPushButton">
-     <property name="text">
-      <string>Delete Selected Backup</string>
-     </property>
-    </widget>
+   <item row="3" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout_11">
+     <item>
+      <widget class="QPushButton" name="restoreExternalPushButton">
+       <property name="text">
+        <string>Restore External Backup</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="createExternalPushButton">
+       <property name="text">
+        <string>Create External Backup</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="deleteSelectedPushButton">
+       <property name="text">
+        <string>Delete Selected Backup</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="restoreLocalPushButton">
+       <property name="text">
+        <string>Restore Local Backup</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="createLocalPushButton">
+       <property name="text">
+        <string>Create Local Backup</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
    </item>
   </layout>
  </widget>

+ 11 - 0
src/gui/widgets/logbookwidget.cpp

@@ -285,3 +285,14 @@ void LogbookWidget::on_flightSearchLlineEdit_textChanged(const QString &arg1)
         return;
     }
 }
+
+void LogbookWidget::repopulateModel()
+{
+    // unset the current model and delete it to avoid leak
+    view->setModel(nullptr);
+    delete displayModel;
+    // create a new model and populate it
+    displayModel = new QSqlTableModel(this);
+    setupModelAndView(ASettings::read(ASettings::Main::LogbookView).toInt());
+    connectSignalsAndSlots();
+}

+ 5 - 0
src/gui/widgets/logbookwidget.h

@@ -55,6 +55,11 @@ private slots:
 public slots:
     void refresh();
     void onLogbookWidget_viewSelectionChanged(SettingsWidget::SettingSignal signal);
+    /*!
+     * \brief LogbookWidget::repopulateModel (public slot) - re-populates the model to cater for a change
+     * to the database connection (for example, when a backup is created)
+     */
+    void repopulateModel();
 
 private:
     Ui::LogbookWidget *ui;

+ 19 - 3
src/gui/widgets/pilotswidget.cpp

@@ -62,10 +62,15 @@ void PilotsWidget::setupModelAndView()
     view->show();
     selectionModel = view->selectionModel();
 
+    connectSignalsAndSlots();
+}
+
+void PilotsWidget::connectSignalsAndSlots()
+{
     QObject::connect(ui->tableView->selectionModel(),   &QItemSelectionModel::selectionChanged,
-                     this,                                    &PilotsWidget::tableView_selectionChanged);
+                     this,                              &PilotsWidget::tableView_selectionChanged);
     QObject::connect(ui->tableView->horizontalHeader(), &QHeaderView::sectionClicked,
-                     this,                                    &PilotsWidget::tableView_headerClicked);
+                     this,                              &PilotsWidget::tableView_headerClicked);
 }
 
 void PilotsWidget::onPilotsWidget_settingChanged(SettingsWidget::SettingSignal signal)
@@ -185,7 +190,7 @@ void PilotsWidget::on_deletePilotButton_clicked()
  */
 void PilotsWidget::onDeleteUnsuccessful()
 {
-    QList<int> foreign_key_constraints = aDB->getForeignKeyConstraints(selectedPilots.first(),
+    const QList<int> foreign_key_constraints = aDB->getForeignKeyConstraints(selectedPilots.first(),
                                                                        ADatabaseTarget::pilots);
     QList<AFlightEntry> constrained_flights;
     for (const auto &row_id : foreign_key_constraints) {
@@ -220,3 +225,14 @@ void PilotsWidget::onDeleteUnsuccessful()
         message_box.exec();
     }
 }
+
+void PilotsWidget::repopulateModel()
+{
+    // unset the current model and delete it to avoid leak
+    view->setModel(nullptr);
+    delete model;
+    // create a new model and populate it
+    model = new QSqlTableModel(this);
+    setupModelAndView();
+    connectSignalsAndSlots();
+}

+ 7 - 0
src/gui/widgets/pilotswidget.h

@@ -77,6 +77,11 @@ public slots:
      */
     void onPilotsWidget_databaseUpdated();
 
+    /*!
+     * \brief PilotsWidget::repopulateModel (public slot) - re-populates the model to cater for a change
+     * to the database connection (for example, when a backup is created)
+     */
+    void repopulateModel();
 private:
     Ui::PilotsWidget *ui;
 
@@ -92,6 +97,8 @@ private:
 
     void setupModelAndView();
 
+    void connectSignalsAndSlots();
+
     inline void refreshView(){model->select();}
 };
 

+ 17 - 0
src/testing/adebug.h

@@ -11,10 +11,27 @@
     #define FUNC_IDENT __func__
 #endif
 
+// CAUTION: qDebug() doesnt print for non-DEBUG builds
 #define DEB qDebug() << FUNC_IDENT << "\n\t"
 #define DEB_SRC DEB
 #define DEB_RAW qDebug() << '\t'
 
+
+// [G]: TODO study cross platform terminal coloring
+// might be silly but coloring specific words does increase
+// ease of reading debug output. We dont have to go overboard
+// start with the header.
+// DRAFT:
+// info -> white
+// warning -> yellow
+// critical -> red
+// there is also fatal which even kills the program and could be purple.
+#define INFO qInfo() << "info:"
+#define WARN qWarning() << "warning:"
+#define CRIT qCritical() << "critical:"
+//#define NOT_IMPLEMENTED qCritical() << FUNC_IDENT << "\n\t" << "~~ NOT IMPLEMENTED ~~";
+#define NOT_IMPLEMENTED(msg) qCritical() << FUNC_IDENT << "\n\tNOT IMPLEMENTED:" << msg
+
 /*!
  * Representation macro for custom classes.
  *