Files
qcma/src/sqlitedb.cpp
codestation ae8657325a Create virtual node table to store category nodes (doesn't exist on the filesystem but need an OHFI).
Enable triggers, disable sync mode and other tweaks so the creation of the database can be faster.
Abort early if a database entry cannot be added.
Fix games and savedatas not being saved with their correct parent id.
Preparing getRootItems to return virtual categories.
Code refactoring.
2014-02-25 09:18:49 -04:30

1226 lines
38 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "cmautils.h"
#include "sqlitedb.h"
#include "sforeader.h"
#include "avdecoder.h"
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QStandardPaths>
#else
#include <QDesktopServices>
#define QStandardPaths QDesktopServices
#define writableLocation storageLocation
#endif
#include <QSettings>
#include <QSqlQuery>
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,"
"title TEXT NOT NULL CHECK (LENGTH(title) > 0),"
"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();
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;
}