/* 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 "smtreeitem.h" #include "archivemodel.h" #include "helper.h" #include "smglobals.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); mAvailableOrders.insert("Local files", Local); mAvailableOrders.insert("Favorites", FavoriteOrder); 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); ArchiveCollector *c4 = new ArchiveCollector(NumFields, Local, this); connect(c4, SIGNAL(finished()), collectorMapper, SLOT(map())); collectorMapper->setMapping(c4, c4); ArchiveCollector *c5 = new ArchiveCollector(NumFields, FavoriteOrder, this); connect(c5, SIGNAL(finished()), collectorMapper, SLOT(map())); collectorMapper->setMapping(c5, c5); connect(collectorMapper, SIGNAL(mapped(QObject*)), this, SLOT(collectorFinished(QObject*))); mCollectors << c1 << c2 << c3 << c4 << c5; connect(this, SIGNAL(needRefresh()), this, SLOT(refresh())); readConfig(); refresh(); } ArchiveModel::~ArchiveModel(){ /* We need this, otherwise there are random * crashes when we exit and a collector is * still runnning */ foreach(ArchiveCollector *c, mCollectors){ c->setCancelled(true); c->wait(); c->setCancelled(false); } } Qt::ItemFlags ArchiveModel::flags(const QModelIndex &index) const{ SmTreeItem *item = itemAt(index); Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if(item->data(Type).toInt() == ArchiveModel::SeriesPartNode){ retval |= Qt::ItemIsDropEnabled; } return retval; } Qt::DropActions ArchiveModel::supportedDragActions() const{ return Qt::MoveAction; } 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){ retval = item->data(Name).toString(); QString subtitle = item->data(Subtitle).toString(); if(!subtitle.isEmpty() && (part > 0)){ retval = QString("%1 - %2 - %3").arg(item->data(Name).toString()).arg(subtitle).arg(QString::number(part)); }else if(part > 0){ retval = QString("%1 %2").arg(item->data(Name).toString()).arg(QString::number(part)); }else if(!subtitle.isEmpty()){ retval = QString("%1 - %2").arg(item->data(Name).toString()).arg(subtitle); } } 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 == GenericIdRole){ return item->data(GenericId); } if(role == SeriesPartIdRole){ return item->data(SeriesPartId); } if(role == SeriesPartRole){ return item->data(SeriesPart); } if(role == TypeRole){ return item->data(Type); } if(role == FavoriteRole){ return item->data(Favorite); } if(role == SubtitleRole){ return item->data(Subtitle); } if(role == CountRole){ return item->data(Count); } if(role == Qt::DecorationRole){ if(index.column() == 0){ return mNodeIcons.value(item->data(Type).toInt()); } return QVariant(); } 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(); QVariant id = item->data(GenericId); QSqlQuery updateQuery(mDb); if(nodeType == GenreNode){ updateQuery.prepare("SELECT update_genres(:value, :id)"); }else if(nodeType == ActorNode){ updateQuery.prepare("SELECT update_actors(:value, :id)"); }else if(nodeType == SeriesNode){ updateQuery.prepare("SELECT update_series(:value, :id)"); }else if(nodeType == SeriesPartNode){ updateQuery.prepare("SELECT move_to_series(:value, :id)"); id = item->data(SeriesPartId); }else{ return false; } updateQuery.bindValue(":value", value); updateQuery.bindValue(":id", id); mDb.transaction(); if(updateQuery.exec()){ mDb.commit(); item->setData(Name, value); emit dataChanged(idx, idx); emit needRefresh(); writeCache(mOrder, root()); }else{ // cannot happen! mDb.rollback(); emitDatabaseError(updateQuery.lastError()); } return false; } 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; } QSet ArchiveModel::seriesPartIds(const QModelIndex &idx) const{ if(!idx.isValid()){ return QSet(); } SmTreeItem *cur = itemAt(idx); QVector remaining; remaining << cur; QSet retval; for(int i = 0; i < cur->childCount(); ++i){ remaining << cur->child(i); } while(cur){ cur = remaining.last(); remaining.pop_back(); for(int i = 0; i < cur->childCount(); ++i){ remaining << cur->child(i); } int curId = cur->data(SeriesPartId).toInt(); retval << curId; if(remaining.isEmpty()){ break; } } return retval; } QStringList ArchiveModel::actors(const QSet &partIds) const{ QStringList ids; foreach(int i, partIds){ ids << QString::number(i); } QString actorQString = QString("SELECT DISTINCT(tactorname) FROM actors, seriesparts_actormap WHERE seriesparts_actormap.iseriesparts_id IN (%1) AND seriesparts_actormap.iactors_id = actors.iactors_id ORDER BY tactorname ASC").arg(ids.join(",")); QStringList retval; QSqlQuery actorQ(actorQString, mDb); while(actorQ.next()){ retval << actorQ.value(0).toString(); } return retval; } QStringList ArchiveModel::allActors() const{ QStringList retval; QSqlQuery q("SELECT tactorname FROM actors ORDER BY tactorname ASC", mDb); while(q.next()){ retval << q.value(0).toString(); } return retval; } void ArchiveModel::setActors(int partId, const QStringList &actors){ QStringList actorsForQ; foreach(QString actor, actors){ actorsForQ << QString("\'%1\'").arg(actor); } QString actorIdQString = QString("SELECT iactors_id FROM actors WHERE tactorname IN (%1)").arg(actorsForQ.join(",")); QSqlQuery actorIdQ(actorIdQString, mDb); QList actorIds; while(actorIdQ.next()){ actorIds << actorIdQ.value(0).toInt(); } if(actorIds.isEmpty()){ return; } mDb.transaction(); QSqlQuery deleteActorsQ(mDb); deleteActorsQ.prepare("DELETE FROM seriesparts_actormap WHERE iseriesparts_id = :pid"); deleteActorsQ.bindValue(":pid", partId); if(!deleteActorsQ.exec()){ mDb.rollback(); return; } QSqlQuery insertActorsQ(mDb); insertActorsQ.prepare("INSERT INTO seriesparts_actormap (iseriesparts_id, iactors_id) VALUES(:pid, :aid)"); bool success = false; foreach(int aid, actorIds){ insertActorsQ.bindValue(":pid", partId); insertActorsQ.bindValue(":aid", aid); success = insertActorsQ.exec(); } if(!success){ mDb.rollback(); return; } mDb.commit(); } QStringList ArchiveModel::genres(const QSet &genreIds) const{ QStringList ids; foreach(int i, genreIds){ ids << QString::number(i); } QString actorQString = QString("SELECT DISTINCT(tgenrename) FROM genres, seriesparts_genremap WHERE seriesparts_genremap.iseriesparts_id IN (%1) AND seriesparts_genremap.igenres_id = genres.igenres_id ORDER BY tgenrename ASC").arg(ids.join(",")); QStringList retval; QSqlQuery actorQ(actorQString, mDb); while(actorQ.next()){ retval << actorQ.value(0).toString(); } return retval; } QStringList ArchiveModel::allGenres() const{ QStringList retval; QSqlQuery q("SELECT tgenrename FROM genres ORDER BY tgenrename ASC", mDb); while(q.next()){ retval << q.value(0).toString(); } return retval; } void ArchiveModel::setGenres(int partId, const QStringList &genres){ QStringList genresForQ; foreach(QString genre, genres){ genresForQ << QString("\'%1\'").arg(genre); } QString genreIdQString = QString("SELECT igenres_id FROM genres WHERE tgenrename IN (%1)").arg(genresForQ.join(",")); QSqlQuery genreIdQ(genreIdQString, mDb); QList genreIds; while(genreIdQ.next()){ genreIds << genreIdQ.value(0).toInt(); } if(genreIds.isEmpty()){ return; } mDb.transaction(); QSqlQuery deleteGenresQ(mDb); deleteGenresQ.prepare("DELETE FROM seriesparts_genremap WHERE iseriesparts_id = :pid"); deleteGenresQ.bindValue(":pid", partId); if(!deleteGenresQ.exec()){ mDb.rollback(); return; } QSqlQuery insertGenresQ(mDb); insertGenresQ.prepare("INSERT INTO seriesparts_genremap (iseriesparts_id, igenres_id) VALUES(:pid, :aid)"); bool success = false; foreach(int gid, genreIds){ insertGenresQ.bindValue(":pid", partId); insertGenresQ.bindValue(":aid", gid); success = insertGenresQ.exec(); } if(!success){ mDb.rollback(); return; } mDb.commit(); } QList ArchiveModel::metadataList(int partId) const{ QSqlQuery metadataQuery(mDb); metadataQuery.prepare("SELECT sireleaseyear, tsourcemedium, tsubject, treleasegroup, tencoderopts, tcomment, sipasses, dadded FROM metadata WHERE iseriespart_id = :id"); metadataQuery.bindValue(":id", partId); metadataQuery.exec(); QList retval; for(int i = 0; i < MetadataNumFields; ++i){ retval << QVariant(); } while(metadataQuery.next()){ for(int i = 0; i < MetadataNumFields; ++i){ retval[i] = metadataQuery.value(i); } } return retval; } void ArchiveModel::setMetadata(int partId, const QList &data){ mDb.transaction(); QSqlQuery delMetadataQ(mDb); delMetadataQ.prepare("DELETE FROM metadata WHERE iseriespart_id = :pid"); delMetadataQ.bindValue(":pid", partId); if(!delMetadataQ.exec()){ mDb.rollback(); return; } QSqlQuery insertMetadataQ(mDb); insertMetadataQ.prepare("INSERT INTO metadata (iseriespart_id, sireleaseyear, tsourcemedium, tsubject, treleasegroup, tencoderopts, tcomment, sipasses, dadded) VALUES(:pid, :rely, :source, :sub, :group, :encopts, :comment, :passes, :added)"); insertMetadataQ.bindValue(":pid", partId); insertMetadataQ.bindValue(":rely", data.at(ReleaseYear)); insertMetadataQ.bindValue(":source", data.at(Source)); insertMetadataQ.bindValue(":sub", data.at(Subject)); insertMetadataQ.bindValue(":group", data.at(ReleaseGroup)); insertMetadataQ.bindValue(":encopts", data.at(EncoderOpts)); insertMetadataQ.bindValue(":comment", data.at(Comment)); insertMetadataQ.bindValue(":passes", data.at(Passes)); insertMetadataQ.bindValue(":added", data.at(Added)); if(!insertMetadataQ.exec()){ mDb.rollback(); return; } mDb.commit(); } bool ArchiveModel::setPartNo(int partId, int newPartId, const QString &subtitle){ mDb.transaction(); QSqlQuery updatePartNoQ(mDb); updatePartNoQ.prepare("UPDATE seriesparts SET iseriespart = :npid, tsubtitle = :tsub WHERE iseriesparts_id = :pid"); updatePartNoQ.bindValue(":pid", partId); updatePartNoQ.bindValue(":npid", newPartId); updatePartNoQ.bindValue(":tsub", subtitle); if(!updatePartNoQ.exec()){ mDb.rollback(); return false; } mDb.commit(); return true; } bool ArchiveModel::setFavorite(int partId, bool favorite){ QSqlQuery favQ(mDb); favQ.prepare("UPDATE seriesparts SET bfavorite = :fav WHERE iseriesparts_id = :id"); favQ.bindValue(":id", partId); favQ.bindValue(":fav", favorite); if(favQ.exec()){ QMutexLocker l(&mRootAccessMx); QModelIndex idx = findRecursive(partId, SeriesPartId, rootIndex()); if(idx.isValid()){ SmTreeItem *item = itemAt(idx); item->setData(Favorite, favorite); emit dataChanged(idx, idx); refresh(); return true; } } return false; } void ArchiveModel::addFiles(int partId, const QStringList files){ foreach(QString file, files){ QFileInfo fi(file); qint64 size = fi.size(); QString md5sum = Helper::md5Sum(file); QVariant durationOrSize; int fileType = FT_GENERALCOVER; QString mimeType = Helper::mimeType(file); QSqlQuery fileQ(mDb); if(mimeType.startsWith("video")){ QVariantMap ffmpegMap = Helper::ffmpegData(file); durationOrSize = ffmpegMap.value("duration"); fileType = FT_MOVIE; fileQ.prepare("INSERT INTO files(iseriespart_id, tfilename, cmd5sum, bisize, sifiletype, iduration) VALUES(:id, :fn, :md5, :size, :type, :durOrSize)"); }else if(mimeType.startsWith("image")){ durationOrSize = Helper::picSize(file); fileQ.prepare("INSERT INTO files(iseriespart_id, tfilename, cmd5sum, bisize, sifiletype, cpicsize) VALUES(:id, :fn, :md5, :size, :type, :durOrSize)"); }else{ QString msg = QString("Unknown mime type %1 for %2").arg(mimeType).arg(file); emit message(msg); continue; } fileQ.bindValue(":id", partId); fileQ.bindValue(":fn", fi.fileName()); fileQ.bindValue(":md5", md5sum); fileQ.bindValue(":size", size); fileQ.bindValue(":type", fileType); if(fileType == FT_MOVIE){ fileQ.bindValue(":durOrSize", durationOrSize.toInt()); }else{ fileQ.bindValue(":durOrSize", durationOrSize.toString()); } QString moveTo = Helper::moveToArchive(file, md5sum); if(!moveTo.isEmpty()){ fileQ.exec(); } } } void ArchiveModel::removeFiles(const QList fileIds){ QSqlQuery removeQ(mDb); removeQ.prepare("DELETE FROM files WHERE ifiles_id = :id"); QSqlQuery dataQ(mDb); dataQ.prepare("SELECT tfilename, cmd5sum FROM files WHERE ifiles_id = :id"); QString md5, fileName; foreach(int id, fileIds){ dataQ.bindValue(":id" ,id); dataQ.exec(); while(dataQ.next()){ fileName = dataQ.value(0).toString(); md5 = dataQ.value(1).toString(); if(Helper::removeFromArchive(fileName, md5)){ removeQ.bindValue(":id", id); removeQ.exec(); } } } } bool ArchiveModel::deleteSeriesPart(int partId){ QSqlQuery filesQ(mDb); filesQ.prepare("SELECT cmd5sum,tfilename FROM files WHERE iseriespart_id = :id"); filesQ.bindValue(":id", partId); QSqlQuery deleteQ(mDb); deleteQ.prepare("DELETE FROM seriesparts WHERE iseriesparts_id = :id"); deleteQ.bindValue(":id", partId); QStringList fileNames; if(filesQ.exec()){ while(filesQ.next()){ QString md5 = filesQ.value(0).toString(); QString fn = filesQ.value(1).toString(); fileNames << Helper::createArchivePath(fn, md5); } foreach(QString f, fileNames){ QFile::remove(f); } } return deleteQ.exec(); } bool ArchiveModel::deleteSeries(int seriesId){ QSqlQuery seriesQ(mDb); seriesQ.prepare("DELETE FROM series WHERE iseries_id = :id"); seriesQ.bindValue(":id", seriesId); return seriesQ.exec(); } bool ArchiveModel::addSeriesPart(int partno, QString subtitle, const QModelIndex &parent){ int seriesId = parent.data(ArchiveModel::GenericIdRole).toInt(); mDb.transaction(); QSqlQuery insertQ(mDb); if(!subtitle.isEmpty()){ insertQ.prepare("INSERT INTO seriesparts(iseriespart, iseries_id, tsubtitle) VALUES(:pno, :sid, :subtitle)"); insertQ.bindValue(":pno", partno); insertQ.bindValue(":sid", seriesId); insertQ.bindValue(":subtitle", subtitle); }else{ insertQ.prepare("INSERT INTO seriesparts(iseriespart, iseries_id) VALUES(:pno, :sid)"); insertQ.bindValue(":pno", partno); insertQ.bindValue(":sid", seriesId); } if(insertQ.exec()){ int nextPartId = -1; QSqlQuery pidQ("SELECT currval('seriesparts_seriesparts_id__seq')", mDb); while(pidQ.next()){ nextPartId = pidQ.value(0).toInt(); } QList data; data << parent.data(ArchiveModel::NameRole).toString() << seriesId << nextPartId << partno << ArchiveModel::SeriesPartNode << false << subtitle << QVariant(); mDb.commit(); return addRow(data, parent, true); } return false; } int ArchiveModel::seriesPartIdFromMd5(const QString &md5){ QSqlQuery partsIdQ(mDb); partsIdQ.prepare("SELECT iseriespart_id FROM files WHERE cmd5sum = :md5"); partsIdQ.bindValue(":md5", md5); int retval = -1; partsIdQ.exec(); while(partsIdQ.next()){ retval = partsIdQ.value(0).toInt(); } if(retval == -1){ partsIdQ.prepare("SELECT iseriespart_id FROM files, files_origin WHERE files_origin.cmd5sum = :md5 AND files_origin.ifiles_id = files.ifiles_id"); partsIdQ.bindValue(":md5", md5); partsIdQ.exec(); while(partsIdQ.next()){ retval = partsIdQ.value(0).toInt(); } } return retval; } void ArchiveModel::setOrder(int order) { mOrder = order; SmTreeItem *rootItem = readCache(mOrder); if(rootItem){ setRoot(rootItem); }else{ refresh(); } setWindowTitle(); } 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->setCancelled(true); c->wait(); c->setCancelled(false); } c->start(); } } void ArchiveModel::readConfig(){ mNodeIcons.insert(SeriesNode, SmGlobals::instance()->iconFor("series")); mNodeIcons.insert(SeriesPartNode, SmGlobals::instance()->iconFor("series")); mNodeIcons.insert(ActorNode, SmGlobals::instance()->iconFor("actor")); mNodeIcons.insert(GenreNode, SmGlobals::instance()->iconFor("genre")); } void ArchiveModel::setWindowTitle(){ QString wt = QString(tr("Movie archive - [%1]").arg(mAvailableOrders.key(mOrder))); emit windowTitle(wt); } void ArchiveModel::collectorFinished(QObject *thread){ ArchiveCollector *t = qobject_cast(thread); if(t->isCancelled()){ return; } // don't block the GUI if we're already running again! if(t->isRunning()){ return; } SmTreeItem *rootCopy = t->rootItem(); int sortOrder = t->sortOrder(); if(sortOrder == mOrder){ QMutexLocker l(&mRootAccessMx); setRoot(rootCopy); } writeCache(sortOrder, rootCopy); QString sortOrderName = mAvailableOrders.key(sortOrder); QString msg = QString("Done reading %1").arg(sortOrderName); emit message(msg); } 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){ mCacheMx.lock(); 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); } mCacheMx.unlock(); } void ArchiveModel::writeRecursive(SmTreeItem *start, QDataStream &stream){ if(start->childCount()){ stream << (qint64)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){ QMutexLocker l(&mCacheMx); 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; case Local: cacheFile.append("localfiles.cache"); break; case FavoriteOrder: cacheFile.append("favorite.cache"); break; default: return QString(); } return cacheFile; } /* * ArchiveFilesModel BEGIN */ ArchiveFilesModel::ArchiveFilesModel(const QStringList &headers, QObject *parent) : SmTreeModel(headers, parent), mTotalSize(0), mTotalDuration(0), mTotalMovies(0), mTotalOthers(0) { mDb = QSqlDatabase::database("treedb"); mRoleDbColumnMap.insert(FilenameRole, "tfilename"); mRoleDbColumnMap.insert(DvdNoRole, "idvd"); mRoleDbColumnMap.insert(FileTypeRole, "sifiletype"); mRoleDbColumnMap.insert(FileNumberRole, "sifileno"); mRoleDbColumnMap.insert(QualityRole, "siquality"); readConfig(); } QVariant ArchiveFilesModel::data(const QModelIndex &index, int role) const { if(!index.isValid()){ return QVariant(); } SmTreeItem *item = itemAt(index); int col = index.column(); if(role == ExpansionRole){ return item->data(Expansion); } if(role == SeriesPartIdRole){ return item->data(SeriesPartId); } if(role == FilenameRole){ return item->data(Filename); } if(role == Md5SumRole){ return item->data(Md5Sum); } if(role == SizeRole){ return item->data(Size); } if(role == DvdNoRole){ return item->data(DvdNo); } if(role == FileTypeRole){ return item->data(FileType); } if(role == FileNumberRole){ return item->data(FileNumber); } if(role == QualityRole){ return item->data(Quality); } if(role == FileIdRole){ return item->data(FileId); } if(role == SizeDurRole){ return item->data(SizeDur); } if(role == SizeDurRole){ return item->data(SizeDur); } if(role == FullPathRole){ return item->data(FullPath); } if(role == SeriesNameRole){ return item->data(SeriesName); } if(role == SeriesPartRole){ return item->data(SeriesPart); } if(role == SubtitleRole){ return item->data(Subtitle); } if(role == Qt::ForegroundRole){ if(col == DvdNo){ if(item->data(DvdNo).toInt() < 0){ return QBrush(Qt::blue); }else{ return QBrush(Qt::red); } } if(col == Size){ qint64 size = item->data(Size).toInt(); qint64 fMax = DVDSIZE; if(size < fMax){ return QBrush(Qt::green); }else{ return QBrush(Qt::red); } } } if(role == Qt::TextAlignmentRole){ switch(col){ case Size: case FileId: case Quality: case FileNumber: case SizeDur: int retval = Qt::AlignRight | Qt::AlignVCenter; return retval; } } if(role == Qt::FontRole){ switch(col){ case Md5Sum: case FullPath: case SizeDur: return QFont("Monospace"); } } if(role == Qt::DecorationRole){ if(index.column() == 0){ if(item->parent() == root() || item->data(FileType) == FT_ORIGIN){ return QVariant(); }else{ return decorationIcon(); } } return QVariant(); } return SmTreeModel::data(index, role); } bool ArchiveFilesModel::setData(const QModelIndex &idx, const QVariant &value, int role){ const QString dbColumn = mRoleDbColumnMap.value(role); if(dbColumn.isEmpty()){ return SmTreeModel::setData(idx, value, role); } QString queryString = QString("UPDATE files SET %1 = :value WHERE ifiles_id = :id").arg(dbColumn); QSqlQuery q(mDb); q.prepare(queryString); q.bindValue(":value", value); q.bindValue(":id", idx.data(FileIdRole)); mDb.transaction(); bool success = q.exec(); if(success){ if(role == FilenameRole){ QFile f(idx.data(FullPathRole).toString()); QFileInfo fi(f); QString dir = fi.absolutePath(); QString newName = QString("%1/%2").arg(dir).arg(value.toString()); success = f.rename(newName); } // check rename, too! if(success){ mDb.commit(); SmTreeItem *item = itemAt(idx); item->setData(idx.column(), value); emit dataChanged(idx, idx); return true; } } mDb.rollback(); return false; } Qt::ItemFlags ArchiveFilesModel::flags(const QModelIndex &index) const{ if(index.isValid()){ QVariant v = index.data(ExpansionRole); if(!v.isNull()){ return Qt::ItemIsEnabled; } } return (Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); } int ArchiveFilesModel::nextDvd() const{ int retval; QSqlQuery q("SELECT max(idvd) FROM files", mDb); while(q.next()){ retval = q.value(0).toInt(); } return retval + 1; } bool ArchiveFilesModel::isMovie(const QModelIndex &idx) const{ return idx.data(FileTypeRole).toInt() == FT_MOVIE; } void ArchiveFilesModel::populate(const QSet &seriesPartIds){ if(seriesPartIds.isEmpty()){ return; } mSeriesPartIds = seriesPartIds; SmTreeItem *root = new SmTreeItem(NumFields); SmTreeItem *movies = new SmTreeItem(NumFields, root); movies->setData(Expansion, QChar(0x2642)); root->appendChild(movies); SmTreeItem *pictures = new SmTreeItem(NumFields, root); pictures->setData(Expansion, QChar(0x2640)); QStringList ids; foreach(int i, seriesPartIds){ ids << QString::number(i); } QString queryString = QString("SELECT iseriespart_id, tfilename, cmd5sum, bisize, idvd, sifiletype, sifileno, siquality, ifiles_id, cpicsize, iduration, series.tseries_name, seriesparts.iseriespart, seriesparts.tsubtitle FROM files, seriesparts, series WHERE iseriespart_id IN (%1) and files.iseriespart_id = seriesparts.iseriesparts_id and seriesparts.iseries_id = series.iseries_id ORDER BY tfilename ASC;").arg(ids.join(",")); QSqlQuery q(queryString, mDb); QSqlQuery originq(mDb); originq.prepare("SELECT ifiles_id, tname, cmd5sum, bisize, ibitrate FROM files_origin where ifiles_id = :fid"); while(q.next()){ QList data; data << QVariant(); //expansion (empty) data << q.value(0) << q.value(1) << q.value(2) << q.value(3) << q.value(4) << q.value(5) << q.value(6) << q.value(7) << q.value(8); mTotalSize += q.value(3).toULongLong(); mTotalDuration += q.value(10).toULongLong(); if(!q.value(9).isNull()){ data << q.value(9); }else if(!q.value(10).isNull()){ data << q.value(10); }else{ data << 0x0; } QString fullPath = Helper::createArchivePath(data.at(Filename).toString(), data.at(Md5Sum).toString()); QFileInfo fullPathFi(fullPath); if(!fullPathFi.exists()){ QString usbPath = Helper::createUSBPath(q.value(1).toString(), q.value(11).toString(), q.value(13).toString(), q.value(4).toInt(), q.value(12).toInt()); QFileInfo usbPathFi(usbPath); if(usbPathFi.exists()){ fullPath = usbPath; } } data << fullPath << q.value(11) << q.value(12) << q.value(13); if(data.at(FileType).toInt() == FT_MOVIE){ ++mTotalMovies; SmTreeItem *newItem = new SmTreeItem(data, movies); movies->appendChild(newItem); if((data.at(DvdNo).toInt() < 0) && (data.at(SizeDur) == 0)){ QVariantMap m = Helper::ffmpegData(data.at(FullPath).toString()); QSqlQuery updDurQ = QSqlQuery(mDb); updDurQ.prepare("UPDATE files SET iduration = :dur WHERE ifiles_id = :id"); // we have to cast this to int explicitly, otherwise Postgres will choke int dur = m.value("duration").toDouble(); updDurQ.bindValue(":dur", dur); updDurQ.bindValue(":id", data.at(FileId)); if(updDurQ.exec()){ data[SizeDur] = dur; } } originq.bindValue(":fid", data.at(FileId)); originq.exec(); while(originq.next()){ QList oData; oData << QVariant() << data.at(SeriesPartId) << originq.value(1) << originq.value(2) << originq.value(3) << -2 << FT_ORIGIN << data.at(FileNumber) << data.at(Quality) << data.at(FileId) << data.at(SizeDur) << tr("n/a") << data.at(SeriesName) << data.at(SeriesPart) << data.at(Subtitle); SmTreeItem *oItem = new SmTreeItem(oData, newItem); newItem->appendChild(oItem); } }else{ ++mTotalOthers; SmTreeItem *newItem = new SmTreeItem(data, pictures); pictures->appendChild(newItem); } } if(pictures->childCount()){ root->appendChild(pictures); }else{ delete pictures; } setRoot(root); } QStringList ArchiveFilesModel::filesForSeriespart(int seriesPartId) const{ QStringList retval; QSqlQuery q(mDb); q.prepare("SELECT tfilename FROM files where iseriespart_id = :id ORDER BY tfilename DESC"); q.bindValue(":id", seriesPartId); q.exec(); while(q.next()){ retval.append(q.value(0).toString()); } return retval; } void ArchiveFilesModel::updateSeriesPartForFile(const QString &md5sum, int newSeriesPart){ QSqlQuery q(mDb); q.prepare("UPDATE files SET iseriespart_id = :id WHERE cmd5sum = :md5"); q.bindValue(":id", newSeriesPart); q.bindValue(":md5", md5sum); q.exec(); } bool ArchiveFilesModel::hasFile(const QString &md5){ if(md5.isEmpty()){ return false; } QSqlDatabase db = QSqlDatabase::database("treedb"); QSqlQuery fileQ(db); fileQ.prepare("SELECT COUNT(*) FROM files WHERE cmd5sum = :md5"); fileQ.bindValue(":md5", md5); bool retval = false; fileQ.exec(); while(fileQ.next()){ if(fileQ.value(0).toInt() == 1){ retval = true; } } return retval; } bool ArchiveFilesModel::setDvdNo(const QString &md5, int dvdno){ QSqlDatabase db = QSqlDatabase::database("treedb"); QSqlQuery dvdNoQ(db); dvdNoQ.prepare("UPDATE files SET idvd = :dvdno WHERE cmd5sum = :md5"); dvdNoQ.bindValue(":dvdno", dvdno); dvdNoQ.bindValue(":md5", md5); return dvdNoQ.exec(); } void ArchiveFilesModel::refresh(){ populate(mSeriesPartIds); } void ArchiveFilesModel::readConfig(){ setDecorationIcon(SmGlobals::instance()->iconFor("file")); } /* * ArchiveCollector BEGIN */ ArchiveCollector::ArchiveCollector(int numFields, int order, QObject *parent) : QThread(parent), mRootItem(0), mNumFields(numFields), mSortOrder(order), mCancelled(false) { QString dbName = QString("%1").arg((qint64(this))); mDb = QSqlDatabase::cloneDatabase(QSqlDatabase::database("treedb"), dbName); mDb.open(); } SmTreeItem *ArchiveCollector::rootItem(){ mAccessMx.lock(); SmTreeItem *retval = copyRecursive(mRootItem, 0); mAccessMx.unlock(); return retval; } SmTreeItem *ArchiveCollector::copyRecursive(SmTreeItem *item, SmTreeItem *parent){ SmTreeItem *newItem = new SmTreeItem(*item); newItem->setParent(parent); if(parent){ parent->appendChild(newItem); } if(item->childCount()){ for(int i = 0; i < item->childCount(); ++i){ copyRecursive(item->child(i), newItem); } } return newItem; } void ArchiveCollector::setCancelled(bool cancel){ QMutexLocker l(&mCancelledMx); mCancelled = cancel; } void ArchiveCollector::run(){ QMutexLocker l(&mAccessMx); // check if db is still connected // if not, skip this run and leave everything as is QSqlQuery dummy(mDb); dummy.prepare("SELECT 1"); if(!dummy.exec()){ mDb.close(); mDb.open(); return; } delete mRootItem; mRootItem = new SmTreeItem(mNumFields); switch (mSortOrder){ case ArchiveModel::SeriesName: populateBySeriesName(); break; case ArchiveModel::Genre: populateByGenre(); break; case ArchiveModel::Actor: populateByActor(); break; case ArchiveModel::Local: populateByLocal(); break; case ArchiveModel::FavoriteOrder: populateByFavorite(); break; default: return; } } void ArchiveCollector::populateBySeriesName() { fetchSeries(QVariant(), mRootItem); for(int i = 0; i < mRootItem->childCount(); ++i){ if(checkCancelled()){ return; } fetchParts(QVariant(), mRootItem->child(i)); } } void ArchiveCollector::populateByGenre(){ QSqlQuery genreIdQuery = QSqlQuery("SELECT igenres_id, tgenrename FROM genres ORDER BY tgenrename", mDb); while(genreIdQuery.next()){ if(checkCancelled()){ return; } 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()){ if(checkCancelled()){ return; } 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::populateByLocal(){ QSqlQuery localQ = QSqlQuery("SELECT DISTINCT(series.iseries_id), tseries_name FROM series LEFT JOIN seriesparts ON series.iseries_id = seriesparts.iseries_id LEFT JOIN files ON seriesparts.iseriesparts_id = files.iseriespart_id WHERE files.sifiletype = 1 AND files.idvd < 1 ORDER BY tseries_name ASC", mDb); while(localQ.next()){ QList localIdData; localIdData << localQ.value(1) << localQ.value(0) << QVariant() << QVariant() << ArchiveModel::SeriesNode << false << QVariant() << QVariant(); SmTreeItem *localIdItem = new SmTreeItem(localIdData, mRootItem); mRootItem->appendChild(localIdItem); } for(int i = 0; i < mRootItem->childCount(); ++i){ fetchParts(mRootItem->child(i)->data(ArchiveModel::GenericId), mRootItem->child(i)); } } void ArchiveCollector::populateByFavorite(){ QSqlQuery favQ = QSqlQuery("SELECT distinct(series.iseries_id), tseries_name FROM series LEFT JOIN seriesparts ON series.iseries_id = seriesparts.iseries_id WHERE seriesparts.bfavorite = true ORDER BY tseries_name ASC", mDb); while(favQ.next()){ QList localIdData; localIdData << favQ.value(1) << favQ.value(0) << QVariant() << QVariant() << ArchiveModel::SeriesNode << false << QVariant() << QVariant(); SmTreeItem *localIdItem = new SmTreeItem(localIdData, mRootItem); mRootItem->appendChild(localIdItem); } for(int i = 0; i < mRootItem->childCount(); ++i){ fetchParts(mRootItem->child(i)->data(ArchiveModel::GenericId), mRootItem->child(i)); } } void ArchiveCollector::fetchChildren(SmTreeItem *parent){ for(int i = 0; i < parent->childCount(); ++i){ if(checkCancelled()){ return; } 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()){ if(checkCancelled()){ return; } 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 DISTINCT(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 DISTINCT(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 DISTINCT(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); }else if(mSortOrder == ArchiveModel::Local){ partsQuery.prepare("SELECT DISTINCT(seriesparts.iseriesparts_id), iseriespart, bfavorite, tsubtitle FROM seriesparts LEFT JOIN files ON files.iseriespart_id = seriesparts.iseriesparts_id WHERE iseries_id = :seriesid AND files.idvd < 1 AND files.sifiletype = 1"); partsQuery.bindValue(":seriesid", id); }else if(mSortOrder == ArchiveModel::FavoriteOrder){ partsQuery.prepare("SELECT DISTINCT(iseriesparts_id), iseriespart, bfavorite, tsubtitle FROM seriesparts WHERE iseries_id = :seriesid and bfavorite = true"); partsQuery.bindValue(":seriesid", id); } partsQuery.exec(); while(partsQuery.next()){ if(checkCancelled()){ return; } 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()); } bool ArchiveCollector::checkCancelled(){ QMutexLocker l(&mCancelledMx); return mCancelled; }