mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-15 18:14:14 +02:00
Merge #1473: no position columns, a later format opened read-only
A symbol changed on a folio kept as it was needs the place its information goes back to: its part is checked for the marker (QetContainer::split()), where the position column said it before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -169,16 +169,18 @@ bool QetContainer::isZip(const QByteArray &content)
|
||||
/**
|
||||
@brief QetContainer::readDocument
|
||||
The project document held in @p content: unzipped and joined for a
|
||||
.qetz, parsed for a .qet.
|
||||
.qetz, parsed for a .qet. @p format, if any: the .qetz's format, 0
|
||||
for a .qet.
|
||||
*/
|
||||
bool QetContainer::readDocument(const QByteArray &content, QDomDocument *document,
|
||||
QString *error, Pictures *pictures)
|
||||
QString *error, Pictures *pictures, int *format)
|
||||
{
|
||||
if (format) *format = 0;
|
||||
if (!isZip(content))
|
||||
return parse(content, document, error);
|
||||
QList<QetZip::Entry> entries;
|
||||
return QetZip::fromBytes(content, &entries, error)
|
||||
&& join(entries, document, error, nullptr, pictures);
|
||||
&& join(entries, document, error, nullptr, pictures, format);
|
||||
}
|
||||
|
||||
QString QetContainer::pictureFile(const QDomElement &image)
|
||||
@@ -277,6 +279,29 @@ QList<QetZip::Entry> QetContainer::split(const QDomDocument &project, const QStr
|
||||
//A folio written as it was read (<diagram qetz-kept-part="..."/>,
|
||||
//stage D4) keeps its part and, through
|
||||
//@p kept_from's project.sqlite, its rows
|
||||
//A symbol changed on a folio kept as it was gets its information
|
||||
//rows written anew: its part must have the place they go back to
|
||||
for (const QDomElement &diagram : childElements(root, QStringLiteral("diagram")))
|
||||
{
|
||||
const QList<QDomElement> edited = childElements(diagram, QStringLiteral("qetz-edited-element"));
|
||||
if (edited.isEmpty()) continue;
|
||||
QDomDocument part;
|
||||
if (!parse(kept_from.value(diagram.attribute(QStringLiteral("qetz-kept-part"))), &part, error))
|
||||
return {};
|
||||
QSet<QString> placed;
|
||||
const QDomNodeList elements = part.elementsByTagName(QStringLiteral("element"));
|
||||
for (int i = 0 ; i < elements.size() ; ++i) {
|
||||
const QDomElement element = elements.at(i).toElement();
|
||||
for (const QDomElement &marker : childElements(element, QStringLiteral("qetz-db")))
|
||||
if (marker.attribute(QStringLiteral("block")) == QLatin1String("elementInformations"))
|
||||
placed.insert(element.attribute(QStringLiteral("uuid")));
|
||||
}
|
||||
for (const QDomElement &symbol : edited)
|
||||
if (!placed.contains(symbol.attribute(QStringLiteral("uuid")))) {
|
||||
setError(error, QStringLiteral("a symbol changed on a folio kept as it was has no place for its information"));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
const QByteArray database = QetContainerDb::extract(
|
||||
root, error, kept_from.value(QStringLiteral("project.sqlite")));
|
||||
if (database.isEmpty())
|
||||
@@ -391,10 +416,11 @@ QList<QetZip::Entry> QetContainer::split(const QDomDocument &project, const QStr
|
||||
nothing names are ignored, so a later format may add some.
|
||||
With @p pictures, the pictures stay out of the document: see the
|
||||
namespace. With @p folio_files, the part each folio was read from, in
|
||||
order.
|
||||
order. @p format, if any: the format the file says it is, above Format
|
||||
for a later one this may read (its min-reader allows it).
|
||||
*/
|
||||
bool QetContainer::join(const QList<QetZip::Entry> &entries, QDomDocument *project, QString *error,
|
||||
QStringList *folio_files, Pictures *pictures)
|
||||
QStringList *folio_files, Pictures *pictures, int *format)
|
||||
{
|
||||
QHash<QString, QByteArray> files;
|
||||
for (const QetZip::Entry &entry : entries)
|
||||
@@ -415,6 +441,8 @@ bool QetContainer::join(const QList<QetZip::Entry> &entries, QDomDocument *proje
|
||||
.arg(Format));
|
||||
return false;
|
||||
}
|
||||
if (format)
|
||||
*format = manifest.documentElement().attribute(QStringLiteral("format")).toInt();
|
||||
|
||||
if (!parse(files.value(QStringLiteral("project.xml")), project, error))
|
||||
return false;
|
||||
|
||||
@@ -57,12 +57,15 @@ namespace QetContainer
|
||||
/// unless their min-reader allows this one.
|
||||
constexpr int Format = 4;
|
||||
/// 3: the database's rows keyed by the items' uuids, true/false as
|
||||
/// 1/0, its PRAGMA user_version the format.
|
||||
/// 1/0, its PRAGMA user_version the format, the symbols' type left
|
||||
/// in the folio files, no column for where a block sat.
|
||||
/// 4: what QElectroTech computes left out of the folios
|
||||
/// (dropComputed()), the tables written without keys or indexes.
|
||||
/// project.sqlite holds the engineering data once: QElectroTech's
|
||||
/// own project database is built from it when the project opens.
|
||||
/// Formats 2 and 3 are still read.
|
||||
/// Formats 2 and 3 are still read; a later format whose min-reader
|
||||
/// allows this one is read too, and said so through join()'s
|
||||
/// @p format.
|
||||
|
||||
QList<QetZip::Entry> split(const QDomDocument &project,
|
||||
const QString &writer = QString(),
|
||||
@@ -70,7 +73,7 @@ namespace QetContainer
|
||||
const QHash<QString, QByteArray> &kept_from = QHash<QString, QByteArray>());
|
||||
bool join(const QList<QetZip::Entry> &entries, QDomDocument *project,
|
||||
QString *error = nullptr, QStringList *folio_files = nullptr,
|
||||
Pictures *pictures = nullptr);
|
||||
Pictures *pictures = nullptr, int *format = nullptr);
|
||||
/// The file join() left the picture of @p image in, or an empty string
|
||||
QString pictureFile(const QDomElement &image);
|
||||
|
||||
@@ -84,7 +87,7 @@ namespace QetContainer
|
||||
bool isZip(const QByteArray &content);
|
||||
/// The project document in @p content, a .qet or a .qetz
|
||||
bool readDocument(const QByteArray &content, QDomDocument *document,
|
||||
QString *error = nullptr, Pictures *pictures = nullptr);
|
||||
QString *error = nullptr, Pictures *pictures = nullptr, int *format = nullptr);
|
||||
}
|
||||
|
||||
#endif // QETCONTAINER_H
|
||||
|
||||
@@ -60,6 +60,15 @@ bool replaceMarker(QDomElement parent, const QDomElement &block)
|
||||
return false;
|
||||
}
|
||||
|
||||
//true if @p parent holds the marker of a block @p tag moved out: where
|
||||
//the block goes back, and that it was there at all
|
||||
bool hasMarker(const QDomElement &parent, const QString &tag)
|
||||
{
|
||||
for (QDomElement m = parent.firstChildElement(MarkerTag) ; !m.isNull() ; m = m.nextSiblingElement(MarkerTag))
|
||||
if (m.attribute(MarkerBlock) == tag) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const QStringList FolioData = {
|
||||
QStringLiteral("title"), QStringLiteral("author"), QStringLiteral("date"),
|
||||
QStringLiteral("folio"), QStringLiteral("indexrev"), QStringLiteral("filename"),
|
||||
@@ -116,11 +125,11 @@ QString schema()
|
||||
"CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT);"
|
||||
"CREATE TABLE project_property (ord INTEGER PRIMARY KEY, name TEXT, value TEXT, attrs TEXT);"
|
||||
"CREATE TABLE folio (uuid TEXT PRIMARY KEY, pos INTEGER NOT NULL UNIQUE, %1,"
|
||||
" props_at INTEGER, props_attrs TEXT);"
|
||||
" props_attrs TEXT);"
|
||||
"CREATE TABLE folio_property (folio_uuid TEXT NOT NULL REFERENCES folio, ord INTEGER NOT NULL,"
|
||||
" name TEXT, value TEXT, attrs TEXT, PRIMARY KEY (folio_uuid, ord));"
|
||||
"CREATE TABLE element (uuid TEXT PRIMARY KEY, folio_uuid TEXT NOT NULL REFERENCES folio, %2,"
|
||||
" info_at INTEGER, info_attrs TEXT, links_at INTEGER, links_attrs TEXT);"
|
||||
" info_attrs TEXT, links_attrs TEXT);"
|
||||
"CREATE TABLE element_info (element_uuid TEXT NOT NULL REFERENCES element, ord INTEGER NOT NULL,"
|
||||
" name TEXT, value TEXT, show INTEGER, attrs TEXT, PRIMARY KEY (element_uuid, ord));"
|
||||
"CREATE TABLE link (element_uuid TEXT NOT NULL REFERENCES element, ord INTEGER NOT NULL,"
|
||||
@@ -320,15 +329,6 @@ bool listBlock(const QDomElement &block, const QString &entry)
|
||||
return true;
|
||||
}
|
||||
|
||||
int childIndex(const QDomNode &child)
|
||||
{
|
||||
int i = 0;
|
||||
for (QDomNode n = child.parentNode().firstChild() ; !n.isNull() ; n = n.nextSibling(), ++i)
|
||||
if (n == child) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
//The attributes @p names of @p e, taken out of it; one that cannot be
|
||||
//stored stays, its value null
|
||||
QVariantList take(QDomElement e, const QStringList &names)
|
||||
@@ -587,9 +587,11 @@ bool openDatabase(Database &db, const QByteArray &database, QString *error)
|
||||
Statement format(db, QStringLiteral("SELECT value FROM meta WHERE key = 'format'"));
|
||||
const QString text = format.next() ? format.value(0).toString() : QString();
|
||||
db.m_format = text.toInt();
|
||||
if (db.m_format < 2 || db.m_format > QetContainer::Format) {
|
||||
setError(error, QStringLiteral("the project database is of format %1, this reads 2 to %2")
|
||||
.arg(text).arg(QetContainer::Format));
|
||||
//A later format is read when the manifest's min-reader allows it
|
||||
//(QetContainer::join()): its writer kept what this reads
|
||||
if (db.m_format < 2) {
|
||||
setError(error, QStringLiteral("the project database is of format %1, this reads format 2 on")
|
||||
.arg(text));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -666,23 +668,25 @@ bool restoreDiagram(Database &db, QDomElement d, UsedRows &used, QString *error)
|
||||
const QString id = keyOf(db, d, FolioAttribute);
|
||||
if (id.isEmpty()) return true;
|
||||
if (!used.use(used.folios, id, QStringLiteral("folio"), error)) return false;
|
||||
Statement f(db, QStringLiteral("SELECT %1, props_at, props_attrs FROM folio WHERE %2 = ?")
|
||||
Statement f(db, QStringLiteral("SELECT %1, props_attrs FROM folio WHERE %2 = ?")
|
||||
.arg(quoted(FolioData), db.folioKey()));
|
||||
f.bind({id});
|
||||
if (!f.next()) { setError(error, QStringLiteral("folio %1 is not in the database").arg(id)); return false; }
|
||||
QVariantList values;
|
||||
for (int i = 0 ; i < FolioData.size() + 2 ; ++i) values << f.value(i);
|
||||
for (int i = 0 ; i < FolioData.size() + 1 ; ++i) values << f.value(i);
|
||||
put(d, FolioData, values, 0);
|
||||
if (!values.at(FolioData.size()).isNull()) {
|
||||
//A moved block goes back where its marker is: the marker says it
|
||||
//was there
|
||||
if (hasMarker(d, QStringLiteral("properties"))) {
|
||||
QDomElement props = document.createElement(QStringLiteral("properties"));
|
||||
setAttributesJson(props, values.at(FolioData.size() + 1));
|
||||
setAttributesJson(props, values.at(FolioData.size()));
|
||||
Statement p(db, QStringLiteral("SELECT name, value, attrs FROM folio_property WHERE %1 = ? ORDER BY ord")
|
||||
.arg(db.folioRef()));
|
||||
p.bind({id});
|
||||
while (p.next())
|
||||
props.appendChild(entry(p.value(0), QStringLiteral("name"), p.value(1), p.value(2),
|
||||
QStringLiteral("property")));
|
||||
restoreWhitespace(props, values.at(FolioData.size() + 1));
|
||||
restoreWhitespace(props, values.at(FolioData.size()));
|
||||
if (!replaceMarker(d, props)) {
|
||||
setError(error, QStringLiteral("folio %1's properties have no place").arg(id));
|
||||
return false;
|
||||
@@ -696,40 +700,40 @@ bool restoreDiagram(Database &db, QDomElement d, UsedRows &used, QString *error)
|
||||
const QString key = keyOf(db, e, KeyAttribute);
|
||||
if (key.isEmpty()) continue;
|
||||
if (!used.use(used.elements, key, QStringLiteral("symbol"), error)) return false;
|
||||
Statement s(db, QStringLiteral("SELECT %1, info_at, info_attrs, links_at, links_attrs, %2 FROM element WHERE %3 = ?")
|
||||
Statement s(db, QStringLiteral("SELECT %1, info_attrs, links_attrs, %2 FROM element WHERE %3 = ?")
|
||||
.arg(quoted(element_data), db.folioRef(), db.itemKey()));
|
||||
s.bind({key});
|
||||
if (!s.next()) { setError(error, QStringLiteral("symbol %1 is not in the database").arg(key)); return false; }
|
||||
if (s.value(element_data.size() + 4).toString() != id) {
|
||||
if (s.value(element_data.size() + 2).toString() != id) {
|
||||
setError(error, QStringLiteral("symbol %1 is on folio %2, the database says another").arg(key, id));
|
||||
return false;
|
||||
}
|
||||
QVariantList v;
|
||||
for (int i = 0 ; i < element_data.size() + 4 ; ++i) v << s.value(i);
|
||||
for (int i = 0 ; i < element_data.size() + 2 ; ++i) v << s.value(i);
|
||||
put(e, element_data, v, 0);
|
||||
const int n = element_data.size();
|
||||
QDomElement info, links;
|
||||
if (!v.at(n).isNull()) {
|
||||
if (hasMarker(e, QStringLiteral("elementInformations"))) {
|
||||
info = document.createElement(QStringLiteral("elementInformations"));
|
||||
setAttributesJson(info, v.at(n + 1));
|
||||
setAttributesJson(info, v.at(n));
|
||||
Statement i(db, QStringLiteral("SELECT name, value, attrs, %1 FROM element_info WHERE %2 = ? ORDER BY ord")
|
||||
.arg(db.showColumn(), db.elementRef()));
|
||||
i.bind({key});
|
||||
while (i.next())
|
||||
info.appendChild(entry(i.value(0), QStringLiteral("name"), i.value(1), i.value(2),
|
||||
QStringLiteral("elementInformation"), i.value(3)));
|
||||
restoreWhitespace(info, v.at(n + 1));
|
||||
restoreWhitespace(info, v.at(n));
|
||||
}
|
||||
if (!v.at(n + 2).isNull()) {
|
||||
if (hasMarker(e, QStringLiteral("links_uuids"))) {
|
||||
links = document.createElement(QStringLiteral("links_uuids"));
|
||||
setAttributesJson(links, v.at(n + 3));
|
||||
setAttributesJson(links, v.at(n + 1));
|
||||
Statement l(db, QStringLiteral("SELECT linked_uuid, attrs FROM link WHERE %1 = ? ORDER BY ord")
|
||||
.arg(db.elementRef()));
|
||||
l.bind({key});
|
||||
while (l.next())
|
||||
links.appendChild(entry(l.value(0), QStringLiteral("uuid"), QVariant(), l.value(1),
|
||||
QStringLiteral("link_uuid")));
|
||||
restoreWhitespace(links, v.at(n + 3));
|
||||
restoreWhitespace(links, v.at(n + 1));
|
||||
}
|
||||
if ((!info.isNull() && !replaceMarker(e, info))
|
||||
|| (!links.isNull() && !replaceMarker(e, links))) {
|
||||
@@ -829,9 +833,9 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
|
||||
|
||||
Statement meta(db, QStringLiteral("INSERT INTO meta VALUES (?,?)"));
|
||||
Statement project_property(db, QStringLiteral("INSERT INTO project_property VALUES (?,?,?,?)"));
|
||||
Statement folio(db, QStringLiteral("INSERT INTO folio VALUES (?,?,%1,?,?)").arg(placeholders(FolioData.size())));
|
||||
Statement folio(db, QStringLiteral("INSERT INTO folio VALUES (?,?,%1,?)").arg(placeholders(FolioData.size())));
|
||||
Statement folio_property(db, QStringLiteral("INSERT INTO folio_property VALUES (?,?,?,?,?)"));
|
||||
Statement element(db, QStringLiteral("INSERT INTO element VALUES (?,?,%1,?,?,?,?)").arg(placeholders(ElementData.size())));
|
||||
Statement element(db, QStringLiteral("INSERT INTO element VALUES (?,?,%1,?,?)").arg(placeholders(ElementData.size())));
|
||||
Statement element_info(db, QStringLiteral("INSERT INTO element_info VALUES (?,?,?,?,?,?)"));
|
||||
Statement link(db, QStringLiteral("INSERT INTO link VALUES (?,?,?,?)"));
|
||||
Statement conductor(db, QStringLiteral("INSERT INTO conductor VALUES (?,?,%1)").arg(placeholders(ConductorData.size())));
|
||||
@@ -873,7 +877,6 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
|
||||
//Project properties
|
||||
const QDomElement project_props = project.firstChildElement(QStringLiteral("properties"));
|
||||
if (!project_props.isNull() && listBlock(project_props, QStringLiteral("property"))) {
|
||||
ok = ok && meta.run({QStringLiteral("props_at"), QString::number(childIndex(project_props))});
|
||||
ok = ok && meta.run({QStringLiteral("props_attrs"), blockJson(project_props)});
|
||||
int ord = 0;
|
||||
for (QDomElement p : children(project_props, QStringLiteral("property"))) {
|
||||
@@ -925,7 +928,7 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
|
||||
setError(error, QStringLiteral("a folio kept as it was, without its database"));
|
||||
return {};
|
||||
}
|
||||
Statement copy(db, QStringLiteral("INSERT INTO folio SELECT uuid, ?, %1, props_at, props_attrs "
|
||||
Statement copy(db, QStringLiteral("INSERT INTO folio SELECT uuid, ?, %1, props_attrs "
|
||||
"FROM old.folio WHERE uuid = ?").arg(quoted(FolioData)));
|
||||
ok = ok && copy.run({pos, id}) && sqlite3_changes(db.m_db) == 1;
|
||||
for (const QString &sql : {
|
||||
@@ -942,7 +945,8 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
|
||||
//Its symbols whose information changed since (QETProject::toXml()):
|
||||
//their information rows written as a symbol's block writes them
|
||||
for (const QDomElement &edited : children(d, QStringLiteral("qetz-edited-element"))) {
|
||||
Statement find(db, QStringLiteral("SELECT uuid FROM element WHERE folio_uuid = ? AND uuid = ? AND info_at IS NOT NULL"));
|
||||
//Its part has the place they go back to (QetContainer::split())
|
||||
Statement find(db, QStringLiteral("SELECT uuid FROM element WHERE folio_uuid = ? AND uuid = ?"));
|
||||
find.bind({id, edited.attribute(QStringLiteral("uuid"))});
|
||||
if (!find.next()) {
|
||||
setError(error, QStringLiteral("a symbol changed on folio %1 kept as it was is not in its database").arg(id));
|
||||
@@ -979,8 +983,7 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
|
||||
row += take(d, FolioData);
|
||||
QDomElement props = d.firstChildElement(QStringLiteral("properties"));
|
||||
const bool move_props = !props.isNull() && listBlock(props, QStringLiteral("property"));
|
||||
row << (move_props ? QVariant(childIndex(props)) : QVariant())
|
||||
<< (move_props ? blockJson(props) : QVariant());
|
||||
row << (move_props ? blockJson(props) : QVariant());
|
||||
ok = ok && folio.run(row);
|
||||
if (move_props) {
|
||||
moveProperties(props, folio_property, {id});
|
||||
@@ -998,24 +1001,20 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB
|
||||
QDomElement links = e.firstChildElement(QStringLiteral("links_uuids"));
|
||||
if (!info.isNull() && !listBlock(info, QStringLiteral("elementInformation"))) info = QDomElement();
|
||||
if (!links.isNull() && !listBlock(links, QStringLiteral("link_uuid"))) links = QDomElement();
|
||||
const int info_at = info.isNull() ? -1 : childIndex(info);
|
||||
const int links_at = links.isNull() ? -1 : childIndex(links);
|
||||
erow << (info_at < 0 ? QVariant() : QVariant(info_at))
|
||||
<< (info_at < 0 ? QVariant() : blockJson(info))
|
||||
<< (links_at < 0 ? QVariant() : QVariant(links_at))
|
||||
<< (links_at < 0 ? QVariant() : blockJson(links));
|
||||
erow << (info.isNull() ? QVariant() : blockJson(info))
|
||||
<< (links.isNull() ? QVariant() : blockJson(links));
|
||||
ok = ok && element.run(erow);
|
||||
if (info_at >= 0)
|
||||
if (!info.isNull())
|
||||
moveInformation(info, key);
|
||||
if (links_at >= 0) {
|
||||
if (!links.isNull()) {
|
||||
int ord = 0;
|
||||
for (QDomElement l : children(links, QStringLiteral("link_uuid")))
|
||||
ok = ok && link.run({key, ord++, l.hasAttribute(QStringLiteral("uuid"))
|
||||
? QVariant(l.attribute(QStringLiteral("uuid"))) : QVariant(),
|
||||
attributesJson(l, {QStringLiteral("uuid")}, QStringLiteral("link_uuid"))});
|
||||
}
|
||||
if (info_at >= 0) leaveMarker(info);
|
||||
if (links_at >= 0) leaveMarker(links);
|
||||
if (!info.isNull()) leaveMarker(info);
|
||||
if (!links.isNull()) leaveMarker(links);
|
||||
if (!own_key) e.setAttribute(KeyAttribute, key);
|
||||
}
|
||||
}
|
||||
@@ -1088,7 +1087,7 @@ bool QetContainerDb::restore(QDomElement project, const QByteArray &database, QS
|
||||
Statement s(db, QStringLiteral("SELECT key, value FROM meta"));
|
||||
while (s.next()) meta.insert(s.value(0).toString(), s.value(1));
|
||||
}
|
||||
if (meta.contains(QStringLiteral("props_at"))) {
|
||||
if (hasMarker(project, QStringLiteral("properties"))) {
|
||||
QDomElement props = document.createElement(QStringLiteral("properties"));
|
||||
setAttributesJson(props, meta.value(QStringLiteral("props_attrs")));
|
||||
Statement s(db, QStringLiteral("SELECT name, value, attrs FROM project_property ORDER BY ord"));
|
||||
@@ -1142,8 +1141,8 @@ bool QetContainerDb::currentFormat(const QByteArray &database)
|
||||
return false;
|
||||
//Its rows are copied as they are: the same columns as this writes
|
||||
const QList<std::pair<QString, int>> columns = {
|
||||
{QStringLiteral("folio"), int(FolioData.size()) + 4},
|
||||
{QStringLiteral("element"), int(ElementData.size()) + 6},
|
||||
{QStringLiteral("folio"), int(FolioData.size()) + 3},
|
||||
{QStringLiteral("element"), int(ElementData.size()) + 4},
|
||||
{QStringLiteral("conductor"), int(ConductorData.size()) + 2}};
|
||||
for (const auto &table : columns) {
|
||||
Statement s(db, QStringLiteral("SELECT count(*) FROM pragma_table_info('%1')").arg(table.first));
|
||||
|
||||
@@ -1984,6 +1984,16 @@ bool QETDiagramEditor::openAndAddProject(
|
||||
addProject(project);
|
||||
DialogWaiting::dropInstance();
|
||||
|
||||
if (interactive && project->isFromLaterVersion()) {
|
||||
QET::QetMessageBox::warning(
|
||||
this,
|
||||
tr("Opening the file read-only", "message box title"),
|
||||
tr("This project was saved by a later version of QElectroTech. "
|
||||
"It is opened read-only, so that saving it cannot lose what "
|
||||
"this version does not know. Use Save As to keep a copy "
|
||||
"in this version's format."));
|
||||
}
|
||||
|
||||
//Report font descriptions which could not be read as-is (written by
|
||||
//an incompatible Qt version or corrupted), so the user learns about
|
||||
//it from somewhere else than the console. See issue #553.
|
||||
|
||||
+10
-2
@@ -484,11 +484,12 @@ QETProject::ProjectState QETProject::openFile(QFile *file)
|
||||
m_container_files.clear();
|
||||
m_folio_parts.clear();
|
||||
bool read = false;
|
||||
int container_format = 0;
|
||||
if (QetContainer::isZip(content)) {
|
||||
QList<QetZip::Entry> entries;
|
||||
read = QetZip::fromBytes(content, &entries, &read_error)
|
||||
&& QetContainer::join(entries, &xml_project, &read_error, &m_folio_parts,
|
||||
&m_container_pictures);
|
||||
&m_container_pictures, &container_format);
|
||||
if (read)
|
||||
for (const QetZip::Entry &entry : std::as_const(entries))
|
||||
m_container_files.insert(entry.name, entry.data);
|
||||
@@ -530,9 +531,16 @@ QETProject::ProjectState QETProject::openFile(QFile *file)
|
||||
<< xml_parse_ms / 1000.0 << ", content "
|
||||
<< (total_ms - xml_parse_ms) / 1000.0 << ")";
|
||||
|
||||
if (!fi.isWritable()) {
|
||||
//Written by a later QElectroTech that lets this one read it: saving
|
||||
//here would drop what this version does not know
|
||||
m_from_later_version = container_format > QetContainer::Format;
|
||||
if (!fi.isWritable() || m_from_later_version) {
|
||||
setReadOnly(true);
|
||||
}
|
||||
if (m_from_later_version) {
|
||||
qInfo().noquote() << QStringLiteral("Project %1 is of .qetz format %2 (this version writes %3): opened read-only")
|
||||
.arg(fi.fileName()).arg(container_format).arg(QetContainer::Format);
|
||||
}
|
||||
if(opened_here) {
|
||||
file->close();
|
||||
}
|
||||
|
||||
@@ -334,6 +334,8 @@ class QETProject : public QObject
|
||||
bool close();
|
||||
QETResult write();
|
||||
bool isReadOnly() const;
|
||||
/// Opened from a .qetz of a later format (opened read-only)
|
||||
bool isFromLaterVersion() const {return m_from_later_version;}
|
||||
void setReadOnly(bool);
|
||||
bool isEmpty() const;
|
||||
ElementsLocation importElement(ElementsLocation &location);
|
||||
@@ -490,6 +492,7 @@ class QETProject : public QObject
|
||||
bool m_modified = false;
|
||||
/// Whether the project is read only
|
||||
bool m_read_only = false;
|
||||
bool m_from_later_version = false;
|
||||
/// Filepath for which this project is considered read only
|
||||
QString read_only_file_path_;
|
||||
/// Default dimensions and properties for new diagrams created within the project
|
||||
|
||||
@@ -379,6 +379,43 @@ private slots:
|
||||
}
|
||||
|
||||
// A file whose manifest needs a later reader is refused, not half read
|
||||
// A later format whose min-reader allows this one opens, and join() says
|
||||
// it is later: QElectroTech opens it read-only
|
||||
void laterFormatAllowedRead()
|
||||
{
|
||||
QDomDocument original;
|
||||
QList<QetZip::Entry> entries = splitFile(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"), &original);
|
||||
QVERIFY(!entries.isEmpty());
|
||||
const int later = QetContainer::Format + 1;
|
||||
for (QetZip::Entry &e : entries)
|
||||
if (e.name == QLatin1String("manifest.xml"))
|
||||
e.data = QString::fromUtf8(e.data).replace(QRegularExpression(QStringLiteral(" format=\"\\d+\"")),
|
||||
QStringLiteral(" format=\"%1\"").arg(later)).toUtf8();
|
||||
alterDatabase(entries, QStringLiteral("UPDATE meta SET value = '%1' WHERE key = 'format'")
|
||||
.arg(later).toUtf8().constData());
|
||||
QDomDocument back;
|
||||
QString error;
|
||||
int format = 0;
|
||||
QVERIFY2(QetContainer::join(entries, &back, &error, nullptr, nullptr, &format), qPrintable(error));
|
||||
QCOMPARE(format, later);
|
||||
const QString d = difference(withoutComputed(original).documentElement(), back.documentElement(), QString());
|
||||
QVERIFY2(d.isEmpty(), qPrintable(d));
|
||||
}
|
||||
|
||||
// The database holds data, not where a block sat in the XML: the folio
|
||||
// file's marker says where it goes back
|
||||
void noPositionColumns()
|
||||
{
|
||||
QDomDocument original;
|
||||
const QList<QetZip::Entry> entries = splitFile(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"), &original);
|
||||
const QByteArray database = entry(entries, QStringLiteral("project.sqlite"));
|
||||
QCOMPARE(query(database, "SELECT group_concat(name) FROM pragma_table_info('element')"),
|
||||
QStringLiteral("uuid,folio_uuid,prefix,freezeLabel,info_attrs,links_attrs"));
|
||||
QCOMPARE(query(database, "SELECT count(*) FROM pragma_table_info('folio') WHERE name LIKE '%_at'"),
|
||||
QStringLiteral("0"));
|
||||
QCOMPARE(query(database, "SELECT count(*) FROM meta WHERE key = 'props_at'"), QStringLiteral("0"));
|
||||
}
|
||||
|
||||
void laterFormatRefused()
|
||||
{
|
||||
QDomDocument original;
|
||||
@@ -455,8 +492,8 @@ private slots:
|
||||
QTest::addColumn<QByteArray>("sql");
|
||||
QTest::addColumn<QString>("said");
|
||||
QTest::newRow("symbol row of no symbol")
|
||||
<< QByteArray("INSERT INTO element SELECT 'x', folio_uuid, prefix, freezeLabel, info_at, info_attrs,"
|
||||
" links_at, links_attrs FROM element LIMIT 1")
|
||||
<< QByteArray("INSERT INTO element SELECT 'x', folio_uuid, prefix, freezeLabel, info_attrs,"
|
||||
" links_attrs FROM element LIMIT 1")
|
||||
<< QStringLiteral("symbol rows");
|
||||
QTest::newRow("symbol row twice")
|
||||
<< QByteArray("INSERT INTO element SELECT * FROM element LIMIT 1")
|
||||
|
||||
Reference in New Issue
Block a user