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