1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2004-06-15
7  * Description : digiKam album types
8  *
9  * Copyright (C) 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
10  * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  * Copyright (C) 2014-2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
12  *
13  * This program is free software; you can redistribute it
14  * and/or modify it under the terms of the GNU General
15  * Public License as published by the Free Software Foundation;
16  * either version 2, or (at your option)
17  * any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * ============================================================ */
25 
26 #include "album.h"
27 
28 // KDE includes
29 
30 #include <klocalizedstring.h>
31 
32 // Local includes
33 
34 #include "digikam_debug.h"
35 #include "coredb.h"
36 #include "albummanager.h"
37 #include "collectionmanager.h"
38 #include "coredbaccess.h"
39 #include "coredburl.h"
40 #include "tagscache.h"
41 
42 namespace Digikam
43 {
44 
Album(Album::Type type,int id,bool root)45 Album::Album(Album::Type type, int id, bool root)
46     : m_root(root),
47       m_usedByLabelsTree(false),
48       m_id(id),
49       m_type(type),
50       m_parent(nullptr)
51 {
52 }
53 
~Album()54 Album::~Album()
55 {
56     if (m_parent)
57     {
58         m_parent->removeChild(this);
59     }
60 
61     clear();
62     AlbumManager::internalInstance->notifyAlbumDeletion(this);
63 }
64 
setParent(Album * const parent)65 void Album::setParent(Album* const parent)
66 {
67     if (parent)
68     {
69         m_parent = parent;
70         parent->insertChild(this);
71     }
72 }
73 
parent() const74 Album* Album::parent() const
75 {
76     return m_parent;
77 }
78 
firstChild() const79 Album* Album::firstChild() const
80 {
81     if (m_childCache.isEmpty())
82     {
83         return nullptr;
84     }
85 
86     return m_childCache.constFirst();
87 }
88 
lastChild() const89 Album* Album::lastChild() const
90 {
91     if (m_childCache.isEmpty())
92     {
93         return nullptr;
94     }
95 
96     return m_childCache.constLast();
97 }
98 
next() const99 Album* Album::next() const
100 {
101     if (!m_parent)
102     {
103         return nullptr;
104     }
105 
106     int row = m_parent->m_childCache.indexOf(const_cast<Album*>(this));
107 
108     if ((row < 0) || ((row + 1) >= m_parent->m_childCache.count()))
109     {
110         return nullptr;
111     }
112 
113     return m_parent->m_childCache.at(row + 1);
114 }
115 
prev() const116 Album* Album::prev() const
117 {
118     if (!m_parent)
119     {
120         return nullptr;
121     }
122 
123     int row = m_parent->m_childCache.indexOf(const_cast<Album*>(this));
124 
125     if (row < 1)
126     {
127         return nullptr;
128     }
129 
130     return m_parent->m_childCache.at(row - 1);
131 }
132 
childAtRow(int row) const133 Album* Album::childAtRow(int row) const
134 {
135     return m_childCache.value(row, nullptr);
136 }
137 
childAlbums(bool recursive)138 AlbumList Album::childAlbums(bool recursive)
139 {
140     AlbumList childList;
141 
142     QVector<Album*>::const_iterator it = m_childCache.constBegin();
143 
144     while (it != m_childCache.constEnd())
145     {
146         childList << (*it);
147 
148         if (recursive)
149         {
150             childList << (*it)->childAlbums(recursive);
151         }
152 
153         ++it;
154     }
155 
156     return childList;
157 }
158 
childAlbumIds(bool recursive)159 QList<int> Album::childAlbumIds(bool recursive)
160 {
161     QList<int> ids;
162 
163     AlbumList childList = childAlbums(recursive);
164 
165     QListIterator<Album*> it(childList);
166 
167     while (it.hasNext())
168     {
169         ids << it.next()->id();
170     }
171 
172     return ids;
173 }
174 
insertChild(Album * const child)175 void Album::insertChild(Album* const child)
176 {
177     if (!child)
178     {
179         return;
180     }
181 
182     m_childCache.append(child);
183 }
184 
removeChild(Album * const child)185 void Album::removeChild(Album* const child)
186 {
187     if (!child)
188     {
189         return;
190     }
191 
192     m_childCache.removeOne(child);
193 }
194 
clear()195 void Album::clear()
196 {
197     while (!m_childCache.isEmpty())
198     {
199         delete m_childCache.takeFirst();
200     }
201 }
202 
globalID() const203 int Album::globalID() const
204 {
205     return globalID(m_type, m_id);
206 }
207 
globalID(Type type,int id)208 int Album::globalID(Type type, int id)
209 {
210     switch (type)
211     {
212         // Use the upper bits to create unique ids.
213         case (PHYSICAL):
214             return id;
215 
216         case (TAG):
217             return (id | (1 << 27));
218 
219         case (DATE):
220             return (id | (1 << 28));
221 
222         case (SEARCH):
223             return (id | (1 << 29));
224 
225         case (FACE):
226             return (id | (1 << 30));
227 
228         default:
229             qCDebug(DIGIKAM_GENERAL_LOG) << "Unknown album type";
230             return -1;
231     }
232 }
233 
id() const234 int Album::id() const
235 {
236     return m_id;
237 }
238 
childCount() const239 int Album::childCount() const
240 {
241     return m_childCache.count();
242 }
243 
rowFromAlbum() const244 int Album::rowFromAlbum() const
245 {
246     if (!m_parent)
247     {
248         return 0;
249     }
250 
251     int row = m_parent->m_childCache.indexOf(const_cast<Album*>(this));
252 
253     return ((row != -1) ? row : 0);
254 }
255 
setTitle(const QString & title)256 void Album::setTitle(const QString& title)
257 {
258     m_title = title;
259 }
260 
title() const261 QString Album::title() const
262 {
263     return m_title;
264 }
265 
type() const266 Album::Type Album::type() const
267 {
268     return m_type;
269 }
270 
setExtraData(const void * const key,void * const value)271 void Album::setExtraData(const void* const key, void* const value)
272 {
273     m_extraMap.insert(key, value);
274 }
275 
removeExtraData(const void * const key)276 void Album::removeExtraData(const void* const key)
277 {
278     m_extraMap.remove(key);
279 }
280 
extraData(const void * const key) const281 void* Album::extraData(const void* const key) const
282 {
283     return m_extraMap.value(key, nullptr);
284 }
285 
isRoot() const286 bool Album::isRoot() const
287 {
288     return m_root;
289 }
290 
isAncestorOf(Album * const album) const291 bool Album::isAncestorOf(Album* const album) const
292 {
293     bool val = false;
294     Album* a = album;
295 
296     while (a && !a->isRoot())
297     {
298         if (a == this)
299         {
300             val = true;
301             break;
302         }
303 
304         a = a->parent();
305     }
306 
307     return val;
308 }
309 
isUsedByLabelsTree() const310 bool Album::isUsedByLabelsTree() const
311 {
312     return m_usedByLabelsTree;
313 }
314 
isTrashAlbum() const315 bool Album::isTrashAlbum() const
316 {
317     if ((m_id < -1) && (m_type == PHYSICAL))
318     {
319         return true;
320     }
321 
322     return false;
323 }
324 
setUsedByLabelsTree(bool isUsed)325 void Album::setUsedByLabelsTree(bool isUsed)
326 {
327     m_usedByLabelsTree = isUsed;
328 }
329 
330 // ------------------------------------------------------------------------------
331 
332 int PAlbum::m_uniqueTrashId = -2;
333 
PAlbum(const QString & title)334 PAlbum::PAlbum(const QString& title)
335     : Album(Album::PHYSICAL, 0, true),
336       m_isAlbumRootAlbum(false),
337       m_albumRootId(-1),
338       m_parentPath(QLatin1Char('/')),
339       m_iconId(0)
340 {
341     setTitle(title);
342     m_path.clear();
343 }
344 
PAlbum(int albumRoot,const QString & label)345 PAlbum::PAlbum(int albumRoot, const QString& label)
346     : Album(Album::PHYSICAL, -1, false),
347       m_isAlbumRootAlbum(true),
348       m_albumRootId(albumRoot),
349       m_parentPath(QLatin1Char('/')),
350       m_iconId(0)
351 {
352     // set the id to -1 (line above). AlbumManager may change that later.
353     setTitle(label);
354     m_path.clear();
355 }
356 
PAlbum(int albumRoot,const QString & parentPath,const QString & title,int id)357 PAlbum::PAlbum(int albumRoot, const QString& parentPath, const QString& title, int id)
358     : Album(Album::PHYSICAL, id, false),
359       m_isAlbumRootAlbum(false),
360       m_albumRootId(albumRoot),
361       m_path(title),
362       m_parentPath(parentPath + QLatin1Char('/')),
363       m_iconId(0),
364       m_date(QDate::currentDate())
365 {
366     // If path is /holidays/2007, title is only "2007", path is "/holidays"
367     setTitle(title);
368 }
369 
PAlbum(const QString & parentPath,int albumRoot)370 PAlbum::PAlbum(const QString& parentPath, int albumRoot)
371     : Album(Album::PHYSICAL, m_uniqueTrashId--, false),
372       m_isAlbumRootAlbum(false),
373       m_albumRootId(albumRoot),
374       m_path(QLatin1String("Trash")),
375       m_parentPath(parentPath + QLatin1Char('/')),
376       m_iconId(0)
377 {
378     setTitle(i18n("Trash"));
379 }
380 
~PAlbum()381 PAlbum::~PAlbum()
382 {
383 }
384 
isAlbumRoot() const385 bool PAlbum::isAlbumRoot() const
386 {
387     return m_isAlbumRootAlbum;
388 }
389 
setCaption(const QString & caption)390 void PAlbum::setCaption(const QString& caption)
391 {
392     m_caption = caption;
393 
394     CoreDbAccess access;
395     access.db()->setAlbumCaption(id(), m_caption);
396 }
397 
setCategory(const QString & category)398 void PAlbum::setCategory(const QString& category)
399 {
400     m_category = category;
401 
402     CoreDbAccess access;
403     access.db()->setAlbumCategory(id(), m_category);
404 }
405 
setDate(const QDate & date)406 void PAlbum::setDate(const QDate& date)
407 {
408     m_date = date;
409 
410     CoreDbAccess access;
411     access.db()->setAlbumDate(id(), m_date);
412 }
413 
albumRootPath() const414 QString PAlbum::albumRootPath() const
415 {
416     return CollectionManager::instance()->albumRootPath(m_albumRootId);
417 }
418 
albumRootLabel() const419 QString PAlbum::albumRootLabel() const
420 {
421     return CollectionManager::instance()->albumRootLabel(m_albumRootId);
422 }
423 
albumRootId() const424 int PAlbum::albumRootId() const
425 {
426     return m_albumRootId;
427 }
428 
caption() const429 QString PAlbum::caption() const
430 {
431     return m_caption;
432 }
433 
category() const434 QString PAlbum::category() const
435 {
436     return m_category;
437 }
438 
date() const439 QDate PAlbum::date() const
440 {
441     return m_date;
442 }
443 
albumPath() const444 QString PAlbum::albumPath() const
445 {
446     return (m_parentPath + m_path);
447 }
448 
databaseUrl() const449 CoreDbUrl PAlbum::databaseUrl() const
450 {
451     return CoreDbUrl::fromAlbumAndName(QString(), albumPath(),
452                                        QUrl::fromLocalFile(albumRootPath()), m_albumRootId);
453 }
454 
prettyUrl() const455 QString PAlbum::prettyUrl() const
456 {
457     QString u = i18n("Albums") + QLatin1Char('/') +
458                                  albumRootLabel() +
459                                  albumPath();
460 
461     if (u.endsWith(QLatin1Char('/')))
462     {
463         u.chop(1);
464     }
465 
466     return u;
467 }
468 
iconId() const469 qlonglong PAlbum::iconId() const
470 {
471     return m_iconId;
472 }
473 
fileUrl() const474 QUrl PAlbum::fileUrl() const
475 {
476     return databaseUrl().fileUrl();
477 }
478 
folderPath() const479 QString PAlbum::folderPath() const
480 {
481     return databaseUrl().fileUrl().toLocalFile();
482 }
483 
484 // --------------------------------------------------------------------------
485 
TAlbum(const QString & title,int id,bool root)486 TAlbum::TAlbum(const QString& title, int id, bool root)
487     : Album(Album::TAG, id, root),
488       m_pid(0),
489       m_iconId(0)
490 {
491     setTitle(title);
492 }
493 
~TAlbum()494 TAlbum::~TAlbum()
495 {
496 }
497 
tagPath(bool leadingSlash) const498 QString TAlbum::tagPath(bool leadingSlash) const
499 {
500     if (isRoot())
501     {
502         return (leadingSlash ? QLatin1String("/") : QLatin1String(""));
503     }
504 
505     QString u;
506 
507     if (parent())
508     {
509         u = (static_cast<TAlbum*>(parent()))->tagPath(leadingSlash);
510 
511         if (!parent()->isRoot())
512         {
513             u += QLatin1Char('/');
514         }
515     }
516 
517     u += title();
518 
519     return u;
520 }
521 
standardIconName() const522 QString TAlbum::standardIconName() const
523 {
524     return (hasProperty(TagPropertyName::person()) ? QLatin1String("smiley")
525                                                    : QLatin1String("tag"));
526 }
527 
prettyUrl() const528 QString TAlbum::prettyUrl() const
529 {
530     return (i18n("Tags") + tagPath(true));
531 }
532 
databaseUrl() const533 CoreDbUrl TAlbum::databaseUrl() const
534 {
535     return CoreDbUrl::fromTagIds(tagIDs());
536 }
537 
tagIDs() const538 QList<int> TAlbum::tagIDs() const
539 {
540     if      (isRoot())
541     {
542         return QList<int>();
543     }
544     else if (parent())
545     {
546         return (static_cast<TAlbum*>(parent())->tagIDs() << id());
547     }
548     else
549     {
550         return (QList<int>() << id());
551     }
552 }
553 
icon() const554 QString TAlbum::icon() const
555 {
556     return m_icon;
557 }
558 
isInternalTag() const559 bool TAlbum::isInternalTag() const
560 {
561     return TagsCache::instance()->isInternalTag(id());
562 }
563 
iconId() const564 qlonglong TAlbum::iconId() const
565 {
566     return m_iconId;
567 }
568 
hasProperty(const QString & key) const569 bool TAlbum::hasProperty(const QString& key) const
570 {
571     return TagsCache::instance()->hasProperty(id(), key);
572 }
573 
property(const QString & key) const574 QString TAlbum::property(const QString& key) const
575 {
576     return TagsCache::instance()->propertyValue(id(), key);
577 }
578 
properties() const579 QMap<QString, QString> TAlbum::properties() const
580 {
581     return TagsCache::instance()->properties(id());
582 }
583 
584 // --------------------------------------------------------------------------
585 
586 int DAlbum::m_uniqueID = 0;
587 
DAlbum(const QDate & date,bool root,Range range)588 DAlbum::DAlbum(const QDate& date, bool root, Range range)
589     : Album(Album::DATE, root ? 0 : ++m_uniqueID, root),
590       m_date(date),
591       m_range(range)
592 {
593     // Set the name of the date album
594 
595     QString dateTitle;
596 
597     if (m_range == Month)
598     {
599         dateTitle = m_date.toString(QLatin1String("MMMM yyyy"));
600     }
601     else
602     {
603         dateTitle = m_date.toString(QLatin1String("yyyy"));
604     }
605 
606     setTitle(dateTitle);
607 }
608 
~DAlbum()609 DAlbum::~DAlbum()
610 {
611 }
612 
date() const613 QDate DAlbum::date() const
614 {
615     return m_date;
616 }
617 
range() const618 DAlbum::Range DAlbum::range() const
619 {
620     return m_range;
621 }
622 
databaseUrl() const623 CoreDbUrl DAlbum::databaseUrl() const
624 {
625     if (m_range == Month)
626     {
627         return CoreDbUrl::fromDateForMonth(m_date);
628     }
629 
630     return CoreDbUrl::fromDateForYear(m_date);
631 }
632 
633 // --------------------------------------------------------------------------
634 
SAlbum(const QString & title,int id,bool root)635 SAlbum::SAlbum(const QString& title, int id, bool root)
636     : Album(Album::SEARCH, id, root),
637       m_searchType(DatabaseSearch::UndefinedType)
638 {
639     setTitle(title);
640 }
641 
~SAlbum()642 SAlbum::~SAlbum()
643 {
644 }
645 
setSearch(DatabaseSearch::Type type,const QString & query)646 void SAlbum::setSearch(DatabaseSearch::Type type, const QString& query)
647 {
648     m_searchType = type;
649     m_query      = query;
650 }
651 
databaseUrl() const652 CoreDbUrl SAlbum::databaseUrl() const
653 {
654     return CoreDbUrl::searchUrl(id());
655 }
656 
query() const657 QString SAlbum::query() const
658 {
659     return m_query;
660 }
661 
searchType() const662 DatabaseSearch::Type SAlbum::searchType() const
663 {
664     return m_searchType;
665 }
666 
isNormalSearch() const667 bool SAlbum::isNormalSearch() const
668 {
669     switch (m_searchType)
670     {
671         case DatabaseSearch::KeywordSearch:
672         case DatabaseSearch::AdvancedSearch:
673         case DatabaseSearch::LegacyUrlSearch:
674             return true;
675 
676         default:
677             return false;
678     }
679 }
680 
isAdvancedSearch() const681 bool SAlbum::isAdvancedSearch() const
682 {
683     return (m_searchType == DatabaseSearch::AdvancedSearch);
684 }
685 
isKeywordSearch() const686 bool SAlbum::isKeywordSearch() const
687 {
688     return (m_searchType == DatabaseSearch::KeywordSearch);
689 }
690 
isTimelineSearch() const691 bool SAlbum::isTimelineSearch() const
692 {
693     return (m_searchType == DatabaseSearch::TimeLineSearch);
694 }
695 
isHaarSearch() const696 bool SAlbum::isHaarSearch() const
697 {
698     return (m_searchType == DatabaseSearch::HaarSearch);
699 }
700 
isMapSearch() const701 bool SAlbum::isMapSearch() const
702 {
703     return (m_searchType == DatabaseSearch::MapSearch);
704 }
705 
isDuplicatesSearch() const706 bool SAlbum::isDuplicatesSearch() const
707 {
708     return (m_searchType == DatabaseSearch::DuplicatesSearch);
709 }
710 
isTemporarySearch() const711 bool SAlbum::isTemporarySearch() const
712 {
713     if (isHaarSearch())
714     {
715         return ((title() == getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch))) ||
716                 (title() == getTemporaryHaarTitle(DatabaseSearch::HaarSketchSearch));
717     }
718 
719     return (title() == getTemporaryTitle(m_searchType));
720 }
721 
displayTitle() const722 QString SAlbum::displayTitle() const
723 {
724     if (isTemporarySearch())
725     {
726         switch (m_searchType)
727         {
728             case DatabaseSearch::TimeLineSearch:
729                 return i18n("Current Timeline Search");
730 
731             case DatabaseSearch::HaarSearch:
732             {
733                 if (title() == getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch))
734                 {
735                     return i18n("Current Fuzzy Image Search");
736                 }
737                 else if (title() == getTemporaryHaarTitle(DatabaseSearch::HaarSketchSearch))
738                 {
739                     return i18n("Current Fuzzy Sketch Search");
740                 }
741 
742                 break;
743             }
744 
745             case DatabaseSearch::MapSearch:
746                 return i18n("Current Map Search");
747 
748             case DatabaseSearch::KeywordSearch:
749             case DatabaseSearch::AdvancedSearch:
750             case DatabaseSearch::LegacyUrlSearch:
751                 return i18n("Current Search");
752 
753             case DatabaseSearch::DuplicatesSearch:
754                 return i18n("Current Duplicates Search");
755 
756             case DatabaseSearch::UndefinedType:
757                 break;
758         }
759     }
760 
761     return title();
762 }
763 
getTemporaryTitle(DatabaseSearch::Type type,DatabaseSearch::HaarSearchType haarType)764 QString SAlbum::getTemporaryTitle(DatabaseSearch::Type type, DatabaseSearch::HaarSearchType haarType)
765 {
766     switch (type)
767     {
768         case DatabaseSearch::TimeLineSearch:
769             return QLatin1String("_Current_Time_Line_Search_");
770 
771         case DatabaseSearch::HaarSearch:
772             return getTemporaryHaarTitle(haarType);
773 
774         case DatabaseSearch::MapSearch:
775             return QLatin1String("_Current_Map_Search_");
776 
777         case DatabaseSearch::KeywordSearch:
778         case DatabaseSearch::AdvancedSearch:
779         case DatabaseSearch::LegacyUrlSearch:
780             return QLatin1String("_Current_Search_View_Search_");
781 
782         case DatabaseSearch::DuplicatesSearch:
783             return QLatin1String("_Current_Duplicates_Search_");
784 
785         default:
786             qCDebug(DIGIKAM_GENERAL_LOG) << "Untreated temporary search type " << type;
787             return QLatin1String("_Current_Unknown_Search_");
788     }
789 }
790 
getTemporaryHaarTitle(DatabaseSearch::HaarSearchType haarType)791 QString SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarSearchType haarType)
792 {
793     switch (haarType)
794     {
795         case DatabaseSearch::HaarImageSearch:
796             return QLatin1String("_Current_Fuzzy_Image_Search_");
797 
798         case DatabaseSearch::HaarSketchSearch:
799             return QLatin1String("_Current_Fuzzy_Sketch_Search_");
800 
801         default:
802             qCDebug(DIGIKAM_GENERAL_LOG) << "Untreated temporary haar search type " << haarType;
803             return QLatin1String("_Current_Unknown_Haar_Search_");
804     }
805 }
806 
807 // --------------------------------------------------------------------------
808 
AlbumIterator(Album * const album)809 AlbumIterator::AlbumIterator(Album* const album)
810     : m_current(album ? album->firstChild() : nullptr),
811       m_root(album)
812 {
813 }
814 
~AlbumIterator()815 AlbumIterator::~AlbumIterator()
816 {
817 }
818 
operator ++()819 AlbumIterator& AlbumIterator::operator++()
820 {
821     if (!m_current)
822     {
823         return *this;
824     }
825 
826     Album* album = m_current->firstChild();
827 
828     if (!album)
829     {
830         while ((album = m_current->next()) == nullptr)
831         {
832             m_current = m_current->parent();
833 
834             if (m_current == m_root)
835             {
836                 // we have reached the root.
837                 // that means no more children
838 
839                 m_current = nullptr;
840                 break;
841             }
842 
843             if (m_current == nullptr)
844             {
845                 break;
846             }
847         }
848     }
849 
850     m_current = album;
851 
852     return *this;
853 }
854 
operator *()855 Album* AlbumIterator::operator*()
856 {
857     return m_current;
858 }
859 
current() const860 Album* AlbumIterator::current() const
861 {
862     return m_current;
863 }
864 
865 } // namespace Digikam
866