Compare commits

...

1 Commits

Author SHA1 Message Date
Jean-Baptiste Mardelle
13adeaffa7 Test if feasible to create and verify a hash when a file is saved to ensure integrity of the saved file 2024-05-03 20:32:53 +02:00
6 changed files with 96 additions and 7 deletions

View File

@@ -1826,14 +1826,16 @@ Fun ProjectItemModel::removeProjectItem_lambda(int binId, int id)
};
}
void ProjectItemModel::checkSequenceIntegrity(const QString activeSequenceId)
const QMap<QString, QByteArray> ProjectItemModel::checkSequenceIntegrity(const QString activeSequenceId)
{
QStringList sequencesIds = pCore->currentDoc()->getTimelinesIds();
const QMap<QString, QByteArray> hash = pCore->currentDoc()->getTimelinesHash();
Q_ASSERT(sequencesIds.contains(activeSequenceId));
QStringList allMltIds = m_binPlaylist->getAllMltIds();
for (auto &i : sequencesIds) {
Q_ASSERT(allMltIds.contains(i));
}
return hash;
}
std::shared_ptr<EffectStackModel> ProjectItemModel::getClipEffectStack(int itemId)

View File

@@ -254,7 +254,7 @@ public:
/** @brief Remove clip references for a timeline. */
void removeReferencedClips(const QUuid &uuid, bool onDeletion);
/** @brief Check that all sequences are correctly stored in the model */
void checkSequenceIntegrity(const QString activeSequenceId);
const QMap<QString, QByteArray> checkSequenceIntegrity(const QString activeSequenceId);
std::shared_ptr<EffectStackModel> getClipEffectStack(int itemId);
protected:

View File

@@ -2210,6 +2210,19 @@ QStringList KdenliveDoc::getTimelinesIds()
return ids;
}
const QMap<QString, QByteArray> KdenliveDoc::getTimelinesHash()
{
QMap<QString, QByteArray> hash;
QMapIterator<QUuid, std::shared_ptr<TimelineItemModel>> j(m_timelines);
while (j.hasNext()) {
j.next();
const QString tId(j.value()->tractor()->get("id"));
hash.insert(tId, j.value()->timelineHash());
qDebug() << "====== INSERTING TIMELINE HASH:\n" << tId << " = " << hash.value(tId) << "\n\nHHHHHHHHHHHHHHHHHHHHH";
}
return hash;
}
void KdenliveDoc::addTimeline(const QUuid &uuid, std::shared_ptr<TimelineItemModel> model, bool force)
{
if (force && m_timelines.find(uuid) != m_timelines.end()) {

View File

@@ -301,6 +301,8 @@ public:
QList<QUuid> getTimelinesUuids() const;
/** @brief Return all timelines MLT ids.*/
QStringList getTimelinesIds();
/** @brief Returns a timeline hash to check project integrity.*/
const QMap<QString, QByteArray> getTimelinesHash();
/** @brief Returns the number of timelines in this project.*/
int openedTimelineCount() const;
/** @brief Get the currently active project name.*/

View File

@@ -63,6 +63,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QSaveFile>
#include <QTimeZone>
#include <QUndoGroup>
#include <QtConcurrent>
static QString getProjectNameFilters(bool ark = true)
{
@@ -540,7 +541,7 @@ bool ProjectManager::saveFileAs(const QString &outputFileName, bool saveOverExis
prepareSave();
QString saveFolder = QFileInfo(outputFileName).absolutePath();
m_project->updateWorkFilesBeforeSave(outputFileName);
checkProjectIntegrity();
const QMap<QString, QByteArray> hash = checkProjectIntegrity();
QString scene = projectSceneList(saveFolder);
if (!m_replacementPattern.isEmpty()) {
QMapIterator<QString, QString> i(m_replacementPattern);
@@ -554,6 +555,14 @@ bool ProjectManager::saveFileAs(const QString &outputFileName, bool saveOverExis
KNotification::event(QStringLiteral("ErrorMessage"), i18n("Saving project file <br><b>%1</B> failed", outputFileName), QPixmap());
return false;
}
// Check the newly saved project file against our current hash
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
m_integrityJob = QtConcurrent::run(this, &ProjectManager::checkFileIntegrity, outputFileName, hash);
#else
m_integrityJob = QtConcurrent::run(&ProjectManager::checkFileIntegrity, this, outputFileName, hash);
#endif
m_watcher.setFuture(m_integrityJob);
scene.clear();
QUrl url = QUrl::fromLocalFile(outputFileName);
// Save timeline thumbnails
std::unordered_map<QString, std::vector<int>> thumbKeys = pCore->window()->getCurrentTimeline()->controller()->getThumbKeys();
@@ -2237,11 +2246,11 @@ void ProjectManager::replaceTimelineInstances(const QString &sourceId, const QSt
m_activeTimelineModel->processTimelineReplacement(instances, sourceId, replacementId, maxDuration, replaceAudio, replaceVideo);
}
void ProjectManager::checkProjectIntegrity()
const QMap<QString, QByteArray> ProjectManager::checkProjectIntegrity()
{
// Ensure the active timeline sequence is correctly inserted in the main_bin playlist
const QString activeSequenceId(m_activeTimelineModel->tractor()->get("id"));
pCore->projectItemModel()->checkSequenceIntegrity(activeSequenceId);
return pCore->projectItemModel()->checkSequenceIntegrity(activeSequenceId);
}
void ProjectManager::handleLog(const QString &message)
@@ -2253,3 +2262,61 @@ void ProjectManager::showTrackEffectStack(int tid)
{
m_activeTimelineModel->showTrackEffectStack(tid);
}
bool ProjectManager::checkFileIntegrity(const QString outFile, const QMap<QString, QByteArray> hash)
{
QDomDocument doc;
QFile file(outFile);
if (!file.open(QIODevice::ReadOnly)) return false;
if (!doc.setContent(&file)) {
file.close();
return false;
}
file.close();
// Find elements
QDomNodeList tractors = doc.documentElement().elementsByTagName(QStringLiteral("tractor"));
QDomNodeList playlists = doc.documentElement().elementsByTagName(QStringLiteral("playlist"));
for (int j = 0; j < tractors.count(); j++) {
QDomElement tractor = tractors.item(j).toElement();
const QString tId = tractor.attribute(QStringLiteral("id"));
if (hash.contains(tId)) {
// Calculate item hash
QDomNodeList tracks = tractor.elementsByTagName(QStringLiteral("track"));
QVector<QString> trackIds;
// Parse tracks (skip the first black background track)
for (int k = 1; k < tracks.count(); k++) {
// Get this sequence's track names
trackIds << tracks.item(k).toElement().attribute("producer");
}
for (int k = 0; k < tractors.count(); k++) {
QDomElement subtractor = tractors.item(j).toElement();
const QString subTid = subtractor.attribute(QStringLiteral("id"));
if (trackIds.contains(subTid)) {
// We found a sequence track, get its 2 playlist ids
QDomNodeList playlistItems = subtractor.elementsByTagName(QStringLiteral("track"));
QVector<QString> playlistIds;
for (int l = 0; l < playlistItems.count(); l++) {
playlistIds << playlistItems.item(l).toElement().attribute("producer");
}
// Ok now we have the 2 playlist for a track, we can process the hash
for (int m = 0; m < playlists.count(); m++) {
QDomElement playlist = playlists.item(m).toElement();
const QString plId = playlist.attribute(QStringLiteral("id"));
if (playlistIds.contains(plId)) {
// Get hash for this one
int pos = 0;
QDomNodeList items = playlist.childNodes();
for (int n = 0; n < items.count(); n++) {
// Handle blanks
// Handle clips
}
}
}
}
}
}
}
return true;
}

View File

@@ -8,11 +8,12 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "kdenlivecore_export.h"
#include <KRecentFilesAction>
#include <QDir>
#include <QElapsedTimer>
#include <QFutureWatcher>
#include <QObject>
#include <QTime>
#include <QTimer>
#include <QUrl>
#include <QElapsedTimer>
#include "timeline2/model/timelineitemmodel.hpp"
@@ -258,6 +259,8 @@ private:
QUrl m_startUrl;
QString m_loadClipsOnOpen;
QMap<QString, QString> m_replacementPattern;
QFutureWatcher<void> m_watcher;
QFuture<void> m_integrityJob;
QAction *m_fileRevert;
KRecentFilesAction *m_recentFilesAction;
@@ -272,7 +275,9 @@ private:
void passSequenceProperties(const QUuid &uuid, std::shared_ptr<Mlt::Producer> prod, Mlt::Tractor tractor, std::shared_ptr<TimelineItemModel> timelineModel,
TimelineWidget *timelineWidget);
/** @brief Ensure sequences are correctly stored in our project model */
void checkProjectIntegrity();
const QMap<QString, QByteArray> checkProjectIntegrity();
/** @brief Ensure the saved project matches out current state */
bool checkFileIntegrity(const QString outFile, const QMap<QString, QByteArray> hash);
/** @brief Opening a project file failed, propose to open a backup */
void abortProjectLoad(const QUrl &url);
};