File indexing completed on 2025-01-19 03:53:20
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 - Physical Album helpers. 0008 * 0009 * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0011 * SPDX-FileCopyrightText: 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "albummanager_p.h" 0018 0019 namespace Digikam 0020 { 0021 0022 void AlbumManager::scanPAlbums() 0023 { 0024 d->scanPAlbumsTimer->stop(); 0025 0026 // first insert all the current normal PAlbums into a map for quick lookup 0027 0028 QHash<int, PAlbum*> oldAlbums; 0029 AlbumIterator it(d->rootPAlbum); 0030 0031 while (it.current()) 0032 { 0033 PAlbum* const a = (PAlbum*)(*it); 0034 oldAlbums[a->id()] = a; 0035 ++it; 0036 } 0037 0038 // scan db and get a list of all albums 0039 0040 QList<AlbumInfo> currentAlbums = CoreDbAccess().db()->scanAlbums(); 0041 0042 // sort by relative path so that parents are created before children 0043 0044 std::sort(currentAlbums.begin(), currentAlbums.end()); 0045 0046 QList<AlbumInfo> newAlbums; 0047 0048 // go through all the Albums and see which ones are already present 0049 0050 Q_FOREACH (const AlbumInfo& info, currentAlbums) 0051 { 0052 // check that location of album is available 0053 0054 if (d->showOnlyAvailableAlbums && 0055 !CollectionManager::instance()->locationForAlbumRootId(info.albumRootId).isAvailable()) 0056 { 0057 continue; 0058 } 0059 0060 if (oldAlbums.contains(info.id)) 0061 { 0062 oldAlbums.remove(info.id); 0063 } 0064 else 0065 { 0066 newAlbums << info; 0067 } 0068 } 0069 0070 // now oldAlbums contains all the deleted albums and 0071 // newAlbums contains all the new albums 0072 0073 // delete old albums, informing all frontends 0074 0075 // The albums have to be removed with children being removed first, 0076 // removePAlbum takes care of that. 0077 // So we only feed it the albums from oldAlbums topmost in hierarchy. 0078 0079 QSet<PAlbum*> topMostOldAlbums; 0080 0081 Q_FOREACH (PAlbum* const album, oldAlbums) 0082 { 0083 if (album->isTrashAlbum()) 0084 { 0085 continue; 0086 } 0087 0088 if (!album->parent() || !oldAlbums.contains(album->parent()->id())) 0089 { 0090 topMostOldAlbums << album; 0091 } 0092 } 0093 0094 Q_FOREACH (PAlbum* const album, topMostOldAlbums) 0095 { 0096 // recursively removes all children and the album 0097 0098 removePAlbum(album); 0099 } 0100 0101 // sort by relative path so that parents are created before children 0102 0103 std::sort(newAlbums.begin(), newAlbums.end()); 0104 0105 // create all new albums 0106 0107 Q_FOREACH (const AlbumInfo& info, newAlbums) 0108 { 0109 if (info.relativePath.isEmpty()) 0110 { 0111 continue; 0112 } 0113 0114 PAlbum* album = nullptr; 0115 PAlbum* parent = nullptr; 0116 0117 if (info.relativePath == QLatin1String("/")) 0118 { 0119 // Albums that represent the root directory of an album root 0120 // We have them as here new albums first time after their creation 0121 0122 parent = d->rootPAlbum; 0123 album = d->albumRootAlbumHash.value(info.albumRootId); 0124 0125 if (!album) 0126 { 0127 qCDebug(DIGIKAM_GENERAL_LOG) << "Did not find album root album in hash"; 0128 continue; 0129 } 0130 0131 // it has been created from the collection location 0132 // with album root id, parentPath "/" and a name, but no album id yet. 0133 0134 album->m_id = info.id; 0135 } 0136 else 0137 { 0138 // last section, no slash 0139 0140 QString name = info.relativePath.section(QLatin1Char('/'), -1, -1); 0141 0142 // all but last sections, leading slash, no trailing slash 0143 0144 QString parentPath = info.relativePath.section(QLatin1Char('/'), 0, -2); 0145 0146 if (parentPath.isEmpty()) 0147 { 0148 parent = d->albumRootAlbumHash.value(info.albumRootId); 0149 } 0150 else 0151 { 0152 parent = d->albumPathHash.value(PAlbumPath(info.albumRootId, parentPath)); 0153 } 0154 0155 if (!parent) 0156 { 0157 qCDebug(DIGIKAM_GENERAL_LOG) << "Could not find parent with url: " 0158 << parentPath << " for: " 0159 << info.relativePath; 0160 continue; 0161 } 0162 0163 // Create the new album 0164 0165 album = new PAlbum(info.albumRootId, parentPath, name, info.id); 0166 } 0167 0168 album->m_caption = info.caption; 0169 album->m_category = info.category; 0170 album->m_date = info.date; 0171 album->m_iconId = info.iconId; 0172 0173 insertPAlbum(album, parent); 0174 0175 if (album->isAlbumRoot()) 0176 { 0177 // Inserting virtual Trash PAlbum for AlbumsRootAlbum using special constructor 0178 0179 PAlbum* trashAlbum = new PAlbum(album->title(), album->id()); 0180 insertPAlbum(trashAlbum, album); 0181 } 0182 } 0183 0184 if (!topMostOldAlbums.isEmpty() || !newAlbums.isEmpty()) 0185 { 0186 Q_EMIT signalAlbumsUpdated(Album::PHYSICAL); 0187 } 0188 0189 getAlbumItemsCount(); 0190 } 0191 0192 void AlbumManager::updateChangedPAlbums() 0193 { 0194 d->updatePAlbumsTimer->stop(); 0195 0196 // scan db and get a list of all albums 0197 0198 QList<AlbumInfo> currentAlbums = CoreDbAccess().db()->scanAlbums(); 0199 bool needScanPAlbums = false; 0200 0201 // Find the AlbumInfo for each id in changedPAlbums 0202 0203 Q_FOREACH (int id, d->changedPAlbums) 0204 { 0205 Q_FOREACH (const AlbumInfo& info, currentAlbums) 0206 { 0207 if (info.id == id) 0208 { 0209 d->changedPAlbums.remove(info.id); 0210 0211 PAlbum* album = findPAlbum(info.id); 0212 0213 if (album) 0214 { 0215 // Renamed? 0216 0217 if (info.relativePath != QLatin1String("/")) 0218 { 0219 // Handle rename of album name 0220 // last section, no slash 0221 0222 QString name = info.relativePath.section(QLatin1Char('/'), -1, -1); 0223 QString parentPath = info.relativePath; 0224 parentPath.chop(name.length()); 0225 0226 if (parentPath != album->m_parentPath || info.albumRootId != album->albumRootId()) 0227 { 0228 // Handle actual move operations: trigger ScanPAlbums 0229 0230 needScanPAlbums = true; 0231 removePAlbum(album); 0232 break; 0233 } 0234 else if (name != album->title()) 0235 { 0236 album->setTitle(name); 0237 updateAlbumPathHash(); 0238 Q_EMIT signalAlbumRenamed(album); 0239 } 0240 } 0241 0242 // Update caption, collection, date 0243 0244 album->m_caption = info.caption; 0245 album->m_category = info.category; 0246 album->m_date = info.date; 0247 0248 // Icon changed? 0249 0250 if (album->m_iconId != info.iconId) 0251 { 0252 album->m_iconId = info.iconId; 0253 Q_EMIT signalAlbumIconChanged(album); 0254 } 0255 } 0256 } 0257 } 0258 } 0259 0260 if (needScanPAlbums) 0261 { 0262 scanPAlbums(); 0263 } 0264 } 0265 0266 AlbumList AlbumManager::allPAlbums() const 0267 { 0268 AlbumList list; 0269 0270 if (d->rootPAlbum) 0271 { 0272 list.append(d->rootPAlbum); 0273 } 0274 0275 AlbumIterator it(d->rootPAlbum); 0276 0277 while (it.current()) 0278 { 0279 list.append(*it); 0280 ++it; 0281 } 0282 0283 return list; 0284 } 0285 0286 PAlbum* AlbumManager::currentPAlbum() const 0287 { 0288 /** 0289 * Temporary fix, to return multiple items, 0290 * iterate and cast each element 0291 */ 0292 if (!d->currentAlbums.isEmpty()) 0293 { 0294 return dynamic_cast<PAlbum*>(d->currentAlbums.first()); 0295 } 0296 else 0297 { 0298 return nullptr; 0299 } 0300 } 0301 0302 PAlbum* AlbumManager::findPAlbum(const QUrl& url) const 0303 { 0304 CollectionLocation location = CollectionManager::instance()->locationForUrl(url); 0305 0306 if (location.isNull()) 0307 { 0308 return nullptr; 0309 } 0310 0311 return d->albumPathHash.value(PAlbumPath(location.id(), CollectionManager::instance()->album(location, url))); 0312 } 0313 0314 PAlbum* AlbumManager::findPAlbum(int id) const 0315 { 0316 if (!d->rootPAlbum) 0317 { 0318 return nullptr; 0319 } 0320 0321 int gid = d->rootPAlbum->globalID() + id; 0322 0323 return static_cast<PAlbum*>((d->allAlbumsIdHash.value(gid))); 0324 } 0325 0326 0327 PAlbum* AlbumManager::createPAlbum(const QString& albumRootPath, const QString& name, 0328 const QString& caption, const QDate& date, 0329 const QString& category, 0330 QString& errMsg) 0331 { 0332 CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRootPath); 0333 0334 return createPAlbum(location, name, caption, date, category, errMsg); 0335 } 0336 0337 PAlbum* AlbumManager::createPAlbum(const CollectionLocation& location, const QString& name, 0338 const QString& caption, const QDate& date, 0339 const QString& category, 0340 QString& errMsg) 0341 { 0342 if (location.isNull() || !location.isAvailable()) 0343 { 0344 errMsg = i18n("The collection location supplied is invalid or currently not available."); 0345 return nullptr; 0346 } 0347 0348 PAlbum* const album = d->albumRootAlbumHash.value(location.id()); 0349 0350 if (!album) 0351 { 0352 errMsg = i18n("No album for collection location: Internal error"); 0353 return nullptr; 0354 } 0355 0356 return createPAlbum(album, name, caption, date, category, errMsg); 0357 } 0358 0359 PAlbum* AlbumManager::createPAlbum(PAlbum* parent, 0360 const QString& name, 0361 const QString& caption, 0362 const QDate& date, 0363 const QString& category, 0364 QString& errMsg) 0365 { 0366 if (!parent) 0367 { 0368 errMsg = i18n("No parent found for album."); 0369 return nullptr; 0370 } 0371 0372 // sanity checks 0373 0374 if (name.isEmpty()) 0375 { 0376 errMsg = i18n("Album name cannot be empty."); 0377 return nullptr; 0378 } 0379 0380 if (name.contains(QLatin1Char('/'))) 0381 { 0382 errMsg = i18n("Album name cannot contain '/'."); 0383 return nullptr; 0384 } 0385 0386 if (parent->isRoot()) 0387 { 0388 errMsg = i18n("createPAlbum does not accept the root album as parent."); 0389 return nullptr; 0390 } 0391 0392 QString albumPath = parent->isAlbumRoot() ? QString(QLatin1Char('/') + name) : QString(parent->albumPath() + QLatin1Char('/') + name); 0393 int albumRootId = parent->albumRootId(); 0394 0395 // first check if we have a sibling album with the same name 0396 0397 PAlbum* child = static_cast<PAlbum*>(parent->firstChild()); 0398 0399 while (child) 0400 { 0401 if ((child->albumRootId() == albumRootId) && (child->albumPath() == albumPath)) 0402 { 0403 errMsg = i18n("An existing album has the same name."); 0404 return nullptr; 0405 } 0406 0407 child = static_cast<PAlbum*>(child->next()); 0408 } 0409 0410 CoreDbUrl url = parent->databaseUrl(); 0411 url = url.adjusted(QUrl::StripTrailingSlash); 0412 url.setPath(url.path() + QLatin1Char('/') + name); 0413 QUrl fileUrl = url.fileUrl(); 0414 0415 bool ret = QDir().mkpath(fileUrl.toLocalFile()); 0416 0417 if (!ret) 0418 { 0419 errMsg = i18n("Failed to create directory '%1'", fileUrl.toString()); // TODO add tags? 0420 return nullptr; 0421 } 0422 0423 ChangingDB changing(d); 0424 int id = CoreDbAccess().db()->addAlbum(albumRootId, albumPath, caption, date, category); 0425 0426 if (id == -1) 0427 { 0428 errMsg = i18n("Failed to add album to database"); 0429 return nullptr; 0430 } 0431 0432 QString parentPath; 0433 0434 if (!parent->isAlbumRoot()) 0435 { 0436 parentPath = parent->albumPath(); 0437 } 0438 0439 PAlbum* const album = new PAlbum(albumRootId, parentPath, name, id); 0440 album->m_caption = caption; 0441 album->m_category = category; 0442 album->m_date = date; 0443 0444 insertPAlbum(album, parent); 0445 Q_EMIT signalAlbumsUpdated(Album::PHYSICAL); 0446 0447 return album; 0448 } 0449 0450 bool AlbumManager::renamePAlbum(PAlbum* album, const QString& newName, 0451 QString& errMsg) 0452 { 0453 if (!album) 0454 { 0455 errMsg = i18n("No such album"); 0456 return false; 0457 } 0458 0459 if (album == d->rootPAlbum) 0460 { 0461 errMsg = i18n("Cannot rename root album"); 0462 return false; 0463 } 0464 0465 if (album->isAlbumRoot()) 0466 { 0467 errMsg = i18n("Cannot rename album root album"); 0468 return false; 0469 } 0470 0471 if (newName.contains(QLatin1Char('/'))) 0472 { 0473 errMsg = i18n("Album name cannot contain '/'"); 0474 return false; 0475 } 0476 0477 // first check if we have another sibling with the same name 0478 0479 if (hasDirectChildAlbumWithTitle(album->m_parent, newName)) 0480 { 0481 errMsg = i18n("Another album with the same name already exists.\n" 0482 "Please choose another name."); 0483 return false; 0484 } 0485 0486 d->albumWatch->removeWatchedPAlbums(album); 0487 0488 // We use a private shortcut around collection scanner noticing our changes, 0489 // we rename them directly. Faster. 0490 0491 ScanController::instance()->suspendCollectionScan(); 0492 0493 QDir dir(album->albumRootPath() + album->m_parentPath); 0494 bool ret = dir.rename(album->title(), newName); 0495 0496 if (!ret) 0497 { 0498 ScanController::instance()->resumeCollectionScan(); 0499 0500 errMsg = i18n("Failed to rename Album"); 0501 return false; 0502 } 0503 0504 QString oldAlbumPath = album->albumPath(); 0505 album->setTitle(newName); 0506 album->m_path = newName; 0507 QString newAlbumPath = album->albumPath(); 0508 0509 // now rename the album and subalbums in the database 0510 { 0511 CoreDbAccess access; 0512 ChangingDB changing(d); 0513 access.db()->renameAlbum(album->id(), album->albumRootId(), album->albumPath()); 0514 0515 PAlbum* subAlbum = nullptr; 0516 AlbumIterator it(album); 0517 0518 while ((subAlbum = static_cast<PAlbum*>(it.current())) != nullptr) 0519 { 0520 subAlbum->m_parentPath = newAlbumPath + subAlbum->m_parentPath.mid(oldAlbumPath.length()); 0521 access.db()->renameAlbum(subAlbum->id(), album->albumRootId(), subAlbum->albumPath()); 0522 Q_EMIT signalAlbumNewPath(subAlbum); 0523 ++it; 0524 } 0525 } 0526 0527 updateAlbumPathHash(); 0528 Q_EMIT signalAlbumRenamed(album); 0529 0530 ScanController::instance()->resumeCollectionScan(); 0531 0532 return true; 0533 } 0534 0535 bool AlbumManager::updatePAlbumIcon(PAlbum* album, qlonglong iconID, QString& errMsg) 0536 { 0537 if (!album) 0538 { 0539 errMsg = i18n("No such album"); 0540 return false; 0541 } 0542 0543 if (album == d->rootPAlbum) 0544 { 0545 errMsg = i18n("Cannot edit root album"); 0546 return false; 0547 } 0548 0549 { 0550 CoreDbAccess access; 0551 ChangingDB changing(d); 0552 access.db()->setAlbumIcon(album->id(), iconID); 0553 album->m_iconId = iconID; 0554 } 0555 0556 Q_EMIT signalAlbumIconChanged(album); 0557 0558 return true; 0559 } 0560 0561 QHash<int, int> AlbumManager::getPAlbumsCount() const 0562 { 0563 return d->pAlbumsCount; 0564 } 0565 0566 void AlbumManager::insertPAlbum(PAlbum* album, PAlbum* parent) 0567 { 0568 if (!album) 0569 { 0570 return; 0571 } 0572 0573 Q_EMIT signalAlbumAboutToBeAdded(album, parent, parent ? parent->lastChild() : nullptr); 0574 0575 if (parent) 0576 { 0577 album->setParent(parent); 0578 } 0579 0580 d->albumPathHash[PAlbumPath(album)] = album; 0581 d->allAlbumsIdHash[album->globalID()] = album; 0582 0583 Q_EMIT signalAlbumAdded(album); 0584 } 0585 0586 void AlbumManager::removePAlbum(PAlbum* album) 0587 { 0588 if (!album) 0589 { 0590 return; 0591 } 0592 0593 // remove all children of this album 0594 0595 Album* child = album->firstChild(); 0596 PAlbum* toBeRemoved = nullptr; 0597 0598 while (child) 0599 { 0600 Album* const next = child->next(); 0601 toBeRemoved = dynamic_cast<PAlbum*>(child); 0602 0603 if (toBeRemoved) 0604 { 0605 removePAlbum(toBeRemoved); 0606 toBeRemoved = nullptr; 0607 } 0608 0609 child = next; 0610 } 0611 0612 Q_EMIT signalAlbumAboutToBeDeleted(album); 0613 d->albumPathHash.remove(PAlbumPath(album)); 0614 d->allAlbumsIdHash.remove(album->globalID()); 0615 0616 CoreDbUrl url = album->databaseUrl(); 0617 0618 if (!d->currentAlbums.isEmpty()) 0619 { 0620 if (album == d->currentAlbums.first()) 0621 { 0622 d->currentAlbums.clear(); 0623 Q_EMIT signalAlbumCurrentChanged(d->currentAlbums); 0624 } 0625 } 0626 0627 if (album->isAlbumRoot()) 0628 { 0629 d->albumRootAlbumHash.remove(album->albumRootId()); 0630 } 0631 0632 Q_EMIT signalAlbumDeleted(album); 0633 quintptr deletedAlbum = reinterpret_cast<quintptr>(album); 0634 delete album; 0635 0636 Q_EMIT signalAlbumHasBeenDeleted(deletedAlbum); 0637 } 0638 0639 void AlbumManager::removeWatchedPAlbums(const PAlbum* const album) 0640 { 0641 d->albumWatch->removeWatchedPAlbums(album); 0642 } 0643 0644 } // namespace Digikam