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