Ensure we check file hash on every project opening to ensure clips have not...

Ensure we check file hash on every project opening to ensure clips have not changed and an incorrect hash is not stored.
parent 1d5899fb
Pipeline #35536 passed with stage
in 11 minutes and 45 seconds
...@@ -996,24 +996,9 @@ const QString ProjectClip::getFileHash() ...@@ -996,24 +996,9 @@ const QString ProjectClip::getFileHash()
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
break; break;
default: default:
QFile file(clipUrl()); QPair<QByteArray, qint64> hashData = calculateHash(clipUrl());
if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file fileHash = hashData.first;
/* ClipController::setProducerProperty(QStringLiteral("kdenlive:file_size"), QString::number(hashData.second));
* 1 MB = 1 second per 450 files (or faster)
* 10 MB = 9 seconds per 450 files (or faster)
*/
if (file.size() > 2000000) {
fileData = file.read(1000000);
if (file.seek(file.size() - 1000000)) {
fileData.append(file.readAll());
}
} else {
fileData = file.readAll();
}
file.close();
ClipController::setProducerProperty(QStringLiteral("kdenlive:file_size"), QString::number(file.size()));
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
}
break; break;
} }
if (fileHash.isEmpty()) { if (fileHash.isEmpty()) {
...@@ -1025,6 +1010,33 @@ const QString ProjectClip::getFileHash() ...@@ -1025,6 +1010,33 @@ const QString ProjectClip::getFileHash()
return result; return result;
} }
const QPair<QByteArray, qint64> ProjectClip::calculateHash(const QString path)
{
QFile file(path);
QByteArray fileHash;
qint64 fSize = 0;
if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
/*
* 1 MB = 1 second per 450 files (or faster)
* 10 MB = 9 seconds per 450 files (or faster)
*/
QByteArray fileData;
fSize = file.size();
if (fSize > 2000000) {
fileData = file.read(1000000);
if (file.seek(file.size() - 1000000)) {
fileData.append(file.readAll());
}
} else {
fileData = file.readAll();
}
file.close();
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
}
return {fileHash, fSize};
}
double ProjectClip::getOriginalFps() const double ProjectClip::getOriginalFps() const
{ {
return originalFps(); return originalFps();
......
...@@ -165,6 +165,8 @@ public: ...@@ -165,6 +165,8 @@ public:
/** @brief The clip hash created from the clip's resource. */ /** @brief The clip hash created from the clip's resource. */
const QString hash(); const QString hash();
/** @brief Callculate a file hash from a path. */
static const QPair<QByteArray, qint64> calculateHash(const QString path);
/** @brief Returns true if we are using a proxy for this clip. */ /** @brief Returns true if we are using a proxy for this clip. */
bool hasProxy() const; bool hasProxy() const;
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "kdenlivesettings.h" #include "kdenlivesettings.h"
#include "kthumb.h" #include "kthumb.h"
#include "titler/titlewidget.h" #include "titler/titlewidget.h"
#include "bin/projectclip.h"
#include <KMessageBox> #include <KMessageBox>
#include <KRecentDirs> #include <KRecentDirs>
...@@ -137,6 +138,7 @@ bool DocumentChecker::hasErrorInClips() ...@@ -137,6 +138,7 @@ bool DocumentChecker::hasErrorInClips()
m_safeImages.clear(); m_safeImages.clear();
m_safeFonts.clear(); m_safeFonts.clear();
m_missingFonts.clear(); m_missingFonts.clear();
m_changedClips.clear();
max = documentProducers.count(); max = documentProducers.count();
QStringList verifiedPaths; QStringList verifiedPaths;
QStringList missingPaths; QStringList missingPaths;
...@@ -281,6 +283,17 @@ bool DocumentChecker::hasErrorInClips() ...@@ -281,6 +283,17 @@ bool DocumentChecker::hasErrorInClips()
m_missingClips.append(e); m_missingClips.append(e);
missingPaths.append(resource); missingPaths.append(resource);
} }
} else if (service.startsWith(QLatin1String("avformat"))) {
// Check if file changed
QByteArray hash = Xml::getXmlProperty(e, "kdenlive:file_hash").toLatin1();
if (!hash.isEmpty()) {
QByteArray fileData = ProjectClip::calculateHash(resource).first;
if (hash != QCryptographicHash::hash(fileData, QCryptographicHash::Md5)) {
// Clip was changed, notify and trigger clip reload
Xml::removeXmlProperty(e, "kdenlive:file_hash");
m_changedClips.append(resource);
}
}
} }
// Make sure we don't query same path twice // Make sure we don't query same path twice
verifiedPaths.append(resource); verifiedPaths.append(resource);
...@@ -410,7 +423,7 @@ bool DocumentChecker::hasErrorInClips() ...@@ -410,7 +423,7 @@ bool DocumentChecker::hasErrorInClips()
} }
} }
if (m_missingClips.isEmpty() && missingLumas.isEmpty() && missingProxies.isEmpty() && missingSources.isEmpty() && m_missingFonts.isEmpty() && if (m_missingClips.isEmpty() && missingLumas.isEmpty() && missingProxies.isEmpty() && missingSources.isEmpty() && m_missingFonts.isEmpty() &&
m_missingFilters.isEmpty()) { m_missingFilters.isEmpty() && m_changedClips.isEmpty()) {
return false; return false;
} }
...@@ -522,11 +535,20 @@ bool DocumentChecker::hasErrorInClips() ...@@ -522,11 +535,20 @@ bool DocumentChecker::hasErrorInClips()
QString clipType = i18n("Title Font"); QString clipType = i18n("Title Font");
QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType); QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType);
item->setData(0, statusRole, CLIPPLACEHOLDER); item->setData(0, statusRole, CLIPPLACEHOLDER);
item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-warning"))); item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-information")));
QString newft = QFontInfo(QFont(font)).family(); QString newft = QFontInfo(QFont(font)).family();
item->setText(1, i18n("%1 will be replaced by %2", font, newft)); item->setText(1, i18n("%1 will be replaced by %2", font, newft));
item->setData(0, typeRole, TITLE_FONT_ELEMENT); item->setData(0, typeRole, TITLE_FONT_ELEMENT);
} }
for (const QString &url : qAsConst(m_changedClips)) {
QString clipType = i18n("Modified Clips");
QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType);
item->setData(0, statusRole, CLIPPLACEHOLDER);
item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-information")));
item->setText(1, i18n("Clip %1 will be reloaded", url));
item->setData(0, typeRole, TITLE_FONT_ELEMENT);
}
QString infoLabel; QString infoLabel;
if (!m_missingClips.isEmpty()) { if (!m_missingClips.isEmpty()) {
...@@ -564,6 +586,13 @@ bool DocumentChecker::hasErrorInClips() ...@@ -564,6 +586,13 @@ bool DocumentChecker::hasErrorInClips()
infoLabel.append(i18np("The project file contains a missing clip, you can still work with its proxy.", infoLabel.append(i18np("The project file contains a missing clip, you can still work with its proxy.",
"The project file contains %1 missing clips, you can still work with their proxies.", missingSources.count())); "The project file contains %1 missing clips, you can still work with their proxies.", missingSources.count()));
} }
if (!m_changedClips.isEmpty()) {
if (!infoLabel.isEmpty()) {
infoLabel.append(QStringLiteral("\n"));
}
infoLabel.append(i18np("The project file contains one modified clip, it will be reloaded.",
"The project file contains %1 modified clips, they will be reloaded.", m_changedClips.count()));
}
if (!infoLabel.isEmpty()) { if (!infoLabel.isEmpty()) {
m_ui.infoLabel->setText(infoLabel); m_ui.infoLabel->setText(infoLabel);
} else { } else {
......
...@@ -74,6 +74,7 @@ private: ...@@ -74,6 +74,7 @@ private:
QStringList m_safeImages; QStringList m_safeImages;
QStringList m_safeFonts; QStringList m_safeFonts;
QStringList m_missingProxyIds; QStringList m_missingProxyIds;
QStringList m_changedClips;
void fixClipItem(QTreeWidgetItem *child, const QDomNodeList &producers, const QDomNodeList &trans); void fixClipItem(QTreeWidgetItem *child, const QDomNodeList &producers, const QDomNodeList &trans);
void fixSourceClipItem(QTreeWidgetItem *child, const QDomNodeList &producers); void fixSourceClipItem(QTreeWidgetItem *child, const QDomNodeList &producers);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment