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 }