Implemented a SQLite-based cache to speed up the elements panel loading.

git-svn-id: svn+ssh://svn.tuxfamily.org/svnroot/qet/qet/branches/0.3@1329 bfdf4180-ca20-0410-9c96-a3a8aa849046
This commit is contained in:
xavier 2011-09-13 21:46:10 +00:00
parent 9f9c67f4cf
commit 2a6ecee31b
15 changed files with 399 additions and 11 deletions

View File

@ -71,7 +71,7 @@ TRANSLATIONS += lang/qet_en.ts lang/qet_es.ts lang/qet_fr.ts lang/qet_ru.ts lang
TRANSLATIONS += lang/qt_es.ts lang/qt_fr.ts lang/qt_ru.ts lang/qt_pt.ts lang/qt_cs.ts lang/qt_pl.ts lang/qt_de.ts lang/qt_it.ts
# Modules Qt utilises par l'application
QT += xml svg network
QT += xml svg network sql
# Configuration de la compilation
CONFIG += debug_and_release warn_on

View File

@ -117,6 +117,7 @@ class ElementDefinition : public ElementsCollectionItem {
virtual ElementDefinition *toElement();
virtual bool equals(ElementDefinition &);
virtual bool removeContent();
virtual QDateTime modificationTime() const = 0;
void copy(MoveElementsDescription *);
void move(MoveElementsDescription *);

View File

@ -74,6 +74,7 @@ class ElementsCollection : public ElementsCollectionItem {
virtual ElementDefinition *createElement(const QString &);
virtual bool isEmpty();
virtual int count();
virtual bool isCacheable() const = 0;
// Methodes propres a la classe ElementsCollection
public:

View File

@ -0,0 +1,267 @@
#include "elementscollectioncache.h"
#include "elementscollection.h"
#include "elementscategory.h"
#include "elementdefinition.h"
#include "customelement.h"
/**
Construct a cache for elements collections.
@param database_path Path of the SQLite database to open.
@param parent Parent QObject
*/
ElementsCollectionCache::ElementsCollectionCache(const QString &database_path, QObject *parent) :
QObject(parent),
locale_("en"),
pixmap_storage_format_("PNG")
{
// initialize the cache SQLite database
static int cache_instances = 0;
QString connection_name = QString("ElementsCollectionCache-%1").arg(cache_instances++);
cache_db_ = QSqlDatabase::addDatabase("QSQLITE", connection_name );
cache_db_.setDatabaseName(database_path);
if (!cache_db_.open()) {
qDebug() << "Unable to open the SQLite database " << database_path << " as " << connection_name << ": " << cache_db_.lastError();
} else {
cache_db_.exec("PRAGMA temp_store=MEMORY");
cache_db_.exec("PRAGMA journal_mode = MEMORY");
cache_db_.exec("PRAGMA synchronous=OFF");
cache_db_.exec("PRAGMA cache_size=10000");
/// @todo the tables could already exist, handle that case.
cache_db_.exec("CREATE TABLE names (path VARCHAR(512) NOT NULL, locale VARCHAR(2) NOT NULL, mtime DATETIME NOT NULL, name VARCHAR(128), PRIMARY KEY(path, locale));");
cache_db_.exec("CREATE TABLE pixmaps (path VARCHAR(512) NOT NULL UNIQUE, mtime DATETIME NOT NULL, pixmap BLOB, PRIMARY KEY(path), FOREIGN KEY(path) REFERENCES names (path) ON DELETE CASCADE);");
// prepare queries
select_name_ = new QSqlQuery(cache_db_);
select_pixmap_ = new QSqlQuery(cache_db_);
insert_name_ = new QSqlQuery(cache_db_);
insert_pixmap_ = new QSqlQuery(cache_db_);
select_name_ -> prepare("SELECT name FROM names WHERE path = :path AND locale = :locale AND mtime > :file_mtime");
select_pixmap_ -> prepare("SELECT pixmap FROM pixmaps WHERE path = :path AND mtime > :file_mtime");
insert_name_ -> prepare("REPLACE INTO names (path, locale, mtime, name) VALUES (:path, :locale, :mtime, :name)");
insert_pixmap_ -> prepare("REPLACE INTO pixmaps (path, mtime, pixmap) VALUES (:path, :mtime, :pixmap)");
}
}
/**
Destructor
*/
ElementsCollectionCache::~ElementsCollectionCache() {
cache_db_.close();
}
/**
Define the locale to be used when dealing with names.
@param locale New locale to be used.
*/
void ElementsCollectionCache::setLocale(const QString &locale) {
locale_ = locale;
}
/**
@return The locale to be used when dealing with names.
*/
QString ElementsCollectionCache::locale() const {
return(locale_);
}
/**
Define the storage format for the pixmaps within the SQLite database. See
Qt's QPixmap documentation for more information.
@param format The new pixmap storage format.
@return True if the format change was accepted, false otherwise.
*/
bool ElementsCollectionCache::setPixmapStorageFormat(const QString &format) {
if (QImageWriter::supportedImageFormats().contains(format.toAscii())) {
pixmap_storage_format_= format;
return(true);
}
return(false);
}
/**
@return the pixmap storage format. Default is "PNG"
@see setPixmapStorageFormat()
*/
QString ElementsCollectionCache::pixmapStorageFormat() const {
return(pixmap_storage_format_);
}
/**
Indicate the cache a new collection is about to be browsed. This is mainly
used to delimit database transactions.
@param collection The elements collection about to be browsed.
*/
void ElementsCollectionCache::beginCollection(ElementsCollection *collection) {
bool use_cache = cache_db_.isOpen() && collection -> isCacheable();
if (use_cache) {
bool transaction_started = cache_db_.transaction();
qDebug() << (transaction_started ? "transaction began for " : "transaction not started for ") << collection -> protocol();
}
}
/**
Indicate the cache the currently browsed collection end has been reached. This
is mainly used to delimit database transactions.
@param collection The elements collection being browsed.
*/
void ElementsCollectionCache::endCollection(ElementsCollection *collection) {
bool use_cache = cache_db_.isOpen() && collection -> isCacheable();
if (use_cache) {
bool transaction_commited = cache_db_.commit();
qDebug() << (transaction_commited ? "transaction commited for " : "transaction not commited for") << collection -> protocol();
}
}
/**
Retrieve the data for a given element, using the cache if available,
filling it otherwise. Data are then available through pixmap() and name()
methods.
@param element The definition of an element.
@see pixmap()
@see name()
@return True if the retrieval succeeded, false otherwise.
*/
bool ElementsCollectionCache::fetchElement(ElementDefinition *element) {
// can we use the cache with this element?
bool use_cache = cache_db_.isOpen() && element -> parentCollection() -> isCacheable();
// attempt to fetch the element name from the cache database
if (!use_cache) {
return(fetchData(element -> location()));
} else {
QString element_path = element -> location().toString();
bool got_name = fetchNameFromCache(element_path, element -> modificationTime());
bool got_pixmap = fetchPixmapFromCache(element_path, element -> modificationTime());
if (got_name && got_pixmap) {
return(true);
}
if (fetchData(element -> location())) {
cacheName(element_path);
cachePixmap(element_path);
}
return(true);
}
}
/**
@return The last name fetched through fetchElement().
*/
QString ElementsCollectionCache::name() const {
return(current_name_);
}
/**
@return The last pixmap fetched through fetchElement().
*/
QPixmap ElementsCollectionCache::pixmap() const {
return(current_pixmap_);
}
/**
Retrieve the data by building the full CustomElement object matching the
given location, without using the cache. Data are then available through
pixmap() and name() methods.
@param Location Location of a given Element.
@return True if the retrieval succeeded, false otherwise.
*/
bool ElementsCollectionCache::fetchData(const ElementsLocation &location) {
int state;
CustomElement *custom_elmt = new CustomElement(location, 0, 0, &state);
if (state) {
qDebug() << "ElementsCollectionCache::fetchData() : Le chargement du composant" << qPrintable(location.toString()) << "a echoue avec le code d'erreur" << state;
} else {
current_name_ = custom_elmt -> name();
current_pixmap_ = custom_elmt -> pixmap();
}
delete custom_elmt;
return(!state);
}
/**
Retrieve the name for an element, given its path and last modification
time. The value is then available through the name() method.
@param path Element path (as obtained using ElementsLocation::toString())
@param file_mtime Date and time of last modification of this element. Any
older cached value will be ignored.
@return True if the retrieval succeeded, false otherwise.
*/
bool ElementsCollectionCache::fetchNameFromCache(const QString &path, const QDateTime &file_mtime) {
select_name_ -> bindValue(":path", path);
select_name_ -> bindValue(":locale", locale_);
select_name_ -> bindValue(":file_mtime", file_mtime);
if (select_name_ -> exec()) {
if (select_name_ -> first()) {
current_name_ = select_name_ -> value(0).toString();
return(true);
}
} else {
qDebug() << "select_name_->exec() failed";
}
return(false);
}
/**
Retrieve the pixmap for an element, given its path and last modification
time. It is then available through the pixmap() method.
@param path Element path (as obtained using ElementsLocation::toString())
@param file_mtime Date and time of last modification of this element. Any
older cached pixmap will be ignored.
@return True if the retrieval succeeded, false otherwise.
*/
bool ElementsCollectionCache::fetchPixmapFromCache(const QString &path, const QDateTime &file_mtime) {
select_pixmap_ -> bindValue(":path", path);
select_pixmap_ -> bindValue(":file_mtime", file_mtime);
if (select_pixmap_ -> exec()) {
if (select_pixmap_ -> first()) {
QByteArray ba = select_pixmap_ -> value(0).toByteArray();
// avoid returning always the same pixmap (i.e. same cacheKey())
current_pixmap_.detach();
current_pixmap_.loadFromData(ba, qPrintable(pixmap_storage_format_));
}
return(true);
} else {
qDebug() << "select_pixmap_->exec() failed";
}
return(false);
}
/**
Cache the current (i.e. last retrieved) name. The cache entry will use
the current date and time and the locale set via setLocale().
@param path Element path (as obtained using ElementsLocation::toString())
@return True if the caching succeeded, false otherwise.
@see name()
*/
bool ElementsCollectionCache::cacheName(const QString &path) {
insert_name_ -> bindValue(":path", path);
insert_name_ -> bindValue(":locale", locale_);
insert_name_ -> bindValue(":mtime", QVariant(QDateTime::currentDateTime()));
insert_name_ -> bindValue(":name", current_name_);
if (!insert_name_ -> exec()) {
qDebug() << cache_db_.lastError();
return(false);
}
return(true);
}
/**
Cache the current (i.e. last retrieved) pixmap. The cache entry will use
the current date and time.
@param path Element path (as obtained using ElementsLocation::toString())
@return True if the caching succeeded, false otherwise.
@see pixmap()
*/
bool ElementsCollectionCache::cachePixmap(const QString &path) {
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
current_pixmap_.save(&buffer, qPrintable(pixmap_storage_format_));
insert_pixmap_ -> bindValue(":path", path);
insert_pixmap_ -> bindValue(":mtime", QVariant(QDateTime::currentDateTime()));
insert_pixmap_ -> bindValue(":pixmap", QVariant(ba));
if (!insert_pixmap_->exec()) {
qDebug() << cache_db_.lastError();
return(false);
}
return(true);
}

View File

@ -0,0 +1,69 @@
/*
Copyright 2006-2011 Xavier Guerrin
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ELEMENTS_COLLECTION_CACHE_H
#define ELEMENTS_COLLECTION_CACHE_H
#include <QtCore>
#include <QtSql>
#include "elementslocation.h"
class ElementsCollection;
class ElementsCategory;
class ElementDefinition;
/**
This class implements a SQLite cache for data related to elements
collections, mainly names and pixmaps. This avoids the cost of parsing XML
definitions of elements and building full CustomElement objects when
(re)loading the elements panel.
*/
class ElementsCollectionCache : public QObject {
public:
// constructor, destructor
ElementsCollectionCache(const QString &database_path, QObject * = 0);
virtual ~ElementsCollectionCache();
// methods
public:
void setLocale(const QString &);
QString locale() const;
bool setPixmapStorageFormat(const QString &);
QString pixmapStorageFormat() const;
void beginCollection(ElementsCollection *);
void endCollection(ElementsCollection *);
bool fetchElement(ElementDefinition *);
QString name() const;
QPixmap pixmap() const;
private:
bool fetchData(const ElementsLocation &);
bool fetchNameFromCache(const QString &, const QDateTime &);
bool fetchPixmapFromCache(const QString &, const QDateTime &);
bool cacheName(const QString &);
bool cachePixmap(const QString &);
// attributes
private:
QSqlDatabase cache_db_; ///< Object providing access to the SQLite database this cache relies on
QSqlQuery *select_name_; ///< Prepared statement to fetch names from the cache
QSqlQuery *select_pixmap_; ///< Prepared statement to fetch pixmaps from the cache
QSqlQuery *insert_name_; ///< Prepared statement to insert names into the cache
QSqlQuery *insert_pixmap_; ///< Prepared statement to insert pixmaps into the cache
QString locale_; ///< Locale to be used when dealing with names
QString pixmap_storage_format_; ///< Storage format for cached pixmaps
QString current_name_; ///< Last name fetched
QPixmap current_pixmap_; ///< Last pixmap fetched
};
#endif

View File

@ -20,6 +20,7 @@
#include "qetproject.h"
#include "diagram.h"
#include "elementscategory.h"
#include "elementscollectioncache.h"
#include "customelement.h"
#include "fileelementscollection.h"
#include "fileelementdefinition.h"
@ -89,7 +90,6 @@ ElementsPanel::ElementsPanel(QWidget *parent) :
first_activation_(true),
first_reload_(true)
{
// selection unique
setSelectionMode(QAbstractItemView::SingleSelection);
setColumnCount(1);
@ -120,6 +120,10 @@ ElementsPanel::ElementsPanel(QWidget *parent) :
// emet un signal au lieu de gerer son menu contextuel
setContextMenuPolicy(Qt::CustomContextMenu);
QString cache_path = QETApp::configDir() + "/elements_cache.sqlite";
cache_ = new ElementsCollectionCache(cache_path, this);
cache_ -> setLocale(QLocale::system().name().left(2)); // @todo we need a unique function to get the good language
}
/**
@ -644,7 +648,9 @@ QTreeWidgetItem *ElementsPanel::addDiagram(QTreeWidgetItem *qtwi_parent, Diagram
QTreeWidgetItem *ElementsPanel::addCollection(QTreeWidgetItem *qtwi_parent, ElementsCollection *collection, const QString &coll_name, const QIcon &icon) {
if (!collection) return(0);
cache_ -> beginCollection(collection);
QTreeWidgetItem *qtwi_coll = addCategory(qtwi_parent, collection -> rootCategory(), coll_name, icon);
cache_ -> endCollection(collection);
return(qtwi_coll);
}
@ -704,21 +710,21 @@ QTreeWidgetItem *ElementsPanel::addCategory(QTreeWidgetItem *qtwi_parent, Elemen
QTreeWidgetItem *ElementsPanel::addElement(QTreeWidgetItem *qtwi_parent, ElementDefinition *element, const QString &elmt_name) {
if (!element) return(0);
QString whats_this = tr("Ceci est un \351l\351ment que vous pouvez ins\351rer dans votre sch\351ma par cliquer-d\351placer");
QString tool_tip = tr("Cliquer-d\351posez cet \351l\351ment sur le sch\351ma pour ins\351rer un \351l\351ment ");
int state;
CustomElement custom_elmt(element -> location(), 0, 0, &state);
if (state) {
qDebug() << "ElementsCategoriesList::addElement() : Le chargement du composant" << qPrintable(element -> location().toString()) << "a echoue avec le code d'erreur" << state;
if (!cache_ -> fetchElement(element)) {
return(0);
}
QString final_name(elmt_name.isEmpty() ? custom_elmt.name() : elmt_name);
QString custom_element_name = cache_ -> name();
QPixmap custom_element_pixmap = cache_ -> pixmap();
QString whats_this = tr("Ceci est un \351l\351ment que vous pouvez ins\351rer dans votre sch\351ma par cliquer-d\351placer");
QString tool_tip = tr("Cliquer-d\351posez cet \351l\351ment sur le sch\351ma pour ins\351rer un \351l\351ment ");
QString final_name(elmt_name.isEmpty() ? custom_element_name : elmt_name);
QTreeWidgetItem *qtwi = new QTreeWidgetItem(qtwi_parent, QStringList(final_name));
qtwi -> setStatusTip(0, tool_tip + "\253 " + custom_elmt.name() + " \273");
qtwi -> setStatusTip(0, tool_tip + "\253 " + custom_element_name + " \273");
qtwi -> setToolTip(0, element -> location().toString());
qtwi -> setWhatsThis(0, whats_this);
qtwi -> setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled);
qtwi -> setIcon(0, QIcon(custom_elmt.pixmap()));
qtwi -> setIcon(0, QIcon(custom_element_pixmap));
// actions speciales pour les elements appartenant a un projet
if (QETProject *element_project = element -> location().project()) {

View File

@ -25,6 +25,7 @@ class ElementsCollection;
class ElementsCollectionItem;
class ElementsCategory;
class ElementDefinition;
class ElementsCollectionCache;
/**
Cette classe represente le panel d'appareils (en tant qu'element
graphique) dans lequel l'utilisateur choisit les composants de
@ -144,5 +145,6 @@ class ElementsPanel : public QTreeWidget {
int loading_progress_;
bool first_activation_;
bool first_reload_;
ElementsCollectionCache *cache_;
};
#endif

View File

@ -198,3 +198,14 @@ void FileElementDefinition::setFilePath(const QString &path) {
}
file_path = file_info.canonicalFilePath();
}
/**
@return the time of the last modification (mtime) for this element file
*/
QDateTime FileElementDefinition::modificationTime() const {
QFileInfo file_info(file_path);
if (!file_info.exists() || !file_info.isReadable()) {
return QDateTime();
}
return(file_info.lastModified());
}

View File

@ -49,6 +49,7 @@ class FileElementDefinition : public ElementDefinition {
virtual bool hasFilePath();
virtual QString filePath();
virtual void setFilePath(const QString &);
virtual QDateTime modificationTime() const;
// attributs
private:

View File

@ -123,3 +123,11 @@ bool FileElementsCollection::isWritable() {
bool FileElementsCollection::write() {
return(true);
}
/**
@return always true, since a file-based elements collection can always be
cached.
*/
bool FileElementsCollection::isCacheable() const {
return(true);
}

View File

@ -48,6 +48,7 @@ class FileElementsCollection : public ElementsCollection {
virtual bool isReadable();
virtual bool isWritable();
virtual bool write();
virtual bool isCacheable() const;
private:
void deleteContent();

View File

@ -213,6 +213,17 @@ void XmlElementDefinition::setFilePath(const QString &) {
// une categorie XML n'a pas de chemin de type fichier
}
/**
@return a null QDateTime object since an XML element does not have a
modification time.
*/
/**
@return the time of the last modification (mtime) for this element file
*/
QDateTime XmlElementDefinition::modificationTime() const {
return QDateTime();
}
QDomElement XmlElementDefinition::writeXml(QDomDocument &xml_doc) const {
QDomElement element_elmt = xml_element_.documentElement();
QDomNode new_node = xml_doc.importNode(element_elmt, true);

View File

@ -52,6 +52,7 @@ class XmlElementDefinition : public ElementDefinition {
virtual bool hasFilePath();
virtual QString filePath();
virtual void setFilePath(const QString &);
virtual QDateTime modificationTime() const;
virtual QDomElement writeXml(QDomDocument &) const;
signals:

View File

@ -115,6 +115,14 @@ bool XmlElementsCollection::write() {
return(true);
}
/**
@return always false, since an XMl-based elements collection should never
be cached.
*/
bool XmlElementsCollection::isCacheable() const {
return(false);
}
QDomElement XmlElementsCollection::writeXml(QDomDocument &xml_doc) const {
QDomElement collection_elmt = root -> writeXml(xml_doc);
collection_elmt.setTagName("collection");

View File

@ -47,6 +47,7 @@ class XmlElementsCollection : public ElementsCollection {
virtual bool isReadable();
virtual bool isWritable();
virtual bool write();
virtual bool isCacheable() const;
virtual QDomElement writeXml(QDomDocument &) const;