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