##// END OF EJS Templates
Implement progression for AmdaProvider.
perrinel -
r695:22c6ca1df132
parent child
Show More
@@ -1,42 +1,44
1 #ifndef SCIQLOP_AMDAPROVIDER_H
1 #ifndef SCIQLOP_AMDAPROVIDER_H
2 #define SCIQLOP_AMDAPROVIDER_H
2 #define SCIQLOP_AMDAPROVIDER_H
3
3
4 #include "AmdaGlobal.h"
4 #include "AmdaGlobal.h"
5
5
6 #include <Data/IDataProvider.h>
6 #include <Data/IDataProvider.h>
7
7
8 #include <QLoggingCategory>
8 #include <QLoggingCategory>
9
9
10 #include <map>
10 #include <map>
11
11
12 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaProvider)
12 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaProvider)
13
13
14 class QNetworkReply;
14 class QNetworkReply;
15 class QNetworkRequest;
15 class QNetworkRequest;
16
16
17 /**
17 /**
18 * @brief The AmdaProvider class is an example of how a data provider can generate data
18 * @brief The AmdaProvider class is an example of how a data provider can generate data
19 */
19 */
20 class SCIQLOP_AMDA_EXPORT AmdaProvider : public IDataProvider {
20 class SCIQLOP_AMDA_EXPORT AmdaProvider : public IDataProvider {
21 Q_OBJECT
21 public:
22 public:
22 explicit AmdaProvider();
23 explicit AmdaProvider();
23 std::shared_ptr<IDataProvider> clone() const override;
24 std::shared_ptr<IDataProvider> clone() const override;
24
25
25 void requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters) override;
26 void requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters) override;
26
27
27 void requestDataAborting(QUuid acqIdentifier) override;
28 void requestDataAborting(QUuid acqIdentifier) override;
28
29
29 private slots:
30 private slots:
30 void onReplyDownloadProgress(QUuid, const QNetworkRequest &, double progress);
31 void onReplyDownloadProgress(QUuid acqIdentifier,
32 std::shared_ptr<QNetworkRequest> networkRequest, double progress);
31
33
32 private:
34 private:
33 void retrieveData(QUuid token, const SqpRange &dateTime, const QVariantHash &data);
35 void retrieveData(QUuid token, const SqpRange &dateTime, const QVariantHash &data);
34
36
35 void updateRequestProgress(QUuid acqIdentifier, std::shared_ptr<QNetworkRequest> request,
37 void updateRequestProgress(QUuid acqIdentifier, std::shared_ptr<QNetworkRequest> request,
36 double progress);
38 double progress);
37
39
38 std::map<QUuid, std::map<std::shared_ptr<QNetworkRequest>, double> >
40 std::map<QUuid, std::map<std::shared_ptr<QNetworkRequest>, double> >
39 m_AcqIdToRequestProgressMap;
41 m_AcqIdToRequestProgressMap;
40 };
42 };
41
43
42 #endif // SCIQLOP_AMDAPROVIDER_H
44 #endif // SCIQLOP_AMDAPROVIDER_H
@@ -1,260 +1,267
1 #include "AmdaProvider.h"
1 #include "AmdaProvider.h"
2 #include "AmdaDefs.h"
2 #include "AmdaDefs.h"
3 #include "AmdaResultParser.h"
3 #include "AmdaResultParser.h"
4
4
5 #include <Common/DateUtils.h>
5 #include <Common/DateUtils.h>
6 #include <Data/DataProviderParameters.h>
6 #include <Data/DataProviderParameters.h>
7 #include <Network/NetworkController.h>
7 #include <Network/NetworkController.h>
8 #include <SqpApplication.h>
8 #include <SqpApplication.h>
9 #include <Variable/Variable.h>
9 #include <Variable/Variable.h>
10
10
11 #include <QNetworkAccessManager>
11 #include <QNetworkAccessManager>
12 #include <QNetworkReply>
12 #include <QNetworkReply>
13 #include <QTemporaryFile>
13 #include <QTemporaryFile>
14 #include <QThread>
14 #include <QThread>
15
15
16 Q_LOGGING_CATEGORY(LOG_AmdaProvider, "AmdaProvider")
16 Q_LOGGING_CATEGORY(LOG_AmdaProvider, "AmdaProvider")
17
17
18 namespace {
18 namespace {
19
19
20 /// URL format for a request on AMDA server. The parameters are as follows:
20 /// URL format for a request on AMDA server. The parameters are as follows:
21 /// - %1: start date
21 /// - %1: start date
22 /// - %2: end date
22 /// - %2: end date
23 /// - %3: parameter id
23 /// - %3: parameter id
24 const auto AMDA_URL_FORMAT = QStringLiteral(
24 const auto AMDA_URL_FORMAT = QStringLiteral(
25 "http://amda.irap.omp.eu/php/rest/"
25 "http://amda.irap.omp.eu/php/rest/"
26 "getParameter.php?startTime=%1&stopTime=%2&parameterID=%3&outputFormat=ASCII&"
26 "getParameter.php?startTime=%1&stopTime=%2&parameterID=%3&outputFormat=ASCII&"
27 "timeFormat=ISO8601&gzip=0");
27 "timeFormat=ISO8601&gzip=0");
28
28
29 /// Dates format passed in the URL (e.g 2013-09-23T09:00)
29 /// Dates format passed in the URL (e.g 2013-09-23T09:00)
30 const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss");
30 const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss");
31
31
32 // struct AmdaProgression {
33 // QUuid acqIdentifier;
34 // std::map<QNetworkRequest, double> m_RequestId;
35 //};
36
37 /// Formats a time to a date that can be passed in URL
32 /// Formats a time to a date that can be passed in URL
38 QString dateFormat(double sqpRange) noexcept
33 QString dateFormat(double sqpRange) noexcept
39 {
34 {
40 auto dateTime = DateUtils::dateTime(sqpRange);
35 auto dateTime = DateUtils::dateTime(sqpRange);
41 return dateTime.toString(AMDA_TIME_FORMAT);
36 return dateTime.toString(AMDA_TIME_FORMAT);
42 }
37 }
43
38
44 AmdaResultParser::ValueType valueType(const QString &valueType)
39 AmdaResultParser::ValueType valueType(const QString &valueType)
45 {
40 {
46 if (valueType == QStringLiteral("scalar")) {
41 if (valueType == QStringLiteral("scalar")) {
47 return AmdaResultParser::ValueType::SCALAR;
42 return AmdaResultParser::ValueType::SCALAR;
48 }
43 }
49 else if (valueType == QStringLiteral("vector")) {
44 else if (valueType == QStringLiteral("vector")) {
50 return AmdaResultParser::ValueType::VECTOR;
45 return AmdaResultParser::ValueType::VECTOR;
51 }
46 }
52 else {
47 else {
53 return AmdaResultParser::ValueType::UNKNOWN;
48 return AmdaResultParser::ValueType::UNKNOWN;
54 }
49 }
55 }
50 }
56
51
57 } // namespace
52 } // namespace
58
53
59 AmdaProvider::AmdaProvider()
54 AmdaProvider::AmdaProvider()
60 {
55 {
61 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::AmdaProvider") << QThread::currentThread();
56 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::AmdaProvider") << QThread::currentThread();
62 if (auto app = sqpApp) {
57 if (auto app = sqpApp) {
63 auto &networkController = app->networkController();
58 auto &networkController = app->networkController();
64 connect(this, SIGNAL(requestConstructed(QNetworkRequest, QUuid,
59 connect(this, SIGNAL(requestConstructed(std::shared_ptr<QNetworkRequest>, QUuid,
65 std::function<void(QNetworkReply *, QUuid)>)),
60 std::function<void(QNetworkReply *, QUuid)>)),
66 &networkController,
61 &networkController,
67 SLOT(onProcessRequested(QNetworkRequest, QUuid,
62 SLOT(onProcessRequested(std::shared_ptr<QNetworkRequest>, QUuid,
68 std::function<void(QNetworkReply *, QUuid)>)));
63 std::function<void(QNetworkReply *, QUuid)>)));
69
64
70
65
71 connect(&sqpApp->networkController(),
66 connect(&sqpApp->networkController(),
72 SIGNAL(replyDownloadProgress(QUuid, const QNetworkRequest &, double)), this,
67 SIGNAL(replyDownloadProgress(QUuid, std::shared_ptr<QNetworkRequest>, double)),
73 SLOT(onReplyDownloadProgress(QUuid, const QNetworkRequest &, double)));
68 this,
69 SLOT(onReplyDownloadProgress(QUuid, std::shared_ptr<QNetworkRequest>, double)));
74 }
70 }
75 }
71 }
76
72
77 std::shared_ptr<IDataProvider> AmdaProvider::clone() const
73 std::shared_ptr<IDataProvider> AmdaProvider::clone() const
78 {
74 {
79 // No copy is made in the clone
75 // No copy is made in the clone
80 return std::make_shared<AmdaProvider>();
76 return std::make_shared<AmdaProvider>();
81 }
77 }
82
78
83 void AmdaProvider::requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters)
79 void AmdaProvider::requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters)
84 {
80 {
85 // NOTE: Try to use multithread if possible
81 // NOTE: Try to use multithread if possible
86 const auto times = parameters.m_Times;
82 const auto times = parameters.m_Times;
87 const auto data = parameters.m_Data;
83 const auto data = parameters.m_Data;
88 for (const auto &dateTime : qAsConst(times)) {
84 for (const auto &dateTime : qAsConst(times)) {
89 this->retrieveData(acqIdentifier, dateTime, data);
85 this->retrieveData(acqIdentifier, dateTime, data);
90
86
91
87
92 // TORM when AMDA will support quick asynchrone request
88 // TORM when AMDA will support quick asynchrone request
93 QThread::msleep(1000);
89 QThread::msleep(1000);
94 }
90 }
95 }
91 }
96
92
97 void AmdaProvider::requestDataAborting(QUuid acqIdentifier)
93 void AmdaProvider::requestDataAborting(QUuid acqIdentifier)
98 {
94 {
99 if (auto app = sqpApp) {
95 if (auto app = sqpApp) {
100 auto &networkController = app->networkController();
96 auto &networkController = app->networkController();
101 networkController.onReplyCanceled(acqIdentifier);
97 networkController.onReplyCanceled(acqIdentifier);
102 }
98 }
103 }
99 }
104
100
105 void AmdaProvider::onReplyDownloadProgress(QUuid acqIdentifier,
101 void AmdaProvider::onReplyDownloadProgress(QUuid acqIdentifier,
106 const QNetworkRequest &networkRequest, double progress)
102 std::shared_ptr<QNetworkRequest> networkRequest,
103 double progress)
107 {
104 {
108 qCCritical(LOG_AmdaProvider()) << tr("onReplyDownloadProgress") << progress;
105 qCDebug(LOG_AmdaProvider()) << tr("onReplyDownloadProgress") << acqIdentifier
106 << networkRequest.get() << progress;
109 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
107 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
110 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
108 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
111
109
112 qCCritical(LOG_AmdaProvider()) << tr("1 onReplyDownloadProgress") << progress;
110 qCDebug(LOG_AmdaProvider()) << tr("1 onReplyDownloadProgress found") << progress;
113 auto requestPtr = &networkRequest;
111 auto requestPtr = networkRequest;
114 auto findRequest
112 auto findRequest = [requestPtr](const auto &entry) { return requestPtr == entry.first; };
115 = [requestPtr](const auto &entry) { return requestPtr == entry.first.get(); };
116
113
117 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
114 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
118 auto requestProgressMapEnd = requestProgressMap.end();
115 auto requestProgressMapEnd = requestProgressMap.end();
119 auto requestProgressMapIt
116 auto requestProgressMapIt
120 = std::find_if(requestProgressMap.begin(), requestProgressMapEnd, findRequest);
117 = std::find_if(requestProgressMap.begin(), requestProgressMapEnd, findRequest);
121
118
122 if (requestProgressMapIt != requestProgressMapEnd) {
119 if (requestProgressMapIt != requestProgressMapEnd) {
123 requestProgressMapIt->second = progress;
120 requestProgressMapIt->second = progress;
124 }
121 }
125 else {
122 else {
126 qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve Request in progress");
123 qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve Request in progress")
124 << acqIdentifier << networkRequest.get() << progress;
127 }
125 }
128 }
126 }
129
127
130 acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
128 acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
131 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
129 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
132 qCCritical(LOG_AmdaProvider()) << tr("2 onReplyDownloadProgress") << progress;
133 double finalProgress = 0.0;
130 double finalProgress = 0.0;
134
131
135 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
132 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
136 auto fraq = requestProgressMap.size();
133 auto fraq = requestProgressMap.size();
137
134
138 for (auto requestProgress : requestProgressMap) {
135 for (auto requestProgress : requestProgressMap) {
139 finalProgress += requestProgress.second;
136 finalProgress += requestProgress.second;
137 qCDebug(LOG_AmdaProvider()) << tr("current final progress without freq:")
138 << finalProgress << requestProgress.second;
140 }
139 }
141
140
142 if (fraq > 0) {
141 if (fraq > 0) {
143 finalProgress = finalProgress / fraq;
142 finalProgress = finalProgress / fraq;
144 }
143 }
145
144
146 qCCritical(LOG_AmdaProvider()) << tr("2 onReplyDownloadProgress") << finalProgress;
145 qCDebug(LOG_AmdaProvider()) << tr("2 onReplyDownloadProgress final progress") << fraq
146 << finalProgress;
147 emit dataProvidedProgress(acqIdentifier, finalProgress);
147 emit dataProvidedProgress(acqIdentifier, finalProgress);
148 }
148 }
149 else {
149 else {
150 emit dataProvidedProgress(acqIdentifier, 0.0);
150 emit dataProvidedProgress(acqIdentifier, 0.0);
151 }
151 }
152 }
152 }
153
153
154 void AmdaProvider::retrieveData(QUuid token, const SqpRange &dateTime, const QVariantHash &data)
154 void AmdaProvider::retrieveData(QUuid token, const SqpRange &dateTime, const QVariantHash &data)
155 {
155 {
156 // Retrieves product ID from data: if the value is invalid, no request is made
156 // Retrieves product ID from data: if the value is invalid, no request is made
157 auto productId = data.value(AMDA_XML_ID_KEY).toString();
157 auto productId = data.value(AMDA_XML_ID_KEY).toString();
158 if (productId.isNull()) {
158 if (productId.isNull()) {
159 qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve data: unknown product id");
159 qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve data: unknown product id");
160 return;
160 return;
161 }
161 }
162 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData") << dateTime;
162 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData") << dateTime;
163
163
164 // Retrieves the data type that determines whether the expected format for the result file is
164 // Retrieves the data type that determines whether the expected format for the result file is
165 // scalar, vector...
165 // scalar, vector...
166 auto productValueType = valueType(data.value(AMDA_DATA_TYPE_KEY).toString());
166 auto productValueType = valueType(data.value(AMDA_DATA_TYPE_KEY).toString());
167
167
168 // /////////// //
168 // /////////// //
169 // Creates URL //
169 // Creates URL //
170 // /////////// //
170 // /////////// //
171
171
172 auto startDate = dateFormat(dateTime.m_TStart);
172 auto startDate = dateFormat(dateTime.m_TStart);
173 auto endDate = dateFormat(dateTime.m_TEnd);
173 auto endDate = dateFormat(dateTime.m_TEnd);
174
174
175 auto url = QUrl{QString{AMDA_URL_FORMAT}.arg(startDate, endDate, productId)};
175 auto url = QUrl{QString{AMDA_URL_FORMAT}.arg(startDate, endDate, productId)};
176 qCInfo(LOG_AmdaProvider()) << tr("TORM AmdaProvider::retrieveData url:") << url;
176 qCDebug(LOG_AmdaProvider()) << tr("TORM AmdaProvider::retrieveData url:") << url;
177 auto tempFile = std::make_shared<QTemporaryFile>();
177 auto tempFile = std::make_shared<QTemporaryFile>();
178
178
179 // LAMBDA
179 // LAMBDA
180 auto httpDownloadFinished = [this, dateTime, tempFile,
180 auto httpDownloadFinished = [this, dateTime, tempFile,
181 productValueType](QNetworkReply *reply, QUuid dataId) noexcept {
181 productValueType](QNetworkReply *reply, QUuid dataId) noexcept {
182
182
183 // Don't do anything if the reply was abort
183 // Don't do anything if the reply was abort
184 if (reply->error() != QNetworkReply::OperationCanceledError) {
184 if (reply->error() != QNetworkReply::OperationCanceledError) {
185
185
186 if (tempFile) {
186 if (tempFile) {
187 auto replyReadAll = reply->readAll();
187 auto replyReadAll = reply->readAll();
188 if (!replyReadAll.isEmpty()) {
188 if (!replyReadAll.isEmpty()) {
189 tempFile->write(replyReadAll);
189 tempFile->write(replyReadAll);
190 }
190 }
191 tempFile->close();
191 tempFile->close();
192
192
193 // Parse results file
193 // Parse results file
194 if (auto dataSeries
194 if (auto dataSeries
195 = AmdaResultParser::readTxt(tempFile->fileName(), productValueType)) {
195 = AmdaResultParser::readTxt(tempFile->fileName(), productValueType)) {
196 emit dataProvided(dataId, dataSeries, dateTime);
196 emit dataProvided(dataId, dataSeries, dateTime);
197 }
197 }
198 else {
198 else {
199 /// @todo ALX : debug
199 /// @todo ALX : debug
200 }
200 }
201 }
201 }
202 m_AcqIdToRequestProgressMap.erase(dataId);
202 m_AcqIdToRequestProgressMap.erase(dataId);
203 }
203 }
204
204
205 };
205 };
206 auto httpFinishedLambda
206 auto httpFinishedLambda
207 = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
207 = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
208
208
209 // Don't do anything if the reply was abort
209 // Don't do anything if the reply was abort
210 if (reply->error() != QNetworkReply::OperationCanceledError) {
210 if (reply->error() != QNetworkReply::OperationCanceledError) {
211 auto downloadFileUrl = QUrl{QString{reply->readAll()}};
211 auto downloadFileUrl = QUrl{QString{reply->readAll()}};
212
212
213 qCInfo(LOG_AmdaProvider())
213 qCInfo(LOG_AmdaProvider())
214 << tr("TORM AmdaProvider::retrieveData downloadFileUrl:") << downloadFileUrl;
214 << tr("TORM AmdaProvider::retrieveData downloadFileUrl:") << downloadFileUrl;
215 // Executes request for downloading file //
215 // Executes request for downloading file //
216
216
217 // Creates destination file
217 // Creates destination file
218 if (tempFile->open()) {
218 if (tempFile->open()) {
219 // Executes request
219 // Executes request and store the request for progression
220 auto request = std::make_shared<QNetworkRequest>(downloadFileUrl);
220 auto request = std::make_shared<QNetworkRequest>(downloadFileUrl);
221 updateRequestProgress(dataId, request, 0.0);
221 updateRequestProgress(dataId, request, 0.0);
222 emit requestConstructed(*request.get(), dataId, httpDownloadFinished);
222 emit requestConstructed(request, dataId, httpDownloadFinished);
223 }
223 }
224 }
224 }
225 else {
225 else {
226 m_AcqIdToRequestProgressMap.erase(dataId);
226 m_AcqIdToRequestProgressMap.erase(dataId);
227 }
227 }
228 };
228 };
229
229
230 // //////////////// //
230 // //////////////// //
231 // Executes request //
231 // Executes request //
232 // //////////////// //
232 // //////////////// //
233
233
234 auto request = std::make_shared<QNetworkRequest>(url);
234 auto request = std::make_shared<QNetworkRequest>(url);
235 qCDebug(LOG_AmdaProvider()) << tr("First Request creation") << request.get();
235 updateRequestProgress(token, request, 0.0);
236 updateRequestProgress(token, request, 0.0);
236
237
237 emit requestConstructed(*request.get(), token, httpFinishedLambda);
238 emit requestConstructed(request, token, httpFinishedLambda);
238 }
239 }
239
240
240 void AmdaProvider::updateRequestProgress(QUuid acqIdentifier,
241 void AmdaProvider::updateRequestProgress(QUuid acqIdentifier,
241 std::shared_ptr<QNetworkRequest> request, double progress)
242 std::shared_ptr<QNetworkRequest> request, double progress)
242 {
243 {
243 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
244 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
244 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
245 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
245 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
246 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
246 auto requestProgressMapIt = requestProgressMap.find(request);
247 auto requestProgressMapIt = requestProgressMap.find(request);
247 if (requestProgressMapIt != requestProgressMap.end()) {
248 if (requestProgressMapIt != requestProgressMap.end()) {
248 requestProgressMapIt->second = progress;
249 requestProgressMapIt->second = progress;
250 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new progress for request")
251 << acqIdentifier << request.get() << progress;
249 }
252 }
250 else {
253 else {
254 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new request") << acqIdentifier
255 << request.get() << progress;
251 acqIdToRequestProgressMapIt->second.insert(std::make_pair(request, progress));
256 acqIdToRequestProgressMapIt->second.insert(std::make_pair(request, progress));
252 }
257 }
253 }
258 }
254 else {
259 else {
260 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new acqIdentifier")
261 << acqIdentifier << request.get() << progress;
255 auto requestProgressMap = std::map<std::shared_ptr<QNetworkRequest>, double>{};
262 auto requestProgressMap = std::map<std::shared_ptr<QNetworkRequest>, double>{};
256 requestProgressMap.insert(std::make_pair(request, progress));
263 requestProgressMap.insert(std::make_pair(request, progress));
257 m_AcqIdToRequestProgressMap.insert(
264 m_AcqIdToRequestProgressMap.insert(
258 std::make_pair(acqIdentifier, std::move(requestProgressMap)));
265 std::make_pair(acqIdentifier, std::move(requestProgressMap)));
259 }
266 }
260 }
267 }
General Comments 2
You need to be logged in to leave comments. Login now