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 }