Shift to master. Not done but database abstraction is nearly done. #3
			
				
			
		
		
		
	
							
								
								
									
										22
									
								
								RecipeDB.pro
								
								
								
								
							
							
						
						
									
										22
									
								
								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,13 @@ 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 \
 | 
			
		||||
    model/database/resulttable.cpp \
 | 
			
		||||
    model/database/recipedatabase.cpp \
 | 
			
		||||
    utils/fileutils.cpp
 | 
			
		||||
 | 
			
		||||
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 +35,14 @@ 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 \
 | 
			
		||||
    model/database/resulttable.h \
 | 
			
		||||
    model/database/recipedatabase.h \
 | 
			
		||||
    utils/fileutils.h
 | 
			
		||||
 | 
			
		||||
LIBS += -ldl \
 | 
			
		||||
 | 
			
		||||
FORMS    += gui/mainwindow.ui
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										28
									
								
								main.cpp
								
								
								
								
							
							
						
						
									
										28
									
								
								main.cpp
								
								
								
								
							| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
#include <QApplication>
 | 
			
		||||
 | 
			
		||||
#include "model/database/database.h"
 | 
			
		||||
#include "model/database/recipedatabase.h"
 | 
			
		||||
 | 
			
		||||
int main(int argc, char *argv[])
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +10,30 @@ int main(int argc, char *argv[])
 | 
			
		|||
    MainWindow w;
 | 
			
		||||
    w.show();
 | 
			
		||||
 | 
			
		||||
    Database db("test.db");
 | 
			
		||||
	//TESTING CODE
 | 
			
		||||
 | 
			
		||||
    return a.exec();
 | 
			
		||||
	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();
 | 
			
		||||
 | 
			
		||||
	//TESTING CODE
 | 
			
		||||
	vector<RecipeIngredient> 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("<b>BOLD</b><i>iTaLiCs</i>"), QImage(), vector<RecipeTag>(), QDate::currentDate(), QTime(0, 30), QTime(0, 25), 10.0f);
 | 
			
		||||
 | 
			
		||||
	bool success = recipeDB.storeRecipe(rec);
 | 
			
		||||
	printf("Storage successful: %d\n", success);
 | 
			
		||||
 | 
			
		||||
	Recipe reloadRec = recipeDB.retrieveRecipe("Example");
 | 
			
		||||
	reloadRec.print();
 | 
			
		||||
 | 
			
		||||
	w.loadFromRecipe(reloadRec);
 | 
			
		||||
 | 
			
		||||
	return a.exec();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,16 +3,47 @@
 | 
			
		|||
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(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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.extractData(stmt);
 | 
			
		||||
 | 
			
		||||
	this->returnCode = sqlite3_finalize(stmt);
 | 
			
		||||
 | 
			
		||||
	return t;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Database::insertInto(string tableName, vector<string> columnNames, vector<string> 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 + ");";
 | 
			
		||||
	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(){
 | 
			
		||||
| 
						 | 
				
			
			@ -28,20 +59,33 @@ void Database::openConnection(){
 | 
			
		|||
 | 
			
		||||
void Database::closeConnection(){
 | 
			
		||||
    this->returnCode = sqlite3_close(this->db);
 | 
			
		||||
    this->dbIsOpen = false;
 | 
			
		||||
	this->dbIsOpen = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
string Database::combineVector(std::vector<string> strings, string mid){
 | 
			
		||||
	if (strings.empty()){
 | 
			
		||||
		return "";
 | 
			
		||||
	}
 | 
			
		||||
	std::string result = surroundString(strings[0], "'");
 | 
			
		||||
	for (std::vector<std::string>::iterator it = strings.begin() + 1; it != strings.end(); ++it){
 | 
			
		||||
		result += mid + surroundString((*it), "'");
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
string Database::surroundString(string s, string surround){
 | 
			
		||||
	return surround+s+surround;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 = 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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Database::getLastInsertedRowId(){
 | 
			
		||||
	return sqlite3_last_insert_rowid(this->db);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,19 +3,35 @@
 | 
			
		|||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <numeric>
 | 
			
		||||
 | 
			
		||||
#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 insertInto(string tableName, vector<string> columnNames, vector<string> 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -27,12 +43,8 @@ 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();
 | 
			
		||||
	std::string combineVector(std::vector<std::string> strings, std::string mid);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // DATABASE_H
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,182 @@
 | 
			
		|||
#include "recipedatabase.h"
 | 
			
		||||
 | 
			
		||||
RecipeDatabase::RecipeDatabase(string filename) : Database(filename){
 | 
			
		||||
	this->ensureTablesExist();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(), "'"));
 | 
			
		||||
	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<string>({
 | 
			
		||||
											"name",
 | 
			
		||||
											"createdDate",
 | 
			
		||||
											"cookTime",
 | 
			
		||||
											"prepTime",
 | 
			
		||||
											"servingCount"
 | 
			
		||||
										}),
 | 
			
		||||
						 vector<string>({
 | 
			
		||||
											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, and tags.
 | 
			
		||||
			int recipeId = this->getLastInsertedRowId();
 | 
			
		||||
			bool ingredientSuccess = true;
 | 
			
		||||
			for (unsigned int i = 0; i < recipe.getIngredients().size(); i++){
 | 
			
		||||
				if (!this->storeRecipeIngredient(recipe.getIngredients()[i], recipeId)){
 | 
			
		||||
					ingredientSuccess = false;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (ingredientSuccess &&
 | 
			
		||||
					this->storeInstruction(recipe.getInstruction(), recipeId) &&
 | 
			
		||||
					this->storeImage(recipe.getImage(), recipeId) &&
 | 
			
		||||
					this->storeTags(recipe.getTags(), recipeId)){
 | 
			
		||||
				this->executeSQL("COMMIT;");
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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->selectFrom("ingredient", "ingredientId", "name="+surroundString(ri.getName(), "'"));
 | 
			
		||||
	int ingId = 0;
 | 
			
		||||
	if (t.isEmpty()){
 | 
			
		||||
		if (!this->insertInto("ingredient", vector<string>({"foodGroup", "name"}), vector<string>({ri.getFoodGroup(), ri.getName()}))){
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		ingId = this->getLastInsertedRowId();
 | 
			
		||||
	} else {
 | 
			
		||||
		ingId = std::stoi(t.valueAt(0, 0));
 | 
			
		||||
	}
 | 
			
		||||
	return this->insertInto("recipeIngredient",
 | 
			
		||||
					 vector<string>({
 | 
			
		||||
										"ingredientId",
 | 
			
		||||
										"recipeId",
 | 
			
		||||
										"quantity",
 | 
			
		||||
										"unitName",
 | 
			
		||||
										"comment"
 | 
			
		||||
									}),
 | 
			
		||||
					 vector<string>({
 | 
			
		||||
										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->selectFrom("ingredient", "*", "name="+surroundString(ingredient.getName(), "'"));
 | 
			
		||||
	if (t.isEmpty()){
 | 
			
		||||
		this->insertInto("ingredient", vector<string>({"foodGroup", "name"}), vector<string>({ingredient.getFoodGroup(), ingredient.getName()}));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RecipeDatabase::storeInstruction(Instruction instruction, int recipeId){
 | 
			
		||||
	return FileUtils::saveInstruction(recipeId, instruction);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RecipeDatabase::storeImage(QImage image, int recipeId){
 | 
			
		||||
	return FileUtils::saveImage(recipeId, image);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RecipeDatabase::storeTags(vector<RecipeTag> tags, int recipeId){
 | 
			
		||||
	for (vector<RecipeTag>::iterator it = tags.begin(); it != tags.end(); ++it){
 | 
			
		||||
		bool s = this->insertInto("recipeTag",
 | 
			
		||||
								  vector<string>({
 | 
			
		||||
													 "recipeId",
 | 
			
		||||
													 "tagName"
 | 
			
		||||
												 }),
 | 
			
		||||
								  vector<string>({
 | 
			
		||||
													 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()){
 | 
			
		||||
		fprintf(stderr, "Error: No recipe with name %s found!\n", name.c_str());
 | 
			
		||||
		return Recipe();
 | 
			
		||||
	}
 | 
			
		||||
	t.printData();
 | 
			
		||||
	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<RecipeIngredient> 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<RecipeIngredient> 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,"
 | 
			
		||||
					 "foodGroup 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,"
 | 
			
		||||
					 "name varchar UNIQUE,"
 | 
			
		||||
					 "createdDate date,"
 | 
			
		||||
					 "prepTime time,"
 | 
			
		||||
					 "cookTime time,"
 | 
			
		||||
					 "servingCount real);");
 | 
			
		||||
	//Recipe tags table.
 | 
			
		||||
	this->executeSQL("CREATE TABLE IF NOT EXISTS recipeTag("
 | 
			
		||||
					 "recipeId int,"
 | 
			
		||||
					 "tagName varchar,"
 | 
			
		||||
					 "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,"
 | 
			
		||||
					 "FOREIGN KEY (ingredientId) REFERENCES ingredient(ingredientId),"
 | 
			
		||||
					 "FOREIGN KEY (recipeId) REFERENCES recipe(recipeId));");
 | 
			
		||||
	this->executeSQL("COMMIT;");
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
#ifndef RECIPEDATABASE_H
 | 
			
		||||
#define RECIPEDATABASE_H
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "database.h"
 | 
			
		||||
#include "model/recipe/recipe.h"
 | 
			
		||||
#include "utils/fileutils.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);
 | 
			
		||||
 | 
			
		||||
		//Stores a full recipe in the database.
 | 
			
		||||
		bool storeRecipe(Recipe recipe);
 | 
			
		||||
 | 
			
		||||
		//SQL Helper methods.
 | 
			
		||||
		bool storeRecipeIngredient(RecipeIngredient ri, int recipeId);
 | 
			
		||||
		void storeIngredient(Ingredient ingredient);
 | 
			
		||||
		bool storeInstruction(Instruction instruction, int recipeId);
 | 
			
		||||
		bool storeImage(QImage image, int recipeId);
 | 
			
		||||
		bool storeTags(vector<RecipeTag> tags, int recipeId);
 | 
			
		||||
 | 
			
		||||
		Recipe retrieveRecipe(string name);
 | 
			
		||||
		vector<RecipeIngredient> retrieveRecipeIngredients(int recipeId);
 | 
			
		||||
	private:
 | 
			
		||||
 | 
			
		||||
		//Utility methods.
 | 
			
		||||
		void ensureTablesExist();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // RECIPEDATABASE_H
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
#include "resulttable.h"
 | 
			
		||||
 | 
			
		||||
ResultTable::ResultTable(){
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultTable::ResultTable(string query){
 | 
			
		||||
	this->originalQuery = query;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ResultTable::extractData(sqlite3_stmt *stmt){
 | 
			
		||||
	this->values.clear();
 | 
			
		||||
	int res = sqlite3_step(stmt);
 | 
			
		||||
	while (res == SQLITE_ROW){
 | 
			
		||||
		processRow(stmt);
 | 
			
		||||
		res = sqlite3_step(stmt);
 | 
			
		||||
	}
 | 
			
		||||
	this->queryCode = res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ResultTable::processRow(sqlite3_stmt *stmt){
 | 
			
		||||
	int colCount = sqlite3_column_count(stmt);
 | 
			
		||||
	vector<string> 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(){
 | 
			
		||||
	if (this->isEmpty()){
 | 
			
		||||
		printf("Result table is empty.\n");
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	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());
 | 
			
		||||
		}
 | 
			
		||||
		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 "";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ResultTable::getReturnCode(){
 | 
			
		||||
	return this->queryCode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
string ResultTable::getOriginalQuery(){
 | 
			
		||||
	return this->originalQuery;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
#ifndef RESULTTABLE_H
 | 
			
		||||
#define RESULTTABLE_H
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#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();
 | 
			
		||||
		//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);
 | 
			
		||||
		//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);
 | 
			
		||||
		int getReturnCode();
 | 
			
		||||
		string getOriginalQuery();
 | 
			
		||||
		unsigned int columnCount();
 | 
			
		||||
		unsigned int rowCount();
 | 
			
		||||
	private:
 | 
			
		||||
		vector<vector<string>> values;
 | 
			
		||||
		int queryCode;
 | 
			
		||||
		string originalQuery;
 | 
			
		||||
 | 
			
		||||
		//Utility methods.
 | 
			
		||||
		string convertToString(sqlite3_value* val);
 | 
			
		||||
		bool isIndexValid(unsigned int row, unsigned int col);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // RESULTTABLE_H
 | 
			
		||||
| 
						 | 
				
			
			@ -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{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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("", "", ""){
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,11 @@ Instruction Recipe::getInstruction() const{
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
QImage Recipe::getImage() const{
 | 
			
		||||
    return this->image;
 | 
			
		||||
	return this->image;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
vector<RecipeTag> 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<RecipeIngredient>::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<RecipeTag>::iterator it = this->tags.begin(); it != this->tags.end(); ++it){
 | 
			
		||||
		RecipeTag t = *it;
 | 
			
		||||
		printf("\t\t%s\n", t.getValue().c_str());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,16 +6,7 @@ MainWindow::MainWindow(QWidget *parent) :
 | 
			
		|||
    ui(new Ui::MainWindow){
 | 
			
		||||
    ui->setupUi(this);
 | 
			
		||||
 | 
			
		||||
    ui->ingredientsListView->setModel(&this->ingredientModel);
 | 
			
		||||
 | 
			
		||||
    //TESTING CODE
 | 
			
		||||
    vector<RecipeIngredient> 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("<b>BOLD</b><i>iTaLiCs</i>"), QImage(), vector<RecipeTag>(), QDate::currentDate(), QTime(0, 30), QTime(0, 25), 10.0f);
 | 
			
		||||
 | 
			
		||||
    this->loadFromRecipe(rec);
 | 
			
		||||
	ui->ingredientsListView->setModel(&this->ingredientModel);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MainWindow::~MainWindow(){
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<<instruction.getHTML().c_str()<<endl;
 | 
			
		||||
		file.close();
 | 
			
		||||
		return true;
 | 
			
		||||
	} else {
 | 
			
		||||
		fprintf(stderr, "Error opening file: %s to write instruction.\n", filename.toStdString().c_str());
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Instruction loadInstruction(int nr){
 | 
			
		||||
	QString filename = appDataPath + QString::fromStdString(std::to_string(nr)) + ".html";
 | 
			
		||||
	QFile file(filename);
 | 
			
		||||
	if (!file.exists()){
 | 
			
		||||
		fprintf(stderr, "Instruction Nr: %d does not exist.\n", nr);
 | 
			
		||||
		return Instruction();
 | 
			
		||||
	}
 | 
			
		||||
	if (file.open(QIODevice::ReadOnly)){
 | 
			
		||||
		QTextStream stream(&file);
 | 
			
		||||
		QString s = stream.readAll();
 | 
			
		||||
		file.close();
 | 
			
		||||
		return Instruction(s.toStdString());
 | 
			
		||||
	} else {
 | 
			
		||||
		fprintf(stderr, "Error opening file: %s to read instruction.\n", filename.toStdString().c_str());
 | 
			
		||||
		return Instruction();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool saveImage(int nr, QImage image){
 | 
			
		||||
	QString filename = appDataPath + QString::fromStdString(std::to_string(nr)) + ".png";
 | 
			
		||||
	QFile file(filename);
 | 
			
		||||
	if (file.open(QIODevice::WriteOnly)){
 | 
			
		||||
		image.save(&file, "PNG");
 | 
			
		||||
		file.close();
 | 
			
		||||
		return true;
 | 
			
		||||
	} else {
 | 
			
		||||
		fprintf(stderr, "Error saving image to file: %s\n", filename.toStdString().c_str());
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QImage loadImage(int nr){
 | 
			
		||||
	QString filename = appDataPath + QString::fromStdString(std::to_string(nr)) + ".png";
 | 
			
		||||
	QImage img(filename);
 | 
			
		||||
	return img;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
#ifndef FILEUTILS_H
 | 
			
		||||
#define FILEUTILS_H
 | 
			
		||||
 | 
			
		||||
#include <QStandardPaths>
 | 
			
		||||
#include <QFile>
 | 
			
		||||
#include <QDir>
 | 
			
		||||
#include <QTextStream>
 | 
			
		||||
#include <QImage>
 | 
			
		||||
 | 
			
		||||
#include "model/recipe/instruction.h"
 | 
			
		||||
 | 
			
		||||
namespace FileUtils{
 | 
			
		||||
 | 
			
		||||
	const QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)+"/.recipeDB/";
 | 
			
		||||
 | 
			
		||||
	void ensureAppDataFolderExists();
 | 
			
		||||
 | 
			
		||||
	bool saveInstruction(int nr, Instruction instruction);
 | 
			
		||||
 | 
			
		||||
	Instruction loadInstruction(int nr);
 | 
			
		||||
 | 
			
		||||
	bool saveImage(int nr, QImage image);
 | 
			
		||||
 | 
			
		||||
	QImage loadImage(int nr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // FILEUTILS_H
 | 
			
		||||
		Loading…
	
		Reference in New Issue