Fixed searchclients to handle new Google URLs correctly; added GUI
[movie-schedule] / src / searchclients / theatersearchclient.cpp
1 // Copyright 2010 Jochen Becher
2 //
3 // This file is part of MovieSchedule.
4 //
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.
9 //
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.
14 //
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/>.
17
18 #include "theatersearchclient.h"
19
20 #include "data/cinemakey.h"
21 #include "data/cinema.h"
22 #include "data/cinemaschedule.h"
23 #include "utils/assertedlocker.h"
24
25 #include <QXmlStreamReader>
26 #include <iostream>
27
28 TheaterSearchClient::TheaterSearchClient(CinemaSchedule *cinema_schedule, QObject *parent)
29     : AbstractSearchClient(parent),
30     _cinema_schedule(cinema_schedule)
31 {
32 }
33
34 void TheaterSearchClient::SearchTheater(const QString &town)
35 {
36     _semaphore.Activate(GetSearchTaskId());
37     setObjectName(QString("TheaterSearchClient:%1").arg(town));
38     _town = town;
39     Search(0);
40 }
41
42 void TheaterSearchClient::CancelAllRunningSearchs()
43 {
44     _semaphore.CancelAll();
45 }
46
47 void TheaterSearchClient::Search(int start)
48 {
49     QUrl url("http://www.google.com/m/movies");
50     url.addQueryItem("action","chgloc");
51     url.addQueryItem("loc", _town);
52     url.addQueryItem("sort", QString::number(0));
53     AbstractSearchClient::Search(url, start);
54 }
55
56 enum State {
57     PARSE_HTML,
58     PARSE_THEATER_LINK,
59     PARSE_THEATER_DIV,
60     PARSE_THEATER_BR,
61     PARSE_THEATER_SPAN,
62     PARSE_THEATER_PHONE,
63     PARSE_NEXT_PAGE_LINK
64 };
65
66 void TheaterSearchClient::ReplyFinished(QNetworkReply *reply)
67 {
68     //std::cout << qPrintable(QString::fromUtf8(reply->readAll().data())) << std::endl;
69     QXmlStreamReader xml(reply);
70     State state = PARSE_HTML;
71     int found = 0;
72     QString theater_name;
73     QString theater_address;
74     QString theater_movies_url;
75     QString theater_phone;
76     QString next_page_url;
77     int next_page_start;
78     while (!xml.atEnd()) {
79         QXmlStreamReader::TokenType token = xml.readNext();
80         if (token == QXmlStreamReader::StartElement) {
81             QString attr_href = xml.attributes().value("href").toString();
82             //std::cout << "name: " << qPrintable(xml.name().toString()) << ", href: " << qPrintable(attr_href) << std::endl;
83             if (state == PARSE_HTML && xml.name() == "a" && attr_href.startsWith("/m/movies")) {
84                 QUrl url = QUrl::fromEncoded(QString("http://www.google.com" + attr_href).toAscii(), QUrl::TolerantMode);
85                 //std::cout << "LINK " << qPrintable(attr_href) << std::endl;
86                 if (url.hasQueryItem("tid")) {
87                     theater_name = "";
88                     theater_address = "";
89                     theater_movies_url = attr_href;
90                     theater_phone = "";
91                     state = PARSE_THEATER_LINK;
92                 } else if (url.hasQueryItem("start")) {
93                     QString sort = url.queryItemValue("sort");
94                     QString start = url.queryItemValue("start");
95                     int istart = start.toInt();
96                     if (sort == "0" && istart > GetStartIndex()) {
97                         //std::cout << "next page LINK " << qPrintable(attr_href) << std::endl;
98                         next_page_url = attr_href;
99                         next_page_start = istart;
100                     }
101                     state = PARSE_NEXT_PAGE_LINK;
102                 } else {
103                     state = PARSE_HTML;
104                 }
105             } else if (state == PARSE_THEATER_DIV && xml.name() == "br") {
106                 state = PARSE_THEATER_BR;
107             } else if (state == PARSE_THEATER_DIV && xml.name() == "span") {
108                 state = PARSE_THEATER_SPAN;
109             } else if (state == PARSE_THEATER_DIV && xml.name() == "a" && attr_href.startsWith("wtai:")) {
110                 state = PARSE_THEATER_PHONE;
111             } else if (state == PARSE_THEATER_DIV && xml.name() == "a") {
112                 state = PARSE_THEATER_BR;
113             } else {
114                 state = PARSE_HTML;
115             }
116         } else if (token == QXmlStreamReader::EndElement) {
117             if (state == PARSE_THEATER_LINK) {
118                 state = PARSE_THEATER_DIV;
119             } else if (state == PARSE_THEATER_BR) {
120                 state = PARSE_THEATER_DIV;
121             } else if (state == PARSE_THEATER_SPAN) {
122                 state = PARSE_THEATER_DIV;
123             } else if (state == PARSE_THEATER_PHONE) {
124                 state = PARSE_THEATER_DIV;
125             } else if (state == PARSE_THEATER_DIV) {
126                 if (!theater_name.isEmpty()) {
127                     AssertedWriteLocker locker(_cinema_schedule->GetLock());
128                     if (!_semaphore.IsActive(GetSearchTaskId())) {
129                         break;
130                     }
131                     ++found;
132                     CinemaKey key(theater_name, theater_address);
133                     Cinema *cinema = _cinema_schedule->FindCinema(key);
134                     if (cinema == 0) {
135                         cinema = _cinema_schedule->AddCinema(key);
136                     }
137                     if (!theater_movies_url.isEmpty()) {
138                         cinema->SetMoviesUrl(theater_movies_url);
139                     }
140                     if (!theater_phone.isEmpty()) {
141                         cinema->SetTelephone(theater_phone);
142                     }
143                 }
144                 state = PARSE_HTML;
145             } else if (state == PARSE_NEXT_PAGE_LINK) {
146                 state = PARSE_HTML;
147             }
148         } else if (token == QXmlStreamReader::Characters) {
149             if (state == PARSE_THEATER_LINK) {
150                 theater_name = xml.text().toString();
151             } else if (state == PARSE_THEATER_PHONE) {
152                 theater_phone = xml.text().toString();
153             } else if (state == PARSE_THEATER_SPAN) {
154                 theater_address = xml.text().toString();
155             }
156         }
157     }
158     if (xml.hasError()) {
159         emit SearchFinished(GetSearchTaskId(), false);
160         std::cout << "xml error (" << xml.lineNumber() << "/" << xml.columnNumber() << "): " << qPrintable(xml.errorString()) << std::endl;
161         emit Error(GetSearchTaskId());
162         deleteLater();
163     } else if (!_semaphore.IsActive(GetSearchTaskId())) {
164         emit Cancelled(GetSearchTaskId());
165         emit SearchFinished(GetSearchTaskId(), false);
166         deleteLater();
167     } else {
168         if (!next_page_url.isEmpty()) {
169             emit Reply(GetSearchTaskId(), true);
170             SearchEncodedUrl(next_page_url, next_page_start);
171         } else {
172             emit Reply(GetSearchTaskId(), false);
173             emit SearchFinished(GetSearchTaskId(), true);
174             deleteLater();
175         }
176     }
177     reply->deleteLater();
178 }
179
180 SearchClientSemaphore TheaterSearchClient::_semaphore;