From 7f0fd90e734437a07ae2abcb6dc7e170e8919dcd Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Wed, 28 Feb 2018 21:43:49 +0100 Subject: [PATCH 01/10] Fixed project to compile on Linux. --- RecipeDB.pro | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/RecipeDB.pro b/RecipeDB.pro index a717ed2..8b1d1c5 100644 --- a/RecipeDB.pro +++ b/RecipeDB.pro @@ -12,8 +12,7 @@ TARGET = RecipeDB TEMPLATE = app -SOURCES += SQLite/sqlite3.c \ - model/recipe/instruction.cpp \ +SOURCES += model/recipe/instruction.cpp \ model/recipe/recipe.cpp \ userInterface/mainwindow.cpp \ main.cpp \ @@ -22,11 +21,10 @@ SOURCES += SQLite/sqlite3.c \ model/recipe/ingredients/ingredient.cpp \ model/recipe/ingredients/ingredientlistmodel.cpp \ model/recipe/ingredients/recipeingredient.cpp \ - model/recipe/tags/recipetag.cpp + model/recipe/tags/recipetag.cpp \ + SQLite/sqlite3.c -HEADERS += SQLite/sqlite3.h \ - SQLite/sqlite3ext.h \ - model/recipe/instruction.h \ +HEADERS += model/recipe/instruction.h \ model/recipe/recipe.h \ userInterface/mainwindow.h \ model/database/database.h \ @@ -34,7 +32,11 @@ HEADERS += SQLite/sqlite3.h \ model/recipe/ingredients/ingredient.h \ model/recipe/ingredients/ingredientlistmodel.h \ model/recipe/ingredients/recipeingredient.h \ - model/recipe/tags/recipetag.h + model/recipe/tags/recipetag.h \ + SQLite/sqlite3.h \ + SQLite/sqlite3ext.h + +LIBS += -ldl \ FORMS += gui/mainwindow.ui From 658bf9fb2b5486d7b5445bed3750672c7b31c5ea Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 1 Mar 2018 17:19:13 +0100 Subject: [PATCH 02/10] Got a working database interaction layer and basic tables. --- RecipeDB.pro | 8 +++- main.cpp | 12 ++++- model/database/database.cpp | 42 +++++++++-------- model/database/database.h | 19 ++++---- model/database/recipedatabase.cpp | 60 +++++++++++++++++++++++++ model/database/recipedatabase.h | 24 ++++++++++ model/database/resulttable.cpp | 75 +++++++++++++++++++++++++++++++ model/database/resulttable.h | 40 +++++++++++++++++ 8 files changed, 249 insertions(+), 31 deletions(-) create mode 100644 model/database/recipedatabase.cpp create mode 100644 model/database/recipedatabase.h create mode 100644 model/database/resulttable.cpp create mode 100644 model/database/resulttable.h diff --git a/RecipeDB.pro b/RecipeDB.pro index 8b1d1c5..eb02709 100644 --- a/RecipeDB.pro +++ b/RecipeDB.pro @@ -22,7 +22,9 @@ SOURCES += model/recipe/instruction.cpp \ model/recipe/ingredients/ingredientlistmodel.cpp \ model/recipe/ingredients/recipeingredient.cpp \ model/recipe/tags/recipetag.cpp \ - SQLite/sqlite3.c + SQLite/sqlite3.c \ + model/database/resulttable.cpp \ + model/database/recipedatabase.cpp HEADERS += model/recipe/instruction.h \ model/recipe/recipe.h \ @@ -34,7 +36,9 @@ HEADERS += model/recipe/instruction.h \ model/recipe/ingredients/recipeingredient.h \ model/recipe/tags/recipetag.h \ SQLite/sqlite3.h \ - SQLite/sqlite3ext.h + SQLite/sqlite3ext.h \ + model/database/resulttable.h \ + model/database/recipedatabase.h LIBS += -ldl \ diff --git a/main.cpp b/main.cpp index 4f87ea1..5e98fcf 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ #include #include "model/database/database.h" +#include "model/database/recipedatabase.h" int main(int argc, char *argv[]) { @@ -9,7 +10,14 @@ int main(int argc, char *argv[]) MainWindow w; w.show(); - Database db("test.db"); + //TESTING CODE + Database db("test.db"); + printf("Table exists: %d\n", db.tableExists("ingredients")); + db.executeSQL("SELECT * FROM ingredients;").printData(); + db.executeSQL("PRAGMA table_info('ingredients');").printData(); + db.executeSQL("SELECT name FROM ingredients WHERE foodGroup == 'fruit';").printData(); - return a.exec(); + RecipeDatabase recipeDB("recipes"); + + return a.exec(); } diff --git a/model/database/database.cpp b/model/database/database.cpp index 23716ab..8087e04 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -3,16 +3,27 @@ Database::Database(string filename){ this->filename = filename; openConnection(); - //TESTING CODE - if (tableExists("ingredients")){ - printf("Ingredients table already exists.\n"); - } else { - printf("Couldn't find the ingredients table.\n"); - } } Database::~Database(){ - closeConnection(); + closeConnection(); +} + +ResultTable Database::executeSQL(string statement){ + sqlite3_stmt* stmt; + this->sql = statement; + this->returnCode = sqlite3_prepare_v2(this->db, statement.c_str(), -1, &stmt, NULL); + ResultTable t; + if (this->returnCode != SQLITE_OK){ + fprintf(stderr, "Unable to successfully prepare SQL statement. Error code: %d\nError Message: %s\n", this->returnCode, sqlite3_errmsg(this->db)); + return t; + } + + t.extractData(stmt); + + this->returnCode = sqlite3_finalize(stmt); + + return t; } void Database::openConnection(){ @@ -32,16 +43,9 @@ void Database::closeConnection(){ } bool Database::tableExists(string tableName){ - if (this->dbIsOpen){ - this->sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';"; - const char* str = this->sql.c_str(); - this->returnCode = sqlite3_exec(this->db, str, NULL, 0, &this->errorMsg); - if (this->returnCode == SQLITE_ERROR){ - fprintf(stderr, "Unable to select name from master table list: %s\n", this->errorMsg); - return false; - } else { - return true; - } - } - return false; + if (tableName.empty() || this->db == NULL || !this->dbIsOpen){ + return false; + } + ResultTable t = executeSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';"); + return !t.isEmpty(); } diff --git a/model/database/database.h b/model/database/database.h index dce935a..cd7ba1c 100644 --- a/model/database/database.h +++ b/model/database/database.h @@ -6,17 +6,25 @@ #include "SQLite/sqlite3.h" #include "model/recipe/ingredients/ingredient.h" +#include "resulttable.h" using namespace std; +/** + * @brief The Database class is responsible for generic abstraction of commonly used database features. + */ + class Database { public: + //Creates and opens a database connection to a file of the given name. If not there, this will generate a database. Database(string filename); - ~Database(); + ~Database(); - void insertIngredient(Ingredient); + //Executes an SQL string statement in a safe way and returns the result. + ResultTable executeSQL(string statement); + bool tableExists(string tableName); private: //SQL Instance variables. string filename; @@ -27,12 +35,7 @@ private: char* errorMsg; void openConnection(); - void closeConnection(); - //Guarantees that all tables from the schema exist. - void ensureTablesExist(); - - //Utility methods. - bool tableExists(string tableName); + void closeConnection(); }; #endif // DATABASE_H diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp new file mode 100644 index 0000000..2eecb83 --- /dev/null +++ b/model/database/recipedatabase.cpp @@ -0,0 +1,60 @@ +#include "recipedatabase.h" + +RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ + this->ensureTablesExist(); +} + +void RecipeDatabase::ensureTablesExist(){ + //Make sure that foreign keys are enabled. + this->executeSQL("PRAGMA foreign_keys = ON;"); + //Ingredients table. + this->executeSQL("CREATE TABLE IF NOT EXISTS ingredient(" + "ingredientId int," + "foodGroup varchar," + "name varchar," + "PRIMARY KEY (ingredientId));"); + //Images table. + this->executeSQL("CREATE TABLE IF NOT EXISTS image(" + "imageNr int," + "contentURL varchar," + "PRIMARY KEY (imageNr));"); + //Instructions table. + this->executeSQL("CREATE TABLE IF NOT EXISTS instruction(" + "instructionNr int," + "contentURL varchar," + "PRIMARY KEY (instructionNr));"); + //Recipe table. + this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" + "recipeId int," + "date date," + "name varchar," + "imageNr int," + "cookTime time," + "prepTime time," + "servingCount real," + "PRIMARY KEY (recipeId)," + "FOREIGN KEY (imageNr) REFERENCES image(imageNr));"); + //Recipe tags table. + this->executeSQL("CREATE TABLE IF NOT EXISTS recipeTag(" + "recipeId int," + "tagName varchar," + "PRIMARY KEY (recipeId)," + "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); + //RecipeIngredient table. + this->executeSQL("CREATE TABLE IF NOT EXISTS recipeIngredient(" + "ingredientId int," + "recipeId int," + "quantity real," + "unitName varchar," + "comment varchar," + "PRIMARY KEY (ingredientId, recipeId)," + "FOREIGN KEY (ingredientId) REFERENCES ingredient(ingredientId)," + "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); + //Recipe Instruction mapping table. + this->executeSQL("CREATE TABLE IF NOT EXISTS recipeInstruction(" + "instructionNr int," + "recipeId int," + "PRIMARY KEY (recipeId)," + "FOREIGN KEY (instructionNr) REFERENCES instruction(instructionNr)," + "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); +} diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h new file mode 100644 index 0000000..f5d317b --- /dev/null +++ b/model/database/recipedatabase.h @@ -0,0 +1,24 @@ +#ifndef RECIPEDATABASE_H +#define RECIPEDATABASE_H + + + +#include "database.h" + +using namespace std; + +/** + * @brief The RecipeDatabase class represents the precise database used for the recipe storage, and is specialized. + */ + +class RecipeDatabase : public Database +{ + public: + RecipeDatabase(string filename); + private: + + //Utility methods. + void ensureTablesExist(); +}; + +#endif // RECIPEDATABASE_H diff --git a/model/database/resulttable.cpp b/model/database/resulttable.cpp new file mode 100644 index 0000000..b8f4bca --- /dev/null +++ b/model/database/resulttable.cpp @@ -0,0 +1,75 @@ +#include "resulttable.h" + +ResultTable::ResultTable(){ + +} + +void ResultTable::extractData(sqlite3_stmt *stmt){ + this->values.clear(); + int res = sqlite3_step(stmt); + while (res == SQLITE_ROW){ + processRow(stmt); + res = sqlite3_step(stmt); + } +} + +void ResultTable::processRow(sqlite3_stmt *stmt){ + int colCount = sqlite3_column_count(stmt); + vector currentRow; + + for (int i = 0; i < colCount; i++){ + currentRow.push_back(convertToString(sqlite3_column_value(stmt, i))); + } + + this->values.push_back(currentRow); +} + +void ResultTable::printData(){ + printf("Printing table: %d by %d\n", this->rowCount(), this->columnCount()); + for (unsigned int row = 0; row < this->rowCount(); row++){ + for (unsigned int col = 0; col < this->columnCount(); col++){ + printf("| %s \t", this->values[row][col].c_str()); + } + printf("\n"); + } +} + +bool ResultTable::isEmpty(){ + return this->values.empty(); +} + +string ResultTable::valueAt(unsigned int row, unsigned int col){ + if (isIndexValid(row, col)){ + return this->values[row][col]; + } else { + fprintf(stderr, "Out of bounds error while trying to get value in result table at [%d, %d].\n", row, col); + return ""; + } +} + +unsigned int ResultTable::columnCount(){ + if (this->isEmpty()){ + return 0; + } + return this->values[0].size(); +} + +unsigned int ResultTable::rowCount(){ + if (this->isEmpty()){ + return 0; + } + return this->values.size(); +} + +string ResultTable::convertToString(sqlite3_value *val){ + const unsigned char* raw_text = sqlite3_value_text(val); + if (raw_text == 0){ + return ""; + } + string st = (const char*) raw_text; + return st; +} + +bool ResultTable::isIndexValid(unsigned int row, unsigned int col){ + return (row < this->values.size()) && (col < this->values[0].size()); +} diff --git a/model/database/resulttable.h b/model/database/resulttable.h new file mode 100644 index 0000000..37550e9 --- /dev/null +++ b/model/database/resulttable.h @@ -0,0 +1,40 @@ +#ifndef RESULTTABLE_H +#define RESULTTABLE_H + +#include +#include + +#include "SQLite/sqlite3.h" + +using namespace std; + +/** + * @brief The ResultTable class is an object that contains the results of an SQL query, in string form. + */ + +class ResultTable +{ + public: + //Constructs an empty table. + ResultTable(); + + //Gets all the data from the result set and stores it internally as strings. + void extractData(sqlite3_stmt* stmt); + //Stores the information from one row of a result set. + void processRow(sqlite3_stmt* stmt); + //Displays the data somewhat legibly. + void printData(); + + bool isEmpty(); + string valueAt(unsigned int row, unsigned int col); + unsigned int columnCount(); + unsigned int rowCount(); + private: + vector> values; + + //Utility methods. + string convertToString(sqlite3_value* val); + bool isIndexValid(unsigned int row, unsigned int col); +}; + +#endif // RESULTTABLE_H From b893372b01476d05590235e082f2fbf80b3b014c Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 1 Mar 2018 17:28:18 +0100 Subject: [PATCH 03/10] Modified SQL table creation, created method stub to insert recipe. --- model/database/recipedatabase.cpp | 6 +++++- model/database/recipedatabase.h | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 2eecb83..f034f4c 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -4,6 +4,10 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ this->ensureTablesExist(); } +void RecipeDatabase::storeRecipe(Recipe recipe){ + ///TODO: Implement this in a smart way using transaction. +} + void RecipeDatabase::ensureTablesExist(){ //Make sure that foreign keys are enabled. this->executeSQL("PRAGMA foreign_keys = ON;"); @@ -47,7 +51,7 @@ void RecipeDatabase::ensureTablesExist(){ "quantity real," "unitName varchar," "comment varchar," - "PRIMARY KEY (ingredientId, recipeId)," + "PRIMARY KEY (recipeId)," "FOREIGN KEY (ingredientId) REFERENCES ingredient(ingredientId)," "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); //Recipe Instruction mapping table. diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index f5d317b..2771017 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -4,6 +4,7 @@ #include "database.h" +#include "model/recipe/recipe.h" using namespace std; @@ -15,6 +16,9 @@ class RecipeDatabase : public Database { public: RecipeDatabase(string filename); + + //Stores a full recipe in the database. + void storeRecipe(Recipe recipe); private: //Utility methods. From e714a2fc6f5416981decb4600a8d47eaf45514b7 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Fri, 2 Mar 2018 09:32:40 +0100 Subject: [PATCH 04/10] Added fileutils class, added ability to store ingredients in database. --- RecipeDB.pro | 6 +++-- main.cpp | 13 +++++---- model/database/recipedatabase.cpp | 45 +++++++++++++++++++------------ model/database/recipedatabase.h | 5 ++++ utils/fileutils.cpp | 5 ++++ utils/fileutils.h | 11 ++++++++ 6 files changed, 61 insertions(+), 24 deletions(-) create mode 100644 utils/fileutils.cpp create mode 100644 utils/fileutils.h diff --git a/RecipeDB.pro b/RecipeDB.pro index eb02709..2c957a5 100644 --- a/RecipeDB.pro +++ b/RecipeDB.pro @@ -24,7 +24,8 @@ SOURCES += model/recipe/instruction.cpp \ model/recipe/tags/recipetag.cpp \ SQLite/sqlite3.c \ model/database/resulttable.cpp \ - model/database/recipedatabase.cpp + model/database/recipedatabase.cpp \ + utils/fileutils.cpp HEADERS += model/recipe/instruction.h \ model/recipe/recipe.h \ @@ -38,7 +39,8 @@ HEADERS += model/recipe/instruction.h \ SQLite/sqlite3.h \ SQLite/sqlite3ext.h \ model/database/resulttable.h \ - model/database/recipedatabase.h + model/database/recipedatabase.h \ + utils/fileutils.h LIBS += -ldl \ diff --git a/main.cpp b/main.cpp index 5e98fcf..085d24a 100644 --- a/main.cpp +++ b/main.cpp @@ -11,13 +11,16 @@ int main(int argc, char *argv[]) w.show(); //TESTING CODE - Database db("test.db"); - printf("Table exists: %d\n", db.tableExists("ingredients")); - db.executeSQL("SELECT * FROM ingredients;").printData(); - db.executeSQL("PRAGMA table_info('ingredients');").printData(); - db.executeSQL("SELECT name FROM ingredients WHERE foodGroup == 'fruit';").printData(); +// Database db("test.db"); +// printf("Table exists: %d\n", db.tableExists("ingredients")); +// db.executeSQL("SELECT * FROM ingredients;").printData(); +// db.executeSQL("PRAGMA table_info('ingredients');").printData(); +// db.executeSQL("SELECT name FROM ingredients WHERE foodGroup == 'fruit';").printData(); RecipeDatabase recipeDB("recipes"); + recipeDB.storeIngredient(Ingredient("Apple", "Fruit")); + recipeDB.storeIngredient(Ingredient("Corn", "Vegetable")); + recipeDB.executeSQL("SELECT ingredientId FROM ingredient;").printData(); return a.exec(); } diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index f034f4c..ed312e8 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -6,6 +6,7 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ void RecipeDatabase::storeRecipe(Recipe recipe){ ///TODO: Implement this in a smart way using transaction. + } void RecipeDatabase::ensureTablesExist(){ @@ -13,52 +14,62 @@ void RecipeDatabase::ensureTablesExist(){ this->executeSQL("PRAGMA foreign_keys = ON;"); //Ingredients table. this->executeSQL("CREATE TABLE IF NOT EXISTS ingredient(" - "ingredientId int," + "ingredientId INTEGER PRIMARY KEY," "foodGroup varchar," - "name varchar," - "PRIMARY KEY (ingredientId));"); + "name varchar);"); //Images table. this->executeSQL("CREATE TABLE IF NOT EXISTS image(" - "imageNr int," - "contentURL varchar," - "PRIMARY KEY (imageNr));"); + "imageNr INTEGER PRIMARY KEY," + "contentURL varchar);"); //Instructions table. this->executeSQL("CREATE TABLE IF NOT EXISTS instruction(" - "instructionNr int," - "contentURL varchar," - "PRIMARY KEY (instructionNr));"); + "instructionNr INTEGER PRIMARY KEY," + "contentURL varchar);"); //Recipe table. this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" - "recipeId int," + "recipeId INTEGER PRIMARY KEY," "date date," "name varchar," "imageNr int," "cookTime time," "prepTime time," "servingCount real," - "PRIMARY KEY (recipeId)," "FOREIGN KEY (imageNr) REFERENCES image(imageNr));"); //Recipe tags table. this->executeSQL("CREATE TABLE IF NOT EXISTS recipeTag(" - "recipeId int," + "recipeId INTEGER PRIMARY KEY," "tagName varchar," - "PRIMARY KEY (recipeId)," "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); //RecipeIngredient table. this->executeSQL("CREATE TABLE IF NOT EXISTS recipeIngredient(" "ingredientId int," - "recipeId int," + "recipeId INTEGER PRIMARY KEY," "quantity real," "unitName varchar," "comment varchar," - "PRIMARY KEY (recipeId)," "FOREIGN KEY (ingredientId) REFERENCES ingredient(ingredientId)," "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); //Recipe Instruction mapping table. this->executeSQL("CREATE TABLE IF NOT EXISTS recipeInstruction(" "instructionNr int," - "recipeId int," - "PRIMARY KEY (recipeId)," + "recipeId INTEGER PRIMARY KEY," "FOREIGN KEY (instructionNr) REFERENCES instruction(instructionNr)," "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); } + +void RecipeDatabase::storeInstruction(Instruction instruction){ + +} + +void RecipeDatabase::storeImage(QImage image){ + +} + +void RecipeDatabase::storeIngredient(Ingredient ingredient){ + ResultTable t = this->executeSQL("SELECT * FROM ingredient WHERE name='"+ingredient.getName()+"';"); + if (!t.isEmpty()){ + fprintf(stderr, "Error during storeIngredient: ingredient with name %s already exists.\n", ingredient.getName().c_str()); + } else { + this->executeSQL("INSERT INTO ingredient (foodGroup, name) VALUES ('"+ ingredient.getFoodGroup() +"', '"+ ingredient.getName() +"');"); + } +} diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index 2771017..a0d311a 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -19,6 +19,11 @@ class RecipeDatabase : public Database //Stores a full recipe in the database. void storeRecipe(Recipe recipe); + + //SQL Helper methods. + void storeInstruction(Instruction instruction); + void storeImage(QImage image); + void storeIngredient(Ingredient ingredient); private: //Utility methods. diff --git a/utils/fileutils.cpp b/utils/fileutils.cpp new file mode 100644 index 0000000..0fa0670 --- /dev/null +++ b/utils/fileutils.cpp @@ -0,0 +1,5 @@ +#include "fileutils.h" + +fileUtils::fileUtils(){ + +} diff --git a/utils/fileutils.h b/utils/fileutils.h new file mode 100644 index 0000000..76bdfc2 --- /dev/null +++ b/utils/fileutils.h @@ -0,0 +1,11 @@ +#ifndef FILEUTILS_H +#define FILEUTILS_H + + +class fileUtils +{ + public: + fileUtils(); +}; + +#endif // FILEUTILS_H \ No newline at end of file From cfd4179664855819d0845b1364ab86aaf86c71e5 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Fri, 2 Mar 2018 10:11:26 +0100 Subject: [PATCH 05/10] Created load and save instruction methods in file utils. --- RecipeDB.pro | 3 +-- main.cpp | 5 +++- utils/fileutils.cpp | 5 ---- utils/fileutils.h | 56 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 14 deletions(-) delete mode 100644 utils/fileutils.cpp diff --git a/RecipeDB.pro b/RecipeDB.pro index 2c957a5..61f3bf6 100644 --- a/RecipeDB.pro +++ b/RecipeDB.pro @@ -24,8 +24,7 @@ SOURCES += model/recipe/instruction.cpp \ model/recipe/tags/recipetag.cpp \ SQLite/sqlite3.c \ model/database/resulttable.cpp \ - model/database/recipedatabase.cpp \ - utils/fileutils.cpp + model/database/recipedatabase.cpp HEADERS += model/recipe/instruction.h \ model/recipe/recipe.h \ diff --git a/main.cpp b/main.cpp index 085d24a..f83a2c0 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,7 @@ #include "model/database/database.h" #include "model/database/recipedatabase.h" +#include "utils/fileutils.h" int main(int argc, char *argv[]) { @@ -20,7 +21,9 @@ int main(int argc, char *argv[]) RecipeDatabase recipeDB("recipes"); recipeDB.storeIngredient(Ingredient("Apple", "Fruit")); recipeDB.storeIngredient(Ingredient("Corn", "Vegetable")); - recipeDB.executeSQL("SELECT ingredientId FROM ingredient;").printData(); + recipeDB.executeSQL("SELECT * FROM ingredient;").printData(); + + FileUtils::saveInstruction(1, Instruction("This is some plain text with no HTML markup.")); return a.exec(); } diff --git a/utils/fileutils.cpp b/utils/fileutils.cpp deleted file mode 100644 index 0fa0670..0000000 --- a/utils/fileutils.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "fileutils.h" - -fileUtils::fileUtils(){ - -} diff --git a/utils/fileutils.h b/utils/fileutils.h index 76bdfc2..ed23f19 100644 --- a/utils/fileutils.h +++ b/utils/fileutils.h @@ -1,11 +1,55 @@ #ifndef FILEUTILS_H #define FILEUTILS_H +#include +#include +#include +#include -class fileUtils -{ - public: - fileUtils(); -}; +#include "model/recipe/instruction.h" -#endif // FILEUTILS_H \ No newline at end of file +namespace FileUtils { + + QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)+"/.recipeDB/"; + + void ensureAppDataFolderExists(){ + QDir folder(appDataPath); + if (!folder.exists()){ + folder.mkpath("."); + } + } + + void saveInstruction(int nr, Instruction instruction){ + ensureAppDataFolderExists(); + QString filename = appDataPath + QString::fromStdString(std::to_string(nr)) +".html"; + QFile file(filename); + if (file.open(QIODevice::WriteOnly)){ + QTextStream stream(&file); + stream< Date: Fri, 2 Mar 2018 11:30:16 +0100 Subject: [PATCH 06/10] In process of creating method to store recipe. --- RecipeDB.pro | 3 +- main.cpp | 17 ++++--- model/database/database.cpp | 32 ++++++++++++- model/database/database.h | 4 ++ model/database/recipedatabase.cpp | 75 ++++++++++++++++--------------- model/database/recipedatabase.h | 3 +- model/database/resulttable.cpp | 6 ++- userInterface/mainwindow.cpp | 11 +---- utils/fileutils.h | 4 +- utils/stringutils.h | 13 ++++++ 10 files changed, 109 insertions(+), 59 deletions(-) create mode 100644 utils/stringutils.h diff --git a/RecipeDB.pro b/RecipeDB.pro index 61f3bf6..4503a07 100644 --- a/RecipeDB.pro +++ b/RecipeDB.pro @@ -39,7 +39,8 @@ HEADERS += model/recipe/instruction.h \ SQLite/sqlite3ext.h \ model/database/resulttable.h \ model/database/recipedatabase.h \ - utils/fileutils.h + utils/fileutils.h \ + utils/stringutils.h LIBS += -ldl \ diff --git a/main.cpp b/main.cpp index f83a2c0..98c1306 100644 --- a/main.cpp +++ b/main.cpp @@ -12,18 +12,23 @@ int main(int argc, char *argv[]) w.show(); //TESTING CODE -// Database db("test.db"); -// printf("Table exists: %d\n", db.tableExists("ingredients")); -// db.executeSQL("SELECT * FROM ingredients;").printData(); -// db.executeSQL("PRAGMA table_info('ingredients');").printData(); -// db.executeSQL("SELECT name FROM ingredients WHERE foodGroup == 'fruit';").printData(); RecipeDatabase recipeDB("recipes"); recipeDB.storeIngredient(Ingredient("Apple", "Fruit")); recipeDB.storeIngredient(Ingredient("Corn", "Vegetable")); + recipeDB.storeIngredient(Ingredient("Lettuce", "Vegetable")); + recipeDB.storeIngredient(Ingredient("Carrot", "Vegetable")); + recipeDB.executeSQL("SELECT * FROM ingredient;").printData(); - FileUtils::saveInstruction(1, Instruction("This is some plain text with no HTML markup.")); + //TESTING CODE + vector ri; + ri.push_back(RecipeIngredient("flour", "grains", 3.0f, UnitOfMeasure("cup", "cups", "c"))); + ri.push_back(RecipeIngredient("Baking Powder", "Additives", 1.0f, UnitOfMeasure("Teaspoon", "Teaspoons", "Tsp"))); + + Recipe rec("Example", ri, Instruction("BOLDiTaLiCs"), QImage(), vector(), QDate::currentDate(), QTime(0, 30), QTime(0, 25), 10.0f); + + w.loadFromRecipe(rec); return a.exec(); } diff --git a/model/database/database.cpp b/model/database/database.cpp index 8087e04..0b843d5 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -15,7 +15,7 @@ ResultTable Database::executeSQL(string statement){ this->returnCode = sqlite3_prepare_v2(this->db, statement.c_str(), -1, &stmt, NULL); ResultTable t; if (this->returnCode != SQLITE_OK){ - fprintf(stderr, "Unable to successfully prepare SQL statement. Error code: %d\nError Message: %s\n", this->returnCode, sqlite3_errmsg(this->db)); + fprintf(stderr, "Unable to successfully prepare SQL statement. Error code: %d\n\tError Message: %s\n", this->returnCode, sqlite3_errmsg(this->db)); return t; } @@ -26,6 +26,19 @@ ResultTable Database::executeSQL(string statement){ return t; } +bool Database::insertInto(string tableName, vector columnNames, vector values){ + if (columnNames.size() != values.size() || columnNames.empty()){ + return false; + } + string query = "INSERT INTO "+tableName+" ("; + string cols = combineVector(columnNames, ", "); + string vals = combineVector(values, ", "); + query += cols + ") VALUES (" + vals + ");"; + printf("Executing query: %s\n", query.c_str()); + this->executeSQL(query); + return true; +} + void Database::openConnection(){ this->returnCode = sqlite3_open(this->filename.c_str(), &this->db); if (this->returnCode || this->db == NULL){ @@ -39,7 +52,18 @@ void Database::openConnection(){ void Database::closeConnection(){ this->returnCode = sqlite3_close(this->db); - this->dbIsOpen = false; + this->dbIsOpen = false; +} + +string Database::combineVector(std::vector strings, string mid){ + if (strings.empty()){ + return ""; + } + std::string result = "'" + strings[0] + "'"; + for (std::vector::iterator it = strings.begin() + 1; it != strings.end(); ++it){ + result += mid + "'" + (*it) + "'"; + } + return result; } bool Database::tableExists(string tableName){ @@ -49,3 +73,7 @@ bool Database::tableExists(string tableName){ ResultTable t = executeSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';"); return !t.isEmpty(); } + +int Database::getLastInsertedRowId(){ + return sqlite3_last_insert_rowid(this->db); +} diff --git a/model/database/database.h b/model/database/database.h index cd7ba1c..3c1b73f 100644 --- a/model/database/database.h +++ b/model/database/database.h @@ -3,6 +3,7 @@ #include #include +#include #include "SQLite/sqlite3.h" #include "model/recipe/ingredients/ingredient.h" @@ -23,8 +24,10 @@ public: //Executes an SQL string statement in a safe way and returns the result. ResultTable executeSQL(string statement); + bool insertInto(string tableName, vector columnNames, vector values); bool tableExists(string tableName); + int getLastInsertedRowId(); private: //SQL Instance variables. string filename; @@ -36,6 +39,7 @@ private: void openConnection(); void closeConnection(); + std::string combineVector(std::vector strings, std::string mid); }; #endif // DATABASE_H diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index ed312e8..5069fe0 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -6,7 +6,43 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ void RecipeDatabase::storeRecipe(Recipe recipe){ ///TODO: Implement this in a smart way using transaction. + ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name='"+recipe.getName()+"';"); + if (!t.isEmpty()){ + fprintf(stderr, "Error storing recipe: Recipe with name %s already exists.\n", recipe.getName().c_str()); + } else { + bool success = this->insertInto("recipe", + vector({ + "name", + "createdDate", + "cookTime", + "prepTime", + "servingCount" + }), + vector({ + recipe.getName(), + recipe.getCreatedDate().toString().toStdString(), + recipe.getCookTime().toString().toStdString(), + recipe.getPrepTime().toString().toStdString(), + std::to_string(recipe.getServings()) + })); + if (success){ + //If successful, proceed to insert instructions, image, and ingredients. + } + } +} + +void RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri){ + +} + +void RecipeDatabase::storeIngredient(Ingredient ingredient){ + ResultTable t = this->executeSQL("SELECT * FROM ingredient WHERE name='"+ingredient.getName()+"';"); + if (!t.isEmpty()){ + fprintf(stderr, "Error during storeIngredient: ingredient with name %s already exists.\n", ingredient.getName().c_str()); + } else { + this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ingredient.getFoodGroup(), ingredient.getName()})); + } } void RecipeDatabase::ensureTablesExist(){ @@ -17,24 +53,14 @@ void RecipeDatabase::ensureTablesExist(){ "ingredientId INTEGER PRIMARY KEY," "foodGroup varchar," "name varchar);"); - //Images table. - this->executeSQL("CREATE TABLE IF NOT EXISTS image(" - "imageNr INTEGER PRIMARY KEY," - "contentURL varchar);"); - //Instructions table. - this->executeSQL("CREATE TABLE IF NOT EXISTS instruction(" - "instructionNr INTEGER PRIMARY KEY," - "contentURL varchar);"); - //Recipe table. + //Recipe table. Each recipe can have at most one instruction, and one image. this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" "recipeId INTEGER PRIMARY KEY," - "date date," + "createdDate date," "name varchar," - "imageNr int," "cookTime time," "prepTime time," - "servingCount real," - "FOREIGN KEY (imageNr) REFERENCES image(imageNr));"); + "servingCount real);"); //Recipe tags table. this->executeSQL("CREATE TABLE IF NOT EXISTS recipeTag(" "recipeId INTEGER PRIMARY KEY," @@ -49,27 +75,4 @@ void RecipeDatabase::ensureTablesExist(){ "comment varchar," "FOREIGN KEY (ingredientId) REFERENCES ingredient(ingredientId)," "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); - //Recipe Instruction mapping table. - this->executeSQL("CREATE TABLE IF NOT EXISTS recipeInstruction(" - "instructionNr int," - "recipeId INTEGER PRIMARY KEY," - "FOREIGN KEY (instructionNr) REFERENCES instruction(instructionNr)," - "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); -} - -void RecipeDatabase::storeInstruction(Instruction instruction){ - -} - -void RecipeDatabase::storeImage(QImage image){ - -} - -void RecipeDatabase::storeIngredient(Ingredient ingredient){ - ResultTable t = this->executeSQL("SELECT * FROM ingredient WHERE name='"+ingredient.getName()+"';"); - if (!t.isEmpty()){ - fprintf(stderr, "Error during storeIngredient: ingredient with name %s already exists.\n", ingredient.getName().c_str()); - } else { - this->executeSQL("INSERT INTO ingredient (foodGroup, name) VALUES ('"+ ingredient.getFoodGroup() +"', '"+ ingredient.getName() +"');"); - } } diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index a0d311a..9c52876 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -21,8 +21,7 @@ class RecipeDatabase : public Database void storeRecipe(Recipe recipe); //SQL Helper methods. - void storeInstruction(Instruction instruction); - void storeImage(QImage image); + void storeRecipeIngredient(RecipeIngredient ri); void storeIngredient(Ingredient ingredient); private: diff --git a/model/database/resulttable.cpp b/model/database/resulttable.cpp index b8f4bca..8b4d144 100644 --- a/model/database/resulttable.cpp +++ b/model/database/resulttable.cpp @@ -25,10 +25,14 @@ void ResultTable::processRow(sqlite3_stmt *stmt){ } void ResultTable::printData(){ + if (this->isEmpty()){ + printf("Result table is empty.\n"); + return; + } printf("Printing table: %d by %d\n", this->rowCount(), this->columnCount()); for (unsigned int row = 0; row < this->rowCount(); row++){ for (unsigned int col = 0; col < this->columnCount(); col++){ - printf("| %s \t", this->values[row][col].c_str()); + printf("| %s ", this->values[row][col].c_str()); } printf("\n"); } diff --git a/userInterface/mainwindow.cpp b/userInterface/mainwindow.cpp index 53aab11..69a6189 100644 --- a/userInterface/mainwindow.cpp +++ b/userInterface/mainwindow.cpp @@ -6,16 +6,7 @@ MainWindow::MainWindow(QWidget *parent) : ui(new Ui::MainWindow){ ui->setupUi(this); - ui->ingredientsListView->setModel(&this->ingredientModel); - - //TESTING CODE - vector ri; - ri.push_back(RecipeIngredient("flour", "grains", 3.0f, UnitOfMeasure("cup", "cups", "c"))); - ri.push_back(RecipeIngredient("Baking Powder", "Additives", 1.0f, UnitOfMeasure("Teaspoon", "Teaspoons", "Tsp"))); - - Recipe rec("Example", ri, Instruction("BOLDiTaLiCs"), QImage(), vector(), QDate::currentDate(), QTime(0, 30), QTime(0, 25), 10.0f); - - this->loadFromRecipe(rec); + ui->ingredientsListView->setModel(&this->ingredientModel); } MainWindow::~MainWindow(){ diff --git a/utils/fileutils.h b/utils/fileutils.h index ed23f19..cb55e09 100644 --- a/utils/fileutils.h +++ b/utils/fileutils.h @@ -19,7 +19,7 @@ namespace FileUtils { } } - void saveInstruction(int nr, Instruction instruction){ + string saveInstruction(int nr, Instruction instruction){ ensureAppDataFolderExists(); QString filename = appDataPath + QString::fromStdString(std::to_string(nr)) +".html"; QFile file(filename); @@ -27,8 +27,10 @@ namespace FileUtils { QTextStream stream(&file); stream< +#include + +namespace StringUtils{ + + + +} + +#endif // STRINGUTILS_H From cacc75f07de2fd0e21ac58034bb570455882c74d Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Fri, 2 Mar 2018 14:14:56 +0100 Subject: [PATCH 07/10] Added ability to store recipe in database. --- RecipeDB.pro | 6 +-- main.cpp | 16 +++++--- model/database/database.cpp | 1 - model/database/recipedatabase.cpp | 63 ++++++++++++++++++++++++++---- model/database/recipedatabase.h | 7 +++- utils/fileutils.cpp | 64 +++++++++++++++++++++++++++++++ utils/fileutils.h | 48 +++++------------------ utils/stringutils.h | 13 ------- 8 files changed, 146 insertions(+), 72 deletions(-) create mode 100644 utils/fileutils.cpp delete mode 100644 utils/stringutils.h diff --git a/RecipeDB.pro b/RecipeDB.pro index 4503a07..2c957a5 100644 --- a/RecipeDB.pro +++ b/RecipeDB.pro @@ -24,7 +24,8 @@ SOURCES += model/recipe/instruction.cpp \ model/recipe/tags/recipetag.cpp \ SQLite/sqlite3.c \ model/database/resulttable.cpp \ - model/database/recipedatabase.cpp + model/database/recipedatabase.cpp \ + utils/fileutils.cpp HEADERS += model/recipe/instruction.h \ model/recipe/recipe.h \ @@ -39,8 +40,7 @@ HEADERS += model/recipe/instruction.h \ SQLite/sqlite3ext.h \ model/database/resulttable.h \ model/database/recipedatabase.h \ - utils/fileutils.h \ - utils/stringutils.h + utils/fileutils.h LIBS += -ldl \ diff --git a/main.cpp b/main.cpp index 98c1306..0deb267 100644 --- a/main.cpp +++ b/main.cpp @@ -3,7 +3,6 @@ #include "model/database/database.h" #include "model/database/recipedatabase.h" -#include "utils/fileutils.h" int main(int argc, char *argv[]) { @@ -14,12 +13,12 @@ int main(int argc, char *argv[]) //TESTING CODE RecipeDatabase recipeDB("recipes"); - recipeDB.storeIngredient(Ingredient("Apple", "Fruit")); - recipeDB.storeIngredient(Ingredient("Corn", "Vegetable")); - recipeDB.storeIngredient(Ingredient("Lettuce", "Vegetable")); - recipeDB.storeIngredient(Ingredient("Carrot", "Vegetable")); +// recipeDB.storeIngredient(Ingredient("Apple", "Fruit")); +// recipeDB.storeIngredient(Ingredient("Corn", "Vegetable")); +// recipeDB.storeIngredient(Ingredient("Lettuce", "Vegetable")); +// recipeDB.storeIngredient(Ingredient("Carrot", "Vegetable")); - recipeDB.executeSQL("SELECT * FROM ingredient;").printData(); +// recipeDB.executeSQL("SELECT * FROM ingredient;").printData(); //TESTING CODE vector ri; @@ -28,6 +27,11 @@ int main(int argc, char *argv[]) Recipe rec("Example", ri, Instruction("BOLDiTaLiCs"), QImage(), vector(), QDate::currentDate(), QTime(0, 30), QTime(0, 25), 10.0f); + bool success = recipeDB.storeRecipe(rec); + printf("Storage successful: %d\n", success); + + recipeDB.executeSQL("SELECT * FROM recipeIngredient;").printData(); + w.loadFromRecipe(rec); return a.exec(); diff --git a/model/database/database.cpp b/model/database/database.cpp index 0b843d5..d4d533d 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -34,7 +34,6 @@ bool Database::insertInto(string tableName, vector columnNames, vectorexecuteSQL(query); return true; } diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 5069fe0..231d5c9 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -4,11 +4,12 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ this->ensureTablesExist(); } -void RecipeDatabase::storeRecipe(Recipe recipe){ +bool RecipeDatabase::storeRecipe(Recipe recipe){ ///TODO: Implement this in a smart way using transaction. ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name='"+recipe.getName()+"';"); if (!t.isEmpty()){ fprintf(stderr, "Error storing recipe: Recipe with name %s already exists.\n", recipe.getName().c_str()); + return false; } else { bool success = this->insertInto("recipe", vector({ @@ -27,24 +28,70 @@ void RecipeDatabase::storeRecipe(Recipe recipe){ })); if (success){ //If successful, proceed to insert instructions, image, and ingredients. - + int recipeId = this->getLastInsertedRowId(); + for (unsigned int i = 0; i < recipe.getIngredients().size(); i++){ + if (!this->storeRecipeIngredient(recipe.getIngredients()[i], recipeId)){ + return false; + } + } + if (!this->storeInstruction(recipe.getInstruction(), recipeId)){ + return false; + } + if (!this->storeImage(recipe.getImage(), recipeId)){ + return false; + } + return true; + } else { + return false; } } } -void RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri){ - +bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ + //First check if the base ingredient has been added to the database. This is done within storeIngredient(). + ResultTable t = this->executeSQL("SELECT ingredientId FROM ingredient WHERE name='"+ri.getName()+"';"); + int ingId = 0; + if (t.isEmpty()){ + if (!this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ri.getFoodGroup(), ri.getName()}))){ + return false; + } + ingId = this->getLastInsertedRowId(); + } else { + ingId = std::stoi(t.valueAt(0, 0)); + } + return this->insertInto("recipeIngredient", + vector({ + "ingredientId", + "recipeId", + "quantity", + "unitName", + "comment" + }), + vector({ + std::to_string(ingId), + std::to_string(recipeId), + std::to_string(ri.getQuantity()), + ri.getUnit().getName(), + ri.getComment() + })); } void RecipeDatabase::storeIngredient(Ingredient ingredient){ ResultTable t = this->executeSQL("SELECT * FROM ingredient WHERE name='"+ingredient.getName()+"';"); - if (!t.isEmpty()){ - fprintf(stderr, "Error during storeIngredient: ingredient with name %s already exists.\n", ingredient.getName().c_str()); - } else { + if (t.isEmpty()){ this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ingredient.getFoodGroup(), ingredient.getName()})); } } +bool RecipeDatabase::storeInstruction(Instruction instruction, int recipeId){ + bool success = FileUtils::saveInstruction(recipeId, instruction); + return success; +} + +bool RecipeDatabase::storeImage(QImage image, int recipeId){ + return FileUtils::saveImage(recipeId, image); +} + void RecipeDatabase::ensureTablesExist(){ //Make sure that foreign keys are enabled. this->executeSQL("PRAGMA foreign_keys = ON;"); @@ -69,7 +116,7 @@ void RecipeDatabase::ensureTablesExist(){ //RecipeIngredient table. this->executeSQL("CREATE TABLE IF NOT EXISTS recipeIngredient(" "ingredientId int," - "recipeId INTEGER PRIMARY KEY," + "recipeId int," "quantity real," "unitName varchar," "comment varchar," diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index 9c52876..72cfa2d 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -5,6 +5,7 @@ #include "database.h" #include "model/recipe/recipe.h" +#include "utils/fileutils.h" using namespace std; @@ -18,11 +19,13 @@ class RecipeDatabase : public Database RecipeDatabase(string filename); //Stores a full recipe in the database. - void storeRecipe(Recipe recipe); + bool storeRecipe(Recipe recipe); //SQL Helper methods. - void storeRecipeIngredient(RecipeIngredient ri); + bool storeRecipeIngredient(RecipeIngredient ri, int recipeId); void storeIngredient(Ingredient ingredient); + bool storeInstruction(Instruction instruction, int recipeId); + bool storeImage(QImage image, int recipeId); private: //Utility methods. diff --git a/utils/fileutils.cpp b/utils/fileutils.cpp new file mode 100644 index 0000000..a453c57 --- /dev/null +++ b/utils/fileutils.cpp @@ -0,0 +1,64 @@ +#include "fileutils.h" + +namespace FileUtils{ + +void ensureAppDataFolderExists(){ + QDir folder(appDataPath); + if (!folder.exists()){ + folder.mkpath("."); + } +} + +bool saveInstruction(int nr, Instruction instruction){ + ensureAppDataFolderExists(); + QString filename = appDataPath + QString::fromStdString(std::to_string(nr)) +".html"; + QFile file(filename); + if (file.open(QIODevice::WriteOnly)){ + QTextStream stream(&file); + stream< #include #include +#include #include "model/recipe/instruction.h" -namespace FileUtils { +namespace FileUtils{ - QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)+"/.recipeDB/"; + const QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)+"/.recipeDB/"; - void ensureAppDataFolderExists(){ - QDir folder(appDataPath); - if (!folder.exists()){ - folder.mkpath("."); - } - } + void ensureAppDataFolderExists(); - string saveInstruction(int nr, Instruction instruction){ - ensureAppDataFolderExists(); - QString filename = appDataPath + QString::fromStdString(std::to_string(nr)) +".html"; - QFile file(filename); - if (file.open(QIODevice::WriteOnly)){ - QTextStream stream(&file); - stream< -#include - -namespace StringUtils{ - - - -} - -#endif // STRINGUTILS_H From 238d7edee310838a763ee10342dd07f4334bd3df Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 3 Mar 2018 08:38:32 +0100 Subject: [PATCH 08/10] Added convenience select method, improved security of recipe storage. --- model/database/database.cpp | 23 +++++++++++++++----- model/database/database.h | 5 +++++ model/database/recipedatabase.cpp | 35 ++++++++++++++++--------------- model/database/recipedatabase.h | 2 ++ model/database/resulttable.cpp | 5 +++++ model/database/resulttable.h | 2 ++ 6 files changed, 50 insertions(+), 22 deletions(-) diff --git a/model/database/database.cpp b/model/database/database.cpp index d4d533d..0e2c0ed 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -34,8 +34,16 @@ bool Database::insertInto(string tableName, vector columnNames, vectorexecuteSQL(query); - return true; + ResultTable t = this->executeSQL(query); + return (t.getReturnCode() == SQLITE_DONE); +} + +ResultTable Database::selectFrom(string tableName, string columnNames, string conditions){ + if (columnNames.size() == 0 || tableName.empty()){ + return ResultTable(); + } + string query = "SELECT " + columnNames + " FROM " + tableName + " WHERE " + conditions + ";"; + return this->executeSQL(query); } void Database::openConnection(){ @@ -58,18 +66,23 @@ string Database::combineVector(std::vector strings, string mid){ if (strings.empty()){ return ""; } - std::string result = "'" + strings[0] + "'"; + std::string result = surroundString(strings[0], "'"); for (std::vector::iterator it = strings.begin() + 1; it != strings.end(); ++it){ - result += mid + "'" + (*it) + "'"; + result += mid + surroundString((*it), "'"); } return result; } +string Database::surroundString(string s, string surround){ + return surround+s+surround; +} + bool Database::tableExists(string tableName){ if (tableName.empty() || this->db == NULL || !this->dbIsOpen){ return false; } - ResultTable t = executeSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';"); + ResultTable t = this->selectFrom("sqlite_master", "name", "type='table' AND name='"+tableName+"'"); + //ResultTable t = executeSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';"); return !t.isEmpty(); } diff --git a/model/database/database.h b/model/database/database.h index 3c1b73f..4a60447 100644 --- a/model/database/database.h +++ b/model/database/database.h @@ -25,9 +25,14 @@ public: //Executes an SQL string statement in a safe way and returns the result. ResultTable executeSQL(string statement); bool insertInto(string tableName, vector columnNames, vector values); + ResultTable selectFrom(string tableName, string columnNames, string conditions); bool tableExists(string tableName); int getLastInsertedRowId(); + +protected: + string surroundString(string s, string surround); + private: //SQL Instance variables. string filename; diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 231d5c9..5edddbb 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -6,10 +6,11 @@ RecipeDatabase::RecipeDatabase(string filename) : Database(filename){ bool RecipeDatabase::storeRecipe(Recipe recipe){ ///TODO: Implement this in a smart way using transaction. - ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name='"+recipe.getName()+"';"); + this->executeSQL("BEGIN;"); + ResultTable t = this->selectFrom("recipe", "*", "name="+surroundString(recipe.getName(), "'")); + //ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name='"+recipe.getName()+"';"); if (!t.isEmpty()){ fprintf(stderr, "Error storing recipe: Recipe with name %s already exists.\n", recipe.getName().c_str()); - return false; } else { bool success = this->insertInto("recipe", vector({ @@ -29,27 +30,27 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ if (success){ //If successful, proceed to insert instructions, image, and ingredients. int recipeId = this->getLastInsertedRowId(); + bool ingredientSuccess = true; for (unsigned int i = 0; i < recipe.getIngredients().size(); i++){ if (!this->storeRecipeIngredient(recipe.getIngredients()[i], recipeId)){ - return false; + ingredientSuccess = false; + break; } } - if (!this->storeInstruction(recipe.getInstruction(), recipeId)){ - return false; + if (ingredientSuccess && this->storeInstruction(recipe.getInstruction(), recipeId) && this->storeImage(recipe.getImage(), recipeId)){ + this->executeSQL("COMMIT;"); + return true; } - if (!this->storeImage(recipe.getImage(), recipeId)){ - return false; - } - return true; - } else { - return false; } } + this->executeSQL("ROLLBACK;"); + return false; } bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ //First check if the base ingredient has been added to the database. This is done within storeIngredient(). - ResultTable t = this->executeSQL("SELECT ingredientId FROM ingredient WHERE name='"+ri.getName()+"';"); + ResultTable t = this->selectFrom("ingredient", "ingredientId", "name="+surroundString(ri.getName(), "'")); + //ResultTable t = this->executeSQL("SELECT ingredientId FROM ingredient WHERE name='"+ri.getName()+"';"); int ingId = 0; if (t.isEmpty()){ if (!this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ri.getFoodGroup(), ri.getName()}))){ @@ -77,15 +78,15 @@ bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ } void RecipeDatabase::storeIngredient(Ingredient ingredient){ - ResultTable t = this->executeSQL("SELECT * FROM ingredient WHERE name='"+ingredient.getName()+"';"); + ResultTable t = this->selectFrom("ingredient", "*", "name="+surroundString(ingredient.getName(), "'")); + //ResultTable t = this->executeSQL("SELECT * FROM ingredient WHERE name='"+ingredient.getName()+"';"); if (t.isEmpty()){ this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ingredient.getFoodGroup(), ingredient.getName()})); } } bool RecipeDatabase::storeInstruction(Instruction instruction, int recipeId){ - bool success = FileUtils::saveInstruction(recipeId, instruction); - return success; + return FileUtils::saveInstruction(recipeId, instruction); } bool RecipeDatabase::storeImage(QImage image, int recipeId){ @@ -99,12 +100,12 @@ void RecipeDatabase::ensureTablesExist(){ this->executeSQL("CREATE TABLE IF NOT EXISTS ingredient(" "ingredientId INTEGER PRIMARY KEY," "foodGroup varchar," - "name varchar);"); + "name varchar UNIQUE);"); //Recipe table. Each recipe can have at most one instruction, and one image. this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" "recipeId INTEGER PRIMARY KEY," "createdDate date," - "name varchar," + "name varchar UNIQUE," "cookTime time," "prepTime time," "servingCount real);"); diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index 72cfa2d..1514cea 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -26,6 +26,8 @@ class RecipeDatabase : public Database void storeIngredient(Ingredient ingredient); bool storeInstruction(Instruction instruction, int recipeId); bool storeImage(QImage image, int recipeId); + + vector retrieveRecipe(string name); private: //Utility methods. diff --git a/model/database/resulttable.cpp b/model/database/resulttable.cpp index 8b4d144..bef5028 100644 --- a/model/database/resulttable.cpp +++ b/model/database/resulttable.cpp @@ -11,6 +11,7 @@ void ResultTable::extractData(sqlite3_stmt *stmt){ processRow(stmt); res = sqlite3_step(stmt); } + this->queryCode = res; } void ResultTable::processRow(sqlite3_stmt *stmt){ @@ -51,6 +52,10 @@ string ResultTable::valueAt(unsigned int row, unsigned int col){ } } +int ResultTable::getReturnCode(){ + return this->queryCode; +} + unsigned int ResultTable::columnCount(){ if (this->isEmpty()){ return 0; diff --git a/model/database/resulttable.h b/model/database/resulttable.h index 37550e9..dd92ead 100644 --- a/model/database/resulttable.h +++ b/model/database/resulttable.h @@ -27,10 +27,12 @@ class ResultTable bool isEmpty(); string valueAt(unsigned int row, unsigned int col); + int getReturnCode(); unsigned int columnCount(); unsigned int rowCount(); private: vector> values; + int queryCode; //Utility methods. string convertToString(sqlite3_value* val); From 77dddef2011634687535c68450e60e1c98a85e76 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 3 Mar 2018 08:48:55 +0100 Subject: [PATCH 09/10] Improved result table functionality --- main.cpp | 2 ++ model/database/database.cpp | 2 +- model/database/recipedatabase.cpp | 10 ++++++++++ model/database/recipedatabase.h | 2 +- model/database/resulttable.cpp | 10 +++++++++- model/database/resulttable.h | 4 ++++ 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 0deb267..e8814ef 100644 --- a/main.cpp +++ b/main.cpp @@ -32,6 +32,8 @@ int main(int argc, char *argv[]) recipeDB.executeSQL("SELECT * FROM recipeIngredient;").printData(); + Recipe reloadRec = recipeDB.retrieveRecipe("Example"); + w.loadFromRecipe(rec); return a.exec(); diff --git a/model/database/database.cpp b/model/database/database.cpp index 0e2c0ed..6fb4394 100644 --- a/model/database/database.cpp +++ b/model/database/database.cpp @@ -13,7 +13,7 @@ ResultTable Database::executeSQL(string statement){ sqlite3_stmt* stmt; this->sql = statement; this->returnCode = sqlite3_prepare_v2(this->db, statement.c_str(), -1, &stmt, NULL); - ResultTable t; + ResultTable t(statement); if (this->returnCode != SQLITE_OK){ fprintf(stderr, "Unable to successfully prepare SQL statement. Error code: %d\n\tError Message: %s\n", this->returnCode, sqlite3_errmsg(this->db)); return t; diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 5edddbb..99bb92c 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -93,6 +93,16 @@ bool RecipeDatabase::storeImage(QImage image, int recipeId){ return FileUtils::saveImage(recipeId, image); } +Recipe RecipeDatabase::retrieveRecipe(string name){ + ResultTable t = this->selectFrom("recipe", "*", "name="+surroundString(name, "'")); + if (t.isEmpty()){ + fprintf(stderr, "Error: No recipe with name %s found!\n", name.c_str()); + return Recipe(); + } + t.printData(); + return Recipe(); +} + void RecipeDatabase::ensureTablesExist(){ //Make sure that foreign keys are enabled. this->executeSQL("PRAGMA foreign_keys = ON;"); diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index 1514cea..4c4d420 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -27,7 +27,7 @@ class RecipeDatabase : public Database bool storeInstruction(Instruction instruction, int recipeId); bool storeImage(QImage image, int recipeId); - vector retrieveRecipe(string name); + Recipe retrieveRecipe(string name); private: //Utility methods. diff --git a/model/database/resulttable.cpp b/model/database/resulttable.cpp index bef5028..9811d64 100644 --- a/model/database/resulttable.cpp +++ b/model/database/resulttable.cpp @@ -4,6 +4,10 @@ ResultTable::ResultTable(){ } +ResultTable::ResultTable(string query){ + this->originalQuery = query; +} + void ResultTable::extractData(sqlite3_stmt *stmt){ this->values.clear(); int res = sqlite3_step(stmt); @@ -30,7 +34,7 @@ void ResultTable::printData(){ printf("Result table is empty.\n"); return; } - printf("Printing table: %d by %d\n", this->rowCount(), this->columnCount()); + printf("Printing table: [%d x %d]\t%s\n", this->rowCount(), this->columnCount(), this->originalQuery.c_str()); for (unsigned int row = 0; row < this->rowCount(); row++){ for (unsigned int col = 0; col < this->columnCount(); col++){ printf("| %s ", this->values[row][col].c_str()); @@ -56,6 +60,10 @@ int ResultTable::getReturnCode(){ return this->queryCode; } +string ResultTable::getOriginalQuery(){ + return this->originalQuery; +} + unsigned int ResultTable::columnCount(){ if (this->isEmpty()){ return 0; diff --git a/model/database/resulttable.h b/model/database/resulttable.h index dd92ead..a138ea3 100644 --- a/model/database/resulttable.h +++ b/model/database/resulttable.h @@ -17,6 +17,8 @@ class ResultTable public: //Constructs an empty table. ResultTable(); + //Constructs a table with the original query saved. + ResultTable(string query); //Gets all the data from the result set and stores it internally as strings. void extractData(sqlite3_stmt* stmt); @@ -28,11 +30,13 @@ class ResultTable bool isEmpty(); string valueAt(unsigned int row, unsigned int col); int getReturnCode(); + string getOriginalQuery(); unsigned int columnCount(); unsigned int rowCount(); private: vector> values; int queryCode; + string originalQuery; //Utility methods. string convertToString(sqlite3_value* val); From 446ff8f5bafb97da4d757cfb9a183eee018a3b06 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 3 Mar 2018 10:18:38 +0100 Subject: [PATCH 10/10] Storage is now complete. Retrieval is partially implemented. --- main.cpp | 5 +- model/database/recipedatabase.cpp | 64 ++++++++++++++++--- model/database/recipedatabase.h | 2 + model/recipe/ingredients/recipeingredient.cpp | 6 +- model/recipe/ingredients/recipeingredient.h | 1 + model/recipe/ingredients/unitofmeasure.cpp | 9 ++- model/recipe/ingredients/unitofmeasure.h | 2 + model/recipe/recipe.cpp | 32 +++++++++- model/recipe/recipe.h | 2 + 9 files changed, 107 insertions(+), 16 deletions(-) diff --git a/main.cpp b/main.cpp index e8814ef..d67a414 100644 --- a/main.cpp +++ b/main.cpp @@ -30,11 +30,10 @@ int main(int argc, char *argv[]) bool success = recipeDB.storeRecipe(rec); printf("Storage successful: %d\n", success); - recipeDB.executeSQL("SELECT * FROM recipeIngredient;").printData(); - Recipe reloadRec = recipeDB.retrieveRecipe("Example"); + reloadRec.print(); - w.loadFromRecipe(rec); + w.loadFromRecipe(reloadRec); return a.exec(); } diff --git a/model/database/recipedatabase.cpp b/model/database/recipedatabase.cpp index 99bb92c..4c991da 100644 --- a/model/database/recipedatabase.cpp +++ b/model/database/recipedatabase.cpp @@ -8,7 +8,6 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ ///TODO: Implement this in a smart way using transaction. this->executeSQL("BEGIN;"); ResultTable t = this->selectFrom("recipe", "*", "name="+surroundString(recipe.getName(), "'")); - //ResultTable t = this->executeSQL("SELECT * FROM recipe WHERE name='"+recipe.getName()+"';"); if (!t.isEmpty()){ fprintf(stderr, "Error storing recipe: Recipe with name %s already exists.\n", recipe.getName().c_str()); } else { @@ -28,7 +27,7 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ std::to_string(recipe.getServings()) })); if (success){ - //If successful, proceed to insert instructions, image, and ingredients. + //If successful, proceed to insert instructions, image, and ingredients, and tags. int recipeId = this->getLastInsertedRowId(); bool ingredientSuccess = true; for (unsigned int i = 0; i < recipe.getIngredients().size(); i++){ @@ -37,7 +36,10 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ break; } } - if (ingredientSuccess && this->storeInstruction(recipe.getInstruction(), recipeId) && this->storeImage(recipe.getImage(), recipeId)){ + if (ingredientSuccess && + this->storeInstruction(recipe.getInstruction(), recipeId) && + this->storeImage(recipe.getImage(), recipeId) && + this->storeTags(recipe.getTags(), recipeId)){ this->executeSQL("COMMIT;"); return true; } @@ -50,7 +52,6 @@ bool RecipeDatabase::storeRecipe(Recipe recipe){ bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ //First check if the base ingredient has been added to the database. This is done within storeIngredient(). ResultTable t = this->selectFrom("ingredient", "ingredientId", "name="+surroundString(ri.getName(), "'")); - //ResultTable t = this->executeSQL("SELECT ingredientId FROM ingredient WHERE name='"+ri.getName()+"';"); int ingId = 0; if (t.isEmpty()){ if (!this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ri.getFoodGroup(), ri.getName()}))){ @@ -79,7 +80,6 @@ bool RecipeDatabase::storeRecipeIngredient(RecipeIngredient ri, int recipeId){ void RecipeDatabase::storeIngredient(Ingredient ingredient){ ResultTable t = this->selectFrom("ingredient", "*", "name="+surroundString(ingredient.getName(), "'")); - //ResultTable t = this->executeSQL("SELECT * FROM ingredient WHERE name='"+ingredient.getName()+"';"); if (t.isEmpty()){ this->insertInto("ingredient", vector({"foodGroup", "name"}), vector({ingredient.getFoodGroup(), ingredient.getName()})); } @@ -93,6 +93,24 @@ bool RecipeDatabase::storeImage(QImage image, int recipeId){ return FileUtils::saveImage(recipeId, image); } +bool RecipeDatabase::storeTags(vector tags, int recipeId){ + for (vector::iterator it = tags.begin(); it != tags.end(); ++it){ + bool s = this->insertInto("recipeTag", + vector({ + "recipeId", + "tagName" + }), + vector({ + std::to_string(recipeId), + (*it).getValue() + })); + if (!s){ + return false; + } + } + return true; +} + Recipe RecipeDatabase::retrieveRecipe(string name){ ResultTable t = this->selectFrom("recipe", "*", "name="+surroundString(name, "'")); if (t.isEmpty()){ @@ -100,12 +118,39 @@ Recipe RecipeDatabase::retrieveRecipe(string name){ return Recipe(); } t.printData(); - return Recipe(); + Recipe r; + int id = std::stoi(t.valueAt(0, 0)); + r.setName(t.valueAt(0, 1)); + r.setCreatedDate(QDate::fromString(QString::fromStdString(t.valueAt(0, 2)))); + r.setPrepTime(QTime::fromString(QString::fromStdString(t.valueAt(0, 3)))); + r.setCookTime(QTime::fromString(QString::fromStdString(t.valueAt(0, 4)))); + r.setServings(std::stof(t.valueAt(0, 5))); + r.setInstruction(FileUtils::loadInstruction(id)); + r.setImage(FileUtils::loadImage(id)); + r.setIngredients(this->retrieveRecipeIngredients(id)); + return r; +} + +vector RecipeDatabase::retrieveRecipeIngredients(int recipeId){ + ResultTable t = this->executeSQL("SELECT ingredient.name, ingredient.foodGroup, recipeIngredient.quantity, recipeIngredient.unitName, recipeIngredient.comment " + "FROM ingredient " + "INNER JOIN recipeIngredient " + "ON ingredient.ingredientId = recipeIngredient.ingredientId " + "AND recipeIngredient.recipeId = "+std::to_string(recipeId)+";"); + t.printData(); + vector ings; + for (unsigned int row = 0; row < t.rowCount(); row++){ + RecipeIngredient r(t.valueAt(row, 0), t.valueAt(row, 1), std::stof(t.valueAt(row, 2)), UnitOfMeasure(t.valueAt(row, 3))); + ings.push_back(r); + } + return ings; } void RecipeDatabase::ensureTablesExist(){ //Make sure that foreign keys are enabled. this->executeSQL("PRAGMA foreign_keys = ON;"); + + this->executeSQL("BEGIN;"); //Ingredients table. this->executeSQL("CREATE TABLE IF NOT EXISTS ingredient(" "ingredientId INTEGER PRIMARY KEY," @@ -114,14 +159,14 @@ void RecipeDatabase::ensureTablesExist(){ //Recipe table. Each recipe can have at most one instruction, and one image. this->executeSQL("CREATE TABLE IF NOT EXISTS recipe(" "recipeId INTEGER PRIMARY KEY," - "createdDate date," "name varchar UNIQUE," - "cookTime time," + "createdDate date," "prepTime time," + "cookTime time," "servingCount real);"); //Recipe tags table. this->executeSQL("CREATE TABLE IF NOT EXISTS recipeTag(" - "recipeId INTEGER PRIMARY KEY," + "recipeId int," "tagName varchar," "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); //RecipeIngredient table. @@ -133,4 +178,5 @@ void RecipeDatabase::ensureTablesExist(){ "comment varchar," "FOREIGN KEY (ingredientId) REFERENCES ingredient(ingredientId)," "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));"); + this->executeSQL("COMMIT;"); } diff --git a/model/database/recipedatabase.h b/model/database/recipedatabase.h index 4c4d420..e91b45d 100644 --- a/model/database/recipedatabase.h +++ b/model/database/recipedatabase.h @@ -26,8 +26,10 @@ class RecipeDatabase : public Database void storeIngredient(Ingredient ingredient); bool storeInstruction(Instruction instruction, int recipeId); bool storeImage(QImage image, int recipeId); + bool storeTags(vector tags, int recipeId); Recipe retrieveRecipe(string name); + vector retrieveRecipeIngredients(int recipeId); private: //Utility methods. diff --git a/model/recipe/ingredients/recipeingredient.cpp b/model/recipe/ingredients/recipeingredient.cpp index 3e09886..c48155c 100644 --- a/model/recipe/ingredients/recipeingredient.cpp +++ b/model/recipe/ingredients/recipeingredient.cpp @@ -9,7 +9,11 @@ RecipeIngredient::RecipeIngredient(Ingredient i, float quantity, UnitOfMeasure u setName(i.getName()); setFoodGroup(i.getFoodGroup()); setQuantity(quantity); - setUnit(unit); + setUnit(unit); +} + +RecipeIngredient::RecipeIngredient(){ + } float RecipeIngredient::getQuantity() const{ diff --git a/model/recipe/ingredients/recipeingredient.h b/model/recipe/ingredients/recipeingredient.h index 54224b3..70c20f6 100644 --- a/model/recipe/ingredients/recipeingredient.h +++ b/model/recipe/ingredients/recipeingredient.h @@ -19,6 +19,7 @@ public: RecipeIngredient(string name, string foodGroup, float quantity, UnitOfMeasure unit); //Constructor using data from a child ingredient. RecipeIngredient(Ingredient i, float quantity, UnitOfMeasure unit); + RecipeIngredient(); //Getters float getQuantity() const; diff --git a/model/recipe/ingredients/unitofmeasure.cpp b/model/recipe/ingredients/unitofmeasure.cpp index ffc0066..ddf62c8 100644 --- a/model/recipe/ingredients/unitofmeasure.cpp +++ b/model/recipe/ingredients/unitofmeasure.cpp @@ -3,7 +3,14 @@ UnitOfMeasure::UnitOfMeasure(string name, string plural, string abbreviation){ this->name = name; this->plural = plural; - this->abbreviation = abbreviation; + this->abbreviation = abbreviation; +} + +UnitOfMeasure::UnitOfMeasure(string name){ + this->name = name; + this->plural = name + "s"; + this->abbreviation = "NULL"; + ///TODO: Make actual guessing of this stuff. } UnitOfMeasure::UnitOfMeasure() : UnitOfMeasure::UnitOfMeasure("", "", ""){ diff --git a/model/recipe/ingredients/unitofmeasure.h b/model/recipe/ingredients/unitofmeasure.h index 2692e5a..1787f14 100644 --- a/model/recipe/ingredients/unitofmeasure.h +++ b/model/recipe/ingredients/unitofmeasure.h @@ -14,6 +14,8 @@ class UnitOfMeasure public: //Full constructor. UnitOfMeasure(string name, string plural, string abbreviation); + //Attempt to guess unit from just a string. + UnitOfMeasure(string name); //Constructor with default values. UnitOfMeasure(); diff --git a/model/recipe/recipe.cpp b/model/recipe/recipe.cpp index 1c6754d..f99e1a2 100644 --- a/model/recipe/recipe.cpp +++ b/model/recipe/recipe.cpp @@ -29,7 +29,11 @@ Instruction Recipe::getInstruction() const{ } QImage Recipe::getImage() const{ - return this->image; + return this->image; +} + +vector Recipe::getTags() const{ + return this->tags; } QDate Recipe::getCreatedDate() const{ @@ -89,5 +93,29 @@ void Recipe::setCookTime(QTime newTime){ } void Recipe::setServings(float newServingsCount){ - this->servings = newServingsCount; + this->servings = newServingsCount; +} + +void Recipe::print(){ + printf("Recipe: %s, Created on: %s, Prep time: %s, Cook time: %s, Serves: %f\n", + this->name.c_str(), + this->createdDate.toString().toStdString().c_str(), + this->prepTime.toString().toStdString().c_str(), + this->cookTime.toString().toStdString().c_str(), + this->servings); + printf("\tInstruction: %s\n", this->instruction.getHTML().c_str()); + printf("\tIngredients:\n"); + for (vector::iterator it = this->ingredients.begin(); it != this->ingredients.end(); ++it){ + RecipeIngredient ri = *it; + printf("\t\t%s, Food Group: %s, Quantity: %f, Unit: %s\n", + ri.getName().c_str(), + ri.getFoodGroup().c_str(), + ri.getQuantity(), + ri.getUnit().getName().c_str()); + } + printf("\tTags:\n"); + for (vector::iterator it = this->tags.begin(); it != this->tags.end(); ++it){ + RecipeTag t = *it; + printf("\t\t%s\n", t.getValue().c_str()); + } } diff --git a/model/recipe/recipe.h b/model/recipe/recipe.h index 1fef395..d013659 100644 --- a/model/recipe/recipe.h +++ b/model/recipe/recipe.h @@ -58,6 +58,8 @@ public: void setPrepTime(QTime newTime); void setCookTime(QTime newTime); void setServings(float newServingsCount); + + void print(); private: //Main information. string name; //The name of the recipe.