1 #include <QApplication>
2 #include <QTest>
3 #include <QFont>
4 #include <QFontInfo>
5 #include <QDateTime>
6 #include <QDir>
7 #include <QFile>
8 #include <QFileInfo>
9 #include <QFileDialog>
10 #include <QMap>
11 #include <QMultiMap>
12 #include <QDateTime>
13 #include <QMessageBox>
14 #include <QScrollBar>
15 #include <QChar>
16
17 #include "collectionrebuilder.h"
18 #include "settings.h"
19 #include "options.h"
20 #include "unzip.h"
21 #include "zip.h"
22 #include "sevenzipfile.h"
23 #if defined(QMC2_LIBARCHIVE_ENABLED)
24 #include "archivefile.h"
25 #endif
26 #include "machinelist.h"
27 #include "macros.h"
28 #include "softwarelist.h"
29
30 #if defined(QMC2_OS_WIN)
31 #include <windows.h>
32 #else
33 #include <unistd.h>
34 #endif
35 #include <sys/stat.h>
36 #include <sys/types.h>
37
38 extern Settings *qmc2Config;
39 extern Options *qmc2Options;
40 extern MachineList *qmc2MachineList;
41 extern SoftwareList *qmc2SoftwareList;
42
43 QHash<QString, QString> CollectionRebuilderThread::m_replacementHash;
44 QStringList CollectionRebuilderThread::m_fileTypes;
45
CollectionRebuilder(ROMAlyzer * myROMAlyzer,QWidget * parent)46 CollectionRebuilder::CollectionRebuilder(ROMAlyzer *myROMAlyzer, QWidget *parent) :
47 QWidget(parent),
48 m_romAlyzer(myROMAlyzer)
49 {
50 setupUi(this);
51
52 switch ( romAlyzer()->mode() ) {
53 case QMC2_ROMALYZER_MODE_SOFTWARE:
54 m_settingsKey = "SoftwareCollectionRebuilder";
55 m_defaultSetEntity = "software";
56 m_defaultRomEntity = "rom";
57 m_defaultDiskEntity = "disk";
58 checkBoxFilterExpressionSoftwareLists->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UseFilterExpressionSoftwareLists", false).toBool());
59 comboBoxFilterSyntaxSoftwareLists->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterSyntaxSoftwareLists", 0).toInt());
60 comboBoxFilterTypeSoftwareLists->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterTypeSoftwareLists", 0).toInt());
61 toolButtonExactMatchSoftwareLists->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExactMatchSoftwareLists", false).toBool());
62 lineEditFilterExpressionSoftwareLists->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterExpressionSoftwareLists", QString()).toString());
63 checkBoxFilterExpression->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UseFilterExpression", false).toBool());
64 comboBoxFilterSyntax->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterSyntax", 0).toInt());
65 comboBoxFilterType->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterType", 0).toInt());
66 toolButtonExactMatch->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExactMatch", false).toBool());
67 lineEditFilterExpression->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterExpression", QString()).toString());
68 m_correctIconPixmap = QPixmap(QString::fromUtf8(":/data/img/software_correct.png"));
69 m_mostlyCorrectIconPixmap = QPixmap(QString::fromUtf8(":/data/img/software_mostlycorrect.png"));
70 m_incorrectIconPixmap = QPixmap(QString::fromUtf8(":/data/img/software_incorrect.png"));
71 m_notFoundIconPixmap = QPixmap(QString::fromUtf8(":/data/img/software_notfound.png"));
72 m_unknownIconPixmap = QPixmap(QString::fromUtf8(":/data/img/software_unknown.png"));
73 hideStateFilter(); // FIXME: add software-state filtering
74 break;
75 case QMC2_ROMALYZER_MODE_SYSTEM:
76 default:
77 m_settingsKey = "CollectionRebuilder";
78 m_defaultSetEntity = "machine";
79 m_defaultRomEntity = "rom";
80 m_defaultDiskEntity = "disk";
81 checkBoxFilterExpressionSoftwareLists->setVisible(false);
82 comboBoxFilterSyntaxSoftwareLists->setVisible(false);
83 comboBoxFilterTypeSoftwareLists->setVisible(false);
84 lineEditFilterExpressionSoftwareLists->setVisible(false);
85 toolButtonClearFilterExpressionSoftwareLists->setVisible(false);
86 toolButtonExactMatchSoftwareLists->setVisible(false);
87 checkBoxFilterExpression->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UseFilterExpression", false).toBool());
88 comboBoxFilterSyntax->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterSyntax", 0).toInt());
89 comboBoxFilterType->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterType", 0).toInt());
90 toolButtonExactMatch->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExactMatch", false).toBool());
91 lineEditFilterExpression->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterExpression", QString()).toString());
92 m_correctIconPixmap = QPixmap(QString::fromUtf8(":/data/img/sphere_green.png"));
93 m_mostlyCorrectIconPixmap = QPixmap(QString::fromUtf8(":/data/img/sphere_yellowgreen.png"));
94 m_incorrectIconPixmap = QPixmap(QString::fromUtf8(":/data/img/sphere_red.png"));
95 m_notFoundIconPixmap = QPixmap(QString::fromUtf8(":/data/img/sphere_grey.png"));
96 m_unknownIconPixmap = QPixmap(QString::fromUtf8(":/data/img/sphere_blue.png"));
97 break;
98 }
99 checkBoxFilterStates->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UseStateFilter", false).toBool());
100 toolButtonStateC->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateC", true).toBool());
101 toolButtonStateM->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateM", true).toBool());
102 toolButtonStateI->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateI", true).toBool());
103 toolButtonStateN->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateN", true).toBool());
104 toolButtonStateU->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateU", true).toBool());
105
106 pushButtonPauseResume->setVisible(false);
107 setIgnoreCheckpoint(false);
108
109 QFont logFont;
110 logFont.fromString(qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/LogFont").toString());
111 plainTextEditLog->setFont(logFont);
112 spinBoxMaxLogSize->setValue(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxLogSize", 10000).toInt());
113 checkBoxEnableLog->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableLog", true).toBool());
114 adjustIconSizes();
115
116 m_rebuilderThread = new CollectionRebuilderThread(this);
117 connect(rebuilderThread(), SIGNAL(log(const QString &)), this, SLOT(log(const QString &)));
118 connect(rebuilderThread(), SIGNAL(rebuildStarted()), this, SLOT(rebuilderThread_rebuildStarted()));
119 connect(rebuilderThread(), SIGNAL(rebuildFinished()), this, SLOT(rebuilderThread_rebuildFinished()));
120 connect(rebuilderThread(), SIGNAL(rebuildPaused()), this, SLOT(rebuilderThread_rebuildPaused()));
121 connect(rebuilderThread(), SIGNAL(rebuildResumed()), this, SLOT(rebuilderThread_rebuildResumed()));
122 connect(rebuilderThread(), SIGNAL(progressTextChanged(const QString &)), this, SLOT(rebuilderThread_progressTextChanged(const QString &)));
123 connect(rebuilderThread(), SIGNAL(progressRangeChanged(int, int)), this, SLOT(rebuilderThread_progressRangeChanged(int, int)));
124 connect(rebuilderThread(), SIGNAL(progressChanged(int)), this, SLOT(rebuilderThread_progressChanged(int)));
125 connect(rebuilderThread(), SIGNAL(statusUpdated(quint64, quint64, quint64)), this, SLOT(rebuilderThread_statusUpdated(quint64, quint64, quint64)));
126 connect(rebuilderThread(), SIGNAL(newMissing(QString, QString, QString, QString, QString, QString, QString)), this, SLOT(rebuilderThread_newMissing(QString, QString, QString, QString, QString, QString, QString)));
127
128 m_missingDumpsViewer = 0;
129 setDefaultEmulator(true);
130
131 m_iconCheckpoint = QIcon(QString::fromUtf8(":/data/img/checkpoint.png"));
132 m_iconNoCheckpoint = QIcon(QString::fromUtf8(":/data/img/no_checkpoint.png"));
133
134 m_animationSequence = 0;
135 connect(&m_animationTimer, SIGNAL(timeout()), this, SLOT(animationTimer_timeout()));
136
137 frameEntities->setVisible(false);
138 toolButtonRemoveXmlSource->setVisible(false);
139 comboBoxXmlSource->blockSignals(true);
140 comboBoxXmlSource->clear();
141 comboBoxXmlSource->insertItem(0, tr("Current default emulator"));
142 if ( qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/Checkpoint", -1).toLongLong() >= 0 ) {
143 comboBoxXmlSource->setItemIcon(0, m_iconCheckpoint);
144 comboBoxXmlSource->setItemData(0, true);
145 } else {
146 comboBoxXmlSource->setItemIcon(0, m_iconNoCheckpoint);
147 comboBoxXmlSource->setItemData(0, false);
148 }
149 QStringList xmlSources = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSources", QStringList()).toStringList();
150 QStringList setEntities = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", QStringList()).toStringList();
151 QStringList romEntities = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", QStringList()).toStringList();
152 QStringList diskEntities = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", QStringList()).toStringList();
153 QStringList checkpointList = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints", QStringList()).toStringList();
154 QStringList softwareCheckpointList = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints", QStringList()).toStringList();
155 bool softwareCheckpointListOk = romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ? (softwareCheckpointList.count() == xmlSources.count()) : true;
156 QList<qint64> checkpoints;
157 foreach (QString cp, checkpointList)
158 checkpoints << cp.toLongLong();
159 int index = 1;
160 if ( xmlSources.count() > 0 && setEntities.count() == xmlSources.count() && romEntities.count() == xmlSources.count() && diskEntities.count() == xmlSources.count() && checkpointList.count() == xmlSources.count() && softwareCheckpointListOk ) {
161 for (int i = 0; i < xmlSources.count(); i++) {
162 QString xmlSource(xmlSources.at(i));
163 QFileInfo fi(xmlSource);
164 if ( fi.exists() && fi.isReadable() ) {
165 comboBoxXmlSource->insertItem(index, xmlSource);
166 if ( checkpoints[i] >= 0 ) {
167 comboBoxXmlSource->setItemIcon(index, m_iconCheckpoint);
168 comboBoxXmlSource->setItemData(index, true);
169 } else {
170 comboBoxXmlSource->setItemIcon(index, m_iconNoCheckpoint);
171 comboBoxXmlSource->setItemData(index, false);
172 }
173 index++;
174 } else {
175 xmlSources.removeAt(i);
176 setEntities.removeAt(i);
177 romEntities.removeAt(i);
178 diskEntities.removeAt(i);
179 checkpointList.removeAt(i);
180 checkpoints.removeAt(i);
181 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE )
182 softwareCheckpointList.removeAt(i);
183 i--;
184 }
185 }
186 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSources", xmlSources);
187 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", setEntities);
188 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", romEntities);
189 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", diskEntities);
190 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints", checkpointList);
191 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE )
192 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints", softwareCheckpointList);
193 } else {
194 qmc2Config->remove(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSources");
195 qmc2Config->remove(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities");
196 qmc2Config->remove(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities");
197 qmc2Config->remove(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities");
198 qmc2Config->remove(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints");
199 qmc2Config->remove(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints");
200 }
201 comboBoxXmlSource->insertSeparator(index);
202 index++;
203 comboBoxXmlSource->insertItem(index, tr("Select XML file..."));
204 comboBoxXmlSource->setItemIcon(index, QIcon(QString::fromUtf8(":/data/img/fileopen.png")));
205 comboBoxXmlSource->setCurrentIndex(0);
206 comboBoxXmlSource->blockSignals(false);
207 lineEditSetEntity->setText(m_defaultSetEntity);
208 lineEditRomEntity->setText(m_defaultRomEntity);
209 lineEditDiskEntity->setText(m_defaultDiskEntity);
210 rebuilderThread_statusUpdated(0, 0, 0);
211 comboBoxXmlSource->setFocus();
212 }
213
~CollectionRebuilder()214 CollectionRebuilder::~CollectionRebuilder()
215 {
216 hideEvent(0);
217 if ( missingDumpsViewer() )
218 delete missingDumpsViewer();
219 if ( rebuilderThread() )
220 delete rebuilderThread();
221 }
222
on_spinBoxMaxLogSize_valueChanged(int value)223 void CollectionRebuilder::on_spinBoxMaxLogSize_valueChanged(int value)
224 {
225 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxLogSize", value);
226 plainTextEditLog->setMaximumBlockCount(value);
227 }
228
log(const QString & message)229 void CollectionRebuilder::log(const QString &message)
230 {
231 if ( checkBoxEnableLog->isChecked() )
232 plainTextEditLog->appendPlainText(QDateTime::currentDateTime().toString("hh:mm:ss.zzz") + ": " + message);
233 }
234
scrollToEnd()235 void CollectionRebuilder::scrollToEnd()
236 {
237 plainTextEditLog->horizontalScrollBar()->setValue(plainTextEditLog->horizontalScrollBar()->minimum());
238 plainTextEditLog->verticalScrollBar()->setValue(plainTextEditLog->verticalScrollBar()->maximum());
239 }
240
adjustIconSizes()241 void CollectionRebuilder::adjustIconSizes()
242 {
243 QFont f;
244 f.fromString(qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/Font").toString());
245 QFontMetrics fm(f);
246 QSize iconSize = QSize(fm.height() - 2, fm.height() - 2);
247
248 pushButtonStartStop->setIconSize(iconSize);
249 pushButtonPauseResume->setIconSize(iconSize);
250 comboBoxXmlSource->setIconSize(iconSize);
251 toolButtonRemoveXmlSource->setIconSize(iconSize);
252 toolButtonViewMissingList->setIconSize(iconSize);
253 toolButtonExactMatch->setIconSize(iconSize);
254 toolButtonClearFilterExpression->setIconSize(iconSize);
255 toolButtonExactMatchSoftwareLists->setIconSize(iconSize);
256 toolButtonClearFilterExpressionSoftwareLists->setIconSize(iconSize);
257
258 QSize iconSizeMiddle = QSize(fm.height(), fm.height());
259 toolButtonStateC->setIcon(QIcon(m_correctIconPixmap.scaled(iconSizeMiddle, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
260 toolButtonStateM->setIcon(QIcon(m_mostlyCorrectIconPixmap.scaled(iconSizeMiddle, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
261 toolButtonStateI->setIcon(QIcon(m_incorrectIconPixmap.scaled(iconSizeMiddle, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
262 toolButtonStateN->setIcon(QIcon(m_notFoundIconPixmap.scaled(iconSizeMiddle, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
263 toolButtonStateU->setIcon(QIcon(m_unknownIconPixmap.scaled(iconSizeMiddle, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
264 }
265
on_pushButtonStartStop_clicked()266 void CollectionRebuilder::on_pushButtonStartStop_clicked()
267 {
268 pushButtonStartStop->setEnabled(false);
269 pushButtonStartStop->update();
270 pushButtonPauseResume->setEnabled(false);
271 pushButtonPauseResume->update();
272 qApp->processEvents();
273 if ( rebuilderThread()->isActive )
274 rebuilderThread()->stopRebuilding = true;
275 else if ( rebuilderThread()->isWaiting ) {
276 newMissingList().clear();
277 if ( comboBoxXmlSource->itemData(comboBoxXmlSource->currentIndex()).toBool() ) {
278 if ( !ignoreCheckpoint() ) {
279 switch ( QMessageBox::question(this, tr("Confirm checkpoint restart"), tr("Restart from stored checkpoint?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No) ) {
280 case QMessageBox::Yes: {
281 qint64 cp = 0;
282 int index = comboBoxXmlSource->currentIndex();
283 if ( index == 0 )
284 cp = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/Checkpoint", -1).toLongLong();
285 else {
286 index -= 1;
287 QStringList checkpointList(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints", QStringList()).toStringList());
288 QStringList softwareCheckpointList(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints", QStringList()).toStringList());
289 if ( index >= 0 && index < checkpointList.count() ) {
290 cp = checkpointList[index].toLongLong();
291 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE )
292 rebuilderThread()->setListCheckpoint(softwareCheckpointList[index], index);
293 } else {
294 cp = -1;
295 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE )
296 rebuilderThread()->setListCheckpoint(QString(), index);
297 }
298 }
299 setDefaultEmulator(comboBoxXmlSource->currentIndex() == 0);
300 setIgnoreCheckpoint(false);
301 rebuilderThread()->checkpointRestart(cp);
302 }
303 break;
304 case QMessageBox::No:
305 rebuilderThread()->setCheckpoint(-1, comboBoxXmlSource->currentIndex());
306 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE )
307 rebuilderThread()->setListCheckpoint(QString(), comboBoxXmlSource->currentIndex());
308 setDefaultEmulator(comboBoxXmlSource->currentIndex() == 0);
309 setIgnoreCheckpoint(false);
310 break;
311 case QMessageBox::Cancel:
312 default:
313 pushButtonStartStop->setEnabled(true);
314 pushButtonPauseResume->setEnabled(true);
315 return;
316 }
317 } else {
318 rebuilderThread()->setCheckpoint(-1, comboBoxXmlSource->currentIndex());
319 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE )
320 rebuilderThread()->setListCheckpoint(QString(), comboBoxXmlSource->currentIndex());
321 setDefaultEmulator(comboBoxXmlSource->currentIndex() == 0);
322 setIgnoreCheckpoint(false);
323 }
324 } else {
325 rebuilderThread()->setCheckpoint(-1, comboBoxXmlSource->currentIndex());
326 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE )
327 rebuilderThread()->setListCheckpoint(QString(), comboBoxXmlSource->currentIndex());
328 setDefaultEmulator(comboBoxXmlSource->currentIndex() == 0);
329 setIgnoreCheckpoint(false);
330 }
331 if ( missingDumpsViewer() ) {
332 missingDumpsViewer()->setDefaultEmulator(defaultEmulator());
333 // FIXME "non-default emulator"
334 //missingDumpsViewer()->frameExport->setVisible(romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ? false : true);
335 missingDumpsViewer()->frameExport->setVisible(romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ? false : defaultEmulator());
336 }
337 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ) {
338 if ( checkBoxFilterExpressionSoftwareLists->isChecked() && !lineEditFilterExpressionSoftwareLists->text().isEmpty() )
339 rebuilderThread()->setFilterExpressionSoftware(lineEditFilterExpressionSoftwareLists->text(), comboBoxFilterSyntaxSoftwareLists->currentIndex(), comboBoxFilterTypeSoftwareLists->currentIndex(), toolButtonExactMatchSoftwareLists->isChecked());
340 else
341 rebuilderThread()->clearFilterExpressionSoftware();
342 }
343 if ( checkBoxFilterExpression->isChecked() && !lineEditFilterExpression->text().isEmpty() )
344 rebuilderThread()->setFilterExpression(lineEditFilterExpression->text(), comboBoxFilterSyntax->currentIndex(), comboBoxFilterType->currentIndex(), toolButtonExactMatch->isChecked());
345 else
346 rebuilderThread()->clearFilterExpression();
347 if ( checkBoxFilterStates->isChecked() && defaultEmulator() )
348 rebuilderThread()->setStateFilter(true, toolButtonStateC->isChecked(), toolButtonStateM->isChecked(), toolButtonStateI->isChecked(), toolButtonStateN->isChecked(), toolButtonStateU->isChecked());
349 else
350 rebuilderThread()->clearStateFilter();
351 rebuilderThread()->useHashCache = romAlyzer()->checkBoxCollectionRebuilderHashCache->isChecked();
352 rebuilderThread()->dryRun = romAlyzer()->checkBoxCollectionRebuilderDryRun->isChecked();
353 rebuilderThread()->waitCondition.wakeAll();
354 }
355 }
356
on_pushButtonPauseResume_clicked()357 void CollectionRebuilder::on_pushButtonPauseResume_clicked()
358 {
359 pushButtonPauseResume->setEnabled(false);
360 if ( rebuilderThread()->isPaused )
361 QTimer::singleShot(0, rebuilderThread(), SLOT(resume()));
362 else
363 QTimer::singleShot(0, rebuilderThread(), SLOT(pause()));
364 }
365
setStateFilterVisibility(bool visible)366 void CollectionRebuilder::setStateFilterVisibility(bool visible)
367 {
368 checkBoxFilterStates->setVisible(visible);
369 toolButtonStateC->setVisible(visible);
370 toolButtonStateM->setVisible(visible);
371 toolButtonStateI->setVisible(visible);
372 toolButtonStateN->setVisible(visible);
373 toolButtonStateU->setVisible(visible);
374 }
375
on_comboBoxXmlSource_currentIndexChanged(int index)376 void CollectionRebuilder::on_comboBoxXmlSource_currentIndexChanged(int index)
377 {
378 static int lastIndex = -1;
379
380 if ( index == 0 ) {
381 if ( lastIndex >= 0 ) {
382 QStringList setEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", QStringList()).toStringList());
383 QStringList romEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", QStringList()).toStringList());
384 QStringList diskEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", QStringList()).toStringList());
385 if ( lastIndex < setEntities.count() ) {
386 setEntities.replace(lastIndex, lineEditSetEntity->text());
387 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", setEntities);
388 }
389 if ( lastIndex < romEntities.count() ) {
390 romEntities.replace(lastIndex, lineEditRomEntity->text());
391 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", romEntities);
392 }
393 if ( lastIndex < diskEntities.count() ) {
394 diskEntities.replace(lastIndex, lineEditDiskEntity->text());
395 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", diskEntities);
396 }
397 }
398 lineEditSetEntity->setText(m_defaultSetEntity);
399 lineEditRomEntity->setText(m_defaultRomEntity);
400 lineEditDiskEntity->setText(m_defaultDiskEntity);
401 frameEntities->setVisible(false);
402 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SYSTEM )
403 showStateFilter(); // FIXME: add software-state filteirng
404 QTimer::singleShot(0, this, SLOT(scrollToEnd()));
405 toolButtonRemoveXmlSource->setVisible(false);
406 lastIndex = -1;
407 } else if ( index == comboBoxXmlSource->count() - 1 ) {
408 QString xmlSource(QFileDialog::getOpenFileName(this, tr("Choose source XML file"), QString(), tr("Data and XML files (*.dat *.xml)") + ";;" + tr("Data files (*.dat)") + ";;" + tr("XML files (*.xml)") + ";;" + tr("All files (*)"), 0, qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog));
409 if ( !xmlSource.isNull() ) {
410 int foundAtIndex = comboBoxXmlSource->findText(xmlSource);
411 if ( foundAtIndex < 0 ) {
412 comboBoxXmlSource->blockSignals(true);
413 QStringList xmlSources(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSources", QStringList()).toStringList());
414 xmlSources << xmlSource;
415 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSources", xmlSources);
416 QStringList setEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", QStringList()).toStringList());
417 setEntities << m_defaultSetEntity;
418 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", setEntities);
419 lineEditSetEntity->setText(m_defaultSetEntity);
420 QStringList romEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", QStringList()).toStringList());
421 romEntities << m_defaultRomEntity;
422 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", romEntities);
423 lineEditRomEntity->setText(m_defaultRomEntity);
424 QStringList diskEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", QStringList()).toStringList());
425 diskEntities << m_defaultDiskEntity;
426 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", diskEntities);
427 lineEditDiskEntity->setText(m_defaultDiskEntity);
428 QStringList checkpointList(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints", QStringList()).toStringList());
429 checkpointList << "-1";
430 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints", checkpointList);
431 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ) {
432 QStringList softwareCheckpointList(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints", QStringList()).toStringList());
433 softwareCheckpointList << QString();
434 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints", softwareCheckpointList);
435 }
436 int insertIndex = comboBoxXmlSource->count() - 2;
437 lastIndex = insertIndex - 1;
438 comboBoxXmlSource->insertItem(insertIndex, xmlSource);
439 comboBoxXmlSource->setItemIcon(insertIndex, m_iconNoCheckpoint);
440 comboBoxXmlSource->setItemData(insertIndex, false);
441 comboBoxXmlSource->setCurrentIndex(insertIndex);
442 comboBoxXmlSource->blockSignals(false);
443 frameEntities->setVisible(true);
444 hideStateFilter();
445 toolButtonRemoveXmlSource->setVisible(true);
446 QTimer::singleShot(0, this, SLOT(scrollToEnd()));
447 } else
448 comboBoxXmlSource->setCurrentIndex(foundAtIndex);
449 } else
450 comboBoxXmlSource->setCurrentIndex(0);
451 raise();
452 } else {
453 index -= 1;
454 if ( index >= 0 ) {
455 QStringList setEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", QStringList()).toStringList());
456 QStringList romEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", QStringList()).toStringList());
457 QStringList diskEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", QStringList()).toStringList());
458 if ( lastIndex >= 0 ) {
459 if ( lastIndex < setEntities.count() ) {
460 setEntities.replace(lastIndex, lineEditSetEntity->text());
461 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", setEntities);
462 }
463 if ( lastIndex < romEntities.count() ) {
464 romEntities.replace(lastIndex, lineEditRomEntity->text());
465 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", romEntities);
466 }
467 if ( lastIndex < diskEntities.count() ) {
468 diskEntities.replace(lastIndex, lineEditDiskEntity->text());
469 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", diskEntities);
470 }
471 }
472 lastIndex = index;
473 lineEditSetEntity->setText(setEntities[index]);
474 lineEditRomEntity->setText(romEntities[index]);
475 lineEditDiskEntity->setText(diskEntities[index]);
476 frameEntities->setVisible(true);
477 hideStateFilter();
478 toolButtonRemoveXmlSource->setVisible(true);
479 QTimer::singleShot(0, this, SLOT(scrollToEnd()));
480 }
481 }
482 }
483
on_toolButtonRemoveXmlSource_clicked()484 void CollectionRebuilder::on_toolButtonRemoveXmlSource_clicked()
485 {
486 int index = comboBoxXmlSource->currentIndex() - 1;
487 comboBoxXmlSource->setCurrentIndex(0);
488 QStringList xmlSources(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSources", QStringList()).toStringList());
489 xmlSources.removeAt(index);
490 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSources", xmlSources);
491 QStringList setEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", QStringList()).toStringList());
492 setEntities.removeAt(index);
493 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetEntities", setEntities);
494 QStringList romEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", QStringList()).toStringList());
495 romEntities.removeAt(index);
496 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/RomEntities", romEntities);
497 QStringList diskEntities(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", QStringList()).toStringList());
498 diskEntities.removeAt(index);
499 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/DiskEntities", diskEntities);
500 QStringList checkpointList(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints", QStringList()).toStringList());
501 checkpointList.removeAt(index);
502 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints", checkpointList);
503 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ) {
504 QStringList softwareCheckpointList = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints", QStringList()).toStringList();
505 softwareCheckpointList.removeAt(index);
506 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints", softwareCheckpointList);
507 }
508 comboBoxXmlSource->blockSignals(true);
509 comboBoxXmlSource->removeItem(index + 1);
510 comboBoxXmlSource->blockSignals(false);
511 }
512
on_toolButtonViewMissingList_clicked()513 void CollectionRebuilder::on_toolButtonViewMissingList_clicked()
514 {
515 if ( missingDumpsViewer() ) {
516 if ( missingDumpsViewer()->isVisible() )
517 missingDumpsViewer()->hide();
518 else
519 missingDumpsViewer()->show();
520 } else {
521 m_missingDumpsViewer = new MissingDumpsViewer(romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ? "SoftwareMissingDumpsViewer" : "MissingDumpsViewer", 0);
522 // FIXME "non-default emulator"
523 //missingDumpsViewer()->frameExport->setVisible(romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ? false : true);
524 missingDumpsViewer()->setDefaultEmulator(defaultEmulator());
525 missingDumpsViewer()->frameExport->setVisible(romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ? false : defaultEmulator());
526 if ( rebuilderThread()->isActive )
527 missingDumpsViewer()->toolButtonExportToDataFile->setEnabled(false);
528 else
529 missingDumpsViewer()->toolButtonExportToDataFile->setEnabled(!newMissingList().isEmpty() || missingDumpsViewer()->treeWidget->topLevelItemCount() > 0);
530 if ( !newMissingList().isEmpty() )
531 QTimer::singleShot(0, this, SLOT(updateMissingList()));
532 missingDumpsViewer()->show();
533 }
534 }
535
rebuilderThread_rebuildStarted()536 void CollectionRebuilder::rebuilderThread_rebuildStarted()
537 {
538 if ( missingDumpsViewer() ) {
539 missingDumpsViewer()->treeWidget->clear();
540 missingDumpsViewer()->toolButtonExportToDataFile->setEnabled(false);
541 }
542 pushButtonStartStop->setIcon(QIcon(QString::fromUtf8(":/data/img/halt.png")));
543 if ( rebuilderThread()->dryRun ) {
544 pushButtonStartStop->setText(tr("Stop dry run"));
545 pushButtonStartStop->setToolTip(tr("Start / stop dry run"));
546 } else {
547 pushButtonStartStop->setText(tr("Stop rebuilding"));
548 pushButtonStartStop->setToolTip(tr("Start / stop rebuilding"));
549 }
550 pushButtonPauseResume->setText(tr("Pause"));
551 pushButtonPauseResume->show();
552 pushButtonStartStop->setEnabled(true);
553 pushButtonPauseResume->setEnabled(true);
554 comboBoxXmlSource->setEnabled(false);
555 comboBoxXmlSource->setItemIcon(comboBoxXmlSource->currentIndex(), m_iconCheckpoint);
556 comboBoxXmlSource->setItemData(comboBoxXmlSource->currentIndex(), true);
557 labelXmlSource->setEnabled(false);
558 toolButtonRemoveXmlSource->setEnabled(false);
559 comboBoxModeSwitch->setEnabled(false);
560 checkBoxFilterExpression->setEnabled(false);
561 checkBoxFilterExpressionSoftwareLists->setEnabled(false);
562 comboBoxFilterSyntax->setEnabled(false);
563 comboBoxFilterSyntaxSoftwareLists->setEnabled(false);
564 comboBoxFilterType->setEnabled(false);
565 comboBoxFilterTypeSoftwareLists->setEnabled(false);
566 lineEditFilterExpression->setEnabled(false);
567 lineEditFilterExpressionSoftwareLists->setEnabled(false);
568 toolButtonExactMatch->setEnabled(false);
569 toolButtonExactMatchSoftwareLists->setEnabled(false);
570 toolButtonClearFilterExpression->setEnabled(false);
571 toolButtonClearFilterExpressionSoftwareLists->setEnabled(false);
572 checkBoxFilterStates->setEnabled(false);
573 toolButtonStateC->setEnabled(false);
574 toolButtonStateM->setEnabled(false);
575 toolButtonStateI->setEnabled(false);
576 toolButtonStateN->setEnabled(false);
577 toolButtonStateU->setEnabled(false);
578 frameEntities->setEnabled(false);
579 romAlyzer()->groupBoxCheckSumDatabase->setEnabled(false);
580 romAlyzer()->groupBoxSetRewriter->setEnabled(false);
581 romAlyzer()->checkBoxCollectionRebuilderDryRun->setEnabled(false);
582 m_animationSequence = 0;
583 m_animationTimer.start(QMC2_ROMALYZER_REBUILD_ANIM_SPEED);
584 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE && qmc2SoftwareList ) {
585 qmc2SoftwareList->rebuildMenuAction->setEnabled(false);
586 qmc2SoftwareList->actionRebuildSoftware->setEnabled(false);
587 qmc2SoftwareList->actionRebuildSoftwareList->setEnabled(false);
588 qmc2SoftwareList->actionRebuildSoftwareLists->setEnabled(false);
589 qmc2SoftwareList->toolButtonRebuildSoftware->setEnabled(false);
590 }
591 qApp->processEvents();
592 }
593
rebuilderThread_rebuildFinished()594 void CollectionRebuilder::rebuilderThread_rebuildFinished()
595 {
596 int index = comboBoxXmlSource->currentIndex();
597 qint64 cp = rebuilderThread()->checkpoint();
598 if ( index == 0 )
599 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/Checkpoint", cp);
600 else {
601 int xmlSourceIndex = index - 1;
602 QStringList checkpointList(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints", QStringList()).toStringList());
603 if ( xmlSourceIndex >= 0 && xmlSourceIndex < checkpointList.count() ) {
604 checkpointList.replace(xmlSourceIndex, QString::number(cp));
605 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceCheckpoints", checkpointList);
606 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ) {
607 QStringList softwareCheckpointList = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints", QStringList()).toStringList();
608 if ( xmlSourceIndex < softwareCheckpointList.count() ) {
609 softwareCheckpointList.replace(xmlSourceIndex, rebuilderThread()->currentListName());
610 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/XmlSourceListCheckpoints", softwareCheckpointList);
611 }
612 }
613 }
614 }
615 if ( index < 0 )
616 return;
617 if ( cp >= 0 ) {
618 comboBoxXmlSource->setItemIcon(index, m_iconCheckpoint);
619 comboBoxXmlSource->setItemData(index, true);
620 } else {
621 comboBoxXmlSource->setItemIcon(index, m_iconNoCheckpoint);
622 comboBoxXmlSource->setItemData(index, false);
623 }
624 if ( missingDumpsViewer() ) {
625 missingDumpsViewer()->toolButtonExportToDataFile->setEnabled(!newMissingList().isEmpty() || missingDumpsViewer()->treeWidget->topLevelItemCount() > 0);
626 missingDumpsViewer()->setDefaultEmulator(defaultEmulator());
627 }
628 pushButtonStartStop->setIcon(QIcon(QString::fromUtf8(":/data/img/refresh.png")));
629 if ( romAlyzer()->checkBoxCollectionRebuilderDryRun->isChecked() ) {
630 pushButtonStartStop->setText(tr("Start dry run"));
631 pushButtonStartStop->setToolTip(tr("Start / stop dry run"));
632 } else {
633 pushButtonStartStop->setText(tr("Start rebuilding"));
634 pushButtonStartStop->setToolTip(tr("Start / stop rebuilding"));
635 }
636 pushButtonPauseResume->hide();
637 pushButtonStartStop->setEnabled(true);
638 pushButtonPauseResume->setEnabled(true);
639 comboBoxXmlSource->setEnabled(true);
640 labelXmlSource->setEnabled(true);
641 toolButtonRemoveXmlSource->setEnabled(true);
642 comboBoxModeSwitch->setEnabled(true);
643 checkBoxFilterExpression->setEnabled(true);
644 checkBoxFilterExpressionSoftwareLists->setEnabled(true);
645 comboBoxFilterSyntax->setEnabled(checkBoxFilterExpression->isChecked());
646 comboBoxFilterSyntaxSoftwareLists->setEnabled(checkBoxFilterExpressionSoftwareLists->isChecked());
647 comboBoxFilterType->setEnabled(checkBoxFilterExpression->isChecked());
648 comboBoxFilterTypeSoftwareLists->setEnabled(checkBoxFilterExpressionSoftwareLists->isChecked());
649 lineEditFilterExpression->setEnabled(checkBoxFilterExpression->isChecked());
650 lineEditFilterExpressionSoftwareLists->setEnabled(checkBoxFilterExpressionSoftwareLists->isChecked());
651 toolButtonExactMatch->setEnabled(checkBoxFilterExpression->isChecked());
652 toolButtonExactMatchSoftwareLists->setEnabled(checkBoxFilterExpressionSoftwareLists->isChecked());
653 toolButtonClearFilterExpression->setEnabled(checkBoxFilterExpression->isChecked());
654 toolButtonClearFilterExpressionSoftwareLists->setEnabled(checkBoxFilterExpressionSoftwareLists->isChecked());
655 checkBoxFilterStates->setEnabled(true);
656 toolButtonStateC->setEnabled(checkBoxFilterStates->isChecked());
657 toolButtonStateM->setEnabled(checkBoxFilterStates->isChecked());
658 toolButtonStateI->setEnabled(checkBoxFilterStates->isChecked());
659 toolButtonStateN->setEnabled(checkBoxFilterStates->isChecked());
660 toolButtonStateU->setEnabled(checkBoxFilterStates->isChecked());
661 frameEntities->setEnabled(true);
662 romAlyzer()->groupBoxCheckSumDatabase->setEnabled(true);
663 romAlyzer()->groupBoxSetRewriter->setEnabled(true);
664 romAlyzer()->checkBoxCollectionRebuilderDryRun->setEnabled(true);
665 m_animationTimer.stop();
666 romAlyzer()->tabWidgetAnalysis->setTabIcon(QMC2_ROMALYZER_PAGE_RCR, QIcon(QString::fromUtf8(":/data/img/rebuild.png")));
667 if ( romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE && qmc2SoftwareList ) {
668 qmc2SoftwareList->rebuildMenuAction->setEnabled(true);
669 qmc2SoftwareList->actionRebuildSoftware->setEnabled(true);
670 qmc2SoftwareList->actionRebuildSoftwareList->setEnabled(true);
671 qmc2SoftwareList->actionRebuildSoftwareLists->setEnabled(true);
672 qmc2SoftwareList->toolButtonRebuildSoftware->setEnabled(true);
673 }
674 qApp->processEvents();
675 if ( !newMissingList().isEmpty() && missingDumpsViewer() )
676 QTimer::singleShot(0, this, SLOT(updateMissingList()));
677 }
678
rebuilderThread_rebuildPaused()679 void CollectionRebuilder::rebuilderThread_rebuildPaused()
680 {
681 pushButtonPauseResume->setText(tr("Resume"));
682 pushButtonPauseResume->setEnabled(true);
683 }
684
rebuilderThread_rebuildResumed()685 void CollectionRebuilder::rebuilderThread_rebuildResumed()
686 {
687 pushButtonPauseResume->setText(tr("Pause"));
688 pushButtonPauseResume->setEnabled(true);
689 }
690
rebuilderThread_progressTextChanged(const QString & text)691 void CollectionRebuilder::rebuilderThread_progressTextChanged(const QString &text)
692 {
693 progressBar->setFormat(text);
694 }
695
rebuilderThread_progressRangeChanged(int min,int max)696 void CollectionRebuilder::rebuilderThread_progressRangeChanged(int min, int max)
697 {
698 progressBar->setRange(min, max);
699 }
700
rebuilderThread_progressChanged(int progress)701 void CollectionRebuilder::rebuilderThread_progressChanged(int progress)
702 {
703 progressBar->setValue(progress);
704 }
705
rebuilderThread_statusUpdated(quint64 setsProcessed,quint64 missingDumps,quint64 missingDisks)706 void CollectionRebuilder::rebuilderThread_statusUpdated(quint64 setsProcessed, quint64 missingDumps, quint64 missingDisks)
707 {
708 QString statusString("<table border=\"0\" cellpadding=\"0\" cellspacing=\"4\" width=\"100%\"><tr>");
709 statusString += "<td nowrap align=\"left\" width=\"16.16%\"><b>" + tr("Sets processed") + ":</b></td><td nowrap align=\"right\" width=\"16.16%\">" + QString::number(setsProcessed) + "</td>";
710 statusString += "<td nowrap align=\"center\" width=\"1%\">|</td>";
711 statusString += "<td nowrap align=\"left\" width=\"16.16%\"><b>" + tr("Missing ROMs") + ":</b></td><td nowrap align=\"right\" width=\"16.16%\">" + QString::number(missingDumps) + "</td>";
712 statusString += "<td nowrap align=\"center\" width=\"1%\">|</td>";
713 statusString += "<td nowrap align=\"left\" width=\"16.16%\"><b>" + tr("Missing disks") + ":</b></td><td nowrap align=\"right\" width=\"16.16%\">" + QString::number(missingDisks) + "</td>";
714 statusString += "<td nowrap align=\"right\" width=\"1%\">|</td>";
715 statusString += "</tr></table>";
716 labelRebuildStatus->setText(statusString);
717 }
718
rebuilderThread_newMissing(QString id,QString type,QString name,QString size,QString crc,QString sha1,QString reason)719 void CollectionRebuilder::rebuilderThread_newMissing(QString id, QString type, QString name, QString size, QString crc, QString sha1, QString reason)
720 {
721 newMissingList() << id + "|" + type + "|" + name + "|" + size + "|" + crc + "|" + sha1 + "|" + reason;
722 }
723
animationTimer_timeout()724 void CollectionRebuilder::animationTimer_timeout()
725 {
726 m_animationTimer.stop();
727 QPixmap pixmap(QString::fromUtf8(":/data/img/rebuild.png"));
728 QPixmap rotatedPixmap(pixmap.size());
729 rotatedPixmap.fill(Qt::transparent);
730 QPainter p(&rotatedPixmap);
731 QSize size = pixmap.size();
732 p.translate(size.height()/2,size.height()/2);
733 p.rotate(24 * ++m_animationSequence);
734 if ( m_animationSequence > 14 )
735 m_animationSequence = 0;
736 p.translate(-size.height()/2,-size.height()/2);
737 p.drawPixmap(0, 0, pixmap);
738 p.end();
739 romAlyzer()->tabWidgetAnalysis->setTabIcon(QMC2_ROMALYZER_PAGE_RCR, QIcon(rotatedPixmap));
740 m_animationTimer.start(QMC2_ROMALYZER_REBUILD_ANIM_SPEED);
741 if ( !newMissingList().isEmpty() && missingDumpsViewer() )
742 QTimer::singleShot(0, this, SLOT(updateMissingList()));
743 }
744
updateMissingList()745 void CollectionRebuilder::updateMissingList()
746 {
747 QList<QTreeWidgetItem *> itemList;
748 foreach (QString newMissing, newMissingList()) {
749 QStringList missingWords(newMissing.split('|'));
750 if ( missingWords.count() >= 7 ) {
751 QTreeWidgetItem *newItem = new QTreeWidgetItem(0);
752 newItem->setText(QMC2_MDV_COLUMN_ID, missingWords.at(0));
753 newItem->setText(QMC2_MDV_COLUMN_TYPE, missingWords.at(1));
754 newItem->setText(QMC2_MDV_COLUMN_NAME, missingWords.at(2));
755 newItem->setText(QMC2_MDV_COLUMN_SIZE, missingWords.at(3));
756 newItem->setText(QMC2_MDV_COLUMN_CRC, missingWords.at(4));
757 newItem->setText(QMC2_MDV_COLUMN_SHA1, missingWords.at(5));
758 newItem->setText(QMC2_MDV_COLUMN_REASON, missingWords.at(6));
759 itemList << newItem;
760 }
761 }
762 missingDumpsViewer()->treeWidget->insertTopLevelItems(0, itemList);
763 newMissingList().clear();
764 if ( rebuilderThread()->isActive )
765 missingDumpsViewer()->toolButtonExportToDataFile->setEnabled(false);
766 else
767 missingDumpsViewer()->toolButtonExportToDataFile->setEnabled(missingDumpsViewer()->treeWidget->topLevelItemCount() > 0);
768 }
769
updateModeSetup()770 void CollectionRebuilder::updateModeSetup()
771 {
772 if ( active() ) {
773 if ( rebuilderThread()->dryRun ) {
774 pushButtonStartStop->setText(tr("Stop dry run"));
775 pushButtonStartStop->setToolTip(tr("Start / stop dry run"));
776 } else {
777 pushButtonStartStop->setText(tr("Stop rebuilding"));
778 pushButtonStartStop->setToolTip(tr("Start / stop rebuilding"));
779 }
780 } else {
781 if ( romAlyzer()->checkBoxCollectionRebuilderDryRun->isChecked() ){
782 pushButtonStartStop->setText(tr("Start dry run"));
783 pushButtonStartStop->setToolTip(tr("Start / stop dry run"));
784 comboBoxModeSwitch->blockSignals(true);
785 comboBoxModeSwitch->setCurrentIndex(1);
786 comboBoxModeSwitch->blockSignals(false);
787 } else {
788 pushButtonStartStop->setText(tr("Start rebuilding"));
789 pushButtonStartStop->setToolTip(tr("Start / stop rebuilding"));
790 comboBoxModeSwitch->blockSignals(true);
791 comboBoxModeSwitch->setCurrentIndex(0);
792 comboBoxModeSwitch->blockSignals(false);
793 }
794 }
795 }
796
on_comboBoxModeSwitch_currentIndexChanged(int index)797 void CollectionRebuilder::on_comboBoxModeSwitch_currentIndexChanged(int index)
798 {
799 if ( index == 0 ) {
800 romAlyzer()->checkBoxCollectionRebuilderDryRun->setChecked(false);
801 pushButtonStartStop->setText(tr("Start rebuilding"));
802 pushButtonStartStop->setToolTip(tr("Start / stop rebuilding"));
803 } else {
804 romAlyzer()->checkBoxCollectionRebuilderDryRun->setChecked(true);
805 pushButtonStartStop->setText(tr("Start dry run"));
806 pushButtonStartStop->setToolTip(tr("Start / stop dry run"));
807 }
808 }
809
showEvent(QShowEvent * e)810 void CollectionRebuilder::showEvent(QShowEvent *e)
811 {
812 QTimer::singleShot(0, this, SLOT(updateModeSetup()));
813 }
814
hideEvent(QHideEvent * e)815 void CollectionRebuilder::hideEvent(QHideEvent *e)
816 {
817 switch ( romAlyzer()->mode() ) {
818 case QMC2_ROMALYZER_MODE_SOFTWARE:
819 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UseFilterExpressionSoftwareLists", checkBoxFilterExpressionSoftwareLists->isChecked());
820 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterSyntaxSoftwareLists", comboBoxFilterSyntaxSoftwareLists->currentIndex());
821 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterTypeSoftwareLists", comboBoxFilterTypeSoftwareLists->currentIndex());
822 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExactMatchSoftwareLists", toolButtonExactMatchSoftwareLists->isChecked());
823 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterExpressionSoftwareLists", lineEditFilterExpressionSoftwareLists->text());
824 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UseFilterExpression", checkBoxFilterExpression->isChecked());
825 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterSyntax", comboBoxFilterSyntax->currentIndex());
826 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterType", comboBoxFilterType->currentIndex());
827 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExactMatch", toolButtonExactMatch->isChecked());
828 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterExpression", lineEditFilterExpression->text());
829 break;
830 case QMC2_ROMALYZER_MODE_SYSTEM:
831 default:
832 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UseFilterExpression", checkBoxFilterExpression->isChecked());
833 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterSyntax", comboBoxFilterSyntax->currentIndex());
834 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterType", comboBoxFilterType->currentIndex());
835 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExactMatch", toolButtonExactMatch->isChecked());
836 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/FilterExpression", lineEditFilterExpression->text());
837 break;
838 }
839 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UseStateFilter", checkBoxFilterStates->isChecked());
840 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateC", toolButtonStateC->isChecked());
841 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateM", toolButtonStateM->isChecked());
842 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateI", toolButtonStateI->isChecked());
843 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateN", toolButtonStateN->isChecked());
844 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/IncludeStateU", toolButtonStateU->isChecked());
845 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableLog", checkBoxEnableLog->isChecked());
846 if ( missingDumpsViewer() )
847 missingDumpsViewer()->close();
848 if ( e )
849 e->ignore();
850 }
851
CollectionRebuilderThread(QObject * parent)852 CollectionRebuilderThread::CollectionRebuilderThread(QObject *parent)
853 : QThread(parent)
854 {
855 isActive = exitThread = isWaiting = isPaused = pauseRequested = stopRebuilding = doFilter = doFilterSoftware = isIncludeFilter = isIncludeFilterSoftware = doFilterState = false;
856 includeStateC = includeStateM = includeStateI = includeStateN = includeStateU = true;
857 exactMatch = exactMatchSoftware = useHashCache = dryRun = false;
858 m_rebuilderDialog = (CollectionRebuilder *)parent;
859 m_checkSumDb = 0;
860 m_xmlIndex = m_xmlIndexCount = m_checkpoint = -1;
861 m_hashCacheUpdateTime = 0;
862 setListEntityStartPattern("<softwarelist name=\"");
863 if ( m_replacementHash.isEmpty() ) {
864 m_replacementHash.insert("&", "&");
865 m_replacementHash.insert("<", "<");
866 m_replacementHash.insert(">", ">");
867 m_replacementHash.insert(""", "\"");
868 m_replacementHash.insert("'", "'");
869 }
870 if ( m_fileTypes.isEmpty() )
871 m_fileTypes << "ZIP" << "7Z" << "CHD" << "FILE";
872 reopenCheckSumDb();
873 switch ( rebuilderDialog()->romAlyzer()->mode() ) {
874 case QMC2_ROMALYZER_MODE_SOFTWARE:
875 m_swlDb = new SoftwareListXmlDatabaseManager(this);
876 m_xmlDb = 0;
877 break;
878 case QMC2_ROMALYZER_MODE_SYSTEM:
879 default:
880 m_xmlDb = new XmlDatabaseManager(this);
881 m_swlDb = 0;
882 break;
883 }
884 start();
885 }
886
~CollectionRebuilderThread()887 CollectionRebuilderThread::~CollectionRebuilderThread()
888 {
889 exitThread = true;
890 waitCondition.wakeAll();
891 wait();
892 if ( checkSumDb() ) {
893 checkSumDb()->disconnect(rebuilderDialog());
894 delete checkSumDb();
895 }
896 if ( xmlDb() )
897 delete xmlDb();
898 if ( swlDb() )
899 delete swlDb();
900 }
901
reopenCheckSumDb()902 void CollectionRebuilderThread::reopenCheckSumDb()
903 {
904 if ( checkSumDb() ) {
905 checkSumDb()->disconnect(rebuilderDialog());
906 delete checkSumDb();
907 }
908 m_checkSumDb = new CheckSumDatabaseManager(this, rebuilderDialog()->romAlyzer()->settingsKey());
909 connect(checkSumDb(), SIGNAL(log(const QString &)), rebuilderDialog(), SLOT(log(const QString &)));
910 }
911
parseXml(QString xml,QString * id,QStringList * romNameList,QStringList * romSha1List,QStringList * romCrcList,QStringList * romSizeList,QStringList * diskNameList,QStringList * diskSha1List,QStringList * diskSizeList)912 bool CollectionRebuilderThread::parseXml(QString xml, QString *id, QStringList *romNameList, QStringList *romSha1List, QStringList *romCrcList, QStringList *romSizeList, QStringList *diskNameList, QStringList *diskSha1List, QStringList *diskSizeList)
913 {
914 static const QString statusString("status=\"");
915 static const QString mergeString("merge=\"");
916 static const QString sha1String("sha1=\"");
917 static const QString crcString("crc=\"");
918 static const QString sizeString("size=\"");
919 static const QChar quoteChar('\"');
920
921 if ( xml.isEmpty() )
922 return false;
923
924 int startIndex = -1;
925 int endIndex = -1;
926 QStringList xmlLines(xml.split('\n'));
927 QString xmlLine(xmlLines.at(0));
928 startIndex = xmlLine.indexOf(setEntityPattern());
929 if ( startIndex >= 0 ) {
930 startIndex += setEntityPattern().length();
931 endIndex = xmlLine.indexOf(quoteChar, startIndex);
932 if ( endIndex >= 0 ) {
933 *id = xmlLine.mid(startIndex, endIndex - startIndex);
934 QString mergeName, romName, status, romSha1, romCrc, romSize, diskName, diskSha1, diskSize;
935 for (int i = 1; i < xmlLines.count(); i++) {
936 xmlLine = xmlLines.at(i);
937 bool romFound = false;
938 startIndex = xmlLine.indexOf(romEntityPattern());
939 if ( startIndex >= 0 ) {
940 startIndex += romEntityPattern().length();
941 endIndex = xmlLine.indexOf(quoteChar, startIndex);
942 if ( endIndex >= 0 ) {
943 romFound = true;
944 romName = xmlLine.mid(startIndex, endIndex - startIndex);
945 mergeName.clear();
946 status.clear();
947 startIndex = xmlLine.indexOf(statusString);
948 if ( startIndex >= 0 ) {
949 startIndex += statusString.length();
950 endIndex = xmlLine.indexOf(quoteChar, startIndex);
951 if ( endIndex >= 0 )
952 status = xmlLine.mid(startIndex, endIndex - startIndex);
953 }
954 startIndex = xmlLine.indexOf(mergeString);
955 if ( startIndex >= 0 ) {
956 startIndex += mergeString.length();
957 endIndex = xmlLine.indexOf(quoteChar, startIndex);
958 if ( endIndex >= 0 )
959 mergeName = xmlLine.mid(startIndex, endIndex - startIndex);
960 }
961 if ( status != "nodump" && (!merge() || mergeName.isEmpty()) ) {
962 romSha1.clear(); romCrc.clear(); romSize.clear();
963 startIndex = xmlLine.indexOf(sha1String);
964 if ( startIndex >= 0 ) {
965 startIndex += sha1String.length();
966 endIndex = xmlLine.indexOf(quoteChar, startIndex);
967 if ( endIndex >= 0 )
968 romSha1 = xmlLine.mid(startIndex, endIndex - startIndex);
969 }
970 startIndex = xmlLine.indexOf(crcString);
971 if ( startIndex >= 0 ) {
972 startIndex += crcString.length();
973 endIndex = xmlLine.indexOf(quoteChar, startIndex);
974 if ( endIndex >= 0 )
975 romCrc = xmlLine.mid(startIndex, endIndex - startIndex);
976 }
977 startIndex = xmlLine.indexOf(sizeString);
978 if ( startIndex >= 0 ) {
979 startIndex += sizeString.length();
980 endIndex = xmlLine.indexOf(quoteChar, startIndex);
981 if ( endIndex >= 0 )
982 romSize = xmlLine.mid(startIndex, endIndex - startIndex);
983 }
984 if ( !romSha1.isEmpty() || !romCrc.isEmpty() ) {
985 romNameList->append(toHumanReadable(romName));
986 romSha1List->append(romSha1);
987 romCrcList->append(romCrc);
988 romSizeList->append(romSize);
989 }
990 }
991 }
992 }
993 if ( romFound )
994 continue;
995 startIndex = xmlLine.indexOf(diskEntityPattern());
996 if ( startIndex >= 0 ) {
997 startIndex += diskEntityPattern().length();
998 endIndex = xmlLine.indexOf(quoteChar, startIndex);
999 if ( endIndex >= 0 ) {
1000 diskName = xmlLine.mid(startIndex, endIndex - startIndex);
1001 mergeName.clear();
1002 startIndex = xmlLine.indexOf(mergeString);
1003 if ( startIndex >= 0 ) {
1004 startIndex += mergeString.length();
1005 endIndex = xmlLine.indexOf(quoteChar, startIndex);
1006 if ( endIndex >= 0 )
1007 mergeName = xmlLine.mid(startIndex, endIndex - startIndex);
1008 }
1009 if ( !merge() || mergeName.isEmpty() ) {
1010 diskSha1.clear();
1011 startIndex = xmlLine.indexOf(sha1String);
1012 if ( startIndex >= 0 ) {
1013 startIndex += sha1String.length();
1014 endIndex = xmlLine.indexOf(quoteChar, startIndex);
1015 if ( endIndex >= 0 )
1016 diskSha1 = xmlLine.mid(startIndex, endIndex - startIndex);
1017 }
1018 diskSize.clear();
1019 startIndex = xmlLine.indexOf(sizeString);
1020 if ( startIndex >= 0 ) {
1021 startIndex += sizeString.length();
1022 endIndex = xmlLine.indexOf(quoteChar, startIndex);
1023 if ( endIndex >= 0 )
1024 diskSize = xmlLine.mid(startIndex, endIndex - startIndex);
1025 }
1026 if ( !diskSha1.isEmpty() ) {
1027 diskNameList->append(toHumanReadable(diskName));
1028 diskSha1List->append(diskSha1);
1029 diskSizeList->append(diskSize);
1030 }
1031 }
1032 }
1033 }
1034 }
1035 return true;
1036 } else
1037 return false;
1038 } else
1039 return false;
1040 }
1041
nextId(QString * id,QStringList * romNameList,QStringList * romSha1List,QStringList * romCrcList,QStringList * romSizeList,QStringList * diskNameList,QStringList * diskSha1List,QStringList * diskSizeList)1042 bool CollectionRebuilderThread::nextId(QString *id, QStringList *romNameList, QStringList *romSha1List, QStringList *romCrcList, QStringList *romSizeList, QStringList *diskNameList, QStringList *diskSha1List, QStringList *diskSizeList)
1043 {
1044 id->clear();
1045 romNameList->clear();
1046 romSha1List->clear();
1047 romCrcList->clear();
1048 romSizeList->clear();
1049 diskNameList->clear();
1050 diskSha1List->clear();
1051 diskSizeList->clear();
1052 if ( m_xmlIndex < 0 || m_xmlIndexCount < 0 ) {
1053 if ( rebuilderDialog()->defaultEmulator() ) {
1054 m_xmlIndex = 0;
1055 switch ( rebuilderDialog()->romAlyzer()->mode() ) {
1056 case QMC2_ROMALYZER_MODE_SOFTWARE:
1057 swlDb()->initIdAtIndexCache();
1058 m_xmlIndexCount = swlDb()->idAtIndexCacheSize();
1059 break;
1060 case QMC2_ROMALYZER_MODE_SYSTEM:
1061 default:
1062 xmlDb()->initIdAtIndexCache();
1063 m_xmlIndexCount = xmlDb()->idAtIndexCacheSize();
1064 break;
1065 }
1066 emit progressRangeChanged(m_xmlIndex, m_xmlIndexCount);
1067 emit progressChanged(m_xmlIndex);
1068 setCheckpoint(m_xmlIndex, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1069 return true;
1070 } else {
1071 m_xmlFile.setFileName(rebuilderDialog()->comboBoxXmlSource->currentText());
1072 if ( m_xmlFile.open(QIODevice::ReadOnly | QIODevice::Text) ) {
1073 m_xmlIndex = 0;
1074 m_xmlIndexCount = m_xmlFile.size() - 1;
1075 emit progressRangeChanged(m_xmlIndex, m_xmlIndexCount);
1076 emit progressChanged(m_xmlIndex);
1077 setCheckpoint(0, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1078 return true;
1079 } else {
1080 emit log(tr("FATAL: can't open XML file '%1' for reading, please check permissions").arg(rebuilderDialog()->comboBoxXmlSource->currentText()));
1081 setCheckpoint(-1, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1082 return false;
1083 }
1084 }
1085 } else {
1086 if ( m_xmlIndex > m_xmlIndexCount ) {
1087 emit progressChanged(m_xmlIndexCount);
1088 setCheckpoint(-1, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1089 return false;
1090 }
1091 if ( rebuilderDialog()->defaultEmulator() ) {
1092 QString setKey;
1093 switch ( rebuilderDialog()->romAlyzer()->mode() ) {
1094 case QMC2_ROMALYZER_MODE_SOFTWARE:
1095 setKey = swlDb()->idAtIndex(m_xmlIndex);
1096 if ( !evaluateFilters(setKey) ) {
1097 m_xmlIndex++;
1098 emit progressChanged(m_xmlIndex);
1099 return true;
1100 }
1101 if ( parseXml(swlDb()->xml(setKey), id, romNameList, romSha1List, romCrcList, romSizeList, diskNameList, diskSha1List, diskSizeList) ) {
1102 *id = setKey;
1103 m_xmlIndex++;
1104 emit progressChanged(m_xmlIndex);
1105 return true;
1106 } else {
1107 emit log(tr("FATAL: XML parsing failed"));
1108 setCheckpoint(-1, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1109 return false;
1110 }
1111 case QMC2_ROMALYZER_MODE_SYSTEM:
1112 default:
1113 setKey = xmlDb()->idAtIndex(m_xmlIndex);
1114 if ( !evaluateFilters(setKey) ) {
1115 m_xmlIndex++;
1116 emit progressChanged(m_xmlIndex);
1117 return true;
1118 }
1119 if ( parseXml(xmlDb()->xml(setKey), id, romNameList, romSha1List, romCrcList, romSizeList, diskNameList, diskSha1List, diskSizeList) ) {
1120 m_xmlIndex++;
1121 emit progressChanged(m_xmlIndex);
1122 return true;
1123 } else {
1124 emit log(tr("FATAL: XML parsing failed"));
1125 setCheckpoint(-1, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1126 return false;
1127 }
1128 }
1129 } else {
1130 QByteArray line(m_xmlFile.readLine());
1131 while ( !m_xmlFile.atEnd() && line.indexOf(setEntityStartPattern()) < 0 && (rebuilderDialog()->romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ? line.indexOf(listEntityStartPattern()) < 0 : true) && !exitThread )
1132 line = m_xmlFile.readLine();
1133 if ( m_xmlFile.atEnd() ) {
1134 emit progressChanged(m_xmlIndexCount);
1135 setCheckpoint(-1, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1136 return false;
1137 } else if ( !exitThread ) {
1138 QString xmlString;
1139 if ( rebuilderDialog()->romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ) {
1140 int startIndex = line.indexOf(listEntityStartPattern());
1141 if ( startIndex >= 0 ) {
1142 startIndex += listEntityStartPattern().length();
1143 setListCheckpoint(line.mid(startIndex, line.indexOf("\"", startIndex) - startIndex), rebuilderDialog()->comboBoxXmlSource->currentIndex());
1144 return true;
1145 }
1146 }
1147 QString setEntityEndPattern("</" + rebuilderDialog()->lineEditSetEntity->text() + ">");
1148 while ( !m_xmlFile.atEnd() && line.indexOf(setEntityEndPattern) < 0 && !exitThread ) {
1149 xmlString += line;
1150 line = m_xmlFile.readLine();
1151 }
1152 if ( !m_xmlFile.atEnd() && !exitThread ) {
1153 xmlString += line;
1154 if ( parseXml(xmlString, id, romNameList, romSha1List, romCrcList, romSizeList, diskNameList, diskSha1List, diskSizeList) ) {
1155 if ( rebuilderDialog()->romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE && !m_currentListName.isEmpty() )
1156 id->prepend(m_currentListName + ":");
1157 if ( !evaluateFilters(*id) )
1158 id->clear();
1159 m_xmlIndex = m_xmlFile.pos();
1160 emit progressChanged(m_xmlIndex);
1161 return true;
1162 } else {
1163 emit log(tr("FATAL: XML parsing failed"));
1164 setCheckpoint(-1, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1165 return false;
1166 }
1167 } else {
1168 emit log(tr("FATAL: XML parsing failed"));
1169 setCheckpoint(-1, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1170 return false;
1171 }
1172 } else {
1173 setCheckpoint(-1, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1174 return false;
1175 }
1176 }
1177 }
1178 }
1179
setCheckpoint(qint64 cp,int xmlSourceIndex)1180 void CollectionRebuilderThread::setCheckpoint(qint64 cp, int xmlSourceIndex)
1181 {
1182 m_checkpoint = cp;
1183 if ( xmlSourceIndex == 0 )
1184 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + rebuilderDialog()->settingsKey() + "/Checkpoint", checkpoint());
1185 else if ( xmlSourceIndex > 0 && xmlSourceIndex < rebuilderDialog()->comboBoxXmlSource->count() - 1 ) {
1186 xmlSourceIndex -= 1;
1187 QStringList checkpointList(qmc2Config->value(QMC2_FRONTEND_PREFIX + rebuilderDialog()->settingsKey() + "/XmlSourceCheckpoints", QStringList()).toStringList());
1188 checkpointList.replace(xmlSourceIndex, QString::number(checkpoint()));
1189 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + rebuilderDialog()->settingsKey() + "/XmlSourceCheckpoints", checkpointList);
1190 }
1191 }
1192
setListCheckpoint(QString list,int xmlSourceIndex)1193 void CollectionRebuilderThread::setListCheckpoint(QString list, int xmlSourceIndex)
1194 {
1195 m_currentListName = list;
1196 if ( xmlSourceIndex > 0 && xmlSourceIndex < rebuilderDialog()->comboBoxXmlSource->count() - 1 ) {
1197 xmlSourceIndex -= 1;
1198 QStringList checkpointList(qmc2Config->value(QMC2_FRONTEND_PREFIX + rebuilderDialog()->settingsKey() + "/XmlSourceListCheckpoints", QStringList()).toStringList());
1199 checkpointList.replace(xmlSourceIndex, list);
1200 qmc2Config->setValue(QMC2_FRONTEND_PREFIX + rebuilderDialog()->settingsKey() + "/XmlSourceListCheckpoints", checkpointList);
1201 }
1202 }
1203
checkpointRestart(qint64 cp)1204 void CollectionRebuilderThread::checkpointRestart(qint64 cp)
1205 {
1206 if ( cp < 0 ) {
1207 m_xmlIndex = m_xmlIndexCount = -1;
1208 return;
1209 }
1210 m_xmlIndex = cp;
1211 if ( rebuilderDialog()->defaultEmulator() ) {
1212 emit log(tr("restarting from checkpoint '%1'").arg(m_xmlIndex));
1213 switch ( rebuilderDialog()->romAlyzer()->mode() ) {
1214 case QMC2_ROMALYZER_MODE_SOFTWARE:
1215 swlDb()->initIdAtIndexCache();
1216 m_xmlIndexCount = swlDb()->idAtIndexCacheSize();
1217 break;
1218 case QMC2_ROMALYZER_MODE_SYSTEM:
1219 default:
1220 xmlDb()->initIdAtIndexCache();
1221 m_xmlIndexCount = xmlDb()->idAtIndexCacheSize();
1222 break;
1223 }
1224 emit progressRangeChanged(m_xmlIndex, m_xmlIndexCount);
1225 emit progressChanged(m_xmlIndex);
1226 } else {
1227 if ( m_xmlFile.isOpen() )
1228 m_xmlFile.close();
1229 m_xmlFile.setFileName(rebuilderDialog()->comboBoxXmlSource->currentText());
1230 if ( m_xmlFile.open(QIODevice::ReadOnly | QIODevice::Text) ) {
1231 emit log(tr("restarting from checkpoint '%1'").arg(m_xmlIndex));
1232 m_xmlIndexCount = m_xmlFile.size() - 1;
1233 m_xmlFile.seek(m_xmlIndex);
1234 emit progressRangeChanged(m_xmlIndex, m_xmlIndexCount);
1235 emit progressChanged(m_xmlIndex);
1236 } else {
1237 emit log(tr("FATAL: can't open XML file '%1' for reading, please check permissions").arg(rebuilderDialog()->comboBoxXmlSource->currentText()));
1238 m_xmlIndex = m_xmlIndexCount = -1;
1239 }
1240 }
1241 setCheckpoint(m_xmlIndex, rebuilderDialog()->comboBoxXmlSource->currentIndex());
1242 }
1243
rewriteSet(QString * setKey,QStringList * romNameList,QStringList * romSha1List,QStringList * romCrcList,QStringList * romSizeList,QStringList * diskNameList,QStringList * diskSha1List)1244 bool CollectionRebuilderThread::rewriteSet(QString *setKey, QStringList *romNameList, QStringList *romSha1List, QStringList *romCrcList, QStringList *romSizeList, QStringList *diskNameList, QStringList *diskSha1List)
1245 {
1246 QString set, baseDir = rebuilderDialog()->romAlyzer()->lineEditSetRewriterOutputPath->text();
1247 if ( rebuilderDialog()->romAlyzer()->mode() == QMC2_ROMALYZER_MODE_SOFTWARE ) {
1248 QStringList setKeyTokens(setKey->split(':', QString::SkipEmptyParts));
1249 if ( setKeyTokens.count() < 2 )
1250 return false;
1251 else {
1252 baseDir += "/" + setKeyTokens.at(0);
1253 set = setKeyTokens.at(1);
1254 }
1255 } else
1256 set = *setKey;
1257 bool rebuildOkay = true;
1258 if ( !romNameList->isEmpty() ) {
1259 switch ( rebuilderDialog()->romAlyzer()->comboBoxSetRewriterReproductionType->currentIndex() ) {
1260 case QMC2_ROMALYZER_RT_ZIP_BUILTIN:
1261 rebuildOkay = writeAllZipData(baseDir, set, romNameList, romSha1List, romCrcList, romSizeList, diskNameList, diskSha1List);
1262 break;
1263 #if defined(QMC2_LIBARCHIVE_ENABLED)
1264 case QMC2_ROMALYZER_RT_ZIP_LIBARCHIVE:
1265 rebuildOkay = writeAllArchiveData(baseDir, set, romNameList, romSha1List, romCrcList, romSizeList, diskNameList, diskSha1List);
1266 break;
1267 #endif
1268 case QMC2_ROMALYZER_RT_FOLDERS:
1269 rebuildOkay = writeAllFileData(baseDir, set, romNameList, romSha1List, romCrcList, romSizeList, diskNameList, diskSha1List);
1270 break;
1271 default:
1272 break;
1273 }
1274 }
1275 if ( rebuildOkay && !diskNameList->isEmpty() ) {
1276 switch ( rebuilderDialog()->romAlyzer()->comboBoxCollectionRebuilderCHDHandling->currentIndex() ) {
1277 case QMC2_COLLECTIONREBUILDER_CHD_HARDLINK:
1278 rebuildOkay = hardlinkChds(baseDir, set, diskNameList, diskSha1List);
1279 break;
1280 case QMC2_COLLECTIONREBUILDER_CHD_SYMLINK:
1281 rebuildOkay = symlinkChds(baseDir, set, diskNameList, diskSha1List);
1282 break;
1283 case QMC2_COLLECTIONREBUILDER_CHD_COPY:
1284 rebuildOkay = copyChds(baseDir, set, diskNameList, diskSha1List);
1285 break;
1286 case QMC2_COLLECTIONREBUILDER_CHD_MOVE:
1287 rebuildOkay = moveChds(baseDir, set, diskNameList, diskSha1List);
1288 break;
1289 default:
1290 break;
1291 }
1292 }
1293 return rebuildOkay;
1294 }
1295
writeAllFileData(QString baseDir,QString id,QStringList * romNameList,QStringList * romSha1List,QStringList * romCrcList,QStringList * romSizeList,QStringList *,QStringList *)1296 bool CollectionRebuilderThread::writeAllFileData(QString baseDir, QString id, QStringList *romNameList, QStringList *romSha1List, QStringList *romCrcList, QStringList *romSizeList, QStringList * /*diskNameList*/, QStringList * /*diskSha1List*/)
1297 {
1298 bool success = true;
1299 QDir d(QDir::cleanPath(baseDir + "/" + id));
1300 if ( !d.exists() )
1301 success = d.mkdir(QDir::cleanPath(baseDir + "/" + id));
1302 int reproducedDumps = 0;
1303 bool ignoreErrors = !rebuilderDialog()->romAlyzer()->checkBoxSetRewriterAbortOnError->isChecked();
1304 for (int i = 0; i < romNameList->count() && !exitThread && success; i++) {
1305 QString fileName(d.absoluteFilePath(romNameList->at(i)));
1306 if ( !createBackup(fileName) ) {
1307 emit log(tr("FATAL: backup creation failed"));
1308 success = false;
1309 }
1310 QFile f(fileName);
1311 QString errorReason(tr("file error"));
1312 if ( success && f.open(QIODevice::WriteOnly) ) {
1313 BigByteArray data;
1314 quint64 size = romSizeList->at(i).toULongLong();
1315 QString path, member, type;
1316 if ( checkSumDb()->getData(romSha1List->at(i), romCrcList->at(i), &size, &path, &member, &type) ) {
1317 switch ( m_fileTypes.indexOf(type) ) {
1318 case QMC2_COLLECTIONREBUILDER_FILETYPE_ZIP:
1319 success = readZipFileData(path, romCrcList->at(i), member, &data);
1320 break;
1321 case QMC2_COLLECTIONREBUILDER_FILETYPE_7Z:
1322 success = readSevenZipFileData(path, romCrcList->at(i), member, &data);
1323 break;
1324 case QMC2_COLLECTIONREBUILDER_FILETYPE_FILE:
1325 success = readFileData(path, &data);
1326 break;
1327 default:
1328 success = false;
1329 errorReason = tr("unknown file type '%1'").arg(type);
1330 break;
1331 }
1332 if ( success ) {
1333 emit log(tr("writing '%1' (size: %2)").arg(fileName).arg(ROMAlyzer::humanReadable(data.length())));
1334 quint64 bytesWritten = 0;
1335 while ( bytesWritten < (quint64)data.length() && !exitThread && success ) {
1336 quint64 bufferLength = QMC2_ZIP_BUFFER_SIZE;
1337 if ( bytesWritten + bufferLength > (quint64)data.length() )
1338 bufferLength = data.length() - bytesWritten;
1339 qint64 len = f.write(data.mid(bytesWritten, bufferLength));
1340 success = (len >= 0);
1341 if ( success ) {
1342 bytesWritten += len;
1343 } else
1344 log(tr("FATAL: failed writing '%1'").arg(fileName));
1345 }
1346 }
1347 f.close();
1348 reproducedDumps++;
1349 } else {
1350 f.close();
1351 f.remove();
1352 }
1353 } else {
1354 emit log(tr("FATAL: failed opening '%1' for writing"));
1355 success = false;
1356 }
1357 if ( !success ) {
1358 emit statusUpdated(setsProcessed, ++missingROMs, missingDisks);
1359 emit newMissing(id, tr("ROM"), romNameList->at(i), romSizeList->at(i), romCrcList->at(i), romSha1List->at(i), errorReason);
1360 }
1361 if ( ignoreErrors )
1362 success = true;
1363 else if ( !success ) {
1364 for (int j = i + 1; j < romNameList->count() && !exitThread; j++) {
1365 emit statusUpdated(setsProcessed, ++missingROMs, missingDisks);
1366 emit newMissing(id, tr("ROM"), romNameList->at(j), romSizeList->at(j), romCrcList->at(j), romSha1List->at(j), errorReason);
1367 }
1368 }
1369 }
1370 if ( reproducedDumps == 0 )
1371 d.rmdir(d.absolutePath());
1372 return success;
1373 }
1374
writeAllZipData(QString baseDir,QString id,QStringList * romNameList,QStringList * romSha1List,QStringList * romCrcList,QStringList * romSizeList,QStringList *,QStringList *)1375 bool CollectionRebuilderThread::writeAllZipData(QString baseDir, QString id, QStringList *romNameList, QStringList *romSha1List, QStringList *romCrcList, QStringList *romSizeList, QStringList * /*diskNameList*/, QStringList * /*diskSha1List*/)
1376 {
1377 QDir d(QDir::cleanPath(baseDir));
1378 if ( !d.exists() )
1379 if ( !d.mkdir(QDir::cleanPath(baseDir)) )
1380 return false;
1381 QString fileName(QDir::cleanPath(baseDir) + "/" + id + ".zip");
1382 if ( !createBackup(fileName) ) {
1383 emit log(tr("FATAL: backup creation failed"));
1384 return false;
1385 }
1386 QFile f(fileName);
1387 if ( f.exists() )
1388 if ( !f.remove() )
1389 return false;
1390 bool success = true;
1391 bool uniqueCRCs = rebuilderDialog()->romAlyzer()->checkBoxSetRewriterUniqueCRCs->isChecked();
1392 bool ignoreErrors = !rebuilderDialog()->romAlyzer()->checkBoxSetRewriterAbortOnError->isChecked();
1393 int zipLevel = rebuilderDialog()->romAlyzer()->spinBoxSetRewriterZipLevel->value();
1394 zipFile zip = zipOpen(fileName.toUtf8().constData(), APPEND_STATUS_CREATE);
1395 if ( zip ) {
1396 emit log(tr("creating new ZIP archive '%1'").arg(fileName));
1397 zip_fileinfo zipInfo;
1398 QDateTime cDT = QDateTime::currentDateTime();
1399 zipInfo.tmz_date.tm_sec = cDT.time().second();
1400 zipInfo.tmz_date.tm_min = cDT.time().minute();
1401 zipInfo.tmz_date.tm_hour = cDT.time().hour();
1402 zipInfo.tmz_date.tm_mday = cDT.date().day();
1403 zipInfo.tmz_date.tm_mon = cDT.date().month() - 1;
1404 zipInfo.tmz_date.tm_year = cDT.date().year();
1405 zipInfo.dosDate = zipInfo.internal_fa = zipInfo.external_fa = 0;
1406 QStringList storedCRCs;
1407 int reproducedDumps = 0;
1408 for (int i = 0; i < romNameList->count() && !exitThread && success; i++) {
1409 if ( uniqueCRCs && storedCRCs.contains(romCrcList->at(i)) ) {
1410 emit log(tr("skipping '%1'").arg(romNameList->at(i)) + " ("+ tr("a dump with CRC '%1' already exists").arg(romCrcList->at(i)) + ")");
1411 continue;
1412 }
1413 QString file(romNameList->at(i));
1414 BigByteArray data;
1415 quint64 size = romSizeList->at(i).toULongLong();
1416 QString path, member, type;
1417 QString errorReason(tr("file error"));
1418 if ( checkSumDb()->getData(romSha1List->at(i), romCrcList->at(i), &size, &path, &member, &type) ) {
1419 switch ( m_fileTypes.indexOf(type) ) {
1420 case QMC2_COLLECTIONREBUILDER_FILETYPE_ZIP:
1421 success = readZipFileData(path, romCrcList->at(i), member, &data);
1422 break;
1423 case QMC2_COLLECTIONREBUILDER_FILETYPE_7Z:
1424 success = readSevenZipFileData(path, romCrcList->at(i), member, &data);
1425 break;
1426 case QMC2_COLLECTIONREBUILDER_FILETYPE_FILE:
1427 success = readFileData(path, &data);
1428 break;
1429 default:
1430 success = false;
1431 errorReason = tr("unknown file type '%1'").arg(type);
1432 break;
1433 }
1434 if ( success && zipOpenNewFileInZip(zip, file.toUtf8().constData(), &zipInfo, file.toUtf8().constData(), file.length(), 0, 0, 0, Z_DEFLATED, zipLevel) == ZIP_OK ) {
1435 emit log(tr("writing '%1' to ZIP archive '%2' (uncompressed size: %3)").arg(file).arg(fileName).arg(ROMAlyzer::humanReadable(data.length())));
1436 quint64 bytesWritten = 0;
1437 while ( bytesWritten < (quint64)data.length() && !exitThread && success ) {
1438 quint64 bufferLength = QMC2_ZIP_BUFFER_SIZE;
1439 if ( bytesWritten + bufferLength > (quint64)data.length() )
1440 bufferLength = data.length() - bytesWritten;
1441 QByteArray writeBuffer(data.mid(bytesWritten, bufferLength));
1442 success = (zipWriteInFileInZip(zip, (const void *)writeBuffer.data(), bufferLength) == ZIP_OK);
1443 if ( success )
1444 bytesWritten += bufferLength;
1445 else {
1446 emit log(tr("FATAL: failed writing '%1' to ZIP archive '%2'").arg(file).arg(fileName));
1447 success = false;
1448 }
1449 }
1450 storedCRCs << romCrcList->at(i);
1451 zipCloseFileInZip(zip);
1452 if ( success )
1453 reproducedDumps++;
1454 }
1455 }
1456 if ( !success ) {
1457 emit statusUpdated(setsProcessed, ++missingROMs, missingDisks);
1458 emit newMissing(id, tr("ROM"), romNameList->at(i), romSizeList->at(i), romCrcList->at(i), romSha1List->at(i), errorReason);
1459 }
1460 if ( ignoreErrors )
1461 success = true;
1462 else if ( !success ) {
1463 for (int j = i + 1; j < romNameList->count() && !exitThread; j++) {
1464 emit statusUpdated(setsProcessed, ++missingROMs, missingDisks);
1465 emit newMissing(id, tr("ROM"), romNameList->at(j), romSizeList->at(j), romCrcList->at(j), romSha1List->at(j), errorReason);
1466 }
1467 }
1468 }
1469 if ( rebuilderDialog()->romAlyzer()->checkBoxSetRewriterAddZipComment->isChecked() )
1470 zipClose(zip, tr("Created by QMC2 v%1 (%2)").arg(XSTR(QMC2_VERSION)).arg(cDT.toString(Qt::SystemLocaleShortDate)).toUtf8().constData());
1471 else
1472 zipClose(zip, "");
1473 if ( reproducedDumps == 0 )
1474 f.remove();
1475 emit log(tr("done (creating new ZIP archive '%1')").arg(fileName));
1476 } else {
1477 emit log(tr("FATAL: failed creating ZIP archive '%1'").arg(fileName));
1478 success = false;
1479 }
1480 return success;
1481 }
1482
1483 #if defined(QMC2_LIBARCHIVE_ENABLED)
writeAllArchiveData(QString baseDir,QString id,QStringList * romNameList,QStringList * romSha1List,QStringList * romCrcList,QStringList * romSizeList,QStringList *,QStringList *)1484 bool CollectionRebuilderThread::writeAllArchiveData(QString baseDir, QString id, QStringList *romNameList, QStringList *romSha1List, QStringList *romCrcList, QStringList *romSizeList, QStringList * /*diskNameList*/, QStringList * /*diskSha1List*/)
1485 {
1486 QDir d(QDir::cleanPath(baseDir));
1487 if ( !d.exists() )
1488 if ( !d.mkdir(QDir::cleanPath(baseDir)) )
1489 return false;
1490 QString fileName(QDir::cleanPath(baseDir) + "/" + id + ".zip");
1491 if ( !createBackup(fileName) ) {
1492 emit log(tr("FATAL: backup creation failed"));
1493 return false;
1494 }
1495 QFile f(fileName);
1496 if ( f.exists() )
1497 if ( !f.remove() )
1498 return false;
1499 bool success = true;
1500 bool uniqueCRCs = rebuilderDialog()->romAlyzer()->checkBoxSetRewriterUniqueCRCs->isChecked();
1501 bool ignoreErrors = !rebuilderDialog()->romAlyzer()->checkBoxSetRewriterAbortOnError->isChecked();
1502 ArchiveFile af(fileName, true, rebuilderDialog()->romAlyzer()->comboBoxSetRewriterLibArchiveDeflate->currentIndex() == 0);
1503 if ( af.open(QIODevice::WriteOnly) ) {
1504 emit log(tr("creating new ZIP archive '%1'").arg(fileName));
1505 QStringList storedCRCs;
1506 int reproducedDumps = 0;
1507 for (int i = 0; i < romNameList->count() && !exitThread && success; i++) {
1508 if ( uniqueCRCs && storedCRCs.contains(romCrcList->at(i)) ) {
1509 emit log(tr("skipping '%1'").arg(romNameList->at(i)) + " ("+ tr("a dump with CRC '%1' already exists").arg(romCrcList->at(i)) + ")");
1510 continue;
1511 }
1512 QString file(romNameList->at(i));
1513 BigByteArray data;
1514 quint64 size = romSizeList->at(i).toULongLong();
1515 QString path, member, type;
1516 QString errorReason(tr("file error"));
1517 if ( checkSumDb()->getData(romSha1List->at(i), romCrcList->at(i), &size, &path, &member, &type) ) {
1518 switch ( m_fileTypes.indexOf(type) ) {
1519 case QMC2_COLLECTIONREBUILDER_FILETYPE_ZIP:
1520 success = readZipFileData(path, romCrcList->at(i), member, &data);
1521 break;
1522 case QMC2_COLLECTIONREBUILDER_FILETYPE_7Z:
1523 success = readSevenZipFileData(path, romCrcList->at(i), member, &data);
1524 break;
1525 case QMC2_COLLECTIONREBUILDER_FILETYPE_FILE:
1526 success = readFileData(path, &data);
1527 break;
1528 default:
1529 success = false;
1530 errorReason = tr("unknown file type '%1'").arg(type);
1531 break;
1532 }
1533 if ( success && af.createEntry(file, data.size()) ) {
1534 emit log(tr("writing '%1' to ZIP archive '%2' (uncompressed size: %3)").arg(file).arg(fileName).arg(ROMAlyzer::humanReadable(data.length())));
1535 if ( !af.writeEntryDataBig(data) ) {
1536 emit log(tr("FATAL: failed writing '%1' to ZIP archive '%2'").arg(file).arg(fileName));
1537 success = false;
1538 }
1539 storedCRCs << romCrcList->at(i);
1540 af.closeEntry();
1541 if ( success )
1542 reproducedDumps++;
1543 }
1544 }
1545 if ( !success ) {
1546 emit statusUpdated(setsProcessed, ++missingROMs, missingDisks);
1547 emit newMissing(id, tr("ROM"), romNameList->at(i), romSizeList->at(i), romCrcList->at(i), romSha1List->at(i), errorReason);
1548 }
1549 if ( ignoreErrors )
1550 success = true;
1551 else if ( !success ) {
1552 for (int j = i + 1; j < romNameList->count() && !exitThread; j++) {
1553 emit statusUpdated(setsProcessed, ++missingROMs, missingDisks);
1554 emit newMissing(id, tr("ROM"), romNameList->at(j), romSizeList->at(j), romCrcList->at(j), romSha1List->at(j), errorReason);
1555 }
1556 }
1557 }
1558 af.close();
1559 if ( reproducedDumps == 0 )
1560 f.remove();
1561 emit log(tr("done (creating new ZIP archive '%1')").arg(fileName));
1562 } else {
1563 emit log(tr("FATAL: failed creating ZIP archive '%1'").arg(fileName));
1564 success = false;
1565 }
1566 return success;
1567 }
1568 #endif
1569
readFileData(QString fileName,BigByteArray * data)1570 bool CollectionRebuilderThread::readFileData(QString fileName, BigByteArray *data)
1571 {
1572 QFile file(fileName);
1573 data->clear();
1574 if ( file.open(QIODevice::ReadOnly) ) {
1575 char ioBuffer[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
1576 int len = 0;
1577 emit log(tr("reading '%1' (size: %2)").arg(fileName).arg(ROMAlyzer::humanReadable(file.size())));
1578 while ( (len = file.read(ioBuffer, QMC2_FILE_BUFFER_SIZE)) > 0 && !exitThread )
1579 data->append(QByteArray((const char *)ioBuffer, len));
1580 file.close();
1581 return true;
1582 } else {
1583 emit log(tr("FATAL: failed reading '%1'").arg(fileName));
1584 return false;
1585 }
1586 }
1587
readSevenZipFileData(QString fileName,QString crc,QString member,BigByteArray * data)1588 bool CollectionRebuilderThread::readSevenZipFileData(QString fileName, QString crc, QString member, BigByteArray *data)
1589 {
1590 SevenZipFile sevenZipFile(fileName);
1591 if ( sevenZipFile.open() ) {
1592 int index = sevenZipFile.indexOfCrc(crc);
1593 if ( index >= 0 ) {
1594 if ( sevenZipFile.isCrcDuplicate(crc) ) {
1595 int nameIndex = sevenZipFile.indexOfName(member);
1596 if ( nameIndex >= 0 )
1597 index = nameIndex;
1598 }
1599 SevenZipMetaData metaData = sevenZipFile.entryList()[index];
1600 emit log(tr("reading '%1' from 7Z archive '%2' (uncompressed size: %3)").arg(metaData.name()).arg(fileName).arg(ROMAlyzer::humanReadable(metaData.size())));
1601 quint64 readLength = sevenZipFile.readBig(index, data); // can't be interrupted!
1602 if ( sevenZipFile.hasError() ) {
1603 emit log(tr("FATAL: failed reading '%1' from 7Z archive '%2'").arg(metaData.name()).arg(fileName));
1604 return false;
1605 }
1606 if ( readLength != metaData.size() ) {
1607 emit log(tr("FATAL: failed reading '%1' from 7Z archive '%2'").arg(metaData.name()).arg(fileName));
1608 return false;
1609 }
1610 return true;
1611 } else {
1612 emit log(tr("FATAL: failed reading from 7Z archive '%1'").arg(fileName));
1613 return false;
1614 }
1615 } else {
1616 emit log(tr("FATAL: failed reading from 7Z archive '%1'").arg(fileName));
1617 return false;
1618 }
1619 }
1620
readZipFileData(QString fileName,QString crc,QString member,BigByteArray * data)1621 bool CollectionRebuilderThread::readZipFileData(QString fileName, QString crc, QString member, BigByteArray *data)
1622 {
1623 bool success = true;
1624 unzFile zipFile = unzOpen(fileName.toUtf8().constData());
1625 if ( zipFile ) {
1626 char ioBuffer[QMC2_ZIP_BUFFER_SIZE];
1627 unz_file_info zipInfo;
1628 QMultiMap<uLong, QString> crcIdentMap;
1629 uLong ulCRC = crc.toULong(0, 16);
1630 do {
1631 if ( unzGetCurrentFileInfo(zipFile, &zipInfo, ioBuffer, QMC2_ROMALYZER_ZIP_BUFFER_SIZE, 0, 0, 0, 0) == UNZ_OK )
1632 crcIdentMap.insert(zipInfo.crc, QString((const char *)ioBuffer));
1633 } while ( unzGoToNextFile(zipFile) == UNZ_OK );
1634 unzGoToFirstFile(zipFile);
1635 if ( crcIdentMap.contains(ulCRC) ) {
1636 QString fn;
1637 QStringList names(crcIdentMap.values(ulCRC));
1638 if ( names.contains(member) )
1639 fn = member;
1640 else
1641 fn = names.at(0);
1642 if ( unzLocateFile(zipFile, fn.toUtf8().constData(), 0) == UNZ_OK ) {
1643 if ( unzOpenCurrentFile(zipFile) == UNZ_OK ) {
1644 if ( unzGetCurrentFileInfo(zipFile, &zipInfo, 0, 0, 0, 0, 0, 0) == UNZ_OK )
1645 emit log(tr("reading '%1' from ZIP archive '%2' (uncompressed size: %3)").arg(fn).arg(fileName).arg(ROMAlyzer::humanReadable(zipInfo.uncompressed_size)));
1646 else
1647 emit log(tr("reading '%1' from ZIP archive '%2' (uncompressed size: %3)").arg(fn).arg(fileName).arg(tr("unknown")));
1648 qint64 len;
1649 while ( (len = unzReadCurrentFile(zipFile, ioBuffer, QMC2_ROMALYZER_ZIP_BUFFER_SIZE)) > 0 && !exitThread )
1650 data->append(QByteArray((const char *)ioBuffer, len));
1651 unzCloseCurrentFile(zipFile);
1652 } else {
1653 emit log(tr("FATAL: failed reading '%1' from ZIP archive '%2'").arg(fn).arg(fileName));
1654 success = false;
1655 }
1656 } else {
1657 emit log(tr("FATAL: failed reading '%1' from ZIP archive '%2'").arg(fn).arg(fileName));
1658 success = false;
1659 }
1660 } else {
1661 emit log(tr("FATAL: CRC '%1' not found in ZIP archive '%2'").arg(crc).arg(fileName));
1662 success = false;
1663 }
1664 unzClose(zipFile);
1665 } else {
1666 emit log(tr("FATAL: failed reading from ZIP archive '%1'").arg(fileName));
1667 success = false;
1668 }
1669 return success;
1670 }
1671
hardlinkChds(QString baseDir,QString id,QStringList * diskNameList,QStringList * diskSha1List)1672 bool CollectionRebuilderThread::hardlinkChds(QString baseDir, QString id, QStringList *diskNameList, QStringList *diskSha1List)
1673 {
1674 QString targetPath(QDir::cleanPath(baseDir) + "/" + id);
1675 QDir d(targetPath);
1676 if ( !d.exists() )
1677 if ( !d.mkdir(QDir::cleanPath(targetPath)) )
1678 return false;
1679 bool success = true;
1680 QString errorReason = tr("file error");
1681 int reproducedDumps = 0;
1682 for (int i = 0; i < diskNameList->count() && !exitThread && success; i++) {
1683 QString fileName(QDir::cleanPath(targetPath) + "/" + diskNameList->at(i) + ".chd");
1684 quint64 size = 0;
1685 QString path, member, type;
1686 if ( checkSumDb()->getData(diskSha1List->at(i), QString(), &size, &path, &member, &type) ) {
1687 if ( m_fileTypes.indexOf(type) == QMC2_COLLECTIONREBUILDER_FILETYPE_CHD ) {
1688 if ( !createBackup(fileName) ) {
1689 emit log(tr("FATAL: backup creation failed"));
1690 return false;
1691 }
1692 if ( !sameFileSystem(path, fileName) ) {
1693 emit log(tr("WARNING: '%1' and '%2' are NOT on the same file system, hard-linking will not work").arg(path).arg(fileName) + " - " + tr("falling back to copy mode"));
1694 emit log(tr("copying CHD file '%1' to '%2'").arg(path).arg(fileName));
1695 QFile targetChd(fileName);
1696 if ( targetChd.exists() )
1697 targetChd.remove();
1698 QFile sourceChd(path);
1699 if ( sourceChd.open(QIODevice::ReadOnly) ) {
1700 if ( targetChd.open(QIODevice::WriteOnly) ) {
1701 char ioBuffer[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
1702 int count = 0;
1703 int len = 0;
1704 while ( success && (len = sourceChd.read(ioBuffer, QMC2_ROMALYZER_FILE_BUFFER_SIZE)) > 0 ) {
1705 if ( count++ % QMC2_COPY_IO_RESPONSE == 0 )
1706 qApp->processEvents();
1707 if ( targetChd.write(ioBuffer, len) != len ) {
1708 emit log(tr("FATAL: I/O error while writing to '%1'").arg(fileName));
1709 success = false;
1710 }
1711 }
1712 } else
1713 success = false;
1714 } else
1715 success = false;
1716 } else {
1717 emit log(tr("hard-linking CHD file '%1' to '%2'").arg(path).arg(fileName));
1718 QFile f(fileName);
1719 if ( f.exists() )
1720 f.remove();
1721 #if defined(QMC2_OS_WIN)
1722 success = CreateHardLink((LPCTSTR)fileName.utf16(), (LPCTSTR)path.utf16(), NULL);
1723 #else
1724 success = ::link(path.toUtf8().constData(), fileName.toUtf8().constData()) == 0;
1725 #endif
1726 }
1727 if ( success )
1728 reproducedDumps++;
1729 else
1730 errorReason = tr("failed hard-linking '%1' to '%2'").arg(path).arg(fileName);
1731 } else {
1732 success = false;
1733 errorReason = tr("invalid file type '%1'").arg(type);
1734 break;
1735 }
1736 }
1737 }
1738 if ( reproducedDumps == 0 )
1739 d.rmdir(d.absolutePath());
1740 return success;
1741 }
1742
symlinkChds(QString baseDir,QString id,QStringList * diskNameList,QStringList * diskSha1List)1743 bool CollectionRebuilderThread::symlinkChds(QString baseDir, QString id, QStringList *diskNameList, QStringList *diskSha1List)
1744 {
1745 QString targetPath(QDir::cleanPath(baseDir) + "/" + id);
1746 QDir d(targetPath);
1747 if ( !d.exists() )
1748 if ( !d.mkdir(QDir::cleanPath(targetPath)) )
1749 return false;
1750 bool success = true;
1751 QString errorReason = tr("file error");
1752 int reproducedDumps = 0;
1753 for (int i = 0; i < diskNameList->count() && !exitThread && success; i++) {
1754 QString fileName(QDir::cleanPath(targetPath) + "/" + diskNameList->at(i) + ".chd");
1755 quint64 size = 0;
1756 QString path, member, type;
1757 if ( checkSumDb()->getData(diskSha1List->at(i), QString(), &size, &path, &member, &type) ) {
1758 if ( m_fileTypes.indexOf(type) == QMC2_COLLECTIONREBUILDER_FILETYPE_CHD ) {
1759 if ( !createBackup(fileName) ) {
1760 emit log(tr("FATAL: backup creation failed"));
1761 return false;
1762 }
1763 emit log(tr("sym-linking CHD file '%1' to '%2'").arg(path).arg(fileName));
1764 QFile f(fileName);
1765 if ( f.exists() )
1766 f.remove();
1767 QFile sourceChd(path);
1768 success = sourceChd.link(fileName);
1769 if ( success )
1770 reproducedDumps++;
1771 else
1772 errorReason = tr("failed sym-linking '%1' to '%2'").arg(path).arg(fileName);
1773 } else {
1774 success = false;
1775 errorReason = tr("invalid file type '%1'").arg(type);
1776 break;
1777 }
1778 }
1779 }
1780 if ( reproducedDumps == 0 )
1781 d.rmdir(d.absolutePath());
1782 return success;
1783 }
1784
copyChds(QString baseDir,QString id,QStringList * diskNameList,QStringList * diskSha1List)1785 bool CollectionRebuilderThread::copyChds(QString baseDir, QString id, QStringList *diskNameList, QStringList *diskSha1List)
1786 {
1787 QString targetPath(QDir::cleanPath(baseDir) + "/" + id);
1788 QDir d(targetPath);
1789 if ( !d.exists() )
1790 if ( !d.mkdir(QDir::cleanPath(targetPath)) )
1791 return false;
1792 bool success = true;
1793 QString errorReason = tr("file error");
1794 int reproducedDumps = 0;
1795 for (int i = 0; i < diskNameList->count() && !exitThread && success; i++) {
1796 QString fileName(QDir::cleanPath(targetPath) + "/" + diskNameList->at(i) + ".chd");
1797 quint64 size = 0;
1798 QString path, member, type;
1799 if ( checkSumDb()->getData(diskSha1List->at(i), QString(), &size, &path, &member, &type) ) {
1800 if ( m_fileTypes.indexOf(type) == QMC2_COLLECTIONREBUILDER_FILETYPE_CHD ) {
1801 if ( !createBackup(fileName) ) {
1802 emit log(tr("FATAL: backup creation failed"));
1803 return false;
1804 }
1805 emit log(tr("copying CHD file '%1' to '%2'").arg(path).arg(fileName));
1806 QFile targetChd(fileName);
1807 if ( targetChd.exists() )
1808 targetChd.remove();
1809 QFile sourceChd(path);
1810 if ( sourceChd.open(QIODevice::ReadOnly) ) {
1811 if ( targetChd.open(QIODevice::WriteOnly) ) {
1812 char ioBuffer[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
1813 int count = 0;
1814 int len = 0;
1815 while ( success && (len = sourceChd.read(ioBuffer, QMC2_ROMALYZER_FILE_BUFFER_SIZE)) > 0 ) {
1816 if ( count++ % QMC2_COPY_IO_RESPONSE == 0 )
1817 qApp->processEvents();
1818 if ( targetChd.write(ioBuffer, len) != len ) {
1819 emit log(tr("FATAL: I/O error while writing to '%1'").arg(fileName));
1820 success = false;
1821 }
1822 }
1823 } else
1824 success = false;
1825 } else
1826 success = false;
1827 if ( success )
1828 reproducedDumps++;
1829 else
1830 errorReason = tr("failed copying '%1' to '%2'").arg(path).arg(fileName);
1831 } else {
1832 success = false;
1833 errorReason = tr("invalid file type '%1'").arg(type);
1834 break;
1835 }
1836 }
1837 }
1838 if ( reproducedDumps == 0 )
1839 d.rmdir(d.absolutePath());
1840 return success;
1841 }
1842
moveChds(QString baseDir,QString id,QStringList * diskNameList,QStringList * diskSha1List)1843 bool CollectionRebuilderThread::moveChds(QString baseDir, QString id, QStringList *diskNameList, QStringList *diskSha1List)
1844 {
1845 QString targetPath(QDir::cleanPath(baseDir) + "/" + id);
1846 QDir d(targetPath);
1847 if ( !d.exists() )
1848 if ( !d.mkdir(QDir::cleanPath(targetPath)) )
1849 return false;
1850 bool success = true;
1851 QString errorReason = tr("file error");
1852 int reproducedDumps = 0;
1853 for (int i = 0; i < diskNameList->count() && !exitThread && success; i++) {
1854 QString fileName(QDir::cleanPath(targetPath) + "/" + diskNameList->at(i) + ".chd");
1855 quint64 size = 0;
1856 QString path, member, type;
1857 if ( checkSumDb()->getData(diskSha1List->at(i), QString(), &size, &path, &member, &type) ) {
1858 if ( path == fileName )
1859 continue;
1860 if ( m_fileTypes.indexOf(type) == QMC2_COLLECTIONREBUILDER_FILETYPE_CHD ) {
1861 if ( !createBackup(fileName) ) {
1862 emit log(tr("FATAL: backup creation failed"));
1863 return false;
1864 }
1865 emit log(tr("moving CHD file '%1' to '%2'").arg(path).arg(fileName));
1866 QFile targetChd(fileName);
1867 if ( targetChd.exists() )
1868 targetChd.remove();
1869 QFile sourceChd(path);
1870 if ( sameFileSystem(path, fileName) )
1871 success = QFile::rename(path, fileName);
1872 else {
1873 if ( sourceChd.open(QIODevice::ReadOnly) ) {
1874 if ( targetChd.open(QIODevice::WriteOnly) ) {
1875 char ioBuffer[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
1876 int count = 0;
1877 int len = 0;
1878 while ( success && (len = sourceChd.read(ioBuffer, QMC2_ROMALYZER_FILE_BUFFER_SIZE)) > 0 ) {
1879 if ( count++ % QMC2_COPY_IO_RESPONSE == 0 )
1880 qApp->processEvents();
1881 if ( targetChd.write(ioBuffer, len) != len ) {
1882 emit log(tr("FATAL: I/O error while writing to '%1'").arg(fileName));
1883 success = false;
1884 }
1885 }
1886 } else
1887 success = false;
1888 } else
1889 success = false;
1890 }
1891 if ( success ) {
1892 reproducedDumps++;
1893 sourceChd.remove();
1894 } else
1895 errorReason = tr("failed moving '%1' to '%2'").arg(path).arg(fileName);
1896 } else {
1897 success = false;
1898 errorReason = tr("invalid file type '%1'").arg(type);
1899 break;
1900 }
1901 }
1902 }
1903 if ( reproducedDumps == 0 )
1904 d.rmdir(d.absolutePath());
1905 return success;
1906 }
1907
sameFileSystem(QString path1,QString path2)1908 bool CollectionRebuilderThread::sameFileSystem(QString path1, QString path2)
1909 {
1910 // - path1 and path2 are expected to be fully-qualified file names
1911 // - the files may or may not exist, however, the folders they belong to MUST exist for this to work
1912 // (otherwise stat() returns ENOENT)
1913 struct stat stat1, stat2;
1914 stat(QFileInfo(path1).absoluteDir().absolutePath().toUtf8().constData(), &stat1);
1915 stat(QFileInfo(path2).absoluteDir().absolutePath().toUtf8().constData(), &stat2);
1916 return stat1.st_dev == stat2.st_dev;
1917 }
1918
createBackup(QString filePath)1919 bool CollectionRebuilderThread::createBackup(QString filePath)
1920 {
1921 if ( !rebuilderDialog()->romAlyzer()->checkBoxCreateBackups->isChecked() || rebuilderDialog()->romAlyzer()->lineEditBackupFolder->text().isEmpty() )
1922 return true;
1923 QFile sourceFile(filePath);
1924 if ( !sourceFile.exists() )
1925 return true;
1926 QDir backupDir(rebuilderDialog()->romAlyzer()->lineEditBackupFolder->text());
1927 QFileInfo backupDirInfo(backupDir.absolutePath());
1928 if ( backupDirInfo.exists() ) {
1929 if ( backupDirInfo.isWritable() ) {
1930 #if defined(QMC2_OS_WIN)
1931 QString filePathCopy = filePath;
1932 QString destinationPath = QDir::cleanPath(QString(backupDir.absolutePath() + "/" + filePathCopy.replace(":", "")));
1933 #else
1934 QString destinationPath = QDir::cleanPath(backupDir.absolutePath() + "/" + filePath);
1935 #endif
1936 QFileInfo destinationPathInfo(destinationPath);
1937 if ( !destinationPathInfo.dir().exists() ) {
1938 if ( !backupDir.mkpath(destinationPathInfo.dir().absolutePath()) ) {
1939 emit log(tr("backup") + ": " + tr("FATAL: target path '%1' cannot be created").arg(destinationPathInfo.dir().absolutePath()));
1940 return false;
1941 }
1942 }
1943 if ( !sourceFile.open(QIODevice::ReadOnly) ) {
1944 emit log(tr("backup") + ": " + tr("FATAL: source file '%1' cannot be opened for reading").arg(filePath));
1945 return false;
1946 }
1947 QFile destinationFile(destinationPath);
1948 if ( destinationFile.open(QIODevice::WriteOnly) ) {
1949 emit log(tr("backup") + ": " + tr("creating backup copy of '%1' as '%2'").arg(filePath).arg(destinationPath));
1950 char ioBuffer[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
1951 int count = 0;
1952 int len = 0;
1953 bool success = true;
1954 while ( success && (len = sourceFile.read(ioBuffer, QMC2_ROMALYZER_FILE_BUFFER_SIZE)) > 0 ) {
1955 if ( count++ % QMC2_BACKUP_IO_RESPONSE == 0 )
1956 qApp->processEvents();
1957 if ( destinationFile.write(ioBuffer, len) != len ) {
1958 emit log(tr("backup") + ": " + tr("FATAL: I/O error while writing to '%1'").arg(destinationPath));
1959 success = false;
1960 }
1961 }
1962 sourceFile.close();
1963 destinationFile.close();
1964 if ( success ) {
1965 emit log(tr("backup") + ": " + tr("done (creating backup copy of '%1' as '%2')").arg(filePath).arg(destinationPath));
1966 return true;
1967 } else
1968 return false;
1969 } else {
1970 emit log(tr("backup") + ": " + tr("FATAL: destination file '%1' cannot be opened for writing").arg(destinationPath));
1971 sourceFile.close();
1972 return false;
1973 }
1974 } else {
1975 emit log(tr("backup") + ": " + tr("FATAL: backup folder '%1' isn't writable").arg(backupDir.absolutePath()));
1976 return false;
1977 }
1978 } else {
1979 emit log(tr("backup") + ": " + tr("FATAL: backup folder '%1' doesn't exist").arg(backupDir.absolutePath()));
1980 return false;
1981 }
1982 }
1983
setFilterExpression(QString expression,int syntax,int type,bool exact)1984 void CollectionRebuilderThread::setFilterExpression(QString expression, int syntax, int type, bool exact)
1985 {
1986 doFilter = !expression.isEmpty();
1987 exactMatch = exact;
1988 isIncludeFilter = (type == 0);
1989 QRegExp::PatternSyntax ps;
1990 switch ( syntax ) {
1991 case 1:
1992 ps = QRegExp::RegExp2;
1993 break;
1994 case 2:
1995 ps = QRegExp::Wildcard;
1996 break;
1997 case 3:
1998 ps = QRegExp::WildcardUnix;
1999 break;
2000 case 4:
2001 ps = QRegExp::FixedString;
2002 break;
2003 case 5:
2004 ps = QRegExp::W3CXmlSchema11;
2005 break;
2006 case 0:
2007 default:
2008 ps = QRegExp::RegExp;
2009 break;
2010 }
2011 filterRx = QRegExp(expression, Qt::CaseSensitive, ps);
2012 if ( doFilter && !filterRx.isValid() ) {
2013 emit log(tr("WARNING: invalid filter expression '%1' ignored").arg(expression));
2014 doFilter = false;
2015 }
2016 }
2017
setFilterExpressionSoftware(QString expression,int syntax,int type,bool exact)2018 void CollectionRebuilderThread::setFilterExpressionSoftware(QString expression, int syntax, int type, bool exact)
2019 {
2020 doFilterSoftware = !expression.isEmpty();
2021 exactMatchSoftware = exact;
2022 isIncludeFilterSoftware = (type == 0);
2023 QRegExp::PatternSyntax ps;
2024 switch ( syntax ) {
2025 case 1:
2026 ps = QRegExp::RegExp2;
2027 break;
2028 case 2:
2029 ps = QRegExp::Wildcard;
2030 break;
2031 case 3:
2032 ps = QRegExp::WildcardUnix;
2033 break;
2034 case 4:
2035 ps = QRegExp::FixedString;
2036 break;
2037 case 5:
2038 ps = QRegExp::W3CXmlSchema11;
2039 break;
2040 case 0:
2041 default:
2042 ps = QRegExp::RegExp;
2043 break;
2044 }
2045 filterRxSoftware = QRegExp(expression, Qt::CaseSensitive, ps);
2046 if ( doFilterSoftware && !filterRxSoftware.isValid() ) {
2047 emit log(tr("WARNING: invalid filter expression '%1' ignored").arg(expression));
2048 doFilterSoftware = false;
2049 }
2050 }
2051
pause()2052 void CollectionRebuilderThread::pause()
2053 {
2054 pauseRequested = true;
2055 }
2056
resume()2057 void CollectionRebuilderThread::resume()
2058 {
2059 isPaused = false;
2060 }
2061
evaluateFilters(QString & setKey)2062 bool CollectionRebuilderThread::evaluateFilters(QString &setKey)
2063 {
2064 static QString list, set;
2065
2066 if ( setKey.isEmpty() )
2067 return false;
2068
2069 switch ( rebuilderDialog()->romAlyzer()->mode() ) {
2070 case QMC2_ROMALYZER_MODE_SOFTWARE: {
2071 QStringList setKeyTokens(setKey.split(':', QString::SkipEmptyParts));
2072 if ( setKeyTokens.count() < 2 )
2073 return false;
2074 list = setKeyTokens.at(0);
2075 if ( doFilterSoftware ) {
2076 if ( isIncludeFilterSoftware ) {
2077 if ( exactMatchSoftware ) {
2078 if ( !filterRxSoftware.exactMatch(list) )
2079 return false;
2080 } else if ( filterRxSoftware.indexIn(list) < 0 )
2081 return false;
2082 } else {
2083 if ( exactMatchSoftware ) {
2084 if ( filterRxSoftware.exactMatch(list) )
2085 return false;
2086 } else if ( filterRxSoftware.indexIn(list) >= 0 )
2087 return false;
2088 }
2089 }
2090 set = setKeyTokens.at(1);
2091 break;
2092 }
2093 case QMC2_ROMALYZER_MODE_SYSTEM:
2094 default:
2095 set = setKey;
2096 if ( doFilterState ) {
2097 switch ( qmc2MachineList->romState(set) ) {
2098 case 'C':
2099 if ( !includeStateC )
2100 return false;
2101 break;
2102 case 'M':
2103 if ( !includeStateM )
2104 return false;
2105 break;
2106 case 'I':
2107 if ( !includeStateI )
2108 return false;
2109 break;
2110 case 'N':
2111 if ( !includeStateN )
2112 return false;
2113 break;
2114 case 'U':
2115 default:
2116 if ( !includeStateU )
2117 return false;
2118 break;
2119 }
2120 }
2121 break;
2122 }
2123 if ( doFilter ) {
2124 if ( isIncludeFilter ) {
2125 if ( exactMatch ) {
2126 if ( !filterRx.exactMatch(set) )
2127 return false;
2128 } else if ( filterRx.indexIn(set) < 0 )
2129 return false;
2130 } else {
2131 if ( exactMatch ) {
2132 if ( filterRx.exactMatch(set) )
2133 return false;
2134 } else if ( filterRx.indexIn(set) >= 0 )
2135 return false;
2136 }
2137 }
2138 return true;
2139 }
2140
checkSumExists(QString sha1,QString crc,quint64 size)2141 bool CollectionRebuilderThread::checkSumExists(QString sha1, QString crc, quint64 size)
2142 {
2143 if ( useHashCache ) {
2144 if ( sha1.isEmpty() ) {
2145 if ( m_hashCache.contains(QString("-%1-%2").arg(crc).arg(size)) )
2146 return true;
2147 else { // rare case so shouldn't hurt
2148 QStringList uniqueKeys(m_hashCache.uniqueKeys());
2149 return uniqueKeys.indexOf(QRegExp(QString(".*-%1-%2").arg(crc).arg(size))) >= 0;
2150 }
2151 } else {
2152 if ( m_hashCache.contains(QString("%1-%2-%3").arg(sha1).arg(crc).arg(size)) )
2153 return true;
2154 else
2155 return m_hashCache.contains(QString("-%1-%2").arg(crc).arg(size));
2156 }
2157 } else
2158 return checkSumDb()->exists(sha1, crc, size);
2159 }
2160
updateHashCache()2161 void CollectionRebuilderThread::updateHashCache()
2162 {
2163 if ( checkSumDb()->scanTime() > m_hashCacheUpdateTime ) {
2164 qint64 row = checkSumDb()->nextRowId(true);
2165 emit progressTextChanged(tr("Preparing"));
2166 emit progressRangeChanged(0, checkSumDb()->checkSumRowCount() - 1);
2167 emit progressChanged(0);
2168 int count = 0;
2169 emit log(tr("updating hash cache"));
2170 m_hashCache.clear();
2171 while ( row > 0 && !exitThread && !stopRebuilding ) {
2172 emit progressChanged(count++);
2173 QString key;
2174 if ( !checkSumDb()->pathOfRow(row, &key, true).isEmpty() )
2175 m_hashCache.insert(key, true);
2176 row = checkSumDb()->nextRowId();
2177 if ( exitThread || stopRebuilding )
2178 break;
2179 }
2180 if ( exitThread || stopRebuilding ) {
2181 m_hashCacheUpdateTime = 0;
2182 m_hashCache.clear();
2183 emit log(tr("hash cache update interrupted"));
2184 } else {
2185 emit log(tr("hash cache updated") + " - " + tr("%n hash(es) loaded", "", m_hashCache.count()));
2186 m_hashCacheUpdateTime = QDateTime::currentDateTime().toTime_t();
2187 emit progressRangeChanged(m_xmlIndex, m_xmlIndexCount);
2188 emit progressChanged(m_xmlIndex);
2189 }
2190 emit progressChanged(0);
2191 }
2192 }
2193
run()2194 void CollectionRebuilderThread::run()
2195 {
2196 emit log(tr("rebuilder thread started"));
2197 while ( !exitThread ) {
2198 emit log(tr("waiting for work"));
2199 mutex.lock();
2200 isWaiting = true;
2201 isActive = isPaused = stopRebuilding = false;
2202 waitCondition.wait(&mutex);
2203 isActive = true;
2204 isWaiting = false;
2205 mutex.unlock();
2206 if ( !exitThread && !stopRebuilding ) {
2207 setsProcessed = missingROMs = missingDisks = 0;
2208 setSetEntityPattern("<" + rebuilderDialog()->lineEditSetEntity->text() + " name=\"");
2209 setRomEntityPattern("<" + rebuilderDialog()->lineEditRomEntity->text() + " name=\"");
2210 setDiskEntityPattern("<" + rebuilderDialog()->lineEditDiskEntity->text() + " name=\"");
2211 setSetEntityStartPattern("<" + rebuilderDialog()->lineEditSetEntity->text() + " name=\"");
2212 setMerge(!rebuilderDialog()->romAlyzer()->checkBoxSetRewriterSelfContainedSets->isChecked());
2213 if ( dryRun )
2214 emit log(tr("dry run started"));
2215 else
2216 emit log(tr("rebuilding started"));
2217 emit statusUpdated(0, 0, 0);
2218 emit rebuildStarted();
2219 QTime rebuildTimer, elapsedTime(0, 0, 0, 0);
2220 rebuildTimer.start();
2221 if ( useHashCache )
2222 updateHashCache();
2223 if ( dryRun )
2224 emit progressTextChanged(tr("Analyzing"));
2225 else
2226 emit progressTextChanged(tr("Rebuilding"));
2227 if ( checkpoint() < 0 )
2228 m_xmlIndex = m_xmlIndexCount = -1;
2229 QString setKey;
2230 QStringList romNameList, romSha1List, romCrcList, romSizeList, diskNameList, diskSha1List, diskSizeList;
2231 while ( !exitThread && !stopRebuilding && nextId(&setKey, &romNameList, &romSha1List, &romCrcList, &romSizeList, &diskNameList, &diskSha1List, &diskSizeList) ) {
2232 bool pauseMessageLogged = false;
2233 while ( (pauseRequested || isPaused) && !exitThread && !stopRebuilding ) {
2234 if ( !pauseMessageLogged ) {
2235 pauseMessageLogged = true;
2236 isPaused = true;
2237 pauseRequested = false;
2238 emit progressTextChanged(tr("Paused"));
2239 emit rebuildPaused();
2240 if ( dryRun )
2241 emit log(tr("dry run paused"));
2242 else
2243 emit log(tr("rebuilding paused"));
2244 }
2245 QTest::qWait(100);
2246 }
2247 if ( pauseMessageLogged && !exitThread && !stopRebuilding ) {
2248 isPaused = false;
2249 if ( dryRun )
2250 emit progressTextChanged(tr("Analyzing"));
2251 else
2252 emit progressTextChanged(tr("Rebuilding"));
2253 emit rebuildResumed();
2254 if ( dryRun )
2255 emit log(tr("dry run resumed"));
2256 else
2257 emit log(tr("rebuilding resumed"));
2258 }
2259 if ( setKey.isEmpty() )
2260 continue;
2261 if ( !exitThread && !stopRebuilding && (!romNameList.isEmpty() || !diskNameList.isEmpty()) ) {
2262 if ( !dryRun )
2263 emit log(tr("set rebuilding started for '%1'").arg(setKey));
2264 for (int i = 0; i < romNameList.count(); i++) {
2265 bool dbStatusGood = checkSumExists(romSha1List[i], romCrcList[i], romSizeList[i].toULongLong());
2266 if ( !dryRun )
2267 emit log(tr("required ROM") + ": " + tr("name = '%1', crc = '%2', sha1 = '%3', database status = '%4'").arg(romNameList[i]).arg(romCrcList[i]).arg(romSha1List[i]).arg(dbStatusGood ? tr("available") : tr("not available")));
2268 if ( !dbStatusGood ) {
2269 missingROMs++;
2270 emit newMissing(setKey, tr("ROM"), romNameList[i], romSizeList[i], romCrcList[i], romSha1List[i], tr("check-sum not available in database"));
2271 }
2272 }
2273 for (int i = 0; i < diskNameList.count(); i++) {
2274 bool dbStatusGood = checkSumExists(diskSha1List[i], useHashCache ? "0" : QString());
2275 if ( !dryRun )
2276 emit log(tr("required disk") + ": " + tr("name = '%1', sha1 = '%2', database status = '%3'").arg(diskNameList[i]).arg(diskSha1List[i]).arg(dbStatusGood ? tr("available") : tr("not available")));
2277 if ( !dbStatusGood ) {
2278 missingDisks++;
2279 emit newMissing(setKey, tr("DISK"), diskNameList[i], diskSizeList[i], QString(), diskSha1List[i], tr("check-sum not available in database"));
2280 }
2281 }
2282 emit statusUpdated(setsProcessed, missingROMs, missingDisks);
2283 if ( !dryRun ) {
2284 bool rewriteOkay = true;
2285 if ( !romNameList.isEmpty() || !diskNameList.isEmpty() )
2286 rewriteOkay = rewriteSet(&setKey, &romNameList, &romSha1List, &romCrcList, &romSizeList, &diskNameList, &diskSha1List);
2287 if ( rewriteOkay )
2288 emit log(tr("set rebuilding finished for '%1'").arg(setKey));
2289 else
2290 emit log(tr("set rebuilding failed for '%1'").arg(setKey));
2291 }
2292 emit statusUpdated(++setsProcessed, missingROMs, missingDisks);
2293 setCheckpoint(m_xmlIndex, rebuilderDialog()->comboBoxXmlSource->currentIndex());
2294 if ( !dryRun ) {
2295 QTest::qWait(1);
2296 yieldCurrentThread();
2297 }
2298 }
2299 }
2300 if ( rebuilderDialog()->defaultEmulator() ) {
2301 if ( xmlDb() )
2302 xmlDb()->clearIdAtIndexCache();
2303 if ( swlDb() )
2304 swlDb()->clearIdAtIndexCache();
2305 }
2306 elapsedTime = elapsedTime.addMSecs(rebuildTimer.elapsed());
2307 if ( dryRun )
2308 emit log(tr("dry run finished - total analysis time = %1, sets processed = %2, missing ROMs = %3, missing disks = %4").arg(elapsedTime.toString("hh:mm:ss.zzz")).arg(setsProcessed).arg(missingROMs).arg(missingDisks));
2309 else
2310 emit log(tr("rebuilding finished - total rebuild time = %1, sets processed = %2, missing ROMs = %3, missing disks = %4").arg(elapsedTime.toString("hh:mm:ss.zzz")).arg(setsProcessed).arg(missingROMs).arg(missingDisks));
2311 emit progressRangeChanged(0, 100);
2312 emit progressChanged(0);
2313 emit progressTextChanged(tr("Idle"));
2314 if ( m_xmlFile.isOpen() )
2315 m_xmlFile.close();
2316 emit rebuildFinished();
2317 }
2318 }
2319 emit log(tr("rebuilder thread ended"));
2320 }
2321