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