From: Enar Väikene Date: Fri, 23 Sep 2011 10:12:06 +0000 (+0300) Subject: Added more tutorial sections. X-Git-Url: https://vaikene.ee/gitweb/overview.html?a=commitdiff_plain;h=0a383af776fe4c1c0ac1059dd3732f32cb6e1c7a;p=evaf Added more tutorial sections. --- diff --git a/www/pswgen06.html b/www/pswgen06.html new file mode 100644 index 0000000..aefb6ec --- /dev/null +++ b/www/pswgen06.html @@ -0,0 +1,562 @@ + + + + + + eVaf Tutorial - 06 - Storage Module + + + + + + + + + +

eVaf Tutorial

+ +

06 - Storage Module

+ +

In this section we start implementing the Storage module. The storage module stores non-sensitive data required to re-generate + passwords. We use a simple SQLITE database in this module, but nothing prevents us replacing it with a more complex storage + module in the future.

+ +

iStorage interface

+ +

Once again, we start by defining the interface for the module and create the file istorage.h in the + src/apps/PswGen/Storage directory:

+ +
/**
+ * @file PswGen/Storage/istorage.h
+ */
+
+#ifndef __PSWGEN_STORAGE_ISTORAGE_H
+#  define __PSWGEN_STORAGE_ISTORAGE_H
+#endif // istorage.h
+ +

Include Qt header files for QObject and QString as they are almost always needed. In additon, we are going to + include headers for QSharedData and QExplicitlySharedDataPointer. We need a container for the data that + we store and making it shared data object with the benefit of automatic reference counting and memory management makes sense.

+ +
#include <QObject>
+#include <QString>
+#include <QSharedData>
+#include <QExplicitlySharedDataPointer>
+ +

Use the eVaf::PswGen namespace:

+ +
namespace eVaf {
+namespace PswGen {
+
+} // namespace eVaf::PswGen
+} // namespace eVaf
+ +

The container class for stored data has the following attributes:

+ + +

The container class belongs to the Storage module and we use the eVaf::PswGen::Storage namespace for it:

+ +
namespace Storage {
+
+class Data : public QSharedData
+{
+private:
+
+    bool mModified;
+    QString mName;
+    int mLength;
+    uint mFlags;
+
+};
+
+} // eVaf::PswGen::Storage
+ +

The extra attribute - mModified - is used to detect unsaved modifications in the data object.

+ +

Add constructors for the shared data class. Creating a data object without name makes no sense and we include the + name attribute in all our constructors:

+ +
public:
+
+    Data(QString const & name)
+        : QSharedData()
+        , mModified(false)
+        , mName(name)
+        , mLength(0)
+        , mFlags(0)
+    {}
+
+    Data(QString const & name, int l, uint f = 0)
+        : QSharedData()
+        , mModified(false)
+        , mName(name)
+        , mLength(l)
+        , mFlags(f)
+    {}
+ +

Fianlly, we add getter and setter methods:

+ +
+    QString const & name() const { return mName; }
+
+    int length() const { return mLength; }
+    void setLength(int value)
+    {
+        if (mLength != value) {
+            mLength = value;
+            mModified = true;
+        }
+    }
+
+    uint flags() const { return mFlags; }
+    void setFlags(uint value)
+    {
+        if (mFlags != value) {
+            mFlags = value;
+            mModified = true;
+        }
+    }
+
+    bool modified() const { return mModified; }
+
+    void reset() { mModified = false; }
+ +

The reset() method resets the modified attribute.

+ +

Continue with the iStorage interface: + +

struct iStorage
+{
+
+};
+ +

According to the specification, we need functions to store and query data:

+ +
+    virtual bool save(QString const & name, QExplicitlySharedDataPointer<Storage::Data> data) = 0;
+
+    virtual QExplicitlySharedDataPointer<Storage::Data> query(QString const & name) const = 0;
+ +

The specification also mentions partial matches where entering "fa" would offer "facebook.com" if it exists. This could be done by + using the QCompleter class that provides auto completion in Qt widgets like QLineEdit and QComboBox. The + QCompleter class needs a QAbstractItemModel for the auto completion word list and we can provide it + from our Storage module: + +

+    virtual QAbstractItemModel * autoCompletionModel() = 0;
+ +

QAbstractItemModel is not known at this moment and we need to add a forward class declaration to the beginning of the + istorage.h file:

+ +
+    class QAbstractItemModel;
+ +

At the the end of the istorage.h file, we declare the iStorage interface:

+ +
Q_DECLARE_INTERFACE(eVaf::PswGen::iStorage, "eVaf.PswGen.iStorage/1.0")
+ +

This is the complete istorage.h file:

+ +
/**
+ * @file PswGen/Storage/istorage.h
+ */
+
+#ifndef __PSWGEN_STORAGE_ISTORAGE_H
+#  define __PSWGEN_STORAGE_ISTORAGE_H
+
+#include <QObject>
+#include <QString>
+#include <QSharedData>
+#include <QExplicitlySharedDataPointer>
+
+class QAbstractItemModel;
+
+namespace eVaf {
+namespace PswGen {
+
+namespace Storage {
+
+/// Data stored for every password.
+class Data : public QSharedData
+{
+public:
+
+    Data(QString const & name)
+        : QSharedData()
+        , mModified(false)
+        , mName(name)
+        , mLength(0)
+        , mFlags(0)
+    {}
+
+    Data(QString const & name, int l, uint f = 0)
+        : QSharedData()
+        , mModified(false)
+        , mName(name)
+        , mLength(l)
+        , mFlags(f)
+    {}
+
+    /// Name of the password
+    QString const & name() const { return mName; }
+
+    /// Length of the generated password
+    int length() const { return mLength; }
+    void setLength(int value)
+    {
+        if (mLength != value) {
+            mLength = value;
+            mModified = true;
+        }
+    }
+
+    /// Optional flags for the password generator
+    uint flags() const { return mFlags; }
+    void setFlags(uint value)
+    {
+        if (mFlags != value) {
+            mFlags = value;
+            mModified = true;
+        }
+    }
+
+    /// Flag indicating that some fields are modified
+    bool modified() const { return mModified; }
+
+    /// Resets the modified flag
+    void reset() { mModified = false; }
+
+private:
+
+    bool mModified;
+    QString mName;
+    int mLength;
+    uint mFlags;
+
+};
+
+} // eVaf::PswGen::Storage
+
+/// Password storage interface.
+struct iStorage
+{
+
+    /// Saves the data record
+    virtual bool save(QString const & name, QExplicitlySharedDataPointer<Storage::Data> data) = 0;
+
+    /// Returns a data record by the name
+    virtual QExplicitlySharedDataPointer<Storage::Data> query(QString const & name) const = 0;
+
+    /// Returns an item model with the names of all the stored passwords
+    virtual QAbstractItemModel * autoCompletionModel() = 0;
+
+};
+
+} // namespace eVaf::PswGen
+} // namespace eVaf
+
+Q_DECLARE_INTERFACE(eVaf::PswGen::iStorage, "eVaf.PswGen.iStorage/1.0")
+
+#endif // istorage.h
+ +

We also create the iStorage file so that we can use #include <Storage/iStorage> in our other modules: + +

#include "istorage.h"
+ +

Storage module

+ +

The Storage module is simple enough to be derived directly from the Plugins::iPlugin interface and needs only one + header/source file. In general, I personally prefer short source files and follow the one-class-per-file rule. Only when classes + are very simple and closely related I put the into the same source file.

+ +

Create the module.h header file in the src/apps/PswGen/Storage directory. Include istorage.h and + Plugins/iPlugin header files. We use the eVaf::PswGen::Storage namespace for public classes and + eVaf::PswGen::Storage::Internal namespace for private classes.

+ +
#ifndef __PSWGEN_STORAGE_MODULE_H
+#  define __PSWGEN_STORAGE_MODULE_H
+
+#include "istorage.h"
+#include <Plugins/iPlugin>
+
+namespace eVaf {
+namespace PswGen {
+
+/// Module that stores options for strong passwords
+namespace Storage {
+
+/// Internal implementation of the Storage module
+namespace Internal {
+
+} // namespace eVaf::PswGen::Storage::Internal
+} // namespace eVaf::PswGen::Storage
+} // namespace eVaf::PswGen
+} // namespace eVaf
+#endif // module.h
+ +

We use the same name Module for the class that implements the module. This class is public and goes to the + eVaf::PswGen::Storage namespace:

+ +
class Module : public Plugins::iPlugin
+{
+    Q_OBJECT
+
+public:
+
+    Module();
+
+    virtual ~Module();
+
+};
+ +

Implement pure virtual methods in the iPlugin interface:

+ +
+    virtual bool init(QString const & args);
+
+    virtual void done();
+
+    virtual bool isReady() const { return mReady; }
+ +

This module needs more complex initialization that the Generator module and we use the mReady flag to + indicate that the module is fully initialized:

+ +
+private:
+
+    bool mReady;
+ +

The StorageImpl class is the internal implementation of the iStorage interface:

+ +
+    Internal::StorageImpl * mStorage;
+ +

We could declare the Internal::StorageImpl class before the Module class, but as I like starting with more + generic (public) classes and then go into the details, we add a forward declaration to the beginning of the file:

+ +
+namespace Internal {
+    class StorageImpl;
+} // namespace eVaf::PswGen::Storage::Internal
+ +

The declaration of the StorageImpl class goes into the eVaf::PswGen::Storage::Internal namespace and the class + shall be derived both from QObject and iStorage. However, since we also need to implement the + QAbstractItemModel data model for the iStorage::autoCompletionModel() method, we derive it from the + QAbstractListModel class instead. We use the QAbstractListModel class because it meets our requirements + to return a list of auto completion words.

+ +
+namespace Internal {
+
+class StorageImpl : public QAbstractListModel, public iStorage
+{
+    Q_OBJECT
+    Q_INTERFACES(eVaf::PswGen::iStorage)
+
+public:
+
+    StorageImpl();
+
+    virtual ~StorageImpl();
+
+};
+
+} // namespace eVaf::PswGen::Storage::Internal
+ +

First, we implement pure virtual methods found in the iStorage interface:

+ +
+    /*
+        iStorage interface
+    */
+
+    virtual bool save(QString const & name, QExplicitlySharedDataPointer<Storage::Data> data);
+
+    virtual QExplicitlySharedDataPointer<Storage::Data> query(QString const & name) const;
+
+    virtual QAbstractItemModel * autoCompletionModel() { return this; }
+ +

Since the QAbstractItemModel data model is implemented by the same class, we can simply return this in the + autoCompletionModel() model.

+ +

The QAbstractListModel class also has pure virtual methods that need to be implemented:

+ +
+    /*
+        QAbstractListModel methods
+    */
+
+    virtual int rowCount(QModelIndex const & parent) const { return mData.count(); }
+
+    virtual QVariant data(QModelIndex const & index, int role = Qt::DisplayRole) const;
+ +

mData here is a container with all the stored data objects, which we add to the private section. We use + a QMap<QString, QExplicitlySharedDataPointer<Storage::Data> > for this purpose as it allows fast lookups + by the name string.

+ +
+private:
+
+    QMap<QString, QExplicitlySharedDataPointer<Storage::Data> > mData;
+ +

Permanent data storage is done using a SQLITE database. We add the database object and a methods to load data from the database. We + also need a method to create database tables if they do not exist yet:

+ +
+    QSqlDatabase mDb;
+
+    bool createTables();
+
+    bool loadData();
+ +

The database object needs names for the connection and for the database, which we can add as constants to the class:

+ +
+    static char const * const DbConnectionName;
+
+    static char const * const DbName;
+ +

The database needs to be opened and closed, and the list of data objects populated with data from the database. For this purpose + we add init() and done() methods to the public section of the class. The init() method can return + false if the initialization fails.

+ +
+    bool init();
+
+    void done();
+ +

This is the final module.h file:

+ +
/**
+ * @file PswGen/Storage/module.h
+ */
+
+#ifndef __PSWGEN_STORAGE_MODULE_H
+#  define __PSWGEN_STORAGE_MODULE_H
+
+#include "istorage.h"
+
+#include <Plugins/iPlugin>
+
+#include <QObject>
+#include <QString>
+#include <QAbstractListModel>
+#include <QMap>
+#include <QtSql/QSqlDatabase>
+
+
+namespace eVaf {
+namespace PswGen {
+
+/// Module that stores options for strong passwords
+namespace Storage {
+
+/// Internal implementation of the Storage module
+namespace Internal {
+    class StorageImpl;
+} // namespace eVaf::PswGen::Storage::Internal
+
+/// Module implementing the iStorage interface.
+class Module : public Plugins::iPlugin
+{
+    Q_OBJECT
+
+public:
+
+    Module();
+
+    virtual ~Module();
+
+    virtual bool init(QString const & args);
+
+    virtual void done();
+
+    virtual bool isReady() const { return mReady; }
+
+
+private: // Members
+
+    /// Flag indicating that the module is ready
+    bool mReady;
+
+    /// iStorage interface instance
+    Internal::StorageImpl * mStorage;
+
+};
+
+namespace Internal {
+
+/// iStorage interface implementation.
+class StorageImpl : public QAbstractListModel, public iStorage
+{
+    Q_OBJECT
+    Q_INTERFACES(eVaf::PswGen::iStorage)
+
+public:
+
+    StorageImpl();
+
+    virtual ~StorageImpl();
+
+    bool init();
+
+    void done();
+
+    /*
+        iStorage interface
+    */
+
+    virtual bool save(QString const & name, QExplicitlySharedDataPointer<Storage::Data> data);
+
+    virtual QExplicitlySharedDataPointer<Storage::Data> query(QString const & name) const;
+
+    virtual QAbstractItemModel * autoCompletionModel() { return this; }
+
+    /*
+        QAbstractListModel methods
+    */
+
+    virtual int rowCount(QModelIndex const & parent) const { return mData.count(); }
+
+    virtual QVariant data(QModelIndex const & index, int role = Qt::DisplayRole) const;
+
+private: // Members
+
+    /// Name of the database connection
+    static char const * const DbConnectionName;
+
+    /// Name of the database file without path
+    static char const * const DbName;
+
+    /// Database connection
+    QSqlDatabase mDb;
+
+    /// List of name/data pairs
+    QMap<QString, QExplicitlySharedDataPointer<Storage::Data> > mData;
+
+
+private: // Methods
+
+    /// Creates database tables if necessary
+    bool createTables();
+
+    /// Loads data from the database
+    bool loadData();
+
+};
+
+} // namespace eVaf::PswGen::Storage::Internal
+} // namespace eVaf::PswGen::Storage
+} // namespace eVaf::PswGen
+} // namespace eVaf
+
+#endif // module.h
+ +

Continue implementing the Storage Module.

+ + + diff --git a/www/pswgen07.html b/www/pswgen07.html new file mode 100644 index 0000000..d35d484 --- /dev/null +++ b/www/pswgen07.html @@ -0,0 +1,563 @@ + + + + + + eVaf Tutorial - 07 - Storage Module + + + + + + + + + +

eVaf Tutorial

+ +

07 - Storage Module

+ +

Create the module.cpp file in the src/apps/PswGen/Storage directory: + +

/**
+ * @file PswGen/Storage/module.cpp
+ */
+
+#include "module.h"
+#include <QtCore>
+ +

Copy version information files from the Generator module:

+ +
evaf/src/apps/PswGen/Storage $ cp ../Generator/version.{h,rc} .
+ +

Modify the version.h file:

+ +
/**
+ * @file PswGen/Storage/version.h
+ */
+
+#ifndef __PSWGEN_STORAGE_VERSION_H
+#define   __PSWGEN_STORAGE_VERSION_H
+
+#include <version_rc.h>
+
+/**
+ * Module/library version number in the form major,minor,release,build
+ */
+#define VER_FILE_VERSION                0,1,1,1
+
+/**
+ * Module/library version number in the string format (shall end with \0)
+ */
+#define VER_FILE_VERSION_STR            "0.1.1.1\0"
+
+/**
+ * Module/library name (shall end with \0)
+ */
+#define VER_MODULE_NAME_STR             "PswStorage\0"
+
+/**
+ * Module type (see version_rc.h for all the types)
+ */
+#define VER_MODULE_TYPE                 MT_GENERIC
+
+/**
+ * Module type in the string format (see version_rc for all the types)
+ */
+#define VER_MODULE_TYPE_STR             MT_GENERIC
+
+/**
+ * Original file name for windows (shall end with \0)
+ */
+#define VER_ORIGINAL_FILE_NAME_STR      "PswStorage.dll\0"
+
+/**
+ * Description of the module/library (shall end with \0)
+ */
+#define VER_FILE_DESCRIPTION_STR         "Module that stores data for generating strong passwords.\0"
+
+#endif // version.h
+ +

Include the version.h header file in module.cpp and export version information:

+ +
+#include "version.h"
+
+VER_EXPORT_VERSION_INFO()
+ +

Make it a proper Qt plugin by using the Q_EXPORT_PLUGIN2() macro:

+ +
Q_EXPORT_PLUGIN2(VER_MODULE_NAME_STR, eVaf::PswGen::Storage::Module)
+ +

The Module class creates the internal StorageImpl object in the constructor, but initializes it in the + init() method. This way we can return errors from the module if initialization fails. Similarly, we finalize the + internal StorageImpl object in the done() method and delete in the destructor.

+ +

The module is ready when the internal StorageImpl object is initalized and we set the mReady flag to + true when the initialization is done and back to false in the done() method.

+ +

The rest of the code sets the name of the object and outputs info messages.

+ +
Module::Module()
+    : Plugins::iPlugin()
+    , mReady(false)
+{
+    setObjectName(QString("%1.%2").arg(VER_MODULE_NAME_STR).arg(__FUNCTION__));
+
+    mStorage = new Internal::StorageImpl;
+
+    EVAF_INFO("%s created", qPrintable(objectName()));
+}
+
+Module::~Module()
+{
+    delete mStorage;
+
+    EVAF_INFO("%s destroyed", qPrintable(objectName()));
+}
+
+bool Module::init(QString const & args)
+{
+    Q_UNUSED(args);
+
+    if (!mStorage->init())
+        return false;
+
+    mReady = true;
+
+    EVAF_INFO("%s initialized", qPrintable(objectName()));
+
+    return true;
+}
+
+void Module::done()
+{
+    mReady = false;
+
+    mStorage->done();
+
+    EVAF_INFO("%s finalized", qPrintable(objectName()));
+}
+ +

The StorageImpl class does very little in the constructor and in the destructor:

+ +
StorageImpl::StorageImpl()
+    : QAbstractListModel()
+{
+    setObjectName(QString("%1.iGenerator").arg(VER_MODULE_NAME_STR));
+
+    EVAF_INFO("%s created", qPrintable(objectName()));
+}
+
+StorageImpl::~StorageImpl()
+{
+    EVAF_INFO("%s destroyed", qPrintable(objectName()));
+}
+ +

Initialization of the StorageImpl class happens in the init() method, where we open the database connection, + create tables if necessary and load data from the database. We also register the iStorage interface in the global + registry of interfaces.

+ +

We use the Common::iApp interface to find the data root directory where the SQLITE database file is going to be + located.

+ +
bool StorageImpl::init()
+{
+    // Open the database
+    if (!QSqlDatabase::contains(DbConnectionName)) {
+        // No database connection yet
+        mDb = QSqlDatabase::addDatabase("QSQLITE", DbConnectionName);
+        mDb.setDatabaseName(Common::iApp::instance()->dataRootDir() + DbName);
+        if (!mDb.open()) {
+            QSqlError err = mDb.lastError();
+            EVAF_ERROR("Failed to open database : %s", qPrintable(err.text()));
+            return false;
+        }
+    }
+    else {
+        // Database connection already exists
+        mDb = QSqlDatabase::database(DbConnectionName);
+    }
+
+    // Create tables if necessary
+    if (!createTables())
+        return false;
+
+    // Load data
+    if (!loadData())
+        return false;
+
+    /// Register our interface
+    Common::iRegistry::instance()->registerInterface("iStorage", this);
+
+    EVAF_INFO("%s initialized", qPrintable(objectName()));
+
+    return true;
+}
+ +

The finalization of the StorageImpl class happens in the done() method, where we only need to clear the list + of shared data objects:

+ +
void StorageImpl::done()
+{
+    mData.clear();
+    EVAF_INFO("%s finalized", qPrintable(objectName()));
+}
+ +

The StorageImpl::save() method verifies that the shared data object is valid and the name not empty. Then it either + adds a new data record to the database or updates an existing one.

+ +
bool StorageImpl::save(QString const & name, QExplicitlySharedDataPointer<Storage::Data> data)
+{
+    EVAF_TEST_X(data, "Data cannot be null");
+    EVAF_TEST_X(!name.isEmpty(), "Name cannot be empty");
+
+    // Is it an update or a new data record?
+    if (mData.constFind(name) != mData.constEnd()) {
+        // This is an update
+        if (data->modified()) {
+            QSqlQuery q(mDb);
+            if (!q.exec(QString("UPDATE data SET length = \'%1\', flags = \'%2\' WHERE name = \'%3\';")
+                            .arg(data->length()).arg(data->flags()).arg(name))) {
+                QSqlError err = mDb.lastError();
+                EVAF_ERROR("Failed to update \'%s\' : %s", qPrintable(name), qPrintable(err.text()));
+                return false;
+            }
+        }
+    }
+    else {
+        // Store to the database
+        QSqlQuery q(mDb);
+        if (!q.exec(QString("INSERT INTO data (name, length, flags) VALUES (\'%1\', %2, %3);")
+                            .arg(name).arg(data->length())
+                            .arg(int(data->flags())))) {
+            QSqlError err = mDb.lastError();
+            EVAF_ERROR("Failed to insert \'%s\' : %s", qPrintable(name), qPrintable(err.text()));
+            return false;
+        }
+
+        // Store also into the local hash
+        mData.insert(name, data);
+
+        // Reset the model
+        reset();
+    }
+
+    data->reset();
+
+    return true;
+}
+ +

Calling the reset() method resets the QAbstractItemModel data model and assures that the associated widget + updates the QCompleter with a fresh list of auto completion words. Resetting the data model in this case is fine as + long as the data model is simple and the list short. The proper way would be finding out the actual insertion point and using + beginInsertRows() and endInsertRows() methods.

+ +

Querying data is much simpler thanks to the internal QMap container:

+ +
QExplicitlySharedDataPointer<Storage::Data> StorageImpl::query(QString const & name) const
+{
+    QMap<QString, QExplicitlySharedDataPointer<Storage::Data> >::const_iterator it = mData.constFind(name);
+    if (it != mData.constEnd())
+        return it.value();
+    else
+        return QExplicitlySharedDataPointer<Storage::Data>();
+}
+ +

We return an empty QExplicitlySharedDataPointer object if no data objects with the given name exists.

+ +

The data() method returns names of data objects for the QCompleter auto completion list of words:

+ +
QVariant StorageImpl::data(QModelIndex const & index, int role) const
+{
+    if (!index.isValid() || index.row() < 0 || index.row() >= mData.size() || index.column() != 0)
+        return QVariant();
+
+    if (role == Qt::EditRole || role == Qt::DisplayRole)
+        return mData.keys().at(index.row());
+
+    return QVariant();
+}
+ +

The createTabled() method creates the data table if it does not exist. This function could be improved to + alter the data table if it exists, but is from an older version. Right now we assume that if the table exists, it + is good for our application:

+ +
bool StorageImpl::createTables()
+{
+    QSqlQuery q(mDb);
+    if (!q.exec("SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'data\';")) {
+        QSqlError err = mDb.lastError();
+        EVAF_ERROR("Failed to query database : %s", qPrintable(err.text()));
+        return false;
+    }
+
+    if (q.isActive() && q.isSelect() && q.first())
+        return true; // We already have a table called 'data'
+
+    // Create the 'data' table
+    if (!q.exec("CREATE TABLE data (name text primary key not null, length integer, flags integer);")) {
+        QSqlError err = mDb.lastError();
+        EVAF_ERROR("Failed to create table \'data\' : %s", qPrintable(err.text()));
+        return false;
+    }
+
+    return true;
+}
+ +

Finally, the loadData() method loads all the data records from the database to the memory:

+ +
bool StorageImpl::loadData()
+{
+    QSqlQuery q(mDb);
+    if (!q.exec("SELECT name, length, flags FROM data;")) {
+        QSqlError err = mDb.lastError();
+        EVAF_ERROR("Failed to query database : %s", qPrintable(err.text()));
+        return false;
+    }
+
+    while (q.next()) {
+        QString name = q.value(0).toString();
+        QExplicitlySharedDataPointer<Storage::Data> data(new Storage::Data(name, q.value(1).toInt(), uint(q.value(2).toInt())));
+        mData.insert(name, data);
+    }
+
+    reset();
+
+    return true;
+}
+ +

Resetting the QAbstractItemModel data model here is ok, becase before loading any data the model is supposed to be + empty.

+ +

Here is the complete module.cpp file: + +

/**
+ * @file PswGen/Storage/module.cpp
+ */
+
+#include "module.h"
+#include "version.h"
+
+#include <Common/Globals>
+#include <Common/iLogger>
+#include <Common/iRegistry>
+#include <Common/iApp>
+
+#include <QtCore>
+#include <QtSql/QtSql>
+
+VER_EXPORT_VERSION_INFO()
+Q_EXPORT_PLUGIN2(VER_MODULE_NAME_STR, eVaf::PswGen::Storage::Module)
+
+using namespace eVaf;
+using namespace eVaf::PswGen;
+using namespace eVaf::PswGen::Storage;
+
+Module::Module()
+    : Plugins::iPlugin()
+    , mReady(false)
+{
+    setObjectName(QString("%1.%2").arg(VER_MODULE_NAME_STR).arg(__FUNCTION__));
+
+    mStorage = new Internal::StorageImpl;
+
+    EVAF_INFO("%s created", qPrintable(objectName()));
+}
+
+Module::~Module()
+{
+    delete mStorage;
+
+    EVAF_INFO("%s destroyed", qPrintable(objectName()));
+}
+
+bool Module::init(QString const & args)
+{
+    Q_UNUSED(args);
+
+    if (!mStorage->init())
+        return false;
+
+    mReady = true;
+
+    EVAF_INFO("%s initialized", qPrintable(objectName()));
+
+    return true;
+}
+
+void Module::done()
+{
+    mReady = false;
+
+    mStorage->done();
+
+    EVAF_INFO("%s finalized", qPrintable(objectName()));
+}
+
+using namespace eVaf::PswGen::Storage::Internal;
+
+char const * const StorageImpl::DbConnectionName = "PswGenDB";
+
+char const * const StorageImpl::DbName = "PswGen.sqlite";
+
+StorageImpl::StorageImpl()
+    : QAbstractListModel()
+{
+    setObjectName(QString("%1.iGenerator").arg(VER_MODULE_NAME_STR));
+
+    EVAF_INFO("%s created", qPrintable(objectName()));
+}
+
+StorageImpl::~StorageImpl()
+{
+    EVAF_INFO("%s destroyed", qPrintable(objectName()));
+}
+
+bool StorageImpl::init()
+{
+    // Open the database
+    if (!QSqlDatabase::contains(DbConnectionName)) {
+        // No database connection yet
+        mDb = QSqlDatabase::addDatabase("QSQLITE", DbConnectionName);
+        mDb.setDatabaseName(Common::iApp::instance()->dataRootDir() + DbName);
+        if (!mDb.open()) {
+            QSqlError err = mDb.lastError();
+            EVAF_ERROR("Failed to open database : %s", qPrintable(err.text()));
+            return false;
+        }
+    }
+    else {
+        // Database connection already exists
+        mDb = QSqlDatabase::database(DbConnectionName);
+    }
+
+    // Create tables if necessary
+    if (!createTables())
+        return false;
+
+    // Load data
+    if (!loadData())
+        return false;
+
+    /// Register our interface
+    Common::iRegistry::instance()->registerInterface("iStorage", this);
+
+    EVAF_INFO("%s initialized", qPrintable(objectName()));
+
+    return true;
+}
+
+void StorageImpl::done()
+{
+    mData.clear();
+    EVAF_INFO("%s finalized", qPrintable(objectName()));
+}
+
+bool StorageImpl::save(QString const & name, QExplicitlySharedDataPointer<Storage::Data> data)
+{
+    EVAF_TEST_X(data, "Data cannot be null");
+    EVAF_TEST_X(!name.isEmpty(), "Name cannot be empty");
+
+    // Is it an update or a new data record?
+    if (mData.constFind(name) != mData.constEnd()) {
+        // This is an update
+        if (data->modified()) {
+            QSqlQuery q(mDb);
+            if (!q.exec(QString("UPDATE data SET length = \'%1\', flags = \'%2\' WHERE name = \'%3\';")
+                            .arg(data->length()).arg(data->flags()).arg(name))) {
+                QSqlError err = mDb.lastError();
+                EVAF_ERROR("Failed to update \'%s\' : %s", qPrintable(name), qPrintable(err.text()));
+                return false;
+            }
+        }
+    }
+    else {
+        // Store to the database
+        QSqlQuery q(mDb);
+        if (!q.exec(QString("INSERT INTO data (name, length, flags) VALUES (\'%1\', %2, %3);")
+                            .arg(name).arg(data->length())
+                            .arg(int(data->flags())))) {
+            QSqlError err = mDb.lastError();
+            EVAF_ERROR("Failed to insert \'%s\' : %s", qPrintable(name), qPrintable(err.text()));
+            return false;
+        }
+
+        // Store also into the local hash
+        mData.insert(name, data);
+
+        // Reset the model
+        reset();
+    }
+
+    data->reset();
+
+    return true;
+}
+
+QExplicitlySharedDataPointer<Storage::Data> StorageImpl::query(QString const & name) const
+{
+    QMap<QString, QExplicitlySharedDataPointer<Storage::Data> >::const_iterator it = mData.constFind(name);
+    if (it != mData.constEnd())
+        return it.value();
+    else
+        return QExplicitlySharedDataPointer<Storage::Data>();
+}
+
+QVariant StorageImpl::data(QModelIndex const & index, int role) const
+{
+    if (!index.isValid() || index.row() < 0 || index.row() >= mData.size() || index.column() != 0)
+        return QVariant();
+
+    if (role == Qt::EditRole || role == Qt::DisplayRole)
+        return mData.keys().at(index.row());
+
+    return QVariant();
+}
+
+bool StorageImpl::createTables()
+{
+    QSqlQuery q(mDb);
+    if (!q.exec("SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'data\';")) {
+        QSqlError err = mDb.lastError();
+        EVAF_ERROR("Failed to query database : %s", qPrintable(err.text()));
+        return false;
+    }
+
+    if (q.isActive() && q.isSelect() && q.first())
+        return true; // We already have a table called 'data'
+
+    // Create the 'data' table
+    if (!q.exec("CREATE TABLE data (name text primary key not null, length integer, flags integer);")) {
+        QSqlError err = mDb.lastError();
+        EVAF_ERROR("Failed to create table \'data\' : %s", qPrintable(err.text()));
+        return false;
+    }
+
+    return true;
+}
+
+bool StorageImpl::loadData()
+{
+    QSqlQuery q(mDb);
+    if (!q.exec("SELECT name, length, flags FROM data;")) {
+        QSqlError err = mDb.lastError();
+        EVAF_ERROR("Failed to query database : %s", qPrintable(err.text()));
+        return false;
+    }
+
+    while (q.next()) {
+        QString name = q.value(0).toString();
+        QExplicitlySharedDataPointer<Storage::Data> data(new Storage::Data(name, q.value(1).toInt(), uint(q.value(2).toInt())));
+        mData.insert(name, data);
+    }
+
+    reset();
+
+    return true;
+}
+ +

Next -- Building Storage Module.

+ + + diff --git a/www/pswgen08.html b/www/pswgen08.html new file mode 100644 index 0000000..761927a --- /dev/null +++ b/www/pswgen08.html @@ -0,0 +1,99 @@ + + + + + + eVaf Tutorial - 08 - Building Storage Module + + + + + + + + + +

eVaf Tutorial

+ +

08 - Buiding Storage Module

+ +

CMakeLists.txt

+ +

Copy an existing CMakeLists.txt file from the Generator module:

+ +
evaf/src/apps/PswGen/Storage $ cp ../CMakeLists.txt .
+ +

We only need to modify the TARGET variable and set QT_USE_QTSQL to TRUE:

+ +
# Name of the target
+set(TARGET PswStorage)
+
+# Qt modules
+set(QT_USE_QTSQL TRUE)
+ +

Here is the final CMakeLists.txt file:

+ +
# src/apps/PswGen/Storage/CMakeLists.txt
+
+# Name of the target
+set(TARGET PswStorage)
+
+# Qt modules
+set(QT_USE_QTSQL TRUE)
+set(QT_DONT_USE_QTGUI TRUE)
+include(${QT_USE_FILE})
+
+# Include directories
+include_directories(${eVaf_INCLUDE})
+
+# Required eVaf libraries
+set(eVaf_LIBRARIES CommonLib PluginsLib)
+
+# Source files
+set(SRCS
+    module.cpp
+)
+
+# Header files for the Qt meta-object compiler
+set(MOC_HDRS
+    module.h
+)
+
+# Version info resource file for Windows builds
+if(WIN32)
+    set(SRCS ${SRCS} version.rc)
+endif(WIN32)
+
+# Run the Qt meta-object compiler
+qt4_wrap_cpp(MOC_SRCS ${MOC_HDRS})
+
+# Compile the module
+add_library(${TARGET} SHARED ${SRCS} ${MOC_SRCS})
+
+# Link the module
+target_link_libraries(${TARGET} ${QT_LIBRARIES} ${eVaf_LIBRARIES})
+ +

Open the CMakeLists.txt file in the parent directory and add the command to include the Storage + sub-directory:

+ +
# src/apps/PswGen/CMakeLists.txt
+# ...
+add_subdirectory(Storage)
+ +

Building the module

+ +

Go to the previously made build directory and build the module:

+ +
evaf $ cd build
+evaf/build $ make PswStorage
+ +

Check the bin directory, which should now contain a new library:

+ +
evaf/build $ ls bin
+libCommonLib.so*  libPluginsLib.so*  libPswGen.so*  libPswStorage.so*
+evaf/build $
+ +

In the next section 09 - GUI Module we write the Graphical User Interface Module.

+ + +