File indexing completed on 2025-01-19 03:53:38

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2007-05-01
0007  * Description : ItemInfo common data
0008  *
0009  * SPDX-FileCopyrightText: 2007-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2014-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText:      2013 by Michael G. Hansen <mike at mghansen dot de>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "iteminfocache.h"
0018 
0019 // Local includes
0020 
0021 #include "coredb.h"
0022 #include "coredbalbuminfo.h"
0023 #include "iteminfo.h"
0024 #include "iteminfolist.h"
0025 #include "iteminfodata.h"
0026 #include "digikam_debug.h"
0027 
0028 namespace Digikam
0029 {
0030 
0031 ItemInfoCache::ItemInfoCache()
0032     : m_needUpdateAlbums(true),
0033       m_needUpdateGrouped(true)
0034 {
0035     qRegisterMetaType<ItemInfo>("ItemInfo");
0036     qRegisterMetaType<ItemInfoList>("ItemInfoList");
0037     qRegisterMetaType<QList<ItemInfo> >("QList<ItemInfo>");
0038 
0039     CoreDbWatch* const dbwatch = CoreDbAccess::databaseWatch();
0040 
0041     connect(dbwatch, SIGNAL(imageChange(ImageChangeset)),
0042             this, SLOT(slotImageChanged(ImageChangeset)),
0043             Qt::DirectConnection);
0044 
0045     connect(dbwatch, SIGNAL(imageTagChange(ImageTagChangeset)),
0046             this, SLOT(slotImageTagChanged(ImageTagChangeset)),
0047             Qt::DirectConnection);
0048 
0049     connect(dbwatch, SIGNAL(albumChange(AlbumChangeset)),
0050             this, SLOT(slotAlbumChange(AlbumChangeset)),
0051             Qt::DirectConnection);
0052 }
0053 
0054 ItemInfoCache::~ItemInfoCache()
0055 {
0056 }
0057 
0058 static bool lessThanForAlbumShortInfo(const AlbumShortInfo& first, const AlbumShortInfo& second)
0059 {
0060     return first.id < second.id;
0061 }
0062 
0063 void ItemInfoCache::checkAlbums()
0064 {
0065     if (m_needUpdateAlbums)
0066     {
0067         // list comes sorted from db
0068 
0069         QList<AlbumShortInfo> infos = CoreDbAccess().db()->getAlbumShortInfos();
0070 
0071         ItemInfoWriteLocker lock;
0072         m_albums                    = infos;
0073         m_needUpdateAlbums          = false;
0074     }
0075 }
0076 
0077 int ItemInfoCache::getImageGroupedCount(qlonglong id)
0078 {
0079     if (m_needUpdateGrouped)
0080     {
0081         QList<qlonglong> ids = CoreDbAccess().db()->getRelatedImagesToByType(DatabaseRelation::Grouped);
0082 
0083         ItemInfoWriteLocker lock;
0084         m_grouped            = ids;
0085         m_needUpdateGrouped  = false;
0086     }
0087 
0088     ItemInfoReadLocker lock;
0089     return m_grouped.count(id);
0090 }
0091 
0092 QExplicitlySharedDataPointer<ItemInfoData> ItemInfoCache::infoForId(qlonglong id)
0093 {
0094     {
0095         ItemInfoReadLocker lock;
0096         QExplicitlySharedDataPointer<ItemInfoData> ptr(m_infoHash.value(id));
0097 
0098         if (ptr)
0099         {
0100             return ptr;
0101         }
0102     }
0103 
0104     ItemInfoWriteLocker lock;
0105     ItemInfoData* const data = new ItemInfoData();
0106     data->id                 = id;
0107     m_infoHash[id]           = data;
0108 
0109     return QExplicitlySharedDataPointer<ItemInfoData>(data);
0110 }
0111 
0112 void ItemInfoCache::cacheByName(const QExplicitlySharedDataPointer<ItemInfoData>& infoPtr)
0113 {
0114     // Called with Write lock
0115 
0116     if (!infoPtr || (infoPtr->id == -1) || infoPtr->name.isEmpty())
0117     {
0118         return;
0119     }
0120 
0121     // Called in a context where we can assume that the entry is not yet cached by name (newly created data)
0122 
0123     m_nameHash.remove(m_dataHash.value(infoPtr->id), infoPtr);
0124     m_dataHash.insert(infoPtr->id, infoPtr->name);
0125     m_nameHash.insert(infoPtr->name, infoPtr);
0126 }
0127 
0128 QExplicitlySharedDataPointer<ItemInfoData> ItemInfoCache::infoForPath(int albumRootId,
0129                                                                       const QString& relativePath, const QString& name)
0130 {
0131     ItemInfoReadLocker lock;
0132 
0133     // We check all entries in the multi hash with matching file name
0134 
0135     QMultiHash<QString, QExplicitlySharedDataPointer<ItemInfoData> >::const_iterator it;
0136 
0137     for (it = m_nameHash.constFind(name) ; (it != m_nameHash.constEnd()) && (it.key() == name) ; ++it)
0138     {
0139         // first check that album root matches
0140 
0141         if (it.value()->albumRootId != albumRootId)
0142         {
0143             continue;
0144         }
0145 
0146         // check that relativePath matches. We get relativePath from entry's id and compare to given name.
0147 
0148         QList<AlbumShortInfo>::const_iterator albumIt = findAlbum(it.value()->albumId);
0149 
0150         if ((albumIt == m_albums.constEnd()) || (albumIt->relativePath != relativePath))
0151         {
0152             continue;
0153         }
0154 
0155         // we have now a match by name, albumRootId and relativePath
0156 
0157         return it.value();
0158     }
0159 
0160     return QExplicitlySharedDataPointer<ItemInfoData>();
0161 }
0162 
0163 void ItemInfoCache::dropInfo(const QExplicitlySharedDataPointer<ItemInfoData>& infoPtr)
0164 {
0165     if (!infoPtr)
0166     {
0167         return;
0168     }
0169 
0170     ItemInfoWriteLocker lock;
0171 
0172     // When we have the last ItemInfoData, the reference counter is at 3.
0173     // Because 2 QExplicitlySharedDataPointers are in cache and 1 is held by m_data.
0174 
0175     if (infoPtr.data()->ref > 3)
0176     {
0177         return;
0178     }
0179 
0180     m_nameHash.remove(m_dataHash.value(infoPtr->id), infoPtr);
0181     m_nameHash.remove(infoPtr->name, infoPtr);
0182     m_infoHash.remove(infoPtr->id);
0183     m_dataHash.remove(infoPtr->id);
0184 }
0185 
0186 QList<AlbumShortInfo>::const_iterator ItemInfoCache::findAlbum(int id)
0187 {
0188     // Called with read lock
0189 
0190     AlbumShortInfo info;
0191     info.id = id;
0192 
0193     // we use the fact that d->infos is sorted by id
0194 
0195     QList<AlbumShortInfo>::const_iterator it;
0196     it = std::lower_bound(m_albums.constBegin(),
0197                           m_albums.constEnd(), info,
0198                           lessThanForAlbumShortInfo);
0199 
0200     if ((it == m_albums.constEnd()) || (info.id < (*it).id))
0201     {
0202         return m_albums.constEnd();
0203     }
0204 
0205     return it;
0206 }
0207 
0208 QString ItemInfoCache::albumRelativePath(int albumId)
0209 {
0210     checkAlbums();
0211     ItemInfoReadLocker lock;
0212     QList<AlbumShortInfo>::const_iterator it = findAlbum(albumId);
0213 
0214     if (it != m_albums.constEnd())
0215     {
0216         return it->relativePath;
0217     }
0218 
0219     return QString();
0220 }
0221 
0222 void ItemInfoCache::invalidate()
0223 {
0224     ItemInfoWriteLocker lock;
0225     QHash<qlonglong, QExplicitlySharedDataPointer<ItemInfoData> >::iterator it;
0226 
0227     for (it = m_infoHash.begin() ; it != m_infoHash.end() ; ++it)
0228     {
0229         (*it)->invalid = true;
0230         (*it)->id      = -1;
0231     }
0232 
0233     m_albums.clear();
0234     m_grouped.clear();
0235     m_nameHash.clear();
0236     m_infoHash.clear();
0237     m_dataHash.clear();
0238     m_needUpdateAlbums  = true;
0239     m_needUpdateGrouped = true;
0240 }
0241 
0242 void ItemInfoCache::slotImageChanged(const ImageChangeset& changeset)
0243 {
0244     ItemInfoWriteLocker lock;
0245 
0246     Q_FOREACH (const qlonglong& imageId, changeset.ids())
0247     {
0248         QHash<qlonglong, QExplicitlySharedDataPointer<ItemInfoData> >::iterator it = m_infoHash.find(imageId);
0249 
0250         if (it != m_infoHash.end())
0251         {
0252             // invalidate the relevant field. It will be lazy-loaded at first access.
0253 
0254             DatabaseFields::Set changes = changeset.changes();
0255 
0256             if (changes & DatabaseFields::ItemCommentsAll)
0257             {
0258                 (*it)->defaultCommentCached = false;
0259                 (*it)->defaultTitleCached   = false;
0260             }
0261 
0262             if (changes & DatabaseFields::Category)
0263             {
0264                 (*it)->categoryCached = false;
0265             }
0266 
0267             if (changes & DatabaseFields::Format)
0268             {
0269                 (*it)->formatCached = false;
0270             }
0271 
0272             if (changes & DatabaseFields::PickLabel)
0273             {
0274                 (*it)->pickLabelCached = false;
0275             }
0276 
0277             if (changes & DatabaseFields::ColorLabel)
0278             {
0279                 (*it)->colorLabelCached = false;
0280             }
0281 
0282             if (changes & DatabaseFields::Rating)
0283             {
0284                 (*it)->ratingCached = false;
0285             }
0286 
0287             if (changes & DatabaseFields::CreationDate)
0288             {
0289                 (*it)->creationDateCached = false;
0290             }
0291 
0292             if (changes & DatabaseFields::ModificationDate)
0293             {
0294                 (*it)->modificationDateCached = false;
0295             }
0296 
0297             if (changes & DatabaseFields::Orientation)
0298             {
0299                 (*it)->orientationCached = false;
0300             }
0301 
0302             if (changes & DatabaseFields::FileSize)
0303             {
0304                 (*it)->fileSizeCached = false;
0305             }
0306 
0307             if (changes & DatabaseFields::UniqueHash)
0308             {
0309                 (*it)->uniqueHashCached = false;
0310             }
0311 
0312             if (changes & DatabaseFields::ManualOrder)
0313             {
0314                 (*it)->manualOrderCached = false;
0315             }
0316 
0317             if ((changes & DatabaseFields::Width) || (changes & DatabaseFields::Height))
0318             {
0319                 (*it)->imageSizeCached = false;
0320             }
0321 
0322             if (changes & DatabaseFields::LatitudeNumber  ||
0323                 changes & DatabaseFields::LongitudeNumber ||
0324                 changes & DatabaseFields::Altitude)
0325             {
0326                 (*it)->positionsCached = false;
0327             }
0328 
0329             if (changes & DatabaseFields::ImageRelations)
0330             {
0331                 (*it)->groupImageCached = false;
0332                 m_needUpdateGrouped     = true;
0333             }
0334 
0335             if (changes.hasFieldsFromVideoMetadata())
0336             {
0337                 const DatabaseFields::VideoMetadata changedVideoMetadata = changes.getVideoMetadata();
0338                 (*it)->videoMetadataCached                              &= ~changedVideoMetadata;
0339                 (*it)->hasVideoMetadata                                  = true;
0340 
0341                 (*it)->databaseFieldsHashRaw.removeAllFields(changedVideoMetadata);
0342             }
0343 
0344             if (changes.hasFieldsFromImageMetadata())
0345             {
0346                 const DatabaseFields::ImageMetadata changedImageMetadata = changes.getImageMetadata();
0347                 (*it)->imageMetadataCached                              &= ~changedImageMetadata;
0348                 (*it)->hasImageMetadata                                  = true;
0349 
0350                 (*it)->databaseFieldsHashRaw.removeAllFields(changedImageMetadata);
0351             }
0352         }
0353         else
0354         {
0355             m_needUpdateGrouped = true;
0356         }
0357     }
0358 }
0359 
0360 void ItemInfoCache::slotImageTagChanged(const ImageTagChangeset& changeset)
0361 {
0362     if (changeset.propertiesWereChanged())
0363     {
0364         ItemInfoWriteLocker lock;
0365 
0366         Q_FOREACH (const qlonglong& imageId, changeset.ids())
0367         {
0368             QHash<qlonglong, QExplicitlySharedDataPointer<ItemInfoData> >::iterator it = m_infoHash.find(imageId);
0369 
0370             if (it != m_infoHash.end())
0371             {
0372                 (*it)->faceCountCached            = false;
0373                 (*it)->faceSuggestionsCached      = false;
0374                 (*it)->unconfirmedFaceCountCached = false;
0375             }
0376         }
0377 
0378         return;
0379     }
0380 
0381     ItemInfoWriteLocker lock;
0382 
0383     Q_FOREACH (const qlonglong& imageId, changeset.ids())
0384     {
0385         QHash<qlonglong, QExplicitlySharedDataPointer<ItemInfoData> >::iterator it = m_infoHash.find(imageId);
0386 
0387         if (it != m_infoHash.end())
0388         {
0389             (*it)->tagIdsCached     = false;
0390             (*it)->colorLabelCached = false;
0391             (*it)->pickLabelCached  = false;
0392         }
0393     }
0394 }
0395 
0396 void ItemInfoCache::slotAlbumChange(const AlbumChangeset& changeset)
0397 {
0398     switch (changeset.operation())
0399     {
0400         case AlbumChangeset::Added:
0401         case AlbumChangeset::Deleted:
0402         case AlbumChangeset::Renamed:
0403         case AlbumChangeset::PropertiesChanged:
0404             m_needUpdateAlbums = true;
0405             break;
0406 
0407         case AlbumChangeset::Unknown:
0408             break;
0409     }
0410 }
0411 
0412 } // namespace Digikam
0413 
0414 #include "moc_iteminfocache.cpp"