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 0000000..377d244 Binary files /dev/null and b/src/.main.c.swp differ 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'; + } +}