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