1 #include <QApplication>
2 #include <QSqlDriver>
3 #include <QSqlQuery>
4 #include <QSqlError>
5 #include <QDateTime>
6 #include <QDir>
7 #include <QUuid>
8 
9 #if defined(QMC2_ARCADE)
10 #include "arcade/arcadesettings.h"
11 #else
12 #include "options.h"
13 #endif
14 #include "settings.h"
15 #include "iconcachedbmgr.h"
16 #include "macros.h"
17 
18 // external global variables
19 #if defined(QMC2_ARCADE)
20 extern ArcadeSettings *globalConfig;
21 #else
22 extern Settings *qmc2Config;
23 #endif
24 
IconCacheDatabaseManager(QObject * parent)25 IconCacheDatabaseManager::IconCacheDatabaseManager(QObject *parent) :
26 	QObject(parent),
27 	m_logActive(false),
28 	m_resetRowCount(true),
29 	m_lastRowCount(-1),
30 	m_query(0)
31 {
32 	m_connectionName = QString("icon-db-connection-%1").arg(QUuid::createUuid().toString());
33 	m_db = QSqlDatabase::addDatabase("QSQLITE", m_connectionName);
34 #if defined(QMC2_ARCADE)
35 	m_db.setDatabaseName(globalConfig->iconCacheDatabaseName());
36 	m_tableBasename = QString("%1_icon_cache").arg(globalConfig->emulatorName().toLower());
37 #else
38 	m_db.setDatabaseName(qmc2Config->value(QMC2_FRONTEND_PREFIX + "FilesAndDirectories/IconCacheDatabase", QString(Options::configPath() + "/%1-icon-cache.db").arg(QMC2_EMU_NAME.toLower())).toString());
39 	m_tableBasename = QString("%1_icon_cache").arg(QMC2_EMU_NAME.toLower());
40 #endif
41 	if ( m_db.open() ) {
42 		QStringList tables(m_db.driver()->tables(QSql::Tables));
43 		if ( tables.count() < 2 || !tables.contains(m_tableBasename) || !tables.contains(QString("%1_metadata").arg(m_tableBasename)) )
44 			recreateDatabase();
45 	} else
46 		emit log(tr("WARNING: failed to open icon cache database '%1': error = '%2'").arg(m_db.databaseName()).arg(m_db.lastError().text()));
47 }
48 
~IconCacheDatabaseManager()49 IconCacheDatabaseManager::~IconCacheDatabaseManager()
50 {
51 	if ( m_query )
52 		delete m_query;
53 	if ( m_db.isOpen() )
54 		m_db.close();
55 }
56 
emulatorVersion()57 QString IconCacheDatabaseManager::emulatorVersion()
58 {
59 	QString emu_version;
60 	QSqlQuery query(m_db);
61 	query.prepare(QString("SELECT emu_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
62 	if ( query.exec() ) {
63 		if ( query.first() )
64 			emu_version = query.value(0).toString();
65 		query.finish();
66 	} else
67 		emit log(tr("WARNING: failed to fetch '%1' from icon cache database: query = '%2', error = '%3'").arg("emu_version").arg(query.lastQuery()).arg(query.lastError().text()));
68 	return emu_version;
69 }
70 
setEmulatorVersion(QString emu_version)71 void IconCacheDatabaseManager::setEmulatorVersion(QString emu_version)
72 {
73 	QSqlQuery query(m_db);
74 	query.prepare(QString("SELECT emu_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
75 	if ( query.exec() ) {
76 		if ( !query.next() ) {
77 			query.finish();
78 			query.prepare(QString("INSERT INTO %1_metadata (emu_version, row) VALUES (:emu_version, 0)").arg(m_tableBasename));
79 			query.bindValue(":emu_version", emu_version);
80 			if ( !query.exec() )
81 				emit log(tr("WARNING: failed to add '%1' to icon cache database: query = '%2', error = '%3'").arg("emu_version").arg(query.lastQuery()).arg(query.lastError().text()));
82 		} else {
83 			query.finish();
84 			query.prepare(QString("UPDATE %1_metadata SET emu_version=:emu_version WHERE row=0").arg(m_tableBasename));
85 			query.bindValue(":emu_version", emu_version);
86 			if ( !query.exec() )
87 				emit log(tr("WARNING: failed to update '%1' in icon cache database: query = '%2', error = '%3'").arg("emu_version").arg(query.lastQuery()).arg(query.lastError().text()));
88 		}
89 		query.finish();
90 	} else
91 		emit log(tr("WARNING: failed to fetch '%1' from icon cache database: query = '%2', error = '%3'").arg("emu_version").arg(query.lastQuery()).arg(query.lastError().text()));
92 }
93 
qmc2Version()94 QString IconCacheDatabaseManager::qmc2Version()
95 {
96 	QString qmc2_version;
97 	QSqlQuery query(m_db);
98 	query.prepare(QString("SELECT qmc2_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
99 	if ( query.exec() ) {
100 		if ( query.first() )
101 			qmc2_version = query.value(0).toString();
102 		query.finish();
103 	} else
104 		emit log(tr("WARNING: failed to fetch '%1' from icon cache database: query = '%2', error = '%3'").arg("qmc2_version").arg(query.lastQuery()).arg(query.lastError().text()));
105 	return qmc2_version;
106 }
107 
setQmc2Version(QString qmc2_version)108 void IconCacheDatabaseManager::setQmc2Version(QString qmc2_version)
109 {
110 	QSqlQuery query(m_db);
111 	query.prepare(QString("SELECT qmc2_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
112 	if ( query.exec() ) {
113 		if ( !query.next() ) {
114 			query.finish();
115 			query.prepare(QString("INSERT INTO %1_metadata (qmc2_version, row) VALUES (:qmc2_version, 0)").arg(m_tableBasename));
116 			query.bindValue(":qmc2_version", qmc2_version);
117 			if ( !query.exec() )
118 				emit log(tr("WARNING: failed to add '%1' to icon cache database: query = '%2', error = '%3'").arg("qmc2_version").arg(query.lastQuery()).arg(query.lastError().text()));
119 		} else {
120 			query.finish();
121 			query.prepare(QString("UPDATE %1_metadata SET qmc2_version=:qmc2_version WHERE row=0").arg(m_tableBasename));
122 			query.bindValue(":qmc2_version", qmc2_version);
123 			if ( !query.exec() )
124 				emit log(tr("WARNING: failed to update '%1' in icon cache database: query = '%2', error = '%3'").arg("qmc2_version").arg(query.lastQuery()).arg(query.lastError().text()));
125 		}
126 		query.finish();
127 	} else
128 		emit log(tr("WARNING: failed to fetch '%1' from icon cache database: query = '%2', error = '%3'").arg("qmc2_version").arg(query.lastQuery()).arg(query.lastError().text()));
129 }
130 
iconCacheVersion()131 int IconCacheDatabaseManager::iconCacheVersion()
132 {
133 	int icon_cache_version = -1;
134 	QSqlQuery query(m_db);
135 	query.prepare(QString("SELECT icon_cache_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
136 	if ( query.exec() ) {
137 		if ( query.first() )
138 			icon_cache_version = query.value(0).toInt();
139 		query.finish();
140 	} else
141 		emit log(tr("WARNING: failed to fetch '%1' from icon cache database: query = '%2', error = '%3'").arg("icon_cache_version").arg(query.lastQuery()).arg(query.lastError().text()));
142 	return icon_cache_version;
143 }
144 
setIconCacheVersion(int icon_cache_version)145 void IconCacheDatabaseManager::setIconCacheVersion(int icon_cache_version)
146 {
147 	QSqlQuery query(m_db);
148 	query.prepare(QString("SELECT icon_cache_version FROM %1_metadata WHERE row=0").arg(m_tableBasename));
149 	if ( query.exec() ) {
150 		if ( !query.next() ) {
151 			query.finish();
152 			query.prepare(QString("INSERT INTO %1_metadata (icon_cache_version, row) VALUES (:icon_cache_version, 0)").arg(m_tableBasename));
153 			query.bindValue(":icon_cache_version", icon_cache_version);
154 			if ( !query.exec() )
155 				emit log(tr("WARNING: failed to add '%1' to icon cache database: query = '%2', error = '%3'").arg("icon_cache_version").arg(query.lastQuery()).arg(query.lastError().text()));
156 		} else {
157 			query.finish();
158 			query.prepare(QString("UPDATE %1_metadata SET icon_cache_version=:icon_cache_version WHERE row=0").arg(m_tableBasename));
159 			query.bindValue(":icon_cache_version", icon_cache_version);
160 			if ( !query.exec() )
161 				emit log(tr("WARNING: failed to update '%1' in icon cache database: query = '%2', error = '%3'").arg("icon_cache_version").arg(query.lastQuery()).arg(query.lastError().text()));
162 		}
163 		query.finish();
164 	} else
165 		emit log(tr("WARNING: failed to fetch '%1' from icon cache database: query = '%2', error = '%3'").arg("icon_cache_version").arg(query.lastQuery()).arg(query.lastError().text()));
166 }
167 
iconCacheRowCount(bool reset)168 qint64 IconCacheDatabaseManager::iconCacheRowCount(bool reset)
169 {
170 	m_resetRowCount |= reset;
171 	if ( m_resetRowCount ) {
172 		QSqlQuery query(m_db);
173 		if ( query.exec(QString("SELECT COUNT(*) FROM %1").arg(m_tableBasename)) ) {
174 			if ( query.first() )
175 				m_lastRowCount = query.value(0).toLongLong();
176 			else
177 				m_lastRowCount = -1;
178 		} else {
179 			emit log(tr("WARNING: failed to fetch row count from icon cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
180 			m_lastRowCount = -1;
181 		}
182 		m_resetRowCount = false;
183 	}
184 	return m_lastRowCount;
185 }
186 
isEmpty()187 bool IconCacheDatabaseManager::isEmpty()
188 {
189 	QSqlQuery query(m_db);
190 	if ( query.exec(QString("SELECT * FROM %1 LIMIT 1").arg(m_tableBasename)) )
191 		return !query.first();
192 	else
193 		return true;
194 }
195 
importRequired(const QStringList & pathList)196 bool IconCacheDatabaseManager::importRequired(const QStringList &pathList)
197 {
198 #if defined(QMC2_ARCADE)
199 	QStringList importPaths(globalConfig->iconCacheImportPaths());
200 	QStringList importDates(globalConfig->iconCacheImportDates());
201 #else
202 	QStringList importPaths(qmc2Config->value(QMC2_EMULATOR_PREFIX + "IconCacheDatabase/ImportPaths", QStringList()).toStringList());
203 	QStringList importDates(qmc2Config->value(QMC2_EMULATOR_PREFIX + "IconCacheDatabase/ImportDates", QStringList()).toStringList());
204 #endif
205 	if ( importPaths.isEmpty() || importDates.isEmpty() )
206 		return true;
207 	if ( importPaths.count() != importDates.count() )
208 		return true;
209 	bool importPathsChanged = false;
210 	foreach (QString path, pathList) {
211 		if ( !importPaths.contains(path) )
212 			importPathsChanged = true;
213 		if ( importPathsChanged )
214 			break;
215 	}
216 	if ( !importPathsChanged ) {
217 		bool datesChanged = false;
218 		foreach (QString path, pathList) {
219 			QFileInfo fi(path);
220 			uint dtImport = importDates.at(importPaths.indexOf(path)).toUInt();
221 			if ( dtImport < fi.lastModified().toTime_t() )
222 				datesChanged = true;
223 			if ( datesChanged )
224 				break;
225 		}
226 		if ( datesChanged )
227 			return true;
228 		else
229 			return iconCacheRowCount() == 0;
230 	} else
231 		return true;
232 }
233 
queryIconData()234 void IconCacheDatabaseManager::queryIconData()
235 {
236 	if ( !m_query )
237 		m_query = new QSqlQuery(m_db);
238 	m_query->clear();
239 	m_query->prepare(QString("SELECT id, icon_data FROM %1").arg(m_tableBasename));
240 	m_query->exec();
241 }
242 
nextIconData(QString * id,QByteArray * icon_data)243 bool IconCacheDatabaseManager::nextIconData(QString *id, QByteArray *icon_data)
244 {
245 	if ( m_query->next() ) {
246 		*id = m_query->value(QMC2_ICDB_INDEX_ID).toString();
247 		*icon_data = m_query->value(QMC2_ICDB_INDEX_ICON_DATA).toByteArray();
248 		return true;
249 	} else
250 		return false;
251 }
252 
setIconData(const QString & id,const QByteArray & icon_data)253 void IconCacheDatabaseManager::setIconData(const QString &id, const QByteArray &icon_data)
254 {
255 	QSqlQuery query(m_db);
256 	query.prepare(QString("INSERT INTO %1 (id, icon_data) VALUES (:id, :icon_data)").arg(m_tableBasename));
257 	query.bindValue(":id", id);
258 	query.bindValue(":icon_data", icon_data);
259 	if ( !query.exec() )
260 		emit log(tr("WARNING: failed to add '%1' to icon cache database: query = '%2', error = '%3'").arg(id).arg(query.lastQuery()).arg(query.lastError().text()));
261 }
262 
iconData(const QString & id)263 QByteArray IconCacheDatabaseManager::iconData(const QString &id)
264 {
265 	QSqlQuery query(m_db);
266 	query.prepare(QString("SELECT icon_data FROM %1 WHERE id=:id LIMIT 1").arg(m_tableBasename));
267 	query.bindValue(":id", id);
268 	if ( query.exec() )
269 		if ( query.first() )
270 			return query.value(0).toByteArray();
271 		else
272 			return QByteArray();
273 	else {
274 		emit log(tr("WARNING: failed to fetch '%1' from icon cache database: query = '%2', error = '%3'").arg("icon_data").arg(query.lastQuery()).arg(query.lastError().text()));
275 		return QByteArray();
276 	}
277 }
278 
exists(const QString & id)279 bool IconCacheDatabaseManager::exists(const QString &id)
280 {
281 	QSqlQuery query(m_db);
282 	query.prepare(QString("SELECT id FROM %1 WHERE id=:id LIMIT 1").arg(m_tableBasename));
283 	query.bindValue(":id", id);
284 	if ( query.exec() )
285 		return query.first();
286 	else {
287 		emit log(tr("WARNING: failed to fetch '%1' from icon cache database: query = '%2', error = '%3'").arg("id").arg(query.lastQuery()).arg(query.lastError().text()));
288 		return false;
289 	}
290 }
291 
databaseSize()292 quint64 IconCacheDatabaseManager::databaseSize()
293 {
294 	QSqlQuery query(m_db);
295 	if ( query.exec("PRAGMA page_count") ) {
296 		if ( query.first() ) {
297 			quint64 page_count = query.value(0).toULongLong();
298 			query.finish();
299 			if ( query.exec("PRAGMA page_size") ) {
300 				if ( query.first() ) {
301 					quint64 page_size = query.value(0).toULongLong();
302 					return page_count * page_size;
303 				} else
304 					return 0;
305 			} else
306 				return 0;
307 		} else
308 			return 0;
309 	} else
310 		return 0;
311 }
312 
setCacheSize(quint64 kiloBytes)313 void IconCacheDatabaseManager::setCacheSize(quint64 kiloBytes)
314 {
315 	QSqlQuery query(m_db);
316 	if ( !query.exec(QString("PRAGMA cache_size = -%1").arg(kiloBytes)) )
317 		emit log(tr("WARNING: failed to change the '%1' setting for the icon cache database: query = '%2', error = '%3'").arg("cache_size").arg(query.lastQuery()).arg(query.lastError().text()));
318 }
319 
setSyncMode(uint syncMode)320 void IconCacheDatabaseManager::setSyncMode(uint syncMode)
321 {
322 	static QStringList dbSyncModes = QStringList() << "OFF" << "NORMAL" << "FULL";
323 	if ( (int)syncMode > dbSyncModes.count() - 1 )
324 		return;
325 	QSqlQuery query(m_db);
326 	if ( !query.exec(QString("PRAGMA synchronous = %1").arg(dbSyncModes.at(syncMode))) )
327 		emit log(tr("WARNING: failed to change the '%1' setting for the icon cache database: query = '%2', error = '%3'").arg("synchronous").arg(query.lastQuery()).arg(query.lastError().text()));
328 }
329 
setJournalMode(uint journalMode)330 void IconCacheDatabaseManager::setJournalMode(uint journalMode)
331 {
332 	static QStringList dbJournalModes = QStringList() << "DELETE" << "TRUNCATE" << "PERSIST" << "MEMORY" << "WAL" << "OFF";
333 	if ( (int)journalMode > dbJournalModes.count() - 1 )
334 		return;
335 	QSqlQuery query(m_db);
336 	if ( !query.exec(QString("PRAGMA journal_mode = %1").arg(dbJournalModes.at(journalMode))) )
337 		emit log(tr("WARNING: failed to change the '%1' setting for the icon cache database: query = '%2', error = '%3'").arg("journal_mode").arg(query.lastQuery()).arg(query.lastError().text()));
338 }
339 
recreateDatabase()340 void IconCacheDatabaseManager::recreateDatabase()
341 {
342 	QSqlQuery query(m_db);
343 	if ( !query.exec(QString("DROP INDEX IF EXISTS %1_index").arg(m_tableBasename)) ) {
344 		emit log(tr("WARNING: failed to remove icon cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
345 		return;
346 	}
347 	query.finish();
348 	if ( !query.exec(QString("DROP TABLE IF EXISTS %1").arg(m_tableBasename)) ) {
349 		emit log(tr("WARNING: failed to remove icon cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
350 		return;
351 	}
352 	query.finish();
353 	if ( !query.exec(QString("DROP TABLE IF EXISTS %1_metadata").arg(m_tableBasename)) ) {
354 		emit log(tr("WARNING: failed to remove icon cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
355 		return;
356 	}
357 	query.finish();
358 	// vaccum'ing the database frees all disk-space previously used
359 	query.exec("VACUUM");
360 	query.finish();
361 	if ( !query.exec(QString("CREATE TABLE %1 (id TEXT PRIMARY KEY, icon_data BLOB, CONSTRAINT %1_unique_id UNIQUE (id))").arg(m_tableBasename)) ) {
362 		emit log(tr("WARNING: failed to create icon cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
363 		return;
364 	}
365 	query.finish();
366 	if ( !query.exec(QString("CREATE INDEX %1_index ON %1 (id)").arg(m_tableBasename)) ) {
367 		emit log(tr("WARNING: failed to create icon cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
368 		return;
369 	}
370 	query.finish();
371 	if ( !query.exec(QString("CREATE TABLE %1_metadata (row INTEGER PRIMARY KEY, emu_version TEXT, qmc2_version TEXT, icon_cache_version INTEGER)").arg(m_tableBasename)) ) {
372 		emit log(tr("WARNING: failed to create icon cache database: query = '%1', error = '%2'").arg(query.lastQuery()).arg(query.lastError().text()));
373 		return;
374 	}
375 	if ( logActive() )
376 		emit log(tr("icon cache database '%1' initialized").arg(m_db.databaseName()));
377 }
378