/* * 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," "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; }