1 // Copyright 2010 Jochen Becher
3 // This file is part of MovieSchedule.
5 // MovieSchedule 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 3 of the License, or
8 // (at your option) any later version.
10 // MovieSchedule 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
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with MovieSchedule. If not, see <http://www.gnu.org/licenses/>.
18 #include "movieschedulesearchclient.h"
20 #include "data/cinemaschedule.h"
21 #include "data/cinema.h"
22 #include "data/scheduleentry.h"
23 #include "data/movie.h"
24 #include "utils/timeutils.h"
25 #include "utils/assertedlocker.h"
27 #include <QXmlStreamReader>
32 MovieScheduleSearchClient::MovieScheduleSearchClient(CinemaSchedule *cinema_schedule, QObject *parent)
33 : AbstractSearchClient(parent),
34 _cinema_schedule(cinema_schedule)
38 void MovieScheduleSearchClient::SearchSchedule(const MovieKey &movie_key, const QString &url)
40 setObjectName(QString("MovieScheduleSearchClient:%1").arg(movie_key.GetName()));
41 _semaphore.Activate(GetSearchTaskId());
42 _movie_key = movie_key;
43 _date = QDate::currentDate();
44 _url = QUrl::fromEncoded(QString("http://www.google.com" + url).toAscii(), QUrl::TolerantMode);
46 _dates_seen.insert("0");
51 void MovieScheduleSearchClient::CancelAllRunningSearchs()
53 _semaphore.CancelAll();
56 void MovieScheduleSearchClient::Search(int start)
58 AbstractSearchClient::Search(_url, start);
61 void MovieScheduleSearchClient::SearchNextDate()
63 if (_date_urls.isEmpty()) {
66 QPair<QUrl, QDate> pair = _date_urls.dequeue();
83 void MovieScheduleSearchClient::ReplyFinished(QNetworkReply *reply)
85 //std::cout << "REPLY" << std::endl;
86 //std::cout << reply->readAll().data() << std::endl;
87 QXmlStreamReader xml(reply);
88 State state = PARSE_HTML;
91 QString theater_address;
92 QString theater_phone;
93 QList<QTime> schedule;
94 QRegExp time_pattern("\\d+:\\d+([aApP][mM])*");
95 while (!xml.atEnd()) {
96 QXmlStreamReader::TokenType token = xml.readNext();
97 if (token == QXmlStreamReader::StartElement) {
98 QString attr_href = xml.attributes().value("href").toString();
99 //std::cout << qPrintable(xml.name().toString()) << ", class " << qPrintable(attr_class) << ", href " << qPrintable(attr_href) << std::endl;
100 if (state == PARSE_HTML && xml.name() == "a" && attr_href.startsWith("/m/movies")) {
101 QUrl url = QUrl::fromEncoded(QString("http://www.google.com" + attr_href).toAscii(), QUrl::TolerantMode);
102 //std::cout << "LINK " << qPrintable(attr_href) << std::endl;
103 if (url.hasQueryItem("date")) {
104 QString v = url.queryItemValue("date");
105 std::cout << "FOUND Date Link " << qPrintable(v) << " from " << qPrintable(url.toString()) << std::endl;
106 if (!_dates_seen.contains(v)) {
107 // TODO replace location with user selected location (Google simplifies to much)
108 _dates_seen.insert(v);
109 _date_urls.append(qMakePair(url, QDate::currentDate().addDays(v.toInt())));
111 state = PARSE_DATE_LINK;
112 } else if (url.hasQueryItem("tid")) {
114 theater_address = "";
117 state = PARSE_THEATER_LINK;
121 } else if (state == PARSE_THEATER_DIV && xml.name() == "a") {
122 if (attr_href.startsWith("wtai:")) {
123 state = PARSE_PHONE_LINK;
127 } else if (state == PARSE_THEATER_DIV && xml.name() == "br") {
129 } else if (state == PARSE_THEATER_DIV && xml.name() == "span") {
134 } else if (token == QXmlStreamReader::EndElement) {
135 if (state == PARSE_DATE_LINK) {
137 } else if (state == PARSE_THEATER_LINK) {
138 state = PARSE_THEATER_DIV;
139 } else if (state == PARSE_BR) {
140 state = PARSE_THEATER_DIV;
141 } else if (state == PARSE_SPAN) {
142 state = PARSE_THEATER_DIV;
143 } else if (state == PARSE_LINK) {
144 state = PARSE_THEATER_DIV;
145 } else if (state == PARSE_PHONE_LINK) {
146 state = PARSE_THEATER_DIV;
147 } else if (state == PARSE_THEATER_DIV) {
149 if (!theater_name.isEmpty()) {
151 if (!schedule.isEmpty()) {
152 AssertedWriteLocker locker(_cinema_schedule->GetLock());
153 if (!_semaphore.IsActive(GetSearchTaskId())) {
156 const Movie *movie = _cinema_schedule->FindMovie(_movie_key);
158 //std::cout << "ADD SCHEDULE " << qPrintable(theater_name) << ", " << qPrintable(theater_address) << std::endl;
159 CinemaKey key(theater_name, theater_address);
160 Cinema *cinema = _cinema_schedule->FindCinema(key);
162 cinema = _cinema_schedule->AddCinema(key);
164 if (!theater_phone.isEmpty()) {
165 cinema->SetTelephone(theater_phone);
167 Q_FOREACH(const QTime time, schedule) {
168 _cinema_schedule->AddSchedule(cinema, movie, time, _date);
174 } else if (token == QXmlStreamReader::Characters) {
175 if (state == PARSE_THEATER_LINK) {
176 //std::cout << "name " << qPrintable(xml.text().toString()) << std::endl;
177 theater_name = xml.text().toString();
178 } else if (state == PARSE_PHONE_LINK) {
179 //std::cout << "phone " << qPrintable(xml.text().toString()) << std::endl;
180 theater_phone = xml.text().toString();
181 } else if (state == PARSE_SPAN) {
182 QString t = xml.text().toString();
185 while ((i = time_pattern.indexIn(t, i)) != -1) {
186 int length = time_pattern.matchedLength();
187 //std::cout << "time " << qPrintable(t.mid(i, length)) << std::endl;
188 QTime time = TimeUtils::FromTimeString(t.mid(i, length));
189 if (time.isValid()) {
190 schedule.append(time);
192 std::cout << "ERROR: time " << qPrintable(t.mid(i, length)) << " is invalid." << std::endl;
198 //std::cout << "address " << qPrintable(t) << std::endl;
204 if (xml.hasError()) {
205 emit SearchFinished(GetSearchTaskId(), false);
206 std::cout << "xml error (" << xml.lineNumber() << "/" << xml.columnNumber() << "): " << qPrintable(xml.errorString()) << std::endl;
207 emit Error(GetSearchTaskId());
209 } else if (!_semaphore.IsActive(GetSearchTaskId())) {
210 emit Cancelled(GetSearchTaskId());
211 emit SearchFinished(GetSearchTaskId(), false);
215 emit Reply(GetSearchTaskId(), true);
216 Search(GetStartIndex() + found);
218 if (!_date_urls.isEmpty()) {
221 emit Reply(GetSearchTaskId(), false);
222 emit SearchFinished(GetSearchTaskId(), true);
227 reply->deleteLater();
228 //std::cout << "REPLY FINISHED" << std::endl;
231 SearchClientSemaphore MovieScheduleSearchClient::_semaphore;