Save a .qetz without building the folios not touched (stage D4)

With QET_LAZY_FOLIOS=1, a project opened from a .qetz's index saves a
.qetz without building the folios it never built, when building them
would write the same: their parts are copied as they were and their rows
of the document's engineering data from the project.sqlite they were read
from (QetContainerDb::extract() with kept_from, QetContainer::split() with
the old entries, pictures included). Whether a folio may be kept follows
the rules of stage 5.5 (QETProject::toXml() with kept_parts): nothing
project-wide changed since opening; a folio whose own data changed is
built with its partners; after an edit, folios holding a table filled
from the database are built; the partners still waiting of every built
folio are built. Trusted only for a project opened from a format 3 index
written by this version whose folios are named by their uuid.

Format 3 names a folio by its uuid in the database (doc_meta folio_ids =
uuid) when every folio has one of its own, so that a kept folio keeps its
rows whatever its place; copies keep the rows' order (ORDER BY rowid).
A folio not built keeps the name of its part until it is built
(Diagram::deferredPart()).

Measured, the 136-folio f2099 opened from its .qetz and saved as a .qetz:
4.75 s with no folio built, 33 s with every folio built; its 136 folio
parts copied byte for byte; a kept save of a kept save is the same bytes.
industrial.qetz untouched: 28 of its 47 folios not built are built to save
(link chains, as in 5.5); after everyday edits on its busiest folio, 7
stay not built.

tst_lazyfolios: an untouched .qetz saved without building, read back,
holds the project; edits saved without building hold, read back, what the
same edits saved with every folio built hold (read back too: a wire
numbered from its potential is saved with no sequential numbers whichever
the way -- reading it back renumbers it, a separate issue); fails when a
kept folio's symbol information is not copied. Corpus, 342 projects: a
.qetz saved without building, read and saved as .qet = eager = master on
all 342. ctest
96/96 with and without QET_LAZY_FOLIOS=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-11 21:21:42 +13:00
parent 8ea051acfc
commit 8ad33f6281
9 changed files with 227 additions and 21 deletions
+39 -9
View File
@@ -18,6 +18,7 @@
#include "qetproject.h"
#include "container/qetcontainer.h"
#include "container/qetcontainerdb.h"
#include "ElementsCollection/xmlelementcollection.h"
#include "autoNum/assignvariables.h"
@@ -2293,8 +2294,12 @@ void QETProject::autoFolioNumberingSelectedFolios(int from,
@brief QETProject::toXml
@return un document XML representant le projet
*/
QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios)
QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList *kept_parts)
{
//With @p kept_parts, the same for a .qetz opened from its index: a
//folio not built is written as <diagram qetz-kept-part="..."/>, its
//part and its rows copied by QetContainer::split()
//(stage D4)
//With @p kept_folios, a folio not built (QET_LAZY_FOLIOS) is
//written as a placeholder <diagram qet_kept_folio="n"/> and its text
//as read from the file goes in kept_folios, for
@@ -2308,8 +2313,8 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios)
//is built with the folios its links reach, as when it is shown:
//their saved texts can show its data (a report, its partner's
//folio label).
const bool keep = kept_folios && unloadedFolioCount()
&& m_kept_folios_trusted && !m_kept_folios_stale;
const bool keep = unloadedFolioCount() && !m_kept_folios_stale
&& ((kept_folios && m_kept_folios_trusted) || (kept_parts && m_kept_parts_trusted));
if (keep) {
for (Diagram *diagram : std::as_const(m_diagrams_list))
if (diagram->headerChanged())
@@ -2423,6 +2428,15 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios)
<< "["
<< diagram
<< "]";
if (keep && !diagram->isLoaded() && kept_parts) {
auto placeholder = xml_doc.createElement(QStringLiteral("diagram"));
placeholder.setAttribute(QStringLiteral("qetz-kept-part"), diagram->deferredPart());
placeholder.setAttribute(QStringLiteral("uuid"), diagram->uuid().toString());
kept_parts->append(diagram->deferredPart());
project_root.appendChild(placeholder);
++order_num;
continue;
}
if (keep && !diagram->isLoaded()) {
auto placeholder = xml_doc.createElement(QStringLiteral("diagram"));
placeholder.setAttribute(QStringLiteral("qet_kept_folio"), order_num);
@@ -2520,7 +2534,7 @@ bool QETProject::isContainerPath(const QString &path)
the whole archive is written.
*/
bool QETProject::writeContainer(const QDomDocument &project, const QString &path, QString *error,
const QByteArray &index)
const QByteArray &index, const QHash<QString, QByteArray> &kept_from)
{
//The project database, for opening without reading the folios
//(stage D2), is in project.sqlite with the
@@ -2528,7 +2542,7 @@ bool QETProject::writeContainer(const QDomDocument &project, const QString &path
QList<QetZip::Entry> entries = QetContainer::split(
project,
QStringLiteral("QElectroTech %1").arg(QetVersion::currentVersion().toString()),
error, index);
error, index, kept_from);
return !entries.isEmpty() && QetZip::write(path, entries, error);
}
@@ -2541,8 +2555,12 @@ bool QETProject::writeContainer(const QDomDocument &project, const QString &path
*/
bool QETProject::writeContainerFile(const QString &path, QString *error)
{
const QDomDocument document = toXml(); //every folio built
return writeContainer(document, path, error, containerIndex());
//The folios not built, written as they were read, when that is
//what building them would write (toXml()); every other folio built
QStringList kept;
const QDomDocument document = toXml(nullptr, &kept);
return writeContainer(document, path, error, containerIndex(),
kept.isEmpty() ? QHash<QString, QByteArray>() : m_container_files);
}
/**
@@ -2552,7 +2570,11 @@ bool QETProject::writeContainerFile(const QString &path, QString *error)
*/
QByteArray QETProject::containerIndex()
{
loadFolios();
//Opened from an index, the folios not built have their rows in the
//database, whose keys are unique; the built ones are looked at
if (!m_kept_parts_trusted) {
loadFolios();
}
QSet<QUuid> seen;
QList<std::pair<QUuid, QString>> headers;
auto unique = [&seen](const QUuid &uuid) {
@@ -2562,6 +2584,10 @@ QByteArray QETProject::containerIndex()
};
for (Diagram *diagram : std::as_const(m_diagrams_list)) {
if (!unique(diagram->uuid())) return QByteArray();
if (!diagram->isLoaded()) {
headers.append({diagram->uuid(), diagram->headerXml()});
continue;
}
for (Element *element : diagram->elements())
if (!unique(element->uuid())) return QByteArray();
for (Conductor *conductor : diagram->conductors())
@@ -3224,9 +3250,13 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
for (Diagram *diagram : std::as_const(m_diagrams_list))
if (!diagram->isLoaded() && diagram->keptText().isEmpty())
m_kept_folios_trusted = false;
m_kept_parts_trusted = m_opened_from_index && unloadedFolioCount() > 0
&& m_project_qet_version == QetVersion::currentVersion()
&& m_data_base.rowsFromDocument()
&& QetContainerDb::foliosByUuid(m_container_files.value(QStringLiteral("project.sqlite")));
m_kept_folios_stale = false;
m_edited_since_open = false;
if (m_kept_folios_trusted)
if (m_kept_folios_trusted || m_kept_parts_trusted)
for (Diagram *diagram : std::as_const(m_diagrams_list))
diagram->rememberHeader();
if (qEnvironmentVariableIntValue("QET_LAZY_FOLIOS_TRACE") == 1)