diff --git a/Makefile b/Makefile index 366f458..b5ec9cd 100644 --- a/Makefile +++ b/Makefile @@ -19,9 +19,9 @@ DEBUG=0 INCFLAGS= LDFLAGS=-lsqlite3 DEFS= -CFLAGS=$(INCFLAGS) -std=c99 -Wall -Wextra -Wfatal-errors -Werror -HDRS=src/arg_parse.h src/util.h src/db.h -OBJS=src/main.o src/arg_parse.o src/db.o +CFLAGS=$(INCFLAGS) -std=gnu99 -Wall -Wextra -Wfatal-errors -Werror +HDRS=src/arg_parse.h src/util.h src/db.h src/cmd.h +OBJS=src/main.o src/arg_parse.o src/db.o src/cmd.o VERSION=1.0 ifeq ($(PREFIX),) diff --git a/src/arg_parse.h b/src/arg_parse.h index 33ae751..1e8dd97 100644 --- a/src/arg_parse.h +++ b/src/arg_parse.h @@ -21,6 +21,7 @@ enum cmd_id { CMD_UNKNOWN = 0, + CMD_ADD, CMD_HELP, CMD_VERSION, }; @@ -31,6 +32,7 @@ struct cmd { }; static const struct cmd commands[] = { + { CMD_ADD, {"add", "new"} }, { CMD_HELP, {"help", "-h", "--help"} }, { CMD_VERSION, {"version", "-v", "--version"} }, }; @@ -48,6 +50,7 @@ static inline void print_help(void) { print_usage(); printf("COMMANDS:\n" + "\tadd, new Add a new recipe to the database\n" "\thelp, -h, --help Show this help information.\n" "\tversion, -v, --version Show version information.\n" "\n"); diff --git a/src/cmd.c b/src/cmd.c new file mode 100644 index 0000000..910dce1 --- /dev/null +++ b/src/cmd.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 Nicolás Ortega Froysa + * Nicolás Ortega Froysa + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "cmd.h" +#include "db.h" + +#include +#include +#include +#include + +int command_add(void) { + char *name = NULL, *description = NULL, *ingredients = NULL, *tags = NULL; + size_t name_len, description_len, ingredients_len, tags_len; + int recipe_id, ingredient_id, tag_id; + + if(!db_open()) { + fprintf(stderr, "Failed to open database. Cannot add new entry.\n"); + return 0; + } + + printf("Name: "); + getline(&name, &name_len, stdin); + // eliminate trailing newline + name[strlen(name) - 1] = '\0'; + + printf("Description: "); + getline(&description, &description_len, stdin); + // eliminate trailing newline + description[strlen(description) - 1] = '\0'; + + printf("Ingredients (comma separated): "); + getline(&ingredients, &ingredients_len, stdin); + // eliminate trailing newline + ingredients[strlen(ingredients) - 1] = '\0'; + + printf("Tags (comma separated): "); + getline(&tags, &tags_len, stdin); + // eliminate trailing newline + tags[strlen(tags) - 1] = '\0'; + + if((recipe_id = db_get_recipe_id(name)) <= 0) + recipe_id = db_add_recipe(name, description); + free(name); + free(description); + + for(char *i = strtok(ingredients, ","); i; i = strtok(NULL,",")) { + // remove leading blank spaces + while(isblank(i[0])) + i += sizeof(char); + + // remove trailing blank spaces + size_t i_len = strlen(i); + while(isblank(i[i_len - 1])) { + i[i_len - 1] = '\0'; + --i_len; + } + + if((ingredient_id = db_get_ingredient_id(i)) <= 0) + ingredient_id = db_add_ingredient(i); + db_conn_recipe_ingredient(recipe_id, ingredient_id); + } + free(ingredients); + + for(char *i = strtok(tags, ","); i; i = strtok(NULL, ",")) { + // remove leading blank spaces + while(isblank(i[0])) + i += sizeof(char); + + // remove trailing blank spaces + size_t i_len = strlen(i); + while(isblank(i[i_len - 1])) { + i[i_len - 1] = '\0'; + --i_len; + } + + if((tag_id = db_get_tag_id(i)) <= 0) + tag_id = db_add_tag(i); + db_conn_recipe_tag(recipe_id, tag_id); + } + free(tags); + + db_close(); + + return 1; +} diff --git a/src/cmd.h b/src/cmd.h new file mode 100644 index 0000000..d5420d3 --- /dev/null +++ b/src/cmd.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 Nicolás Ortega Froysa + * Nicolás Ortega Froysa + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +int command_add(void); diff --git a/src/db.c b/src/db.c index 43fdf5f..1db2381 100644 --- a/src/db.c +++ b/src/db.c @@ -25,7 +25,7 @@ #include static sqlite3 *db = NULL; -static const int db_version = 1; +static const int db_version = 1; int db_open(void) { const char *xdg_data_home; @@ -42,14 +42,16 @@ int db_open(void) { strcpy(db_path, xdg_data_home); strcat(db_path, "/menu-helper"); - if(access(db_path, F_OK) != 0) { - printf("Creating database: %s\n", db_path); + if(access(db_path, F_OK) != 0) mkdir(db_path, 0700); - new_db = 1; - } strcat(db_path, "/recipes.db"); + if(access(db_path, F_OK) != 0) { + printf("Creating database: %s\n", db_path); + new_db = 1; + } + rc = sqlite3_open(db_path, &db); free(db_path); @@ -74,3 +76,146 @@ void db_close(void) { sqlite3_close(db); } + +int query_id_cb(void *recipe_id_var, int col_num, char **col_data, char **col_name) { + int *recipe_id_ptr = (int*)recipe_id_var; + int ret = 1; + + for(int i = 0; i < col_num; ++i) { + if(strcmp(col_name[i], "id") == 0) { + *recipe_id_ptr = atoi(col_data[i]); + ret = 0; + break; + } + } + + return ret; +} + +int table_get_id_by_name(const char *table, const char *name) { + const char *sel_query_fmt = "SELECT id FROM %s WHERE lower(name)=lower('%s');"; + char *sel_query; + int id = 0; + + sel_query = malloc(strlen(table) + strlen(name) + strlen(sel_query_fmt) + 1); + sprintf(sel_query, sel_query_fmt, table, name); + if(sqlite3_exec(db, sel_query, query_id_cb, &id, NULL) != SQLITE_OK) { + free(sel_query); + return -2; + } + + free(sel_query); + + return id; +} + +int db_add_recipe(const char *name, const char *description) { + const char *add_query_fmt = "INSERT INTO recipes(name,description) VALUES('%s','%s');"; + char *add_query; + + if(!db) + return -1; + + add_query = malloc(strlen(name) + strlen(description) + strlen(add_query_fmt) + 1); + sprintf(add_query, add_query_fmt, name, description); + if(sqlite3_exec(db, add_query, NULL, NULL, NULL) != SQLITE_OK) { + free(add_query); + return -2; + } + free(add_query); + + return db_get_recipe_id(name); +} + +int db_get_recipe_id(const char *name) { + if(!db) + return -1; + + return table_get_id_by_name("recipes", name); +} + +int db_add_ingredient(const char *name) { + const char *add_query_fmt = "INSERT INTO ingredients(name) VALUES(lower('%s'));"; + char *add_query; + + if(!db) + return -1; + + add_query = malloc(strlen(name) + strlen(add_query_fmt) + 1); + sprintf(add_query, add_query_fmt, name); + if(sqlite3_exec(db, add_query, NULL, NULL, NULL) != SQLITE_OK) { + free(add_query); + return -2; + } + free(add_query); + + return db_get_ingredient_id(name); +} + +int db_get_ingredient_id(const char *name) { + if(!db) + return -1; + + return table_get_id_by_name("ingredients", name); +} + +int db_add_tag(const char *name) { + const char *add_query_fmt = "INSERT INTO tags(name) VALUES('%s');"; + char *add_query; + + if(!db) + return -1; + + add_query = malloc(strlen(name) + strlen(add_query_fmt) + 1); + sprintf(add_query, add_query_fmt, name); + if(sqlite3_exec(db, add_query, NULL, NULL, NULL) != SQLITE_OK) { + free(add_query); + return -2; + } + free(add_query); + + return db_get_tag_id(name); +} + +int db_get_tag_id(const char *name) { + if(!db) + return -1; + + return table_get_id_by_name("tags", name); +} + +int db_conn_recipe_ingredient(int recipe_id, int ingredient_id) { + const char *add_conn_fmt = "INSERT INTO recipe_ingredient(recipe_id, ingredient_id) VALUES(%d,%d);"; + char *add_conn_query; + + if(!db) + return -1; + + add_conn_query = malloc(strlen(add_conn_fmt) + (recipe_id % 10) + (ingredient_id % 10)); + sprintf(add_conn_query, add_conn_fmt, recipe_id, ingredient_id); + if(sqlite3_exec(db, add_conn_query, NULL, NULL, NULL) != SQLITE_OK) { + free(add_conn_query); + return -2; + } + free(add_conn_query); + + return 1; +} + +int db_conn_recipe_tag(int recipe_id, int tag_id) { + const char *add_conn_fmt = "INSERT INTO recipe_tag(recipe_id, tag_id) VALUES(%d,%d);"; + char *add_conn_query; + + if(!db) + return -1; + + add_conn_query = malloc(strlen(add_conn_fmt) + (recipe_id % 10) + (tag_id % 10)); + sprintf(add_conn_query, add_conn_fmt, recipe_id, tag_id); + if(sqlite3_exec(db, add_conn_query, NULL, NULL, NULL) != SQLITE_OK) { + free(add_conn_query); + return -2; + } + free(add_conn_query); + + return 1; +} diff --git a/src/db.h b/src/db.h index 1967b25..0b70e82 100644 --- a/src/db.h +++ b/src/db.h @@ -19,3 +19,46 @@ int db_open(void); void db_close(void); + +/** + * @brief Add a new recipe to the database. + * + * @param name Name of the new recipe. + * @param description Short description. + * + * @return ID of newly created recipe, -1 if DB isn't open, -2 on other failure. + */ +int db_add_recipe(const char *name, const char *description); +int db_get_recipe_id(const char *name); +static inline int db_recipe_exists(const char *name) { + return (db_get_recipe_id(name) > 0); +} + +/** + * @brief Add a new ingredient to the database. + * + * @param name Name of the new ingredient. + * + * @return ID of newly created ingredient, -1 if DB isn't open, -2 on other failure. + */ +int db_add_ingredient(const char *name); +int db_get_ingredient_id(const char *name); +static inline int db_ingredient_exists(const char *name) { + return (db_get_ingredient_id(name) > 0); +} + +/** + * @brief Add a new tag to the database. + * + * @param name Name of the new tag. + * + * @return ID of newly created tag, -1 if DB isn't open, -2 on other failure. + */ +int db_add_tag(const char *name); +int db_get_tag_id(const char *name); +static inline int db_tag_exists(const char *name) { + return (db_get_tag_id(name) > 0); +} + +int db_conn_recipe_ingredient(int recipe_id, int ingredient_id); +int db_conn_recipe_tag(int recipe_id, int tag_id); diff --git a/src/main.c b/src/main.c index 32c1a5c..1c51870 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,7 @@ #include #include "arg_parse.h" +#include "cmd.h" int main(int argc, char *argv[]) { enum cmd_id id; @@ -32,6 +33,9 @@ int main(int argc, char *argv[]) { id = parse_args(argv[1]); switch(id) { + case CMD_ADD: + command_add(); + break; case CMD_HELP: print_help(); break;