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