diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index d71bedfe4..095945401 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -253,6 +253,58 @@ void projectDataBase::updateDB(const QDomDocument &document) updateDB(); } +/** + @brief projectDataBase::documentRefusal + @return why populateFromDocument() will refuse @p document, for the + reasons it gives that the document alone can tell -- a folio, symbol + or wire with no saved uuid, two symbols sharing one, a folio number + using %autonum -- or an empty string. The other reasons need the + project's definitions or every folio's links; a document refused for + one of those is still filled from the folios, only later. +*/ +QString projectDataBase::documentRefusal(const QDomDocument &document) +{ + if (qEnvironmentVariableIntValue("QET_DATABASE_FROM_FOLIOS") == 1) { + return QStringLiteral("QET_DATABASE_FROM_FOLIOS is set"); + } + QSet element_uuids; + const QDomNodeList diagram_nodes = document.elementsByTagName(QStringLiteral("diagram")); + for (int i = 0 ; i < diagram_nodes.size() ; ++i) + { + QDomElement diagram_xml = diagram_nodes.at(i).toElement(); + if (QUuid(diagram_xml.attribute(QStringLiteral("uuid"))).isNull()) { + return QStringLiteral("a folio has no saved uuid"); + } + if (diagram_xml.attribute(QStringLiteral("folio")).contains(QStringLiteral("%autonum"))) { + return QStringLiteral("a folio number uses %autonum"); + } + for (QDomElement element_xml : QET::findInDomElement( + diagram_xml, QStringLiteral("elements"), QStringLiteral("element"))) + { + //Skipped by Diagram::fromXml() as well + if (!Element::valideXml(element_xml)) { + continue; + } + const QUuid uuid(element_xml.attribute(QStringLiteral("uuid"))); + if (uuid.isNull() || element_uuids.contains(uuid)) { + return QStringLiteral("an element has no saved uuid, or shares one"); + } + element_uuids.insert(uuid); + } + for (QDomElement conductor_xml : QET::findInDomElement( + diagram_xml, QStringLiteral("conductors"), QStringLiteral("conductor"))) + { + if (Conductor::valideXml(conductor_xml) + && (QUuid(conductor_xml.attribute(QStringLiteral("uuid"))).isNull() + || !conductor_xml.hasAttribute(QStringLiteral("element1")) + || !conductor_xml.hasAttribute(QStringLiteral("element2")))) { + return QStringLiteral("a conductor has no saved uuid, or names its ends the older way"); + } + } + } + return QString(); +} + namespace { struct DocumentTerminal { diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 0dd92aaa1..faf92ba6f 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -67,6 +67,14 @@ class projectDataBase : public QObject populateFromDocument()), from the built folios otherwise. */ void updateDB(const QDomDocument &document); + /** + Why updateDB(document) will fill the tables from the folios + rather than from @p document, for the reasons the document + alone can tell; empty when it knows of none. Read before the + folios are: a project whose tables will come from the folios + builds every folio as it opens, even with QET_LAZY_FOLIOS. + */ + static QString documentRefusal(const QDomDocument &document); /** Suppress the full rebuild performed by updateDB(). diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 913bc9b77..676f20efb 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -3018,6 +3018,19 @@ void QETProject::readProjectXml(QDomDocument &xml_project) //built (DB-ACCESSORS-PLAN.md stage 4.6) m_data_base.prefillFromDocument(xml_project); + //QET_LAZY_FOLIOS leaves the folios to be built when needed only + //when the database can be filled from the document: otherwise + //filling it from the folios builds every one of them anyway, late + //and more slowly than reading them now + m_lazy_open = qEnvironmentVariableIntValue("QET_LAZY_FOLIOS") == 1; + if (m_lazy_open) { + const QString why = projectDataBase::documentRefusal(xml_project); + if (!why.isEmpty()) { + qInfo().noquote() << "Project opened with every folio built:" << why; + m_lazy_open = false; + } + } + //Load the diagrams readDiagramsXml(xml_project); const qint64 diagrams_ms = phase_timer.restart(); @@ -3134,7 +3147,7 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project) //Where each line of the file starts, for a folio's text: lines //[its start, the next element's start) (QETProject::toXml()) QList line_starts; - if (qEnvironmentVariableIntValue("QET_LAZY_FOLIOS") == 1 && !m_opened_content.isEmpty()) { + if (m_lazy_open && !m_opened_content.isEmpty()) { line_starts << 0; for (qsizetype i = 0 ; i < m_opened_content.size() ; ++i) if (m_opened_content.at(i) == '\n') line_starts << i + 1; @@ -3181,7 +3194,7 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project) connect(diagram, &Diagram::usedTitleBlockTemplateChanged, this, &QETProject::usedTitleBlockTemplateChanged); - m_deferring_folios = qEnvironmentVariableIntValue("QET_LAZY_FOLIOS") == 1; + m_deferring_folios = m_lazy_open; diagram->initFromXml(diagram_xml_element); m_deferring_folios = false; if (!diagram->isLoaded() && !line_starts.isEmpty()) diff --git a/sources/qetproject.h b/sources/qetproject.h index 30da9d1a4..9bdc240c7 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -512,6 +512,9 @@ class QETProject : public QObject const DiagramContext &information, const QHash &titles_by_formula) const; bool m_deferring_folios = false; + /// Opening with folios built only when needed: QET_LAZY_FOLIOS + /// is set and the database can be filled from the document + bool m_lazy_open = false; /// Saving may write a folio not built as it was read /// (writesKeptFolios()): the file is this version's, its /// database rows came from it, and nothing project-wide that diff --git a/tests/qttest/lazy_folios_probe.cpp b/tests/qttest/lazy_folios_probe.cpp index daebbba50..94f238a35 100644 --- a/tests/qttest/lazy_folios_probe.cpp +++ b/tests/qttest/lazy_folios_probe.cpp @@ -37,6 +37,8 @@ #include "../../sources/undocommand/deleteqgraphicsitemcommand.h" #include "../../sources/factory/elementfactory.h" #include "../../sources/undocommand/addgraphicsobjectcommand.h" +#include "../../sources/dataBase/projectdatabase.h" +#include static void check(bool value, const char *message) { if (!value) throw std::runtime_error(message); } @@ -567,6 +569,44 @@ int main(int argc, char **argv) check(potential(wire) == potential(usual_wire), "a potential followed as when built"); std::printf("potential through reports: %d wires, %d folios not built\n", int(potential(wire).size()), followed.unloadedFolioCount()); + } + //Two symbols sharing a uuid: the database is filled from the + //folios, which builds every one of them, so the project opens + //with every folio built rather than building them all later + { + QFile file(path); + check(file.open(QIODevice::ReadOnly), "fixture read"); + QDomDocument document; + check(bool(document.setContent(&file)), "fixture parsed"); + file.close(); + check(projectDataBase::documentRefusal(document).isEmpty(), "the fixture fills the database itself"); + QDomNodeList elements = document.elementsByTagName(QStringLiteral("element")); + QList placed; + for (int i = 0; i < elements.size(); ++i) { + QDomElement e = elements.at(i).toElement(); + if (Element::valideXml(e)) placed << e; + } + check(placed.size() > 1, "two placed symbols"); + placed.last().setAttribute(QStringLiteral("uuid"), placed.first().attribute(QStringLiteral("uuid"))); + check(!projectDataBase::documentRefusal(document).isEmpty(), "a shared uuid is seen before the folios are read"); + const QString shared = output + "/lazy_folios_shared_uuid.qet"; + QFile out(shared); + check(out.open(QIODevice::WriteOnly), "copy written"); + out.write(document.toByteArray(4)); + out.close(); + qputenv("QET_LAZY_FOLIOS", "1"); + qputenv("QET_LAZY_FOLIOS_TRACE", "1"); + static int built_late; built_late = 0; + const QtMessageHandler previous = qInstallMessageHandler( + [](QtMsgType, const QMessageLogContext &, const QString &message) { + if (message.contains(QStringLiteral("built for a caller of diagrams()"))) ++built_late; + }); + QETProject opened(shared); + qInstallMessageHandler(previous); + qunsetenv("QET_LAZY_FOLIOS_TRACE"); + check(opened.state() == QETProject::Ok, "a project with a shared uuid opens"); + check(opened.unloadedFolioCount() == 0, "a project with a shared uuid opens with every folio built"); + check(built_late == 0, "a project with a shared uuid builds its folios as it reads them, not later"); } std::printf("PASS: %d folios\n", int(folios.size())); } catch (const std::exception &error) {