mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-25 15:34:13 +01:00
Fix the saved date and time lagging one save in the title block
The title block variables of a save (saveddate, saveddate-eu, saveddate-us, savedtime, savedfilename, savedfilepath) were set after the file was written, and also when a project was opened. A file therefore held the previous save's date and path: after editing and saving, the title block showed the date of the save before. And every save but the first in a session wrote the time of the one before, so a project saved twice without a change gave two different files, a diff for nothing in a project kept in git (discussion #1512). stampSaved() now sets them before writing, when the project has unsaved changes (the modified flag or the undo stack moved since the last save, as the window's "[modified]" uses) or is written to another file. Otherwise they keep what the file holds, and opening a project no longer sets them. tst_savedstamps: an unchanged project saved twice in place is byte-identical; a moved symbol then a save gives today's date and the file's own path. Both fail without the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
This commit is contained in:
+50
-23
@@ -474,6 +474,8 @@ QETProject::ProjectState QETProject::openFile(QFile *file)
|
|||||||
}
|
}
|
||||||
QFileInfo fi(*file);
|
QFileInfo fi(*file);
|
||||||
setFilePath(fi.absoluteFilePath());
|
setFilePath(fi.absoluteFilePath());
|
||||||
|
//Saved again unchanged, the file keeps its saved date and path
|
||||||
|
m_stamped_path = m_file_path;
|
||||||
|
|
||||||
//Extract the content of the xml. The raw bytes are kept : a project
|
//Extract the content of the xml. The raw bytes are kept : a project
|
||||||
//file without a persisted uuid derives its uuid from them.
|
//file without a persisted uuid derives its uuid from them.
|
||||||
@@ -1161,16 +1163,8 @@ void QETProject::setFilePath(const QString &filepath)
|
|||||||
setReadOnly(false);
|
setReadOnly(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//title block variables should be updated after file save as dialog is confirmed, before file is saved.
|
//The saved date, time and path of the title block are set by
|
||||||
m_project_properties.addValue("saveddate", QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat));
|
//write(), when the project is saved (stampSaved())
|
||||||
m_project_properties.addValue("saveddate-eu", QDate::currentDate().toString("dd-MM-yyyy"));
|
|
||||||
m_project_properties.addValue("saveddate-us", QDate::currentDate().toString("yyyy-MM-dd"));
|
|
||||||
m_project_properties.addValue("savedtime", QDateTime::currentDateTime().toString("HH:mm"));
|
|
||||||
m_project_properties.addValue("savedfilename", QFileInfo(filePath()).completeBaseName());
|
|
||||||
m_project_properties.addValue("savedfilepath", filePath());
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
emit projectFilePathChanged(this, m_file_path);
|
emit projectFilePathChanged(this, m_file_path);
|
||||||
emit projectInformationsChanged(this);
|
emit projectInformationsChanged(this);
|
||||||
updateDiagramsFolioData();
|
updateDiagramsFolioData();
|
||||||
@@ -2801,6 +2795,33 @@ bool QETProject::changedOnDisk(QDateTime *when) const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief QETProject::stampSaved
|
||||||
|
Set the title block variables of the save (saveddate, saveddate-eu,
|
||||||
|
saveddate-us, savedtime, savedfilename, savedfilepath) to now and to
|
||||||
|
the file being written, when there is something new to save: the
|
||||||
|
project changed since it was opened or last saved, or it is written to
|
||||||
|
another file. Otherwise they keep what the file holds, so saving an
|
||||||
|
unchanged project gives an identical file (F124).
|
||||||
|
@return true if they were set
|
||||||
|
*/
|
||||||
|
bool QETProject::stampSaved()
|
||||||
|
{
|
||||||
|
const bool changed = m_modified || m_undo_stack->index() != m_stamped_undo_index;
|
||||||
|
if (!changed && m_file_path == m_stamped_path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_project_properties.addValue("saveddate", QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat));
|
||||||
|
m_project_properties.addValue("saveddate-eu", QDate::currentDate().toString("dd-MM-yyyy"));
|
||||||
|
m_project_properties.addValue("saveddate-us", QDate::currentDate().toString("yyyy-MM-dd"));
|
||||||
|
m_project_properties.addValue("savedtime", QDateTime::currentDateTime().toString("HH:mm"));
|
||||||
|
m_project_properties.addValue("savedfilename", QFileInfo(filePath()).completeBaseName());
|
||||||
|
m_project_properties.addValue("savedfilepath", filePath());
|
||||||
|
emit projectInformationsChanged(this);
|
||||||
|
updateDiagramsFolioData();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief QETProject::write
|
@brief QETProject::write
|
||||||
Save the project in a file.
|
Save the project in a file.
|
||||||
@@ -2868,13 +2889,28 @@ QETResult QETProject::write(bool overwrite_changed)
|
|||||||
if (!PreviousVersions::keep(m_file_path, PreviousVersions::count(), &backup_error))
|
if (!PreviousVersions::keep(m_file_path, PreviousVersions::count(), &backup_error))
|
||||||
qWarning().noquote() << "Previous version of" << m_file_path << "not kept:" << backup_error;
|
qWarning().noquote() << "Previous version of" << m_file_path << "not kept:" << backup_error;
|
||||||
|
|
||||||
|
//The saved date, time and path go into the file being written, and
|
||||||
|
//only when there is something new to save: an unchanged project
|
||||||
|
//saves to an identical file (F124, a project kept in git). A
|
||||||
|
//failed write puts the previous values back.
|
||||||
|
const DiagramContext stamped_before = m_project_properties;
|
||||||
|
const bool stamped = stampSaved();
|
||||||
|
const auto failed_stamped = [&](const QString &error) {
|
||||||
|
if (stamped) {
|
||||||
|
m_project_properties = stamped_before;
|
||||||
|
emit projectInformationsChanged(this);
|
||||||
|
updateDiagramsFolioData();
|
||||||
|
}
|
||||||
|
return failed(error);
|
||||||
|
};
|
||||||
|
|
||||||
QString error_message;
|
QString error_message;
|
||||||
if (isContainerPath(m_file_path))
|
if (isContainerPath(m_file_path))
|
||||||
{
|
{
|
||||||
//A container is written from the whole document: every folio
|
//A container is written from the whole document: every folio
|
||||||
//built (stage D4 writes it from the database)
|
//built (stage D4 writes it from the database)
|
||||||
if (!writeContainerFile(m_file_path, &error_message))
|
if (!writeContainerFile(m_file_path, &error_message))
|
||||||
return(failed(error_message));
|
return(failed_stamped(error_message));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2884,7 +2920,7 @@ QETResult QETProject::write(bool overwrite_changed)
|
|||||||
QDomDocument xml_project(toXml(&kept_folios));
|
QDomDocument xml_project(toXml(&kept_folios));
|
||||||
if (!QET::writeXmlText(spliceKeptFolios(xml_project.toString(4), kept_folios),
|
if (!QET::writeXmlText(spliceKeptFolios(xml_project.toString(4), kept_folios),
|
||||||
m_file_path, &error_message))
|
m_file_path, &error_message))
|
||||||
return(failed(error_message));
|
return(failed_stamped(error_message));
|
||||||
}
|
}
|
||||||
|
|
||||||
rememberDiskState();
|
rememberDiskState();
|
||||||
@@ -2905,17 +2941,8 @@ QETResult QETProject::write(bool overwrite_changed)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//title block variables should be updated after file save dialog is confirmed, before file is saved.
|
m_stamped_path = m_file_path;
|
||||||
m_project_properties.addValue("saveddate", QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat));
|
m_stamped_undo_index = m_undo_stack->index();
|
||||||
m_project_properties.addValue("saveddate-us", QDate::currentDate().toString("yyyy-MM-dd"));
|
|
||||||
m_project_properties.addValue("saveddate-eu", QDate::currentDate().toString("dd-MM-yyyy"));
|
|
||||||
m_project_properties.addValue("savedtime", QDateTime::currentDateTime().toString("HH:mm"));
|
|
||||||
m_project_properties.addValue("savedfilename", QFileInfo(filePath()).completeBaseName());
|
|
||||||
m_project_properties.addValue("savedfilepath", filePath());
|
|
||||||
|
|
||||||
emit projectInformationsChanged(this);
|
|
||||||
updateDiagramsFolioData();
|
|
||||||
|
|
||||||
setModified(false);
|
setModified(false);
|
||||||
return(QETResult());
|
return(QETResult());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -441,6 +441,7 @@ class QETProject : public QObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool stampSaved();
|
||||||
void readProjectXml(QDomDocument &xml_project);
|
void readProjectXml(QDomDocument &xml_project);
|
||||||
void readDiagramsXml(QDomDocument &xml_project);
|
void readDiagramsXml(QDomDocument &xml_project);
|
||||||
void readElementsCollectionXml(QDomDocument &xml_project);
|
void readElementsCollectionXml(QDomDocument &xml_project);
|
||||||
@@ -570,6 +571,10 @@ class QETProject : public QObject
|
|||||||
bool m_kept_folios_trusted = false;
|
bool m_kept_folios_trusted = false;
|
||||||
/// Read from a .qet whose path nobody chose since: saved beside it
|
/// Read from a .qet whose path nobody chose since: saved beside it
|
||||||
bool m_opened_from_qet = false;
|
bool m_opened_from_qet = false;
|
||||||
|
/// The file the saved date and path were set for, and the undo
|
||||||
|
/// index then: an unchanged project keeps them (stampSaved())
|
||||||
|
QString m_stamped_path;
|
||||||
|
int m_stamped_undo_index = 0;
|
||||||
/// The project file as this QET last read or wrote it, to see
|
/// The project file as this QET last read or wrote it, to see
|
||||||
/// whether something else changed it since (changedOnDisk())
|
/// whether something else changed it since (changedOnDisk())
|
||||||
QString m_disk_path;
|
QString m_disk_path;
|
||||||
|
|||||||
@@ -889,6 +889,18 @@ if(QET_HAS_SCRIPTING)
|
|||||||
target_compile_definitions(tst_scriptconductoruuid PRIVATE
|
target_compile_definitions(tst_scriptconductoruuid PRIVATE
|
||||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||||
|
|
||||||
|
# The saved date, time and path of the title block change only when the
|
||||||
|
# project changed, and go into the file that save writes (F124): an
|
||||||
|
# unchanged project saved twice gives an identical file.
|
||||||
|
add_executable(
|
||||||
|
tst_savedstamps
|
||||||
|
tst_savedstamps.cpp)
|
||||||
|
add_test(NAME tst_savedstamps COMMAND tst_savedstamps)
|
||||||
|
add_dependencies(tst_savedstamps qelectrotech)
|
||||||
|
target_link_libraries(tst_savedstamps PRIVATE Qt::Test Qt::Xml)
|
||||||
|
target_compile_definitions(tst_savedstamps PRIVATE
|
||||||
|
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||||
|
|
||||||
# qet.terminalPosition() and qet.conductorPath(), checked against each
|
# qet.terminalPosition() and qet.conductorPath(), checked against each
|
||||||
# other on a fixture's conductors.
|
# other on a fixture's conductors.
|
||||||
add_executable(
|
add_executable(
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2006-2026 The QElectroTech Team
|
||||||
|
This file is part of QElectroTech.
|
||||||
|
|
||||||
|
QElectroTech is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
QElectroTech is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <QtTest>
|
||||||
|
#include <QDate>
|
||||||
|
#include <QDomDocument>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
|
||||||
|
/**
|
||||||
|
The title block variables of a save (saveddate, savedtime,
|
||||||
|
savedfilename, savedfilepath...) are set when the project is saved
|
||||||
|
with something new in it, and go into the file that save writes. They
|
||||||
|
used to be set after writing, so each save wrote the time of the one
|
||||||
|
before: a project saved twice without a change gave two different
|
||||||
|
files, and a changed project was saved with the previous date.
|
||||||
|
|
||||||
|
Runs the real binary on fixtures/unlinked_contact_label.qet, whose
|
||||||
|
stamps say 2026-10-02 18:39 in a temporary folder:
|
||||||
|
- saved twice in its own file without a change: byte-identical;
|
||||||
|
- one symbol moved, then saved: today's date and the file's own path.
|
||||||
|
*/
|
||||||
|
class tst_savedstamps : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
QTemporaryDir m_dir;
|
||||||
|
QString m_base;
|
||||||
|
|
||||||
|
QString run(const QStringList &arguments)
|
||||||
|
{
|
||||||
|
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||||
|
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||||
|
//Its own settings and single-instance socket: another test's
|
||||||
|
//QElectroTech running at the same time would otherwise be
|
||||||
|
//handed this run (SingleApplication)
|
||||||
|
const QString home = m_dir.filePath(QStringLiteral("home"));
|
||||||
|
QDir().mkpath(home);
|
||||||
|
env.insert(QStringLiteral("HOME"), home);
|
||||||
|
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
|
||||||
|
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
|
||||||
|
env.insert(QStringLiteral("TMPDIR"), m_dir.path());
|
||||||
|
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
|
||||||
|
QProcess proc;
|
||||||
|
proc.setProcessEnvironment(env);
|
||||||
|
proc.setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), arguments);
|
||||||
|
if (!proc.waitForFinished(120000) || proc.exitStatus() != QProcess::NormalExit
|
||||||
|
|| proc.exitCode() != 0)
|
||||||
|
return QString();
|
||||||
|
return QString::fromUtf8(proc.readAll()) + QStringLiteral("\nEXIT 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString script(const QString &name, const QString &body)
|
||||||
|
{
|
||||||
|
const QString path = m_dir.filePath(name);
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) return QString();
|
||||||
|
file.write(body.toUtf8());
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The saved-* properties of @p qetz, read back through --resave
|
||||||
|
QMap<QString, QString> stamps(const QString &qetz)
|
||||||
|
{
|
||||||
|
const QString xml = qetz + QStringLiteral(".qet");
|
||||||
|
if (run({QStringLiteral("--resave"), qetz, xml}).isEmpty()) return {};
|
||||||
|
QFile file(xml);
|
||||||
|
QDomDocument doc;
|
||||||
|
if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file)) return {};
|
||||||
|
QMap<QString, QString> out;
|
||||||
|
const QDomNodeList props = doc.documentElement().firstChildElement(
|
||||||
|
QStringLiteral("properties")).elementsByTagName(QStringLiteral("property"));
|
||||||
|
for (int i = 0; i < props.size(); ++i) {
|
||||||
|
const QDomElement p = props.at(i).toElement();
|
||||||
|
if (p.attribute(QStringLiteral("name")).startsWith(QLatin1String("saved")))
|
||||||
|
out.insert(p.attribute(QStringLiteral("name")), p.text());
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray bytes(const QString &path)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initTestCase()
|
||||||
|
{
|
||||||
|
QVERIFY2(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)), "qelectrotech binary not found");
|
||||||
|
QVERIFY(m_dir.isValid());
|
||||||
|
const QString fixture = QFINDTESTDATA("fixtures/unlinked_contact_label.qet");
|
||||||
|
QVERIFY2(!fixture.isEmpty(), "fixture project not found");
|
||||||
|
//The fixture as a .qetz, its stamps as they were: a copy
|
||||||
|
//written by --resave is not a save of the project
|
||||||
|
m_base = m_dir.filePath(QStringLiteral("base.qetz"));
|
||||||
|
QVERIFY(!run({QStringLiteral("--resave"), fixture, m_base}).isEmpty());
|
||||||
|
QCOMPARE(stamps(m_base).value(QStringLiteral("saveddate-us")), QStringLiteral("2026-10-02"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void unchangedSavesAreIdentical()
|
||||||
|
{
|
||||||
|
const QString project = m_dir.filePath(QStringLiteral("unchanged.qetz"));
|
||||||
|
QVERIFY(QFile::copy(m_base, project));
|
||||||
|
const QString js = script(QStringLiteral("unchanged.js"),
|
||||||
|
QStringLiteral("qet.save('');\nqet.save('');\nqet.log('SAVED twice');\n"));
|
||||||
|
QVERIFY(run({QStringLiteral("--run"), js, project}).contains(QLatin1String("SAVED twice")));
|
||||||
|
QCOMPARE(bytes(project), bytes(m_base));
|
||||||
|
}
|
||||||
|
|
||||||
|
void changedSaveIsStampedNow()
|
||||||
|
{
|
||||||
|
const QString project = m_dir.filePath(QStringLiteral("changed.qetz"));
|
||||||
|
QVERIFY(QFile::copy(m_base, project));
|
||||||
|
const QString js = script(QStringLiteral("changed.js"), QStringLiteral(
|
||||||
|
"var u = qet.elementUuids(0)[0];\n"
|
||||||
|
"qet.log('MOVED ' + qet.moveElement(0, u, 10, 0));\n"
|
||||||
|
"qet.save('');\n"));
|
||||||
|
QVERIFY(run({QStringLiteral("--run"), js, project}).contains(QLatin1String("MOVED true")));
|
||||||
|
const QMap<QString, QString> s = stamps(project);
|
||||||
|
QCOMPARE(s.value(QStringLiteral("saveddate-us")), QDate::currentDate().toString(QStringLiteral("yyyy-MM-dd")));
|
||||||
|
QCOMPARE(s.value(QStringLiteral("savedfilepath")), QFileInfo(project).absoluteFilePath());
|
||||||
|
QCOMPARE(s.value(QStringLiteral("savedfilename")), QStringLiteral("changed"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(tst_savedstamps)
|
||||||
|
#include "tst_savedstamps.moc"
|
||||||
Reference in New Issue
Block a user