From 3f16114d1b1660fea726624a25eab45242163db8 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Mon, 9 May 2016 15:57:26 +0200 Subject: [PATCH] Refactor --- CMakeLists.txt | 23 ++- bliss | 2 +- include/analysis.h | 36 ++++ include/utilities.h | 15 ++ src/.main.c.swp | Bin 0 -> 20480 bytes src/analysis.c | 316 +++++++++++++++++++++++++++++++++++ src/main.c | 394 +++++++------------------------------------- src/utilities.c | 13 ++ 8 files changed, 458 insertions(+), 341 deletions(-) create mode 100644 include/analysis.h create mode 100644 include/utilities.h create mode 100644 src/.main.c.swp create mode 100644 src/analysis.c create mode 100644 src/utilities.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 73aba2b..b0f7d17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,26 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required (VERSION 2.8) -project(MPDBliss C) +project (MPDBliss C) -add_subdirectory(bliss) +add_subdirectory (bliss) file (GLOB MPDBLISS_SRC "src/*.c") -include_directories("include/" "bliss/include") + +# TODO \/ +find_package(PkgConfig REQUIRED) +pkg_check_modules(MULTIMEDIA REQUIRED libavformat libavutil libavcodec) +pkg_check_modules(RESAMPLE QUIET libswresample) +if(NOT RESAMPLE_FOUND) + pkg_check_modules(RESAMPLE REQUIRED libavresample) + set(AVRESAMPLE TRUE) +else() + set(AVRESAMPLE FALSE) +endif() +include_directories(${MULTIMEDIA_INCLUDE_DIRS} ${RESAMPLE_INCLUDE_DIRS} include/ bliss/include) +link_directories(${MULTIMEDIA_LIBRARY_DIRS} ${RESAMPLE_LIBRARY_DIRS}) +add_definitions(${MULTIMEDIA_CFLAGS_OTHER} ${RESAMPLE_CFLAGS_OTHER}) +add_definitions (-Wall -Wno-long-long -pedantic -std=c99) +# TODO /\ add_executable (mpdbliss ${MPDBLISS_SRC}) diff --git a/bliss b/bliss index 0b289b9..39fe24d 160000 --- a/bliss +++ b/bliss @@ -1 +1 @@ -Subproject commit 0b289b9be7c9bbabef61c6131b629fc707b564e7 +Subproject commit 39fe24d79f2fa4e253a29e66f5f7c0c33e4b84d5 diff --git a/include/analysis.h b/include/analysis.h new file mode 100644 index 0000000..f8b5bfb --- /dev/null +++ b/include/analysis.h @@ -0,0 +1,36 @@ +#ifndef ANALYSIS_H +#define ANALYSIS_H + +#include + +/** + * TODO + */ +int _init_db(char* data_folder, char* db_path); + + +/** + * TODO + */ +int _parse_music_helper( + sqlite3* dbh, + const char *base_path, + const char *song_uri); + + +/** + * Rescan errored files + * + * @param db_path Path to the db file to use. + * @param base_path Root directory of the MPD library. + * @return 0 on success. Non-zero otherwise. + */ +int _rescan_errored(const char *db_path, const char *base_path); + + +/** + * TODO + */ +int _purge_db(const char* db_path); + +#endif // ANALYSIS_H diff --git a/include/utilities.h b/include/utilities.h new file mode 100644 index 0000000..946af3f --- /dev/null +++ b/include/utilities.h @@ -0,0 +1,15 @@ +#ifndef UTILITIES_H +#define UTILITIES_H + +#define DEFAULT_STRING_LENGTH 1024 + + +/** + * Strip the trailing slash from a string. + * + * @param[in] str String to strip slash from. + * @param[out] str Stripped string. + */ +void strip_trailing_slash(char* str); + +#endif // UTILITIES_H diff --git a/src/.main.c.swp b/src/.main.c.swp new file mode 100644 index 0000000000000000000000000000000000000000..377d244ba6b26844004dd3f40b6eed49c4ca2e37 GIT binary patch literal 20480 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 literal 0 HcmV?d00001 diff --git a/src/analysis.c b/src/analysis.c new file mode 100644 index 0000000..0881565 --- /dev/null +++ b/src/analysis.c @@ -0,0 +1,316 @@ +#include "analysis.h" + +#include +#include +#include +#include + +#include + +#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; +} diff --git a/src/main.c b/src/main.c index f6f823e..a051163 100644 --- a/src/main.c +++ b/src/main.c @@ -1,30 +1,23 @@ -#include #include #include #include #include -#include #include #include #include -#include "bliss.h" +#include "analysis.h" #include "cmdline.h" +#include "utilities.h" // TODO: Handle deletions from db -#define DEFAULT_STRING_LENGTH 255 - -// Data file path to store latest seen mtimes and db -char mpdbliss_data_file[DEFAULT_STRING_LENGTH] = ""; -char mpdbliss_data_db[DEFAULT_STRING_LENGTH] = ""; // IDLE loop control variable volatile bool mpd_run_idle_loop = true; // MPD connection handler struct mpd_connection *conn; - /** * Handle interruption when waiting for MPD IDLE items. */ @@ -42,192 +35,18 @@ void sigint_catch_function(int signo) } -/** - * Strip the trailing slash from a string. - * - * @param[in] str String to strip slash from. - * @param[out] str Stripped string. - */ -void strip_trailing_slash(char* str) -{ - size_t length = strlen(str); - if ('/' == str[length - 1]) { - str[length - 1] = '\0'; - } -} - - - -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); - // 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; -} - - /** * Update the database. * * @param mpd_connection MPD connection object to use. * @param initial_mtime Initial mtime to use. * @param mpd_base_path Root directory of the MPD library. + * @param mpdbliss_data_db Path to the db to use. */ -void update_database( - struct mpd_connection *conn, +long int update_database( time_t initial_mtime, - const char *mpd_base_path + const char *mpd_base_path, + const char* mpdbliss_data_db ) { // Store latest mtime seen @@ -237,25 +56,25 @@ void update_database( struct mpd_stats* stats = mpd_run_stats(conn); if (NULL == stats) { fprintf(stderr, "Unable to fetch number of songs in the db.\n"); - return; + return -1; } unsigned int n_songs = mpd_stats_get_number_of_songs(stats); if (0 == n_songs) { fprintf(stderr, "Unable to fetch number of songs in the db.\n"); - return; + return -1; } // Get the list of all the files to process if (!mpd_send_list_all_meta(conn, NULL)) { fprintf(stderr, "Unable to get a full list of items in the db.\n"); - return; + return -1; } // Connect to SQLite db sqlite3 *dbh; if (0 != sqlite3_open(mpdbliss_data_db, &dbh)) { fprintf(stderr, "Unable to open SQLite db.\n"); - return; + return -1; } // Retrieve the received list in memory, to prevent timeout @@ -313,82 +132,20 @@ void update_database( // Check if exit was due to an error if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { printf("MPD Error: %s\n", mpd_connection_get_error_message(conn)); - return; - } - - // Update last_mtime, if no error occured. - FILE *fp = fopen(mpdbliss_data_file, "w+"); - if (NULL != fp) { - fprintf(fp, "%d\n", latest_mtime); - fclose(fp); - } - else { - fprintf(stderr, "Unable to store latest mtime seen.\n"); - return; + return -1; } free(entities); printf("Done! :)\n"); -} - -/** - * 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); - - printf("Done! :)\n"); + // Return last_mtime, if no error occured. + return latest_mtime; } int main(int argc, char** argv) { - struct gengetopt_args_info args_info; - // Scan arguments + struct gengetopt_args_info args_info; if (0 != cmdline_parser(argc, argv, &args_info)) { exit(EXIT_FAILURE) ; } @@ -426,115 +183,65 @@ int main(int argc, char** argv) { char mpd_base_path[DEFAULT_STRING_LENGTH] = ""; strncat(mpd_base_path, args_info.mpd_root_arg, DEFAULT_STRING_LENGTH); strip_trailing_slash(mpd_base_path); - strncat(mpd_base_path, "/", DEFAULT_STRING_LENGTH); + strncat(mpd_base_path, "/", DEFAULT_STRING_LENGTH - strlen(mpd_base_path)); - // Get data directory - char *xdg_data_home_env = getenv("XDG_DATA_HOME"); - if (NULL == xdg_data_home_env) { - strncat(mpdbliss_data_file, getenv("HOME"), DEFAULT_STRING_LENGTH); - strip_trailing_slash(mpdbliss_data_file); - strncat(mpdbliss_data_file, "/.local/share/mpdbliss", DEFAULT_STRING_LENGTH); - } - else { - strncpy(mpdbliss_data_file, xdg_data_home_env, DEFAULT_STRING_LENGTH); - strip_trailing_slash(mpdbliss_data_file); - strncat(mpdbliss_data_file, "/mpdbliss", DEFAULT_STRING_LENGTH); - } - - // Ensure data folder exists - mkdir(mpdbliss_data_file, 0700); + // Get data directory, init db file + char mpdbliss_data_folder[DEFAULT_STRING_LENGTH] = ""; + char mpdbliss_data_db[DEFAULT_STRING_LENGTH] = ""; + if (0 != _init_db(mpdbliss_data_folder, mpdbliss_data_db)) { + exit(EXIT_FAILURE); + } // Set data file path - strncat(mpdbliss_data_db, mpdbliss_data_file, DEFAULT_STRING_LENGTH); - strncat(mpdbliss_data_db, "/db.sqlite3", DEFAULT_STRING_LENGTH); - strncat(mpdbliss_data_file, "/latest_mtime.txt", DEFAULT_STRING_LENGTH); + char mpdbliss_data_file[DEFAULT_STRING_LENGTH] = ""; + strncat(mpdbliss_data_file, mpdbliss_data_folder, DEFAULT_STRING_LENGTH); + strncat(mpdbliss_data_file, "/latest_mtime.txt", DEFAULT_STRING_LENGTH - strlen(mpdbliss_data_file)); // Get latest mtime time_t last_mtime = 0; // Set it to epoch by default FILE *fp = fopen(mpdbliss_data_file, "r"); if (NULL != fp) { // Read it from file if applicable - fscanf(fp, "%d\n", &last_mtime); + fscanf(fp, "%ld\n", &last_mtime); fclose(fp); } - // Initialize database table - sqlite3 *dbh; - if (0 != sqlite3_open_v2(mpdbliss_data_db, &dbh, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)) { - fprintf(stderr, "Unable to open SQLite db.\n"); - return EXIT_FAILURE; - } - 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 EXIT_FAILURE; - } - 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 EXIT_FAILURE; - } - 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 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; DELETE FROM errors; COMMIT", NULL, NULL, NULL); - if (SQLITE_OK != dberr) { - fprintf(stderr, "Error purging existing data in db: %s.\n", sqlite3_errmsg(dbh)); - return EXIT_FAILURE; - } + if (0 != _purge_db(mpdbliss_data_db)) { + exit(EXIT_FAILURE); + } // Set last_mtime to 0 last_mtime = 0; } - // Close db connection - sqlite3_close(dbh); // Check if a full rescan is needed if (1 == args_info.rescan_flag) { - update_database(conn, last_mtime, mpd_base_path); - } + last_mtime = update_database(last_mtime, mpd_base_path, mpdbliss_data_db); + if (last_mtime < 0) { + fprintf(stderr, "An error occurred while scanning library.\n"); + exit(EXIT_FAILURE); + } + } // Else, if we want to rescan errored files else if (1 == args_info.rescan_errors_flag) { - rescan_errored(mpd_base_path); + // Update last_mtime + _rescan_errored(mpdbliss_data_db, mpd_base_path); } // Else, if we requested an update of the db else if (true == args_info.update_flag) { // Rescan from last known mtime - update_database(conn, last_mtime, mpd_base_path); + last_mtime = update_database(last_mtime, mpd_base_path, mpdbliss_data_db); + if (last_mtime < 0) { + fprintf(stderr, "An error occurred while scanning library.\n"); + exit(EXIT_FAILURE); + } } else { // Setting signal handler if (signal(SIGINT, sigint_catch_function) == SIG_ERR) { fprintf(stderr, "An error occurred while setting a signal handler.\n"); - return EXIT_FAILURE; + exit(EXIT_FAILURE); } while (mpd_run_idle_loop) { @@ -542,12 +249,27 @@ int main(int argc, char** argv) { mpd_run_idle_mask(conn, MPD_IDLE_DATABASE); // Rescan from last known mtime - update_database(conn, last_mtime, mpd_base_path); + last_mtime = update_database(last_mtime, mpd_base_path, mpdbliss_data_db); + if (last_mtime < 0) { + fprintf(stderr, "An error occurred while scanning library.\n"); + exit(EXIT_FAILURE); + } // Stop listening to MPD IDLE mpd_run_noidle(conn); } } + // Write last_mtime + fp = fopen(mpdbliss_data_file, "w+"); + if (NULL != fp) { + fprintf(fp, "%ld\n", last_mtime); + fclose(fp); + } + else { + fprintf(stderr, "Unable to store latest mtime seen.\n"); + exit(EXIT_FAILURE); + } + return EXIT_SUCCESS; } diff --git a/src/utilities.c b/src/utilities.c new file mode 100644 index 0000000..f7cce9b --- /dev/null +++ b/src/utilities.c @@ -0,0 +1,13 @@ +#include +#include + +#include "utilities.h" + + +void strip_trailing_slash(char* str) +{ + size_t length = strlen(str); + if ('/' == str[length - 1]) { + str[length - 1] = '\0'; + } +}