317 lines
11 KiB
C
317 lines
11 KiB
C
|
#include "analysis.h"
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/stat.h>
|
||
|
|
||
|
#include <bliss.h>
|
||
|
|
||
|
#include "utilities.h"
|
||
|
|
||
|
|
||
|
int _init_db(char *data_folder, char* db_path)
|
||
|
{
|
||
|
data_folder[0] = '\0';
|
||
|
db_path[0] = '\0';
|
||
|
char *xdg_data_home_env = getenv("XDG_DATA_HOME");
|
||
|
if (NULL == xdg_data_home_env) {
|
||
|
strncat(data_folder, getenv("HOME"), DEFAULT_STRING_LENGTH);
|
||
|
strip_trailing_slash(data_folder);
|
||
|
strncat(data_folder, "/.local/share/mpdbliss", DEFAULT_STRING_LENGTH - strlen(data_folder));
|
||
|
}
|
||
|
else {
|
||
|
strncpy(data_folder, xdg_data_home_env, DEFAULT_STRING_LENGTH);
|
||
|
strip_trailing_slash(data_folder);
|
||
|
strncat(data_folder, "/mpdbliss", DEFAULT_STRING_LENGTH - strlen(data_folder));
|
||
|
}
|
||
|
|
||
|
// Ensure data folder exists
|
||
|
mkdir(data_folder, 0700);
|
||
|
|
||
|
// Db path
|
||
|
strncat(db_path, data_folder, DEFAULT_STRING_LENGTH);
|
||
|
strncat(db_path, "/db.sqlite3", DEFAULT_STRING_LENGTH - strlen(db_path));
|
||
|
|
||
|
sqlite3 *dbh;
|
||
|
if (0 != sqlite3_open_v2(db_path, &dbh, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)) {
|
||
|
fprintf(stderr, "Unable to open SQLite db.\n");
|
||
|
return 1;
|
||
|
}
|
||
|
int dberr = sqlite3_exec(dbh, "PRAGMA foreign_keys = ON", NULL, NULL, NULL);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error creating db: %s.\n", sqlite3_errmsg(dbh));
|
||
|
sqlite3_close(dbh);
|
||
|
return 1;
|
||
|
}
|
||
|
dberr = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS songs( \
|
||
|
id INTEGER PRIMARY KEY, \
|
||
|
tempo REAL, \
|
||
|
amplitude REAL, \
|
||
|
frequency REAL, \
|
||
|
attack REAL, \
|
||
|
filename TEXT UNIQUE)",
|
||
|
NULL, NULL, NULL);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error creating db: %s.\n", sqlite3_errmsg(dbh));
|
||
|
sqlite3_close(dbh);
|
||
|
return 1;
|
||
|
}
|
||
|
dberr = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS distances( \
|
||
|
song1 INTEGER, \
|
||
|
song2 INTEGER, \
|
||
|
distance REAL, \
|
||
|
FOREIGN KEY(song1) REFERENCES songs(id) ON DELETE CASCADE, \
|
||
|
FOREIGN KEY(song2) REFERENCES songs(id) ON DELETE CASCADE, \
|
||
|
UNIQUE (song1, song2))",
|
||
|
NULL, NULL, NULL);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error creating db: %s.\n", sqlite3_errmsg(dbh));
|
||
|
sqlite3_close(dbh);
|
||
|
return 1;
|
||
|
}
|
||
|
dberr = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS errors( \
|
||
|
id INTEGER PRIMARY KEY, \
|
||
|
filename TEXT UNIQUE)", NULL, NULL, NULL);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error creating db: %s.\n", sqlite3_errmsg(dbh));
|
||
|
sqlite3_close(dbh);
|
||
|
return 1;
|
||
|
}
|
||
|
sqlite3_close(dbh);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int _parse_music_helper(
|
||
|
sqlite3* dbh,
|
||
|
const char *base_path,
|
||
|
const char *song_uri)
|
||
|
{
|
||
|
sqlite3_stmt *res;
|
||
|
|
||
|
// Compute full uri
|
||
|
printf("\nAdding new song to db: %s\n", song_uri);
|
||
|
char song_full_uri[DEFAULT_STRING_LENGTH] = "";
|
||
|
strncat(song_full_uri, base_path, DEFAULT_STRING_LENGTH);
|
||
|
strncat(song_full_uri, song_uri, DEFAULT_STRING_LENGTH);
|
||
|
|
||
|
// Pass it to bliss
|
||
|
struct bl_song song_analysis;
|
||
|
bl_initialize_song(&song_analysis);
|
||
|
if (BL_UNEXPECTED == bl_analyze(song_full_uri, &song_analysis)) {
|
||
|
fprintf(stderr, "Error while parsing song: %s.\n\n", song_full_uri);
|
||
|
// Free song analysis
|
||
|
bl_free_song(&song_analysis);
|
||
|
// Store error in db
|
||
|
sqlite3_prepare_v2(dbh,
|
||
|
"INSERT INTO errors(filename) VALUES(?)",
|
||
|
-1, &res, 0);
|
||
|
sqlite3_bind_text(res, 1, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||
|
sqlite3_step(res);
|
||
|
sqlite3_finalize(res);
|
||
|
// Pass file
|
||
|
return 1;
|
||
|
}
|
||
|
// Insert into db
|
||
|
// Begin transaction
|
||
|
int dberr = sqlite3_exec(dbh, "BEGIN TRANSACTION", NULL, NULL, NULL);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error while inserting data in db: %s\n\n", sqlite3_errmsg(dbh));
|
||
|
// Free song analysis
|
||
|
bl_free_song(&song_analysis);
|
||
|
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
|
||
|
// Store error in db
|
||
|
sqlite3_prepare_v2(dbh,
|
||
|
"INSERT INTO errors(filename) VALUES(?)",
|
||
|
-1, &res, 0);
|
||
|
sqlite3_bind_text(res, 1, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||
|
sqlite3_step(res);
|
||
|
sqlite3_finalize(res);
|
||
|
// Pass file
|
||
|
return 1;
|
||
|
}
|
||
|
// Insert song analysis in database
|
||
|
dberr = sqlite3_prepare_v2(dbh,
|
||
|
"INSERT INTO songs(tempo, amplitude, frequency, attack, filename) VALUES(?, ?, ?, ?, ?)",
|
||
|
-1, &res, 0);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error while inserting data in db: %s\n\n", sqlite3_errmsg(dbh));
|
||
|
// Free song analysis
|
||
|
bl_free_song(&song_analysis);
|
||
|
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
|
||
|
// Store error in db
|
||
|
sqlite3_prepare_v2(dbh,
|
||
|
"INSERT INTO errors(filename) VALUES(?)",
|
||
|
-1, &res, 0);
|
||
|
sqlite3_bind_text(res, 1, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||
|
sqlite3_step(res);
|
||
|
sqlite3_finalize(res);
|
||
|
// Pass file
|
||
|
return 1;
|
||
|
}
|
||
|
sqlite3_bind_double(res, 1, song_analysis.force_vector.tempo);
|
||
|
sqlite3_bind_double(res, 2, song_analysis.force_vector.amplitude);
|
||
|
sqlite3_bind_double(res, 3, song_analysis.force_vector.frequency);
|
||
|
sqlite3_bind_double(res, 4, song_analysis.force_vector.attack);
|
||
|
sqlite3_bind_text(res, 5, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||
|
sqlite3_step(res);
|
||
|
sqlite3_finalize(res);
|
||
|
int last_id = sqlite3_last_insert_rowid(dbh);
|
||
|
// Insert updated distances
|
||
|
dberr = sqlite3_prepare_v2(dbh, "SELECT id, tempo, amplitude, frequency, attack FROM songs", -1, &res, 0);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error while inserting data in db: %s\n\n", sqlite3_errmsg(dbh));
|
||
|
// Free song analysis
|
||
|
bl_free_song(&song_analysis);
|
||
|
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
|
||
|
// Store error in db
|
||
|
sqlite3_prepare_v2(dbh,
|
||
|
"INSERT INTO errors(filename) VALUES(?)",
|
||
|
-1, &res, 0);
|
||
|
sqlite3_bind_text(res, 1, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||
|
sqlite3_step(res);
|
||
|
sqlite3_finalize(res);
|
||
|
// Pass file
|
||
|
return 1;
|
||
|
}
|
||
|
int dberr2 = SQLITE_OK;
|
||
|
while (sqlite3_step(res) == SQLITE_ROW) {
|
||
|
int id = sqlite3_column_int(res, 0);
|
||
|
if (id == last_id) {
|
||
|
// Skip last inserted item
|
||
|
continue;
|
||
|
}
|
||
|
struct force_vector_s song_db;
|
||
|
song_db.tempo = sqlite3_column_double(res, 1);
|
||
|
song_db.amplitude = sqlite3_column_double(res, 2);
|
||
|
song_db.frequency = sqlite3_column_double(res, 3);
|
||
|
song_db.attack = sqlite3_column_double(res, 4);
|
||
|
float distance = bl_distance(song_analysis.force_vector, song_db);
|
||
|
|
||
|
sqlite3_stmt *res2;
|
||
|
dberr2 = sqlite3_prepare_v2(dbh,
|
||
|
"INSERT INTO distances(song1, song2, distance) VALUES(?, ?, ?)",
|
||
|
-1, &res2, 0);
|
||
|
if (SQLITE_OK != dberr2) {
|
||
|
fprintf(stderr, "Error while inserting data in db: %s\n\n", sqlite3_errmsg(dbh));
|
||
|
break;
|
||
|
}
|
||
|
sqlite3_bind_int(res2, 1, last_id);
|
||
|
sqlite3_bind_int(res2, 2, id);
|
||
|
sqlite3_bind_double(res2, 3, distance);
|
||
|
sqlite3_step(res2);
|
||
|
sqlite3_finalize(res2);
|
||
|
}
|
||
|
if (SQLITE_OK != dberr2) {
|
||
|
// Free song analysis
|
||
|
bl_free_song(&song_analysis);
|
||
|
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
|
||
|
// Store error in db
|
||
|
sqlite3_prepare_v2(dbh,
|
||
|
"INSERT INTO errors(filename) VALUES(?)",
|
||
|
-1, &res, 0);
|
||
|
sqlite3_bind_text(res, 1, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||
|
sqlite3_step(res);
|
||
|
sqlite3_finalize(res);
|
||
|
// Pass file
|
||
|
return 1;
|
||
|
}
|
||
|
sqlite3_finalize(res);
|
||
|
// Commit transaction
|
||
|
dberr = sqlite3_exec(dbh, "COMMIT", NULL, NULL, NULL);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error while inserting data in db: %s\n\n", sqlite3_errmsg(dbh));
|
||
|
// Free song analysis
|
||
|
bl_free_song(&song_analysis);
|
||
|
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
|
||
|
// Store error in db
|
||
|
sqlite3_prepare_v2(dbh,
|
||
|
"INSERT INTO errors(filename) VALUES(?)",
|
||
|
-1, &res, 0);
|
||
|
sqlite3_bind_text(res, 1, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||
|
sqlite3_step(res);
|
||
|
sqlite3_finalize(res);
|
||
|
// Pass file
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
// Free song analysis
|
||
|
bl_free_song(&song_analysis);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int _rescan_errored(const char *db_path, const char *base_path)
|
||
|
{
|
||
|
// Connect to SQLite db
|
||
|
sqlite3 *dbh;
|
||
|
if (0 != sqlite3_open(db_path, &dbh)) {
|
||
|
fprintf(stderr, "Unable to open SQLite db.\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
// Get the list of all the files to process
|
||
|
sqlite3_stmt *res = NULL;
|
||
|
int dberr = sqlite3_exec(dbh, "SELECT filename FROM errors", NULL, NULL, NULL);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error while fetching data in db: %s\n\n", sqlite3_errmsg(dbh));
|
||
|
sqlite3_close(dbh);
|
||
|
return 1;
|
||
|
}
|
||
|
// Handle the files
|
||
|
while (sqlite3_step(res) == SQLITE_ROW) {
|
||
|
const char* filename = (char*) sqlite3_column_text(res, 1);
|
||
|
|
||
|
// Delete it from errors list
|
||
|
sqlite3_stmt *res2;
|
||
|
int dberr2 = sqlite3_prepare_v2(dbh,
|
||
|
"DELETE FROM errors WHERE filename=?",
|
||
|
-1, &res2, 0);
|
||
|
if (SQLITE_OK != dberr2) {
|
||
|
fprintf(stderr, "Error while deleting error from db: %s\n\n", sqlite3_errmsg(dbh));
|
||
|
continue;
|
||
|
}
|
||
|
sqlite3_bind_text(res2, 1, filename, strlen(filename), SQLITE_STATIC);
|
||
|
sqlite3_step(res2);
|
||
|
sqlite3_finalize(res2);
|
||
|
|
||
|
// Try to import it back
|
||
|
if (1 == _parse_music_helper(dbh, base_path, filename)) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
sqlite3_finalize(res);
|
||
|
|
||
|
// Close SQLite connection
|
||
|
sqlite3_close(dbh);
|
||
|
|
||
|
printf("Done! :)\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int _purge_db(const char* db_path)
|
||
|
{
|
||
|
sqlite3 *dbh;
|
||
|
if (0 != sqlite3_open_v2(db_path, &dbh, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)) {
|
||
|
fprintf(stderr, "Unable to open SQLite db.\n");
|
||
|
return 1;
|
||
|
}
|
||
|
int dberr = sqlite3_exec(dbh, "PRAGMA foreign_keys = ON", NULL, NULL, NULL);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Unable to open SQLite db.\n");
|
||
|
sqlite3_close(dbh);
|
||
|
return 1;
|
||
|
}
|
||
|
dberr = sqlite3_exec(dbh, "BEGIN TRANSACTION; DELETE FROM distances; DELETE FROM songs; DELETE FROM errors; COMMIT", NULL, NULL, NULL);
|
||
|
if (SQLITE_OK != dberr) {
|
||
|
fprintf(stderr, "Error purging existing data in db: %s.\n", sqlite3_errmsg(dbh));
|
||
|
sqlite3_close(dbh);
|
||
|
return 1;
|
||
|
}
|
||
|
sqlite3_close(dbh);
|
||
|
return 0;
|
||
|
}
|