// Copyright 2010 Jochen Becher
//
// This file is part of MovieSchedule.
//
// MovieSchedule is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MovieSchedule is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with MovieSchedule. If not, see .
#include "moviesearchclient.h"
#include "data/cinemaschedule.h"
#include "data/movie.h"
#include "data/moviekey.h"
#include "utils/assertedlocker.h"
#include
#include
#include
MovieSearchClient::MovieSearchClient(CinemaSchedule *cinema_schedule, QObject *parent)
: AbstractSearchClient(parent),
_cinema_schedule(cinema_schedule)
{
}
void MovieSearchClient::SearchMovie(const QString &town)
{
_semaphore.Activate(GetSearchTaskId());
setObjectName(QString("MovieSearchClient:%1").arg(town));
_town = town;
Search(0);
}
void MovieSearchClient::CancelAllRunningSearchs()
{
_semaphore.CancelAll();
}
void MovieSearchClient::Search(int start)
{
QUrl url("http://www.google.com/m/movies");
url.addQueryItem("action","chgloc");
url.addQueryItem("loc", _town);
url.addQueryItem("sort", QString::number(1));
AbstractSearchClient::Search(url, start);
}
enum State {
PARSE_HTML,
PARSE_MOVIE_LINK,
PARSE_EXPECT_DIV1,
PARSE_EXPECT_DIV2, PARSE_DIV2,
PARSE_SPAN, PARSE_RATING, PARSE_TRAILER,
PARSE_NEXT_PAGE_LINK
};
void MovieSearchClient::ReplyFinished(QNetworkReply *reply)
{
//std::cout << qPrintable(QString(reply->readAll())) << std::endl;
QXmlStreamReader xml(reply);
State state = PARSE_HTML;
int found = 0;
QString movie_name;
QVector movie_spans;
double movie_rating = -1.0;
QString movie_theaters_url;
QRegExp duration_pattern("((\\d+)hr )?(\\d+)min");
QRegExp reviews_pattern("\\d+ review(s)?");
QString next_page_url;
int next_page_start;
while (!xml.atEnd()) {
QXmlStreamReader::TokenType token = xml.readNext();
if (token == QXmlStreamReader::StartElement) {
QString attr_class = xml.attributes().value("class").toString();
QString attr_href = xml.attributes().value("href").toString();
//std::cout << "tag = " << qPrintable(xml.name().toString()) << ", class = " << qPrintable(attr_class) << ", href = " << qPrintable(attr_href) << std::endl;
if (state == PARSE_HTML && xml.name() == "a" && attr_href.startsWith("/m/movies")) {
QUrl url = QUrl::fromEncoded(QString("http://www.google.com" + attr_href).toAscii(), QUrl::TolerantMode);
//std::cout << "LINK " << qPrintable(attr_href) << std::endl;
if (url.hasQueryItem("mid")) {
movie_name = "";
movie_spans.clear();
movie_rating = -1.0;
movie_theaters_url = attr_href;
state = PARSE_MOVIE_LINK;
} else if (url.hasQueryItem("start")) {
QString sort = url.queryItemValue("sort");
QString start = url.queryItemValue("start");
int istart = start.toInt();
if (sort == "1" && istart > GetStartIndex()) {
//std::cout << "next page LINK " << qPrintable(attr_href) << std::endl;
next_page_url = attr_href;
next_page_start = istart;
}
state = PARSE_NEXT_PAGE_LINK;
} else {
state = PARSE_HTML;
}
} else if (state == PARSE_EXPECT_DIV2 && xml.name() == "div") {
state = PARSE_DIV2;
} else if (state == PARSE_DIV2 && xml.name() == "span") {
movie_spans.append("");
state = PARSE_SPAN;
} else if (state == PARSE_DIV2 && xml.name() == "img") {
movie_rating = (double) xml.attributes().value("src").at(41).digitValue() / 10.0;
//std::cout << "rating: " << movie_rating << std::endl;
state = PARSE_RATING;
} else if (state == PARSE_DIV2 && xml.name() == "a") {
state = PARSE_TRAILER;
} else if (state != PARSE_HTML) {
state = PARSE_HTML;
}
} else if (token == QXmlStreamReader::EndElement) {
if (state == PARSE_MOVIE_LINK) {
state = PARSE_EXPECT_DIV1;
} else if (state == PARSE_EXPECT_DIV1) {
state = PARSE_EXPECT_DIV2;
} else if (state == PARSE_EXPECT_DIV2) {
state = PARSE_HTML;
} else if (state == PARSE_SPAN) {
state = PARSE_DIV2;
} else if (state == PARSE_RATING) {
state = PARSE_DIV2;
} else if (state == PARSE_TRAILER) {
state = PARSE_DIV2;
} else if (state == PARSE_DIV2) {
if (!movie_name.isEmpty()) {
AssertedWriteLocker locker(_cinema_schedule->GetLock());
if (!_semaphore.IsActive(GetSearchTaskId())) {
break;
}
//std::cout << "ADD MOVIE " << qPrintable(movie_name) << std::endl;
++found;
MovieKey key(movie_name);
Movie *movie = _cinema_schedule->FindMovie(key);
if (movie == 0) {
movie = _cinema_schedule->AddMovie(key);
}
if (!movie_theaters_url.isEmpty()) {
movie->SetTheatersUrl(movie_theaters_url);
}
if (movie_rating >= 0.0) {
movie->SetRate(movie_rating);
}
Q_FOREACH (QString s, movie_spans) {
if (duration_pattern.exactMatch(s)) {
QString hours = duration_pattern.cap(2);
QString minutes = duration_pattern.cap(3);
//std::cout << "hours = " << qPrintable(hours) << ", minutes = " << qPrintable(minutes) << ",0: " << qPrintable(duration_pattern.cap(0)) << ", 1: " << qPrintable(duration_pattern.cap(1)) << std::endl;
movie->SetDuration(QTime(hours.toInt(), minutes.toInt()));
} else if (reviews_pattern.exactMatch(s)) {
// Ignore number of reviews
} else {
movie->SetComment(s);
}
}
}
state = PARSE_HTML;
} else if (state == PARSE_NEXT_PAGE_LINK) {
state = PARSE_HTML;
}
} else if (token == QXmlStreamReader::Characters) {
if (state == PARSE_MOVIE_LINK) {
//std::cout << "movie: " << qPrintable(xml.text().toString()) << std::endl;
movie_name = xml.text().toString();
} else if (state == PARSE_SPAN) {
int i = movie_spans.size()-1;
if (movie_spans[i].isEmpty()) {
movie_spans[i] = xml.text().toString();
} else if (!xml.text().isEmpty()) {
movie_spans[i] += " ";
movie_spans[i] += xml.text().toString();
}
//std::cout << " span: " << qPrintable(movie_spans[i]) << std::endl;
}
}
}
if (xml.hasError()) {
std::cout << "xml error (" << xml.lineNumber() << "/" << xml.columnNumber() << "): " << qPrintable(xml.errorString()) << std::endl;
emit Error(GetSearchTaskId());
emit SearchFinished(GetSearchTaskId(), false);
deleteLater();
} else if (!_semaphore.IsActive(GetSearchTaskId())) {
emit Cancelled(GetSearchTaskId());
emit SearchFinished(GetSearchTaskId(), false);
deleteLater();
} else {
if (!next_page_url.isEmpty()) {
emit Reply(GetSearchTaskId(), true);
SearchEncodedUrl(next_page_url, next_page_start);
} else {
emit Reply(GetSearchTaskId(), false);
emit SearchFinished(GetSearchTaskId(), true);
deleteLater();
}
}
reply->deleteLater();
}
SearchClientSemaphore MovieSearchClient::_semaphore;