File indexing completed on 2024-05-12 05:09:30
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 "doubanfetcher.h" 0026 #include "../collections/bookcollection.h" 0027 #include "../collections/videocollection.h" 0028 #include "../collections/musiccollection.h" 0029 #include "../images/imagefactory.h" 0030 #include "../core/filehandler.h" 0031 #include "../utils/guiproxy.h" 0032 #include "../utils/isbnvalidator.h" 0033 #include "../utils/string_utils.h" 0034 #include "../utils/mapvalue.h" 0035 #include "../tellico_debug.h" 0036 0037 #include <KLocalizedString> 0038 #include <KConfigGroup> 0039 #include <KJob> 0040 #include <KJobUiDelegate> 0041 #include <KJobWidgets/KJobWidgets> 0042 #include <KIO/StoredTransferJob> 0043 0044 #include <QLabel> 0045 #include <QFile> 0046 #include <QTextStream> 0047 #include <QVBoxLayout> 0048 #include <QTextCodec> 0049 #include <QUrlQuery> 0050 #include <QJsonDocument> 0051 #include <QJsonObject> 0052 0053 namespace { 0054 static const int DOUBAN_MAX_RETURNS_TOTAL = 20; 0055 static const char* DOUBAN_API_URL = "https://api.douban.com/v2/"; 0056 static const char* DOUBAN_API_KEY = "93a5facb2118d2e0d1b32e4a2c1e497d01310d6c41715e3ba0c36c58f2c27e4c3705cdafe783261621180632ebdd8ebf55659efb97f6e0d42d192241f0914777"; 0057 } 0058 0059 using namespace Tellico; 0060 using Tellico::Fetch::DoubanFetcher; 0061 0062 DoubanFetcher::DoubanFetcher(QObject* parent_) 0063 : Fetcher(parent_) 0064 , m_started(false) { 0065 } 0066 0067 DoubanFetcher::~DoubanFetcher() { 0068 } 0069 0070 QString DoubanFetcher::source() const { 0071 return m_name.isEmpty() ? defaultName() : m_name; 0072 } 0073 0074 bool DoubanFetcher::canSearch(Fetch::FetchKey k) const { 0075 return k == Keyword || k == ISBN; 0076 } 0077 0078 bool DoubanFetcher::canFetch(int type) const { 0079 return type == Data::Collection::Book 0080 || type == Data::Collection::Bibtex 0081 || type == Data::Collection::Video 0082 || type == Data::Collection::Album; 0083 } 0084 0085 void DoubanFetcher::readConfigHook(const KConfigGroup& config_) { 0086 Q_UNUSED(config_); 0087 } 0088 0089 void DoubanFetcher::search() { 0090 m_started = true; 0091 // split ISBN values 0092 QStringList searchTerms; 0093 if(request().key() == ISBN) { 0094 searchTerms = FieldFormat::splitValue(request().value()); 0095 } else { 0096 searchTerms += request().value(); 0097 } 0098 foreach(const QString& searchTerm, searchTerms) { 0099 doSearch(searchTerm); 0100 } 0101 if(m_jobs.isEmpty()) { 0102 stop(); 0103 } 0104 } 0105 0106 void DoubanFetcher::doSearch(const QString& term_) { 0107 QUrl u(QString::fromLatin1(DOUBAN_API_URL)); 0108 0109 switch(request().collectionType()) { 0110 case Data::Collection::Book: 0111 case Data::Collection::Bibtex: 0112 u.setPath(u.path() + QLatin1String("book/")); 0113 break; 0114 0115 case Data::Collection::Video: 0116 u.setPath(u.path() + QLatin1String("movie/")); 0117 break; 0118 0119 case Data::Collection::Album: 0120 u.setPath(u.path() + QLatin1String("music/")); 0121 break; 0122 0123 default: 0124 myWarning() << "bad collection type:" << request().collectionType(); 0125 } 0126 0127 0128 QUrlQuery q; 0129 switch(request().key()) { 0130 case ISBN: 0131 u.setPath(u.path() + QLatin1String("isbn/") + ISBNValidator::cleanValue(term_)); 0132 break; 0133 0134 case Keyword: 0135 u.setPath(u.path() + QLatin1String("search")); 0136 q.addQueryItem(QStringLiteral("q"), term_); 0137 q.addQueryItem(QStringLiteral("count"), QString::number(DOUBAN_MAX_RETURNS_TOTAL)); 0138 break; 0139 0140 default: 0141 myWarning() << source() << "- key not recognized:" << request().key(); 0142 return; 0143 } 0144 0145 q.addQueryItem(QLatin1String("start"), QString::number(0)); 0146 q.addQueryItem(QLatin1String("apiKey"), Tellico::reverseObfuscate(DOUBAN_API_KEY)); 0147 u.setQuery(q); 0148 if(!m_testUrl1.isEmpty()) u = m_testUrl1; 0149 // myDebug() << "url:" << u.url(); 0150 0151 QPointer<KIO::StoredTransferJob> job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0152 KJobWidgets::setWindow(job, GUI::Proxy::widget()); 0153 if(request().key() == ISBN) { 0154 connect(job.data(), &KJob::result, this, &DoubanFetcher::slotCompleteISBN); 0155 } else { 0156 connect(job.data(), &KJob::result, this, &DoubanFetcher::slotComplete); 0157 } 0158 m_jobs << job; 0159 } 0160 0161 void DoubanFetcher::endJob(KIO::StoredTransferJob* job_) { 0162 m_jobs.removeAll(job_); 0163 if(m_jobs.isEmpty()) { 0164 stop(); 0165 } 0166 } 0167 0168 void DoubanFetcher::stop() { 0169 if(!m_started) { 0170 return; 0171 } 0172 foreach(auto job, m_jobs) { 0173 if(job) { 0174 job->kill(); 0175 } 0176 } 0177 m_jobs.clear(); 0178 m_started = false; 0179 emit signalDone(this); 0180 } 0181 0182 void DoubanFetcher::slotCompleteISBN(KJob* job_) { 0183 KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_); 0184 0185 if(job->error()) { 0186 myDebug() << "job error -" << job->errorString(); 0187 if(GUI::Proxy::widget()) job->uiDelegate()->showErrorMessage(); 0188 endJob(job); 0189 return; 0190 } 0191 0192 QByteArray data = job->data(); 0193 if(data.isEmpty()) { 0194 myDebug() << "no data"; 0195 endJob(job); 0196 return; 0197 } 0198 0199 QJsonDocument doc = QJsonDocument::fromJson(data); 0200 const QVariantMap resultMap = doc.object().toVariantMap(); 0201 0202 // code == 6000 for no result, 997 for provisioning error, 109 for invalid credential, 104 for invalid apikey, 105 is blocked apikey 0203 const auto code = mapValue(resultMap, "code"); 0204 if(code == QLatin1String("6000") || 0205 code == QLatin1String("997") || 0206 code == QLatin1String("109") || 0207 code == QLatin1String("104") || 0208 code == QLatin1String("105")) { 0209 message(mapValue(resultMap, "msg"), MessageHandler::Error); 0210 myDebug() << "Douban: no result -" << mapValue(resultMap, "msg"); 0211 } else { 0212 Data::EntryPtr entry = createEntry(resultMap); 0213 if(entry) { 0214 FetchResult* r = new FetchResult(this, entry); 0215 m_entries.insert(r->uid, entry); 0216 m_matches.insert(r->uid, QUrl(mapValue(resultMap, "url"))); 0217 emit signalResultFound(r); 0218 } 0219 } 0220 0221 endJob(job); 0222 } 0223 0224 void DoubanFetcher::slotComplete(KJob* job_) { 0225 KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_); 0226 0227 if(job->error()) { 0228 job->uiDelegate()->showErrorMessage(); 0229 endJob(job); 0230 return; 0231 } 0232 0233 QByteArray data = job->data(); 0234 if(data.isEmpty()) { 0235 myDebug() << "no data"; 0236 endJob(job); 0237 return; 0238 } 0239 0240 #if 0 0241 myWarning() << "Remove debug from doubanfetcher.cpp"; 0242 QFile f(QString::fromLatin1("/tmp/test-douban1.json")); 0243 if(f.open(QIODevice::WriteOnly)) { 0244 QTextStream t(&f); 0245 t.setCodec("UTF-8"); 0246 t << data; 0247 } 0248 f.close(); 0249 #endif 0250 0251 QJsonDocument doc = QJsonDocument::fromJson(data); 0252 const QVariantMap resultsMap = doc.object().toVariantMap(); 0253 switch(request().collectionType()) { 0254 case Data::Collection::Book: 0255 case Data::Collection::Bibtex: 0256 foreach(const QVariant& v, resultsMap.value(QLatin1String("books")).toList()) { 0257 const QVariantMap resultMap = v.toMap(); 0258 FetchResult* r = new FetchResult(this, mapValue(resultMap, "title"), 0259 mapValue(resultMap, "author") + QLatin1Char('/') + 0260 mapValue(resultMap, "publisher") + QLatin1Char('/') + 0261 mapValue(resultMap, "pubdate").left(4)); 0262 m_matches.insert(r->uid, QUrl(mapValue(resultMap, "url"))); 0263 emit signalResultFound(r); 0264 } 0265 break; 0266 0267 case Data::Collection::Video: 0268 foreach(const QVariant& v, resultsMap.value(QLatin1String("subjects")).toList()) { 0269 const QVariantMap resultMap = v.toMap(); 0270 FetchResult* r = new FetchResult(this, mapValue(resultMap, "title"), 0271 mapValue(resultMap, "directors", "name") + QLatin1Char('/') + 0272 mapValue(resultMap, "year")); 0273 // movie results don't appear to have a url field 0274 m_matches.insert(r->uid, QUrl(QLatin1String(DOUBAN_API_URL) + 0275 QLatin1String("movie/subject/") + 0276 mapValue(resultMap, "id"))); 0277 emit signalResultFound(r); 0278 } 0279 break; 0280 0281 case Data::Collection::Album: 0282 foreach(const QVariant& v, resultsMap.value(QLatin1String("musics")).toList()) { 0283 const QVariantMap resultMap = v.toMap(); 0284 FetchResult* r = new FetchResult(this, mapValue(resultMap, "title"), 0285 mapValue(resultMap, "attrs", "singer") + QLatin1Char('/') + 0286 mapValue(resultMap, "attrs", "publisher") + QLatin1Char('/') + 0287 mapValue(resultMap, "attrs", "pubdate").left(4)); 0288 // movie results don't appear to have a url field 0289 m_matches.insert(r->uid, QUrl(QLatin1String(DOUBAN_API_URL) + 0290 QLatin1String("music/") + 0291 mapValue(resultMap, "id"))); 0292 emit signalResultFound(r); 0293 } 0294 break; 0295 0296 default: 0297 break; 0298 } 0299 0300 endJob(job); 0301 } 0302 0303 Tellico::Data::EntryPtr DoubanFetcher::fetchEntryHook(uint uid_) { 0304 Data::EntryPtr entry = m_entries.value(uid_); 0305 if(entry) { 0306 return entry; 0307 } 0308 0309 QUrl url = m_matches.value(uid_); 0310 QUrlQuery q; 0311 q.addQueryItem(QLatin1String("apiKey"), Tellico::reverseObfuscate(DOUBAN_API_KEY)); 0312 url.setQuery(q); 0313 if(!m_testUrl2.isEmpty()) url = m_testUrl2; 0314 QByteArray data = FileHandler::readDataFile(url, true); 0315 #if 0 0316 myWarning() << "Remove output debug from doubanfetcher.cpp"; 0317 QFile f(QLatin1String("/tmp/test-douban2.json")); 0318 if(f.open(QIODevice::WriteOnly)) { 0319 QTextStream t(&f); 0320 t.setCodec("UTF-8"); 0321 t << data; 0322 } 0323 f.close(); 0324 #endif 0325 0326 QJsonDocument doc = QJsonDocument::fromJson(data); 0327 entry = createEntry(doc.object().toVariantMap()); 0328 0329 if(entry) { 0330 m_entries.insert(uid_, entry); 0331 } 0332 return entry; 0333 } 0334 0335 Tellico::Data::EntryPtr DoubanFetcher::createEntry(const QVariantMap& resultMap_) { 0336 Data::CollPtr coll; 0337 Data::EntryPtr entry; 0338 switch(request().collectionType()) { 0339 case Data::Collection::Book: 0340 case Data::Collection::Bibtex: 0341 coll = new Data::BookCollection(true); 0342 if(optionalFields().contains(QStringLiteral("origtitle")) && 0343 !mapValue(resultMap_, "origin_title").isEmpty() && 0344 !coll->hasField(QStringLiteral("origtitle"))) { 0345 Data::FieldPtr f(new Data::Field(QStringLiteral("origtitle"), i18n("Original Title"))); 0346 f->setFormatType(FieldFormat::FormatTitle); 0347 coll->addField(f); 0348 } 0349 if(optionalFields().contains(QStringLiteral("douban")) && 0350 !coll->hasField(QStringLiteral("douban"))) { 0351 Data::FieldPtr f(new Data::Field(QStringLiteral("douban"), i18n("Douban Link"), Data::Field::URL)); 0352 f->setCategory(i18n("General")); 0353 coll->addField(f); 0354 } 0355 entry = new Data::Entry(coll); 0356 populateBookEntry(entry, resultMap_); 0357 break; 0358 case Data::Collection::Video: 0359 coll = new Data::VideoCollection(true); 0360 if(optionalFields().contains(QStringLiteral("origtitle")) && 0361 !mapValue(resultMap_, "original_title").isEmpty() && 0362 !coll->hasField(QStringLiteral("origtitle"))) { 0363 Data::FieldPtr f(new Data::Field(QStringLiteral("origtitle"), i18n("Original Title"))); 0364 f->setFormatType(FieldFormat::FormatTitle); 0365 coll->addField(f); 0366 } 0367 if(optionalFields().contains(QStringLiteral("douban")) && 0368 !coll->hasField(QStringLiteral("douban"))) { 0369 Data::FieldPtr f(new Data::Field(QStringLiteral("douban"), i18n("Douban Link"), Data::Field::URL)); 0370 f->setCategory(i18n("General")); 0371 coll->addField(f); 0372 } 0373 entry = new Data::Entry(coll); 0374 populateVideoEntry(entry, resultMap_); 0375 break; 0376 case Data::Collection::Album: 0377 coll = new Data::MusicCollection(true); 0378 if(optionalFields().contains(QStringLiteral("origtitle")) && 0379 !mapValue(resultMap_, "original_title").isEmpty() && 0380 !coll->hasField(QStringLiteral("origtitle"))) { 0381 Data::FieldPtr f(new Data::Field(QStringLiteral("origtitle"), i18n("Original Title"))); 0382 f->setFormatType(FieldFormat::FormatTitle); 0383 coll->addField(f); 0384 } 0385 if(optionalFields().contains(QStringLiteral("douban")) && 0386 !coll->hasField(QStringLiteral("douban"))) { 0387 Data::FieldPtr f(new Data::Field(QStringLiteral("douban"), i18n("Douban Link"), Data::Field::URL)); 0388 f->setCategory(i18n("General")); 0389 coll->addField(f); 0390 } 0391 entry = new Data::Entry(coll); 0392 populateMusicEntry(entry, resultMap_); 0393 break; 0394 default: 0395 break; 0396 } 0397 Q_ASSERT(coll); 0398 Q_ASSERT(entry); 0399 if(!coll || !entry) { 0400 return entry; 0401 } 0402 0403 // Now read the info_url which is a table with additional info like binding and ISBN 0404 QString info_url = mapValue(resultMap_, "info_url"); 0405 if(info_url.isEmpty()) { 0406 info_url = mapValue(resultMap_, "intro_url"); 0407 } 0408 if(!info_url.isEmpty()) { 0409 const QString infoHtml = Tellico::decodeHTML(FileHandler::readTextFile(QUrl::fromUserInput(info_url), true)); 0410 QRegularExpression tableRowRx(QStringLiteral("<tr>.*?<td>(.+?)</td>.*?<td>(.+?)</td>.*?</tr>"), 0411 QRegularExpression::DotMatchesEverythingOption); 0412 QRegularExpressionMatchIterator i = tableRowRx.globalMatch(infoHtml); 0413 while(i.hasNext()) { 0414 QRegularExpressionMatch match = i.next(); 0415 const QString m1 = match.captured(1).simplified(); 0416 const QString m2 = match.captured(2).simplified(); 0417 if(m1 == QString::fromUtf8("原作名")) { 0418 const QString origtitle = QStringLiteral("origtitle"); 0419 if(entry->collection()->hasField(origtitle) && entry->field(origtitle).isEmpty()) { 0420 entry->setField(origtitle, m2); 0421 } 0422 } else if(m1 == QString::fromUtf8("装帧")) { 0423 if(m2 == QStringLiteral("精装")) { 0424 entry->setField(QStringLiteral("binding"), i18n("Hardback")); 0425 } else if(m2 == QStringLiteral("平装")) { 0426 entry->setField(QStringLiteral("binding"), i18n("Paperback")); 0427 } 0428 } else if(m1 == QLatin1String("ISBN")) { 0429 entry->setField(QStringLiteral("isbn"), m2); 0430 } else if(m1 == QString::fromUtf8("介质")) { 0431 if(m2.contains(QLatin1String("CD"))) { 0432 entry->setField(QStringLiteral("medium"), i18n("Compact Disc")); 0433 } 0434 } else if(m1 == QString::fromUtf8("流派")) { 0435 static const QRegularExpression slashRx(QLatin1String("\\s*/\\s*")); 0436 const QStringList genres = m2.split(slashRx); 0437 entry->setField(QStringLiteral("genre"), genres.join(FieldFormat::rowDelimiterString())); 0438 } else if(m1 == QString::fromUtf8("片长")) { 0439 static const QRegularExpression digits(QLatin1String("\\d+")); 0440 QRegularExpressionMatch digitsMatch = digits.match(m2); 0441 if(digitsMatch.hasMatch()) { 0442 entry->setField(QStringLiteral("running-time"), digitsMatch.captured()); 0443 } 0444 } else if(m1 == QString::fromUtf8("编剧")) { 0445 entry->setField(QStringLiteral("writer"), m2); 0446 } 0447 } 0448 } 0449 0450 const QString image_id = entry->field(QStringLiteral("cover")); 0451 // if it's still a url, we need to load it 0452 if(image_id.startsWith(QLatin1String("http"))) { 0453 const QString id = ImageFactory::addImage(QUrl::fromUserInput(image_id), true); 0454 if(id.isEmpty()) { 0455 message(i18n("The cover image could not be loaded."), MessageHandler::Warning); 0456 entry->setField(QStringLiteral("cover"), QString()); 0457 } else { 0458 entry->setField(QStringLiteral("cover"), id); 0459 } 0460 } 0461 return entry; 0462 } 0463 0464 void DoubanFetcher::populateBookEntry(Data::EntryPtr entry, const QVariantMap& resultMap_) { 0465 entry->setField(QStringLiteral("title"), mapValue(resultMap_, "title")); 0466 entry->setField(QStringLiteral("subtitle"), mapValue(resultMap_, "subtitle")); 0467 entry->setField(QStringLiteral("author"), mapValue(resultMap_, "author")); 0468 entry->setField(QStringLiteral("translator"), mapValue(resultMap_, "translator")); 0469 if(resultMap_.contains(QLatin1String("publisher"))) { 0470 entry->setField(QStringLiteral("publisher"), mapValue(resultMap_, "publisher")); 0471 } else { 0472 entry->setField(QStringLiteral("publisher"), mapValue(resultMap_, "press")); 0473 } 0474 0475 const QString binding = mapValue(resultMap_, "binding"); 0476 if(binding == QStringLiteral("精装")) { 0477 entry->setField(QStringLiteral("binding"), i18n("Hardback")); 0478 } else if(binding == QStringLiteral("平装")) { 0479 entry->setField(QStringLiteral("binding"), i18n("Paperback")); 0480 } 0481 0482 entry->setField(QStringLiteral("pub_year"), mapValue(resultMap_, "pubdate").left(4)); 0483 QString isbn; 0484 if(resultMap_.contains(QLatin1String("isbn10"))) { 0485 isbn = mapValue(resultMap_, "isbn10"); 0486 } else if(request().key() == ISBN && !request().value().contains(QLatin1Char(';'))) { 0487 isbn = request().value(); 0488 } 0489 entry->setField(QStringLiteral("isbn"), ISBNValidator::isbn10(isbn)); 0490 entry->setField(QStringLiteral("pages"), mapValue(resultMap_, "pages")); 0491 if(resultMap_.contains(QLatin1String("cover_url"))) { 0492 entry->setField(QStringLiteral("cover"), mapValue(resultMap_, "cover_url")); 0493 } else { 0494 entry->setField(QStringLiteral("cover"), mapValue(resultMap_, "image")); 0495 } 0496 0497 QString keyword = mapValue(resultMap_, "tags", "title"); 0498 if(keyword.isEmpty()) { 0499 keyword = mapValue(resultMap_, "tags", "name"); 0500 } 0501 entry->setField(QStringLiteral("keyword"), keyword); 0502 0503 if(entry->collection()->hasField(QStringLiteral("origtitle")) && 0504 !mapValue(resultMap_, "origin_title").isEmpty()) { 0505 entry->setField(QStringLiteral("origtitle"), mapValue(resultMap_, "origin_title")); 0506 } 0507 if(entry->collection()->hasField(QStringLiteral("douban"))) { 0508 if(resultMap_.contains(QLatin1String("alt"))) { 0509 entry->setField(QStringLiteral("douban"), mapValue(resultMap_, "alt")); 0510 } else { 0511 entry->setField(QStringLiteral("douban"), mapValue(resultMap_, "url")); 0512 } 0513 } 0514 if(resultMap_.contains(QLatin1String("summary"))) { 0515 entry->setField(QStringLiteral("plot"), mapValue(resultMap_, "summary")); 0516 } else { 0517 entry->setField(QStringLiteral("plot"), mapValue(resultMap_, "intro")); 0518 } 0519 } 0520 0521 void DoubanFetcher::populateVideoEntry(Data::EntryPtr entry, const QVariantMap& resultMap_) { 0522 entry->setField(QStringLiteral("title"), mapValue(resultMap_, "title")); 0523 entry->setField(QStringLiteral("genre"), mapValue(resultMap_, "genres")); 0524 entry->setField(QStringLiteral("director"), mapValue(resultMap_, "directors", "name")); 0525 entry->setField(QStringLiteral("writer"), mapValue(resultMap_, "writers", "name")); 0526 entry->setField(QStringLiteral("year"), mapValue(resultMap_, "year")); 0527 if(resultMap_.contains(QLatin1String("cover_url"))) { 0528 entry->setField(QStringLiteral("cover"), mapValue(resultMap_, "cover_url")); 0529 } else { 0530 entry->setField(QStringLiteral("cover"), mapValue(resultMap_, "images", "medium")); 0531 } 0532 if(resultMap_.contains(QLatin1String("summary"))) { 0533 entry->setField(QStringLiteral("plot"), mapValue(resultMap_, "summary")); 0534 } else { 0535 entry->setField(QStringLiteral("plot"), mapValue(resultMap_, "intro")); 0536 } 0537 0538 QStringList actors; 0539 foreach(const QVariant& v, resultMap_.value(QLatin1String("casts")).toList()) { 0540 actors << v.toMap().value(QStringLiteral("name")).toString(); 0541 } 0542 entry->setField(QStringLiteral("cast"), actors.join(FieldFormat::rowDelimiterString())); 0543 0544 if(optionalFields().contains(QStringLiteral("origtitle")) && 0545 !mapValue(resultMap_, "original_title").isEmpty()) { 0546 entry->setField(QStringLiteral("origtitle"), mapValue(resultMap_, "original_title")); 0547 } 0548 if(optionalFields().contains(QStringLiteral("douban"))) { 0549 entry->setField(QStringLiteral("douban"), mapValue(resultMap_, "alt")); 0550 } 0551 } 0552 0553 void DoubanFetcher::populateMusicEntry(Data::EntryPtr entry, const QVariantMap& resultMap_) { 0554 entry->setField(QStringLiteral("title"), mapValue(resultMap_, "title")); 0555 if(resultMap_.contains(QLatin1String("cover_url"))) { 0556 entry->setField(QStringLiteral("cover"), mapValue(resultMap_, "cover_url")); 0557 } else { 0558 entry->setField(QStringLiteral("cover"), mapValue(resultMap_, "image")); 0559 } 0560 entry->setField(QStringLiteral("artist"), mapValue(resultMap_, "attrs", "singer")); 0561 entry->setField(QStringLiteral("label"), mapValue(resultMap_, "attrs", "publisher")); 0562 entry->setField(QStringLiteral("year"), mapValue(resultMap_, "attrs", "pubdate").left(4)); 0563 0564 if(mapValue(resultMap_, "attrs", "media") == QLatin1String("Audio CD") || 0565 mapValue(resultMap_, "attrs", "media") == QLatin1String("CD")) { 0566 entry->setField(QStringLiteral("medium"), i18n("Compact Disc")); 0567 } 0568 0569 QStringList values, tracks; 0570 foreach(const QVariant& v, resultMap_.value(QLatin1String("songs")).toList()) { 0571 const QVariantMap trackMap = v.toMap(); 0572 QString title = mapValue(trackMap, "title"); 0573 QString artists = mapValue(trackMap, "artist_names"); 0574 if(artists.isEmpty()) artists = entry->field(QStringLiteral("artist")); 0575 QString duration = mapValue(trackMap, "duration"); 0576 tracks << title + FieldFormat::columnDelimiterString() + 0577 artists + FieldFormat::columnDelimiterString() + 0578 Tellico::minutes(duration.toInt()); 0579 } 0580 entry->setField(QStringLiteral("track"), tracks.join(FieldFormat::rowDelimiterString())); 0581 0582 if(optionalFields().contains(QStringLiteral("origtitle")) && 0583 !mapValue(resultMap_, "original_title").isEmpty()) { 0584 entry->setField(QStringLiteral("origtitle"), mapValue(resultMap_, "original_title")); 0585 } 0586 if(optionalFields().contains(QStringLiteral("douban"))) { 0587 entry->setField(QStringLiteral("douban"), mapValue(resultMap_, "alt")); 0588 } 0589 } 0590 0591 Tellico::Fetch::FetchRequest DoubanFetcher::updateRequest(Data::EntryPtr entry_) { 0592 QString isbn = entry_->field(QStringLiteral("isbn")); 0593 if(!isbn.isEmpty()) { 0594 return FetchRequest(ISBN, isbn); 0595 } 0596 0597 QString title = entry_->field(QStringLiteral("title")); 0598 if(!title.isEmpty()) { 0599 return FetchRequest(Title, title); 0600 } 0601 return FetchRequest(); 0602 } 0603 0604 Tellico::Fetch::ConfigWidget* DoubanFetcher::configWidget(QWidget* parent_) const { 0605 return new DoubanFetcher::ConfigWidget(parent_, this); 0606 } 0607 0608 QString DoubanFetcher::defaultName() { 0609 return QStringLiteral("Douban.com"); 0610 } 0611 0612 QString DoubanFetcher::defaultIcon() { 0613 return favIcon("http://www.douban.com"); 0614 } 0615 0616 Tellico::StringHash DoubanFetcher::allOptionalFields() { 0617 StringHash hash; 0618 hash[QStringLiteral("origtitle")] = i18n("Original Title"); 0619 hash[QStringLiteral("douban")] = i18n("Douban Link"); 0620 return hash; 0621 } 0622 0623 DoubanFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const DoubanFetcher* fetcher_) 0624 : Fetch::ConfigWidget(parent_) { 0625 QVBoxLayout* l = new QVBoxLayout(optionsWidget()); 0626 l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget())); 0627 l->addStretch(); 0628 0629 // now add additional fields widget 0630 addFieldsWidget(DoubanFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList()); 0631 } 0632 0633 void DoubanFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) { 0634 Q_UNUSED(config_); 0635 } 0636 0637 QString DoubanFetcher::ConfigWidget::preferredName() const { 0638 return DoubanFetcher::defaultName(); 0639 }