/* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "consistencycheck.h" #include "helper.h" ConsistencyCheck::ConsistencyCheck(QWidget *parent, Qt::WindowFlags f) : SmDialog(parent, f), mChecker(0){ // setup widget // OK QGroupBox *okBox = new QGroupBox(tr("Ok")); QHBoxLayout *okLayout = new QHBoxLayout; mOkDisplay = new QPlainTextEdit; mOkDisplay->setReadOnly(true); mOkDisplay->setLineWrapMode(QPlainTextEdit::NoWrap); okLayout->addWidget(mOkDisplay); okBox->setLayout(okLayout); // Errors QGroupBox *errorBox = new QGroupBox(tr("Errors")); QHBoxLayout *errorLayout = new QHBoxLayout; mErrorDisplay = new QPlainTextEdit; mErrorDisplay->setReadOnly(true); mErrorDisplay->setLineWrapMode(QPlainTextEdit::NoWrap); errorLayout->addWidget(mErrorDisplay); errorBox->setLayout(errorLayout); // Progress QGroupBox *progressBox = new QGroupBox(tr("Progress")); QVBoxLayout *progressLayout = new QVBoxLayout; mProgress = new QProgressBar(); progressLayout->addWidget(mProgress); mActivity = new QLabel(tr("Idle...")); progressLayout->addWidget(mActivity); progressBox->setLayout(progressLayout); // Buttons mCancelExit = new QPushButton(tr("Close")); mCheckDb = new QPushButton(tr("Check database")); mCheckFs = new QPushButton(tr("Check Filesystem")); mCleanup = new QPushButton(tr("Cleanup...")); connect(mCleanup, SIGNAL(clicked()), this, SLOT(cleanup())); mCleanup->setEnabled(false); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addWidget(mCheckDb); buttonLayout->addWidget(mCheckFs); buttonLayout->addWidget(mCleanup); buttonLayout->addStretch(); buttonLayout->addWidget(mCancelExit); connect(mCancelExit, SIGNAL(clicked()), this, SLOT(accept())); // signal mapper QSignalMapper *mapper = new QSignalMapper(this); connect(mCheckDb, SIGNAL(clicked()), mapper, SLOT(map())); mapper->setMapping(mCheckDb, ConsistencyCheck::DbCheck); connect(mCheckFs, SIGNAL(clicked()), mapper, SLOT(map())); mapper->setMapping(mCheckFs, ConsistencyCheck::FsCheck); connect(mapper, SIGNAL(mapped(int)), this, SLOT(startChecker(int))); //misc mChecker = new ConsistencyChecker(this); connect(mChecker, SIGNAL(started()), this, SLOT(checkerStarted())); connect(mChecker, SIGNAL(finished()), this, SLOT(checkerFinished())); connect(mChecker, SIGNAL(consistencyMsg(QString)), this, SLOT(addMessage(QString))); connect(mChecker, SIGNAL(approxTotal(int)), this, SLOT(setProgressBarMax(int))); connect(mChecker, SIGNAL(progress(int)), this, SLOT(setProgress(int))); // main layout QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(okBox); mainLayout->addWidget(errorBox); mainLayout->addWidget(progressBox); mainLayout->addLayout(buttonLayout); setWindowTitle(tr("Checking consistency")); setLayout(mainLayout); setMinimumWidth(600); } ConsistencyCheck::~ConsistencyCheck(){ mChecker->deleteLater(); } void ConsistencyCheck::setProgressBarMax(int max){ mProgress->reset(); mProgress->setMaximum(max); QString activity = QString(tr("Expecting %1 files")).arg(QString::number(max)); mActivity->setText(activity); } void ConsistencyCheck::setProgress(int value){ if(value >= mProgress->maximum()){ mProgress->setMaximum(value + 100); QString activity = QString(tr("Just got pregnant. Expecting %1 now.")).arg(QString::number(value + 100)); mActivity->setText(activity); } mProgress->setValue(value); } void ConsistencyCheck::startChecker(int checkerType){ if(mChecker->status() == ConsistencyChecker::Fail){ return; } mOkDisplay->clear(); mErrorDisplay->clear(); switch(checkerType){ case DbCheck: mChecker->setMode(ConsistencyCheck::DbCheck); break; case FsCheck: mChecker->setMode(ConsistencyCheck::FsCheck); ; break; default: ; } mChecker->check(); } void ConsistencyCheck::addMessage(const QString &message){ QTextCursor cursor; QTextCharFormat fmt; if(message.startsWith("OK")){ cursor = QTextCursor(mOkDisplay->textCursor()); fmt.setForeground(QBrush(Qt::green)); }else{ cursor = QTextCursor(mErrorDisplay->textCursor()); fmt.setForeground(QBrush(Qt::red)); } QTextBlock curBlock = cursor.block(); if(!curBlock.text().isEmpty()){ cursor.insertBlock(); } cursor.setCharFormat(fmt); cursor.insertText(message); } void ConsistencyCheck::checkerStarted(){ mCheckDb->setEnabled(false); mCheckFs->setEnabled(false); mCancelExit->setText(tr("Cancel")); mCancelExit->disconnect(); connect(mCancelExit, SIGNAL(clicked()), this, SLOT(cancelChecker())); } void ConsistencyCheck::checkerFinished(){ mProgress->setValue(mProgress->maximum()); mActivity->setText(tr("Idle...")); mCheckDb->setEnabled(true); mCheckFs->setEnabled(true); mCancelExit->setText(tr("Close")); mCancelExit->disconnect(); connect(mCancelExit, SIGNAL(clicked()), this, SLOT(accept())); mChecker->setCancel(false); if(!mChecker->strayFiles().isEmpty() || !mChecker->strayFileIds().isEmpty()){ mCleanup->setEnabled(true); } } void ConsistencyCheck::cancelChecker(){ mChecker->setCancel(true); } void ConsistencyCheck::cleanup(){ QStringList strayFiles = mChecker->strayFiles(); moveFiles(strayFiles); if(mChecker->mode() == DbCheck){ QList fileIds = mChecker->strayFileIds(); if(!fileIds.isEmpty()){ QString msg = QString(tr("Really delete %1 files from database?")).arg(QString::number(fileIds.count())); int retval = QMessageBox::critical(this, tr("Delete from database"), msg, QMessageBox::Yes | QMessageBox::No); if(retval == QMessageBox::Yes){ mChecker->deleteFileIds(fileIds); } } QList picIds = mChecker->strayPicIds(); if(!picIds.isEmpty()){ QString msg = QString(tr("Really delete %1 pictures from database?")).arg(QString::number(picIds.count())); int retval = QMessageBox::critical(this, tr("Delete from database"), msg, QMessageBox::Yes | QMessageBox::No); if(retval == QMessageBox::Yes){ //mChecker->deleteIds(fileIds); } } } mCleanup->setEnabled(false); } void ConsistencyCheck::moveFiles(const QStringList &files){ if(files.isEmpty()){ return; } QSettings s; QString startDir = s.value("paths/selecteddir").toString(); QString targetDir = QFileDialog::getExistingDirectory(this, tr("Move stray files to..."), startDir); if(!targetDir.isEmpty()){ foreach(QString file, files){ QFileInfo fi(file); QString tgt = QString("%1%2%3").arg(targetDir).arg(QDir::separator()).arg(fi.fileName()); QFileInfo tfi(tgt); if(tfi.exists()){ QString msg = QString(tr("Target %1 exists. Overwrite?")).arg(tfi.absoluteFilePath()); int retval = QMessageBox::critical(this, tr("File exists"), msg, QMessageBox::Yes | QMessageBox::No); if(retval == QMessageBox::No){ continue; } } QFile::rename(file, tgt); } } } ConsistencyChecker::ConsistencyChecker(QObject *parent) : QThread(parent), mCanceled(false), mMode(-1), mStatus(Ok){ mDb = QSqlDatabase::cloneDatabase(QSqlDatabase::database("treedb"), "checkerDb"); if(!mDb.open()){ mStatus = Fail; } mFileQuery = new QSqlQuery(mDb); mFileQuery->prepare("SELECT COUNT(*) FROM files where cmd5sum = :md5"); } ConsistencyChecker::~ConsistencyChecker(){ delete mFileQuery; mDb.close(); } void ConsistencyChecker::check(){ if(!isRunning()){ start(); } } const QStringList ConsistencyChecker::strayFiles(){ QMutexLocker locker(&mStrayFilesMutex); return mStrayFiles; } void ConsistencyChecker::clearStrayFiles(){ QMutexLocker locker(&mStrayFilesMutex); mStrayFiles.clear(); } const QList ConsistencyChecker::strayFileIds(){ QMutexLocker locker(&mStrayFileIdMutex); return mStrayFileIds; } const QList ConsistencyChecker::strayPicIds(){ QMutexLocker locker(&mStrayPicsIdMutex); return mStrayPicIds; } void ConsistencyChecker::clearStrayIds(){ QMutexLocker locker(&mStrayFileIdMutex); mStrayFileIds.clear(); } void ConsistencyChecker::setCancel(bool cancel){ QMutexLocker locker(&mCancelMutex); mCanceled = cancel; } void ConsistencyChecker::deleteFileIds(const QList &ids){ QSqlQuery idQuery(mDb); idQuery.prepare("DELETE FROM files WHERE ifiles_id = :id"); foreach(int id, ids){ idQuery.bindValue(":id", id); idQuery.exec(); } } void ConsistencyChecker::deletePicIds(const QList &ids){ QSqlQuery idQuery(mDb); idQuery.prepare("DELETE FROM pics WHERE ipicsid = :id"); foreach(int id, ids){ idQuery.bindValue(":id", id); idQuery.exec(); } } void ConsistencyChecker::run(){ clearStrayFiles(); clearStrayIds(); if(mMode == ConsistencyCheck::FsCheck){ fsCheck(); } if(mMode == ConsistencyCheck::DbCheck){ dbCheck(); } } void ConsistencyChecker::doDbCheckFiles(){ QSqlQuery fileDbQuery = QSqlQuery("SELECT tfilename, cmd5sum, idvd, ifiles_id FROM files ORDER BY tfilename", mDb); while(fileDbQuery.next()){ ++mCurCount; if(mCurCount % 50 == 0){ emit progress(mCurCount); } QMutexLocker locker(&mCancelMutex); if(mCanceled){ quit(); }else{ locker.unlock(); } QString fileName = fileDbQuery.value(0).toString(); QString md5sum = fileDbQuery.value(1).toString(); int dvdNo = fileDbQuery.value(2).toInt(); int fileId = fileDbQuery.value(3).toInt(); QString fullPath = archivePath(fileName, md5sum); if(fullPath.isEmpty()){ if(dvdNo != -1){ emit consistencyMsg(QString(tr("OK: %1")).arg(fileName)); }else{ mStrayFileIdMutex.lock(); mStrayFileIds << fileId; mStrayFileIdMutex.unlock(); emit consistencyMsg(QString(tr("NOT FOUND: %1")).arg(fileName)); } }else{ if(dvdNo == -1){ emit consistencyMsg(QString(tr("OK: %1")).arg(fileName)); }else{ QString mimeType = Helper::mimeType(fullPath); if(mimeType.startsWith("image")){ emit consistencyMsg(QString(tr("OK: %1")).arg(fileName)); }else{ mStrayFilesMutex.lock(); mStrayFiles << fullPath; mStrayFilesMutex.unlock(); QString msg = QString(tr("FOUND: %1 (DVD: %2)")).arg(fullPath).arg(dvdNo); emit consistencyMsg(msg); } } } } quit(); } void ConsistencyChecker::doDbCheckPics(){ QSqlQuery picsQuery = QSqlQuery("SELECT tfilename, cmd5sum, ipicsid FROM pics ORDER BY tfilename", mDb); while(picsQuery.next()){ ++mCurCount; if(mCurCount % 50 == 0){ emit progress(mCurCount); } QMutexLocker locker(&mCancelMutex); if(mCanceled){ quit(); }else{ locker.unlock(); } QString fileName = picsQuery.value(0).toString(); QString md5sum = picsQuery.value(1).toString(); QString fullPath = archivePath(fileName, md5sum); if(fullPath.isEmpty()){ mStrayPicsIdMutex.lock(); mStrayPicIds << picsQuery.value(2).toInt(); mStrayPicsIdMutex.unlock(); emit consistencyMsg(QString(tr("NOT FOUND: %1")).arg(fileName)); }else{ emit consistencyMsg(QString(tr("OK: %1")).arg(fileName)); } } } void ConsistencyChecker::dbCheck(){ QSqlQuery numFilesQ("SELECT COUNT(*) FROM files", mDb); int numFiles; while(numFilesQ.next()){ numFiles = numFilesQ.value(0).toInt(); } QSqlQuery numPicsQ("SELECT COUNT(*) FROM pics", mDb); int numPics; while(numPicsQ.next()){ numPics = numPicsQ.value(0).toInt(); } emit approxTotal(numFiles + numPics); mCurCount = 0; doDbCheckFiles(); doDbCheckPics(); quit(); } void ConsistencyChecker::fsCheck(){ mStrayFilesMutex.lock(); mStrayFiles.clear(); mStrayFilesMutex.unlock(); QVector md5sums; QSqlQuery picsmd5("SELECT cmd5sum FROM pics", mDb); while(picsmd5.next()){ md5sums << picsmd5.value(0).toString(); } QSqlQuery filesmd5("SELECT cmd5sum FROM files", mDb); while(filesmd5.next()){ md5sums << filesmd5.value(0).toString(); } emit approxTotal(md5sums.size()); mCurCount = 0; QSettings s; QString startDir = s.value("paths/archivedir").toString(); QFileInfo startFi(startDir); doFsCheck(startFi, md5sums); quit(); } void ConsistencyChecker::doFsCheck(const QFileInfo &start, const QVector &md5sums){ if(start.isDir()){ QDir curDir(start.absoluteFilePath()); foreach(QFileInfo fi, curDir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)){ mCancelMutex.lock(); if(mCanceled){ mCancelMutex.unlock(); return; } mCancelMutex.unlock(); if(fi.isDir()){ doFsCheck(fi, md5sums); }else{ QString md5sum = Helper::md5Sum(fi.absoluteFilePath()); if(md5sums.contains(md5sum)){ emit consistencyMsg(QString(tr("OK: %1 -> %2")).arg(fi.fileName()).arg(md5sum)); }else{ mStrayFilesMutex.lock(); mStrayFiles << fi.absoluteFilePath(); mStrayFilesMutex.unlock(); emit consistencyMsg(QString(tr("NOT FOUND: %1 -> %2")).arg(fi.fileName()).arg(md5sum)); } ++mCurCount; if(mCurCount % 50 == 0){ emit progress(mCurCount); } } } } } QString ConsistencyChecker::archivePath(const QString &fileName, const QString &md5sum) const { QString filePath = Helper::createArchivePath(fileName, md5sum); QFileInfo fi(filePath); if(!fi.exists()){ filePath = Helper::createArchivePath(fileName, md5sum, true); fi = QFileInfo(filePath); if(!fi.exists()){ filePath.clear(); } } return filePath; }