diff --git a/inc/lektor/common.h b/inc/lektor/common.h index 12cea20343d4b5f0ff8524ec938046bd0be1788a..21f6076dc8bd109e7a32fe0a3987522d8452ed63 100644 --- a/inc/lektor/common.h +++ b/inc/lektor/common.h @@ -32,12 +32,12 @@ #define NOTHING /* Usefull to return nothing in previous macros */ #define STRTOL(ret, str, endptr, err_flag) \ -{ \ +({ \ err_flag = 0; \ errno = 0; \ ret = str == NULL ? 0 : strtol(str, &(endptr), 0); \ err_flag = errno != 0 || endptr == str; \ -} +}) /* Custom defined assert. */ extern void (*__lkt_assert)(const char *file, int line, const char *func, const char *msg); @@ -100,6 +100,11 @@ void __lkt_log_unlock(enum log_level); #define LKT_MESSAGE_MAX 2048 #define LKT_DEFAULT_LIST_SIZE 10 +#define LKT_BACKLOG 32 +#define COMMAND_LIST_MAX 64 +#define BUFFER_OUT_MAX 16 +#define MPD_VERSION "0.21.16" + typedef volatile enum { MPD_IDLE_NONE = 0, @@ -123,11 +128,6 @@ typedef volatile enum { MPD_IDLE_STICKER | MPD_IDLE_SUBSCRIPTION | MPD_IDLE_MESSAGE, } mpd_idle_flag; -#define LKT_BACKLOG 32 -#define COMMAND_LIST_MAX 64 -#define BUFFER_OUT_MAX 16 -#define MPD_VERSION "0.21.16" - #define STR_MATCH(str1, str2) (! strcasecmp(str1, str2)) #define STR_NMATCH(str1, str2, n) (! strncasecmp(str1, str2, n)) diff --git a/inc/lektor/database.h b/inc/lektor/database.h index e980b680ac3dd7b0026b09492f4f3609826663f6..d8174136119e9032d14b0fc32d83dbf01cd95771 100644 --- a/inc/lektor/database.h +++ b/inc/lektor/database.h @@ -171,6 +171,7 @@ struct lkt_playlist_metadata { }; /* Control playlists */ +bool database_plt_touch (volatile sqlite3 *db, const char *name); bool database_plt_create (volatile sqlite3 *db, const char *name); bool database_plt_remove (volatile sqlite3 *db, const char *name); bool database_plt_remove_pos(volatile sqlite3 *db, const char *name, int pos); diff --git a/src/database/playlist.c b/src/database/playlist.c index eef0491dad14187106e1c128f5b9620d4b0bfcf1..35c45950e690abbd4a0c75368911051d9067bc76 100644 --- a/src/database/playlist.c +++ b/src/database/playlist.c @@ -9,6 +9,34 @@ #include "macro.h" +bool +database_plt_touch(volatile sqlite3 *db, const char *name) +{ + static const char *SQL_STMT = "SELECT name FROM playlist WHERE name = ?"; + static const char *SQL_TOUCH = "UPDATE playlist SET last_update = strftime('%s', 'now') WHERE name = ?"; + sqlite3_stmt *stmt = NULL; + bool sta = false; + + SQLITE_PREPARE(db, stmt, SQL_STMT, error); + SQLITE_BIND_TEXT(db, stmt, 1, name, error); + + if (sqlite3_step(stmt) != SQLITE_ROW) { + sta = database_plt_create(db, name); + } else { + sqlite3_finalize(stmt); + stmt = NULL; + SQLITE_PREPARE(db, stmt, SQL_TOUCH, error); + SQLITE_BIND_TEXT(db, stmt, 1, name, error); + SQLITE_STEP_DONE(db, stmt, error); + sta = true; + } + +error: + if (stmt) + sqlite3_finalize(stmt); + return sta; +} + bool database_plt_create(volatile sqlite3 *db, const char *name) { @@ -159,22 +187,29 @@ error: bool database_plt_add_uri(volatile sqlite3 *db, const char *name, struct lkt_uri *uri) { - static const char *SQL = - "INSERT OR REPLACE INTO kara_playlist (kara_id, playlist_id) " - "SELECT" - " kara.id," - " (SELECT playlist.id FROM playlist WHERE name = ? COLLATE NOCASE)" - "FROM kara WHERE kara.%s LIKE ?;"; - char SQL_STMT[LKT_MAX_SQLITE_STATEMENT], sta = false; - sqlite3_stmt *stmt; - - safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL, uri->column_name); - SQLITE_PREPARE(db, stmt, SQL_STMT, error); - SQLITE_BIND_TEXT(db, stmt, 1, name, error); - SQLITE_BIND_TEXT(db, stmt, 2, (char *) uri->value, error); - SQLITE_STEP_DONE(db, stmt, error); - sta = true; + if (uri->is_int) { + LOG_ERROR("DB", "Add by ID (where the uri is an integer) is not implemented yet"); + return false; + } + + else { + static const char *SQL = + "INSERT OR REPLACE INTO kara_playlist (kara_id, playlist_id) " + "SELECT" + " kara.id," + " (SELECT playlist.id FROM playlist WHERE name = ? COLLATE NOCASE)" + "FROM kara WHERE kara.%s LIKE ?;"; + char SQL_STMT[LKT_MAX_SQLITE_STATEMENT], sta = false; + sqlite3_stmt *stmt; + + safe_snprintf(SQL_STMT, LKT_MAX_SQLITE_STATEMENT, SQL, uri->column_name); + SQLITE_PREPARE(db, stmt, SQL_STMT, error); + SQLITE_BIND_TEXT(db, stmt, 1, name, error); + SQLITE_BIND_TEXT(db, stmt, 2, (char *) uri->value, error); + SQLITE_STEP_DONE(db, stmt, error); + sta = true; error: - sqlite3_finalize(stmt); - return sta; + sqlite3_finalize(stmt); + return sta; + } } diff --git a/src/module/module_repo.c b/src/module/module_repo.c index 5866a92f7b50ffe2f36a13e206313f82b228289b..c029e999a38607917f01b266c2d75fc721f9bca1 100644 --- a/src/module/module_repo.c +++ b/src/module/module_repo.c @@ -80,25 +80,30 @@ struct __file { /* Recursive mkdir, where the last word of the string is a file, not a folder. */ static inline void -__mkdir(const char *dir) +__mkdir(const char *dir, unsigned int umask) { /* TODO pour le Kubat du futur: include le umask dans la conf. */ + if (umask == 0) + umask = 00700; + char tmp[PATH_MAX]; char *p = NULL; safe_snprintf(tmp, sizeof(tmp) / sizeof(char), "%s", dir); size_t len = strlen(tmp); if (tmp[len - 1] == '/') tmp[len - 1] = 0; + for (p = tmp + 1; *p; p++) { if (*p == '/') { *p = 0; - mkdir(tmp, 00700); + mkdir(tmp, umask); *p = '/'; } } + /* Do the final mkdir, because it's a folder. */ - mkdir(tmp, 00700); + mkdir(tmp, umask); } static inline void @@ -115,7 +120,7 @@ __craft_filename_non_obfuscate(char str[PATH_MAX], size_t len, struct kara *kara * possible. The program will fail later, when write will be attempted. */ len += safe_snprintf(str + len, PATH_MAX - len, "%s/%s/%s/", kara->mdt.category, kara->mdt.language, kara->mdt.author_name); - __mkdir(str); + __mkdir(str, 0); if (access(str, R_OK | W_OK)) LOG_ERROR("REPO", "No access in read / write for folder %s", str); safe_snprintf(str + len, PATH_MAX - len, "%s - %s%d - %s.mkv", kara->mdt.source_name, @@ -190,7 +195,7 @@ __safe_json_get_long(struct json_object *json, const char *key, long *ret) } static int -__json_sync(struct module_repo_internal *repo, struct json_object **json) +__json_dl(const char *url, struct json_object **json) { RETURN_UNLESS(json, "Invalid argument", 1); CURL *curl_handle; @@ -208,15 +213,14 @@ __json_sync(struct module_repo_internal *repo, struct json_object **json) curl_handle = curl_easy_init(); curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl_handle, CURLOPT_URL, repo->get_all_json); + curl_easy_setopt(curl_handle, CURLOPT_URL, url); curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, __write_mem); curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &file); curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); res = curl_easy_perform(curl_handle); if (res != CURLE_OK) { - LOG_ERROR("CURL", "curl_easy_perform failed on url %s: %s", repo->get_all_json, - curl_easy_strerror(res)); + LOG_ERROR("CURL", "curl_easy_perform failed on url %s: %s", url, curl_easy_strerror(res)); goto err; } @@ -439,7 +443,7 @@ __worker_update(void *__repo) struct json_object *json; LOG_INFO("REPO", "Download kara list from %s (%s), directory is %s", repo->name, repo->get_all_json, repo->kara_dir); - if (__json_sync(repo, &json)) { + if (__json_dl(repo->get_all_json, &json)) { LOG_ERROR("REPO", "Failed to get json, possibly no internet connexion or repo is down"); pthread_exit(NULL); } @@ -475,8 +479,8 @@ __worker_rescan(void *__repo) repo->updating &= REPO_UPDATE_KARA; GOTO_IF(pthread_mutex_unlock(&(repo->mtx)), "Failed to unlock", end_no_lock); - database_update(repo->db, kara_prefix, 0); /* Don't check timestamp. - * TODO: Sometimes we want to check them */ + database_update(repo->db, kara_prefix, 0); + /* Don't check timestamp. TODO: Sometimes we want to check them */ GOTO_IF(pthread_mutex_lock(&(repo->mtx)), "Failed to lock", end_no_lock); repo->updating &= (~ REPO_UPDATE_KARA); @@ -487,6 +491,61 @@ end_no_lock: pthread_exit(NULL); } +static inline void +__handle_fav_list(struct module_repo_internal *repo, char *fav, size_t fav_size) +{ + struct json_object *json, *item_json = NULL; + char fav_url[LKT_LINE_MAX]; + char *fav_origin = NULL; + + memset(fav_url, 0, LKT_LINE_MAX * sizeof(char)); + strncat(fav_url, repo->get_fav_json, LKT_LINE_MAX - 1); + strncat(fav_url, fav, LKT_LINE_MAX - 1); + + RETURN_IF(__json_dl(fav_url, &json), "Failed to download fav list", NOTHING); + + /* Prepend by `@`, to diferentiate with playlists */ + size_t fav_len = strlen(fav); + if (fav_size - 1 == fav_len) { + fav_origin = fav; + LOG_WARN("REPO", "Fav list has a name to big to prepend it by '@'. Possible collision with other playlists"); + } else { + memmove(fav + sizeof(char), fav, fav_len * sizeof(char)); + fav_origin = fav; + fav[0] = (char) ("@"); + fav += sizeof(char); + LOG_INFO("REPO", "Importing fav list '%s' as '%s'", fav_origin, fav); + } + + database_plt_touch(repo->db, fav); + struct lkt_uri uri = { + .type = uri_id, + .is_int = true, + }; + + size_t len = json_object_array_length(json), i; + long id; + for (i = 0; i < len; ++i) { + item_json = json_object_array_get_idx(json, i); + if (item_json == NULL) { + LOG_ERROR("REPO", "There is no kara at index %ld in fav list %s", i, fav); + continue; + } + if (__safe_json_get_long(item_json, "id", &id)) { + LOG_ERROR("REPO", "Failed to get the id of the kara in fav list %s", fav_origin); + continue; + } + uri.id = id; + if (!database_plt_add_uri(repo->db, fav, &uri)) { + LOG_ERROR("REPO", "Failed to add kara %ld to playlist %s", fav); + continue; + } + } + + LOG_INFO("REPO", "Finished importing fav list '%s' as '%s'", fav_origin, fav); + json_object_put(json); +} + static void * __worker_import_favorites(void *__repo) { @@ -496,19 +555,33 @@ __worker_import_favorites(void *__repo) repo->updating &= REPO_UPDATE_FAV; GOTO_IF(pthread_mutex_unlock(&(repo->mtx)), "Failed to unlock", end_no_lock); - // struct json_object *json; + struct json_object *json, *item_json = NULL; LOG_INFO("REPO", "Download favorite lists from %s (%s), directory is %s", - repo->name, repo->get_all_json, repo->kara_dir); - // if (__json_sync(repo, &json)) { - // LOG_ERROR("REPO", "Failed to get json, possibly no internet connexion or repo is down"); - // pthread_exit(NULL); - // } - // __handle_got_json(repo->db, repo, json); - // LOG_INFO("REPO", "Finished to download and insert kara list"); - // json_object_put(json); - // __handle_deleted_kara(repo->db); - // LOG_INFO("REPO", "Finished to deal with deleted kara"); - // database_updated(repo->db); + repo->name, repo->get_fav_json, repo->kara_dir); + if (__json_dl(repo->get_fav_json, &json)) { + LOG_ERROR("REPO", "Failed to get json, possibly no internet connexion or repo is down"); + pthread_exit(NULL); + } + LOG_INFO("REPO", "Finished to dl favorite lists"); + + size_t len = json_object_array_length(json), i; + char fav_name[LKT_LINE_MAX]; + for (i = 0; i < len; ++i) { + item_json = json_object_array_get_idx(json, i); + if (item_json == NULL) { + LOG_ERROR("REPO", "There is no item with index %ld in fav list", i); + continue; + } + GOTO_IF(__safe_json_get_string(item_json, "pseudo", fav_name, LKT_LINE_MAX), + "Field 'pseudo' not found in json item", error); + + /* TODO: Add a way to use the workers to do this for each fav list */ + __handle_fav_list(repo, fav_name, LKT_LINE_MAX); + } + +error: + json_object_put(json); + LOG_INFO("REPO", "Finished to deal with %ld favorite lists", len); GOTO_IF(pthread_mutex_lock(&(repo->mtx)), "Failed to lock", end_no_lock); repo->updating &= (~ REPO_UPDATE_FAV);