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