/* 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 "smtreeitem.h" #include "archivemodel.h" ArchiveModel::ArchiveModel(const QStringList &headers, QObject *parent) : SmTreeModel(headers, parent), mOrder(NoOrder){ mDb = QSqlDatabase::database("treedb"); mAvailableOrders.insert("Series Name", SeriesName); mAvailableOrders.insert("Actor", Actor); mAvailableOrders.insert("Genre", Genre); QSignalMapper *collectorMapper = new QSignalMapper(this); ArchiveCollector *c1 = new ArchiveCollector(NumFields, SeriesName, this); connect(c1, SIGNAL(finished()), collectorMapper, SLOT(map())); collectorMapper->setMapping(c1, c1); ArchiveCollector *c2 = new ArchiveCollector(NumFields, Actor, this); connect(c2, SIGNAL(finished()), collectorMapper, SLOT(map())); collectorMapper->setMapping(c2, c2); ArchiveCollector *c3 = new ArchiveCollector(NumFields, Genre, this); connect(c3, SIGNAL(finished()), collectorMapper, SLOT(map())); collectorMapper->setMapping(c3, c3); connect(collectorMapper, SIGNAL(mapped(QObject*)), this, SLOT(collectorFinished(QObject*))); mCollectors << c1 << c2 << c3; connect(this, SIGNAL(needRefresh()), this, SLOT(refresh())); refresh(); } const QStringList ArchiveModel::availableOrders() const { QStringList retval = mAvailableOrders.keys(); qSort(retval); return retval; } QVariant ArchiveModel::data(const QModelIndex &index, int role) const{ SmTreeItem *item = itemAt(index); if(role == Qt::DisplayRole && index.column() == 0){ QString retval; int part = item->data(SeriesPart).toInt(); int childCount = item->data(Count).toInt(); int nodeType = item->data(Type).toInt(); if(nodeType == SeriesNode || nodeType == GenreNode || nodeType == ActorNode){ retval = QString("%1 (%2)").arg(item->data(Name).toString()).arg(QString::number(childCount)); }else if(nodeType == SeriesPartNode){ if(part > 0){ retval = QString("%1 %2").arg(item->data(Name).toString()).arg(QString::number(part)); }else{ QString subtitle = item->data(Subtitle).toString(); if(!subtitle.isEmpty()){ retval = QString("%1 - %2").arg(item->data(Name).toString()).arg(item->data(Subtitle).toString()); } } }else{ retval = item->data(Name).toString(); } return retval; } if(role == Qt::ForegroundRole){ bool favorite = item->data(Favorite).toBool(); if(favorite){ return QColor(Qt::darkGreen); } } if(role == NameRole){ return item->data(Name); } if(role == TypeRole){ return item->data(Type); } return SmTreeModel::data(index, role); } bool ArchiveModel::setData(const QModelIndex &idx, const QVariant &value, int role){ if(role != Qt::EditRole){ return false; } // any other column is invisible, hence... if(idx.column() != Name){ return false; } SmTreeItem *item = itemAt(idx); int nodeType = item->data(Type).toInt(); QSqlQuery updateQuery(mDb); if(nodeType == GenreNode){ updateQuery.prepare("UPDATE genres SET tgenrename = :value WHERE igenres_id = :id"); }else if(nodeType == ActorNode){ updateQuery.prepare("UPDATE actors SET tactorname = :value WHERE iactors_id = :id"); }else if(nodeType == SeriesNode){ updateQuery.prepare("UPDATE series SET tseries_name = :value WHERE iseries_id = :id"); }else{ return false; } QVariant id = item->data(GenericId); updateQuery.bindValue(":value", value); updateQuery.bindValue(":id", id); mDb.transaction(); if(updateQuery.exec()){ mDb.commit(); item->setData(Name, value); emit dataChanged(idx, idx); if(nodeType == SeriesNode){ emit needRefresh(); return true; } writeCache(mOrder, root()); }else{ mDb.rollback(); emitDatabaseError(updateQuery.lastError()); } return false; } bool ArchiveModel::matchRecursive(const QModelIndex &parent, const QRegExp ®ex, int column) const { if(!parent.isValid()){ return false; } // first try the parent itself QString value = parent.data().toString(); if(value.isEmpty()){ return false; } if(value.contains(regex)){ return true; } // no match, check for children SmTreeItem *item = static_cast(parent.internalPointer()); if(!item->childCount()){ //leaf, chech parents return checkParents(item, regex, column); } // we have children, traverse them bool retval = false; for(int i = 0; i < item->childCount(); ++i){ QModelIndex newIdx = createIndex(i, column, item->child(i)); retval = matchRecursive(newIdx, regex, column); if(retval){ break; } } return retval; } bool ArchiveModel::removeNode(const QModelIndex &idx){ if(!idx.isValid()){ return false; } SmTreeItem *item = itemAt(idx); if(item->childCount()){ return false; } int nodeType = item->data(Type).toInt(); QSqlQuery removeQuery(mDb); if(nodeType == GenreNode){ removeQuery.prepare("DELETE FROM genres WHERE igenres_id = :id"); }else if(nodeType == ActorNode){ removeQuery.prepare("DELETE FROM actors WHERE iactors_id = :id"); }else if(nodeType == SeriesNode){ removeQuery.prepare("DELETE FROM series WHERE iseries_id = :id"); }else{ return false; } QVariant id = item->data(GenericId); removeQuery.bindValue(":id", id); mDb.transaction(); if(removeQuery.exec()){ mDb.commit(); if(nodeType == GenreNode || nodeType == ActorNode){ removeRows(idx.row(), 1, idx.parent()); }else if(nodeType == SeriesNode){ emit needRefresh(); } writeCache(mOrder, root()); return true; }else{ mDb.rollback(); emitDatabaseError(removeQuery.lastError()); } return false; } QStringList ArchiveModel::indexToPath(const QModelIndex &idx) const { QStringList retval; SmTreeItem *item = itemAt(idx); while (item) { retval << item->data(Name).toString(); item = item->parent(); } std::reverse(retval.begin(), retval.end()); return retval; } QModelIndexList ArchiveModel::pathToIndex(const QStringList &path) const { QModelIndexList retval; QModelIndex parentIdx = QModelIndex(); for(int i = 0; i < path.count(); ++i){ QString curItem = path.at(i); QModelIndex found = find(curItem, Name, parentIdx); if(found.isValid()){ retval << found; parentIdx = found; } } return retval; } void ArchiveModel::setOrder(int order) { mOrder = order; SmTreeItem *rootItem = readCache(mOrder); if(rootItem){ setRoot(rootItem); emit collectorDone(); } } void ArchiveModel::setOrder(const QString &order){ int orderNum = mAvailableOrders.value(order); setOrder(orderNum); setHeaderData(0, Qt::Horizontal, order, Qt::DisplayRole); } void ArchiveModel::refresh(){ emit message(tr("Reading archive data...")); foreach(ArchiveCollector *c, mCollectors){ if(c->isRunning()){ c->cancel(); } c->start(); } } void ArchiveModel::collectorFinished(QObject *thread){ ArchiveCollector *t = qobject_cast(thread); SmTreeItem *rootCopy = t->rootItem(); int sortOrder = t->sortOrder(); if(sortOrder == mOrder){ emit collectorAboutToBeDone(); setRoot(rootCopy); emit collectorDone(); } writeCache(sortOrder, rootCopy); QString sortOrderName = mAvailableOrders.key(sortOrder); QString msg = QString("Done reading %1").arg(sortOrderName); emit message(msg); } bool ArchiveModel::checkParents(const SmTreeItem *item, const QRegExp ®ex, int column) const { while(item != root()){ QString value = item->data(column).toString(); if(value.contains(regex)){ return true; } item = item->parent(); } return false; } void ArchiveModel::emitDatabaseError(const QSqlError &e){ QString databaseText = e.databaseText().isEmpty() ? tr("(none)") : e.databaseText(); QString driverText = e.driverText().isEmpty() ? tr("(none)") : e.driverText(); QString errormsg = QString(tr("Database error:
  • driverText(): %1
  • databaseText(): %2
")).arg(driverText).arg(databaseText); emit databaseError(errormsg); } void ArchiveModel::writeCache(int o, SmTreeItem *rItem){ QFile cache(cacheFile(o)); cache.open(QIODevice::WriteOnly); QDataStream stream(&cache); // write root id, no need to write data // it's invalid anyway, we just need it as an anchor stream << (qint64)rItem; for(int i = 0; i < rItem->childCount(); ++i){ writeRecursive(rItem->child(i), stream); } } void ArchiveModel::writeRecursive(SmTreeItem *start, QDataStream &stream){ if(start->childCount()){ stream << (quint64)start->parent() << (qint64)start; writeItem(start, stream); for(int i = 0; i < start->childCount(); ++i){ writeRecursive(start->child(i), stream); } }else{ stream << (qint64)start->parent() << (qint64)start; writeItem(start, stream); } } void ArchiveModel::writeItem(SmTreeItem *item, QDataStream &stream){ for(int i = 0; i < NumFields; ++i){ stream << item->data(i); } } SmTreeItem *ArchiveModel::readCache(int o){ QFile cache(cacheFile(o)); if(!cache.exists() || !cache.size()){ return 0; } cache.open(QIODevice::ReadOnly); QDataStream stream(&cache); QHash idHash; qint64 rootId; stream >> rootId; SmTreeItem *rootItem = new SmTreeItem(NumFields); idHash.insert(rootId, rootItem); qint64 parentId = 0; qint64 myId = 0; while(!stream.atEnd()){ stream >> parentId >> myId; SmTreeItem *curItem = readItem(stream); SmTreeItem *parentItem = idHash.value(parentId, 0); Q_ASSERT(parentItem); curItem->setParent(parentItem); parentItem->appendChild(curItem); idHash.insert(myId, curItem); } return rootItem; } SmTreeItem *ArchiveModel::readItem(QDataStream &stream) const{ QList data; QVariant cur; for(int i = 0; i < NumFields; ++i){ stream >> cur; data << cur; } SmTreeItem *retval = new SmTreeItem(data); return retval; } const QString ArchiveModel::cacheFile(int o) const{ QSettings s; QString archive = s.value("paths/archivedir").toString(); QString cacheFile = QString("%1/.archivecache/").arg(archive); switch(o){ case Actor: cacheFile.append("actors.cache"); break; case Genre: cacheFile.append("genre.cache"); break; case SeriesName: cacheFile.append("seriesname.cache"); break; default: return QString(); } return cacheFile; } ArchiveCollector::ArchiveCollector(int numFields, int order, QObject *parent) : QThread(parent), mRootItem(0), mNumFields(numFields), mSortOrder(order) { QString dbName = QString("%1").arg((qint64(this))); mDb = QSqlDatabase::cloneDatabase(QSqlDatabase::database("treedb"), dbName); mDb.open(); } SmTreeItem *ArchiveCollector::rootItem(){ mAccessMx.lock(); SmTreeItem *retval = new SmTreeItem(*mRootItem); mAccessMx.unlock(); return retval; } void ArchiveCollector::cancel(){ QMutexLocker l(&mCancelledMx); mCancelled = true; } void ArchiveCollector::run(){ mAccessMx.lock(); delete mRootItem; mRootItem = new SmTreeItem(mNumFields); switch (mSortOrder){ case ArchiveModel::SeriesName: populateBySeriesName(); break; case ArchiveModel::Genre: populateByGenre(); break; case ArchiveModel::Actor: populateByActor(); break; default: return; } mAccessMx.unlock(); } void ArchiveCollector::populateBySeriesName() { fetchSeries(QVariant(), mRootItem); for(int i = 0; i < mRootItem->childCount(); ++i){ checkCancelled(); fetchParts(QVariant(), mRootItem->child(i)); } } void ArchiveCollector::populateByGenre(){ QSqlQuery genreIdQuery = QSqlQuery("SELECT igenres_id, tgenrename FROM genres ORDER BY tgenrename", mDb); while(genreIdQuery.next()){ checkCancelled(); QList genreIdData; genreIdData << genreIdQuery.value(1) << genreIdQuery.value(0) << QVariant() << QVariant() << ArchiveModel::GenreNode << false << QVariant() << QVariant(); SmTreeItem *genreIdItem = new SmTreeItem(genreIdData, mRootItem); mRootItem->appendChild(genreIdItem); } fetchChildren(mRootItem); } void ArchiveCollector::populateByActor(){ QSqlQuery actorIdQuery = QSqlQuery("SELECT iactors_id, tactorname FROM actors ORDER BY tactorname", mDb); while(actorIdQuery.next()){ checkCancelled(); QList actorIdData; actorIdData << actorIdQuery.value(1) << actorIdQuery.value(0) << QVariant() << QVariant() << ArchiveModel::ActorNode << false << QVariant() << QVariant(); SmTreeItem *actorIdItem = new SmTreeItem(actorIdData, mRootItem); mRootItem->appendChild(actorIdItem); } fetchChildren(mRootItem); } void ArchiveCollector::fetchChildren(SmTreeItem *parent){ for(int i = 0; i < parent->childCount(); ++i){ checkCancelled(); fetchSeries(parent->child(i)->data(ArchiveModel::GenericId), parent->child(i)); for(int j = 0; j < parent->child(i)->childCount(); ++j){ checkCancelled(); SmTreeItem *seriesItem = parent->child(i)->child(j); fetchParts(parent->child(i)->data(ArchiveModel::GenericId), seriesItem); } } } void ArchiveCollector::fetchSeries(const QVariant &id, SmTreeItem *parent){ QSqlQuery seriesIdQuery(mDb); switch (mSortOrder) { case ArchiveModel::SeriesName: seriesIdQuery.prepare("SELECT iseries_id, tseries_name FROM series ORDER BY tseries_name"); break; case ArchiveModel::Genre: seriesIdQuery.prepare("SELECT distinct(series.iseries_id), tseries_name FROM series, seriesparts, seriesparts_genremap WHERE seriesparts_genremap.igenres_id = :genreid AND seriesparts_genremap.iseriesparts_id = seriesparts.iseriesparts_id AND seriesparts.iseries_id = series.iseries_id ORDER BY tseries_name"); seriesIdQuery.bindValue(":genreid", id); break; case ArchiveModel::Actor: seriesIdQuery.prepare("SELECT distinct(series.iseries_id), tseries_name FROM series, seriesparts, seriesparts_actormap WHERE seriesparts_actormap.iactors_id = :actorid AND seriesparts_actormap.iseriesparts_id = seriesparts.iseriesparts_id AND seriesparts.iseries_id = series.iseries_id ORDER BY tseries_name"); seriesIdQuery.bindValue(":actorid", id); break; default: qFatal("ArchiveTreeModel::fetchSeries: invalid order!"); return; } if(!seriesIdQuery.exec()){ qWarning("ArchiveTreeModel::fetchSeries: failed to exectue query!"); return; } while(seriesIdQuery.next()){ checkCancelled(); QList seriesData; seriesData << seriesIdQuery.value(1) << seriesIdQuery.value(0) << QVariant() << QVariant() << ArchiveModel::SeriesNode << false << QVariant() << QVariant(); SmTreeItem *seriesItem = new SmTreeItem(seriesData, parent); parent->appendChild(seriesItem); } parent->setData(ArchiveModel::Count, parent->childCount()); } void ArchiveCollector::fetchParts(const QVariant &id, SmTreeItem *parent){ QSqlQuery partsQuery(mDb); if(mSortOrder == ArchiveModel::SeriesName){ partsQuery.prepare("SELECT iseriesparts_id, iseriespart, bfavorite, tsubtitle FROM seriesparts WHERE iseries_id = :id ORDER BY iseriespart"); partsQuery.bindValue(":id", parent->data(ArchiveModel::GenericId)); }else if(mSortOrder == ArchiveModel::Genre){ partsQuery.prepare("SELECT seriesparts.iseriesparts_id, iseriespart, bfavorite, tsubtitle FROM seriesparts, seriesparts_genremap WHERE iseries_id = :id AND seriesparts.iseriesparts_id = seriesparts_genremap.iseriesparts_id AND seriesparts_genremap.igenres_id = :genreid ORDER BY iseriespart"); partsQuery.bindValue(":id", parent->data(ArchiveModel::GenericId)); partsQuery.bindValue(":genreid", id); }else if(mSortOrder == ArchiveModel::Actor){ partsQuery.prepare("SELECT seriesparts.iseriesparts_id, iseriespart, bfavorite, tsubtitle FROM seriesparts, seriesparts_actormap WHERE iseries_id = :id AND seriesparts.iseriesparts_id = seriesparts_actormap.iseriesparts_id AND seriesparts_actormap.iactors_id = :actorid ORDER BY iseriespart"); partsQuery.bindValue(":id", parent->data(ArchiveModel::GenericId)); partsQuery.bindValue(":actorid", id); } partsQuery.exec(); while(partsQuery.next()){ checkCancelled(); QList partData; partData << parent->data(ArchiveModel::Name) << parent->data(ArchiveModel::GenericId) << partsQuery.value(0) << partsQuery.value(1) << ArchiveModel::SeriesPartNode << partsQuery.value(2) << partsQuery.value(3) << QVariant(); SmTreeItem *partItem = new SmTreeItem(partData, parent); parent->appendChild(partItem); } parent->setData(ArchiveModel::Count, parent->childCount()); } void ArchiveCollector::checkCancelled(){ QMutexLocker l(&mCancelledMx); if(mCancelled){ mCancelled = false; quit(); } }