1 #include <QApplication>
2 #include <QFileDialog>
3 #include <QDateTime>
4 #include <QFontMetrics>
5 #include <QFont>
6 #include <QTime>
7 #include <QTest>
8 #include <QDir>
9 #include <QFileInfo>
10 #if defined(QMC2_OS_WIN)
11 #include <windows.h>
12 #endif
13 
14 #include "qmc2main.h"
15 #include "options.h"
16 #include "settings.h"
17 #include "rompathcleaner.h"
18 
19 extern MainWindow *qmc2MainWindow;
20 extern Options *qmc2Options;
21 extern Settings *qmc2Config;
22 
RomPathCleaner(const QString & settingsKey,QWidget * parent)23 RomPathCleaner::RomPathCleaner(const QString &settingsKey, QWidget *parent) :
24 	QWidget(parent),
25 	m_cleanerThread(0),
26 	m_settingsKey(settingsKey)
27 {
28 	setupUi(this);
29 	comboBoxCheckedPath->insertSeparator(QMC2_RPC_PATH_INDEX_SEPARATOR);
30 	pushButtonPauseResume->setVisible(false);
31 
32 	QFont logFont;
33 	logFont.fromString(qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/LogFont").toString());
34 	plainTextEditLog->setFont(logFont);
35 	comboBoxModeSwitch->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/Mode", QMC2_RPC_MODE_INDEX_DRYRUN).toInt());
36 	spinBoxMaxLogSize->setValue(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxLogSize", 10000).toInt());
37 	checkBoxEnableLog->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableLog", true).toBool());
38 
39 	m_cleanerThread = new RomPathCleanerThread(this);
40 	connect(cleanerThread(), SIGNAL(log(const QString &)), this, SLOT(log(const QString &)));
41 	connect(cleanerThread(), SIGNAL(checkStarted()), this, SLOT(cleanerThread_checkStarted()));
42 	connect(cleanerThread(), SIGNAL(checkFinished()), this, SLOT(cleanerThread_checkFinished()));
43 	connect(cleanerThread(), SIGNAL(checkPaused()), this, SLOT(cleanerThread_checkPaused()));
44 	connect(cleanerThread(), SIGNAL(checkResumed()), this, SLOT(cleanerThread_checkResumed()));
45 	connect(cleanerThread(), SIGNAL(progressTextChanged(const QString &)), this, SLOT(cleanerThread_progressTextChanged(const QString &)));
46 	connect(cleanerThread(), SIGNAL(progressRangeChanged(int, int)), this, SLOT(cleanerThread_progressRangeChanged(int, int)));
47 	connect(cleanerThread(), SIGNAL(progressChanged(int)), this, SLOT(cleanerThread_progressChanged(int)));
48 	connect(cleanerThread(), SIGNAL(statusUpdated(quint64, quint64, quint64, quint64, quint64)), this, SLOT(cleanerThread_statusUpdated(quint64, quint64, quint64, quint64, quint64)));
49 }
50 
~RomPathCleaner()51 RomPathCleaner::~RomPathCleaner()
52 {
53 	hideEvent(0);
54 	if ( cleanerThread() )
55 		delete cleanerThread();
56 }
57 
adjustIconSizes()58 void RomPathCleaner::adjustIconSizes()
59 {
60 	QFont f(qApp->font());
61 	QFontMetrics fm(f);
62 	QSize iconSize(fm.height() - 2, fm.height() - 2);
63 	comboBoxCheckedPath->setIconSize(iconSize);
64 	pushButtonStartStop->setIconSize(iconSize);
65 	pushButtonPauseResume->setIconSize(iconSize);
66 }
67 
log(const QString & message)68 void RomPathCleaner::log(const QString &message)
69 {
70 	if ( checkBoxEnableLog->isChecked() )
71 		plainTextEditLog->appendPlainText(QDateTime::currentDateTime().toString("hh:mm:ss.zzz") + ": " + message);
72 }
73 
cleanerThread_checkStarted()74 void RomPathCleaner::cleanerThread_checkStarted()
75 {
76 	pushButtonStartStop->setIcon(QIcon(QString::fromUtf8(":/data/img/halt.png")));
77 	pushButtonStartStop->setText(tr("Stop check"));
78 	pushButtonPauseResume->setText(tr("Pause"));
79 	pushButtonPauseResume->show();
80 	pushButtonStartStop->setEnabled(true);
81 	pushButtonPauseResume->setEnabled(true);
82 	labelCheckedPath->setEnabled(false);
83 	comboBoxCheckedPath->setEnabled(false);
84 	labelModeSwitch->setEnabled(false);
85 	comboBoxModeSwitch->setEnabled(false);
86 }
87 
cleanerThread_checkFinished()88 void RomPathCleaner::cleanerThread_checkFinished()
89 {
90 	pushButtonStartStop->setIcon(QIcon(QString::fromUtf8(":/data/img/refresh.png")));
91 	pushButtonStartStop->setText(tr("Start check"));
92 	pushButtonPauseResume->hide();
93 	pushButtonStartStop->setEnabled(true);
94 	pushButtonPauseResume->setEnabled(true);
95 	labelCheckedPath->setEnabled(true);
96 	comboBoxCheckedPath->setEnabled(true);
97 	labelModeSwitch->setEnabled(true);
98 	comboBoxModeSwitch->setEnabled(true);
99 }
100 
cleanerThread_checkPaused()101 void RomPathCleaner::cleanerThread_checkPaused()
102 {
103 	pushButtonPauseResume->setText(tr("Resume"));
104 	pushButtonPauseResume->setEnabled(true);
105 }
106 
cleanerThread_checkResumed()107 void RomPathCleaner::cleanerThread_checkResumed()
108 {
109 	pushButtonPauseResume->setText(tr("Pause"));
110 	pushButtonPauseResume->setEnabled(true);
111 }
112 
cleanerThread_progressTextChanged(const QString & text)113 void RomPathCleaner::cleanerThread_progressTextChanged(const QString &text)
114 {
115 	progressBar->setFormat(text);
116 }
117 
cleanerThread_progressRangeChanged(int min,int max)118 void RomPathCleaner::cleanerThread_progressRangeChanged(int min, int max)
119 {
120 	progressBar->setRange(min, max);
121 }
122 
cleanerThread_progressChanged(int progress)123 void RomPathCleaner::cleanerThread_progressChanged(int progress)
124 {
125 	progressBar->setValue(progress);
126 }
127 
cleanerThread_statusUpdated(quint64 filesProcessed,quint64 renamedFiles,quint64 obsoleteROMs,quint64 obsoleteDisks,quint64 invalidFiles)128 void RomPathCleaner::cleanerThread_statusUpdated(quint64 filesProcessed, quint64 renamedFiles, quint64 obsoleteROMs, quint64 obsoleteDisks, quint64 invalidFiles)
129 {
130 	QString statusString("<table border=\"0\" cellpadding=\"0\" cellspacing=\"4\" width=\"100%\"><tr>");
131 	statusString += "<td nowrap align=\"left\"><b>" + tr("Files processed") + ":</b></td><td nowrap align=\"right\">" + QString::number(filesProcessed) + "</td>";
132 	statusString += "<td nowrap align=\"center\" width=\"1%\">|</td>";
133 	statusString += "<td nowrap align=\"left\"><b>" + tr("Renamed files") + ":</b></td><td nowrap align=\"right\">" + QString::number(renamedFiles) + "</td>";
134 	statusString += "<td nowrap align=\"center\" width=\"1%\">|</td>";
135 	statusString += "<td nowrap align=\"left\"><b>" + tr("Obsolete ROMs / disks") + ":</b></td><td nowrap align=\"right\">" + QString::number(obsoleteROMs) + " / " + QString::number(obsoleteDisks) + "</td>";
136 	statusString += "<td nowrap align=\"right\" width=\"1%\">|</td>";
137 	statusString += "<td nowrap align=\"left\"><b>" + tr("Invalid files") + ":</b></td><td nowrap align=\"right\">" + QString::number(invalidFiles) + "</td>";
138 	statusString += "</tr></table>";
139 	labelStatus->setText(statusString);
140 }
141 
on_comboBoxCheckedPath_activated(int index)142 void RomPathCleaner::on_comboBoxCheckedPath_activated(int index)
143 {
144 	if ( index == QMC2_RPC_PATH_INDEX_SELECT ) {
145 		QString path(QFileDialog::getExistingDirectory(this, tr("Select path to be checked"), QString(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks | (qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog)));
146 		if ( !path.isEmpty() ) {
147 			while ( comboBoxCheckedPath->count() > QMC2_RPC_PATH_INDEX_CUSTOMPATH )
148 				comboBoxCheckedPath->removeItem(comboBoxCheckedPath->count() - 1);
149 			comboBoxCheckedPath->insertItem(QMC2_RPC_PATH_INDEX_CUSTOMPATH, path);
150 			comboBoxCheckedPath->setCurrentIndex(QMC2_RPC_PATH_INDEX_CUSTOMPATH);
151 		} else
152 			comboBoxCheckedPath->setCurrentIndex(QMC2_RPC_PATH_INDEX_ROMPATH);
153 	}
154 }
155 
on_pushButtonStartStop_clicked()156 void RomPathCleaner::on_pushButtonStartStop_clicked()
157 {
158 	pushButtonStartStop->setEnabled(false);
159 	pushButtonStartStop->update();
160 	pushButtonPauseResume->setEnabled(false);
161 	pushButtonPauseResume->update();
162 	qApp->processEvents();
163 	if ( cleanerThread()->active() )
164 		cleanerThread()->requestStop();
165 	else {
166 		switch ( comboBoxCheckedPath->currentIndex() ) {
167 			case QMC2_RPC_PATH_INDEX_CUSTOMPATH:
168 				cleanerThread()->setCheckedPaths(QStringList() << comboBoxCheckedPath->currentText());
169 				break;
170 			default:
171 			case QMC2_RPC_PATH_INDEX_ROMPATH:
172 				if ( qmc2Config->contains(QMC2_EMULATOR_PREFIX + "Configuration/Global/rompath") )
173 					cleanerThread()->setCheckedPaths(qmc2Config->value(QMC2_EMULATOR_PREFIX + "Configuration/Global/rompath", QString()).toString().split(';', QString::SkipEmptyParts));
174 				else if ( qmc2Config->contains(QMC2_EMULATOR_PREFIX + "FilesAndDirectories/WorkingDirectory") )
175 					cleanerThread()->setCheckedPaths(QStringList() << qmc2Config->value(QMC2_EMULATOR_PREFIX + "FilesAndDirectories/WorkingDirectory", QString()).toString() + "/roms");
176 				else
177 					cleanerThread()->setCheckedPaths(QStringList() << "roms");
178 				break;
179 		}
180 		plainTextEditLog->clear();
181 		cleanerThread()->waitCondition().wakeAll();
182 	}
183 }
184 
on_pushButtonPauseResume_clicked()185 void RomPathCleaner::on_pushButtonPauseResume_clicked()
186 {
187 	pushButtonPauseResume->setEnabled(false);
188 	if ( cleanerThread()->paused() )
189 		QTimer::singleShot(0, cleanerThread(), SLOT(resume()));
190 	else
191 		QTimer::singleShot(0, cleanerThread(), SLOT(pause()));
192 }
193 
on_spinBoxMaxLogSize_valueChanged(int value)194 void RomPathCleaner::on_spinBoxMaxLogSize_valueChanged(int value)
195 {
196 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxLogSize", value);
197 	plainTextEditLog->setMaximumBlockCount(value);
198 }
199 
hideEvent(QHideEvent * e)200 void RomPathCleaner::hideEvent(QHideEvent *e)
201 {
202 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/Mode", comboBoxModeSwitch->currentIndex());
203 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableLog", checkBoxEnableLog->isChecked());
204 }
205 
RomPathCleanerThread(QObject * parent)206 RomPathCleanerThread::RomPathCleanerThread(QObject *parent) :
207 	QThread(parent),
208 	m_exit(false),
209 	m_stop(false),
210 	m_active(false),
211 	m_waiting(false),
212 	m_paused(false),
213 	m_filesProcessed(0),
214 	m_renamedFiles(0),
215 	m_obsoleteROMs(0),
216 	m_obsoleteDisks(0),
217 	m_invalidFiles(0)
218 {
219 	start();
220 }
221 
~RomPathCleanerThread()222 RomPathCleanerThread::~RomPathCleanerThread()
223 {
224 	requestExit();
225 	m_waitCondition.wakeAll();
226 	wait();
227 }
228 
run()229 void RomPathCleanerThread::run()
230 {
231 	emit log(tr("cleaner thread started"));
232 	while ( !m_exit && !m_stop ) {
233 		emit log(tr("waiting for work"));
234 		m_mutex.lock();
235 		m_waiting = true;
236 		m_active = m_paused = false;
237 		m_checkedPaths.clear();
238 		m_waitCondition.wait(&m_mutex);
239 		m_active = true;
240 		m_waiting = m_stop = false;
241 		m_mutex.unlock();
242 		if ( !m_exit && !m_stop ) {
243 			m_filesProcessed = m_renamedFiles = m_obsoleteROMs = m_obsoleteDisks = m_invalidFiles = 0;
244 			emit statusUpdated(m_filesProcessed, m_renamedFiles, m_obsoleteROMs, m_obsoleteDisks, m_invalidFiles);
245 			emit log(tr("check started"));
246 			emit checkStarted();
247 			QTime checkTimer, elapsedTime(0, 0, 0, 0);
248 			checkTimer.start();
249 			int pathCount = 1;
250 			foreach (QString path, m_checkedPaths) {
251 				path = QDir::cleanPath(path);
252 				emit progressTextChanged(tr("Cleaning up path %1 / %2").arg(pathCount++).arg(m_checkedPaths.count()));
253 				emit log(tr("checking path '%1'").arg(path));
254 				QStringList fileList;
255 				emit log(tr("reading directory tree"));
256 				recursiveFileList(path, &fileList);
257 				emit log(tr("path contains %n file(s)", "", fileList.count()));
258 				emit progressRangeChanged(0, fileList.count());
259 				emit progressChanged(0);
260 				int index = 0;
261 				while ( !m_exit && !m_stop && index < fileList.count()) {
262 					if ( m_paused ) {
263 						emit log(tr("check paused"));
264 						emit checkPaused();
265 						while ( m_paused && !m_stop && !m_exit )
266 							QTest::qWait(100);
267 						if ( !m_paused ) {
268 							emit log(tr("check resumed"));
269 							emit checkResumed();
270 						}
271 					}
272 					if ( !m_paused && !m_stop && !m_exit ) {
273 						QString filePath(fileList.at(index));
274 						// FIXME
275 					}
276 					if ( m_filesProcessed % QMC2_RPC_STATUS_UPDATE == 0 ) {
277 						emit statusUpdated(m_filesProcessed, m_renamedFiles, m_obsoleteROMs, m_obsoleteDisks, m_invalidFiles);
278 						emit progressChanged(index);
279 						QTest::qWait(1);
280 					}
281 					m_filesProcessed++;
282 					index++;
283 				}
284 				emit log(tr("done (checking path '%1')").arg(path));
285 			}
286 			emit statusUpdated(m_filesProcessed, m_renamedFiles, m_obsoleteROMs, m_obsoleteDisks, m_invalidFiles);
287 			elapsedTime = elapsedTime.addMSecs(checkTimer.elapsed());
288 			emit log(tr("check finished") + " - " + tr("total check time = %1, files processed = %2, renamed files = %3, obsolete ROMs = %4, obsolete disks = %5, invalid files = %6").arg(elapsedTime.toString("hh:mm:ss.zzz")).arg(m_filesProcessed).arg(m_renamedFiles).arg(m_obsoleteROMs).arg(m_obsoleteDisks).arg(m_invalidFiles));
289 			emit checkFinished();
290 		}
291 		emit progressRangeChanged(0, 100);
292 		emit progressChanged(0);
293 		emit progressTextChanged(tr("Idle"));
294 		m_stop = false;
295 	}
296 	emit log(tr("cleaner thread ended"));
297 }
298 
recursiveFileList(const QString & sDir,QStringList * fileNames)299 void RomPathCleanerThread::recursiveFileList(const QString &sDir, QStringList *fileNames)
300 {
301 	if ( m_exit || m_stop )
302 		return;
303 #if defined(QMC2_OS_WIN)
304 	WIN32_FIND_DATA ffd;
305 	QString dirName(QDir::toNativeSeparators(QDir::cleanPath(sDir + "/*")));
306 #ifdef UNICODE
307 	HANDLE hFind = FindFirstFile((TCHAR *)dirName.utf16(), &ffd);
308 #else
309 	HANDLE hFind = FindFirstFile((TCHAR *)dirName.toUtf8().constData(), &ffd);
310 #endif
311 	if ( !m_exit && !m_stop && hFind != INVALID_HANDLE_VALUE ) {
312 		do {
313 #ifdef UNICODE
314 			QString fName(QString::fromUtf16((ushort*)ffd.cFileName));
315 #else
316 			QString fName(QString::fromLocal8Bit(ffd.cFileName));
317 #endif
318 			if ( ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
319 				if ( fName != ".." && fName != "." )
320 					recursiveFileList(sDir + "/" + fName, fileNames);
321 			} else
322 				fileNames->append(sDir + "/" + fName);
323 		} while ( !m_exit && !m_stop && FindNextFile(hFind, &ffd) != 0 );
324 	}
325 #else
326 	QDir dir(sDir);
327 	foreach (QFileInfo info, dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden | QDir::System)) {
328 		if ( m_exit || m_stop )
329 			break;
330 		QString path(info.filePath());
331 		if ( info.isDir() ) {
332 			// directory recursion
333 			if ( info.fileName() != ".." && info.fileName() != "." )
334 				recursiveFileList(path, fileNames);
335 		} else
336 			fileNames->append(path);
337 	}
338 #endif
339 }
340