diff --git a/resources/misc/db_init_memory.sql b/resources/misc/db_init_memory.sql new file mode 100644 index 000000000..3e889a369 --- /dev/null +++ b/resources/misc/db_init_memory.sql @@ -0,0 +1,65 @@ +DROP TABLE IF EXISTS Information; +-- ! +CREATE TABLE IF NOT EXISTS Information ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); +-- ! +INSERT INTO Information VALUES ('schema_version', '0.0.1'); +-- ! +DROP TABLE IF EXISTS Categories; +-- ! +CREATE TABLE IF NOT EXISTS Categories ( + id INTEGER PRIMARY KEY, + parent_id INTEGER NOT NULL, + title TEXT NOT NULL UNIQUE CHECK (title != ''), + description TEXT, + date_created INTEGER NOT NULL CHECK (date_created != 0), + icon BLOB, + type INTEGER NOT NULL, + + FOREIGN KEY (parent_id) REFERENCES Categories (id) +); +-- ! +DROP TABLE IF EXISTS Feeds; +-- ! +CREATE TABLE IF NOT EXISTS Feeds ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL CHECK (title != ''), + description TEXT, + date_created INTEGER NOT NULL CHECK (date_created != 0), + icon BLOB, + category INTEGER NOT NULL CHECK (category >= -1), + encoding TEXT NOT NULL CHECK (encoding != ''), + url TEXT NOT NULL UNIQUE CHECK (url != ''), + language TEXT, + type INTEGER NOT NULL CHECK (type >= 0) +); +-- ! +DROP TABLE IF EXISTS FeedsData; +-- ! +CREATE TABLE IF NOT EXISTS FeedsData ( + id INTEGER NOT NULL, + key TEXT NOT NULL, + value TEXT, + + PRIMARY KEY (id, key), + FOREIGN KEY (id) REFERENCES Feeds (id) +); +-- ! +DROP TABLE IF EXISTS Messages; +-- ! +CREATE TABLE IF NOT EXISTS Messages ( + id INTEGER PRIMARY KEY, + read INTEGER(1) NOT NULL CHECK (read >= 0 AND read <= 1) DEFAULT (0), + deleted INTEGER(1) NOT NULL CHECK (deleted >= 0 AND deleted <= 1) DEFAULT (0), + important INTEGER(1) NOT NULL CHECK (important >= 0 AND important <= 1) DEFAULT (0), + feed INTEGER NOT NULL, + title TEXT NOT NULL CHECK (title != ''), + url TEXT, + author TEXT, + date_created INTEGER NOT NULL CHECK (date_created != 0), + contents TEXT, + + FOREIGN KEY (feed) REFERENCES Feeds (id) +); \ No newline at end of file diff --git a/src/core/databasefactory.cpp b/src/core/databasefactory.cpp index b02817fa3..ffac61984 100644 --- a/src/core/databasefactory.cpp +++ b/src/core/databasefactory.cpp @@ -13,7 +13,7 @@ QPointer DatabaseFactory::s_instance; DatabaseFactory::DatabaseFactory(QObject *parent) - : QObject(parent), m_initialized(false) { + : QObject(parent), m_fileBasedinitialized(false), m_inMemoryInitialized(false) { assemblyDatabaseFilePath(); } @@ -42,6 +42,96 @@ void DatabaseFactory::assemblyDatabaseFilePath() { } } +QSqlDatabase DatabaseFactory::initializeInMemory() { + QSqlDatabase database = QSqlDatabase::addDatabase(DATABASE_DRIVER); + + database.setDatabaseName(":memory:"); + + if (!database.open()) { + qFatal("Database was NOT opened. Delivered error message: '%s'", + qPrintable(database.lastError().text())); + } + else { + QSqlQuery query_db(database); + + query_db.setForwardOnly(true); + query_db.exec("PRAGMA encoding = \"UTF-8\""); + query_db.exec("PRAGMA synchronous = OFF"); + query_db.exec("PRAGMA journal_mode = MEMORY"); + query_db.exec("PRAGMA page_size = 4096"); + query_db.exec("PRAGMA cache_size = 16384"); + query_db.exec("PRAGMA count_changes = OFF"); + query_db.exec("PRAGMA temp_store = MEMORY"); + + // Sample query which checks for existence of tables. + query_db.exec("SELECT value FROM Information WHERE key = 'schema_version'"); + + if (query_db.lastError().isValid()) { + qWarning("Error occurred. Database is not initialized. Initializing now."); + + QFile file_init(APP_MISC_PATH + QDir::separator() + APP_DB_INIT_MEMORY); + + if (!file_init.open(QIODevice::ReadOnly | QIODevice::Text)) { + // Database initialization file not opened. HUGE problem. + qFatal("Database initialization file '%s' from directory '%s' was not found. Database is uninitialized.", + APP_DB_INIT_FILE, + qPrintable(APP_MISC_PATH)); + } + + QStringList statements = QString(file_init.readAll()).split(APP_DB_INIT_SPLIT, + QString::SkipEmptyParts); + database.transaction(); + + foreach(const QString &statement, statements) { + query_db.exec(statement); + + if (query_db.lastError().isValid()) { + qFatal("Database initialization failed. Initialization script '%s' is not correct.", + APP_DB_INIT_FILE); + } + } + + database.commit(); + qDebug("Database backend should be ready now."); + } + else { + query_db.next(); + + qDebug("In-memory database connection seems to be established."); + qDebug("Database has version '%s'.", qPrintable(query_db.value(0).toString())); + } + + // Loading messages from file-based database. + QSqlDatabase file_database = connection("fdb", false); + + QSqlQuery copy_msgs(database); + + // Attach database. + copy_msgs.exec(QString("ATTACH DATABASE '%1' AS 'storage';").arg(file_database.databaseName())); + + // Copy all stuff. + QStringList tables; tables << "Categories" << "Feeds" << "FeedsData" << + "Messages"; + + foreach (const QString &table, tables) { + copy_msgs.exec(QString("INSERT INTO main.%1 SELECT * FROM storage.%1;").arg(table)); + } + + // Detach database and finish. + copy_msgs.exec("DETACH 'storage'"); + copy_msgs.finish(); + + // DB is attached. + + query_db.finish(); + } + + // Everything is initialized now. + m_inMemoryInitialized = true; + + return database; +} + QString DatabaseFactory::getDatabasePath() { return m_databasePath; } @@ -55,7 +145,8 @@ QString DatabaseFactory::getDatabasePath() { // a pri vypinani se zase :memory: presype do // souborove databaze -QSqlDatabase DatabaseFactory::initialize(const QString &connection_name) { +QSqlDatabase DatabaseFactory::initializeFileBased(const QString &connection_name, + bool in_memory) { // Prepare file paths. QDir db_path(getDatabasePath()); QFile db_file(db_path.absoluteFilePath(APP_DB_FILE)); @@ -72,10 +163,10 @@ QSqlDatabase DatabaseFactory::initialize(const QString &connection_name) { } // Folders are created. Create new QSQLDatabase object. - QSqlDatabase database = QSqlDatabase::addDatabase(DATABASE_DRIVER, - connection_name); + QSqlDatabase database; - // Setup database file path. + database = QSqlDatabase::addDatabase(DATABASE_DRIVER, + connection_name); database.setDatabaseName(db_file.fileName()); if (!database.open()) { @@ -139,14 +230,39 @@ QSqlDatabase DatabaseFactory::initialize(const QString &connection_name) { } // Everything is initialized now. - m_initialized = true; + m_fileBasedinitialized = true; return database; } -QSqlDatabase DatabaseFactory::connection(const QString &connection_name) { - if (!m_initialized) { - return initialize(connection_name); +QSqlDatabase DatabaseFactory::connection(const QString &connection_name, + bool in_memory) { + if (in_memory) { + // We request in-memory database. + if (!m_inMemoryInitialized) { + // It is not initialized yet. + return initializeInMemory(); + } + else { + QSqlDatabase database = QSqlDatabase::database(); + + database.setDatabaseName(":memory:"); + + if (!database.isOpen() && !database.open()) { + qFatal("In-memory database was NOT opened. Delivered error message: '%s'.", + qPrintable(database.lastError().text())); + } + else { + qDebug("In-memory database connection seems to be established."); + } + + return database; + } + } + + if (!m_fileBasedinitialized) { + // File-based database is not yet initialised. + return initializeFileBased(connection_name, in_memory); } else { QSqlDatabase database; @@ -187,3 +303,26 @@ void DatabaseFactory::removeConnection(const QString &connection_name) { qDebug("Removing database connection '%s'.", qPrintable(connection_name)); QSqlDatabase::removeDatabase(connection_name); } + +void DatabaseFactory::saveMemoryDatabase() { + QSqlDatabase database = connection(); + QSqlDatabase file_database = connection("fdb", false); + + QSqlQuery copy_msgs(database); + + // Attach database. + copy_msgs.exec(QString("ATTACH DATABASE '%1' AS 'storage';").arg(file_database.databaseName())); + + // Copy all stuff. + QStringList tables; tables << "Categories" << "Feeds" << "FeedsData" << + "Messages"; + + foreach (const QString &table, tables) { + copy_msgs.exec(QString("DELETE FROM storage.%1;").arg(table)); + copy_msgs.exec(QString("INSERT INTO storage.%1 SELECT * FROM main.%1;").arg(table)); + } + + // Detach database and finish. + copy_msgs.exec("DETACH 'storage'"); + copy_msgs.finish(); +} diff --git a/src/core/databasefactory.h b/src/core/databasefactory.h index 4815bea55..6331532e2 100644 --- a/src/core/databasefactory.h +++ b/src/core/databasefactory.h @@ -17,10 +17,15 @@ class DatabaseFactory : public QObject { QString getDatabasePath(); // NOTE: This always returns OPENED database. - QSqlDatabase connection(const QString &connection_name); + QSqlDatabase connection(const QString &connection_name = QString(), + bool in_memory = true); // Removes connection. - void removeConnection(const QString &connection_name); + void removeConnection(const QString &connection_name = QString()); + + // Performs saving of items from in-memory database + // to file-based database. + void saveMemoryDatabase(); // Singleton getter. static DatabaseFactory *instance(); @@ -34,13 +39,15 @@ class DatabaseFactory : public QObject { // Creates new connection, initializes database and // returns opened connection. - QSqlDatabase initialize(const QString &connection_name); + QSqlDatabase initializeInMemory(); + QSqlDatabase initializeFileBased(const QString &connection_name, bool in_memory); // Path to database file. QString m_databasePath; // Is database file initialized? - bool m_initialized; + bool m_fileBasedinitialized; + bool m_inMemoryInitialized; // Private singleton value. static QPointer s_instance; diff --git a/src/core/defs.h.in b/src/core/defs.h.in index 6dad95f41..a13bf1684 100755 --- a/src/core/defs.h.in +++ b/src/core/defs.h.in @@ -38,6 +38,7 @@ #define INTERNAL_URL_NEWSPAPER "@APP_LOW_NAME@:newspaper" #define APP_DB_INIT_FILE "db_init.sql" +#define APP_DB_INIT_MEMORY "db_init_memory.sql" #define APP_DB_INIT_SPLIT "-- !\n" #define APP_DB_PATH "data/database/local" #define APP_DB_FILE "database.db" diff --git a/src/gui/formmain.cpp b/src/gui/formmain.cpp index cb8d56f64..9f8341c2e 100755 --- a/src/gui/formmain.cpp +++ b/src/gui/formmain.cpp @@ -3,6 +3,7 @@ #include "core/defs.h" #include "core/settings.h" #include "core/systemfactory.h" +#include "core/databasefactory.h" #include "gui/formabout.h" #include "gui/formsettings.h" #include "gui/webbrowser.h" @@ -193,6 +194,9 @@ void FormMain::onAboutToQuit() { qDebug("Cleaning up resources and saving application state."); m_ui->m_tabWidget->feedMessageViewer()->quitDownloader(); + + DatabaseFactory::instance()->saveMemoryDatabase(); + saveSize(); }