/* 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 "filestreemodel.h" #include "smtreeitem.h" #include "helper.h" #include "seriestreemodel.h" #include "smglobals.h" FilesTreeModel::FilesTreeModel(QStringList &headers, QObject *parent) : SmTreeModel(headers, parent), /*mMode(Normal),*/ mMagic(0xAABBCCDD){ //database setup mDb = QSqlDatabase::database("treedb"); mUpdateDvdQuery = new QSqlQuery(mDb); mUpdateDvdQuery->prepare("UPDATE files SET idvd = :dvd WHERE ifiles_id = :id"); mUpdateQualityQuery = new QSqlQuery(mDb); mUpdateQualityQuery->prepare("UPDATE files SET siquality = :quality WHERE ifiles_id = :id"); mUpdatePartNoQuery = new QSqlQuery(mDb); mUpdatePartNoQuery->prepare("UPDATE files SET sifileno = :partno WHERE ifiles_id = :id"); mUpdateFileTypeQuery = new QSqlQuery(mDb); mUpdateFileTypeQuery->prepare("UPDATE files SET sifiletype = :filetype WHERE ifiles_id = :id"); mInsertFileQuery = new QSqlQuery(mDb); mInsertFileQuery->prepare("INSERT INTO files(iseriespart_id, tfilename, cmd5sum, bisize, idvd, sifiletype, sifileno, siquality) VALUES(:seriespartid, :fname, :md5, :size, :dvd, :type, :fileno, :quality)"); mFilesQuery = new QSqlQuery(mDb); mFilesQuery->prepare("SELECT tfilename, cmd5sum FROM files WHERE iseriespart_id = :id"); mDeleteFileQuery = new QSqlQuery(mDb); mDeleteFileQuery->prepare("DELETE FROM files WHERE ifiles_id = :id"); mFileTypeQuery = new QSqlQuery(mDb); mFileTypeQuery->prepare("SELECT sifiletype FROM files WHERE cmd5sum = :md5"); mFileSizeLessThanQueryTemplate = QString("SELECT cmd5sum FROM files WHERE bisize <= :size AND sifiletype = 1 AND idvd = -1 ORDER BY bisize DESC LIMIT %1"); //file types mFileTypes.insert(1, tr("Movie")); mFileTypes.insert(2, tr("Front cover")); mFileTypes.insert(3, tr("Back cover")); mFileTypes.insert(4, tr("General cover")); //cover types mCoverTypes = mFileTypes; mCoverTypes.remove(1); //misc mSeriesModel = static_cast(SmGlobals::instance()->model("SeriesModel")); readCache(); mEditableColumns.insert(tr("Set part no."), PartNo); mEditableColumns.insert(tr("Set quality"), Quality); mEditableColumns.insert(tr("Set file type"), FileType); mEditableColumns.insert(tr("Set dvd no."), DvdNo); mDvdSize = SmGlobals::instance()->dvdSize(); } FilesTreeModel::~FilesTreeModel(){ delete mUpdateDvdQuery; delete mUpdateQualityQuery; delete mUpdatePartNoQuery; delete mUpdateFileTypeQuery; delete mInsertFileQuery; delete mFilesQuery; delete mDeleteFileQuery; delete mFileTypeQuery; mDb = QSqlDatabase(); } void FilesTreeModel::setIds(const QList &seriesPartIds){ QStringList ids; foreach(int s, seriesPartIds){ ids << QString::number(s); } QString query = QString("SELECT iseriespart_id, tfilename, cmd5sum, bisize, idvd, sifiletype, sifileno, siquality, ifiles_id, series.tseries_name, seriesparts.iseriespart, seriesparts.bfavorite 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, sifileno ASC").arg(ids.join(",")); QSqlQuery filesQuery(mDb); filesQuery.prepare(query); populate(filesQuery); } QVariant FilesTreeModel::data(const QModelIndex &index, int role) const{ if(!index.isValid()){ return QVariant(); } SmTreeItem *item = static_cast(index.internalPointer()); if(role == Qt::DisplayRole){ if(index.column() == DvdNo){ if(index.data(FileTypeRole) == QVariant()){ return QVariant(); } if(item->data(DvdNo).toInt() == -1){ return QString(tr("(local)")); }else{ QString retval = QString(tr("DVD #%1")).arg(item->data(DvdNo).toInt()); return retval; } } if(index.column() == PartNo){ if(item->data(PartNo).toInt() == -1){ return QVariant(); } } if(index.column() == Quality){ if(item->data(Quality).toInt() < 1){ return QVariant(); } return item->data(Quality); } if(index.column() == FileType){ return mFileTypes.value(item->data(FileType).toInt()); } if(index.column() == FullPath){ if(item->data(DvdNo).toInt() > 0){ return QString("(not available)"); } } return item->data(index.column()); } if(role == Qt::ToolTipRole){ if((mMode == SeriesTreeModel::Archived) && (item->data(FileType) == Movie)){ int seriesPartId = item->data(SeriesPartId).toInt(); int seriesId = mSeriesModel->seriesIdByPartId(seriesPartId); if(seriesId != -1){ QModelIndex seriesIdx = mSeriesModel->findValue(seriesId, QModelIndex(), SeriesTreeModel::SeriesId); if(seriesIdx.isValid()){ QModelIndex seriesPartIdx = mSeriesModel->findValue(seriesPartId, seriesIdx, SeriesTreeModel::SeriesPartId); QHash files = filesBySeriesPartId(seriesPartId); QString retval = QString(tr("

%1 %2

")).arg(seriesPartIdx.data(SeriesTreeModel::NameRole).toString()).arg(QString::number(seriesPartIdx.data(SeriesTreeModel::SeriesPartRole).toInt())); retval.append(QString(tr("

Files:

"))); retval.append(tr("
    ")); QHash::const_iterator it = files.constBegin(); while(it != files.constEnd()){ retval.append(QString("
  • %1
  • ").arg(it.key())); ++it; } retval.append(tr("
")); return retval; } } } } if(role == Qt::EditRole){ return item->data(index.column()); } if(role == Qt::DecorationRole){ if(index.column() == 0){ return decorationIcon(); } } if(role == Qt::TextAlignmentRole){ if(index.column() > 0 && index.column() < 4){ return Qt::AlignRight; } } if(role == Qt::ForegroundRole){ if(index.column() == SizeDisplay){ if(item->data(Size).toLongLong() > mDvdSize){ return QColor(Qt::red); } return QColor(Qt::green); } if(index.column() == FileName){ if(item->data(Favorite).toBool() == true){ return mFavoriteColor; }else if(item->data(DvdNo) == -1){ return mLocalColor; }else if(item->data(DvdNo).toInt() > 0){ return mArchivedColor; } } } if(role == Qt::FontRole){ if(index.column() == Md5Sum){ return QFont("courier"); } } if(role == FileNameRole){ return item->data(FileName); } if(role == FullPathRole){ return item->data(FullPath); } if(role == SizeRole){ return item->data(Size); } if(role == DvdNoRole){ return item->data(DvdNo); } if(role == SizeDisplay){ return item->data(SizeDisplay); } if(role == FileTypeRole){ return item->data(FileType); } if(role == Md5SumRole){ return item->data(Md5Sum); } if(role == PartNoRole){ return item->data(PartNo); } if(role == SeriesPartIdRole){ return item->data(SeriesPartId); } if(role == QualityRole){ return item->data(Quality); } if(role == FilesIdRole){ return item->data(FilesId); } if(role == SeriesPartRole){ return item->data(SeriesPart); } if(role == DisplayNameRole){ return item->data(DisplayName); } if(role == SeriesNameRole){ return item->data(SeriesName); } if(role == FavoriteRole){ return item->data(Favorite); } return QVariant(); } bool FilesTreeModel::setData(const QModelIndex &index, const QVariant &value, int role){ if(!index.isValid()){ return false; } if(role == Qt::EditRole){ SmTreeItem *item = static_cast(index.internalPointer()); if(index.column() == DvdNo){ mUpdateDvdQuery->bindValue(":id", index.data(FilesIdRole)); mUpdateDvdQuery->bindValue(":dvd", value); if(mUpdateDvdQuery->exec()){ item->setData(DvdNo, value); bool isLocal = (value.toInt() == -1) ? true : false; if(mode() == SeriesTreeModel::Local){ if(!isLocal){ removeRow(index.row(), index.parent()); } }else if(mode() == SeriesTreeModel::Archived){ if(isLocal){ removeRow(index.row(), index.parent()); } } return true; } } if(index.column() == Quality){ if(value.toInt() > 10 || value.toInt() < 0){ return false; } mUpdateQualityQuery->bindValue(":id", index.data(FilesIdRole)); mUpdateQualityQuery->bindValue(":quality", value); if(mUpdateQualityQuery->exec()){ item->setData(Quality, value); return true; } } if(index.column() == PartNo){ mUpdatePartNoQuery->bindValue(":id", index.data(FilesIdRole)); mUpdatePartNoQuery->bindValue(":partno", value); if(mUpdatePartNoQuery->exec()){ item->setData(PartNo, value); return true; } } if(index.column() == FileType){ mUpdateFileTypeQuery->bindValue(":id", index.data(FilesIdRole)); mUpdateFileTypeQuery->bindValue(":filetype", value); if(mUpdateFileTypeQuery->exec()){ int oldFileType = index.data(FileTypeRole).toInt(); item->setData(FileType, value); QModelIndex newParent = QModelIndex(); if((oldFileType == 1) && (value.toInt() != 1)){ newParent = find("Covers"); } if((oldFileType > 1) && (value.toInt() == 1)){ newParent = find("Movies"); } if(newParent.isValid()){ reparent(index, newParent); } return true; } } } return false; } Qt::ItemFlags FilesTreeModel::flags(const QModelIndex &index) const{ if(!index.isValid()){ return 0; } if(index.data(FileTypeRole) == QVariant()){ return Qt::ItemIsEnabled; } int retval = Qt::ItemIsEnabled | Qt::ItemIsSelectable; if(mEditableColumns.values().contains(index.column())){ retval |= Qt::ItemIsEditable; } if(mode() == SeriesTreeModel::Local){ if(index.data(FavoriteRole).toBool() == true){ int inverse = ~Qt::ItemIsSelectable; retval &= inverse; } } return static_cast(retval); } QHash FilesTreeModel::filesBySeriesPartId(int seriesPartId) const{ mFilesQuery->bindValue(":id", seriesPartId); QHash retval; if(mFilesQuery->exec()){ while(mFilesQuery->next()){ retval.insert(mFilesQuery->value(0).toString(), mFilesQuery->value(1).toString()); } } return retval; } QList > FilesTreeModel::streamInfo(const QString &path) const{ QList > retval; QSettings s; QString ffProbe = s.value("paths/ffprobe").toString(); QStringList args; args << "-show_streams" << path; QProcess ffproc; ffproc.start(ffProbe, args); if(!ffproc.waitForStarted()){ return retval; } ffproc.waitForFinished(); QByteArray ffData = ffproc.readAllStandardOutput(); QList lines = ffData.split('\n'); QMap current; foreach(QString l, lines){ if(l.startsWith("[STREAM]")){ continue; } if(l.startsWith("[/STREAM]")){ retval << current; current.clear(); continue; } QStringList parts = l.split('='); if(parts.size() == 2){ current.insert(parts.at(0), parts.at(1)); } } return retval; } QList > FilesTreeModel::streamInfo(const QModelIndex &idx) const{ return streamInfo(idx.data(FullPathRole).toString()); } QMap FilesTreeModel::pictureInfo(const QString &path) const{ QMap retval; QImage img(path); if(img.isNull()){ return retval; } retval.insert("Width", QString::number(img.width())); retval.insert("Height", QString::number(img.height())); retval.insert("Colors", QString::number(img.colorCount())); retval.insert("Color depth", QString::number(img.depth())); retval.insert("Byte count", QString::number(img.byteCount())); retval.insert("Bytes per line", QString::number(img.bytesPerLine())); retval.insert("Alpha channel", img.hasAlphaChannel() ? tr("yes") : tr("no")); retval.insert("Gray scale", img.isGrayscale() ? tr("yes") : tr("no")); return retval; } QMap FilesTreeModel::pictureInfo(const QModelIndex &idx) const{ return pictureInfo(idx.data(FullPathRole).toString()); } QMap FilesTreeModel::pictureMetaInfo(const QModelIndex &idx){ QMap retval; if(!idx.isValid()){ return retval; } QString fullPath = idx.data(FullPathRole).toString(); QImage img(fullPath); if(img.isNull()){ return retval; } foreach(QString v, img.textKeys()){ retval.insert(v, img.text(v)); } return retval; } QModelIndexList FilesTreeModel::fileSizeLessThan(quint64 size, quint16 limit) const { QModelIndexList retval; QString queryString = mFileSizeLessThanQueryTemplate.arg(QString::number(limit)); QSqlQuery query(mDb); query.prepare(queryString); query.bindValue(":size", size); query.exec(); while(query.next()){ QVariant md5sum = query.value(0); QModelIndex curIdx = findRecursive(md5sum, Md5Sum, index(0, Md5Sum, QModelIndex())); if(curIdx.isValid()){ retval << curIdx; } } return retval; } int FilesTreeModel::fileType(const QString &md5sum) const{ if(md5sum == "00000000000000000000000000000000"){ return -1; } mFileTypeQuery->bindValue(":md5", md5sum); int retval = -1; mFileTypeQuery->exec(); while(mFileTypeQuery->next()){ retval = mFileTypeQuery->value(0).toInt(); } return retval; } bool FilesTreeModel::addFile(const QString &fullPath, int type, int quality, int filePart, int seriesPartId, int dvd){ QFileInfo fi(fullPath); qint64 size = fi.size(); QString md5Sum = Helper::md5Sum(fullPath); if(!fi.exists()){ //should be a dvd size = mDvdSize; md5Sum = QString(32, '0'); } //prepare query mDb.transaction(); mInsertFileQuery->bindValue(":seriespartid", seriesPartId); mInsertFileQuery->bindValue(":fname", fi.fileName()); mInsertFileQuery->bindValue(":md5", md5Sum); mInsertFileQuery->bindValue(":size", size); mInsertFileQuery->bindValue(":dvd", dvd); mInsertFileQuery->bindValue(":type", type); mInsertFileQuery->bindValue(":fileno", filePart); if(type == Movie){ mInsertFileQuery->bindValue(":quality", quality); }else{ mInsertFileQuery->bindValue(":quality", QVariant(QVariant::Int)); } //insert item if(mInsertFileQuery->exec()){ mDb.commit(); return true; } mDb.rollback(); return false; } bool FilesTreeModel::deleteFile(const QModelIndex &file){ QVariant fileId = file.data(FilesIdRole); mDb.transaction(); mDeleteFileQuery->bindValue(":id", fileId); if(!mDeleteFileQuery->exec()){ mDb.rollback(); return false; } removeRows(file.row(), 1, file.parent()); mDb.commit(); return true; } bool FilesTreeModel::deleteFiles(const QModelIndexList &files){ foreach(QModelIndex i, files){ bool retval = deleteFile(i); if(!retval){ return false; } } return true; } void FilesTreeModel::readCache(){ QString settingsDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); QString cacheFile = QString("%1/%2").arg(settingsDir).arg("sizeduration.cache"); QFileInfo fi(cacheFile); if(!fi.exists()){ return; } QFile cache(cacheFile); cache.open(QIODevice::ReadOnly); QDataStream stream(&cache); qint32 magic; stream >> magic; if(magic != mMagic){ QFile::remove(cacheFile); return; } while(!stream.atEnd()){ QString key, value; stream >> key; stream >> value; mPicsDurationCache.insert(key, value); } } void FilesTreeModel::writeCache(){ QString settingsDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); QFileInfo fi(settingsDir); if(!fi.exists()){ QDir root = QDir::root(); root.mkpath(settingsDir); } QString cacheFile = QString("%1/%2").arg(settingsDir).arg("sizeduration.cache"); QFile cache(cacheFile); cache.open(QIODevice::WriteOnly); QDataStream stream(&cache); stream << mMagic; QHash::const_iterator it = mPicsDurationCache.constBegin(); while(it != mPicsDurationCache.constEnd()){ stream << it.key() << it.value(); ++it; } } void FilesTreeModel::readSettings(){ QSettings s; QVariant local = s.value("ui/localcolor", Qt::darkBlue); mLocalColor = local.value(); QVariant archived = s.value("ui/archivedcolor", Qt::black); mArchivedColor = archived.value(); QVariant favorite = s.value("ui/favoritecolor", Qt::red); mFavoriteColor = favorite.value(); } void FilesTreeModel::populate(QSqlQuery &filesQuery){ SmTreeItem *root = new SmTreeItem(16); SmTreeItem *files = new SmTreeItem(16, root); files->setData(FileName, tr("Movies")); root->appendChild(files); SmTreeItem *covers = new SmTreeItem(16, root); covers->setData(FileName, tr("Covers")); root->appendChild(covers); QLocale l; filesQuery.exec(); while(filesQuery.next()){ QList data; data << filesQuery.value(1); //filename data << filesQuery.value(6); //partno data << l.toString(filesQuery.value(3).toLongLong()); //displaysize data << filesQuery.value(7); //quality data << filesQuery.value(4); //dvdno data << Helper::createArchivePath(filesQuery.value(1).toString(), filesQuery.value(2).toString()); //fullpath data << filesQuery.value(3); //size data << filesQuery.value(5); //filetype data << filesQuery.value(2); //md5sum data << filesQuery.value(0); //seriesparts_id data << filesQuery.value(8); //files_id data << filesQuery.value(10); //seriespart QString displayName = QString("%1 %2").arg(filesQuery.value(9).toString()).arg(filesQuery.value(10).toString()); data << displayName; data << tr("N/A"); // duration or size data << filesQuery.value(9).toString(); // series name data << filesQuery.value(11); QString fullPath = data.at(FullPath).toString(); QFileInfo fi(fullPath); switch(filesQuery.value(5).toInt()){ case Movie:{ if(fi.exists()){ if(mPicsDurationCache.contains(fullPath)){ data[SizeDuration] = mPicsDurationCache.value(fullPath); SmTreeItem *item = new SmTreeItem(data, files); files->appendChild(item); break; } QList > sInfo = streamInfo(fullPath); for(int i = 0; i < sInfo.size(); ++i){ QString duration = sInfo.at(i).value("duration"); if(!duration.isEmpty() && (duration != "N/A")){ QString durFromSeconds = Helper::durationFromSecs(duration.toFloat()); mPicsDurationCache.insert(fullPath, durFromSeconds); data[SizeDuration] = durFromSeconds; } } } SmTreeItem *item = new SmTreeItem(data, files); files->appendChild(item); break; } case FrontCover: case BackCover: case GeneralCover:{ if(fi.exists()){ if(mPicsDurationCache.contains(fullPath)){ data[SizeDuration] = mPicsDurationCache.value(fullPath); SmTreeItem *item = new SmTreeItem(data, files); covers->appendChild(item); break; } } QMap pInfo = pictureInfo(fullPath); if(!pInfo.isEmpty()){ QString setVal = QString("%1x%2").arg(pInfo.value("Width")).arg(pInfo.value("Height")); mPicsDurationCache.insert(fullPath, setVal); data[SizeDuration] = setVal; } SmTreeItem *item = new SmTreeItem(data, covers); covers->appendChild(item); break; } default: ; } } setRoot(root); }