From 35d99a1c054b717742d2c760334dd98dee92633c Mon Sep 17 00:00:00 2001 From: uncor3 Date: Thu, 9 Apr 2026 18:44:20 +0000 Subject: [PATCH] optimize gallerywidget --- src/core/services/load_heic.cpp | 18 ++-- src/gallerywidget.cpp | 112 ++++++++++----------- src/gallerywidget.h | 3 +- src/imageloader.cpp | 172 ++++++++++---------------------- src/imageloader.h | 22 ++-- src/imagetask.h | 36 +++---- src/photomodel.cpp | 88 ++++++++-------- src/photomodel.h | 10 +- 8 files changed, 202 insertions(+), 259 deletions(-) diff --git a/src/core/services/load_heic.cpp b/src/core/services/load_heic.cpp index 8cb883c..8561bf4 100644 --- a/src/core/services/load_heic.cpp +++ b/src/core/services/load_heic.cpp @@ -24,12 +24,12 @@ #include #include -QPixmap load_heic(const QByteArray &imageData) +QImage load_heic(const QByteArray &imageData) { heif_context *ctx = heif_context_alloc(); if (!ctx) { qWarning() << "Failed to allocate heif_context"; - return QPixmap(); + return QImage(); } heif_error err = heif_context_read_from_memory(ctx, imageData.constData(), @@ -37,7 +37,7 @@ QPixmap load_heic(const QByteArray &imageData) if (err.code != heif_error_Ok) { qWarning() << "Failed to read HEIC from memory:" << err.message; heif_context_free(ctx); - return QPixmap(); + return QImage(); } heif_image_handle *handle; @@ -45,7 +45,7 @@ QPixmap load_heic(const QByteArray &imageData) if (err.code != heif_error_Ok) { qWarning() << "Failed to get primary image handle:" << err.message; heif_context_free(ctx); - return QPixmap(); + return QImage(); } heif_image *img; @@ -55,7 +55,7 @@ QPixmap load_heic(const QByteArray &imageData) qWarning() << "Failed to decode HEIC image:" << err.message; heif_image_handle_release(handle); heif_context_free(ctx); - return QPixmap(); + return QImage(); } int width = heif_image_get_width(img, heif_channel_interleaved); @@ -73,15 +73,15 @@ QPixmap load_heic(const QByteArray &imageData) heif_image_release(img); heif_image_handle_release(handle); heif_context_free(ctx); - return QPixmap(); + return QImage(); } QImage qimg(data, width, height, stride, QImage::Format_RGB888); - QPixmap result = QPixmap::fromImage(qimg); - + QImage copy = + qimg.copy(); // Deep copy since the original data will be freed heif_image_release(img); heif_image_handle_release(handle); heif_context_free(ctx); - return result; + return copy; } diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index 9921e0a..fbcc991 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -20,6 +20,7 @@ #include "gallerywidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" +#include "imageloader.h" #include "iomanagerclient.h" #include "mediapreviewdialog.h" #include "photomodel.h" @@ -42,14 +43,12 @@ #include #include -// todo: dont load paths on main thread, handle /* FIXME: this needs to be refactored once we figure out how to query Photos.sqlite Check out: https://github.com/ScottKjr3347/iOS_Local_PL_Photos.sqlite_Queries */ - GalleryWidget::GalleryWidget(const std::shared_ptr device, QWidget *parent) : QWidget{parent}, m_device(device) @@ -295,9 +294,7 @@ void GalleryWidget::onExportSelected() QList exportItems; for (const QString &filePath : filePaths) { - QString fileName = filePath.split('/').last(); exportItems.append(filePath); - // exportItems.append(ExportItem(filePath, fileName, m_device->udid)); } qDebug() << "Starting export of selected files:" << exportItems.size() @@ -335,14 +332,14 @@ void GalleryWidget::onExportAll() if (!m_model) return; - QStringList filePaths = m_model->getFilteredFilePaths(); + QList exportItems = m_model->getFilteredFilePaths(); - if (filePaths.isEmpty()) { + if (exportItems.isEmpty()) { QMessageBox::information(this, "No Items", "No items to export."); return; } QString message = - QString("Export all %1 items currently shown?").arg(filePaths.size()); + QString("Export all %1 items currently shown?").arg(exportItems.size()); int reply = QMessageBox::question(this, "Export All", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); @@ -356,11 +353,6 @@ void GalleryWidget::onExportAll() return; } - QList exportItems; - for (const QString &filePath : filePaths) { - QString fileName = filePath.split('/').last(); - } - qDebug() << "Starting export of:" << exportItems.size() << "items to" << exportDir; @@ -590,8 +582,11 @@ void GalleryWidget::setControlsEnabled(bool enabled) { m_sortComboBox->setEnabled(enabled); m_filterComboBox->setEnabled(enabled); - m_exportSelectedButton->setEnabled( - enabled && m_listView && m_listView->selectionModel()->hasSelection()); + + const bool hasSelection = m_listView && m_listView->selectionModel() && + m_listView->selectionModel()->hasSelection(); + + m_exportSelectedButton->setEnabled(enabled && hasSelection); } /* @@ -600,21 +595,24 @@ void GalleryWidget::setControlsEnabled(bool enabled) Check out: https://github.com/ScottKjr3347/iOS_Local_PL_Photos.sqlite_Queries */ -QIcon GalleryWidget::loadAlbumThumbnail(const QString &albumPath) +QImage +GalleryWidget::loadAlbumThumbnail(const QString &albumPath, + std::shared_ptr device) { - // Get album directory contents - QList albumTree = m_device->afc_backend->list_dir(albumPath); - - if (albumTree.isEmpty()) { - qDebug() << "Failed to read album directory:" << albumPath; - return QIcon(); + if (QCoreApplication::closingDown() || !QGuiApplication::instance()) { + return {}; + } + + QList albumTree = device->afc_backend->list_dir(albumPath); + if (albumTree.isEmpty()) { + qDebug() << "Failed to read album directory:" << albumPath; + return {}; } - // Find the first image file QString firstImagePath; for (const QString &fileName : albumTree) { bool isDir = - m_device->afc_backend->is_directory((albumPath + "/" + fileName)); + device->afc_backend->is_directory((albumPath + "/" + fileName)); if (!isDir && (fileName.endsWith(".JPG", Qt::CaseInsensitive) || fileName.endsWith(".PNG", Qt::CaseInsensitive) || fileName.endsWith(".HEIC", Qt::CaseInsensitive))) { @@ -624,57 +622,55 @@ QIcon GalleryWidget::loadAlbumThumbnail(const QString &albumPath) } if (firstImagePath.isEmpty()) { - qDebug() << "No images found in album:" << albumPath; - return QIcon(); + return {}; } - QByteArray imageData = - m_device->afc_backend->file_to_buffer(firstImagePath); + QByteArray imageData = device->afc_backend->file_to_buffer(firstImagePath); - if (imageData.isEmpty()) { - qDebug() << "Could not read image data for thumbnail:" << albumPath; - return QIcon(); - } - - QPixmap thumbnail; - - // Load HEIC + QImage thumbnail; if (firstImagePath.endsWith(".HEIC", Qt::CaseInsensitive)) { - qDebug() << "Loading HEIC thumbnail from:" << firstImagePath; thumbnail = load_heic(imageData); } else { - // Load other formats - if (!thumbnail.loadFromData(imageData)) { - qDebug() << "Could not decode image data for thumbnail:" - << firstImagePath; - return QIcon(); - } + thumbnail.loadFromData(imageData); } - if (thumbnail.isNull()) { - qDebug() << "Failed to load thumbnail from:" << firstImagePath; - return QIcon(); - } - - return QIcon(thumbnail); + return thumbnail; } void GalleryWidget::loadAlbumThumbnailAsync(const QString &albumPath, QStandardItem *item) { - auto *watcher = new QFutureWatcher(this); + Q_UNUSED(item); - connect(watcher, &QFutureWatcher::finished, this, [watcher, item]() { - QIcon result = watcher->result(); - if (!result.isNull()) { - item->setIcon(result); - } - watcher->deleteLater(); + auto *watcher = new QFutureWatcher(this); + const auto device = m_device; + + connect(watcher, &QFutureWatcher::finished, this, + [this, watcher, albumPath]() { + const QImage result = watcher->result(); + watcher->deleteLater(); + + if (result.isNull() || !m_albumModel || + QCoreApplication::closingDown() || + !QGuiApplication::instance()) { + return; + } + + for (int row = 0; row < m_albumModel->rowCount(); ++row) { + QModelIndex idx = m_albumModel->index(row, 0); + if (idx.data(Qt::UserRole).toString() == albumPath) { + if (auto *it = m_albumModel->itemFromIndex(idx)) { + it->setIcon(QIcon(QPixmap::fromImage(result))); + } + break; + } + } + }); + + QFuture future = QtConcurrent::run([albumPath, device]() { + return loadAlbumThumbnail(albumPath, device); }); - QFuture future = QtConcurrent::run( - [this, albumPath]() { return loadAlbumThumbnail(albumPath); }); - watcher->setFuture(future); } diff --git a/src/gallerywidget.h b/src/gallerywidget.h index f3a6357..97c08d5 100644 --- a/src/gallerywidget.h +++ b/src/gallerywidget.h @@ -70,7 +70,8 @@ private: void onAlbumListLoaded(const QList &dcimTree); void setControlsEnabled(bool enabled); QString selectExportDirectory(); - QIcon loadAlbumThumbnail(const QString &albumPath); + static QImage loadAlbumThumbnail(const QString &albumPath, + std::shared_ptr device); void loadAlbumThumbnailAsync(const QString &albumPath, QStandardItem *item); void onPhotoContextMenu(const QPoint &pos); PhotoModel::FilterType getCurrentFilterType() const; diff --git a/src/imageloader.cpp b/src/imageloader.cpp index 9537cb5..f3603cc 100644 --- a/src/imageloader.cpp +++ b/src/imageloader.cpp @@ -16,9 +16,12 @@ extern "C" { ImageLoader::ImageLoader(QObject *parent) : QObject(parent) { // TODO: maybe finetune to hardware ? - m_pool.setMaxThreadCount(10); - // 350 MB cache for thumbnails - m_cache.setMaxCost(350); + m_pool.setMaxThreadCount(15); + + if (qApp) { + connect(qApp, &QCoreApplication::aboutToQuit, this, + [this]() { clear(); }); + } } bool ImageLoader::isLoading(const QString &path) @@ -31,11 +34,6 @@ void ImageLoader::requestThumbnail( const std::shared_ptr device, const QString &path, unsigned int row) { - if (auto *cached = m_cache.object(path)) { - emit thumbnailReady(path, *cached, row); - return; - } - { QMutexLocker locker(&m_mutex); if (m_pendingTasks.contains(path)) @@ -66,26 +64,19 @@ void ImageLoader::requestImageWithCallback( int priority, std::function callback, std::optional> hause_arrest, bool useAfc2) { - - /* - FIXME: row is passed as priority - nothing dangerous but a bit hacky, could be handled better - */ //scale=false auto *task = new ImageTask(device, path, priority, false, hause_arrest, useAfc2); - /* - TODO: should we do this ? - this function is meant for the media preview dialog, - which only loads a image at a time - and not really related to the thumbnails in the photomodel - */ - // m_pendingTasks[path] = task; - connect( task, &ImageTask::finished, this, - [this, path, callback](const QString &, const QPixmap &pixmap, - unsigned int row) { callback(pixmap); }, + [callback](const QString &, const QImage &image, unsigned int) { + if (QCoreApplication::closingDown() || + !QGuiApplication::instance()) { + callback(QPixmap()); + return; + } + callback(image.isNull() ? QPixmap() : QPixmap::fromImage(image)); + }, Qt::QueuedConnection); m_pool.start(task, priority); @@ -95,92 +86,59 @@ void ImageLoader::cancelThumbnail(const QString &path) { qDebug() << "Attempting to cancel thumbnail loading for" << path; - QMutexLocker locker(&m_mutex); + ImageTask *task = nullptr; + { + QMutexLocker locker(&m_mutex); + task = m_pendingTasks.take(path); + } - if (!m_pendingTasks.contains(path)) { + if (!task) { return; } - ImageTask *task = m_pendingTasks.value(path); - if (task && m_pool.tryTake(task)) { + if (m_pool.tryTake(task)) { qDebug() << "Cancelled thumbnail loading for" << path; - m_pendingTasks.remove(path); + // should be safe to delete delete task; - } else { - m_pendingTasks.remove(path); } } void ImageLoader::clear() { qDebug() << "Clearing ImageLoader cache and pending tasks"; - m_pool.clear(); + m_pool.waitForDone(); - { - QMutexLocker locker(&m_mutex); - - for (auto it = m_pendingTasks.begin(); it != m_pendingTasks.end();) { - ImageTask *task = it.value(); - if (task && m_pool.tryTake(task)) { - qDebug() << "Cancelled pending task"; - // FIXME: should we do auto delete? - // delete task; - } - it = m_pendingTasks.erase(it); - } - } - - /* - TODO: This could make the UI unresponsive but - maybe a good approch to handle - async cancellation properly(wireless) - Wait for any running tasks to complete - */ - // m_pool.waitForDone(); - - { - QMutexLocker locker(&m_mutex); - m_pendingTasks.clear(); - } - - m_cache.clear(); + QMutexLocker locker(&m_mutex); + m_pendingTasks.clear(); } -void ImageLoader::onTaskFinished(const QString &path, const QPixmap &pixmap, +void ImageLoader::onTaskFinished(const QString &path, const QImage &image, unsigned int row) { - ImageTask *task = nullptr; - { QMutexLocker locker(&m_mutex); - if (!m_pendingTasks.contains(path)) { return; } - - task = m_pendingTasks.take(path); + m_pendingTasks.remove(path); } - // FIXME - // if (task) { - // delete task; - // } - - // Cache - m_cache.insert(path, new QPixmap(pixmap)); + if (QCoreApplication::closingDown() || !QGuiApplication::instance()) { + return; + } + const QPixmap pixmap = + image.isNull() ? QPixmap() : QPixmap::fromImage(image); emit thumbnailReady(path, pixmap, row); } // almost a copy of loadThumbnailFromDevice but without any scaling logic -QPixmap ImageLoader::loadImage( +QImage ImageLoader::loadImage( const std::shared_ptr device, const QString &filePath, std::optional> hause_arrest, bool useAfc2) { - if (QCoreApplication::closingDown()) { - qDebug() << "Application is closing, aborting loadImage for" - << filePath; + if (QCoreApplication::closingDown() || !QGuiApplication::instance()) { return {}; } QByteArray imageData; @@ -194,14 +152,9 @@ QPixmap ImageLoader::loadImage( imageData = device->afc_backend->file_to_buffer(filePath); } - if (imageData.isEmpty()) { - qDebug() << "Could not read from device:" << filePath; - return {}; - } - if (filePath.endsWith(".HEIC", Qt::CaseInsensitive)) { - QPixmap img = load_heic(imageData); - return img.isNull() ? QPixmap() : img; + QImage img = load_heic(imageData); + return img.isNull() ? QImage() : img; } QBuffer buffer(&imageData); @@ -211,31 +164,24 @@ QPixmap ImageLoader::loadImage( if (reader.canRead()) { QImage image = reader.read(); if (!image.isNull()) { - return QPixmap::fromImage(image); + return image; } - qDebug() << "QImageReader failed to decode" << filePath - << "Error:" << reader.errorString(); } - // Fallback for formats QImageReader might struggle with - QPixmap pixmap; - if (pixmap.loadFromData(imageData)) { - return pixmap; + QImage fallback; + if (fallback.loadFromData(imageData)) { + return fallback; } - qDebug() << "Could not decode image data for:" << filePath; return {}; } -QPixmap ImageLoader::loadThumbnailFromDevice( +QImage ImageLoader::loadThumbnailFromDevice( const std::shared_ptr device, const QString &filePath, const QSize &size, std::optional> hause_arrest, bool useAfc2) { - if (QCoreApplication::closingDown()) { - qDebug() - << "Application is closing, aborting loadThumbnailFromDevice for" - << filePath; + if (QCoreApplication::closingDown() || !QGuiApplication::instance()) { return {}; } @@ -250,14 +196,9 @@ QPixmap ImageLoader::loadThumbnailFromDevice( imageData = device->afc_backend->file_to_buffer(filePath); } - if (imageData.isEmpty()) { - qDebug() << "Could not read from device:" << filePath; - return {}; - } - if (filePath.endsWith(".HEIC", Qt::CaseInsensitive)) { - QPixmap img = load_heic(imageData); - return img.isNull() ? QPixmap() + QImage img = load_heic(imageData); + return img.isNull() ? QImage() : img.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); } @@ -269,31 +210,26 @@ QPixmap ImageLoader::loadThumbnailFromDevice( if (reader.canRead()) { QImage image = reader.read(); if (!image.isNull()) { - QImage scaled = image.scaled(size, Qt::KeepAspectRatio, - Qt::SmoothTransformation); - return QPixmap::fromImage(scaled); + return image.scaled(size, Qt::KeepAspectRatio, + Qt::SmoothTransformation); } - qDebug() << "QImageReader failed to decode" << filePath - << "Error:" << reader.errorString(); } - // Fallback for formats QImageReader might struggle with - QPixmap original; - if (original.loadFromData(imageData)) { - return original.scaled(size, Qt::KeepAspectRatio, + QImage fallback; + if (fallback.loadFromData(imageData)) { + return fallback.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); } - qDebug() << "Could not decode image data for:" << filePath; return {}; } -QPixmap ImageLoader::generateVideoThumbnailFFmpeg( +QImage ImageLoader::generateVideoThumbnailFFmpeg( const std::shared_ptr device, const QString &filePath, const QSize &requestedSize, std::optional> hause_arrest, bool useAfc2) { - QPixmap thumbnail; + QImage thumbnail; if (QCoreApplication::closingDown()) { qDebug() << "Application is closing, aborting " "generateVideoThumbnailFFmpeg for" @@ -551,9 +487,9 @@ QPixmap ImageLoader::generateVideoThumbnailFFmpeg( might need to abstract the main logic to get the frame and handle scaling separately */ - thumbnail = QPixmap::fromImage( + thumbnail = imgCopy.scaled(requestedSize, Qt::KeepAspectRatio, - Qt::SmoothTransformation)); + Qt::SmoothTransformation); } av_frame_free(&rgbFrame); @@ -570,4 +506,4 @@ QPixmap ImageLoader::generateVideoThumbnailFFmpeg( avformat_close_input(&formatCtx); return thumbnail; -} +} \ No newline at end of file diff --git a/src/imageloader.h b/src/imageloader.h index 788f7f0..8f75b70 100644 --- a/src/imageloader.h +++ b/src/imageloader.h @@ -3,6 +3,7 @@ #include "iDescriptor.h" #include +#include #include #include #include @@ -13,8 +14,6 @@ class ImageTask; -typedef struct AfcClient *AfcClientHandle; - class ImageLoader : public QObject { Q_OBJECT @@ -36,30 +35,31 @@ public: void cancelThumbnail(const QString &path); bool isLoading(const QString &path); void clear(); - QCache m_cache; - static QPixmap loadThumbnailFromDevice( + static QImage loadThumbnailFromDevice( const std::shared_ptr device, const QString &filePath, const QSize &size, std::optional> hause_arrest = std::nullopt, bool useAfc2 = false); - static QPixmap generateVideoThumbnailFFmpeg( + + static QImage generateVideoThumbnailFFmpeg( const std::shared_ptr device, const QString &filePath, const QSize &size, std::optional> hause_arrest = std::nullopt, bool useAfc2 = false); - static QPixmap loadImage(const std::shared_ptr device, - const QString &filePath, - std::optional> - hause_arrest = std::nullopt, - bool useAfc2 = false); + + static QImage loadImage(const std::shared_ptr device, + const QString &filePath, + std::optional> + hause_arrest = std::nullopt, + bool useAfc2 = false); signals: void thumbnailReady(const QString &path, const QPixmap &image, unsigned int row); private slots: - void onTaskFinished(const QString &path, const QPixmap &image, + void onTaskFinished(const QString &path, const QImage &image, unsigned int row); private: diff --git a/src/imagetask.h b/src/imagetask.h index afe1c53..85b63b8 100644 --- a/src/imagetask.h +++ b/src/imagetask.h @@ -3,14 +3,14 @@ #include "iDescriptor-ui.h" #include "iDescriptor.h" +#include "imageloader.h" +#include #include #include #include #include #include -#include "imageloader.h" - class ImageTask : public QObject, public QRunnable { Q_OBJECT @@ -27,30 +27,32 @@ public: } signals: - void finished(const QString &path, const QPixmap &image, unsigned int row); + void finished(const QString &path, const QImage &image, unsigned int row); protected: void run() override { - bool isVideo = iDescriptor::Utils::isVideoFile(m_path); + if (QCoreApplication::closingDown()) { + return; + } + + const bool isVideo = iDescriptor::Utils::isVideoFile(m_path); if (isVideo) { - QPixmap thumbnail = ImageLoader::generateVideoThumbnailFFmpeg( + QImage image = ImageLoader::generateVideoThumbnailFFmpeg( m_device, m_path, THUMBNAIL_SIZE, m_hause_arrest, m_useAfc2); + emit finished(m_path, image, m_row); + return; + } - emit finished(m_path, thumbnail, m_row); + if (m_isThumbnail) { + QImage image = ImageLoader::loadThumbnailFromDevice( + m_device, m_path, THUMBNAIL_SIZE, m_hause_arrest, m_useAfc2); + emit finished(m_path, image, m_row); } else { - if (m_isThumbnail) { - QPixmap image = ImageLoader::loadThumbnailFromDevice( - m_device, m_path, THUMBNAIL_SIZE, m_hause_arrest, - m_useAfc2); - emit finished(m_path, image, m_row); - } else { - qDebug() << "Loading full image for:" << m_path; - QPixmap image = ImageLoader::loadImage( - m_device, m_path, m_hause_arrest, m_useAfc2); - emit finished(m_path, image, m_row); - } + QImage image = ImageLoader::loadImage(m_device, m_path, + m_hause_arrest, m_useAfc2); + emit finished(m_path, image, m_row); } } diff --git a/src/photomodel.cpp b/src/photomodel.cpp index 9489e9a..2266f1f 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -38,6 +38,8 @@ PhotoModel::PhotoModel(const std::shared_ptr device, : QAbstractListModel(parent), m_device(device), m_sortOrder(NewestFirst), m_filterType(filterType) { + // 350 MB cache for thumbnails + m_cache.setMaxCost(350 * 1024 * 1024); } void PhotoModel::clear() @@ -50,13 +52,8 @@ void PhotoModel::clear() m_photos.clear(); m_allPhotos.clear(); endResetModel(); - + m_cache.clear(); qDebug() << "Cleared PhotoModel data"; - // FIXME : bug we use the same loader on every device - // FIXME: we shouldn't do this - // QHashPrivate::Span>::hasNode - // qhash.h 310 0x5555559d50e1 - // ImageLoader::sharedInstance().clear(); } PhotoModel::~PhotoModel() @@ -90,8 +87,8 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const case Qt::DecorationRole: { ImageLoader &imgloader = ImageLoader::sharedInstance(); - // Check memory cache first - if (QPixmap *cached = imgloader.m_cache.object(info.filePath)) { + // Check cache first + if (QPixmap *cached = m_cache.object(info.filePath)) { return QIcon(*cached); } @@ -126,7 +123,8 @@ void PhotoModel::onThumbnailReady(const QString &path, const QPixmap &pixmap, unsigned int rowHint) { Q_UNUSED(pixmap); - + int cacheCost = pixmap.width() * pixmap.height() * pixmap.depth() / 8; + m_cache.insert(path, new QPixmap(pixmap), cacheCost); QMutexLocker locker(&m_mutex); int row = -1; @@ -147,9 +145,7 @@ void PhotoModel::onThumbnailReady(const QString &path, const QPixmap &pixmap, if (row == -1) { // Thumbnail arrived for an item that is no longer in the model - qDebug() << "PhotoModel::onThumbnailReady: path not in current model:" - << path << "(rowHint =" << rowHint - << ", size =" << m_photos.size() << ")"; + qDebug() << "PhotoModel::onThumbnailReady: path not in current model"; return; } @@ -158,42 +154,36 @@ void PhotoModel::onThumbnailReady(const QString &path, const QPixmap &pixmap, emit dataChanged(idx, idx, {Qt::DecorationRole}); } -bool PhotoModel::populatePhotoPaths() +QPair> +PhotoModel::populatePhotoPaths(QString albumPath, + std::shared_ptr device) { - // FIXME:DEADLOCK? - // QMutexLocker locker(&m_mutex); - connect(&ImageLoader::sharedInstance(), &ImageLoader::thumbnailReady, this, - &PhotoModel::onThumbnailReady); - if (m_albumPath.isEmpty()) { + + if (albumPath.isEmpty()) { qDebug() << "No album path set, skipping population"; - return false; + return {false, {}}; } - m_allPhotos.clear(); QMap photoPaths = - m_device->afc_backend->list_dir_with_creation_date(m_albumPath); + device->afc_backend->list_dir_with_creation_date(albumPath); + QList photos; for (auto it = photoPaths.constBegin(); it != photoPaths.constEnd(); ++it) { const QString &fileName = it.key(); const QVariant &creationDateVariant = it.value(); if (iDescriptor::Utils::isGalleryFile(fileName)) { PhotoInfo info; - info.filePath = m_albumPath + "/" + fileName; + info.filePath = albumPath + "/" + fileName; info.fileName = fileName; info.thumbnailRequested = false; info.dateTime = creationDateVariant.toDateTime(); info.fileType = determineFileType(fileName); - m_allPhotos.append(info); + photos.append(info); } } - // // Apply initial filtering and sorting, which will also reset the model - applyFilterAndSort(); - - qDebug() << "Loaded" << m_allPhotos.size() << "media files from device"; - qDebug() << "After filtering:" << m_photos.size() << "items shown"; - return true; + return {true, photos}; } // Sorting and filtering methods @@ -291,40 +281,58 @@ QStringList PhotoModel::getAllFilePaths() const return paths; } -QStringList PhotoModel::getFilteredFilePaths() const +QList PhotoModel::getFilteredFilePaths() const { - QStringList paths; + QList paths; for (const PhotoInfo &info : m_photos) { paths.append(info.filePath); } return paths; } -PhotoInfo::FileType PhotoModel::determineFileType(const QString &fileName) const +PhotoInfo::FileType PhotoModel::determineFileType(const QString &fileName) { if (iDescriptor::Utils::isVideoFile(fileName)) return PhotoInfo::Video; return PhotoInfo::Image; } -int count = 0; void PhotoModel::setAlbumPath(const QString &albumPath) { qDebug() << "Setting new album path:" << albumPath; + clear(); + connect(&ImageLoader::sharedInstance(), &ImageLoader::thumbnailReady, this, + &PhotoModel::onThumbnailReady, Qt::UniqueConnection); m_albumPath = albumPath; - QFutureWatcher *futureWatcher = new QFutureWatcher(this); - QFuture future = - QtConcurrent::run([this]() { return populatePhotoPaths(); }); + + const auto device = m_device; + + auto *futureWatcher = + new QFutureWatcher>>(this); + + QFuture>> future = + QtConcurrent::run([albumPath, device]() { + return populatePhotoPaths(albumPath, device); + }); + futureWatcher->setFuture(future); - connect(futureWatcher, &QFutureWatcher::finished, this, + + connect(futureWatcher, + &QFutureWatcher>>::finished, this, [this, futureWatcher]() { + const auto result = futureWatcher->result(); futureWatcher->deleteLater(); - bool success = futureWatcher->result(); + + const bool success = result.first; + const QList photos = result.second; + + m_allPhotos = photos; if (success) { qDebug() << "Finished populating photo paths for album:" << m_albumPath; + applyFilterAndSort(); emit albumPathSet(); } else { qDebug() << "Failed to populate photo paths for album:" @@ -332,6 +340,4 @@ void PhotoModel::setAlbumPath(const QString &albumPath) emit albumPathSetFailed(); } }); -} -// TODO:REMOVE -void PhotoModel::refreshPhotos() { populatePhotoPaths(); } +} \ No newline at end of file diff --git a/src/photomodel.h b/src/photomodel.h index 93787ae..680a20f 100644 --- a/src/photomodel.h +++ b/src/photomodel.h @@ -63,7 +63,6 @@ public: // Album management void setAlbumPath(const QString &albumPath); - void refreshPhotos(); // Sorting and filtering void setSortOrder(SortOrder order); @@ -78,7 +77,7 @@ public: // Get all items for export QStringList getAllFilePaths() const; - QStringList getFilteredFilePaths() const; + QList getFilteredFilePaths() const; void clear(); @@ -93,14 +92,17 @@ private: FilterType m_filterType; QMutex m_mutex; + QCache m_cache; // Helper methods - bool populatePhotoPaths(); + static QPair> + populatePhotoPaths(QString albumPath, + std::shared_ptr device); void applyFilterAndSort(); void sortPhotos(QList &photos) const; bool matchesFilter(const PhotoInfo &info) const; - PhotoInfo::FileType determineFileType(const QString &fileName) const; + static PhotoInfo::FileType determineFileType(const QString &fileName); private slots: void onThumbnailReady(const QString &path, const QPixmap &pixmap,