diff --git a/qcma.pro b/qcma.pro index 42986a8..604c773 100644 --- a/qcma.pro +++ b/qcma.pro @@ -7,7 +7,8 @@ QT += core \ gui \ widgets \ - network + network \ + sql TARGET = qcma @@ -17,10 +18,9 @@ TEMPLATE = app SOURCES += src/main.cpp \ src/capability.cpp \ - src/database.cpp \ src/cmaobject.cpp \ src/cmarootobject.cpp \ - src/utils.cpp \ + src/cmautils.cpp \ src/mainwidget.cpp \ src/singleapplication.cpp \ src/sforeader.cpp \ @@ -31,21 +31,23 @@ SOURCES += src/main.cpp \ src/clientmanager.cpp \ src/filterlineedit.cpp \ src/dds.cpp \ + src/sqlitedb.cpp \ + src/httpdownloader.cpp \ + src/qlistdb.cpp \ + src/database.cpp \ # forms src/forms/backupitem.cpp \ src/forms/backupmanagerform.cpp \ src/forms/configwidget.cpp \ src/forms/confirmdialog.cpp \ src/forms/pinform.cpp \ - src/forms/progressform.cpp \ - src/httpdownloader.cpp + src/forms/progressform.cpp HEADERS += \ src/capability.h \ - src/database.h \ src/cmaobject.h \ src/cmarootobject.h \ - src/utils.h \ + src/cmautils.h \ src/mainwidget.h \ src/singleapplication.h \ src/sforeader.h \ @@ -56,14 +58,17 @@ HEADERS += \ src/clientmanager.h \ src/filterlineedit.h \ src/dds.h \ + src/sqlitedb.h \ + src/httpdownloader.h \ + src/qlistdb.h \ + src/database.h \ # forms src/forms/backupitem.h \ src/forms/backupmanagerform.h \ src/forms/configwidget.h \ src/forms/confirmdialog.h \ src/forms/pinform.h \ - src/forms/progressform.h \ - src/httpdownloader.h + src/forms/progressform.h FORMS += \ src/forms/configwidget.ui \ diff --git a/src/avdecoder.cpp b/src/avdecoder.cpp index 2f70bff..c5b0142 100644 --- a/src/avdecoder.cpp +++ b/src/avdecoder.cpp @@ -17,9 +17,9 @@ * along with this program. If not, see . */ -#include "utils.h" +#include "cmautils.h" #include "avdecoder.h" -#include "cmaobject.h" +#include "database.h" #include #include diff --git a/src/clientmanager.cpp b/src/clientmanager.cpp index 3b2e6b5..7cce9d9 100644 --- a/src/clientmanager.cpp +++ b/src/clientmanager.cpp @@ -19,15 +19,15 @@ #include "clientmanager.h" #include "cmaclient.h" -#include "utils.h" +#include "cmautils.h" #include "forms/progressform.h" #include #include -ClientManager::ClientManager(QObject *parent) : - QObject(parent) +ClientManager::ClientManager(Database *db, QObject *parent) : + QObject(parent), m_db(db) { } @@ -63,11 +63,10 @@ void ClientManager::start() // 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); + connect(m_db, SIGNAL(fileAdded(QString)), &progress, SLOT(setFileName(QString))); + connect(m_db, SIGNAL(directoryAdded(QString)), &progress, SLOT(setDirectoryName(QString))); + connect(m_db, SIGNAL(updated(int)), this, SLOT(databaseUpdated(int))); + connect(&progress, SIGNAL(canceled()), m_db, SLOT(cancelOperation()), Qt::DirectConnection); thread_count = 0; qDebug("Starting cma threads"); @@ -76,7 +75,7 @@ void ClientManager::start() if(!settings.value("disableUSB", false).toBool()) { usb_thread = new QThread(); - client = new CmaClient(); + client = new CmaClient(m_db); usb_thread->setObjectName("usb_thread"); connect(usb_thread, SIGNAL(started()), client, SLOT(connectUsb())); connect(client, SIGNAL(messageSent(QString)), this, SIGNAL(messageSent(QString))); @@ -96,7 +95,7 @@ void ClientManager::start() if(!settings.value("disableWireless", false).toBool()) { wireless_thread = new QThread(); - client = new CmaClient(); + client = new CmaClient(m_db); wireless_thread->setObjectName("wireless_thread"); connect(wireless_thread, SIGNAL(started()), client, SLOT(connectWireless())); connect(client, SIGNAL(messageSent(QString)), this, SIGNAL(messageSent(QString))); @@ -123,13 +122,12 @@ void ClientManager::start() void ClientManager::refreshDatabase() { - bool prepared; - if(!db.reload(prepared)) { - if(prepared) { - emit messageSent(tr("Cannot refresh the database while is in use")); - } else { - emit messageSent(tr("No PS Vita system has been registered")); - } + if(m_db->load()) { + return; + } + + if(!m_db->rescan()) { + emit messageSent(tr("No PS Vita system has been registered")); } else { progress.showDelayed(1000); } diff --git a/src/clientmanager.h b/src/clientmanager.h index 9ff8792..40fa562 100644 --- a/src/clientmanager.h +++ b/src/clientmanager.h @@ -20,7 +20,7 @@ #ifndef CLIENTMANAGER_H #define CLIENTMANAGER_H -#include "database.h" +#include "qlistdb.h" #include "forms/pinform.h" #include "forms/progressform.h" @@ -31,18 +31,18 @@ class ClientManager : public QObject { Q_OBJECT public: - explicit ClientManager(QObject *parent = 0); + explicit ClientManager(Database *db, QObject *parent = 0); ~ClientManager(); void start(); void stop(); - Database db; - private: int thread_count; QMutex mutex; + Database *m_db; + PinForm pinForm; ProgressForm progress; diff --git a/src/cmaclient.cpp b/src/cmaclient.cpp index daa3efd..b843230 100644 --- a/src/cmaclient.cpp +++ b/src/cmaclient.cpp @@ -21,7 +21,7 @@ #include "capability.h" #include "avdecoder.h" #include "cmaevent.h" -#include "utils.h" +#include "cmautils.h" #include #include @@ -45,8 +45,8 @@ bool CmaClient::in_progress = false; CmaClient *CmaClient::this_object = NULL; -CmaClient::CmaClient(QObject *parent) : - QObject(parent) +CmaClient::CmaClient(Database *db, QObject *parent) : + QObject(parent), m_db(db) { this_object = this; } @@ -188,7 +188,7 @@ void CmaClient::enterEventLoop(vita_device_t *device) qDebug("Starting event loop"); - CmaEvent eventLoop (device); + CmaEvent eventLoop(m_db, device); QThread thread; thread.setObjectName("event_thread"); diff --git a/src/cmaclient.h b/src/cmaclient.h index d2023f8..4fd0700 100644 --- a/src/cmaclient.h +++ b/src/cmaclient.h @@ -20,9 +20,8 @@ #ifndef CMACLIENT_H #define CMACLIENT_H -#include "database.h" +#include "qlistdb.h" #include "cmaevent.h" -#include "cmaobject.h" #include "cmabroadcast.h" #include @@ -36,7 +35,7 @@ class CmaClient : public QObject { Q_OBJECT public: - explicit CmaClient(QObject *parent = 0); + explicit CmaClient(Database *db, QObject *parent = 0); static bool isRunning(); void launch(); @@ -56,6 +55,7 @@ private: static void registrationComplete(); CmaBroadcast broadcast; + Database *m_db; static QString tempOnlineId; //TODO: move all the control variables to the client manager class diff --git a/src/cmaevent.cpp b/src/cmaevent.cpp index add1f2d..d504b67 100644 --- a/src/cmaevent.cpp +++ b/src/cmaevent.cpp @@ -18,24 +18,22 @@ */ #include "cmaevent.h" -#include "utils.h" +#include "cmautils.h" #include #include #include #include #include +#include #include -#include - -Database *CmaEvent::db = NULL; QFile *CmaEvent::m_file = NULL; -metadata_t CmaEvent::g_thumbmeta = {0, 0, 0, NULL, NULL, 0, 0, 0, Thumbnail, {{17, 240, 136, 0, 1, 1.0f, 2}}, NULL}; +static metadata_t g_thumbmeta = {0, 0, 0, NULL, NULL, 0, 0, 0, Thumbnail, {{17, 240, 136, 0, 1, 1.0f, 2}}, NULL}; -CmaEvent::CmaEvent(vita_device_t *s_device) : - device(s_device), is_active(true) +CmaEvent::CmaEvent(Database *db, vita_device_t *s_device) : + device(s_device), m_db(db), is_active(true) { } @@ -149,15 +147,13 @@ void CmaEvent::processEvent() qDebug("Ended event, code: 0x%x, id: %d", t_event.Code, t_event.Param1); } -quint16 CmaEvent::processAllObjects(CMAObject *parent, quint32 handle) +quint16 CmaEvent::processAllObjects(metadata_t &parent_metadata, quint32 handle) { - qDebug("Called %s, handle: %d, parent name: %s", Q_FUNC_INFO, handle, parent->metadata.name); + qDebug("Called %s, handle: %d, parent name: %s", Q_FUNC_INFO, handle, parent_metadata.name); char *name; - int fileType = -1; int dataType; - - uint32_t *p_handles; + quint32 *p_handles; unsigned int p_len; if(VitaMTP_GetObject_Info(device, handle, &name, &dataType) != PTP_RC_OK) { @@ -174,15 +170,17 @@ quint16 CmaEvent::processAllObjects(CMAObject *parent, quint32 handle) } - CMAObject *object = db->pathToObject(name, parent->metadata.ohfi); + int ohfi = m_db->getPathId(name, parent_metadata.ohfi); - if(object) { - qDebug("Deleting %s", object->path.toStdString().c_str()); - removeRecursively(object->path); - db->remove(object); + if(ohfi > 0) { + const QString fullpath = m_db->getAbsolutePath(ohfi); + qDebug() << "Deleting" << fullpath; + removeRecursively(fullpath); + m_db->deleteEntry(ohfi); } - QDir dir(parent->path); + QString fullpath = m_db->getAbsolutePath(parent_metadata.ohfi); + QDir dir(fullpath); if(dataType & Folder) { if(!dir.mkpath(name)) { @@ -205,33 +203,24 @@ quint16 CmaEvent::processAllObjects(CMAObject *parent, quint32 handle) VitaMTP_GetObject_Callback(device, handle, &size, CmaEvent::writeCallback); m_file->close(); delete m_file; - - // get the root ohfi type - CMAObject *root_obj = parent; - while(root_obj->parent) { - root_obj = root_obj->parent; - } - - fileType = Database::checkFileType(dir.absoluteFilePath(name), root_obj->metadata.ohfi); } } - QFileInfo info(dir, name); - object = new CMAObject(parent); - object->initObject(info, fileType); - object->metadata.handle = handle; - db->append(parent->metadata.ohfi, object); + int new_ohfi = m_db->insertObjectEntry(fullpath, name, parent_metadata.ohfi); + qDebug("Added object %s with OHFI %i to database", name, new_ohfi); free(name); - qDebug("Added object %s with OHFI %i to database", object->metadata.path, object->metadata.ohfi); - if(dataType & Folder) { + metadata_t folder_metadata; + m_db->getObjectMetadata(new_ohfi, folder_metadata); + folder_metadata.handle = handle; + for(unsigned int i = 0; i < p_len; i++) { - quint16 ret = processAllObjects(object, p_handles[i]); + quint16 ret = processAllObjects(folder_metadata, p_handles[i]); if(ret != PTP_RC_OK) { - qDebug("Deleteting object with OHFI %d", object->metadata.ohfi); - db->remove(object); + qDebug("Deleteting object with OHFI %d", new_ohfi); + m_db->deleteEntry(new_ohfi); return ret; } } @@ -251,17 +240,17 @@ void CmaEvent::vitaEventGetTreatObject(vita_event_t *event, int eventId) return; } - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); - CMAObject *parent = db->ohfiToObject(treatObject.ohfiParent); + metadata_t metadata; - if(parent == NULL) { + if(!m_db->getObjectMetadata(treatObject.ohfiParent, metadata)) { 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)); + VitaMTP_ReportResult(device, eventId, processAllObjects(metadata, treatObject.handle)); } void CmaEvent::vitaEventSendCopyConfirmationInfo(vita_event_t *event, int eventId) @@ -274,23 +263,22 @@ void CmaEvent::vitaEventSendCopyConfirmationInfo(vita_event_t *event, int eventI return; } - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); - quint64 size = 0; + qint64 size; + qint64 total_size = 0; for(quint32 i = 0; i < info->count; i++) { - CMAObject *object; - - if((object = db->ohfiToObject(info->ohfi[i])) == NULL) { + if((size = m_db->getObjectSize(info->ohfi[i])) < 0) { qWarning("Cannot find OHFI %d", info->ohfi[i]); free(info); return; } - size += object->metadata.size; + total_size += size; } - if(VitaMTP_SendCopyConfirmationInfo(device, eventId, info, size) != PTP_RC_OK) { + if(VitaMTP_SendCopyConfirmationInfo(device, eventId, info, total_size) != PTP_RC_OK) { qWarning("Error sending copy confirmation"); } else { VitaMTP_ReportResult(device, eventId, PTP_RC_OK); @@ -309,21 +297,20 @@ void CmaEvent::vitaEventSendObjectMetadataItems(vita_event_t *event, int eventId return; } - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); - CMAObject *object = db->ohfiToObject(ohfi); + metadata_t metadata; - if(object == NULL) { + if(!m_db->getObjectMetadata(ohfi, metadata)) { 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); + metadata.next_metadata = NULL; + qDebug("Sending metadata for OHFI %d (%s)", ohfi, metadata.path); - quint16 ret = VitaMTP_SendObjectMetadata(device, eventId, metadata); + quint16 ret = VitaMTP_SendObjectMetadata(device, eventId, &metadata); if(ret != PTP_RC_OK) { qWarning("Error sending metadata. Code: %04X", ret); } else { @@ -366,10 +353,10 @@ 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); + QMutexLocker locker(&m_db->mutex); - uint ohfi = event->Param2; - int items = db->filterObjects(ohfi, NULL); + int ohfi = event->Param2; + int items = m_db->childObjectCount(ohfi); if(VitaMTP_SendNumOfObject(device, eventId, items) != PTP_RC_OK) { qWarning("Error occured receiving object count for OHFI parent %d", ohfi); @@ -389,10 +376,11 @@ void CmaEvent::vitaEventSendObjectMetadata(vita_event_t *event, int eventId) qWarning("GetBrowseInfo failed"); return; } - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); - metadata_t *meta; - int count = db->filterObjects(browse.ohfiParent, &meta, browse.index, browse.numObjects); // if meta is null, will return empty XML + metadata_t *meta = NULL; + + int count = m_db->getObjectMetadatas(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 @@ -400,6 +388,7 @@ void CmaEvent::vitaEventSendObjectMetadata(vita_event_t *event, int eventId) } else { VitaMTP_ReportResult(device, eventId, PTP_RC_OK); } + m_db->freeMetadata(meta); } void CmaEvent::vitaEventSendObject(vita_event_t *event, int eventId) @@ -408,33 +397,31 @@ void CmaEvent::vitaEventSendObject(vita_event_t *event, int eventId) int ohfi = event->Param2; - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); qDebug("Searching object with OHFI %d", ohfi); - Database::find_data iters; - if(!db->find(ohfi, iters)) { + metadata_t *metadata = NULL; + if(!m_db->getObjectMetadatas(ohfi, &metadata)) { 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; + metadata_t *start = metadata; + quint32 parentHandle = event->Param3; + bool send_folder = metadata->dataType & Folder; + quint32 handle; do { - len = object->metadata.size; - m_file = new QFile(object->path); + unsigned long len = metadata->size; + m_file = new QFile(m_db->getAbsolutePath(metadata->ohfi)); // 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(metadata->dataType & File) { if(!m_file->open(QIODevice::ReadOnly)) { - qWarning("Failed to read %s", object->path.toStdString().c_str()); + qWarning() << "Failed to read" << m_file->fileName(); delete m_file; VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Not_Exist_Object); return; @@ -445,18 +432,20 @@ void CmaEvent::vitaEventSendObject(vita_event_t *event, int eventId) // 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; + if(start != metadata) { + metadata_t parent_metadata; + m_db->getObjectMetadata(metadata->ohfiParent, parent_metadata); + parentHandle = parent_metadata.handle; } // send the data over - qDebug("Sending %s of %lu bytes to device", object->metadata.name, len); + qDebug("Sending %s of %lu bytes to device", metadata->name, len); qDebug("OHFI %d with handle 0x%08X", ohfi, parentHandle); VitaMTP_RegisterCancelEventId(eventId); - quint16 ret = VitaMTP_SendObject_Callback(device, &parentHandle, &handle, &object->metadata, &CmaEvent::readCallback); + quint16 ret = VitaMTP_SendObject_Callback(device, &parentHandle, &handle, metadata, &CmaEvent::readCallback); if(ret != PTP_RC_OK) { - qWarning("Sending of %s failed. Code: %04X", object->metadata.name, ret); + qWarning("Sending of %s failed. Code: %04X", metadata->name, ret); if(ret == PTP_ERROR_CANCEL) { VitaMTP_ReportResult(device, eventId, PTP_RC_GeneralError); } @@ -465,9 +454,9 @@ void CmaEvent::vitaEventSendObject(vita_event_t *event, int eventId) return; } - object->metadata.handle = handle; + metadata->handle = handle; - if(object->metadata.dataType & File) { + if(metadata->dataType & File) { m_file->close(); delete m_file; } @@ -477,9 +466,11 @@ void CmaEvent::vitaEventSendObject(vita_event_t *event, int eventId) break; } - object = *++iters.it; + metadata = metadata->next_metadata; - } while(iters.it != iters.end && object->metadata.ohfiParent >= OHFI_OFFSET); // get everything under this "folder" + } while(metadata && metadata->ohfiParent >= OHFI_BASE_VALUE); // get everything under this "folder" + + m_db->freeMetadata(start); VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_OK, handle); @@ -571,20 +562,22 @@ void CmaEvent::vitaEventSendObjectStatus(vita_event_t *event, int eventId) return; } - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); - CMAObject *object = db->pathToObject(objectstatus.title, objectstatus.ohfiRoot); + qDebug("Checking for path %s under ohfi %i", objectstatus.title, objectstatus.ohfiRoot); + int ohfi = m_db->getPathId(objectstatus.title, objectstatus.ohfiRoot); - if(object == NULL) { // not in database, don't return metadata + if(ohfi == 0) { // 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); + metadata_t metadata = metadata_t(); + m_db->getObjectMetadata(ohfi, metadata); + metadata.next_metadata = NULL; + qDebug("Sending metadata for OHFI %d", ohfi); - if(VitaMTP_SendObjectMetadata(device, eventId, metadata) != PTP_RC_OK) { - qWarning("Error sending metadata for %d", object->metadata.ohfi); + if(VitaMTP_SendObjectMetadata(device, eventId, &metadata) != PTP_RC_OK) { + qWarning("Error sending metadata for %d", ohfi); } else { VitaMTP_ReportResult(device, eventId, PTP_RC_OK); } @@ -597,25 +590,28 @@ 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); + QMutexLocker locker(&m_db->mutex); int ohfi = event->Param2; - CMAObject *object = db->ohfiToObject(ohfi); + metadata_t metadata; - if(object == NULL) { + if(!m_db->getObjectMetadata(ohfi, metadata)) { 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); + QString fullpath = m_db->getAbsolutePath(ohfi); + QByteArray data = getThumbnail(fullpath, metadata.dataType, &g_thumbmeta); if(data.size() == 0) { - qWarning("Cannot find/read thumbnail for %s", object->path.toStdString().c_str()); + qWarning() << "Cannot find/read thumbnail for" << fullpath; VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Data); return; } + //FIXME: remove this after fixing vitamtp + // workaround for the vitamtp locale bug char *locale = strdup(setlocale(LC_ALL, NULL)); setlocale(LC_ALL, "C"); @@ -635,20 +631,21 @@ 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); + QMutexLocker locker(&m_db->mutex); int ohfi = event->Param2; - CMAObject *object = db->ohfiToObject(ohfi); + metadata_t metadata; - if(object == NULL) { + if(!m_db->getObjectMetadata(ohfi, metadata)) { 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); + QString fullpath = m_db->getAbsolutePath(ohfi); + qDebug() << QString("Deleting %1, OHFI: %2").arg(fullpath, QString::number(ohfi)); + removeRecursively(fullpath); + m_db->deleteEntry(ohfi); VitaMTP_ReportResult(device, eventId, PTP_RC_OK); } @@ -668,7 +665,7 @@ void CmaEvent::vitaEventGetSettingInfo(vita_event_t *event, int eventId) QSettings settings; if(settings.value("lastAccountId").toString() != settingsinfo->current_account.accountId) { - db->setUUID(settingsinfo->current_account.accountId); + m_db->setUUID(settingsinfo->current_account.accountId); // set the database to be updated ASAP emit refreshDatabase(); } @@ -725,26 +722,28 @@ void CmaEvent::vitaEventSendPartOfObject(vita_event_t *event, int eventId) return; } - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); - CMAObject *object = db->ohfiToObject(part_init.ohfi); + QString fullpath = m_db->getAbsolutePath(part_init.ohfi); - if(object == NULL) { + if(fullpath.isNull()) { qWarning("Cannot find object for OHFI %d", part_init.ohfi); VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Context); return; } - QFile file(object->path); + QFile file(fullpath); if(!file.open(QIODevice::ReadOnly)) { - qWarning("Cannot read %s", object->path.toStdString().c_str()); + qWarning() << "Cannot read" << fullpath; 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); + qDebug() << QString("Sending %1 at file offset %2 for %3 bytes").arg( + fullpath, QString::number(part_init.offset), QString::number(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); @@ -765,12 +764,12 @@ void CmaEvent::vitaEventOperateObject(vita_event_t *event, int eventId) return; } - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); - CMAObject *root = db->ohfiToObject(operateobject.ohfi); + QString fullpath = m_db->getAbsolutePath(operateobject.ohfi); // end for renaming only - if(root == NULL) { + if(fullpath.isNull()) { VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Not_Exist_Object); return; } @@ -779,64 +778,46 @@ void CmaEvent::vitaEventOperateObject(vita_event_t *event, int eventId) case VITA_OPERATE_CREATE_FOLDER: { qDebug("Operate command %d: Create folder %s", operateobject.cmd, operateobject.title); - QDir dir(root->path); + QDir dir(fullpath); 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); + int new_ohfi = m_db->insertObjectEntry(fullpath, operateobject.title, operateobject.ohfi); + qDebug("Created folder %s with OHFI %d", operateobject.title, new_ohfi); + VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_OK, new_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); + QFile file(fullpath + 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); + int new_ohfi = m_db->insertObjectEntry(fullpath, operateobject.title, operateobject.ohfi); + //qDebug("Created file %s with OHFI %d under parent %s", newobj->metadata.path, new_ohfi, root->metadata.path); + VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_OK, new_ohfi); } break; } case VITA_OPERATE_RENAME: { - qDebug("Operate command %d: Rename %s to %s", operateobject.cmd, root->metadata.name, operateobject.title); + //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(); - } - } + m_db->renameObject(operateobject.ohfi, operateobject.title); + QString newpath = m_db->getAbsolutePath(operateobject.ohfi); // rename in filesystem - if(!QFile(oldpath).rename(root->path)) { - qWarning("Unable to rename %s to %s", oldname.toStdString().c_str(), operateobject.title); + if(!QFile(fullpath).rename(newpath)) { + qWarning("Unable to rename %s to %s", fullpath.toLocal8Bit().constData(), 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); + qDebug("Renamed OHFI %d from %s to %s", operateobject.ohfi, fullpath.toLocal8Bit().constData(), newpath.toLocal8Bit().constData()); + VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_OK, operateobject.ohfi); break; } @@ -861,27 +842,33 @@ void CmaEvent::vitaEventGetPartOfObject(vita_event_t *event, int eventId) return; } - QMutexLocker locker(&db->mutex); - CMAObject *object = db->ohfiToObject(part_init.ohfi); + QMutexLocker locker(&m_db->mutex); + QString fullpath = m_db->getAbsolutePath(part_init.ohfi); - if(object == NULL) { + if(fullpath.isNull()) { 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); + qDebug() << QString("Receiving %1 at offset %2 for %3 bytes").arg( + fullpath, QString::number(part_init.offset), QString::number(part_init.size) + ); - QFile file(object->path); + QFile file(fullpath); if(!file.open(QIODevice::ReadWrite)) { - qWarning("Cannot write to file %s", object->path.toStdString().c_str()); + qWarning() << "Cannot write to file" << fullpath; 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); + m_db->setObjectSize(part_init.ohfi, part_init.size); + + qDebug() << QString("Written %1 bytes to %2 at offset %3").arg( + QString::number(part_init.size), fullpath, QString::number(part_init.offset) + ); + VitaMTP_ReportResult(device, eventId, PTP_RC_OK); } @@ -892,23 +879,23 @@ 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); + QMutexLocker locker(&m_db->mutex); int ohfi = event->Param2; - CMAObject *object = db->ohfiToObject(ohfi); + QString fullpath = m_db->getAbsolutePath(ohfi); - if(object == NULL) { + if(fullpath.isNull()) { qWarning("Error: Cannot find OHFI %d", ohfi); VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_OHFI); return; } else { - QFile file(object->path); + QFile file(fullpath); if(!file.exists()) { // create the directory if doesn't exist so the query don't fail - qDebug("Creating %s", object->path.toStdString().c_str()); + qDebug() << "Creating" << fullpath; - if(!QDir(QDir::root()).mkpath(object->path)) { + if(!QDir(QDir::root()).mkpath(QDir(fullpath).absolutePath())) { qWarning("Create directory failed"); VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Permission); return; @@ -919,7 +906,7 @@ void CmaEvent::vitaEventSendStorageSize(vita_event_t *event, int eventId) quint64 total; quint64 free; - if(!getDiskSpace(object->path, &free, &total)) { + if(!getDiskSpace(fullpath, &free, &total)) { qWarning("Cannot get disk space"); VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Invalid_Permission); return; @@ -946,14 +933,14 @@ void CmaEvent::vitaEventCheckExistance(vita_event_t *event, int eventId) return; } - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); - CMAObject *object = db->pathToObject(existance.name, 0); + int ohfi = m_db->getPathId(existance.name, 0); - if(object == NULL) { + if(ohfi == 0) { VitaMTP_ReportResult(device, eventId, PTP_RC_VITA_Different_Object); } else { - VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_VITA_Same_Object, object->metadata.ohfi); + VitaMTP_ReportResultWithParam(device, eventId, PTP_RC_VITA_Same_Object, ohfi); } VitaMTP_ReportResult(device, eventId, PTP_RC_OK); diff --git a/src/cmaevent.h b/src/cmaevent.h index e2f1d98..1119d03 100644 --- a/src/cmaevent.h +++ b/src/cmaevent.h @@ -20,10 +20,10 @@ #ifndef CMAEVENT_H #define CMAEVENT_H -#include "cmaobject.h" #include "database.h" #include "httpdownloader.h" +#include #include #include #include @@ -34,15 +34,11 @@ class CmaEvent : public QObject { Q_OBJECT public: - explicit CmaEvent(vita_device_t *s_device); - + explicit CmaEvent(Database *db, 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); + uint16_t processAllObjects(metadata_t &metadata, 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); @@ -73,13 +69,14 @@ private: vita_device_t *device; vita_event_t t_event; + Database *m_db; + // control variables bool is_active; QMutex mutex; QMutex active; QSemaphore sema; - static metadata_t g_thumbmeta; static QFile *m_file; signals: diff --git a/src/cmaobject.cpp b/src/cmaobject.cpp index 9236c55..6e0a3fc 100644 --- a/src/cmaobject.cpp +++ b/src/cmaobject.cpp @@ -20,7 +20,8 @@ #include "cmaobject.h" #include "sforeader.h" #include "avdecoder.h" -#include "utils.h" +#include "database.h" +#include "cmautils.h" #include #include @@ -29,24 +30,6 @@ 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() { @@ -126,48 +109,6 @@ void CMAObject::loadSfoMetadata(const QString &path) } } -void CMAObject::loadMusicMetadata(const QString &path) -{ - AVDecoder decoder; - bool skipMetadata = QSettings().value("skipMetadata", false).toBool(); - - if(!skipMetadata && 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; - bool skipMetadata = QSettings().value("skipMetadata", false).toBool(); - - if(!skipMetadata && decoder.open(path)) { - decoder.getVideoMetadata(metadata); - } else { - metadata.data.video.title = strdup(metadata.name); - metadata.data.video.explanation = strdup(""); - metadata.data.video.copyright = strdup(""); - // default to H264 video codec - metadata.data.video.tracks->data.track_video.codecType = CODEC_TYPE_AVC; - } -} - -void CMAObject::loadPhotoMetadata(const QString &path) -{ - QImage img; - bool skipMetadata = QSettings().value("skipMetadata", false).toBool(); - - if(!skipMetadata && img.load(path)) { - metadata.data.photo.tracks->data.track_photo.width = img.width(); - metadata.data.photo.tracks->data.track_photo.height = img.height(); - } - metadata.data.photo.title = strdup(metadata.name); -} - void CMAObject::initObject(const QFileInfo &file, int file_type) { metadata.name = strdup(file.fileName().toUtf8().data()); @@ -199,7 +140,7 @@ void CMAObject::initObject(const QFileInfo &file, int file_type) 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()); + Database::loadMusicMetadata(file.absoluteFilePath(), metadata); } else if(MASK_SET(metadata.dataType, Video | File)) { metadata.data.video.fileName = strdup(metadata.name); metadata.data.video.dateTimeUpdated = file.created().toTime_t(); @@ -209,7 +150,7 @@ void CMAObject::initObject(const QFileInfo &file, int file_type) metadata.data.video.numTracks = 1; metadata.data.video.tracks = new media_track(); metadata.data.video.tracks->type = VITA_TRACK_TYPE_VIDEO; - loadVideoMetadata(file.absoluteFilePath()); + Database::loadVideoMetadata(file.absoluteFilePath(), metadata); } else if(MASK_SET(metadata.dataType, Photo | File)) { if(file_type < 0) { @@ -225,7 +166,7 @@ void CMAObject::initObject(const QFileInfo &file, int file_type) 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()); + Database::loadPhotoMetadata(file.absoluteFilePath(), metadata); } path = file.absoluteFilePath(); @@ -240,15 +181,6 @@ void CMAObject::initObject(const QFileInfo &file, int file_type) 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) { diff --git a/src/cmaobject.h b/src/cmaobject.h index eb49b8c..4f36a07 100644 --- a/src/cmaobject.h +++ b/src/cmaobject.h @@ -27,26 +27,6 @@ #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: @@ -54,7 +34,6 @@ public: ~CMAObject(); void refreshPath(); - bool removeReferencedObject(); void rename(const QString &name); void updateObjectSize(qint64 size); bool hasParent(const CMAObject *obj); @@ -72,16 +51,6 @@ public: 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; @@ -91,9 +60,6 @@ protected: private: void loadSfoMetadata(const QString &path); - void loadMusicMetadata(const QString &path); - void loadVideoMetadata(const QString &path); - void loadPhotoMetadata(const QString &path); }; #endif // CMAOBJECT_H diff --git a/src/utils.cpp b/src/cmautils.cpp similarity index 84% rename from src/utils.cpp rename to src/cmautils.cpp index 22059f0..0275180 100644 --- a/src/utils.cpp +++ b/src/cmautils.cpp @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -#include "utils.h" +#include "cmautils.h" #include "avdecoder.h" #include @@ -52,31 +52,37 @@ bool getDiskSpace(const QString &dir, quint64 *free, quint64 *total) return false; } -bool removeRecursively(const QString &dirName) +bool removeRecursively(const QString &path) { + QFileInfo info(path); + + if(info.isDir()) { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - return QDir(dirName).removeRecursively(); + return QDir(path).removeRecursively(); #else - bool result = false; - QDir dir(dirName); + bool result = false; + QDir dir(path); - 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(dir.exists(path)) { + 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; + if(!result) { + return result; + } } + result = dir.rmdir(path); } - result = dir.rmdir(dirName); - } - return result; + return result; #endif + } else { + return QFile::remove(path); + } } QByteArray findFolderAlbumArt(const QString path, metadata_t *metadata) @@ -156,7 +162,7 @@ QByteArray getThumbnail(const QString &path, DataType type, metadata_t *metadata return data; } -QString readable_size(quint64 size, bool use_gib) +QString readable_size(qint64 size, bool use_gib) { QStringList list; list << "KiB" << "MiB"; diff --git a/src/utils.h b/src/cmautils.h similarity index 93% rename from src/utils.h rename to src/cmautils.h index 8d8deba..a69af6d 100644 --- a/src/utils.h +++ b/src/cmautils.h @@ -45,8 +45,8 @@ public: }; #endif -bool removeRecursively(const QString &dirName); -QString readable_size(quint64 size, bool use_gib = false); +bool removeRecursively(const QString &path); +QString readable_size(qint64 size, bool use_gib = false); bool getDiskSpace(const QString &dir, quint64 *free, quint64 *total); QByteArray getThumbnail(const QString &path, DataType type, metadata_t *metadata); diff --git a/src/database.cpp b/src/database.cpp index 448a3dd..ec6a33a 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -1,89 +1,45 @@ -/* - * QCMA: Cross-platform content manager assistant for the PS Vita - * - * Copyright (C) 2013 Codestation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - #include "database.h" -#include "cmaobject.h" +#include "avdecoder.h" -#include -#include #include -#include #include -#include -Database::Database() : +const file_type audio_list[] = { + {"mp3", FILE_FORMAT_MP3, CODEC_TYPE_MP3}, + {"mp4", FILE_FORMAT_MP4, CODEC_TYPE_AAC}, + {"wav", FILE_FORMAT_WAV, CODEC_TYPE_PCM} +}; + +const file_type 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 file_type video_list[] = { + {"mp4", FILE_FORMAT_MP4, 0} +}; + +Database::Database(QObject *parent) : + QObject(parent), 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(bool &prepared) -{ - if(mutex.tryLock()) { - if(CMARootObject::uuid != "ffffffffffffffff") { - timer->start(); - prepared = true; - } else { - mutex.unlock(); - prepared = false; - return false; - } - return true; - } else { - return false; - } -} - +#include void Database::process() { - destroy(); + qDebug("Starting database_thread: 0x%016" PRIxPTR, (uintptr_t)QThread::currentThreadId()); + clear(); cancel_operation = false; int count = create(); cancel_operation = false; - qDebug("Added %i entries to the database", count); + qDebug("Total entries added to the database: %i", count); if(count < 0) { - destroy(); + clear(); } emit updated(count); mutex.unlock(); @@ -101,403 +57,26 @@ bool Database::continueOperation() 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 ¤t_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((*root).takeFirst()); - delete first; - qDeleteAll(*root); - } - - object_list.clear(); -} - -bool Database::removeInternal(root_list &list, const CMAObject *obj) -{ - bool found = false; - QList::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(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(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)) { + for(int i = 0, max = sizeof(audio_list) / sizeof(file_type); i < max; i++) { + if(path.endsWith(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)) { + for(int i = 0, max = sizeof(photo_list) / sizeof(file_type); i< max; i++) { + if(path.endsWith(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)) { + for(int i = 0, max = sizeof(video_list) / sizeof(file_type); i< max; i++) { + if(path.endsWith(video_list[i].file_ext, Qt::CaseInsensitive)) { return i; } } @@ -507,3 +86,45 @@ int Database::checkFileType(const QString path, int ohfi_root) } return -1; } + +void Database::loadMusicMetadata(const QString &path, metadata_t &metadata) +{ + AVDecoder decoder; + bool skipMetadata = QSettings().value("skipMetadata", false).toBool(); + + if(!skipMetadata && decoder.open(path)) { + decoder.getAudioMetadata(metadata); + } else { + metadata.data.music.album = strdup(""); + metadata.data.music.artist = strdup(""); + metadata.data.music.title = strdup(metadata.name); + } +} + +void Database::loadVideoMetadata(const QString &path, metadata_t &metadata) +{ + AVDecoder decoder; + bool skipMetadata = QSettings().value("skipMetadata", false).toBool(); + + if(!skipMetadata && decoder.open(path)) { + decoder.getVideoMetadata(metadata); + } else { + metadata.data.video.title = strdup(metadata.name); + metadata.data.video.explanation = strdup(""); + metadata.data.video.copyright = strdup(""); + // default to H264 video codec + metadata.data.video.tracks->data.track_video.codecType = CODEC_TYPE_AVC; + } +} + +void Database::loadPhotoMetadata(const QString &path, metadata_t &metadata) +{ + QImage img; + bool skipMetadata = QSettings().value("skipMetadata", false).toBool(); + + if(!skipMetadata && img.load(path)) { + metadata.data.photo.tracks->data.track_photo.width = img.width(); + metadata.data.photo.tracks->data.track_photo.height = img.height(); + } + metadata.data.photo.title = strdup(metadata.name); +} diff --git a/src/database.h b/src/database.h index 0f5a184..95b98f9 100644 --- a/src/database.h +++ b/src/database.h @@ -1,92 +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 . - */ - #ifndef DATABASE_H #define DATABASE_H -#include "cmarootobject.h" - -#include -#include #include #include -#include #include +typedef struct { + const char *file_ext; + int file_format; + int file_codec; +} file_type; + +#define OHFI_BASE_VALUE 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 + +extern const file_type audio_list[3]; +extern const file_type photo_list[7]; +extern const file_type video_list[1]; + class Database : public QObject { Q_OBJECT public: - typedef struct { - QList::const_iterator it; - QList::const_iterator end; - } find_data; + explicit Database(QObject *parent = 0); - explicit Database(); - ~Database(); + virtual bool load() = 0; + virtual bool rescan() = 0; + virtual void setUUID(const QString &uuid) = 0; - bool reload(bool &prepared); - 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); + virtual int childObjectCount(int parent_ohfi) = 0; + virtual bool deleteEntry(int ohfi, int root_ohfi = 0) = 0; + virtual QString getAbsolutePath(int ohfi) = 0; + virtual QString getRelativePath(int ohfi) = 0; + virtual bool getObjectMetadata(int ohfi, metadata_t &metadata) = 0; + virtual int getObjectMetadatas(int parent_ohfi, metadata_t **metadata, int index = 0, int max_number = 0) = 0; + virtual qint64 getObjectSize(int ohfi) = 0; + virtual int getPathId(const char *name, int ohfi) = 0; + virtual int insertObjectEntry(const QString &path, const QString &name, int parent_ohfi) = 0; + virtual bool renameObject(int ohfi, const QString &name) = 0; + virtual void setObjectSize(int ohfi, qint64 size) = 0; + virtual int getParentId(int ohfi) = 0; + virtual int getRootId(int ohfi) = 0; + virtual void freeMetadata(metadata_t *metadata) = 0; static int checkFileType(const QString path, int ohfi_root); + static void loadMusicMetadata(const QString &path, metadata_t &metadata); + static void loadPhotoMetadata(const QString &path, metadata_t &metadata); + static void loadVideoMetadata(const QString &path, metadata_t &metadata); QMutex mutex; -private: - typedef QList root_list; - typedef QMap 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 ¤t_path); - CMAObject *pathToObjectInternal(const root_list &list, const char *path); - static bool lessThanComparator(const CMAObject *a, const CMAObject *b); - void dumpMetadataList(const metadata_t *p_head); +protected: bool continueOperation(); +private: + + virtual void clear() = 0; + virtual int create() = 0; + // control variables QMutex cancel; bool cancel_operation; - QTimer *timer; - QThread *thread; - map_list object_list; - signals: void fileAdded(QString); void directoryAdded(QString); diff --git a/src/forms/backupitem.cpp b/src/forms/backupitem.cpp index 3cb6198..8257ffb 100644 --- a/src/forms/backupitem.cpp +++ b/src/forms/backupitem.cpp @@ -19,7 +19,7 @@ #include "backupitem.h" #include "ui_backupitem.h" -#include "utils.h" +#include "cmautils.h" #include "dds.h" #include diff --git a/src/forms/backupmanagerform.cpp b/src/forms/backupmanagerform.cpp index c5fb9ab..3f83244 100644 --- a/src/forms/backupmanagerform.cpp +++ b/src/forms/backupmanagerform.cpp @@ -22,7 +22,7 @@ #include "cmaobject.h" #include "sforeader.h" #include "confirmdialog.h" -#include "utils.h" +#include "cmautils.h" #include "filterlineedit.h" #include @@ -32,8 +32,8 @@ #include -BackupManagerForm::BackupManagerForm(QWidget *parent) : - QWidget(parent), +BackupManagerForm::BackupManagerForm(Database *db, QWidget *parent) : + QDialog(parent), m_db(db), ui(new Ui::BackupManagerForm) { ui->setupUi(this); @@ -64,13 +64,11 @@ void BackupManagerForm::removeEntry(BackupItem *item) return; } - QMutexLocker locker(&db->mutex); + QMutexLocker locker(&m_db->mutex); - CMAObject *obj = db->ohfiToObject(item->ohfi); - if(obj) { - obj->removeReferencedObject(); - db->remove(obj); - } + int parent_ohfi = m_db->getParentId(item->ohfi); + removeRecursively(m_db->getAbsolutePath(item->ohfi)); + m_db->deleteEntry(item->ohfi); for(int i = 0; i < ui->tableWidget->rowCount(); ++i) { BackupItem *iter_item = static_cast(ui->tableWidget->cellWidget(i, 0)); @@ -80,13 +78,12 @@ void BackupManagerForm::removeEntry(BackupItem *item) } } - obj = db->ohfiToObject(obj->metadata.ohfiParent); - if(obj) { - setBackupUsage(obj->metadata.size); + if(parent_ohfi > 0) { + setBackupUsage(m_db->getObjectSize(parent_ohfi)); } } -void BackupManagerForm::setBackupUsage(quint64 size) +void BackupManagerForm::setBackupUsage(qint64 size) { ui->usageLabel->setText(tr("Backup disk usage: %1").arg(readable_size(size, true))); } @@ -144,18 +141,17 @@ void BackupManagerForm::loadBackupListing(int index) sys_dir = true; } - db->mutex.lock(); - - // get the item list - metadata_t *meta; - int row_count = db->filterObjects(ohfi, &meta); + m_db->mutex.lock(); + // get the item list + metadata_t *meta = NULL; + int row_count = m_db->getObjectMetadatas(ohfi, &meta); ui->tableWidget->setRowCount(row_count); // exit if there aren't any items if(row_count == 0) { setBackupUsage(0); - db->mutex.unlock(); + m_db->mutex.unlock(); return; } @@ -167,13 +163,14 @@ void BackupManagerForm::loadBackupListing(int index) #else horiz_header->setResizeMode(QHeaderView::Stretch); #endif - CMAObject *obj = db->ohfiToObject(ohfi); - setBackupUsage(obj->metadata.size); - + qint64 backup_size = m_db->getObjectSize(ohfi); + setBackupUsage(backup_size); + QString path = m_db->getAbsolutePath(ohfi); QList item_list; + metadata_t *first = meta; while(meta) { - QString base_path = obj->path + QDir::separator() + meta->name; + QString base_path = path + QDir::separator() + meta->name; QString parent_path = sys_dir ? base_path + QDir::separator() + "sce_sys" : base_path; SfoReader reader; QString game_name; @@ -213,13 +210,15 @@ void BackupManagerForm::loadBackupListing(int index) item->setItemInfo(game_name, size, info); item->setItemIcon(QDir(parent_path).absoluteFilePath(sys_dir ? "icon0.png" : "ICON0.PNG"), img_width, ohfi == VITA_OHFI_PSMAPP); - item->setDirectory(obj->path + QDir::separator() + meta->name); + item->setDirectory(path + QDir::separator() + meta->name); item->resize(646, 68); item_list << item; meta = meta->next_metadata; } + m_db->freeMetadata(first); + qSort(item_list.begin(), item_list.end(), BackupItem::lessThan); int row; @@ -233,7 +232,7 @@ void BackupManagerForm::loadBackupListing(int index) } vert_header->setUpdatesEnabled(true); - db->mutex.unlock(); + m_db->mutex.unlock(); // apply filter this->on_filterLineEdit_textChanged(ui->filterLineEdit->text()); diff --git a/src/forms/backupmanagerform.h b/src/forms/backupmanagerform.h index e09fef7..2613df3 100644 --- a/src/forms/backupmanagerform.h +++ b/src/forms/backupmanagerform.h @@ -23,25 +23,25 @@ #include "database.h" #include "backupitem.h" -#include +#include namespace Ui { class BackupManagerForm; } -class BackupManagerForm : public QWidget +class BackupManagerForm : public QDialog { Q_OBJECT public: - explicit BackupManagerForm(QWidget *parent = 0); + explicit BackupManagerForm(Database *db, QWidget *parent = 0); ~BackupManagerForm(); - Database *db; + Database *m_db; private: void setupForm(); - void setBackupUsage(quint64 size); + void setBackupUsage(qint64 size); Ui::BackupManagerForm *ui; diff --git a/src/forms/configwidget.cpp b/src/forms/configwidget.cpp index f7ebf24..6f5b14a 100644 --- a/src/forms/configwidget.cpp +++ b/src/forms/configwidget.cpp @@ -83,6 +83,7 @@ void ConfigWidget::setDefaultData() ui->metadataCheck->setChecked(settings.value("skipMetadata", false).toBool()); ui->usbCheck->setChecked(settings.value("disableUSB", false).toBool()); ui->wifiCheck->setChecked(settings.value("disableWireless", false).toBool()); + ui->databaseSelect->setCurrentIndex(settings.value("useMemoryStorage", true).toBool() ? 0 : 1); } ConfigWidget::~ConfigWidget() @@ -154,6 +155,7 @@ void ConfigWidget::accept() settings.setValue("skipMetadata", ui->metadataCheck->isChecked()); settings.setValue("disableUSB", ui->usbCheck->isChecked()); settings.setValue("disableWireless", ui->wifiCheck->isChecked()); + settings.setValue("useMemoryStorage", ui->databaseSelect->currentIndex() == 0); settings.sync(); done(Accepted); diff --git a/src/forms/configwidget.ui b/src/forms/configwidget.ui index 9fe7cc7..3e3d5fd 100644 --- a/src/forms/configwidget.ui +++ b/src/forms/configwidget.ui @@ -282,7 +282,7 @@ - + false @@ -291,6 +291,11 @@ In Memory + + + SQLite + + diff --git a/src/mainwidget.cpp b/src/mainwidget.cpp index 35d8088..e6c178e 100644 --- a/src/mainwidget.cpp +++ b/src/mainwidget.cpp @@ -19,8 +19,10 @@ #include "mainwidget.h" #include "cmaclient.h" -#include "cmaevent.h" -#include "utils.h" +#include "cmautils.h" + +#include "qlistdb.h" +#include "sqlitedb.h" #include #include @@ -43,7 +45,7 @@ const QStringList MainWidget::path_list = QStringList() << "photoPath" << "music bool sleptOnce = false; MainWidget::MainWidget(QWidget *parent) : - QWidget(parent) + QWidget(parent), db(NULL), configForm(NULL), managerForm(NULL), backupForm(NULL) { } @@ -54,12 +56,12 @@ void MainWidget::checkSettings() foreach(const QString &path, path_list) { if(!settings.contains(path)) { first_run = true; - dialog.show(); + configForm->show(); return; } } first_run = false; - manager.start(); + managerForm->start(); } void MainWidget::dialogResult(int result) @@ -67,7 +69,7 @@ void MainWidget::dialogResult(int result) if(result == QDialog::Accepted) { if(first_run) { first_run = false; - manager.start(); + managerForm->start(); } } else if(first_run) { qApp->quit(); @@ -80,7 +82,7 @@ void MainWidget::stopServer() if(CmaClient::isRunning()) { receiveMessage(tr("Stopping QCMA (disconnect your PS Vita)")); } - manager.stop(); + managerForm->stop(); } void MainWidget::deviceDisconnect() @@ -117,6 +119,16 @@ void MainWidget::deviceConnected(QString message) void MainWidget::prepareApplication() { + //TODO: delete database before exit + if(QSettings().value("useMemoryStorage", true).toBool()) { + db = new QListDB(); + } else { + db = new SQLiteDB(); + } + + configForm = new ConfigWidget(this); + backupForm = new BackupManagerForm(db, this); + managerForm = new ClientManager(db, this); connectSignals(); createTrayIcon(); checkSettings(); @@ -124,13 +136,13 @@ void MainWidget::prepareApplication() 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(deviceConnected(QString))); - connect(&manager, SIGNAL(deviceDisconnected()), this, SLOT(deviceDisconnect())); - connect(&manager, SIGNAL(messageSent(QString)), this, SLOT(receiveMessage(QString))); + connect(configForm, SIGNAL(finished(int)), this, SLOT(dialogResult(int))); + connect(managerForm, SIGNAL(stopped()), qApp, SLOT(quit())); + connect(managerForm, SIGNAL(deviceConnected(QString)), this, SLOT(deviceConnected(QString))); + connect(managerForm, SIGNAL(deviceDisconnected()), this, SLOT(deviceDisconnect())); + connect(managerForm, SIGNAL(messageSent(QString)), this, SLOT(receiveMessage(QString))); - form.db = &manager.db; + //backupForm.db = managerForm.db; } void MainWidget::setTrayTooltip(QString message) @@ -144,8 +156,8 @@ void MainWidget::setTrayTooltip(QString message) void MainWidget::openManager() { - form.loadBackupListing(-1); - form.show(); + backupForm->loadBackupListing(-1); + backupForm->show(); } void MainWidget::showAboutDialog() @@ -186,9 +198,9 @@ void MainWidget::createTrayIcon() about_qt = new QAction(tr("Abou&t Qt"), this); quit = new QAction(tr("&Quit"), this); - connect(options, SIGNAL(triggered()), &dialog, SLOT(open())); + connect(options, SIGNAL(triggered()), configForm, SLOT(open())); connect(backup, SIGNAL(triggered()), this, SLOT(openManager())); - connect(reload, SIGNAL(triggered()), &manager, SLOT(refreshDatabase())); + connect(reload, SIGNAL(triggered()), managerForm, SLOT(refreshDatabase())); connect(about, SIGNAL(triggered()), this, SLOT(showAboutDialog())); connect(about_qt, SIGNAL(triggered()), this, SLOT(showAboutQt())); connect(quit, SIGNAL(triggered()), this, SLOT(stopServer())); @@ -255,4 +267,5 @@ MainWidget::~MainWidget() #ifndef ENABLE_KDE_NOTIFIER trayIcon->hide(); #endif + delete db; } diff --git a/src/mainwidget.h b/src/mainwidget.h index 2d0080f..ac80db3 100644 --- a/src/mainwidget.h +++ b/src/mainwidget.h @@ -52,10 +52,13 @@ private: bool first_run; + // database + Database *db; + // forms - ConfigWidget dialog; - ClientManager manager; - BackupManagerForm form; + ConfigWidget *configForm; + ClientManager *managerForm; + BackupManagerForm *backupForm; //system tray QAction *quit; diff --git a/src/qlistdb.cpp b/src/qlistdb.cpp new file mode 100644 index 0000000..ed5f13a --- /dev/null +++ b/src/qlistdb.cpp @@ -0,0 +1,568 @@ +/* + * QCMA: Cross-platform content manager assistant for the PS Vita + * + * Copyright (C) 2013 Codestation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "cmautils.h" +#include "qlistdb.h" +#include "cmaobject.h" + +#include +#include +#include +#include +#include +#include + +QListDB::QListDB(QObject *parent) : + Database(parent) +{ + QString uuid = QSettings().value("lastAccountId", "ffffffffffffffff").toString(); + CMARootObject::uuid = uuid; + thread = new QThread(); + moveToThread(thread); + timer = new QTimer(); + thread->start(); + + timer->setInterval(0); + timer->setSingleShot(true); + connect(timer, SIGNAL(timeout()), this, SLOT(process())); +} + +QListDB::~QListDB() +{ + clear(); + timer->stop(); + delete timer; + thread->quit(); + thread->wait(); + delete thread; +} + +void QListDB::setUUID(const QString &uuid) +{ + CMARootObject::uuid = uuid; + QSettings().setValue("lastAccountId", uuid); +} + +bool QListDB::load() +{ + // not implemented + return false; +} + +bool QListDB::rescan() +{ + if(mutex.tryLock(1000)) { + if(CMARootObject::uuid != "ffffffffffffffff") { + timer->start(); + return true; + } else { + mutex.unlock(); + return false; + } + } + return false; +} + +void QListDB::clear() +{ + for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) { + CMARootObject *first = static_cast((*root).takeFirst()); + delete first; + qDeleteAll(*root); + } + + object_list.clear(); +} + +int QListDB::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; + } + + qDebug("Added objects for OHFI 0x%02X: %i", ohfi_array[i], dir_count); + + total_objects += dir_count; + object_list[ohfi_array[i]] = list; + } + return total_objects; +} + +CMAObject *QListDB::getParent(CMAObject *last_dir, const QString ¤t_path) +{ + while(last_dir && current_path != last_dir->path) { + last_dir = last_dir->parent; + } + + return last_dir; +} + +int QListDB::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 QListDB::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; +} + +bool QListDB::removeInternal(root_list &list, int ohfi) +{ + bool found = false; + QList::iterator it = list.begin(); + + while(it != list.end()) { + if(!found && (*it)->metadata.ohfi == ohfi) { + // update the size of the parent objects + (*it)->updateObjectSize(-(*it)->metadata.size); + it = list.erase(it); + found = true; + } else if(found && (*it)->metadata.ohfiParent == ohfi) { + it = list.erase(it); + } else { + ++it; + } + } + + return found; +} + +bool QListDB::lessThanComparator(const CMAObject *a, const CMAObject *b) +{ + return a->metadata.ohfi < b->metadata.ohfi; +} + +bool QListDB::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 QListDB::findInternal(const root_list &list, int ohfi, find_data &data) +{ + if(hasFilter(static_cast(list.first()), ohfi)) { + data.it = list.begin(); + } else { + CMAObject obj; + obj.setOhfi(ohfi); + data.it = qBinaryFind(list.begin(), list.end(), &obj, QListDB::lessThanComparator); + } + data.end = list.end(); + return data.it != data.end; +} + +bool QListDB::find(int ohfi, QListDB::find_data &data) +{ + for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) { + if(findInternal(*root, ohfi, data)) { + return true; + } + } + return false; +} + +CMAObject *QListDB::ohfiToObject(int ohfi) +{ + find_data data; + return find(ohfi, data) ? *data.it : NULL; +} + +CMAObject *QListDB::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; +} + +int QListDB::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 QListDB::dumpMetadataList(const metadata_t *p_head) +{ + QMutexLocker locker(&mutex); + + while(p_head) { + qDebug("Metadata: %s with OHFI %d", p_head->name, p_head->ohfi); + p_head = p_head->next_metadata; + } +} + +bool QListDB::getObjectMetadata(int ohfi, metadata_t &metadata) +{ + QMutexLocker locker(&mutex); + + CMAObject *obj = ohfiToObject(ohfi); + if(obj) { + //TODO: return the pointer instead of copying the struct + metadata = obj->metadata; + return true; + } + return false; +} + +int QListDB::childObjectCount(int parent_ohfi) +{ + return getObjectMetadatas(parent_ohfi, NULL); +} + +bool QListDB::deleteEntry(int ohfi, int root_ohfi) +{ + QMutexLocker locker(&mutex); + + if(root_ohfi) { + return removeInternal(object_list[root_ohfi], ohfi); + } else { + for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) { + if(removeInternal(*root, ohfi)) { + return true; + } + } + } + return false; +} + +int QListDB::getObjectMetadatas(int parent_ohfi, metadata_t **metadata, int index, int max_number) +{ + QMutexLocker locker(&mutex); + CMARootObject *parent = static_cast(ohfiToObject(parent_ohfi)); + + if(parent == NULL) { + return 0; + } + + int type = parent->metadata.type; + + if(parent->metadata.ohfi < OHFI_OFFSET && parent->filters) { // if we have filters + if(parent_ohfi == parent->metadata.ohfi) { // if we are looking at root + return parent->getFilters(metadata); + } else { // we are looking at a filter + for(int j = 0; j < parent->num_filters; j++) { + if(parent->filters[j].ohfi == parent_ohfi) { + 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(metadata != NULL) { + *metadata = temp.next_metadata; + } + + return numObjects; +} + +qint64 QListDB::getObjectSize(int ohfi) +{ + QMutexLocker locker(&mutex); + + CMAObject *obj = ohfiToObject(ohfi); + return obj ? obj->metadata.size : -1; +} + +int QListDB::getPathId(const char *name, int ohfi) +{ + QMutexLocker locker(&mutex); + + for(map_list::iterator root = object_list.begin(); root != object_list.end(); ++root) { + + if(ohfi && (*root).first()->metadata.ohfi != ohfi) { + continue; + } + CMAObject *obj = pathToObjectInternal(*root, name); + + if(obj) { + return obj->metadata.ohfi; + } + } + return 0; +} + +int QListDB::insertObjectEntry(const QString &path, const QString &name, int parent_ohfi) +{ + QMutexLocker locker(&mutex); + + CMAObject *parent_obj = ohfiToObject(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_obj, QListDB::lessThanComparator); + + if(it != cat_list->end()) { + CMAObject *newobj = new CMAObject(parent_obj); + + // get the root object + while(parent_obj->parent) { + parent_obj = parent_obj->parent; + } + + QFileInfo info(path, name); + newobj->initObject(info, parent_obj->metadata.dataType); + cat_list->append(newobj); + return newobj->metadata.ohfi; + } + } + + return 0; +} + +QString QListDB::getAbsolutePath(int ohfi) +{ + QMutexLocker locker(&mutex); + CMAObject *obj = ohfiToObject(ohfi); + return obj ? obj->path : NULL; +} + +QString QListDB::getRelativePath(int ohfi) +{ + QMutexLocker locker(&mutex); + CMAObject *obj = ohfiToObject(ohfi); + return obj ? obj->metadata.path : NULL; +} + +bool QListDB::renameObject(int ohfi, const QString &name) +{ + QMutexLocker locker(&mutex); + + CMAObject *root = ohfiToObject(ohfi); + + if(!root) { + return false; + } + + //rename the current object + root->rename(name); + QListDB::find_data iters; + 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(); + } + } + + return true; +} + +void QListDB::setObjectSize(int ohfi, qint64 size) +{ + QMutexLocker locker(&mutex); + CMAObject *obj = ohfiToObject(ohfi); + + if(obj) { + obj->updateObjectSize(size); + } +} + +int QListDB::getRootId(int ohfi) +{ + QMutexLocker locker(&mutex); + CMAObject *obj = ohfiToObject(ohfi); + + if(!obj) { + return 0; + } + + while(obj->parent) { + obj = obj->parent; + } + + return obj->metadata.ohfi; +} + +int QListDB::getParentId(int ohfi) +{ + QMutexLocker locker(&mutex); + CMAObject *obj = ohfiToObject(ohfi); + + return obj ? obj->metadata.ohfiParent : 0; +} diff --git a/src/qlistdb.h b/src/qlistdb.h new file mode 100644 index 0000000..da45c86 --- /dev/null +++ b/src/qlistdb.h @@ -0,0 +1,94 @@ +/* + * QCMA: Cross-platform content manager assistant for the PS Vita + * + * Copyright (C) 2013 Codestation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef QLISTDB_H +#define QLISTDB_H + +#include "database.h" +#include "cmarootobject.h" + +#include +#include +#include +#include +#include + +#include + +class QListDB : public Database +{ + Q_OBJECT +public: + explicit QListDB(QObject *parent = 0); + ~QListDB(); + + bool load(); + bool rescan(); + void close(); + void clear(); + + bool reload(bool &prepared); + void setUUID(const QString &uuid); + + int childObjectCount(int parent_ohfi); + bool deleteEntry(int ohfi, int root_ohfi = 0); + QString getAbsolutePath(int ohfi); + bool getObjectMetadata(int ohfi, metadata_t &metadata); + int getObjectMetadatas(int parent_ohfi, metadata_t **metadata, int index = 0, int max_number = 0); + qint64 getObjectSize(int ohfi); + int getParentId(int ohfi); + int getPathId(const char *name, int ohfi); + QString getRelativePath(int ohfi); + int getRootId(int ohfi); + int insertObjectEntry(const QString &path, const QString &name, int parent_ohfi); + bool renameObject(int ohfi, const QString &name); + void setObjectSize(int ohfi, qint64 size); + void freeMetadata(metadata_t *metadata) { + Q_UNUSED(metadata); + } + +private: + typedef struct { + QList::const_iterator it; + QList::const_iterator end; + } find_data; + + typedef QList root_list; + typedef QMap map_list; + + int create(); + 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, int ohfi); + bool findInternal(const root_list &list, int ohfi, find_data &data); + CMAObject *getParent(CMAObject *last_dir, const QString ¤t_path); + CMAObject *pathToObjectInternal(const root_list &list, const char *path); + static bool lessThanComparator(const CMAObject *a, const CMAObject *b); + void dumpMetadataList(const metadata_t *p_head); + bool find(int ohfi, find_data &data); + int acceptFilteredObject(const CMAObject *parent, const CMAObject *current, int type); + CMAObject *ohfiToObject(int ohfi); + + QTimer *timer; + QThread *thread; + map_list object_list; +}; + +#endif // QLISTDB_H diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp new file mode 100644 index 0000000..039c248 --- /dev/null +++ b/src/sqlitedb.cpp @@ -0,0 +1,1300 @@ +/* + * QCMA: Cross-platform content manager assistant for the PS Vita + * + * Copyright (C) 2014 Codestation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "cmautils.h" +#include "sqlitedb.h" +#include "sforeader.h" +#include "avdecoder.h" + +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +#include +#else +#include +#define QStandardPaths QDesktopServices +#define writableLocation storageLocation +#endif + +#include +#include + +static const char create_adjacent[] = "CREATE TABLE IF NOT EXISTS adjacent_objects (" + "parent_id INTEGER NOT NULL REFERENCES object_node(object_id) ON DELETE CASCADE," + "child_id INTEGER NOT NULL REFERENCES object_node(object_id) ON DELETE CASCADE)"; + +static const char create_obj_node[] = "CREATE TABLE IF NOT EXISTS object_node (" + "object_id INTEGER PRIMARY KEY AUTOINCREMENT," + "type INTEGER NOT NULL," + "data_type INTEGER NOT NULL," + "title TEXT," + "child_count INTEGER NOT NULL DEFAULT 0," + "reference_count INTEGER NOT NULL DEFAULT 0);"; + +static const char create_sources[] = "CREATE TABLE IF NOT EXISTS sources (" + "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," + "path TEXT NOT NULL CHECK (LENGTH(path) > 0)," + "size INTEGER," + "date_created TIMESTAMP," + "date_modified TIMESTAMP)"; + +static const char create_music[] = "CREATE TABLE IF NOT EXISTS music (" + "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," + "file_format INTEGER," + "audio_bitrate INTEGER," + "audio_codec INTEGER," + "duration INTEGER," + "genre_id INTEGER REFERENCES object_node(object_id) ON DELETE SET NULL," + "track_id INTEGER REFERENCES object_node(object_id) ON DELETE SET NULL," + "artist_id INTEGER REFERENCES object_node(object_id) ON DELETE SET NULL," + "album_id INTEGER REFERENCES object_node(object_id) ON DELETE SET NULL," + "artist TEXT," + "album TEXT," + "track_number INTEGER)"; + +static const char create_apps[] = "CREATE TABLE IF NOT EXISTS application (" + "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," + "title TEXT NOT NULL CHECK (LENGTH(title) > 0)," + "app_type INTEGER)"; + +static const char create_virtual[] = "CREATE TABLE IF NOT EXISTS virtual_nodes (" + "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," + "app_type INTEGER)"; + +static const char create_photos[] = "CREATE TABLE IF NOT EXISTS photos (" + "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," + "date_created TIMESTAMP," + "month_created TEXT," + "file_format INTEGER," + "photo_codec INTEGER," + "width INTEGER," + "height INTEGER)"; + +static const char create_videos[] = "CREATE TABLE IF NOT EXISTS videos (" + "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," + "file_format INTEGER," + "parental_level INTEGER," + "explanation TEXT," + "copyright TEXT," + "width INTEGER," + "height INTEGER," + "video_codec INTEGER," + "video_bitrate INTEGER," + "audio_codec INTEGER," + "audio_bitrate INTEGER," + "duration INTEGER)"; + +static const char create_savedata[] = "CREATE TABLE IF NOT EXISTS savedata (" + "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," + "detail TEXT," + "dir_name TEXT," + "title TEXT," + "date_updated TIMESTAMP)"; + +static const char create_trigger_node[] = "CREATE TRIGGER IF NOT EXISTS trg_objnode_deletechilds BEFORE DELETE ON object_node " + "FOR EACH ROW BEGIN " + "DELETE FROM object_node WHERE object_id IN " + "(SELECT child_id FROM adjacent_objects WHERE parent_id == OLD.object_id);" + "END"; + +static const char create_trigger_adjins[] = "CREATE TRIGGER IF NOT EXISTS trg_adjacentobjects_ins AFTER INSERT ON adjacent_objects " + "FOR EACH ROW BEGIN " + "UPDATE object_node SET child_count = child_count + 1 WHERE object_id = NEW.parent_id;" + "UPDATE object_node SET reference_count = reference_count + 1 WHERE object_id = NEW.child_id;" + "END"; + +static const char create_trigger_adjdel[] = "CREATE TRIGGER IF NOT EXISTS trg_adjacentobjects_del AFTER DELETE ON adjacent_objects " + "FOR EACH ROW BEGIN " + "UPDATE object_node SET child_count = child_count - 1 WHERE object_id = OLD.parent_id;" + "UPDATE object_node SET reference_count = reference_count - 1 WHERE object_id = OLD.child_id;" + "DELETE FROM object_node WHERE object_id = OLD.parent_id AND child_count <= 0;" + "DELETE FROM object_node WHERE object_id = OLD.child_id AND reference_count <= 0;" + "END"; + +static const char *table_list[] = { + create_adjacent, create_obj_node, create_sources, + create_music, create_photos, create_videos, create_savedata, create_apps, create_virtual +}; + +static const char *trigger_list[] = { + create_trigger_node, create_trigger_adjins, create_trigger_adjdel +}; + +static 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 + }; + +SQLiteDB::SQLiteDB(QObject *parent) : + Database(parent) +{ + uuid = QSettings().value("lastAccountId", "ffffffffffffffff").toString(); + thread = new QThread(); + moveToThread(thread); + timer = new QTimer(); + thread->start(); + timer->setInterval(0); + timer->setSingleShot(true); + connect(timer, SIGNAL(timeout()), this, SLOT(process())); + + // fetch a configured database path if it exists + QString db_path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + db_path = QSettings().value("databasePath", db_path).toString(); + QDir(QDir::root()).mkpath(db_path); + + db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName(db_path + QDir::separator() + "qcma.sqlite"); +} + +SQLiteDB::~SQLiteDB() +{ + db.close(); + timer->stop(); + delete timer; + thread->quit(); + thread->wait(); + delete thread; +} + + +void SQLiteDB::setUUID(const QString &uuid) +{ + this->uuid = uuid; + QSettings().setValue("lastAccountId", uuid); +} + +bool SQLiteDB::load() +{ + bool success = false; + + if(db.open()) { + QSqlQuery query; + query.exec("PRAGMA foreign_keys = ON"); + query.exec("PRAGMA synchronous = OFF"); + query.exec("PRAGMA user_version = 1"); + query.exec("PRAGMA journal_mode = MEMORY"); + query.exec("PRAGMA recursive_triggers = true"); + query.exec("PRAGMA vacuum_db.synchronous = OFF"); + + qDebug() << "Database created/registered"; + success = !initialize(); + } else { + const QSqlError error = db.lastError(); + qWarning() << "Error opening connection to the database:" << error.text(); + } + + return success; +} + +bool SQLiteDB::rescan() +{ + if(mutex.tryLock(1000)) { + if(uuid != "ffffffffffffffff") { + timer->start(); + return true; + } else { + mutex.unlock(); + return false; + } + } + return false; +} + +void SQLiteDB::clear() +{ + db.close(); + //QSqlDatabase::removeDatabase("QSQLITE"); + QString db_path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + db_path = QSettings().value("databasePath", db_path).toString(); + QFile(db_path + QDir::separator() + "qcma.sqlite").remove(); + load(); +} + +bool SQLiteDB::initialize() +{ + QSqlQuery query; + + for(unsigned int i = 0; i < sizeof(table_list) / sizeof(const char *); i++) { + if(!query.exec(table_list[i])) { + qDebug() << query.lastError(); + return false; + } + } + + for(unsigned int i = 0; i < sizeof(trigger_list) / sizeof(const char *); i++) { + if(!query.exec(trigger_list[i])) { + qDebug() << query.lastError(); + return false; + } + } + + // force object_id to start at 256 + if(query.exec("INSERT INTO object_node (object_id, data_type, type) VALUES (255, 0, 0)")) { + query.exec("DELETE FROM object_node WHERE object_id = 255"); + } + return true; +} + +QSqlError SQLiteDB::getLastError() +{ + return db.lastError(); +} + +int SQLiteDB::create() +{ + int total_objects = 0; + + db.transaction(); + + if(!insertVirtualEntries()) { + db.rollback(); + return -1; + } + + for(int i = 0, max = sizeof(ohfi_array) / sizeof(int); i < max; i++) { + QString base_path = getBasePath(ohfi_array[i]); + int dir_count = recursiveScanRootDirectory(base_path, NULL, ohfi_array[i], ohfi_array[i]); + + if(dir_count < 0) { + db.rollback(); + return -1; + } + + //qDebug("Added %i objects for OHFI %#02X", dir_count, ohfi_array[i]); + + total_objects += dir_count; + } + db.commit(); + return total_objects; +} + +QString SQLiteDB::getBasePath(int root_ohfi) +{ + QString base_path; + QSettings settings; + + switch(root_ohfi) { + case VITA_OHFI_MUSIC: + base_path = settings.value("musicPath").toString(); + break; + case VITA_OHFI_VIDEO: + base_path = settings.value("videoPath").toString(); + break; + case VITA_OHFI_PHOTO: + base_path = settings.value("photoPath").toString(); + break; + case VITA_OHFI_BACKUP: + base_path = settings.value("appsPath").toString() + "/SYSTEM/" + uuid; + break; + case VITA_OHFI_VITAAPP: + base_path = settings.value("appsPath").toString() + "/APP/" + uuid; + break; + case VITA_OHFI_PSPAPP: + base_path = settings.value("appsPath").toString() + "/PGAME/" + uuid; + break; + case VITA_OHFI_PSPSAVE: + base_path = settings.value("appsPath").toString() + "/PSAVEDATA/" + uuid; + break; + case VITA_OHFI_PSXAPP: + base_path = settings.value("appsPath").toString() + "/PSGAME/" + uuid; + break; + case VITA_OHFI_PSMAPP: + base_path = settings.value("appsPath").toString() + "/PSM/" + uuid; + break; + } + return base_path; +} + +int SQLiteDB::recursiveScanRootDirectory(const QString &base_path, const QString &rel_path, int parent_ohfi, int root_ohfi) +{ + int total_objects = 0; + + QDir dir(base_path + "/" + rel_path); + dir.setSorting(QDir::Name); + QFileInfoList qsl = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); + + foreach(const QFileInfo &info, qsl) { + + if(!continueOperation()) { + return -1; + } + + //qDebug() << "Processing " << info.fileName(); + + QString rel_name = rel_path.isNull() ? info.fileName() : rel_path + "/" + info.fileName(); + + int ohfi = insertObjectEntryInternal(base_path, rel_name, parent_ohfi, root_ohfi); + + if(ohfi > 0) { + // update progress dialog + if(info.isDir()) { + emit directoryAdded(base_path + "/" + rel_name); + int inserted = recursiveScanRootDirectory(base_path, rel_name, ohfi, root_ohfi); + if(inserted < 0) { + return -1; + } + + total_objects += inserted; + qint64 dirsize = getChildenTotalSize(ohfi); + setObjectSize(ohfi, dirsize); + } else if(info.isFile()) { + emit fileAdded(info.fileName()); + total_objects++; + } + } + } + + return total_objects; +} + +int SQLiteDB::insertObjectEntry(const QString &path, const QString &name, int parent_ohfi) +{ + int ohfi; + int type; + + if((type = getObjectType(parent_ohfi)) == 0) { + return 0; + } + db.transaction(); + if((ohfi = insertObjectEntryInternal(path, name, parent_ohfi, type)) == 0) { + db.rollback(); + } + db.commit(); + + return ohfi; +} + +int SQLiteDB::insertObjectEntryInternal(const QString &path, const QString &name, int parent_ohfi, int root_ohfi) +{ + int ohfi; + QFileInfo info(path, name); + + if(info.isDir()) { + ohfi = insertDefaultEntry(path, name, info.fileName(), parent_ohfi, Folder); + switch(parent_ohfi) { + case VITA_OHFI_VITAAPP: + case VITA_OHFI_PSPAPP: + case VITA_OHFI_PSXAPP: + case VITA_OHFI_PSMAPP: + case VITA_OHFI_BACKUP: + insertApplicationEntry(name, ohfi, parent_ohfi); + } + } else { + switch(root_ohfi) { + case VITA_OHFI_MUSIC: + ohfi = insertMusicEntry(path, name, parent_ohfi, File | Music); + break; + case VITA_OHFI_PHOTO: + ohfi = insertPhotoEntry(path, name, parent_ohfi, File | Photo); + break; + case VITA_OHFI_VIDEO: + ohfi = insertVideoEntry(path, name, parent_ohfi, File | Video); + break; + case VITA_OHFI_PSPSAVE: + ohfi = insertSavedataEntry(path, name, parent_ohfi, File | SaveData); + break; + case VITA_OHFI_VITAAPP: + case VITA_OHFI_PSPAPP: + case VITA_OHFI_PSXAPP: + case VITA_OHFI_PSMAPP: + case VITA_OHFI_BACKUP: + ohfi = insertDefaultEntry(path, name, info.fileName(), parent_ohfi, File | App); + } + } + return ohfi; +} + + +int SQLiteDB::getPathId(const QString &path) +{ + QSqlQuery query(QString("SELECT object_id from sources WHERE path = %1").arg(path)); + if(query.next()) { + return query.value(0).toInt(); + } else { + qDebug() << query.lastError(); + return -1; + } +} + +QString SQLiteDB::getPathFromId(int ohfi) +{ + QSqlQuery query(QString("SELECT path FROM sources WHERE object_id = %1").arg(ohfi)); + if(query.next()) { + return query.value(0).toString(); + } else { + qDebug() << query.lastError(); + return QString(); + } +} + +bool SQLiteDB::updateSize(int ohfi, quint64 size) +{ + QSqlQuery query(QString("UPDATE sources SET size = %1 WHERE object_id == %2").arg(size).arg(ohfi)); + return query.exec(); +} + +bool SQLiteDB::deleteEntry(int ohfi) +{ + QSqlQuery query(QString("DELETE FROM object_node WHERE object_id == %1").arg(ohfi)); + bool ret = query.exec(); + if(!ret) { + qDebug() << query.lastError(); + } + return ret; +} + +bool SQLiteDB::updateAdjacencyList(int ohfi, int parent) +{ + QSqlQuery query; + query.prepare("SELECT * FROM adjacent_objects WHERE parent_id == :parent_id AND child_id == :child_id"); + query.bindValue(0, parent); + query.bindValue(1, ohfi); + + if(query.exec() && query.next()) { + return true; + } + + query.prepare("INSERT INTO adjacent_objects (parent_id, child_id)" + "VALUES (:parentid, :child_id)"); + query.bindValue(0, parent); + query.bindValue(1, ohfi); + bool ret = query.exec(); + if(!ret) { + qDebug() << query.lastError(); + } + return ret; +} + +int SQLiteDB::insertDefaultEntry(const QString &path, const QString &name, const QString &title, int parent, int type) +{ + int ohfi = 0; + + if((ohfi = insertNodeEntry(title, VITA_DIR_TYPE_MASK_REGULAR, type)) == 0) { + return 0; + } + + if(parent >= OHFI_BASE_VALUE && !updateAdjacencyList(ohfi, parent)) { + return 0; + } + + if(!name.isNull() && !insertSourceEntry(ohfi, path, name)) { + return 0; + } + return ohfi; +} + +int SQLiteDB::insertNodeEntry(const QString &title, int type, int data_type) +{ + QSqlQuery query; + + query.prepare("INSERT INTO object_node (type, data_type, title) VALUES (:type, :data_type, :title)"); + query.bindValue(0, type); + query.bindValue(1, data_type); + query.bindValue(2, title); + + if(!query.exec() || !query.exec("SELECT last_insert_rowid()") || !query.next()) { + qDebug() << query.lastError(); + return 0; + } + + return query.value(0).toInt(); +} + +bool SQLiteDB::insertSourceEntry(uint object_id, const QString &path, const QString &name) +{ + QVariant size, date_created, date_modified; + + QFileInfo info(path, name); + if(info.isFile()) { + size = QVariant(info.size()); + date_created = QVariant(info.created().toTime_t()); + } else { + size = QVariant(QVariant::LongLong); + date_created = QVariant(QVariant::UInt); + } + + date_modified = QVariant(info.lastModified().toTime_t()); + + QSqlQuery query; + query.prepare("REPLACE INTO sources (object_id, path, size, date_created, date_modified)" + "VALUES (:object_id, :path, :size, :date_created, :date_modified)"); + query.bindValue(0, object_id); + query.bindValue(1, name); + query.bindValue(2, size); + query.bindValue(3, date_created); + query.bindValue(4, date_modified); + bool ret = query.exec(); + if(!ret) { + qDebug() << query.lastError(); + } + return ret; +} + +uint SQLiteDB::insertMusicEntry(const QString &path, const QString &name, int parent, int type) +{ + bool ok; + int ohfi; + AVDecoder decoder; + quint64 duration; + const char *artist, *album, *albumartist, *genre, *track, *title; + int file_format, audio_codec, audio_bitrate, genre_id, artist_id, track_id, album_id, track_number; + + int file_type = checkFileType(name, VITA_OHFI_MUSIC); + if(file_type < 0) { + //qDebug() << "Excluding from database:" << path; + return 0; + } + + if(!decoder.open(path + "/" + name)) { + return 0; + } + + album = decoder.getMetadataEntry("album"); + genre = decoder.getMetadataEntry("genre"); + artist = decoder.getMetadataEntry("artist"); + albumartist = decoder.getMetadataEntry("album_artist"); + track = decoder.getMetadataEntry("track"); + + track_number = QString(track).split("/")[0].toInt(&ok); + if(!ok) { + // set track number to 1 by default + track_number = 1; + } + + if(decoder.loadCodec(AVDecoder::CODEC_AUDIO)) { + audio_bitrate = decoder.getBitrate(); + } else { + audio_bitrate = 0; + } + + duration = decoder.getDuration(); + + file_format = audio_list[file_type].file_format; + audio_codec = audio_list[file_type].file_codec; + + QByteArray basename = QFileInfo(name).baseName().toUtf8(); + title = decoder.getMetadataEntry("title", basename.constData()); + + if((ohfi = insertDefaultEntry(path, name, title, parent, type)) == 0) { + return 0; + } + + if(genre) { + genre_id = insertNodeEntry(genre, VITA_DIR_TYPE_MASK_GENRES, type); + if(!updateAdjacencyList(ohfi, genre_id)) { + return 0; + } + } else { + genre_id = 0; + } + + if(artist) { + track_id = insertNodeEntry(artist, VITA_DIR_TYPE_MASK_ARTISTS, type); + if(!updateAdjacencyList(ohfi, track_id)) { + return 0; + } + } else { + track_id = 0; + } + + if(albumartist) { + artist_id = insertNodeEntry(albumartist, VITA_DIR_TYPE_MASK_REGULAR, type); + if(!updateAdjacencyList(ohfi, artist_id)) { + return 0; + } + } else { + artist_id = 0; + } + + if(album) { + album_id = insertNodeEntry(album, VITA_DIR_TYPE_MASK_ALBUMS, type); + + if(track_id && !updateAdjacencyList(ohfi, album_id)) { + return 0; + } + if(track_id && !updateAdjacencyList(album_id, track_id)) { + return 0; + } + if(artist_id && !updateAdjacencyList(album_id, artist_id)) { + return 0; + } + } else { + album_id = 0; + } + + QSqlQuery query; + query.prepare("REPLACE INTO music" + "(object_id, file_format, audio_codec, audio_bitrate, duration, genre_id, artist_id, album_id, track_id, artist, album, track_number)" + "VALUES (:object_id, :file_format, :audio_codec, :audio_bitrate, :duration, :genre_id, :artist_id, :album_id, :track_id, :artist, :album, :track_number)"); + + query.bindValue(0, ohfi); + query.bindValue(1, file_format); + query.bindValue(2, audio_codec); + query.bindValue(3, audio_bitrate); + query.bindValue(4, duration); + query.bindValue(5, genre_id ? genre_id : QVariant(QVariant::Int)); + query.bindValue(6, artist_id ? artist_id : QVariant(QVariant::Int)); + query.bindValue(7, album_id ? album_id : QVariant(QVariant::Int)); + query.bindValue(8, track_id ? track_id : QVariant(QVariant::Int)); + query.bindValue(9, artist); + query.bindValue(10, album); + query.bindValue(11, track_number); + + if(!query.exec()) { + return 0; + } + + return ohfi; +} + +uint SQLiteDB::insertVideoEntry(const QString &path, const QString &name, int parent, int type) +{ + int ohfi; + AVDecoder decoder; + quint64 duration; + int file_format, parental_level, width, height, video_codec, video_bitrate, audio_codec, audio_bitrate; + const char *explanation, *copyright, *title; + + if(!decoder.open(path + "/" + name) || !decoder.loadCodec(AVDecoder::CODEC_VIDEO)) { + return 0; + } + + int file_type = checkFileType(name, VITA_OHFI_VIDEO); + if(file_type < 0) { + //qDebug() << "Excluding from database:" << path; + return 0; + } + + parental_level = 0; + explanation = decoder.getMetadataEntry("comments", ""); + copyright = decoder.getMetadataEntry("copyright", ""); + width = decoder.getWidth(); + height = decoder.getHeight(); + duration = decoder.getDuration(); + video_codec = CODEC_TYPE_AVC; + video_bitrate = decoder.getBitrate(); + file_format = video_list[file_type].file_format; + + if(decoder.loadCodec(AVDecoder::CODEC_AUDIO)) { + audio_codec = CODEC_TYPE_AAC; + audio_bitrate = decoder.getBitrate(); + } else { + audio_codec = 0; + audio_bitrate = 0; + } + + QByteArray basename = QFileInfo(name).baseName().toUtf8(); + //title = decoder.getMetadataEntry("title", basename.constData()); + title = basename.constData(); + + + if((ohfi = insertDefaultEntry(path, name, title, parent, type)) == 0) { + return 0; + } + + QSqlQuery query; + query.prepare("REPLACE INTO videos" + "(object_id, file_format, parental_level, explanation, copyright, width, height, video_codec, video_bitrate, audio_codec, audio_bitrate, duration)" + "VALUES (:object_id, :file_format, :parental_level, :explanation, :copyright, :width, :height, :video_codec, :video_bitrate, :audio_codec, :audio_bitrate, :duration)"); + + query.bindValue(0, ohfi); + query.bindValue(1, file_format); + query.bindValue(2, parental_level); + query.bindValue(3, explanation); + query.bindValue(4, copyright); + query.bindValue(5, width); + query.bindValue(6, height); + query.bindValue(7, video_codec); + query.bindValue(8, video_bitrate); + query.bindValue(9, audio_codec); + query.bindValue(10, audio_bitrate); + query.bindValue(11, duration); + + if(!query.exec()) { + qDebug() << query.lastError().text(); + return 0; + } + + return ohfi; +} + +uint SQLiteDB::insertPhotoEntry(const QString &path, const QString &name, int parent, int type) +{ + int ohfi; + QImage img; + uint date_created; + int width, height, file_format, photo_codec; + + int file_type = checkFileType(name, VITA_OHFI_PHOTO); + if(file_type < 0) { + //qDebug() << "Excluding from database:" << path; + return 0; + } + + if(!img.load(path + "/" + name)) { + return 0; + } + + QDateTime date = QFileInfo(path + "/" + name).created(); + date_created = date.toTime_t(); + QString month_created = date.toString("yyyy/MM"); + + width = img.width(); + height = img.height(); + file_format = photo_list[file_type].file_format; + photo_codec = photo_list[file_type].file_codec; + + QByteArray basename = QFileInfo(name).baseName().toUtf8(); + + if((ohfi = insertDefaultEntry(path, name, basename, parent, type)) == 0) { + return 0; + } + + QSqlQuery query; + query.prepare("REPLACE INTO photos" + "(object_id, date_created, file_format, photo_codec, width, height, month_created)" + "VALUES (:object_id, :date_created, :file_format, :photo_codec, :width, :height, :month_created)"); + + query.bindValue(0, ohfi); + query.bindValue(1, date_created); + query.bindValue(2, file_format); + query.bindValue(3, photo_codec); + query.bindValue(4, width); + query.bindValue(5, height); + query.bindValue(6, month_created); + + if(!query.exec()) { + return 0; + } + + return ohfi; +} + +uint SQLiteDB::insertSavedataEntry(const QString &path, const QString &name, int parent, int type) +{ + int ohfi; + SfoReader reader; + uint date_updated = 0; + const char *title = NULL; + const char *savedata_detail = NULL; + const char *savedata_directory = NULL; + + QString file_name = QFileInfo(name).fileName(); + QByteArray utf8name = file_name.toUtf8(); + + if(file_name.endsWith(".sfo", Qt::CaseInsensitive) && reader.load(path + "/" + name)) { + title = reader.value("TITLE", utf8name.constData()); + savedata_detail = reader.value("SAVEDATA_DETAIL", ""); + savedata_directory = reader.value("SAVEDATA_DIRECTORY", utf8name.constData()); + date_updated = QFileInfo(path + "/" + name).lastModified().toTime_t(); + } + + if((ohfi = insertDefaultEntry(path, name, title, parent, type)) == 0) { + return 0; + } + + if(!title) { + return ohfi; + } + + QSqlQuery query; + query.prepare("REPLACE INTO savedata" + "(object_id, detail, dir_name, title, date_updated)" + "VALUES (:object_id, :detail, :dir_name, :title, :updated)"); + + query.bindValue(0, parent); + query.bindValue(1, savedata_detail); + query.bindValue(2, savedata_directory); + query.bindValue(3, title); + query.bindValue(4, date_updated); + + if(!query.exec()) { + return 0; + } + + return ohfi; +} + + +bool SQLiteDB::insertApplicationEntry(const QString &name, int ohfi, int app_type) +{ + QString title_id = QFileInfo(name).fileName(); + + QSqlQuery query; + query.prepare("REPLACE INTO application" + "(object_id, title, app_type)" + "VALUES (:object_id, :title, :app_type)"); + + query.bindValue(0, ohfi); + query.bindValue(1, title_id); + query.bindValue(2, app_type); + + if(!query.exec()) { + return false; + } + + return true; +} + +int SQLiteDB::childObjectCount(int parent_ohfi) +{ + QSqlQuery query; + query.prepare("SELECT count(child_id) FROM adjacent_objects WHERE parent_id = :object_id"); + query.bindValue(0, parent_ohfi); + if(!query.exec() || !query.next()) { + qDebug() << query.lastError(); + return -1; + } else { + return query.value(0).toInt(); + } +} + +bool SQLiteDB::deleteEntry(int ohfi, int root_ohfi) +{ + Q_UNUSED(root_ohfi); + qDebug("Deleting node: %i", ohfi); + + QSqlQuery query("DELETE FROM object_node WHERE object_id = :object_id"); + query.bindValue(0, ohfi); + return query.exec(); +} + +void SQLiteDB::fillMetadata(const QSqlQuery &query, metadata_t &metadata) +{ + metadata.ohfi = query.value("ohfi").toInt(); + metadata.ohfiParent = query.value("parent").toInt(); + metadata.name = strdup(query.value("name").toByteArray().constData()); + metadata.path = strdup(query.value("path").toByteArray().constData()); + metadata.type = VITA_DIR_TYPE_MASK_REGULAR; + metadata.dataType = (DataType)query.value("data_type").toInt(); + metadata.size = query.value("size").toULongLong(); + metadata.dateTimeCreated = query.value("date_created").toInt(); + metadata.next_metadata = NULL; + //TODO: fill the rest of the metadata +} + +bool SQLiteDB::getObjectMetadata(int ohfi, metadata_t &metadata) +{ + QSqlQuery query( + "SELECT " + "t0.object_id as ohfi," + "t1.parent_id as parent," + "t2.path as path," + "t0.title as name," + "t0.type as type," + "t0.data_type as data_type," + "t2.size as size," + "t2.date_created as date_created " + "FROM object_node t0 " + "JOIN adjacent_objects t1 ON t1.child_id = t0.object_id " + "JOIN sources t2 ON t2.object_id = t0.object_id " + "WHERE t0.object_id = :object_id"); + query.bindValue(0, ohfi); + + if(query.exec() && query.next()) { + fillMetadata(query, metadata); + return true; + } else { + qDebug() << query.lastError(); + return false; + } +} + +int SQLiteDB::getObjectMetadatas(int parent_ohfi, metadata_t **metadata, int index, int max_number) +{ + if(metadata == NULL) { + return childObjectCount(parent_ohfi); + } + + if(parent_ohfi < OHFI_BASE_VALUE) { + return getRootItems(parent_ohfi, metadata); + } + + int count = 0; + QString query_str( + "SELECT " + "t0.child_id as ohfi," + "t0.parent_id as parent," + "t2.path as path," + "t1.title as name," + "t1.type as type," + "t1.data_type as data_type," + "t2.size as size," + "t2.date_created as date_created " + "FROM adjacent_objects t0 " + "JOIN object_node t1 ON t0.child_id = t1.object_id " + "JOIN sources t2 ON t0.child_id = t2.object_id " + "WHERE t0.parent_id = :parent_id "); + + if(max_number > 0) { + query_str += "LIMIT :limit OFFSET :offset"; + } + + QSqlQuery query(query_str); + query.bindValue(0, parent_ohfi); + + if(max_number > 0) { + query.bindValue(1, max_number); + query.bindValue(2, index); + } + + if(query.exec()) { + metadata_t **last = &*metadata; + while(query.next()) { + metadata_t *meta = new metadata_t(); + fillMetadata(query, *meta); + *last = meta; + last = &meta->next_metadata; + count++; + } + } else { + qDebug() << query.lastError(); + } + return count; +} + +qint64 SQLiteDB::getObjectSize(int ohfi) +{ + if(ohfi < OHFI_BASE_VALUE) { + return getChildenTotalSize(ohfi); + } + + QSqlQuery query; + query.prepare("SELECT size FROM sources WHERE object_id = :object_id"); + query.bindValue(0, ohfi); + if(!query.exec() || !query.next()) { + qDebug() << query.lastError(); + return -1; + } else { + return query.value(0).toInt(); + } +} + +int SQLiteDB::getPathId(const char *name, int ohfi) +{ + //FIXME: use ohfi to filter by category + Q_UNUSED(ohfi); + QSqlQuery query; + + query.prepare("SELECT object_id FROM sources WHERE path = :path"); + query.bindValue(0, name); + if(!query.exec() || !query.next()) { + qDebug() << query.lastError(); + return 0; + } else { + return query.value(0).toInt(); + } +} + +QString SQLiteDB::getAbsolutePath(int ohfi) +{ + int root_ohfi = ohfi < OHFI_BASE_VALUE ? ohfi : getRootId(ohfi); + QString base_path = getBasePath(root_ohfi); + QString rel_path = getRelativePath(ohfi); + return rel_path.isNull() ? base_path : base_path + "/" + rel_path; +} + +QString SQLiteDB::getRelativePath(int ohfi) +{ + QSqlQuery query; + query.prepare("SELECT path FROM sources WHERE object_id = :object_id"); + query.bindValue(0, ohfi); + if(!query.exec() || !query.next()) { + qDebug() << query.lastError(); + return NULL; + } else { + return query.value(0).toString(); + } +} + +bool SQLiteDB::updateObjectPath(int ohfi, const QString &name) +{ + int parent_ohfi = getParentId(ohfi); + QString parent_path, file; + + if(name.isNull()) { + QSqlQuery name_query("SELECT title FROM object_node WHERE object_id = :object_id"); + name_query.bindValue(0, ohfi); + if(!name_query.exec() || !name_query.next()) { + return false; + } + file = name_query.value(0).toString(); + } else { + file = name; + } + + if(parent_ohfi >= OHFI_BASE_VALUE) { + parent_path = getRelativePath(parent_ohfi); + if(parent_path.isNull()) { + parent_path = file; + } else { + parent_path += "/" + file; + } + } else { + parent_path = file; + } + + QSqlQuery query("UPDATE sources SET path = :path WHERE object_id = :object_id"); + query.bindValue(0, parent_path); + query.bindValue(1, ohfi); + + if(!query.exec()) { + return false; + } + + DataType type = (DataType)getObjectType(ohfi); + + if(type & Folder) { + QSqlQuery child_query("SELECT child_id, data_type FROM adjacent_objects " + "JOIN object_node ON object_id = child_id" + "WHERE parent_id = :parent_id"); + child_query.bindValue(0, ohfi); + + if(query.exec()) { + while(query.next()) { + int child_ohfi = query.value(0).toInt(); + if(!updateObjectPath(child_ohfi, NULL)) { + return false; + } + } + } + } + + return true; +} + +bool SQLiteDB::renameObject(int ohfi, const QString &name) +{ + QSqlQuery query("UPDATE object_node SET title = :title WHERE object_id = :object_id"); + query.bindValue(0, name); + query.bindValue(1, ohfi); + + if(!query.exec()) { + return false; + } + + return updateObjectPath(ohfi, name); +} + +void SQLiteDB::setObjectSize(int ohfi, qint64 size) +{ + QSqlQuery query; + query.prepare("UPDATE sources SET size = :size WHERE object_id = :object_id"); + query.bindValue(0, size); + query.bindValue(1, ohfi); + if(!query.exec()) { + qDebug() << query.lastError(); + } +} + +qint64 SQLiteDB::getChildenTotalSize(int ohfi) +{ + QSqlQuery query; + query.prepare("SELECT SUM(t0.size) FROM sources t0 " + "JOIN adjacent_objects t1 ON t0.object_id = t1.child_id " + "where t1.parent_id = :parent_id"); + query.bindValue(0, ohfi); + if(!query.exec() || !query.next()) { + qDebug() << query.lastError(); + return -1; + } else { + return query.value(0).toLongLong(); + } +} + +int SQLiteDB::getRootId(int ohfi) +{ + QSqlQuery query; + int root_ohfi = ohfi; + + query.prepare("SELECT parent_id FROM adjacent_objects WHERE child_id = :child_id"); + while(root_ohfi >= OHFI_BASE_VALUE) { + query.bindValue(0, root_ohfi); + if(!query.exec() || !query.next()) { + qDebug() << query.lastError(); + root_ohfi = 0; + } else { + root_ohfi = query.value(0).toInt(); + } + } + return root_ohfi; +} + +int SQLiteDB::getObjectType(int ohfi) +{ + QSqlQuery query; + query.prepare("SELECT type FROM object_node WHERE object_id = :object_id"); + query.bindValue(0, ohfi); + if(!query.exec() || !query.next()) { + qDebug() << query.lastError(); + return 0; + } else { + return query.value(0).toInt(); + } +} + +int SQLiteDB::getParentId(int ohfi) +{ + QSqlQuery query; + query.prepare("SELECT parent_id FROM adjacent_objects WHERE child_id = :child_id"); + query.bindValue(0, ohfi); + if(!query.exec() || !query.next()) { + qDebug() << query.lastError(); + return 0; + } else { + return query.value(0).toInt(); + } +} + +void SQLiteDB::freeMetadata(metadata_t *metadata) +{ + while(metadata) { + metadata_t *current = metadata; + metadata = metadata->next_metadata; + delete current; + } +} + +int SQLiteDB::getRootItems(int root_ohfi, metadata_t **metadata) +{ + QSqlQuery query; + int count = 0; + + switch(root_ohfi) { + case VITA_OHFI_MUSIC: + //query = QSqlQuery("SELECT * FROM music"); + break; + case VITA_OHFI_PHOTO: + //query = QSqlQuery("SELECT * FROM photos"); + break; + case VITA_OHFI_VIDEO: + //query = QSqlQuery("SELECT * FROM videos"); + break; + case VITA_OHFI_PSPSAVE: + //query = QSqlQuery("SELECT * FROM savedata"); + break; + case VITA_OHFI_VITAAPP: + case VITA_OHFI_PSPAPP: + case VITA_OHFI_PSXAPP: + case VITA_OHFI_PSMAPP: + case VITA_OHFI_BACKUP: + query = QSqlQuery("SELECT " + "t0.object_id as ohfi," + ":app_type as parent," + "t3.path as path," + "t0.title as name," + "t1.type as type," + "t1.data_type as data_type," + "t3.size as size," + "t3.date_created as date_created " + "FROM application t0 " + "JOIN object_node t1 on t0.object_id = t1.object_id " + "JOIN sources t3 ON t3.object_id = t0.object_id " + "WHERE app_type = :app_type"); + query.bindValue(0, root_ohfi); + if(query.exec()) { + metadata_t **last = &*metadata; + while(query.next()) { + metadata_t *meta = new metadata_t(); + fillMetadata(query, *meta); + *last = meta; + last = &meta->next_metadata; + count++; + } + } + break; + default: + qFatal("Invalid root ohfi type"); + } + + return count; +} + +bool SQLiteDB::insertVirtualEntry(int ohfi) +{ + QSqlQuery query; + query.prepare("REPLACE INTO virtual_nodes (object_id)" + "VALUES (:object_id)"); + query.bindValue(0, ohfi); + bool ret = query.exec(); + if(!ret) { + qDebug() << query.lastError(); + } + return ret; +} + +bool SQLiteDB::insertVirtualEntries() +{ + int ohfi; + + if((ohfi = insertNodeEntry("Folders", VITA_DIR_TYPE_MASK_REGULAR, Video)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + if((ohfi = insertNodeEntry("All", VITA_DIR_TYPE_MASK_ALL, Video)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + if((ohfi = insertNodeEntry("Folders", VITA_DIR_TYPE_MASK_REGULAR, Photo)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + if((ohfi = insertNodeEntry("Month", VITA_DIR_TYPE_MASK_MONTH, Photo)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + if((ohfi = insertNodeEntry("All", VITA_DIR_TYPE_MASK_ALL, Photo)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + if((ohfi = insertNodeEntry("Artists", VITA_DIR_TYPE_MASK_ARTISTS, Music)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + if((ohfi = insertNodeEntry("Albums", VITA_DIR_TYPE_MASK_ALBUMS, Music)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + if((ohfi = insertNodeEntry("Songs", VITA_DIR_TYPE_MASK_SONGS, Music)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + if((ohfi = insertNodeEntry("Genres", VITA_DIR_TYPE_MASK_GENRES, Music)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + if((ohfi = insertNodeEntry("Playlists", VITA_DIR_TYPE_MASK_PLAYLISTS, Music)) > 0) + insertVirtualEntry(ohfi); + else + return false; + + return true; +} diff --git a/src/sqlitedb.h b/src/sqlitedb.h new file mode 100644 index 0000000..669d820 --- /dev/null +++ b/src/sqlitedb.h @@ -0,0 +1,101 @@ +/* + * QCMA: Cross-platform content manager assistant for the PS Vita + * + * Copyright (C) 2014 Codestation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SQLITEDB_H +#define SQLITEDB_H + +#include "database.h" + +#include + +#include +#include +#include +#include +#include + +class SQLiteDB : public Database +{ + Q_OBJECT +public: + explicit SQLiteDB(QObject *parent = 0); + ~SQLiteDB(); + + bool load(); + bool rescan(); + void close(); + + bool reload(bool &prepared); + void setUUID(const QString &uuid); + + bool open(); + int create(); + void clear(); + bool initialize(); + QSqlError getLastError(); + + int childObjectCount(int parent_ohfi); + bool deleteEntry(int ohfi, int root_ohfi = 0); + QString getAbsolutePath(int ohfi); + bool getObjectMetadata(int ohfi, metadata_t &metadata); + int getObjectMetadatas(int parent_ohfi, metadata_t **metadata, int index = 0, int max_number = 0); + qint64 getObjectSize(int ohfi); + int getParentId(int ohfi); + int getPathId(const char *name, int ohfi); + QString getRelativePath(int ohfi); + int getRootId(int ohfi); + int insertObjectEntry(const QString &path, const QString &name, int parent_ohfi); + bool renameObject(int ohfi, const QString &name); + void setObjectSize(int ohfi, qint64 size); + void freeMetadata(metadata_t *metadata); + + int getPathId(const QString &path); + QString getPathFromId(int ohfi); + bool updateSize(int ohfi, quint64 size); + bool deleteEntry(int ohfi); + bool insertSourceEntry(uint object_id, const QString &path, const QString &name); + uint insertMusicEntry(const QString &path, const QString &name, int parent, int type); + uint insertVideoEntry(const QString &path, const QString &name, int parent, int type); + uint insertPhotoEntry(const QString &path, const QString &name, int parent, int type); + uint insertSavedataEntry(const QString &path, const QString &name, int parent, int type); + bool insertApplicationEntry(const QString &name, int ohfi, int app_type); + +private: + int recursiveScanRootDirectory(const QString &base_path, const QString &rel_path, int parent_ohfi, int root_ohfi); + int insertObjectEntryInternal(const QString &path, const QString &name, int parent_ohfi, int type); + int insertDefaultEntry(const QString &path, const QString &name, const QString &title, int parent, int type); + int insertNodeEntry(const QString &title, int type, int data_type); + bool updateAdjacencyList(int ohfi, int parent); + QString getBasePath(int root_ohfi); + int getObjectType(int ohfi); + void fillMetadata(const QSqlQuery &query, metadata_t &metadata); + qint64 getChildenTotalSize(int ohfi); + bool updateObjectPath(int ohfi, const QString &name); + int getRootItems(int root_ohfi, metadata_t **metadata); + bool insertVirtualEntries(); + bool insertVirtualEntry(int ohfi); + + QTimer *timer; + QThread *thread; + + QString uuid; + QSqlDatabase db; +}; + +#endif // SQLITEDB_H