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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2007-03-21
0007  * Description : Collection scanning to database - scan utilities.
0008  *
0009  * SPDX-FileCopyrightText: 2005-2006 by Tom Albers <tomalbers at kde dot nl>
0010  * SPDX-FileCopyrightText: 2007-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0011  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "collectionscanner_p.h"
0018 
0019 namespace Digikam
0020 {
0021 
0022 void CollectionScanner::loadNameFilters()
0023 {
0024     if (!d->nameFilters.isEmpty())
0025     {
0026         return;
0027     }
0028 
0029     QStringList imageFilter, audioFilter, videoFilter, ignoreDirectory;
0030 
0031     CoreDbAccess().db()->getFilterSettings(&imageFilter, &videoFilter, &audioFilter);
0032     CoreDbAccess().db()->getIgnoreDirectoryFilterSettings(&ignoreDirectory);
0033 
0034     // three sets to find category of a file
0035 
0036     d->imageFilterSet  = QSet<QString>(imageFilter.begin(),     imageFilter.end());
0037     d->audioFilterSet  = QSet<QString>(audioFilter.begin(),     audioFilter.end());
0038     d->videoFilterSet  = QSet<QString>(videoFilter.begin(),     videoFilter.end());
0039     d->ignoreDirectory = QSet<QString>(ignoreDirectory.begin(), ignoreDirectory.end());
0040 
0041     d->nameFilters     = d->imageFilterSet + d->audioFilterSet + d->videoFilterSet;
0042 }
0043 
0044 void CollectionScanner::mainEntryPoint(bool complete)
0045 {
0046     loadNameFilters();
0047     d->recordHistoryIds = !complete;
0048 }
0049 
0050 void CollectionScanner::safelyRemoveAlbums(const QList<int>& albumIds)
0051 {
0052     // Remove the items (orphan items, detach them from the album, but keep entries for a certain time)
0053     // Make album orphan (no album root, keep entries until next application start)
0054 
0055     CoreDbAccess access;
0056     CoreDbTransaction transaction(&access);
0057 
0058     Q_FOREACH (int albumId, albumIds)
0059     {
0060         QList<qlonglong> ids = access.db()->getItemIDsInAlbum(albumId);
0061         access.db()->removeItemsFromAlbum(albumId, ids);
0062         access.db()->makeStaleAlbum(albumId);
0063         itemsWereRemoved(ids);
0064     }
0065 }
0066 
0067 int CollectionScanner::checkAlbum(const CollectionLocation& location, const QString& album)
0068 {
0069     // get album id if album exists
0070 
0071     int albumID = CoreDbAccess().db()->getAlbumForPath(location.id(), album, false);
0072 
0073     d->establishedSourceAlbums.remove(albumID);
0074 
0075     // create if necessary
0076 
0077     if (albumID == -1)
0078     {
0079         QFileInfo fi(location.albumRootPath() + album);
0080         albumID = CoreDbAccess().db()->addAlbum(location.id(), album, QString(), fi.lastModified().date(), QString());
0081 
0082         // have album this one was copied from?
0083 
0084         if (d->hints)
0085         {
0086             CollectionScannerHints::Album src;
0087             {
0088                 QReadLocker locker(&d->hints->lock);
0089                 src = d->hints->albumHints.value(CollectionScannerHints::DstPath(location.id(), album));
0090             }
0091 
0092             if (!src.isNull())
0093             {
0094                 //qCDebug(DIGIKAM_DATABASE_LOG) << "Identified album" << src.albumId << "as source of new album" << fi.filePath();
0095 
0096                 CoreDbAccess().db()->copyAlbumProperties(src.albumId, albumID);
0097                 d->establishedSourceAlbums[albumID] = src.albumId;
0098             }
0099         }
0100     }
0101 
0102     return albumID;
0103 }
0104 
0105 void CollectionScanner::copyFileProperties(const ItemInfo& source, const ItemInfo& d)
0106 {
0107     if (source.isNull() || d.isNull())
0108     {
0109         return;
0110     }
0111 
0112     ItemInfo dest(d);
0113     CoreDbOperationGroup group;
0114 
0115     qCDebug(DIGIKAM_DATABASE_LOG) << "Copying properties from" << source.id() << "to" << dest.id();
0116 
0117     // Rating, creation dates
0118 
0119     DatabaseFields::ItemInformation imageInfoFields = DatabaseFields::Rating       |
0120                                                       DatabaseFields::CreationDate |
0121                                                       DatabaseFields::DigitizationDate;
0122 
0123     QVariantList imageInfos = CoreDbAccess().db()->getItemInformation(source.id(), imageInfoFields);
0124 
0125     if (!imageInfos.isEmpty())
0126     {
0127         CoreDbAccess().db()->changeItemInformation(dest.id(), imageInfos, imageInfoFields);
0128     }
0129 
0130     // Copy public tags
0131 
0132     Q_FOREACH (int tagId, TagsCache::instance()->publicTags(source.tagIds()))
0133     {
0134         dest.setTag(tagId);
0135     }
0136 
0137     // Copy color and pick label
0138 
0139     dest.setPickLabel(source.pickLabel());
0140     dest.setColorLabel(source.colorLabel());
0141 
0142     // important: skip other internal tags, such a history tags. Therefore CoreDB::copyImageTags is not to be used.
0143 
0144     // GPS data
0145 
0146     QVariantList positionData = CoreDbAccess().db()->getItemPosition(source.id(), DatabaseFields::ItemPositionsAll);
0147 
0148     if (!positionData.isEmpty())
0149     {
0150         CoreDbAccess().db()->addItemPosition(dest.id(), positionData, DatabaseFields::ItemPositionsAll);
0151     }
0152 
0153     // Comments
0154 
0155     {
0156         CoreDbAccess access;
0157         ItemComments commentsSource(access, source.id());
0158         ItemComments commentsDest(access, dest.id());
0159         commentsDest.replaceFrom(commentsSource);
0160         commentsDest.apply(access);
0161     }
0162 
0163     // Copyright info
0164 
0165     ItemCopyright copyrightDest(dest.id());
0166     copyrightDest.replaceFrom(ItemCopyright(source.id()));
0167 
0168     // Image Properties
0169 
0170     CoreDbAccess().db()->copyImageProperties(source.id(), dest.id());
0171 }
0172 
0173 void CollectionScanner::itemsWereRemoved(const QList<qlonglong>& removedIds)
0174 {
0175     // set time stamp
0176 
0177     d->removedItems();
0178 
0179     // manage relations
0180 
0181     QList<qlonglong> relatedImages = CoreDbAccess().db()->getOneRelatedImageEach(removedIds, DatabaseRelation::DerivedFrom);
0182     qCDebug(DIGIKAM_DATABASE_LOG) << "Removed items:" << removedIds << "related items:" << relatedImages;
0183 
0184     if (d->recordHistoryIds)
0185     {
0186         Q_FOREACH (const qlonglong& id, relatedImages)
0187         {
0188             d->needTaggingHistorySet << id;
0189         }
0190     }
0191     else
0192     {
0193         int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph());
0194         CoreDbAccess().db()->addTagsToItems(relatedImages, QList<int>() << needTaggingTag);
0195     }
0196 }
0197 
0198 int CollectionScanner::countItemsInFolder(const QString& path)
0199 {
0200     QDir dir(path);
0201 
0202     if (!dir.exists() || !dir.isReadable())
0203     {
0204         return 0;
0205     }
0206 
0207     CollectionLocation location = CollectionManager::instance()->locationForPath(path);
0208 
0209     if (!location.isNull() && databaseInitialScanDone())
0210     {
0211         QString album = CollectionManager::instance()->album(path);
0212         int albumID   = CoreDbAccess().db()->getAlbumForPath(location.id(), album, false);
0213 
0214         if (albumID != -1)
0215         {
0216             QPair<int, int> numberPair = CoreDbAccess().db()->
0217                                          getNumberOfAllItemsAndAlbums(albumID);
0218 
0219             if (numberPair.first)
0220             {
0221                 return (numberPair.first + numberPair.second);
0222             }
0223         }
0224     }
0225 
0226     // Also count the current folder
0227 
0228     int items = 1;
0229 
0230     QDirIterator it(dir.path(), QDir::Dirs    |
0231                                 QDir::Files   |
0232                                 QDir::NoDotAndDotDot,
0233                                 QDirIterator::Subdirectories);
0234 
0235     while (it.hasNext())
0236     {
0237         it.next();
0238         ++items;
0239     }
0240 
0241     return items;
0242 }
0243 
0244 DatabaseItem::Category CollectionScanner::category(const QFileInfo& info)
0245 {
0246     QString suffix = info.suffix().toLower();
0247 
0248     if      (d->imageFilterSet.contains(suffix))
0249     {
0250         return DatabaseItem::Image;
0251     }
0252     else if (d->audioFilterSet.contains(suffix))
0253     {
0254         return DatabaseItem::Audio;
0255     }
0256     else if (d->videoFilterSet.contains(suffix))
0257     {
0258         return DatabaseItem::Video;
0259     }
0260     else
0261     {
0262         return DatabaseItem::Other;
0263     }
0264 }
0265 
0266 void CollectionScanner::markDatabaseAsScanned()
0267 {
0268     CoreDbAccess access;
0269     access.db()->setSetting(QLatin1String("Scanned"), QDateTime::currentDateTime().toString(Qt::ISODate));
0270 }
0271 
0272 void CollectionScanner::updateRemovedItemsTime()
0273 {
0274     // Called after a complete or partial scan finishes, to write the value
0275     // held in d->removedItemsTime to the database
0276 
0277     if (!d->removedItemsTime.isNull())
0278     {
0279         CoreDbAccess().db()->setSetting(QLatin1String("RemovedItemsTime"), d->removedItemsTime.toString(Qt::ISODate));
0280         d->removedItemsTime = QDateTime();
0281     }
0282 }
0283 
0284 void CollectionScanner::incrementDeleteRemovedCompleteScanCount()
0285 {
0286     CoreDbAccess access;
0287     int count = access.db()->getSetting(QLatin1String("DeleteRemovedCompleteScanCount")).toInt();
0288     ++count;
0289     access.db()->setSetting(QLatin1String("DeleteRemovedCompleteScanCount"), QString::number(count));
0290 }
0291 
0292 void CollectionScanner::resetDeleteRemovedSettings()
0293 {
0294     CoreDbAccess().db()->setSetting(QLatin1String("RemovedItemsTime"),               QString());
0295     CoreDbAccess().db()->setSetting(QLatin1String("DeleteRemovedTime"),              QDateTime::currentDateTime().toString(Qt::ISODate));
0296     CoreDbAccess().db()->setSetting(QLatin1String("DeleteRemovedCompleteScanCount"), QString::number(0));
0297 }
0298 
0299 bool CollectionScanner::checkDeleteRemoved()
0300 {
0301     // returns true if removed items shall be deleted
0302 
0303     CoreDbAccess access;
0304 
0305     // retrieve last time an item was removed (not deleted, but set to status removed)
0306 
0307     QString removedItemsTimeString = access.db()->getSetting(QLatin1String("RemovedItemsTime"));
0308 
0309     if (removedItemsTimeString.isNull())
0310     {
0311         return false;
0312     }
0313 
0314     // retrieve last time removed items were (definitely) deleted from db
0315 
0316     QString deleteRemovedTimeString = access.db()->getSetting(QLatin1String("DeleteRemovedTime"));
0317     QDateTime removedItemsTime, deleteRemovedTime;
0318 
0319     if (!removedItemsTimeString.isNull())
0320     {
0321         removedItemsTime = QDateTime::fromString(removedItemsTimeString, Qt::ISODate);
0322     }
0323 
0324     if (!deleteRemovedTimeString.isNull())
0325     {
0326         deleteRemovedTime = QDateTime::fromString(deleteRemovedTimeString, Qt::ISODate);
0327     }
0328 
0329     QDateTime now     = QDateTime::currentDateTime();
0330 
0331     // retrieve number of complete collection scans since the last time that removed items were deleted
0332 
0333     int completeScans = access.db()->getSetting(QLatin1String("DeleteRemovedCompleteScanCount")).toInt();
0334 
0335     // No removed items? No need to delete any
0336 
0337     if (!removedItemsTime.isValid())
0338     {
0339         return false;
0340     }
0341 
0342     // give at least a week between removed item deletions
0343 
0344     if (deleteRemovedTime.isValid())
0345     {
0346         if (deleteRemovedTime.daysTo(now) <= 7)
0347         {
0348             return false;
0349         }
0350     }
0351 
0352     // Now look at time since items were removed, and the number of complete scans
0353     // since removed items were deleted. Values arbitrarily chosen.
0354 
0355     int daysPast = removedItemsTime.daysTo(now);
0356 
0357     return (
0358             ((daysPast > 7)  && (completeScans > 2)) ||
0359             ((daysPast > 30) && (completeScans > 0)) ||
0360             (completeScans > 30)
0361            );
0362 }
0363 
0364 // ------------------------------------------------------------------------------------------
0365 
0366 #if 0
0367 
0368 void CollectionScanner::scanForStaleAlbums()
0369 {
0370     QStringList albumRootPaths = CollectionManager::instance()->allAvailableAlbumRootPaths();
0371 
0372     for (QStringList::const_iterator it = albumRootPaths.constBegin() ;
0373          it != albumRootPaths.constEnd() ; ++it)
0374     {
0375         scanForStaleAlbums(*it);
0376     }
0377 }
0378 
0379 void CollectionScanner::scanForStaleAlbums(const QString& albumRoot)
0380 {
0381     Q_UNUSED(albumRoot);
0382     QList<AlbumShortInfo> albumList = CoreDbAccess().db()->getAlbumShortInfos();
0383     QList<AlbumShortInfo> toBeDeleted;
0384 
0385     QList<AlbumShortInfo>::const_iterator it;
0386 
0387     for (it = albumList.constBegin() ; it != albumList.constEnd() ; ++it)
0388     {
0389         QFileInfo fileInfo((*it).albumRoot + (*it).url);
0390 
0391         if (!fileInfo.exists() || !fileInfo.isDir())
0392         {
0393             m_foldersToBeDeleted << (*it);
0394         }
0395     }
0396 }
0397 
0398 QStringList CollectionScanner::formattedListOfStaleAlbums()
0399 {
0400     QStringList list;
0401     QList<AlbumShortInfo>::const_iterator it;
0402 
0403     for (it = m_foldersToBeDeleted.constBegin() ;
0404          it != m_foldersToBeDeleted.constEnd() ; ++it)
0405     {
0406         list << (*it).url;
0407     }
0408 
0409     return list;
0410 }
0411 
0412 void CollectionScanner::removeStaleAlbums()
0413 {
0414     CoreDbAccess access;
0415     CoreDbTransaction transaction(&access);
0416     QList<AlbumShortInfo>::const_iterator it;
0417 
0418     for (it = m_foldersToBeDeleted.constBegin() ; it != m_foldersToBeDeleted.constEnd() ; ++it)
0419     {
0420         qCDebug(DIGIKAM_DATABASE_LOG) << "Removing album " << (*it).albumRoot + QLatin1Char('/') + (*it).url;
0421         access.db()->deleteAlbum((*it).id);
0422     }
0423 }
0424 
0425 QStringList CollectionScanner::formattedListOfStaleFiles()
0426 {
0427     QStringList listToBeDeleted;
0428 
0429     CoreDbAccess access;
0430     QList<QPair<QString, int> >::const_iterator it;
0431 
0432     for (it = m_filesToBeDeleted.constBegin() ; it != m_filesToBeDeleted.constEnd() ; ++it)
0433     {
0434         QString location = QLatin1String(" (") + access.db()->getAlbumPath((*it).second) + QLatin1Char(')');
0435 
0436         listToBeDeleted.append((*it).first + location);
0437     }
0438 
0439     return listToBeDeleted;
0440 }
0441 
0442 void CollectionScanner::removeStaleFiles()
0443 {
0444     CoreDbAccess access;
0445     CoreDbTransaction transaction(&access);
0446     QList<QPair<QString, int> >::const_iterator it;
0447 
0448     for (it = m_filesToBeDeleted.constBegin() ; it != m_filesToBeDeleted.constEnd() ; ++it)
0449     {
0450         qCDebug(DIGIKAM_DATABASE_LOG) << "Removing: " << (*it).first << " in " << (*it).second;
0451         access.db()->deleteItem( (*it).second, (*it).first );
0452     }
0453 }
0454 
0455 void CollectionScanner::scanAlbums()
0456 {
0457     QStringList albumRootPaths = CollectionManager::instance()->allAvailableAlbumRootPaths();
0458     int count = 0;
0459 
0460     for (QStringList::const_iterator it = albumRootPaths.constBegin() ; it != albumRootPaths.constEnd() ; ++it)
0461     {
0462         count += countItemsInFolder(*it);
0463     }
0464 
0465     Q_EMIT totalFilesToScan(count);
0466 
0467     for (QStringList::const_iterator it = albumRootPaths.constBegin() ; it != albumRootPaths.constEnd() ; ++it)
0468     {
0469         QDir dir(*it);
0470         QStringList fileList(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot));
0471         CoreDbTransaction transaction;
0472 
0473         Q_FOREACH (const QString& dir, fileList)
0474         {
0475             scanAlbum(*it, QLatin1Char('/') + dir);
0476         }
0477     }
0478 }
0479 
0480 void CollectionScanner::scan(const QString& folderPath)
0481 {
0482     CollectionManager* const manager = CollectionManager::instance();
0483     QUrl url;
0484     url.setPath(folderPath);
0485     QString albumRoot = manager->albumRootPath(url);
0486     QString album     = manager->album(url);
0487 
0488     if (albumRoot.isNull())
0489     {
0490         qCWarning(DIGIKAM_DATABASE_LOG) << "scanAlbums(QString): folder " << folderPath << " not found in collection.";
0491         return;
0492     }
0493 
0494     scan(albumRoot, album);
0495 }
0496 
0497 void CollectionScanner::scan(const QString& albumRoot, const QString& album)
0498 {
0499     // Step one: remove invalid albums
0500 
0501     scanForStaleAlbums(albumRoot);
0502     removeStaleAlbums();
0503 
0504     Q_EMIT totalFilesToScan(countItemsInFolder(albumRoot + album));
0505 
0506     // Step two: Scan directories
0507 
0508     if (album == QLatin1String("/"))
0509     {
0510         // Don't scan files under album root, only descend into directories (?)
0511 
0512         QDir dir(albumRoot + album);
0513         QStringList fileList(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot));
0514 
0515         CoreDbTransaction transaction;
0516 
0517         for (QStringList::const_iterator fileIt = fileList.constBegin() ; fileIt != fileList.constEnd() ; ++fileIt)
0518         {
0519             scanAlbum(albumRoot, QLatin1Char('/') + (*fileIt));
0520         }
0521     }
0522     else
0523     {
0524         CoreDbTransaction transaction;
0525         scanAlbum(albumRoot, album);
0526     }
0527 
0528     // Step three: Remove invalid files
0529 
0530     removeStaleFiles();
0531 }
0532 
0533 void CollectionScanner::scanAlbum(const QString& filePath)
0534 {
0535     QUrl url;
0536     url.setPath(filePath);
0537     scanAlbum(CollectionManager::instance()->albumRootPath(url), CollectionManager::instance()->album(url));
0538 }
0539 
0540 void CollectionScanner::scanAlbum(const QString& albumRoot, const QString& album)
0541 {
0542     // + Adds album if it does not yet exist in the db.
0543     // + Recursively scans subalbums of album.
0544     // + Adds files if they do not yet exist in the db.
0545     // + Adds stale files from the db to m_filesToBeDeleted
0546     // - Does not add stale albums to m_foldersToBeDeleted.
0547 
0548     QDir dir( albumRoot + album );
0549 
0550     if (!dir.exists() || !dir.isReadable())
0551     {
0552         qCWarning(DIGIKAM_DATABASE_LOG) << "Folder does not exist or is not readable: " << dir.path();
0553 
0554         return;
0555     }
0556 
0557     Q_EMIT startScanningAlbum(albumRoot, album);
0558 
0559     // get album id if album exists
0560 
0561     int albumID = CoreDbAccess().db()->getAlbumForPath(albumRoot, album, false);
0562 
0563     if (albumID == -1)
0564     {
0565         QFileInfo fi(albumRoot + album);
0566         albumID = CoreDbAccess().db()->addAlbum(albumRoot, album, QString(), fi.lastModified().date(), QString());
0567     }
0568 
0569     QStringList filesInAlbum = CoreDbAccess().db()->getItemNamesInAlbum( albumID );
0570 
0571     QSet<QString> filesFoundInDB;
0572 
0573     for (QStringList::const_iterator it = filesInAlbum.constBegin() ;
0574          it != filesInAlbum.constEnd() ; ++it)
0575     {
0576         filesFoundInDB << *it;
0577     }
0578 
0579     const QFileInfoList list = dir.entryInfoList(m_nameFilters, QDir::AllDirs | QDir::Files  | QDir::NoDotAndDotDot /*not CaseSensitive*/);
0580 
0581     QFileInfoList::const_iterator fi;
0582 
0583     for (fi = list.constBegin() ; fi != list.constEnd() ; ++fi)
0584     {
0585         if      (fi->isFile())
0586         {
0587             if      (filesFoundInDB.contains(fi->fileName()) )
0588             {
0589                 filesFoundInDB.remove(fi->fileName());
0590             }
0591             else if (fi->completeSuffix() == QLatin1String("digikamtempfile.tmp"))
0592             {
0593                 // ignore temp files we created ourselves
0594                 continue;
0595             }
0596             else
0597             {
0598                 qCDebug(DIGIKAM_DATABASE_LOG) << "Adding item " << fi->fileName();
0599                 addItem(albumID, albumRoot, album, fi->fileName());
0600             }
0601         }
0602         else if (fi->isDir())
0603         {
0604             scanAlbum( albumRoot, album + QLatin1Char('/') + fi->fileName() );
0605         }
0606     }
0607 
0608     // Removing items from the db which we did not see on disk.
0609 
0610     if (!filesFoundInDB.isEmpty())
0611     {
0612         QSetIterator<QString> it(filesFoundInDB);
0613 
0614         while (it.hasNext())
0615         {
0616             QPair<QString, int> pair(it.next(),albumID);
0617 
0618             if (m_filesToBeDeleted.indexOf(pair) == -1)
0619             {
0620                 m_filesToBeDeleted << pair;
0621             }
0622         }
0623     }
0624 
0625     Q_EMIT finishedScanningAlbum(albumRoot, album, list.count());
0626 }
0627 
0628 void CollectionScanner::updateItemsWithoutDate()
0629 {
0630     QStringList urls  = CoreDbAccess().db()->getAllItemURLsWithoutDate();
0631 
0632     Q_EMIT totalFilesToScan(urls.count());
0633 
0634     QString albumRoot = CoreDbAccess::albumRoot();
0635 
0636     {
0637         CoreDbTransaction transaction;
0638 
0639         for (QStringList::const_iterator it = urls.constBegin() ; it != urls.constEnd() ; ++it)
0640         {
0641             Q_EMIT scanningFile(*it);
0642 
0643             QFileInfo fi(*it);
0644             QString albumURL = fi.path();
0645             albumURL         = QDir::cleanPath(albumURL.remove(albumRoot));
0646             int albumID      = CoreDbAccess().db()->getAlbumForPath(albumRoot, albumURL);
0647 
0648             if (albumID <= 0)
0649             {
0650                 qCWarning(DIGIKAM_DATABASE_LOG) << "Album ID == -1: " << albumURL;
0651             }
0652 
0653             if (fi.exists())
0654             {
0655                 CollectionScanner::updateItemDate(albumID, albumRoot, albumURL, fi.fileName());
0656             }
0657             else
0658             {
0659                 QPair<QString, int> pair(fi.fileName(), albumID);
0660 
0661                 if (m_filesToBeDeleted.indexOf(pair) == -1)
0662                 {
0663                     m_filesToBeDeleted << pair;
0664                 }
0665             }
0666         }
0667     }
0668 }
0669 
0670 int CollectionScanner::countItemsInFolder(const QString& directory)
0671 {
0672     int items = 0;
0673 
0674     QDir dir(directory);
0675 
0676     if (!dir.exists() || !dir.isReadable())
0677     {
0678         return 0;
0679     }
0680 
0681     QFileInfoList list = dir.entryInfoList();
0682 
0683     items += list.count();
0684 
0685     QFileInfoList::const_iterator fi;
0686 
0687     for (fi = list.constBegin() ; fi != list.constEnd() ; ++fi)
0688     {
0689         if (fi->isDir()                            &&
0690             (fi->fileName() != QLatin1String(".")) &&
0691             (fi->fileName() != QLatin1String("..")))
0692         {
0693             items += countItemsInFolder( fi->filePath() );
0694         }
0695     }
0696 
0697     return items;
0698 }
0699 
0700 void CollectionScanner::markDatabaseAsScanned()
0701 {
0702     CoreDbAccess access;
0703     access.db()->setSetting("Scanned", QDateTime::currentDateTime().toString(Qt::ISODate));
0704 }
0705 
0706 // ------------------- Tools ------------------------
0707 
0708 void CollectionScanner::addItem(int albumID, const QString& albumRoot,
0709                                 const QString& album, const QString& fileName)
0710 {
0711     CoreDbAccess access;
0712     addItem(access, albumID, albumRoot, album, fileName);
0713 }
0714 
0715 void CollectionScanner::addItem(Digikam::CoreDbAccess& access, int albumID,
0716                                 const QString& albumRoot, const QString& album,
0717                                 const QString& fileName)
0718 {
0719     QString filePath = albumRoot + album + QLatin1Char('/') + fileName;
0720 
0721     QString     comment;
0722     QStringList keywords;
0723     QDateTime   datetime;
0724     int         rating;
0725 
0726     QScopedPointer<DMetadata> metadata(new DMetadata(filePath));
0727 
0728     // Try to get comments from image :
0729     // In first, from standard JPEG comments, or
0730     // In second, from EXIF comments tag, or
0731     // In third, from IPTC comments tag.
0732 
0733     comment  = metadata->getImageComment();
0734 
0735     // Try to get date and time from image :
0736     // In first, from EXIF date & time tags, or
0737     // In second, from IPTC date & time tags.
0738 
0739     datetime = metadata->getItemDateTime();
0740 
0741     // Try to get image rating from IPTC Urgency tag
0742     // else use file system time stamp.
0743     rating   = metadata->getItemRating();
0744 
0745     if (!datetime.isValid())
0746     {
0747         QFileInfo info(filePath);
0748         datetime = info.lastModified();
0749     }
0750 
0751     // Try to get image tags from IPTC keywords tags.
0752 
0753     metadata->getItemTagsPath(keywords);
0754 
0755     access.db()->addItem(albumID, fileName, datetime, comment, rating, keywords);
0756 }
0757 
0758 void CollectionScanner::updateItemDate(int albumID, const QString& albumRoot,
0759                                        const QString& album, const QString& fileName)
0760 {
0761     CoreDbAccess access;
0762     updateItemDate(access, albumID, albumRoot, album, fileName);
0763 }
0764 
0765 void CollectionScanner::updateItemDate(Digikam::CoreDbAccess& access, int albumID,
0766                                        const QString& albumRoot, const QString& album,
0767                                        const QString& fileName)
0768 {
0769     QString filePath = albumRoot + album + QLatin1Char('/') + fileName;
0770 
0771     QDateTime datetime;
0772 
0773     QScopedPointer<DMetadata> metadata(new DMetadata(filePath));
0774 
0775     // Trying to get date and time from image :
0776     // In first, from EXIF date & time tags, or
0777     // In second, from IPTC date & time tags.
0778 
0779     datetime = metadata->getItemDateTime();
0780 
0781     if (!datetime.isValid())
0782     {
0783         QFileInfo info(filePath);
0784         datetime = info.lastModified();
0785     }
0786 
0787     access.db()->setItemDate(albumID, fileName, datetime);
0788 }
0789 
0790 #endif // 0
0791 
0792 } // namespace Digikam