/* 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 "consistencycheck.h" #include "helper.h" ConsistencyCheck::ConsistencyCheck(QWidget *parent, Qt::WindowFlags f) : SmDialog(parent, f), mChecker(0){ // setup widget QLabel *okLabel = new QLabel(tr("Ok")); mOkDisplay = new QPlainTextEdit; mOkDisplay->setReadOnly(true); mOkDisplay->setLineWrapMode(QPlainTextEdit::NoWrap); QLabel *errorLabel = new QLabel(tr("Errors")); mErrorDisplay = new QPlainTextEdit; mErrorDisplay->setReadOnly(true); mErrorDisplay->setLineWrapMode(QPlainTextEdit::NoWrap); 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))); // main layout QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(okLabel); mainLayout->addWidget(mOkDisplay); mainLayout->addWidget(errorLabel); mainLayout->addWidget(mErrorDisplay); mainLayout->addLayout(buttonLayout); setWindowTitle(tr("Checking consistency")); setLayout(mainLayout); setMinimumWidth(600); } 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(){ 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->strayIds().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->strayIds(); if(!fileIds.isEmpty()){ QString msg = QString(tr("Really delete %1 entries 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->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::strayIds(){ QMutexLocker locker(&mStrayIdsMutex); return mStrayIds; } void ConsistencyChecker::clearStrayIds(){ QMutexLocker locker(&mStrayIdsMutex); mStrayIds.clear(); } void ConsistencyChecker::setCancel(bool cancel){ QMutexLocker locker(&mCancelMutex); mCanceled = cancel; } void ConsistencyChecker::deleteIds(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::run(){ clearStrayFiles(); clearStrayIds(); if(mMode == ConsistencyCheck::FsCheck){ fsCheck(); } if(mMode == ConsistencyCheck::DbCheck){ dbCheck(); } } void ConsistencyChecker::dbCheck(){ QSqlQuery fileDbQuery = QSqlQuery("SELECT tfilename, cmd5sum, idvd, ifiles_id FROM files ORDER BY tfilename", mDb); while(fileDbQuery.next()){ 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{ mStrayIdsMutex.lock(); mStrayIds << fileId; mStrayIdsMutex.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::fsCheck(){ mStrayFilesMutex.lock(); mStrayFiles.clear(); mStrayFilesMutex.unlock(); QSettings s; QString startDir = s.value("paths/archivedir").toString(); QFileInfo startFi(startDir); doFsCheck(startFi); quit(); } void ConsistencyChecker::doFsCheck(const QFileInfo &start){ if(start.isDir()){ QMutexLocker locker(&mCancelMutex); if(mCanceled){ quit(); }else{ locker.unlock(); } QDir curDir(start.absoluteFilePath()); foreach(QFileInfo fi, curDir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)){ if(fi.isDir()){ doFsCheck(fi); }else{ QString md5sum = Helper::md5Sum(fi.absoluteFilePath()); mFileQuery->bindValue(":md5sum", md5sum); mFileQuery->exec(); while(mFileQuery->next()){ int count = mFileQuery->value(0).toInt(); if(count > 0){ 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)); } } } } } } 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; }