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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2004-06-15
0007  * Description : Albums manager interface.
0008  *
0009  * SPDX-FileCopyrightText: 2004      by Renchi Raju <renchi dot raju at gmail dot com>
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0012  * SPDX-FileCopyrightText: 2015      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "albummanager_p.h"
0019 
0020 namespace Digikam
0021 {
0022 
0023 Q_GLOBAL_STATIC(AlbumManagerCreator, creator)
0024 
0025 /**
0026  * A friend-class shortcut to circumvent accessing this from within the destructor
0027  */
0028 AlbumManager* AlbumManager::internalInstance = nullptr;
0029 
0030 AlbumManager* AlbumManager::instance()
0031 {
0032     return &creator->object;
0033 }
0034 
0035 // -----------------------------------------------------------------------------------
0036 
0037 AlbumManager::AlbumManager()
0038     : d(new Private)
0039 {
0040     qRegisterMetaType<QHash<QDateTime,int>>("QHash<QDateTime,int>");
0041     qRegisterMetaType<QMap<int,int>>("QMap<int,int>");
0042     qRegisterMetaType<QHash<int,int>>("QHash<int,int>");
0043     qRegisterMetaType<QMap<QString,QHash<int,int> >>("QMap<QString,QHash<int,int> >");
0044 
0045     internalInstance = this;
0046     d->albumWatch    = new AlbumWatch(this);
0047 
0048     // these operations are pretty fast, no need for long queuing
0049 
0050     d->scanPAlbumsTimer = new QTimer(this);
0051     d->scanPAlbumsTimer->setInterval(150);
0052     d->scanPAlbumsTimer->setSingleShot(true);
0053 
0054     connect(d->scanPAlbumsTimer, SIGNAL(timeout()),
0055             this, SLOT(scanPAlbums()));
0056 
0057     d->scanTAlbumsTimer = new QTimer(this);
0058     d->scanTAlbumsTimer->setInterval(150);
0059     d->scanTAlbumsTimer->setSingleShot(true);
0060 
0061     connect(d->scanTAlbumsTimer, SIGNAL(timeout()),
0062             this, SLOT(scanTAlbums()));
0063 
0064     d->scanSAlbumsTimer = new QTimer(this);
0065     d->scanSAlbumsTimer->setInterval(150);
0066     d->scanSAlbumsTimer->setSingleShot(true);
0067 
0068     connect(d->scanSAlbumsTimer, SIGNAL(timeout()),
0069             this, SLOT(scanSAlbums()));
0070 
0071     d->updatePAlbumsTimer = new QTimer(this);
0072     d->updatePAlbumsTimer->setInterval(150);
0073     d->updatePAlbumsTimer->setSingleShot(true);
0074 
0075     connect(d->updatePAlbumsTimer, SIGNAL(timeout()),
0076             this, SLOT(updateChangedPAlbums()));
0077 
0078     // this operation is much more expensive than the other scan methods
0079 
0080     d->scanDAlbumsTimer = new QTimer(this);
0081     d->scanDAlbumsTimer->setInterval(30 * 1000);
0082     d->scanDAlbumsTimer->setSingleShot(true);
0083 
0084     connect(d->scanDAlbumsTimer, SIGNAL(timeout()),
0085             this, SLOT(scanDAlbumsScheduled()));
0086 
0087     // moderately expensive
0088 
0089     d->albumItemCountTimer = new QTimer(this);
0090     d->albumItemCountTimer->setInterval(1000);
0091     d->albumItemCountTimer->setSingleShot(true);
0092 
0093     connect(d->albumItemCountTimer, SIGNAL(timeout()),
0094             this, SLOT(getAlbumItemsCount()));
0095 
0096     // more expensive
0097 
0098     d->tagItemCountTimer = new QTimer(this);
0099     d->tagItemCountTimer->setInterval(2500);
0100     d->tagItemCountTimer->setSingleShot(true);
0101 
0102     connect(d->tagItemCountTimer, SIGNAL(timeout()),
0103             this, SLOT(getTagItemsCount()));
0104 }
0105 
0106 AlbumManager::~AlbumManager()
0107 {
0108     delete d->rootPAlbum;
0109     delete d->rootTAlbum;
0110     delete d->rootDAlbum;
0111     delete d->rootSAlbum;
0112 
0113     internalInstance = nullptr;
0114     delete d;
0115 }
0116 
0117 void AlbumManager::cleanUp()
0118 {
0119     // This is what we prefer to do before Application destruction
0120 
0121     if (d->dateListJob)
0122     {
0123         disconnect(d->dateListJob, nullptr, this, nullptr);
0124 
0125         d->dateListJob->cancel();
0126         d->dateListJob = nullptr;
0127     }
0128 
0129     if (d->albumListJob)
0130     {
0131         disconnect(d->albumListJob, nullptr, this, nullptr);
0132 
0133         d->albumListJob->cancel();
0134         d->albumListJob = nullptr;
0135     }
0136 
0137     if (d->tagListJob)
0138     {
0139         disconnect(d->tagListJob, nullptr, this, nullptr);
0140 
0141         d->tagListJob->cancel();
0142         d->tagListJob = nullptr;
0143     }
0144 
0145     if (d->personListJob)
0146     {
0147         disconnect(d->personListJob, nullptr, this, nullptr);
0148 
0149         d->personListJob->cancel();
0150         d->personListJob = nullptr;
0151     }
0152 
0153     d->scanPAlbumsTimer->stop();
0154     d->scanTAlbumsTimer->stop();
0155     d->scanSAlbumsTimer->stop();
0156     d->scanDAlbumsTimer->stop();
0157     d->tagItemCountTimer->stop();
0158     d->updatePAlbumsTimer->stop();
0159     d->albumItemCountTimer->stop();
0160 }
0161 
0162 void AlbumManager::startScan()
0163 {
0164     if (!d->changed)
0165     {
0166         return;
0167     }
0168 
0169     d->changed = false;
0170 
0171     // create root albums
0172 
0173     d->rootPAlbum = new PAlbum(i18n("Albums"));
0174     insertPAlbum(d->rootPAlbum, nullptr);
0175 
0176     d->rootTAlbum = new TAlbum(i18n("Tags"), 0, true);
0177     insertTAlbum(d->rootTAlbum, nullptr);
0178 
0179     d->rootSAlbum = new SAlbum(i18n("Searches"), 0, true);
0180     Q_EMIT signalAlbumAboutToBeAdded(d->rootSAlbum, nullptr, nullptr);
0181     d->allAlbumsIdHash[d->rootSAlbum->globalID()] = d->rootSAlbum;
0182     Q_EMIT signalAlbumAdded(d->rootSAlbum);
0183 
0184     d->rootDAlbum = new DAlbum(QDate(), true);
0185     Q_EMIT signalAlbumAboutToBeAdded(d->rootDAlbum, nullptr, nullptr);
0186     d->allAlbumsIdHash[d->rootDAlbum->globalID()] = d->rootDAlbum;
0187     Q_EMIT signalAlbumAdded(d->rootDAlbum);
0188 
0189     // Create albums for album roots. Reuse logic implemented in the method
0190 
0191     Q_FOREACH (const CollectionLocation& location, CollectionManager::instance()->allLocations())
0192     {
0193         handleCollectionStatusChange(location, CollectionLocation::LocationNull);
0194     }
0195 
0196     // listen to location status changes
0197 
0198     connect(CollectionManager::instance(), SIGNAL(locationStatusChanged(CollectionLocation,int)),
0199             this, SLOT(slotCollectionLocationStatusChanged(CollectionLocation,int)));
0200 
0201     connect(CollectionManager::instance(), SIGNAL(locationPropertiesChanged(CollectionLocation)),
0202             this, SLOT(slotCollectionLocationPropertiesChanged(CollectionLocation)));
0203 
0204     // reload albums
0205 
0206     refresh();
0207 
0208     // listen to album database changes
0209 
0210     connect(CoreDbAccess::databaseWatch(), SIGNAL(albumChange(AlbumChangeset)),
0211             this, SLOT(slotAlbumChange(AlbumChangeset)));
0212 
0213     connect(CoreDbAccess::databaseWatch(), SIGNAL(tagChange(TagChangeset)),
0214             this, SLOT(slotTagChange(TagChangeset)));
0215 
0216     connect(CoreDbAccess::databaseWatch(), SIGNAL(searchChange(SearchChangeset)),
0217             this, SLOT(slotSearchChange(SearchChangeset)));
0218 
0219     // listen to collection image changes
0220 
0221     connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)),
0222             this, SLOT(slotCollectionImageChange(CollectionImageChangeset)));
0223 
0224     connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)),
0225             this, SLOT(slotImageTagChange(ImageTagChangeset)));
0226 
0227     // listen to image attribute changes
0228 
0229     connect(ItemAttributesWatch::instance(), SIGNAL(signalImageDateChanged(qlonglong)),
0230             d->scanDAlbumsTimer, SLOT(start()));
0231 
0232     Q_EMIT signalAllAlbumsLoaded();
0233 }
0234 
0235 bool AlbumManager::isShowingOnlyAvailableAlbums() const
0236 {
0237     return d->showOnlyAvailableAlbums;
0238 }
0239 
0240 void AlbumManager::setShowOnlyAvailableAlbums(bool onlyAvailable)
0241 {
0242     if (d->showOnlyAvailableAlbums == onlyAvailable)
0243     {
0244         return;
0245     }
0246 
0247     d->showOnlyAvailableAlbums = onlyAvailable;
0248 
0249     Q_EMIT signalShowOnlyAvailableAlbumsChanged(d->showOnlyAvailableAlbums);
0250 
0251     // We need to update the unavailable locations.
0252     // We assume the handleCollectionStatusChange does the right thing (even though old status == current status)
0253 
0254     Q_FOREACH (const CollectionLocation& location, CollectionManager::instance()->allLocations())
0255     {
0256         if (location.status() == CollectionLocation::LocationUnavailable)
0257         {
0258             handleCollectionStatusChange(location, CollectionLocation::LocationUnavailable);
0259         }
0260     }
0261 }
0262 
0263 void AlbumManager::refresh()
0264 {
0265     scanPAlbums();
0266     scanTAlbums();
0267     scanSAlbums();
0268     scanDAlbums();
0269 }
0270 
0271 void AlbumManager::prepareItemCounts()
0272 {
0273     // There is no way to find out if any data we had collected
0274     // previously is still valid - recompute
0275 
0276     scanDAlbums();
0277     getAlbumItemsCount();
0278     getTagItemsCount();
0279 }
0280 
0281 void AlbumManager::slotImagesDeleted(const QList<qlonglong>& imageIds)
0282 {
0283     qCDebug(DIGIKAM_GENERAL_LOG) << "Got image deletion notification from ItemViewUtilities for " << imageIds.size() << " images.";
0284 
0285     QSet<SAlbum*> sAlbumsToUpdate;
0286     QSet<qlonglong> deletedImages(imageIds.begin(), imageIds.end());
0287 
0288     QList<SAlbum*> sAlbums = findSAlbumsBySearchType(DatabaseSearch::DuplicatesSearch);
0289 
0290     Q_FOREACH (SAlbum* const sAlbum, sAlbums)
0291     {
0292         // Read the search query XML and save the image ids
0293 
0294         SearchXmlReader reader(sAlbum->query());
0295         SearchXml::Element element;
0296         QSet<qlonglong> images;
0297 
0298         while ((element = reader.readNext()) != SearchXml::End)
0299         {
0300             if ((element == SearchXml::Field) && (reader.fieldName().compare(QLatin1String("imageid")) == 0))
0301             {
0302                 const auto list = reader.valueToLongLongList();
0303                 images = QSet<qlonglong>(list.begin(), list.end());
0304             }
0305         }
0306 
0307         // If the deleted images are part of the SAlbum,
0308         // mark the album as ready for deletion and the images as ready for rescan.
0309 
0310         if (images.intersects(deletedImages))
0311         {
0312             sAlbumsToUpdate.insert(sAlbum);
0313         }
0314     }
0315 
0316     if (!sAlbumsToUpdate.isEmpty())
0317     {
0318         Q_EMIT signalUpdateDuplicatesAlbums(sAlbumsToUpdate.values(), deletedImages.values());
0319     }
0320 }
0321 
0322 } // namespace Digikam
0323 
0324 #include "moc_albummanager.cpp"