diff --git a/autotests/courseresource/test_courseresource.cpp b/autotests/courseresource/test_courseresource.cpp index 8110c97..73a6061 100644 --- a/autotests/courseresource/test_courseresource.cpp +++ b/autotests/courseresource/test_courseresource.cpp @@ -1,255 +1,192 @@ /* * Copyright 2013 Andreas Cord-Landwehr * * This program 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 this program. If not, see . */ #include "test_courseresource.h" #include "resourcerepositorystub.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/resources/languageresource.h" #include "core/resources/courseresource.h" #include #include #include #include #include #include #include #include #include TestCourseResource::TestCourseResource() { } void TestCourseResource::init() { } void TestCourseResource::cleanup() { } void TestCourseResource::courseSchemeValidationTest() { QUrl schemeFile = QUrl::fromLocalFile(QStringLiteral("schemes/course.xsd")); QXmlSchema courseSchema; QVERIFY(courseSchema.load(schemeFile)); QVERIFY(courseSchema.isValid()); } void TestCourseResource::loadCourseResource() { Language language; language.setId("de"); ResourceRepositoryStub repository({&language}); const QString courseDirectory = "data/courses/de/"; const QString courseFile = courseDirectory + "de.xml"; CourseResource course(QUrl::fromLocalFile(courseFile), &repository); QCOMPARE(course.file().toLocalFile(), courseFile); QCOMPARE(course.id(), "de"); QCOMPARE(course.foreignId(), "artikulate-basic"); QCOMPARE(course.title(), "Artikulate Deutsch"); QCOMPARE(course.description(), "Ein Kurs in (hoch-)deutscher Aussprache."); QVERIFY(course.language() != nullptr); QCOMPARE(course.language()->id(), "de"); QCOMPARE(course.unitList().count(), 1); const auto unit = course.unitList().first(); QVERIFY(unit != nullptr); QCOMPARE(unit->id(), "1"); QCOMPARE(unit->title(), QStringLiteral("Auf der Straße")); QCOMPARE(unit->foreignId(), "{dd60f04a-eb37-44b7-9787-67aaf7d3578d}"); QCOMPARE(unit->phraseList().count(), 3); // note: this test takes the silent assumption that phrases are added to the list in same // order as they are defined in the file. This assumption should be made explicit or dropped const auto firstPhrase = unit->phraseList().first(); QVERIFY(firstPhrase != nullptr); QCOMPARE(firstPhrase->id(), "1"); QCOMPARE(firstPhrase->foreignId(), "{3a4c1926-60d7-44c6-80d1-03165a641c75}"); QCOMPARE(firstPhrase->text(), "Guten Tag."); QCOMPARE(firstPhrase->soundFileUrl(), courseDirectory + "de_01.ogg"); QCOMPARE(firstPhrase->type(), Phrase::Type::Sentence); QVERIFY(firstPhrase->phonemes().isEmpty()); } void TestCourseResource::unitAddAndRemoveHandling() { // boilerplate Language language; language.setId("de"); ResourceRepositoryStub repository({&language}); const QString courseDirectory = "data/courses/de/"; const QString courseFile = courseDirectory + "de.xml"; CourseResource course(QUrl::fromLocalFile(courseFile), &repository); // begin of test Unit unit; unit.setId("testunit"); const int initialUnitNumber = course.unitList().count(); QCOMPARE(initialUnitNumber, 1); QSignalSpy spyAboutToBeAdded(&course, SIGNAL(unitAboutToBeAdded(Unit*, int))); QSignalSpy spyAdded(&course, SIGNAL(unitAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); course.addUnit(&unit); QCOMPARE(course.unitList().count(), initialUnitNumber + 1); QCOMPARE(spyAboutToBeAdded.count(), 1); QCOMPARE(spyAdded.count(), 1); } void TestCourseResource::coursePropertyChanges() { // boilerplate Language language; language.setId("de"); ResourceRepositoryStub repository({&language}); const QString courseDirectory = "data/courses/de/"; const QString courseFile = courseDirectory + "de.xml"; CourseResource course(QUrl::fromLocalFile(courseFile), &repository); // id { const QString value = "newId"; QSignalSpy spy(&course, SIGNAL(idChanged())); QCOMPARE(spy.count(), 0); course.setId(value); QCOMPARE(course.id(), value); QCOMPARE(spy.count(), 1); } // foreign id { const QString value = "newForeignId"; QSignalSpy spy(&course, SIGNAL(foreignIdChanged())); QCOMPARE(spy.count(), 0); course.setForeignId(value); QCOMPARE(course.foreignId(), value); QCOMPARE(spy.count(), 1); } // title { const QString value = "newTitle"; QSignalSpy spy(&course, SIGNAL(titleChanged())); QCOMPARE(spy.count(), 0); course.setTitle(value); QCOMPARE(course.title(), value); QCOMPARE(spy.count(), 1); } // title { const QString value = "newI18nTitle"; QSignalSpy spy(&course, SIGNAL(i18nTitleChanged())); QCOMPARE(spy.count(), 0); course.setI18nTitle(value); QCOMPARE(course.i18nTitle(), value); QCOMPARE(spy.count(), 1); } // description { const QString value = "newDescription"; QSignalSpy spy(&course, SIGNAL(descriptionChanged())); QCOMPARE(spy.count(), 0); course.setDescription(value); QCOMPARE(course.description(), value); QCOMPARE(spy.count(), 1); } // language { Language testLanguage; QSignalSpy spy(&course, SIGNAL(languageChanged())); QCOMPARE(spy.count(), 0); course.setLanguage(&testLanguage); QCOMPARE(course.language(), &testLanguage); QCOMPARE(spy.count(), 1); } } -// FIXME porting break -void TestCourseResource::fileLoadSaveCompleteness() -{ -// ResourceManager manager; -// manager.addLanguage(QUrl::fromLocalFile(QStringLiteral("data/languages/de.xml"))); -// manager.addCourse(QUrl::fromLocalFile(QStringLiteral("data/courses/de.xml"))); - -// // test to encure further logic -// QVERIFY(manager.courseResources(manager.languageResources().constFirst()->language()).count() == 1); - -// Course *testCourse = manager.courseResources(manager.languageResources().constFirst()->language()).constFirst()->course(); -// QTemporaryFile outputFile; -// outputFile.open(); -// QUrl oldFileName = testCourse->file(); -// testCourse->setFile(QUrl::fromLocalFile(outputFile.fileName())); -// testCourse->setLanguage(manager.languageResources().constFirst()->language()); -// testCourse->sync(); -// testCourse->setFile(oldFileName); // restore for later tests - -// QFile file(outputFile.fileName()); -// if (!file.open(QIODevice::ReadOnly)) { -// qCritical() << "Could not open file to read."; -// } - -// //TODO this only works, since the resource manager not checks uniqueness of course ids! -// manager.addCourse(QUrl::fromLocalFile(outputFile.fileName())); -// Course *compareCourse = manager.courseResources(manager.languageResources().constFirst()->language()).constLast()->course(); - -// // test that we actually call the different files -// QVERIFY(testCourse->file().toLocalFile() != compareCourse->file().toLocalFile()); -// QVERIFY(testCourse->id() == compareCourse->id()); -// QVERIFY(testCourse->foreignId() == compareCourse->foreignId()); -// QVERIFY(testCourse->title() == compareCourse->title()); -// QVERIFY(testCourse->description() == compareCourse->description()); -// QVERIFY(testCourse->language()->id() == compareCourse->language()->id()); -// QVERIFY(testCourse->unitList().count() == compareCourse->unitList().count()); - -// Unit *testUnit = testCourse->unitList().constFirst(); -// Unit *compareUnit = compareCourse->unitList().constFirst(); -// QVERIFY(testUnit->id() == compareUnit->id()); -// QVERIFY(testUnit->foreignId() == compareUnit->foreignId()); -// QVERIFY(testUnit->title() == compareUnit->title()); -// QVERIFY(testUnit->phraseList().count() == compareUnit->phraseList().count()); - -// Phrase *testPhrase = testUnit->phraseList().constFirst(); -// Phrase *comparePhrase = new Phrase(this); -// // Note that this actually means that we DO NOT respect phrase orders by list order! -// foreach (Phrase *phrase, compareUnit->phraseList()) { -// if (testPhrase->id() == phrase->id()) { -// comparePhrase = phrase; -// break; -// } -// } -// QVERIFY(testPhrase->id() == comparePhrase->id()); -// QVERIFY(testPhrase->foreignId() == comparePhrase->foreignId()); -// QVERIFY(testPhrase->text() == comparePhrase->text()); -// QVERIFY(testPhrase->type() == comparePhrase->type()); -// QVERIFY(testPhrase->sound().fileName() == comparePhrase->sound().fileName()); -// QVERIFY(testPhrase->phonemes().count() == comparePhrase->phonemes().count()); -// //FIXME implement phoneme checks after phonemes are fully implemented -} - - QTEST_GUILESS_MAIN(TestCourseResource) diff --git a/autotests/courseresource/test_courseresource.h b/autotests/courseresource/test_courseresource.h index 1139079..9ce1930 100644 --- a/autotests/courseresource/test_courseresource.h +++ b/autotests/courseresource/test_courseresource.h @@ -1,75 +1,69 @@ /* * Copyright 2013-2019 Andreas Cord-Landwehr * * This program 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 this program. If not, see . */ #ifndef TESTCOURSERESOURCE_H #define TESTCOURSERESOURCE_H #include #include class TestCourseResource : public QObject { Q_OBJECT public: TestCourseResource(); private slots: /** * @brief Called before every test case. */ void init(); /** * @brief Called after every test case. */ void cleanup(); /** * @brief Test if course XSD specification is valid. */ void courseSchemeValidationTest(); /** * @brief Test simple loading of course resource XML file */ void loadCourseResource(); /** * @brief Test handling of unit insertions (specifically, the signals) */ void unitAddAndRemoveHandling(); /** * @brief Test of all course property changes except unit handling */ void coursePropertyChanges(); - /** - * Test if serialization of unserialized file gives original file. - * TODO this is a test by only string equality and should improved to test on a data level - */ - void fileLoadSaveCompleteness(); - private: bool m_systemUseCourseRepositoryValue; }; #endif diff --git a/autotests/skeletonresource/test_skeletonresource.cpp b/autotests/skeletonresource/test_skeletonresource.cpp index a083cf1..f256b29 100644 --- a/autotests/skeletonresource/test_skeletonresource.cpp +++ b/autotests/skeletonresource/test_skeletonresource.cpp @@ -1,225 +1,212 @@ /* * Copyright 2019 Andreas Cord-Landwehr * * This program 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 this program. If not, see . */ #include "test_skeletonresource.h" #include "resourcerepositorystub.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/resources/languageresource.h" #include "core/resources/skeletonresource.h" #include #include #include #include #include #include #include #include #include TestSkeletonResource::TestSkeletonResource() { } void TestSkeletonResource::init() { } void TestSkeletonResource::cleanup() { } void TestSkeletonResource::schemeValidationTest() { QUrl skeletonFile = QUrl::fromLocalFile(QStringLiteral("schemes/skeleton.xsd")); QXmlSchema skeletonScheme; QVERIFY(skeletonScheme.load(skeletonFile)); QVERIFY(skeletonScheme.isValid()); } void TestSkeletonResource::loadSkeletonResource() { Language language; language.setId("de"); ResourceRepositoryStub repository({&language}); const QString courseDirectory = "data/contributorrepository/skeletons/"; const QString courseFile = courseDirectory + "skeleton.xml"; SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository); QCOMPARE(course.file().toLocalFile(), courseFile); QCOMPARE(course.id(), "skeleton-testdata"); QCOMPARE(course.foreignId(), "skeleton-testdata"); // always same as ID QCOMPARE(course.title(), "Artikulate Test Course Title"); QCOMPARE(course.description(), "Artikulate Test Course Description"); QVERIFY(course.language() == nullptr); // a skeleton must not have a language QCOMPARE(course.unitList().count(), 2); const auto unit = course.unitList().first(); QVERIFY(unit != nullptr); QCOMPARE(unit->id(), "{11111111-b885-4833-97ff-27cb1ca2f543}"); QCOMPARE(unit->title(), QStringLiteral("Numbers")); QCOMPARE(unit->phraseList().count(), 2); // note: this test takes the silent assumption that phrases are added to the list in same // order as they are defined in the file. This assumption should be made explicit or dropped const auto firstPhrase = unit->phraseList().first(); QVERIFY(firstPhrase != nullptr); QCOMPARE(firstPhrase->id(), "{22222222-9234-4da5-a6fe-dbd5104f57d5}"); QCOMPARE(firstPhrase->text(), "0"); QCOMPARE(firstPhrase->type(), Phrase::Type::Word); const auto secondPhrase = unit->phraseList().at(1); QVERIFY(secondPhrase != nullptr); QCOMPARE(secondPhrase->id(), "{333333333-b4a9-4264-9a26-75a55aa5d302}"); QCOMPARE(secondPhrase->text(), "1"); QCOMPARE(secondPhrase->type(), Phrase::Type::Word); } void TestSkeletonResource::unitAddAndRemoveHandling() { // boilerplate Language language; language.setId("de"); ResourceRepositoryStub repository({&language}); - const QString courseDirectory = "data/courses/de/"; - const QString courseFile = courseDirectory + "de.xml"; + const QString courseDirectory = "data/contributorrepository/skeletons/"; + const QString courseFile = courseDirectory + "skeleton.xml"; SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository); // begin of test Unit unit; unit.setId("testunit"); const int initialUnitNumber = course.unitList().count(); - QCOMPARE(initialUnitNumber, 1); + QCOMPARE(initialUnitNumber, 2); QSignalSpy spyAboutToBeAdded(&course, SIGNAL(unitAboutToBeAdded(Unit*, int))); QSignalSpy spyAdded(&course, SIGNAL(unitAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); course.addUnit(&unit); QCOMPARE(course.unitList().count(), initialUnitNumber + 1); QCOMPARE(spyAboutToBeAdded.count(), 1); QCOMPARE(spyAdded.count(), 1); } void TestSkeletonResource::coursePropertyChanges() { // boilerplate Language language; language.setId("de"); ResourceRepositoryStub repository({&language}); - const QString courseDirectory = "data/courses/de/"; - const QString courseFile = courseDirectory + "de.xml"; + const QString courseDirectory = "data/contributorrepository/skeletons/"; + const QString courseFile = courseDirectory + "skeleton.xml"; SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository); // id { const QString value = "newId"; QSignalSpy spy(&course, SIGNAL(idChanged())); QCOMPARE(spy.count(), 0); course.setId(value); QCOMPARE(course.id(), value); QCOMPARE(spy.count(), 1); } // title { const QString value = "newTitle"; QSignalSpy spy(&course, SIGNAL(titleChanged())); QCOMPARE(spy.count(), 0); course.setTitle(value); QCOMPARE(course.title(), value); QCOMPARE(spy.count(), 1); } // description { const QString value = "newDescription"; QSignalSpy spy(&course, SIGNAL(descriptionChanged())); QCOMPARE(spy.count(), 0); course.setDescription(value); QCOMPARE(course.description(), value); QCOMPARE(spy.count(), 1); } } -// FIXME porting break -//void TestCourseResource::fileLoadSaveCompleteness() -//{ -// ResourceManager manager; -// manager.addLanguage(QUrl::fromLocalFile(QStringLiteral("data/languages/de.xml"))); -// manager.addCourse(QUrl::fromLocalFile(QStringLiteral("data/courses/de.xml"))); - -// // test to encure further logic -// QVERIFY(manager.courseResources(manager.languageResources().constFirst()->language()).count() == 1); - -// Course *testCourse = manager.courseResources(manager.languageResources().constFirst()->language()).constFirst()->course(); -// QTemporaryFile outputFile; -// outputFile.open(); -// QUrl oldFileName = testCourse->file(); -// testCourse->setFile(QUrl::fromLocalFile(outputFile.fileName())); -// testCourse->setLanguage(manager.languageResources().constFirst()->language()); -// testCourse->sync(); -// testCourse->setFile(oldFileName); // restore for later tests - -// QFile file(outputFile.fileName()); -// if (!file.open(QIODevice::ReadOnly)) { -// qCritical() << "Could not open file to read."; -// } - -// //TODO this only works, since the resource manager not checks uniqueness of course ids! -// manager.addCourse(QUrl::fromLocalFile(outputFile.fileName())); -// Course *compareCourse = manager.courseResources(manager.languageResources().constFirst()->language()).constLast()->course(); - -// // test that we actually call the different files -// QVERIFY(testCourse->file().toLocalFile() != compareCourse->file().toLocalFile()); -// QVERIFY(testCourse->id() == compareCourse->id()); -// QVERIFY(testCourse->foreignId() == compareCourse->foreignId()); -// QVERIFY(testCourse->title() == compareCourse->title()); -// QVERIFY(testCourse->description() == compareCourse->description()); -// QVERIFY(testCourse->language()->id() == compareCourse->language()->id()); -// QVERIFY(testCourse->unitList().count() == compareCourse->unitList().count()); - -// Unit *testUnit = testCourse->unitList().constFirst(); -// Unit *compareUnit = compareCourse->unitList().constFirst(); -// QVERIFY(testUnit->id() == compareUnit->id()); -// QVERIFY(testUnit->foreignId() == compareUnit->foreignId()); -// QVERIFY(testUnit->title() == compareUnit->title()); -// QVERIFY(testUnit->phraseList().count() == compareUnit->phraseList().count()); - -// Phrase *testPhrase = testUnit->phraseList().constFirst(); -// Phrase *comparePhrase = new Phrase(this); -// // Note that this actually means that we DO NOT respect phrase orders by list order! -// foreach (Phrase *phrase, compareUnit->phraseList()) { -// if (testPhrase->id() == phrase->id()) { -// comparePhrase = phrase; -// break; -// } -// } -// QVERIFY(testPhrase->id() == comparePhrase->id()); -// QVERIFY(testPhrase->foreignId() == comparePhrase->foreignId()); -// QVERIFY(testPhrase->text() == comparePhrase->text()); -// QVERIFY(testPhrase->type() == comparePhrase->type()); -// QVERIFY(testPhrase->sound().fileName() == comparePhrase->sound().fileName()); -// QVERIFY(testPhrase->phonemes().count() == comparePhrase->phonemes().count()); -// //FIXME implement phoneme checks after phonemes are fully implemented -//} +void TestSkeletonResource::fileLoadSaveCompleteness() +{ + // boilerplate + Language language; + language.setId("de"); + ResourceRepositoryStub repository({&language}); + const QString courseDirectory = "data/contributorrepository/skeletons/"; + const QString courseFile = courseDirectory + "skeleton.xml"; + SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository); + QTemporaryFile outputFile; + outputFile.open(); + course.exportCourse(QUrl::fromLocalFile(outputFile.fileName())); + + // note: this only works, since the resource manager not checks uniqueness of course ids! + SkeletonResource loadedCourse(QUrl::fromLocalFile(outputFile.fileName()), &repository); + + // test that we actually call the different files + QVERIFY(course.file().toLocalFile() != loadedCourse.file().toLocalFile()); + QCOMPARE(loadedCourse.id(), course.id()); + QCOMPARE(loadedCourse.foreignId(), course.foreignId()); + QCOMPARE(loadedCourse.title(), course.title()); + QCOMPARE(loadedCourse.description(), course.description()); + QCOMPARE(loadedCourse.language(), course.language()); + QCOMPARE(loadedCourse.unitList().count(), course.unitList().count()); + + Unit *testUnit = course.unitList().constFirst(); + Unit *compareUnit = loadedCourse.unitList().constFirst(); + QCOMPARE(testUnit->id(), compareUnit->id()); + QCOMPARE(testUnit->foreignId(), compareUnit->foreignId()); + QCOMPARE(testUnit->title(), compareUnit->title()); + QCOMPARE(testUnit->phraseList().count(), compareUnit->phraseList().count()); + + Phrase *testPhrase = testUnit->phraseList().constFirst(); + Phrase *comparePhrase = new Phrase(this); + // note that this actually means that we DO NOT respect phrase orders by list order + for (Phrase *phrase : compareUnit->phraseList()) { + if (testPhrase->id() == phrase->id()) { + comparePhrase = phrase; + break; + } + } + QVERIFY(testPhrase->id() == comparePhrase->id()); + QVERIFY(testPhrase->foreignId() == comparePhrase->foreignId()); + QVERIFY(testPhrase->text() == comparePhrase->text()); + QVERIFY(testPhrase->type() == comparePhrase->type()); + QVERIFY(testPhrase->sound().fileName() == comparePhrase->sound().fileName()); + QVERIFY(testPhrase->phonemes().count() == comparePhrase->phonemes().count()); +} QTEST_GUILESS_MAIN(TestSkeletonResource) diff --git a/autotests/skeletonresource/test_skeletonresource.h b/autotests/skeletonresource/test_skeletonresource.h index 19f284e..7fede40 100644 --- a/autotests/skeletonresource/test_skeletonresource.h +++ b/autotests/skeletonresource/test_skeletonresource.h @@ -1,75 +1,75 @@ /* * Copyright 2013-2019 Andreas Cord-Landwehr * * This program 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 this program. If not, see . */ #ifndef TESTSKELETONRESOURCE_H #define TESTSKELETONRESOURCE_H #include #include class TestSkeletonResource : public QObject { Q_OBJECT public: TestSkeletonResource(); private slots: /** * @brief Called before every test case. */ void init(); /** * @brief Called after every test case. */ void cleanup(); /** * @brief Test if course XSD specification is valid. */ void schemeValidationTest(); /** * @brief Test simple loading of course resource XML file */ void loadSkeletonResource(); /** * @brief Test handling of unit insertions (specifically, the signals) */ void unitAddAndRemoveHandling(); /** * @brief Test of all course property changes except unit handling */ void coursePropertyChanges(); /** * Test if serialization of unserialized file gives original file. * TODO this is a test by only string equality and should improved to test on a data level */ -// void fileLoadSaveCompleteness(); + void fileLoadSaveCompleteness(); private: bool m_systemUseCourseRepositoryValue; }; #endif diff --git a/src/core/resources/courseparser.cpp b/src/core/resources/courseparser.cpp index 7b40373..da8d6ce 100644 --- a/src/core/resources/courseparser.cpp +++ b/src/core/resources/courseparser.cpp @@ -1,418 +1,414 @@ /* * Copyright 2013-2019 Andreas Cord-Landwehr * * This program 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 this program. If not, see . */ #include "courseparser.h" #include "core/icourse.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/phoneme.h" #include "artikulate_debug.h" #include #include #include #include #include #include #include #include QXmlSchema CourseParser::loadXmlSchema(const QString &schemeName) { QString relPath = QStringLiteral(":/artikulate/schemes/%1.xsd").arg(schemeName); QUrl file = QUrl::fromLocalFile(relPath); QXmlSchema schema; if (file.isEmpty() || schema.load(file) == false) { qCWarning(ARTIKULATE_PARSER()) << "Schema at file " << file.toLocalFile() << " is invalid."; } return schema; } QDomDocument CourseParser::loadDomDocument(const QUrl &path, const QXmlSchema &schema) { QDomDocument document; QXmlSchemaValidator validator(schema); if (!validator.validate(path)) { qCWarning(ARTIKULATE_PARSER()) << "Schema is not valid, aborting loading of XML document:" << path.toLocalFile(); return document; } QString errorMsg; QFile file(path.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { if (!document.setContent(&file, &errorMsg)) { qCWarning(ARTIKULATE_PARSER()) << errorMsg; } } else { qCWarning(ARTIKULATE_PARSER()) << "Could not open XML document " << path.toLocalFile() << " for reading, aborting."; } return document; } QVector CourseParser::parseUnits(const QUrl &path) { QVector units; QFileInfo info(path.toLocalFile()); if (!info.exists()) { qCCritical(ARTIKULATE_PARSER()()) << "No course file available at location" << path.toLocalFile(); return units; } QXmlStreamReader xml; QFile file(path.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { xml.setDevice(&file); xml.readNextStartElement(); while (!xml.atEnd() && !xml.hasError()) { bool elementOk{ false }; QXmlStreamReader::TokenType token = xml.readNext(); if (token == QXmlStreamReader::StartDocument) { continue; } if (token == QXmlStreamReader::StartElement) { if (xml.name() == "units") { continue; } else if (xml.name() == "unit") { auto unit = parseUnit(xml, elementOk); if (elementOk) { units.append(unit); } } } } if (xml.hasError()) { qCCritical(ARTIKULATE_PARSER()) << "Error occurred when reading Course XML file:" << path.toLocalFile(); } } else { qCCritical(ARTIKULATE_PARSER()) << "Could not open course file" << path.toLocalFile(); } xml.clear(); file.close(); return units; } Unit * CourseParser::parseUnit(QXmlStreamReader &xml, bool &ok) { Unit * unit = new Unit(nullptr); ok = true; if (xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "unit") { qCWarning(ARTIKULATE_PARSER()) << "Expected to parse 'unit' element, aborting here"; return unit; } xml.readNext(); while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "unit")) { if (xml.tokenType() == QXmlStreamReader::StartElement) { bool elementOk{ false }; if (xml.name() == "id") { unit->setId(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "title") { unit->setTitle(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "phrases") { // nothing to do } else if (xml.name() == "phrase") { auto phrase = parsePhrase(xml, elementOk); if (elementOk) { unit->addPhrase(phrase); } ok &= elementOk; } else { qCWarning(ARTIKULATE_PARSER()) << "Skipping unknown token" << xml.name(); } } xml.readNext(); } if (!ok) { qCWarning(ARTIKULATE_PARSER()) << "Errors occured while parsing unit" << unit->title() << unit->id(); } return unit; } Phrase * CourseParser::parsePhrase(QXmlStreamReader &xml, bool &ok) { Phrase * phrase = new Phrase(nullptr); ok = true; if (xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "phrase") { qCWarning(ARTIKULATE_PARSER()) << "Expected to parse 'phrase' element, aborting here"; ok = false; return phrase; } xml.readNext(); while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "phrase")) { if (xml.tokenType() == QXmlStreamReader::StartElement) { bool elementOk{ false }; if (xml.name() == "id") { phrase->setId(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "text") { phrase->setText(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "type") { const QString type = parseElement(xml, elementOk); if (type == "word") { phrase->setType(Phrase::Word); } else if (type == "expression") { phrase->setType(Phrase::Expression); } else if (type == "sentence") { phrase->setType(Phrase::Sentence); } else if (type == "paragraph") { phrase->setType(Phrase::Paragraph); } ok &= elementOk; } else { qCWarning(ARTIKULATE_PARSER()) << "Skipping unknown token" << xml.name(); } } xml.readNext(); } if (!ok) { qCWarning(ARTIKULATE_PARSER()) << "Errors occured while parsing phrase" << phrase->text() << phrase->id(); } return phrase; } QString CourseParser::parseElement(QXmlStreamReader& xml, bool &ok) { ok = true; if (xml.tokenType() != QXmlStreamReader::StartElement) { qCCritical(ARTIKULATE_PARSER()) << "Parsing element that does not start with a start element"; ok = false; return QString(); } -// if(xml.tokenType() != QXmlStreamReader::Characters) { -// qCCritical(ARTIKULATE_PARSER()) << "Parsing non-character token"; -// return QString(); -// } QString elementName = xml.name().toString(); xml.readNext(); - qCDebug(ARTIKULATE_PARSER()) << "parsed: " << elementName << " / " << xml.text().toString(); +// qCDebug(ARTIKULATE_PARSER()) << "parsed: " << elementName << " / " << xml.text().toString(); return xml.text().toString(); } Phrase * CourseParser::parsePhrase(QDomElement phraseNode, Unit* parentUnit) { const ICourse *course = parentUnit->course(); Q_ASSERT(course != nullptr); Phrase *phrase = new Phrase(parentUnit); phrase->setId(phraseNode.firstChildElement(QStringLiteral("id")).text()); phrase->setText(phraseNode.firstChildElement(QStringLiteral("text")).text()); phrase->seti18nText(phraseNode.firstChildElement(QStringLiteral("i18nText")).text()); phrase->setUnit(parentUnit); if (!phraseNode.firstChildElement(QStringLiteral("soundFile")).text().isEmpty()) { phrase->setSound(QUrl::fromLocalFile( course->file().adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + phraseNode.firstChildElement(QStringLiteral("soundFile")).text()) ); } phrase->setType(phraseNode.firstChildElement(QStringLiteral("type")).text()); phrase->setEditState(phraseNode.firstChildElement(QStringLiteral("editState")).text()); if (!phraseNode.firstChildElement(QStringLiteral("foreignId")).isNull()) { phrase->setForeignId(phraseNode.firstChildElement(QStringLiteral("foreignId")).text()); } // add phonemes QList phonemes = course->language()->phonemes(); for (QDomElement phonemeID = phraseNode.firstChildElement(QStringLiteral("phonemes")).firstChildElement(); !phonemeID.isNull(); phonemeID = phonemeID.nextSiblingElement()) { QString id = phonemeID.text(); if (id.isEmpty()) { qCritical() << "Phoneme ID string is empty for phrase "<< phrase->id() <<", aborting."; continue; } for (Phoneme *phoneme : phonemes) { if (phoneme->id() == id) { phrase->addPhoneme(phoneme); break; } } } if (!phraseNode.firstChildElement(QStringLiteral("excluded")).isNull() && phraseNode.firstChildElement(QStringLiteral("excluded")).text() == QLatin1String("true")) { phrase->setExcluded(true); } return phrase; } QDomDocument CourseParser::serializedDocument(ICourse *course, bool trainingExport) { QDomDocument document; // prepare xml header QDomProcessingInstruction header = document.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\"")); document.appendChild(header); // create main element QDomElement root = document.createElement(QStringLiteral("course")); document.appendChild(root); QDomElement idElement = document.createElement(QStringLiteral("id")); QDomElement titleElement = document.createElement(QStringLiteral("title")); QDomElement descriptionElement = document.createElement(QStringLiteral("description")); QDomElement languageElement = document.createElement(QStringLiteral("language")); idElement.appendChild(document.createTextNode(course->id())); titleElement.appendChild(document.createTextNode(course->title())); descriptionElement.appendChild(document.createTextNode(course->description())); languageElement.appendChild(document.createTextNode(course->id())); QDomElement unitListElement = document.createElement(QStringLiteral("units")); // create units for (Unit *unit : course->unitList()) { QDomElement unitElement = document.createElement(QStringLiteral("unit")); QDomElement unitIdElement = document.createElement(QStringLiteral("id")); QDomElement unitTitleElement = document.createElement(QStringLiteral("title")); QDomElement unitPhraseListElement = document.createElement(QStringLiteral("phrases")); unitIdElement.appendChild(document.createTextNode(unit->id())); unitTitleElement.appendChild(document.createTextNode(unit->title())); // construct phrases for (Phrase *phrase : unit->phraseList()) { if (trainingExport && phrase->soundFileUrl().isEmpty()) { continue; } unitPhraseListElement.appendChild(serializedPhrase(phrase, document)); } if (trainingExport && unitPhraseListElement.childNodes().count() == 0) { continue; } // construct the unit element unitElement.appendChild(unitIdElement); if (!unit->foreignId().isEmpty()) { QDomElement unitForeignIdElement = document.createElement(QStringLiteral("foreignId")); unitForeignIdElement.appendChild(document.createTextNode(unit->foreignId())); unitElement.appendChild(unitForeignIdElement); } unitElement.appendChild(unitTitleElement); unitElement.appendChild(unitPhraseListElement); unitListElement.appendChild(unitElement); } root.appendChild(idElement); if (!course->foreignId().isEmpty()) { QDomElement courseForeignIdElement = document.createElement(QStringLiteral("foreignId")); courseForeignIdElement.appendChild(document.createTextNode(course->foreignId())); root.appendChild(courseForeignIdElement); } root.appendChild(titleElement); root.appendChild(descriptionElement); root.appendChild(languageElement); root.appendChild(unitListElement); return document; } QDomElement CourseParser::serializedPhrase(Phrase *phrase, QDomDocument &document) { QDomElement phraseElement = document.createElement(QStringLiteral("phrase")); QDomElement phraseIdElement = document.createElement(QStringLiteral("id")); QDomElement phraseTextElement = document.createElement(QStringLiteral("text")); QDomElement phrasei18nTextElement = document.createElement(QStringLiteral("i18nText")); QDomElement phraseSoundFileElement = document.createElement(QStringLiteral("soundFile")); QDomElement phraseTypeElement = document.createElement(QStringLiteral("type")); QDomElement phraseEditStateElement = document.createElement(QStringLiteral("editState")); QDomElement phrasePhonemeListElement = document.createElement(QStringLiteral("phonemes")); phraseIdElement.appendChild(document.createTextNode(phrase->id())); phraseTextElement.appendChild(document.createTextNode(phrase->text())); phrasei18nTextElement.appendChild(document.createTextNode(phrase->i18nText())); phraseSoundFileElement.appendChild(document.createTextNode(phrase->sound().fileName())); phraseTypeElement.appendChild(document.createTextNode(phrase->typeString())); phraseEditStateElement.appendChild(document.createTextNode(phrase->editStateString())); // add phonemes foreach (Phoneme *phoneme, phrase->phonemes()) { QDomElement phonemeElement = document.createElement(QStringLiteral("phonemeID")); phonemeElement.appendChild(document.createTextNode(phoneme->id())); phrasePhonemeListElement.appendChild(phonemeElement); } phraseElement.appendChild(phraseIdElement); if (!phrase->foreignId().isEmpty()) { QDomElement phraseForeignIdElement = document.createElement(QStringLiteral("foreignId")); phraseForeignIdElement.appendChild(document.createTextNode(phrase->foreignId())); phraseElement.appendChild(phraseForeignIdElement); } phraseElement.appendChild(phraseTextElement); phraseElement.appendChild(phrasei18nTextElement); phraseElement.appendChild(phraseSoundFileElement); phraseElement.appendChild(phraseTypeElement); phraseElement.appendChild(phraseEditStateElement); phraseElement.appendChild(phrasePhonemeListElement); if (phrase->isExcluded()) { QDomElement phraseIsExcludedElement = document.createElement(QStringLiteral("excluded")); phraseIsExcludedElement.appendChild(document.createTextNode(QStringLiteral("true"))); phraseElement.appendChild(phraseIsExcludedElement); } return phraseElement; } bool CourseParser::exportCourseToGhnsPackage(ICourse *course, const QString &exportPath) { // filename const QString fileName = course->id() + ".tar.bz2"; KTar tar = KTar(exportPath + '/' + fileName, QStringLiteral("application/x-bzip")); if (!tar.open(QIODevice::WriteOnly)) { qCWarning(ARTIKULATE_CORE()) << "Unable to open tar file" << exportPath + '/' + fileName << "in write mode, aborting."; return false; } for (auto *unit : course->unitList()) { for (auto *phrase : unit->phraseList()) { if (QFile::exists(phrase->soundFileUrl())) { tar.addLocalFile(phrase->soundFileUrl(), phrase->id() + ".ogg"); } } } tar.writeFile(course->id() + ".xml", CourseParser::serializedDocument(course, true).toByteArray()); tar.close(); return true; } diff --git a/src/core/resources/skeletonresource.cpp b/src/core/resources/skeletonresource.cpp index 4bd18f1..d208404 100644 --- a/src/core/resources/skeletonresource.cpp +++ b/src/core/resources/skeletonresource.cpp @@ -1,266 +1,287 @@ /* * Copyright 2013 Andreas Cord-Landwehr * * This program 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 this program. If not, see . */ #include "skeletonresource.h" #include "courseparser.h" #include "core/language.h" #include "core/unit.h" #include "editablecourseresource.h" #include "core/phoneme.h" #include "core/phonemegroup.h" #include "core/resources/languageresource.h" +#include #include #include #include #include +#include #include "artikulate_debug.h" class SkeletonResourcePrivate { public: SkeletonResourcePrivate(const QUrl &path) : m_path(path) { // load basic information from language file, but does not parse everything QXmlStreamReader xml; QFile file(path.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { xml.setDevice(&file); xml.readNextStartElement(); while (xml.readNext() && !xml.atEnd()) { if (xml.name() == "id") { m_identifier = xml.readElementText(); continue; } if (xml.name() == "title") { m_title = xml.readElementText(); continue; } if (xml.name() == "description") { m_description = xml.readElementText(); continue; } // quit reading when basic elements are read if (!m_identifier.isEmpty() && !m_title.isEmpty() && !m_description.isEmpty() ) { break; } } if (xml.hasError()) { qCritical() << "Error occurred when reading Course XML file:" << path.toLocalFile(); } } else { qCCritical(ARTIKULATE_CORE()) << "Could not open course file" << path.toLocalFile(); } xml.clear(); file.close(); } - QVector units() { - if (m_unitsParsed) { - return m_units; - } - m_units = CourseParser::parseUnits(m_path); - m_unitsParsed = true; - return m_units; - } + QVector units(); + + void appendUnit(Unit *unit); + + /** + * @return the skeleton resource as serialized byte array + */ + QDomDocument serializedSkeleton(); QUrl m_path; QString m_identifier; QString m_title; QString m_description; - QVector m_units; bool m_unitsParsed{ false }; + +protected: + QVector m_units; ///!< the units variable is loaded lazily and shall never be access directly }; +QVector SkeletonResourcePrivate::units() +{ + if (m_unitsParsed) { + return m_units; + } + m_units = CourseParser::parseUnits(m_path); + m_unitsParsed = true; + return m_units; +} + +void SkeletonResourcePrivate::appendUnit(Unit *unit) { + units(); // ensure that units are parsed + m_units.append(unit); +} + +QDomDocument SkeletonResourcePrivate::serializedSkeleton() +{ + QDomDocument document; + // prepare xml header + QDomProcessingInstruction header = document.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\"")); + document.appendChild(header); + + // create main element + QDomElement root = document.createElement(QStringLiteral("skeleton")); + document.appendChild(root); + + QDomElement idElement = document.createElement(QStringLiteral("id")); + QDomElement titleElement = document.createElement(QStringLiteral("title")); + QDomElement descriptionElement = document.createElement(QStringLiteral("description")); + + idElement.appendChild(document.createTextNode(m_identifier)); + titleElement.appendChild(document.createTextNode(m_title)); + descriptionElement.appendChild(document.createTextNode(m_description)); + + QDomElement unitListElement = document.createElement(QStringLiteral("units")); + // create units + for (auto unit : units()) { + QDomElement unitElement = document.createElement(QStringLiteral("unit")); + + QDomElement unitIdElement = document.createElement(QStringLiteral("id")); + QDomElement unitTitleElement = document.createElement(QStringLiteral("title")); + QDomElement unitPhraseListElement = document.createElement(QStringLiteral("phrases")); + unitIdElement.appendChild(document.createTextNode(unit->id())); + unitTitleElement.appendChild(document.createTextNode(unit->title())); + + // construct phrases + for (Phrase *phrase : unit->phraseList()) { + QDomElement phraseElement = document.createElement(QStringLiteral("phrase")); + QDomElement phraseIdElement = document.createElement(QStringLiteral("id")); + QDomElement phraseTextElement = document.createElement(QStringLiteral("text")); + QDomElement phraseTypeElement = document.createElement(QStringLiteral("type")); + + phraseIdElement.appendChild(document.createTextNode(phrase->id())); + phraseTextElement.appendChild(document.createTextNode(phrase->text())); + phraseTypeElement.appendChild(document.createTextNode(phrase->typeString())); + + phraseElement.appendChild(phraseIdElement); + phraseElement.appendChild(phraseTextElement); + phraseElement.appendChild(phraseTypeElement); + + unitPhraseListElement.appendChild(phraseElement); + } + + // construct the unit element + unitElement.appendChild(unitIdElement); + unitElement.appendChild(unitTitleElement); + unitElement.appendChild(unitPhraseListElement); + + unitListElement.appendChild(unitElement); + } + + root.appendChild(idElement); + root.appendChild(titleElement); + root.appendChild(descriptionElement); + root.appendChild(unitListElement); + + return document; +} + SkeletonResource::SkeletonResource(const QUrl &path, IResourceRepository *repository) : ICourse() , d(new SkeletonResourcePrivate(path)) { Q_UNUSED(repository); } SkeletonResource::~SkeletonResource() = default; QString SkeletonResource::id() const { return d->m_identifier; } void SkeletonResource::setId(const QString &id) { if (d->m_identifier == id) { return; } d->m_identifier = id; emit idChanged(); } QString SkeletonResource::foreignId() const { return id(); } QString SkeletonResource::title() const { return d->m_title; } void SkeletonResource::setTitle(const QString &title) { if (d->m_title == title) { return; } d->m_title = title; emit titleChanged(); } QString SkeletonResource::i18nTitle() const { // there are no localized titles available return title(); } QString SkeletonResource::description() const { return d->m_description; } void SkeletonResource::setDescription(const QString &description) { if (d->m_description == description) { return; } d->m_description = description; emit descriptionChanged(); } -void SkeletonResource::addUnit(Unit *unit) +bool SkeletonResource::exportCourse(const QUrl &filePath) { - emit unitAboutToBeAdded(unit, d->m_units.count() - 1); - d->m_units.append(unit); - emit unitAdded(); -} - -void SkeletonResource::sync() -{ - Q_ASSERT(file().isValid()); - Q_ASSERT(file().isLocalFile()); - Q_ASSERT(!file().isEmpty()); - -// // not writing back if not modified -// if (!d->m_skeletonResource->modified()) { -// qCDebug(ARTIKULATE_LOG) << "Aborting sync, skeleton was not modified."; -// return; -// } - - QDomDocument document; - // prepare xml header - QDomProcessingInstruction header = document.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\"")); - document.appendChild(header); - - // create main element - QDomElement root = document.createElement(QStringLiteral("skeleton")); - document.appendChild(root); - - QDomElement idElement = document.createElement(QStringLiteral("id")); - QDomElement titleElement = document.createElement(QStringLiteral("title")); - QDomElement descriptionElement = document.createElement(QStringLiteral("description")); - - idElement.appendChild(document.createTextNode(id())); - titleElement.appendChild(document.createTextNode(title())); - descriptionElement.appendChild(document.createTextNode(description())); - - QDomElement unitListElement = document.createElement(QStringLiteral("units")); - // create units - for (auto unit : d->m_units) { - QDomElement unitElement = document.createElement(QStringLiteral("unit")); - - QDomElement unitIdElement = document.createElement(QStringLiteral("id")); - QDomElement unitTitleElement = document.createElement(QStringLiteral("title")); - QDomElement unitPhraseListElement = document.createElement(QStringLiteral("phrases")); - unitIdElement.appendChild(document.createTextNode(unit->id())); - unitTitleElement.appendChild(document.createTextNode(unit->title())); - - // construct phrases - foreach (Phrase *phrase, unit->phraseList()) { - QDomElement phraseElement = document.createElement(QStringLiteral("phrase")); - QDomElement phraseIdElement = document.createElement(QStringLiteral("id")); - QDomElement phraseTextElement = document.createElement(QStringLiteral("text")); - QDomElement phraseTypeElement = document.createElement(QStringLiteral("type")); - - phraseIdElement.appendChild(document.createTextNode(phrase->id())); - phraseTextElement.appendChild(document.createTextNode(phrase->text())); - phraseTypeElement.appendChild(document.createTextNode(phrase->typeString())); - - phraseElement.appendChild(phraseIdElement); - phraseElement.appendChild(phraseTextElement); - phraseElement.appendChild(phraseTypeElement); - - unitPhraseListElement.appendChild(phraseElement); - } - - // construct the unit element - unitElement.appendChild(unitIdElement); - unitElement.appendChild(unitTitleElement); - unitElement.appendChild(unitPhraseListElement); - - unitListElement.appendChild(unitElement); + // write back to file + // create directories if necessary + QFileInfo info(filePath.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); + if (!info.exists()) { + qCDebug(ARTIKULATE_LOG()) << "create xml output file directory, not existing"; + QDir dir; + dir.mkpath(filePath.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); } - root.appendChild(idElement); - root.appendChild(titleElement); - root.appendChild(descriptionElement); - root.appendChild(unitListElement); - - - // write back to file - //TODO port to KSaveFile - QFile filePath(file().toLocalFile()); - if (!filePath.open(QIODevice::WriteOnly)) { - qCWarning(ARTIKULATE_LOG) << "Unable to open file " << filePath.fileName() << " in write mode, aborting."; - return; + //TODO port to atomic file swap + QFile file(filePath.toLocalFile()); + if (!file.open(QIODevice::WriteOnly)) { + qCWarning(ARTIKULATE_LOG()) << "Unable to open file " << filePath << " in write mode, aborting."; + return false; } + file.write(d->serializedSkeleton().toByteArray()); + + return true; +} - filePath.write(document.toByteArray()); - return; +void SkeletonResource::addUnit(Unit *unit) +{ + emit unitAboutToBeAdded(unit, d->units().count() - 1); + d->appendUnit(unit); + emit unitAdded(); } Language * SkeletonResource::language() const { // skeleton must not have a dedicated language return nullptr; } QList SkeletonResource::unitList() { return d->units().toList(); } QUrl SkeletonResource::file() const { return d->m_path; } diff --git a/src/core/resources/skeletonresource.h b/src/core/resources/skeletonresource.h index 07817d0..98075b1 100644 --- a/src/core/resources/skeletonresource.h +++ b/src/core/resources/skeletonresource.h @@ -1,75 +1,76 @@ /* * Copyright 2013 Andreas Cord-Landwehr * * This program 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 this program. If not, see . */ #ifndef SKELETONRESOURCE_H #define SKELETONRESOURCE_H #include "artikulatecore_export.h" #include "core/icourse.h" #include class SkeletonResourcePrivate; class IResourceRepository; /** * @brief The SkeletonResource class is a decorator for EditableCourseResource */ class ARTIKULATECORE_EXPORT SkeletonResource : public ICourse { Q_OBJECT Q_INTERFACES(ICourse) public: /** * Create course resource from file. */ explicit SkeletonResource(const QUrl &path, IResourceRepository *repository); ~SkeletonResource() override; QString id() const override; void setId(const QString &id); QString foreignId() const override; QString title() const override; void setTitle(const QString &title); QString i18nTitle() const override; QString description() const override; void setDescription(const QString &description); Language * language() const override; QList unitList() override; QUrl file() const override; + bool exportCourse(const QUrl &filePath); + void addUnit(Unit *unit); - void sync(); bool isModified() const { return true;} //FIXME private: const QScopedPointer d; }; #endif