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
|
TEMPLATE = app
|
||||||
|
|
||||||
|
|
||||||
SOURCES += SQLite/sqlite3.c \
|
SOURCES += model/recipe/instruction.cpp \
|
||||||
model/recipe/instruction.cpp \
|
|
||||||
model/recipe/recipe.cpp \
|
model/recipe/recipe.cpp \
|
||||||
userInterface/mainwindow.cpp \
|
userInterface/mainwindow.cpp \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
|
@ -22,11 +21,13 @@ SOURCES += SQLite/sqlite3.c \
|
||||||
model/recipe/ingredients/ingredient.cpp \
|
model/recipe/ingredients/ingredient.cpp \
|
||||||
model/recipe/ingredients/ingredientlistmodel.cpp \
|
model/recipe/ingredients/ingredientlistmodel.cpp \
|
||||||
model/recipe/ingredients/recipeingredient.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 \
|
HEADERS += model/recipe/instruction.h \
|
||||||
SQLite/sqlite3ext.h \
|
|
||||||
model/recipe/instruction.h \
|
|
||||||
model/recipe/recipe.h \
|
model/recipe/recipe.h \
|
||||||
userInterface/mainwindow.h \
|
userInterface/mainwindow.h \
|
||||||
model/database/database.h \
|
model/database/database.h \
|
||||||
|
@ -34,7 +35,14 @@ HEADERS += SQLite/sqlite3.h \
|
||||||
model/recipe/ingredients/ingredient.h \
|
model/recipe/ingredients/ingredient.h \
|
||||||
model/recipe/ingredients/ingredientlistmodel.h \
|
model/recipe/ingredients/ingredientlistmodel.h \
|
||||||
model/recipe/ingredients/recipeingredient.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
|
FORMS += gui/mainwindow.ui
|
||||||
|
|
||||||
|
|
28
main.cpp
28
main.cpp
|
@ -2,6 +2,7 @@
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
||||||
#include "model/database/database.h"
|
#include "model/database/database.h"
|
||||||
|
#include "model/database/recipedatabase.h"
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
@ -9,7 +10,30 @@ int main(int argc, char *argv[])
|
||||||
MainWindow w;
|
MainWindow w;
|
||||||
w.show();
|
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){
|
Database::Database(string filename){
|
||||||
this->filename = filename;
|
this->filename = filename;
|
||||||
openConnection();
|
openConnection();
|
||||||
//TESTING CODE
|
|
||||||
if (tableExists("ingredients")){
|
|
||||||
printf("Ingredients table already exists.\n");
|
|
||||||
} else {
|
|
||||||
printf("Couldn't find the ingredients table.\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Database::~Database(){
|
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(){
|
void Database::openConnection(){
|
||||||
|
@ -28,20 +59,33 @@ void Database::openConnection(){
|
||||||
|
|
||||||
void Database::closeConnection(){
|
void Database::closeConnection(){
|
||||||
this->returnCode = sqlite3_close(this->db);
|
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){
|
bool Database::tableExists(string tableName){
|
||||||
if (this->dbIsOpen){
|
if (tableName.empty() || this->db == NULL || !this->dbIsOpen){
|
||||||
this->sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';";
|
return false;
|
||||||
const char* str = this->sql.c_str();
|
}
|
||||||
this->returnCode = sqlite3_exec(this->db, str, NULL, 0, &this->errorMsg);
|
ResultTable t = this->selectFrom("sqlite_master", "name", "type='table' AND name='"+tableName+"'");
|
||||||
if (this->returnCode == SQLITE_ERROR){
|
//ResultTable t = executeSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='"+tableName+"';");
|
||||||
fprintf(stderr, "Unable to select name from master table list: %s\n", this->errorMsg);
|
return !t.isEmpty();
|
||||||
return false;
|
}
|
||||||
} else {
|
|
||||||
return true;
|
int Database::getLastInsertedRowId(){
|
||||||
}
|
return sqlite3_last_insert_rowid(this->db);
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,35 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
#include "SQLite/sqlite3.h"
|
#include "SQLite/sqlite3.h"
|
||||||
#include "model/recipe/ingredients/ingredient.h"
|
#include "model/recipe/ingredients/ingredient.h"
|
||||||
|
#include "resulttable.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The Database class is responsible for generic abstraction of commonly used database features.
|
||||||
|
*/
|
||||||
|
|
||||||
class Database
|
class Database
|
||||||
{
|
{
|
||||||
public:
|
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(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:
|
private:
|
||||||
//SQL Instance variables.
|
//SQL Instance variables.
|
||||||
|
@ -27,12 +43,8 @@ private:
|
||||||
char* errorMsg;
|
char* errorMsg;
|
||||||
|
|
||||||
void openConnection();
|
void openConnection();
|
||||||
void closeConnection();
|
void closeConnection();
|
||||||
//Guarantees that all tables from the schema exist.
|
std::string combineVector(std::vector<std::string> strings, std::string mid);
|
||||||
void ensureTablesExist();
|
|
||||||
|
|
||||||
//Utility methods.
|
|
||||||
bool tableExists(string tableName);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DATABASE_H
|
#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());
|
setName(i.getName());
|
||||||
setFoodGroup(i.getFoodGroup());
|
setFoodGroup(i.getFoodGroup());
|
||||||
setQuantity(quantity);
|
setQuantity(quantity);
|
||||||
setUnit(unit);
|
setUnit(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
RecipeIngredient::RecipeIngredient(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float RecipeIngredient::getQuantity() const{
|
float RecipeIngredient::getQuantity() const{
|
||||||
|
|
|
@ -19,6 +19,7 @@ public:
|
||||||
RecipeIngredient(string name, string foodGroup, float quantity, UnitOfMeasure unit);
|
RecipeIngredient(string name, string foodGroup, float quantity, UnitOfMeasure unit);
|
||||||
//Constructor using data from a child ingredient.
|
//Constructor using data from a child ingredient.
|
||||||
RecipeIngredient(Ingredient i, float quantity, UnitOfMeasure unit);
|
RecipeIngredient(Ingredient i, float quantity, UnitOfMeasure unit);
|
||||||
|
RecipeIngredient();
|
||||||
|
|
||||||
//Getters
|
//Getters
|
||||||
float getQuantity() const;
|
float getQuantity() const;
|
||||||
|
|
|
@ -3,7 +3,14 @@
|
||||||
UnitOfMeasure::UnitOfMeasure(string name, string plural, string abbreviation){
|
UnitOfMeasure::UnitOfMeasure(string name, string plural, string abbreviation){
|
||||||
this->name = name;
|
this->name = name;
|
||||||
this->plural = plural;
|
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("", "", ""){
|
UnitOfMeasure::UnitOfMeasure() : UnitOfMeasure::UnitOfMeasure("", "", ""){
|
||||||
|
|
|
@ -14,6 +14,8 @@ class UnitOfMeasure
|
||||||
public:
|
public:
|
||||||
//Full constructor.
|
//Full constructor.
|
||||||
UnitOfMeasure(string name, string plural, string abbreviation);
|
UnitOfMeasure(string name, string plural, string abbreviation);
|
||||||
|
//Attempt to guess unit from just a string.
|
||||||
|
UnitOfMeasure(string name);
|
||||||
//Constructor with default values.
|
//Constructor with default values.
|
||||||
UnitOfMeasure();
|
UnitOfMeasure();
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,11 @@ Instruction Recipe::getInstruction() const{
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage Recipe::getImage() const{
|
QImage Recipe::getImage() const{
|
||||||
return this->image;
|
return this->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<RecipeTag> Recipe::getTags() const{
|
||||||
|
return this->tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDate Recipe::getCreatedDate() const{
|
QDate Recipe::getCreatedDate() const{
|
||||||
|
@ -89,5 +93,29 @@ void Recipe::setCookTime(QTime newTime){
|
||||||
}
|
}
|
||||||
|
|
||||||
void Recipe::setServings(float newServingsCount){
|
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 setPrepTime(QTime newTime);
|
||||||
void setCookTime(QTime newTime);
|
void setCookTime(QTime newTime);
|
||||||
void setServings(float newServingsCount);
|
void setServings(float newServingsCount);
|
||||||
|
|
||||||
|
void print();
|
||||||
private:
|
private:
|
||||||
//Main information.
|
//Main information.
|
||||||
string name; //The name of the recipe.
|
string name; //The name of the recipe.
|
||||||
|
|
|
@ -6,16 +6,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
ui(new Ui::MainWindow){
|
ui(new Ui::MainWindow){
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
ui->ingredientsListView->setModel(&this->ingredientModel);
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow(){
|
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