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"