Skip to content

Commit 04c2133

Browse files
committed
fix: deinitialize excerpts after export to preserve lightweight state
When exporting parts that were temporarily initialized for export, they would remain initialized and get saved as full excerpts on subsequent save operations. This fix adds deinitExcerpts() which restores excerpts to their lightweight state after export completes. Changes: - Add ExcerptNotation::deinit() to reset excerpt to lightweight state - Add MasterNotation::deinitExcerpts() interface method - Call deinitExcerpts in export DEFER block after export completes - Make Excerpt::setInited() public for use by deinit - Add test for lightweight excerpt init/deinit cycle
1 parent 3679ee9 commit 04c2133

File tree

8 files changed

+144
-1
lines changed

8 files changed

+144
-1
lines changed

src/engraving/dom/excerpt.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,13 @@ class Excerpt
9696
static void cloneSpanner(Spanner* s, Score* score, track_idx_t dstTrack, track_idx_t dstTrack2);
9797
static void createLinkedTabs(MasterScore* score);
9898

99+
void setInited(bool inited);
100+
99101
private:
100102
friend class MasterScore;
101103

102104
static void promoteGapRestsToRealRests(const Measure* measure, staff_idx_t staffIdx);
103105

104-
void setInited(bool inited);
105106
void writeNameToMetaTags();
106107

107108
void updateTracksMapping();

src/engraving/tests/parts_tests.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,3 +1634,112 @@ TEST_F(Engraving_PartsTests, saveLightweightExcerpt)
16341634
// Cleanup - comment out for debugging
16351635
std::filesystem::remove(std::filesystem::path(tempFile.toStdString()));
16361636
}
1637+
1638+
//---------------------------------------------------------
1639+
// lightweightExcerptAfterInitDeinit
1640+
/// Test that lightweight excerpts remain lightweight after being
1641+
/// temporarily initialized (e.g., for export) and then deinitialized.
1642+
/// This simulates the export flow where excerpts are initialized for
1643+
/// rendering but should return to lightweight state after export.
1644+
/// Related: https://github.com/musescore/MuseScore/issues/31656
1645+
//---------------------------------------------------------
1646+
1647+
TEST_F(Engraving_PartsTests, lightweightExcerptAfterInitDeinit)
1648+
{
1649+
using namespace muse::io;
1650+
using namespace mu::engraving::rw;
1651+
1652+
// Load a score
1653+
MasterScore* score = ScoreRW::readScore(PARTS_DATA_DIR + u"part-all.mscx");
1654+
ASSERT_TRUE(score);
1655+
1656+
// Create a lightweight excerpt
1657+
Excerpt* excerpt = new Excerpt(score);
1658+
Part* part = score->parts().front();
1659+
excerpt->parts().push_back(part);
1660+
excerpt->setInitialPartId(part->id());
1661+
1662+
String customName = u"Init Deinit Test Part";
1663+
excerpt->setName(customName, false);
1664+
1665+
// Add as lightweight excerpt
1666+
score->addLightweightExcerpt(excerpt);
1667+
1668+
// Verify it's lightweight
1669+
ASSERT_EQ(excerpt->excerptScore(), nullptr) << "Excerpt should start as lightweight";
1670+
1671+
// Initialize the excerpt (simulates what export does)
1672+
score->initExcerpt(excerpt);
1673+
ASSERT_NE(excerpt->excerptScore(), nullptr) << "Excerpt should be initialized after initExcerpt";
1674+
EXPECT_TRUE(excerpt->inited()) << "Excerpt should be marked as inited";
1675+
1676+
// Deinitialize the excerpt (simulates what happens after export)
1677+
Score* excerptScore = excerpt->excerptScore();
1678+
excerpt->setExcerptScore(nullptr);
1679+
excerpt->setInited(false);
1680+
delete excerptScore;
1681+
1682+
// Verify it's back to lightweight
1683+
ASSERT_EQ(excerpt->excerptScore(), nullptr) << "Excerpt should be lightweight after deinit";
1684+
EXPECT_FALSE(excerpt->inited()) << "Excerpt should not be marked as inited after deinit";
1685+
1686+
// Save to MSCZ
1687+
String tempFile = String::fromStdString(
1688+
(std::filesystem::temp_directory_path() / "init-deinit-test.mscz").string());
1689+
1690+
if (File::exists(tempFile)) {
1691+
File::remove(tempFile);
1692+
}
1693+
1694+
{
1695+
MscWriter::Params writerParams;
1696+
writerParams.filePath = tempFile;
1697+
writerParams.mode = MscIoMode::Zip;
1698+
1699+
MscWriter mscWriter(writerParams);
1700+
ASSERT_TRUE(mscWriter.open()) << "Failed to open MscWriter";
1701+
1702+
MscSaver saver(score->iocContext());
1703+
bool saveOk = saver.writeMscz(score, mscWriter, false);
1704+
ASSERT_TRUE(saveOk) << "Failed to save score";
1705+
}
1706+
1707+
delete score;
1708+
1709+
// Verify the saved file has lightweight excerpt
1710+
{
1711+
MscReader::Params readerParams;
1712+
readerParams.filePath = tempFile;
1713+
readerParams.mode = MscIoMode::Zip;
1714+
1715+
MscReader mscReader(readerParams);
1716+
ASSERT_TRUE(mscReader.open()) << "Failed to open MscReader";
1717+
1718+
std::vector<String> excerptFiles = mscReader.excerptFileNames();
1719+
ASSERT_EQ(excerptFiles.size(), 1u) << "Should have one excerpt file";
1720+
1721+
ByteArray excerptData = mscReader.readExcerptFile(excerptFiles.front());
1722+
ASSERT_FALSE(excerptData.empty()) << "Excerpt file should exist";
1723+
1724+
String excerptXml = String::fromUtf8(excerptData);
1725+
1726+
// Should be lightweight (small size, lightweight marker, no full content)
1727+
EXPECT_TRUE(excerptXml.contains(u"<lightweight>")) << "Should have lightweight marker";
1728+
EXPECT_FALSE(excerptXml.contains(u"<Staff>")) << "Should not have Staff element";
1729+
EXPECT_FALSE(excerptXml.contains(u"<Measure>")) << "Should not have Measure element";
1730+
EXPECT_LT(excerptData.size(), 1024u) << "Lightweight excerpt should be small (< 1KB)";
1731+
}
1732+
1733+
// Reload and verify
1734+
MasterScore* reloadedScore = ScoreRW::readScore(tempFile, true);
1735+
ASSERT_TRUE(reloadedScore) << "Failed to reload score";
1736+
1737+
ASSERT_EQ(reloadedScore->excerpts().size(), 1u) << "Should have one excerpt";
1738+
Excerpt* loadedExcerpt = reloadedScore->excerpts().front();
1739+
1740+
EXPECT_EQ(loadedExcerpt->name(), customName) << "Name should be preserved";
1741+
EXPECT_EQ(loadedExcerpt->excerptScore(), nullptr) << "Should still be lightweight after reload";
1742+
1743+
delete reloadedScore;
1744+
std::filesystem::remove(std::filesystem::path(tempFile.toStdString()));
1745+
}

src/notation/imasternotation.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class IMasterNotation
5656
virtual const ExcerptNotationList& potentialExcerpts() const = 0;
5757

5858
virtual void initExcerpts(const ExcerptNotationList& excerpts) = 0;
59+
virtual void deinitExcerpts(const ExcerptNotationList& excerpts) = 0;
5960
virtual void setExcerpts(const ExcerptNotationList& excerpts) = 0;
6061
virtual void resetExcerpt(IExcerptNotationPtr excerpt) = 0;
6162
virtual void sortExcerpts(ExcerptNotationList& excerpts) = 0;

src/notation/internal/excerptnotation.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ void ExcerptNotation::init()
5454
m_inited = true;
5555
}
5656

57+
void ExcerptNotation::deinit()
58+
{
59+
if (!m_inited) {
60+
return;
61+
}
62+
63+
// Delete the excerptScore and reset to lightweight state
64+
mu::engraving::Score* excerptScore = m_excerpt->excerptScore();
65+
if (excerptScore) {
66+
setScore(nullptr);
67+
m_excerpt->setExcerptScore(nullptr);
68+
m_excerpt->setInited(false);
69+
delete excerptScore;
70+
}
71+
72+
m_inited = false;
73+
}
74+
5775
void ExcerptNotation::reinit(engraving::Excerpt* newExcerpt)
5876
{
5977
m_inited = false;

src/notation/internal/excerptnotation.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class ExcerptNotation : public IExcerptNotation, public Notation, public std::en
3434
~ExcerptNotation() override;
3535

3636
void init();
37+
void deinit();
3738
void reinit(engraving::Excerpt* newExcerpt);
3839

3940
engraving::Excerpt* excerpt() const;

src/notation/internal/masternotation.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,14 @@ void MasterNotation::initExcerpts(const ExcerptNotationList& excerpts)
473473
}
474474
}
475475

476+
void MasterNotation::deinitExcerpts(const ExcerptNotationList& excerpts)
477+
{
478+
for (const IExcerptNotationPtr& excerptNotation : excerpts) {
479+
ExcerptNotation* impl = get_impl(excerptNotation);
480+
impl->deinit();
481+
}
482+
}
483+
476484
void MasterNotation::setExcerpts(const ExcerptNotationList& excerpts)
477485
{
478486
TRACEFUNC;

src/notation/internal/masternotation.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class MasterNotation : public IMasterNotation, public Notation, public std::enab
5959
const ExcerptNotationList& potentialExcerpts() const override;
6060

6161
void initExcerpts(const ExcerptNotationList& excerpts) override;
62+
void deinitExcerpts(const ExcerptNotationList& excerpts) override;
6263
void setExcerpts(const ExcerptNotationList& excerpts) override;
6364
void resetExcerpt(IExcerptNotationPtr excerptNotation) override;
6465
void sortExcerpts(ExcerptNotationList& excerpts) override;

src/project/internal/exportprojectscenario.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ bool ExportProjectScenario::exportScores(notation::INotationPtrList notations, c
177177
// Restore view modes
178178
setViewModes(notations, viewModes);
179179

180+
// Deinitialize excerpts that were only initialized for export
181+
// This keeps them as lightweight excerpts so they don't get saved as full excerpts
182+
masterNotation()->deinitExcerpts(excerptsToInit);
183+
180184
if (writerProgress) {
181185
m_exportProgress.finish(muse::make_ok());
182186
writerProgress->progressChanged().disconnect(this);

0 commit comments

Comments
 (0)