Reimplemented broadcaster so it can send 503 when a connection is in

place.
Removed qcma/cmaserver class and reimplemented them in cmaclient.
Make sure that the scanned files are sorted by filename.
Implement filters so the database doesn't pick up unsupported formats.
Implement album cover display for album listing in the vita.
Fixed bug with streaming of pictures in wireless mode.
Some code refactoring.
This commit is contained in:
codestation
2013-08-19 09:46:03 -04:30
parent 0d8c0a2b24
commit d93de053e9
18 changed files with 581 additions and 269 deletions

View File

@@ -52,7 +52,10 @@ bool DeviceCapability::exchangeInfo(vita_device_t *device)
return false;
}
VitaMTP_Data_Free_Capability(vita_capabilities); // TODO: Use this data
// TODO: vitamtp needs to send the full metadata info to know the expected format
// of thumbnails, for example. Until then lets discard the received info.
VitaMTP_Data_Free_Capability(vita_capabilities);
// Send the host's capabilities
capability_info_t *pc_capabilities = generate_pc_capability_info();

101
cmabroadcast.cpp Normal file
View File

@@ -0,0 +1,101 @@
/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
*/
#include "cmabroadcast.h"
#include <QDebug>
#include <QHostInfo>
#include <QMutexLocker>
#include <QSettings>
#include <QUuid>
#include <vitamtp.h>
const QString CmaBroadcast::broadcast_reply =
"%1\r\n"
"host-id:%2\r\n"
"host-type:%3\r\n"
"host-name:%4\r\n"
"host-mtp-protocol-version:%5\r\n"
"host-request-port:%6\r\n"
"host-wireless-protocol-version:%7\r\n";
const char *CmaBroadcast::broadcast_query = "SRCH * HTTP/1.1\r\n";
const char *CmaBroadcast::broadcast_ok = "HTTP/1.1 200 OK";
const char *CmaBroadcast::broadcast_unavailable = "HTTP/1.1 503 NG";
CmaBroadcast::CmaBroadcast(QObject *parent) :
QObject(parent)
{
QSettings settings;
// generate a GUID if doesn't exist yet in settings
uuid = settings.value("guid").toString();
if(uuid.isEmpty()) {
uuid = QUuid::createUuid().toString();
settings.setValue("guid", uuid);
}
hostname = QHostInfo::localHostName();
setAvailable();
socket = new QUdpSocket(this);
socket->bind(QHostAddress::Any, QCMA_REQUEST_PORT);
connect(socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
}
void CmaBroadcast::readPendingDatagrams()
{
if(socket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(socket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
if(datagram.contains(broadcast_query)) {
QMutexLocker locker(&mutex);
socket->writeDatagram(reply.toUtf8(), sender, senderPort);
} else {
qWarning("Unknown request: %.*s\n", datagram.length(), datagram.constData());
}
}
}
void CmaBroadcast::setAvailable()
{
QMutexLocker locker(&mutex);
reply = broadcast_reply
.arg(broadcast_ok, uuid, "win", hostname)
.arg(VITAMTP_PROTOCOL_MAX_VERSION, 8, 10, QChar('0'))
.arg(QCMA_REQUEST_PORT)
.arg(VITAMTP_WIRELESS_MAX_VERSION, 8, 10, QChar('0'));
}
void CmaBroadcast::setUnavailable()
{
QMutexLocker locker(&mutex);
reply = broadcast_reply
.arg(broadcast_unavailable, uuid, "win", hostname)
.arg(VITAMTP_PROTOCOL_MAX_VERSION, 8, 10, QChar('0'))
.arg(QCMA_REQUEST_PORT)
.arg(VITAMTP_WIRELESS_MAX_VERSION, 8, 10, QChar('0'));
}

56
cmabroadcast.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
*/
#ifndef CMABROADCAST_H
#define CMABROADCAST_H
#include <QMutex>
#include <QObject>
#include <QUdpSocket>
#define QCMA_REQUEST_PORT 9309
class CmaBroadcast : public QObject
{
Q_OBJECT
public:
explicit CmaBroadcast(QObject *parent = 0);
private:
void replyBroadcast(const QByteArray &datagram);
QMutex mutex;
QString uuid;
QString reply;
QString hostname;
QUdpSocket *socket;
static const QString broadcast_reply;
static const char *broadcast_query;
static const char *broadcast_ok;
static const char *broadcast_unavailable;
public slots:
void setAvailable();
void setUnavailable();
private slots:
void readPendingDatagrams();
};
#endif // CMABROADCAST_H

View File

@@ -1,7 +1,28 @@
/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
*/
#include "cmaclient.h"
#include "capability.h"
#include "utils.h"
#include "wirelessworker.h"
#include "QApplication"
#include <QBuffer>
#include <QDateTime>
#include <QDebug>
@@ -10,55 +31,76 @@
#include <QSettings>
#include <QUrl>
const metadata_t CmaClient::g_thumbmeta = {0, 0, 0, NULL, NULL, 0, 0, 0, Thumbnail, {{18, 144, 80, 0, 1, 1.0f, 2}}, NULL};
#include <MediaInfo/MediaInfo.h>
#include <MediaInfo/File__Analyse_Automatic.h>
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(QObject *parent) :
BaseWorker(parent)
{
}
CmaClient::CmaClient(Database *database, vita_device_t *device, QObject *parent) :
BaseWorker(parent), db(database), device(device)
vita_device_t *CmaClient::getDeviceConnection()
{
}
int num_tries = 0;
vita_device_t *vita;
typedef BroadcastSignal BS;
CmaClient::~CmaClient()
{
if(device) {
close();
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(&BS::info, 0, 2, BS::deviceRegistered, BS::generatePin);
if(vita || !active) {
break;
}
qDebug("No Vita detected via wireless, attempt %i", ++num_tries);
}
}
void CmaClient::close()
{
VitaMTP_SendHostStatus(device, VITA_HOST_STATUS_EndConnection);
VitaMTP_Release_Device(device);
device = NULL;
return vita;
}
void CmaClient::process()
{
qDebug("Vita connected: id %s", VitaMTP_Get_Identification(device));
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() << "From listener:"<< QThread::currentThreadId();
qDebug("Starting event loop");
while(connected) {
if(VitaMTP_Read_Event(device, &event) < 0) {
@@ -134,9 +176,10 @@ void CmaClient::enterEventLoop()
default:
vitaEventUnimplementated(&event, event.Param1);
}
qDebug("Ended event, code: 0x%x, id: %d", event.Code, event.Param1);
}
qDebug() << "Finished event thread for:" << QThread::currentThreadId();
emit finished();
qDebug("Finished event thread");
emit deviceDisconnected();
}
quint16 CmaClient::processAllObjects(CMAObject *parent, quint32 handle)
@@ -154,12 +197,12 @@ quint16 CmaClient::processAllObjects(CMAObject *parent, quint32 handle)
return PTP_RC_VITA_Invalid_Data;
}
CMAObject *object = db->pathToObject(remote_meta.name, parent->metadata.ohfi);
CMAObject *object = db.pathToObject(remote_meta.name, parent->metadata.ohfi);
if(object) {
qDebug("Deleting %s", object->path.toStdString().c_str());
removeRecursively(object->path);
db->remove(object);
db.remove(object);
}
QDir dir(parent->path);
@@ -188,7 +231,7 @@ quint16 CmaClient::processAllObjects(CMAObject *parent, quint32 handle)
object = new CMAObject(parent);
object->initObject(info);
object->metadata.handle = remote_meta.handle;
db->append(parent->metadata.ohfi, object);
db.append(parent->metadata.ohfi, object);
free(remote_meta.name);
qDebug("Added object %s with OHFI %i to database", object->metadata.path, object->metadata.ohfi);
@@ -199,7 +242,7 @@ quint16 CmaClient::processAllObjects(CMAObject *parent, quint32 handle)
if(ret != PTP_RC_OK) {
qDebug("Deleteting object with OHFI %d", object->metadata.ohfi);
db->remove(object);
db.remove(object);
free(data.fileData);
return ret;
}
@@ -221,9 +264,9 @@ void CmaClient::vitaEventGetTreatObject(vita_event_t *event, int eventId)
return;
}
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
CMAObject *parent = db->ohfiToObject(treatObject.ohfiParent);
CMAObject *parent = db.ohfiToObject(treatObject.ohfiParent);
if(parent == NULL) {
qWarning("Cannot find parent OHFI %d", treatObject.ohfiParent);
@@ -244,14 +287,14 @@ void CmaClient::vitaEventSendCopyConfirmationInfo(vita_event_t *event, int event
return;
}
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
quint64 size = 0;
for(quint32 i = 0; i < info->count; i++) {
CMAObject *object;
if((object = db->ohfiToObject(info->ohfi[i])) == NULL) {
if((object = db.ohfiToObject(info->ohfi[i])) == NULL) {
qWarning("Cannot find OHFI %d", info->ohfi[i]);
free(info);
return;
@@ -279,9 +322,9 @@ void CmaClient::vitaEventSendObjectMetadataItems(vita_event_t *event, int eventI
return;
}
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
CMAObject *object = db->ohfiToObject(ohfi);
CMAObject *object = db.ohfiToObject(ohfi);
if(object == NULL) {
qWarning("Cannot find OHFI %d in database", ohfi);
@@ -324,10 +367,10 @@ void CmaClient::vitaEventSendNumOfObject(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
uint ohfi = event->Param2;
int items = db->filterObjects(ohfi, NULL);
int items = db.filterObjects(ohfi, NULL);
if(VitaMTP_SendNumOfObject(device, eventId, items) != PTP_RC_OK) {
qWarning("Error occured receiving object count for OHFI parent %d", ohfi);
@@ -347,10 +390,10 @@ void CmaClient::vitaEventSendObjectMetadata(vita_event_t *event, int eventId)
qWarning("GetBrowseInfo failed");
return;
}
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
metadata_t *meta;
int count = db->filterObjects(browse.ohfiParent, &meta); // if meta is null, will return empty XML
int count = db.filterObjects(browse.ohfiParent, &meta); // if meta is null, will return empty XML
qDebug("Sending %i metadata filtered objects for OHFI %d", count, browse.ohfiParent);
if(VitaMTP_SendObjectMetadata(device, eventId, meta) != PTP_RC_OK) { // send all objects with OHFI parent
@@ -366,10 +409,12 @@ void CmaClient::vitaEventSendObject(vita_event_t *event, int eventId)
int ohfi = event->Param2;
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
qDebug("Searching object with OHFI %d", ohfi);
Database::find_data iters;
if(!db->find(ohfi, iters)) {
if(!db.find(ohfi, iters)) {
qWarning("Failed to find OHFI %d", ohfi);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_OHFI);
return;
@@ -379,6 +424,7 @@ void CmaClient::vitaEventSendObject(vita_event_t *event, int eventId)
CMAObject *object = *iters.it;
CMAObject *start = object;
uint parentHandle = event->Param3;
bool send_folder = object->metadata.dataType & Folder;
uint handle;
do {
@@ -415,12 +461,22 @@ void CmaClient::vitaEventSendObject(vita_event_t *event, int eventId)
}
object->metadata.handle = handle;
if(object->metadata.dataType & File) {
file.unmap(data);
object = *iters.it++;
}
// break early if only a file needs to be sent
if(!send_folder) {
break;
}
object = *++iters.it;
} while(iters.it != iters.end && object->metadata.ohfiParent >= OHFI_OFFSET); // get everything under this "folder"
VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_OK, handle);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Data); // TODO: Send thumbnail
}
@@ -488,9 +544,9 @@ void CmaClient::vitaEventSendObjectStatus(vita_event_t *event, int eventId)
return;
}
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
CMAObject *object = db->pathToObject(objectstatus.title, objectstatus.ohfiRoot);
CMAObject *object = db.pathToObject(objectstatus.title, objectstatus.ohfiRoot);
if(object == NULL) { // not in database, don't return metadata
qDebug("Object %s not in database (OHFI: %i). Sending OK response for non-existence", objectstatus.title, objectstatus.ohfiRoot);
@@ -514,10 +570,10 @@ void CmaClient::vitaEventSendObjectThumb(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
int ohfi = event->Param2;
CMAObject *object = db->ohfiToObject(ohfi);
CMAObject *object = db.ohfiToObject(ohfi);
if(object == NULL) {
qWarning("Cannot find OHFI %d in database.", ohfi);
@@ -525,34 +581,13 @@ void CmaClient::vitaEventSendObjectThumb(vita_event_t *event, int eventId)
return;
}
QByteArray data;
QByteArray data = getThumbnail(object->path, object->metadata.dataType, &g_thumbmeta);
if(MASK_SET(object->metadata.dataType, SaveData)) {
QString thumbpath = QDir(object->path).absoluteFilePath("ICON0.PNG");
qDebug("Sending savedata thumbnail from %s", thumbpath.toStdString().c_str());
QFile file(thumbpath);
if(!file.open(QIODevice::ReadOnly)) {
qWarning("Cannot find thumbnail %s", thumbpath.toStdString().c_str());
if(data.size() == 0) {
qWarning("Cannot find/read thumbnail for %s", object->path.toStdString().c_str());
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Data);
return;
}
data = file.readAll();
} else {
QImage img;
if(!MASK_SET(object->metadata.dataType, Photo) || !img.load(object->path)) {
qWarning("Thumbnail sending for the file %s is not supported", object->metadata.path);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Data);
return;
}
qDebug("Creating thumbnail of %s", object->metadata.name);
QBuffer buffer(&data);
buffer.open(QIODevice::WriteOnly);
QImage result = img.scaled(256, 256, Qt::KeepAspectRatio, Qt::FastTransformation);
result.save(&buffer, "JPEG");
}
// workaround for the vitamtp locale bug
char *locale = strdup(setlocale(LC_ALL, NULL));
@@ -573,10 +608,10 @@ void CmaClient::vitaEventDeleteObject(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
int ohfi = event->Param2;
CMAObject *object = db->ohfiToObject(ohfi);
CMAObject *object = db.ohfiToObject(ohfi);
if(object == NULL) {
qWarning("OHFI %d not found", ohfi);
@@ -586,7 +621,7 @@ void CmaClient::vitaEventDeleteObject(vita_event_t *event, int eventId)
qDebug("Deleting %s, OHFI: %i", object->metadata.path, object->metadata.ohfi);
removeRecursively(object->path);
db->remove(object);
db.remove(object);
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
@@ -603,10 +638,12 @@ void CmaClient::vitaEventGetSettingInfo(vita_event_t *event, int eventId)
qDebug("Current account id: %s", settingsinfo->current_account.accountId);
db->setUUID(settingsinfo->current_account.accountId);
if(QSettings().value("lastAccountId").toString() != settingsinfo->current_account.accountId) {
db.setUUID(settingsinfo->current_account.accountId);
// set the database to be updated ASAP
emit refreshDatabase();
}
// free all the information
VitaMTP_Data_Free_Settings(settingsinfo);
@@ -660,9 +697,9 @@ void CmaClient::vitaEventSendPartOfObject(vita_event_t *event, int eventId)
return;
}
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
CMAObject *object = db->ohfiToObject(part_init.ohfi);
CMAObject *object = db.ohfiToObject(part_init.ohfi);
if(object == NULL) {
qWarning("Cannot find object for OHFI %d", part_init.ohfi);
@@ -700,9 +737,9 @@ void CmaClient::vitaEventOperateObject(vita_event_t *event, int eventId)
return;
}
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
CMAObject *root = db->ohfiToObject(operateobject.ohfi);
CMAObject *root = db.ohfiToObject(operateobject.ohfi);
// end for renaming only
if(root == NULL) {
@@ -721,7 +758,7 @@ void CmaClient::vitaEventOperateObject(vita_event_t *event, int eventId)
} else {
CMAObject *newobj = new CMAObject(root);
newobj->initObject(QFileInfo(dir, operateobject.title));
db->append(operateobject.ohfi, newobj);
db.append(operateobject.ohfi, newobj);
qDebug("Created folder %s with OHFI %d under parent %s", newobj->metadata.path, newobj->metadata.ohfi, root->metadata.path);
VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_OK, newobj->metadata.ohfi);
}
@@ -737,7 +774,7 @@ void CmaClient::vitaEventOperateObject(vita_event_t *event, int eventId)
} else {
CMAObject *newobj = new CMAObject(root);
newobj->initObject(file);
db->append(root->metadata.ohfi, newobj);
db.append(root->metadata.ohfi, newobj);
qDebug("Created file %s with OHFI %d under parent %s", newobj->metadata.path, newobj->metadata.ohfi, root->metadata.path);
VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_OK, newobj->metadata.ohfi);
}
@@ -752,7 +789,7 @@ void CmaClient::vitaEventOperateObject(vita_event_t *event, int eventId)
//rename the current object
root->rename(operateobject.title);
Database::find_data iters;
db->find(root->metadata.ohfi, iters);
db.find(root->metadata.ohfi, iters);
// rename the rest of the list only if has the renamed parent in some part of the chain
while(iters.it != iters.end) {
@@ -796,8 +833,8 @@ void CmaClient::vitaEventGetPartOfObject(vita_event_t *event, int eventId)
return;
}
QMutexLocker locker(&db->mutex);
CMAObject *object = db->ohfiToObject(part_init.ohfi);
QMutexLocker locker(&db.mutex);
CMAObject *object = db.ohfiToObject(part_init.ohfi);
if(object == NULL) {
qWarning("Cannot find OHFI %d", part_init.ohfi);
@@ -828,10 +865,10 @@ void CmaClient::vitaEventSendStorageSize(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
int ohfi = event->Param2;
CMAObject *object = db->ohfiToObject(ohfi);
CMAObject *object = db.ohfiToObject(ohfi);
if(object == NULL) {
qWarning("Error: Cannot find OHFI %d", ohfi);
@@ -882,9 +919,9 @@ void CmaClient::vitaEventCheckExistance(vita_event_t *event, int eventId)
return;
}
QMutexLocker locker(&db->mutex);
QMutexLocker locker(&db.mutex);
CMAObject *object = db->pathToObject(existance.name, 0);
CMAObject *object = db.pathToObject(existance.name, 0);
if(object == NULL) {
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Different_Object);
@@ -894,3 +931,23 @@ void CmaClient::vitaEventCheckExistance(vita_event_t *event, int eventId)
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
void CmaClient::close()
{
VitaMTP_SendHostStatus(device, VITA_HOST_STATUS_EndConnection);
VitaMTP_Release_Device(device);
}
void CmaClient::stop()
{
active = false;
connected = false;
waitCondition.wakeAll();
}
CmaClient::~CmaClient()
{
if(device) {
close();
}
}

View File

@@ -1,12 +1,33 @@
/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
*/
#ifndef CMACLIENT_H
#define CMACLIENT_H
#include "baseworker.h"
#include "cmabroadcast.h"
#include "cmaobject.h"
#include "database.h"
#include <QObject>
#include <QString>
#include <QWaitCondition>
extern "C" {
#include <vitamtp.h>
@@ -17,11 +38,13 @@ class CmaClient : public BaseWorker
Q_OBJECT
public:
explicit CmaClient(QObject *parent = 0);
explicit CmaClient(Database *database, vita_device_t *device, QObject *parent = 0);
~CmaClient();
Database db;
private:
void enterEventLoop();
vita_device_t *getDeviceConnection();
uint16_t processAllObjects(CMAObject *parent, uint32_t handle);
void vitaEventSendObject(vita_event_t *event, int eventId);
@@ -46,18 +69,23 @@ private:
void vitaEventSendNPAccountInfo(vita_event_t *event, int eventId);
void vitaEventRequestTerminate(vita_event_t *event, int eventId);
Database *db;
QWaitCondition waitCondition;
CmaBroadcast broadcast;
vita_device_t *device;
volatile bool active;
volatile bool connected;
static const metadata_t g_thumbmeta;
static metadata_t g_thumbmeta;
signals:
void receivedPin(int);
void deviceDetected();
void deviceConnected(QString);
void deviceDisconnected();
void refreshDatabase();
void terminate();
public slots:
void close();
void stop();
private slots:
void process();

View File

@@ -38,9 +38,12 @@ void CMARootObject::initObject(const QString &path)
metadata.dataType = Music;
this->path = path;
num_filters = 1;
filters = new metadata_t[1];
//createFilter(&filters[0], "Folders", VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_PLAYLISTS);
createFilter(&filters[0], "All", VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_SONGS);
filters = new metadata_t[2];
//createFilter(&filters[0], "Artists", VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_ARTISTS);
createFilter(&filters[0], "Albums", VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_ALBUMS);
createFilter(&filters[1], "All", VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_SONGS);
//createFilter(&filters[3], "Genres", VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_GENRES);
//createFilter(&filters[4], "Playlists", VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_PLAYLISTS);
break;
case VITA_OHFI_PHOTO:
@@ -119,6 +122,7 @@ void CMARootObject::createFilter(metadata_t *filter, const char *name, int type)
filter->size = 0;
filter->dataType = static_cast<DataType>(Folder | Special);
filter->next_metadata = NULL;
qDebug("Added filter %s to database with OHFI %d (%s)", name, filter->ohfi, metadata.name);
}
int CMARootObject::getFilters(metadata_t **p_head)

View File

@@ -1,72 +0,0 @@
#include "cmaserver.h"
#include "wirelessworker.h"
#include <QDebug>
#include <QHostInfo>
#include <QMutex>
#include <QSettings>
CmaServer::CmaServer(QObject *parent) :
QObject(parent), num_retries(0)
{
strcpy(hostname, QHostInfo::localHostName().toStdString().c_str());
BroadcastSignal::info.name = hostname;
}
void CmaServer::listen()
{
QThread *thread = new QThread();
if(QSettings().value("wireless", false).toBool()) {
connect(&timer, SIGNAL(timeout()), this, SLOT(connectWireless()));
broadcast.start();
timer.setSingleShot(true);
} else {
timer.setSingleShot(false);
connect(&timer, SIGNAL(timeout()), this, SLOT(connectUsb()));
}
moveToThread(thread);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
timer.start(2000);
thread->start();
}
void CmaServer::connectUsb()
{
qDebug() << "From connectUsb: "<< QThread::currentThreadId();
vita_device_t *vita = VitaMTP_Get_First_USB_Vita();
if(vita) {
qDebug("Detected PS Vita via USB");
timer.stop();
emit newConnection(vita);
} else {
qDebug("No PS Vita found. Attempt %d", ++num_retries);
}
}
void CmaServer::connectWireless()
{
typedef BroadcastSignal BS;
qDebug() << "From connectWireless: "<< QThread::currentThreadId();
vita_device_t *vita = VitaMTP_Get_First_Wireless_Vita(&BS::info, 0, 0, BS::deviceRegistered, BS::generatePin);
if(vita) {
qDebug("Detected PS Vita in wireless mode");
emit newConnection(vita);
} else {
qDebug("PS Vita couldn't be detected. Attempt %d", ++num_retries);
}
}
void CmaServer::continueProcess()
{
qDebug("Restarting CmaServer thread");
num_retries = 0;
timer.start();
}
void CmaServer::stopProcess()
{
timer.stop();
}

View File

@@ -1,45 +0,0 @@
#ifndef CMASERVER_H
#define CMASERVER_H
#include "wirelessworker.h"
extern "C" {
#include <vitamtp.h>
}
#include <limits.h>
#include <QObject>
#include <QTimer>
#include <QThread>
#include <QWaitCondition>
class CmaServer : public QObject
{
Q_OBJECT
public:
explicit CmaServer(QObject *parent = 0);
void listen();
void close();
int num_retries;
QTimer timer;
QThread *thread;
char hostname[256];
BroadcastSignal broadcast;
signals:
void newConnection(vita_device_t *);
void createdPin(int);
void finished();
public slots:
void continueProcess();
void stopProcess();
private slots:
void connectWireless();
void connectUsb();
};
#endif // CMASERVER_H

View File

@@ -27,9 +27,14 @@
#define OHFI_OFFSET 1000
const QStringList Database::image_types = QStringList() << "jpg" << "jpeg" << "png" << "tif" << "tiff" << "bmp" << "gif" << "mpo";
const QStringList Database::audio_types = QStringList() << "mp3" << "mp4" << "wav";
const QStringList Database::video_types = QStringList() << "mp4";
Database::Database(QObject *parent) :
QObject(parent), mutex(QMutex::Recursive)
{
CMARootObject::uuid = QSettings().value("lastAccountId", "ffffffffffffffff").toString();
}
Database::~Database()
@@ -40,6 +45,7 @@ Database::~Database()
void Database::setUUID(const QString uuid)
{
CMARootObject::uuid = uuid;
QSettings().setValue("lastAccountId", uuid);
}
int Database::create()
@@ -75,12 +81,13 @@ int Database::create()
case VITA_OHFI_PSPSAVE:
case VITA_OHFI_PSXAPP:
case VITA_OHFI_PSMAPP:
obj->initObject(settings.value("appsPath").toString());
}
root_list list;
list << obj;
total_objects += scanRootDirectory(list);
total_objects += recursiveScanRootDirectory(list, obj, ohfi_array[i]);
object_list[ohfi_array[i]] = list;
}
return total_objects;
@@ -95,7 +102,7 @@ CMAObject *Database::getParent(CMAObject *last_dir, const QString &current_path)
return last_dir;
}
int Database::scanRootDirectory(root_list &list)
int Database::scanRootDirectory(root_list &list, int ohfi_type)
{
int total_objects = 0;
CMAObject *last_dir = list.first();
@@ -105,8 +112,16 @@ int Database::scanRootDirectory(root_list &list)
while(it.hasNext()) {
it.next();
CMAObject *obj = new CMAObject(getParent(last_dir, it.fileInfo().path()));
obj->initObject(it.fileInfo());
QFileInfo info = it.fileInfo();
if(info.isFile() && !checkFileType(info.absoluteFilePath(), ohfi_type)) {
//qDebug("Excluding %s from database", info.absoluteFilePath().toStdString().c_str());
continue;
}
CMAObject *obj = new CMAObject(getParent(last_dir, info.path()));
obj->initObject(info);
qDebug("Added %s to database with OHFI %d", obj->metadata.name, obj->metadata.ohfi);
list << obj;
if(obj->metadata.dataType & Folder) {
@@ -118,6 +133,33 @@ int Database::scanRootDirectory(root_list &list)
return total_objects;
}
int Database::recursiveScanRootDirectory(root_list &list, CMAObject *parent, int ohfi_type)
{
int total_objects = 0;
QDir dir(parent->path);
dir.setSorting(QDir::Name | QDir::DirsFirst);
QFileInfoList qsl = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
foreach(const QFileInfo &info, qsl) {
if(info.isFile() && !checkFileType(info.absoluteFilePath(), ohfi_type)) {
//qDebug("Excluding %s from database", info.absoluteFilePath().toStdString().c_str());>
} else {
CMAObject *obj = new CMAObject(parent);
obj->initObject(info);
qDebug("Added %s to database with OHFI %d", obj->metadata.name, obj->metadata.ohfi);
list << obj;
if(info.isDir()) {
total_objects += recursiveScanRootDirectory(list, obj, ohfi_type);
} else {
total_objects++;
}
}
}
return total_objects;
}
void Database::destroy()
{
QMutexLocker locker(&mutex);
@@ -282,6 +324,14 @@ int Database::acceptFilteredObject(const CMAObject *parent, const CMAObject *cur
return result;
}
void Database::dumpMetadataList(const metadata_t *p_head)
{
while(p_head) {
qDebug("Metadata: %s with OHFI %d", p_head->name, p_head->ohfi);
p_head = p_head->next_metadata;
}
}
int Database::filterObjects(int ohfiParent, metadata_t **p_head)
{
QMutexLocker locker(&mutex);
@@ -330,5 +380,38 @@ int Database::filterObjects(int ohfiParent, metadata_t **p_head)
*p_head = temp.next_metadata;
}
if(p_head) {
dumpMetadataList(*p_head);
}
return numObjects;
}
bool Database::checkFileType(const QString path, int ohfi_root)
{
switch(ohfi_root) {
case VITA_OHFI_MUSIC:
foreach(const QString &ext, audio_types) {
if(path.endsWith(ext, Qt::CaseInsensitive)) {
return true;
}
}
break;
case VITA_OHFI_PHOTO:
foreach(const QString &ext, image_types) {
if(path.endsWith(ext, Qt::CaseInsensitive)) {
return true;
}
}
break;
case VITA_OHFI_VIDEO:
foreach(const QString &ext, video_types) {
if(path.endsWith(ext, Qt::CaseInsensitive)) {
return true;
}
}
break;
default:
return true;
}
return false;
}

View File

@@ -62,13 +62,20 @@ private:
typedef QList<CMAObject *> root_list;
typedef QMap<int, root_list> map_list;
int scanRootDirectory(root_list &list);
static const QStringList audio_types;
static const QStringList image_types;
static const QStringList video_types;
int scanRootDirectory(root_list &list,int ohfi_type);
int recursiveScanRootDirectory(root_list &list, CMAObject *parent, int ohfi_type);
bool hasFilter(const CMARootObject *object,int ohfi);
bool removeInternal(root_list &list, const CMAObject *obj);
bool findInternal(const root_list &list, int ohfi, find_data &data);
CMAObject *getParent(CMAObject *last_dir, const QString &current_path);
CMAObject *pathToObjectInternal(const root_list &list, const char *path);
static bool lessThanComparator(const CMAObject *a, const CMAObject *b);
bool checkFileType(const QString path, int ohfi_root);
void dumpMetadataList(const metadata_t *p_head);
map_list object_list;

View File

@@ -43,8 +43,6 @@ int main(int argc, char *argv[])
SingleApplication app(argc, argv);
if(!app.arguments().contains("--with-debug")) {
VitaMTP_Set_Logging(VitaMTP_NONE);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
@@ -56,7 +54,7 @@ int main(int argc, char *argv[])
VitaMTP_Set_Logging(VitaMTP_VERBOSE);
}
qDebug()<<"From main thread: "<<QThread::currentThreadId();
qDebug() << "From main thread: "<< QThread::currentThreadId();
// set the organization/application for QSettings to work properly
app.setOrganizationName("qcma");

View File

@@ -28,6 +28,7 @@
#include <QMenu>
#include <QSettings>
#include <QTimer>
#include <QSettings>
const QStringList MainWidget::path_list = QStringList() << "photoPath" << "musicPath" << "videoPath" << "appsPath" << "urlPath";
@@ -64,34 +65,22 @@ void MainWidget::dialogResult(int result)
void MainWidget::startServer()
{
connect(&server, SIGNAL(newConnection(vita_device_t *)), this, SLOT(startClient(vita_device_t*)));
connect(&server, SIGNAL(createdPin(int)), this, SLOT(showPin(int)));
//connect(&server, SIGNAL(finished()), qApp, SLOT(quit()));
qDebug("Starting cma event loop");
clientLoop.start();
}
qDebug("Starting cma server");
server.listen();
void MainWidget::stopServer()
{
clientLoop.stop();
}
void MainWidget::refreshDatabase()
{
db.mutex.lock();
db.destroy();
int count = db.create();
qDebug("Indexed %i elements in the database", count);
db.mutex.unlock();
}
void MainWidget::startClient(vita_device_t *device)
{
server.stopProcess();
qDebug() << "From startClient: "<< QThread::currentThreadId();
qDebug("Starting new client connection");
CmaClient *client = new CmaClient(&db, device);
connect(client, SIGNAL(deviceConnected(QString)), this, SLOT(receiveMessage(QString)));
connect(client, SIGNAL(deviceConnected(QString)), this, SLOT(setTrayTooltip(QString)));
connect(client, SIGNAL(refreshDatabase()), this, SLOT(refreshDatabase()));
connect(client, SIGNAL(finished()), &server, SLOT(continueProcess()));
client->start();
clientLoop.db.mutex.lock();
clientLoop.db.destroy();
int count = clientLoop.db.create();
qDebug("Added %i entries to the database", count);
clientLoop.db.mutex.unlock();
}
void MainWidget::deviceDisconnect()
@@ -103,6 +92,12 @@ void MainWidget::deviceDisconnect()
void MainWidget::connectSignals()
{
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()));
}
void MainWidget::showPin(int pin)
@@ -115,6 +110,7 @@ void MainWidget::prepareApplication()
connectSignals();
createTrayIcon();
checkSettings();
refreshDatabase();
}
void MainWidget::setTrayTooltip(QString message)
@@ -153,7 +149,7 @@ void MainWidget::createTrayIcon()
connect(options, SIGNAL(triggered()), &dialog, SLOT(open()));
//connect(reload, SIGNAL(triggered()), &CmaWorker, SLOT(allowRefresh()), Qt::DirectConnection);
connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
connect(quit, SIGNAL(triggered()), this, SLOT(stopServer()));
connect(wireless, SIGNAL(triggered()), this, SLOT(toggleWireless()));
QMenu *trayIconMenu = new QMenu(this);

View File

@@ -21,8 +21,7 @@
#define MAINWIDGET_H
#include "configwidget.h"
#include "cmaserver.h"
#include "database.h"
#include "cmaclient.h"
#include <QAction>
#include <QWidget>
@@ -56,8 +55,7 @@ private:
QAction *reload;
QAction *options;
QAction *wireless;
Database db;
CmaServer server;
CmaClient clientLoop;
const static QStringList path_list;
private slots:
@@ -67,9 +65,9 @@ private slots:
void setTrayTooltip(QString message);
void toggleWireless();
void showPin(int pin);
void startClient(vita_device_t *device);
void refreshDatabase();
void startServer();
void stopServer();
};
#endif // MAINWIDGET_H

View File

@@ -25,8 +25,8 @@ SOURCES += main.cpp \
singleapplication.cpp \
baseworker.cpp \
sforeader.cpp \
cmaserver.cpp \
cmaclient.cpp
cmaclient.cpp \
cmabroadcast.cpp
HEADERS += \
wirelessworker.h \
@@ -40,11 +40,11 @@ HEADERS += \
singleapplication.h \
baseworker.h \
sforeader.h \
cmaserver.h \
cmaclient.h
cmaclient.h \
cmabroadcast.h
CONFIG += link_pkgconfig
PKGCONFIG += libvitamtp libmediainfo
PKGCONFIG += libmediainfo
QMAKE_CXXFLAGS += -Wno-write-strings -Wall
@@ -57,3 +57,11 @@ OTHER_FILES += \
FORMS += \
configwidget.ui
win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../../../usr/lib/release/ -lvitamtp
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../../../usr/lib/debug/ -lvitamtp
else:unix: LIBS += $$PWD/../../../../usr/lib/libvitamtp.a -lusb-1.0 -lxml2
INCLUDEPATH += $$PWD/../../../../usr/include
DEPENDPATH += $$PWD/../../../../usr/include

View File

@@ -19,19 +19,20 @@
#include "utils.h"
#include <QBuffer>
#include <QDebug>
#include <QDir>
#include <QImage>
extern "C" {
#include <vitamtp.h>
}
#ifdef Q_OS_WIN32
#include <windows.h>
#else
#include <sys/statvfs.h>
#endif
#include <MediaInfo/MediaInfo.h>
#include <MediaInfo/File__Analyse_Automatic.h>
bool getDiskSpace(const QString &dir, quint64 *free, quint64 *total)
{
#ifdef Q_OS_WIN32
@@ -79,3 +80,83 @@ bool removeRecursively(const QString &dirName)
return result;
#endif
}
QByteArray findFolderAlbumArt(const QString path, metadata_t *metadata)
{
QByteArray data;
QDir folder(path);
QStringList files = folder.entryList(QDir::Files | QDir::Readable);
const QStringList cover_list = QStringList() << "album" << "cover" << "front";
const QStringList ext_list = QStringList() << "jpg" << "jpeg" << "png" << "gif";
foreach(const QString &file, files) {
foreach(const QString &cover, cover_list) {
foreach(const QString &ext, ext_list) {
if(file.compare(QString("%1.%2").arg(cover, ext), Qt::CaseInsensitive) == 0) {
qDebug() << "Trying to load album art from" << folder.absoluteFilePath(file);
QImage img;
if(img.load(folder.absoluteFilePath(file))) {
QBuffer buffer(&data);
buffer.open(QIODevice::WriteOnly);
QImage result = img.scaled(256, 250, Qt::KeepAspectRatio, Qt::SmoothTransformation);
result.save(&buffer, "JPEG");
metadata->data.thumbnail.width = result.width();
metadata->data.thumbnail.height = result.height();
}
// only try with the first match
break;
}
}
}
}
return data;
}
QByteArray getThumbnail(const QString &path, DataType type, metadata_t *metadata)
{
QByteArray data;
if(MASK_SET(type, SaveData)) {
QFile file(QDir(path).absoluteFilePath("ICON0.PNG"));
if(file.open(QIODevice::ReadOnly)) {
data = file.readAll();
}
} else if(MASK_SET(type, Photo)) {
QImage img;
if(img.load(path)) {
QBuffer buffer(&data);
buffer.open(QIODevice::WriteOnly);
QImage result = img.scaled(213, 120, Qt::KeepAspectRatio, Qt::FastTransformation);
result.save(&buffer, "JPEG");
metadata->data.thumbnail.width = result.width();
metadata->data.thumbnail.height = result.height();
}
} else if(MASK_SET(type, Music)) {
if(MASK_SET(type, Folder)) {
// TODO: try to load an album cover from one of the audio files.
data = findFolderAlbumArt(path, metadata);
} else {
MediaInfoLib::MediaInfo media;
if(media.Open(path.toStdWString())) {
QString base64 = QString::fromStdWString(media.Get(MediaInfoLib::Stream_General, 0, MediaInfoLib::General_Cover_Data));
QImage img;
if(base64.size() > 0 && img.loadFromData(QByteArray::fromBase64(base64.toUtf8()))) {
QBuffer buffer(&data);
buffer.open(QIODevice::WriteOnly);
// use smooth transformation for the ambum art since is important to display correctly
QImage result = img.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation);
result.save(&buffer, "JPEG");
metadata->data.thumbnail.width = result.width();
metadata->data.thumbnail.height = result.height();
}
}
}
}
//TODO: implement thumbnails for videos
return data;
}

View File

@@ -20,9 +20,14 @@
#ifndef UTILS_H
#define UTILS_H
#include <QByteArray>
#include <QString>
#include <QThread>
extern "C" {
#include <vitamtp.h>
}
// Qt4 doesn't have public methods for Thread::*sleep
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
typedef QThread Sleeper;
@@ -38,5 +43,6 @@
bool removeRecursively(const QString &dirName);
bool getDiskSpace(const QString &dir, quint64 *free, quint64 *total);
QByteArray getThumbnail(const QString &path, DataType type, metadata_t *metadata);
#endif // UTILS_H

View File

@@ -35,6 +35,8 @@ BroadcastSignal::BroadcastSignal(QObject *parent) :
qsrand(QTime::currentTime().msec());
this_object = this;
started = false;
strcpy(hostname, QHostInfo::localHostName().toStdString().c_str());
BroadcastSignal::info.name = hostname;
}
BroadcastSignal::~BroadcastSignal()
@@ -54,7 +56,7 @@ int BroadcastSignal::generatePin(wireless_vita_info_t *info, int *p_err)
int pin = qrand() % 100000000;
qDebug("Your registration PIN for %s is: %08d", info->name, pin);
*p_err = 0;
emit this_object->createdPin(pin);
emit this_object->receivedPin(pin);
return pin;
}

View File

@@ -45,11 +45,12 @@ public:
private:
volatile bool started;
char hostname[256];
//used to emit a signal from a static method
static BroadcastSignal *this_object;
signals:
void createdPin(int);
void receivedPin(int);
public slots:
void stopBroadcast();