1 /*
2     SPDX-FileCopyrightText: 2013-2014 Andreas Cord-Landwehr <cordlandwehr@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 
7 #include "profilemanager.h"
8 #include "learner.h"
9 #include "storage.h"
10 
11 #include "liblearner_debug.h"
12 #include <KConfigCore/KConfig>
13 #include <KConfigCore/KConfigGroup>
14 #include <KLocalizedString>
15 #include <QFileDialog>
16 #include <QList>
17 #include <memory>
18 
19 using namespace LearnerProfile;
20 
21 /// BEGIN: ProfileManagerPrivate
22 namespace LearnerProfile
23 {
24 class ProfileManagerPrivate
25 {
26 public:
27     ProfileManagerPrivate();
~ProfileManagerPrivate()28     ~ProfileManagerPrivate()
29     {
30     }
31 
32     void sync();
33 
34     QList<Learner *> m_profiles;
35     Learner *m_activeProfile;
36     QList<LearningGoal *> m_goals;
37     std::unique_ptr<KConfig> m_config;
38     Storage m_storage;
39 };
40 }
41 
ProfileManagerPrivate()42 LearnerProfile::ProfileManagerPrivate::ProfileManagerPrivate()
43     : m_profiles(QList<Learner *>())
44     , m_activeProfile(nullptr)
45     , m_config(new KConfig(QStringLiteral("learnerprofilerc")))
46 {
47     // load all profiles from storage
48     m_goals.append(m_storage.loadGoals());
49     m_profiles.append(m_storage.loadProfiles(m_goals));
50 
51     // set last used profile
52     KConfigGroup activeProfileGroup(m_config.get(), "ActiveProfile");
53     int lastProfileId = activeProfileGroup.readEntry("profileId", "0").toInt();
54     QList<int> activeGoalsCategories = activeProfileGroup.readEntry("activeGoalsCategories", QList<int>());
55     QList<QString> activeGoalsIdentifiers = activeProfileGroup.readEntry("activeGoalsIdentifiers", QList<QString>());
56     foreach (Learner *learner, m_profiles) {
57         if (learner->identifier() == lastProfileId) {
58             m_activeProfile = learner;
59             // set active goals
60             if (activeGoalsCategories.count() == activeGoalsIdentifiers.count()) {
61                 for (int i = 0; i < activeGoalsCategories.count(); ++i) {
62                     m_activeProfile->setActiveGoal(static_cast<Learner::Category>(activeGoalsCategories.at(i)), activeGoalsIdentifiers.at(i));
63                 }
64             } else {
65                 qCCritical(LIBLEARNER_LOG()) << "Inconsistent goal category / identifier pairs found: aborting.";
66             }
67             break;
68         }
69     }
70     if (m_activeProfile == nullptr) {
71         qCDebug(LIBLEARNER_LOG()) << "No last active profile found, falling back to first found profile";
72         if (m_profiles.size() > 0) {
73             m_activeProfile = m_profiles.at(0);
74         }
75     }
76 }
77 
sync()78 void ProfileManagerPrivate::sync()
79 {
80     // sync last used profile data
81     if (m_activeProfile) {
82         KConfigGroup activeProfileGroup(m_config.get(), "ActiveProfile");
83         activeProfileGroup.writeEntry("profileId", m_activeProfile->identifier());
84 
85         // compute activer learning goals by category
86         QList<int> goalCatogries;
87         QList<QString> goalIdentifiers;
88         // compute used goals
89         foreach (LearningGoal *goal, m_activeProfile->goals()) {
90             if (!goalCatogries.contains(static_cast<int>(goal->category()))) {
91                 goalCatogries.append(static_cast<int>(goal->category()));
92             }
93         }
94         // compute active goals
95         foreach (int category, goalCatogries) {
96             goalIdentifiers.append(m_activeProfile->activeGoal(static_cast<Learner::Category>(category))->identifier());
97         }
98         activeProfileGroup.writeEntry("activeGoalsCategories", goalCatogries);
99         activeProfileGroup.writeEntry("activeGoalsIdentifiers", goalIdentifiers);
100     } else {
101         qCCritical(LIBLEARNER_LOG()) << "No active profile selected, aborting sync.";
102     }
103     m_config->sync();
104 
105     // TODO only sync changed learner
106     foreach (Learner *learner, m_profiles) {
107         m_storage.storeProfile(learner);
108     }
109 }
110 /// END: ProfileManagerPrivate
111 
ProfileManager(QObject * parent)112 ProfileManager::ProfileManager(QObject *parent)
113     : QObject(parent)
114     , d(new ProfileManagerPrivate)
115 {
116     connect(this, &ProfileManager::profileAdded, this, &ProfileManager::profileCountChanged);
117     connect(this, &ProfileManager::profileRemoved, this, &ProfileManager::profileCountChanged);
118 
119     foreach (Learner *learner, d->m_profiles) {
120         connect(learner, &Learner::goalRemoved, this, &ProfileManager::removeLearningGoal);
121     }
122 }
123 
~ProfileManager()124 ProfileManager::~ProfileManager()
125 {
126     for (auto learner : d->m_profiles) {
127         learner->deleteLater();
128     }
129     for (auto goal : d->m_goals) {
130         goal->deleteLater();
131     }
132 }
133 
profiles() const134 QList<Learner *> ProfileManager::profiles() const
135 {
136     return d->m_profiles;
137 }
138 
profileCount() const139 int ProfileManager::profileCount() const
140 {
141     return profiles().length();
142 }
143 
openImageFileDialog()144 void ProfileManager::openImageFileDialog()
145 {
146     const QString imagePath = QFileDialog::getOpenFileName(nullptr, i18n("Open Image"), QLatin1String(""), i18n("Image Files (*.png *.jpg *.bmp)"));
147     d->m_activeProfile->importImage(imagePath);
148 }
149 
addProfile(const QString & name)150 Learner *ProfileManager::addProfile(const QString &name)
151 {
152     Learner *learner = new Learner(this);
153     learner->setName(name);
154 
155     // set id
156     int maxUsedId = 0;
157     foreach (Learner *cpLearner, d->m_profiles) {
158         if (cpLearner->identifier() >= maxUsedId) {
159             maxUsedId = cpLearner->identifier();
160         }
161     }
162     learner->setIdentifier(maxUsedId + 1);
163 
164     d->m_profiles.append(learner);
165     d->m_storage.storeProfile(learner);
166     emit profileAdded(learner, d->m_profiles.count() - 1);
167 
168     if (activeProfile() == nullptr) {
169         setActiveProfile(learner);
170     }
171 
172     connect(learner, &Learner::goalRemoved, this, &ProfileManager::removeLearningGoal);
173 
174     return learner;
175 }
176 
removeProfile(Learner * learner)177 void ProfileManager::removeProfile(Learner *learner)
178 {
179     int index = d->m_profiles.indexOf(learner);
180     if (index < 0) {
181         qCWarning(LIBLEARNER_LOG()) << "Profile was not found, aborting";
182         return;
183     }
184     emit profileAboutToBeRemoved(index);
185     d->m_profiles.removeAt(index);
186     d->m_storage.removeProfile(learner);
187 
188     if (d->m_activeProfile == learner) {
189         if (d->m_profiles.isEmpty()) {
190             setActiveProfile(nullptr);
191         } else {
192             setActiveProfile(d->m_profiles.at(0));
193         }
194     }
195     emit profileRemoved();
196 }
197 
removeLearningGoal(Learner * learner,LearningGoal * goal)198 void ProfileManager::removeLearningGoal(Learner *learner, LearningGoal *goal)
199 {
200     d->m_storage.removeRelation(learner, goal);
201 }
202 
profile(int index)203 Learner *ProfileManager::profile(int index)
204 {
205     if (index < 0 || index >= profiles().count()) {
206         return nullptr;
207     }
208     return profiles().at(index);
209 }
210 
goals() const211 QList<LearningGoal *> ProfileManager::goals() const
212 {
213     return d->m_goals;
214 }
215 
registerGoal(LearningGoal::Category category,const QString & identifier,const QString & name)216 LearningGoal *ProfileManager::registerGoal(LearningGoal::Category category, const QString &identifier, const QString &name)
217 {
218     // test whether goal is already registered
219     foreach (LearningGoal *cmpGoal, d->m_goals) {
220         if (cmpGoal->category() == category && cmpGoal->identifier() == identifier) {
221             return cmpGoal;
222         }
223     }
224     LearningGoal *goal = new LearningGoal(category, identifier, this);
225     goal->setName(name);
226     d->m_goals.append(goal);
227     d->m_storage.storeGoal(goal);
228     return goal;
229 }
230 
goal(LearningGoal::Category category,const QString & identifier) const231 LearnerProfile::LearningGoal *LearnerProfile::ProfileManager::goal(LearningGoal::Category category, const QString &identifier) const
232 {
233     foreach (LearningGoal *goal, d->m_goals) {
234         if (goal->category() == category && goal->identifier() == identifier) {
235             return goal;
236         }
237     }
238     return nullptr;
239 }
240 
recordProgress(Learner * learner,LearningGoal * goal,const QString & container,const QString & item,int logPayload,int valuePayload)241 void ProfileManager::recordProgress(Learner *learner, LearningGoal *goal, const QString &container, const QString &item, int logPayload, int valuePayload)
242 {
243     if (!learner) {
244         qCDebug(LIBLEARNER_LOG()) << "No learner set, no data stored";
245         return;
246     }
247     d->m_storage.storeProgressLog(learner, goal, container, item, logPayload, QDateTime::currentDateTime());
248     d->m_storage.storeProgressValue(learner, goal, container, item, valuePayload);
249 }
250 
progressValues(Learner * learner,LearningGoal * goal,const QString & container) const251 QHash<QString, int> ProfileManager::progressValues(Learner *learner, LearningGoal *goal, const QString &container) const
252 {
253     if (!learner || !goal) {
254         return QHash<QString, int>();
255     }
256     return d->m_storage.readProgressValues(learner, goal, container);
257 }
258 
sync()259 void ProfileManager::sync()
260 {
261     d->sync();
262 }
263 
sync(Learner * learner)264 void ProfileManager::sync(Learner *learner)
265 {
266     d->m_storage.storeProfile(learner);
267 }
268 
activeProfile() const269 Learner *ProfileManager::activeProfile() const
270 {
271     return d->m_activeProfile;
272 }
273 
setActiveProfile(Learner * learner)274 void ProfileManager::setActiveProfile(Learner *learner)
275 {
276     if (learner == d->m_activeProfile) {
277         return;
278     }
279     d->m_activeProfile = learner;
280     emit activeProfileChanged();
281 }
282