File indexing completed on 2024-11-24 04:44:35
0001 /* 0002 SPDX-FileCopyrightText: 2008 Bertjan Broeksema <broeksema@kde.org> 0003 SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "singlefileresourcebase.h" 0009 0010 #include <Akonadi/ChangeRecorder> 0011 #include <Akonadi/EntityDisplayAttribute> 0012 #include <Akonadi/ItemFetchScope> 0013 0014 #include <KDirWatch> 0015 #include <KIO/Job> 0016 #include <KLocalizedString> 0017 #include <QDebug> 0018 #include <kio/global.h> 0019 0020 #include <KConfigGroup> 0021 0022 #include <QCryptographicHash> 0023 #include <QDir> 0024 #include <QStandardPaths> 0025 #include <QTimer> 0026 0027 Q_DECLARE_METATYPE(QEventLoopLocker *) 0028 0029 using namespace Akonadi; 0030 0031 SingleFileResourceBase::SingleFileResourceBase(const QString &id) 0032 : ResourceBase(id) 0033 { 0034 connect(this, &SingleFileResourceBase::reloadConfiguration, this, [this]() { 0035 applyConfigurationChanges(); 0036 reloadFile(); 0037 synchronizeCollectionTree(); 0038 }); 0039 QTimer::singleShot(0, this, [this]() { 0040 readFile(); 0041 }); 0042 0043 changeRecorder()->itemFetchScope().fetchFullPayload(); 0044 changeRecorder()->fetchCollection(true); 0045 0046 connect(changeRecorder(), &ChangeRecorder::changesAdded, this, &SingleFileResourceBase::scheduleWrite); 0047 0048 connect(KDirWatch::self(), &KDirWatch::dirty, this, &SingleFileResourceBase::fileChanged); 0049 connect(KDirWatch::self(), &KDirWatch::created, this, &SingleFileResourceBase::fileChanged); 0050 } 0051 0052 void SingleFileResourceBase::applyConfigurationChanges() 0053 { 0054 } 0055 0056 KSharedConfig::Ptr SingleFileResourceBase::runtimeConfig() const 0057 { 0058 return KSharedConfig::openConfig(name() + QLatin1StringView("rc"), KConfig::SimpleConfig, QStandardPaths::CacheLocation); 0059 } 0060 0061 bool SingleFileResourceBase::readLocalFile(const QString &fileName) 0062 { 0063 const QByteArray newHash = calculateHash(fileName); 0064 if (mCurrentHash != newHash) { 0065 if (!mCurrentHash.isEmpty()) { 0066 // There was a hash stored in the config file or a cached one from 0067 // a previous read and it is different from the hash we just read. 0068 handleHashChange(); 0069 } 0070 0071 if (!readFromFile(fileName)) { 0072 mCurrentHash.clear(); 0073 mCurrentUrl = QUrl(); // reset so we don't accidentally overwrite the file 0074 return false; 0075 } 0076 0077 if (mCurrentHash.isEmpty()) { 0078 // This is the very first time we read the file so make sure to store 0079 // the hash as writeFile() might not be called at all (e.g in case of 0080 // read only resources). 0081 saveHash(newHash); 0082 } 0083 0084 // Only synchronize when the contents of the file have changed wrt to 0085 // the last time this file was read. Before we synchronize first 0086 // clearCache is called to make sure that the cached items get the 0087 // actual values as present in the file. 0088 invalidateCache(rootCollection()); 0089 synchronize(); 0090 } else { 0091 // The hash didn't change, notify implementing resources about the 0092 // actual file name that should be used when reading the file is 0093 // necessary. 0094 setLocalFileName(fileName); 0095 } 0096 0097 mCurrentHash = newHash; 0098 return true; 0099 } 0100 0101 void SingleFileResourceBase::setLocalFileName(const QString &fileName) 0102 { 0103 // Default implementation. 0104 if (!readFromFile(fileName)) { 0105 mCurrentHash.clear(); 0106 mCurrentUrl = QUrl(); // reset so we don't accidentally overwrite the file 0107 return; 0108 } 0109 } 0110 0111 QString SingleFileResourceBase::cacheFile() const 0112 { 0113 const QString currentDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); 0114 QDir().mkpath(currentDir); 0115 return currentDir + QLatin1Char('/') + identifier(); 0116 } 0117 0118 QByteArray SingleFileResourceBase::calculateHash(const QString &fileName) const 0119 { 0120 QFile file(fileName); 0121 if (!file.exists()) { 0122 return {}; 0123 } 0124 0125 if (!file.open(QIODevice::ReadOnly)) { 0126 return {}; 0127 } 0128 0129 QCryptographicHash hash(QCryptographicHash::Md5); 0130 qint64 blockSize = 512 * 1024; // Read blocks of 512K 0131 0132 while (!file.atEnd()) { 0133 hash.addData(file.read(blockSize)); 0134 } 0135 0136 file.close(); 0137 0138 return hash.result(); 0139 } 0140 0141 void SingleFileResourceBase::handleHashChange() 0142 { 0143 // Default implementation does nothing. 0144 qDebug() << "The hash has changed."; 0145 } 0146 0147 QByteArray SingleFileResourceBase::loadHash() const 0148 { 0149 KConfigGroup generalGroup(runtimeConfig(), QStringLiteral("General")); 0150 return QByteArray::fromHex(generalGroup.readEntry<QByteArray>("hash", QByteArray())); 0151 } 0152 0153 void SingleFileResourceBase::saveHash(const QByteArray &hash) const 0154 { 0155 KSharedConfig::Ptr config = runtimeConfig(); 0156 KConfigGroup generalGroup(config, QStringLiteral("General")); 0157 generalGroup.writeEntry("hash", hash.toHex()); 0158 config->sync(); 0159 } 0160 0161 void SingleFileResourceBase::setSupportedMimetypes(const QStringList &mimeTypes, const QString &icon) 0162 { 0163 mSupportedMimetypes = mimeTypes; 0164 mCollectionIcon = icon; 0165 } 0166 0167 void SingleFileResourceBase::collectionChanged(const Akonadi::Collection &collection) 0168 { 0169 const QString newName = collection.displayName(); 0170 if (collection.hasAttribute<EntityDisplayAttribute>()) { 0171 const auto attr = collection.attribute<EntityDisplayAttribute>(); 0172 if (!attr->iconName().isEmpty()) { 0173 mCollectionIcon = attr->iconName(); 0174 } 0175 } 0176 0177 if (newName != name()) { 0178 setName(newName); 0179 } 0180 0181 changeCommitted(collection); 0182 } 0183 0184 void SingleFileResourceBase::reloadFile() 0185 { 0186 // Update the network setting. 0187 setNeedsNetwork(!mCurrentUrl.isEmpty() && !mCurrentUrl.isLocalFile()); 0188 0189 // if we have something loaded already, make sure we write that back in case 0190 // the settings changed 0191 if (!mCurrentUrl.isEmpty() && !readOnly()) { 0192 writeFile(); 0193 } 0194 0195 readFile(); 0196 0197 // name or rights could have changed 0198 synchronizeCollectionTree(); 0199 } 0200 0201 void SingleFileResourceBase::handleProgress(KJob *, unsigned long pct) 0202 { 0203 Q_EMIT percent(pct); 0204 } 0205 0206 void SingleFileResourceBase::fileChanged(const QString &fileName) 0207 { 0208 if (fileName != mCurrentUrl.toLocalFile()) { 0209 return; 0210 } 0211 0212 const QByteArray newHash = calculateHash(fileName); 0213 0214 // There is only a need to synchronize when the file was changed by another 0215 // process. At this point we're sure that it is the file that the resource 0216 // was configured for because of the check at the beginning of this function. 0217 if (newHash == mCurrentHash) { 0218 return; 0219 } 0220 0221 if (!mCurrentUrl.isEmpty()) { 0222 QString lostFoundFileName; 0223 const QUrl prevUrl = mCurrentUrl; 0224 int i = 0; 0225 do { 0226 lostFoundFileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + identifier() + QLatin1Char('/') 0227 + prevUrl.fileName() + QLatin1Char('-') + QString::number(++i); 0228 } while (QFileInfo::exists(lostFoundFileName)); 0229 0230 // create the directory if it doesn't exist yet 0231 QDir dir = QFileInfo(lostFoundFileName).dir(); 0232 if (!dir.exists()) { 0233 dir.mkpath(dir.path()); 0234 } 0235 0236 mCurrentUrl = QUrl::fromLocalFile(lostFoundFileName); 0237 writeFile(); 0238 mCurrentUrl = prevUrl; 0239 0240 const QString message = i18n( 0241 "The file '%1' was changed on disk. " 0242 "As a precaution, a backup of its previous contents has been created at '%2'.", 0243 prevUrl.toDisplayString(), 0244 QUrl::fromLocalFile(lostFoundFileName).toDisplayString()); 0245 Q_EMIT warning(message); 0246 } 0247 0248 readFile(); 0249 0250 // Notify resources, so that information bound to the file like indexes etc. 0251 // can be updated. 0252 handleHashChange(); 0253 invalidateCache(rootCollection()); 0254 synchronize(); 0255 } 0256 0257 void SingleFileResourceBase::scheduleWrite() 0258 { 0259 scheduleCustomTask(this, "writeFile", QVariant(true), ResourceBase::AfterChangeReplay); 0260 } 0261 0262 void SingleFileResourceBase::slotDownloadJobResult(KJob *job) 0263 { 0264 if (job->error() && job->error() != KIO::ERR_DOES_NOT_EXIST) { 0265 const QString message = i18n("Could not load file '%1'.", mCurrentUrl.toDisplayString()); 0266 qWarning() << message; 0267 Q_EMIT status(Broken, message); 0268 } else { 0269 readLocalFile(QUrl::fromLocalFile(cacheFile()).toLocalFile()); 0270 } 0271 0272 mDownloadJob = nullptr; 0273 auto ref = job->property("QEventLoopLocker").value<QEventLoopLocker *>(); 0274 if (ref) { 0275 delete ref; 0276 } 0277 0278 Q_EMIT status(Idle, i18nc("@info:status", "Ready")); 0279 } 0280 0281 void SingleFileResourceBase::slotUploadJobResult(KJob *job) 0282 { 0283 if (job->error()) { 0284 const QString message = i18n("Could not save file '%1'.", mCurrentUrl.toDisplayString()); 0285 qWarning() << message; 0286 Q_EMIT status(Broken, message); 0287 } 0288 0289 mUploadJob = nullptr; 0290 auto ref = job->property("QEventLoopLocker").value<QEventLoopLocker *>(); 0291 if (ref) { 0292 delete ref; 0293 } 0294 0295 Q_EMIT status(Idle, i18nc("@info:status", "Ready")); 0296 } 0297 0298 #include "moc_singlefileresourcebase.cpp"