diff --git a/capability.cpp b/capability.cpp index 3aebed3..ac5182c 100644 --- a/capability.cpp +++ b/capability.cpp @@ -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(); diff --git a/cmabroadcast.cpp b/cmabroadcast.cpp new file mode 100644 index 0000000..91456c1 --- /dev/null +++ b/cmabroadcast.cpp @@ -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 . + */ + +#include "cmabroadcast.h" + +#include +#include +#include +#include +#include + +#include + +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')); +} diff --git a/cmabroadcast.h b/cmabroadcast.h new file mode 100644 index 0000000..a30c98f --- /dev/null +++ b/cmabroadcast.h @@ -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 . + */ + +#ifndef CMABROADCAST_H +#define CMABROADCAST_H + +#include +#include +#include + +#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 diff --git a/cmaclient.cpp b/cmaclient.cpp index 269d42e..10c6cd0 100644 --- a/cmaclient.cpp +++ b/cmaclient.cpp @@ -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 . + */ + #include "cmaclient.h" #include "capability.h" #include "utils.h" +#include "wirelessworker.h" +#include "QApplication" #include #include #include @@ -10,55 +31,76 @@ #include #include -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 +#include + +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(); - DeviceCapability *vita_info = new DeviceCapability(); + connect(&broadcast, SIGNAL(receivedPin(int)), this, SIGNAL(receivedPin(int))); - if(!vita_info->exchangeInfo(device)) { - qCritical("Error while exchanging info with the vita"); - close(); + 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(); } - - // Conection successful, inform the user - emit deviceConnected(QString(tr("Connected to ")) + vita_info->getOnlineId()); - connected = true; - enterEventLoop(); + 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; - file.unmap(data); - object = *iters.it++; + + if(object->metadata.dataType & File) { + file.unmap(data); + } + + // 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,33 +581,12 @@ 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()); - 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"); + 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; } // workaround for the vitamtp locale bug @@ -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); - // set the database to be updated ASAP - emit refreshDatabase(); + 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(); + } +} diff --git a/cmaclient.h b/cmaclient.h index af40560..5b6a5c2 100644 --- a/cmaclient.h +++ b/cmaclient.h @@ -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 . + */ + #ifndef CMACLIENT_H #define CMACLIENT_H #include "baseworker.h" +#include "cmabroadcast.h" #include "cmaobject.h" #include "database.h" #include #include +#include extern "C" { #include @@ -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(); diff --git a/cmarootobject.cpp b/cmarootobject.cpp index 999f8b7..afbac7b 100644 --- a/cmarootobject.cpp +++ b/cmarootobject.cpp @@ -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(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) diff --git a/cmaserver.cpp b/cmaserver.cpp deleted file mode 100644 index 2969c53..0000000 --- a/cmaserver.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "cmaserver.h" -#include "wirelessworker.h" - -#include -#include -#include -#include - -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(); -} diff --git a/cmaserver.h b/cmaserver.h deleted file mode 100644 index 95855eb..0000000 --- a/cmaserver.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef CMASERVER_H -#define CMASERVER_H - -#include "wirelessworker.h" - -extern "C" { -#include -} - -#include - -#include -#include -#include -#include - -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 diff --git a/database.cpp b/database.cpp index 598c75d..d38ef8f 100644 --- a/database.cpp +++ b/database.cpp @@ -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 ¤t_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; +} diff --git a/database.h b/database.h index 40c6744..cb47ff8 100644 --- a/database.h +++ b/database.h @@ -62,13 +62,20 @@ private: typedef QList root_list; typedef QMap 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 ¤t_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; diff --git a/main.cpp b/main.cpp index 3e48253..4a4f1ed 100644 --- a/main.cpp +++ b/main.cpp @@ -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: "< #include #include +#include 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); diff --git a/mainwidget.h b/mainwidget.h index 3184613..30ad0af 100644 --- a/mainwidget.h +++ b/mainwidget.h @@ -21,8 +21,7 @@ #define MAINWIDGET_H #include "configwidget.h" -#include "cmaserver.h" -#include "database.h" +#include "cmaclient.h" #include #include @@ -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 diff --git a/qcma.pro b/qcma.pro index 09522fb..74f3587 100644 --- a/qcma.pro +++ b/qcma.pro @@ -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 + diff --git a/utils.cpp b/utils.cpp index 1cd9559..418425b 100644 --- a/utils.cpp +++ b/utils.cpp @@ -19,19 +19,20 @@ #include "utils.h" +#include +#include #include #include -extern "C" { -#include -} - #ifdef Q_OS_WIN32 #include #else #include #endif +#include +#include + 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; +} diff --git a/utils.h b/utils.h index 632d22e..3484eec 100644 --- a/utils.h +++ b/utils.h @@ -20,9 +20,14 @@ #ifndef UTILS_H #define UTILS_H +#include #include #include +extern "C" { +#include +} + // 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 diff --git a/wirelessworker.cpp b/wirelessworker.cpp index 1550a48..759e283 100644 --- a/wirelessworker.cpp +++ b/wirelessworker.cpp @@ -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; } diff --git a/wirelessworker.h b/wirelessworker.h index 82c602c..9d40c4c 100644 --- a/wirelessworker.h +++ b/wirelessworker.h @@ -44,12 +44,13 @@ public: private: - volatile bool started; + 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();