1 #include <QSqlDriver>
2 #include <QSqlQuery>
3 #include <QSqlError>
4 #include <QDir>
5 #include <QUuid>
6
7 #include "macros.h"
8 #include "qmc2main.h"
9 #include "settings.h"
10 #include "options.h"
11 #include "xmldbmgr.h"
12
13 // external global variables
14 extern MainWindow *qmc2MainWindow;
15 extern Settings *qmc2Config;
16
XmlDatabaseManager(QObject * parent)17 XmlDatabaseManager::XmlDatabaseManager(QObject *parent) :
18 QObject(parent)
19 {
20 setLogActive(true);
21 m_connectionName = QString("xml-cache-db-connection-%1").arg(QUuid::createUuid().toString());
22 m_db = QSqlDatabase::addDatabase("QSQLITE", m_connectionName);
23 m_db.setDatabaseName(qmc2Config->value(QMC2_EMULATOR_PREFIX + "FilesAndDirectories/XmlCacheDatabase", QString(Options::configPath() + "/%1-xml-cache.db").arg(QMC2_EMU_NAME.toLower())).toString());
24 m_tableBasename = QString("%1_xml_cache").arg(QMC2_EMU_NAME.toLower());
25 if ( m_db.open() ) {
26 QStringList tables(m_db.driver()->tables(QSql::Tables));
27 if ( tables.count() != 2 || !tables.contains(m_tableBasename) || !tables.contains(QString("%1_metadata").arg(m_tableBasename)) )
28 recreateDatabase();
29 } else
30 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to open XML cache database '%1': error = '%2'").arg(m_db.databaseName()).arg(m_db.lastError().text()));
31 }
32
~XmlDatabaseManager()33 XmlDatabaseManager::~XmlDatabaseManager()
34 {
35 if ( m_db.isOpen() )
36 m_db.close();
37 }
38
emulatorVersion()39 QString XmlDatabaseManager::emulatorVersion()
40 {
41 QString emu_version;
42 QSqlQuery query(m_db);
43 query.prepare(QString("SELECT emu_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
44 if ( query.exec() ) {
45 if ( query.first() )
46 emu_version = query.value(0).toString();
47 query.finish();
48 } else
49 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("emu_version").arg(query.lastQuery()).arg(query.lastError().text()));
50 return emu_version;
51 }
52
setEmulatorVersion(QString emu_version)53 void XmlDatabaseManager::setEmulatorVersion(QString emu_version)
54 {
55 QSqlQuery query(m_db);
56 query.prepare(QString("SELECT emu_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
57 if ( query.exec() ) {
58 if ( !query.next() ) {
59 query.finish();
60 query.prepare(QString("INSERT INTO %1_metadata (emu_version, row) VALUES (:emu_version, 0)").arg(m_tableBasename));
61 query.bindValue(":emu_version", emu_version);
62 if ( !query.exec() )
63 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to add '%1' to XML cache database: query = '%2', error = '%3'").arg("emu_version").arg(query.lastQuery()).arg(query.lastError().text()));
64 } else {
65 query.finish();
66 query.prepare(QString("UPDATE %1_metadata SET emu_version=:emu_version WHERE row=0").arg(m_tableBasename));
67 query.bindValue(":emu_version", emu_version);
68 if ( !query.exec() )
69 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to update '%1' in XML cache database: query = '%2', error = '%3'").arg("emu_version").arg(query.lastQuery()).arg(query.lastError().text()));
70 }
71 query.finish();
72 } else
73 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("emu_version").arg(query.lastQuery()).arg(query.lastError().text()));
74 }
75
qmc2Version()76 QString XmlDatabaseManager::qmc2Version()
77 {
78 QString qmc2_version;
79 QSqlQuery query(m_db);
80 query.prepare(QString("SELECT qmc2_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
81 if ( query.exec() ) {
82 if ( query.first() )
83 qmc2_version = query.value(0).toString();
84 query.finish();
85 } else
86 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("qmc2_version").arg(query.lastQuery()).arg(query.lastError().text()));
87 return qmc2_version;
88 }
89
setQmc2Version(QString qmc2_version)90 void XmlDatabaseManager::setQmc2Version(QString qmc2_version)
91 {
92 QSqlQuery query(m_db);
93 query.prepare(QString("SELECT qmc2_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
94 if ( query.exec() ) {
95 if ( !query.next() ) {
96 query.finish();
97 query.prepare(QString("INSERT INTO %1_metadata (qmc2_version, row) VALUES (:qmc2_version, 0)").arg(m_tableBasename));
98 query.bindValue(":qmc2_version", qmc2_version);
99 if ( !query.exec() )
100 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to add '%1' to XML cache database: query = '%2', error = '%3'").arg("qmc2_version").arg(query.lastQuery()).arg(query.lastError().text()));
101 } else {
102 query.finish();
103 query.prepare(QString("UPDATE %1_metadata SET qmc2_version=:qmc2_version WHERE row=0").arg(m_tableBasename));
104 query.bindValue(":qmc2_version", qmc2_version);
105 if ( !query.exec() )
106 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to update '%1' in XML cache database: query = '%2', error = '%3'").arg("qmc2_version").arg(query.lastQuery()).arg(query.lastError().text()));
107 }
108 query.finish();
109 } else
110 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("qmc2_version").arg(query.lastQuery()).arg(query.lastError().text()));
111 }
112
xmlCacheVersion()113 int XmlDatabaseManager::xmlCacheVersion()
114 {
115 int xmlcache_version = -1;
116 QSqlQuery query(m_db);
117 query.prepare(QString("SELECT xmlcache_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
118 if ( query.exec() ) {
119 if ( query.first() )
120 xmlcache_version = query.value(0).toInt();
121 query.finish();
122 } else
123 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("xmlcache_version").arg(query.lastQuery()).arg(query.lastError().text()));
124 return xmlcache_version;
125 }
126
setXmlCacheVersion(int xmlcache_version)127 void XmlDatabaseManager::setXmlCacheVersion(int xmlcache_version)
128 {
129 QSqlQuery query(m_db);
130 query.prepare(QString("SELECT xmlcache_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
131 if ( query.exec() ) {
132 if ( !query.next() ) {
133 query.finish();
134 query.prepare(QString("INSERT INTO %1_metadata (xmlcache_version, row) VALUES (:xmlcache_version, 0)").arg(m_tableBasename));
135 query.bindValue(":xmlcache_version", xmlcache_version);
136 if ( !query.exec() )
137 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to add '%1' to XML cache database: query = '%2', error = '%3'").arg("xmlcache_version").arg(query.lastQuery()).arg(query.lastError().text()));
138 } else {
139 query.finish();
140 query.prepare(QString("UPDATE %1_metadata SET xmlcache_version=:xmlcache_version WHERE row=0").arg(m_tableBasename));
141 query.bindValue(":xmlcache_version", xmlcache_version);
142 if ( !query.exec() )
143 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to update '%1' in XML cache database: query = '%2', error = '%3'").arg("xmlcache_version").arg(query.lastQuery()).arg(query.lastError().text()));
144 }
145 query.finish();
146 } else
147 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("xmlcache_version").arg(query.lastQuery()).arg(query.lastError().text()));
148 }
149
dtd()150 QString XmlDatabaseManager::dtd()
151 {
152 QString dtd;
153 QSqlQuery query(m_db);
154 query.prepare(QString("SELECT dtd FROM %1_metadata WHERE row=0").arg(m_tableBasename));
155 if ( query.exec() ) {
156 if ( query.first() )
157 dtd = query.value(0).toString();
158 query.finish();
159 } else
160 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("dtd").arg(query.lastQuery()).arg(query.lastError().text()));
161 return dtd;
162 }
163
setDtd(QString dtd)164 void XmlDatabaseManager::setDtd(QString dtd)
165 {
166 QSqlQuery query(m_db);
167 query.prepare(QString("SELECT dtd FROM %1_metadata WHERE row=0").arg(m_tableBasename));
168 if ( query.exec() ) {
169 if ( !query.next() ) {
170 query.finish();
171 query.prepare(QString("INSERT INTO %1_metadata (dtd, row) VALUES (:dtd, 0)").arg(m_tableBasename));
172 query.bindValue(":dtd", dtd);
173 if ( !query.exec() )
174 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to add '%1' to XML cache database: query = '%2', error = '%3'").arg("dtd").arg(query.lastQuery()).arg(query.lastError().text()));
175 } else {
176 query.finish();
177 query.prepare(QString("UPDATE %1_metadata SET dtd=:dtd WHERE row=0").arg(m_tableBasename));
178 query.bindValue(":dtd", dtd);
179 if ( !query.exec() )
180 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to update '%1' in XML cache database: query = '%2', error = '%3'").arg("dtd").arg(query.lastQuery()).arg(query.lastError().text()));
181 }
182 query.finish();
183 } else
184 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("dtd").arg(query.lastQuery()).arg(query.lastError().text()));
185 }
186
xml(QString id)187 QString XmlDatabaseManager::xml(QString id)
188 {
189 QString xml;
190 QSqlQuery query(m_db);
191 query.prepare(QString("SELECT xml FROM %1 WHERE id=:id").arg(m_tableBasename));
192 query.bindValue(":id", id);
193 if ( query.exec() ) {
194 if ( query.first() )
195 xml = query.value(0).toString();
196 query.finish();
197 } else
198 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("xml").arg(query.lastQuery()).arg(query.lastError().text()));
199 return xml;
200 }
201
xml(int rowid)202 QString XmlDatabaseManager::xml(int rowid)
203 {
204 QString xml;
205 QSqlQuery query(m_db);
206 query.prepare(QString("SELECT xml FROM %1 WHERE rowid=:rowid").arg(m_tableBasename));
207 query.bindValue(":rowid", rowid);
208 if ( query.exec() ) {
209 if ( query.first() )
210 xml = query.value(0).toString();
211 query.finish();
212 } else
213 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("xml").arg(query.lastQuery()).arg(query.lastError().text()));
214 return xml;
215 }
216
setXml(QString id,QString xml)217 void XmlDatabaseManager::setXml(QString id, QString xml)
218 {
219 QSqlQuery query(m_db);
220 query.prepare(QString("SELECT xml FROM %1 WHERE id=:id").arg(m_tableBasename));
221 query.bindValue(":id", id);
222 if ( query.exec() ) {
223 if ( !query.next() ) {
224 query.finish();
225 query.prepare(QString("INSERT INTO %1 (id, xml) VALUES (:id, :xml)").arg(m_tableBasename));
226 query.bindValue(":id", id);
227 query.bindValue(":xml", xml);
228 if ( !query.exec() )
229 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to add '%1' to XML cache database: query = '%2', error = '%3'").arg("xml").arg(query.lastQuery()).arg(query.lastError().text()));
230 } else {
231 query.finish();
232 query.prepare(QString("UPDATE %1 SET xml=:xml WHERE id=:id").arg(m_tableBasename));
233 query.bindValue(":id", id);
234 query.bindValue(":xml", xml);
235 if ( !query.exec() )
236 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to update '%1' in XML cache database: query = '%2', error = '%3'").arg("xml").arg(query.lastQuery()).arg(query.lastError().text()));
237 }
238 query.finish();
239 } else
240 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("xml").arg(query.lastQuery()).arg(query.lastError().text()));
241 }
242
xmlRowCount()243 qint64 XmlDatabaseManager::xmlRowCount()
244 {
245 QSqlQuery query(m_db);
246 if ( query.exec(QString("SELECT COUNT(*) FROM %1").arg(m_tableBasename)) ) {
247 if ( query.first() )
248 return query.value(0).toLongLong();
249 else
250 return -1;
251 } else {
252 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch row count from XML cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
253 return -1;
254 }
255 }
256
nextRowId(bool refreshRowIds)257 qint64 XmlDatabaseManager::nextRowId(bool refreshRowIds)
258 {
259 if ( refreshRowIds ) {
260 m_rowIdList.clear();
261 m_lastRowId = -1;
262 QSqlQuery query(m_db);
263 if ( query.exec(QString("SELECT rowid FROM %1").arg(m_tableBasename)) ) {
264 if ( query.first() ) {
265 do {
266 m_rowIdList << query.value(0).toLongLong();
267 } while ( query.next() );
268 m_lastRowId = 0;
269 return m_rowIdList[0];
270 }
271 } else
272 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch row IDs from XML cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
273 } else if ( m_lastRowId > -1 ) {
274 m_lastRowId++;
275 if ( m_lastRowId < m_rowIdList.count() )
276 return m_rowIdList[m_lastRowId];
277 }
278 return -1;
279 }
280
idAtIndex(int index)281 QString XmlDatabaseManager::idAtIndex(int index)
282 {
283 if ( index < 0 ) {
284 m_idAtIndexCache.clear();
285 QSqlQuery query(m_db);
286 if ( query.exec(QString("SELECT id FROM %1 ORDER BY rowid").arg(m_tableBasename)) ) {
287 if ( query.first() ) {
288 do {
289 m_idAtIndexCache << query.value(0).toString();
290 } while ( query.next() );
291 return m_idAtIndexCache[0];
292 } else
293 return QString();
294 } else {
295 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("id").arg(query.lastQuery()).arg(query.lastError().text()));
296 return QString();
297 }
298 } else {
299 if ( index < m_idAtIndexCache.count() )
300 return m_idAtIndexCache[index];
301 else
302 return QString();
303 }
304 }
305
exists(QString id)306 bool XmlDatabaseManager::exists(QString id)
307 {
308 QSqlQuery query(m_db);
309 query.prepare(QString("SELECT id FROM %1 WHERE id=:id LIMIT 1").arg(m_tableBasename));
310 query.bindValue(":id", id);
311 if ( query.exec() )
312 return query.first();
313 else {
314 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to fetch '%1' from XML cache database: query = '%2', error = '%3'").arg("id").arg(query.lastQuery()).arg(query.lastError().text()));
315 return false;
316 }
317 }
318
parentOf(QString id)319 QString XmlDatabaseManager::parentOf(QString id)
320 {
321 QString xmlString = xml(id);
322 if ( !xmlString.isEmpty() ) {
323 int startIndex = xmlString.indexOf("cloneof=\"");
324 if ( startIndex > 0 ) {
325 startIndex += 9;
326 int endIndex = xmlString.indexOf("\"", startIndex);
327 if ( endIndex >= 0 )
328 return xmlString.mid(startIndex, endIndex - startIndex);
329 else
330 return QString();
331 } else
332 return QString();
333 } else
334 return QString();
335 }
336
databaseSize()337 quint64 XmlDatabaseManager::databaseSize()
338 {
339 QSqlQuery query(m_db);
340 if ( query.exec("PRAGMA page_count") ) {
341 if ( query.first() ) {
342 quint64 page_count = query.value(0).toULongLong();
343 query.finish();
344 if ( query.exec("PRAGMA page_size") ) {
345 if ( query.first() ) {
346 quint64 page_size = query.value(0).toULongLong();
347 return page_count * page_size;
348 } else
349 return 0;
350 } else
351 return 0;
352 } else
353 return 0;
354 } else
355 return 0;
356 }
357
setCacheSize(quint64 kiloBytes)358 void XmlDatabaseManager::setCacheSize(quint64 kiloBytes)
359 {
360 QSqlQuery query(m_db);
361 if ( !query.exec(QString("PRAGMA cache_size = -%1").arg(kiloBytes)) )
362 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to change the '%1' setting for the XML cache database: query = '%2', error = '%3'").arg("cache_size").arg(query.lastQuery()).arg(query.lastError().text()));
363 }
364
setSyncMode(uint syncMode)365 void XmlDatabaseManager::setSyncMode(uint syncMode)
366 {
367 static QStringList dbSyncModes = QStringList() << "OFF" << "NORMAL" << "FULL";
368 if ( (int)syncMode > dbSyncModes.count() - 1 )
369 return;
370 QSqlQuery query(m_db);
371 if ( !query.exec(QString("PRAGMA synchronous = %1").arg(dbSyncModes[syncMode])) )
372 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to change the '%1' setting for the XML cache database: query = '%2', error = '%3'").arg("synchronous").arg(query.lastQuery()).arg(query.lastError().text()));
373 }
374
setJournalMode(uint journalMode)375 void XmlDatabaseManager::setJournalMode(uint journalMode)
376 {
377 static QStringList dbJournalModes = QStringList() << "DELETE" << "TRUNCATE" << "PERSIST" << "MEMORY" << "WAL" << "OFF";
378 if ( (int)journalMode > dbJournalModes.count() - 1 )
379 return;
380 QSqlQuery query(m_db);
381 if ( !query.exec(QString("PRAGMA journal_mode = %1").arg(dbJournalModes[journalMode])) )
382 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to change the '%1' setting for the XML cache database: query = '%2', error = '%3'").arg("journal_mode").arg(query.lastQuery()).arg(query.lastError().text()));
383 }
384
recreateDatabase()385 void XmlDatabaseManager::recreateDatabase()
386 {
387 QSqlQuery query(m_db);
388 if ( !query.exec(QString("DROP INDEX IF EXISTS %1_index").arg(m_tableBasename)) ) {
389 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to remove XML cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
390 return;
391 }
392 query.finish();
393 if ( !query.exec(QString("DROP TABLE IF EXISTS %1").arg(m_tableBasename)) ) {
394 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to remove XML cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
395 return;
396 }
397 query.finish();
398 if ( !query.exec(QString("DROP TABLE IF EXISTS %1_metadata").arg(m_tableBasename)) ) {
399 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to remove XML cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
400 return;
401 }
402 query.finish();
403 // vaccum'ing the database frees all disk-space previously used
404 query.exec("VACUUM");
405 query.finish();
406 if ( !query.exec(QString("CREATE TABLE %1 (id TEXT PRIMARY KEY, xml TEXT, CONSTRAINT %1_unique_id UNIQUE (id))").arg(m_tableBasename)) ) {
407 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to create XML cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
408 return;
409 }
410 query.finish();
411 if ( !query.exec(QString("CREATE INDEX %1_index ON %1 (id)").arg(m_tableBasename)) ) {
412 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to create XML cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
413 return;
414 }
415 query.finish();
416 if ( !query.exec(QString("CREATE TABLE %1_metadata (row INTEGER PRIMARY KEY, dtd TEXT, emu_version TEXT, qmc2_version TEXT, xmlcache_version INTEGER)").arg(m_tableBasename)) ) {
417 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: failed to create XML cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
418 return;
419 }
420 if ( logActive() )
421 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("XML cache database '%1' initialized").arg(m_db.databaseName()));
422 }
423