mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-04-20 07:52:30 +02:00
Return IODevices for track URLs through callbacks.
This commit is contained in:
parent
20bfb48be7
commit
230fbdcef9
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright 2010-2012, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -39,6 +40,8 @@
|
||||
#include "utils/Logger.h"
|
||||
#include "playlist/SingleTrackPlaylistInterface.h"
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QDir>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
@ -430,30 +433,43 @@ AudioEngine::onNowPlayingInfoReady( const Tomahawk::InfoSystem::InfoType type )
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
void
|
||||
AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
|
||||
{
|
||||
if ( result.isNull() )
|
||||
{
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentTrack( result );
|
||||
|
||||
if ( !TomahawkUtils::isHttpResult( m_currentTrack->url() ) &&
|
||||
!TomahawkUtils::isLocalResult( m_currentTrack->url() ) )
|
||||
{
|
||||
boost::function< void ( QSharedPointer< QIODevice >& ) > callback =
|
||||
boost::bind( &AudioEngine::performLoadTrack, this, result, _1 );
|
||||
Servent::instance()->getIODeviceForUrl( m_currentTrack, callback );
|
||||
}
|
||||
else
|
||||
{
|
||||
QSharedPointer< QIODevice > io;
|
||||
performLoadTrack( result, io );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioEngine::performLoadTrack( const Tomahawk::result_ptr& result, QSharedPointer< QIODevice >& io )
|
||||
{
|
||||
bool err = false;
|
||||
{
|
||||
QSharedPointer<QIODevice> io;
|
||||
|
||||
if ( result.isNull() )
|
||||
err = true;
|
||||
else
|
||||
if ( !TomahawkUtils::isHttpResult( m_currentTrack->url() ) &&
|
||||
!TomahawkUtils::isLocalResult( m_currentTrack->url() ) &&
|
||||
( !io || io.isNull() ) )
|
||||
{
|
||||
setCurrentTrack( result );
|
||||
|
||||
if ( !TomahawkUtils::isHttpResult( m_currentTrack->url() ) &&
|
||||
!TomahawkUtils::isLocalResult( m_currentTrack->url() ) )
|
||||
{
|
||||
io = Servent::instance()->getIODeviceForUrl( m_currentTrack );
|
||||
|
||||
if ( !io || io.isNull() )
|
||||
{
|
||||
tLog() << "Error getting iodevice for" << result->url();
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
tLog() << "Error getting iodevice for" << result->url();
|
||||
err = true;
|
||||
}
|
||||
|
||||
if ( !err )
|
||||
@ -520,11 +536,11 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
|
||||
if ( err )
|
||||
{
|
||||
stop();
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_waitingOnNewTrack = false;
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright 2010-2012, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -128,7 +129,8 @@ signals:
|
||||
void error( AudioEngine::AudioErrorCode errorCode );
|
||||
|
||||
private slots:
|
||||
bool loadTrack( const Tomahawk::result_ptr& result );
|
||||
void loadTrack( const Tomahawk::result_ptr& result ); //async!
|
||||
void performLoadTrack( const Tomahawk::result_ptr& result, QSharedPointer< QIODevice >& io ); //only call from loadTrack kthxbi
|
||||
void loadPreviousTrack();
|
||||
void loadNextTrack();
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -75,17 +76,18 @@ Servent::Servent( QObject* parent )
|
||||
setProxy( QNetworkProxy::NoProxy );
|
||||
|
||||
{
|
||||
boost::function<QSharedPointer<QIODevice>(result_ptr)> fac = boost::bind( &Servent::localFileIODeviceFactory, this, _1 );
|
||||
// _1 = result, _2 = callback function for IODevice
|
||||
IODeviceFactoryFunc fac = boost::bind( &Servent::localFileIODeviceFactory, this, _1, _2 );
|
||||
this->registerIODeviceFactory( "file", fac );
|
||||
}
|
||||
|
||||
{
|
||||
boost::function<QSharedPointer<QIODevice>(result_ptr)> fac = boost::bind( &Servent::remoteIODeviceFactory, this, _1 );
|
||||
IODeviceFactoryFunc fac = boost::bind( &Servent::remoteIODeviceFactory, this, _1, _2 );
|
||||
this->registerIODeviceFactory( "servent", fac );
|
||||
}
|
||||
|
||||
{
|
||||
boost::function<QSharedPointer<QIODevice>(result_ptr)> fac = boost::bind( &Servent::httpIODeviceFactory, this, _1 );
|
||||
IODeviceFactoryFunc fac = boost::bind( &Servent::httpIODeviceFactory, this, _1, _2 );
|
||||
this->registerIODeviceFactory( "http", fac );
|
||||
this->registerIODeviceFactory( "https", fac );
|
||||
}
|
||||
@ -944,8 +946,9 @@ Servent::claimOffer( ControlConnection* cc, const QString &nodeid, const QString
|
||||
}
|
||||
|
||||
|
||||
QSharedPointer<QIODevice>
|
||||
Servent::remoteIODeviceFactory( const result_ptr& result )
|
||||
void
|
||||
Servent::remoteIODeviceFactory( const Tomahawk::result_ptr& result,
|
||||
boost::function< void ( QSharedPointer< QIODevice >& ) > callback )
|
||||
{
|
||||
QSharedPointer<QIODevice> sp;
|
||||
|
||||
@ -954,12 +957,18 @@ Servent::remoteIODeviceFactory( const result_ptr& result )
|
||||
const QString fileId = parts.at( 1 );
|
||||
source_ptr s = SourceList::instance()->get( sourceName );
|
||||
if ( s.isNull() || !s->controlConnection() )
|
||||
return sp;
|
||||
{
|
||||
callback( sp );
|
||||
return;
|
||||
}
|
||||
|
||||
ControlConnection* cc = s->controlConnection();
|
||||
StreamConnection* sc = new StreamConnection( this, cc, fileId, result );
|
||||
createParallelConnection( cc, sc, QString( "FILE_REQUEST_KEY:%1" ).arg( fileId ) );
|
||||
return sc->iodevice();
|
||||
|
||||
//boost::functions cannot accept temporaries as parameters
|
||||
sp = sc->iodevice();
|
||||
callback( sp );
|
||||
}
|
||||
|
||||
|
||||
@ -1066,45 +1075,61 @@ Servent::triggerDBSync()
|
||||
|
||||
|
||||
void
|
||||
Servent::registerIODeviceFactory( const QString &proto, boost::function<QSharedPointer<QIODevice>(Tomahawk::result_ptr)> fac )
|
||||
Servent::registerIODeviceFactory( const QString &proto,
|
||||
IODeviceFactoryFunc fac )
|
||||
{
|
||||
m_iofactories.insert( proto, fac );
|
||||
}
|
||||
|
||||
|
||||
QSharedPointer<QIODevice>
|
||||
Servent::getIODeviceForUrl( const Tomahawk::result_ptr& result )
|
||||
void
|
||||
Servent::getIODeviceForUrl( const Tomahawk::result_ptr& result,
|
||||
boost::function< void ( QSharedPointer< QIODevice >& ) > callback )
|
||||
{
|
||||
QSharedPointer<QIODevice> sp;
|
||||
|
||||
QRegExp rx( "^([a-zA-Z0-9]+)://(.+)$" );
|
||||
if ( rx.indexIn( result->url() ) == -1 )
|
||||
return sp;
|
||||
{
|
||||
callback( sp );
|
||||
return;
|
||||
}
|
||||
|
||||
const QString proto = rx.cap( 1 );
|
||||
if ( !m_iofactories.contains( proto ) )
|
||||
return sp;
|
||||
{
|
||||
callback( sp );
|
||||
return;
|
||||
}
|
||||
|
||||
return m_iofactories.value( proto )( result );
|
||||
//QtScriptResolverHelper::customIODeviceFactory is async!
|
||||
m_iofactories.value( proto )( result, callback );
|
||||
}
|
||||
|
||||
|
||||
QSharedPointer<QIODevice>
|
||||
Servent::localFileIODeviceFactory( const Tomahawk::result_ptr& result )
|
||||
void
|
||||
Servent::localFileIODeviceFactory( const Tomahawk::result_ptr& result,
|
||||
boost::function< void ( QSharedPointer< QIODevice >& ) > callback )
|
||||
{
|
||||
// ignore "file://" at front of url
|
||||
QFile* io = new QFile( result->url().mid( QString( "file://" ).length() ) );
|
||||
if ( io )
|
||||
io->open( QIODevice::ReadOnly );
|
||||
|
||||
return QSharedPointer<QIODevice>( io );
|
||||
//boost::functions cannot accept temporaries as parameters
|
||||
QSharedPointer< QIODevice > sp = QSharedPointer<QIODevice>( io );
|
||||
callback( sp );
|
||||
}
|
||||
|
||||
|
||||
QSharedPointer<QIODevice>
|
||||
Servent::httpIODeviceFactory( const Tomahawk::result_ptr& result )
|
||||
void
|
||||
Servent::httpIODeviceFactory( const Tomahawk::result_ptr& result,
|
||||
boost::function< void ( QSharedPointer< QIODevice >& ) > callback )
|
||||
{
|
||||
QNetworkRequest req( result->url() );
|
||||
QNetworkReply* reply = TomahawkUtils::nam()->get( req );
|
||||
return QSharedPointer<QIODevice>( reply, &QObject::deleteLater );
|
||||
|
||||
//boost::functions cannot accept temporaries as parameters
|
||||
QSharedPointer< QIODevice > sp = QSharedPointer< QIODevice >( reply, &QObject::deleteLater );
|
||||
callback( sp );
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -54,6 +55,9 @@ class PortFwdThread;
|
||||
class PeerInfo;
|
||||
class SipInfo;
|
||||
|
||||
typedef boost::function< void( const Tomahawk::result_ptr&,
|
||||
boost::function< void( QSharedPointer< QIODevice >& ) > )> IODeviceFactoryFunc;
|
||||
|
||||
// this is used to hold a bit of state, so when a connected signal is emitted
|
||||
// from a socket, we can associate it with a Connection object etc.
|
||||
class DLLEXPORT QTcpSocketExtra : public QTcpSocket
|
||||
@ -121,7 +125,6 @@ public:
|
||||
QString externalAddress() const { return !m_externalHostname.isNull() ? m_externalHostname : m_externalAddress.toString(); }
|
||||
int externalPort() const { return m_externalPort; }
|
||||
|
||||
QSharedPointer< QIODevice > remoteIODeviceFactory( const Tomahawk::result_ptr& );
|
||||
static bool isIPWhitelisted( QHostAddress ip );
|
||||
|
||||
bool connectedToSession( const QString& session );
|
||||
@ -129,10 +132,11 @@ public:
|
||||
|
||||
QList< StreamConnection* > streams() const { return m_scsessions; }
|
||||
|
||||
QSharedPointer< QIODevice > getIODeviceForUrl( const Tomahawk::result_ptr& result );
|
||||
void registerIODeviceFactory( const QString &proto, boost::function< QSharedPointer< QIODevice >(Tomahawk::result_ptr) > fac );
|
||||
QSharedPointer< QIODevice > localFileIODeviceFactory( const Tomahawk::result_ptr& result );
|
||||
QSharedPointer< QIODevice > httpIODeviceFactory( const Tomahawk::result_ptr& result );
|
||||
void getIODeviceForUrl( const Tomahawk::result_ptr& result, boost::function< void ( QSharedPointer< QIODevice >& ) > callback );
|
||||
void registerIODeviceFactory( const QString &proto, IODeviceFactoryFunc fac );
|
||||
void remoteIODeviceFactory( const Tomahawk::result_ptr& result, boost::function< void ( QSharedPointer< QIODevice >& ) > callback );
|
||||
void localFileIODeviceFactory( const Tomahawk::result_ptr& result, boost::function< void ( QSharedPointer< QIODevice >& ) > callback );
|
||||
void httpIODeviceFactory( const Tomahawk::result_ptr& result, boost::function< void ( QSharedPointer< QIODevice >& ) > callback );
|
||||
|
||||
bool isReady() const { return m_ready; };
|
||||
|
||||
@ -184,7 +188,7 @@ private:
|
||||
QList< StreamConnection* > m_scsessions;
|
||||
QMutex m_ftsession_mut;
|
||||
|
||||
QMap< QString,boost::function< QSharedPointer< QIODevice >(Tomahawk::result_ptr) > > m_iofactories;
|
||||
QMap< QString, IODeviceFactoryFunc > m_iofactories;
|
||||
|
||||
QPointer< PortFwdThread > m_portfwd;
|
||||
static Servent* s_instance;
|
||||
|
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -19,10 +20,7 @@
|
||||
|
||||
#include "StreamConnection.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "Result.h"
|
||||
|
||||
#include "BufferIoDevice.h"
|
||||
#include "network/ControlConnection.h"
|
||||
#include "network/Servent.h"
|
||||
@ -31,6 +29,10 @@
|
||||
#include "SourceList.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include <QFile>
|
||||
|
||||
using namespace Tomahawk;
|
||||
|
||||
|
||||
@ -179,8 +181,16 @@ StreamConnection::startSending( const Tomahawk::result_ptr& result )
|
||||
m_result = result;
|
||||
qDebug() << "Starting to transmit" << m_result->url();
|
||||
|
||||
QSharedPointer<QIODevice> io = Servent::instance()->getIODeviceForUrl( m_result );
|
||||
if( !io )
|
||||
boost::function< void ( QSharedPointer< QIODevice >& ) > callback =
|
||||
boost::bind( &StreamConnection::reallyStartSending, this, result, _1 );
|
||||
Servent::instance()->getIODeviceForUrl( m_result, callback );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StreamConnection::reallyStartSending( const Tomahawk::result_ptr& result, QSharedPointer< QIODevice >& io )
|
||||
{
|
||||
if( !io || io.isNull() )
|
||||
{
|
||||
qDebug() << "Couldn't read from source:" << m_result->url();
|
||||
shutdown();
|
||||
|
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -71,7 +72,8 @@ protected slots:
|
||||
virtual void handleMsg( msg_ptr msg );
|
||||
|
||||
private slots:
|
||||
void startSending( const Tomahawk::result_ptr& );
|
||||
void startSending( const Tomahawk::result_ptr& result );
|
||||
void reallyStartSending( const Tomahawk::result_ptr& result, QSharedPointer< QIODevice >& io ); //only called back from startSending
|
||||
void sendSome();
|
||||
void showStats( qint64 tx, qint64 rx );
|
||||
|
||||
|
@ -293,7 +293,9 @@ QtScriptResolverHelper::md5( const QByteArray& input )
|
||||
void
|
||||
QtScriptResolverHelper::addCustomUrlHandler( const QString& protocol, const QString& callbackFuncName )
|
||||
{
|
||||
boost::function<QSharedPointer<QIODevice>(Tomahawk::result_ptr)> fac = boost::bind( &QtScriptResolverHelper::customIODeviceFactory, this, _1 );
|
||||
boost::function< void( const Tomahawk::result_ptr&,
|
||||
boost::function< void( QSharedPointer< QIODevice >& ) > )> fac =
|
||||
boost::bind( &QtScriptResolverHelper::customIODeviceFactory, this, _1, _2 );
|
||||
Servent::instance()->registerIODeviceFactory( protocol, fac );
|
||||
|
||||
m_urlCallback = callbackFuncName;
|
||||
@ -314,22 +316,31 @@ QtScriptResolverHelper::base64Decode( const QByteArray& input )
|
||||
}
|
||||
|
||||
|
||||
QSharedPointer< QIODevice >
|
||||
QtScriptResolverHelper::customIODeviceFactory( const Tomahawk::result_ptr& result )
|
||||
void
|
||||
QtScriptResolverHelper::customIODeviceFactory( const Tomahawk::result_ptr& result,
|
||||
boost::function< void( QSharedPointer< QIODevice >& ) > callback )
|
||||
{
|
||||
QString getUrl = QString( "Tomahawk.resolver.instance.%1( '%2' );" ).arg( m_urlCallback )
|
||||
.arg( QString( QUrl( result->url() ).toEncoded() ) );
|
||||
|
||||
QString urlStr = m_resolver->m_engine->mainFrame()->evaluateJavaScript( getUrl ).toString();
|
||||
|
||||
QSharedPointer< QIODevice > sp;
|
||||
if ( urlStr.isEmpty() )
|
||||
return QSharedPointer< QIODevice >();
|
||||
{
|
||||
|
||||
callback( sp );
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl url = QUrl::fromEncoded( urlStr.toUtf8() );
|
||||
QNetworkRequest req( url );
|
||||
tDebug() << "Creating a QNetowrkReply with url:" << req.url().toString();
|
||||
QNetworkReply* reply = TomahawkUtils::nam()->get( req );
|
||||
return QSharedPointer<QIODevice>( reply, &QObject::deleteLater );
|
||||
|
||||
//boost::functions cannot accept temporaries as parameters
|
||||
sp = QSharedPointer< QIODevice >( reply, &QObject::deleteLater );
|
||||
callback( sp );
|
||||
}
|
||||
|
||||
|
||||
|
@ -59,7 +59,8 @@ public:
|
||||
Q_INVOKABLE QByteArray base64Encode( const QByteArray& input );
|
||||
Q_INVOKABLE QByteArray base64Decode( const QByteArray& input );
|
||||
|
||||
QSharedPointer<QIODevice> customIODeviceFactory( const Tomahawk::result_ptr& result );
|
||||
void customIODeviceFactory( const Tomahawk::result_ptr& result,
|
||||
boost::function< void( QSharedPointer< QIODevice >& ) > callback ); // async
|
||||
|
||||
public slots:
|
||||
QByteArray readRaw( const QString& fileName );
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -30,6 +31,8 @@
|
||||
#include "Pipeline.h"
|
||||
#include "Source.h"
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include <QHash>
|
||||
|
||||
|
||||
@ -189,8 +192,16 @@ Api_v1::sid( QxtWebRequestEvent* event, QString unused )
|
||||
return send404( event );
|
||||
}
|
||||
|
||||
QSharedPointer<QIODevice> iodev = Servent::instance()->getIODeviceForUrl( rp );
|
||||
if ( iodev.isNull() )
|
||||
boost::function< void ( QSharedPointer< QIODevice >& ) > callback =
|
||||
boost::bind( &Api_v1::processSid, this, event, rp, _1 );
|
||||
Servent::instance()->getIODeviceForUrl( rp, callback );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Api_v1::processSid( QxtWebRequestEvent* event, Tomahawk::result_ptr& rp, QSharedPointer< QIODevice >& iodev )
|
||||
{
|
||||
if ( !iodev || iodev.isNull() )
|
||||
{
|
||||
return send404( event ); // 503?
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
*
|
||||
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* Tomahawk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -36,6 +37,12 @@
|
||||
#include <QSharedPointer>
|
||||
#include <QStringList>
|
||||
|
||||
namespace Tomahawk
|
||||
{
|
||||
class Result;
|
||||
typedef QSharedPointer< Result > result_ptr;
|
||||
}
|
||||
|
||||
class Api_v1 : public QxtWebSlotService
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -70,6 +77,7 @@ public slots:
|
||||
void index( QxtWebRequestEvent* event );
|
||||
|
||||
private:
|
||||
void processSid( QxtWebRequestEvent* event, Tomahawk::result_ptr&, QSharedPointer< QIODevice >& );
|
||||
QxtWebRequestEvent* m_storedEvent;
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user