File indexing completed on 2024-04-28 04:18:53

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2008 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "timeutils.h"
0023 
0024 // STL
0025 #include <memory>
0026 
0027 // Qt
0028 #include <QDateTime>
0029 
0030 // KF
0031 #include <KFileItem>
0032 
0033 // Exiv2
0034 #include <exiv2/exiv2.hpp>
0035 
0036 // Local
0037 #include "gwenview_lib_debug.h"
0038 #include <lib/exiv2imageloader.h>
0039 #include <lib/urlutils.h>
0040 
0041 namespace Gwenview
0042 {
0043 namespace TimeUtils
0044 {
0045 static Exiv2::ExifData::const_iterator findDateTimeKey(const Exiv2::ExifData &exifData)
0046 {
0047     // Ordered list of keys to try
0048     static QList<Exiv2::ExifKey> lst = QList<Exiv2::ExifKey>() << Exiv2::ExifKey("Exif.Photo.DateTimeOriginal") << Exiv2::ExifKey("Exif.Image.DateTimeOriginal")
0049                                                                << Exiv2::ExifKey("Exif.Photo.DateTimeDigitized") << Exiv2::ExifKey("Exif.Image.DateTime");
0050 
0051     Exiv2::ExifData::const_iterator it, end = exifData.end();
0052     for (const Exiv2::ExifKey &key : qAsConst(lst)) {
0053         it = exifData.findKey(key);
0054         if (it != end) {
0055             return it;
0056         }
0057     }
0058     return end;
0059 }
0060 
0061 struct CacheItem {
0062     QDateTime fileMTime;
0063     QDateTime realTime;
0064 
0065     void update(const KFileItem &fileItem)
0066     {
0067         QDateTime time = fileItem.time(KFileItem::ModificationTime);
0068         if (fileMTime == time) {
0069             return;
0070         }
0071 
0072         fileMTime = time;
0073 
0074         if (!updateFromExif(fileItem.url())) {
0075             realTime = time;
0076         }
0077     }
0078 
0079     bool updateFromExif(const QUrl &url)
0080     {
0081         if (!UrlUtils::urlIsFastLocalFile(url)) {
0082             return false;
0083         }
0084         const QString path = url.path();
0085         Exiv2ImageLoader loader;
0086 
0087         if (!loader.load(path)) {
0088             return false;
0089         }
0090         std::unique_ptr<Exiv2::Image> img(loader.popImage().release());
0091         try {
0092             Exiv2::ExifData exifData = img->exifData();
0093             if (exifData.empty()) {
0094                 return false;
0095             }
0096             auto it = findDateTimeKey(exifData);
0097             if (it == exifData.end()) {
0098                 qCWarning(GWENVIEW_LIB_LOG) << "No date in exif header of" << path;
0099                 return false;
0100             }
0101 
0102             std::ostringstream stream;
0103             stream << *it;
0104             const QString value = QString::fromLocal8Bit(stream.str().c_str());
0105 
0106             const QDateTime dt = QDateTime::fromString(value, QStringLiteral("yyyy:MM:dd hh:mm:ss"));
0107             if (!dt.isValid()) {
0108                 qCWarning(GWENVIEW_LIB_LOG) << "Invalid date in exif header of" << path;
0109                 return false;
0110             }
0111 
0112             realTime = dt;
0113             return true;
0114         } catch (const Exiv2::Error &error) {
0115             qCWarning(GWENVIEW_LIB_LOG) << "Failed to read date from exif header of" << path << ". Error:" << error.what();
0116             return false;
0117         }
0118     }
0119 };
0120 
0121 using Cache = QHash<QUrl, CacheItem>;
0122 
0123 QDateTime dateTimeForFileItem(const KFileItem &fileItem, CachePolicy cachePolicy)
0124 {
0125     if (cachePolicy == SkipCache) {
0126         CacheItem item;
0127         item.update(fileItem);
0128         return item.realTime;
0129     }
0130 
0131     static Cache cache;
0132     const QUrl url = fileItem.targetUrl();
0133 
0134     Cache::iterator it = cache.find(url);
0135     if (it == cache.end()) {
0136         it = cache.insert(url, CacheItem());
0137     }
0138 
0139     it.value().update(fileItem);
0140     return it.value().realTime;
0141 }
0142 
0143 } // namespace
0144 
0145 } // namespace