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 "groups.h"
0021 
0022 #include <QNetworkReply>
0023 #include <QXmlStreamReader>
0024 #include <QUrl>
0025 #include <QTimer>
0026 
0027 #include "internalnetworkaccessmanager.h"
0028 #include "zotero/api.h"
0029 #include "logging_networking.h"
0030 
0031 using namespace Zotero;
0032 
0033 class Zotero::Groups::Private
0034 {
0035 private:
0036     Zotero::Groups *p;
0037 
0038 public:
0039     QSharedPointer<Zotero::API> api;
0040 
0041     Private(QSharedPointer<Zotero::API> a, Zotero::Groups *parent)
0042             : p(parent), api(a) {
0043         initialized = false;
0044         busy = false;
0045     }
0046 
0047     bool initialized, busy;
0048 
0049     QMap<int, QString> groups;
0050 
0051     QNetworkReply *requestZoteroUrl(const QUrl &url) {
0052         busy = true;
0053         QUrl internalUrl = url;
0054         api->addLimitToUrl(internalUrl);
0055         QNetworkRequest request = api->request(internalUrl);
0056         QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request);
0057         connect(reply, &QNetworkReply::finished, p, &Zotero::Groups::finishedFetchingGroups);
0058         return reply;
0059     }
0060 };
0061 
0062 Groups::Groups(QSharedPointer<Zotero::API> api, QObject *parent)
0063         : QObject(parent), d(new Zotero::Groups::Private(api, this))
0064 {
0065     QUrl url = api->baseUrl();
0066     Q_ASSERT_X(url.path().contains(QLatin1String("users/")), "Groups::Groups(QSharedPointer<Zotero::API> api, QObject *parent)", "Provided base URL does not contain 'users/' as expected");
0067     url = url.adjusted(QUrl::StripTrailingSlash);
0068     url.setPath(url.path() + QStringLiteral("/groups"));
0069 
0070     if (d->api->inBackoffMode())
0071         /// If Zotero asked to 'back off', wait until this period is over before issuing the next request
0072         QTimer::singleShot((d->api->backoffSecondsLeft() + 1) * 1000, this, [ = ]() {
0073             d->requestZoteroUrl(url);
0074         });
0075     else
0076         d->requestZoteroUrl(url);
0077 }
0078 
0079 Groups::~Groups()
0080 {
0081     delete d;
0082 }
0083 
0084 bool Groups::initialized() const
0085 {
0086     return d->initialized;
0087 }
0088 
0089 bool Groups::busy() const
0090 {
0091     return d->busy;
0092 }
0093 
0094 QMap<int, QString> Groups::groups() const
0095 {
0096     return d->groups;
0097 }
0098 
0099 void Groups::finishedFetchingGroups()
0100 {
0101     QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
0102 
0103     if (reply->hasRawHeader("Backoff")) {
0104         bool ok = false;
0105         int time = QString::fromLatin1(reply->rawHeader("Backoff").constData()).toInt(&ok);
0106         if (!ok) time = 10; ///< parsing argument of raw header 'Backoff' failed? 10 seconds is fallback
0107         d->api->startBackoff(time);
0108     } else if (reply->hasRawHeader("Retry-After")) {
0109         bool ok = false;
0110         int time = QString::fromLatin1(reply->rawHeader("Retry-After").constData()).toInt(&ok);
0111         if (!ok) time = 10; ///< parsing argument of raw header 'Retry-After' failed? 10 seconds is fallback
0112         d->api->startBackoff(time);
0113     }
0114 
0115     if (reply->error() == QNetworkReply::NoError) {
0116         QString nextPage;
0117         QXmlStreamReader xmlReader(reply);
0118         while (!xmlReader.atEnd() && !xmlReader.hasError()) {
0119             const QXmlStreamReader::TokenType tt = xmlReader.readNext();
0120             if (tt == QXmlStreamReader::StartElement && xmlReader.name() == QStringLiteral("entry")) {
0121                 QString label;
0122                 int groupId = -1;
0123                 while (!xmlReader.atEnd() && !xmlReader.hasError()) {
0124                     const QXmlStreamReader::TokenType tt = xmlReader.readNext();
0125                     if (tt == QXmlStreamReader::StartElement && xmlReader.name() == QStringLiteral("title"))
0126                         label = xmlReader.readElementText(QXmlStreamReader::IncludeChildElements);
0127                     else if (tt == QXmlStreamReader::StartElement && xmlReader.name() == QStringLiteral("groupID")) {
0128                         bool ok = false;
0129                         groupId = xmlReader.readElementText(QXmlStreamReader::IncludeChildElements).toInt(&ok);
0130                         if (groupId < 1) groupId = -1;
0131                     } else if (tt == QXmlStreamReader::EndElement && xmlReader.name() == QStringLiteral("entry"))
0132                         break;
0133                 }
0134 
0135                 if (!label.isEmpty() && groupId > 0)
0136                     d->groups.insert(groupId, label);
0137             } else if (tt == QXmlStreamReader::StartElement && xmlReader.name() == QStringLiteral("link")) {
0138                 const QXmlStreamAttributes attrs = xmlReader.attributes();
0139                 if (attrs.hasAttribute(QStringLiteral("rel")) && attrs.hasAttribute(QStringLiteral("href")) && attrs.value(QStringLiteral("rel")) == QStringLiteral("next"))
0140                     nextPage = attrs.value(QStringLiteral("href")).toString();
0141             } else if (tt == QXmlStreamReader::EndElement && xmlReader.name() == QStringLiteral("feed"))
0142                 break;
0143         }
0144 
0145         if (!nextPage.isEmpty()) {
0146             if (d->api->inBackoffMode())
0147                 /// If Zotero asked to 'back off', wait until this period is over before issuing the next request
0148                 QTimer::singleShot((d->api->backoffSecondsLeft() + 1) * 1000, this, [ = ]() {
0149                     d->requestZoteroUrl(QUrl{nextPage});
0150                 });
0151             else
0152                 d->requestZoteroUrl(QUrl{nextPage});
0153         } else {
0154             d->busy = false;
0155             d->initialized = true;
0156             Q_EMIT finishedLoading();
0157         }
0158     } else {
0159         qCWarning(LOG_KBIBTEX_NETWORKING) << reply->errorString(); ///< something went wrong
0160         d->busy = false;
0161         d->initialized = false;
0162         Q_EMIT finishedLoading();
0163     }
0164 }