File indexing completed on 2024-05-05 04:48:36

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Urs Wolfer <uwolfer@kde.org>                                      *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #ifndef AMAROK_NETWORKACCESSMANAGERPROXY
0018 #define AMAROK_NETWORKACCESSMANAGERPROXY
0019 
0020 #include "amarok_export.h"
0021 #include <config.h>
0022 #include "core/support/Debug.h"
0023 
0024 #include <KIO/AccessManager>
0025 
0026 #include <QNetworkReply>
0027 #include <QPointer>
0028 #include <QUrl>
0029 #include <QThread>
0030 #include <QTimer>
0031 
0032 
0033 class NetworkAccessManagerProxy;
0034 #ifdef DEBUG_BUILD_TYPE
0035 class NetworkAccessViewer;
0036 #endif // DEBUG_BUILD_TYPE
0037 
0038 namespace The
0039 {
0040     AMAROK_EXPORT NetworkAccessManagerProxy *networkAccessManager();
0041 }
0042 
0043 class AMAROK_EXPORT NetworkAccessManagerProxy : public KIO::Integration::AccessManager
0044 {
0045     Q_OBJECT
0046 
0047 public:
0048     static NetworkAccessManagerProxy *instance();
0049     static void destroy();
0050     ~NetworkAccessManagerProxy() override;
0051 
0052     struct Error
0053     {
0054         QNetworkReply::NetworkError code;
0055         QString description;
0056     };
0057 
0058     /**
0059      * Gets the contents of the target @p url. It is a convenience wrapper
0060      * around QNetworkAccessManager::get() where the user supplies a
0061      * slot @p method to be called when the content is retrieved.
0062      * NOTE: On redirects requestRedirected is emitted.
0063      *
0064      * @param url the url to get the content from.
0065      * @param receiver the receiver object to call @p method on.
0066      * @param method the method to call when content is retrieved.
0067      * @param type the Qt::ConnectionType used for calling the @p method.
0068      * @return a QNetworkReply object for custom monitoring.
0069      */
0070     template<typename Return, typename Object, typename... Args>
0071     QNetworkReply *getData( const QUrl &url, Object *receiver, Return ( Object::*method )( Args... ),
0072                             Qt::ConnectionType type = Qt::AutoConnection )
0073     {
0074         if( !url.isValid() )
0075         {
0076             const QMetaObject *mo = receiver->metaObject();
0077             debug() << QStringLiteral( "Error: URL '%1' is invalid (from %2)" ).arg( url.url(), mo->className() );
0078             return nullptr;
0079         }
0080 
0081         QNetworkReply *r = get( QNetworkRequest(url) );
0082         m_urlMap.insert( url, r );
0083         auto lambda = [this, r, receiver, method, type] ()
0084         {
0085             replyFinished( r, QPointer<Object>( receiver ), method, type );
0086         };
0087         connect( r, &QNetworkReply::finished, this, lambda );
0088         return r;
0089     }
0090 
0091     int abortGet( const QUrl &url );
0092     int abortGet( const QList<QUrl> &urls );
0093 
0094     /**
0095      * Gets the URL to which a server redirects the request.
0096      * An empty QUrl will be returned if the request was not redirected.
0097      *
0098      * @param reply The QNetworkReply which contains all information about
0099      *              the reply from the server.
0100      *
0101      * @return The URL to which the server redirected the request or an empty
0102      *         URL if there was no redirect.
0103      */
0104     QUrl getRedirectUrl( QNetworkReply *reply );
0105 
0106 #ifdef DEBUG_BUILD_TYPE
0107     NetworkAccessViewer *networkAccessViewer();
0108     void setNetworkAccessViewer( NetworkAccessViewer *viewer );
0109 #endif // DEBUG_BUILD_TYPE
0110 
0111 Q_SIGNALS:
0112     void requestRedirectedUrl( const QUrl &sourceUrl, const QUrl &targetUrl );
0113     void requestRedirectedReply( QNetworkReply* oldReply, QNetworkReply *newReply );
0114 
0115 public Q_SLOTS:
0116     void slotError( QObject *reply );
0117 
0118 protected:
0119     QNetworkReply *createRequest(Operation op, const QNetworkRequest &req,
0120                                          QIODevice *outgoingData = nullptr) override;
0121 
0122 private:
0123     NetworkAccessManagerProxy( QObject *parent = nullptr );
0124 
0125     template <typename Return, typename Object, typename... Args>
0126     void replyFinished( QNetworkReply *reply, QPointer<Object> receiver, Return ( Object::*method )( Args... ), Qt::ConnectionType type )
0127     {
0128         if( !reply || !receiver )
0129             return;
0130 
0131         QUrl url = reply->request().url();
0132         QByteArray data = reply->readAll();
0133         data.detach(); // detach so the bytes are not deleted before methods are invoked
0134 
0135         // There may have been a redirect.
0136         QUrl redirectUrl = getRedirectUrl( reply );
0137 
0138         // Check if there's no redirect.
0139         if( redirectUrl.isEmpty() )
0140         {
0141             Error err = { reply->error(), reply->errorString() };
0142 
0143             if( type == Qt::AutoConnection )
0144             {
0145                 if( QThread::currentThread() == receiver->thread() )
0146                     type = Qt::DirectConnection;
0147                 else
0148                     type = Qt::QueuedConnection;
0149             }
0150 
0151             if( type == Qt::DirectConnection )
0152                 ( receiver->*method )( url, data, err );
0153             else
0154             {
0155                 auto lambda = [receiver, method, url, data, err] ()
0156                 {
0157                     ( receiver->*method )( url, data, err );
0158                 };
0159                 QTimer::singleShot( 0, receiver, lambda );
0160             }
0161         }
0162         else
0163         {
0164             debug() << "the server is redirecting the request to: " << redirectUrl;
0165 
0166             // Let's try to fetch the data again, but this time from the new url.
0167             QNetworkReply *newReply = getData( redirectUrl, receiver.data(), method, type );
0168 
0169             Q_EMIT requestRedirectedUrl( url, redirectUrl );
0170             Q_EMIT requestRedirectedReply( reply, newReply );
0171         }
0172 
0173         reply->deleteLater();
0174     }
0175 
0176     static NetworkAccessManagerProxy *s_instance;
0177 
0178     QMultiHash<QUrl, QNetworkReply*> m_urlMap;
0179     QString m_userAgent;
0180 
0181 #ifdef DEBUG_BUILD_TYPE
0182     NetworkAccessViewer *m_viewer;
0183 #endif // DEBUG_BUILD_TYPE
0184 
0185     Q_DISABLE_COPY( NetworkAccessManagerProxy )
0186 };
0187 
0188 Q_DECLARE_METATYPE( NetworkAccessManagerProxy::Error )
0189 #endif // AMAROK_NETWORKACCESSMANAGERPROXY