1 /*
2 	Copyright 2006-2019 The QElectroTech Team
3 	This file is part of QElectroTech.
4 
5 	QElectroTech is free software: you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation, either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	QElectroTech is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "elementscollectioncache.h"
19 #include "factory/elementfactory.h"
20 #include "element.h"
21 #include "qet.h"
22 
23 #include <QImageWriter>
24 #include <QSqlQuery>
25 #include <QSqlError>
26 
27 /**
28 	Construct a cache for elements collections.
29 	@param database_path Path of the SQLite database to open.
30 	@param parent Parent QObject
31 */
ElementsCollectionCache(const QString & database_path,QObject * parent)32 ElementsCollectionCache::ElementsCollectionCache(const QString &database_path, QObject *parent) :
33 	QObject(parent),
34 	locale_("en"),
35 	pixmap_storage_format_("PNG")
36 {
37 	// initialize the cache SQLite database
38 	static int cache_instances = 0;
39 	QString connection_name = QString("ElementsCollectionCache-%1").arg(cache_instances++);
40 	cache_db_ = QSqlDatabase::addDatabase("QSQLITE", connection_name);
41 	cache_db_.setDatabaseName(database_path);
42 
43 	if (!cache_db_.open())
44 		qDebug() << "Unable to open the SQLite database " << database_path << " as " << connection_name << ": " << cache_db_.lastError();
45 	else
46 	{
47 		cache_db_.exec("PRAGMA temp_store = MEMORY");
48 		cache_db_.exec("PRAGMA journal_mode = MEMORY");
49 		cache_db_.exec("PRAGMA page_size = 4096");
50 		cache_db_.exec("PRAGMA cache_size = 16384");
51 		cache_db_.exec("PRAGMA locking_mode = EXCLUSIVE");
52 		cache_db_.exec("PRAGMA synchronous = OFF");
53 
54 			//TODO This code remove old table with mtime for create table with uuid, created at version 0,5
55 			//see to remove this code at version 0,6 or 0,7 when all users will table with uuid.
56 		QSqlQuery table_name(cache_db_);
57 		if (table_name.exec("PRAGMA table_info(names)"))
58 		{
59 			if (table_name.seek(2))
60 			{
61 				QString str = table_name.value(1).toString();
62 				table_name.finish();
63 				if (str == "mtime")
64 				{
65 					QSqlQuery error;
66 					error = cache_db_.exec("DROP TABLE names");
67 					error = cache_db_.exec("DROP TABLE pixmaps");
68 				}
69 			}
70 			else
71 				table_name.finish();
72 		}
73 
74 			//@TODO the tables could already exist, handle that case.
75 		cache_db_.exec("CREATE TABLE names"
76 					   "("
77 					   "path VARCHAR(512) NOT NULL,"
78 					   "locale VARCHAR(2) NOT NULL,"
79 					   "uuid VARCHAR(512) NOT NULL,"
80 					   "name VARCHAR(128),"
81 					   "PRIMARY KEY(path, locale)"
82 					   ");");
83 
84 		cache_db_.exec("CREATE TABLE pixmaps"
85 					   "("
86 					   "path VARCHAR(512) NOT NULL UNIQUE,"
87 					   "uuid VARCHAR(512) NOT NULL,"
88 					   "pixmap BLOB, PRIMARY KEY(path),"
89 					   "FOREIGN KEY(path) REFERENCES names (path) ON DELETE CASCADE);");
90 
91 			// prepare queries
92 		select_name_   = new QSqlQuery(cache_db_);
93 		select_pixmap_ = new QSqlQuery(cache_db_);
94 		insert_name_   = new QSqlQuery(cache_db_);
95 		insert_pixmap_ = new QSqlQuery(cache_db_);
96 		select_name_   -> prepare("SELECT name FROM names WHERE path = :path AND locale = :locale AND uuid = :uuid");
97 		select_pixmap_ -> prepare("SELECT pixmap FROM pixmaps WHERE path = :path AND uuid = :uuid");
98 		insert_name_   -> prepare("REPLACE INTO names (path, locale, uuid, name) VALUES (:path, :locale, :uuid, :name)");
99 		insert_pixmap_ -> prepare("REPLACE INTO pixmaps (path, uuid, pixmap) VALUES (:path, :uuid, :pixmap)");
100 	}
101 }
102 
103 /**
104 	Destructor
105 */
~ElementsCollectionCache()106 ElementsCollectionCache::~ElementsCollectionCache() {
107 	delete select_name_;
108 	delete select_pixmap_;
109 	delete insert_name_;
110 	delete insert_pixmap_;
111 	cache_db_.close();
112 }
113 
114 /**
115 	Define the locale to be used when dealing with names.
116 	@param locale New locale to be used.
117 */
setLocale(const QString & locale)118 void ElementsCollectionCache::setLocale(const QString &locale) {
119 	locale_ = locale;
120 }
121 
122 /**
123 	@return The locale to be used when dealing with names.
124 */
locale() const125 QString ElementsCollectionCache::locale() const {
126 	return(locale_);
127 }
128 
129 /**
130 	Define the storage format for the pixmaps within the SQLite database. See
131 	Qt's QPixmap documentation for more information.
132 	@param format The new pixmap storage format.
133 	@return True if the format change was accepted, false otherwise.
134 */
setPixmapStorageFormat(const QString & format)135 bool ElementsCollectionCache::setPixmapStorageFormat(const QString &format) {
136 	if (QImageWriter::supportedImageFormats().contains(format.toLatin1())) {
137 		pixmap_storage_format_= format;
138 		return(true);
139 	}
140 	return(false);
141 }
142 
143 /**
144 	@return the pixmap storage format. Default is "PNG"
145 	@see setPixmapStorageFormat()
146 */
pixmapStorageFormat() const147 QString ElementsCollectionCache::pixmapStorageFormat() const {
148 	return(pixmap_storage_format_);
149 }
150 
151 /**
152  * @brief ElementsCollectionCache::fetchElement
153  * Retrieve the data for a given element, using the cache if available,
154  * filling it otherwise. Data are then available through pixmap() and name() methods.
155  * @param location The definition of an element.
156  * @see pixmap()
157  * @see name()
158  * @return True if the retrieval succeeded, false otherwise.
159  */
fetchElement(ElementsLocation & location)160 bool ElementsCollectionCache::fetchElement(ElementsLocation &location)
161 {
162 		// can we use the cache with this element?
163 	bool use_cache = cache_db_.isOpen() && !location.isProject();
164 
165 		// attempt to fetch the element name from the cache database
166 	if (!use_cache) {
167 		return(fetchData(location));
168 	}
169 	else
170 	{
171 		QString element_path = location.toString();
172 		bool got_name   = fetchNameFromCache(element_path, location.uuid());
173 		bool got_pixmap = fetchPixmapFromCache(element_path, location.uuid());
174 
175 		if (got_name && got_pixmap) {
176 			return(true);
177 		}
178 
179 		if (fetchData(location))
180 		{
181 			cacheName(element_path, location.uuid());
182 			cachePixmap(element_path, location.uuid());
183 		}
184 		return(true);
185 	}
186 }
187 
188 /**
189 	@return The last name fetched through fetchElement().
190 */
name() const191 QString ElementsCollectionCache::name() const {
192 	return(current_name_);
193 }
194 
195 /**
196 	@return The last pixmap fetched through fetchElement().
197 */
pixmap() const198 QPixmap ElementsCollectionCache::pixmap() const {
199 	return(current_pixmap_);
200 }
201 
202 /**
203 	Retrieve the data by building the full CustomElement object matching the
204 	given location, without using the cache. Data are then available through
205 	pixmap() and name() methods.
206 	@param Location Location of a given Element.
207 	@return True if the retrieval succeeded, false otherwise.
208 */
fetchData(const ElementsLocation & location)209 bool ElementsCollectionCache::fetchData(const ElementsLocation &location) {
210 	int state;
211 	Element *custom_elmt = ElementFactory::Instance() -> createElement(location, nullptr, &state);
212 	if (state) {
213 		qDebug() << "ElementsCollectionCache::fetchData() : Le chargement du composant" << qPrintable(location.toString()) << "a echoue avec le code d'erreur" << state;
214 	} else {
215 		current_name_   = custom_elmt -> name();
216 		current_pixmap_ = custom_elmt -> pixmap();
217 	}
218 	delete custom_elmt;
219 	return(!state);
220 }
221 
222 /**
223  * @brief ElementsCollectionCache::fetchNameFromCache
224  * Retrieve the name for an element, given its path and uuid
225  * The value is then available through the name() method.
226  * @param path : Element path (as obtained using ElementsLocation::toString())
227  * @param uuid : Element uuid
228  * @return True if the retrieval succeeded, false otherwise.
229  */
fetchNameFromCache(const QString & path,const QUuid & uuid)230 bool ElementsCollectionCache::fetchNameFromCache(const QString &path, const QUuid &uuid)
231 {
232 	select_name_ -> bindValue(":path", path);
233 	select_name_ -> bindValue(":locale", locale_);
234 	select_name_ -> bindValue(":uuid", uuid.toString());
235 	if (select_name_ -> exec())
236 	{
237 		if (select_name_ -> first())
238 		{
239 			current_name_ = select_name_ -> value(0).toString();
240 			select_name_ -> finish();
241 			return(true);
242 		}
243 	}
244 	else
245 		qDebug() << "select_name_->exec() failed";
246 
247 	return(false);
248 }
249 
250 /**
251  * @brief ElementsCollectionCache::fetchPixmapFromCache
252  * Retrieve the pixmap for an element, given its path and uuid.
253  * It is then available through the pixmap() method.
254  * @param path : Element path (as obtained using ElementsLocation::toString())
255  * @param uuid : Element uuid
256  * @return True if the retrieval succeeded, false otherwise.
257  */
fetchPixmapFromCache(const QString & path,const QUuid & uuid)258 bool ElementsCollectionCache::fetchPixmapFromCache(const QString &path, const QUuid &uuid)
259 {
260 	select_pixmap_ -> bindValue(":path", path);
261 	select_pixmap_ -> bindValue(":uuid", uuid.toString());
262 	if (select_pixmap_ -> exec())
263 	{
264 		if (select_pixmap_ -> first())
265 		{
266 			QByteArray ba = select_pixmap_ -> value(0).toByteArray();
267 			// avoid returning always the same pixmap (i.e. same cacheKey())
268 			current_pixmap_.detach();
269 			current_pixmap_.loadFromData(ba, qPrintable(pixmap_storage_format_));
270 			select_pixmap_ -> finish();
271 		}
272 		return(true);
273 	}
274 	else
275 		qDebug() << "select_pixmap_->exec() failed";
276 
277 	return(false);
278 }
279 
280 /**
281  * @brief ElementsCollectionCache::cacheName
282  * Cache the current (i.e. last retrieved) name The cache entry will use the locale set via setLocale().
283  * @param path : Element path (as obtained using ElementsLocation::toString())
284  * @param uuid :Element uuid
285  * @return True if the caching succeeded, false otherwise.
286  * @see name()
287  */
cacheName(const QString & path,const QUuid & uuid)288 bool ElementsCollectionCache::cacheName(const QString &path, const QUuid &uuid)
289 {
290 	insert_name_ -> bindValue(":path",   path);
291 	insert_name_ -> bindValue(":locale", locale_);
292 	insert_name_ -> bindValue(":uuid",  uuid.toString());
293 	insert_name_ -> bindValue(":name",   current_name_);
294 	if (!insert_name_ -> exec())
295 	{
296 		qDebug() << cache_db_.lastError();
297 		return(false);
298 	}
299 	return(true);
300 }
301 
302 /**
303  * @brief ElementsCollectionCache::cachePixmap
304  * Cache the current (i.e. last retrieved) pixmap
305  * @param path : Element path (as obtained using ElementsLocation::toString())
306  * @param uuid : Element uuid
307  * @return True if the caching succeeded, false otherwise.
308  * @see pixmap()
309  */
cachePixmap(const QString & path,const QUuid & uuid)310 bool ElementsCollectionCache::cachePixmap(const QString &path, const QUuid &uuid)
311 {
312 	QByteArray ba;
313 	QBuffer buffer(&ba);
314 	buffer.open(QIODevice::WriteOnly);
315 	current_pixmap_.save(&buffer, qPrintable(pixmap_storage_format_));
316 	insert_pixmap_ -> bindValue(":path", path);
317 	insert_pixmap_ -> bindValue(":uuid", uuid.toString());
318 	insert_pixmap_ -> bindValue(":pixmap", QVariant(ba));
319 	if (!insert_pixmap_->exec())
320 	{
321 		qDebug() << cache_db_.lastError();
322 		return(false);
323 	}
324 	return(true);
325 }
326