File indexing completed on 2024-11-24 04:34:34

0001 /***************************************************************************
0002  *   SPDX-License-Identifier: GPL-2.0-or-later
0003  *                                                                         *
0004  *   SPDX-FileCopyrightText: 2004-2018 Thomas Fischer <fischer@unix-ag.uni-kl.de>
0005  *                                                                         *
0006  *   This program is free software; you can redistribute it and/or modify  *
0007  *   it under the terms of the GNU General Public License as published by  *
0008  *   the Free Software Foundation; either version 2 of the License, or     *
0009  *   (at your option) any later version.                                   *
0010  *                                                                         *
0011  *   This program is distributed in the hope that it will be useful,       *
0012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0014  *   GNU General Public License for more details.                          *
0015  *                                                                         *
0016  *   You should have received a copy of the GNU General Public License     *
0017  *   along with this program; if not, see <https://www.gnu.org/licenses/>. *
0018  ***************************************************************************/
0019 
0020 #include "items.h"
0021 
0022 #include <QNetworkRequest>
0023 #include <QNetworkReply>
0024 #include <QXmlStreamReader>
0025 #include <QUrlQuery>
0026 #include <QTimer>
0027 
0028 #include <File>
0029 #include <FileImporterBibTeX>
0030 #include "zotero/api.h"
0031 #include "internalnetworkaccessmanager.h"
0032 #include "logging_networking.h"
0033 
0034 using namespace Zotero;
0035 
0036 class Zotero::Items::Private
0037 {
0038 private:
0039     Zotero::Items *p;
0040 
0041 public:
0042     QSharedPointer<Zotero::API> api;
0043 
0044     Private(QSharedPointer<Zotero::API> a, Zotero::Items *parent)
0045             : p(parent), api(a) {
0046         /// nothing
0047     }
0048 
0049     QNetworkReply *requestZoteroUrl(const QUrl &url) {
0050         QUrl internalUrl = url;
0051         api->addLimitToUrl(internalUrl);
0052         QNetworkRequest request = api->request(internalUrl);
0053         QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request);
0054         connect(reply, &QNetworkReply::finished, p, &Zotero::Items::finishedFetchingItems);
0055         return reply;
0056     }
0057 
0058     void retrieveItems(const QUrl &url, int start) {
0059         QUrl internalUrl = url;
0060 
0061         static const QString queryItemStart = QStringLiteral("start");
0062         QUrlQuery query(internalUrl);
0063         query.removeQueryItem(queryItemStart);
0064         query.addQueryItem(queryItemStart, QString::number(start));
0065         internalUrl.setQuery(query);
0066 
0067         if (api->inBackoffMode())
0068             /// If Zotero asked to 'back off', wait until this period is over before issuing the next request
0069             QTimer::singleShot((api->backoffSecondsLeft() + 1) * 1000, p, [ = ]() {
0070                 requestZoteroUrl(internalUrl);
0071             });
0072         else
0073             requestZoteroUrl(internalUrl);
0074     }
0075 };
0076 
0077 Items::Items(QSharedPointer<Zotero::API> api, QObject *parent)
0078         : QObject(parent), d(new Zotero::Items::Private(api, this))
0079 {
0080     /// nothing
0081 }
0082 
0083 Items::~Items()
0084 {
0085     delete d;
0086 }
0087 
0088 void Items::retrieveItemsByCollection(const QString &collection)
0089 {
0090     QUrl url = d->api->baseUrl().adjusted(QUrl::StripTrailingSlash);
0091     if (collection.isEmpty())
0092         url.setPath(url.path() + QStringLiteral("/items"));
0093     else
0094         url.setPath(url.path() + QString(QStringLiteral("/collections/%1/items")).arg(collection));
0095     QUrlQuery query(url);
0096     query.addQueryItem(QStringLiteral("format"), QStringLiteral("bibtex"));
0097     url.setQuery(query);
0098 
0099     if (d->api->inBackoffMode())
0100         /// If Zotero asked to 'back off', wait until this period is over before issuing the next request
0101         QTimer::singleShot((d->api->backoffSecondsLeft() + 1) * 1000, this, [ = ]() {
0102             d->retrieveItems(url, 0);
0103         });
0104     else
0105         d->retrieveItems(url, 0);
0106 }
0107 
0108 void Items::retrieveItemsByTag(const QString &tag)
0109 {
0110     QUrl url = d->api->baseUrl().adjusted(QUrl::StripTrailingSlash);
0111     QUrlQuery query(url);
0112     if (!tag.isEmpty())
0113         query.addQueryItem(QStringLiteral("tag"), tag);
0114     url.setPath(url.path() + QStringLiteral("/items"));
0115     query.addQueryItem(QStringLiteral("format"), QStringLiteral("bibtex"));
0116     url.setQuery(query);
0117 
0118     if (d->api->inBackoffMode())
0119         /// If Zotero asked to 'back off', wait until this period is over before issuing the next request
0120         QTimer::singleShot((d->api->backoffSecondsLeft() + 1) * 1000, this, [ = ]() {
0121             d->retrieveItems(url, 0);
0122         });
0123     else
0124         d->retrieveItems(url, 0);
0125 }
0126 
0127 void Items::finishedFetchingItems()
0128 {
0129     QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
0130     static const QString queryItemStart = QStringLiteral("start");
0131     bool ok = false;
0132     const int start = QUrlQuery(reply->url()).queryItemValue(queryItemStart).toInt(&ok);
0133 
0134     if (reply->hasRawHeader("Backoff")) {
0135         bool ok = false;
0136         int time = QString::fromLatin1(reply->rawHeader("Backoff").constData()).toInt(&ok);
0137         if (!ok) time = 10; ///< parsing argument of raw header 'Backoff' failed? 10 seconds is fallback
0138         d->api->startBackoff(time);
0139     } else if (reply->hasRawHeader("Retry-After")) {
0140         bool ok = false;
0141         int time = QString::fromLatin1(reply->rawHeader("Retry-After").constData()).toInt(&ok);
0142         if (!ok) time = 10; ///< parsing argument of raw header 'Retry-After' failed? 10 seconds is fallback
0143         d->api->startBackoff(time);
0144     }
0145 
0146     if (reply->error() == QNetworkReply::NoError && ok) {
0147         const QString bibTeXcode = QString::fromUtf8(reply->readAll().constData());
0148         /// Non-empty result?
0149         if (!bibTeXcode.isEmpty()) {
0150             FileImporterBibTeX importer(this);
0151             /// Parse text into bibliography object
0152             File *bibtexFile = importer.fromString(bibTeXcode);
0153 
0154             /// Perform basic sanity checks ...
0155             if (bibtexFile != nullptr && !bibtexFile->isEmpty()) {
0156                 for (const QSharedPointer<Element> &element : const_cast<const File &>(*bibtexFile)) {
0157                     Q_EMIT foundElement(element); ///< ... and publish result
0158                 }
0159             }
0160 
0161             delete bibtexFile;
0162 
0163             /// Non-empty result means there may be more ...
0164             d->retrieveItems(reply->url(), start + Zotero::API::limit);
0165         } else {
0166             /// Done retrieving BibTeX code
0167             Q_EMIT stoppedSearch(0); // TODO proper error codes
0168         }
0169     } else {
0170         qCWarning(LOG_KBIBTEX_NETWORKING) << reply->errorString(); ///< something went wrong
0171         Q_EMIT stoppedSearch(1); // TODO proper error codes
0172     }
0173 }