Next: 07 - Storage Module, Previous: 05 - Building Generator 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.
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 query() method returns an explicitly shared shared data pointer, which has the following advantages:
The drawback is that if a module needs a local copy of the data object, it has to use the QExplicitlySharedDataPointer::detach() method to create a deep copy of the data object.
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"
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.