1 /*
2 * Copyright (C) 2018 Emeric Poupon
3 *
4 * This file is part of LMS.
5 *
6 * LMS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * LMS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with LMS. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "DatabaseSettingsView.hpp"
21
22 #include <Wt/WComboBox.h>
23 #include <Wt/WFormModel.h>
24 #include <Wt/WLineEdit.h>
25 #include <Wt/WPushButton.h>
26 #include <Wt/WString.h>
27 #include <Wt/WTemplateFormView.h>
28
29 #include "database/Cluster.hpp"
30 #include "database/ScanSettings.hpp"
31 #include "database/Session.hpp"
32 #include "scanner/IScanner.hpp"
33 #include "utils/Logger.hpp"
34 #include "utils/Service.hpp"
35 #include "utils/String.hpp"
36
37 #include "common/DirectoryValidator.hpp"
38 #include "common/MandatoryValidator.hpp"
39 #include "common/ValueStringModel.hpp"
40 #include "ScannerController.hpp"
41 #include "LmsApplication.hpp"
42
43 namespace UserInterface {
44
45 using namespace Database;
46
47
48 class DatabaseSettingsModel : public Wt::WFormModel
49 {
50 public:
51 // Associate each field with a unique string literal.
52 static inline constexpr Field MediaDirectoryField {"media-directory"};
53 static inline constexpr Field UpdatePeriodField {"update-period"};
54 static inline constexpr Field UpdateStartTimeField {"update-start-time"};
55 static inline constexpr Field RecommendationEngineTypeField {"recommendation-engine-type"};
56 static inline constexpr Field TagsField {"tags"};
57
58 using UpdatePeriodModel = ValueStringModel<ScanSettings::UpdatePeriod>;
59
DatabaseSettingsModel()60 DatabaseSettingsModel()
61 {
62 initializeModels();
63
64 addField(MediaDirectoryField);
65 addField(UpdatePeriodField);
66 addField(UpdateStartTimeField);
67 addField(RecommendationEngineTypeField);
68 addField(TagsField);
69
70 auto dirValidator {createDirectoryValidator()};
71 dirValidator->setMandatory(true);
72 setValidator(MediaDirectoryField, dirValidator);
73
74 setValidator(UpdatePeriodField, createMandatoryValidator());
75 setValidator(UpdateStartTimeField, createMandatoryValidator());
76 setValidator(RecommendationEngineTypeField, createMandatoryValidator());
77 setValidator(TagsField, createTagsValidator());
78
79 // populate the model with initial data
80 loadData();
81 }
82
updatePeriodModel()83 std::shared_ptr<UpdatePeriodModel> updatePeriodModel() { return _updatePeriodModel; }
updateStartTimeModel()84 std::shared_ptr<Wt::WAbstractItemModel> updateStartTimeModel() { return _updateStartTimeModel; }
recommendationEngineTypeModel()85 std::shared_ptr<Wt::WAbstractItemModel> recommendationEngineTypeModel() { return _recommendationEngineTypeModel; }
86
loadData()87 void loadData()
88 {
89 auto transaction {LmsApp->getDbSession().createSharedTransaction()};
90
91 const ScanSettings::pointer scanSettings {ScanSettings::get(LmsApp->getDbSession())};
92
93 setValue(MediaDirectoryField, scanSettings->getMediaDirectory().string());
94
95 auto periodRow {_updatePeriodModel->getRowFromValue(scanSettings->getUpdatePeriod())};
96 if (periodRow)
97 setValue(UpdatePeriodField, _updatePeriodModel->getString(*periodRow));
98
99 auto startTimeRow {_updateStartTimeModel->getRowFromValue(scanSettings->getUpdateStartTime())};
100 if (startTimeRow)
101 setValue(UpdateStartTimeField, _updateStartTimeModel->getString(*startTimeRow));
102
103 if (scanSettings->getUpdatePeriod() == ScanSettings::UpdatePeriod::Hourly
104 || scanSettings->getUpdatePeriod() == ScanSettings::UpdatePeriod::Never)
105 {
106 setReadOnly(DatabaseSettingsModel::UpdateStartTimeField, true);
107 }
108
109 auto recommendationEngineTypeRow {_recommendationEngineTypeModel->getRowFromValue(scanSettings->getRecommendationEngineType())};
110 if (recommendationEngineTypeRow)
111 setValue(RecommendationEngineTypeField, _recommendationEngineTypeModel->getString(*recommendationEngineTypeRow));
112
113 auto clusterTypes {scanSettings->getClusterTypes()};
114 if (!clusterTypes.empty())
115 {
116 std::vector<std::string> names;
117 std::transform(clusterTypes.begin(), clusterTypes.end(), std::back_inserter(names), [](auto clusterType) { return clusterType->getName(); });
118 setValue(TagsField, StringUtils::joinStrings(names, " "));
119 }
120 }
121
saveData()122 void saveData()
123 {
124 auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
125
126 ScanSettings::pointer scanSettings {ScanSettings::get(LmsApp->getDbSession())};
127
128 scanSettings.modify()->setMediaDirectory(valueText(MediaDirectoryField).toUTF8());
129
130 auto updatePeriodRow {_updatePeriodModel->getRowFromString(valueText(UpdatePeriodField))};
131 if (updatePeriodRow)
132 scanSettings.modify()->setUpdatePeriod(_updatePeriodModel->getValue(*updatePeriodRow));
133
134 auto startTimeRow {_updateStartTimeModel->getRowFromString(valueText(UpdateStartTimeField))};
135 if (startTimeRow)
136 scanSettings.modify()->setUpdateStartTime(_updateStartTimeModel->getValue(*startTimeRow));
137
138 auto recommendationEngineTypeRow {_recommendationEngineTypeModel->getRowFromString(valueText(RecommendationEngineTypeField))};
139 if (recommendationEngineTypeRow)
140 scanSettings.modify()->setRecommendationEngineType(_recommendationEngineTypeModel->getValue(*recommendationEngineTypeRow));
141
142 auto clusterTypes {StringUtils::splitString(valueText(TagsField).toUTF8(), " ")};
143 scanSettings.modify()->setClusterTypes(LmsApp->getDbSession(), std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
144 }
145
146 private:
createTagsValidator()147 static std::shared_ptr<Wt::WValidator> createTagsValidator()
148 {
149 auto v = std::make_shared<Wt::WValidator>();
150 return v;
151 }
152
initializeModels()153 void initializeModels()
154 {
155 _updatePeriodModel = std::make_shared<ValueStringModel<ScanSettings::UpdatePeriod>>();
156 _updatePeriodModel->add(Wt::WString::tr("Lms.Admin.Database.never"), ScanSettings::UpdatePeriod::Never);
157 _updatePeriodModel->add(Wt::WString::tr("Lms.Admin.Database.hourly"), ScanSettings::UpdatePeriod::Hourly);
158 _updatePeriodModel->add(Wt::WString::tr("Lms.Admin.Database.daily"), ScanSettings::UpdatePeriod::Daily);
159 _updatePeriodModel->add(Wt::WString::tr("Lms.Admin.Database.weekly"), ScanSettings::UpdatePeriod::Weekly);
160 _updatePeriodModel->add(Wt::WString::tr("Lms.Admin.Database.monthly"), ScanSettings::UpdatePeriod::Monthly);
161
162 _updateStartTimeModel = std::make_shared<ValueStringModel<Wt::WTime>>();
163 for (std::size_t i = 0; i < 24; ++i)
164 {
165 Wt::WTime time {static_cast<int>(i), 0};
166 _updateStartTimeModel->add(time.toString(), time);
167 }
168
169 _recommendationEngineTypeModel = std::make_shared<ValueStringModel<ScanSettings::RecommendationEngineType>>();
170 _recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.clusters"), ScanSettings::RecommendationEngineType::Clusters);
171 _recommendationEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.recommendation-engine-type.features"), ScanSettings::RecommendationEngineType::Features);
172 }
173
174 std::shared_ptr<UpdatePeriodModel> _updatePeriodModel;
175 std::shared_ptr<ValueStringModel<Wt::WTime>> _updateStartTimeModel;
176 std::shared_ptr<ValueStringModel<ScanSettings::RecommendationEngineType>> _recommendationEngineTypeModel;
177 };
178
DatabaseSettingsView()179 DatabaseSettingsView::DatabaseSettingsView()
180 {
181 wApp->internalPathChanged().connect(this, [this]
182 {
183 refreshView();
184 });
185
186 refreshView();
187 }
188
189 void
refreshView()190 DatabaseSettingsView::refreshView()
191 {
192 if (!wApp->internalPathMatches("/admin/database"))
193 return;
194
195 clear();
196
197 auto t {addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.Database.template"))};
198 auto model {std::make_shared<DatabaseSettingsModel>()};
199
200 // Media Directory
201 t->setFormWidget(DatabaseSettingsModel::MediaDirectoryField, std::make_unique<Wt::WLineEdit>());
202
203 // Update Period
204 auto updatePeriod {std::make_unique<Wt::WComboBox>()};
205 updatePeriod->setModel(model->updatePeriodModel());
206 updatePeriod->activated().connect([=](int row)
207 {
208 const ScanSettings::UpdatePeriod period {model->updatePeriodModel()->getValue(row)};
209 model->setReadOnly(DatabaseSettingsModel::UpdateStartTimeField, period == ScanSettings::UpdatePeriod::Hourly || period == ScanSettings::UpdatePeriod::Never);
210 t->updateModel(model.get());
211 t->updateView(model.get());
212 });
213 t->setFormWidget(DatabaseSettingsModel::UpdatePeriodField, std::move(updatePeriod));
214
215 // Update Start Time
216 auto updateStartTime {std::make_unique<Wt::WComboBox>()};
217 updateStartTime->setModel(model->updateStartTimeModel());
218 t->setFormWidget(DatabaseSettingsModel::UpdateStartTimeField, std::move(updateStartTime));
219
220 // recommendation engine type
221 auto recommendationEngineType {std::make_unique<Wt::WComboBox>()};
222 recommendationEngineType->setModel(model->recommendationEngineTypeModel());
223 t->setFormWidget(DatabaseSettingsModel::RecommendationEngineTypeField, std::move(recommendationEngineType));
224
225 // Tags
226 t->setFormWidget(DatabaseSettingsModel::TagsField, std::make_unique<Wt::WLineEdit>());
227
228 // Buttons
229 Wt::WPushButton *saveBtn = t->bindWidget("apply-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.apply")));
230 Wt::WPushButton *discardBtn = t->bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard")));
231 Wt::WPushButton *immScanBtn = t->bindWidget("immediate-scan-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.Admin.Database.immediate-scan")));
232
233 t->bindNew<ScannerController>("scanner-controller");
234
235 saveBtn->clicked().connect([=]
236 {
237 t->updateModel(model.get());
238
239 if (model->validate())
240 {
241 model->saveData();
242
243 Service<Scanner::IScanner>::get()->requestImmediateScan(false);
244 LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr("Lms.Admin.Database.settings-saved"));
245 }
246
247 // Udate the view: Delete any validation message in the view, etc.
248 t->updateView(model.get());
249 });
250
251 discardBtn->clicked().connect([=]
252 {
253 model->loadData();
254 model->validate();
255 t->updateView(model.get());
256 });
257
258 immScanBtn->clicked().connect([=]
259 {
260 Service<Scanner::IScanner>::get()->requestImmediateScan(false);
261 });
262
263 t->updateView(model.get());
264 }
265
266 } // namespace UserInterface
267
268
269