File indexing completed on 2025-02-02 05:18:03
0001 /* 0002 * SPDX-FileCopyrightText: 2015 Lars Pontoppidan <dev.larpon@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "mediaframe.h" 0008 0009 #include <QCryptographicHash> 0010 #include <QDebug> 0011 #include <QDir> 0012 #include <QDirIterator> 0013 #include <QFileInfo> 0014 #include <QImageReader> 0015 #include <QMimeDatabase> 0016 #include <QRandomGenerator> 0017 #include <QRegularExpression> 0018 #include <QTime> 0019 #include <QUrl> 0020 0021 #include <KIO/StoredTransferJob> 0022 0023 MediaFrame::MediaFrame(QObject *parent) 0024 : QObject(parent) 0025 { 0026 const auto imageMimeTypeNames = QImageReader::supportedMimeTypes(); 0027 QMimeDatabase mimeDb; 0028 for (const auto &imageMimeTypeName : imageMimeTypeNames) { 0029 const auto mimeType = mimeDb.mimeTypeForName(QLatin1String(imageMimeTypeName)); 0030 m_filters << mimeType.globPatterns(); 0031 } 0032 qDebug() << "Added" << m_filters.count() << "filters"; 0033 // qDebug() << m_filters; 0034 m_next = 0; 0035 0036 connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &MediaFrame::slotItemChanged); 0037 connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &MediaFrame::slotItemChanged); 0038 } 0039 0040 MediaFrame::~MediaFrame() = default; 0041 0042 int MediaFrame::count() const 0043 { 0044 return m_allFiles.count(); 0045 } 0046 0047 bool MediaFrame::random() const 0048 { 0049 return m_random; 0050 } 0051 0052 void MediaFrame::setRandom(bool random) 0053 { 0054 if (random != m_random) { 0055 m_random = random; 0056 Q_EMIT randomChanged(); 0057 } 0058 } 0059 0060 int MediaFrame::random(int min, int max) 0061 { 0062 if (min > max) { 0063 int temp = min; 0064 min = max; 0065 max = temp; 0066 } 0067 0068 // qDebug() << "random" << min << "<->" << max << "=" << ((qrand()%(max-min+1))+min); 0069 return (QRandomGenerator::global()->bounded((max - min + 1)) + min); 0070 } 0071 0072 QString MediaFrame::getCacheDirectory() 0073 { 0074 return QDir::temp().absolutePath(); 0075 } 0076 0077 QString MediaFrame::hash(const QString &str) 0078 { 0079 return QString::fromLatin1(QCryptographicHash::hash(str.toUtf8(), QCryptographicHash::Md5).toHex()); 0080 } 0081 0082 bool MediaFrame::isDir(const QString &path) 0083 { 0084 return QDir(path).exists(); 0085 } 0086 0087 bool MediaFrame::isDirEmpty(const QString &path) 0088 { 0089 return (isDir(path) && QDir(path).entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()); 0090 } 0091 0092 bool MediaFrame::isFile(const QString &path) 0093 { 0094 // Check if the file exists and is not a directory 0095 return (QFileInfo::exists(path) && QFileInfo(path).isFile()); 0096 } 0097 0098 void MediaFrame::add(const QString &path) 0099 { 0100 add(path, AddOption::NON_RECURSIVE); 0101 } 0102 0103 void MediaFrame::add(const QString &path, AddOption option) 0104 { 0105 if (isAdded(path)) { 0106 qWarning() << "Path" << path << "already added"; 0107 return; 0108 } 0109 0110 QUrl url = QUrl(path); 0111 QString localPath = url.toString(QUrl::PreferLocalFile); 0112 // qDebug() << "Local path" << localPath << "Path" << path; 0113 0114 QStringList paths; 0115 QString filePath; 0116 0117 if (isDir(localPath)) { 0118 if (!isDirEmpty(localPath)) { 0119 QDirIterator dirIterator( 0120 localPath, 0121 m_filters, 0122 QDir::Files, 0123 (option == AddOption::RECURSIVE ? QDirIterator::Subdirectories | QDirIterator::FollowSymlinks : QDirIterator::NoIteratorFlags)); 0124 0125 while (dirIterator.hasNext()) { 0126 dirIterator.next(); 0127 0128 filePath = "file://" + dirIterator.filePath(); 0129 paths.append(filePath); 0130 m_allFiles.append(filePath); 0131 // qDebug() << "Appended" << filePath; 0132 Q_EMIT countChanged(); 0133 } 0134 if (paths.count() > 0) { 0135 m_pathMap.insert(path, paths); 0136 qDebug() << "Added" << paths.count() << "files from" << path; 0137 } else { 0138 qWarning() << "No images found in directory" << path; 0139 } 0140 } else { 0141 qWarning() << "Not adding empty directory" << path; 0142 } 0143 0144 // the pictures have to be sorted before adding them to the list, 0145 // because the QDirIterator sorts them in a different way than QDir::entryList 0146 // paths.sort(); 0147 0148 } else if (isFile(localPath)) { 0149 paths.append(path); 0150 m_pathMap.insert(path, paths); 0151 m_allFiles.append(path); 0152 qDebug() << "Added" << paths.count() << "files from" << path; 0153 Q_EMIT countChanged(); 0154 } else { 0155 if (url.isValid() && !url.isLocalFile()) { 0156 qDebug() << "Adding" << url.toString() << "as remote file"; 0157 paths.append(path); 0158 m_pathMap.insert(path, paths); 0159 m_allFiles.append(path); 0160 Q_EMIT countChanged(); 0161 } else { 0162 qWarning() << "Path" << path << "is not a valid file url or directory"; 0163 } 0164 } 0165 } 0166 0167 void MediaFrame::clear() 0168 { 0169 m_pathMap.clear(); 0170 m_allFiles.clear(); 0171 Q_EMIT countChanged(); 0172 } 0173 0174 void MediaFrame::watch(const QString &path) 0175 { 0176 QUrl url = QUrl(path); 0177 QString localPath = url.toString(QUrl::PreferLocalFile); 0178 if (isFile(localPath)) { 0179 if (!m_watchFile.isEmpty()) { 0180 // qDebug() << "Removing" << m_watchFile << "from watch list"; 0181 m_watcher.removePath(m_watchFile); 0182 } else { 0183 qDebug() << "Nothing in watch list"; 0184 } 0185 0186 // qDebug() << "watching" << localPath << "for changes"; 0187 m_watcher.addPath(localPath); 0188 m_watchFile = localPath; 0189 } else { 0190 qWarning() << "Can't watch remote file" << path << "for changes"; 0191 } 0192 } 0193 0194 bool MediaFrame::isAdded(const QString &path) 0195 { 0196 return (m_pathMap.contains(path)); 0197 } 0198 0199 void MediaFrame::get(QJSValue successCallback) 0200 { 0201 get(successCallback, QJSValue::UndefinedValue); 0202 } 0203 0204 void MediaFrame::get(QJSValue successCallback, QJSValue errorCallback) 0205 { 0206 int size = m_allFiles.count() - 1; 0207 0208 QString path; 0209 QString errorMessage; 0210 QJSValueList args; 0211 0212 if (size < 1) { 0213 if (size == 0) { 0214 path = m_allFiles.at(0); 0215 0216 if (successCallback.isCallable()) { 0217 args << QJSValue(path); 0218 successCallback.call(args); 0219 } 0220 return; 0221 } else { 0222 errorMessage = QStringLiteral("No files available"); 0223 qWarning() << errorMessage; 0224 0225 args << QJSValue(errorMessage); 0226 errorCallback.call(args); 0227 return; 0228 } 0229 } 0230 0231 if (m_random) { 0232 path = m_allFiles.at(this->random(0, size)); 0233 } else { 0234 path = m_allFiles.at(m_next); 0235 m_next++; 0236 if (m_next > size) { 0237 qDebug() << "Resetting next count from" << m_next << "due to queue size" << size; 0238 m_next = 0; 0239 } 0240 } 0241 0242 QUrl url = QUrl(path); 0243 0244 if (url.isValid()) { 0245 QString localPath = url.toString(QUrl::PreferLocalFile); 0246 0247 if (!isFile(localPath)) { 0248 m_filename = path.section(QLatin1Char('/'), -1); 0249 0250 QString cachedFile = getCacheDirectory() + QLatin1Char('/') + hash(path) + QLatin1Char('_') + m_filename; 0251 0252 if (isFile(cachedFile)) { 0253 // File has been cached 0254 qDebug() << path << "is cached as" << cachedFile; 0255 0256 if (successCallback.isCallable()) { 0257 args << QJSValue(cachedFile); 0258 successCallback.call(args); 0259 } 0260 return; 0261 } 0262 0263 m_successCallback = successCallback; 0264 m_errorCallback = errorCallback; 0265 m_filename = cachedFile; 0266 0267 qDebug() << path << "doesn't exist locally, trying remote."; 0268 0269 KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); 0270 connect(job, &KJob::finished, this, &MediaFrame::slotFinished); 0271 0272 } else { 0273 if (successCallback.isCallable()) { 0274 args << QJSValue(path); 0275 successCallback.call(args); 0276 } 0277 return; 0278 } 0279 } else { 0280 errorMessage = path + QLatin1String(" is not a valid URL"); 0281 qCritical() << errorMessage; 0282 0283 if (errorCallback.isCallable()) { 0284 args << QJSValue(errorMessage); 0285 errorCallback.call(args); 0286 } 0287 return; 0288 } 0289 } 0290 0291 void MediaFrame::pushHistory(const QString &string) 0292 { 0293 const int oldCount = m_history.count(); 0294 0295 m_history.prepend(string); 0296 0297 // Keep a sane history size 0298 if (m_history.length() > 50) { 0299 m_history.removeLast(); 0300 } 0301 0302 if (oldCount != m_history.count()) { 0303 Q_EMIT historyLengthChanged(); 0304 } 0305 } 0306 0307 QString MediaFrame::popHistory() 0308 { 0309 if (m_history.isEmpty()) { 0310 return QString(); 0311 } 0312 0313 const QString item = m_history.takeFirst(); 0314 Q_EMIT historyLengthChanged(); 0315 return item; 0316 } 0317 0318 int MediaFrame::historyLength() const 0319 { 0320 return m_history.length(); 0321 } 0322 0323 void MediaFrame::pushFuture(const QString &string) 0324 { 0325 m_future.prepend(string); 0326 Q_EMIT futureLengthChanged(); 0327 } 0328 0329 QString MediaFrame::popFuture() 0330 { 0331 if (m_future.isEmpty()) { 0332 return QString(); 0333 } 0334 0335 const QString item = m_future.takeFirst(); 0336 Q_EMIT futureLengthChanged(); 0337 return item; 0338 } 0339 0340 int MediaFrame::futureLength() const 0341 { 0342 return m_future.length(); 0343 } 0344 0345 void MediaFrame::slotItemChanged(const QString &path) 0346 { 0347 Q_EMIT itemChanged(path); 0348 } 0349 0350 void MediaFrame::slotFinished(KJob *job) 0351 { 0352 QString errorMessage; 0353 QJSValueList args; 0354 0355 if (job->error()) { 0356 errorMessage = QLatin1String("Error loading image: ") + job->errorString(); 0357 qCritical() << errorMessage; 0358 0359 if (m_errorCallback.isCallable()) { 0360 args << QJSValue(errorMessage); 0361 m_errorCallback.call(args); 0362 } 0363 } else if (KIO::StoredTransferJob *transferJob = qobject_cast<KIO::StoredTransferJob *>(job)) { 0364 QImage image; 0365 0366 // TODO make proper caching calls 0367 QString path = m_filename; 0368 qDebug() << "Saving download to" << path; 0369 0370 image.loadFromData(transferJob->data()); 0371 image.save(path); 0372 0373 qDebug() << "Saved to" << path; 0374 0375 if (m_successCallback.isCallable()) { 0376 args << QJSValue(path); 0377 m_successCallback.call(args); 0378 } 0379 } else { 0380 errorMessage = QStringLiteral("Unknown error occurred"); 0381 0382 qCritical() << errorMessage; 0383 0384 if (m_errorCallback.isCallable()) { 0385 args << QJSValue(errorMessage); 0386 m_errorCallback.call(args); 0387 } 0388 } 0389 }