File indexing completed on 2024-05-12 05:09:33

0001 /***************************************************************************
0002     Copyright (C) 2011 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "filmasterfetcher.h"
0026 #include "../collections/videocollection.h"
0027 #include "../images/imagefactory.h"
0028 #include "../utils/guiproxy.h"
0029 #include "../utils/mapvalue.h"
0030 #include "../entry.h"
0031 #include "../core/filehandler.h"
0032 #include "../tellico_debug.h"
0033 
0034 #include <KLocalizedString>
0035 #include <KIO/Job>
0036 #include <KIO/JobUiDelegate>
0037 #include <KJobWidgets/KJobWidgets>
0038 
0039 #include <QLabel>
0040 #include <QFile>
0041 #include <QTextStream>
0042 #include <QGridLayout>
0043 #include <QTextCodec>
0044 #include <QJsonDocument>
0045 #include <QJsonObject>
0046 #include <QUrlQuery>
0047 
0048 namespace {
0049   static const char* FILMASTER_API_URL = "http://api.filmaster.com";
0050   static const char* FILMASTER_QUERY_URL = "http://api.filmaster.com/1.1/search/";
0051 }
0052 
0053 using namespace Tellico;
0054 using Tellico::Fetch::FilmasterFetcher;
0055 
0056 FilmasterFetcher::FilmasterFetcher(QObject* parent_)
0057     : Fetcher(parent_), m_started(false) {
0058 }
0059 
0060 FilmasterFetcher::~FilmasterFetcher() {
0061 }
0062 
0063 QString FilmasterFetcher::source() const {
0064   return m_name.isEmpty() ? defaultName() : m_name;
0065 }
0066 
0067 QString FilmasterFetcher::attribution() const {
0068   return i18n("This data is licensed under <a href=""%1"">specific terms</a>.",
0069               QLatin1String("http://filmaster.com/license/"));
0070 }
0071 
0072 bool FilmasterFetcher::canSearch(Fetch::FetchKey k) const {
0073   return k == Title || k == Person || k == Keyword;
0074 }
0075 
0076 bool FilmasterFetcher::canFetch(int type) const {
0077   return type == Data::Collection::Video;
0078 }
0079 
0080 void FilmasterFetcher::readConfigHook(const KConfigGroup&) {
0081 }
0082 
0083 void FilmasterFetcher::search() {
0084   m_started = true;
0085 
0086   QUrl u(QString::fromLatin1(FILMASTER_QUERY_URL));
0087 
0088   switch(request().key()) {
0089     case Title:
0090       u.setPath(u.path() + QLatin1String("film/"));
0091       break;
0092 
0093     case Person:
0094       u.setPath(u.path() + QLatin1String("person/"));
0095       break;
0096 
0097     case Keyword:
0098       break;
0099 
0100     default:
0101       stop();
0102       return;
0103   }
0104   QUrlQuery q;
0105   q.addQueryItem(QStringLiteral("phrase"), request().value());
0106   u.setQuery(q);
0107 
0108 //  myDebug() << "url:" << u;
0109 
0110   QPointer<KIO::StoredTransferJob> job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
0111   KJobWidgets::setWindow(job, GUI::Proxy::widget());
0112   connect(job.data(), &KJob::result, this, &FilmasterFetcher::slotComplete);
0113 }
0114 
0115 void FilmasterFetcher::stop() {
0116   if(!m_started) {
0117     return;
0118   }
0119   if(m_job) {
0120     m_job->kill();
0121     m_job = nullptr;
0122   }
0123   m_started = false;
0124   emit signalDone(this);
0125 }
0126 
0127 Tellico::Data::EntryPtr FilmasterFetcher::fetchEntryHook(uint uid_) {
0128   Data::EntryPtr entry = m_entries.value(uid_);
0129   if(!entry) {
0130     myWarning() << "no entry in dict";
0131     return Data::EntryPtr();
0132   }
0133 
0134   const QString image = entry->field(QStringLiteral("cover"));
0135   if(image.contains(QLatin1Char('/'))) {
0136     QUrl imageUrl;
0137     if(image.startsWith(QLatin1String("http"))) {
0138       imageUrl = QUrl(image);
0139     } else if(image.startsWith(QLatin1String("//"))) {
0140       imageUrl = QUrl(QLatin1String("http:") + image);
0141     } else {
0142       imageUrl = QUrl(QString::fromLatin1(FILMASTER_API_URL));
0143       imageUrl.setPath(imageUrl.path() + image);
0144     }
0145     const QString id = ImageFactory::addImage(imageUrl, true);
0146     if(id.isEmpty()) {
0147       myDebug() << "Failed to load" << imageUrl;
0148       message(i18n("The cover image could not be loaded."), MessageHandler::Warning);
0149     }
0150     // empty image ID is ok
0151     entry->setField(QStringLiteral("cover"), id);
0152   }
0153   return entry;
0154 }
0155 
0156 Tellico::Fetch::FetchRequest FilmasterFetcher::updateRequest(Data::EntryPtr entry_) {
0157   const QString title = entry_->field(QStringLiteral("title"));
0158   if(!title.isEmpty()) {
0159     return FetchRequest(Title, title);
0160   }
0161   return FetchRequest();
0162 }
0163 
0164 void FilmasterFetcher::slotComplete(KJob* job_) {
0165   KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_);
0166 //  myDebug();
0167 
0168   if(job->error()) {
0169     job->uiDelegate()->showErrorMessage();
0170     stop();
0171     return;
0172   }
0173 
0174   QByteArray data = job->data();
0175   if(data.isEmpty()) {
0176     myDebug() << "no data";
0177     stop();
0178     return;
0179   }
0180   // see bug 319662. If fetcher is cancelled, job is killed
0181   // if the pointer is retained, it gets double-deleted
0182   m_job = nullptr;
0183 
0184 #if 0
0185   myWarning() << "Remove debug from filmasterfetcher.cpp";
0186   QFile f(QString::fromLatin1("/tmp/test.json"));
0187   if(f.open(QIODevice::WriteOnly)) {
0188     QTextStream t(&f);
0189     t.setCodec("UTF-8");
0190     t << data;
0191   }
0192   f.close();
0193 #endif
0194 
0195   QJsonDocument doc = QJsonDocument::fromJson(data);
0196   QVariantMap resultsMap = doc.object().toVariantMap();
0197   QVariantList resultList;
0198   switch(request().key()) {
0199     case Title:
0200       resultList = resultsMap.value(QStringLiteral("best_results")).toList()
0201                  + resultsMap.value(QStringLiteral("results")).toList();
0202       break;
0203 
0204     case Person:
0205       {
0206         const QVariantList personList = resultsMap.value(QStringLiteral("best_results")).toList();
0207         QStringList uris;
0208         foreach(const QVariant& person, personList) {
0209           const QVariantMap personMap = person.toMap();
0210           uris << mapValue(personMap, "films_played_uri");
0211           uris << mapValue(personMap, "films_directed_uri");
0212         }
0213         foreach(const QString& uri, uris) {
0214           QUrl u(QString::fromLatin1(FILMASTER_API_URL));
0215           u.setPath(uri);
0216           QString output = FileHandler::readTextFile(u, false /*quiet*/, true /*utf8*/);
0217           QJsonDocument doc2 = QJsonDocument::fromJson(output.toUtf8());
0218           resultList += doc2.object().toVariantMap().value(QStringLiteral("objects")).toList();
0219         }
0220       }
0221       break;
0222 
0223     case Keyword:
0224       resultList = resultsMap.value(QStringLiteral("films")).toMap().value(QStringLiteral("best_results")).toList();
0225       break;
0226 
0227     default:
0228       break;
0229   }
0230 
0231   if(resultList.isEmpty()) {
0232     myDebug() << "no results";
0233     stop();
0234     return;
0235   }
0236 
0237   Data::CollPtr coll(new Data::VideoCollection(true));
0238   if(!coll->hasField(QStringLiteral("filmaster")) && optionalFields().contains(QStringLiteral("filmaster"))) {
0239     Data::FieldPtr field(new Data::Field(QStringLiteral("filmaster"), i18n("Filmaster Link"), Data::Field::URL));
0240     field->setCategory(i18n("General"));
0241     coll->addField(field);
0242   }
0243 
0244   foreach(const QVariant& result, resultList) {
0245 //    myDebug() << "found result:" << result;
0246     Data::EntryPtr entry(new Data::Entry(coll));
0247     populateEntry(entry, result.toMap());
0248 
0249     FetchResult* r = new FetchResult(this, entry);
0250     m_entries.insert(r->uid, entry);
0251     emit signalResultFound(r);
0252   }
0253 
0254 //  m_start = m_entries.count();
0255 //  m_hasMoreResults = m_start <= m_total;
0256   m_hasMoreResults = false; // for now, no continued searches
0257   stop();
0258 }
0259 
0260 void FilmasterFetcher::populateEntry(Data::EntryPtr entry_, const QVariantMap& result_) {
0261   entry_->setField(QStringLiteral("title"), mapValue(result_, "title"));
0262   entry_->setField(QStringLiteral("year"), mapValue(result_, "release_year"));
0263   entry_->setField(QStringLiteral("genre"), mapValue(result_, "tags"));
0264   entry_->setField(QStringLiteral("nationality"), mapValue(result_, "production_country_list"));
0265   entry_->setField(QStringLiteral("cover"), mapValue(result_, "image"));
0266   entry_->setField(QStringLiteral("plot"), mapValue(result_, "description"));
0267 
0268   QStringList directors;
0269   foreach(const QVariant& director, result_.value(QLatin1String("directors")).toList()) {
0270     const QVariantMap directorMap = director.toMap();
0271     directors << mapValue(directorMap, "name") + QLatin1Char(' ') + mapValue(directorMap, "surname");
0272   }
0273   if(!directors.isEmpty()) {
0274     entry_->setField(QStringLiteral("director"), directors.join(FieldFormat::delimiterString()));
0275   }
0276 
0277   const QString castUri = mapValue(result_, "characters_uri");
0278   if(!castUri.isEmpty()) {
0279     QUrl u(QString::fromLatin1(FILMASTER_API_URL));
0280     u.setPath(castUri);
0281     QString output = FileHandler::readTextFile(u, false /*quiet*/, true /*utf8*/);
0282     QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8());
0283     QVariantList castList = doc.object().toVariantMap().value(QStringLiteral("objects")).toList();
0284     QStringList castLines;
0285     foreach(const QVariant& castResult, castList) {
0286       const QVariantMap castMap = castResult.toMap();
0287       const QVariantMap nameMap = castMap.value(QStringLiteral("person")).toMap();
0288       castLines << mapValue(nameMap, "name") + QLatin1Char(' ') + mapValue(nameMap, "surname")
0289                  + FieldFormat::columnDelimiterString()
0290                  + mapValue(castMap, "character");
0291     }
0292     if(!castLines.isEmpty()) {
0293       entry_->setField(QStringLiteral("cast"), castLines.join(FieldFormat::rowDelimiterString()));
0294     }
0295   }
0296 
0297   if(optionalFields().contains(QStringLiteral("filmaster"))) {
0298     entry_->setField(QStringLiteral("filmaster"), QLatin1String("http://filmaster.com/film/") + mapValue(result_, "permalink"));
0299   }
0300 }
0301 
0302 Tellico::Fetch::ConfigWidget* FilmasterFetcher::configWidget(QWidget* parent_) const {
0303   return new FilmasterFetcher::ConfigWidget(parent_, this);
0304 }
0305 
0306 QString FilmasterFetcher::defaultName() {
0307   return QStringLiteral("Filmaster"); // no translation
0308 }
0309 
0310 QString FilmasterFetcher::defaultIcon() {
0311   return favIcon("https://filmaster.com/static/favicon.ico");
0312 }
0313 
0314 Tellico::StringHash FilmasterFetcher::allOptionalFields() {
0315   StringHash hash;
0316   hash[QStringLiteral("filmaster")] = i18n("Filmaster Link");
0317   return hash;
0318 }
0319 
0320 FilmasterFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const FilmasterFetcher* fetcher_)
0321     : Fetch::ConfigWidget(parent_) {
0322   QVBoxLayout* l = new QVBoxLayout(optionsWidget());
0323   l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
0324   l->addStretch();
0325 
0326   // now add additional fields widget
0327   addFieldsWidget(FilmasterFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList());
0328 }
0329 
0330 void FilmasterFetcher::ConfigWidget::saveConfigHook(KConfigGroup&) {
0331 }
0332 
0333 QString FilmasterFetcher::ConfigWidget::preferredName() const {
0334   return FilmasterFetcher::defaultName();
0335 }