File indexing completed on 2025-04-20 04:02:14
0001 /* 0002 * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "fileinfo.h" 0008 0009 #include <optional> 0010 0011 #include <QGlobalStatic> 0012 #include <QImageReader> 0013 #include <QMetaObject> 0014 #include <QMimeDatabase> 0015 #include <QRunnable> 0016 #include <QThreadPool> 0017 0018 struct FileInfoCacheEntry { 0019 QUrl source; 0020 QString mimeType; 0021 FileInfo::Type type = FileInfo::UnknownType; 0022 int width = -1; 0023 int height = -1; 0024 }; 0025 0026 /** 0027 * To make FileInfo objects cheap to use from QML, we cache the information it 0028 * uses in a separate structure. This allows FileInfo to quickly lookup and 0029 * retrieve information if we have already analyzed a file before. If not, we 0030 * use a background job to retrieve the actual information so we do not block 0031 * any other thread with potentially expensive file operations. 0032 */ 0033 class FileInfoCache : public QObject 0034 { 0035 Q_OBJECT 0036 public: 0037 FileInfoCache(); 0038 0039 std::shared_ptr<FileInfoCacheEntry> get(const QUrl &url); 0040 0041 void read(const QUrl &url); 0042 void readingFinished(const QUrl &url, std::shared_ptr<FileInfoCacheEntry> entry); 0043 0044 Q_SIGNAL void cacheUpdated(const QUrl &url); 0045 0046 QThreadPool threadPool; 0047 QHash<QUrl, std::shared_ptr<FileInfoCacheEntry>> cache; 0048 }; 0049 0050 Q_GLOBAL_STATIC(FileInfoCache, cache); 0051 0052 class FileInfoRunnable : public QRunnable 0053 { 0054 public: 0055 QUrl source; 0056 0057 void run() override 0058 { 0059 auto entry = std::make_shared<FileInfoCacheEntry>(); 0060 entry->source = source; 0061 0062 QMimeDatabase db; 0063 auto mimeType = db.mimeTypeForFile(source.toLocalFile(), QMimeDatabase::MatchContent); 0064 0065 if (!mimeType.isValid()) { 0066 // Mime type is not valid, so either the source does not exist or we 0067 // don't have permission to read it. In any case, we cannot retrieve 0068 // information for this file, so abort. 0069 0070 // Make a local copy of the source variable so we don't need to 0071 // capture "this" which will be destroyed after it completes. 0072 auto s = source; 0073 0074 QMetaObject::invokeMethod( 0075 cache(), 0076 [s]() { 0077 cache()->readingFinished(s, nullptr); 0078 }, 0079 Qt::QueuedConnection); 0080 return; 0081 } 0082 0083 auto mimeTypeName = mimeType.name(); 0084 entry->mimeType = mimeTypeName; 0085 0086 if (mimeTypeName.startsWith(QStringLiteral("video/")) || // 0087 mimeTypeName == QStringLiteral("application/x-matroska")) { 0088 entry->type = FileInfo::VideoType; 0089 } else if (mimeTypeName.startsWith(QStringLiteral("image/svg"))) { 0090 entry->type = FileInfo::VectorImageType; 0091 } else if (mimeTypeName == QStringLiteral("image/gif")) { 0092 entry->type = FileInfo::AnimatedImageType; 0093 } else if (mimeTypeName.startsWith(QStringLiteral("image/"))) { 0094 entry->type = FileInfo::RasterImageType; 0095 } 0096 0097 if (entry->type != FileInfo::VideoType) { 0098 QImageReader reader(source.toLocalFile()); 0099 auto size = reader.size(); 0100 if (size.isValid()) { 0101 entry->width = size.width(); 0102 entry->height = size.height(); 0103 } else { 0104 auto image = reader.read(); 0105 entry->width = image.width(); 0106 entry->height = image.height(); 0107 } 0108 } 0109 0110 QMetaObject::invokeMethod( 0111 cache(), 0112 [entry]() { 0113 cache()->readingFinished(entry->source, entry); 0114 }, 0115 Qt::QueuedConnection); 0116 } 0117 }; 0118 0119 FileInfoCache::FileInfoCache() 0120 : QObject(nullptr) 0121 { 0122 // Since the runnable is mostly IO bound, there is not really any reason to 0123 // execute more than one of it in parallel. 0124 threadPool.setMaxThreadCount(1); 0125 } 0126 0127 std::shared_ptr<FileInfoCacheEntry> FileInfoCache::get(const QUrl &url) 0128 { 0129 if (!url.isValid()) { 0130 return nullptr; 0131 } 0132 0133 if (cache.contains(url)) { 0134 return cache.value(url); 0135 } 0136 0137 return nullptr; 0138 } 0139 0140 void FileInfoCache::read(const QUrl &url) 0141 { 0142 auto runnable = new FileInfoRunnable; 0143 runnable->source = url; 0144 threadPool.start(runnable); 0145 } 0146 0147 void FileInfoCache::readingFinished(const QUrl &source, std::shared_ptr<FileInfoCacheEntry> entry) 0148 { 0149 if (entry) { 0150 cache.insert(source, entry); 0151 } 0152 Q_EMIT cacheUpdated(source); 0153 } 0154 0155 FileInfo::FileInfo(QObject *parent) 0156 : QObject(parent) 0157 { 0158 connect(cache(), &FileInfoCache::cacheUpdated, this, &FileInfo::onCacheUpdated); 0159 } 0160 0161 FileInfo::~FileInfo() = default; 0162 0163 QUrl FileInfo::source() const 0164 { 0165 return m_source; 0166 } 0167 0168 void FileInfo::setSource(const QUrl &source) 0169 { 0170 if (m_source == source) { 0171 return; 0172 } 0173 0174 m_source = source; 0175 Q_EMIT sourceChanged(); 0176 0177 auto result = cache()->get(source); 0178 if (!result) { 0179 setStatus(Reading); 0180 cache()->read(source); 0181 return; 0182 } 0183 0184 m_info = result; 0185 Q_EMIT infoChanged(); 0186 0187 setStatus(Ready); 0188 } 0189 0190 FileInfo::Status FileInfo::status() const 0191 { 0192 return m_status; 0193 } 0194 0195 QString FileInfo::mimeType() const 0196 { 0197 if (!m_info) { 0198 return QString{}; 0199 } 0200 0201 return m_info->mimeType; 0202 } 0203 0204 FileInfo::Type FileInfo::type() const 0205 { 0206 if (!m_info) { 0207 return UnknownType; 0208 } 0209 0210 return m_info->type; 0211 } 0212 0213 int FileInfo::width() const 0214 { 0215 if (!m_info) { 0216 return -1; 0217 } 0218 0219 return m_info->width; 0220 } 0221 0222 int FileInfo::height() const 0223 { 0224 if (!m_info) { 0225 return -1; 0226 } 0227 0228 return m_info->height; 0229 } 0230 0231 void FileInfo::setStatus(FileInfo::Status newStatus) 0232 { 0233 if (newStatus == m_status) { 0234 return; 0235 } 0236 0237 m_status = newStatus; 0238 Q_EMIT statusChanged(); 0239 } 0240 0241 void FileInfo::onCacheUpdated(const QUrl &source) 0242 { 0243 if (source != m_source) { 0244 return; 0245 } 0246 0247 auto result = cache->get(source); 0248 if (result) { 0249 m_info = result; 0250 Q_EMIT infoChanged(); 0251 0252 setStatus(Ready); 0253 } else { 0254 setStatus(Error); 0255 } 0256 } 0257 0258 #include "fileinfo.moc"