Bump version to 0.9.0
[quandoparte] / application / stationschedulemodel.cpp
1 /*
2
3 Copyright (C) 2011 Luciano Montanaro <mikelima@cirulla.net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; see the file COPYING.  If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19
20 */
21
22 #include "stationschedulemodel.h"
23
24 #include "dataprovider.h"
25 #include "settings.h"
26
27 #include <QtGlobal>
28 #include <QDebug>
29 #if (QT_VERSION >= QT_VERSION_CHECK(5, 1, 0))
30 #include <QtWebKitWidgets>
31 #else
32 #include <QWebElement>
33 #include <QWebFrame>
34 #include <QWebPage>
35 #endif
36
37 StationScheduleModel::StationScheduleModel(const QString &name, QObject *parent) :
38     QAbstractListModel(parent),
39     m_name(name),
40     m_error(QString())
41 {
42     DataProvider *provider = DataProvider::instance();
43     connect(provider, SIGNAL(stationScheduleReady(QByteArray,QUrl)),
44             this, SLOT(parse(QByteArray,QUrl)));
45     connect(provider, SIGNAL(error()),
46             this, SLOT(onNetworkError()));
47     Settings *settings = Settings::instance();
48     m_scheduleType = settings->showArrivalsPreferred() ? ArrivalSchedule : DepartureSchedule;
49 #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
50     setRoleNames(roleNames());
51 #endif
52 }
53
54 const QString &StationScheduleModel::name()
55 {
56     return m_name;
57 }
58
59 void StationScheduleModel::setName(const QString &name)
60 {
61     if (name != m_name) {
62         m_name = name;
63         emit nameChanged();
64     }
65 }
66
67 const QString &StationScheduleModel::code()
68 {
69     return m_code;
70 }
71
72 void StationScheduleModel::setCode(const QString &code)
73 {
74     if (code != m_code) {
75         m_code = code;
76         emit codeChanged();
77     }
78 }
79
80 const QString &StationScheduleModel::error()
81 {
82     return m_error;
83 }
84
85 void StationScheduleModel::setError(const QString &error)
86 {
87     if (error != m_error) {
88         m_error = error;
89         emit errorChanged();
90     }
91 }
92
93 QHash<int, QByteArray> StationScheduleModel::roleNames() const
94 {
95     QHash<int, QByteArray> roles;
96     roles[TrainRole] = "train";
97     roles[DepartureStationRole] = "departureStation";
98     roles[DepartureTimeRole] = "departureTime";
99     roles[ArrivalStationRole] = "arrivalStation";
100     roles[ArrivalTimeRole] = "arrivalTime";
101     roles[DetailsUrlRole] = "detailsUrl";
102     roles[DelayRole] = "delay";
103     roles[DelayClassRole] = "delayClass";
104     roles[ExpectedPlatformRole] = "expectedPlatform";
105     roles[ActualPlatformRole] = "actualPlatform";
106
107     return roles;
108 }
109
110 StationScheduleModel::ScheduleType StationScheduleModel::type()
111 {
112     return m_scheduleType;
113 }
114
115 void StationScheduleModel::setType(StationScheduleModel::ScheduleType type)
116 {
117     if (type != m_scheduleType) {
118         emit layoutAboutToBeChanged();
119         beginResetModel();
120         m_scheduleType = type;
121         emit typeChanged();
122         endResetModel();
123         emit layoutChanged();
124         Settings *settings = Settings::instance();
125         settings->setShowArrivalsPreferred(m_scheduleType == ArrivalSchedule ? true : false);
126     }
127 }
128
129 static void parseDelayClass(const QWebElement &element, StationScheduleItem &item)
130 {
131     if (!element.isNull()) {
132         QWebElement image = element.findFirst("img");
133         if (!image.isNull()) {
134             int delayClass = 42;
135             QString imageName = image.attribute("src");
136             if (!imageName.isEmpty()) {
137                 QRegExp delayClassRegexp("pallinoRit([0-9])\\.png");
138                 int pos = delayClassRegexp.indexIn(imageName);
139                 qDebug() << "regexp matched at pos:" << pos << "match:" << delayClassRegexp.cap(0);
140                 delayClass =  (pos >= 0) ? delayClassRegexp.cap(1).toInt() : 0;
141             }
142             item.setDelayClass(delayClass);
143         } else {
144             qDebug() << "img not found";
145         }
146     } else {
147         qDebug() << "div.bloccotreno not found";
148     }
149 }
150
151 static void parseDetailsUrl(const QWebElement &element, StationScheduleItem &item)
152 {
153     if (!element.isNull()) {
154         QWebElement link = element.findFirst("a");
155         QString url = link.attribute("href");
156         item.setDetailsUrl(url);
157     } else {
158         qDebug() << "link not found";
159     }
160 }
161
162 static void parseTrain(const QString &text, StationScheduleItem &item)
163 {
164     QRegExp filter("^(Per|Da) (.*)\\n"
165                    "Delle ore (.*)\n"
166                    "Binario Previsto: (.*)\n"
167                    "Binario Reale: (.*)\n"
168                    "(.*)$");
169     int pos = filter.indexIn(text);
170     if (pos >= 0) {
171         if (filter.cap(1) == "Per") {
172             item.setDepartureStation(filter.cap(2));
173             item.setDepartureTime(filter.cap(3));
174         } else {
175             item.setArrivalStation(filter.cap(2));
176             item.setArrivalTime(filter.cap(3));
177         }
178         QString delayDescription = filter.cap(6);
179         if (delayDescription == "in orario") {
180             item.setDelay(QObject::tr("On time"));
181         } else {
182             QRegExp delayRegExp("ritardo ([0-9]+)(| minuti)");
183             int pos = delayRegExp.indexIn(delayDescription);
184             if (pos >= 0) {
185                 item.setDelay(QString(QObject::tr("Delay: %1 minutes")).arg(delayRegExp.cap(1)));
186             } else {
187                 // Does not match, let the user parse it
188                 item.setDelay(delayDescription);
189             }
190         }
191         item.setExpectedPlatform(filter.cap(4));
192         item.setActualPlatform(filter.cap(5));
193     } else {
194         qDebug() << "could not parse" << text;
195     }
196 }
197
198 StationScheduleItem parseResult(const QWebElement &result)
199 {
200     StationScheduleItem item;
201
202     QWebElement current = result.findFirst("h2");
203     if (!current.isNull()) {
204         item.setTrain(current.toPlainText());
205     }
206     parseDetailsUrl(result, item);
207     current = result.findFirst("div.bloccotreno");
208     parseDelayClass(current, item);
209     QString rawText = current.toPlainText();
210     parseTrain(rawText, item);
211
212     qDebug() << "train:" << item.train();
213     qDebug() << "delayClass:" << item.delayClass();
214     qDebug() << "detailsUrl:" << item.detailsUrl();
215     qDebug() << "departureStation:" << item.departureStation();
216     qDebug() << "departureTime:" << item.departureTime();
217     qDebug() << "arrivalStation:" << item.arrivalStation();
218     qDebug() << "arrivalTime:" << item.arrivalTime();
219     qDebug() << "expectedPlatform:" << item.expectedPlatform();
220     qDebug() << "actualPlatform:" << item.actualPlatform();
221     qDebug() << "delay:" << item.delay();
222     return item;
223 }
224
225 void StationScheduleModel::parse(const QByteArray &htmlReply, const QUrl &baseUrl)
226 {
227     Q_UNUSED(baseUrl);
228     qDebug() << "--- start of query result --- cut here ------";
229     qDebug() << QString::fromUtf8(htmlReply.constData());
230     qDebug() << "--- end of query result ----- cut here ------";
231
232     emit layoutAboutToBeChanged();
233     beginResetModel();
234     QWebPage page;
235     page.mainFrame()->setContent(htmlReply, "text/html", baseUrl);
236     QWebElement doc = page.mainFrame()->documentElement();
237
238     // Check if the page is reporting an error before parsing it
239     QWebElement errorElement = doc.findFirst("span.errore");
240     if (!errorElement.isNull()) {
241         m_departureSchedules.clear();
242         m_arrivalSchedules.clear();
243         QString errorText = errorElement.toPlainText().trimmed();
244         if (errorText == "localita' non trovata") {
245             setError(tr("Unknown station"));
246         } else {
247             setError(tr("Unknown error"));
248         }
249         qDebug() << "error:" << error();
250         return;
251     }
252     // Find the first div
253     QWebElement current = doc.findFirst("div");
254
255     qDebug() << "skipping to the departures";
256     // Skip to the first div of class corpocentrale, which contains the first
257     // departure-related contents
258     while (!current.classes().contains("corpocentrale")) {
259         current = current.nextSibling();
260         qDebug() << "skipping to the next element";
261         if (current.isNull())
262             break;
263     }
264     // Mark every div as a departure class element; the next corpocentrale
265     // marks the start of the arrivals section
266     qDebug() << "marking departures";
267     do {
268         if (current.classes().contains("bloccorisultato")) {
269             StationScheduleItem schedule = parseResult(current);
270             if (schedule.isValid()) {
271                 m_departureSchedules << schedule;
272             }
273         }
274         current = current.nextSibling();
275         qDebug() << "marking as departures";
276         if (current.isNull())
277             break;
278     } while (!current.classes().contains("corpocentrale"));
279
280     // Mark everything as an arrival, until reaching the footer
281     while (!current.classes().contains("footer")) {
282         if (current.classes().contains("bloccorisultato")) {
283             StationScheduleItem schedule = parseResult(current);
284             if (schedule.isValid()) {
285                 m_arrivalSchedules << schedule;
286             }
287         }
288         current = current.nextSibling();
289         qDebug() << "marking as arrival";
290         if (current.isNull())
291             break;
292     }
293     endResetModel();
294     emit layoutChanged();
295 }
296
297 void StationScheduleModel::onNetworkError()
298 {
299     qDebug()<< "Station Schedule Model got a Network Error";
300     m_error = tr("Network error");
301     emit errorChanged();
302 }
303
304 void StationScheduleModel::fetch(const QString &name, const QString &code)
305 {
306     DataProvider *provider = DataProvider::instance();
307
308     if (!error().isEmpty())
309         setError(QString());
310     m_departureSchedules.clear();
311     m_arrivalSchedules.clear();
312     provider->fetchStationSchedule(name, code);
313     setName(name);
314     setCode(code);
315 }
316
317 int StationScheduleModel::rowCount(const QModelIndex &parent) const
318 {
319     Q_UNUSED(parent);
320     if (m_scheduleType == DepartureSchedule) {
321         return m_departureSchedules.count();
322     } else {
323         return m_arrivalSchedules.count();
324     }
325 }
326
327 QVariant StationScheduleModel::data(const QModelIndex &index, int role) const
328 {
329     if (!index.isValid()) {
330         return QVariant();
331     }
332     const QList<StationScheduleItem> &schedules =
333             (m_scheduleType == DepartureSchedule) ? m_departureSchedules : m_arrivalSchedules;
334     if (index.row() < 0 || index.row() >= schedules.count()) {
335         return QVariant();
336     }
337     StationScheduleItem item = schedules[index.row()];
338     switch (role) {
339     case Qt::DisplayRole:
340     case TrainRole:
341         return QVariant::fromValue(item.train());
342     case DepartureStationRole:
343         return QVariant::fromValue(item.departureStation());
344     case DepartureTimeRole:
345         return QVariant::fromValue(item.departureTime());
346     case ArrivalStationRole:
347         return QVariant::fromValue(item.arrivalStation());
348     case ArrivalTimeRole:
349         return QVariant::fromValue(item.arrivalTime());
350     case DetailsUrlRole:
351         return QVariant::fromValue(item.detailsUrl());
352     case DelayRole:
353         return QVariant::fromValue(item.delay());
354     case DelayClassRole:
355         return QVariant::fromValue(item.delayClass());
356     case ExpectedPlatformRole:
357         return QVariant::fromValue(item.expectedPlatform());
358     case ActualPlatformRole:
359         return QVariant::fromValue(item.actualPlatform());
360     default:
361         return QVariant::fromValue(QString("Unknown role requested"));
362     }
363 }