/* 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 "dbanalyzer.h" #include "smtreemodel.h" #include "smtreeitem.h" DbAnalyzerDialog::DbAnalyzerDialog(QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f), mMarkMode(DbAnalyzer::NOMARKS), mCurrentView(0){ //create tab widget mTab = new QTabWidget; //setup analyzer mAnalyzer = new DbAnalyzer(this); //no actors QWidget *noActorsT = new QWidget; QStringList noActorsHeaders = QStringList() << tr("Series") << tr("Part/Subtitle") << tr("Series Part") << tr("Seriespart Id") << tr("Series Id") << tr("Seriespart"); mNoActorsV = new QTreeView; mNoActorsM = new SmTreeModel(noActorsHeaders, this); mNoActorsV->setModel(mNoActorsM); QVBoxLayout *noActorsL = new QVBoxLayout; noActorsL->addWidget(mNoActorsV); mNoActorsV->setColumnHidden(2, true); mNoActorsV->setColumnHidden(3, true); mNoActorsV->setColumnHidden(4, true); mNoActorsV->setColumnHidden(5, true); mNoActorsV->setEditTriggers(QTreeView::NoEditTriggers); mNoActorsV->setSelectionBehavior(QAbstractItemView::SelectRows); mNoActorsV->setSelectionMode(QAbstractItemView::ExtendedSelection); mNoActorsV->setAlternatingRowColors(true); connect(mNoActorsV, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(noDataDoubleClicked(QModelIndex))); noActorsT->setLayout(noActorsL); mCurrentView = mNoActorsV; mMarkMode = DbAnalyzer::MARKS_ACTORS; //no covers QWidget *noCoversT = new QWidget; QStringList noCoversHeaders = QStringList() << tr("Series") << tr("Part/Subtitle") << tr("Series Part") << tr("Seriespart Id") << tr("Series Id") << tr("Seriespart"); mNoCoversV = new QTreeView; mNoCoversM = new SmTreeModel(noCoversHeaders, this); mNoCoversV->setModel(mNoCoversM); QVBoxLayout *noCoversL = new QVBoxLayout; noCoversL->addWidget(mNoCoversV); mNoCoversV->setColumnHidden(2, true); mNoCoversV->setColumnHidden(3, true); mNoCoversV->setColumnHidden(4, true); mNoCoversV->setColumnHidden(5, true); mNoCoversV->setEditTriggers(QTreeView::NoEditTriggers); mNoCoversV->setSelectionBehavior(QAbstractItemView::SelectRows); mNoCoversV->setSelectionMode(QAbstractItemView::ExtendedSelection); mNoCoversV->setAlternatingRowColors(true); connect(mNoCoversV, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(noDataDoubleClicked(QModelIndex))); noCoversT->setLayout(noCoversL); //stray actors QWidget *strayActorsT = new QWidget; QStringList strayActorsHeaders = QStringList() << tr("Actor") << tr("Actor Id") << tr("Count"); mStrayActorsV = new QTreeView; mStrayActorsM = new SmTreeModel(strayActorsHeaders, this); mStrayActorsV->setModel(mStrayActorsM); QVBoxLayout *strayActorsL = new QVBoxLayout; strayActorsL->addWidget(mStrayActorsV); mStrayActorsV->setColumnHidden(1, true); mStrayActorsV->setEditTriggers(QTreeView::NoEditTriggers); mStrayActorsV->setSelectionBehavior(QAbstractItemView::SelectRows); mStrayActorsV->setSelectionMode(QAbstractItemView::ExtendedSelection); mStrayActorsV->setAlternatingRowColors(true); strayActorsT->setLayout(strayActorsL); //stray genres QWidget *strayGenresT = new QWidget; QStringList strayGenresHeaders = QStringList() << tr("Genre") << tr("Genre Id") << tr("Count"); mStrayGenresV = new QTreeView; mStrayGenresM = new SmTreeModel(strayGenresHeaders, this); mStrayGenresV->setModel(mStrayGenresM); QVBoxLayout *strayGenresL = new QVBoxLayout; strayGenresL->addWidget(mStrayGenresV); mStrayGenresV->setColumnHidden(1, true); mStrayGenresV->setEditTriggers(QTreeView::NoEditTriggers); mStrayGenresV->setSelectionBehavior(QAbstractItemView::SelectRows); mStrayGenresV->setSelectionMode(QAbstractItemView::ExtendedSelection); mStrayGenresV->setAlternatingRowColors(true); strayGenresT->setLayout(strayGenresL); //buttons mCancel = new QPushButton(tr("Cancel")); connect(mCancel, SIGNAL(clicked()), this, SLOT(cancelAnalyzer())); mRefresh = new QPushButton(tr("Refresh")); connect(mRefresh, SIGNAL(clicked()), this, SLOT(refresh())); mClose = new QPushButton(tr("Close")); connect(mClose, SIGNAL(clicked()), this, SLOT(accept())); mMark = new QPushButton(tr("Mark")); //button menu QMenu *markMenu = new QMenu(mMark); QAction *markOkA = new QAction(tr("Set OK"), this); QAction *deleteMarkA = new QAction(tr("Delete mark"), this); markMenu->addAction(markOkA); markMenu->addAction(deleteMarkA); mMark->setMenu(markMenu); connect(markOkA, SIGNAL(triggered()), this, SLOT(setMarks())); connect(deleteMarkA, SIGNAL(triggered()), this, SLOT(deleteMarks())); mDelete = new QPushButton(tr("Delete...")); connect(mDelete, SIGNAL(clicked()), this, SLOT(deleteItems())); mButtonStack = new QStackedLayout; mButtonStack->addWidget(mMark); mButtonStack->addWidget(mDelete); //setup dialog mTab->addTab(noActorsT, tr("No Actors")); mTab->addTab(noCoversT, tr("No Covers")); mTab->addTab(strayActorsT, tr("Stray actors")); mTab->addTab(strayGenresT, tr("Stray genres")); //buttons QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addWidget(mCancel); buttonLayout->addWidget(mRefresh); buttonLayout->addStretch(); buttonLayout->addLayout(mButtonStack); buttonLayout->addWidget(mClose); //totals mTotal = new QLabel(tr("Total items: 0")); mTotal->setAlignment(Qt::AlignCenter); QHBoxLayout *totalLayout = new QHBoxLayout; totalLayout->addWidget(mTotal); //setup dialog QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(mTab); mainLayout->addLayout(totalLayout); mainLayout->addLayout(buttonLayout); setLayout(mainLayout); setMinimumWidth(500); //get things going connect(mTab, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); connect(mAnalyzer, SIGNAL(started()), this, SLOT(analyzerStarted())); connect(mAnalyzer, SIGNAL(finished()), this, SLOT(analyzerFinished())); for(int i = 0; i < mTab->count(); ++i){ mTotals << QString(); } mAnalyzer->start(); } void DbAnalyzerDialog::refresh(){ if(!mAnalyzer->isRunning()){ mAnalyzer->start(); } } void DbAnalyzerDialog::cancelAnalyzer(){ mAnalyzer->setCancel(true); } void DbAnalyzerDialog::analyzerStarted(){ mCancel->setEnabled(true); mRefresh->setEnabled(false); mClose->setEnabled(false); } void DbAnalyzerDialog::analyzerFinished(){ mCancel->setEnabled(false); mRefresh->setEnabled(true); mClose->setEnabled(true); populate(mNoActorsV, mNoActorsM, mAnalyzer->noActors(), mAnalyzer->actorMarks()); populate(mNoCoversV, mNoCoversM, mAnalyzer->noCovers(), mAnalyzer->coverMarks()); populate(mStrayActorsV, mStrayActorsM, mAnalyzer->strayActors()); populate(mStrayGenresV, mStrayGenresM, mAnalyzer->strayGenres()); QString totalString = QString(tr("Total items: %1")); mTotals[0] = totalString.arg(QString::number(mAnalyzer->noActors().count())); mTotals[1] = totalString.arg(QString::number(mAnalyzer->noCovers().count())); mTotals[2] = totalString.arg(QString::number(mAnalyzer->strayActors().count())); mTotals[3] = totalString.arg(QString::number(mAnalyzer->strayGenres().count())); mTotal->setText(mTotals.at(mTab->currentIndex())); } void DbAnalyzerDialog::noDataDoubleClicked(const QModelIndex &idx){ if(mTab->currentIndex() > 1){ return; } if(!idx.isValid()){ return; } const QAbstractItemModel *model = idx.model(); QModelIndex seriesPartIdx = model->index(idx.row(), 5, idx.parent()); QModelIndex seriesIdx = model->index(idx.row(), 4, idx.parent()); emit partClicked(seriesPartIdx.data().toInt(), seriesIdx.data().toInt()); } void DbAnalyzerDialog::deleteItems(){ QTreeView *view = 0; int deleteMode; switch(mTab->currentIndex()){ case 0: case 1: return; break; case 2: view = mStrayActorsV; deleteMode = Actors; break; case 3: view = mStrayGenresV; deleteMode = Genres; break; default: view = 0; break; } Q_ASSERT(view); QModelIndexList selected = view->selectionModel()->selectedRows(1); QList ids; foreach(QModelIndex i, selected){ ids << i.data().toInt(); } emit delItems(deleteMode, ids); } void DbAnalyzerDialog::tabChanged(int index){ switch(index){ case 0: mMarkMode = DbAnalyzer::MARKS_ACTORS; mCurrentView = mNoActorsV; mButtonStack->setCurrentWidget(mMark); break; case 1: mMarkMode = DbAnalyzer::MARKS_COVERS; mCurrentView = mNoCoversV; mButtonStack->setCurrentWidget(mMark); break; case 2: mMarkMode = DbAnalyzer::NOMARKS; mCurrentView = mStrayActorsV; mButtonStack->setCurrentWidget(mDelete); break; case 3: mMarkMode = DbAnalyzer::NOMARKS; mCurrentView = mStrayGenresV; mButtonStack->setCurrentWidget(mDelete); break; default: mMarkMode = DbAnalyzer::NOMARKS; mCurrentView = 0; break; } mTotal->setText(mTotals.at(index)); } void DbAnalyzerDialog::setMarks(){ if(mMarkMode != DbAnalyzer::NOMARKS){ const QList curIds = currentIds(); mAnalyzer->setMarks(curIds, mMarkMode, 1); refresh(); } } void DbAnalyzerDialog::deleteMarks(){ if(mMarkMode != DbAnalyzer::NOMARKS){ const QList curIds = currentIds(); mAnalyzer->deleteMarks(curIds, mMarkMode); refresh(); } } void DbAnalyzerDialog::populate(QTreeView *view, SmTreeModel *model, const QList > &data, const QHash &marks){ if(data.isEmpty()){ return; } const int columns = data.first().count(); if(columns == 0){ return; } SmTreeItem *root = new SmTreeItem(columns); foreach(QList l, data){ SmTreeItem *child = new SmTreeItem(l, root); if(!marks.isEmpty()){ int seriesId = l.at(4).toInt(); QColor fgColor = Qt::red; if(marks.contains(seriesId) && marks.value(seriesId).toInt() > 0){ fgColor = Qt::green; } child->setForegroundColor(fgColor); } root->appendChild(child); } model->setRoot(root); view->resizeColumnToContents(0); } const QList DbAnalyzerDialog::currentIds() const { QTreeView *curView = qobject_cast(mCurrentView); Q_ASSERT(curView); QModelIndexList curIdxs = curView->selectionModel()->selectedRows(4); QList retval; foreach(QModelIndex i, curIdxs){ retval << i.data().toInt(); } return retval; } DbAnalyzer::DbAnalyzer(QObject *parent) : QThread(parent), mCanceled(false), mStatus(Fail) { mDb = QSqlDatabase::cloneDatabase(QSqlDatabase::database("treedb"), "analyzerDb"); mDb.open(); mStatus = mDb.isOpen() ? Ok : Fail; mNoActorQuery = new QSqlQuery(mDb); mNoActorQuery->prepare("SELECT series.tseries_name, seriesparts.iseriespart, seriesparts.tsubtitle, series.iseries_id, seriesparts.iseriesparts_id FROM series, seriesparts LEFT JOIN seriesparts_actormap ON seriesparts.iseriesparts_id = seriesparts_actormap.iseriesparts_id WHERE iactors_id IS NULL AND seriesparts.iseries_id = series.iseries_id ORDER BY tseries_name"); mStrayActorsQuery = new QSqlQuery(mDb); mStrayActorsQuery->prepare("SELECT actors.tactorname, actors.iactors_id, COUNT(seriesparts_actormap.iactors_id) FROM actors LEFT JOIN seriesparts_actormap ON actors.iactors_id = seriesparts_actormap.iactors_id WHERE seriesparts_actormap.iactors_id IS NULL GROUP BY actors.iactors_id, actors.tactorname ORDER BY actors.tactorname"); mStrayGenresQuery = new QSqlQuery(mDb); mStrayGenresQuery->prepare("SELECT genres.tgenrename, genres.igenres_id, COUNT(seriesparts_genremap.igenres_id) FROM genres LEFT JOIN seriesparts_genremap ON genres.igenres_id = seriesparts_genremap.igenres_id WHERE seriesparts_genremap.igenres_id IS NULL GROUP BY genres.igenres_id, genres.tgenrename ORDER BY genres.tgenrename"); mNoCoverQuery = new QSqlQuery(mDb); mNoCoverQuery->prepare("SELECT series.tseries_name, seriesparts.iseriespart, seriesparts.tsubtitle, series.iseries_id, seriesparts.iseriesparts_id FROM files, series, seriesparts WHERE files.iseriespart_id = seriesparts.iseriesparts_id AND seriesparts.iseries_id = series.iseries_id GROUP BY files.iseriespart_id, series.tseries_name,seriesparts.iseriespart, seriesparts.tsubtitle, seriesparts.iseriesparts_id, series.iseries_id HAVING COUNT(iseriespart_id) = 1 ORDER BY series.tseries_name"); mMarksQuery = new QSqlQuery(mDb); mMarksQuery->prepare("SELECT id, mark_id FROM marks where mark_reason = :reason"); mSetMarksQuery = new QSqlQuery(mDb); mSetMarksQuery->prepare("INSERT INTO marks (id, mark_reason, mark_id) VALUES(:seriesid, :reason, :mark_id)"); mDeleteMarksQuery = new QSqlQuery(mDb); mDeleteMarksQuery->prepare("DELETE FROM marks WHERE id = :id AND mark_reason = :reason"); } DbAnalyzer::~DbAnalyzer(){ delete mNoActorQuery; delete mNoCoverQuery; delete mStrayActorsQuery; delete mStrayGenresQuery; delete mMarksQuery; delete mSetMarksQuery; delete mDeleteMarksQuery; mDb.close(); mDb = QSqlDatabase(); QSqlDatabase::removeDatabase("analyzerDb"); } void DbAnalyzer::setCancel(bool canceled){ QMutexLocker m(&mCancelMutex); mCanceled = canceled; } void DbAnalyzer::setMarks(const QList &ids, int reason, int mark_id){ mDb.transaction(); mSetMarksQuery->bindValue(":reason", reason); mSetMarksQuery->bindValue(":mark_id", mark_id); foreach(int id, ids){ mSetMarksQuery->bindValue(":seriesid", id); if(!mSetMarksQuery->exec()){ mDb.rollback(); return; } } mDb.commit(); } void DbAnalyzer::deleteMarks(const QList &ids, int reason){ mDb.transaction(); mDeleteMarksQuery->bindValue(":reason", reason); foreach(int id, ids){ mDeleteMarksQuery->bindValue(":id", id); if(!mDeleteMarksQuery->exec()){ mDb.rollback(); return; } } mDb.commit(); } void DbAnalyzer::run(){ mNoActorR = noDataCheck(mNoActorQuery); mActorMarks = marks(MARKS_ACTORS); mNoCoverR = noDataCheck(mNoCoverQuery); mCoverMarks = marks(MARKS_COVERS); mStrayActorR = strayCheck(mStrayActorsQuery); mStrayGenresR = strayCheck(mStrayGenresQuery); } const QList > DbAnalyzer::noDataCheck(QSqlQuery *query){ if(!query->exec()){ setStatus(Fail); return QList >(); } //read data QList > retval; while(query->next()){ if(mCanceled){ break; } QList res; res << query->value(0); if(!query->value(2).isNull()){ res << query->value(2); }else{ res << query->value(1); } res << query->value(1) << query->value(2) << query->value(3) << query->value(4); retval << res; } return retval; } const QList > DbAnalyzer::strayCheck(QSqlQuery *query){ if(!query->exec()){ QMutexLocker m(&mStatusMutex); mStatus = Fail; return QList >(); } QList > retval; while(query->next()){ if(mCanceled){ break; } QList res; res << query->value(0) << query->value(1) << query->value(2); retval << res; } return retval; } QHash DbAnalyzer::marks(int markType){ QHash retval; if(markType == NOMARKS){ return retval; } mMarksQuery->bindValue(":reason", markType); if(!mMarksQuery->exec()){ setStatus(Fail); return retval; } while(mMarksQuery->next()){ retval.insert(mMarksQuery->value(0).toInt(), mMarksQuery->value(1)); } return retval; } void DbAnalyzer::setStatus(int status){ QMutexLocker m(&mStatusMutex); mStatus = status; }