Changed folder structure

This commit is contained in:
Xian Nox
2013-12-16 10:34:48 +01:00
parent e89e636a72
commit efced3781c
69 changed files with 83 additions and 80 deletions

278
src/avdecoder.cpp Normal file
View File

@@ -0,0 +1,278 @@
/*
* 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 "avdecoder.h"
#include "cmaobject.h"
#include <QDebug>
#include <QBuffer>
#include <QFile>
#include <QSettings>
AVDecoder::AvInit init;
AVDecoder::AVDecoder() :
pFormatCtx(NULL)
{
}
AVDecoder::~AVDecoder()
{
if(pFormatCtx) {
avformat_close_input(&pFormatCtx);
}
}
bool AVDecoder::open(const QString filename)
{
if(avformat_open_input(&pFormatCtx, QFile::encodeName(filename).constData(), NULL, NULL) != 0) {
return false;
}
if(avformat_find_stream_info(pFormatCtx, NULL) < 0) {
avformat_close_input(&pFormatCtx);
return false;
}
return true;
}
void AVDecoder::getAudioMetadata(metadata_t &metadata)
{
AVDictionaryEntry *entry;
AVDictionary* file_metadata = pFormatCtx->metadata;
if((entry = av_dict_get(file_metadata, "artist", NULL, 0)) != NULL) {
metadata.data.music.artist = strdup(entry->value);
} else {
metadata.data.music.artist = strdup("");
}
if((entry = av_dict_get(file_metadata, "album", NULL, 0)) != NULL) {
metadata.data.music.album = strdup(entry->value);
} else {
metadata.data.music.album = strdup("");
}
if((entry = av_dict_get(file_metadata, "title", NULL, 0)) != NULL) {
metadata.data.music.title = strdup(entry->value);
} else {
metadata.data.music.title = strdup("");
}
metadata.data.music.tracks->data.track_audio.bitrate = pFormatCtx->bit_rate;
}
void AVDecoder::getVideoMetadata(metadata_t &metadata)
{
AVDictionaryEntry *entry;
AVDictionary* file_metadata = pFormatCtx->metadata;
if((entry = av_dict_get(file_metadata, "copyright", NULL, 0)) != NULL) {
metadata.data.video.copyright = strdup(entry->value);
} else {
metadata.data.video.copyright = strdup("");
}
if((entry = av_dict_get(file_metadata, "comments", NULL, 0)) != NULL) {
metadata.data.video.explanation = strdup(entry->value);
} else {
metadata.data.video.explanation = strdup("");
}
if((entry = av_dict_get(file_metadata, "title", NULL, 0)) != NULL) {
metadata.data.video.title = strdup(entry->value);
} else {
metadata.data.video.title = strdup(metadata.name);
}
metadata.data.video.tracks->data.track_video.duration = pFormatCtx->duration / 1000;
metadata.data.video.tracks->data.track_video.bitrate = pFormatCtx->bit_rate;
int stream_index;
AVCodec *codec = NULL;
if((stream_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0)) >= 0) {
AVCodecContext *pCodecCtx = pFormatCtx->streams[stream_index]->codec;
metadata.data.video.tracks->data.track_video.width = pCodecCtx->width;
metadata.data.video.tracks->data.track_video.height = pCodecCtx->height;
if(strcmp(codec->name, "h264") == 0) {
metadata.data.video.tracks->data.track_video.codecType = CODEC_TYPE_AVC;
} else if(strcmp(codec->name, "mpeg4") == 0) {
metadata.data.video.tracks->data.track_video.codecType = CODEC_TYPE_MPEG4;
} else {
metadata.data.video.tracks->data.track_video.codecType = 0;
}
}
}
void AVDecoder::getPictureMetadata(metadata_t &metadata)
{
int stream_index;
AVCodec *codec = NULL;
if((stream_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0)) >= 0) {
AVCodecContext *pCodecCtx = pFormatCtx->streams[stream_index]->codec;
metadata.data.photo.tracks->data.track_photo.width = pCodecCtx->width;
metadata.data.photo.tracks->data.track_photo.height = pCodecCtx->height;
}
metadata.data.photo.title = strdup(metadata.name);
}
QByteArray AVDecoder::getAudioThumbnail(int width, int height)
{
QByteArray data;
int stream_index;
AVCodec *codec = NULL;
if((stream_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0)) < 0) {
// no thumbnail
return data;
}
AVPacket pkt;
if(av_read_frame(pFormatCtx, &pkt) >= 0) {
// first frame == first thumbnail (hopefully)
QBuffer imgbuffer(&data);
imgbuffer.open(QIODevice::WriteOnly);
QImage img = QImage::fromData(QByteArray((const char *)pkt.data, pkt.size));
QImage result = img.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
result.save(&imgbuffer, "JPEG");
}
return data;
}
AVFrame *AVDecoder::getDecodedFrame(AVCodecContext *pCodecCtx, int stream_index)
{
AVFrame *pFrame = avcodec_alloc_frame();
AVPacket packet;
int frame_finished = 0;
while(!frame_finished && av_read_frame(pFormatCtx, &packet)>=0) {
if(packet.stream_index == stream_index) {
avcodec_decode_video2(pCodecCtx, pFrame, &frame_finished, &packet);
}
av_free_packet(&packet);
}
if(frame_finished) {
return pFrame;
} else {
av_free(pFrame);
return NULL;
}
}
QByteArray AVDecoder::getVideoThumbnail(int width, int height)
{
QByteArray data;
int stream_index;
AVFrame *pFrame;
AVDictionary *opts = NULL;
AVCodec *codec = NULL;
AVCodecContext *pCodecCtx = NULL;
int percentage = QSettings().value("videoThumbnailSeekPercentage", 30).toInt();
if((stream_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0)) < 0) {
return data;
}
pCodecCtx = pFormatCtx->streams[stream_index]->codec;
if(avcodec_open2(pCodecCtx, codec, &opts) < 0) {
avcodec_close(pCodecCtx);
return data;
}
qint64 seek_pos = pFormatCtx->duration * percentage / (AV_TIME_BASE * 100);
qint64 frame = av_rescale(seek_pos,pFormatCtx->streams[stream_index]->time_base.den, pFormatCtx->streams[stream_index]->time_base.num);
if(avformat_seek_file(pFormatCtx, stream_index, 0, frame, frame, AVSEEK_FLAG_FRAME) < 0) {
avcodec_close(pCodecCtx);
return data;
}
if((pFrame = getDecodedFrame(pCodecCtx, stream_index)) == NULL) {
avcodec_close(pCodecCtx);
return data;
}
AVFrame *pFrameRGB = avcodec_alloc_frame();
int numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
uint8_t *buffer = (uint8_t *)av_malloc(numBytes);
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
SwsContext *sws_ctx = sws_getContext(
pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_RGB24,
SWS_BICUBIC,
NULL,
NULL,
NULL
);
if(!sws_ctx) {
avcodec_close(pCodecCtx);
return data;
}
sws_scale(
sws_ctx,
pFrame->data,
pFrame->linesize,
0,
pCodecCtx->height,
pFrameRGB->data,
pFrameRGB->linesize
);
QImage image(pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB888);
for(int y = 0, h = pCodecCtx->height, w = pCodecCtx->width; y < h; y++) {
memcpy(image.scanLine(y), pFrameRGB->data[0] + y * pFrameRGB->linesize[0], w * 3);
}
QBuffer imgbuffer(&data);
imgbuffer.open(QIODevice::WriteOnly);
QImage result = image.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
result.save(&imgbuffer, "JPEG");
av_free(buffer);
av_free(pFrameRGB);
av_free(pFrame);
avcodec_close(pCodecCtx);
return data;
}
void AVDecoder::close()
{
avformat_close_input(&pFormatCtx);
pFormatCtx = NULL;
}

68
src/avdecoder.h Normal file
View File

@@ -0,0 +1,68 @@
/*
* 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 AVDECODER_H
#define AVDECODER_H
#include <QImage>
#include <QString>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/mathematics.h>
}
#include <vitamtp.h>
class AVDecoder
{
public:
AVDecoder();
~AVDecoder();
bool open(const QString filename);
void close();
QByteArray getAudioThumbnail(int width, int height);
QByteArray getVideoThumbnail(int width, int height);
void getPictureMetadata(metadata_t &metadata);
void getAudioMetadata(metadata_t &metadata);
void getVideoMetadata(metadata_t &metadata);
// simulate a static constructor to initialize libav only once
class AvInit
{
public:
AvInit() {
av_register_all();
}
};
static AvInit init;
private:
void AVFrameToQImage(AVFrame &frame, QImage &image, int width, int height);
AVFrame *getDecodedFrame(AVCodecContext *pCodecCtx, int stream_index);
AVFormatContext *pFormatCtx;
};
#endif // AVDECODER_H

115
src/capability.cpp Normal file
View File

@@ -0,0 +1,115 @@
/*
* 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 "capability.h"
#include <QDebug>
#include <QHostInfo>
bool DeviceCapability::exchangeInfo(vita_device_t *device)
{
if(VitaMTP_GetVitaInfo(device, &vita_info) != PTP_RC_OK) {
qWarning("Cannot retreve device information.");
return false;
}
if(vita_info.protocolVersion > VITAMTP_PROTOCOL_MAX_VERSION) {
qWarning("Vita wants protocol version %08d while we only support %08d. Attempting to continue.",
vita_info.protocolVersion, VITAMTP_PROTOCOL_MAX_VERSION);
}
QString hostname = QHostInfo::localHostName();
const initiator_info_t *pc_info = VitaMTP_Data_Initiator_New(hostname.toUtf8().data(), vita_info.protocolVersion);
// Next, we send the client's (this program) info (discard the const here)
if(VitaMTP_SendInitiatorInfo(device, (initiator_info_t *)pc_info) != PTP_RC_OK) {
qWarning("Cannot send host information.");
return false;
}
if(vita_info.protocolVersion >= VITAMTP_PROTOCOL_FW_2_10) {
// Get the device's capabilities
capability_info_t *vita_capabilities;
if(VitaMTP_GetVitaCapabilityInfo(device, &vita_capabilities) != PTP_RC_OK) {
qWarning("Failed to get capability information from Vita.");
return false;
}
// 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();
if(VitaMTP_SendPCCapabilityInfo(device, pc_capabilities) != PTP_RC_OK) {
qWarning("Failed to send capability information to Vita.");
free_pc_capability_info(pc_capabilities);
return false;
}
free_pc_capability_info(pc_capabilities);
}
// Finally, we tell the Vita we are connected
if(VitaMTP_SendHostStatus(device, VITA_HOST_STATUS_Connected) != PTP_RC_OK) {
qWarning("Cannot send host status.");
return false;
}
VitaMTP_Data_Free_Initiator(pc_info);
return true;
}
void DeviceCapability::free_pc_capability_info(capability_info_t *info)
{
delete[] &info->functions.formats.next_item[-1];
delete[] &info->functions.next_item[-1];
delete info;
}
capability_info_t *DeviceCapability::generate_pc_capability_info()
{
typedef capability_info::capability_info_function tfunction;
typedef tfunction::capability_info_format tformat;
// TODO: Actually do this based on QCMA capabilities
capability_info_t *pc_capabilities = new capability_info_t;
pc_capabilities->version = "1.0";
tfunction *functions = new tfunction[3]();
tformat *game_formats = new tformat[5]();
game_formats[0].contentType = "vitaApp";
game_formats[0].next_item = &game_formats[1];
game_formats[1].contentType = "PSPGame";
game_formats[1].next_item = &game_formats[2];
game_formats[2].contentType = "PSPSaveData";
game_formats[2].next_item = &game_formats[3];
game_formats[3].contentType = "PSGame";
game_formats[3].next_item = &game_formats[4];
game_formats[4].contentType = "PSMApp";
functions[0].type = "game";
functions[0].formats = game_formats[0];
functions[0].next_item = &functions[1];
functions[1].type = "backup";
functions[1].next_item = &functions[2];
functions[2].type = "systemUpdate";
pc_capabilities->functions = functions[0];
return pc_capabilities;
}

52
src/capability.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* 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 CAPABILITY_H
#define CAPABILITY_H
#include <vitamtp.h>
class DeviceCapability
{
public:
explicit DeviceCapability() {}
bool exchangeInfo(vita_device_t *device);
//TODO: vita_info_t doesn't retrieve this info, update vitamtp to get it
const char *getVersion() {
return "";
}
const char *getProtocol() {
return "";
}
const char *getOnlineId() {
return "PS Vita";
}
const char *getModelInfo() {
return "";
}
private:
capability_info_t *generate_pc_capability_info();
void free_pc_capability_info(capability_info_t *info);
vita_info_t vita_info;
};
#endif // CAPABILITY_H

130
src/clientmanager.cpp Normal file
View File

@@ -0,0 +1,130 @@
/*
* 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 "clientmanager.h"
#include "cmaclient.h"
#include "utils.h"
#include "forms/progressform.h"
#include <vitamtp.h>
ClientManager::ClientManager(QObject *parent) :
QObject(parent)
{
}
ClientManager::~ClientManager()
{
VitaMTP_Cleanup();
}
void ClientManager::databaseUpdated(int count)
{
progress.hide();
if(count >= 0) {
emit messageSent(tr("Added %1 items to the database").arg(count));
} else {
emit messageSent(tr("Database indexing aborted by user"));
}
}
void ClientManager::showPinDialog(QString name, int pin)
{
pinForm.setPin(name, pin);
pinForm.startCountdown();
}
void ClientManager::start()
{
if(VitaMTP_Init() < 0) {
emit messageSent(tr("Cannot initialize VitaMTP library"));
return;
}
// initializing database for the first use
refreshDatabase();
CmaEvent::db = &db;
connect(&db, SIGNAL(fileAdded(QString)), &progress, SLOT(setFileName(QString)));
connect(&db, SIGNAL(directoryAdded(QString)), &progress, SLOT(setDirectoryName(QString)));
connect(&db, SIGNAL(updated(int)), this, SLOT(databaseUpdated(int)));
connect(&progress, SIGNAL(canceled()), &db, SLOT(cancelOperation()), Qt::DirectConnection);
thread_count = 2;
qDebug("Starting cma threads");
CmaClient *client;
usb_thread = new QThread();
client = new CmaClient();
usb_thread->setObjectName("usb_thread");
connect(usb_thread, SIGNAL(started()), client, SLOT(connectUsb()));
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()));
connect(usb_thread, SIGNAL(finished()), client, SLOT(deleteLater()));
connect(client, SIGNAL(deviceConnected(QString)), this, SIGNAL(deviceConnected(QString)));
connect(client, SIGNAL(deviceDisconnected()), this, SIGNAL(deviceDisconnected()));
connect(client, SIGNAL(refreshDatabase()), this, SLOT(refreshDatabase()));
client->moveToThread(usb_thread);
usb_thread->start();
wireless_thread = new QThread();
client = new CmaClient();
wireless_thread->setObjectName("wireless_thread");
connect(wireless_thread, SIGNAL(started()), client, SLOT(connectWireless()));
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()));
connect(wireless_thread, SIGNAL(finished()), this, SLOT(threadStopped()));
connect(wireless_thread, SIGNAL(finished()), client, SLOT(deleteLater()));
connect(client, SIGNAL(pinComplete()), &pinForm, SLOT(hide()));
connect(client, SIGNAL(deviceConnected(QString)), this, SIGNAL(deviceConnected(QString)));
connect(client, SIGNAL(deviceDisconnected()), this, SIGNAL(deviceDisconnected()));
connect(client, SIGNAL(refreshDatabase()), this, SLOT(refreshDatabase()));
client->moveToThread(wireless_thread);
wireless_thread->start();
}
void ClientManager::refreshDatabase()
{
if(!db.reload()) {
emit messageSent(tr("Cannot refresh the database while is in use"));
} else {
progress.show();
}
}
void ClientManager::stop()
{
if(CmaClient::stop() < 0) {
emit stopped();
}
}
void ClientManager::threadStopped()
{
mutex.lock();
if(--thread_count == 0) {
emit stopped();
}
mutex.unlock();
}

66
src/clientmanager.h Normal file
View File

@@ -0,0 +1,66 @@
/*
* 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 CLIENTMANAGER_H
#define CLIENTMANAGER_H
#include "database.h"
#include "forms/pinform.h"
#include "forms/progressform.h"
#include <QObject>
#include <QThread>
class ClientManager : public QObject
{
Q_OBJECT
public:
explicit ClientManager(QObject *parent = 0);
~ClientManager();
void start();
void stop();
Database db;
private:
int thread_count;
QMutex mutex;
PinForm pinForm;
ProgressForm progress;
QThread *usb_thread;
QThread *wireless_thread;
signals:
void stopped();
void receivedPin(int);
void deviceDisconnected();
void messageSent(QString);
void deviceConnected(QString);
private slots:
void threadStopped();
void refreshDatabase();
void databaseUpdated(int count);
void showPinDialog(QString name, int pin);
};
#endif // CLIENTMANAGER_H

107
src/cmabroadcast.cpp Normal file
View File

@@ -0,0 +1,107 @@
/*
* 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"
"host-supported-device:PS Vita, PS Vita TV\r\n";
const char *CmaBroadcast::broadcast_query_start = "SRCH";
const char *CmaBroadcast::broadcast_query_end = " * 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().mid(1,36);
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.startsWith(broadcast_query_start) && datagram.contains(broadcast_query_end)) {
QMutexLocker locker(&mutex);
socket->writeDatagram(reply, sender, senderPort);
} else {
qWarning("Unknown request: %.*s\n", datagram.length(), datagram.constData());
}
}
}
void CmaBroadcast::setAvailable()
{
QMutexLocker locker(&mutex);
reply.clear();
reply.insert(0, 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')));
reply.append('\0');
}
void CmaBroadcast::setUnavailable()
{
QMutexLocker locker(&mutex);
reply.clear();
reply.insert(0, 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')));
reply.append('\0');
}

58
src/cmabroadcast.h Normal file
View File

@@ -0,0 +1,58 @@
/*
* 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;
QByteArray reply;
QString hostname;
QUdpSocket *socket;
static const QString broadcast_reply;
static const char *broadcast_query_start;
static const char *broadcast_query_end;
static const char *broadcast_ok;
static const char *broadcast_unavailable;
public slots:
void setAvailable();
void setUnavailable();
private slots:
void readPendingDatagrams();
};
#endif // CMABROADCAST_H

231
src/cmaclient.cpp Normal file
View File

@@ -0,0 +1,231 @@
/*
* 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 "avdecoder.h"
#include "cmaevent.h"
#include "utils.h"
#include <QApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QImage>
#include <QTime>
#include <QSettings>
#include <QUrl>
QMutex CmaClient::mutex;
QMutex CmaClient::runner;
QMutex CmaClient::cancel;
QSemaphore CmaClient::sema;
bool CmaClient::is_active = false;
bool CmaClient::in_progress = false;
int CmaClient::is_cancelled = false;
CmaClient *CmaClient::this_object = NULL;
CmaClient::CmaClient(QObject *parent) :
QObject(parent)
{
this_object = this;
}
void CmaClient::connectUsb()
{
vita_device_t *vita;
qDebug("Starting usb_thread: %lu", (unsigned long)QThread::currentThreadId());
setActive(true);
do {
if((vita = VitaMTP_Get_First_USB_Vita()) !=NULL) {
processNewConnection(vita);
} else {
//TODO: replace this with an event-driven setup
Sleeper::msleep(2000);
mutex.lock();
if(in_progress) {
sema.acquire();
}
mutex.unlock();
}
} while(isActive());
qDebug("Finishing usb_thread");
emit finished();
}
void CmaClient::connectWireless()
{
vita_device_t *vita;
wireless_host_info_t host = {NULL, NULL, NULL, QCMA_REQUEST_PORT};
typedef CmaClient CC;
QTime now = QTime::currentTime();
qsrand(now.msec());
qDebug("Starting wireless_thread: %lu", (unsigned long)QThread::currentThreadId());
setActive(true);
do {
if((vita = VitaMTP_Get_First_Wireless_Vita(&host, 0, CC::cancelCallback, CC::deviceRegistered, CC::generatePin, CC::registrationComplete)) != NULL) {
processNewConnection(vita);
} else {
Sleeper::msleep(2000);
mutex.lock();
if(in_progress) {
sema.acquire();
}
mutex.unlock();;
}
} while(isActive());
qDebug("Finishing wireless_thread");
emit finished();
}
void CmaClient::processNewConnection(vita_device_t *device)
{
QMutexLocker locker(&mutex);
in_progress = true;
broadcast.setUnavailable();
qDebug("Vita connected: id %s", VitaMTP_Get_Identification(device));
DeviceCapability vita_info;
if(!vita_info.exchangeInfo(device)) {
qCritical("Error while exchanging info with the vita");
} else {
// Conection successful, inform the user
emit deviceConnected(QString(tr("Connected to ")) + vita_info.getOnlineId());
enterEventLoop(device);
}
VitaMTP_SendHostStatus(device, VITA_HOST_STATUS_EndConnection);
qDebug("Releasing device...");
VitaMTP_Release_Device(device);
emit deviceDisconnected();
broadcast.setAvailable();
in_progress = false;
sema.release();
}
void CmaClient::registrationComplete()
{
qDebug("Registration completed");
emit this_object->pinComplete();
}
int CmaClient::deviceRegistered(const char *deviceid)
{
qDebug("Got connection request from %s", deviceid);
// TODO: check the device to see if is already registered
return 1;
}
int CmaClient::generatePin(wireless_vita_info_t *info, int *p_err)
{
qDebug("Registration request from %s (MAC: %s)", info->name, info->mac_addr);
int pin = rand() % 10000 * 10000 | rand() % 10000;
qDebug("Your registration PIN for %s is: %08d", info->name, pin);
*p_err = 0;
emit this_object->receivedPin(info->name, pin);
return pin;
}
int CmaClient::cancelCallback()
{
QMutexLocker locker(&cancel);
return is_cancelled;
}
void CmaClient::enterEventLoop(vita_device_t *device)
{
vita_event_t event;
qDebug("Starting event loop");
CmaEvent eventLoop (device);
QThread thread;
thread.setObjectName("event_thread");
eventLoop.moveToThread(&thread);
connect(&thread, SIGNAL(started()), &eventLoop, SLOT(process()));
connect(&eventLoop, SIGNAL(finishedEventLoop()), &thread, SLOT(quit()), Qt::DirectConnection);
thread.start();
while(isActive()) {
if(VitaMTP_Read_Event(device, &event) < 0) {
qWarning("Error reading event from Vita.");
break;
}
// do not create a event for this since there aren't more events to read
if(event.Code == PTP_EC_VITA_RequestTerminate) {
qDebug("Terminating event thread");
break;
// this one shuold be processed inmediately
} else if(event.Code == PTP_EC_VITA_RequestCancelTask) {
eventLoop.vitaEventCancelTask(&event, event.Param1);
qDebug("Ended event, code: 0x%x, id: %d", event.Code, event.Param1);
continue;
}
// the events are processed synchronously except for cancel/terminate
qDebug("Sending new event");
eventLoop.setEvent(event);
}
eventLoop.stop();
thread.wait();
qDebug("Finishing event loop");
}
int CmaClient::stop()
{
if(!isActive()) {
return -1;
}
CmaClient::setActive(false);
cancel.lock();
is_cancelled = true;
cancel.unlock();
return 0;
}
bool CmaClient::isActive()
{
QMutexLocker locker(&runner);
return is_active;
}
void CmaClient::setActive(bool state)
{
QMutexLocker locker(&runner);
is_active = state;
}

86
src/cmaclient.h Normal file
View File

@@ -0,0 +1,86 @@
/*
* 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 "database.h"
#include "cmaevent.h"
#include "cmaobject.h"
#include "cmabroadcast.h"
#include <QObject>
#include <QSemaphore>
#include <QString>
#include <vitamtp.h>
class CmaClient : public QObject
{
Q_OBJECT
public:
explicit CmaClient(QObject *parent = 0);
void launch();
private:
static bool isActive();
static void setActive(bool state);
static bool isEventLoopEnabled();
static void setEventLoop(bool state);
void enterEventLoop(vita_device_t *device);
void processNewConnection(vita_device_t *device);
static int deviceRegistered(const char *deviceid);
static int generatePin(wireless_vita_info_t *info, int *p_err);
static int cancelCallback();
static void registrationComplete();
CmaBroadcast broadcast;
//TODO: move all the control variables to the client manager class
static bool is_active;
static bool in_progress;
static int is_cancelled;
static CmaClient *this_object;
static QMutex mutex;
static QMutex runner;
static QMutex cancel;
static QSemaphore sema;
signals:
void newEvent(vita_event_t event);
void receivedPin(QString, int);
void pinComplete();
void deviceDetected();
void deviceConnected(QString);
void deviceDisconnected();
void refreshDatabase();
void finished();
public slots:
static int stop();
private slots:
void connectUsb();
void connectWireless();
};
#endif // CMACLIENT_H

905
src/cmaevent.cpp Normal file
View File

@@ -0,0 +1,905 @@
/*
* 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 "cmaevent.h"
#include "utils.h"
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QSettings>
#include <QUrl>
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
Database *CmaEvent::db = NULL;
metadata_t CmaEvent::g_thumbmeta = {0, 0, 0, NULL, NULL, 0, 0, 0, Thumbnail, {{17, 144, 80, 0, 1, 1.0f, 2}}, NULL};
CmaEvent::CmaEvent(vita_device_t *s_device) :
device(s_device), is_active(true)
{
}
void CmaEvent::process()
{
qDebug("Starting event_thread: %lu", (unsigned long)QThread::currentThreadId());
while(true) {
sema.acquire();
if(!isActive()) {
break;
}
mutex.lock();
processEvent();
mutex.unlock();
}
qDebug("Finishing event_thread");
emit finishedEventLoop();
}
bool CmaEvent::isActive()
{
QMutexLocker locker(&active);
return is_active;
}
void CmaEvent::stop()
{
QMutexLocker locker(&active);
is_active = false;
sema.release();
}
void CmaEvent::setDevice(vita_device_t *device)
{
QMutexLocker locker(&mutex);
this->device = device;
}
void CmaEvent::setEvent(vita_event_t event)
{
mutex.lock();
this->t_event = event;
mutex.unlock();
sema.release();
}
void CmaEvent::processEvent()
{
switch(t_event.Code) {
case PTP_EC_VITA_RequestSendNumOfObject:
vitaEventSendNumOfObject(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendObjectMetadata:
vitaEventSendObjectMetadata(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendObject:
vitaEventSendObject(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendHttpObjectFromURL:
vitaEventSendHttpObjectFromURL(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_Unknown1: // unimplemented
vitaEventUnimplementated(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendObjectStatus:
vitaEventSendObjectStatus(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendObjectThumb:
vitaEventSendObjectThumb(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestDeleteObject:
vitaEventDeleteObject(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestGetSettingInfo:
vitaEventGetSettingInfo(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendHttpObjectPropFromURL:
vitaEventSendHttpObjectPropFromURL(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendPartOfObject:
vitaEventSendPartOfObject(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestOperateObject:
vitaEventOperateObject(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestGetPartOfObject:
vitaEventGetPartOfObject(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendStorageSize:
vitaEventSendStorageSize(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestCheckExistance:
vitaEventCheckExistance(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestGetTreatObject:
vitaEventGetTreatObject(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendCopyConfirmationInfo:
vitaEventSendCopyConfirmationInfo(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendObjectMetadataItems:
vitaEventSendObjectMetadataItems(&t_event, t_event.Param1);
break;
case PTP_EC_VITA_RequestSendNPAccountInfo:
vitaEventSendNPAccountInfo(&t_event, t_event.Param1);
break;
default:
vitaEventUnimplementated(&t_event, t_event.Param1);
}
qDebug("Ended event, code: 0x%x, id: %d", t_event.Code, t_event.Param1);
}
quint16 CmaEvent::processAllObjects(CMAObject *parent, quint32 handle)
{
union {
unsigned char *fileData;
uint32_t *handles;
} data;
metadata_t remote_meta;
unsigned int length;
if(VitaMTP_GetObject(device, handle, &remote_meta, (void **)&data, &length) != PTP_RC_OK) {
qWarning("Cannot get object for handle %d", handle);
return PTP_RC_VITA_Invalid_Data;
}
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);
}
QDir dir(parent->path);
if(remote_meta.dataType & Folder) {
if(!dir.mkpath(remote_meta.name)) {
qWarning("Cannot create directory: %s", remote_meta.name);
free(data.fileData);
free(remote_meta.name);
return PTP_RC_VITA_Failed_Operate_Object;
}
} else {
QFile file(dir.absoluteFilePath(remote_meta.name));
if(!file.open(QIODevice::WriteOnly)) {
qWarning("Cannot write to %s", remote_meta.name);
free(data.fileData);
free(remote_meta.name);
return PTP_RC_VITA_Invalid_Permission;
} else {
file.write((const char *)data.fileData, remote_meta.size);
}
}
QFileInfo info(dir, remote_meta.name);
object = new CMAObject(parent);
object->initObject(info);
object->metadata.handle = remote_meta.handle;
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);
if(remote_meta.dataType & Folder) {
for(unsigned int i = 0; i < length; i++) {
quint16 ret = processAllObjects(object, data.handles[i]);
if(ret != PTP_RC_OK) {
qDebug("Deleteting object with OHFI %d", object->metadata.ohfi);
db->remove(object);
free(data.fileData);
return ret;
}
}
}
free(data.fileData);
return PTP_RC_OK;
}
void CmaEvent::vitaEventGetTreatObject(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
treat_object_t treatObject;
if(VitaMTP_GetTreatObject(device, eventId, &treatObject) != PTP_RC_OK) {
qWarning("Cannot get information on object to get");
return;
}
QMutexLocker locker(&db->mutex);
CMAObject *parent = db->ohfiToObject(treatObject.ohfiParent);
if(parent == NULL) {
qWarning("Cannot find parent OHFI %d", treatObject.ohfiParent);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_OHFI);
return;
}
VitaMTP_ReportResult(device, eventId, processAllObjects(parent, treatObject.handle));
}
void CmaEvent::vitaEventSendCopyConfirmationInfo(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
copy_confirmation_info_t *info;
if(VitaMTP_SendCopyConfirmationInfoInit(device, eventId, &info) != PTP_RC_OK) {
qWarning("Error recieving initial information.");
return;
}
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) {
qWarning("Cannot find OHFI %d", info->ohfi[i]);
free(info);
return;
}
size += object->metadata.size;
}
if(VitaMTP_SendCopyConfirmationInfo(device, eventId, info, size) != PTP_RC_OK) {
qWarning("Error sending copy confirmation");
} else {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
free(info);
}
void CmaEvent::vitaEventSendObjectMetadataItems(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
quint32 ohfi;
if(VitaMTP_SendObjectMetadataItems(device, eventId, &ohfi) != PTP_RC_OK) {
qWarning("Cannot get OHFI for retreving metadata");
return;
}
QMutexLocker locker(&db->mutex);
CMAObject *object = db->ohfiToObject(ohfi);
if(object == NULL) {
qWarning("Cannot find OHFI %d in database", ohfi);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_OHFI);
return;
}
metadata_t *metadata = &object->metadata;
metadata->next_metadata = NULL;
qDebug("Sending metadata for OHFI %d (%s)", ohfi, metadata->path);
quint16 ret = VitaMTP_SendObjectMetadata(device, eventId, metadata);
if(ret != PTP_RC_OK) {
qWarning("Error sending metadata. Code: %04X", ret);
} else {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
}
void CmaEvent::vitaEventSendNPAccountInfo(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
// AFAIK, Sony hasn't even implemented this in their CMA
qWarning("Event 0x%x unimplemented!", event->Code);
}
void CmaEvent::vitaEventUnimplementated(vita_event_t *event, int eventId)
{
qWarning("Unknown event not handled, code: 0x%x, id: %d", event->Code, eventId);
qWarning("Param1: 0x%08X, Param2: 0x%08X, Param3: 0x%08X", event->Param1, event->Param2, event->Param3);
}
void CmaEvent::vitaEventCancelTask(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
quint32 eventIdToCancel = event->Param2;
qDebug("Cancelling event %d", eventIdToCancel);
quint16 ret = VitaMTP_CancelTask(device, eventIdToCancel);
// wait until the current event finishes so we can report the result to the device
qDebug("Waiting for send event to finish");
mutex.lock();
if(ret == PTP_RC_OK) {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
mutex.unlock();
}
void CmaEvent::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);
uint ohfi = event->Param2;
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);
} else {
qDebug("Returned count of %d objects for OHFI parent %d", items, ohfi);
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
}
void CmaEvent::vitaEventSendObjectMetadata(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
browse_info_t browse;
if(VitaMTP_GetBrowseInfo(device, eventId, &browse) != PTP_RC_OK) {
qWarning("GetBrowseInfo failed");
return;
}
QMutexLocker locker(&db->mutex);
metadata_t *meta;
int count = db->filterObjects(browse.ohfiParent, &meta, browse.index, browse.numObjects); // 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
qWarning("Sending metadata for OHFI parent %d failed", browse.ohfiParent);
} else {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
}
void CmaEvent::vitaEventSendObject(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
int ohfi = event->Param2;
QMutexLocker locker(&db->mutex);
qDebug("Searching object with OHFI %d", ohfi);
Database::find_data iters;
if(!db->find(ohfi, iters)) {
qWarning("Failed to find OHFI %d", ohfi);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_OHFI);
return;
}
unsigned long len = 0;
CMAObject *object = *iters.it;
CMAObject *start = object;
uint parentHandle = event->Param3;
bool send_folder = object->metadata.dataType & Folder;
uint handle;
do {
uchar *data = NULL;
len = object->metadata.size;
QFile file(object->path);
// read the file to send if it's not a directory
// if it is a directory, data and len are not used by VitaMTP
if(object->metadata.dataType & File) {
if(!file.open(QIODevice::ReadOnly) || (data = file.map(0, file.size())) == NULL) {
qWarning("Failed to read %s", object->path.toStdString().c_str());
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Not_Exist_Object);
return;
}
}
// get the PTP object ID for the parent to put the object
// we know the parent has to be before the current node
// the first time this is called, parentHandle is left untouched
if(start != object) {
parentHandle = object->parent->metadata.handle;
}
// send the data over
qDebug("Sending %s of %lu bytes to device", object->metadata.name, len);
qDebug("OHFI %d with handle 0x%08X", ohfi, parentHandle);
VitaMTP_RegisterCancelEventId(eventId);
quint16 ret = VitaMTP_SendObject(device, &parentHandle, &handle, &object->metadata, data);
if(ret != PTP_RC_OK) {
qWarning("Sending of %s failed. Code: %04X", object->metadata.name, ret);
file.unmap(data);
return;
}
object->metadata.handle = handle;
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
}
void CmaEvent::vitaEventSendHttpObjectFromURL(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
char *url;
if(VitaMTP_GetUrl(device, eventId, &url) != PTP_RC_OK) {
qWarning("Failed to recieve URL");
return;
}
QString urlpath = QSettings().value("urlPath").toString();
QString basename = QFileInfo(QUrl(url).path()).fileName();
QFile file(QDir(urlpath).absoluteFilePath(basename));
QByteArray data;
if(!file.open(QIODevice::ReadOnly)) {
if(basename == "psp2-updatelist.xml") {
qDebug("Found request for update list. Sending cached data");
QFile res(":/main/resources/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);
free(url);
return;
}
} else {
data = file.readAll();
}
qDebug("Sending %i bytes of data for HTTP request %s", data.size(), url);
if(VitaMTP_SendHttpObjectFromURL(device, eventId, data.data(), data.size()) != PTP_RC_OK) {
qWarning("Failed to send HTTP object");
} else {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
free(url);
}
void CmaEvent::vitaEventSendObjectStatus(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
object_status_t objectstatus;
if(VitaMTP_SendObjectStatus(device, eventId, &objectstatus) != PTP_RC_OK) {
qWarning("Failed to get information for object status.");
return;
}
QMutexLocker locker(&db->mutex);
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);
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
} else {
metadata_t *metadata = &object->metadata;
metadata->next_metadata = NULL;
qDebug("Sending metadata for OHFI %d", object->metadata.ohfi);
if(VitaMTP_SendObjectMetadata(device, eventId, metadata) != PTP_RC_OK) {
qWarning("Error sending metadata for %d", object->metadata.ohfi);
} else {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
}
free(objectstatus.title);
}
void CmaEvent::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);
int ohfi = event->Param2;
CMAObject *object = db->ohfiToObject(ohfi);
if(object == NULL) {
qWarning("Cannot find OHFI %d in database.", ohfi);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_OHFI);
return;
}
QByteArray data = getThumbnail(object->path, object->metadata.dataType, &g_thumbmeta);
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
char *locale = strdup(setlocale(LC_ALL, NULL));
setlocale(LC_ALL, "C");
if(VitaMTP_SendObjectThumb(device, eventId, (metadata_t *)&g_thumbmeta, (uchar *)data.data(), data.size()) != PTP_RC_OK) {
qWarning("Error sending thumbnail");
} else {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
// restore locale
setlocale(LC_ALL, locale);
free(locale);
}
void CmaEvent::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);
int ohfi = event->Param2;
CMAObject *object = db->ohfiToObject(ohfi);
if(object == NULL) {
qWarning("OHFI %d not found", ohfi);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_OHFI);
return;
}
qDebug("Deleting %s, OHFI: %i", object->metadata.path, object->metadata.ohfi);
removeRecursively(object->path);
db->remove(object);
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
void CmaEvent::vitaEventGetSettingInfo(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
settings_info_t *settingsinfo;
if(VitaMTP_GetSettingInfo(device, eventId, &settingsinfo) != PTP_RC_OK) {
qWarning("Failed to get setting info from Vita.");
return;
}
qDebug("Current account id: %s", settingsinfo->current_account.accountId);
QSettings settings;
// Always refresh the account name
settings.setValue("lastOnlineId", settingsinfo->current_account.userName);
if(settings.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);
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
void CmaEvent::vitaEventSendHttpObjectPropFromURL(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
char *url;
if(VitaMTP_GetUrl(device, eventId, &url) != PTP_RC_OK) {
qWarning("Failed to get URL");
return;
}
QString urlpath = QSettings().value("urlPath").toString();
QString basename = QFileInfo(url).fileName();
QFileInfo file(QDir(urlpath).absoluteFilePath(basename));
if(!file.exists()) {
qWarning("The file %s is not accesible", url);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Failed_Download);
free(url);
return;
}
QString timestamp = file.lastModified().toString();
http_object_prop_t httpobjectprop;
httpobjectprop.timestamp = timestamp.toUtf8().data();
httpobjectprop.timestamp_len = timestamp.toUtf8().size();
if(VitaMTP_SendHttpObjectPropFromURL(device, eventId, &httpobjectprop) != PTP_RC_OK) {
qWarning("Failed to send object properties");
} else {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
free(url);
}
void CmaEvent::vitaEventSendPartOfObject(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
send_part_init_t part_init;
if(VitaMTP_SendPartOfObjectInit(device, eventId, &part_init) != PTP_RC_OK) {
qWarning("Cannot get information on object to send");
return;
}
QMutexLocker locker(&db->mutex);
CMAObject *object = db->ohfiToObject(part_init.ohfi);
if(object == NULL) {
qWarning("Cannot find object for OHFI %d", part_init.ohfi);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Context);
return;
}
QFile file(object->path);
if(!file.open(QIODevice::ReadOnly)) {
qWarning("Cannot read %s", object->path.toStdString().c_str());
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Not_Exist_Object);
return;
} else {
file.seek(part_init.offset);
QByteArray data = file.read(part_init.size);
qDebug("Sending %s at file offset %"PRIu64" for %"PRIu64" bytes", object->metadata.path, part_init.offset, part_init.size);
if(VitaMTP_SendPartOfObject(device, eventId, (unsigned char *)data.data(), data.size()) != PTP_RC_OK) {
qWarning("Failed to send part of object OHFI %d", part_init.ohfi);
} else {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
}
}
void CmaEvent::vitaEventOperateObject(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
operate_object_t operateobject;
if(VitaMTP_OperateObject(device, eventId, &operateobject) != PTP_RC_OK) {
qWarning("Cannot get information on object to operate");
return;
}
QMutexLocker locker(&db->mutex);
CMAObject *root = db->ohfiToObject(operateobject.ohfi);
// end for renaming only
if(root == NULL) {
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Not_Exist_Object);
return;
}
switch(operateobject.cmd) {
case VITA_OPERATE_CREATE_FOLDER: {
qDebug("Operate command %d: Create folder %s", operateobject.cmd, operateobject.title);
QDir dir(root->path);
if(!dir.mkdir(operateobject.title)) {
qWarning("Unable to create temporary folder: %s", operateobject.title);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Failed_Operate_Object);
} else {
CMAObject *newobj = new CMAObject(root);
newobj->initObject(QFileInfo(dir, operateobject.title));
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);
}
break;
}
case VITA_OPERATE_CREATE_FILE: {
qDebug("Operate command %d: Create file %s", operateobject.cmd, operateobject.title);
QFile file(root->path + QDir::separator() + operateobject.title);
if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qWarning("Unable to create temporary file: %s", operateobject.title);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Failed_Operate_Object);
} else {
CMAObject *newobj = new CMAObject(root);
newobj->initObject(file);
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);
}
break;
}
case VITA_OPERATE_RENAME: {
qDebug("Operate command %d: Rename %s to %s", operateobject.cmd, root->metadata.name, operateobject.title);
QString oldpath = root->path;
QString oldname = root->metadata.name;
//rename the current object
root->rename(operateobject.title);
Database::find_data 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) {
CMAObject *obj = *iters.it++;
if(obj->hasParent(root)) {
obj->refreshPath();
}
}
// rename in filesystem
if(!QFile(oldpath).rename(root->path)) {
qWarning("Unable to rename %s to %s", oldname.toStdString().c_str(), operateobject.title);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Failed_Operate_Object);
break;
}
qDebug("Renamed OHFI %d from %s to %s", root->metadata.ohfi, oldname.toStdString().c_str(), root->metadata.name);
VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_OK, root->metadata.ohfi);
break;
}
default:
qWarning("Operate command %d: Not implemented", operateobject.cmd);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Failed_Operate_Object);
break;
}
free(operateobject.title);
}
void CmaEvent::vitaEventGetPartOfObject(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
unsigned char *data;
send_part_init_t part_init;
if(VitaMTP_GetPartOfObject(device, eventId, &part_init, &data) != PTP_RC_OK) {
qWarning("Cannot get object from device");
return;
}
QMutexLocker locker(&db->mutex);
CMAObject *object = db->ohfiToObject(part_init.ohfi);
if(object == NULL) {
qWarning("Cannot find OHFI %d", part_init.ohfi);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_OHFI);
free(data);
return;
}
qDebug("Receiving %s at offset %"PRIu64" for %"PRIu64" bytes", object->metadata.path, part_init.offset, part_init.size);
QFile file(object->path);
if(!file.open(QIODevice::ReadWrite)) {
qWarning("Cannot write to file %s", object->path.toStdString().c_str());
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Permission);
} else {
file.seek(part_init.offset);
file.write((const char *)data, part_init.size);
object->updateObjectSize(part_init.size);
qDebug("Written %"PRIu64" bytes to %s at offset %"PRIu64, part_init.size, object->path.toStdString().c_str(), part_init.offset);
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
free(data);
}
void CmaEvent::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);
int ohfi = event->Param2;
CMAObject *object = db->ohfiToObject(ohfi);
if(object == NULL) {
qWarning("Error: Cannot find OHFI %d", ohfi);
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_OHFI);
return;
} else {
QFile file(object->path);
if(!file.exists()) {
// create the directory if doesn't exist so the query don't fail
qDebug("Creating %s", object->path.toStdString().c_str());
if(!QDir(QDir::root()).mkpath(object->path)) {
qWarning("Create directory failed");
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Permission);
return;
}
}
}
quint64 total;
quint64 free;
if(!getDiskSpace(object->path, &free, &total)) {
qWarning("Cannot get disk space");
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Permission);
return;
}
qDebug("Storage stats for drive containing OHFI %d, free: %llu, total: %llu", ohfi, free, total);
if(VitaMTP_SendStorageSize(device, eventId, total, free) != PTP_RC_OK) {
qWarning("Send storage size failed");
} else {
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}
}
void CmaEvent::vitaEventCheckExistance(vita_event_t *event, int eventId)
{
qDebug("Event recieved in %s, code: 0x%x, id: %d", Q_FUNC_INFO, event->Code, eventId);
int handle = event->Param2;
existance_object_t existance;
if(VitaMTP_CheckExistance(device, handle, &existance) != PTP_RC_OK) {
qWarning("Cannot read information on object to be sent");
return;
}
QMutexLocker locker(&db->mutex);
CMAObject *object = db->pathToObject(existance.name, 0);
if(object == NULL) {
VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Different_Object);
} else {
VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_VITA_Same_Object, object->metadata.ohfi);
}
VitaMTP_ReportResult(device, eventId, PTP_RC_OK);
}

89
src/cmaevent.h Normal file
View File

@@ -0,0 +1,89 @@
/*
* 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 CMAEVENT_H
#define CMAEVENT_H
#include "cmaobject.h"
#include "database.h"
#include <QObject>
#include <QSemaphore>
#include <vitamtp.h>
class CmaEvent : public QObject
{
Q_OBJECT
public:
explicit CmaEvent(vita_device_t *s_device);
void vitaEventCancelTask(vita_event_t *event, int eventId);
// don't make the db reference static
static Database *db;
private:
uint16_t processAllObjects(CMAObject *parent, uint32_t handle);
void vitaEventSendObject(vita_event_t *event, int eventId);
void vitaEventSendObjectMetadata(vita_event_t *event, int eventId);
void vitaEventSendNumOfObject(vita_event_t *event, int eventId);
void vitaEventSendHttpObjectFromURL(vita_event_t *event, int eventId);
void vitaEventUnimplementated(vita_event_t *event, int eventId);
void vitaEventSendObjectStatus(vita_event_t *event, int eventId);
void vitaEventSendObjectThumb(vita_event_t *event, int eventId);
void vitaEventDeleteObject(vita_event_t *event, int eventId);
void vitaEventGetSettingInfo(vita_event_t *event, int eventId);
void vitaEventSendHttpObjectPropFromURL(vita_event_t *event, int eventId);
void vitaEventSendPartOfObject(vita_event_t *event, int eventId);
void vitaEventOperateObject(vita_event_t *event, int eventId);
void vitaEventGetPartOfObject(vita_event_t *event, int eventId);
void vitaEventSendStorageSize(vita_event_t *event, int eventId);
void vitaEventCheckExistance(vita_event_t *event, int eventId);
void vitaEventGetTreatObject(vita_event_t *event, int eventId);
void vitaEventSendCopyConfirmationInfo(vita_event_t *event, int eventId);
void vitaEventSendObjectMetadataItems(vita_event_t *event, int eventId);
void vitaEventSendNPAccountInfo(vita_event_t *event, int eventId);
void processEvent();
bool isActive();
void setDevice(vita_device_t *device);
vita_device_t *device;
vita_event_t t_event;
// control variables
bool is_active;
QMutex mutex;
QMutex active;
QSemaphore sema;
static metadata_t g_thumbmeta;
signals:
void finishedEventLoop();
void refreshDatabase();
public slots:
void process();
void setEvent(vita_event_t event);
void stop();
};
#endif // CMAEVENT_H

263
src/cmaobject.cpp Normal file
View File

@@ -0,0 +1,263 @@
/*
* 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 "cmaobject.h"
#include "sforeader.h"
#include "avdecoder.h"
#include "utils.h"
#include <QDir>
#include <QDateTime>
#include <QDebug>
int CMAObject::ohfi_count = OHFI_OFFSET;
const CMAObject::file_type CMAObject::audio_list[] = {
{"mp3", FILE_FORMAT_MP3, CODEC_TYPE_MP3},
{"mp4", FILE_FORMAT_MP4, CODEC_TYPE_AAC},
{"wav", FILE_FORMAT_WAV, CODEC_TYPE_PCM}
};
const CMAObject::file_type CMAObject::photo_list[] = {
{"jpg", FILE_FORMAT_JPG, CODEC_TYPE_JPG},
{"jpeg", FILE_FORMAT_JPG, CODEC_TYPE_JPG},
{"png", FILE_FORMAT_PNG, CODEC_TYPE_PNG},
{"tif", FILE_FORMAT_TIF, CODEC_TYPE_TIF},
{"tiff", FILE_FORMAT_TIF, CODEC_TYPE_TIF},
{"bmp", FILE_FORMAT_BMP, CODEC_TYPE_BMP},
{"gif", FILE_FORMAT_GIF, CODEC_TYPE_GIF},
};
const char *CMAObject::video_list[] = {"mp4"};
CMAObject::CMAObject(CMAObject *obj_parent) :
parent(obj_parent), metadata()
{
}
CMAObject::~CMAObject()
{
free(metadata.name);
free(metadata.path);
if(MASK_SET(metadata.dataType, SaveData | Folder)) {
free(metadata.data.saveData.title);
free(metadata.data.saveData.detail);
free(metadata.data.saveData.dirName);
free(metadata.data.saveData.savedataTitle);
} else if(MASK_SET(metadata.dataType, Photo | File)) {
free(metadata.data.photo.title);
free(metadata.data.photo.fileName);
delete metadata.data.photo.tracks;
} else if(MASK_SET(metadata.dataType, Music | File)) {
free(metadata.data.music.title);
free(metadata.data.music.fileName);
free(metadata.data.music.album);
free(metadata.data.music.artist);
delete metadata.data.music.tracks;
} else if(MASK_SET(metadata.dataType, Video | File)) {
free(metadata.data.video.title);
free(metadata.data.video.explanation);
free(metadata.data.video.fileName);
free(metadata.data.video.copyright);
delete metadata.data.video.tracks;
}
}
void CMAObject::loadSfoMetadata(const QString &path)
{
QString sfo = QDir(path).absoluteFilePath("PARAM.SFO");
SfoReader reader;
if(reader.load(sfo)) {
metadata.data.saveData.title = strdup(reader.value("TITLE", ""));
metadata.data.saveData.detail = strdup(reader.value("SAVEDATA_DETAIL", ""));
metadata.data.saveData.savedataTitle = strdup(reader.value("SAVEDATA_TITLE", ""));
metadata.data.saveData.dateTimeUpdated = QFileInfo(sfo).created().toTime_t();
} else {
metadata.data.saveData.title = strdup(metadata.name);
metadata.data.saveData.detail = strdup("");
metadata.data.saveData.savedataTitle = strdup("");
metadata.data.saveData.dateTimeUpdated = 0;
}
}
void CMAObject::loadMusicMetadata(const QString &path)
{
AVDecoder decoder;
if(decoder.open(path)) {
decoder.getAudioMetadata(metadata);
} else {
metadata.data.music.album = strdup(parent->metadata.name ? parent->metadata.name : "");
metadata.data.music.artist = strdup("");
metadata.data.music.title = strdup(metadata.name);
}
}
void CMAObject::loadVideoMetadata(const QString &path)
{
AVDecoder decoder;
if(decoder.open(path)) {
decoder.getVideoMetadata(metadata);
} else {
metadata.data.video.title = strdup(metadata.name);
metadata.data.video.explanation = strdup("");
metadata.data.video.copyright = strdup("");
}
}
void CMAObject::loadPhotoMetadata(const QString &path)
{
AVDecoder decoder;
if(decoder.open(path)) {
decoder.getPictureMetadata(metadata);
} else {
metadata.data.photo.title = strdup(metadata.name);
}
}
void CMAObject::initObject(const QFileInfo &file, int file_type)
{
metadata.name = strdup(file.fileName().toUtf8().data());
metadata.ohfiParent = parent->metadata.ohfi;
metadata.ohfi = ohfi_count++;
metadata.type = VITA_DIR_TYPE_MASK_REGULAR; // ignored for files
metadata.dateTimeCreated = file.created().toTime_t();
metadata.size = 0;
DataType type = file.isFile() ? File : Folder;
metadata.dataType = (DataType)(type | (parent->metadata.dataType & ~Folder));
// create additional metadata
if(MASK_SET(metadata.dataType, SaveData | Folder)) {
metadata.data.saveData.dirName = strdup(metadata.name);
metadata.data.saveData.statusType = 1;
loadSfoMetadata(file.absoluteFilePath());
} else if(MASK_SET(metadata.dataType, Music | File)) {
metadata.data.music.fileName = strdup(metadata.name);
metadata.data.music.fileFormatType = audio_list[file_type].file_format;
metadata.data.music.statusType = 1;
metadata.data.music.numTracks = 1;
metadata.data.music.tracks = new media_track();
metadata.data.music.tracks->type = VITA_TRACK_TYPE_AUDIO;
metadata.data.music.tracks->data.track_photo.codecType = audio_list[file_type].file_codec;
loadMusicMetadata(file.absoluteFilePath());
} else if(MASK_SET(metadata.dataType, Video | File)) {
metadata.data.video.fileName = strdup(metadata.name);
metadata.data.video.dateTimeUpdated = file.created().toTime_t();
metadata.data.video.statusType = 1;
metadata.data.video.fileFormatType = FILE_FORMAT_MP4;
metadata.data.video.parentalLevel = 0;
metadata.data.video.numTracks = 1;
metadata.data.video.tracks = new media_track();
metadata.data.video.tracks->type = VITA_TRACK_TYPE_VIDEO;
loadVideoMetadata(file.absoluteFilePath());
} else if(MASK_SET(metadata.dataType, Photo | File)) {
metadata.data.photo.fileName = strdup(metadata.name);
metadata.data.photo.fileFormatType = photo_list[file_type].file_format;
metadata.data.photo.statusType = 1;
metadata.data.photo.dateTimeOriginal = file.created().toTime_t();
metadata.data.photo.numTracks = 1;
metadata.data.photo.tracks = new media_track();
metadata.data.photo.tracks->type = VITA_TRACK_TYPE_PHOTO;
metadata.data.photo.tracks->data.track_photo.codecType = photo_list[file_type].file_codec;
loadPhotoMetadata(file.absoluteFilePath());
}
path = file.absoluteFilePath();
if(parent->metadata.path == NULL) {
metadata.path = strdup(metadata.name);
} else {
QString newpath = QString(parent->metadata.path) + "/" + metadata.name;
metadata.path = strdup(newpath.toUtf8().data());
}
updateObjectSize(file.size());
}
bool CMAObject::removeReferencedObject()
{
if(metadata.dataType & Folder) {
return removeRecursively(path);
} else {
return QFile::remove(path);
}
}
void CMAObject::updateObjectSize(qint64 size)
{
if(parent) {
parent->updateObjectSize(size);
}
//FIXME: size should be quint64
metadata.size += size;
}
void CMAObject::rename(const QString &newname)
{
free(metadata.name);
metadata.name = strdup(newname.toUtf8().data());
if(metadata.path) {
QStringList metadata_path(QString(metadata.path).split("/"));
metadata_path.replace(metadata_path.count() - 1, newname);
free(metadata.path);
metadata.path = strdup(metadata_path.join("/").toUtf8().data());
}
path = QFileInfo(path).absoluteDir().path() + "/" + newname;
}
void CMAObject::refreshPath()
{
if(parent) {
free(metadata.path);
QString newpath(QString(parent->metadata.path) + "/" + metadata.name);
metadata.path = strdup(newpath.toUtf8().data());
path = parent->path + "/" + metadata.name;
}
}
bool CMAObject::hasParent(const CMAObject *obj)
{
if(parent) {
if(metadata.ohfiParent == obj->metadata.ohfi) {
return true;
} else {
return parent->hasParent(obj);
}
}
return false;
}
bool CMAObject::operator==(const CMAObject &obj)
{
return metadata.ohfi == obj.metadata.ohfi;
}
bool CMAObject::operator!=(const CMAObject &obj)
{
return metadata.ohfi != obj.metadata.ohfi;
}
bool CMAObject::operator<(const CMAObject &obj)
{
return metadata.ohfi < obj.metadata.ohfi;
}

99
src/cmaobject.h Normal file
View File

@@ -0,0 +1,99 @@
/*
* 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 CMAOBJECT_H
#define CMAOBJECT_H
#include <QFileInfo>
#include <QString>
#include <vitamtp.h>
#define OHFI_OFFSET 256
#define FILE_FORMAT_MP4 1
#define FILE_FORMAT_WAV 2
#define FILE_FORMAT_MP3 3
#define FILE_FORMAT_JPG 4
#define FILE_FORMAT_PNG 5
#define FILE_FORMAT_GIF 6
#define FILE_FORMAT_BMP 7
#define FILE_FORMAT_TIF 8
#define CODEC_TYPE_MPEG4 2
#define CODEC_TYPE_AVC 3
#define CODEC_TYPE_MP3 12
#define CODEC_TYPE_AAC 13
#define CODEC_TYPE_PCM 15
#define CODEC_TYPE_JPG 17
#define CODEC_TYPE_PNG 18
#define CODEC_TYPE_TIF 19
#define CODEC_TYPE_BMP 20
#define CODEC_TYPE_GIF 21
class CMAObject
{
public:
explicit CMAObject(CMAObject *parent = 0);
~CMAObject();
void refreshPath();
bool removeReferencedObject();
void rename(const QString &name);
void updateObjectSize(qint64 size);
bool hasParent(const CMAObject *obj);
void initObject(const QFileInfo &file, int file_type = -1);
bool operator==(const CMAObject &obj);
bool operator!=(const CMAObject &obj);
bool operator<(const CMAObject &obj);
inline void setOhfi(int ohfi) {
metadata.ohfi = ohfi;
}
inline static void resetOhfiCounter() {
ohfi_count = OHFI_OFFSET;
}
typedef struct {
const char *file_ext;
int file_format;
int file_codec;
} file_type;
static const file_type audio_list[3];
static const file_type photo_list[7];
static const char *video_list[1];
QString path;
CMAObject *parent;
metadata_t metadata;
protected:
static int ohfi_count;
private:
void loadSfoMetadata(const QString &path);
void loadMusicMetadata(const QString &path);
void loadVideoMetadata(const QString &path);
void loadPhotoMetadata(const QString &path);
};
#endif // CMAOBJECT_H

147
src/cmarootobject.cpp Normal file
View File

@@ -0,0 +1,147 @@
/*
* 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 "cmarootobject.h"
#include <QDir>
QString CMARootObject::uuid = "ffffffffffffffff";
CMARootObject::CMARootObject(int ohfi) :
num_filters(0), filters(NULL), root_ohfi(ohfi)
{
}
void CMARootObject::initObject(const QString &path)
{
metadata.ohfi = root_ohfi;
metadata.type = VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_REGULAR;
switch(root_ohfi) {
case VITA_OHFI_MUSIC:
metadata.dataType = Music;
this->path = path;
num_filters = 5;
filters = new metadata_t[5];
createFilter(&filters[0], "Artists", VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_ARTISTS);
createFilter(&filters[1], "Albums", VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_ALBUMS);
createFilter(&filters[2], "Songs", 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:
metadata.dataType = Photo;
this->path = path;
num_filters = 2;
filters = new metadata_t[2];
createFilter(&filters[0], "Folders", VITA_DIR_TYPE_MASK_PHOTO | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_REGULAR);
createFilter(&filters[1], "All", VITA_DIR_TYPE_MASK_PHOTO | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_ALL);
break;
case VITA_OHFI_VIDEO:
metadata.dataType = Video;
this->path = path;
num_filters = 2;
filters = new metadata_t[2];
createFilter(&filters[0], "Folders", VITA_DIR_TYPE_MASK_VIDEO | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_REGULAR);
createFilter(&filters[1], "All", VITA_DIR_TYPE_MASK_VIDEO | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_ALL);
break;
case VITA_OHFI_VITAAPP:
metadata.dataType = App;
this->path = QDir(QDir(path).absoluteFilePath("APP")).absoluteFilePath(uuid);
num_filters = 0;
break;
case VITA_OHFI_PSPAPP:
metadata.dataType = App;
this->path = QDir(QDir(path).absoluteFilePath("PGAME")).absoluteFilePath(uuid);
num_filters = 0;
break;
case VITA_OHFI_PSPSAVE:
metadata.dataType = SaveData;
this->path = QDir(QDir(path).absoluteFilePath("PSAVEDATA")).absoluteFilePath(uuid);
num_filters = 0;
break;
case VITA_OHFI_PSXAPP:
metadata.dataType = App;
this->path = QDir(QDir(path).absoluteFilePath("PSGAME")).absoluteFilePath(uuid);
num_filters = 0;
break;
case VITA_OHFI_PSMAPP:
metadata.dataType = App;
this->path = QDir(QDir(path).absoluteFilePath("PSM")).absoluteFilePath(uuid);
num_filters = 0;
break;
case VITA_OHFI_BACKUP:
metadata.dataType = App;
this->path = QDir(QDir(path).absoluteFilePath("SYSTEM")).absoluteFilePath(uuid);
num_filters = 0;
}
// create the backup directories
QDir dir(this->path);
dir.mkpath(dir.absolutePath());
}
CMARootObject::~CMARootObject()
{
for(int i = 0; i < num_filters; i++) {
free(filters[i].name);
free(filters[i].path);
}
delete[] filters;
}
void CMARootObject::createFilter(metadata_t *filter, const char *name, int type)
{
filter->ohfiParent = metadata.ohfi;
filter->ohfi = ohfi_count++;
filter->name = strdup(name);
filter->path = strdup(metadata.path ? metadata.path : "");
filter->type = type;
filter->dateTimeCreated = 0;
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)
{
int numObjects = num_filters;
for(int i = 0; i < numObjects; i++) {
filters[i].next_metadata = &filters[i + 1];
}
filters[numObjects - 1].next_metadata = NULL;
if(p_head != NULL) {
*p_head = filters;
}
return numObjects;
}

49
src/cmarootobject.h Normal file
View File

@@ -0,0 +1,49 @@
/*
* 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 CMAROOTOBJECT_H
#define CMAROOTOBJECT_H
#include "cmaobject.h"
#include <QList>
#include <vitamtp.h>
class CMARootObject : public CMAObject
{
public:
explicit CMARootObject(int ohfi);
~CMARootObject();
void initObject(const QString &path);
void remove(const CMAObject *obj);
int getFilters(metadata_t **p_head);
int num_filters;
metadata_t *filters;
static QString uuid;
private:
void createFilter(metadata_t *filter, const char *name, int type);
int root_ohfi;
};
#endif // CMAROOTOBJECT_H

503
src/database.cpp Normal file
View File

@@ -0,0 +1,503 @@
/*
* 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 "database.h"
#include "cmaobject.h"
#include <QDir>
#include <QDirIterator>
#include <QSettings>
#include <QTextStream>
#include <QThread>
#include <QDebug>
Database::Database() :
mutex(QMutex::Recursive)
{
QString uuid = QSettings().value("lastAccountId", "ffffffffffffffff").toString();
CMARootObject::uuid = uuid;
thread = new QThread();
timer = new QTimer();
moveToThread(thread);
thread->start();
timer->setInterval(0);
timer->setSingleShot(true);
connect(timer, SIGNAL(timeout()), this, SLOT(process()));
}
Database::~Database()
{
destroy();
timer->stop();
delete timer;
thread->quit();
thread->wait();
delete thread;
}
void Database::setUUID(const QString uuid)
{
CMARootObject::uuid = uuid;
QSettings().setValue("lastAccountId", uuid);
}
bool Database::reload()
{
if(mutex.tryLock()) {
timer->start();
return true;
} else {
return false;
}
}
void Database::process()
{
destroy();
cancel_operation = false;
int count = create();
cancel_operation = false;
qDebug("Added %i entries to the database", count);
if(count < 0) {
destroy();
}
emit updated(count);
mutex.unlock();
}
void Database::cancelOperation()
{
QMutexLocker locker(&cancel);
cancel_operation = true;
}
bool Database::continueOperation()
{
QMutexLocker locker(&cancel);
return !cancel_operation;
}
int Database::create()
{
int total_objects = 0;
//QMutexLocker locker(&mutex);
const int ohfi_array[] = { VITA_OHFI_MUSIC, VITA_OHFI_PHOTO, VITA_OHFI_VIDEO,
VITA_OHFI_BACKUP, VITA_OHFI_VITAAPP, VITA_OHFI_PSPAPP,
VITA_OHFI_PSPSAVE, VITA_OHFI_PSXAPP, VITA_OHFI_PSMAPP
};
CMAObject::resetOhfiCounter();
QSettings settings;
for(int i = 0, max = sizeof(ohfi_array) / sizeof(int); i < max; i++) {
CMARootObject *obj = new CMARootObject(ohfi_array[i]);
switch(ohfi_array[i]) {
case VITA_OHFI_MUSIC:
obj->initObject(settings.value("musicPath").toString());
break;
case VITA_OHFI_PHOTO:
obj->initObject(settings.value("photoPath").toString());
break;
case VITA_OHFI_VIDEO:
obj->initObject(settings.value("videoPath").toString());
break;
case VITA_OHFI_BACKUP:
case VITA_OHFI_VITAAPP:
case VITA_OHFI_PSPAPP:
case VITA_OHFI_PSPSAVE:
case VITA_OHFI_PSXAPP:
case VITA_OHFI_PSMAPP:
obj->initObject(settings.value("appsPath").toString());
}
root_list list;
list << obj;
emit directoryAdded(obj->path);
int dir_count = recursiveScanRootDirectory(list, obj, ohfi_array[i]);
if(dir_count < 0) {
return -1;
}
total_objects += dir_count;
object_list[ohfi_array[i]] = list;
}
return total_objects;
}
CMAObject *Database::getParent(CMAObject *last_dir, const QString &current_path)
{
while(last_dir && current_path != last_dir->path) {
last_dir = last_dir->parent;
}
return last_dir;
}
int Database::scanRootDirectory(root_list &list, int ohfi_type)
{
int file_type = -1;
int total_objects = 0;
CMAObject *last_dir = list.first();
QDir dir(last_dir->path);
dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
QDirIterator it(dir, QDirIterator::Subdirectories);
while(it.hasNext()) {
if(!continueOperation()) {
return -1;
}
it.next();
QFileInfo info = it.fileInfo();
if(info.isFile()) {
if((file_type = checkFileType(info.absoluteFilePath(), ohfi_type)) < 0) {
//qDebug("Excluding %s from database", info.absoluteFilePath().toStdString().c_str());
continue;
}
}
CMAObject *obj = new CMAObject(getParent(last_dir, info.path()));
obj->initObject(info, file_type);
//qDebug("Added %s to database with OHFI %d", obj->metadata.name, obj->metadata.ohfi);
list << obj;
if(obj->metadata.dataType & Folder) {
last_dir = obj;
} else {
total_objects++;
}
}
return total_objects;
}
int Database::recursiveScanRootDirectory(root_list &list, CMAObject *parent, int ohfi_type)
{
int file_type = -1;
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(!continueOperation()) {
return -1;
}
if(info.isFile() && (file_type = checkFileType(info.absoluteFilePath(), ohfi_type)) < 0) {
//qDebug("Excluding %s from database", info.absoluteFilePath().toStdString().c_str());>
} else {
CMAObject *obj = new CMAObject(parent);
obj->initObject(info, file_type);
emit fileAdded(obj->metadata.name);
//qDebug("Added %s to database with OHFI %d", obj->metadata.name, obj->metadata.ohfi);
list << obj;
if(info.isDir()) {
emit directoryAdded(obj->path);
total_objects += recursiveScanRootDirectory(list, obj, ohfi_type);
} else {
total_objects++;
}
}
}
return total_objects;
}
void Database::destroy()
{
//QMutexLocker locker(&mutex);
for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) {
CMARootObject *first = static_cast<CMARootObject *>((*root).takeFirst());
delete first;
qDeleteAll(*root);
}
object_list.clear();
}
bool Database::removeInternal(root_list &list, const CMAObject *obj)
{
bool found = false;
QList<CMAObject *>::iterator it = list.begin();
while(it != list.end()) {
if(!found && (*it) == obj) {
// update the size of the parent objects
(*it)->updateObjectSize(-(*it)->metadata.size);
it = list.erase(it);
found = true;
} else if(found && (*it)->metadata.ohfiParent == obj->metadata.ohfi) {
it = list.erase(it);
} else {
++it;
}
}
return found;
}
bool Database::remove(const CMAObject *obj, int ohfi_root)
{
QMutexLocker locker(&mutex);
if(ohfi_root) {
return removeInternal(object_list[ohfi_root], obj);
} else {
for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) {
if(removeInternal(*root, obj)) {
return true;
}
}
}
return false;
}
bool Database::lessThanComparator(const CMAObject *a, const CMAObject *b)
{
return a->metadata.ohfi < b->metadata.ohfi;
}
bool Database::hasFilter(const CMARootObject *object,int ohfi)
{
for(int i = 0; i < object->num_filters; i++) {
if(object->filters[i].ohfi == ohfi) {
return true;
}
}
return false;
}
bool Database::findInternal(const root_list &list, int ohfi, find_data &data)
{
if(hasFilter(static_cast<CMARootObject *>(list.first()), ohfi)) {
data.it = list.begin();
} else {
CMAObject obj;
obj.setOhfi(ohfi);
data.it = qBinaryFind(list.begin(), list.end(), &obj, Database::lessThanComparator);
}
data.end = list.end();
return data.it != data.end;
}
bool Database::find(int ohfi, Database::find_data &data)
{
QMutexLocker locker(&mutex);
for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) {
if(findInternal(*root, ohfi, data)) {
return true;
}
}
return false;
}
void Database::append(int parent_ohfi, CMAObject *object)
{
QMutexLocker locker(&mutex);
CMAObject parent;
parent.setOhfi(parent_ohfi);
for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) {
root_list *cat_list = &(*root);
root_list::const_iterator it = qBinaryFind(cat_list->begin(), cat_list->end(), &parent, Database::lessThanComparator);
if(it != cat_list->end()) {
cat_list->append(object);
break;
}
}
}
CMAObject *Database::ohfiToObject(int ohfi)
{
QMutexLocker locker(&mutex);
find_data data;
return find(ohfi, data) ? *data.it : NULL;
}
CMAObject *Database::pathToObjectInternal(const root_list &list, const char *path)
{
// skip the first element since is the root element
root_list::const_iterator skipped_first = ++list.begin();
for(root_list::const_iterator obj = skipped_first; obj != list.end(); ++obj) {
if(strcasecmp(path, (*obj)->metadata.path) == 0) {
return (*obj);
}
}
return NULL;
}
CMAObject *Database::pathToObject(const char *path, int ohfiRoot)
{
QMutexLocker locker(&mutex);
for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) {
if(ohfiRoot && (*root).first()->metadata.ohfi != ohfiRoot) {
continue;
}
CMAObject *obj = pathToObjectInternal(*root, path);
if(obj) {
return obj;
}
}
return NULL;
}
int Database::acceptFilteredObject(const CMAObject *parent, const CMAObject *current, int type)
{
QMutexLocker locker(&mutex);
int result = 0;
if(MASK_SET(type, VITA_DIR_TYPE_MASK_PHOTO)) {
result = (current->metadata.dataType & Photo);
} else if(MASK_SET(type, VITA_DIR_TYPE_MASK_VIDEO)) {
result = (current->metadata.dataType & Video);
} else if(MASK_SET(type, VITA_DIR_TYPE_MASK_MUSIC)) {
result = (current->metadata.dataType & Music);
}
if(type == (VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_ARTISTS)) {
// unimplemented
return 0;
} else if(type == (VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_GENRES)) {
// unimplemented
return 0;
} else if(type == (VITA_DIR_TYPE_MASK_MUSIC | VITA_DIR_TYPE_MASK_ROOT | VITA_DIR_TYPE_MASK_PLAYLISTS)) {
// unimplemented
return 0;
} else if(type & (VITA_DIR_TYPE_MASK_ALL | VITA_DIR_TYPE_MASK_SONGS)) {
result = result && (current->metadata.dataType & File);
} else if(type & (VITA_DIR_TYPE_MASK_REGULAR)) {
result = (parent->metadata.ohfi == current->metadata.ohfiParent);
}
// TODO: Support other filter types
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, int index, int max_number)
{
QMutexLocker locker(&mutex);
CMARootObject *parent = static_cast<CMARootObject *>(ohfiToObject(ohfiParent));
if(parent == NULL) {
return 0;
}
int type = parent->metadata.type;
if(parent->metadata.ohfi < OHFI_OFFSET && parent->filters) { // if we have filters
if(ohfiParent == parent->metadata.ohfi) { // if we are looking at root
return parent->getFilters(p_head);
} else { // we are looking at a filter
for(int j = 0; j < parent->num_filters; j++) {
if(parent->filters[j].ohfi == ohfiParent) {
type = parent->filters[j].type;
break;
}
}
}
}
int offset = 0;
int numObjects = 0;
metadata_t temp = metadata_t();
metadata_t *tail = &temp;
for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) {
for(root_list::iterator object = (*root).begin(); object != (*root).end(); ++object) {
if(acceptFilteredObject(parent, *object, type)) {
if(offset++ >= index) {
tail->next_metadata = &(*object)->metadata;
tail = tail->next_metadata;
numObjects++;
}
if(max_number > 0 && numObjects >= max_number) {
break;
}
}
}
if(numObjects > 0) {
break;
}
}
tail->next_metadata = NULL;
if(p_head != NULL) {
*p_head = temp.next_metadata;
}
return numObjects;
}
int Database::checkFileType(const QString path, int ohfi_root)
{
switch(ohfi_root) {
case VITA_OHFI_MUSIC:
for(int i = 0, max = sizeof(CMAObject::audio_list) / sizeof(CMAObject::file_type); i < max; i++) {
if(path.endsWith(CMAObject::audio_list[i].file_ext, Qt::CaseInsensitive)) {
return i;
}
}
break;
case VITA_OHFI_PHOTO:
for(int i = 0, max = sizeof(CMAObject::photo_list) / sizeof(CMAObject::file_type); i < max; i++) {
if(path.endsWith(CMAObject::photo_list[i].file_ext, Qt::CaseInsensitive)) {
return i;
}
}
break;
case VITA_OHFI_VIDEO:
for(int i = 0, max = sizeof(CMAObject::video_list) / sizeof(const char *); i < max; i++) {
if(path.endsWith(CMAObject::video_list[i], Qt::CaseInsensitive)) {
return i;
}
}
break;
default:
return 0;
}
return -1;
}

101
src/database.h 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/>.
*/
#ifndef DATABASE_H
#define DATABASE_H
#include "cmarootobject.h"
#include <QList>
#include <QMap>
#include <QMutex>
#include <QObject>
#include <QTimer>
#include <vitamtp.h>
class Database : public QObject
{
Q_OBJECT
public:
typedef struct {
QList<CMAObject *>::const_iterator it;
QList<CMAObject *>::const_iterator end;
} find_data;
explicit Database();
~Database();
bool reload();
void setUUID(const QString uuid);
void addEntries(CMAObject *root);
CMAObject *ohfiToObject(int ohfi);
bool find(int ohfi, find_data &data);
void append(int parent_ohfi, CMAObject *object);
bool remove(const CMAObject *obj, int ohfi_root = 0);
int filterObjects(int ohfiParent, metadata_t **p_head, int index = 0, int max_number = 0);
CMAObject *pathToObject(const char *path, int ohfiRoot);
int acceptFilteredObject(const CMAObject *parent, const CMAObject *current, int type);
QMutex mutex;
private:
typedef QList<CMAObject *> root_list;
typedef QMap<int, root_list> map_list;
static const QStringList audio_types;
static const QStringList image_types;
static const QStringList video_types;
int create();
void destroy();
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);
int checkFileType(const QString path, int ohfi_root);
void dumpMetadataList(const metadata_t *p_head);
bool continueOperation();
// control variables
QMutex cancel;
bool cancel_operation;
QTimer *timer;
QThread *thread;
map_list object_list;
signals:
void fileAdded(QString);
void directoryAdded(QString);
void updated(int);
protected slots:
void process();
public slots:
void cancelOperation();
};
#endif // DATABASE_H

87
src/forms/backupitem.cpp Normal file
View File

@@ -0,0 +1,87 @@
/*
* 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 "backupitem.h"
#include "ui_backupitem.h"
#include "../utils.h"
#include <QDesktopServices>
#include <QUrl>
const QString BackupItem::nameTemplate = "<html><head/><body>"
"<p><span style=\" font-size:12pt; font-weight:600;\">%1</span></p>"
"<p><span style=\" font-size:10pt;\">%2</span></p>"
"</body></html>";
BackupItem::BackupItem(QWidget *parent) :
QWidget(parent),
ui(new Ui::BackupItem)
{
ui->setupUi(this);
// connect the buttons
connect(ui->openButton, SIGNAL(clicked()), this, SLOT(openDirectory()));
connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(removeEntry()));
}
BackupItem::~BackupItem()
{
delete ui;
}
void BackupItem::openDirectory()
{
QDesktopServices::openUrl(QUrl("file:///" + path));
}
void BackupItem::removeEntry()
{
emit deleteEntry(this);
}
const QPixmap *BackupItem::getIconPixmap()
{
return ui->itemPicture->pixmap();
}
void BackupItem::setDirectory(const QString path)
{
this->path = path;
}
void BackupItem::setItemInfo(const QString name, const QString size)
{
ui->itemName->setText(nameTemplate.arg(name, size));
}
int BackupItem::getIconWidth()
{
return ui->itemPicture->width();
}
void BackupItem::setItemIcon(const QString path, int width)
{
ui->itemPicture->setMinimumWidth(width);
ui->itemPicture->setPixmap(QPixmap(path));
}
bool BackupItem::lessThan(const BackupItem *s1, const BackupItem *s2)
{
return s1->title.compare(s2->title) < 0;
}

62
src/forms/backupitem.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* 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 BACKUPITEM_H
#define BACKUPITEM_H
#include <QWidget>
namespace Ui {
class BackupItem;
}
class BackupItem : public QWidget
{
Q_OBJECT
public:
explicit BackupItem(QWidget *parent = 0);
~BackupItem();
void setItemInfo(const QString name, const QString size);
void setItemIcon(const QString path, int width = 48);
void setDirectory(const QString path);
const QPixmap *getIconPixmap();
int getIconWidth();
static bool lessThan(const BackupItem *s1, const BackupItem *s2);
int row;
int ohfi;
QString title;
private:
QString path;
Ui::BackupItem *ui;
static const QString nameTemplate;
signals:
void deleteEntry(BackupItem *entry);
private slots:
void openDirectory();
void removeEntry();
};
#endif // BACKUPITEM_H

102
src/forms/backupitem.ui Normal file
View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BackupItem</class>
<widget class="QWidget" name="BackupItem">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>634</width>
<height>86</height>
</rect>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="itemPicture">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>0</width>
<height>48</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="itemName">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt; font-weight:600;&quot;&gt;Game Name&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;0.00 GiB&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Delete entry</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="openButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Open folder</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,221 @@
/*
* 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 "backupmanagerform.h"
#include "ui_backupmanagerform.h"
#include "../cmaobject.h"
#include "../sforeader.h"
#include "confirmdialog.h"
#include "../utils.h"
#include "filterlineedit.h"
#include <QDebug>
#include <QDialogButtonBox>
#include <QDir>
#include <QSettings>
#include <vitamtp.h>
BackupManagerForm::BackupManagerForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::BackupManagerForm)
{
ui->setupUi(this);
setupForm();
}
BackupManagerForm::~BackupManagerForm()
{
delete ui;
}
void BackupManagerForm::setupForm()
{
this->resize(800, 480);
connect(ui->backupComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(loadBackupListing(int)));
ui->tableWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
ui->tableWidget->horizontalHeader()->hide();
// the the account name when vitamtp returns this value
ui->accountBox->addItem(QSettings().value("lastOnlineId", tr("Default account")).toString());
}
void BackupManagerForm::removeEntry(BackupItem *item)
{
ConfirmDialog msgBox;
msgBox.setMessageText(tr("Are you sure to remove the backup of the following entry?"), item->title);
msgBox.setMessagePixmap(*item->getIconPixmap(), item->getIconWidth());
if(msgBox.exec() == 0) {
return;
}
QMutexLocker locker(&db->mutex);
CMAObject *obj = db->ohfiToObject(item->ohfi);
if(obj) {
obj->removeReferencedObject();
db->remove(obj);
}
ui->tableWidget->removeRow(item->row);
obj = db->ohfiToObject(obj->metadata.ohfiParent);
if(obj) {
setBackupUsage(obj->metadata.size);
}
}
void BackupManagerForm::setBackupUsage(quint64 size)
{
ui->usageLabel->setText(tr("Backup disk usage: %1").arg(readable_size(size, true)));
}
void BackupManagerForm::loadBackupListing(int index)
{
int ohfi;
bool sys_dir;
int img_width;
ui->tableWidget->clear();
switch(index) {
case 0:
ohfi = VITA_OHFI_VITAAPP;
img_width = 48;
sys_dir = true;
break;
case 1:
ohfi = VITA_OHFI_PSPAPP;
img_width = 80;
sys_dir = true;
break;
case 2:
ohfi = VITA_OHFI_PSMAPP;
img_width = 48;
sys_dir = true;
break;
case 3:
ohfi = VITA_OHFI_PSXAPP;
img_width = 48;
sys_dir = true;
break;
case 4:
ohfi = VITA_OHFI_PSPSAVE;
img_width = 80;
sys_dir = false;
break;
case 5:
ohfi = VITA_OHFI_BACKUP;
img_width = 48;
sys_dir = false;
break;
default:
ohfi = VITA_OHFI_VITAAPP;
img_width = 48;
sys_dir = true;
}
db->mutex.lock();
// get the item list
metadata_t *meta;
int row_count = db->filterObjects(ohfi, &meta);
ui->tableWidget->setRowCount(row_count);
// adjust the table item width to fill all the widget
QHeaderView *vert_header = ui->tableWidget->verticalHeader();
QHeaderView *horiz_header = ui->tableWidget->horizontalHeader();
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
horiz_header->setSectionResizeMode(QHeaderView::Stretch);
#else
horiz_header->setResizeMode(QHeaderView::Stretch);
#endif
CMAObject *obj = db->ohfiToObject(ohfi);
setBackupUsage(obj->metadata.size);
QList<BackupItem *> item_list;
while(meta) {
QString base_path = obj->path + QDir::separator() + meta->name;
QString parent_path = sys_dir ? base_path + QDir::separator() + "sce_sys" : base_path;
SfoReader reader;
QString game_name;
// retrieve the game name from the SFO
if(reader.load(QDir(parent_path).absoluteFilePath(sys_dir ? "param.sfo" : "PARAM.SFO"))) {
game_name = QString::fromUtf8(reader.value("TITLE", meta->name));
} else {
game_name = QString(meta->name);
}
BackupItem *item = new BackupItem();
// save the game title and ohfi for sorting/deleting
item->ohfi = meta->ohfi;
item->title = game_name;
connect(item, SIGNAL(deleteEntry(BackupItem*)), this, SLOT(removeEntry(BackupItem*)));
QString size = readable_size(meta->size);
// check if the game data is present, else is just a LiveArea launcher
if(sys_dir && !(QDir(base_path + QDir::separator() + "app").exists() || QDir(base_path + QDir::separator() + "game").exists())) {
size.append(tr(" - (Launcher only)"));
}
item->setItemInfo(game_name, size);
item->setItemIcon(QDir(parent_path).absoluteFilePath(sys_dir ? "icon0.png" : "ICON0.PNG"), img_width);
item->setDirectory(obj->path + QDir::separator() + meta->name);
item->resize(646, 70);
item_list << item;
meta = meta->next_metadata;
}
qSort(item_list.begin(), item_list.end(), BackupItem::lessThan);
int row;
QList<BackupItem *>::iterator it;
vert_header->setUpdatesEnabled(false);
// insert the sorted items into the table
for(it = item_list.begin(), row = 0; it != item_list.end(); ++it, ++row) {
(*it)->row = row;
ui->tableWidget->setCellWidget(row, 0, *it);
vert_header->resizeSection(row, 70);
}
vert_header->setUpdatesEnabled(true);
db->mutex.unlock();
// apply filter
this->on_filterLineEdit_textChanged(ui->filterLineEdit->text());
}
void BackupManagerForm::on_filterLineEdit_textChanged(const QString &arg1)
{
if(arg1 != tr("Filter")) {
for(int i = 0; i < ui->tableWidget->rowCount(); ++i) {
BackupItem *item = (BackupItem*) ui->tableWidget->cellWidget(i, 0);
if(item->title.contains(arg1, Qt::CaseInsensitive)) {
ui->tableWidget->setRowHidden(i, false);
} else {
ui->tableWidget->setRowHidden(i, true);
}
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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 BACKUPMANAGERFORM_H
#define BACKUPMANAGERFORM_H
#include "../database.h"
#include "backupitem.h"
#include <QWidget>
namespace Ui {
class BackupManagerForm;
}
class BackupManagerForm : public QWidget
{
Q_OBJECT
public:
explicit BackupManagerForm(QWidget *parent = 0);
~BackupManagerForm();
Database *db;
private:
void setupForm();
void setBackupUsage(quint64 size);
Ui::BackupManagerForm *ui;
public slots:
void loadBackupListing(int index);
void removeEntry(BackupItem *item);
private slots:
void on_filterLineEdit_textChanged(const QString &arg1);
};
#endif // BACKUPMANAGERFORM_H

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BackupManagerForm</class>
<widget class="QWidget" name="BackupManagerForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>857</width>
<height>478</height>
</rect>
</property>
<property name="windowTitle">
<string>Backup Manager</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Online ID / Username</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="accountBox"/>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Backup Type</string>
</property>
<property name="margin">
<number>2</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="backupComboBox">
<item>
<property name="text">
<string>PS Vita Games</string>
</property>
</item>
<item>
<property name="text">
<string>PSP Games</string>
</property>
</item>
<item>
<property name="text">
<string>PSM Games</string>
</property>
</item>
<item>
<property name="text">
<string>PSOne Games</string>
</property>
</item>
<item>
<property name="text">
<string>PSP Savedatas</string>
</property>
</item>
<item>
<property name="text">
<string>Backups</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QTableWidget" name="tableWidget">
<property name="columnCount">
<number>1</number>
</property>
<column/>
</widget>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="usageLabel">
<property name="text">
<string>Backup disk usage</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="FilterLineEdit" name="filterLineEdit">
<property name="styleSheet">
<string notr="true">color:gray;font-style:italic</string>
</property>
<property name="text">
<string>Filter</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FilterLineEdit</class>
<extends>QLineEdit</extends>
<header>filterlineedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

150
src/forms/configwidget.cpp Normal file
View File

@@ -0,0 +1,150 @@
/*
* 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 "configwidget.h"
#include "ui_configwidget.h"
extern "C" {
#include <vitamtp.h>
}
#include <QFileDialog>
#include <QSettings>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QStandardPaths>
#else
#include <QDesktopServices>
#define QStandardPaths QDesktopServices
#define writableLocation storageLocation
#endif
ConfigWidget::ConfigWidget(QWidget *parent) :
QDialog(parent),
ui(new Ui::ConfigWidget)
{
ui->setupUi(this);
connectSignals();
setDefaultDirs();
}
void ConfigWidget::connectSignals()
{
QSignalMapper *mapper = new QSignalMapper(this);
mapper->setMapping(ui->photoBtn, BTN_PHOTO);
mapper->setMapping(ui->musicBtn, BTN_MUSIC);
mapper->setMapping(ui->videoBtn, BTN_VIDEO);
mapper->setMapping(ui->appBtn, BTN_APPS);
mapper->setMapping(ui->urlBtn, BTN_URL);
connect(ui->photoBtn, SIGNAL(clicked()), mapper, SLOT(map()));
connect(ui->musicBtn, SIGNAL(clicked()), mapper, SLOT(map()));
connect(ui->videoBtn, SIGNAL(clicked()), mapper, SLOT(map()));
connect(ui->appBtn, SIGNAL(clicked()), mapper, SLOT(map()));
connect(ui->urlBtn, SIGNAL(clicked()), mapper, SLOT(map()));
connect(mapper, SIGNAL(mapped(int)), this, SLOT(browseBtnPressed(int)));
connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
}
void ConfigWidget::setDefaultDirs()
{
QString defaultdir;
QSettings settings;
defaultdir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
ui->photoPath->setText(settings.value("photoPath", defaultdir).toString());
defaultdir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
ui->musicPath->setText(settings.value("musicPath", defaultdir).toString());
defaultdir = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation);
ui->videoPath->setText(settings.value("videoPath", defaultdir).toString());
defaultdir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
defaultdir.append(QDir::separator()).append("PS Vita");
ui->appPath->setText(settings.value("appsPath", defaultdir).toString());
defaultdir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
defaultdir.append(QDir::separator()).append("PSV Updates");
ui->urlPath->setText(settings.value("urlPath", defaultdir).toString());
}
ConfigWidget::~ConfigWidget()
{
delete ui;
}
void ConfigWidget::browseBtnPressed(int btn)
{
QString msg;
QLineEdit *lineedit;
switch(btn) {
case BTN_PHOTO:
lineedit = ui->photoPath;
msg = tr("Select the folder to be used as a photo source");
break;
case BTN_MUSIC:
lineedit = ui->musicPath;
msg = tr("Select the folder to be used as a music source");
break;
case BTN_VIDEO:
lineedit = ui->videoPath;
msg = tr("Select the folder to be used as a video source");
break;
case BTN_APPS:
lineedit = ui->appPath;
msg = tr("Select the folder to be used to save PS Vita games and backups");
break;
case BTN_URL:
lineedit = ui->urlPath;
msg = tr("Select the folder to be used to fetch software updates");
break;
default:
return;
}
QString selected = QFileDialog::getExistingDirectory(this, msg, lineedit->text(), QFileDialog::ShowDirsOnly);
if(!selected.isEmpty()) {
lineedit->setText(selected);
}
}
void ConfigWidget::savePath(QSettings &settings, const QLineEdit *edit, const QString &key)
{
QString path = edit->text();
if(path.endsWith(QDir::separator())) {
path.chop(1);
}
settings.setValue(key, path);
QDir(QDir::root()).mkpath(path);
}
void ConfigWidget::accept()
{
QSettings settings;
savePath(settings, ui->photoPath, "photoPath");
savePath(settings, ui->musicPath, "musicPath");
savePath(settings, ui->videoPath, "videoPath");
savePath(settings, ui->appPath, "appsPath");
savePath(settings, ui->urlPath, "urlPath");
settings.sync();
done(Accepted);
}

54
src/forms/configwidget.h Normal file
View File

@@ -0,0 +1,54 @@
/*
* 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 CONFIGWIDGET_H
#define CONFIGWIDGET_H
#include <QDialog>
#include <QLineEdit>
#include <QSettings>
#include <QSignalMapper>
namespace Ui {
class ConfigWidget;
}
class ConfigWidget : public QDialog
{
Q_OBJECT
public:
explicit ConfigWidget(QWidget *parent = 0);
~ConfigWidget();
private:
enum browse_buttons {BTN_PHOTO, BTN_MUSIC, BTN_VIDEO, BTN_APPS, BTN_URL};
void connectSignals();
void setDefaultDirs();
void savePath(QSettings &settings, const QLineEdit *edit, const QString &key);
Ui::ConfigWidget *ui;
private slots:
void browseBtnPressed(int from);
void accept();
};
#endif // CONFIGWIDGET_H

342
src/forms/configwidget.ui Normal file
View File

@@ -0,0 +1,342 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigWidget</class>
<widget class="QWidget" name="ConfigWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>405</height>
</rect>
</property>
<property name="windowTitle">
<string>QCMA Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Folders</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Specify the folders that the PS Vita will access for each content type</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="whatsThis">
<string>This is the location your Screenshots and Pictures are Saved to/Imported from.</string>
</property>
<property name="text">
<string>Photo Folder</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="photoPath">
<property name="toolTip">
<string>This is the location your Screenshots and Pictures are Saved to/Imported from.</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="photoBtn">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="whatsThis">
<string>This is the location your Videos are Saved to/Imported from.</string>
</property>
<property name="text">
<string>Video Folder</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLineEdit" name="videoPath">
<property name="toolTip">
<string>This is the location your Videos are Saved to/Imported from.</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="videoBtn">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="label_4">
<property name="whatsThis">
<string>This is the location your Music is Saved to/Imported from.</string>
</property>
<property name="text">
<string>Music Folder</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLineEdit" name="musicPath">
<property name="toolTip">
<string>This is the location your Music is Saved to/Imported from.</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="musicBtn">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLabel" name="label_6">
<property name="whatsThis">
<string>This is the location your Games, Apps, Savegames, and System Backups are Saved to/Imported from.</string>
</property>
<property name="text">
<string>Applications / Backups</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="appPath">
<property name="toolTip">
<string>This is the location your Games, Apps, Savegames, and System Backups are Saved to/Imported from.</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="appBtn">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="label_5">
<property name="whatsThis">
<string>This is the location your Software Updates and Browser Data is Saved to/Imported from.</string>
</property>
<property name="text">
<string>Updates / Web content</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLineEdit" name="urlPath">
<property name="toolTip">
<string>This is the location your Software Updates and Browser Data is Saved to/Imported from.</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="urlBtn">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Other</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="0">
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Advanced settings&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Offline Mode</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Skip metadata extraction</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_3">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Update database automatically when files on the PC are changed</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_4">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Disable USB monitoring</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_5">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Disable Wi-Fi monitoring</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Database backend</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBox">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>In Memory</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,50 @@
/*
* 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 "confirmdialog.h"
#include "ui_confirmdialog.h"
const QString ConfirmDialog::messageTemplate = "<html><head/><body>"
"<p><span style=\"font-size:10pt;\">%1</span></p>"
"<p><span style=\"font-size:12pt; font-weight:600;\">%2</span></p>"
"</body></html>";
ConfirmDialog::ConfirmDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::ConfirmDialog)
{
ui->setupUi(this);
this->layout()->setSizeConstraint(QLayout::SetFixedSize);
}
void ConfirmDialog::setMessageText(const QString message, const QString game_title)
{
ui->confirmText->setText(messageTemplate.arg(message, game_title));
}
void ConfirmDialog::setMessagePixmap(const QPixmap &pixmap, int width)
{
ui->itemPicture->setPixmap(pixmap);
ui->itemPicture->setMinimumWidth(width);
}
ConfirmDialog::~ConfirmDialog()
{
delete ui;
}

46
src/forms/confirmdialog.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* 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 CONFIRMDIALOG_H
#define CONFIRMDIALOG_H
#include <QDialog>
namespace Ui {
class ConfirmDialog;
}
class ConfirmDialog : public QDialog
{
Q_OBJECT
public:
explicit ConfirmDialog(QWidget *parent = 0);
~ConfirmDialog();
void setMessageText(const QString message, const QString game_title);
void setMessagePixmap(const QPixmap &pixmap, int width);
static const QString messageTemplate;
private:
Ui::ConfirmDialog *ui;
};
#endif // CONFIRMDIALOG_H

120
src/forms/confirmdialog.ui Normal file
View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfirmDialog</class>
<widget class="QDialog" name="ConfirmDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>519</width>
<height>106</height>
</rect>
</property>
<property name="windowTitle">
<string>Confirmation Message</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="itemPicture">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>0</width>
<height>48</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="confirmText">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Are you sure to delete the backup of the following game?&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt; font-weight:600;&quot;&gt;Game Name&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;
</string>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfirmDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfirmDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

71
src/forms/pinform.cpp Normal file
View File

@@ -0,0 +1,71 @@
/*
* 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 "pinform.h"
#include "ui_pinform.h"
#include <QDebug>
#include <QDesktopWidget>
const QString PinForm::pinFormat =
"<html><head/><body>"
"<p><span style=\"font-size:24pt; font-weight:600;\">%1</span></p>"
"</body></html>";
PinForm::PinForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::PinForm)
{
ui->setupUi(this);
move(QApplication::desktop()->screen()->rect().center() - rect().center());
setFixedSize(size());
setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint);
connect(ui->cancelButton, SIGNAL(clicked()), this, SLOT(hide()));
}
void PinForm::setPin(QString name, int pin)
{
qDebug() << "Got pin from user " << name;
ui->deviceLabel->setText(tr("Device: %1 (PS Vita)").arg(name));
ui->pinLabel->setText(pinFormat.arg(QString::number(pin), 8, QChar('0')));
show();
}
void PinForm::startCountdown()
{
timer.setInterval(1000);
counter = 300;
connect(&timer, SIGNAL(timeout()), this, SLOT(decreaseTimer()));
timer.start();
}
void PinForm::decreaseTimer()
{
counter--;
if(counter == 0) {
timer.stop();
hide();
}
ui->timeLabel->setText(tr("Time remaining: %1 seconds").arg(counter));
}
PinForm::~PinForm()
{
delete ui;
}

55
src/forms/pinform.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* 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 PINFORM_H
#define PINFORM_H
#include <QTimer>
#include <QWidget>
namespace Ui {
class PinForm;
}
class PinForm : public QWidget
{
Q_OBJECT
public:
explicit PinForm(QWidget *parent = 0);
~PinForm();
private:
Ui::PinForm *ui;
// pin timeout
int counter;
QTimer timer;
static const QString pinFormat;
public slots:
void startCountdown();
void setPin(QString name, int pin);
private slots:
void decreaseTimer();
};
#endif // PINFORM_H

115
src/forms/pinform.ui Normal file
View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PinForm</class>
<widget class="QWidget" name="PinForm">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>526</width>
<height>216</height>
</rect>
</property>
<property name="windowTitle">
<string>Device pairing</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>An unregistered PS Vita system is connecting with QCMA via Wi-Fi</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deviceLabel">
<property name="text">
<string>Device: PS Vita</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Input the following number in the PS Vita system to register it with QCMA</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pinLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;12345678&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="timeLabel">
<property name="text">
<string>Time remaining: 300 seconds</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,64 @@
/*
* 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 "progressform.h"
#include "ui_progressform.h"
#include <QDesktopWidget>
#include <QMessageBox>
ProgressForm::ProgressForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::ProgressForm)
{
ui->setupUi(this);
move(QApplication::desktop()->screen()->rect().center() - rect().center());
setFixedSize(size());
setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint);
connect(ui->cancelButton, SIGNAL(clicked()), this, SLOT(cancelConfirm()));
}
ProgressForm::~ProgressForm()
{
delete ui;
}
void ProgressForm::cancelConfirm()
{
QMessageBox box;
box.setText(tr("Database indexing in progress"));
box.setInformativeText(tr("Are you sure to cancel the database indexing?"));
box.setIcon(QMessageBox::Warning);
box.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
if(box.exec() == QMessageBox::Ok) {
emit canceled();
}
}
void ProgressForm::setFileName(QString file)
{
QString elided = ui->fileLabel->fontMetrics().elidedText(file, Qt::ElideMiddle, ui->fileLabel->width(), 0);
ui->fileLabel->setText(elided);
}
void ProgressForm::setDirectoryName(QString dir)
{
QString elided = ui->directoryLabel->fontMetrics().elidedText(dir, Qt::ElideMiddle, ui->directoryLabel->width(), 0);
ui->directoryLabel->setText(elided);
}

51
src/forms/progressform.h Normal file
View File

@@ -0,0 +1,51 @@
/*
* 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 PROGRESSFORM_H
#define PROGRESSFORM_H
#include <QWidget>
namespace Ui {
class ProgressForm;
}
class ProgressForm : public QWidget
{
Q_OBJECT
public:
explicit ProgressForm(QWidget *parent = 0);
~ProgressForm();
private:
Ui::ProgressForm *ui;
signals:
void canceled();
private slots:
void cancelConfirm();
public slots:
void setDirectoryName(QString dir);
void setFileName(QString file);
};
#endif // PROGRESSFORM_H

80
src/forms/progressform.ui Normal file
View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProgressForm</class>
<widget class="QWidget" name="ProgressForm">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>602</width>
<height>138</height>
</rect>
</property>
<property name="windowTitle">
<string>Refreshing database...</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;Reading directory:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="directoryLabel">
<property name="text">
<string>directory name</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;Processing file:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fileLabel">
<property name="text">
<string>file name</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

106
src/main.cpp Normal file
View File

@@ -0,0 +1,106 @@
/*
* 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 Q_OS_WIN32
#include <signal.h>
#endif
#include <QDebug>
#include <QLocale>
#include <QThread>
#include <QTranslator>
#include "singleapplication.h"
#include "mainwidget.h"
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
void noMessageOutput(QtMsgType type, const QMessageLogContext &, const QString & str)
{
const char * msg = str.toStdString().c_str();
#else
void noMessageOutput(QtMsgType type, const char *msg)
{
#endif
Q_UNUSED(type);
Q_UNUSED(msg);
}
int main(int argc, char *argv[])
{
if(SingleApplication::sendMessage(QObject::tr("A instance of QCMA is already running"))) {
return 0;
}
SingleApplication app(argc, argv);
#ifndef Q_OS_WIN32
// FIXME: libmtp sends SIGPIPE if a socket write fails crashing the whole app
// the proper fix is to libmtp to handle the cancel properly or ignoring
// SIGPIPE on the socket
signal(SIGPIPE, SIG_IGN);
#endif
if(app.arguments().contains("--with-debug")) {
VitaMTP_Set_Logging(VitaMTP_DEBUG);
} else if(app.arguments().contains("--verbose")) {
VitaMTP_Set_Logging(VitaMTP_VERBOSE);
} else {
VitaMTP_Set_Logging(VitaMTP_NONE);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
qInstallMessageHandler(noMessageOutput);
#else
qInstallMsgHandler(noMessageOutput);
#endif
}
qDebug("Starting QCMA %s", QCMA_VER);
QTranslator translator;
QString locale = QLocale().system().name();
qDebug("Current locale: %s", locale.toUtf8().data());
if(app.arguments().contains("--set-locale")) {
int index = app.arguments().indexOf("--set-locale");
if(index + 1 < app.arguments().length()) {
qDebug("Enforcing locale: %s", app.arguments().at(index + 1).toUtf8().data());
locale = app.arguments().at(index + 1);
}
}
if(translator.load("qcma." + locale, ":/main/resources/translations")) {
app.installTranslator(&translator);
}
qDebug("Starting main thread: %lu", (unsigned long)QThread::currentThreadId());
// set the organization/application for QSettings to work properly
app.setOrganizationName("qcma");
app.setApplicationName("qcma");
//TODO: check if this is actually needed since we don't have a main window by default
QApplication::setQuitOnLastWindowClosed(false);
MainWidget widget;
widget.prepareApplication();
// receive the message from another process
QObject::connect(&app, SIGNAL(messageAvailable(QString)), &widget, SLOT(receiveMessage(QString)));
return app.exec();
}

186
src/mainwidget.cpp Normal file
View File

@@ -0,0 +1,186 @@
/*
* 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 "mainwidget.h"
#include "cmaclient.h"
#include "cmaevent.h"
#include "utils.h"
#include <QAction>
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QGridLayout>
#include <QMenu>
#include <QMessageBox>
#include <QSettings>
#include <QTimer>
#include <QSettings>
#include <QSpacerItem>
const QStringList MainWidget::path_list = QStringList() << "photoPath" << "musicPath" << "videoPath" << "appsPath" << "urlPath";
MainWidget::MainWidget(QWidget *parent) :
QWidget(parent)
{
}
void MainWidget::checkSettings()
{
QSettings settings;
// make sure that all the paths are set, else show the config dialog
foreach(const QString &path, path_list) {
if(!settings.contains(path)) {
first_run = true;
dialog.show();
return;
}
}
first_run = false;
manager.start();
}
void MainWidget::dialogResult(int result)
{
if(result == QDialog::Accepted) {
if(first_run) {
first_run = false;
manager.start();
}
} else if(first_run) {
qApp->quit();
}
}
void MainWidget::stopServer()
{
setTrayTooltip(tr("Shutting down..."));
receiveMessage(tr("Stopping QCMA..."));
manager.stop();
}
void MainWidget::deviceDisconnect()
{
setTrayTooltip(tr("Disconnected"));
receiveMessage(tr("The device has been disconnected"));
}
void MainWidget::prepareApplication()
{
connectSignals();
createTrayIcon();
checkSettings();
}
void MainWidget::connectSignals()
{
connect(&dialog, SIGNAL(finished(int)), this, SLOT(dialogResult(int)));
connect(&manager, SIGNAL(stopped()), qApp, SLOT(quit()));
connect(&manager, SIGNAL(deviceConnected(QString)), this, SLOT(receiveMessage(QString)));
connect(&manager, SIGNAL(deviceConnected(QString)), this, SLOT(setTrayTooltip(QString)));
connect(&manager, SIGNAL(deviceDisconnected()), this, SLOT(deviceDisconnect()));
connect(&manager, SIGNAL(messageSent(QString)), this, SLOT(receiveMessage(QString)));
form.db = &manager.db;
}
void MainWidget::setTrayTooltip(QString message)
{
trayIcon->setToolTip(message);
}
void MainWidget::openManager()
{
form.loadBackupListing(0);
form.show();
}
void MainWidget::showAboutDialog()
{
QMessageBox about;
about.setText(QString("QCMA ") + QCMA_VER);
about.setWindowTitle(tr("About QCMA"));
about.setInformativeText(tr("Copyright (C) 2013 Codestation") + "\n");
about.setStandardButtons(QMessageBox::Ok);
about.setIconPixmap(QPixmap(":/main/images/qcma.png"));
about.setDefaultButton(QMessageBox::Ok);
// hack to expand the messagebox minimum size
QSpacerItem* horizontalSpacer = new QSpacerItem(300, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
QGridLayout* layout = (QGridLayout*)about.layout();
layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
about.show();
about.exec();
}
void MainWidget::showAboutQt()
{
QMessageBox::aboutQt(this);
}
void MainWidget::createTrayIcon()
{
options = new QAction(tr("&Settings"), this);
reload = new QAction(tr("&Refresh database"), this);
backup = new QAction(tr("&Backup Manager"), this);
about = new QAction(tr("&About QCMA"), this);
about_qt = new QAction(tr("Abou&t Qt"), this);
quit = new QAction(tr("&Quit"), this);
connect(options, SIGNAL(triggered()), &dialog, SLOT(open()));
connect(backup, SIGNAL(triggered()), this, SLOT(openManager()));
connect(reload, SIGNAL(triggered()), &manager, SLOT(refreshDatabase()));
connect(about, SIGNAL(triggered()), this, SLOT(showAboutDialog()));
connect(about_qt, SIGNAL(triggered()), this, SLOT(showAboutQt()));
connect(quit, SIGNAL(triggered()), this, SLOT(stopServer()));
QMenu *trayIconMenu = new QMenu(this);
trayIconMenu->addAction(options);
trayIconMenu->addAction(reload);
trayIconMenu->addAction(backup);
trayIconMenu->addSeparator();
trayIconMenu->addAction(about);
trayIconMenu->addAction(about_qt);
trayIconMenu->addAction(quit);
trayIcon = new QSystemTrayIcon(this);
trayIcon->setContextMenu(trayIconMenu);
#ifndef Q_OS_WIN32
trayIcon->setIcon(QIcon(":/main/images/psv_icon.png"));
#else
trayIcon->setIcon(QIcon(":/main/images/psv_icon_16.png"));
#endif
trayIcon->show();
// try to avoid the iconTray Qt bug
Sleeper::sleep(1);
}
void MainWidget::receiveMessage(QString message)
{
if(trayIcon->isVisible()) {
trayIcon->showMessage(tr("Information"), message);
}
}
MainWidget::~MainWidget()
{
trayIcon->hide();
}

78
src/mainwidget.h Normal file
View File

@@ -0,0 +1,78 @@
/*
* 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 MAINWIDGET_H
#define MAINWIDGET_H
#include "cmaclient.h"
#include "clientmanager.h"
#include "forms/configwidget.h"
#include "forms/backupmanagerform.h"
#include "forms/progressform.h"
#include <QAction>
#include <QWidget>
#include <QSystemTrayIcon>
#include <vitamtp.h>
class MainWidget : public QWidget
{
Q_OBJECT
public:
explicit MainWidget(QWidget *parent = 0);
~MainWidget();
void prepareApplication();
private:
void connectSignals();
void createTrayIcon();
void checkSettings();
bool first_run;
// forms
ConfigWidget dialog;
ClientManager manager;
BackupManagerForm form;
//system tray
QAction *quit;
QAction *reload;
QAction *options;
QAction *backup;
QAction *about;
QAction *about_qt;
QSystemTrayIcon *trayIcon;
const static QStringList path_list;
private slots:
void stopServer();
void openManager();
void showAboutQt();
void showAboutDialog();
void deviceDisconnect();
void dialogResult(int result);
void receiveMessage(QString message);
void setTrayTooltip(QString message);
};
#endif // MAINWIDGET_H

51
src/sforeader.cpp Normal file
View File

@@ -0,0 +1,51 @@
/*
* 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 "sforeader.h"
#include <QFile>
SfoReader::SfoReader()
{
}
bool SfoReader::load(const QString &path)
{
QFile file(path);
if(file.open(QIODevice::ReadOnly)) {
data = file.readAll();
key_offset = data.constData();
header = (sfo_header *)key_offset;
index = (sfo_index *)(key_offset + sizeof(sfo_header));
return true;
}
return false;
}
const char *SfoReader::value(const char *key, const char *defaultValue)
{
const char *base_key = key_offset + header->key_offset;
for(uint i = 0; i < header->pair_count; i++) {
const char *curr_key = base_key + index[i].key_offset;
if(strcmp(key, curr_key) == 0) {
return key_offset + header->value_offset + index[i].data_offset;
}
}
return defaultValue;
}

56
src/sforeader.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 SFOREADER_H
#define SFOREADER_H
#include <QString>
class SfoReader
{
public:
SfoReader();
bool load(const QString &path);
const char *value(const char *key, const char *defaultValue);
private:
typedef struct {
quint16 key_offset;
uchar alignment;
uchar data_type;
quint32 value_size;
quint32 value_size_with_padding;
quint32 data_offset;
} __attribute__((packed)) sfo_index;
typedef struct {
char id[4];
quint32 version;
quint32 key_offset;
quint32 value_offset;
quint32 pair_count;
} __attribute__((packed)) sfo_header;
QByteArray data;
const char *key_offset;
const sfo_header *header;
const sfo_index *index;
};
#endif // SFOREADER_H

69
src/singleapplication.cpp Normal file
View File

@@ -0,0 +1,69 @@
/*
* 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 "singleapplication.h"
#include <QDebug>
const int SingleApplication::timeout = 500;
const QString SingleApplication::SHARED_KEY = "QCMA_KEY";
SingleApplication::SingleApplication(int &argc, char **argv) :
QApplication(argc, argv)
{
server = new QLocalServer(this);
connect(server, SIGNAL(newConnection()), this, SLOT(receiveMessage()));
QLocalServer::removeServer(SHARED_KEY);
server->listen(SHARED_KEY);
}
void SingleApplication::receiveMessage()
{
QLocalSocket *socket = server->nextPendingConnection();
if(!socket->waitForReadyRead(timeout)) {
qDebug() << socket->errorString();
return;
}
QByteArray byteArray = socket->readAll();
QString message = QString::fromUtf8(byteArray.constData());
emit messageAvailable(message);
socket->disconnectFromServer();
}
bool SingleApplication::sendMessage(const QString &message)
{
QLocalSocket socket;
socket.connectToServer(SHARED_KEY, QIODevice::WriteOnly);
if(!socket.waitForConnected(timeout)) {
return false;
}
socket.write(message.toUtf8());
if(!socket.waitForBytesWritten(timeout)) {
qDebug() << socket.errorString();
return false;
}
socket.disconnectFromServer();
return true;
}

50
src/singleapplication.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* 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 SINGLEAPPLICATION_H
#define SINGLEAPPLICATION_H
#include <QApplication>
#include <QLocalSocket>
#include <QLocalServer>
#include <QSharedMemory>
class SingleApplication : public QApplication
{
Q_OBJECT
public:
explicit SingleApplication(int &argc, char **argv);
static bool sendMessage(const QString &message);
private:
QLocalServer *server;
static const int timeout;
static const QString SHARED_KEY;
signals:
void messageAvailable(QString message);
public slots:
void receiveMessage();
};
#endif // SINGLEAPPLICATION_H

177
src/utils.cpp Normal file
View File

@@ -0,0 +1,177 @@
/*
* 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 "utils.h"
#include "avdecoder.h"
#include <QBuffer>
#include <QDebug>
#include <QDir>
#include <QImage>
#ifdef Q_OS_WIN32
#include <windows.h>
#else
#include <sys/statvfs.h>
#endif
bool getDiskSpace(const QString &dir, quint64 *free, quint64 *total)
{
#ifdef Q_OS_WIN32
if(GetDiskFreeSpaceEx(dir.toStdWString().c_str(), (ULARGE_INTEGER *)free, (ULARGE_INTEGER *)total, NULL) != 0) {
return true;
}
#else
struct statvfs stat;
if(statvfs(dir.toUtf8().data(), &stat) == 0) {
*total = stat.f_frsize * stat.f_blocks;
*free = stat.f_frsize * stat.f_bfree;
return true;
}
#endif
return false;
}
bool removeRecursively(const QString &dirName)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
return QDir(dirName).removeRecursively();
#else
bool result = false;
QDir dir(dirName);
if(dir.exists(dirName)) {
Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
if(info.isDir()) {
result = removeRecursively(info.absoluteFilePath());
} else {
result = QFile::remove(info.absoluteFilePath());
}
if(!result) {
return result;
}
}
result = dir.rmdir(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 {
AVDecoder decoder;
if(decoder.open(path)) {
data = decoder.getAudioThumbnail(256, 256);
metadata->data.thumbnail.width = 256;
metadata->data.thumbnail.height = 256;
}
}
} else if(MASK_SET(type, Video)) {
AVDecoder decoder;
if(decoder.open(path)) {
data = decoder.getVideoThumbnail(256, 256);
metadata->data.thumbnail.width = 256;
metadata->data.thumbnail.height = 256;
}
}
return data;
}
QString readable_size(quint64 size, bool use_gib)
{
QStringList list;
list << "KiB" << "MiB";
if(use_gib) {
list << "GiB";
}
QStringListIterator i(list);
QString unit("bytes");
float size_f = size;
while(size_f >= 1024.0 && i.hasNext()) {
unit = i.next();
size_f /= 1024.0;
}
return QString().setNum(size_f,'f',2) + " " + unit;
}

53
src/utils.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* 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 UTILS_H
#define UTILS_H
#include <QByteArray>
#include <QString>
#include <QThread>
#include <vitamtp.h>
// Qt4 doesn't have public methods for Thread::*sleep
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
typedef QThread Sleeper;
#else
class Sleeper : QThread
{
public:
static void sleep(unsigned long secs) {
QThread::sleep(secs);
}
static void msleep(unsigned long msecs) {
QThread::msleep(msecs);
}
static void usleep(unsigned long usecs) {
QThread::usleep(usecs);
}
};
#endif
bool removeRecursively(const QString &dirName);
QString readable_size(quint64 size, bool use_gib = false);
bool getDiskSpace(const QString &dir, quint64 *free, quint64 *total);
QByteArray getThumbnail(const QString &path, DataType type, metadata_t *metadata);
#endif // UTILS_H