From 88d51420e78b35e9619f439e1eb6279f121cc6d3 Mon Sep 17 00:00:00 2001 From: codestation Date: Sun, 5 Jan 2014 20:58:46 -0430 Subject: [PATCH] Implement online mode: now qcma can connect to the Internet to check and download update files. Update embedded xml file. Updated translations. --- qcma.pro | 6 +- resources/translations/qcma.es.ts | 28 ++++-- resources/translations/qcma.ja.ts | 26 +++-- resources/xml/psp2-updatelist.xml | 63 ++++++++++-- src/clientmanager.cpp | 2 + src/cmaclient.cpp | 1 + src/cmaclient.h | 1 + src/cmaevent.cpp | 52 ++++++++-- src/cmaevent.h | 8 +- src/forms/configwidget.cpp | 1 + src/forms/configwidget.ui | 5 +- src/httpdownloader.cpp | 161 ++++++++++++++++++++++++++++++ src/httpdownloader.h | 63 ++++++++++++ 13 files changed, 371 insertions(+), 46 deletions(-) create mode 100644 src/httpdownloader.cpp create mode 100644 src/httpdownloader.h diff --git a/qcma.pro b/qcma.pro index 10de49d..9f1fecf 100644 --- a/qcma.pro +++ b/qcma.pro @@ -35,7 +35,8 @@ SOURCES += src/main.cpp \ src/forms/configwidget.cpp \ src/forms/confirmdialog.cpp \ src/forms/pinform.cpp \ - src/forms/progressform.cpp + src/forms/progressform.cpp \ + src/httpdownloader.cpp HEADERS += \ src/capability.h \ @@ -59,7 +60,8 @@ HEADERS += \ src/forms/configwidget.h \ src/forms/confirmdialog.h \ src/forms/pinform.h \ - src/forms/progressform.h + src/forms/progressform.h \ + src/httpdownloader.h INCLUDEPATH += src/ diff --git a/resources/translations/qcma.es.ts b/resources/translations/qcma.es.ts index ec9edba..995bbdd 100644 --- a/resources/translations/qcma.es.ts +++ b/resources/translations/qcma.es.ts @@ -120,12 +120,12 @@ No se pudo inicializar VitaMTP - + Cannot refresh the database while is in use No se puede actualizar la base de datos mientras se encuentre en uso - + No PS Vita system has been registered Nigún sistema PS Vita ha sido registrado @@ -239,37 +239,37 @@ - + Offline Mode - + Modo desconectado - + Skip metadata extraction - + Update database automatically when files on the PC are changed - + Disable USB monitoring - + Disable Wi-Fi monitoring - + Database backend - + In Memory @@ -322,6 +322,14 @@ Filtro + + HTTPDownloader + + + Network error: %1 + Error de red: %1 + + MainWidget diff --git a/resources/translations/qcma.ja.ts b/resources/translations/qcma.ja.ts index 4d234a2..90490b2 100644 --- a/resources/translations/qcma.ja.ts +++ b/resources/translations/qcma.ja.ts @@ -120,12 +120,12 @@ - + Cannot refresh the database while is in use 使用中にはデータベースを更新できません - + No PS Vita system has been registered @@ -239,37 +239,37 @@ <html><head/><body><p align="center"><span style=" font-size:14pt; font-weight:600;">詳細設定</span></p></body></html> - + Offline Mode オフラインモード - + Skip metadata extraction メタデータの展開をスキップする - + Update database automatically when files on the PC are changed PCのファイルが変更された際にデータベースを自動的に更新する - + Disable USB monitoring USBの監視を無効にする - + Disable Wi-Fi monitoring Wi-Fiの監視を無効にする - + Database backend データベース保存 - + In Memory メモリ内 @@ -323,6 +323,14 @@ + + HTTPDownloader + + + Network error: %1 + + + MainWidget diff --git a/resources/xml/psp2-updatelist.xml b/resources/xml/psp2-updatelist.xml index 9508ab6..3c853d0 100644 --- a/resources/xml/psp2-updatelist.xml +++ b/resources/xml/psp2-updatelist.xml @@ -2,38 +2,83 @@ - + + + + http://www.example.com/PSP2UPDAT.PUP + + - + + + + http://www.example.com/PSP2UPDAT.PUP + + - + + + + http://www.example.com/PSP2UPDAT.PUP + + - + + + + http://www.example.com/PSP2UPDAT.PUP + + - + + + + http://www.example.com/PSP2UPDAT.PUP + + - + + + + http://www.example.com/PSP2UPDAT.PUP + + - + + + + http://www.example.com/PSP2UPDAT.PUP + + - + + + + http://www.example.com/PSP2UPDAT.PUP + + - + + + + http://www.example.com/PSP2UPDAT.PUP + + diff --git a/src/clientmanager.cpp b/src/clientmanager.cpp index b5a4085..d46976a 100644 --- a/src/clientmanager.cpp +++ b/src/clientmanager.cpp @@ -73,6 +73,7 @@ void ClientManager::start() client = new CmaClient(); usb_thread->setObjectName("usb_thread"); connect(usb_thread, SIGNAL(started()), client, SLOT(connectUsb())); + connect(client, SIGNAL(messageSent(QString)), this, SIGNAL(messageSent(QString))); connect(client, SIGNAL(finished()), usb_thread, SLOT(quit()), Qt::DirectConnection); connect(usb_thread, SIGNAL(finished()), usb_thread, SLOT(deleteLater())); connect(usb_thread, SIGNAL(finished()), this, SLOT(threadStopped())); @@ -90,6 +91,7 @@ void ClientManager::start() client = new CmaClient(); wireless_thread->setObjectName("wireless_thread"); connect(wireless_thread, SIGNAL(started()), client, SLOT(connectWireless())); + connect(client, SIGNAL(messageSent(QString)), this, SIGNAL(messageSent(QString))); connect(client, SIGNAL(receivedPin(QString,int)), this, SLOT(showPinDialog(QString,int))); connect(client, SIGNAL(finished()), wireless_thread, SLOT(quit()), Qt::DirectConnection); connect(wireless_thread, SIGNAL(finished()), wireless_thread, SLOT(deleteLater())); diff --git a/src/cmaclient.cpp b/src/cmaclient.cpp index 3fe5802..aa32a0c 100644 --- a/src/cmaclient.cpp +++ b/src/cmaclient.cpp @@ -193,6 +193,7 @@ void CmaClient::enterEventLoop(vita_device_t *device) connect(&thread, SIGNAL(started()), &eventLoop, SLOT(process())); connect(&eventLoop, SIGNAL(refreshDatabase()), this, SIGNAL(refreshDatabase()), Qt::DirectConnection); connect(&eventLoop, SIGNAL(finishedEventLoop()), &thread, SLOT(quit()), Qt::DirectConnection); + connect(&eventLoop, SIGNAL(messageSent(QString)), this, SIGNAL(messageSent(QString)), Qt::DirectConnection); thread.start(); while(isActive()) { diff --git a/src/cmaclient.h b/src/cmaclient.h index df90c9e..6d1c195 100644 --- a/src/cmaclient.h +++ b/src/cmaclient.h @@ -73,6 +73,7 @@ signals: void pinComplete(); void deviceDetected(); void deviceConnected(QString); + void messageSent(QString); void deviceDisconnected(); void refreshDatabase(); void finished(); diff --git a/src/cmaevent.cpp b/src/cmaevent.cpp index 57dc629..80e18dc 100644 --- a/src/cmaevent.cpp +++ b/src/cmaevent.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -486,20 +487,51 @@ void CmaEvent::vitaEventSendHttpObjectFromURL(vita_event_t *event, int eventId) QFile file(QDir(urlpath).absoluteFilePath(basename)); QByteArray data; + QSettings settings; + + bool offlineMode = settings.value("offlineMode", true).toBool(); if(!file.open(QIODevice::ReadOnly)) { - if(basename == "psp2-updatelist.xml") { + if(offlineMode && basename == "psp2-updatelist.xml") { qDebug("Found request for update list. Sending cached data"); QFile res(":/main/resources/xml/psp2-updatelist.xml"); res.open(QIODevice::ReadOnly); data = res.readAll(); - } else { - qWarning("Failed to download %s", url); - VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Failed_Download); + + } else if(!offlineMode) { + HTTPDownloader downloader(url); + QThread *http_thread = new QThread(); + http_thread->setObjectName("http_thread"); + connect(http_thread, SIGNAL(started()), &downloader, SLOT(downloadFile())); + connect(&downloader, SIGNAL(messageSent(QString)), SIGNAL(messageSent(QString)), Qt::DirectConnection); + downloader.moveToThread(http_thread); + http_thread->start(); + + int remote_size = (int)downloader.getFileSize(); + + if(remote_size != -1) { + // add the size of the file length to the total filesize + remote_size += 8; + qDebug("Sending %i bytes of data for HTTP request %s", remote_size, url); + + if(VitaMTP_SendData_Callback(device, eventId, PTP_OC_VITA_SendHttpObjectFromURL, remote_size, HTTPDownloader::readCallback) != PTP_RC_OK) { + qWarning("Failed to send HTTP object"); + } else { + VitaMTP_ReportResult(device, eventId, PTP_RC_OK); + } + + } else { + qWarning("No valid content-length in header, aborting"); + VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Failed_Download); + } + free(url); + http_thread->quit(); + http_thread->deleteLater(); return; } } else { + qDebug("Reading from local file"); data = file.readAll(); } @@ -913,18 +945,18 @@ void CmaEvent::vitaEventCheckExistance(vita_event_t *event, int eventId) VitaMTP_ReportResult(device, eventId, PTP_RC_OK); } -int CmaEvent::readCallback(unsigned char *data, unsigned long wantlen, unsigned long *gotlen) +int CmaEvent::readCallback(unsigned char *data, int64_t wantlen, int64_t *gotlen) { - QByteArray qdata = m_file->read(wantlen); - *gotlen = qdata.size(); - if(*gotlen) { - memcpy(data, qdata.constData(), qdata.size()); + *gotlen = m_file->read((char *)data, wantlen); + + if(*gotlen == -1) { + return -1; } return PTP_RC_OK; } -int CmaEvent::writeCallback(const unsigned char *data, unsigned long size, unsigned long *written) +int CmaEvent::writeCallback(const unsigned char *data, int64_t size, int64_t *written) { int ret = m_file->write((const char *)data, size); if(ret != -1) { diff --git a/src/cmaevent.h b/src/cmaevent.h index a64d7b3..6e21aea 100644 --- a/src/cmaevent.h +++ b/src/cmaevent.h @@ -22,7 +22,9 @@ #include "cmaobject.h" #include "database.h" +#include "httpdownloader.h" +#include #include #include @@ -61,8 +63,9 @@ private: void vitaEventSendObjectMetadataItems(vita_event_t *event, int eventId); void vitaEventSendNPAccountInfo(vita_event_t *event, int eventId); - static int readCallback(unsigned char *data, unsigned long wantlen, unsigned long *gotlen); - static int writeCallback(const unsigned char *data, unsigned long size, unsigned long *written); + static int readCallback(unsigned char *data, int64_t wantlen, int64_t *gotlen); + static int writeCallback(const unsigned char *data, int64_t size, int64_t *written); + static int readHTTPCallback(unsigned char *data, int64_t wantlen, int64_t *gotlen); void processEvent(); bool isActive(); @@ -83,6 +86,7 @@ private: signals: void finishedEventLoop(); void refreshDatabase(); + void messageSent(QString); public slots: void process(); diff --git a/src/forms/configwidget.cpp b/src/forms/configwidget.cpp index 2f0b804..aedd3db 100644 --- a/src/forms/configwidget.cpp +++ b/src/forms/configwidget.cpp @@ -145,6 +145,7 @@ void ConfigWidget::accept() savePath(settings, ui->videoPath, "videoPath"); savePath(settings, ui->appPath, "appsPath"); savePath(settings, ui->urlPath, "urlPath"); + settings.setValue("offlineMode", ui->offlineCheck->isChecked()); settings.sync(); done(Accepted); } diff --git a/src/forms/configwidget.ui b/src/forms/configwidget.ui index cb305ee..90acb7e 100644 --- a/src/forms/configwidget.ui +++ b/src/forms/configwidget.ui @@ -232,10 +232,7 @@ - - - false - + Offline Mode diff --git a/src/httpdownloader.cpp b/src/httpdownloader.cpp new file mode 100644 index 0000000..46a8843 --- /dev/null +++ b/src/httpdownloader.cpp @@ -0,0 +1,161 @@ +/* + * QCMA: Cross-platform content manager assistant for the PS Vita + * + * Copyright (C) 2014 Codestation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "httpdownloader.h" + +#include + +#include +#include + +QNetworkReply *HTTPDownloader::reply = NULL; +volatile qint64 HTTPDownloader::m_contentLength = -1; + +QMutex HTTPDownloader::dataAvailable; +QMutex HTTPDownloader::dataRead; + +char *HTTPDownloader::buffer = NULL; +qint64 HTTPDownloader::bufferSize = 0; +qint64 HTTPDownloader::downloadLeft = 0; + +HTTPDownloader::HTTPDownloader(const QString &url, QObject *parent) : + QObject(parent), remote_url(url), firstRead(true) +{ + lengthMutex.lock(); +} + +HTTPDownloader::~HTTPDownloader() +{ + lengthMutex.unlock(); + free(buffer); +} + +void HTTPDownloader::downloadFile() +{ + dataAvailable.lock(); + qDebug("Starting http_thread: 0x%016" PRIxPTR, (uintptr_t)QThread::currentThreadId()); + request = new QNetworkAccessManager(this); + reply = request->get(QNetworkRequest(QUrl(remote_url))); + connect(reply, SIGNAL(metaDataChanged()), this, SLOT(metadataChanged())); + connect(reply, SIGNAL(readyRead()), this, SLOT(readyRead())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(error(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater())); + connect(reply, SIGNAL(finished()), request, SLOT(deleteLater())); +} + +void HTTPDownloader::metadataChanged() +{ + QVariant len = reply->header(QNetworkRequest::ContentLengthHeader); + if(len.isValid()) { + m_contentLength = len.toInt(); + downloadLeft = m_contentLength + 8; + } else { + m_contentLength = -1; + } + lengthMutex.unlock(); +} + +qint64 HTTPDownloader::getFileSize() +{ + lengthMutex.lock(); + return m_contentLength; +} + +void HTTPDownloader::readyRead() +{ + dataRead.lock(); + + int currOffset = bufferSize; + if(bufferSize == 0) { + bufferSize = reply->bytesAvailable(); + if(firstRead) { + bufferSize += 8; + currOffset += 8; + } + // start with a 16KiB buffer + buffer = (char *)malloc(16384 + 2048); + if(firstRead) { + *(uint64_t *)buffer = m_contentLength; + firstRead = false; + } + + } else { + bufferSize += reply->bytesAvailable(); + if(bufferSize > 16384 + 2048) { + buffer = (char *)realloc(buffer, bufferSize); + } + } + reply->read(buffer + currOffset, reply->bytesAvailable()); + + downloadLeft -= bufferSize; + + if(bufferSize > 16384 || downloadLeft == 0) { + dataAvailable.unlock(); + } + dataRead.unlock(); +} + +int HTTPDownloader::readCallback(unsigned char *data, int64_t wantlen, int64_t *gotlen) +{ + if(!dataAvailable.tryLock(30000)) { + qWarning("Connection timeout while receiving data from network, aborting"); + return -1; + } + + dataRead.lock(); + + if(bufferSize == 0) { + dataRead.unlock(); + return -1; + } + + if(bufferSize < wantlen) { + wantlen = bufferSize; + } + + memcpy(data, buffer, wantlen); + bufferSize -= wantlen; + *gotlen = wantlen; + + if(bufferSize > 0) { + memmove(buffer, buffer + wantlen, bufferSize); + if(bufferSize >= 16384 || downloadLeft == 0) { + dataAvailable.unlock(); + } + } + + *gotlen = wantlen; + dataRead.unlock(); + + return PTP_RC_OK; +} + +void HTTPDownloader::error(QNetworkReply::NetworkError errorCode) +{ + Q_UNUSED(errorCode); + + emit messageSent(tr("Network error: %1").arg(reply->errorString())); + + // set buffer to zero so a read callback can be aborted + dataRead.lock(); + bufferSize = 0; + dataRead.unlock(); + lengthMutex.unlock(); +} diff --git a/src/httpdownloader.h b/src/httpdownloader.h new file mode 100644 index 0000000..7146309 --- /dev/null +++ b/src/httpdownloader.h @@ -0,0 +1,63 @@ +/* + * QCMA: Cross-platform content manager assistant for the PS Vita + * + * Copyright (C) 2014 Codestation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef HTTPDOWNLOADER_H +#define HTTPDOWNLOADER_H + +#include +#include +#include +#include + +class HTTPDownloader : public QObject +{ + Q_OBJECT +public: + explicit HTTPDownloader(const QString &url, QObject *parent = 0); + ~HTTPDownloader(); + qint64 getFileSize(); + +signals: + void messageSent(QString); + +public slots: + void downloadFile(); + static int readCallback(unsigned char *data, int64_t wantlen, int64_t *gotlen); + void metadataChanged(); + void readyRead(); + void error(QNetworkReply::NetworkError); + +private: + QString remote_url; + QNetworkAccessManager *request; + QMutex lengthMutex; + bool firstRead; + + static QMutex dataAvailable; + static QMutex dataRead; + + static QNetworkReply *reply; + volatile static qint64 m_contentLength; + + static char *buffer; + static qint64 bufferSize; + static qint64 downloadLeft; +}; + +#endif // HTTPDOWNLOADER_H