diff --git a/inc/mthread/mthread.h b/inc/mthread/mthread.h index cef1aa21e938592721d142f15b4807049d2dc529..869e0ddd4c3b0ecfc0b1e4eaca697a5a5761046a 100644 --- a/inc/mthread/mthread.h +++ b/inc/mthread/mthread.h @@ -48,35 +48,35 @@ typedef struct mthread_sem_s mthread_sem_t; /* Function for handling threads. */ -int mthread_create(mthread_t *__threadp, const mthread_attr_t __attr, void *(*__start_routine) (void *), void *__arg); +extern int mthread_create(mthread_t *__threadp, const mthread_attr_t __attr, void *(*__start_routine) (void *), void *__arg); -mthread_t mthread_self(void); -int mthread_equal (mthread_t __thread1, mthread_t __thread2); -void mthread_exit (void *__retval); -int mthread_join (mthread_t __th, void **__thread_return); +extern mthread_t mthread_self(void); +extern int mthread_equal (mthread_t __thread1, mthread_t __thread2); +extern void mthread_exit (void *__retval); +extern int mthread_join (mthread_t __th, void **__thread_return); /* Functions for mutex handling. */ -int mthread_mutex_init (mthread_mutex_t *__mutex, const mthread_mutexattr_t *__mutex_attr); -int mthread_mutex_destroy(mthread_mutex_t *__mutex); -int mthread_mutex_trylock(mthread_mutex_t *__mutex); -int mthread_mutex_lock (mthread_mutex_t *__mutex); -int mthread_mutex_unlock (mthread_mutex_t *__mutex); +extern int mthread_mutex_init (mthread_mutex_t *__mutex, const mthread_mutexattr_t *__mutex_attr); +extern int mthread_mutex_destroy(mthread_mutex_t *__mutex); +extern int mthread_mutex_trylock(mthread_mutex_t *__mutex); +extern int mthread_mutex_lock (mthread_mutex_t *__mutex); +extern int mthread_mutex_unlock (mthread_mutex_t *__mutex); /* Functions for handling conditional variables. */ -int mthread_cond_init (mthread_cond_t *__cond, const mthread_condattr_t *__cond_attr); -int mthread_cond_destroy (mthread_cond_t *__cond); -int mthread_cond_signal (mthread_cond_t *__cond); -int mthread_cond_broadcast(mthread_cond_t *__cond); -int mthread_cond_wait (mthread_cond_t *__cond, mthread_mutex_t *__mutex); +extern int mthread_cond_init (mthread_cond_t *__cond, const mthread_condattr_t *__cond_attr); +extern int mthread_cond_destroy (mthread_cond_t *__cond); +extern int mthread_cond_signal (mthread_cond_t *__cond); +extern int mthread_cond_broadcast(mthread_cond_t *__cond); +extern int mthread_cond_wait (mthread_cond_t *__cond, mthread_mutex_t *__mutex); /* Functions for handling thread-specific data. */ -int mthread_key_create (mthread_key_t *__key, void (*__destr_function) (void *)); -int mthread_key_delete (mthread_key_t __key); -int mthread_setspecific (mthread_key_t __key, const void *__pointer); -void *mthread_getspecific(mthread_key_t __key); +extern int mthread_key_create (mthread_key_t *__key, void (*__destr_function) (void *)); +extern int mthread_key_delete (mthread_key_t __key); +extern int mthread_setspecific (mthread_key_t __key, const void *__pointer); +extern void *mthread_getspecific(mthread_key_t __key); /* Functions for handling initialization. */ @@ -88,21 +88,21 @@ void *mthread_getspecific(mthread_key_t __key); The initialization functions might throw exception which is why this function is not marked with . */ -int mthread_once(mthread_once_t *__once_control, void (*__init_routine) (void)); +extern int mthread_once(mthread_once_t *__once_control, void (*__init_routine) (void)); /* Functions for handling semaphore. */ -int mthread_sem_init(mthread_sem_t *sem, unsigned int value); -int mthread_sem_wait(mthread_sem_t *sem); /* P(sem), wait(sem) */ -int mthread_sem_post(mthread_sem_t *sem); /* V(sem), signal(sem) */ +extern int mthread_sem_init(mthread_sem_t *sem, unsigned int value); +extern int mthread_sem_wait(mthread_sem_t *sem); /* P(sem), wait(sem) */ +extern int mthread_sem_post(mthread_sem_t *sem); /* V(sem), signal(sem) */ -int mthread_sem_getvalue(mthread_sem_t *sem, int *sval); -int mthread_sem_trywait (mthread_sem_t *sem); -int mthread_sem_destroy (mthread_sem_t *sem); /* undo sem_init() */ +extern int mthread_sem_getvalue(mthread_sem_t *sem, int *sval); +extern int mthread_sem_trywait (mthread_sem_t *sem); +extern int mthread_sem_destroy (mthread_sem_t *sem); /* undo sem_init() */ -void mthread_yield(); +extern void mthread_yield(); /* Initialize mthread. */ -void mthread_init(void); -void *mthread_main(void *arg); +extern void mthread_init(void); +extern void *mthread_main(void *arg); diff --git a/meson.build b/meson.build index f92d8edd08fb11ebfbc60f6398a0825b809623ee..091dd75832c62eabeef329eeab8df89277a56bcd 100644 --- a/meson.build +++ b/meson.build @@ -60,7 +60,6 @@ core_sources = [ 'src/mkv/bufferfd.c' , 'src/utils.c' , 'src/uri.c' , 'src/ini/ini.c' - , 'src/repo/curl.c' , 'src/repo/downloader.c' , 'src/repo/sync.c' , 'src/thread.c' @@ -106,7 +105,7 @@ srv = executable( meson.project_name() + 'd' metadata = executable( 'lktadm' , files('src/main/lktadm.c', 'src/cmd.c') , include_directories : includes - , dependencies : bin_deps + , dependencies : [ bin_deps, mthread_deps ] , install : true ) diff --git a/src/repo/curl.c b/src/repo/curl.c deleted file mode 100644 index 727070127ee09396e13cf8bd1abd834f14507aa1..0000000000000000000000000000000000000000 --- a/src/repo/curl.c +++ /dev/null @@ -1,301 +0,0 @@ -#define _POSIX_C_SOURCE 200809L - -#include <common/common.h> -#include <lektor/repo.h> -#include <lektor/macro.h> -#include <lektor/database.h> -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/stat.h> -#include <fcntl.h> - -static volatile unsigned int curl_init = false; - -#define URL_MAX_LEN 1024 -#define DEFAULT_URL "https://kurisu.iiens.net" -#define GET_ID_JSON DEFAULT_URL "/api_karas.php?id=%ld" -#define GET_ID_FILE DEFAULT_URL "/download.php?id=%ld" - -struct memory { - void *mem; - size_t size; -}; - -struct file { - const char *path; - int fd; -}; - -static size_t -write_mem__(char *data, size_t size, size_t nmem, void *user) -{ - size_t realsize = size * nmem; - struct memory *mem = (struct memory *) user; - - void *ptr = realloc(mem->mem, mem->size + realsize); - RETURN_UNLESS(ptr, "Out of memory", 0); - - mem->mem = ptr; - memcpy(((uint8_t *) mem->mem) + mem->size, data, realsize); - mem->size += realsize; - return realsize; -} - -static size_t -write_disk__(char *data, size_t size, size_t nmem, void *user) -{ - ssize_t realsize = size * nmem; - struct file *file = (struct file *) user; - RETURN_IF(write(file->fd, data, realsize) != realsize, "Failed to write", 0); - return realsize; -} - -int -repo_new(struct lkt_repo *const repo_, const char *name_, const char *url_, volatile sqlite3 *db) -{ - if (!curl_init) { - curl_global_init(CURL_GLOBAL_ALL); - curl_init = 1; - } else - ++curl_init; - - struct lkt_repo repo = { - /* Just the repo */ - .name = name_, - .base_url = url_, - .get_id_json = GET_ID_JSON, - .get_id_file = GET_ID_FILE, - .get_all_json = DEFAULT_URL "/api_karas.php", // TODO - .kara_dir = "/home/kara/", // TODO - .version = 1, - - /* The db */ - .db = db, - }; - - memcpy(repo_, &repo, sizeof(struct lkt_repo)); - return 0; -} - -void -repo_free(struct lkt_repo *const repo) -{ - UNUSED(repo); - --curl_init; - if (!curl_init) - curl_global_cleanup(); -} - -int -safe_json_get_string(struct json_object *jobj, const char *key, char *content, const size_t len) -{ - const char *got; - struct json_object *field; - RETURN_UNLESS(json_object_object_get_ex(jobj, key, &field), "Key not found in json", 1); - got = json_object_get_string(field); - RETURN_UNLESS(got, "Got a NULL for the key, may be an error", 1); - strncpy(content, got, len - 1); - content[len - 1] = 0; - return 0; -} - -int -safe_json_get_int32(struct json_object *json, const char *key, int32_t *ret) -{ - struct json_object *field; - RETURN_UNLESS(json_object_object_get_ex(json, key, &field), "Key not found", 1); - errno = 0; - *ret = json_object_get_int(field); - RETURN_IF(errno = EINVAL, "Invalid 32bit integer", 1); - RETURN_IF(*ret == INT32_MAX || *ret == INT32_MIN, "Out of bound 32bit integer", 1); - return 0; -} - -int -repo_get_alljson_sync(struct lkt_repo *const repo, struct json_object **json) -{ - RETURN_UNLESS(json, "Invalid argument", 1); - - CURL *curl_handle; - CURLcode res; - int ret = 1; - - struct memory file = { - .mem = NULL, - .size = 0. - }; - - curl_handle = curl_easy_init(); - curl_easy_setopt(curl_handle, CURLOPT_URL, repo->get_all_json); - 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_SCT("CURL", "curl_easy_perform failed: %s", curl_easy_strerror(res)); - free(file.mem); - goto err; - } - - *json = json_tokener_parse(file.mem); - ret = 0; -err: - curl_easy_cleanup(curl_handle); - return ret; -} - -int -repo_get_id(struct lkt_repo *const repo, const uint64_t id, struct kara_metadata *mdt) -{ - CURL *curl_handle; - CURLcode res; - struct json_object *jobj, *field; - int ret = 1, err = 0; - - RETURN_UNLESS(mdt, "Invalid argument", 1); - char *url = calloc(URL_MAX_LEN, sizeof(char)); - RETURN_UNLESS(url, "Out of memory", errno = ENOMEM); - - struct memory file = { - .mem = NULL, - .size = 0, - }; - - memset(url, 0, URL_MAX_LEN * sizeof(char)); - snprintf(url, URL_MAX_LEN - 1, repo->get_id_json, id); - url[URL_MAX_LEN - 1] = 0; - curl_handle = curl_easy_init(); - 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_SCT("CURL", "curl_easy_perform failed: %s", curl_easy_strerror(res)); - free(url); - free(file.mem); - curl_easy_cleanup(curl_handle); - return 1; - } - - curl_easy_cleanup(curl_handle); - - /* Read the json. */ - - jobj = json_tokener_parse(file.mem); - - if (json_object_object_get_ex(jobj, "message", &field)) { - LOG_ERROR("Kara with id %lu not found, message is: %s", id, json_object_get_string(field)); - goto err; - } - - LOG_INFO("Got kara with id %lu", id); - - err |= safe_json_get_string(jobj, "song_name", mdt->song_name, LEKTOR_TAG_MAX); - err |= safe_json_get_string(jobj, "source_name", mdt->source_name, LEKTOR_TAG_MAX); - err |= safe_json_get_string(jobj, "category", mdt->category, LEKTOR_TAG_MAX); - err |= safe_json_get_string(jobj, "language", mdt->language, LEKTOR_TAG_MAX); - err |= safe_json_get_string(jobj, "author_name", mdt->author_name, LEKTOR_TAG_MAX); - err |= safe_json_get_string(jobj, "song_type", mdt->song_type, LEKTOR_TAG_MAX); - - errno = 0; - - if (!json_object_object_get_ex(jobj, "song_number", &field)) { - LOG_ERROR("%s", "No object with key 'song_number' in json, error"); - goto err; - } - - mdt->song_number = json_object_get_int(field); - - if (errno == EINVAL) { - LOG_ERROR("%s", "Invalid integer for field with key 'song_number'"); - goto err; - } - - if (mdt->song_number == INT32_MAX || mdt->song_number == INT32_MIN) { - LOG_ERROR("Out of bound int32_t '%d' for field with key 'song_number'", mdt->song_number); - goto err; - } - - - ret = 0; -err: - free(url); - json_object_put(jobj); /* Delete object. */ - return ret; -} - -int -repo_download_id_sync(struct lkt_repo *const repo, const uint64_t id, const char *kara_path, struct kara_metadata *mdt_ret) -{ - RETURN_UNLESS(kara_path, "Invalid argument", 1); - struct kara_metadata mdt; - CURL *curl_handle; - int ret = 1, fd; - char *url = calloc(URL_MAX_LEN, sizeof(char)); - char *ct; - RETURN_UNLESS(url, "Out of memory", errno = ENOMEM); - - if (repo_get_id(repo, id, mdt_ret ? mdt_ret : &mdt)) { - LOG_ERROR("%s", "Failed to get kara metadata from kurisu"); - goto err_no_curl; - } - - errno = 0; - fd = open(kara_path, - O_WRONLY | O_APPEND | O_CREAT | O_EXCL | O_NOFOLLOW, - S_IRUSR | S_IWUSR); - - if (fd < 0) { - if (errno == EEXIST) - LOG_ERROR("File '%s' already exists", kara_path); - - else - LOG_ERROR("Could not open file '%s'", kara_path); - - goto err_no_curl; - } - - struct file file = { - .path = kara_path, - .fd = fd, - }; - - memset(url, 0, URL_MAX_LEN * sizeof(char)); - snprintf(url, URL_MAX_LEN - 1, repo->get_id_file, id); - url[URL_MAX_LEN - 1] = 0; - curl_handle = curl_easy_init(); - curl_easy_setopt(curl_handle, CURLOPT_URL, url); - curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_disk__); - curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &file); - curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); - ret = curl_easy_perform(curl_handle); - - if (ret != CURLE_OK) { - LOG_ERROR_SCT("CURL", "curl_easy_perform failed: %s", curl_easy_strerror(ret)); - goto err; - } - - if (CURLE_OK == (ret = curl_easy_getinfo(curl_handle, CURLINFO_CONTENT_TYPE, &ct))) - LOG_INFO_SCT("CURL", "Content-Type is '%s'", ct); - else { - LOG_ERROR_SCT("CURL", "Failed to get Content-Type: %s", curl_easy_strerror(ret)); - goto err; - } - - if (repo->db && !database_update_add((sqlite3 *) repo->db, kara_path, mdt_ret ? mdt_ret : &mdt, id, true)) { - LOG_ERROR("%s", "Failed to add kara to database"); - goto err; - } - - ret = 0; -err: - curl_easy_cleanup(curl_handle); -err_no_curl: - free(url); - return ret; -} diff --git a/src/repo/downloader.c b/src/repo/downloader.c index d6ad28e13f245418d9870ca5123554719770b0f6..10f27fd5e4fb20b126e4ef08b39b7d07121c92c5 100644 --- a/src/repo/downloader.c +++ b/src/repo/downloader.c @@ -5,17 +5,305 @@ #include <unistd.h> #include <string.h> #include <limits.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <common/common.h> #include <mthread/mthread.h> #include <lektor/macro.h> #include <lektor/repo.h> #include <lektor/database.h> -/* Find it in the repo/curl.c file. FIXME */ -extern int -safe_json_get_string(struct json_object *jobj, const char *key, char *content, const size_t len); -extern int -safe_json_get_int32(struct json_object *json, const char *key, int32_t *ret); +static volatile unsigned int curl_init = false; + +#define URL_MAX_LEN 1024 +#define DEFAULT_URL "https://kurisu.iiens.net" +#define GET_ID_JSON DEFAULT_URL "/api_karas.php?id=%ld" +#define GET_ID_FILE DEFAULT_URL "/download.php?id=%ld" + +struct memory { + void *mem; + size_t size; +}; + +struct file { + const char *path; + int fd; +}; + +static size_t +write_mem__(char *data, size_t size, size_t nmem, void *user) +{ + size_t realsize = size * nmem; + struct memory *mem = (struct memory *) user; + + void *ptr = realloc(mem->mem, mem->size + realsize); + RETURN_UNLESS(ptr, "Out of memory", 0); + + mem->mem = ptr; + memcpy(((uint8_t *) mem->mem) + mem->size, data, realsize); + mem->size += realsize; + return realsize; +} + +static size_t +write_disk__(char *data, size_t size, size_t nmem, void *user) +{ + ssize_t realsize = size * nmem; + struct file *file = (struct file *) user; + RETURN_IF(write(file->fd, data, realsize) != realsize, "Failed to write", 0); + return realsize; +} + +int +repo_new(struct lkt_repo *const repo_, const char *name_, const char *url_, volatile sqlite3 *db) +{ + if (!curl_init) { + curl_global_init(CURL_GLOBAL_ALL); + curl_init = 1; + } else + ++curl_init; + + struct lkt_repo repo = { + /* Just the repo */ + .name = name_, + .base_url = url_, + .get_id_json = GET_ID_JSON, + .get_id_file = GET_ID_FILE, + .get_all_json = DEFAULT_URL "/api_karas.php", // TODO + .kara_dir = "/home/kara/", // TODO + .version = 1, + + /* The db */ + .db = db, + }; + + memcpy(repo_, &repo, sizeof(struct lkt_repo)); + return 0; +} + +void +repo_free(struct lkt_repo *const repo) +{ + UNUSED(repo); + --curl_init; + if (!curl_init) + curl_global_cleanup(); +} + +int +safe_json_get_string(struct json_object *jobj, const char *key, char *content, const size_t len) +{ + const char *got; + struct json_object *field; + RETURN_UNLESS(json_object_object_get_ex(jobj, key, &field), "Key not found in json", 1); + got = json_object_get_string(field); + RETURN_UNLESS(got, "Got a NULL for the key, may be an error", 1); + strncpy(content, got, len - 1); + content[len - 1] = 0; + return 0; +} + +int +safe_json_get_int32(struct json_object *json, const char *key, int32_t *ret) +{ + struct json_object *field; + RETURN_UNLESS(json_object_object_get_ex(json, key, &field), "Key not found", 1); + errno = 0; + *ret = json_object_get_int(field); + RETURN_IF(errno = EINVAL, "Invalid 32bit integer", 1); + RETURN_IF(*ret == INT32_MAX || *ret == INT32_MIN, "Out of bound 32bit integer", 1); + return 0; +} + +int +repo_get_alljson_sync(struct lkt_repo *const repo, struct json_object **json) +{ + RETURN_UNLESS(json, "Invalid argument", 1); + + CURL *curl_handle; + CURLcode res; + int ret = 1; + + struct memory file = { + .mem = NULL, + .size = 0. + }; + + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, repo->get_all_json); + 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_SCT("CURL", "curl_easy_perform failed: %s", curl_easy_strerror(res)); + free(file.mem); + goto err; + } + + *json = json_tokener_parse(file.mem); + ret = 0; +err: + curl_easy_cleanup(curl_handle); + return ret; +} + +int +repo_get_id(struct lkt_repo *const repo, const uint64_t id, struct kara_metadata *mdt) +{ + CURL *curl_handle; + CURLcode res; + struct json_object *jobj, *field; + int ret = 1, err = 0; + + RETURN_UNLESS(mdt, "Invalid argument", 1); + char *url = calloc(URL_MAX_LEN, sizeof(char)); + RETURN_UNLESS(url, "Out of memory", errno = ENOMEM); + + struct memory file = { + .mem = NULL, + .size = 0, + }; + + memset(url, 0, URL_MAX_LEN * sizeof(char)); + snprintf(url, URL_MAX_LEN - 1, repo->get_id_json, id); + url[URL_MAX_LEN - 1] = 0; + curl_handle = curl_easy_init(); + 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_SCT("CURL", "curl_easy_perform failed: %s", curl_easy_strerror(res)); + free(url); + free(file.mem); + curl_easy_cleanup(curl_handle); + return 1; + } + + curl_easy_cleanup(curl_handle); + + /* Read the json. */ + + jobj = json_tokener_parse(file.mem); + + if (json_object_object_get_ex(jobj, "message", &field)) { + LOG_ERROR("Kara with id %lu not found, message is: %s", id, json_object_get_string(field)); + goto err; + } + + LOG_INFO("Got kara with id %lu", id); + + err |= safe_json_get_string(jobj, "song_name", mdt->song_name, LEKTOR_TAG_MAX); + err |= safe_json_get_string(jobj, "source_name", mdt->source_name, LEKTOR_TAG_MAX); + err |= safe_json_get_string(jobj, "category", mdt->category, LEKTOR_TAG_MAX); + err |= safe_json_get_string(jobj, "language", mdt->language, LEKTOR_TAG_MAX); + err |= safe_json_get_string(jobj, "author_name", mdt->author_name, LEKTOR_TAG_MAX); + err |= safe_json_get_string(jobj, "song_type", mdt->song_type, LEKTOR_TAG_MAX); + + errno = 0; + + if (!json_object_object_get_ex(jobj, "song_number", &field)) { + LOG_ERROR("%s", "No object with key 'song_number' in json, error"); + goto err; + } + + mdt->song_number = json_object_get_int(field); + + if (errno == EINVAL) { + LOG_ERROR("%s", "Invalid integer for field with key 'song_number'"); + goto err; + } + + if (mdt->song_number == INT32_MAX || mdt->song_number == INT32_MIN) { + LOG_ERROR("Out of bound int32_t '%d' for field with key 'song_number'", mdt->song_number); + goto err; + } + + + ret = 0; +err: + free(url); + json_object_put(jobj); /* Delete object. */ + return ret; +} + +int +repo_download_id_sync(struct lkt_repo *const repo, const uint64_t id, const char *kara_path, struct kara_metadata *mdt_ret) +{ + RETURN_UNLESS(kara_path, "Invalid argument", 1); + struct kara_metadata mdt; + CURL *curl_handle; + int ret = 1, fd; + char *url = calloc(URL_MAX_LEN, sizeof(char)); + char *ct; + RETURN_UNLESS(url, "Out of memory", errno = ENOMEM); + + if (repo_get_id(repo, id, mdt_ret ? mdt_ret : &mdt)) { + LOG_ERROR("%s", "Failed to get kara metadata from kurisu"); + goto err_no_curl; + } + + errno = 0; + fd = open(kara_path, + O_WRONLY | O_APPEND | O_CREAT | O_EXCL | O_NOFOLLOW, + S_IRUSR | S_IWUSR); + + if (fd < 0) { + if (errno == EEXIST) + LOG_ERROR("File '%s' already exists", kara_path); + + else + LOG_ERROR("Could not open file '%s'", kara_path); + + goto err_no_curl; + } + + struct file file = { + .path = kara_path, + .fd = fd, + }; + + memset(url, 0, URL_MAX_LEN * sizeof(char)); + snprintf(url, URL_MAX_LEN - 1, repo->get_id_file, id); + url[URL_MAX_LEN - 1] = 0; + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, url); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_disk__); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &file); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); + ret = curl_easy_perform(curl_handle); + + if (ret != CURLE_OK) { + LOG_ERROR_SCT("CURL", "curl_easy_perform failed: %s", curl_easy_strerror(ret)); + goto err; + } + + if (CURLE_OK == (ret = curl_easy_getinfo(curl_handle, CURLINFO_CONTENT_TYPE, &ct))) + LOG_INFO_SCT("CURL", "Content-Type is '%s'", ct); + else { + LOG_ERROR_SCT("CURL", "Failed to get Content-Type: %s", curl_easy_strerror(ret)); + goto err; + } + + if (repo->db && !database_update_add((sqlite3 *) repo->db, kara_path, mdt_ret ? mdt_ret : &mdt, id, true)) { + LOG_ERROR("%s", "Failed to add kara to database"); + goto err; + } + + ret = 0; +err: + curl_easy_cleanup(curl_handle); +err_no_curl: + free(url); + return ret; +} /* Download a kara */