File indexing completed on 2024-05-19 04:56:02

0001 /**
0002  * \file fileproxymodel.cpp
0003  * Proxy for filesystem model which filters files.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 22-Mar-2011
0008  *
0009  * Copyright (C) 2011-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "fileproxymodel.h"
0028 #include <QTimer>
0029 #include <QRegularExpression>
0030 #include "taggedfilesystemmodel.h"
0031 #include "itaggedfilefactory.h"
0032 #include "config.h"
0033 
0034 namespace {
0035 
0036 QHash<int,QByteArray> getRoleHash()
0037 {
0038   QHash<int, QByteArray> roles;
0039   roles[FileSystemModel::FileNameRole] = "fileName";
0040   roles[FileSystemModel::FilePathRole] = "filePath";
0041   roles[TaggedFileSystemModel::IconIdRole] = "iconId";
0042   roles[TaggedFileSystemModel::TruncatedRole] = "truncated";
0043   roles[TaggedFileSystemModel::IsDirRole] = "isDir";
0044   roles[Qt::CheckStateRole] = "checkState";
0045   return roles;
0046 }
0047 
0048 }
0049 
0050 /**
0051  * Constructor.
0052  *
0053  * @param parent parent object
0054  */
0055 FileProxyModel::FileProxyModel(QObject* parent)
0056   : QSortFilterProxyModel(parent),
0057     m_fsModel(nullptr),
0058     m_loadTimer(new QTimer(this)), m_sortTimer(new QTimer(this)),
0059     m_numModifiedFiles(0), m_isLoading(false)
0060 {
0061   setObjectName(QLatin1String("FileProxyModel"));
0062   m_loadTimer->setSingleShot(true);
0063   m_loadTimer->setInterval(1000);
0064   connect(m_loadTimer, &QTimer::timeout, this, &FileProxyModel::onDirectoryLoaded);
0065   m_sortTimer->setSingleShot(true);
0066   m_sortTimer->setInterval(100);
0067   connect(m_sortTimer, &QTimer::timeout, this, &FileProxyModel::emitSortingFinished);
0068 }
0069 
0070 /**
0071  * Destructor.
0072  */
0073 FileProxyModel::~FileProxyModel()
0074 {
0075 }
0076 
0077 /**
0078  * Map role identifiers to role property names in scripting languages.
0079  * @return hash mapping role identifiers to names.
0080  */
0081 QHash<int,QByteArray> FileProxyModel::roleNames() const
0082 {
0083   static QHash<int, QByteArray> roles = getRoleHash();
0084   return roles;
0085 }
0086 
0087 /**
0088  * Get file information of model index.
0089  * @return file information
0090  */
0091 QFileInfo FileProxyModel::fileInfo(const QModelIndex& index) const
0092 {
0093   if (m_fsModel) {
0094     QModelIndex sourceIndex(mapToSource(index));
0095     return m_fsModel->fileInfo(sourceIndex);
0096   }
0097   return QFileInfo();
0098 }
0099 
0100 /**
0101  * Get file path of model index.
0102  * @return path to file or directory
0103  */
0104 QString FileProxyModel::filePath(const QModelIndex& index) const
0105 {
0106   if (m_fsModel) {
0107     QModelIndex sourceIndex(mapToSource(index));
0108     return m_fsModel->filePath(sourceIndex);
0109   }
0110   return QString();
0111 }
0112 
0113 /**
0114  * Get file name of model index.
0115  * @return name of file or directory
0116  */
0117 QString FileProxyModel::fileName(const QModelIndex& index) const
0118 {
0119   if (m_fsModel) {
0120     QModelIndex sourceIndex(mapToSource(index));
0121     return m_fsModel->fileName(sourceIndex);
0122   }
0123   return QString();
0124 }
0125 
0126 /**
0127  * Check if model index represents directory.
0128  * @return true if directory
0129  */
0130 bool FileProxyModel::isDir(const QModelIndex& index) const
0131 {
0132   if (m_fsModel) {
0133     QModelIndex sourceIndex(mapToSource(index));
0134     return m_fsModel->isDir(sourceIndex);
0135   }
0136   return false;
0137 }
0138 
0139 /**
0140  * Delete file of index.
0141  * @return true if ok
0142  */
0143 bool FileProxyModel::remove(const QModelIndex& index) const
0144 {
0145   if (m_fsModel) {
0146     QModelIndex sourceIndex(mapToSource(index));
0147     return m_fsModel->remove(sourceIndex);
0148   }
0149   return false;
0150 }
0151 
0152 /**
0153  * Delete directory of index.
0154  * @return true if ok
0155  */
0156 bool FileProxyModel::rmdir(const QModelIndex& index) const
0157 {
0158   if (m_fsModel) {
0159     QModelIndex sourceIndex(mapToSource(index));
0160     return m_fsModel->rmdir(sourceIndex);
0161   }
0162   return false;
0163 }
0164 
0165 /**
0166  * Create a directory with @a name in the @a parent model index.
0167  * @return index of created directory.
0168  */
0169 QModelIndex FileProxyModel::mkdir(const QModelIndex& parent, const QString& name) const
0170 {
0171   if (m_fsModel) {
0172     QModelIndex sourceIndex(mapToSource(parent));
0173     return mapFromSource(m_fsModel->mkdir(sourceIndex, name));
0174   }
0175   return QModelIndex();
0176 }
0177 
0178 /**
0179  * Rename file or directory of @a index to @a newName.
0180  * @return true if ok
0181  */
0182 bool FileProxyModel::rename(const QModelIndex& index, const QString& newName)
0183 {
0184   if (m_fsModel) {
0185     QModelIndex sourceIndex(mapToSource(index));
0186     return m_fsModel->rename(sourceIndex, newName);
0187   }
0188   return false;
0189 }
0190 
0191 /**
0192  * Get index for given path and column.
0193  * @param path path to file or directory
0194  * @param column model column
0195  * @return model index, invalid if not found.
0196  */
0197 QModelIndex FileProxyModel::index(const QString& path, int column) const
0198 {
0199   if (m_fsModel) {
0200     if (QModelIndex sourceIndex = m_fsModel->index(path, column);
0201         sourceIndex.isValid()) {
0202       return mapFromSource(sourceIndex);
0203     }
0204   }
0205   return QModelIndex();
0206 }
0207 
0208 /**
0209  * Check if row should be included in model.
0210  *
0211  * @param srcRow source row
0212  * @param srcParent source parent
0213  *
0214  * @return true to include row.
0215  */
0216 bool FileProxyModel::filterAcceptsRow(
0217     int srcRow, const QModelIndex& srcParent) const
0218 {
0219   if (QAbstractItemModel* srcModel = sourceModel()) {
0220     QModelIndex srcIndex(srcModel->index(srcRow, 0, srcParent));
0221     if (!m_filteredOut.isEmpty()) {
0222       if (m_filteredOut.contains(srcIndex))
0223         return false;
0224     }
0225     QString item(srcIndex.data().toString());
0226     if (item == QLatin1String(".") || item == QLatin1String(".."))
0227       return false;
0228     if (!m_fsModel)
0229       return true;
0230     if (m_fsModel->isDir(srcIndex))
0231       return passesExcludeFolderFilters(m_fsModel->filePath(srcIndex));
0232     if (m_extensions.isEmpty())
0233       return true;
0234     for (auto it = m_extensions.constBegin(); it != m_extensions.constEnd(); ++it) {
0235       if (item.endsWith(*it, Qt::CaseInsensitive))
0236         return true;
0237     }
0238   }
0239   return false;
0240 }
0241 
0242 /**
0243  * Get item flags.
0244  * @param index index of item
0245  * @return default flags plus drag enabled depending on
0246  * setExclusiveDraggableIndex().
0247  */
0248 Qt::ItemFlags FileProxyModel::flags(const QModelIndex& index) const
0249 {
0250   Qt::ItemFlags itemFlags = QSortFilterProxyModel::flags(index);
0251 
0252   if (index.isValid()) {
0253     if (!m_exclusiveDraggableIndex.isValid() ||
0254         index == m_exclusiveDraggableIndex) {
0255       itemFlags |= Qt::ItemIsDragEnabled;
0256     } else {
0257       itemFlags &= ~Qt::ItemIsDragEnabled;
0258     }
0259   }
0260   if (index.column() < TaggedFileSystemModel::NUM_FILESYSTEM_COLUMNS) {
0261     // Prevent inplace editing (i.e. renaming) of files and directories.
0262     itemFlags &= ~Qt::ItemIsEditable;
0263   } else {
0264     itemFlags |= Qt::ItemIsEditable;
0265   }
0266 
0267   return itemFlags;
0268 }
0269 
0270 /**
0271  * Set source model.
0272  * @param sourceModel source model, must be TaggedFileSystemModel
0273  */
0274 void FileProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
0275 {
0276   auto fsModel = qobject_cast<TaggedFileSystemModel*>(sourceModel);
0277   Q_ASSERT_X(fsModel != nullptr , "setSourceModel",
0278              "sourceModel is not TaggedFileSystemModel");
0279   if (fsModel != m_fsModel) {
0280     if (m_fsModel) {
0281       m_isLoading = false;
0282       disconnect(m_fsModel, &FileSystemModel::rootPathChanged,
0283                  this, &FileProxyModel::onStartLoading);
0284       disconnect(m_fsModel, &FileSystemModel::directoryLoaded,
0285                  this, &FileProxyModel::onDirectoryLoaded);
0286       disconnect(m_fsModel, &TaggedFileSystemModel::fileModificationChanged,
0287                  this, &FileProxyModel::onFileModificationChanged);
0288     }
0289     m_fsModel = fsModel;
0290     if (m_fsModel) {
0291       connect(m_fsModel, &FileSystemModel::rootPathChanged,
0292               this, &FileProxyModel::onStartLoading);
0293       connect(m_fsModel, &FileSystemModel::directoryLoaded,
0294               this, &FileProxyModel::onDirectoryLoaded);
0295       connect(m_fsModel, &TaggedFileSystemModel::fileModificationChanged,
0296               this, &FileProxyModel::onFileModificationChanged);
0297     }
0298   }
0299   QSortFilterProxyModel::setSourceModel(sourceModel);
0300 }
0301 
0302 /**
0303  * Called when directoryLoaded() is emitted.
0304  */
0305 void FileProxyModel::onDirectoryLoaded()
0306 {
0307   m_loadTimer->stop();
0308   m_sortTimer->start();
0309 }
0310 
0311 /**
0312  * Emit sortingFinished().
0313  */
0314 void FileProxyModel::emitSortingFinished()
0315 {
0316   m_isLoading = false;
0317   emit sortingFinished();
0318 }
0319 
0320 /**
0321  * Count items in model.
0322  * @param rootIndex index of root item
0323  * @param folderCount the folder count is returned here
0324  * @param fileCount the file count is returned here
0325  */
0326 void FileProxyModel::countItems(const QModelIndex& rootIndex,
0327                                 int& folderCount, int& fileCount) const
0328 {
0329   folderCount = 0;
0330   fileCount = 0;
0331   QModelIndexList todo;
0332   todo.append(rootIndex);
0333   while (!todo.isEmpty()) {
0334     QModelIndex parent = todo.takeFirst();
0335     for (int row = 0, numRows = rowCount(parent); row < numRows; ++row) {
0336       if (QModelIndex idx = index(row, 0, parent); !hasChildren(idx)) {
0337         ++fileCount;
0338       } else {
0339         ++folderCount;
0340         todo.append(idx);
0341       }
0342     }
0343   }
0344 }
0345 
0346 /**
0347  * Called when loading the directory starts.
0348  */
0349 void FileProxyModel::onStartLoading()
0350 {
0351   m_isLoading = true;
0352   // Last resort timeout for the case that directoryLoaded() would not be
0353   // fired and for empty directories with Qt < 4.7
0354   m_loadTimer->start();
0355 }
0356 
0357 /**
0358  * Check if more data is available.
0359  * @param parent parent index of items to fetch
0360  * @return true if more data available.
0361  */
0362 bool FileProxyModel::canFetchMore(const QModelIndex& parent) const
0363 {
0364   if (QString path = filePath(parent);
0365       !passesIncludeFolderFilters(path) || !passesExcludeFolderFilters(path))
0366     return false;
0367 
0368   return QSortFilterProxyModel::canFetchMore(parent);
0369 }
0370 
0371 /**
0372  * Fetches any available data.
0373  * @param parent parent index of items to fetch
0374  */
0375 void FileProxyModel::fetchMore(const QModelIndex& parent)
0376 {
0377   onStartLoading();
0378   QSortFilterProxyModel::fetchMore(parent);
0379 }
0380 
0381 /**
0382  * Sort model.
0383  *
0384  * This method will directly call FileSystemModel::sort() on the
0385  * sourceModel() to take advantage of that specialized behavior. This
0386  * will change the order in the source model.
0387  *
0388  * @param column column to sort
0389  * @param order ascending or descending order
0390  */
0391 void FileProxyModel::sort(int column, Qt::SortOrder order)
0392 {
0393   if (QAbstractItemModel* srcModel = nullptr;
0394       rowCount() > 0 && (srcModel = sourceModel()) != nullptr) {
0395     if (column < TaggedFileSystemModel::NUM_FILESYSTEM_COLUMNS) {
0396       if (sortColumn() >= TaggedFileSystemModel::NUM_FILESYSTEM_COLUMNS) {
0397         // restore the source model order
0398         QSortFilterProxyModel::sort(-1, order);
0399       }
0400       srcModel->sort(column, order);
0401     } else {
0402       QSortFilterProxyModel::sort(column, order);
0403     }
0404   }
0405 }
0406 
0407 /**
0408  * Sets the name filters to apply against the existing files.
0409  * @param filters list of strings containing wildcards like "*.mp3"
0410  */
0411 void FileProxyModel::setNameFilters(const QStringList& filters)
0412 {
0413   QRegularExpression wildcardRe(QLatin1String("\\.\\w+"));
0414   QSet<QString> exts;
0415   for (const QString& filter : filters) {
0416     auto it = wildcardRe.globalMatch(filter);
0417     while (it.hasNext()) {
0418       auto match = it.next();
0419       int pos = match.capturedStart();
0420       int len = match.capturedLength();
0421       exts.insert(filter.mid(pos, len).toLower());
0422     }
0423   }
0424   QStringList oldExtensions(m_extensions);
0425 #if QT_VERSION >= 0x050e00
0426   m_extensions = QStringList(exts.constBegin(), exts.constEnd());
0427 #else
0428   m_extensions = exts.toList();
0429 #endif
0430   if (m_extensions != oldExtensions) {
0431     invalidateFilter();
0432   }
0433 }
0434 
0435 /**
0436  * Filter out a model index.
0437  * @param index source model index which has to be filtered out
0438  */
0439 void FileProxyModel::filterOutIndex(const QPersistentModelIndex& index)
0440 {
0441   m_filteredOut.insert(index);
0442 }
0443 
0444 /**
0445  * Reset internal data of the model.
0446  * Is called from endResetModel().
0447  */
0448 void FileProxyModel::resetInternalData()
0449 {
0450   QSortFilterProxyModel::resetInternalData();
0451   m_filteredOut.clear();
0452   m_loadTimer->stop();
0453   m_sortTimer->stop();
0454   m_numModifiedFiles = 0;
0455   m_isLoading = false;
0456 }
0457 
0458 /**
0459  * Stop filtering out indexes.
0460  */
0461 void FileProxyModel::disableFilteringOutIndexes()
0462 {
0463   m_filteredOut.clear();
0464   invalidateFilter();
0465 }
0466 
0467 /**
0468  * Check if index filter is active.
0469  * @return true if indexes are filtered out
0470  */
0471 bool FileProxyModel::isFilteringOutIndexes() const
0472 {
0473   return !m_filteredOut.isEmpty();
0474 }
0475 
0476 /**
0477  * Make filter changes active after adding indexes to be filtered out.
0478  */
0479 void FileProxyModel::applyFilteringOutIndexes()
0480 {
0481   invalidateFilter();
0482 }
0483 
0484 /**
0485  * Set filters for included and excluded folders.
0486  * @param includeFolders wildcard expressions for folders to be included
0487  * @param excludeFolders wildcard expressions for folders to be excluded
0488  */
0489 void FileProxyModel::setFolderFilters(const QStringList& includeFolders,
0490                                       const QStringList& excludeFolders)
0491 {
0492   QList<QRegularExpression> oldIncludeFolderFilters, oldExcludeFolderFilters;
0493   m_includeFolderFilters.swap(oldIncludeFolderFilters);
0494   m_excludeFolderFilters.swap(oldExcludeFolderFilters);
0495   for (QString filter : includeFolders) {
0496     filter.replace(QLatin1Char('\\'), QLatin1Char('/'));
0497 #if QT_VERSION >= 0x050f00
0498     filter = QRegularExpression::wildcardToRegularExpression(filter);
0499 #else
0500     filter = FileSystemModel::wildcardToRegularExpression(filter);
0501 #endif
0502     m_includeFolderFilters.append(
0503           QRegularExpression(filter, QRegularExpression::CaseInsensitiveOption));
0504   }
0505 
0506   for (QString filter : excludeFolders) {
0507     filter.replace(QLatin1Char('\\'), QLatin1Char('/'));
0508 #if QT_VERSION >= 0x050f00
0509     filter = QRegularExpression::wildcardToRegularExpression(filter);
0510 #else
0511     filter = FileSystemModel::wildcardToRegularExpression(filter);
0512 #endif
0513     m_excludeFolderFilters.append(
0514           QRegularExpression(filter, QRegularExpression::CaseInsensitiveOption));
0515   }
0516 
0517   if (m_includeFolderFilters != oldIncludeFolderFilters ||
0518       m_excludeFolderFilters != oldExcludeFolderFilters) {
0519     invalidateFilter();
0520   }
0521 }
0522 
0523 /**
0524  * Check if a directory path passes the include folder filters.
0525  * @param dirPath absolute path to directory
0526  * @return true if path passes filters.
0527  */
0528 bool FileProxyModel::passesIncludeFolderFilters(const QString& dirPath) const
0529 {
0530   if (!m_includeFolderFilters.isEmpty()) {
0531     bool included = false;
0532     for (auto it = m_includeFolderFilters.constBegin();
0533          it != m_includeFolderFilters.constEnd();
0534          ++it) {
0535       if (it->match(dirPath).hasMatch()) {
0536         included = true;
0537         break;
0538       }
0539     }
0540     if (!included) {
0541       return false;
0542     }
0543   }
0544 
0545   return true;
0546 }
0547 
0548 /**
0549  * Check if a directory path passes the include folder filters.
0550  * @param dirPath absolute path to directory
0551  * @return true if path passes filters.
0552  */
0553 bool FileProxyModel::passesExcludeFolderFilters(const QString& dirPath) const
0554 {
0555   if (!m_excludeFolderFilters.isEmpty()) {
0556     for (auto it = m_excludeFolderFilters.constBegin();
0557          it != m_excludeFolderFilters.constEnd();
0558          ++it) {
0559       if (it->match(dirPath).hasMatch()) {
0560         return false;
0561       }
0562     }
0563   }
0564 
0565   return true;
0566 }
0567 
0568 /**
0569  * Get tagged file of model index.
0570  *
0571  * @param index model index
0572  *
0573  * @return tagged file, 0 is returned if the index does not contain a
0574  * TaggedFile or if has a TaggedFile which is null.
0575  */
0576 TaggedFile* FileProxyModel::getTaggedFileOfIndex(const QModelIndex& index) {
0577   return TaggedFileSystemModel::getTaggedFileOfIndex(index);
0578 }
0579 
0580 /**
0581  * Get directory path if model index is of directory.
0582  *
0583  * @param index model index
0584  *
0585  * @return directory path, null if not directory
0586  */
0587 QString FileProxyModel::getPathIfIndexOfDir(const QModelIndex& index) {
0588   const auto model =
0589       qobject_cast<const FileProxyModel*>(index.model());
0590   if (!model || !model->isDir(index))
0591     return QString();
0592 
0593   return model->filePath(index);
0594 }
0595 
0596 /**
0597  * Read tagged file with ID3v2.4.0.
0598  *
0599  * @param taggedFile tagged file
0600  *
0601  * @return tagged file (can be newly created tagged file).
0602  */
0603 TaggedFile* FileProxyModel::readWithId3V24(TaggedFile* taggedFile)
0604 {
0605   const QPersistentModelIndex& index = taggedFile->getIndex();
0606   if (TaggedFile* tagLibFile = TaggedFileSystemModel::createTaggedFile(
0607           TaggedFile::TF_ID3v24, taggedFile->getFilename(), index)) {
0608     if (index.isValid()) {
0609       QVariant data;
0610       data.setValue(tagLibFile);
0611       // setData() will not invalidate the model, so this should be safe.
0612       if (auto setDataModel = const_cast<QAbstractItemModel*>(
0613             index.model())) {
0614         setDataModel->setData(index, data, TaggedFileSystemModel::TaggedFileRole);
0615       }
0616     }
0617     taggedFile = tagLibFile;
0618     taggedFile->readTags(false);
0619   }
0620   return taggedFile;
0621 }
0622 /**
0623  * Read tagged file with ID3v2.3.0.
0624  *
0625  * @param taggedFile tagged file
0626  *
0627  * @return tagged file (can be newly created tagged file).
0628  */
0629 TaggedFile* FileProxyModel::readWithId3V23(TaggedFile* taggedFile)
0630 {
0631   const QPersistentModelIndex& index = taggedFile->getIndex();
0632   if (TaggedFile* id3libFile = TaggedFileSystemModel::createTaggedFile(
0633           TaggedFile::TF_ID3v23, taggedFile->getFilename(), index)) {
0634     if (index.isValid()) {
0635       QVariant data;
0636       data.setValue(id3libFile);
0637       // setData() will not invalidate the model, so this should be safe.
0638       if (auto setDataModel = const_cast<QAbstractItemModel*>(index.model())) {
0639         setDataModel->setData(index, data, TaggedFileSystemModel::TaggedFileRole);
0640       }
0641     }
0642     taggedFile = id3libFile;
0643     taggedFile->readTags(false);
0644   }
0645   return taggedFile;
0646 }
0647 
0648 /**
0649  * Read file with ID3v2.4 if it has an ID3v2.4 or ID3v2.2 tag.
0650  * ID3v2.2 files are also read with ID3v2.4 because id3lib corrupts
0651  * images in ID3v2.2 tags.
0652  *
0653  * @param taggedFile tagged file
0654  *
0655  * @return tagged file (can be new TagLibFile).
0656  */
0657 TaggedFile* FileProxyModel::readWithId3V24IfId3V24(TaggedFile* taggedFile)
0658 {
0659   if (taggedFile &&
0660       (taggedFile->taggedFileFeatures() &
0661        (TaggedFile::TF_ID3v23 | TaggedFile::TF_ID3v24)) ==
0662         TaggedFile::TF_ID3v23 &&
0663       !taggedFile->isChanged() &&
0664       taggedFile->isTagInformationRead() && taggedFile->hasTag(Frame::Tag_Id3v2)) {
0665     if (QString id3v2Version = taggedFile->getTagFormat(Frame::Tag_Id3v2);
0666         id3v2Version.isNull() || id3v2Version == QLatin1String("ID3v2.2.0")) {
0667       taggedFile = readWithId3V24(taggedFile);
0668     }
0669   }
0670   return taggedFile;
0671 }
0672 
0673 /**
0674  * Read tagged file with Ogg FLAC.
0675  *
0676  * @param taggedFile tagged file
0677  *
0678  * @return tagged file (can be newly created tagged file).
0679  */
0680 TaggedFile* FileProxyModel::readWithOggFlac(TaggedFile* taggedFile)
0681 {
0682   const QPersistentModelIndex& index = taggedFile->getIndex();
0683   if (TaggedFile* tagLibFile = TaggedFileSystemModel::createTaggedFile(
0684           TaggedFile::TF_OggFlac, taggedFile->getFilename(), index)) {
0685     if (index.isValid()) {
0686       QVariant data;
0687       data.setValue(tagLibFile);
0688       // setData() will not invalidate the model, so this should be safe.
0689       if (auto setDataModel = const_cast<QAbstractItemModel*>(index.model())) {
0690         setDataModel->setData(index, data, TaggedFileSystemModel::TaggedFileRole);
0691       }
0692     }
0693     taggedFile = tagLibFile;
0694     taggedFile->readTags(false);
0695   }
0696   return taggedFile;
0697 }
0698 
0699 /**
0700  * Try to read Ogg file with invalid tag detail info as an Ogg FLAC file.
0701  *
0702  * @param taggedFile tagged file
0703  *
0704  * @return tagged file (can be new TagLibFile).
0705  */
0706 TaggedFile* FileProxyModel::readWithOggFlacIfInvalidOgg(TaggedFile* taggedFile)
0707 {
0708   if (taggedFile &&
0709       (taggedFile->taggedFileFeatures() &
0710        (TaggedFile::TF_OggPictures | TaggedFile::TF_OggFlac)) ==
0711         TaggedFile::TF_OggPictures &&
0712       !taggedFile->isChanged() &&
0713       taggedFile->isTagInformationRead()) {
0714     TaggedFile::DetailInfo info;
0715     taggedFile->getDetailInfo(info);
0716     if (!info.valid) {
0717       taggedFile = readWithOggFlac(taggedFile);
0718     }
0719   }
0720   return taggedFile;
0721 }
0722 
0723 /**
0724  * Call readTags() on tagged file.
0725  * Reread file with other metadata plugin if it is not supported by current
0726  * plugin.
0727  *
0728  * @param taggedFile tagged file
0729  *
0730  * @return tagged file (can be new TaggedFile).
0731  */
0732 TaggedFile* FileProxyModel::readTagsFromTaggedFile(TaggedFile* taggedFile)
0733 {
0734   taggedFile->readTags(false);
0735   taggedFile = readWithId3V24IfId3V24(taggedFile);
0736   taggedFile = readWithOggFlacIfInvalidOgg(taggedFile);
0737   return taggedFile;
0738 }
0739 
0740 /**
0741  * Called when the source model emits fileModificationChanged().
0742  * @param srcIndex source model index
0743  * @param modified true if file is modified
0744  */
0745 void FileProxyModel::onFileModificationChanged(const QModelIndex& srcIndex,
0746                                                bool modified)
0747 {
0748   auto index = mapFromSource(srcIndex);
0749   emit fileModificationChanged(index, modified);
0750   emit dataChanged(index, index);
0751   bool lastIsModified = isModified();
0752   if (modified) {
0753     ++m_numModifiedFiles;
0754   } else if (m_numModifiedFiles > 0) {
0755     --m_numModifiedFiles;
0756   }
0757   if (bool newIsModified = isModified(); newIsModified != lastIsModified) {
0758     emit modifiedChanged(newIsModified);
0759   }
0760 }
0761 
0762 /**
0763  * Get icon provider.
0764  * @return icon provider.
0765  */
0766 CoreTaggedFileIconProvider* FileProxyModel::getIconProvider() const
0767 {
0768   if (m_fsModel) {
0769     return m_fsModel->getIconProvider();
0770   }
0771   return nullptr;
0772 }
0773 
0774 /**
0775  * Access to tagged file factories.
0776  * @return reference to tagged file factories.
0777  */
0778 QList<ITaggedFileFactory*>& FileProxyModel::taggedFileFactories()
0779 {
0780   return TaggedFileSystemModel::taggedFileFactories();
0781 }
0782 
0783 /**
0784  * Create name-file pattern pairs for all supported types.
0785  * The order is the same as in createFilterString().
0786  *
0787  * @return pairs containing name, pattern, e.g. ("MP3", "*.mp3"), ...,
0788  * ("All Files", "*").
0789  */
0790 QList<QPair<QString, QString> > FileProxyModel::createNameFilters()
0791 {
0792   QStringList extensions;
0793   const auto factories = taggedFileFactories();
0794   for (ITaggedFileFactory* factory : factories) {
0795     const auto keys = factory->taggedFileKeys();
0796     for (const QString& key : keys) {
0797       extensions.append(factory->supportedFileExtensions(key));
0798     }
0799   }
0800   // remove duplicates
0801   extensions.sort();
0802   QString lastExt(QLatin1String(""));
0803   for (auto it = extensions.begin(); it != extensions.end();) {
0804     if (*it == lastExt) {
0805       it = extensions.erase(it);
0806     } else {
0807       lastExt = *it;
0808       ++it;
0809     }
0810   }
0811 
0812   QString allPatterns;
0813   QList<QPair<QString, QString> > nameFilters;
0814   for (auto it = extensions.constBegin();
0815        it != extensions.constEnd();
0816        ++it) {
0817     QString text = it->mid(1).toUpper();
0818     QString pattern = QLatin1Char('*') + *it;
0819     if (!allPatterns.isEmpty()) {
0820       allPatterns += QLatin1Char(' ');
0821     }
0822     allPatterns += pattern;
0823     nameFilters.append(qMakePair(text, pattern));
0824   }
0825   if (!allPatterns.isEmpty()) {
0826     // Add extensions for playlists.
0827     allPatterns += QLatin1String(" *.m3u *.pls *.xspf");
0828     nameFilters.prepend(qMakePair(tr("All Supported Files"), allPatterns));
0829   }
0830   nameFilters.append(qMakePair(tr("All Files"), QString(QLatin1Char('*'))));
0831   return nameFilters;
0832 }