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 "moviesearchclient.h"
20 #include "data/cinemaschedule.h"
21 #include "data/movie.h"
22 #include "data/moviekey.h"
23 #include "utils/assertedlocker.h"
25 #include <QXmlStreamReader>
29 MovieSearchClient::MovieSearchClient(CinemaSchedule *cinema_schedule, QObject *parent)
30 : AbstractSearchClient(parent),
31 _cinema_schedule(cinema_schedule)
35 void MovieSearchClient::SearchMovie(const QString &town)
37 _semaphore.Activate(GetSearchTaskId());
38 setObjectName(QString("MovieSearchClient:%1").arg(town));
43 void MovieSearchClient::CancelAllRunningSearchs()
45 _semaphore.CancelAll();
48 void MovieSearchClient::Search(int start)
50 QUrl url("http://www.google.com/m/movies");
51 url.addQueryItem("loc", _town);
52 url.addQueryItem("sort", QString::number(1));
53 AbstractSearchClient::Search(url, start);
60 PARSE_EXPECT_DIV2, PARSE_DIV2,
61 PARSE_SPAN, PARSE_RATING, PARSE_TRAILER
64 void MovieSearchClient::ReplyFinished(QNetworkReply *reply)
66 //std::cout << qPrintable(QString(reply->readAll())) << std::endl;
67 QXmlStreamReader xml(reply);
68 State state = PARSE_HTML;
71 QVector<QString> movie_spans;
72 double movie_rating = -1.0;
73 QString movie_theaters_url;
74 QRegExp duration_pattern("((\\d+)hr )?(\\d+)min");
75 QRegExp reviews_pattern("\\d+ review(s)?");
76 while (!xml.atEnd()) {
77 QXmlStreamReader::TokenType token = xml.readNext();
78 if (token == QXmlStreamReader::StartElement) {
79 QString attr_class = xml.attributes().value("class").toString();
80 QString attr_href = xml.attributes().value("href").toString();
81 //std::cout << "tag = " << qPrintable(xml.name().toString()) << ", class = " << qPrintable(attr_class) << ", href = " << qPrintable(attr_href) << std::endl;
82 if (state == PARSE_HTML && xml.name() == "a" && attr_href.startsWith("/m/movies")) {
83 QUrl url = QUrl::fromEncoded(QString("http://www.google.com" + attr_href).toAscii(), QUrl::TolerantMode);
84 //std::cout << "LINK " << qPrintable(attr_href) << std::endl;
85 if (url.hasQueryItem("mid")) {
89 movie_theaters_url = attr_href;
90 state = PARSE_MOVIE_LINK;
94 } else if (state == PARSE_EXPECT_DIV2 && xml.name() == "div") {
96 } else if (state == PARSE_DIV2 && xml.name() == "span" && attr_class.isEmpty()) {
97 movie_spans.append("");
99 } else if (state == PARSE_DIV2 && xml.name() == "img") {
100 movie_rating = (double) xml.attributes().value("src").at(41).digitValue() / 10.0;
101 //std::cout << "rating: " << movie_rating << std::endl;
102 state = PARSE_RATING;
103 } else if (state == PARSE_DIV2 && xml.name() == "a") {
104 state = PARSE_TRAILER;
105 } else if (state != PARSE_HTML) {
108 } else if (token == QXmlStreamReader::EndElement) {
109 if (state == PARSE_MOVIE_LINK) {
110 state = PARSE_EXPECT_DIV1;
111 } else if (state == PARSE_EXPECT_DIV1) {
112 state = PARSE_EXPECT_DIV2;
113 } else if (state == PARSE_EXPECT_DIV2) {
115 } else if (state == PARSE_SPAN) {
117 } else if (state == PARSE_RATING) {
119 } else if (state == PARSE_TRAILER) {
121 } else if (state == PARSE_DIV2) {
122 if (!movie_name.isEmpty()) {
123 AssertedWriteLocker locker(_cinema_schedule->GetLock());
124 if (!_semaphore.IsActive(GetSearchTaskId())) {
127 //std::cout << "ADD MOVIE " << qPrintable(movie_name) << std::endl;
129 MovieKey key(movie_name);
130 Movie *movie = _cinema_schedule->FindMovie(key);
132 movie = _cinema_schedule->AddMovie(key);
134 if (!movie_theaters_url.isEmpty()) {
135 movie->SetTheatersUrl(movie_theaters_url);
137 if (movie_rating >= 0.0) {
138 movie->SetRate(movie_rating);
140 Q_FOREACH (QString s, movie_spans) {
141 if (duration_pattern.exactMatch(s)) {
142 QString hours = duration_pattern.cap(2);
143 QString minutes = duration_pattern.cap(3);
144 //std::cout << "hours = " << qPrintable(hours) << ", minutes = " << qPrintable(minutes) << ",0: " << qPrintable(duration_pattern.cap(0)) << ", 1: " << qPrintable(duration_pattern.cap(1)) << std::endl;
145 movie->SetDuration(QTime(hours.toInt(), minutes.toInt()));
146 } else if (reviews_pattern.exactMatch(s)) {
147 // Ignore number of reviews
149 movie->SetComment(s);
155 } else if (token == QXmlStreamReader::Characters) {
156 if (state == PARSE_MOVIE_LINK) {
157 //std::cout << "movie: " << qPrintable(xml.text().toString()) << std::endl;
158 movie_name = xml.text().toString();
159 } else if (state == PARSE_SPAN) {
160 int i = movie_spans.size()-1;
161 if (movie_spans[i].isEmpty()) {
162 movie_spans[i] = xml.text().toString();
163 } else if (!xml.text().isEmpty()) {
164 movie_spans[i] += " ";
165 movie_spans[i] += xml.text().toString();
167 //std::cout << " span: " << qPrintable(movie_spans[i]) << std::endl;
171 if (xml.hasError()) {
172 std::cout << "xml error (" << xml.lineNumber() << "/" << xml.columnNumber() << "): " << qPrintable(xml.errorString()) << std::endl;
173 emit Error(GetSearchTaskId());
174 emit SearchFinished(GetSearchTaskId(), false);
176 } else if (!_semaphore.IsActive(GetSearchTaskId())) {
177 emit Cancelled(GetSearchTaskId());
178 emit SearchFinished(GetSearchTaskId(), false);
182 emit Reply(GetSearchTaskId(), true);
183 Search(GetStartIndex() + found);
185 emit Reply(GetSearchTaskId(), false);
186 emit SearchFinished(GetSearchTaskId(), true);
190 reply->deleteLater();
193 SearchClientSemaphore MovieSearchClient::_semaphore;