File indexing completed on 2025-01-05 03:53:47

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2004-06-15
0007  * Description : digiKam album types
0008  *
0009  * SPDX-FileCopyrightText: 2004-2005 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: 2014-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 "album.h"
0018 
0019 // KDE includes
0020 
0021 #include <klocalizedstring.h>
0022 
0023 // Local includes
0024 
0025 #include "digikam_debug.h"
0026 #include "dtrash.h"
0027 #include "coredb.h"
0028 #include "coredburl.h"
0029 #include "coredbaccess.h"
0030 #include "albummanager.h"
0031 #include "collectionmanager.h"
0032 #include "tagscache.h"
0033 
0034 namespace Digikam
0035 {
0036 
0037 Album::Album(Album::Type type, int id, bool root)
0038     : m_root            (root),
0039       m_usedByLabelsTree(false),
0040       m_albumInDeletion (false),
0041       m_id              (id),
0042       m_type            (type),
0043       m_parent          (nullptr)
0044 {
0045 }
0046 
0047 Album::~Album()
0048 {
0049     m_albumInDeletion = true;
0050 
0051     if (m_parent)
0052     {
0053         m_parent->removeChild(this);
0054     }
0055 
0056     clear();
0057     AlbumManager::internalInstance->notifyAlbumDeletion(this);
0058 }
0059 
0060 void Album::setParent(Album* const parent)
0061 {
0062     if (parent)
0063     {
0064         m_parent = parent;
0065         parent->insertChild(this);
0066     }
0067 }
0068 
0069 Album* Album::parent() const
0070 {
0071     return m_parent;
0072 }
0073 
0074 Album* Album::firstChild() const
0075 {
0076     QReadLocker locker(&m_cacheLock);
0077 
0078     if (m_childCache.isEmpty())
0079     {
0080         return nullptr;
0081     }
0082 
0083     return m_childCache.constFirst();
0084 }
0085 
0086 Album* Album::lastChild() const
0087 {
0088     QReadLocker locker(&m_cacheLock);
0089 
0090     if (m_childCache.isEmpty())
0091     {
0092         return nullptr;
0093     }
0094 
0095     return m_childCache.constLast();
0096 }
0097 
0098 Album* Album::next() const
0099 {
0100     if (!m_parent)
0101     {
0102         return nullptr;
0103     }
0104 
0105     QReadLocker parentLocker(&m_parent->m_cacheLock);
0106 
0107     int row = m_parent->m_childCache.indexOf(const_cast<Album*>(this));
0108 
0109     if ((row < 0) || ((row + 1) >= m_parent->m_childCache.size()))
0110     {
0111         return nullptr;
0112     }
0113 
0114     return m_parent->m_childCache.at(row + 1);
0115 }
0116 
0117 Album* Album::prev() const
0118 {
0119     if (!m_parent)
0120     {
0121         return nullptr;
0122     }
0123 
0124     QReadLocker parentLocker(&m_parent->m_cacheLock);
0125 
0126     int row = m_parent->m_childCache.indexOf(const_cast<Album*>(this));
0127 
0128     if (row < 1)
0129     {
0130         return nullptr;
0131     }
0132 
0133     return m_parent->m_childCache.at(row - 1);
0134 }
0135 
0136 Album* Album::childAtRow(int row) const
0137 {
0138     if (m_albumInDeletion)
0139     {
0140         return nullptr;
0141     }
0142 
0143     QReadLocker locker(&m_cacheLock);
0144 
0145     if (m_albumInDeletion || (row < 0) || (row >= m_childCache.size()))
0146     {
0147         return nullptr;
0148     }
0149 
0150     return m_childCache.at(row);
0151 }
0152 
0153 AlbumList Album::childAlbums(bool recursive)
0154 {
0155     AlbumList childList;
0156 
0157     QReadLocker locker(&m_cacheLock);
0158 
0159     QList<Album*>::const_iterator it = m_childCache.constBegin();
0160 
0161     while (it != m_childCache.constEnd())
0162     {
0163         childList << (*it);
0164 
0165         if (recursive)
0166         {
0167             childList << (*it)->childAlbums(recursive);
0168         }
0169 
0170         ++it;
0171     }
0172 
0173     return childList;
0174 }
0175 
0176 QList<int> Album::childAlbumIds(bool recursive)
0177 {
0178     QList<int> ids;
0179 
0180     AlbumList childList = childAlbums(recursive);
0181 
0182     QListIterator<Album*> it(childList);
0183 
0184     while (it.hasNext())
0185     {
0186         ids << it.next()->id();
0187     }
0188 
0189     return ids;
0190 }
0191 
0192 void Album::insertChild(Album* const child)
0193 {
0194     if (!child)
0195     {
0196         return;
0197     }
0198 
0199     QWriteLocker locker(&m_cacheLock);
0200 
0201     m_childCache.append(child);
0202 }
0203 
0204 void Album::removeChild(Album* const child)
0205 {
0206     if (!child)
0207     {
0208         return;
0209     }
0210 
0211     QWriteLocker locker(&m_cacheLock);
0212 
0213     m_childCache.removeOne(child);
0214 }
0215 
0216 void Album::clear()
0217 {
0218     QWriteLocker locker(&m_cacheLock);
0219 
0220     while (!m_childCache.isEmpty())
0221     {
0222         Album* const child = m_childCache.takeLast();
0223         child->m_parent    = nullptr;
0224 
0225         delete child;
0226     }
0227 }
0228 
0229 int Album::globalID() const
0230 {
0231     return globalID(m_type, m_id);
0232 }
0233 
0234 int Album::globalID(Type type, int id)
0235 {
0236     switch (type)
0237     {
0238         // Use the upper bits to create unique ids.
0239         case (PHYSICAL):
0240             return id;
0241 
0242         case (TAG):
0243             return (id | (1 << 27));
0244 
0245         case (DATE):
0246             return (id | (1 << 28));
0247 
0248         case (SEARCH):
0249             return (id | (1 << 29));
0250 
0251         case (FACE):
0252             return (id | (1 << 30));
0253 
0254         default:
0255             qCDebug(DIGIKAM_GENERAL_LOG) << "Unknown album type";
0256             return -1;
0257     }
0258 }
0259 
0260 int Album::id() const
0261 {
0262     return m_id;
0263 }
0264 
0265 int Album::childCount() const
0266 {
0267     QReadLocker locker(&m_cacheLock);
0268 
0269     return m_childCache.count();
0270 }
0271 
0272 int Album::rowFromAlbum() const
0273 {
0274     if (!m_parent)
0275     {
0276         return 0;
0277     }
0278 
0279     QReadLocker parentLocker(&m_parent->m_cacheLock);
0280 
0281     int row = m_parent->m_childCache.indexOf(const_cast<Album*>(this));
0282 
0283     return ((row != -1) ? row : 0);
0284 }
0285 
0286 void Album::setTitle(const QString& title)
0287 {
0288     m_title = title;
0289 }
0290 
0291 QString Album::title() const
0292 {
0293     return m_title;
0294 }
0295 
0296 Album::Type Album::type() const
0297 {
0298     return m_type;
0299 }
0300 
0301 void Album::setExtraData(const void* const key, void* const value)
0302 {
0303     m_extraMap.insert(key, value);
0304 }
0305 
0306 void Album::removeExtraData(const void* const key)
0307 {
0308     m_extraMap.remove(key);
0309 }
0310 
0311 void* Album::extraData(const void* const key) const
0312 {
0313     return m_extraMap.value(key, nullptr);
0314 }
0315 
0316 bool Album::isRoot() const
0317 {
0318     return m_root;
0319 }
0320 
0321 bool Album::isAncestorOf(Album* const album) const
0322 {
0323     bool val = false;
0324     Album* a = album;
0325 
0326     while (a && !a->isRoot())
0327     {
0328         if (a == this)
0329         {
0330             val = true;
0331             break;
0332         }
0333 
0334         a = a->parent();
0335     }
0336 
0337     return val;
0338 }
0339 
0340 bool Album::isUsedByLabelsTree() const
0341 {
0342     return m_usedByLabelsTree;
0343 }
0344 
0345 bool Album::isTrashAlbum() const
0346 {
0347     if ((m_id < -1) && (m_type == PHYSICAL))
0348     {
0349         return true;
0350     }
0351 
0352     return false;
0353 }
0354 
0355 void Album::setUsedByLabelsTree(bool isUsed)
0356 {
0357     m_usedByLabelsTree = isUsed;
0358 }
0359 
0360 // ------------------------------------------------------------------------------
0361 
0362 int PAlbum::m_uniqueTrashId = -2;
0363 
0364 PAlbum::PAlbum(const QString& title)
0365     : Album             (Album::PHYSICAL, 0, true),
0366       m_isAlbumRootAlbum(false),
0367       m_albumRootId     (-1),
0368       m_parentPath      (QLatin1Char('/')),
0369       m_iconId          (0)
0370 {
0371     setTitle(title);
0372     m_path.clear();
0373 }
0374 
0375 PAlbum::PAlbum(int albumRoot, const QString& label)
0376     : Album             (Album::PHYSICAL, -1, false),
0377       m_isAlbumRootAlbum(true),
0378       m_albumRootId     (albumRoot),
0379       m_parentPath      (QLatin1Char('/')),
0380       m_iconId          (0)
0381 {
0382     // set the id to -1 (line above). AlbumManager may change that later.
0383     setTitle(label);
0384     m_path.clear();
0385 }
0386 
0387 PAlbum::PAlbum(int albumRoot, const QString& parentPath, const QString& title, int id)
0388     : Album             (Album::PHYSICAL, id, false),
0389       m_isAlbumRootAlbum(false),
0390       m_albumRootId     (albumRoot),
0391       m_path            (title),
0392       m_parentPath      (parentPath + QLatin1Char('/')),
0393       m_iconId          (0),
0394       m_date            (QDate::currentDate())
0395 {
0396     // If path is /holidays/2007, title is only "2007", path is "/holidays"
0397     setTitle(title);
0398 }
0399 
0400 PAlbum::PAlbum(const QString& parentPath, int albumRoot)
0401     : Album             (Album::PHYSICAL, m_uniqueTrashId--, false),
0402       m_isAlbumRootAlbum(false),
0403       m_albumRootId     (albumRoot),
0404       m_path            (DTrash::TRASH_FOLDER),
0405       m_parentPath      (parentPath + QLatin1Char('/')),
0406       m_iconId          (0)
0407 {
0408     setTitle(i18n("Trash"));
0409 }
0410 
0411 PAlbum::~PAlbum()
0412 {
0413 }
0414 
0415 bool PAlbum::isAlbumRoot() const
0416 {
0417     return m_isAlbumRootAlbum;
0418 }
0419 
0420 void PAlbum::setCaption(const QString& caption)
0421 {
0422     m_caption = caption;
0423 
0424     CoreDbAccess access;
0425     access.db()->setAlbumCaption(id(), m_caption);
0426 }
0427 
0428 void PAlbum::setCategory(const QString& category)
0429 {
0430     m_category = category;
0431 
0432     CoreDbAccess access;
0433     access.db()->setAlbumCategory(id(), m_category);
0434 }
0435 
0436 void PAlbum::setDate(const QDate& date)
0437 {
0438     m_date = date;
0439 
0440     CoreDbAccess access;
0441     access.db()->setAlbumDate(id(), m_date);
0442 }
0443 
0444 QString PAlbum::albumRootPath() const
0445 {
0446     return CollectionManager::instance()->albumRootPath(m_albumRootId);
0447 }
0448 
0449 QString PAlbum::albumRootLabel() const
0450 {
0451     return CollectionManager::instance()->albumRootLabel(m_albumRootId);
0452 }
0453 
0454 int PAlbum::albumRootId() const
0455 {
0456     return m_albumRootId;
0457 }
0458 
0459 QString PAlbum::caption() const
0460 {
0461     return m_caption;
0462 }
0463 
0464 QString PAlbum::category() const
0465 {
0466     return m_category;
0467 }
0468 
0469 QDate PAlbum::date() const
0470 {
0471     return m_date;
0472 }
0473 
0474 QString PAlbum::albumPath() const
0475 {
0476     return (m_parentPath + m_path);
0477 }
0478 
0479 CoreDbUrl PAlbum::databaseUrl() const
0480 {
0481     return CoreDbUrl::fromAlbumAndName(QString(), albumPath(),
0482                                        QUrl::fromLocalFile(albumRootPath()), m_albumRootId);
0483 }
0484 
0485 QString PAlbum::prettyUrl() const
0486 {
0487     QString u = i18n("Albums") + QLatin1Char('/') +
0488                                  albumRootLabel() +
0489                                  albumPath();
0490 
0491     if (u.endsWith(QLatin1Char('/')))
0492     {
0493         u.chop(1);
0494     }
0495 
0496     return u;
0497 }
0498 
0499 qlonglong PAlbum::iconId() const
0500 {
0501     return m_iconId;
0502 }
0503 
0504 QUrl PAlbum::fileUrl() const
0505 {
0506     return databaseUrl().fileUrl();
0507 }
0508 
0509 QString PAlbum::folderPath() const
0510 {
0511     return databaseUrl().fileUrl().toLocalFile();
0512 }
0513 
0514 // --------------------------------------------------------------------------
0515 
0516 TAlbum::TAlbum(const QString& title, int id, bool root)
0517     : Album   (Album::TAG, id, root),
0518       m_pid   (0),
0519       m_iconId(0)
0520 {
0521     setTitle(title);
0522 }
0523 
0524 TAlbum::~TAlbum()
0525 {
0526 }
0527 
0528 QString TAlbum::tagPath(bool leadingSlash) const
0529 {
0530     if (isRoot())
0531     {
0532         return (leadingSlash ? QLatin1String("/") : QLatin1String(""));
0533     }
0534 
0535     QString u;
0536 
0537     if (parent())
0538     {
0539         u = (static_cast<TAlbum*>(parent()))->tagPath(leadingSlash);
0540 
0541         if (!parent()->isRoot())
0542         {
0543             u += QLatin1Char('/');
0544         }
0545     }
0546 
0547     u += title();
0548 
0549     return u;
0550 }
0551 
0552 QString TAlbum::standardIconName() const
0553 {
0554     return (hasProperty(TagPropertyName::person()) ? QLatin1String("smiley")
0555                                                    : QLatin1String("tag"));
0556 }
0557 
0558 QString TAlbum::prettyUrl() const
0559 {
0560     return (i18n("Tags") + tagPath(true));
0561 }
0562 
0563 CoreDbUrl TAlbum::databaseUrl() const
0564 {
0565     return CoreDbUrl::fromTagIds(tagIDs());
0566 }
0567 
0568 QList<int> TAlbum::tagIDs() const
0569 {
0570     if      (isRoot())
0571     {
0572         return QList<int>();
0573     }
0574     else if (parent())
0575     {
0576         return (static_cast<TAlbum*>(parent())->tagIDs() << id());
0577     }
0578     else
0579     {
0580         return (QList<int>() << id());
0581     }
0582 }
0583 
0584 QString TAlbum::icon() const
0585 {
0586     return m_icon;
0587 }
0588 
0589 bool TAlbum::isInternalTag() const
0590 {
0591     return TagsCache::instance()->isInternalTag(id());
0592 }
0593 
0594 qlonglong TAlbum::iconId() const
0595 {
0596     return m_iconId;
0597 }
0598 
0599 bool TAlbum::hasProperty(const QString& key) const
0600 {
0601     return TagsCache::instance()->hasProperty(id(), key);
0602 }
0603 
0604 QString TAlbum::property(const QString& key) const
0605 {
0606     return TagsCache::instance()->propertyValue(id(), key);
0607 }
0608 
0609 QMap<QString, QString> TAlbum::properties() const
0610 {
0611     return TagsCache::instance()->properties(id());
0612 }
0613 
0614 // --------------------------------------------------------------------------
0615 
0616 int DAlbum::m_uniqueID = 0;
0617 
0618 DAlbum::DAlbum(const QDate& date, bool root, Range range)
0619     : Album  (Album::DATE, root ? 0 : ++m_uniqueID, root),
0620       m_date (date),
0621       m_range(range)
0622 {
0623     // Set the name of the date album
0624 
0625     QString dateTitle;
0626 
0627     if (m_range == Month)
0628     {
0629         dateTitle = m_date.toString(QLatin1String("MMMM yyyy"));
0630     }
0631     else
0632     {
0633         dateTitle = m_date.toString(QLatin1String("yyyy"));
0634     }
0635 
0636     setTitle(dateTitle);
0637 }
0638 
0639 DAlbum::~DAlbum()
0640 {
0641 }
0642 
0643 QDate DAlbum::date() const
0644 {
0645     return m_date;
0646 }
0647 
0648 DAlbum::Range DAlbum::range() const
0649 {
0650     return m_range;
0651 }
0652 
0653 CoreDbUrl DAlbum::databaseUrl() const
0654 {
0655     if (m_range == Month)
0656     {
0657         return CoreDbUrl::fromDateForMonth(m_date);
0658     }
0659 
0660     return CoreDbUrl::fromDateForYear(m_date);
0661 }
0662 
0663 // --------------------------------------------------------------------------
0664 
0665 SAlbum::SAlbum(const QString& title, int id, bool root)
0666     : Album       (Album::SEARCH, id, root),
0667       m_searchType(DatabaseSearch::UndefinedType)
0668 {
0669     setTitle(title);
0670 }
0671 
0672 SAlbum::~SAlbum()
0673 {
0674 }
0675 
0676 void SAlbum::setSearch(DatabaseSearch::Type type, const QString& query)
0677 {
0678     m_searchType = type;
0679     m_query      = query;
0680 }
0681 
0682 CoreDbUrl SAlbum::databaseUrl() const
0683 {
0684     return CoreDbUrl::searchUrl(id());
0685 }
0686 
0687 QString SAlbum::query() const
0688 {
0689     return m_query;
0690 }
0691 
0692 DatabaseSearch::Type SAlbum::searchType() const
0693 {
0694     return m_searchType;
0695 }
0696 
0697 bool SAlbum::isNormalSearch() const
0698 {
0699     switch (m_searchType)
0700     {
0701         case DatabaseSearch::KeywordSearch:
0702         case DatabaseSearch::AdvancedSearch:
0703         case DatabaseSearch::LegacyUrlSearch:
0704             return true;
0705 
0706         default:
0707             return false;
0708     }
0709 }
0710 
0711 bool SAlbum::isAdvancedSearch() const
0712 {
0713     return (m_searchType == DatabaseSearch::AdvancedSearch);
0714 }
0715 
0716 bool SAlbum::isKeywordSearch() const
0717 {
0718     return (m_searchType == DatabaseSearch::KeywordSearch);
0719 }
0720 
0721 bool SAlbum::isTimelineSearch() const
0722 {
0723     return (m_searchType == DatabaseSearch::TimeLineSearch);
0724 }
0725 
0726 bool SAlbum::isHaarSearch() const
0727 {
0728     return (m_searchType == DatabaseSearch::HaarSearch);
0729 }
0730 
0731 bool SAlbum::isMapSearch() const
0732 {
0733     return (m_searchType == DatabaseSearch::MapSearch);
0734 }
0735 
0736 bool SAlbum::isDuplicatesSearch() const
0737 {
0738     return (m_searchType == DatabaseSearch::DuplicatesSearch);
0739 }
0740 
0741 bool SAlbum::isTemporarySearch() const
0742 {
0743     if (isHaarSearch())
0744     {
0745         return ((title() == getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch))) ||
0746                 (title() == getTemporaryHaarTitle(DatabaseSearch::HaarSketchSearch));
0747     }
0748 
0749     return (title() == getTemporaryTitle(m_searchType));
0750 }
0751 
0752 QString SAlbum::displayTitle() const
0753 {
0754     if (isTemporarySearch())
0755     {
0756         switch (m_searchType)
0757         {
0758             case DatabaseSearch::TimeLineSearch:
0759                 return i18n("Current Timeline Search");
0760 
0761             case DatabaseSearch::HaarSearch:
0762             {
0763                 if (title() == getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch))
0764                 {
0765                     return i18n("Current Fuzzy Image Search");
0766                 }
0767                 else if (title() == getTemporaryHaarTitle(DatabaseSearch::HaarSketchSearch))
0768                 {
0769                     return i18n("Current Fuzzy Sketch Search");
0770                 }
0771 
0772                 break;
0773             }
0774 
0775             case DatabaseSearch::MapSearch:
0776                 return i18n("Current Map Search");
0777 
0778             case DatabaseSearch::KeywordSearch:
0779             case DatabaseSearch::AdvancedSearch:
0780             case DatabaseSearch::LegacyUrlSearch:
0781                 return i18n("Current Search");
0782 
0783             case DatabaseSearch::DuplicatesSearch:
0784                 return i18n("Current Duplicates Search");
0785 
0786             case DatabaseSearch::UndefinedType:
0787                 break;
0788         }
0789     }
0790 
0791     return title();
0792 }
0793 
0794 QString SAlbum::getTemporaryTitle(DatabaseSearch::Type type, DatabaseSearch::HaarSearchType haarType)
0795 {
0796     switch (type)
0797     {
0798         case DatabaseSearch::TimeLineSearch:
0799             return QLatin1String("_Current_Time_Line_Search_");
0800 
0801         case DatabaseSearch::HaarSearch:
0802             return getTemporaryHaarTitle(haarType);
0803 
0804         case DatabaseSearch::MapSearch:
0805             return QLatin1String("_Current_Map_Search_");
0806 
0807         case DatabaseSearch::KeywordSearch:
0808         case DatabaseSearch::AdvancedSearch:
0809         case DatabaseSearch::LegacyUrlSearch:
0810             return QLatin1String("_Current_Search_View_Search_");
0811 
0812         case DatabaseSearch::DuplicatesSearch:
0813             return QLatin1String("_Current_Duplicates_Search_");
0814 
0815         default:
0816             qCDebug(DIGIKAM_GENERAL_LOG) << "Untreated temporary search type " << type;
0817             return QLatin1String("_Current_Unknown_Search_");
0818     }
0819 }
0820 
0821 QString SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarSearchType haarType)
0822 {
0823     switch (haarType)
0824     {
0825         case DatabaseSearch::HaarImageSearch:
0826             return QLatin1String("_Current_Fuzzy_Image_Search_");
0827 
0828         case DatabaseSearch::HaarSketchSearch:
0829             return QLatin1String("_Current_Fuzzy_Sketch_Search_");
0830 
0831         default:
0832             qCDebug(DIGIKAM_GENERAL_LOG) << "Untreated temporary haar search type " << haarType;
0833             return QLatin1String("_Current_Unknown_Haar_Search_");
0834     }
0835 }
0836 
0837 // --------------------------------------------------------------------------
0838 
0839 AlbumIterator::AlbumIterator(Album* const album)
0840     : m_current(album ? album->firstChild() : nullptr),
0841       m_root   (album)
0842 {
0843 }
0844 
0845 AlbumIterator::~AlbumIterator()
0846 {
0847 }
0848 
0849 AlbumIterator& AlbumIterator::operator++()
0850 {
0851     if (!m_current)
0852     {
0853         return *this;
0854     }
0855 
0856     Album* album = m_current->firstChild();
0857 
0858     if (!album)
0859     {
0860         while ((album = m_current->next()) == nullptr)
0861         {
0862             m_current = m_current->parent();
0863 
0864             if (m_current == m_root)
0865             {
0866                 // we have reached the root.
0867                 // that means no more children
0868 
0869                 m_current = nullptr;
0870                 break;
0871             }
0872 
0873             if (m_current == nullptr)
0874             {
0875                 break;
0876             }
0877         }
0878     }
0879 
0880     m_current = album;
0881 
0882     return *this;
0883 }
0884 
0885 Album* AlbumIterator::operator*()
0886 {
0887     return m_current;
0888 }
0889 
0890 Album* AlbumIterator::current() const
0891 {
0892     return m_current;
0893 }
0894 
0895 } // namespace Digikam