diff --git a/qcma.pro b/qcma.pro index 5555337..39291a5 100644 --- a/qcma.pro +++ b/qcma.pro @@ -7,7 +7,8 @@ QT += core \ gui \ widgets \ - network + network \ + sql TARGET = qcma @@ -31,6 +32,7 @@ SOURCES += src/main.cpp \ src/clientmanager.cpp \ src/filterlineedit.cpp \ src/dds.cpp \ + src/sqlitedb.cpp \ # forms src/forms/backupitem.cpp \ src/forms/backupmanagerform.cpp \ @@ -56,6 +58,7 @@ HEADERS += \ src/clientmanager.h \ src/filterlineedit.h \ src/dds.h \ + src/sqlitedb.h \ # forms src/forms/backupitem.h \ src/forms/backupmanagerform.h \ diff --git a/src/avdecoder.cpp b/src/avdecoder.cpp index 66315b3..42156d1 100644 --- a/src/avdecoder.cpp +++ b/src/avdecoder.cpp @@ -28,7 +28,7 @@ AVDecoder::AvInit init; AVDecoder::AVDecoder() : - pFormatCtx(NULL), file(NULL) + pFormatCtx(NULL), pCodecCtx(NULL), file(NULL) { } @@ -95,6 +95,16 @@ bool AVDecoder::open(const QString filename) return true; } +const char *AVDecoder::getMetadataEntry(const char *key, const char *default_value) +{ + AVDictionaryEntry *entry; + + if((entry = av_dict_get(pFormatCtx->metadata, key, NULL, 0)) != NULL) { + return entry->value; + } + return default_value; +} + void AVDecoder::getAudioMetadata(metadata_t &metadata) { AVDictionaryEntry *entry; @@ -164,6 +174,42 @@ void AVDecoder::getVideoMetadata(metadata_t &metadata) } } +int AVDecoder::getWidth() +{ + return pCodecCtx->width; +} + +int AVDecoder::getHeight() +{ + return pCodecCtx->height; +} + +int AVDecoder::getDuration() +{ + return pFormatCtx->duration / 1000; +} + +int AVDecoder::getBitrate() +{ + return pFormatCtx->bit_rate; +} + +int AVDecoder::getCodecBitrate() +{ + return pCodecCtx->bit_rate; +} + +bool AVDecoder::loadCodec(codec_type codec) +{ + int stream_index = av_find_best_stream(pFormatCtx, (AVMediaType)codec, -1, -1, NULL, 0); + if(stream_index >= 0) { + pCodecCtx = pFormatCtx->streams[stream_index]->codec; + return true; + } else { + return false; + } +} + QByteArray AVDecoder::getAudioThumbnail(int width, int height) { QByteArray data; diff --git a/src/avdecoder.h b/src/avdecoder.h index 360d46f..1e6d866 100644 --- a/src/avdecoder.h +++ b/src/avdecoder.h @@ -39,13 +39,23 @@ public: AVDecoder(); ~AVDecoder(); + enum codec_type {CODEC_VIDEO = AVMEDIA_TYPE_VIDEO, CODEC_AUDIO = AVMEDIA_TYPE_AUDIO}; + bool open(const QString filename); void close(); + bool loadCodec(codec_type codec); QByteArray getAudioThumbnail(int width, int height); QByteArray getVideoThumbnail(int width, int height); void getAudioMetadata(metadata_t &metadata); void getVideoMetadata(metadata_t &metadata); + const char *getMetadataEntry(const char *key, const char *default_value = NULL); + + int getWidth(); + int getHeight(); + int getDuration(); + int getBitrate(); + int getCodecBitrate(); // simulate a static constructor to initialize libav only once class AvInit @@ -66,6 +76,7 @@ private: static int64_t seekFunction(void* opaque, int64_t offset, int whence); AVFormatContext *pFormatCtx; + AVCodecContext *pCodecCtx; QFile *file; }; diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp new file mode 100644 index 0000000..c832e75 --- /dev/null +++ b/src/sqlitedb.cpp @@ -0,0 +1,810 @@ +#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," + "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 UNIQUE 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_photos[] = "CREATE TABLE IF NOT EXISTS photos (" + "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," + "date_created TIMESTAMP," + "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"; + +typedef struct { + const char *file_ext; + int file_format; + int file_codec; +} file_type; + +#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 + +static 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} +}; + +static 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}, +}; + +static const file_type video_list[] = { + {"mp4", FILE_FORMAT_MP4, 0} +}; + + +static const char *table_list[] = { + create_adjacent, create_obj_node, create_sources, + create_music, create_photos, create_videos, create_savedata +}; + +static const char *trigger_list[] = { + create_trigger_node, create_trigger_adjins, create_trigger_adjdel +}; + +SQLiteDB::SQLiteDB(QObject *parent) : + QObject(parent) +{ + uuid = QSettings().value("lastAccountId", "ffffffffffffffff").toString(); +} + +SQLiteDB::~SQLiteDB() +{ + db.close(); +} + +bool SQLiteDB::open() +{ + // 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"); + + return db.open(); +} + +void SQLiteDB::remove() +{ + QString db_path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + db_path = QSettings().value("databasePath", db_path).toString(); + QDir(QDir::root()).mkpath(db_path); + + QFile(db_path + QDir::separator() + "qcma.sqlite").remove(); +} + +bool SQLiteDB::initialize() +{ + if (!db.isOpen()) { + return false; + } + QSqlQuery query; + + for(unsigned int i = 0; i < sizeof(table_list) / sizeof(const char *); i++) { + if(!query.exec(table_list[i])) { + return false; + } + } + + for(unsigned int i = 0; i < sizeof(trigger_list) / sizeof(const char *); i++) { + if(!query.exec(trigger_list[i])) { + return false; + } + } + + // force object_id to start at 256 + if(query.exec("INSERT INTO object_node (object_id, type) VALUES (255, 0)")) { + query.exec("DELETE FROM object_node WHERE object_id = 255"); + } + return true; +} + +QSqlError SQLiteDB::getLastError() +{ + return db.lastError(); +} + +int SQLiteDB::checkFileType(const QString path, int ohfi_root) +{ + switch(ohfi_root) { + case VITA_OHFI_MUSIC: + 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(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(video_list) / sizeof(file_type); i< max; i++) { + if(path.endsWith(video_list[i].file_ext, Qt::CaseInsensitive)) { + return i; + } + } + break; + } + return -1; +} + +int SQLiteDB::create() +{ + int total_objects = 0; + + const int ohfi_array[] = { VITA_OHFI_MUSIC, VITA_OHFI_VIDEO, VITA_OHFI_PHOTO, + VITA_OHFI_BACKUP, VITA_OHFI_VITAAPP, VITA_OHFI_PSPAPP, + VITA_OHFI_PSPSAVE, VITA_OHFI_PSXAPP, VITA_OHFI_PSMAPP + }; + + QSettings settings; + QString base_path; + + for(int i = 0, max = sizeof(ohfi_array) / sizeof(int); i < max; i++) { + switch(ohfi_array[i]) { + 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: + continue; + base_path = settings.value("appsPath").toString() + QDir::separator() + "SYSTEM" + QDir::separator() + uuid; + break; + case VITA_OHFI_VITAAPP: + continue; + base_path = settings.value("appsPath").toString() + QDir::separator() + "APP" + QDir::separator() + uuid; + break; + case VITA_OHFI_PSPAPP: + continue; + base_path = settings.value("appsPath").toString() + QDir::separator() + "PGAME" + QDir::separator() + uuid; + break; + case VITA_OHFI_PSPSAVE: + base_path = settings.value("appsPath").toString() + QDir::separator() + "PSAVEDATA" + QDir::separator() + uuid; + break; + case VITA_OHFI_PSXAPP: + continue; + base_path = settings.value("appsPath").toString() + QDir::separator() + "PSGAME" + QDir::separator() + uuid; + break; + case VITA_OHFI_PSMAPP: + continue; + base_path = settings.value("appsPath").toString() + QDir::separator() + "PSM" + QDir::separator() + uuid; + break; + } + + int dir_count = recursiveScanRootDirectory(base_path, ohfi_array[i], OBJECT_FOLDER); + + if(dir_count < 0) { + return -1; + } + total_objects += dir_count; + } + return total_objects; +} + +int SQLiteDB::recursiveScanRootDirectory(const QString &base_path, int parent, int parent_type) +{ + int total_objects = 0; + + QDir dir(base_path); + dir.setSorting(QDir::Name); + QFileInfoList qsl = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); + + foreach(const QFileInfo &info, qsl) { + qDebug() << "Processing " << info.fileName(); + if(info.isDir()) { + int ohfi = insertDirectoryEntry(info.absoluteFilePath(), parent_type, parent); + total_objects += recursiveScanRootDirectory(info.absoluteFilePath(), ohfi, parent_type); + } else if(info.isFile()) { + switch(parent_type) { + case VITA_OHFI_MUSIC: + insertMusicEntry(info.absoluteFilePath(), OBJECT_MUSIC | (parent_type & ~OBJECT_FOLDER), parent); + break; + case VITA_OHFI_PHOTO: + insertPhotoEntry(info.absoluteFilePath(), OBJECT_PHOTO | (parent_type & ~OBJECT_FOLDER), parent); + break; + case VITA_OHFI_VIDEO: + insertVideoEntry(info.absoluteFilePath(), OBJECT_VIDEO | (parent_type & ~OBJECT_FOLDER), parent); + break; + case VITA_OHFI_PSPSAVE: + insertSavedataEntry(info.absoluteFilePath(), OBJECT_SAVEDATA | (parent_type & ~OBJECT_FOLDER), parent); + break; + } + + total_objects++; + } + } + + return total_objects; +} + +int SQLiteDB::getPathId(const QString &path) +{ + if (!db.isOpen()) { + return -1; + } + QSqlQuery query(QString("SELECT object_id from sources WHERE path = %1").arg(path)); + if(query.next()) { + return query.value(0).toInt(); + } else { + return -1; + } +} + +QString SQLiteDB::getPathFromId(int ohfi) +{ + if (!db.isOpen()) { + return QString(); + } + QSqlQuery query(QString("SELECT path FROM sources WHERE object_id = %1").arg(ohfi)); + if(query.next()) { + return query.value(0).toString(); + } else { + return QString(); + } +} + +bool SQLiteDB::updateSize(int ohfi, quint64 size) +{ + if (!db.isOpen()) { + return false; + } + 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)); + return query.exec(); +} + +bool SQLiteDB::deleteEntry(const QString &path) +{ + QSqlQuery query(QString("DELETE FROM object_node WHERE object_id == (SELECT object_id FROM sources WHERE path == %1)").arg(path)); + return query.exec(); +} + +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); + return query.exec(); +} + +uint SQLiteDB::insertDirectoryEntry(const QString &path, int type, int parent) +{ + uint ohfi; + db.transaction(); + QString dirname = QFileInfo(path).fileName(); + + if((ohfi = insertObjectEntry(dirname.toUtf8().constData(), type)) == 0) { + db.rollback(); + return 0; + } + + if(parent && !updateAdjacencyList(ohfi, parent)) { + db.rollback(); + return 0; + } + + if(!insertSourceEntry(ohfi, path)) { + db.rollback(); + return 0; + } + db.commit(); + return ohfi; +} + +uint SQLiteDB::insertObjectEntry(const char *title, int type) +{ + QSqlQuery query; + + query.prepare("SELECT object_id FROM object_node WHERE type == :type and title == :title"); + query.bindValue(0, type); + query.bindValue(1, title); + + if(!query.exec() || !query.next()) { + query.prepare("INSERT INTO object_node (type, title) VALUES (:type, :title)"); + query.bindValue(0, type); + query.bindValue(1, title); + + if(!query.exec() || !query.exec("SELECT last_insert_rowid()") || !query.next()) { + return 0; + } + } + + return query.value(0).toInt(); +} + +bool SQLiteDB::insertSourceEntry(uint object_id, const QString &path) +{ + qint64 size; + uint date_created, date_modified; + + QFileInfo info(path); + size = info.size(); + date_created = info.created().toTime_t(); + date_modified = 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, path); + query.bindValue(2, size); + query.bindValue(3, date_created); + query.bindValue(4, date_modified); + return query.exec(); +} + +uint SQLiteDB::insertMusicEntry(const QString &path, int type, int parent) +{ + bool ok; + uint 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(path, type); + if(file_type < 0) { + qDebug() << "Excluding from database:" << path; + return 0; + } + + if(!decoder.open(path)) { + 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).toInt(&ok); + if(!ok) { + // set track number to 1 by default + track_number = 1; + } + audio_bitrate = decoder.getBitrate(); + duration = decoder.getDuration(); + + file_format = audio_list[file_type].file_format; + audio_codec = audio_list[file_type].file_codec; + + QByteArray basename = QFileInfo(path).baseName().toUtf8(); + title = decoder.getMetadataEntry("title", basename.constData()); + + db.transaction(); + + if((ohfi = insertObjectEntry(title, type)) == 0) { + db.rollback(); + return 0; + } + + if(parent && !updateAdjacencyList(ohfi, parent)) { + db.rollback(); + return 0; + } + + if(!insertSourceEntry(ohfi, path)) { + db.rollback(); + return 0; + } + + if(genre) { + genre_id = insertObjectEntry(genre, type | OBJECT_GENRE); + if(!updateAdjacencyList(ohfi, genre_id)) { + db.rollback(); + return 0; + } + } else { + genre_id = 0; + } + + if(artist) { + track_id = insertObjectEntry(artist, type | OBJECT_ARTIST); + if(!updateAdjacencyList(ohfi, track_id)) { + db.rollback(); + return 0; + } + } else { + track_id = 0; + } + + if(albumartist) { + artist_id = insertObjectEntry(albumartist, type | OBJECT_ALBUM_ARTIST); + if(!updateAdjacencyList(ohfi, artist_id)) { + db.rollback(); + return 0; + } + } else { + artist_id = 0; + } + + if(album) { + album_id = insertObjectEntry(album, type | OBJECT_ALBUM); + + if(track_id && !updateAdjacencyList(ohfi, album_id)) { + db.rollback(); + return 0; + } + if(track_id && !updateAdjacencyList(album_id, track_id)) { + db.rollback(); + return 0; + } + if(artist_id && !updateAdjacencyList(album_id, artist_id)) { + db.rollback(); + 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()) { + db.rollback(); + return 0; + } + + db.commit(); + return ohfi; +} + +uint SQLiteDB::insertVideoEntry(const QString &path, int type, int parent) +{ + 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) || !decoder.loadCodec(AVDecoder::CODEC_VIDEO)) { + return 0; + } + + int file_type = checkFileType(path, type); + 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 = 3; + video_bitrate = decoder.getCodecBitrate(); + file_format = video_list[file_type].file_format; + + if(decoder.loadCodec(AVDecoder::CODEC_AUDIO)) { + audio_codec = 13; + audio_bitrate = decoder.getCodecBitrate(); + } else { + audio_codec = 0; + audio_bitrate = 0; + } + + QByteArray basename = QFileInfo(path).baseName().toUtf8(); + //title = decoder.getMetadataEntry("title", basename.constData()); + title = basename.constData(); + + db.transaction(); + + if((ohfi = insertObjectEntry(title, type)) == 0) { + db.rollback(); + return 0; + } + + if(parent && !updateAdjacencyList(ohfi, parent)) { + db.rollback(); + return 0; + } + + if(!insertSourceEntry(ohfi, path)) { + db.rollback(); + 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(); + db.rollback(); + return 0; + } + + db.commit(); + return ohfi; +} + +uint SQLiteDB::insertPhotoEntry(const QString &path, int type, int parent) +{ + int ohfi; + QImage img; + uint date_created; + int width, height, file_format, photo_codec; + + int file_type = checkFileType(path, type); + if(file_type < 0) { + qDebug() << "Excluding from database:" << path; + return 0; + } + + if(!img.load(path)) { + return 0; + } + + date_created = QFileInfo(path).created().toTime_t(); + 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(path).baseName().toUtf8(); + + db.transaction(); + + if((ohfi = insertObjectEntry(basename.constData(), type)) == 0) { + db.rollback(); + return 0; + } + + if(parent && !updateAdjacencyList(ohfi, parent)) { + db.rollback(); + return 0; + } + + if(!insertSourceEntry(ohfi, path)) { + db.rollback(); + return 0; + } + + QSqlQuery query; + query.prepare("REPLACE INTO photos" + "(object_id, date_created, file_format, photo_codec, width, height)" + "VALUES (:object_id, :date_created, :file_format, :photo_codec, :width, :height)"); + + 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); + + if(!query.exec()) { + db.rollback(); + return 0; + } + + db.commit(); + return ohfi; +} + +uint SQLiteDB::insertSavedataEntry(const QString &path, int type, int parent) +{ + int ohfi; + + uint date_updated; + const char *title, *savedata_detail, *savedata_directory; + + SfoReader reader; + QString base_name = QFileInfo(path).baseName(); + QString sfo_file = QDir(path).absoluteFilePath("PARAM.SFO"); + + if(!reader.load(sfo_file)) { + return 0; + } + + title = reader.value("TITLE", base_name.toUtf8().constData()); + savedata_detail = reader.value("SAVEDATA_DETAIL", ""); + savedata_directory = reader.value("SAVEDATA_DIRECTORY", base_name.toUtf8().constData()); + date_updated = QFileInfo(sfo_file).lastModified().toTime_t(); + + db.transaction(); + + if((ohfi = insertObjectEntry(title, type)) == 0) { + db.rollback(); + return 0; + } + + if(parent && !updateAdjacencyList(ohfi, parent)) { + db.rollback(); + return 0; + } + + if(!insertSourceEntry(ohfi, path)) { + db.rollback(); + return 0; + } + + 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, ohfi); + query.bindValue(1, savedata_detail); + query.bindValue(2, savedata_directory); + query.bindValue(3, title); + query.bindValue(4, date_updated); + + if(!query.exec()) { + db.rollback(); + return 0; + } + + db.commit(); + return ohfi; +} diff --git a/src/sqlitedb.h b/src/sqlitedb.h new file mode 100644 index 0000000..52dd144 --- /dev/null +++ b/src/sqlitedb.h @@ -0,0 +1,66 @@ +#ifndef SQLITEDB_H +#define SQLITEDB_H + +#include + +#include +#include +#include + +#define OBJECT_FILE 0x10000000 +#define OBJECT_FOLDER 0x20000000 +#define OBJECT_SPECIAL 0x40000000 + +#define OBJECT_MUSIC 0x00000100 +#define OBJECT_PHOTO 0x00000200 +#define OBJECT_VIDEO 0x00000400 + +#define OBJECT_ALBUM 0x00000002 +#define OBJECT_ARTIST 0x00000005 +#define OBJECT_ALBUM_ARTIST 0x00000008 +#define OBJECT_GENRE 0x0000000B + +#define OBJECT_SAVEDATA 0x00040000 +#define OBJECT_SAVEDATA_FILE 0x00000002 + +class SQLiteDB : public QObject +{ + Q_OBJECT +public: + explicit SQLiteDB(QObject *parent = 0); + ~SQLiteDB(); + + bool open(); + int create(); + void remove(); + bool initialize(); + QSqlError getLastError(); + + int getPathId(const QString &path); + QString getPathFromId(int ohfi); + bool updateSize(int ohfi, quint64 size); + bool deleteEntry(int ohfi); + bool deleteEntry(const QString &path); + uint insertObjectEntry(const char *title, int type); + bool insertSourceEntry(uint object_id, const QString &path); + uint insertMusicEntry(const QString &path, int type, int parent); + uint insertVideoEntry(const QString &path, int type, int parent); + uint insertPhotoEntry(const QString &path, int type, int parent); + uint insertSavedataEntry(const QString &path, int type, int parent); + +private: + int recursiveScanRootDirectory(const QString &base_path, int parent, int type); + uint insertDirectoryEntry(const QString &path, int type, int parent); + int checkFileType(const QString path, int ohfi_root); + bool updateAdjacencyList(int ohfi, int parent); + + QString uuid; + QSqlDatabase db; + +signals: + +public slots: + +}; + +#endif // SQLITEDB_H