File indexing completed on 2024-04-28 15:39:58
0001 /* SPDX-FileCopyrightText: 2018 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0002 SPDX-FileCopyrightText: 2007-2010 Tuomas Suutari <thsuut@utu.fi> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "MD5.h" 0008 0009 #include <kpabase/FileName.h> 0010 0011 #include <QCryptographicHash> 0012 #include <QFile> 0013 #include <QHash> 0014 #include <QIODevice> 0015 #include <QMutex> 0016 0017 static QMutex s_MD5CacheMutex; 0018 static QHash<DB::FileName, DB::MD5> s_MD5Cache; 0019 0020 DB::MD5::MD5() 0021 : m_isNull(true) 0022 , m_v0(0) 0023 , m_v1(0) 0024 { 0025 } 0026 0027 DB::MD5::MD5(const QString &md5str) 0028 : m_isNull(md5str.isEmpty()) 0029 , m_v0(md5str.leftRef(16).toULongLong(nullptr, 16)) 0030 , m_v1(md5str.midRef(16, 16).toULongLong(nullptr, 16)) 0031 { 0032 } 0033 0034 bool DB::MD5::isNull() const 0035 { 0036 return m_isNull; 0037 } 0038 0039 DB::MD5 &DB::MD5::operator=(const QString &md5str) 0040 { 0041 if (md5str.isEmpty()) { 0042 m_isNull = true; 0043 } else { 0044 m_isNull = false; 0045 m_v0 = md5str.leftRef(16).toULongLong(nullptr, 16); 0046 m_v1 = md5str.midRef(16, 16).toULongLong(nullptr, 16); 0047 } 0048 return *this; 0049 } 0050 0051 QString DB::MD5::toHexString() const 0052 { 0053 QString res; 0054 static QChar ZERO(QChar::fromLatin1('0')); 0055 if (!isNull()) { 0056 res += QString::number(m_v0, 16).rightJustified(16, ZERO); 0057 res += QString::number(m_v1, 16).rightJustified(16, ZERO); 0058 } 0059 return res; 0060 } 0061 0062 bool DB::MD5::operator==(const DB::MD5 &other) const 0063 { 0064 if (isNull() || other.isNull()) 0065 return isNull() == other.isNull(); 0066 0067 return (m_v0 == other.m_v0 && m_v1 == other.m_v1); 0068 } 0069 0070 bool DB::MD5::operator!=(const DB::MD5 &other) const 0071 { 0072 return !(*this == other); 0073 } 0074 0075 bool DB::MD5::operator<(const DB::MD5 &other) const 0076 { 0077 if (isNull() || other.isNull()) 0078 return isNull() && !other.isNull(); 0079 0080 return (m_v0 < other.m_v0 || (m_v0 == other.m_v0 && (m_v1 < other.m_v1))); 0081 } 0082 0083 void DB::MD5::resetMD5Cache() 0084 { 0085 QMutexLocker locker(&s_MD5CacheMutex); 0086 s_MD5Cache.clear(); 0087 } 0088 0089 namespace 0090 { 0091 // Determined experimentally to yield best results (on Seagate 2TB 2.5" disk, 0092 // 5400 RPM). Performance is very similar at 524288. Above that, performance 0093 // was significantly worse. Below that, performance also deteriorated. 0094 // This assumes use of one image scout thread (see DB/ImageScout.cpp). Without 0095 // a scout thread, performance was about 10-15% worse. 0096 constexpr int MD5_BUFFER_SIZE = 262144; 0097 } 0098 0099 DB::MD5 DB::MD5Sum(const DB::FileName &fileName) 0100 { 0101 QMutexLocker locker(&s_MD5CacheMutex); 0102 if (s_MD5Cache.contains(fileName)) { 0103 return s_MD5Cache[fileName]; 0104 } 0105 locker.unlock(); 0106 // It's possible that the checksum will be added between now 0107 // and when we add it, but as long as the file contents don't change 0108 // during that interval, the checksums will match. 0109 // Holding the lock while the checksum is being computed will 0110 // defeat the whole purpose. 0111 DB::MD5 checksum; 0112 QFile file(fileName.absolute()); 0113 if (file.open(QIODevice::ReadOnly)) { 0114 QCryptographicHash md5calculator(QCryptographicHash::Md5); 0115 while (!file.atEnd()) { 0116 QByteArray md5Buffer(file.read(MD5_BUFFER_SIZE)); 0117 md5calculator.addData(md5Buffer); 0118 } 0119 file.close(); 0120 checksum = DB::MD5(QString::fromLatin1(md5calculator.result().toHex())); 0121 } 0122 locker.relock(); 0123 s_MD5Cache[fileName] = checksum; 0124 return checksum; 0125 } 0126 0127 void DB::PreloadMD5Sum(const DB::FileName &fileName) 0128 { 0129 (void)MD5Sum(fileName); 0130 }