Store errors in db and allow to pass them again

This commit is contained in:
Lucas Verney 2016-04-06 18:57:11 +02:00
parent d8e86bf62d
commit 287e1ab0f4
4 changed files with 253 additions and 88 deletions

View File

@ -41,6 +41,8 @@ struct gengetopt_args_info
const char *version_help; /**< @brief Print version and exit help description. */
int rescan_flag; /**< @brief Rescan the whole MPD database. (default=off). */
const char *rescan_help; /**< @brief Rescan the whole MPD database. help description. */
int rescan_errors_flag; /**< @brief Rescan the errored files from the MPD database. (default=off). */
const char *rescan_errors_help; /**< @brief Rescan the errored files from the MPD database. help description. */
int update_flag; /**< @brief Trigger an update. (default=off). */
const char *update_help; /**< @brief Trigger an update. help description. */
char * mpd_root_arg; /**< @brief MPD library base path.. */
@ -56,6 +58,7 @@ struct gengetopt_args_info
unsigned int help_given ; /**< @brief Whether help was given. */
unsigned int version_given ; /**< @brief Whether version was given. */
unsigned int rescan_given ; /**< @brief Whether rescan was given. */
unsigned int rescan_errors_given ; /**< @brief Whether rescan-errors was given. */
unsigned int update_given ; /**< @brief Whether update was given. */
unsigned int mpd_root_given ; /**< @brief Whether mpd_root was given. */
unsigned int host_given ; /**< @brief Whether host was given. */

View File

@ -3,6 +3,7 @@ version "VERSION"
purpose "Binding MPD and Bliss."
option "rescan" r "Rescan the whole MPD database." flag off
option "rescan-errors" e "Rescan the errored files from the MPD database." flag off
option "update" u "Trigger an update." flag off
option "mpd_root" - "MPD library base path." string
option "host" - "MPD host." string default="" optional

View File

@ -37,6 +37,7 @@ const char *gengetopt_args_info_help[] = {
" -h, --help Print help and exit",
" -V, --version Print version and exit",
" -r, --rescan Rescan the whole MPD database. (default=off)",
" -e, --rescan-errors Rescan the errored files from the MPD database.\n (default=off)",
" -u, --update Trigger an update. (default=off)",
" --mpd_root=STRING MPD library base path.",
" --host=STRING MPD host. (default=`')",
@ -71,6 +72,7 @@ void clear_given (struct gengetopt_args_info *args_info)
args_info->help_given = 0 ;
args_info->version_given = 0 ;
args_info->rescan_given = 0 ;
args_info->rescan_errors_given = 0 ;
args_info->update_given = 0 ;
args_info->mpd_root_given = 0 ;
args_info->host_given = 0 ;
@ -82,6 +84,7 @@ void clear_args (struct gengetopt_args_info *args_info)
{
FIX_UNUSED (args_info);
args_info->rescan_flag = 0;
args_info->rescan_errors_flag = 0;
args_info->update_flag = 0;
args_info->mpd_root_arg = NULL;
args_info->mpd_root_orig = NULL;
@ -100,10 +103,11 @@ void init_args_info(struct gengetopt_args_info *args_info)
args_info->help_help = gengetopt_args_info_help[0] ;
args_info->version_help = gengetopt_args_info_help[1] ;
args_info->rescan_help = gengetopt_args_info_help[2] ;
args_info->update_help = gengetopt_args_info_help[3] ;
args_info->mpd_root_help = gengetopt_args_info_help[4] ;
args_info->host_help = gengetopt_args_info_help[5] ;
args_info->port_help = gengetopt_args_info_help[6] ;
args_info->rescan_errors_help = gengetopt_args_info_help[3] ;
args_info->update_help = gengetopt_args_info_help[4] ;
args_info->mpd_root_help = gengetopt_args_info_help[5] ;
args_info->host_help = gengetopt_args_info_help[6] ;
args_info->port_help = gengetopt_args_info_help[7] ;
}
@ -228,6 +232,8 @@ cmdline_parser_dump(FILE *outfile, struct gengetopt_args_info *args_info)
write_into_file(outfile, "version", 0, 0 );
if (args_info->rescan_given)
write_into_file(outfile, "rescan", 0, 0 );
if (args_info->rescan_errors_given)
write_into_file(outfile, "rescan-errors", 0, 0 );
if (args_info->update_given)
write_into_file(outfile, "update", 0, 0 );
if (args_info->mpd_root_given)
@ -521,6 +527,7 @@ cmdline_parser_internal (
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
{ "rescan", 0, NULL, 'r' },
{ "rescan-errors", 0, NULL, 'e' },
{ "update", 0, NULL, 'u' },
{ "mpd_root", 1, NULL, 0 },
{ "host", 1, NULL, 0 },
@ -528,7 +535,7 @@ cmdline_parser_internal (
{ 0, 0, 0, 0 }
};
c = getopt_long (argc, argv, "hVru", long_options, &option_index);
c = getopt_long (argc, argv, "hVreu", long_options, &option_index);
if (c == -1) break; /* Exit from `while (1)' loop. */
@ -553,6 +560,16 @@ cmdline_parser_internal (
additional_error))
goto failure;
break;
case 'e': /* Rescan the errored files from the MPD database.. */
if (update_arg((void *)&(args_info->rescan_errors_flag), 0, &(args_info->rescan_errors_given),
&(local_args_info.rescan_errors_given), optarg, 0, 0, ARG_FLAG,
check_ambiguity, override, 1, 0, "rescan-errors", 'e',
additional_error))
goto failure;
break;
case 'u': /* Trigger an update.. */

View File

@ -57,6 +57,151 @@ void strip_trailing_slash(char* str)
}
int _parse_music_helper(
sqlite3* dbh,
const char *mpd_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, mpd_base_path, DEFAULT_STRING_LENGTH);
strncat(song_full_uri, song_uri, DEFAULT_STRING_LENGTH);
// Pass it to bliss
struct bl_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);
// 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));
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));
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));
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
return 1;
}
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) {
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));
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;
}
return 0;
}
/**
* Update the database.
*
@ -67,7 +212,7 @@ void strip_trailing_slash(char* str)
void update_database(
struct mpd_connection *conn,
time_t initial_mtime,
char *mpd_base_path
const char *mpd_base_path
)
{
// Store latest mtime seen
@ -111,86 +256,7 @@ void update_database(
// Compute bl_analyze and store it
const char *song_uri = mpd_song_get_uri(song);
printf("\nAdding new song to db: %s\n", song_uri);
struct bl_song song_analysis;
char song_full_uri[DEFAULT_STRING_LENGTH] = "";
strncat(song_full_uri, mpd_base_path, DEFAULT_STRING_LENGTH);
strncat(song_full_uri, song_uri, DEFAULT_STRING_LENGTH);
if (BL_UNEXPECTED == bl_analyze(song_full_uri, &song_analysis)) {
fprintf(stderr, "Error while parsing song: %s.\n\n", song_uri);
continue;
}
// Insert into db
sqlite3_stmt *res;
// 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));
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
continue;
}
// 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));
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
continue;
}
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));
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
continue;
}
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) {
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
continue;
}
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));
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
if (1 == _parse_music_helper(dbh, mpd_base_path, song_uri)) {
continue;
}
@ -220,6 +286,57 @@ void update_database(
}
/**
* Rescan errored files
*
* @param mpd_base_path Root directory of the MPD library.
*/
void rescan_errored(const char *mpd_base_path)
{
// Connect to SQLite db
sqlite3 *dbh;
if (0 != sqlite3_open(mpdbliss_data_db, &dbh)) {
fprintf(stderr, "Unable to open SQLite db.\n");
return;
}
// Get the list of all the files to process
sqlite3_stmt *res;
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;
}
// Handle the files
while (sqlite3_step(res) == SQLITE_ROW) {
const char* filename = 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, mpd_base_path, filename)) {
continue;
}
}
sqlite3_finalize(res);
// Close SQLite connection
sqlite3_close(dbh);
}
int main(int argc, char** argv) {
struct gengetopt_args_info args_info;
@ -233,14 +350,29 @@ int main(int argc, char** argv) {
if (strlen(args_info.host_arg) > 0) {
mpd_host = args_info.host_arg;
}
conn = mpd_connection_new(
struct mpd_settings* conn_settings = mpd_settings_new(
mpd_host,
args_info.port_arg,
30000);
0,
NULL,
NULL);
// Connect
conn = mpd_connection_new(
mpd_settings_get_host(conn_settings),
mpd_settings_get_port(conn_settings),
mpd_settings_get_timeout_ms(conn_settings));
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) {
fprintf(stderr, "Unable to connect to the MPD server.\n");
exit(EXIT_FAILURE);
}
// Handle passwords
const char* mpd_password = mpd_settings_get_password(conn_settings);
if (NULL != mpd_password) {
if (!mpd_run_password(conn, mpd_password)) {
fprintf(stderr, "Unable to send password to the MPD server.\n");
exit(EXIT_FAILURE);
}
}
char *mpd_base_path = args_info.mpd_root_arg;
@ -312,6 +444,14 @@ int main(int argc, char** argv) {
sqlite3_close(dbh);
return EXIT_FAILURE;
}
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 EXIT_FAILURE;
}
// Purge db if a rescan is needed
if (1 == args_info.rescan_flag) {
dberr = sqlite3_exec(dbh, "BEGIN TRANSACTION; DELETE FROM distances; DELETE FROM songs; COMMIT", NULL, NULL, NULL);
@ -327,6 +467,10 @@ int main(int argc, char** argv) {
if (1 == args_info.rescan_flag) {
update_database(conn, last_mtime, mpd_base_path);
}
// Else, if we want to rescan errored files
if (1 == args_info.rescan_errors_flag) {
rescan_errored(mpd_base_path);
}
// Else, if we requested an update of the db
else if (true == args_info.update_flag) {
// Rescan from last known mtime