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