Reimplemented CmaClient (again) to support concurrent connections.
This commit is contained in:
		
							
								
								
									
										166
									
								
								cmaclient.cpp
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								cmaclient.cpp
									
									
									
									
									
								
							@@ -30,16 +30,97 @@
 | 
			
		||||
#include <QSettings>
 | 
			
		||||
#include <QUrl>
 | 
			
		||||
 | 
			
		||||
#include <MediaInfo/MediaInfo.h>
 | 
			
		||||
#include <MediaInfo/File__Analyse_Automatic.h>
 | 
			
		||||
QMutex CmaClient::mutex;
 | 
			
		||||
QMutex CmaClient::runner;
 | 
			
		||||
Database CmaClient::db;
 | 
			
		||||
bool CmaClient::is_running = true;
 | 
			
		||||
 | 
			
		||||
metadata_t CmaClient::g_thumbmeta = {0, 0, 0, NULL, NULL, 0, 0, 0, Thumbnail, {{18, 144, 80, 0, 1, 1.0f, 2}}, NULL};
 | 
			
		||||
CmaClient *CmaClient::this_object = NULL;
 | 
			
		||||
 | 
			
		||||
CmaClient::CmaClient(QObject *parent) :
 | 
			
		||||
    BaseWorker(parent)
 | 
			
		||||
    QObject(parent)
 | 
			
		||||
{
 | 
			
		||||
    this_object = this;
 | 
			
		||||
    device = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CmaClient::isRunning()
 | 
			
		||||
{
 | 
			
		||||
    QMutexLocker locker(&runner);
 | 
			
		||||
    return is_running;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CmaClient::setRunning(bool state)
 | 
			
		||||
{
 | 
			
		||||
    QMutexLocker locker(&runner);
 | 
			
		||||
    is_running = state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CmaClient::connectUsb()
 | 
			
		||||
{
 | 
			
		||||
    vita_device_t *vita;
 | 
			
		||||
    int num_tries = 0;
 | 
			
		||||
 | 
			
		||||
    qDebug() << "Starting usb_thread:" << QThread::currentThreadId();
 | 
			
		||||
 | 
			
		||||
    while(isRunning()) {
 | 
			
		||||
        if((vita = VitaMTP_Get_First_USB_Vita()) !=NULL) {
 | 
			
		||||
            processNewConnection(vita);
 | 
			
		||||
        } else {
 | 
			
		||||
            qDebug("No Vita detected via USB, attempt %i", ++num_tries);
 | 
			
		||||
            if(mutex.tryLock()) {
 | 
			
		||||
                mutex.unlock();
 | 
			
		||||
                Sleeper::msleep(2000);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qDebug("Finishing usb_thread");
 | 
			
		||||
    emit finished();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CmaClient::connectWireless()
 | 
			
		||||
{
 | 
			
		||||
    vita_device_t *vita;
 | 
			
		||||
    wireless_host_info_t host;
 | 
			
		||||
    host.port = QCMA_REQUEST_PORT;
 | 
			
		||||
    typedef CmaClient CC;
 | 
			
		||||
 | 
			
		||||
    qDebug() << "Starting wireless_thread:" << QThread::currentThreadId();
 | 
			
		||||
 | 
			
		||||
    while(isRunning()) {
 | 
			
		||||
        if((vita = VitaMTP_Get_First_Wireless_Vita(&host, 0, 2, CC::deviceRegistered, CC::generatePin)) != NULL) {
 | 
			
		||||
            processNewConnection(vita);
 | 
			
		||||
        } else {
 | 
			
		||||
            qDebug("Wireless listener was cancelled");
 | 
			
		||||
            mutex.lock();
 | 
			
		||||
            // wait until the event loop of the usb thread is finished
 | 
			
		||||
            mutex.unlock();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    qDebug("Finishing wireless_thread");
 | 
			
		||||
    emit finished();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CmaClient::processNewConnection(vita_device_t *device)
 | 
			
		||||
{
 | 
			
		||||
    QMutexLocker locker(&mutex);
 | 
			
		||||
 | 
			
		||||
    broadcast.setUnavailable();
 | 
			
		||||
    this->device = device;
 | 
			
		||||
    qDebug("Vita connected: id %s", VitaMTP_Get_Identification(device));
 | 
			
		||||
    DeviceCapability *vita_info = new DeviceCapability();
 | 
			
		||||
 | 
			
		||||
    if(!vita_info->exchangeInfo(device)) {
 | 
			
		||||
        qCritical("Error while exchanging info with the vita");
 | 
			
		||||
    } else {
 | 
			
		||||
        // Conection successful, inform the user
 | 
			
		||||
        emit deviceConnected(QString(tr("Connected to ")) + vita_info->getOnlineId());
 | 
			
		||||
        enterEventLoop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    broadcast.setAvailable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int CmaClient::deviceRegistered(const char *deviceid)
 | 
			
		||||
@@ -58,72 +139,16 @@ int CmaClient::generatePin(wireless_vita_info_t *info, int *p_err)
 | 
			
		||||
    return pin;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
vita_device_t *CmaClient::getDeviceConnection()
 | 
			
		||||
{
 | 
			
		||||
    int num_tries = 0;
 | 
			
		||||
    vita_device_t *vita;
 | 
			
		||||
    wireless_host_info_t host;
 | 
			
		||||
    host.port = QCMA_REQUEST_PORT;
 | 
			
		||||
 | 
			
		||||
    while(active) {
 | 
			
		||||
        vita = VitaMTP_Get_First_USB_Vita();
 | 
			
		||||
        if(vita || !active) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        qDebug("No Vita detected via USB, attempt %i", ++num_tries);
 | 
			
		||||
        vita = VitaMTP_Get_First_Wireless_Vita(&host, 0, 3, CmaClient::deviceRegistered, CmaClient::generatePin);
 | 
			
		||||
        if(vita || !active) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        qDebug("No Vita detected via wireless, attempt %i", ++num_tries);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return vita;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CmaClient::process()
 | 
			
		||||
{
 | 
			
		||||
    qDebug() << "Starting CmaClient:" << QThread::currentThreadId();
 | 
			
		||||
 | 
			
		||||
    connect(&broadcast, SIGNAL(receivedPin(int)), this, SIGNAL(receivedPin(int)));
 | 
			
		||||
 | 
			
		||||
    active = true;
 | 
			
		||||
 | 
			
		||||
    while(active) {
 | 
			
		||||
 | 
			
		||||
        if((device = getDeviceConnection()) == NULL) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        broadcast.setUnavailable();
 | 
			
		||||
        qDebug("Vita connected: id %s", VitaMTP_Get_Identification(device));
 | 
			
		||||
        DeviceCapability *vita_info = new DeviceCapability();
 | 
			
		||||
 | 
			
		||||
        if(!vita_info->exchangeInfo(device)) {
 | 
			
		||||
            qCritical("Error while exchanging info with the vita");
 | 
			
		||||
            close();
 | 
			
		||||
            broadcast.setAvailable();
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Conection successful, inform the user
 | 
			
		||||
        emit deviceConnected(QString(tr("Connected to ")) + vita_info->getOnlineId());
 | 
			
		||||
        connected = true;
 | 
			
		||||
        enterEventLoop();
 | 
			
		||||
        broadcast.setAvailable();
 | 
			
		||||
    }
 | 
			
		||||
    emit finished();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CmaClient::enterEventLoop()
 | 
			
		||||
{
 | 
			
		||||
    vita_event_t event;
 | 
			
		||||
 | 
			
		||||
    qDebug("Starting event loop");
 | 
			
		||||
 | 
			
		||||
    while(connected) {
 | 
			
		||||
    while(isRunning()) {
 | 
			
		||||
        if(VitaMTP_Read_Event(device, &event) < 0) {
 | 
			
		||||
            qWarning("Error reading event from Vita.");
 | 
			
		||||
            connected = false;
 | 
			
		||||
            setRunning(false);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -372,7 +397,7 @@ void CmaClient::vitaEventRequestTerminate(vita_event_t *event, int eventId)
 | 
			
		||||
{
 | 
			
		||||
    qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
 | 
			
		||||
    //qWarning("Event 0x%x unimplemented!", event->Code);
 | 
			
		||||
    connected = false;
 | 
			
		||||
    setRunning(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CmaClient::vitaEventUnimplementated(vita_event_t *event, int eventId)
 | 
			
		||||
@@ -505,7 +530,7 @@ void CmaClient::vitaEventCancelTask(vita_event_t *event, int eventId)
 | 
			
		||||
    int eventIdToCancel = event->Param2;
 | 
			
		||||
    VitaMTP_CancelTask(device, eventIdToCancel);
 | 
			
		||||
    qWarning("Event CancelTask (0x%x) unimplemented!", event->Code);
 | 
			
		||||
    connected = false;
 | 
			
		||||
    setRunning(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CmaClient::vitaEventSendHttpObjectFromURL(vita_event_t *event, int eventId)
 | 
			
		||||
@@ -951,20 +976,19 @@ void CmaClient::vitaEventCheckExistance(vita_event_t *event, int eventId)
 | 
			
		||||
 | 
			
		||||
void CmaClient::close()
 | 
			
		||||
{
 | 
			
		||||
    VitaMTP_SendHostStatus(device, VITA_HOST_STATUS_EndConnection);
 | 
			
		||||
    VitaMTP_Release_Device(device);
 | 
			
		||||
    if(device) {
 | 
			
		||||
        VitaMTP_SendHostStatus(device, VITA_HOST_STATUS_EndConnection);
 | 
			
		||||
        VitaMTP_Release_Device(device);
 | 
			
		||||
        device = NULL;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CmaClient::stop()
 | 
			
		||||
{
 | 
			
		||||
    active = false;
 | 
			
		||||
    connected = false;
 | 
			
		||||
    waitCondition.wakeAll();
 | 
			
		||||
    CmaClient::setRunning(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CmaClient::~CmaClient()
 | 
			
		||||
{
 | 
			
		||||
    if(device) {
 | 
			
		||||
        close();
 | 
			
		||||
    }
 | 
			
		||||
    close();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								cmaclient.h
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								cmaclient.h
									
									
									
									
									
								
							@@ -33,18 +33,23 @@ extern "C" {
 | 
			
		||||
#include <vitamtp.h>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CmaClient : public BaseWorker
 | 
			
		||||
class CmaClient : public QObject
 | 
			
		||||
{
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
public:
 | 
			
		||||
    explicit CmaClient(QObject *parent = 0);
 | 
			
		||||
    ~CmaClient();
 | 
			
		||||
 | 
			
		||||
    Database db;
 | 
			
		||||
    void launch();
 | 
			
		||||
 | 
			
		||||
    static Database db;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    static bool isRunning();
 | 
			
		||||
    static void setRunning(bool state);
 | 
			
		||||
    void enterEventLoop();
 | 
			
		||||
    vita_device_t *getDeviceConnection();
 | 
			
		||||
 | 
			
		||||
    void processNewConnection(vita_device_t *device);
 | 
			
		||||
 | 
			
		||||
    uint16_t processAllObjects(CMAObject *parent, uint32_t handle);
 | 
			
		||||
    void vitaEventSendObject(vita_event_t *event, int eventId);
 | 
			
		||||
@@ -72,13 +77,13 @@ private:
 | 
			
		||||
    static int deviceRegistered(const char *deviceid);
 | 
			
		||||
    static int generatePin(wireless_vita_info_t *info, int *p_err);
 | 
			
		||||
 | 
			
		||||
    QWaitCondition waitCondition;    
 | 
			
		||||
    CmaBroadcast broadcast;
 | 
			
		||||
    vita_device_t *device;
 | 
			
		||||
    volatile bool active;
 | 
			
		||||
    volatile bool connected;
 | 
			
		||||
    static bool is_running;
 | 
			
		||||
    static metadata_t g_thumbmeta;
 | 
			
		||||
    static CmaClient *this_object;
 | 
			
		||||
    static QMutex mutex;
 | 
			
		||||
    static QMutex runner;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
    void receivedPin(int);
 | 
			
		||||
@@ -86,13 +91,15 @@ signals:
 | 
			
		||||
    void deviceConnected(QString);
 | 
			
		||||
    void deviceDisconnected();
 | 
			
		||||
    void refreshDatabase();
 | 
			
		||||
    void finished();
 | 
			
		||||
 | 
			
		||||
public slots:
 | 
			
		||||
    void close();
 | 
			
		||||
    void stop();
 | 
			
		||||
    static void stop();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
    void process();
 | 
			
		||||
    void connectUsb();
 | 
			
		||||
    void connectWireless();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // CMACLIENT_H
 | 
			
		||||
 
 | 
			
		||||
@@ -65,22 +65,46 @@ void MainWidget::dialogResult(int result)
 | 
			
		||||
 | 
			
		||||
void MainWidget::startServer()
 | 
			
		||||
{
 | 
			
		||||
    qDebug("Starting cma event loop");
 | 
			
		||||
    clientLoop.start("CmaClient");
 | 
			
		||||
    qDebug("Starting cma threads");
 | 
			
		||||
    QThread *thread;
 | 
			
		||||
    CmaClient *client;
 | 
			
		||||
 | 
			
		||||
    thread = new QThread();
 | 
			
		||||
    client = new CmaClient();
 | 
			
		||||
    thread->setObjectName("usb_thread");
 | 
			
		||||
    connect(thread, SIGNAL(started()), client, SLOT(connectUsb()));
 | 
			
		||||
    connect(client, SIGNAL(receivedPin(int)), this, SLOT(showPin(int)));
 | 
			
		||||
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
 | 
			
		||||
    connect(thread, SIGNAL(finished()), client, SLOT(deleteLater()));
 | 
			
		||||
    connectClientSignals(client);
 | 
			
		||||
 | 
			
		||||
    client->moveToThread(thread);
 | 
			
		||||
    thread->start();
 | 
			
		||||
 | 
			
		||||
    thread = new QThread();
 | 
			
		||||
    client = new CmaClient();
 | 
			
		||||
    thread->setObjectName("wireless_thread");
 | 
			
		||||
    connect(thread, SIGNAL(started()), client, SLOT(connectWireless()));
 | 
			
		||||
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
 | 
			
		||||
    connect(thread, SIGNAL(finished()), client, SLOT(deleteLater()));
 | 
			
		||||
    connectClientSignals(client);
 | 
			
		||||
    client->moveToThread(thread);
 | 
			
		||||
    thread->start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainWidget::stopServer()
 | 
			
		||||
{
 | 
			
		||||
    clientLoop.stop();
 | 
			
		||||
    CmaClient::stop();
 | 
			
		||||
    qApp->quit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainWidget::refreshDatabase()
 | 
			
		||||
{
 | 
			
		||||
    clientLoop.db.mutex.lock();
 | 
			
		||||
    clientLoop.db.destroy();
 | 
			
		||||
    int count = clientLoop.db.create();
 | 
			
		||||
    QMutexLocker locker(&CmaClient::db.mutex);
 | 
			
		||||
 | 
			
		||||
    CmaClient::db.destroy();
 | 
			
		||||
    int count = CmaClient::db.create();
 | 
			
		||||
    qDebug("Added %i entries to the database", count);
 | 
			
		||||
    clientLoop.db.mutex.unlock();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainWidget::deviceDisconnect()
 | 
			
		||||
@@ -89,15 +113,12 @@ void MainWidget::deviceDisconnect()
 | 
			
		||||
    receiveMessage(tr("The device has been disconnected"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainWidget::connectSignals()
 | 
			
		||||
void MainWidget::connectClientSignals(CmaClient *client)
 | 
			
		||||
{
 | 
			
		||||
    connect(&dialog, SIGNAL(finished(int)), this, SLOT(dialogResult(int)));
 | 
			
		||||
    connect(&clientLoop, SIGNAL(receivedPin(int)), this, SLOT(showPin(int)));
 | 
			
		||||
    connect(&clientLoop, SIGNAL(deviceConnected(QString)), this, SLOT(receiveMessage(QString)));
 | 
			
		||||
    connect(&clientLoop, SIGNAL(deviceConnected(QString)), this, SLOT(setTrayTooltip(QString)));
 | 
			
		||||
    connect(&clientLoop, SIGNAL(deviceDisconnected()), this, SLOT(deviceDisconnect()));
 | 
			
		||||
    connect(&clientLoop, SIGNAL(refreshDatabase()), this, SLOT(refreshDatabase()));
 | 
			
		||||
    connect(&clientLoop, SIGNAL(finished()), qApp, SLOT(quit()));
 | 
			
		||||
    connect(client, SIGNAL(deviceConnected(QString)), this, SLOT(receiveMessage(QString)));
 | 
			
		||||
    connect(client, SIGNAL(deviceConnected(QString)), this, SLOT(setTrayTooltip(QString)));
 | 
			
		||||
    connect(client, SIGNAL(deviceDisconnected()), this, SLOT(deviceDisconnect()));
 | 
			
		||||
    connect(client, SIGNAL(refreshDatabase()), this, SLOT(refreshDatabase()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainWidget::showPin(int pin)
 | 
			
		||||
@@ -107,7 +128,7 @@ void MainWidget::showPin(int pin)
 | 
			
		||||
 | 
			
		||||
void MainWidget::prepareApplication()
 | 
			
		||||
{
 | 
			
		||||
    connectSignals();
 | 
			
		||||
    connect(&dialog, SIGNAL(finished(int)), this, SLOT(dialogResult(int)));
 | 
			
		||||
    createTrayIcon();
 | 
			
		||||
    checkSettings();
 | 
			
		||||
    refreshDatabase();
 | 
			
		||||
@@ -118,31 +139,8 @@ void MainWidget::setTrayTooltip(QString message)
 | 
			
		||||
    trayIcon->setToolTip(message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainWidget::toggleWireless()
 | 
			
		||||
{
 | 
			
		||||
    QSettings settings;
 | 
			
		||||
    if(wireless->isChecked()) {
 | 
			
		||||
        wireless->setText(tr("&Wireless enabled"));
 | 
			
		||||
        settings.setValue("wireless", true);
 | 
			
		||||
    } else {
 | 
			
		||||
        wireless->setText(tr("&Wireless disabled"));
 | 
			
		||||
        settings.setValue("wireless", false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainWidget::createTrayIcon()
 | 
			
		||||
{
 | 
			
		||||
    QSettings settings;
 | 
			
		||||
    wireless = new QAction(this);
 | 
			
		||||
    wireless->setCheckable(true);
 | 
			
		||||
 | 
			
		||||
    if(settings.value("wireless", false).toBool()) {
 | 
			
		||||
        wireless->setText(tr("&Wireless enabled"));
 | 
			
		||||
        wireless->setChecked(true);
 | 
			
		||||
    } else {
 | 
			
		||||
        wireless->setText(tr("&Wireless disabled"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    options = new QAction(tr("&Settings"), this);
 | 
			
		||||
    reload = new QAction(tr("&Refresh database"), this);
 | 
			
		||||
    quit = new QAction(tr("&Quit"), this);
 | 
			
		||||
@@ -150,12 +148,10 @@ void MainWidget::createTrayIcon()
 | 
			
		||||
    connect(options, SIGNAL(triggered()), &dialog, SLOT(open()));
 | 
			
		||||
    //connect(reload, SIGNAL(triggered()), &CmaWorker, SLOT(allowRefresh()), Qt::DirectConnection);
 | 
			
		||||
    connect(quit, SIGNAL(triggered()), this, SLOT(stopServer()));
 | 
			
		||||
    connect(wireless, SIGNAL(triggered()), this, SLOT(toggleWireless()));
 | 
			
		||||
 | 
			
		||||
    QMenu *trayIconMenu = new QMenu(this);
 | 
			
		||||
    trayIconMenu->addAction(options);
 | 
			
		||||
    trayIconMenu->addAction(reload);
 | 
			
		||||
    trayIconMenu->addAction(wireless);
 | 
			
		||||
    trayIconMenu->addSeparator();
 | 
			
		||||
    trayIconMenu->addAction(quit);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ public:
 | 
			
		||||
private:
 | 
			
		||||
    void createTrayIcon();
 | 
			
		||||
    void checkSettings();
 | 
			
		||||
    void connectSignals();
 | 
			
		||||
    void connectClientSignals(CmaClient *client);
 | 
			
		||||
 | 
			
		||||
    bool first_run;
 | 
			
		||||
    ConfigWidget dialog;
 | 
			
		||||
@@ -54,7 +54,6 @@ private:
 | 
			
		||||
    QAction *quit;
 | 
			
		||||
    QAction *reload;
 | 
			
		||||
    QAction *options;
 | 
			
		||||
    QAction *wireless;
 | 
			
		||||
    CmaClient clientLoop;
 | 
			
		||||
    const static QStringList path_list;
 | 
			
		||||
 | 
			
		||||
@@ -63,7 +62,6 @@ private slots:
 | 
			
		||||
    void dialogResult(int result);
 | 
			
		||||
    void receiveMessage(QString message);
 | 
			
		||||
    void setTrayTooltip(QString message);
 | 
			
		||||
    void toggleWireless();
 | 
			
		||||
    void showPin(int pin);
 | 
			
		||||
    void refreshDatabase();
 | 
			
		||||
    void startServer();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user