[graphics/digikam] core/utilities/fuzzysearch: Fix crash in find duplicates albumm item class while running multithreaded method to update properties from the database to the list view.
Gilles Caulier <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 6cd4b106e33b2fa9e32b69e06fb553975e6714d3 by Gilles Caulier.
Committed on 23/07/2026 at 14:11.
Pushed by cgilles into branch 'master'.
Fix crash in find duplicates albumm item class while running multithreaded method to update properties from the database to the list view.
Never call QTreeWidgetItem method from a separated thread as the class is not reentrant (GUI). Use signals/Slots mechanism to forward database information from the separated thread to the GUI.
BUGS: 514721
FIXED-IN: 8.2.0
M +85 -50 core/utilities/fuzzysearch/findduplicatesalbumitem.cpp
M +15 -6 core/utilities/fuzzysearch/findduplicatesalbumitem.h
https://invent.kde.org/graphics/digikam/-/commit/6cd4b106e33b2fa9e32b69e06fb553975e6714d3
diff --git a/core/utilities/fuzzysearch/findduplicatesalbumitem.cpp b/core/utilities/fuzzysearch/findduplicatesalbumitem.cpp
index 2f6bed67df..dac066f60e 100644
--- a/core/utilities/fuzzysearch/findduplicatesalbumitem.cpp
+++ b/core/utilities/fuzzysearch/findduplicatesalbumitem.cpp
@@ -19,6 +19,7 @@
#include <QtConcurrentRun>
#include <QPainter>
#include <QIcon>
+#include <QMutexLocker>
// Local includes
@@ -42,22 +43,38 @@ public:
public:
- bool hasThumb = false;
+ bool hasThumb = false;
+ SAlbum* album = nullptr;
+ int itemCount = 0;
- SAlbum* album = nullptr;
- int itemCount = 0;
-
- ItemInfo refImgInfo;
-
- QFuture<void> calcTask;
+ ItemInfo refImgInfo;
+ QFuture<void> calcTask;
+ mutable QMutex mutex; ///< Mutex to protect access to shared data
};
FindDuplicatesAlbumItem::FindDuplicatesAlbumItem(QTreeWidget* const parent, SAlbum* const album)
- : QTreeWidgetItem(parent),
+ : QObject (parent),
+ QTreeWidgetItem(parent),
d (new Private)
{
d->album = album;
+ // Connect signals for UI updates
+
+ connect(this, &FindDuplicatesAlbumItem::signalUpdateItemText,
+ this, [this](int column, const QString& text)
+ {
+ setText(column, text);
+ }
+ );
+
+ connect(this, &FindDuplicatesAlbumItem::signalSetItemHidden,
+ this, [this](bool hidden)
+ {
+ setHidden(hidden);
+ }
+ );
+
if (d->album)
{
qlonglong refImage = d->album->title().toLongLong();
@@ -81,6 +98,11 @@ FindDuplicatesAlbumItem::FindDuplicatesAlbumItem(QTreeWidget* const parent, SAlb
FindDuplicatesAlbumItem::~FindDuplicatesAlbumItem()
{
+ // Cancel the task if it is running
+
+ d->calcTask.cancel();
+ d->calcTask.waitForFinished();
+
delete d;
}
@@ -123,17 +145,17 @@ void FindDuplicatesAlbumItem::calculateInfos(const QList<qlonglong>& deletedImag
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
- &FindDuplicatesAlbumItem::calculateInfosMultithreaded, this,
- deletedImages
+ &FindDuplicatesAlbumItem::calculateInfosMultithreaded, this,
+ deletedImages
#else
- this, &FindDuplicatesAlbumItem::calculateInfosMultithreaded,
- deletedImages
+ this, &FindDuplicatesAlbumItem::calculateInfosMultithreaded,
+ deletedImages
#endif
- );
+ );
}
void FindDuplicatesAlbumItem::waitForCalculate()
@@ -143,6 +165,8 @@ void FindDuplicatesAlbumItem::waitForCalculate()
int FindDuplicatesAlbumItem::itemCount() const
{
+ QMutexLocker locker(&d->mutex);
+
return d->itemCount;
}
@@ -173,11 +197,15 @@ void FindDuplicatesAlbumItem::setThumb(const QPixmap& pix, bool hasThumb)
SAlbum* FindDuplicatesAlbumItem::album() const
{
+ QMutexLocker locker(&d->mutex);
+
return d->album;
}
QUrl FindDuplicatesAlbumItem::refUrl() const
{
+ QMutexLocker locker(&d->mutex);
+
return d->refImgInfo.fileUrl();
}
@@ -207,61 +235,68 @@ bool FindDuplicatesAlbumItem::operator<(const QTreeWidgetItem& other) const
void FindDuplicatesAlbumItem::calculateInfosMultithreaded(const QList<qlonglong>& deletedImages)
{
ActionThreadBase::setCurrentThreadName(QLatin1String(__FUNCTION__)); // To customize thread name
+ double avgSim = 0.0;
- if (!d->album)
+ QMutexLocker locker(&d->mutex);
{
- return;
- }
+ if (!d->album || d->calcTask.isCanceled())
+ {
+ return;
+ }
- qlonglong refImage = d->album->title().toLongLong();
- /*
- q CDebug(DIGIKAM_GENERAL_LOG) << "Calculating info for album" << refIma*ge;
- */
- SearchXmlReader reader(d->album->query());
- reader.readToFirstField();
+ qlonglong refImage = d->album->title().toLongLong();
+ SearchXmlReader reader(d->album->query());
+ reader.readToFirstField();
- // Get the defined image ids.
+ // Get the defined image ids.
- const QList<qlonglong>& list = reader.valueToLongLongList();
+ const QList<qlonglong>& list = reader.valueToLongLongList();
- // Only images that are not removed/obsolete should be shown.
+ // Only images that are not removed/obsolete should be shown.
- QList<qlonglong> filteredList;
- double avgSim = 0.0;
+ QList<qlonglong> filteredList;
- for (const qlonglong& imageId : std::as_const(list))
- {
- ItemInfo info(imageId);
+ for (const qlonglong& imageId : std::as_const(list))
+ {
+ if (d->calcTask.isCanceled())
+ {
+ return;
+ }
- // If image is not deleted in this moment and was also not
- // removed before.
+ ItemInfo info(imageId);
- if (!deletedImages.contains(imageId) && !info.isRemoved())
- {
- filteredList << imageId;
+ // If image is not deleted in this moment and was also not removed before.
- if (imageId != refImage)
+ if (!deletedImages.contains(imageId) && !info.isRemoved())
{
- avgSim += info.similarityTo(refImage);
+ filteredList << imageId;
+
+ if (imageId != refImage)
+ {
+ avgSim += info.similarityTo(refImage);
+ }
}
}
- }
- d->itemCount = filteredList.count();
- /*
- q CDebug(DIGIKAM_GENERAL_LOG) << "New Item count:" << d->itemCount; *
- */
- if (d->itemCount > 1)
- {
- avgSim /= d->itemCount - (filteredList.contains(refImage) ? 1 : 0);
+ d->itemCount = filteredList.count();
+
+ if (d->itemCount > 1)
+ {
+ avgSim /= d->itemCount - (filteredList.contains(refImage) ? 1 : 0);
+ }
}
- else
+
+ // Emit signals to update UI in the main thread
+
+ Q_EMIT signalUpdateItemText(Column::RESULT_COUNT, QString::number(d->itemCount));
+ Q_EMIT signalUpdateItemText(Column::AVG_SIMILARITY, QString::number(static_cast<int>(avgSim * 100)));
+
+ if (d->itemCount <= 1)
{
- this->setHidden(true);
+ Q_EMIT signalSetItemHidden(true);
}
-
- setText(Column::RESULT_COUNT, QString::number(d->itemCount));
- setText(Column::AVG_SIMILARITY, QString::number((int)(avgSim * 100)));
}
} // namespace Digikam
+
+#include "moc_findduplicatesalbumitem.cpp"
diff --git a/core/utilities/fuzzysearch/findduplicatesalbumitem.h b/core/utilities/fuzzysearch/findduplicatesalbumitem.h
index 006899dd9a..e41ab208cb 100644
--- a/core/utilities/fuzzysearch/findduplicatesalbumitem.h
+++ b/core/utilities/fuzzysearch/findduplicatesalbumitem.h
@@ -19,6 +19,8 @@
#include <QTreeWidget>
#include <QUrl>
#include <QList>
+#include <QMutex>
+#include <QFuture>
// Local includes
@@ -28,8 +30,10 @@
namespace Digikam
{
-class DIGIKAM_GUI_EXPORT FindDuplicatesAlbumItem : public QTreeWidgetItem
+class DIGIKAM_GUI_EXPORT FindDuplicatesAlbumItem : public QObject,
+ public QTreeWidgetItem
{
+ Q_OBJECT
public:
@@ -41,7 +45,7 @@ public:
RESULT_COUNT,
AVG_SIMILARITY,
- NUMBER_COLUMNS // Must be the last one.
+ NUMBER_COLUMNS ///< Must be the last one.
};
public:
@@ -69,12 +73,17 @@ public:
SAlbum* album() const;
QUrl refUrl() const;
- void setThumb(const QPixmap& pix,
- bool hasThumb = true);
-
bool operator<(const QTreeWidgetItem& other) const override;
+
QList<ItemInfo> duplicatedItems();
+ void setThumb(const QPixmap& pix, bool hasThumb = true);
+
+Q_SIGNALS:
+
+ void signalUpdateItemText(int column, const QString& text);
+ void signalSetItemHidden(bool hidden);
+
private:
void calculateInfosMultithreaded(const QList<qlonglong>& deletedImages);
@@ -82,7 +91,7 @@ private:
private:
class Private;
- Private* const d = nullptr;
+ Private* const d;
private: