diff --git a/autotests/integrationtests/iresourcerepository_integration/test_iresourcerepository.cpp b/autotests/integrationtests/iresourcerepository_integration/test_iresourcerepository.cpp index 4a3c48c..d147424 100644 --- a/autotests/integrationtests/iresourcerepository_integration/test_iresourcerepository.cpp +++ b/autotests/integrationtests/iresourcerepository_integration/test_iresourcerepository.cpp @@ -1,89 +1,92 @@ /* * 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_iresourcerepository.h" #include #include #include #include "core/resourcerepository.h" #include "core/contributorrepository.h" #include "core/language.h" +#include "core/icourse.h" +#include "core/unit.h" #include "../src/settings.h" void TestIResourceRepository::init() { // check that test data is deployed at the expected location QVERIFY(QFile::exists("data/courses/de/de.xml")); QVERIFY(QFile::exists("data/courses/fr/fr.xml")); } void TestIResourceRepository::resourceRepository() { ResourceRepository repository(QUrl::fromLocalFile("data/courses/")); QCOMPARE(repository.storageLocation(), "data/courses/"); performInterfaceTests(&repository); } void TestIResourceRepository::contributorRepository() { ContributorRepository repository; repository.setStorageLocation("data/contributorrepository/"); // contributor repository requires subdirectory "courses" QCOMPARE(repository.storageLocation(), "data/contributorrepository/"); performInterfaceTests(&repository); } void TestIResourceRepository::performInterfaceTests(IResourceRepository *interface) { QVERIFY(interface->languages().count() > 0); // automatically load languages QCOMPARE(interface->courses().count(), 0); // load courses only on demand // test adding QSignalSpy spyAboutToBeAdded(dynamic_cast(interface), SIGNAL(courseAboutToBeAdded(ICourse*, int))); QSignalSpy spyAdded(dynamic_cast(interface), SIGNAL(courseAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); interface->reloadCourses(); // initial loading of courses QCOMPARE(interface->courses().count(), 2); QCOMPARE(spyAboutToBeAdded.count(), 2); QCOMPARE(spyAdded.count(), 2); // test reloading of courses interface->reloadCourses(); // initial loading of courses QCOMPARE(interface->courses().count(), 2); // test removal // note: repository does not provide removal of courses, yet // test access of courses grouped by language auto languages = interface->languages(); std::shared_ptr german; for (auto language : interface->languages()) { if (language->id() == "de") { german = language; break; } } QVERIFY(german != nullptr); // ensure that German language was found QCOMPARE(interface->courses(german->id()).count(), 1); // there is exactly one German course QCOMPARE(interface->courses(nullptr).count(), 2); // all courses in total are 2 + QVERIFY(interface->courses().first()->units().size() > 0); } QTEST_GUILESS_MAIN(TestIResourceRepository) diff --git a/autotests/unittests/courseresource/test_courseresource.cpp b/autotests/unittests/courseresource/test_courseresource.cpp index a26fd33..a390b2b 100644 --- a/autotests/unittests/courseresource/test_courseresource.cpp +++ b/autotests/unittests/courseresource/test_courseresource.cpp @@ -1,197 +1,199 @@ /* * 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/phonemegroup.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::loadCourseResource() { std::unique_ptr language(new Language); language->setId("de"); auto group = language->addPhonemeGroup("id", "title"); group->addPhoneme("g", "G"); group->addPhoneme("u", "U"); std::vector> languages; languages.push_back(std::move(language)); ResourceRepositoryStub repository(std::move(languages)); 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.units().count(), 1); + QCOMPARE(course.units().first()->course(), &course); const auto unit = course.units().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); QCOMPARE(firstPhrase->phonemes().count(), 2); } void TestCourseResource::unitAddAndRemoveHandling() { // boilerplate std::unique_ptr language(new Language); language->setId("de"); std::vector> languages; languages.push_back(std::move(language)); ResourceRepositoryStub repository(std::move(languages)); const QString courseDirectory = "data/courses/de/"; const QString courseFile = courseDirectory + "de.xml"; CourseResource course(QUrl::fromLocalFile(courseFile), &repository); // begin of test std::unique_ptr unit(new Unit); unit->setId("testunit"); const int initialUnitNumber = course.units().count(); QCOMPARE(initialUnitNumber, 1); QSignalSpy spyAboutToBeAdded(&course, SIGNAL(unitAboutToBeAdded(std::shared_ptr, int))); QSignalSpy spyAdded(&course, SIGNAL(unitAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); - course.addUnit(std::move(unit)); + auto sharedUnit = course.addUnit(std::move(unit)); QCOMPARE(course.units().count(), initialUnitNumber + 1); QCOMPARE(spyAboutToBeAdded.count(), 1); QCOMPARE(spyAdded.count(), 1); + QCOMPARE(sharedUnit->course(), &course); } void TestCourseResource::coursePropertyChanges() { // boilerplate std::unique_ptr language(new Language); language->setId("de"); std::vector> languages; languages.push_back(std::move(language)); ResourceRepositoryStub repository(std::move(languages)); 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 { std::shared_ptr testLanguage; QSignalSpy spy(&course, SIGNAL(languageChanged())); QCOMPARE(spy.count(), 0); course.setLanguage(testLanguage); QCOMPARE(course.language(), testLanguage); QCOMPARE(spy.count(), 1); } } QTEST_GUILESS_MAIN(TestCourseResource) diff --git a/autotests/unittests/editablecourseresource/test_editablecourseresource.cpp b/autotests/unittests/editablecourseresource/test_editablecourseresource.cpp index 398cf79..78f6d95 100644 --- a/autotests/unittests/editablecourseresource/test_editablecourseresource.cpp +++ b/autotests/unittests/editablecourseresource/test_editablecourseresource.cpp @@ -1,250 +1,252 @@ /* * 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 "test_editablecourseresource.h" #include "resourcerepositorystub.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/resources/courseparser.h" #include "core/resources/languageresource.h" #include "core/resources/editablecourseresource.h" #include #include #include #include #include #include #include #include #include #include TestEditableCourseResource::TestEditableCourseResource() { } void TestEditableCourseResource::init() { } void TestEditableCourseResource::cleanup() { } void TestEditableCourseResource::courseSchemeValidationTest() { QUrl schemeFile = QUrl::fromLocalFile(":/artikulate/schemes/course.xsd"); QXmlSchema courseSchema; QVERIFY(courseSchema.load(schemeFile)); QVERIFY(courseSchema.isValid()); //TODO shall be used in skeleton specific test QUrl skeletonFile = QUrl::fromLocalFile(":/artikulate/schemes/skeleton.xsd"); QXmlSchema skeletonScheme; QVERIFY(skeletonScheme.load(skeletonFile)); QVERIFY(skeletonScheme.isValid()); } void TestEditableCourseResource::loadCourseResource() { std::unique_ptr language(new Language); language->setId("de"); std::vector> languages; languages.push_back(std::move(language)); ResourceRepositoryStub repository(std::move(languages)); EditableCourseResource course(QUrl::fromLocalFile(":/courses/de.xml"), &repository); QCOMPARE(course.file().toLocalFile(), ":/courses/de.xml"); 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.units().count(), 1); + QCOMPARE(course.units().first()->course(), &course); const auto unit = course.units().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(), ":/courses/de_01.ogg"); QCOMPARE(firstPhrase->type(), Phrase::Type::Sentence); QVERIFY(firstPhrase->phonemes().isEmpty()); } void TestEditableCourseResource::unitAddAndRemoveHandling() { // boilerplate std::unique_ptr language(new Language); language->setId("de"); std::vector> languages; languages.push_back(std::move(language)); ResourceRepositoryStub repository(std::move(languages)); EditableCourseResource course(QUrl::fromLocalFile(":/courses/de.xml"), &repository); // begin of test std::unique_ptr unit(new Unit); unit->setId("testunit"); const int initialUnitNumber = course.units().count(); QCOMPARE(initialUnitNumber, 1); QSignalSpy spyAboutToBeAdded(&course, SIGNAL(unitAboutToBeAdded(std::shared_ptr, int))); QSignalSpy spyAdded(&course, SIGNAL(unitAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); - course.addUnit(std::move(unit)); + auto sharedUnit = course.addUnit(std::move(unit)); QCOMPARE(course.units().count(), initialUnitNumber + 1); QCOMPARE(spyAboutToBeAdded.count(), 1); QCOMPARE(spyAdded.count(), 1); + QCOMPARE(sharedUnit->course(), &course); } void TestEditableCourseResource::coursePropertyChanges() { // boilerplate std::unique_ptr language(new Language); language->setId("de"); std::vector> languages; languages.push_back(std::move(language)); ResourceRepositoryStub repository(std::move(languages)); CourseResource course(QUrl::fromLocalFile(":/courses/de.xml"), &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 { std::shared_ptr testLanguage; QSignalSpy spy(&course, SIGNAL(languageChanged())); QCOMPARE(spy.count(), 0); course.setLanguage(testLanguage); QCOMPARE(course.language(), testLanguage); QCOMPARE(spy.count(), 1); } } void TestEditableCourseResource::fileLoadSaveCompleteness() { // boilerplate std::unique_ptr language(new Language); language->setId("de"); std::vector> languages; languages.push_back(std::move(language)); ResourceRepositoryStub repository(std::move(languages)); EditableCourseResource course(QUrl::fromLocalFile(":/courses/de.xml"), &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! EditableCourseResource loadedCourse(QUrl::fromLocalFile(outputFile.fileName()), &repository); // test that we actually call the different files QVERIFY(course.file().toLocalFile() != loadedCourse.file().toLocalFile()); QVERIFY(course.id() == loadedCourse.id()); QVERIFY(course.foreignId() == loadedCourse.foreignId()); QVERIFY(course.title() == loadedCourse.title()); QVERIFY(course.description() == loadedCourse.description()); QVERIFY(course.language()->id() == loadedCourse.language()->id()); QVERIFY(course.units().count() == loadedCourse.units().count()); auto testUnit = course.units().constFirst(); auto compareUnit = loadedCourse.units().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 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(TestEditableCourseResource) diff --git a/src/core/resources/courseresource.cpp b/src/core/resources/courseresource.cpp index b02b0d4..129b20d 100644 --- a/src/core/resources/courseresource.cpp +++ b/src/core/resources/courseresource.cpp @@ -1,267 +1,268 @@ /* * Copyright 2013-2015 Andreas Cord-Landwehr * Copyright 2013 Oindrila Gupta * * 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 "courseresource.h" #include "courseparser.h" #include "core/language.h" #include "core/unit.h" #include "core/phoneme.h" #include "core/phonemegroup.h" #include "core/language.h" #include "core/iresourcerepository.h" #include #include #include #include #include #include #include #include #include "artikulate_debug.h" class CourseResourcePrivate { public: CourseResourcePrivate() = default; ~CourseResourcePrivate(); void loadCourse(CourseResource *parent); IResourceRepository *m_repository{ nullptr }; QUrl m_file; QString m_identifier; QString m_foreignId; QString m_title; QString m_languageId; std::shared_ptr m_language; QString m_i18nTitle; QString m_description; QVector> m_units; bool m_courseLoaded{ false }; ///> phonemes = m_language->phonemes(); auto units = CourseParser::parseUnits(m_file, phonemes); for (auto &unit : units) { parent->addUnit(std::move(unit)); } } CourseResource::CourseResource(const QUrl &path, IResourceRepository *repository) : ICourse(repository) , d(new CourseResourcePrivate()) { QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); d->m_file = path; d->m_repository = repository; // 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") { d->m_identifier = xml.readElementText(); continue; } if (xml.name() == "foreignId") { d->m_foreignId = xml.readElementText(); continue; } //TODO i18nTitle must be implemented, currently missing and hence not parsed if (xml.name() == "title") { d->m_title = xml.readElementText(); d->m_i18nTitle = d->m_title; continue; } if (xml.name() == "description") { d->m_description = xml.readElementText(); continue; } if (xml.name() == "language") { d->m_languageId = xml.readElementText(); continue; } // quit reading when basic elements are read if (!d->m_identifier.isEmpty() && !d->m_title.isEmpty() && !d->m_i18nTitle.isEmpty() && !d->m_description.isEmpty() && !d->m_languageId.isEmpty() && !d->m_foreignId.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(); // find correct language if (repository != nullptr) { for (const auto &language : repository->languages()) { if (language == nullptr) { continue; } if (language->id() == d->m_languageId) { d->m_language = language; } } } if (d->m_language == nullptr) { qCCritical(ARTIKULATE_CORE()) << "A course with an unknown language was loaded"; } } CourseResource::~CourseResource() = default; QString CourseResource::id() const { return d->m_identifier; } void CourseResource::setId(const QString &id) { if (d->m_identifier == id) { return; } d->m_identifier = id; emit idChanged(); } QString CourseResource::foreignId() const { return d->m_foreignId; } void CourseResource::setForeignId(const QString &foreignId) { if (d->m_foreignId == foreignId) { return; } d->m_foreignId = foreignId; emit foreignIdChanged(); } QString CourseResource::title() const { return d->m_title; } void CourseResource::setTitle(const QString &title) { if (d->m_title == title) { return; } d->m_title = title; emit titleChanged(); } QString CourseResource::i18nTitle() const { return d->m_i18nTitle; } void CourseResource::setI18nTitle(const QString &i18nTitle) { if (d->m_i18nTitle == i18nTitle) { return; } d->m_i18nTitle = i18nTitle; emit i18nTitleChanged(); } QString CourseResource::description() const { return d->m_description; } void CourseResource::setDescription(const QString &description) { if (d->m_description == description) { return; } d->m_description = description; emit descriptionChanged(); } std::shared_ptr CourseResource::language() const { return d->m_language; } void CourseResource::setLanguage(std::shared_ptr language) { if (d->m_language == language) { return; } d->m_language = language; emit languageChanged(); } std::shared_ptr CourseResource::addUnit(std::unique_ptr unit) { std::shared_ptr storedUnit(std::move(unit)); + storedUnit->setCourse(this); emit unitAboutToBeAdded(storedUnit, d->m_units.count() - 1); d->m_units.append(storedUnit); emit unitAdded(); return storedUnit; } QVector> CourseResource::units() { if (d->m_courseLoaded == false) { d->loadCourse(this); } return d->m_units; } QUrl CourseResource::file() const { return d->m_file; } diff --git a/src/core/resources/editablecourseresource.cpp b/src/core/resources/editablecourseresource.cpp index 57d58cb..f1bbb99 100644 --- a/src/core/resources/editablecourseresource.cpp +++ b/src/core/resources/editablecourseresource.cpp @@ -1,221 +1,228 @@ /* * 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 "editablecourseresource.h" #include "courseparser.h" #include "artikulate_debug.h" #include "core/unit.h" #include "core/phrase.h" #include "core/phoneme.h" #include #include #include #include #include #include #include #include #include EditableCourseResource::EditableCourseResource(const QUrl &path, IResourceRepository *repository) : IEditableCourse() , m_course(new CourseResource(path, repository)) { QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); + + for (auto unit : m_course->units()) { + unit->setCourse(this); + } + connect(m_course.get(), &ICourse::unitAboutToBeAdded, this, &ICourse::unitAboutToBeAdded); connect(m_course.get(), &ICourse::unitAdded, this, &ICourse::unitAdded); connect(m_course.get(), &CourseResource::idChanged, this, &EditableCourseResource::idChanged); connect(m_course.get(), &CourseResource::foreignIdChanged, this, &EditableCourseResource::foreignIdChanged); connect(m_course.get(), &CourseResource::titleChanged, this, &EditableCourseResource::titleChanged); connect(m_course.get(), &CourseResource::descriptionChanged, this, &EditableCourseResource::descriptionChanged); connect(m_course.get(), &CourseResource::languageChanged, this, &EditableCourseResource::languageChanged); } QString EditableCourseResource::id() const { return m_course->id(); } void EditableCourseResource::setId(QString id) { m_course->setId(id); } QString EditableCourseResource::foreignId() const { return m_course->foreignId(); } void EditableCourseResource::setForeignId(QString foreignId) { m_course->setForeignId(std::move(foreignId)); } QString EditableCourseResource::title() const { return m_course->title(); } void EditableCourseResource::setTitle(QString title) { m_course->setTitle(title); } QString EditableCourseResource::i18nTitle() const { return m_course->i18nTitle(); } void EditableCourseResource::setI18nTitle(QString i18nTitle) { m_course->setI18nTitle(i18nTitle); } QString EditableCourseResource::description() const { return m_course->description(); } void EditableCourseResource::setDescription(QString description) { m_course->setDescription(description); } std::shared_ptr EditableCourseResource::language() const { return m_course->language(); } void EditableCourseResource::setLanguage(std::shared_ptr language) { m_course->setLanguage(language); } QUrl EditableCourseResource::file() const { return m_course->file(); } void EditableCourseResource::sync() { Q_ASSERT(file().isValid()); Q_ASSERT(file().isLocalFile()); Q_ASSERT(!file().isEmpty()); // not writing back if not modified if (!m_modified) { qCDebug(ARTIKULATE_LOG()) << "Aborting sync, course was not modified."; return; } exportCourse(file()); } bool EditableCourseResource::exportCourse(const QUrl &filePath) { // 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()); } //TODO port to KSaveFile QFile file(filePath.toLocalFile()); if (!file.open(QIODevice::WriteOnly)) { qCWarning(ARTIKULATE_LOG()) << "Unable to open file " << file.fileName() << " in write mode, aborting."; return false; } file.write(CourseParser::serializedDocument(m_course.get(), false).toByteArray()); return true; } std::shared_ptr EditableCourseResource::addUnit(std::unique_ptr unit) { setModified(true); - return m_course->addUnit(std::move(unit)); + auto sharedUnit = m_course->addUnit(std::move(unit)); + sharedUnit->setCourse(this); + return sharedUnit; } QVector> EditableCourseResource::units() { return m_course->units(); } bool EditableCourseResource::isModified() const { return m_modified; } void EditableCourseResource::setModified(bool modified) { m_modified = modified; } Unit * EditableCourseResource::createUnit() { // find first unused id QStringList unitIds; for (auto unit : m_course->units()) { unitIds.append(unit->id()); } QString id = QUuid::createUuid().toString(); while (unitIds.contains(id)) { id = QUuid::createUuid().toString(); qCWarning(ARTIKULATE_LOG) << "Unit id generator has found a collision, recreating id."; } // create unit std::unique_ptr unit(new Unit(this)); unit->setCourse(this); unit->setId(id); unit->setTitle(i18n("New Unit")); auto sharedUnit = addUnit(std::move(unit)); return sharedUnit.get(); } Phrase * EditableCourseResource::createPhrase(Unit *unit) { // find globally unique phrase id inside course QStringList phraseIds; for (auto unit : m_course->units()) { for (auto *phrase : unit->phraseList()) { phraseIds.append(phrase->id()); } } QString id = QUuid::createUuid().toString(); while (phraseIds.contains(id)) { id = QUuid::createUuid().toString(); qCWarning(ARTIKULATE_LOG) << "Phrase id generator has found a collision, recreating id."; } // create unit Phrase *phrase = new Phrase(this); phrase->setId(id); phrase->setText(QLatin1String("")); phrase->setType(Phrase::Word); unit->addPhrase(phrase); return phrase; }