summaryrefslogtreecommitdiffstats
path: root/krecipes/src/backends
diff options
context:
space:
mode:
authorSlávek Banko <slavek.banko@axis.cz>2013-07-31 14:57:12 +0200
committerSlávek Banko <slavek.banko@axis.cz>2013-07-31 14:57:12 +0200
commit1a79d6f5bfce52feb71c264d1b63d78a3da584c4 (patch)
tree740b3faa8a9acff0a524733ce5dc5b036bc53969 /krecipes/src/backends
downloadkrecipes-1a79d6f5bfce52feb71c264d1b63d78a3da584c4.tar.gz
krecipes-1a79d6f5bfce52feb71c264d1b63d78a3da584c4.zip
Initial import of krecipes 1.0-beta2
Diffstat (limited to 'krecipes/src/backends')
-rw-r--r--krecipes/src/backends/Makefile.am42
-rw-r--r--krecipes/src/backends/MySQL/Makefile.am18
-rw-r--r--krecipes/src/backends/MySQL/mysqlrecipedb.cpp554
-rw-r--r--krecipes/src/backends/MySQL/mysqlrecipedb.h56
-rw-r--r--krecipes/src/backends/PostgreSQL/Makefile.am17
-rw-r--r--krecipes/src/backends/PostgreSQL/psqlrecipedb.cpp560
-rw-r--r--krecipes/src/backends/PostgreSQL/psqlrecipedb.h64
-rw-r--r--krecipes/src/backends/SQLite/Makefile.am19
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/Makefile.am17
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.cpp171
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.h59
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.cpp197
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.h142
-rw-r--r--krecipes/src/backends/SQLite/literecipedb.cpp1034
-rw-r--r--krecipes/src/backends/SQLite/literecipedb.h60
-rw-r--r--krecipes/src/backends/SQLite/qsql_sqlite.cpp459
-rw-r--r--krecipes/src/backends/SQLite/qsql_sqlite.h53
-rw-r--r--krecipes/src/backends/progressinterface.cpp79
-rw-r--r--krecipes/src/backends/progressinterface.h70
-rw-r--r--krecipes/src/backends/qsqlrecipedb.cpp2775
-rw-r--r--krecipes/src/backends/qsqlrecipedb.h228
-rw-r--r--krecipes/src/backends/recipedb.cpp989
-rw-r--r--krecipes/src/backends/recipedb.h397
-rw-r--r--krecipes/src/backends/searchparameters.h63
-rw-r--r--krecipes/src/backends/usda_ingredient_data.h486
-rw-r--r--krecipes/src/backends/usda_property_data.h76
-rw-r--r--krecipes/src/backends/usda_unit_data.h104
27 files changed, 8789 insertions, 0 deletions
diff --git a/krecipes/src/backends/Makefile.am b/krecipes/src/backends/Makefile.am
new file mode 100644
index 0000000..9ef190d
--- /dev/null
+++ b/krecipes/src/backends/Makefile.am
@@ -0,0 +1,42 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. $(all_includes)
+
+# Check for optional database libs
+
+
+if link_lib_SQLITE
+sqlite_subdirs=SQLite
+sqlite_libs=SQLite/libkrecsqlite.la
+endif
+
+if link_lib_MYSQL
+mysql_subdirs=MySQL
+mysql_libs=MySQL/libkrecmysql.la
+endif
+
+if link_lib_POSTGRESQL
+psql_subdirs=PostgreSQL
+psql_libs=PostgreSQL/libkrecpsql.la
+endif
+
+# Optional subdirectories
+
+SUBDIRS=$(sqlite_subdirs) $(mysql_subdirs) $(psql_subdirs)
+
+# Instructions for building the convenience library
+noinst_LTLIBRARIES=libkrecipesdbs.la
+libkrecipesdbs_la_SOURCES=recipedb.cpp qsqlrecipedb.cpp progressinterface.cpp
+libkrecipesdbs_la_METASOURCES=AUTO
+
+libkrecipesdbs_la_LIBADD= $(mysql_libs) $(sqlite_libs) $(psql_libs)
+
+#the library search path.
+libkrecipesdbs_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
+#install the following headers
+# include_HEADERS = recipedb.h
diff --git a/krecipes/src/backends/MySQL/Makefile.am b/krecipes/src/backends/MySQL/Makefile.am
new file mode 100644
index 0000000..193496e
--- /dev/null
+++ b/krecipes/src/backends/MySQL/Makefile.am
@@ -0,0 +1,18 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../.. $(all_includes)
+
+
+# Instructions for building the convenience library
+noinst_LTLIBRARIES=libkrecmysql.la
+libkrecmysql_la_SOURCES=mysqlrecipedb.cpp
+libkrecmysql_la_METASOURCES=AUTO
+
+
+#the library search path.
+libkrecmysql_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
diff --git a/krecipes/src/backends/MySQL/mysqlrecipedb.cpp b/krecipes/src/backends/MySQL/mysqlrecipedb.cpp
new file mode 100644
index 0000000..151954e
--- /dev/null
+++ b/krecipes/src/backends/MySQL/mysqlrecipedb.cpp
@@ -0,0 +1,554 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "mysqlrecipedb.h"
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <ktempfile.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kglobal.h>
+
+MySQLRecipeDB::MySQLRecipeDB( const QString &host, const QString &user, const QString &pass, const QString &DBname, int port ) : QSqlRecipeDB( host, user, pass, DBname, port )
+{}
+
+MySQLRecipeDB::~MySQLRecipeDB()
+{}
+
+void MySQLRecipeDB::createDB()
+{
+ QString real_db_name = database->databaseName();
+
+ //we have to be connected to some database in order to create the Krecipes database
+ //so long as the permissions given are allowed access to "mysql', this works
+ database->setDatabaseName( "mysql" );
+ if ( database->open() ) {
+ // Create the Database (Note: needs permissions)
+ //FIXME: I've noticed certain characters cause this to fail (such as '-'). Somehow let the user know.
+ QSqlQuery query( QString( "CREATE DATABASE %1" ).arg( real_db_name ), database );
+ if ( !query.isActive() )
+ kdDebug() << "create query failed: " << database->lastError().databaseText() << endl;
+
+ database->close();
+ }
+ else
+ kdDebug() << "create open failed: " << database->lastError().databaseText() << endl;
+
+ database->setDatabaseName( real_db_name );
+}
+
+QStringList MySQLRecipeDB::backupCommand() const
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup("Server");
+
+ QStringList command;
+ command<<config->readEntry( "MySQLDumpPath", "mysqldump" )<<"-q";
+
+ QString pass = config->readEntry("Password", QString::null);
+ if ( !pass.isEmpty() )
+ command<<"-p"+pass;
+
+ QString user = config->readEntry("Username", QString::null);
+ command<<"-u"+user;
+
+ command<<"-h"+config->readEntry("Host", "localhost");
+
+ int port = config->readNumEntry("Port", 0);
+ if ( port > 0 )
+ command<<"-P"+QString::number(port);
+
+ command<<database->databaseName();
+ return command;
+}
+
+QStringList MySQLRecipeDB::restoreCommand() const
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup("Server");
+
+ QStringList command;
+ command<<config->readEntry( "MySQLPath", "mysql" );
+
+ QString pass = config->readEntry("Password", QString::null);
+ if ( !pass.isEmpty() )
+ command<<"-p"+pass;
+
+ QString user = config->readEntry("Username", QString::null);
+ command<<"-u"+user;
+
+ int port = config->readNumEntry("Port", 0);
+ if ( port > 0 )
+ command<<"-P"+QString::number(port);
+
+ command<<"-h"+config->readEntry("Host", "localhost");
+
+ command<<database->databaseName();
+ return command;
+}
+
+void MySQLRecipeDB::createTable( const QString &tableName )
+{
+
+ QStringList commands;
+
+ if ( tableName == "recipes" )
+ commands << QString( "CREATE TABLE recipes (id INTEGER NOT NULL AUTO_INCREMENT,title VARCHAR(%1), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id int(11) DEFAULT '-1', instructions TEXT, photo BLOB, prep_time TIME, ctime TIMESTAMP, mtime TIMESTAMP, atime TIMESTAMP, PRIMARY KEY (id));" ).arg( maxRecipeTitleLength() );
+
+ else if ( tableName == "ingredients" )
+ commands << QString( "CREATE TABLE ingredients (id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxIngredientNameLength() );
+
+ else if ( tableName == "ingredient_list" )
+ commands << "CREATE TABLE ingredient_list (id INTEGER NOT NULL AUTO_INCREMENT, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, substitute_for INTEGER, PRIMARY KEY(id), INDEX ridil_index(recipe_id), INDEX iidil_index(ingredient_id), INDEX gidil_index(group_id))";
+
+ else if ( tableName == "unit_list" )
+ commands << "CREATE TABLE unit_list (ingredient_id INTEGER, unit_id INTEGER);";
+
+ else if ( tableName == "units" )
+ commands << QString( "CREATE TABLE units (id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(%1), name_abbrev VARCHAR(%2), plural VARCHAR(%3), plural_abbrev VARCHAR(%4), type INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (id));" )
+ .arg( maxUnitNameLength() ).arg( maxUnitNameLength() ).arg( maxUnitNameLength() ).arg( maxUnitNameLength() );
+
+ else if ( tableName == "prep_methods" )
+ commands << QString( "CREATE TABLE prep_methods (id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxPrepMethodNameLength() );
+
+ else if ( tableName == "prep_method_list" )
+ commands << "CREATE TABLE prep_method_list (ingredient_list_id int(11) NOT NULL,prep_method_id int(11) NOT NULL, order_index int(11), INDEX iid_index (ingredient_list_id), INDEX pid_index (prep_method_id));";
+
+ else if ( tableName == "ingredient_info" )
+ commands << "CREATE TABLE ingredient_info (ingredient_id INTEGER, property_id INTEGER, amount FLOAT, per_units INTEGER);";
+
+ else if ( tableName == "ingredient_properties" )
+ commands << "CREATE TABLE ingredient_properties (id INTEGER NOT NULL AUTO_INCREMENT,name VARCHAR(20), units VARCHAR(20), PRIMARY KEY (id));";
+
+ else if ( tableName == "ingredient_weights" )
+ commands << "CREATE TABLE ingredient_weights (id INTEGER NOT NULL AUTO_INCREMENT, ingredient_id INTEGER NOT NULL, amount FLOAT, unit_id INTEGER, weight FLOAT, weight_unit_id INTEGER, prep_method_id INTEGER, PRIMARY KEY (id), INDEX(ingredient_id), INDEX(unit_id), INDEX(weight_unit_id), INDEX(prep_method_id) );";
+
+ else if ( tableName == "units_conversion" )
+ commands << "CREATE TABLE units_conversion (unit1_id INTEGER, unit2_id INTEGER, ratio FLOAT);";
+
+ else if ( tableName == "categories" )
+ commands << QString( "CREATE TABLE categories (id int(11) NOT NULL auto_increment, name varchar(%1) default NULL, parent_id int(11) NOT NULL default -1, PRIMARY KEY (id), INDEX parent_id_index(parent_id));" ).arg( maxCategoryNameLength() );
+
+ else if ( tableName == "category_list" )
+ commands << "CREATE TABLE category_list (recipe_id int(11) NOT NULL,category_id int(11) NOT NULL, INDEX rid_index (recipe_id), INDEX cid_index (category_id));";
+
+ else if ( tableName == "authors" )
+ commands << QString( "CREATE TABLE authors (id int(11) NOT NULL auto_increment, name varchar(%1) default NULL,PRIMARY KEY (id));" ).arg( maxAuthorNameLength() );
+
+ else if ( tableName == "author_list" )
+ commands << "CREATE TABLE author_list (recipe_id int(11) NOT NULL,author_id int(11) NOT NULL);";
+
+ else if ( tableName == "db_info" ) {
+ commands << "CREATE TABLE db_info (ver FLOAT NOT NULL,generated_by varchar(200) default NULL);";
+ commands << QString( "INSERT INTO db_info VALUES(%1,'Krecipes %2');" ).arg( latestDBVersion() ).arg( krecipes_version() );
+ }
+ else if ( tableName == "ingredient_groups" ) {
+ commands << QString( "CREATE TABLE `ingredient_groups` (`id` int(11) NOT NULL auto_increment, `name` varchar(%1), PRIMARY KEY (`id`));" ).arg( maxIngGroupNameLength() );
+ }
+ else if ( tableName == "yield_types" ) {
+ commands << QString( "CREATE TABLE `yield_types` (`id` int(11) NOT NULL auto_increment, `name` varchar(%1), PRIMARY KEY (`id`));" ).arg( 20 );
+ }
+
+ else if ( tableName == "ratings" )
+ commands << "CREATE TABLE ratings (id INTEGER NOT NULL AUTO_INCREMENT, recipe_id int(11) NOT NULL, comment TEXT, rater TEXT, created TIMESTAMP, PRIMARY KEY (id));";
+
+ else if ( tableName == "rating_criteria" )
+ commands << "CREATE TABLE rating_criteria (id INTEGER NOT NULL AUTO_INCREMENT, name TEXT, PRIMARY KEY (id));";
+
+ else if ( tableName == "rating_criterion_list" )
+ commands << "CREATE TABLE rating_criterion_list (rating_id INTEGER NOT NULL, rating_criterion_id INTEGER, stars FLOAT);";
+
+ else
+ return ;
+
+ QSqlQuery databaseToCreate( QString::null, database );
+
+ // execute the queries
+ for ( QStringList::const_iterator it = commands.begin(); it != commands.end(); ++it )
+ databaseToCreate.exec( ( *it ) );
+}
+
+void MySQLRecipeDB::portOldDatabases( float version )
+{
+ kdDebug() << "Current database version is..." << version << "\n";
+ QString command;
+
+ // Note that version no. means the version in which this DB structure
+ // was introduced. To work with SVN users, the database will be incrementally
+ // upgraded for each change made between releases (e.g. 0.81, 0.82,... are
+ // what will become 0.9)
+
+ if ( qRound(version*10) < 3 ) // The database was generated with a version older than v 0.3. First update to 0.3 version
+ {
+
+ // Add new columns to existing tables (creating new tables is not necessary. Integrity check does that before)
+ command = "ALTER TABLE recipes ADD COLUMN persons int(11) AFTER title;";
+ QSqlQuery tableToAlter( command, database );
+
+ // Set the version to the new one (0.3)
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.3,'Krecipes 0.4');"; // Set the new version
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*10) < 4 ) // Upgrade to the current DB version 0.4
+ {
+
+ // Add new columns to existing tables (creating any new tables is not necessary. Integrity check does that before)
+ command = "ALTER TABLE ingredient_list ADD COLUMN order_index int(11) AFTER unit_id;";
+ QSqlQuery tableToAlter( command, database );
+
+ // Missing indexes in the previous versions
+ command = "CREATE index rid_index ON category_list(recipe_id)";
+ tableToAlter.exec( command );
+
+ command = "CREATE index cid_index ON category_list(category_id)";
+ tableToAlter.exec( command );
+
+ command = "CREATE index ridil_index ON ingredient_list(recipe_id)";
+ tableToAlter.exec( command );
+
+ command = "CREATE index iidil_index ON ingredient_list(ingredient_id)";
+ tableToAlter.exec( command );
+
+ // Port data
+
+ //*1:: Recipes have always category -1 to speed up searches (no JOINs needed)
+ command = "SELECT r.id FROM recipes r;"; // Find all recipes
+ QSqlQuery categoryToAdd( QString::null, database );
+ tableToAlter.exec( command );
+ if ( tableToAlter.isActive() )
+ {
+ while ( tableToAlter.next() ) {
+ int recipeId = tableToAlter.value( 0 ).toInt();
+ QString cCommand = QString( "INSERT INTO category_list VALUES (%1,-1);" ).arg( recipeId );
+ categoryToAdd.exec( cCommand );
+
+ emit progress();
+ }
+ }
+
+ // Set the version to the new one (0.4)
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.4,'Krecipes 0.4');"; // Set the new version
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*10) < 5 ) {
+ command = QString( "CREATE TABLE prep_methods (id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxPrepMethodNameLength() );
+ QSqlQuery tableToAlter( command, database );
+
+ command = "ALTER TABLE ingredient_list ADD COLUMN prep_method_id int(11) AFTER unit_id;";
+ tableToAlter.exec( command );
+ command = "UPDATE ingredient_list SET prep_method_id=-1 WHERE prep_method_id IS NULL;";
+ tableToAlter.exec( command );
+
+ command = "ALTER TABLE authors MODIFY name VARCHAR(50);";
+ tableToAlter.exec( command );
+ command = "ALTER TABLE categories MODIFY name VARCHAR(40);";
+ tableToAlter.exec( command );
+
+ // Set the version to the new one (0.5)
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.5,'Krecipes 0.5');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*10) < 6 ) {
+ command = "ALTER TABLE categories ADD COLUMN parent_id int(11) NOT NULL default '-1' AFTER name;";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.6,'Krecipes 0.6');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 61 ) {
+ QString command = "ALTER TABLE `recipes` ADD COLUMN `prep_time` TIME DEFAULT NULL";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.61,'Krecipes 0.6');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 62 ) {
+ QString command = "ALTER TABLE `ingredient_list` ADD COLUMN `group_id` int(11) default '-1' AFTER order_index;";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.62,'Krecipes 0.7');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 63 ) {
+ QString command = "ALTER TABLE `units` ADD COLUMN `plural` varchar(20) DEFAULT NULL AFTER name;";
+ QSqlQuery tableToAlter( command, database );
+
+ QSqlQuery result( "SELECT id,name FROM units WHERE plural IS NULL", database );
+ if ( result.isActive() ) {
+ while ( result.next() ) {
+ command = "UPDATE units SET plural='" + result.value( 1 ).toString() + "' WHERE id=" + QString::number( result.value( 0 ).toInt() );
+ QSqlQuery query( command, database );
+
+ emit progress();
+ }
+ }
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.63,'Krecipes 0.7');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*10) < 7 ) { //simply call 0.63 -> 0.7
+ QString command = "UPDATE db_info SET ver='0.7';";
+ QSqlQuery query( command, database );
+ }
+
+ if ( qRound(version*100) < 81 ) {
+ QString command = "ALTER TABLE `ingredient_list` ADD COLUMN `amount_offset` FLOAT DEFAULT '0' AFTER amount;";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "UPDATE db_info SET ver='0.81',generated_by='Krecipes SVN (20050816)';";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 82 ) {
+ QString command = "ALTER TABLE `recipes` ADD COLUMN `yield_amount` FLOAT DEFAULT '0' AFTER persons;";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "ALTER TABLE `recipes` ADD COLUMN `yield_amount_offset` FLOAT DEFAULT '0' AFTER yield_amount;";
+ tableToAlter.exec(command);
+
+ command = "ALTER TABLE `recipes` ADD COLUMN `yield_type_id` INTEGER DEFAULT '-1' AFTER yield_amount_offset;";
+ tableToAlter.exec(command);
+
+ QSqlQuery result( "SELECT id,persons FROM recipes", database );
+ if ( result.isActive() ) {
+ while ( result.next() ) {
+ command = "UPDATE recipes SET yield_amount='" + QString::number( result.value( 1 ).toInt() ) + "' WHERE id=" + QString::number( result.value( 0 ).toInt() );
+ QSqlQuery query( command, database );
+
+ emit progress();
+ }
+ }
+
+ command = "ALTER TABLE `recipes` DROP COLUMN `persons`;";
+ tableToAlter.exec( command );
+
+ command = "UPDATE db_info SET ver='0.82',generated_by='Krecipes SVN (20050902)';";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 83 ) {
+ database->transaction();
+
+ //====add a id columns to 'ingredient_list' to identify it for the prep method list
+ database->exec( "RENAME TABLE ingredient_list TO ingredient_list_copy;" );
+ database->exec( "CREATE TABLE ingredient_list (id INTEGER NOT NULL AUTO_INCREMENT, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, PRIMARY KEY(id), INDEX ridil_index(recipe_id), INDEX iidil_index(ingredient_id));" );
+
+ QSqlQuery copyQuery = database->exec( "SELECT recipe_id,ingredient_id,amount,amount_offset,unit_id,prep_method_id,order_index,group_id FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO ingredient_list VALUES (NULL, ?, ?, ?, ?, ?, ?, ?)" );
+ query.addBindValue( copyQuery.value( 0 ) );
+ query.addBindValue( copyQuery.value( 1 ) );
+ query.addBindValue( copyQuery.value( 2 ) );
+ query.addBindValue( copyQuery.value( 3 ) );
+ query.addBindValue( copyQuery.value( 4 ) );
+ query.addBindValue( copyQuery.value( 6 ) );
+ query.addBindValue( copyQuery.value( 7 ) );
+ query.exec();
+
+ int prep_method_id = copyQuery.value( 5 ).toInt();
+ if ( prep_method_id != -1 ) {
+ query.prepare( "INSERT INTO prep_method_list VALUES (?, ?, ?);" );
+ query.addBindValue( lastInsertID() );
+ query.addBindValue( prep_method_id );
+ query.addBindValue( 1 );
+ query.exec();
+ }
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ database->exec( "UPDATE db_info SET ver='0.83',generated_by='Krecipes SVN (20050909)';" );
+
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.83 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 84 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE recipes ADD COLUMN ctime TIMESTAMP;" );
+ database->exec( "ALTER TABLE recipes ADD COLUMN mtime TIMESTAMP;" );
+ database->exec( "ALTER TABLE recipes ADD COLUMN atime TIMESTAMP;" );
+
+ database->exec( "UPDATE recipes SET ctime=CURRENT_TIMESTAMP, mtime=CURRENT_TIMESTAMP, atime=CURRENT_TIMESTAMP;" );
+
+ database->exec( "UPDATE db_info SET ver='0.84',generated_by='Krecipes SVN (20050913)';" );
+
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.84 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 85 ) {
+ database->transaction();
+
+ QSqlQuery query( "SELECT id,photo FROM recipes", database );
+
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ storePhoto( query.value(0).toInt(), query.value(1).toByteArray() );
+
+ emit progress();
+ }
+ }
+
+
+ database->exec( "UPDATE db_info SET ver='0.85',generated_by='Krecipes SVN (20050926)';" );
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.85 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 86 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE ingredient_list ADD INDEX (group_id)" );
+
+ QSqlQuery query( "SELECT id,name FROM ingredient_groups ORDER BY name", database );
+
+ QString last;
+ int lastID;
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QString name = query.value(1).toString();
+ int id = query.value(0).toInt();
+ if ( last == name ) {
+ QString command = QString("UPDATE ingredient_list SET group_id=%1 WHERE group_id=%2").arg(lastID).arg(id);
+ database->exec(command);
+
+ command = QString("DELETE FROM ingredient_groups WHERE id=%1").arg(id);
+ database->exec(command);
+ }
+ last = name;
+ lastID = id;
+
+ emit progress();
+ }
+ }
+
+ database->exec( "UPDATE db_info SET ver='0.86',generated_by='Krecipes SVN (20050928)';" );
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.86 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 87 ) {
+ //Load this default data so the user knows what rating criteria is
+ database->exec( QString("INSERT INTO rating_criteria VALUES (1,'%1')").arg(i18n("Overall")) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (2,'%1')").arg(i18n("Taste") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (3,'%1')").arg(i18n("Appearance") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (4,'%1')").arg(i18n("Originality") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (5,'%1')").arg(i18n("Ease of Preparation") ) );
+
+ database->exec( "UPDATE db_info SET ver='0.87',generated_by='Krecipes SVN (20051014)'" );
+ }
+
+ if ( qRound(version*100) < 90 ) {
+ database->exec("UPDATE db_info SET ver='0.9',generated_by='Krecipes 0.9'");
+ }
+
+ if ( qRound(version*100) < 91 ) {
+ database->exec("CREATE index parent_id_index ON categories(parent_id)");
+ database->exec("UPDATE db_info SET ver='0.91',generated_by='Krecipes SVN (20060526)'");
+ }
+
+ if ( qRound(version*100) < 92 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE units ADD COLUMN name_abbrev VARCHAR(20) AFTER name");
+ database->exec( "ALTER TABLE units ADD COLUMN plural_abbrev VARCHAR(20) AFTER plural");
+
+ database->exec("UPDATE db_info SET ver='0.92',generated_by='Krecipes SVN (20060609)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.92 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 93 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE ingredient_list ADD COLUMN substitute_for INTEGER AFTER group_id");
+
+ database->exec("UPDATE db_info SET ver='0.93',generated_by='Krecipes SVN (20060615)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.93 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 94 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE units ADD COLUMN type INTEGER NOT NULL DEFAULT 0 AFTER plural_abbrev");
+
+ database->exec("UPDATE db_info SET ver='0.94',generated_by='Krecipes SVN (20060712)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.94 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 95 ) {
+ database->exec( "DROP TABLE ingredient_weights" );
+ createTable( "ingredient_weights" );
+ database->exec( "UPDATE db_info SET ver='0.95',generated_by='Krecipes SVN (20060726)'" );
+ }
+}
+
+int MySQLRecipeDB::lastInsertID()
+{
+ QSqlQuery lastInsertID( "SELECT LAST_INSERT_ID();", database );
+
+ int id = -1;
+ if ( lastInsertID.isActive() && lastInsertID.next() )
+ id = lastInsertID.value( 0 ).toInt();
+
+ return id;
+}
+
+void MySQLRecipeDB::givePermissions( const QString &dbName, const QString &username, const QString &password, const QString &clientHost )
+{
+ QString command;
+
+ if ( !password.isEmpty() )
+ command = QString( "GRANT ALL ON %1.* TO '%2'@'%3' IDENTIFIED BY '%4';" ).arg( dbName ).arg( username ).arg( clientHost ).arg( password );
+ else
+ command = QString( "GRANT ALL ON %1.* TO '%2'@'%3';" ).arg( dbName ).arg( username ).arg( clientHost );
+
+ kdDebug() << "I'm doing the query to setup permissions\n";
+
+ QSqlQuery permissionsToSet( command, database );
+}
+
+#include "mysqlrecipedb.moc"
diff --git a/krecipes/src/backends/MySQL/mysqlrecipedb.h b/krecipes/src/backends/MySQL/mysqlrecipedb.h
new file mode 100644
index 0000000..01ac5a4
--- /dev/null
+++ b/krecipes/src/backends/MySQL/mysqlrecipedb.h
@@ -0,0 +1,56 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+
+#ifndef MYSQLRECIPEDB_H
+#define MYSQLRECIPEDB_H
+
+#define MYSQL_DRIVER "QMYSQL3"
+
+#include "qsqlrecipedb.h"
+
+/**
+@author Unai Garro
+*/
+class MySQLRecipeDB : public QSqlRecipeDB
+{
+
+ Q_OBJECT
+
+private:
+ void createDB( void );
+
+public:
+ MySQLRecipeDB( const QString &host, const QString &user = QString::null, const QString &pass = QString::null, const QString &DBName = DEFAULT_DB_NAME, int port = 0 );
+ ~MySQLRecipeDB( void );
+
+ int lastInsertID();
+
+ void createTable( const QString &tableName );
+ void givePermissions( const QString &dbName, const QString &username, const QString &password = QString::null, const QString &clientHost = "localhost" );
+
+protected:
+ QString qsqlDriverPlugin() const
+ {
+ return MYSQL_DRIVER;
+ }
+
+private:
+ void portOldDatabases( float version );
+ QStringList backupCommand() const;
+ QStringList restoreCommand() const;
+};
+
+
+
+
+#endif
diff --git a/krecipes/src/backends/PostgreSQL/Makefile.am b/krecipes/src/backends/PostgreSQL/Makefile.am
new file mode 100644
index 0000000..9295a6f
--- /dev/null
+++ b/krecipes/src/backends/PostgreSQL/Makefile.am
@@ -0,0 +1,17 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../.. $(all_includes)
+
+
+# Instructions for building the convenience library
+noinst_LTLIBRARIES=libkrecpsql.la
+libkrecpsql_la_SOURCES=psqlrecipedb.cpp
+libkrecpsql_la_METASOURCES=AUTO
+
+
+#the library search path.
+libkrecpsql_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
diff --git a/krecipes/src/backends/PostgreSQL/psqlrecipedb.cpp b/krecipes/src/backends/PostgreSQL/psqlrecipedb.cpp
new file mode 100644
index 0000000..c3a94e4
--- /dev/null
+++ b/krecipes/src/backends/PostgreSQL/psqlrecipedb.cpp
@@ -0,0 +1,560 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "psqlrecipedb.h"
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <ktempfile.h>
+#include <klocale.h>
+#include <kconfig.h>
+
+#include <qvariant.h>
+
+//Note: PostgreSQL's database names are always lowercase
+PSqlRecipeDB::PSqlRecipeDB( const QString& host, const QString& user, const QString& pass, const QString& DBname, int port ) : QSqlRecipeDB( host, user, pass, DBname.lower(), port )
+{}
+
+PSqlRecipeDB::~PSqlRecipeDB()
+{}
+
+void PSqlRecipeDB::createDB()
+{
+ QString real_db_name = database->databaseName();
+
+ //we have to be connected to some database in order to create the Krecipes database
+ //so long as the permissions given are allowed access to "template1', this works
+ database->setDatabaseName( "template1" );
+ if ( database->open() ) {
+ QSqlQuery query( QString( "CREATE DATABASE %1" ).arg( real_db_name ), database );
+ if ( !query.isActive() )
+ kdDebug() << "create query failed: " << database->lastError().databaseText() << endl;
+
+ database->close();
+ }
+ else
+ kdDebug() << "create open failed: " << database->lastError().databaseText() << endl;
+
+ database->setDatabaseName( real_db_name );
+}
+
+QStringList PSqlRecipeDB::backupCommand() const
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup("Server");
+
+ QStringList command;
+ command<<config->readEntry( "PgDumpPath", "pg_dump" )<<"-d"<<database->databaseName()
+ <<"-U"<<config->readEntry( "Username" );
+
+ int port = config->readNumEntry( "Port", 0 );
+ if ( port > 0 )
+ command<<"-p"<<QString::number(port);
+
+ return command;
+}
+
+QStringList PSqlRecipeDB::restoreCommand() const
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup("Server");
+
+ QStringList command;
+ command<<config->readEntry( "PsqlPath", "psql" )<<database->databaseName()
+ <<"-U"<<config->readEntry( "Username" );
+
+ int port = config->readNumEntry( "Port", 0 );
+ if ( port > 0 )
+ command<<"-p"<<QString::number(port);
+
+ return command;
+}
+
+void PSqlRecipeDB::createTable( const QString &tableName )
+{
+
+ QStringList commands;
+
+ if ( tableName == "recipes" )
+ commands << "CREATE TABLE recipes (id SERIAL NOT NULL PRIMARY KEY,title CHARACTER VARYING, yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER DEFAULT '-1', instructions TEXT, photo TEXT, prep_time TIME, ctime TIMESTAMP, mtime TIMESTAMP, atime TIMESTAMP );";
+
+ else if ( tableName == "ingredients" )
+ commands << "CREATE TABLE ingredients (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+
+ else if ( tableName == "ingredient_list" ) {
+ commands << "CREATE TABLE ingredient_list (id SERIAL NOT NULL PRIMARY KEY, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, substitute_for INTEGER);";
+ commands << "CREATE INDEX ridil_index ON ingredient_list USING BTREE (recipe_id);";
+ commands << "CREATE INDEX iidil_index ON ingredient_list USING BTREE (ingredient_id);";
+ commands << "CREATE INDEX gidil_index ON ingredient_list USING BTREE (group_id);";
+ }
+
+ else if ( tableName == "unit_list" )
+ commands << "CREATE TABLE unit_list (ingredient_id INTEGER, unit_id INTEGER);";
+
+ else if ( tableName == "units" )
+ commands << "CREATE TABLE units (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING, name_abbrev CHARACTER VARYING, plural CHARACTER VARYING, plural_abbrev CHARACTER VARYING, type INTEGER NOT NULL DEFAULT '0' );";
+
+ else if ( tableName == "prep_methods" )
+ commands << "CREATE TABLE prep_methods (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+ else if ( tableName == "prep_method_list" ) {
+ commands << "CREATE TABLE prep_method_list (ingredient_list_id INTEGER NOT NULL,prep_method_id INTEGER NOT NULL, order_index INTEGER);";
+ commands << "CREATE INDEX iid_index ON prep_method_list USING BTREE (ingredient_list_id);";
+ commands << "CREATE INDEX pid_index ON prep_method_list USING BTREE (prep_method_id);";
+ }
+ else if ( tableName == "ingredient_info" )
+ commands << "CREATE TABLE ingredient_info (ingredient_id INTEGER, property_id INTEGER, amount FLOAT, per_units INTEGER);";
+
+ else if ( tableName == "ingredient_properties" )
+ commands << "CREATE TABLE ingredient_properties (id SERIAL NOT NULL,name CHARACTER VARYING, units CHARACTER VARYING);";
+
+ else if ( tableName == "ingredient_weights" ) {
+ commands << "CREATE TABLE ingredient_weights (id SERIAL NOT NULL PRIMARY KEY, ingredient_id INTEGER NOT NULL, amount FLOAT, unit_id INTEGER, weight FLOAT, weight_unit_id INTEGER, prep_method_id INTEGER );"
+ << "CREATE INDEX weight_wid_index ON ingredient_weights USING BTREE (weight_unit_id)"
+ << "CREATE INDEX weight_pid_index ON ingredient_weights USING BTREE (prep_method_id)"
+ << "CREATE INDEX weight_uid_index ON ingredient_weights USING BTREE (unit_id)"
+ << "CREATE INDEX weight_iid_index ON ingredient_weights USING BTREE (ingredient_id)";
+ }
+
+ else if ( tableName == "units_conversion" )
+ commands << "CREATE TABLE units_conversion (unit1_id INTEGER, unit2_id INTEGER, ratio FLOAT);";
+
+ else if ( tableName == "categories" ) {
+ commands << "CREATE TABLE categories (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING default NULL, parent_id INTEGER NOT NULL default -1);";
+ commands << "CREATE index parent_id_index ON categories USING BTREE(parent_id);";
+ }
+ else if ( tableName == "category_list" ) {
+ commands << "CREATE TABLE category_list (recipe_id INTEGER NOT NULL,category_id INTEGER NOT NULL);";
+ commands << "CREATE INDEX rid_index ON category_list USING BTREE (recipe_id);";
+ commands << "CREATE INDEX cid_index ON category_list USING BTREE (category_id);";
+ }
+
+ else if ( tableName == "authors" )
+ commands << "CREATE TABLE authors (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING default NULL);";
+
+ else if ( tableName == "author_list" )
+ commands << "CREATE TABLE author_list (recipe_id INTEGER NOT NULL,author_id INTEGER NOT NULL);";
+
+ else if ( tableName == "db_info" ) {
+ commands << "CREATE TABLE db_info (ver FLOAT NOT NULL,generated_by CHARACTER VARYING default NULL);";
+ commands << QString( "INSERT INTO db_info VALUES(%1,'Krecipes %2');" ).arg( latestDBVersion() ).arg( krecipes_version() );
+ }
+ else if ( tableName == "ingredient_groups" ) {
+ commands << "CREATE TABLE ingredient_groups (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+ }
+ else if ( tableName == "yield_types" ) {
+ commands << "CREATE TABLE yield_types (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+ }
+
+ else if ( tableName == "ratings" )
+ commands << "CREATE TABLE ratings (id SERIAL NOT NULL PRIMARY KEY, recipe_id INTEGER NOT NULL, comment CHARACTER VARYING, rater CHARACTER VARYING, created TIMESTAMP);";
+
+ else if ( tableName == "rating_criteria" )
+ commands << "CREATE TABLE rating_criteria (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+
+ else if ( tableName == "rating_criterion_list" )
+ commands << "CREATE TABLE rating_criterion_list (rating_id INTEGER NOT NULL, rating_criterion_id INTEGER, stars FLOAT);";
+ else
+ return ;
+
+ QSqlQuery databaseToCreate( QString::null, database );
+
+ // execute the queries
+ for ( QStringList::const_iterator it = commands.begin(); it != commands.end(); ++it )
+ databaseToCreate.exec( *it );
+}
+
+void PSqlRecipeDB::initializeData()
+{
+ QSqlRecipeDB::initializeData();
+
+ QSqlQuery updateSeq( "SELECT setval('units_id_seq',(SELECT COUNT(1) FROM units))", database );
+ updateSeq.exec( "SELECT setval('categories_id_seq',(SELECT COUNT(1) FROM categories))" );
+}
+
+void PSqlRecipeDB::portOldDatabases( float version )
+{
+ kdDebug() << "Current database version is..." << version << "\n";
+ QString command;
+
+ if ( qRound(version*10) < 7 ) {
+ //version added
+ }
+
+ if ( qRound(version*100) < 81 ) {
+ database->transaction();
+
+ addColumn("CREATE TABLE %1 (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, %2 unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER, group_id INTEGER);","amount_offset FLOAT","'0'","ingredient_list",3);
+
+ QSqlQuery query(QString::null,database);
+ query.exec( "CREATE INDEX ridil_index ON ingredient_list USING BTREE (recipe_id);" );
+ query.exec( "CREATE INDEX iidil_index ON ingredient_list USING BTREE (ingredient_id);");
+
+ query.exec( "UPDATE db_info SET ver='0.81',generated_by='Krecipes SVN (20050816)';" );
+
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.81 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 82 ) {
+ database->transaction();
+
+ //==================add a columns to 'recipes' to allow yield range + yield type
+ database->exec( "CREATE TABLE recipes_copy (id SERIAL NOT NULL PRIMARY KEY,title CHARACTER VARYING, persons INTEGER, instructions TEXT, photo TEXT, prep_time TIME);" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,title,persons,instructions,photo,prep_time FROM recipes;" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO recipes_copy VALUES (?, ?, ?, ?, ?, ?)" );
+ query.addBindValue( copyQuery.value( 0 ) );
+ query.addBindValue( copyQuery.value( 1 ) );
+ query.addBindValue( copyQuery.value( 2 ) );
+ query.addBindValue( copyQuery.value( 3 ) );
+ query.addBindValue( copyQuery.value( 4 ) );
+ query.addBindValue( copyQuery.value( 5 ) );
+ query.exec();
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes" );
+ database->exec( "CREATE TABLE recipes (id SERIAL NOT NULL PRIMARY KEY,title CHARACTER VARYING, yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER DEFAULT '-1', instructions TEXT, photo TEXT, prep_time TIME);" );
+ copyQuery = database->exec( "SELECT id,title,persons,instructions,photo,prep_time FROM recipes_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO recipes VALUES (?, ?, ?, ?, ?, ?, ?, ?)" );
+ query.addBindValue( copyQuery.value( 0 ) ); //id
+ query.addBindValue( copyQuery.value( 1 ) ); //title
+ query.addBindValue( copyQuery.value( 2 ) ); //persons, now yield_amount
+ query.addBindValue( 0 ); //yield_amount_offset
+ query.addBindValue( -1 ); //yield_type_id
+ query.addBindValue( copyQuery.value( 3 ) ); //instructions
+ query.addBindValue( copyQuery.value( 4 ) ); //photo
+ query.addBindValue( copyQuery.value( 5 ) ); //prep_time
+ query.exec();
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes_copy" );
+
+ database->exec( "UPDATE db_info SET ver='0.82',generated_by='Krecipes SVN (20050902)';" );
+
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.82 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 83 ) {
+ database->transaction();
+
+ //====add a id columns to 'ingredient_list' to identify it for the prep method list
+ database->exec( "ALTER TABLE ingredient_list RENAME TO ingredient_list_copy;" );
+
+ database->exec( "CREATE TABLE ingredient_list (id SERIAL NOT NULL PRIMARY KEY, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER);" );
+
+ QSqlQuery copyQuery = database->exec( "SELECT recipe_id,ingredient_id,amount,amount_offset,unit_id,prep_method_id,order_index,group_id FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ int ing_list_id = getNextInsertID("ingredient_list","id");
+
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO ingredient_list VALUES (?, ?, ?, ?, ?, ?, ?, ?)" );
+ query.addBindValue( ing_list_id );
+ query.addBindValue( copyQuery.value( 0 ) );
+ query.addBindValue( copyQuery.value( 1 ) );
+ query.addBindValue( copyQuery.value( 2 ) );
+ query.addBindValue( copyQuery.value( 3 ) );
+ query.addBindValue( copyQuery.value( 4 ) );
+ query.addBindValue( copyQuery.value( 6 ) );
+ query.addBindValue( copyQuery.value( 7 ) );
+ query.exec();
+
+ int prep_method_id = copyQuery.value( 5 ).toInt();
+ if ( prep_method_id != -1 ) {
+ query.prepare( "INSERT INTO prep_method_list VALUES (?, ?, ?);" );
+ query.addBindValue( ing_list_id );
+ query.addBindValue( prep_method_id );
+ query.addBindValue( 1 );
+ query.exec();
+ }
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ database->exec( "CREATE INDEX ridil_index ON ingredient_list USING BTREE (recipe_id);" );
+ database->exec( "CREATE INDEX iidil_index ON ingredient_list USING BTREE (ingredient_id);" );
+
+ database->exec( "UPDATE db_info SET ver='0.83',generated_by='Krecipes SVN (20050909)';" );
+
+ if ( !database->commit() ) {
+ kdDebug()<<"Update to 0.83 failed. Maybe you should try again."<<endl;
+ return;
+ }
+ }
+
+ if ( qRound(version*100) < 84 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE recipes ADD COLUMN ctime TIMESTAMP" );
+ database->exec( "ALTER TABLE recipes ADD COLUMN mtime TIMESTAMP" );
+ database->exec( "ALTER TABLE recipes ADD COLUMN atime TIMESTAMP" );
+
+ database->exec( "UPDATE recipes SET ctime=CURRENT_TIMESTAMP, mtime=CURRENT_TIMESTAMP, atime=CURRENT_TIMESTAMP;" );
+
+ database->exec( "UPDATE db_info SET ver='0.84',generated_by='Krecipes SVN (20050913)';" );
+
+ if ( !database->commit() ) {
+ kdDebug()<<"Update to 0.84 failed. Maybe you should try again."<<endl;
+ return;
+ }
+ }
+
+ if ( qRound(version*100) < 85 ) { //this change altered the photo format, but this backend already used the newer format
+ database->transaction();
+
+ database->exec( "UPDATE db_info SET ver='0.85',generated_by='Krecipes SVN (20050926)';" );
+
+ if ( !database->commit() ) {
+ kdDebug()<<"Update to 0.85 failed. Maybe you should try again."<<endl;
+ return;
+ }
+ }
+
+ if ( qRound(version*100) < 86 ) {
+ database->transaction();
+
+ database->exec( "CREATE INDEX gidil_index ON ingredient_list USING BTREE (group_id);" );
+
+ QSqlQuery query( "SELECT id,name FROM ingredient_groups ORDER BY name", database );
+
+ QString last;
+ int lastID;
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QString name = query.value(1).toString();
+ int id = query.value(0).toInt();
+ if ( last == name ) {
+ QString command = QString("UPDATE ingredient_list SET group_id=%1 WHERE group_id=%2").arg(lastID).arg(id);
+ database->exec(command);
+
+ command = QString("DELETE FROM ingredient_groups WHERE id=%1").arg(id);
+ database->exec(command);
+ }
+ last = name;
+ lastID = id;
+
+ emit progress();
+ }
+ }
+
+ database->exec( "UPDATE db_info SET ver='0.86',generated_by='Krecipes SVN (20050928)';" );
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.86 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 87 ) {
+ //Load this default data so the user knows what rating criteria is
+ database->exec( QString("INSERT INTO rating_criteria VALUES (1,'%1')").arg(i18n("Overall")) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (2,'%1')").arg(i18n("Taste") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (3,'%1')").arg(i18n("Appearance") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (4,'%1')").arg(i18n("Originality") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (5,'%1')").arg(i18n("Ease of Preparation") ) );
+
+ database->exec( "UPDATE db_info SET ver='0.87',generated_by='Krecipes SVN (20051014)'" );
+ }
+
+ if ( qRound(version*100) < 90 ) {
+ database->exec("UPDATE db_info SET ver='0.9',generated_by='Krecipes 0.9'");
+ }
+
+ if ( qRound(version*100) < 91 ) {
+ database->exec("CREATE index parent_id_index ON categories USING BTREE(parent_id)");
+ database->exec("UPDATE db_info SET ver='0.91',generated_by='Krecipes SVN (20060526)'");
+ }
+
+ if ( qRound(version*100) < 92 ) {
+ database->transaction();
+
+ //==================add a columns to 'units' to allow unit abbreviations
+ database->exec( "ALTER TABLE units RENAME TO units_copy" );
+
+ int nextval = -1;
+ QSqlQuery getID( "SELECT nextval('units_id_seq')", database );
+ if ( getID.isActive() && getID.first() )
+ nextval = getID.value( 0 ).toInt();
+ if ( nextval == -1 )
+ kdDebug() << "Database update failed! Unable to update units sequence." << endl;
+
+ database->exec( "CREATE TABLE units (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING, name_abbrev CHARACTER VARYING, plural CHARACTER VARYING, plural_abbrev CHARACTER VARYING )" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,name,plural FROM units_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO units VALUES(?, ?, ?, ?, ?)" );
+ query.addBindValue( copyQuery.value( 0 ) );
+ query.addBindValue( copyQuery.value( 1 ) );
+ query.addBindValue( QVariant() );
+ query.addBindValue( copyQuery.value( 2 ) );
+ query.addBindValue( QVariant() );
+ query.exec();
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units_copy" );
+
+ database->exec( "ALTER TABLE units_id_seq1 RENAME TO units_id_seq" );
+ database->exec( "ALTER SEQUENCE units_id_seq RESTART WITH "+QString::number(nextval) );
+
+ database->exec("UPDATE db_info SET ver='0.92',generated_by='Krecipes SVN (20060609)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.92 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 93 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE ingredient_list ADD COLUMN substitute_for INTEGER" );
+
+ database->exec("UPDATE db_info SET ver='0.93',generated_by='Krecipes SVN (20060616)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.93 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 94 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE units ADD COLUMN type INTEGER NOT NULL DEFAULT '0'" );
+
+ database->exec("UPDATE db_info SET ver='0.94',generated_by='Krecipes SVN (20060712)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.94 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 95 ) {
+ database->exec( "DROP TABLE ingredient_weights" );
+ createTable( "ingredient_weights" );
+ database->exec( "UPDATE db_info SET ver='0.95',generated_by='Krecipes SVN (20060726)'" );
+ }
+}
+
+void PSqlRecipeDB::addColumn( const QString &new_table_sql, const QString &new_col_info, const QString &default_value, const QString &table_name, int col_index )
+{
+ QString command;
+
+ command = QString(new_table_sql).arg(table_name+"_copy").arg(QString::null);
+ kdDebug()<<"calling: "<<command<<endl;
+ QSqlQuery query( command, database );
+
+ command = "SELECT * FROM "+table_name+";";
+ query.exec( command );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QStringList dataList;
+ for ( int i = 0 ;; ++i ) {
+ QVariant variant = query.value(i);
+ if ( variant.type() == QVariant::Invalid ) break;
+
+ dataList << "'"+variant.toString()+"'";
+ }
+ command = "INSERT INTO "+table_name+"_copy VALUES("+dataList.join(",")+");";
+ kdDebug()<<"calling: "<<command<<endl;
+ QSqlQuery insert_query( command, database );
+
+ emit progress();
+ }
+ }
+ query.exec( "DROP TABLE "+table_name+";" );
+ query.exec( QString(new_table_sql).arg(table_name).arg(new_col_info+",") );
+ query.exec( "SELECT * FROM "+table_name+"_copy;" );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QStringList dataList;
+ for ( int i = 0 ;; ++i ) {
+ if ( i == col_index )
+ dataList << default_value;
+
+ QVariant variant = query.value(i);
+ if ( variant.type() == QVariant::Invalid ) break;
+
+ dataList << "'"+variant.toString()+"'";
+ }
+ command = "INSERT INTO "+table_name+" VALUES(" +dataList.join(",")+");";
+ QSqlQuery insert_query( command, database );
+ kdDebug()<<"calling: "<<command<<endl;
+
+ emit progress();
+ }
+ }
+ query.exec( "DROP TABLE "+table_name+"_copy;" );
+}
+
+int PSqlRecipeDB::lastInsertID()
+{
+ return last_insert_id;
+}
+
+int PSqlRecipeDB::getNextInsertID( const QString &table, const QString &column )
+{
+ QString command = QString( "SELECT nextval('%1_%2_seq');" ).arg( table ).arg( column );
+ QSqlQuery getID( command, database );
+
+ if ( getID.isActive() && getID.first() ) {
+ last_insert_id = getID.value( 0 ).toInt();
+ }
+ else
+ last_insert_id = -1;
+
+ return last_insert_id;
+}
+
+void PSqlRecipeDB::givePermissions( const QString & /*dbName*/, const QString &username, const QString &password, const QString & /*clientHost*/ )
+{
+ QStringList tables;
+ tables << "ingredient_info" << "ingredient_list" << "ingredient_properties" << "ingredients" << "recipes" << "unit_list" << "units" << "units_conversion" << "categories" << "category_list" << "authors" << "author_list" << "prep_methods" << "db_info" << "ingredient_groups" << "ingredient_weights" << "prep_method_list" << "yield_types" << "ratings" << "rating_criteria" << "rating_criterion_list";
+
+ //we also have to grant permissions on the sequences created
+ tables << "authors_id_seq" << "categories_id_seq" << "ingredient_properties_id_seq" << "ingredient_weights_id_seq" << "ingredients_id_seq" << "prep_methods_id_seq" << "recipes_id_seq" << "units_id_seq" << "ingredient_groups_id_seq" << "yield_types_id_seq" << "ingredient_list_id_seq" << "ratings_id_seq" << "rating_criteria_id_seq";
+
+ QString command;
+
+ kdDebug() << "I'm doing the query to create the new user" << endl;
+ command = "CREATE USER " + username;
+ if ( !password.isEmpty() )
+ command.append( "WITH PASSWORD '" + password + "'" );
+ command.append( ";" );
+ QSqlQuery permissionsToSet( command, database );
+
+ kdDebug() << "I'm doing the query to setup permissions\n";
+ command = QString( "GRANT ALL ON %1 TO %2;" ).arg( tables.join( "," ) ).arg( username );
+ permissionsToSet.exec( command );
+}
+
+void PSqlRecipeDB::empty( void )
+{
+ QSqlRecipeDB::empty();
+
+ QStringList tables;
+ tables << "authors_id_seq" << "categories_id_seq" << "ingredient_properties_id_seq" << "ingredient_weights_id_seq" << "ingredients_id_seq" << "prep_methods_id_seq" << "recipes_id_seq" << "units_id_seq" << "ingredient_groups_id_seq" << "yield_types_id_seq" << "ingredient_list_id_seq" << "prep_method_list_id_seq" << "ratings_id_seq" << "rating_criteria_id_seq";
+
+ QSqlQuery tablesToEmpty( QString::null, database );
+ for ( QStringList::Iterator it = tables.begin(); it != tables.end(); ++it ) {
+ QString command = QString( "DELETE FROM %1;" ).arg( *it );
+ tablesToEmpty.exec( command );
+ }
+}
+
+#include "psqlrecipedb.moc"
diff --git a/krecipes/src/backends/PostgreSQL/psqlrecipedb.h b/krecipes/src/backends/PostgreSQL/psqlrecipedb.h
new file mode 100644
index 0000000..9727909
--- /dev/null
+++ b/krecipes/src/backends/PostgreSQL/psqlrecipedb.h
@@ -0,0 +1,64 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+
+#ifndef PSQLRECIPEDB_H
+#define PSQLRECIPEDB_H
+
+#define PSQL_DRIVER "QPSQL7"
+
+#include "qsqlrecipedb.h"
+
+/**
+@author Jason Kivlighn
+*/
+class PSqlRecipeDB : public QSqlRecipeDB
+{
+
+ Q_OBJECT
+
+private:
+ void createDB( void );
+
+public:
+ PSqlRecipeDB( const QString& host, const QString& user = QString::null, const QString& pass = QString::null, const QString& DBName = DEFAULT_DB_NAME, int port = 0 );
+ ~PSqlRecipeDB( void );
+
+ int lastInsertID();
+ void initializeData();
+
+ void createTable( const QString &tableName );
+ void givePermissions( const QString &dbName, const QString &username, const QString &password, const QString &clientHost );
+
+protected:
+ virtual QString qsqlDriverPlugin() const
+ {
+ return PSQL_DRIVER;
+ }
+ virtual int getNextInsertID( const QString &table, const QString &column );
+
+ virtual void empty( void );
+
+private:
+ void portOldDatabases( float version );
+ QStringList backupCommand() const;
+ QStringList restoreCommand() const;
+
+ void addColumn( const QString &new_table_sql, const QString &new_col_info, const QString &default_value, const QString &table_name, int col_index );
+
+ int last_insert_id;
+};
+
+
+
+
+#endif
diff --git a/krecipes/src/backends/SQLite/Makefile.am b/krecipes/src/backends/SQLite/Makefile.am
new file mode 100644
index 0000000..c29f5d3
--- /dev/null
+++ b/krecipes/src/backends/SQLite/Makefile.am
@@ -0,0 +1,19 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../.. $(all_includes)
+
+SUBDIRS = libqsqlite
+
+# Instructions for building the convenience library
+noinst_LTLIBRARIES=libkrecsqlite.la
+libkrecsqlite_la_SOURCES=literecipedb.cpp qsql_sqlite.cpp
+libkrecsqlite_la_METASOURCES=AUTO
+
+libkrecsqlite_la_LIBADD=libqsqlite/libkrecqsqlite.la
+
+#the library search path.
+libkrecsqlite_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
diff --git a/krecipes/src/backends/SQLite/libqsqlite/Makefile.am b/krecipes/src/backends/SQLite/libqsqlite/Makefile.am
new file mode 100644
index 0000000..b368a5a
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/Makefile.am
@@ -0,0 +1,17 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = $(all_includes)
+
+#building the library
+
+noinst_LTLIBRARIES=libkrecqsqlite.la
+libkrecqsqlite_la_SOURCES=krecqsqlitedb.cpp krecqsqliteresult.cpp
+libkrecqsqlite_la_METASOURCES=AUTO
+
+#the library search path.
+libkrecqsqlite_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
diff --git a/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.cpp b/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.cpp
new file mode 100644
index 0000000..e6175d3
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.cpp
@@ -0,0 +1,171 @@
+/***************************************************************************
+* *
+* Copyright (C) 2003 *
+* by Unai Garro (ugarro@users.sourceforge.net) *
+* Martin Imobersteg <imm@gmx.ch> *
+* and opie project *
+* *
+* *
+* This code was originally developed by the opie project, on which *
+* Martin Imobersteg based his work. *
+* This file is adds a small extension, necessary to perform some minimum *
+* SQL actions *
+* *
+* (this project is different from that in qsqlite.sf.net) *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+
+#include "krecqsqlitedb.h"
+#include "krecqsqliteresult.h"
+
+#include <qvaluelist.h>
+
+#include <kdebug.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if HAVE_SQLITE
+#include <sqlite.h>
+#elif HAVE_SQLITE3
+#include <sqlite3.h>
+#endif
+#include <stdlib.h>
+
+QSQLiteDB::QSQLiteDB( QObject *, const char * )
+{}
+
+bool QSQLiteDB::open( const QString &dbname )
+{
+ char * errmsg = 0;
+
+#if HAVE_SQLITE
+
+ m_db = sqlite_open( dbname.latin1(), 0, &errmsg );
+#elif HAVE_SQLITE3
+
+ int res = sqlite3_open( dbname.latin1(), &m_db );
+
+ if ( res != SQLITE_OK )
+ return false;
+#endif
+
+ if ( m_db == 0L ) {
+#if HAVE_SQLITE
+ sqlite_freemem( errmsg );
+#elif HAVE_SQLITE3
+ sqlite3_free( errmsg );
+#endif
+ return false;
+ }
+
+ int func_res;
+ #if HAVE_SQLITE
+ func_res = sqlite_create_function(m_db,"lastInsertID",0,&lastInsertID,m_db );
+ #elif HAVE_SQLITE3
+ func_res = sqlite3_create_function(m_db,"lastInsertID",0,SQLITE_ANY,m_db,
+ &lastInsertID, 0, 0 );
+ #endif
+
+ return func_res == 0;
+}
+
+void QSQLiteDB::close()
+{
+ if ( m_db ) {
+#if HAVE_SQLITE
+ sqlite_close( m_db );
+#elif HAVE_SQLITE3
+
+ sqlite3_close( m_db );
+#endif
+
+ m_db = 0L;
+ }
+}
+
+QSQLiteResult QSQLiteDB::executeQuery( const QString &query, int *lastID )
+{
+ QSQLiteResult res;
+ if ( !m_db ) {
+ return res;
+ }
+
+ char *errmsg = 0;
+#if HAVE_SQLITE
+
+ if ( sqlite_exec( m_db, query.latin1(), &call_back, &res, &errmsg ) > 0 )
+#elif HAVE_SQLITE3
+
+ if ( sqlite3_exec( m_db, query.latin1(), &call_back, &res, &errmsg ) > 0 )
+#endif
+
+ {
+ kdDebug() << "SQLite error: " << errmsg << endl <<
+ "\t (Query: " << query << ")" << endl;
+ res.setError( errmsg );
+ res.setStatus( QSQLiteResult::Failure );
+#if HAVE_SQLITE
+ sqlite_freemem( errmsg );
+#elif HAVE_SQLITE3
+ sqlite3_free( errmsg );
+#endif
+ }
+
+ if ( lastID ) {
+#if HAVE_SQLITE
+ * lastID = sqlite_last_insert_rowid( m_db );
+#elif HAVE_SQLITE3
+
+ *lastID = sqlite3_last_insert_rowid( m_db );
+#endif
+
+ }
+
+ res.setStatus( QSQLiteResult::Success );
+
+ return res;
+}
+
+int QSQLiteDB::call_back( void* result, int argc, char** argv, char** columns )
+{
+ QSQLiteResult * res = ( QSQLiteResult* ) result;
+
+ QMap<QString, QCString> tableString;
+ QMap<int, QCString> tableInt;
+
+ for ( int i = 0; i < argc; i++ ) {
+ tableInt.insert( i, argv[ i ] );
+ tableString.insert( columns[ i ],
+ argv[ i ] );
+ }
+
+ QSQLiteResultRow row( tableString, tableInt );
+ res->addRow( row );
+
+ return 0;
+}
+
+#if HAVE_SQLITE
+void QSQLiteDB::lastInsertID(sqlite_func *context,int argc,const char**)
+{
+ Q_ASSERT( argc==0 );
+
+ void *db = sqlite_user_data(context);
+ sqlite_set_result_int(context, sqlite_last_insert_rowid( (sqlite*)db ) );
+}
+#elif HAVE_SQLITE3
+void QSQLiteDB::lastInsertID( sqlite3_context *context, int argc, sqlite3_value ** )
+{
+ Q_ASSERT( argc==0 );
+
+ void *db = sqlite3_user_data(context);
+ sqlite3_result_int(context, sqlite3_last_insert_rowid( (sqlite3*)db ) );
+}
+#endif
+
diff --git a/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.h b/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.h
new file mode 100644
index 0000000..c0df13b
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+* *
+* Copyright (C) 2003 *
+* by Unai Garro (ugarro@users.sourceforge.net) *
+* Martin Imobersteg <imm@gmx.ch> *
+* and opie project *
+* *
+* *
+* This code was originally developed by the opie project, on which *
+* Martin Imobersteg based his work. *
+* This file is adds a small extension, necessary to perform some minimum *
+* SQL actions *
+* *
+* (this project is different from that in qsqlite.sf.net) *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef QSQLITEDB_H
+#define QSQLITEDB_H
+
+#include <qvaluelist.h>
+#include <qstringlist.h>
+#include <qobject.h>
+
+#include "config.h"
+#if HAVE_SQLITE
+#include <sqlite.h>
+#elif HAVE_SQLITE3
+#include <sqlite3.h>
+#endif
+
+#include "krecqsqliteresult.h"
+
+
+class QSQLiteDB
+{
+public:
+ QSQLiteDB( QObject *parent = 0, const char *name = 0 );
+ bool open( const QString &dbname );
+ void close();
+ QSQLiteResult executeQuery( const QString &query, int *lastID = 0 );
+ int size(); //Returns the number of rows returned
+
+private:
+ static int call_back( void* res, int argc, char** argv, char** columns );
+
+ #if HAVE_SQLITE
+ static void lastInsertID(sqlite_func*,int,const char**);
+ sqlite *m_db;
+ #elif HAVE_SQLITE3
+ static void lastInsertID(sqlite3_context *context, int argc, sqlite3_value **argv);
+ sqlite3 *m_db;
+ #endif
+};
+
+#endif
diff --git a/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.cpp b/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.cpp
new file mode 100644
index 0000000..4bebfed
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.cpp
@@ -0,0 +1,197 @@
+/***************************************************************************
+* *
+* Copyright (C) 2003 *
+* by Unai Garro (ugarro@users.sourceforge.net) *
+* Martin Imobersteg <imm@gmx.ch> *
+* and opie project *
+* *
+* *
+* This code was originally developed by the opie project, on which *
+* Martin Imobersteg based his work. *
+* This file is adds a small extension, necessary to perform some minimum *
+* SQL actions *
+* *
+* (this project is different from that in qsqlite.sf.net) *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "krecqsqliteresult.h"
+
+QSQLiteResultRow::QSQLiteResultRow( const TableString& string, const TableInt& Int )
+ : m_string( string ), m_int( Int )
+{}
+
+QSQLiteResultRow::~QSQLiteResultRow()
+{}
+
+QSQLiteResultRow::QSQLiteResultRow( const QSQLiteResultRow& item )
+{
+ *this = item;
+}
+
+QSQLiteResultRow &QSQLiteResultRow::operator=( const QSQLiteResultRow& other )
+{
+ m_string = other.m_string;
+ m_int = other.m_int;
+ return *this;
+}
+
+QSQLiteResultRow::TableString QSQLiteResultRow::tableString() const
+{
+ return m_string;
+}
+
+QSQLiteResultRow::TableInt QSQLiteResultRow::tableInt() const
+{
+ return m_int;
+}
+
+QCString QSQLiteResultRow::data( const QString& columnName, bool *ok )
+{
+ TableString::Iterator it = m_string.find( columnName );
+
+ /* if found */
+ if ( it != m_string.end() ) {
+ if ( ok )
+ * ok = true;
+ return it.data();
+ }
+ else {
+ if ( ok )
+ * ok = false;
+ return QCString(0);
+ }
+
+}
+
+QCString QSQLiteResultRow::data( int column, bool *ok )
+{
+ TableInt::Iterator it = m_int.find( column );
+
+ // if found
+ if ( it != m_int.end() ) {
+ if ( ok )
+ * ok = true;
+ return it.data();
+ }
+ else {
+ if ( ok )
+ * ok = false;
+ return QCString(0);
+ }
+}
+
+/*
+ * DateFormat is 'YYYY-MM-DD'
+ */
+QDate QSQLiteResultRow::dataToDate( const QString& column, bool *ok )
+{
+ QDate date = QDate::currentDate();
+ QString str = data( column, ok );
+ if ( !str.isEmpty() ) {
+ ; // convert
+ }
+ return date;
+}
+
+QDate QSQLiteResultRow::dataToDate( int column, bool *ok )
+{
+ QDate date = QDate::currentDate();
+ QString str = data( column, ok );
+ if ( !str.isEmpty() ) {
+ ; // convert
+ }
+ return date;
+}
+
+QDateTime QSQLiteResultRow::dataToDateTime( const QString& column )
+{
+ QDateTime time = QDateTime::currentDateTime();
+ return time;
+}
+
+QDateTime QSQLiteResultRow::dataToDateTime( int column )
+{
+ QDateTime time = QDateTime::currentDateTime();
+ return time;
+}
+
+QSQLiteResult::QSQLiteResult( enum Status status,
+ const QSQLiteResult::Columns& list,
+ const QString &error )
+ : m_status( status ), m_list( list ), m_error( error )
+{}
+
+QSQLiteResult::~QSQLiteResult()
+{}
+
+QSQLiteResult::Status QSQLiteResult::getStatus() const
+{
+ return m_status;
+}
+
+void QSQLiteResult::setStatus( QSQLiteResult::Status status )
+{
+ m_status = status;
+}
+
+QSQLiteResult::Columns QSQLiteResult::getResults() const
+{
+ return m_list;
+}
+
+void QSQLiteResult::setResults( const QSQLiteResult::Columns& result )
+{
+ m_list = result;
+}
+
+void QSQLiteResult::addRow( QSQLiteResultRow row )
+{
+ m_list.append( row );
+}
+
+QString QSQLiteResult::getError() const
+{
+ return m_error;
+}
+
+void QSQLiteResult::setError( const QString &error )
+{
+ m_error = error;
+}
+
+QSQLiteResultRow QSQLiteResult::first()
+{
+ it = m_list.begin();
+ return ( *it );
+}
+
+QSQLiteResultRow QSQLiteResult::next()
+{
+ ++it;
+ return ( *it );
+}
+
+bool QSQLiteResult::atEnd()
+{
+ if ( it == m_list.end() ) {
+ return true;
+ }
+
+ return false;
+}
+
+QSQLiteResult::Columns::ConstIterator QSQLiteResult::iterator() const
+{
+ QSQLiteResult::Columns::ConstIterator it;
+ it = m_list.begin();
+ return it;
+}
+
+int QSQLiteResult::size() const
+{
+ return ( m_list.size() );
+}
diff --git a/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.h b/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.h
new file mode 100644
index 0000000..d833eda
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.h
@@ -0,0 +1,142 @@
+/***************************************************************************
+* *
+* Copyright (C) 2003 *
+* by Unai Garro (ugarro@users.sourceforge.net) *
+* Martin Imobersteg <imm@gmx.ch> *
+* and opie project *
+* *
+* *
+* This code was originally developed by the opie project, on which *
+* Martin Imobersteg based his work. *
+* This file is adds a small extension, necessary to perform some minimum *
+* SQL actions *
+* *
+* (this project is different from that in qsqlite.sf.net) *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef QSQLITERESULT_H
+#define QSQLITERESULT_H
+
+#include <qdatetime.h>
+#include <qmap.h>
+#include <qvaluelist.h>
+
+/**
+ * ResultRow represents one row of the resulting answer
+ */
+class QSQLiteResultRow
+{
+public:
+ /**
+ * TableString is used to establish the relations
+ * between the column name and the real item
+ */
+ typedef QMap<QString, QCString> TableString;
+
+ /**
+ * TableInt is used to establish a relation between a
+ * position of a column and the row value
+ */
+ typedef QMap<int, QCString> TableInt;
+
+ /**
+ * Default c'tor. It has a TableString and a TableInt
+ */
+ QSQLiteResultRow( const TableString& = TableString(),
+ const TableInt& = TableInt() );
+ QSQLiteResultRow( const QSQLiteResultRow& );
+ ~QSQLiteResultRow();
+ QSQLiteResultRow &operator=( const QSQLiteResultRow& );
+
+ /**
+ * returns the TableString
+ */
+ TableString tableString() const;
+
+ /**
+ * returns the TableInt
+ */
+ TableInt tableInt() const;
+
+ /**
+ * retrieves the Data from columnName
+ *
+ */
+ QCString data( const QString& columnName, bool *ok = 0 );
+
+ /**
+ * QString for column number
+ */
+ QCString data( int columnNumber, bool *ok = 0 );
+
+ /**
+ * Date conversion from columnName
+ */
+ QDate dataToDate( const QString& columnName, bool *ok = 0 );
+
+ /**
+ * Date conversion from column-number
+ */
+ QDate dataToDate( int columnNumber, bool *ok = 0 );
+
+ QDateTime dataToDateTime( const QString& columName );
+ QDateTime dataToDateTime( int columnNumber );
+
+private:
+ TableString m_string;
+ TableInt m_int;
+};
+
+/**
+ * the QSQLiteResult
+ * either a SQL statusment failed or succeeded
+ */
+class QSQLiteResult
+{
+
+public:
+ typedef QValueList<QSQLiteResultRow> Columns;
+
+ /** The Status of a Result */
+ enum Status{ Success = 0, Failure, Undefined };
+
+ /**
+ * default c'tor
+ * @param status The Status of the Result
+ * @param r ResultItems
+ * @param error Error Message
+ */
+ QSQLiteResult( enum Status status = Undefined,
+ const Columns &r = Columns(),
+ const QString &error = 0L );
+ ~QSQLiteResult();
+
+ Status getStatus() const;
+ Columns getResults() const;
+ QString getError() const;
+
+ void setStatus( enum Status status );
+ void setResults( const Columns &result );
+ void setError( const QString &error );
+
+ void addRow( QSQLiteResultRow row );
+
+ QSQLiteResultRow first();
+ QSQLiteResultRow next();
+ bool atEnd();
+
+ Columns::ConstIterator iterator() const;
+ int size() const;
+
+private:
+ enum Status m_status;
+ Columns m_list;
+ QString m_error;
+ Columns::Iterator it;
+};
+
+#endif
diff --git a/krecipes/src/backends/SQLite/literecipedb.cpp b/krecipes/src/backends/SQLite/literecipedb.cpp
new file mode 100644
index 0000000..ccd88ce
--- /dev/null
+++ b/krecipes/src/backends/SQLite/literecipedb.cpp
@@ -0,0 +1,1034 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "literecipedb.h"
+
+#include <qbuffer.h>
+
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <klocale.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if HAVE_SQLITE3
+#include <sqlite3.h>
+#elif HAVE_SQLITE
+#include <sqlite.h>
+#endif
+
+//keep these two around for porting old databases
+int sqlite_decode_binary( const unsigned char *in, unsigned char *out );
+QString escape( const QString &s );
+
+LiteRecipeDB::LiteRecipeDB( const QString &_dbFile ) : QSqlRecipeDB( QString::null, QString::null, QString::null, _dbFile )
+{
+/* KConfig * config = KGlobal::config();
+ config->setGroup( "Server" );
+
+ if ( dbFile.isNull() )
+ dbFile = config->readEntry( "DBFile", locateLocal ( "appdata", DB_FILENAME ) );
+*/
+}
+
+LiteRecipeDB::~LiteRecipeDB()
+{
+}
+
+int LiteRecipeDB::lastInsertID()
+{
+ int lastID = -1;
+ QSqlQuery query("SELECT lastInsertID()",database);
+ if ( query.isActive() && query.first() )
+ lastID = query.value(0).toInt();
+
+ //kdDebug()<<"lastInsertID(): "<<lastID<<endl;
+
+ return lastID;
+}
+
+QStringList LiteRecipeDB::backupCommand() const
+{
+ #if HAVE_SQLITE
+ QString binary = "sqlite";
+ #elif HAVE_SQLITE3
+ QString binary = "sqlite3";
+ #endif
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Server" );
+ binary = config->readEntry( "SQLitePath", binary );
+
+ QStringList command;
+ command<<binary<<database->databaseName()<<".dump";
+ return command;
+}
+
+QStringList LiteRecipeDB::restoreCommand() const
+{
+ #if HAVE_SQLITE
+ QString binary = "sqlite";
+ #elif HAVE_SQLITE3
+ QString binary = "sqlite3";
+ #endif
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Server" );
+ binary = config->readEntry( "SQLitePath", binary );
+
+ QStringList command;
+ command<<binary<<database->databaseName();
+ return command;
+}
+
+void LiteRecipeDB::createDB()
+{
+ //The file is created by SQLite automatically
+}
+
+void LiteRecipeDB::createTable( const QString &tableName )
+{
+
+ QStringList commands;
+
+ if ( tableName == "recipes" )
+ commands << QString( "CREATE TABLE recipes (id INTEGER NOT NULL,title VARCHAR(%1), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER DEFAULT '-1', instructions TEXT, photo BLOB, prep_time TIME, ctime TIMESTAMP, mtime TIMESTAMP, atime TIMESTAMP, PRIMARY KEY (id));" ).arg( maxRecipeTitleLength() );
+
+ else if ( tableName == "ingredients" )
+ commands << QString( "CREATE TABLE ingredients (id INTEGER NOT NULL, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxIngredientNameLength() );
+
+ else if ( tableName == "ingredient_list" ) {
+ commands << "CREATE TABLE ingredient_list (id INTEGER NOT NULL, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, substitute_for INTEGER, PRIMARY KEY(id) );"
+ << "CREATE index ridil_index ON ingredient_list(recipe_id);"
+ << "CREATE index iidil_index ON ingredient_list(ingredient_id);"
+ << "CREATE index gidil_index ON ingredient_list(group_id);";
+ }
+
+ else if ( tableName == "unit_list" )
+ commands << "CREATE TABLE unit_list (ingredient_id INTEGER, unit_id INTEGER);";
+
+ else if ( tableName == "units" )
+ commands << QString( "CREATE TABLE units (id INTEGER NOT NULL, name VARCHAR(%1), name_abbrev VARCHAR(%2), plural VARCHAR(%3), plural_abbrev VARCHAR(%4), type INTEGER NOT NULL DEFAULT '0',PRIMARY KEY (id));" )
+ .arg( maxUnitNameLength() ).arg( maxUnitNameLength() ).arg( maxUnitNameLength() ).arg( maxUnitNameLength() );
+
+ else if ( tableName == "prep_methods" )
+ commands << QString( "CREATE TABLE prep_methods (id INTEGER NOT NULL, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxPrepMethodNameLength() );
+
+ else if ( tableName == "prep_method_list" ) {
+ commands << "CREATE TABLE prep_method_list (ingredient_list_id INTEGER NOT NULL,prep_method_id INTEGER NOT NULL, order_index INTEGER );"
+ << "CREATE index iid_index ON prep_method_list(ingredient_list_id);"
+ << "CREATE index pid_index ON prep_method_list(prep_method_id);";
+ }
+
+ else if ( tableName == "ingredient_info" )
+ commands << "CREATE TABLE ingredient_info (ingredient_id INTEGER, property_id INTEGER, amount FLOAT, per_units INTEGER);";
+
+ else if ( tableName == "ingredient_properties" )
+ commands << "CREATE TABLE ingredient_properties (id INTEGER NOT NULL,name VARCHAR(20), units VARCHAR(20), PRIMARY KEY (id));";
+
+ else if ( tableName == "ingredient_weights" ) {
+ commands << "CREATE TABLE ingredient_weights (id INTEGER NOT NULL, ingredient_id INTEGER NOT NULL, amount FLOAT, unit_id INTEGER, weight FLOAT, weight_unit_id INTEGER, prep_method_id INTEGER, PRIMARY KEY (id) );"
+
+ << "CREATE index weight_wid_index ON ingredient_weights(weight_unit_id)"
+ << "CREATE index weight_pid_index ON ingredient_weights(prep_method_id)"
+ << "CREATE index weight_uid_index ON ingredient_weights(unit_id)"
+ << "CREATE index weight_iid_index ON ingredient_weights(ingredient_id)";
+ }
+
+ else if ( tableName == "units_conversion" )
+ commands << "CREATE TABLE units_conversion (unit1_id INTEGER, unit2_id INTEGER, ratio FLOAT);";
+
+ else if ( tableName == "categories" ) {
+ commands << QString( "CREATE TABLE categories (id INTEGER NOT NULL, name varchar(%1) default NULL, parent_id INGEGER NOT NULL default -1, PRIMARY KEY (id));" ).arg( maxCategoryNameLength() );
+ commands << "CREATE index parent_id_index ON categories(parent_id);";
+ }
+ else if ( tableName == "category_list" ) {
+ commands << "CREATE TABLE category_list (recipe_id INTEGER NOT NULL,category_id INTEGER NOT NULL);"
+ << "CREATE index rid_index ON category_list(recipe_id);"
+ << "CREATE index cid_index ON category_list(category_id);";
+ }
+
+ else if ( tableName == "authors" )
+ commands << QString( "CREATE TABLE authors (id INTEGER NOT NULL, name varchar(%1) default NULL,PRIMARY KEY (id));" ).arg( maxAuthorNameLength() );
+
+ else if ( tableName == "author_list" )
+ commands << "CREATE TABLE author_list (recipe_id INTEGER NOT NULL,author_id INTEGER NOT NULL);";
+
+ else if ( tableName == "db_info" ) {
+ commands << "CREATE TABLE db_info (ver FLOAT NOT NULL,generated_by varchar(200) default NULL);";
+ commands << QString( "INSERT INTO db_info VALUES(%1,'Krecipes %2');" ).arg( latestDBVersion() ).arg( krecipes_version() );
+ }
+ else if ( tableName == "ingredient_groups" ) {
+ commands << QString( "CREATE TABLE ingredient_groups (id INTEGER NOT NULL, name varchar(%1), PRIMARY KEY (id));" ).arg( maxIngGroupNameLength() );
+ }
+ else if ( tableName == "yield_types" ) {
+ commands << QString( "CREATE TABLE yield_types (id INTEGER NOT NULL, name varchar(%1), PRIMARY KEY (id));" ).arg( maxYieldTypeLength() );
+ }
+
+ else if ( tableName == "ratings" )
+ commands << "CREATE TABLE ratings (id INTEGER NOT NULL, recipe_id int(11) NOT NULL, comment TEXT, rater TEXT, created TIMESTAMP, PRIMARY KEY (id));";
+
+ else if ( tableName == "rating_criteria" )
+ commands << "CREATE TABLE rating_criteria (id INTEGER NOT NULL, name TEXT, PRIMARY KEY (id));";
+
+ else if ( tableName == "rating_criterion_list" )
+ commands << "CREATE TABLE rating_criterion_list (rating_id INTEGER NOT NULL, rating_criterion_id INTEGER, stars FLOAT);";
+
+ else
+ return ;
+
+ // execute the queries
+ for ( QStringList::const_iterator it = commands.begin(); it != commands.end(); ++it )
+ database->exec( *it );
+
+}
+
+void LiteRecipeDB::portOldDatabases( float version )
+{
+ QString command;
+ if ( qRound(version*10) < 5 ) {
+ //===========add prep_method_id to ingredient_list table
+ //There's no ALTER command in SQLite, so we have to copy all data to a new table and then recreate the table with the prep_method_id
+ database->exec( "CREATE TABLE ingredient_list_copy (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, unit_id INTEGER, order_index INTEGER);" );
+ QSqlQuery copyQuery( "SELECT recipe_id,ingredient_id,amount,unit_id,order_index FROM ingredient_list;", database );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO ingredient_list_copy VALUES(%1,%2,%3,%4,%5);" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( copyQuery.value( 1 ).toInt() )
+ .arg( copyQuery.value( 2 ).toDouble() )
+ .arg( copyQuery.value( 3 ).toInt() )
+ .arg( copyQuery.value( 4 ).toInt() );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list" );
+ database->exec( "CREATE TABLE ingredient_list (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER);" );
+ copyQuery = database->exec( "SELECT * FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO ingredient_list VALUES(%1,%2,%3,%4,%5,%6);" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( copyQuery.value( 1 ).toInt() )
+ .arg( copyQuery.value( 2 ).toDouble() )
+ .arg( copyQuery.value( 3 ).toInt() )
+ .arg( -1 ) //default prep method
+ .arg( copyQuery.value( 4 ).toInt() );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ database->exec( "CREATE index ridil_index ON ingredient_list(recipe_id);" );
+ database->exec( "CREATE index iidil_index ON ingredient_list(ingredient_id);" );
+
+
+ //==============expand length of author name to 50 characters
+ database->exec( "CREATE TABLE authors_copy (id INTEGER, name varchar(20));" );
+ copyQuery = database->exec( "SELECT * FROM authors;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO authors_copy VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE authors" );
+ database->exec( "CREATE TABLE authors (id INTEGER NOT NULL, name varchar(50) default NULL,PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT * FROM authors_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO authors VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE authors_copy" );
+
+
+ //==================expand length of category name to 40 characters
+ database->exec( "CREATE TABLE categories_copy (id INTEGER, name varchar(20));" );
+ copyQuery = database->exec( "SELECT * FROM categories;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO categories_copy VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE categories" );
+ database->exec( "CREATE TABLE categories (id INTEGER NOT NULL, name varchar(40) default NULL,PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT * FROM categories_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO categories VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE categories_copy" );
+
+ //================Set the version to the new one (0.5)
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.5,'Krecipes 0.5');";
+ database->exec( command );
+ }
+
+ if ( qRound(version*10) < 6 ) {
+ //==================add a column to 'categories' to allow subcategories
+ database->exec( "CREATE TABLE categories_copy (id INTEGER, name varchar(40));" );
+ QSqlQuery copyQuery = database->exec( "SELECT * FROM categories;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO categories_copy VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE categories" );
+ database->exec( "CREATE TABLE categories (id INTEGER NOT NULL, name varchar(40) default NULL, parent_id INTEGER NOT NULL, PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT * FROM categories_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO categories VALUES(%1,'%2',-1);" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE categories_copy" );
+
+ //================Set the version to the new one (0.6)
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.6,'Krecipes 0.6');";
+ database->exec( command );
+ }
+
+ if ( qRound(version*100) < 61 ) {
+ //==================add a column to 'recipes' to allow prep time
+ database->exec( "CREATE TABLE recipes_copy (id INTEGER NOT NULL,title VARCHAR(200),persons INTEGER,instructions TEXT, photo BLOB, PRIMARY KEY (id));" );
+ QSqlQuery copyQuery = database->exec( "SELECT * FROM recipes;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO recipes_copy VALUES(%1,'%2','%3','%4','%5');" )
+ .arg( escape( copyQuery.value( 0 ).toString() ) )
+ .arg( escape( copyQuery.value( 1 ).toString() ) )
+ .arg( escape( copyQuery.value( 2 ).toString() ) )
+ .arg( escape( copyQuery.value( 3 ).toString() ) )
+ .arg( escape( copyQuery.value( 4 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes" );
+ database->exec( "CREATE TABLE recipes (id INTEGER NOT NULL,title VARCHAR(200),persons INTEGER,instructions TEXT, photo BLOB, prep_time TIME, PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT * FROM recipes_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO recipes VALUES(%1,'%2','%3','%4','%5',NULL);" )
+ .arg( escape( copyQuery.value( 0 ).toString() ) )
+ .arg( escape( copyQuery.value( 1 ).toString() ) )
+ .arg( escape( copyQuery.value( 2 ).toString() ) )
+ .arg( escape( copyQuery.value( 3 ).toString() ) )
+ .arg( escape( copyQuery.value( 4 ).toString() ) );
+
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes_copy" );
+
+ //================Set the version to the new one (0.61)
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.61,'Krecipes 0.6');";
+ database->exec( command );
+ }
+
+ if ( qRound(version*100) < 62 ) {
+ database->transaction();
+
+ //==================add a column to 'ingredient_list' to allow grouping ingredients
+ database->exec( "CREATE TABLE ingredient_list_copy (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER);" );
+ QSqlQuery copyQuery = database->exec( "SELECT * FROM ingredient_list;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO ingredient_list_copy VALUES('" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "');";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list" );
+ database->exec( "CREATE TABLE ingredient_list (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER, group_id INTEGER);" );
+ copyQuery = database->exec( "SELECT * FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO ingredient_list VALUES('" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "',-1)";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.62,'Krecipes 0.7');";
+ database->exec( command );
+
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 63 ) {
+ database->transaction();
+
+ //==================add a column to 'units' to allow handling plurals
+ database->exec( "CREATE TABLE units_copy (id INTEGER NOT NULL, name VARCHAR(20), PRIMARY KEY (id));" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,name FROM units;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO units_copy VALUES('" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "');";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units" );
+ database->exec( "CREATE TABLE units (id INTEGER NOT NULL, name VARCHAR(20), plural VARCHAR(20), PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT id,name FROM units_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO units VALUES('" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "',NULL)";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units_copy" );
+
+ QSqlQuery result = database->exec( "SELECT id,name FROM units WHERE plural ISNULL;" );
+ if ( result.isActive() ) {
+ while ( result.next() ) {
+ command = "UPDATE units SET plural='" + escape( result.value( 1 ).toString() ) + "' WHERE id=" + QString::number( result.value( 0 ).toInt() );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.63,'Krecipes 0.7');";
+ database->exec( command );
+
+ database->commit();
+ }
+
+ if ( qRound(version*10) < 7 ) { //simply call 0.63 -> 0.7
+ database->exec( "UPDATE db_info SET ver='0.7';" );
+ }
+
+ if ( qRound(version*100) < 81 ) {
+ database->transaction();
+ addColumn("CREATE TABLE %1 (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, %2 unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER, group_id INTEGER)","amount_offset FLOAT","'0'","ingredient_list",3);
+
+ //addColumn() doesn't preserve indexes
+ database->exec("CREATE index ridil_index ON ingredient_list(recipe_id)");
+ database->exec("CREATE index iidil_index ON ingredient_list(ingredient_id)");
+
+ database->exec( "UPDATE db_info SET ver='0.81',generated_by='Krecipes SVN (20050816)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 82 ) {
+ database->transaction();
+
+ //==================add a columns to 'recipes' to allow yield range + yield type
+ database->exec( "CREATE TABLE recipes_copy (id INTEGER NOT NULL,title VARCHAR(200),persons INTEGER,instructions TEXT, photo BLOB, prep_time TIME, PRIMARY KEY (id));" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,title,persons,instructions,photo,prep_time FROM recipes;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO recipes_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //title
+ + "','" + escape( copyQuery.value( 2 ).toString() ) //persons
+ + "','" + escape( copyQuery.value( 3 ).toString() ) //instructions
+ + "','" + escape( copyQuery.value( 4 ).toString() ) //photo
+ + "','" + escape( copyQuery.value( 5 ).toString() ) //prep_time
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes" );
+ database->exec( "CREATE TABLE recipes (id INTEGER NOT NULL,title VARCHAR(200), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER, instructions TEXT, photo BLOB, prep_time TIME, PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT id,title,persons,instructions,photo,prep_time FROM recipes_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO recipes VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //title
+ + "','" + escape( copyQuery.value( 2 ).toString() ) //persons, now yield_amount
+ + "','0" //yield_amount_offset
+ + "','-1" //yield_type_id
+ + "','" + escape( copyQuery.value( 3 ).toString() ) //instructions
+ + "','" + escape( copyQuery.value( 4 ).toString() ) //photo
+ + "','" + escape( copyQuery.value( 5 ).toString() ) //prep_time
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes_copy" );
+
+ database->exec( "UPDATE db_info SET ver='0.82',generated_by='Krecipes SVN (20050902)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 83 ) {
+ database->transaction();
+
+ //====add a id columns to 'ingredient_list' to identify it for the prep method list
+ database->exec( "CREATE TABLE ingredient_list_copy (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER, group_id INTEGER);" );
+ QSqlQuery copyQuery = database->exec( "SELECT recipe_id,ingredient_id,amount,amount_offset,unit_id,prep_method_id,order_index,group_id FROM ingredient_list" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO ingredient_list_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "','" + escape( copyQuery.value( 6 ).toString() )
+ + "','" + escape( copyQuery.value( 7 ).toString() )
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list" );
+ database->exec( "CREATE TABLE ingredient_list (id INTEGER NOT NULL, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, PRIMARY KEY(id) );" );
+
+ copyQuery = database->exec( "SELECT recipe_id,ingredient_id,amount,amount_offset,unit_id,prep_method_id,order_index,group_id FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO ingredient_list VALUES("
+ + QString("NULL")
+ + ",'" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 6 ).toString() )
+ + "','" + escape( copyQuery.value( 7 ).toString() )
+ + "')";
+ database->exec( command );
+
+ int prep_method_id = copyQuery.value( 5 ).toInt();
+ if ( prep_method_id != -1 ) {
+ command = "INSERT INTO prep_method_list VALUES('"
+ + QString::number(lastInsertID())
+ + "','" + QString::number(prep_method_id)
+ + "','1" //order_index
+ + "')";
+ database->exec( command );
+ }
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ database->exec( "CREATE INDEX ridil_index ON ingredient_list(recipe_id);" );
+ database->exec( "CREATE INDEX iidil_index ON ingredient_list(ingredient_id);" );
+
+ database->exec( "UPDATE db_info SET ver='0.83',generated_by='Krecipes SVN (20050909)';" );
+
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 84 ) {
+ database->transaction();
+
+ //==================add a columns to 'recipes' to allow storing atime, mtime, ctime
+ database->exec( "CREATE TABLE recipes_copy (id INTEGER NOT NULL,title VARCHAR(200), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER, instructions TEXT, photo BLOB, prep_time TIME, PRIMARY KEY (id));" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,title,yield_amount,yield_amount_offset,yield_type_id,instructions,photo,prep_time FROM recipes;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO recipes_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "','" + escape( copyQuery.value( 6 ).toString() )
+ + "','" + escape( copyQuery.value( 7 ).toString() )
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes" );
+ database->exec( "CREATE TABLE recipes (id INTEGER NOT NULL,title VARCHAR(200), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER DEFAULT '-1', instructions TEXT, photo BLOB, prep_time TIME, ctime TIMESTAMP, mtime TIMESTAMP, atime TIMESTAMP, PRIMARY KEY (id))" );
+ copyQuery = database->exec( "SELECT id,title,yield_amount,yield_amount_offset,yield_type_id,instructions,photo,prep_time FROM recipes_copy" );
+ if ( copyQuery.isActive() ) {
+
+ QString current_timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO recipes VALUES('"
+ + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "','" + escape( copyQuery.value( 6 ).toString() )
+ + "','" + escape( copyQuery.value( 7 ).toString() )
+ + "','" + escape( current_timestamp ) //ctime
+ + "','" + escape( current_timestamp ) //mtime
+ + "','" + escape( current_timestamp ) //atime
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes_copy" );
+
+ database->exec( "UPDATE db_info SET ver='0.84',generated_by='Krecipes SVN (20050913)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 85 ) {
+ database->transaction();
+
+ QSqlQuery query( "SELECT id,photo FROM recipes", database );
+
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QImage photo;
+ QString photoString = query.value(1).toString();
+
+ // Decode the photo
+ uchar *photoArray = new uchar [ photoString.length() + 1 ];
+ memcpy( photoArray, photoString.latin1(), photoString.length() * sizeof( char ) );
+ sqlite_decode_binary( ( uchar* ) photoArray, ( uchar* ) photoArray );
+
+ photo.loadFromData( photoArray, photoString.length() );
+
+ // picture will now have a ready-to-use image
+ delete[] photoArray;
+
+ QByteArray ba;
+ QBuffer buffer( ba );
+ buffer.open( IO_WriteOnly );
+ QImageIO iio( &buffer, "JPEG" );
+ iio.setImage( photo );
+ iio.write();
+ //recipe->photo.save( &buffer, "JPEG" ); don't need QImageIO in QT 3.2
+ storePhoto( query.value(0).toInt(), ba );
+
+ emit progress();
+ }
+ }
+
+
+ database->exec( "UPDATE db_info SET ver='0.85',generated_by='Krecipes SVN (20050926)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 86 ) {
+ database->transaction();
+
+ database->exec( "CREATE index gidil_index ON ingredient_list(group_id)" );
+
+ QSqlQuery query( "SELECT id,name FROM ingredient_groups ORDER BY name", database );
+
+ QString last;
+ int lastID;
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QString name = query.value(1).toString();
+ int id = query.value(0).toInt();
+ if ( last == name ) {
+ QString command = QString("UPDATE ingredient_list SET group_id=%1 WHERE group_id=%2").arg(lastID).arg(id);
+ database->exec(command);
+
+ command = QString("DELETE FROM ingredient_groups WHERE id=%1").arg(id);
+ database->exec(command);
+ }
+ last = name;
+ lastID = id;
+
+ emit progress();
+ }
+ }
+
+ database->exec( "UPDATE db_info SET ver='0.86',generated_by='Krecipes SVN (20050928)'" );
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.86 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 87 ) {
+ //Load this default data so the user knows what rating criteria is
+ database->exec( QString("INSERT INTO rating_criteria VALUES (1,'%1')").arg(i18n("Overall")) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (2,'%1')").arg(i18n("Taste") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (3,'%1')").arg(i18n("Appearance") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (4,'%1')").arg(i18n("Originality") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (5,'%1')").arg(i18n("Ease of Preparation") ) );
+
+ database->exec( "UPDATE db_info SET ver='0.87',generated_by='Krecipes SVN (20051014)'" );
+ }
+
+ if ( qRound(version*100) < 90 ) {
+ database->exec("UPDATE db_info SET ver='0.9',generated_by='Krecipes 0.9'");
+ }
+
+ if ( qRound(version*100) < 91 ) {
+ database->exec("CREATE index parent_id_index ON categories(parent_id)");
+ database->exec("UPDATE db_info SET ver='0.91',generated_by='Krecipes SVN (20060526)'");
+ }
+
+ if ( qRound(version*100) < 92 ) {
+ database->transaction();
+
+ //==================add a columns to 'units' to allow unit abbreviations
+ database->exec( "CREATE TABLE units_copy (id INTEGER NOT NULL, name VARCHAR(20), plural VARCHAR(20))" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,name,plural FROM units" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO units_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //name
+ + "','" + escape( copyQuery.value( 2 ).toString() ) //plural
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units" );
+ database->exec( "CREATE TABLE units (id INTEGER NOT NULL, name VARCHAR(20), name_abbrev VARCHAR(20), plural VARCHAR(20), plural_abbrev VARCHAR(20), PRIMARY KEY (id))" );
+ copyQuery = database->exec( "SELECT id,name,plural FROM units_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO units VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //name
+ + "',NULL" //name_abbrev
+ + ",'" + escape( copyQuery.value( 2 ).toString() ) //plural
+ + "',NULL" //plural_abbrev
+ + ")";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units_copy" );
+
+ database->exec("UPDATE db_info SET ver='0.92',generated_by='Krecipes SVN (20060609)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.92 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 93 ) {
+ database->transaction();
+
+ addColumn("CREATE TABLE %1 (id INTEGER NOT NULL, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, %2 PRIMARY KEY(id) )","substitute_for INTEGER","NULL","ingredient_list",8);
+
+ database->exec( "CREATE index ridil_index ON ingredient_list(recipe_id)" );
+ database->exec( "CREATE index iidil_index ON ingredient_list(ingredient_id)" );
+ database->exec( "CREATE index gidil_index ON ingredient_list(group_id)" );
+
+ database->exec( "UPDATE db_info SET ver='0.93',generated_by='Krecipes SVN (20060616)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 94 ) {
+ database->transaction();
+
+ //==================add a column to 'units' to allow specifying a type
+ database->exec( "CREATE TABLE units_copy (id INTEGER NOT NULL, name VARCHAR(20), name_abbrev VARCHAR(20), plural VARCHAR(20), plural_abbrev VARCHAR(20))" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,name,name_abbrev,plural,plural_abbrev FROM units" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ QString name_abbrev = escape( copyQuery.value( 2 ).toString() );
+ if ( name_abbrev.isEmpty() )
+ name_abbrev = "NULL";
+ else {
+ name_abbrev.prepend("'");
+ name_abbrev.append("'");
+ }
+ QString plural_abbrev = escape( copyQuery.value( 4 ).toString() );
+ if ( plural_abbrev.isEmpty() )
+ plural_abbrev = "NULL";
+ else {
+ plural_abbrev.prepend("'");
+ plural_abbrev.append("'");
+ }
+
+ command = "INSERT INTO units_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //name
+ + "'," + name_abbrev //name_abbrev
+ + ",'" + escape( copyQuery.value( 3 ).toString() ) //plural
+ + "'," + plural_abbrev //plural_abbrev
+ + ")";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units" );
+ database->exec( "CREATE TABLE units (id INTEGER NOT NULL, name VARCHAR(20), name_abbrev VARCHAR(20), plural VARCHAR(20), plural_abbrev VARCHAR(20), type INTEGER NOT NULL, PRIMARY KEY (id))" );
+ copyQuery = database->exec( "SELECT id,name,name_abbrev,plural,plural_abbrev FROM units_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ QString name_abbrev = escape( copyQuery.value( 2 ).toString() );
+ if ( name_abbrev.isEmpty() )
+ name_abbrev = "NULL";
+ else {
+ name_abbrev.prepend("'");
+ name_abbrev.append("'");
+ }
+ QString plural_abbrev = escape( copyQuery.value( 4 ).toString() );
+ if ( plural_abbrev.isEmpty() )
+ plural_abbrev = "NULL";
+ else {
+ plural_abbrev.prepend("'");
+ plural_abbrev.append("'");
+ }
+
+ command = "INSERT INTO units VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //name
+ + "'," + name_abbrev //name_abbrev
+ + ",'" + escape( copyQuery.value( 3 ).toString() ) //plural
+ + "'," + plural_abbrev //plural_abbrev
+ + ",'0')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units_copy" );
+
+
+ database->exec( "UPDATE db_info SET ver='0.94',generated_by='Krecipes SVN (20060712)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 95 ) {
+ database->exec( "DROP TABLE ingredient_weights" );
+ createTable( "ingredient_weights" );
+ database->exec( "UPDATE db_info SET ver='0.95',generated_by='Krecipes SVN (20060726)'" );
+ }
+}
+
+void LiteRecipeDB::addColumn( const QString &new_table_sql, const QString &new_col_info, const QString &default_value, const QString &table_name, int col_index )
+{
+ QString command;
+
+ command = QString(new_table_sql).arg(table_name+"_copy").arg(QString::null);
+ kdDebug()<<"calling: "<<command<<endl;
+ database->exec( command );
+
+ command = "SELECT * FROM "+table_name;
+ kdDebug()<<"calling: "<<command<<endl;
+ QSqlQuery copyQuery = database->exec( command );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QStringList dataList;
+ for ( int i = 0 ;; ++i ) {
+ if ( copyQuery.value(i).isNull() )
+ break;
+
+ QString data = copyQuery.value(i).toString();
+
+ dataList << "'"+escape(data)+"'";
+ }
+ command = "INSERT INTO "+table_name+"_copy VALUES("+dataList.join(",")+");";
+ kdDebug()<<"calling: "<<command<<endl;
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE "+table_name );
+ database->exec( QString(new_table_sql).arg(table_name).arg(new_col_info+",") );
+ copyQuery = database->exec( "SELECT * FROM "+table_name+"_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QStringList dataList;
+ for ( int i = 0 ;; ++i ) {
+ if ( i == col_index )
+ dataList << default_value;
+
+ if ( copyQuery.value(i).isNull() )
+ break;
+
+ QString data = copyQuery.value(i).toString();
+
+ dataList << "'"+escape(data)+"'";
+ }
+ command = "INSERT INTO "+table_name+" VALUES(" +dataList.join(",")+")";
+ kdDebug()<<"calling: "<<command<<endl;
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE "+table_name+"_copy" );
+}
+
+QString LiteRecipeDB::escapeAndEncode( const QString &s ) const
+{
+ QString s_escaped;
+
+ // Escape
+ s_escaped = escape( QString::fromLatin1(s.utf8()) );
+
+ // Return encoded
+ return s_escaped.latin1(); // Note that the text has already been converted before escaping.
+}
+
+/*
+** Decode the string "in" into binary data and write it into "out".
+** This routine reverses the encoding created by sqlite_encode_binary().
+** The output will always be a few bytes less than the input. The number
+** of bytes of output is returned. If the input is not a well-formed
+** encoding, -1 is returned.
+**
+** The "in" and "out" parameters may point to the same buffer in order
+** to decode a string in place.
+*/
+int sqlite_decode_binary( const unsigned char *in, unsigned char *out )
+{
+ int i, c, e;
+ e = *( in++ );
+ i = 0;
+ while ( ( c = *( in++ ) ) != 0 ) {
+ if ( c == 1 ) {
+ c = *( in++ );
+ if ( c == 1 ) {
+ c = 0;
+ }
+ else if ( c == 2 ) {
+ c = 1;
+ }
+ else if ( c == 3 ) {
+ c = '\'';
+ }
+ else {
+ return -1;
+ }
+ }
+ out[ i++ ] = ( c + e ) & 0xff;
+ }
+ return i;
+}
+
+QString escape( const QString &s )
+{
+ QString s_escaped = s;
+
+ if ( !s_escaped.isEmpty() ) { //###: sqlite_mprintf() seems to fill an empty string with garbage
+ // Escape using SQLite's function
+#if HAVE_SQLITE
+ char * escaped = sqlite_mprintf( "%q", s.latin1() ); // Escape the string(allocates memory)
+#elif HAVE_SQLITE3
+ char * escaped = sqlite3_mprintf( "%q", s.latin1() ); // Escape the string(allocates memory)
+#endif
+ s_escaped = escaped;
+#if HAVE_SQLITE
+ sqlite_freemem( escaped ); // free allocated memory
+#elif HAVE_SQLITE3
+ sqlite3_free( escaped ); // free allocated memory
+#endif
+ }
+
+ return ( s_escaped );
+}
+
+#include "literecipedb.moc"
diff --git a/krecipes/src/backends/SQLite/literecipedb.h b/krecipes/src/backends/SQLite/literecipedb.h
new file mode 100644
index 0000000..c83672f
--- /dev/null
+++ b/krecipes/src/backends/SQLite/literecipedb.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef LITERECIPEDB_H
+#define LITERECIPEDB_H
+
+#include "backends/qsqlrecipedb.h"
+
+#include <qstring.h>
+
+#include "qsql_sqlite.h"
+
+#define SQLITE_DRIVER "KRESQLITE"
+
+class LiteRecipeDB : public QSqlRecipeDB
+{
+
+ Q_OBJECT
+
+private:
+ void createDB( void );
+
+public:
+ LiteRecipeDB( const QString &DBName = DEFAULT_DB_NAME );
+ ~LiteRecipeDB( void );
+
+ virtual int lastInsertID();
+
+ virtual void createTable( const QString &tableName );
+ virtual void givePermissions(const QString&, const QString&, const QString&, const QString&){} //no permissions in this backend
+
+protected:
+ virtual QSqlDriver *qsqlDriver() const
+ {
+ return new KreSQLiteDriver();
+ }
+
+ virtual QString escapeAndEncode( const QString &s ) const;
+
+private:
+ virtual void portOldDatabases( float version );
+ virtual QStringList backupCommand() const;
+ virtual QStringList restoreCommand() const;
+
+ void addColumn( const QString &new_table_sql, const QString &new_col_info, const QString &default_value, const QString &table_name, int col_index );
+};
+
+
+
+
+#endif
diff --git a/krecipes/src/backends/SQLite/qsql_sqlite.cpp b/krecipes/src/backends/SQLite/qsql_sqlite.cpp
new file mode 100644
index 0000000..b39a10b
--- /dev/null
+++ b/krecipes/src/backends/SQLite/qsql_sqlite.cpp
@@ -0,0 +1,459 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "qsql_sqlite.h"
+
+#include <qdatetime.h>
+#include <qregexp.h>
+#include <qfile.h>
+
+#include <qptrvector.h>
+#include <unistd.h>
+
+#include <kdebug.h>
+
+#include "libqsqlite/krecqsqlitedb.h"
+#include "libqsqlite/krecqsqliteresult.h"
+
+#define QSQLITE_DRIVER_NAME "KRE_QSQLITE"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if HAVE_SQLITE3
+#include <sqlite3.h>
+#define sqlite_free sqlite3_free
+#elif HAVE_SQLITE
+#include <sqlite.h>
+#endif
+
+class KreSQLiteResult : public QSqlResult
+{
+public:
+ KreSQLiteResult( const KreSQLiteDriver *d ): QSqlResult( d )
+ {
+ db = d->db;
+ }
+ ~KreSQLiteResult() {}
+protected:
+ QVariant data( int );
+ bool reset ( const QString& );
+ bool fetch( int );
+ bool fetchFirst();
+ bool fetchNext();
+ bool fetchLast();
+ bool isNull( int );
+ QSqlRecord record();
+ int size();
+ int numRowsAffected();
+
+private:
+ QSQLiteResult result;
+ QSQLiteResultRow row;
+
+ QSQLiteDB *db;
+};
+
+
+QVariant KreSQLiteResult::data( int field )
+{
+ if ( !isSelect() ) {
+ //qWarning( "KreSQLiteResult::data: column %d out of range", field );
+ qWarning( "KreSQLiteResult::data: not a select statement" );
+ return QVariant();
+ }
+
+ return QVariant(row.data(field));
+}
+
+bool KreSQLiteResult::fetch( int i )
+{
+kdDebug()<<"fetch_i"<<endl;
+ if ( isForwardOnly() ) { // fake a forward seek
+ if ( at() < i ) {
+ int x = i - at();
+ while ( --x && fetchNext() );
+ return fetchNext();
+ } else {
+ return false;
+ }
+ }
+ if ( at() == i )
+ return true;
+
+ row = result.first();
+ for ( int j = 0; j < i; ++j ) {
+ if ( result.atEnd() )
+ return false;
+
+ row = result.next();
+ }
+
+ setAt( i );
+ return true;
+}
+
+bool KreSQLiteResult::fetchNext()
+{
+ row = result.next();
+
+ if ( result.atEnd() )
+ return false;
+
+ setAt( at() + 1 );
+ return true;
+}
+
+bool KreSQLiteResult::fetchFirst()
+{
+ row = result.first();
+
+ if ( result.atEnd() )
+ return false;
+
+ setAt(0);
+ return true;
+}
+
+bool KreSQLiteResult::fetchLast()
+{kdDebug()<<"fetchlast"<<endl;
+ if ( isForwardOnly() ) { // fake this since MySQL can't seek on forward only queries
+ bool success = fetchNext(); // did we move at all?
+ while ( fetchNext() );
+ return success;
+ }
+ int numRows = size();
+ if ( !numRows )
+ return false;
+ return fetch( numRows - 1 );
+}
+
+bool KreSQLiteResult::isNull( int i )
+{
+ return false;
+}
+
+QSqlRecord KreSQLiteResult::record()
+{kdDebug()<<"record"<<endl;
+ return QSqlRecord();
+}
+
+int KreSQLiteResult::size()
+{
+//kdDebug()<<"size: "<<result.size()<<endl;
+ return result.size();
+}
+
+int KreSQLiteResult::numRowsAffected()
+{kdDebug()<<"numrowsaffected"<<endl;
+ return 1;/*sqlite3_changes(db)*/
+}
+
+/*
+ Execute \a query.
+*/
+bool KreSQLiteResult::reset(const QString& query)
+{
+ // this is where we build a query.
+ if (!driver())
+ return false;
+ if (!driver()-> isOpen() || driver()->isOpenError())
+ return false;
+
+ //cleanup
+ setAt( -1 );
+
+ setSelect(true);
+
+
+ result = db->executeQuery( query );
+ int res = result.getStatus();
+
+ if (res != SQLITE_OK ) {
+ setLastError(QSqlError("Unable to execute statement", result.getError(), QSqlError::Statement, res));
+ }
+
+ setActive(true);
+ return true;
+}
+
+/////////////////////////////////////////////////////////
+
+KreSQLiteDriver::KreSQLiteDriver(QObject * parent, const char * name)
+ : QSqlDriver(parent, name ? name : QSQLITE_DRIVER_NAME)
+{
+}
+
+KreSQLiteDriver::KreSQLiteDriver(QSQLiteDB *connection, QObject *parent, const char *name)
+ : QSqlDriver(parent, name ? name : QSQLITE_DRIVER_NAME)
+{
+ db = connection;
+ setOpen(true);
+ setOpenError(false);
+}
+
+
+KreSQLiteDriver::~KreSQLiteDriver()
+{
+}
+
+bool KreSQLiteDriver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case QuerySize:
+ case Transactions:
+ return true;
+ // case BLOB:
+ default:
+ return false;
+ }
+}
+
+/*
+ SQLite dbs have no user name, passwords, hosts or ports.
+ just file names.
+*/
+bool KreSQLiteDriver::open(const QString & file, const QString &, const QString &, const QString &, int)
+{
+ if (isOpen())
+ close();
+
+ if (file.isEmpty())
+ return false;
+
+ db = new QSQLiteDB;
+ if ( !db->open(QFile::encodeName(file)) ) {
+ setLastError(QSqlError("Error to open database", 0, QSqlError::Connection));
+ setOpenError(true);
+ setOpen(false);
+ return false;
+ }
+
+ setOpen(true);
+ setOpenError(false);
+ return true;
+}
+
+void KreSQLiteDriver::close()
+{
+ if (isOpen()) {
+ db->close();
+ delete db; db = 0;
+ setOpen(false);
+ setOpenError(false);
+ }
+}
+
+QSqlQuery KreSQLiteDriver::createQuery() const
+{
+ return QSqlQuery(new KreSQLiteResult(this));
+}
+
+bool KreSQLiteDriver::beginTransaction()
+{
+ if (!isOpen() || isOpenError())
+ return false;
+
+ QSQLiteResult result = db->executeQuery( "BEGIN" );
+ int status = result.getStatus();
+ if (status == QSQLiteResult::Success)
+ return true;
+
+ setLastError(QSqlError("Unable to begin transaction", result.getError(), QSqlError::Transaction, status));
+ return false;
+}
+
+bool KreSQLiteDriver::commitTransaction()
+{
+ if (!isOpen() || isOpenError())
+ return false;
+
+ QSQLiteResult result = db->executeQuery( "COMMIT" );
+ int status = result.getStatus();
+ if (status == QSQLiteResult::Success)
+ return true;
+
+ setLastError(QSqlError("Unable to commit transaction", result.getError(), QSqlError::Transaction, status));
+ return false;
+}
+
+bool KreSQLiteDriver::rollbackTransaction()
+{
+ if (!isOpen() || isOpenError())
+ return false;
+
+ QSQLiteResult result = db->executeQuery( "ROLLBACK" );
+ int status = result.getStatus();
+ if (status == SQLITE_OK)
+ return true;
+
+ setLastError(QSqlError("Unable to rollback transaction", result.getError(), QSqlError::Transaction, status));
+ return false;
+}
+
+QStringList KreSQLiteDriver::tables(const QString &typeName) const
+{
+ QStringList res;
+ if (!isOpen())
+ return res;
+
+ int type = typeName.toInt();
+
+ QSqlQuery q = createQuery();
+ q.setForwardOnly(true);
+ #if (QT_VERSION-0 >= 0x030000)
+ if ((type & (int)QSql::Tables) && (type & (int)QSql::Views))
+ q.exec("SELECT name FROM sqlite_master WHERE type='table' OR type='view'");
+ else if (typeName.isEmpty() || (type & (int)QSql::Tables))
+ q.exec("SELECT name FROM sqlite_master WHERE type='table'");
+ else if (type & (int)QSql::Views)
+ q.exec("SELECT name FROM sqlite_master WHERE type='view'");
+ #else
+ q.exec("SELECT name FROM sqlite_master WHERE type='table' OR type='view'");
+ #endif
+
+ if (q.isActive()) {
+ while(q.next()) {
+ res.append(q.value(0).toString());
+ }
+ }
+
+ #if (QT_VERSION-0 >= 0x030000)
+ if (type & (int)QSql::SystemTables) {
+ // there are no internal tables beside this one:
+ res.append("sqlite_master");
+ }
+ #endif
+
+ return res;
+}
+
+QSqlIndex KreSQLiteDriver::primaryIndex(const QString &tblname) const
+{
+ QSqlRecordInfo rec(recordInfo(tblname)); // expensive :(
+
+ if (!isOpen())
+ return QSqlIndex();
+
+ QSqlQuery q = createQuery();
+ q.setForwardOnly(true);
+ // finrst find a UNIQUE INDEX
+ q.exec("PRAGMA index_list('" + tblname + "');");
+ QString indexname;
+ while(q.next()) {
+ if (q.value(2).toInt()==1) {
+ indexname = q.value(1).toString();
+ break;
+ }
+ }
+ if (indexname.isEmpty())
+ return QSqlIndex();
+
+ q.exec("PRAGMA index_info('" + indexname + "');");
+
+ QSqlIndex index(tblname, indexname);
+ while(q.next()) {
+ QString name = q.value(2).toString();
+ QSqlVariant::Type type = QSqlVariant::Invalid;
+ if (rec.contains(name))
+ type = rec.find(name).type();
+ index.append(QSqlField(name, type));
+ }
+ return index;
+}
+
+#if 0
+QSqlRecordInfo KreSQLiteDriver::recordInfo(const QString &tbl) const
+{
+ if (!isOpen())
+ return QSqlRecordInfo();
+
+ QSqlQuery q = createQuery();
+ q.setForwardOnly(true);
+ q.exec("SELECT * FROM " + tbl + " LIMIT 1");
+return QSqlRecordInfo();
+// return recordInfo(q);
+}
+
+QSqlRecord KreSQLiteDriver::record(const QString &tblname) const
+{
+ if (!isOpen())
+ return QSqlRecord();
+
+ return recordInfo(tblname).toRecord();
+}
+
+QSqlRecord KreSQLiteDriver::record(const QSqlQuery& query) const
+{
+ if (query.isActive() && query.driver() == this) {
+ KreSQLiteResult* result = (KreSQLiteResult*)query.result();
+ return result->rInf.toRecord();
+ }
+ return QSqlRecord();
+}
+
+QSqlRecordInfo KreSQLiteDriver::recordInfo(const QSqlQuery& query) const
+{
+ if (query.isActive() && query.driver() == this) {
+ KreSQLiteResult* result = (KreSQLiteResult*)query.result();
+ return result->rInf;
+ }
+ return QSqlRecordInfo();
+}
+
+//this would be used below in formatValue()
+static QString escape( const QString &s )
+{
+ QString s_escaped = s;
+
+ if ( !s_escaped.isEmpty() ) { //###: sqlite_mprintf() seems to fill an empty string with garbage
+ // Escape using SQLite's function
+#if HAVE_SQLITE
+ char * escaped = sqlite_mprintf( "%q", s.latin1() ); // Escape the string(allocates memory)
+#elif HAVE_SQLITE3
+ char * escaped = sqlite3_mprintf( "%q", s.latin1() ); // Escape the string(allocates memory)
+#endif
+ s_escaped = escaped;
+#if HAVE_SQLITE
+ sqlite_freemem( escaped ); // free allocated memory
+#elif HAVE_SQLITE3
+ sqlite3_free( escaped ); // free allocated memory
+#endif
+ }
+
+ return ( s_escaped );
+}
+
+
+// Everything is considered a string given the implementation of this driver (we don't have field info). This would ruin a QByteArray (for the photo).
+QString KreSQLiteDriver::formatValue( const QSqlField* field, bool trimStrings ) const
+{
+ QString r;
+ if ( field->isNull() ) {
+ r = nullText();
+ } else {
+ switch( field->type() ) {
+ case QVariant::String:
+ case QVariant::CString: {
+ // Escape '\' characters
+ r = QSqlDriver::formatValue( field );
+ //r = escape(r);
+ //kdDebug()<<"escaping sqlite string: "<<r<<endl;
+ break;
+ }
+ default:
+ r = QSqlDriver::formatValue( field, trimStrings );
+ }
+ }
+ return r;
+}
+#endif
+
diff --git a/krecipes/src/backends/SQLite/qsql_sqlite.h b/krecipes/src/backends/SQLite/qsql_sqlite.h
new file mode 100644
index 0000000..8d19ff2
--- /dev/null
+++ b/krecipes/src/backends/SQLite/qsql_sqlite.h
@@ -0,0 +1,53 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef QSQL_SQLITE_H
+#define QSQL_SQLITE_H
+
+#include <qsqldriver.h>
+#include <qsqlresult.h>
+#include <qsqlrecord.h>
+#include <qsqlindex.h>
+
+typedef QVariant QSqlVariant;
+
+class QSQLiteDB;
+
+class KreSQLiteDriver : public QSqlDriver
+{
+public:
+ KreSQLiteDriver(QObject * parent = 0, const char * name = 0);
+ KreSQLiteDriver(QSQLiteDB *connection, QObject *parent = 0, const char *name = 0);
+ ~KreSQLiteDriver();
+
+ bool hasFeature( DriverFeature ) const;
+ bool open( const QString&,
+ const QString&,
+ const QString&,
+ const QString&,
+ int );
+ void close();
+ QSqlQuery createQuery() const;
+
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+ QStringList tables(const QString &typeName) const;
+ QSqlIndex primaryIndex(const QString &tblname) const;
+ //QSqlRecordInfo recordInfo(const QString &tbl) const;
+ //QSqlRecord record(const QString &tblname) const;
+ //QString formatValue( const QSqlField* field, bool trimStrings ) const;
+
+private:
+ friend class KreSQLiteResult;
+
+ QSQLiteDB *db;
+};
+#endif
diff --git a/krecipes/src/backends/progressinterface.cpp b/krecipes/src/backends/progressinterface.cpp
new file mode 100644
index 0000000..7d2a7bd
--- /dev/null
+++ b/krecipes/src/backends/progressinterface.cpp
@@ -0,0 +1,79 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "progressinterface.h"
+
+#include <qapplication.h>
+
+#include <kprogress.h>
+
+#include "recipedb.h"
+
+ProgressInterface::ProgressInterface( QWidget *parent ) : progress_dlg(0), database(0), m_rate(1), m_rate_at(0)
+{
+ slot_obj = new ProgressSlotObject(parent,this);
+}
+
+ProgressInterface::~ProgressInterface()
+{
+ listenOn(0);
+ delete slot_obj;
+}
+
+void ProgressInterface::progressBegin( int steps, const QString &caption, const QString &text, int rate )
+{
+ m_rate = rate;
+
+ progress_dlg = new KProgressDialog((QWidget*)slot_obj->parent(),0,caption,text,true);
+
+ if ( steps == 0 )
+ progress_dlg->progressBar()->setPercentageVisible(false);
+ progress_dlg->progressBar()->setTotalSteps( steps );
+}
+
+void ProgressInterface::progressDone()
+{
+ delete progress_dlg;
+ progress_dlg = 0;
+
+ m_rate_at = 0;
+}
+
+void ProgressInterface::progress()
+{
+ if ( progress_dlg->wasCancelled() ) {
+ database->cancelOperation();
+ }
+ else {
+ ++m_rate_at;
+
+ if ( m_rate_at % m_rate == 0 ) {
+ progress_dlg->progressBar()->advance(1);
+ m_rate_at = 0;
+ }
+ qApp->processEvents();
+ }
+}
+
+void ProgressInterface::listenOn( RecipeDB *db )
+{
+ if ( database)
+ database->disconnect(slot_obj);
+
+ if ( db ) {
+ slot_obj->connect( db, SIGNAL(progressBegin(int,const QString&,const QString&,int)), slot_obj, SLOT(progressBegin(int,const QString&,const QString&,int)) );
+ slot_obj->connect( db, SIGNAL(progressDone()), slot_obj, SLOT(progressDone()) );
+ slot_obj->connect( db, SIGNAL(progress()), slot_obj, SLOT(progress()) );
+ }
+
+ database = db;
+}
+
+#include "progressinterface.moc"
diff --git a/krecipes/src/backends/progressinterface.h b/krecipes/src/backends/progressinterface.h
new file mode 100644
index 0000000..53c90fe
--- /dev/null
+++ b/krecipes/src/backends/progressinterface.h
@@ -0,0 +1,70 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PROGRESSINTERFACE_H
+#define PROGRESSINTERFACE_H
+
+#include <qobject.h>
+#include <qwidget.h>
+
+class KProgressDialog;
+
+class RecipeDB;
+class ProgressSlotObject;
+
+/** This class is used to monitor events from the database that may take
+ * awhile. Before a potentially long operation, the database will
+ * call progressBegin(), progress() a number of times, and then progressDone().
+ *
+ * This class may be subclassed to perform certain operations during long
+ * operations by implementing the three virtual functions. The default
+ * implementation displays a progress bar dialog.
+ */
+class ProgressInterface
+{
+public:
+ ProgressInterface( QWidget *parent );
+ ~ProgressInterface();
+
+ void listenOn( RecipeDB* );
+
+protected:
+ friend class ProgressSlotObject;
+
+ virtual void progressBegin(int,const QString &caption,const QString &text,int rate);
+ virtual void progressDone();
+ virtual void progress();
+
+private:
+ ProgressSlotObject *slot_obj;
+ KProgressDialog *progress_dlg;
+ RecipeDB *database;
+
+ int m_rate;
+ int m_rate_at;
+};
+
+class ProgressSlotObject : public QObject
+{
+Q_OBJECT
+
+public:
+ ProgressSlotObject( QWidget*parent, ProgressInterface *p ) : QObject(parent), pInterface(p){}
+
+public slots:
+ void progressBegin(int i,const QString &caption=QString::null,const QString &text=QString::null,int rate=1){ pInterface->progressBegin(i,caption,text,rate); }
+ void progressDone(){ pInterface->progressDone(); }
+ void progress(){ pInterface->progress(); }
+
+private:
+ ProgressInterface *pInterface;
+};
+
+#endif
diff --git a/krecipes/src/backends/qsqlrecipedb.cpp b/krecipes/src/backends/qsqlrecipedb.cpp
new file mode 100644
index 0000000..1192f43
--- /dev/null
+++ b/krecipes/src/backends/qsqlrecipedb.cpp
@@ -0,0 +1,2775 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2004-2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <stdlib.h>
+
+#include "qsqlrecipedb.h"
+#include "datablocks/categorytree.h"
+#include "datablocks/rating.h"
+#include "datablocks/weight.h"
+
+#include "propertycalculator.h"
+
+#include <qbuffer.h>
+#include <qtextcodec.h>
+#include <qvariant.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <ktempfile.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kmdcodec.h>
+
+int QSqlRecipeDB::m_refCount = 0;
+
+QSqlRecipeDB::QSqlRecipeDB( const QString &host, const QString &user, const QString &pass, const QString &name, int port ) : RecipeDB(),
+ connectionName("connection" + QString::number( m_refCount+1 ))
+{
+ DBuser = user;
+ DBpass = pass;
+ DBhost = host;
+ DBname = name;
+ DBport = port;
+
+ dbOK = false; //it isn't ok until we've connect()'ed
+ ++m_refCount;
+
+ QTextCodec::setCodecForCStrings(QTextCodec::codecForName("Latin1")); //this is the default, but let's explicitly set this to be sure
+}
+
+QSqlRecipeDB::~QSqlRecipeDB()
+{
+ if ( dbOK ) {
+ database->close();
+ }
+
+ QSqlDatabase::removeDatabase( connectionName );
+ --m_refCount;
+}
+
+void QSqlRecipeDB::connect( bool create_db, bool create_tables )
+{
+ kdDebug() << i18n( "QSqlRecipeDB: Opening Database..." ) << endl;
+ kdDebug() << "Parameters: \n\thost: " << DBhost << "\n\tuser: " << DBuser << "\n\ttable: " << DBname << endl;
+
+ bool driver_found = false;
+
+ if ( qsqlDriver() ) //we're using a built-in driver
+ driver_found = true;
+ else {
+ QStringList drivers = QSqlDatabase::drivers();
+ for ( QStringList::const_iterator it = drivers.begin(); it != drivers.end(); ++it ) {
+ if ( ( *it ) == qsqlDriverPlugin() ) {
+ driver_found = true;
+ break;
+ }
+ }
+ }
+
+ if ( !driver_found ) {
+ dbErr = QString( i18n( "The Qt database plug-in (%1) is not installed. This plug-in is required for using this database backend." ) ).arg( qsqlDriverPlugin() );
+ return ;
+ }
+
+ //we need to have a unique connection name for each QSqlRecipeDB class as multiple db's may be open at once (db to db transfer)
+ if ( qsqlDriver() )
+ database = QSqlDatabase::addDatabase( qsqlDriver(), connectionName );
+ else if ( !qsqlDriverPlugin().isEmpty() )
+ database = QSqlDatabase::addDatabase( qsqlDriverPlugin(), connectionName );
+ else
+ kdDebug()<<"Fatal internal error! Backend incorrectly written!"<<endl;
+
+ database->setDatabaseName( DBname );
+ if ( !( DBuser.isNull() ) )
+ database->setUserName( DBuser );
+ if ( !( DBpass.isNull() ) )
+ database->setPassword( DBpass );
+ database->setHostName( DBhost );
+ if ( DBport > 0 )
+ database->setPort(DBport);
+
+ kdDebug() << i18n( "Parameters set. Calling db->open()" ) << endl;
+
+ if ( !database->open() ) {
+ //Try to create the database
+ if ( create_db ) {
+ kdDebug() << i18n( "Failing to open database. Trying to create it" ) << endl;
+ createDB();
+ }
+ else {
+ // Handle the error (passively)
+ dbErr = QString( i18n( "Krecipes could not open the database using the driver '%2' (with username: \"%1\"). You may not have the necessary permissions, or the server may be down." ) ).arg( DBuser ).arg( qsqlDriverPlugin() );
+ }
+
+ //Now Reopen the Database and signal & exit if it fails
+ if ( !database->open() ) {
+ QString error = i18n( "Database message: %1" ).arg( database->lastError().databaseText() );
+ kdDebug() << i18n( "Failing to open database. Exiting\n" ).latin1();
+
+ // Handle the error (passively)
+ dbErr = QString( i18n( "Krecipes could not open the database using the driver '%2' (with username: \"%1\"). You may not have the necessary permissions, or the server may be down." ) ).arg( DBuser ).arg( qsqlDriverPlugin() );
+ return ;
+ }
+ }
+
+ if ( int( qRound( databaseVersion() * 1e5 ) ) > int( qRound( latestDBVersion() * 1e5 ) ) ) { //correct for float's imprecision
+ dbErr = i18n( "This database was created with a newer version of Krecipes and cannot be opened." );
+ return ;
+ }
+
+ // Check integrity of the database (tables). If not possible, exit
+ // Because checkIntegrity() will create tables if they don't exist,
+ // we don't want to run this when creating the database. We would be
+ // logged in as another user (usually the superuser and not have ownership of the tables
+ if ( create_tables && !checkIntegrity() ) {
+ dbErr = i18n( "Failed to fix database structure.\nIf you are using SQLite, this is often caused by using an SQLite 2 database with SQLite 3 installed. If this is the case, make sure both SQLite 2 and 3 are installed, and then run 'krecipes --convert-sqlite3' to update your database to the new structure." );
+ return;
+ }
+
+ // Database was opened correctly
+ m_query = QSqlQuery( QString::null, database );
+ m_query.setForwardOnly(true);
+ dbOK = true;
+}
+
+void QSqlRecipeDB::execSQL( const QString &command )
+{
+ database->exec( command );
+}
+
+void QSqlRecipeDB::loadRecipes( RecipeList *rlist, int items, QValueList<int> ids )
+{
+ // Empty the recipe first
+ rlist->empty();
+
+ QMap <int, RecipeList::Iterator> recipeIterators; // Stores the iterator of each recipe in the list;
+
+ QString command;
+
+ QString current_timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
+
+ QStringList ids_str;
+ for ( QValueList<int>::const_iterator it = ids.begin(); it != ids.end(); ++it ) {
+ QString number_str = QString::number(*it);
+ ids_str << number_str;
+
+ if ( !(items & RecipeDB::Noatime) )
+ database->exec( "UPDATE recipes SET ctime=ctime,mtime=mtime,atime='"+current_timestamp+"' WHERE id="+number_str );
+ }
+
+ // Read title, author, yield, and instructions as specified
+ command = "SELECT id";
+ if ( items & RecipeDB::Title ) command += ",title";
+ if ( items & RecipeDB::Instructions ) command += ",instructions";
+ if ( items & RecipeDB::PrepTime ) command += ",prep_time";
+ if ( items & RecipeDB::Yield ) command += ",yield_amount,yield_amount_offset,yield_type_id";
+ command += " FROM recipes"+(ids_str.count()!=0?" WHERE id IN ("+ids_str.join(",")+")":"");
+
+ QSqlQuery recipeQuery(command,database);
+ if ( recipeQuery.isActive() ) {
+ while ( recipeQuery.next() ) {
+ int row_at = 0;
+
+ Recipe recipe;
+ recipe.recipeID = recipeQuery.value( row_at ).toInt(); ++row_at;
+
+ if ( items & RecipeDB::Title ) {
+ recipe.title = unescapeAndDecode( recipeQuery.value( row_at ).toCString() ); ++row_at;
+ }
+
+ if ( items & RecipeDB::Instructions ) {
+ recipe.instructions = unescapeAndDecode( recipeQuery.value( row_at ).toCString() ); ++row_at;
+ }
+
+ if ( items & RecipeDB::PrepTime ) {
+ recipe.prepTime = recipeQuery.value( row_at ).toTime(); ++row_at;
+ }
+
+ if ( items & RecipeDB::Yield ) {
+ recipe.yield.amount = recipeQuery.value( row_at ).toDouble(); ++row_at;
+ recipe.yield.amount_offset = recipeQuery.value( row_at ).toDouble(); ++row_at;
+ recipe.yield.type_id = recipeQuery.value( row_at ).toInt(); ++row_at;
+ if ( recipe.yield.type_id != -1 ) {
+ QString y_command = QString("SELECT name FROM yield_types WHERE id=%1;").arg(recipe.yield.type_id);
+ QSqlQuery yield_query(y_command,database);
+ if ( yield_query.isActive() && yield_query.first() )
+ recipe.yield.type = unescapeAndDecode(yield_query.value( 0 ).toCString());
+ else
+ kdDebug()<<yield_query.lastError().databaseText()<<endl;
+ }
+ }
+
+ if ( items & RecipeDB::Meta )
+ loadRecipeMetadata(&recipe);
+
+ recipeIterators[ recipe.recipeID ] = rlist->append( recipe );
+ }
+ }
+
+ // Read the ingredients
+ if ( items & RecipeDB::Ingredients ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ if ( items & RecipeDB::NamesOnly ) {
+ if ( items & IngredientAmounts )
+ command = QString( "SELECT il.ingredient_id,i.name,il.substitute_for,il.amount,il.amount_offset,u.id,u.type FROM ingredients i, ingredient_list il, units u WHERE il.recipe_id=%1 AND i.id=il.ingredient_id AND u.id=il.unit_id ORDER BY il.order_index" ).arg( (*recipe_it).recipeID );
+ else
+ command = QString( "SELECT il.ingredient_id,i.name,il.substitute_for FROM ingredients i, ingredient_list il WHERE il.recipe_id=%1 AND i.id=il.ingredient_id" ).arg( (*recipe_it).recipeID );
+ }
+ else
+ command = QString( "SELECT il.ingredient_id,i.name,il.substitute_for,il.amount,il.amount_offset,u.id,u.name,u.plural,u.name_abbrev,u.plural_abbrev,u.type,il.group_id,il.id FROM ingredients i, ingredient_list il, units u WHERE il.recipe_id=%1 AND i.id=il.ingredient_id AND u.id=il.unit_id ORDER BY il.order_index" ).arg( (*recipe_it).recipeID );
+
+ QSqlQuery ingredientQuery( command, database );
+ if ( ingredientQuery.isActive() ) {
+ RecipeList::Iterator it = recipeIterators[ (*recipe_it).recipeID ];
+ while ( ingredientQuery.next() ) {
+ Ingredient ing;
+ ing.ingredientID = ingredientQuery.value( 0 ).toInt();
+ ing.name = unescapeAndDecode( ingredientQuery.value( 1 ).toCString() );
+
+ if ( items & RecipeDB::NamesOnly ) {
+ if ( items & IngredientAmounts ) {
+ ing.amount = ingredientQuery.value( 3 ).toDouble();
+ ing.amount_offset = ingredientQuery.value( 4 ).toDouble();
+ ing.units.id = ingredientQuery.value( 5 ).toInt();
+ ing.units.type = (Unit::Type)ingredientQuery.value( 6 ).toInt();
+ }
+ }
+ else {
+ ing.amount = ingredientQuery.value( 3 ).toDouble();
+ ing.amount_offset = ingredientQuery.value( 4 ).toDouble();
+ ing.units.id = ingredientQuery.value( 5 ).toInt();
+ ing.units.name = unescapeAndDecode( ingredientQuery.value( 6 ).toCString() );
+ ing.units.plural = unescapeAndDecode( ingredientQuery.value( 7 ).toCString() );
+ ing.units.name_abbrev = unescapeAndDecode( ingredientQuery.value( 8 ).toCString() );
+ ing.units.plural_abbrev = unescapeAndDecode( ingredientQuery.value( 9 ).toCString() );
+ ing.units.type = (Unit::Type)ingredientQuery.value( 10 ).toInt();
+
+ //if we don't have both name and plural, use what we have as both
+ if ( ing.units.name.isEmpty() )
+ ing.units.name = ing.units.plural;
+ else if ( ing.units.plural.isEmpty() )
+ ing.units.plural = ing.units.name;
+
+ ing.groupID = ingredientQuery.value( 11 ).toInt();
+ if ( ing.groupID != -1 ) {
+ QSqlQuery toLoad( QString( "SELECT name FROM ingredient_groups WHERE id=%1" ).arg( ing.groupID ), database );
+ if ( toLoad.isActive() && toLoad.first() )
+ ing.group = unescapeAndDecode( toLoad.value( 0 ).toCString() );
+ }
+
+ command = QString("SELECT pl.prep_method_id,p.name FROM prep_methods p, prep_method_list pl WHERE pl.ingredient_list_id=%1 AND p.id=pl.prep_method_id ORDER BY pl.order_index;").arg(ingredientQuery.value( 12 ).toInt());
+ QSqlQuery ingPrepMethodsQuery( command, database );
+ if ( ingPrepMethodsQuery.isActive() ) {
+ while ( ingPrepMethodsQuery.next() ) {
+ ing.prepMethodList.append( Element( unescapeAndDecode(ingPrepMethodsQuery.value(1).toCString()),ingPrepMethodsQuery.value(0).toInt()) );
+ }
+ }
+ }
+
+ if ( ingredientQuery.value( 2 ).toInt() > 0 ) {
+ //given the ordering, we can assume substitute_for is the id of the last
+ //ingredient in the list
+ //int substitute_for = ingredientQuery.value( 2 ).toInt();
+ (*it).ingList.last().substitutes.append( ing );
+ }
+ else
+ (*it).ingList.append( ing );
+ }
+ }
+ }
+ }
+
+ //Load the Image
+ if ( items & RecipeDB::Photo ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+ loadPhoto( (*it).recipeID, (*it).photo );
+ }
+ }
+
+ //Load the category list
+ if ( items & RecipeDB::Categories ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+
+ command = QString( "SELECT cl.category_id,c.name FROM category_list cl, categories c WHERE recipe_id=%1 AND cl.category_id=c.id;" ).arg( (*it).recipeID );
+
+ m_query.exec( command );
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element el;
+ el.id = m_query.value( 0 ).toInt();
+ el.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ (*it).categoryList.append( el );
+ }
+ }
+ }
+ }
+
+ //Load the author list
+ if ( items & RecipeDB::Authors ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+
+ command = QString( "SELECT al.author_id,a.name FROM author_list al, authors a WHERE recipe_id=%1 AND al.author_id=a.id;" ).arg( (*it).recipeID );
+
+ m_query.exec( command );
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element el;
+ el.id = m_query.value( 0 ).toInt();
+ el.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ (*it).authorList.append( el );
+ }
+ }
+ }
+ }
+
+ //Load the ratings
+ if ( items & RecipeDB::Ratings ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+
+ command = QString( "SELECT id,comment,rater FROM ratings WHERE recipe_id=%1 ORDER BY created DESC" ).arg( (*it).recipeID );
+ QSqlQuery query( command, database );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Rating r;
+ r.id = query.value( 0 ).toInt();
+ r.comment = unescapeAndDecode( query.value( 1 ).toCString() );
+ r.rater = unescapeAndDecode( query.value( 2 ).toCString() );
+
+ command = QString( "SELECT rc.id,rc.name,rl.stars FROM rating_criteria rc, rating_criterion_list rl WHERE rating_id=%1 AND rl.rating_criterion_id=rc.id" ).arg(r.id);
+ QSqlQuery criterionQuery( command, database );
+ if ( criterionQuery.isActive() ) {
+ while ( criterionQuery.next() ) {
+ RatingCriteria rc;
+ rc.id = criterionQuery.value( 0 ).toInt();
+ rc.name = unescapeAndDecode( criterionQuery.value( 1 ).toCString() );
+ rc.stars = criterionQuery.value( 2 ).toDouble();
+ r.append( rc );
+ }
+ }
+
+ (*it).ratingList.append( r );
+ }
+ }
+ }
+ }
+
+ if ( items & RecipeDB::Properties ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+ calculateProperties( *it, this );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadIngredientGroups( ElementList *list )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM ingredient_groups ORDER BY name;";
+ m_query.exec( command );
+
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element group;
+ group.id = m_query.value( 0 ).toInt();
+ group.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( group );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadIngredients( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM ingredients ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ m_query.exec( command );
+
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element ing;
+ ing.id = m_query.value( 0 ).toInt();
+ ing.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( ing );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadPrepMethods( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM prep_methods ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ m_query.exec( command );
+
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element prep_method;
+ prep_method.id = m_query.value( 0 ).toInt();
+ prep_method.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( prep_method );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadYieldTypes( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM yield_types ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ m_query.exec( command );
+
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element el;
+ el.id = m_query.value( 0 ).toInt();
+ el.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( el );
+ }
+ }
+}
+
+void QSqlRecipeDB::createNewPrepMethod( const QString &prepMethodName )
+{
+ QString command;
+ QString real_name = prepMethodName.left( maxPrepMethodNameLength() );
+
+ command = QString( "INSERT INTO prep_methods VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "prep_methods", "id" ) );
+ QSqlQuery prepMethodToCreate( command, database );
+
+ emit prepMethodCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::modPrepMethod( int prepMethodID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE prep_methods SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( prepMethodID );
+ QSqlQuery prepMethodToCreate( command, database );
+
+ emit prepMethodRemoved( prepMethodID );
+ emit prepMethodCreated( Element( newLabel, prepMethodID ) );
+}
+
+void QSqlRecipeDB::modProperty( int propertyID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE ingredient_properties SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( propertyID );
+ QSqlQuery createQuery( command, database );
+
+ emit propertyRemoved( propertyID );
+ emit propertyCreated( propertyName( propertyID ) );
+}
+
+void QSqlRecipeDB::loadPossibleUnits( int ingredientID, UnitList *list )
+{
+ list->clear();
+
+ QString command;
+
+ command = QString( "SELECT u.id,u.name,u.plural,u.name_abbrev,u.plural_abbrev,u.type FROM unit_list ul, units u WHERE ul.ingredient_id=%1 AND ul.unit_id=u.id;" ).arg( ingredientID );
+
+ QSqlQuery unitToLoad( command, database );
+
+ if ( unitToLoad.isActive() ) {
+ while ( unitToLoad.next() ) {
+ Unit unit;
+ unit.id = unitToLoad.value( 0 ).toInt();
+ unit.name = unescapeAndDecode( unitToLoad.value( 1 ).toCString() );
+ unit.plural = unescapeAndDecode( unitToLoad.value( 2 ).toCString() );
+ unit.name_abbrev = unescapeAndDecode( unitToLoad.value( 3 ).toCString() );
+ unit.plural_abbrev = unescapeAndDecode( unitToLoad.value( 4 ).toCString() );
+ unit.type = (Unit::Type) unitToLoad.value( 5 ).toInt();
+
+ list->append( unit );
+ }
+ }
+
+
+}
+
+void QSqlRecipeDB::storePhoto( int recipeID, const QByteArray &data )
+{
+ QSqlQuery query( QString::null, database );
+
+ query.prepare( "UPDATE recipes SET photo=?,ctime=ctime,atime=atime,mtime=mtime WHERE id=" + QString::number( recipeID ) );
+ query.addBindValue( KCodecs::base64Encode( data ) );
+ query.exec();
+}
+
+void QSqlRecipeDB::loadPhoto( int recipeID, QPixmap &photo )
+{
+ QString command = QString( "SELECT photo FROM recipes WHERE id=%1;" ).arg( recipeID );
+ QSqlQuery query( command, database );
+
+ if ( query.isActive() && query.first() ) {
+ QCString decodedPic;
+ QPixmap pix;
+ KCodecs::base64Decode( query.value( 0 ).toCString(), decodedPic );
+ int len = decodedPic.size();
+
+ if ( len > 0 ) {
+ QByteArray picData( len );
+ memcpy( picData.data(), decodedPic.data(), len );
+
+ bool ok = pix.loadFromData( picData, "JPEG" );
+ if ( ok )
+ photo = pix;
+ }
+ }
+}
+
+void QSqlRecipeDB::loadRecipeMetadata( Recipe *recipe )
+{
+ QString command = "SELECT ctime,mtime,atime FROM recipes WHERE id="+QString::number(recipe->recipeID);
+
+ QSqlQuery query( command, database );
+ if ( query.isActive() && query.first() ) {
+ recipe->ctime = query.value(0).toDateTime();
+ recipe->mtime = query.value(1).toDateTime();
+ recipe->atime = query.value(2).toDateTime();
+ }
+}
+
+void QSqlRecipeDB::saveRecipe( Recipe *recipe )
+{
+ // Check if it's a new recipe or it exists (supossedly) already.
+
+ bool newRecipe;
+ newRecipe = ( recipe->recipeID == -1 );
+ // First check if the recipe ID is set, if so, update (not create)
+ // Be carefull, first check if the recipe hasn't been deleted while changing.
+
+ QSqlQuery recipeToSave( QString::null, database );
+
+ QString command;
+
+ QDateTime current_datetime = QDateTime::currentDateTime();
+ QString current_timestamp = current_datetime.toString(Qt::ISODate);
+ if ( newRecipe ) {
+ command = QString( "INSERT INTO recipes VALUES ("+getNextInsertIDStr("recipes","id")+",'%1',%2,'%3','%4','%5',NULL,'%6','%7','%8','%9');" ) // Id is autoincremented
+ .arg( escapeAndEncode( recipe->title ) )
+ .arg( recipe->yield.amount )
+ .arg( recipe->yield.amount_offset )
+ .arg( recipe->yield.type_id )
+ .arg( escapeAndEncode( recipe->instructions ) )
+ .arg( recipe->prepTime.toString( "hh:mm:ss" ) )
+ .arg( current_timestamp )
+ .arg( current_timestamp )
+ .arg( current_timestamp );
+ recipe->mtime = recipe->ctime = recipe->atime = current_datetime;
+ }
+ else {
+ command = QString( "UPDATE recipes SET title='%1',yield_amount='%2',yield_amount_offset='%3',yield_type_id='%4',instructions='%5',prep_time='%6',mtime='%8',ctime=ctime,atime=atime WHERE id=%7;" )
+ .arg( escapeAndEncode( recipe->title ) )
+ .arg( recipe->yield.amount )
+ .arg( recipe->yield.amount_offset )
+ .arg( recipe->yield.type_id )
+ .arg( escapeAndEncode( recipe->instructions ) )
+ .arg( recipe->prepTime.toString( "hh:mm:ss" ) )
+ .arg( recipe->recipeID )
+ .arg( current_timestamp );
+ recipe->mtime = current_datetime;
+ }
+ recipeToSave.exec( command );
+
+ if ( !newRecipe ) {
+ // Clean up yield_types which have no recipe that they belong to
+ QStringList ids;
+ command = QString( "SELECT DISTINCT(yield_type_id) FROM recipes" );
+ recipeToSave.exec( command );
+ if ( recipeToSave.isActive() ) {
+ while ( recipeToSave.next() ) {
+ if ( recipeToSave.value( 0 ).toInt() != -1 )
+ ids << QString::number( recipeToSave.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM yield_types WHERE id NOT IN ( %1 )" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ recipeToSave.exec( command );
+ }
+
+ // If it's a new recipe, identify the ID that was given to the recipe and store in the Recipe itself
+ int recipeID;
+ if ( newRecipe ) {
+ recipeID = lastInsertID();
+ recipe->recipeID = recipeID;
+ }
+ recipeID = recipe->recipeID;
+ loadRecipeMetadata(recipe);
+
+ // Let's begin storing the Image!
+ if ( !recipe->photo.isNull() ) {
+ QByteArray ba;
+ QBuffer buffer( ba );
+ buffer.open( IO_WriteOnly );
+ QImageIO iio( &buffer, "JPEG" );
+ iio.setImage( recipe->photo.convertToImage() );
+ iio.write();
+ //recipe->photo.save( &buffer, "JPEG" ); don't need QImageIO in QT 3.2
+
+ storePhoto( recipeID, ba );
+ }
+ else {
+ recipeToSave.exec( "UPDATE recipes SET photo=NULL, mtime=mtime, ctime=ctime, atime=atime WHERE id=" + QString::number( recipeID ) );
+ }
+
+ // Save the ingredient list (first delete if we are updating)
+ command = QString( "SELECT id FROM ingredient_list WHERE recipe_id=%1" ).arg(recipeID);
+ recipeToSave.exec( command );
+ if ( recipeToSave.isActive() ) {
+ while ( recipeToSave.next() ) {
+ command = QString("DELETE FROM prep_method_list WHERE ingredient_list_id=%1")
+ .arg(recipeToSave.value(0).toInt());
+ database->exec(command);
+ }
+ }
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id=%1;" )
+ .arg( recipeID );
+ recipeToSave.exec( command );
+
+ int order_index = 0;
+ for ( IngredientList::const_iterator ing_it = recipe->ingList.begin(); ing_it != recipe->ingList.end(); ++ing_it ) {
+ order_index++;
+ QString ing_list_id_str = getNextInsertIDStr("ingredient_list","id");
+ command = QString( "INSERT INTO ingredient_list VALUES (%1,%2,%3,%4,%5,%6,%7,%8,NULL);" )
+ .arg( ing_list_id_str )
+ .arg( recipeID )
+ .arg( ( *ing_it ).ingredientID )
+ .arg( ( *ing_it ).amount )
+ .arg( ( *ing_it ).amount_offset )
+ .arg( ( *ing_it ).units.id )
+ .arg( order_index )
+ .arg( ( *ing_it ).groupID );
+ recipeToSave.exec( command );
+
+ int ing_list_id = lastInsertID();
+ int prep_order_index = 0;
+ for ( ElementList::const_iterator prep_it = (*ing_it).prepMethodList.begin(); prep_it != (*ing_it).prepMethodList.end(); ++prep_it ) {
+ prep_order_index++;
+ command = QString( "INSERT INTO prep_method_list VALUES (%1,%2,%3);" )
+ .arg( ing_list_id )
+ .arg( ( *prep_it ).id )
+ .arg( prep_order_index );
+ recipeToSave.exec( command );
+ }
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) {
+ order_index++;
+ QString ing_list_id_str = getNextInsertIDStr("ingredient_list","id");
+ command = QString( "INSERT INTO ingredient_list VALUES (%1,%2,%3,%4,%5,%6,%7,%8,%9);" )
+ .arg( ing_list_id_str )
+ .arg( recipeID )
+ .arg( ( *sub_it ).ingredientID )
+ .arg( ( *sub_it ).amount )
+ .arg( ( *sub_it ).amount_offset )
+ .arg( ( *sub_it ).units.id )
+ .arg( order_index )
+ .arg( ( *sub_it ).groupID )
+ .arg( (*ing_it).ingredientID );
+ recipeToSave.exec( command );
+
+ int ing_list_id = lastInsertID();
+ int prep_order_index = 0;
+ for ( ElementList::const_iterator prep_it = (*sub_it).prepMethodList.begin(); prep_it != (*sub_it).prepMethodList.end(); ++prep_it ) {
+ prep_order_index++;
+ command = QString( "INSERT INTO prep_method_list VALUES (%1,%2,%3);" )
+ .arg( ing_list_id )
+ .arg( ( *prep_it ).id )
+ .arg( prep_order_index );
+ recipeToSave.exec( command );
+ }
+ }
+ }
+
+ // Save the category list for the recipe (first delete, in case we are updating)
+ command = QString( "DELETE FROM category_list WHERE recipe_id=%1;" )
+ .arg( recipeID );
+ recipeToSave.exec( command );
+
+ ElementList::const_iterator cat_it = recipe->categoryList.end(); // Start from last, mysql seems to work in lifo format... so it's read first the latest inserted one (newest)
+ --cat_it;
+ for ( unsigned int i = 0; i < recipe->categoryList.count(); i++ ) {
+ command = QString( "INSERT INTO category_list VALUES (%1,%2);" )
+ .arg( recipeID )
+ .arg( ( *cat_it ).id );
+ recipeToSave.exec( command );
+
+ --cat_it;
+ }
+
+ //Add the default category -1 to ease and speed up searches
+
+ command = QString( "INSERT INTO category_list VALUES (%1,-1);" )
+ .arg( recipeID );
+ recipeToSave.exec( command );
+
+
+ // Save the author list for the recipe (first delete, in case we are updating)
+ command = QString( "DELETE FROM author_list WHERE recipe_id=%1;" )
+ .arg( recipeID );
+ recipeToSave.exec( command );
+
+ ElementList::const_iterator author_it = recipe->authorList.end(); // Start from last, mysql seems to work in lifo format... so it's read first the latest inserted one (newest)
+ --author_it;
+ for ( unsigned int i = 0; i < recipe->authorList.count(); i++ ) {
+ command = QString( "INSERT INTO author_list VALUES (%1,%2);" )
+ .arg( recipeID )
+ .arg( ( *author_it ).id );
+ recipeToSave.exec( command );
+
+ --author_it;
+ }
+
+ // Save the ratings (first delete criterion list if we are updating)
+ command = QString( "SELECT id FROM ratings WHERE recipe_id=%1" ).arg(recipeID);
+ recipeToSave.exec( command );
+ if ( recipeToSave.isActive() ) {
+ while ( recipeToSave.next() ) {
+
+ command = QString("DELETE FROM rating_criterion_list WHERE rating_id=%1")
+ .arg(recipeToSave.value(0).toInt());
+ database->exec(command);
+ }
+ }
+
+ QStringList ids;
+
+ for ( RatingList::iterator rating_it = recipe->ratingList.begin(); rating_it != recipe->ratingList.end(); ++rating_it ) {
+ //double average = (*rating_it).average();
+ if ( (*rating_it).id == -1 ) //new rating
+ command ="INSERT INTO ratings VALUES("+QString(getNextInsertIDStr("ratings","id"))+","+QString::number(recipeID)+",'"+QString(escapeAndEncode((*rating_it).comment))+"','"+QString(escapeAndEncode((*rating_it).rater))+/*"','"+QString::number(average)+*/"','"+current_timestamp+"')";
+ else //existing rating
+ command = "UPDATE ratings SET "
+ "comment='"+QString(escapeAndEncode((*rating_it).comment))+"',"
+ "rater='"+QString(escapeAndEncode((*rating_it).rater))+"',"
+ "created=created "
+ "WHERE id="+QString::number((*rating_it).id);
+
+ recipeToSave.exec( command );
+
+ if ( (*rating_it).id == -1 )
+ (*rating_it).id = lastInsertID();
+
+ for ( QValueList<RatingCriteria>::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ command = QString( "INSERT INTO rating_criterion_list VALUES("+QString::number((*rating_it).id)+","+QString::number((*rc_it).id)+","+QString::number((*rc_it).stars)+")" );
+ recipeToSave.exec( command );
+ }
+
+ ids << QString::number((*rating_it).id);
+ }
+
+ // only delete those ratings that don't exist anymore
+ command = QString( "DELETE FROM ratings WHERE recipe_id=%1 AND id NOT IN( %2 )" )
+ .arg( recipeID ).arg( ids.join(",") );
+ recipeToSave.exec( command );
+
+ if ( newRecipe )
+ emit recipeCreated( Element( recipe->title.left( maxRecipeTitleLength() ), recipeID ), recipe->categoryList );
+ else
+ emit recipeModified( Element( recipe->title.left( maxRecipeTitleLength() ), recipeID ), recipe->categoryList );
+}
+
+void QSqlRecipeDB::loadRecipeList( ElementList *list, int categoryID, bool recursive )
+{
+ QString command;
+
+ if ( categoryID == -1 ) // load just the list
+ command = "SELECT id,title FROM recipes;";
+ else // load the list of those in the specified category
+ command = QString( "SELECT r.id,r.title FROM recipes r,category_list cl WHERE r.id=cl.recipe_id AND cl.category_id=%1 ORDER BY r.title" ).arg( categoryID );
+
+ if ( recursive ) {
+ QSqlQuery subcategories( QString("SELECT id FROM categories WHERE parent_id='%1'").arg(categoryID), database );
+ if ( subcategories.isActive() ) {
+ while ( subcategories.next() ) {
+ loadRecipeList(list,subcategories.value( 0 ).toInt(),true);
+ }
+ }
+ }
+
+ QSqlQuery recipeToLoad( command, database );
+
+ if ( recipeToLoad.isActive() ) {
+ while ( recipeToLoad.next() ) {
+ Element recipe;
+ recipe.id = recipeToLoad.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( recipeToLoad.value( 1 ).toCString() );
+ list->append( recipe );
+ }
+ }
+}
+
+
+void QSqlRecipeDB::loadUncategorizedRecipes( ElementList *list )
+{
+ list->clear();
+
+ QString command = "SELECT r.id,r.title FROM recipes r,category_list cl WHERE r.id=cl.recipe_id GROUP BY id HAVING COUNT(*)=1 ORDER BY r.title DESC";
+ m_query.exec( command );
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element recipe;
+ recipe.id = m_query.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( recipe );
+ }
+ }
+}
+
+
+
+void QSqlRecipeDB::removeRecipe( int id )
+{
+ emit recipeRemoved( id );
+
+ QString command;
+
+ command = QString( "DELETE FROM recipes WHERE id=%1;" ).arg( id );
+ QSqlQuery recipeToRemove( command, database );
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id=%1;" ).arg( id );
+ recipeToRemove.exec( command );
+ command = QString( "DELETE FROM category_list WHERE recipe_id=%1;" ).arg( id );
+ recipeToRemove.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ QStringList ids;
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ recipeToRemove.exec( command );
+ if ( recipeToRemove.isActive() ) {
+ while ( recipeToRemove.next() ) {
+ if ( recipeToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( recipeToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ recipeToRemove.exec( command );
+
+ // Clean up yield_types which have no recipe that they belong to
+ ids.clear();
+ command = QString( "SELECT DISTINCT(yield_type_id) FROM recipes" );
+ recipeToRemove.exec( command );
+ if ( recipeToRemove.isActive() ) {
+ while ( recipeToRemove.next() ) {
+ if ( recipeToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( recipeToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM yield_types WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ recipeToRemove.exec( command );
+}
+
+void QSqlRecipeDB::removeRecipeFromCategory( int recipeID, int categoryID )
+{
+ QString command;
+ command = QString( "DELETE FROM category_list WHERE recipe_id=%1 AND category_id=%2;" ).arg( recipeID ).arg( categoryID );
+ QSqlQuery recipeToRemove( command, database );
+
+ emit recipeRemoved( recipeID, categoryID );
+}
+
+void QSqlRecipeDB::categorizeRecipe( int recipeID, const ElementList &categoryList )
+{
+ QString command;
+
+ //emit recipeRemoved( recipeID, -1 );
+
+ for ( ElementList::const_iterator it = categoryList.begin(); it != categoryList.end(); ++it ) {
+ command = QString( "INSERT INTO category_list VALUES(%1,%2)" ).arg( recipeID ).arg( (*it).id );
+ database->exec( command );
+ }
+
+ emit recipeModified( Element(recipeTitle(recipeID),recipeID), categoryList );
+}
+
+void QSqlRecipeDB::createNewIngGroup( const QString &name )
+{
+ QString command;
+ QString real_name = name.left( maxIngGroupNameLength() );
+
+ command = QString( "INSERT INTO ingredient_groups VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "ingredient_groups", "id" ) );
+ QSqlQuery query( command, database );
+
+ emit ingGroupCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::createNewIngredient( const QString &ingredientName )
+{
+ QString command;
+ QString real_name = ingredientName.left( maxIngredientNameLength() );
+
+ command = QString( "INSERT INTO ingredients VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "ingredients", "id" ) );
+ QSqlQuery ingredientToCreate( command, database );
+
+ emit ingredientCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::createNewRating( const QString &rating )
+{
+ QString command;
+ QString real_name = rating/*.left( maxIngredientNameLength() )*/;
+
+ command = QString( "INSERT INTO rating_criteria VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "rating_criteria", "id" ) );
+ QSqlQuery toCreate( command, database );
+
+ emit ratingCriteriaCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::createNewYieldType( const QString &name )
+{
+ QString command;
+ QString real_name = name.left( maxYieldTypeLength() );
+
+ command = QString( "INSERT INTO yield_types VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "yield_types", "id" ) );
+ database->exec(command);
+
+ //emit yieldTypeCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::modIngredientGroup( int groupID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE ingredient_groups SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( groupID );
+ QSqlQuery ingredientToCreate( command, database );
+
+ emit ingGroupRemoved( groupID );
+ emit ingGroupCreated( Element( newLabel, groupID ) );
+}
+
+void QSqlRecipeDB::modIngredient( int ingredientID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE ingredients SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( ingredientID );
+ QSqlQuery ingredientToCreate( command, database );
+
+ emit ingredientRemoved( ingredientID );
+ emit ingredientCreated( Element( newLabel, ingredientID ) );
+}
+
+void QSqlRecipeDB::addUnitToIngredient( int ingredientID, int unitID )
+{
+ QString command;
+
+ command = QString( "INSERT INTO unit_list VALUES(%1,%2);" ).arg( ingredientID ).arg( unitID );
+ QSqlQuery ingredientToCreate( command, database );
+}
+
+void QSqlRecipeDB::loadUnits( UnitList *list, Unit::Type type, int limit, int offset )
+{
+ list->clear();
+
+ QString command;
+
+ command = "SELECT id,name,name_abbrev,plural,plural_abbrev,type FROM units "
+ +((type==Unit::All)?"":"WHERE type="+QString::number(type))
+ +" ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+
+ QSqlQuery unitToLoad( command, database );
+
+ if ( unitToLoad.isActive() ) {
+ while ( unitToLoad.next() ) {
+ Unit unit;
+ unit.id = unitToLoad.value( 0 ).toInt();
+ unit.name = unescapeAndDecode( unitToLoad.value( 1 ).toCString() );
+ unit.name_abbrev = unescapeAndDecode( unitToLoad.value( 2 ).toCString() );
+ unit.plural = unescapeAndDecode( unitToLoad.value( 3 ).toCString() );
+ unit.plural_abbrev = unescapeAndDecode( unitToLoad.value( 4 ).toCString() );
+ unit.type = (Unit::Type)unitToLoad.value( 5 ).toInt();
+ list->append( unit );
+ }
+ }
+}
+
+void QSqlRecipeDB::removeUnitFromIngredient( int ingredientID, int unitID )
+{
+ QString command;
+
+ command = QString( "DELETE FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ QSqlQuery unitToRemove( command, database );
+
+ // Remove any recipe using that combination of ingredients also (user must have been warned before calling this function!)
+
+ command = QString( "SELECT r.id FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.ingredient_id=%1 AND il.unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ emit recipeRemoved( unitToRemove.value( 0 ).toInt() );
+ database->exec( QString( "DELETE FROM recipes WHERE id=%1;" ).arg( unitToRemove.value( 0 ).toInt() ) );
+ }
+ }
+
+ // Remove any ingredient in ingredient_list which has references to this unit and ingredient
+ command = QString( "DELETE FROM ingredient_list WHERE ingredient_id=%1 AND unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Remove any ingredient properties from ingredient_info where the this ingredient+unit is being used (user must have been warned before calling this function!)
+ command = QString( "DELETE FROM ingredient_info ii WHERE ii.ingredient_id=%1 AND ii.per_units=%2;" ).arg( ingredientID ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Clean up ingredient_list which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_list WHERE recipe_id NOT IN ( SELECT id FROM recipes );)
+ QStringList ids;
+ command = QString( "SELECT id FROM recipes;" );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ ids << QString::number( unitToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Clean up category_list which have no recipe that they belong to
+ command = QString( "DELETE FROM category_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ ids.clear();
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ if ( unitToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( unitToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+}
+
+void QSqlRecipeDB::removeIngredientGroup( int groupID )
+{
+ QString command;
+
+ // First remove the ingredient
+
+ command = QString( "DELETE FROM ingredient_groups WHERE id=%1" ).arg( groupID );
+ QSqlQuery toDelete( command, database );
+
+ // Remove all the unit entries for this ingredient
+
+ command = QString( "UPDATE ingredient_list SET group_id='-1' WHERE group_id=%1" ).arg( groupID );
+ toDelete.exec( command );
+
+ emit ingGroupRemoved( groupID );
+}
+
+void QSqlRecipeDB::removeIngredient( int ingredientID )
+{
+ QString command;
+
+ // First remove the ingredient
+
+ command = QString( "DELETE FROM ingredients WHERE id=%1;" ).arg( ingredientID );
+ QSqlQuery ingredientToDelete( command, database );
+
+ // Remove all the unit entries for this ingredient
+
+ command = QString( "DELETE FROM unit_list WHERE ingredient_id=%1;" ).arg( ingredientID );
+ ingredientToDelete.exec( command );
+
+ // Remove any recipe using that ingredient
+
+ command = QString( "SELECT r.id FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.ingredient_id=%1;" ).arg( ingredientID );
+ ingredientToDelete.exec( command );
+ if ( ingredientToDelete.isActive() ) {
+ while ( ingredientToDelete.next() ) {
+ emit recipeRemoved( ingredientToDelete.value( 0 ).toInt() );
+ database->exec( QString( "DELETE FROM recipes WHERE id=%1;" ).arg( ingredientToDelete.value( 0 ).toInt() ) );
+ }
+ }
+
+ // Remove any ingredient in ingredient_list which has references to this ingredient
+ command = QString( "DELETE FROM ingredient_list WHERE ingredient_id=%1;" ).arg( ingredientID );
+ ingredientToDelete.exec( command );
+
+ // Clean up ingredient_list which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_list WHERE recipe_id NOT IN ( SELECT id FROM recipes );)
+ QStringList ids;
+ command = QString( "SELECT id FROM recipes;" );
+ ingredientToDelete.exec( command );
+ if ( ingredientToDelete.isActive() ) {
+ while ( ingredientToDelete.next() ) {
+ ids << QString::number( ingredientToDelete.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ ingredientToDelete.exec( command );
+
+ // Clean up category_list which have no recipe that they belong to. Same method as above
+ command = QString( "DELETE FROM category_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ ingredientToDelete.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ ids.clear();
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ ingredientToDelete.exec( command );
+ if ( ingredientToDelete.isActive() ) {
+ while ( ingredientToDelete.next() ) {
+ if ( ingredientToDelete.value( 0 ).toInt() != -1 )
+ ids << QString::number( ingredientToDelete.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ ingredientToDelete.exec( command );
+
+ // Remove property list of this ingredient
+ command = QString( "DELETE FROM ingredient_info WHERE ingredient_id=%1;" ).arg( ingredientID );
+ ingredientToDelete.exec( command );
+
+ emit ingredientRemoved( ingredientID );
+}
+
+void QSqlRecipeDB::removeIngredientWeight( int id )
+{
+ QString command;
+
+ // First remove the ingredient
+
+ command = QString( "DELETE FROM ingredient_weights WHERE id=%1" ).arg( id );
+ QSqlQuery toDelete( command, database );
+}
+
+void QSqlRecipeDB::addIngredientWeight( const Weight &w )
+{
+ QString command;
+ if ( w.id != -1 ) {
+ command = QString( "UPDATE ingredient_weights SET ingredient_id=%1,amount=%2,unit_id=%3,weight=%4,weight_unit_id=%5,prep_method_id=%7 WHERE id=%6" )
+ .arg(w.ingredientID)
+ .arg(w.perAmount)
+ .arg(w.perAmountUnitID)
+ .arg(w.weight)
+ .arg(w.weightUnitID)
+ .arg(w.id)
+ .arg(w.prepMethodID);
+ }
+ else {
+ command = QString( "INSERT INTO ingredient_weights VALUES(%6,%1,%2,%3,%4,%5,%7)" )
+ .arg(w.ingredientID)
+ .arg(w.perAmount)
+ .arg(w.perAmountUnitID)
+ .arg(w.weight)
+ .arg(w.weightUnitID)
+ .arg(getNextInsertIDStr( "ingredient_weights", "id" ))
+ .arg(w.prepMethodID);
+ }
+ QSqlQuery query( command, database );
+}
+
+void QSqlRecipeDB::addProperty( const QString &name, const QString &units )
+{
+ QString command;
+ QString real_name = name.left( maxPropertyNameLength() );
+
+ command = QString( "INSERT INTO ingredient_properties VALUES(%3,'%1','%2');" )
+ .arg( escapeAndEncode( real_name ) )
+ .arg( escapeAndEncode( units ) )
+ .arg( getNextInsertIDStr( "ingredient_properties", "id" ) );
+ QSqlQuery propertyToAdd( command, database );
+
+ emit propertyCreated( IngredientProperty( real_name, units, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::loadProperties( IngredientPropertyList *list, int ingredientID )
+{
+ list->clear();
+ QString command;
+ bool usePerUnit;
+ if ( ingredientID >= 0 ) // Load properties of this ingredient
+ {
+ usePerUnit = true;
+ command = QString( "SELECT ip.id,ip.name,ip.units,ii.per_units,u.name,u.type,ii.amount,ii.ingredient_id FROM ingredient_properties ip, ingredient_info ii, units u WHERE ii.ingredient_id=%1 AND ii.property_id=ip.id AND ii.per_units=u.id;" ).arg( ingredientID );
+ }
+ else if ( ingredientID == -1 ) // Load the properties of all the ingredients
+ {
+ usePerUnit = true;
+ command = QString( "SELECT ip.id,ip.name,ip.units,ii.per_units,u.name,u.type,ii.amount,ii.ingredient_id FROM ingredient_properties ip, ingredient_info ii, units u WHERE ii.property_id=ip.id AND ii.per_units=u.id;" );
+ }
+ else // Load the whole property list (just the list of possible properties, not the ingredient properties)
+ {
+ usePerUnit = false;
+ command = QString( "SELECT id,name,units FROM ingredient_properties;" );
+ }
+
+ QSqlQuery propertiesToLoad ( command, database );
+ // Load the results into the list
+ if ( propertiesToLoad.isActive() ) {
+ while ( propertiesToLoad.next() ) {
+ IngredientProperty prop;
+ prop.id = propertiesToLoad.value( 0 ).toInt();
+ prop.name = unescapeAndDecode( propertiesToLoad.value( 1 ).toCString() );
+ prop.units = unescapeAndDecode( propertiesToLoad.value( 2 ).toCString() );
+ if ( usePerUnit ) {
+ prop.perUnit.id = propertiesToLoad.value( 3 ).toInt();
+ prop.perUnit.name = unescapeAndDecode( propertiesToLoad.value( 4 ).toCString() );
+ prop.perUnit.type = (Unit::Type)propertiesToLoad.value( 5 ).toInt();
+ }
+
+ if ( ingredientID >= -1 )
+ prop.amount = propertiesToLoad.value( 6 ).toDouble();
+ else
+ prop.amount = -1; // Property is generic, not attached to an ingredient
+
+ if ( ingredientID >= -1 )
+ prop.ingredientID = propertiesToLoad.value( 7 ).toInt();
+
+ list->append( prop );
+ }
+ }
+}
+
+void QSqlRecipeDB::changePropertyAmountToIngredient( int ingredientID, int propertyID, double amount, int per_units )
+{
+ QString command;
+ command = QString( "UPDATE ingredient_info SET amount=%1 WHERE ingredient_id=%2 AND property_id=%3 AND per_units=%4;" ).arg( amount ).arg( ingredientID ).arg( propertyID ).arg( per_units );
+ QSqlQuery infoToChange( command, database );
+}
+
+void QSqlRecipeDB::addPropertyToIngredient( int ingredientID, int propertyID, double amount, int perUnitsID )
+{
+ QString command;
+
+ command = QString( "INSERT INTO ingredient_info VALUES(%1,%2,%3,%4);" ).arg( ingredientID ).arg( propertyID ).arg( amount ).arg( perUnitsID );
+ QSqlQuery propertyToAdd( command, database );
+}
+
+
+void QSqlRecipeDB::removePropertyFromIngredient( int ingredientID, int propertyID, int perUnitID )
+{
+ QString command;
+ // remove property from ingredient info. Note that there could be duplicates with different units (per_units). Remove just the one especified.
+ command = QString( "DELETE FROM ingredient_info WHERE ingredient_id=%1 AND property_id=%2 AND per_units=%3;" ).arg( ingredientID ).arg( propertyID ).arg( perUnitID );
+ QSqlQuery propertyToRemove( command, database );
+}
+
+void QSqlRecipeDB::removeProperty( int propertyID )
+{
+ QString command;
+
+ // Remove property from the ingredient_properties
+ command = QString( "DELETE FROM ingredient_properties WHERE id=%1;" ).arg( propertyID );
+ QSqlQuery propertyToRemove( command, database );
+
+ // Remove any ingredient info that uses this property
+ command = QString( "DELETE FROM ingredient_info WHERE property_id=%1;" ).arg( propertyID );
+ propertyToRemove.exec( command );
+
+ emit propertyRemoved( propertyID );
+}
+
+void QSqlRecipeDB::removeUnit( int unitID )
+{
+ QString command;
+ // Remove the unit first
+ command = QString( "DELETE FROM units WHERE id=%1;" ).arg( unitID );
+ QSqlQuery unitToRemove( command, database );
+
+ //Remove the unit from ingredients using it
+
+ command = QString( "DELETE FROM unit_list WHERE unit_id=%1;" ).arg( unitID );
+ unitToRemove.exec( command );
+
+
+ // Remove any recipe using that unit in the ingredient list (user must have been warned before calling this function!)
+
+ command = QString( "SELECT r.id FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.unit_id=%1;" ).arg( unitID );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ emit recipeRemoved( unitToRemove.value( 0 ).toInt() );
+ database->exec( QString( "DELETE FROM recipes WHERE id=%1;" ).arg( unitToRemove.value( 0 ).toInt() ) );
+ }
+ }
+
+ // Remove any ingredient in ingredient_list which has references to this unit
+ command = QString( "DELETE FROM ingredient_list WHERE unit_id=%1;" ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Clean up ingredient_list which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_list WHERE recipe_id NOT IN ( SELECT id FROM recipes );)
+ QStringList ids;
+ command = QString( "SELECT id FROM recipes;" );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ ids << QString::number( unitToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Clean up category_list which have no recipe that they belong to. Same method as above
+ command = QString( "DELETE FROM category_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ ids.clear();
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ if ( unitToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( unitToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Remove the ingredient properties using this unit (user must be warned before calling this function)
+ command = QString( "DELETE FROM ingredient_info WHERE per_units=%1;" ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Remove the unit conversion ratios with this unit
+ command = QString( "DELETE FROM units_conversion WHERE unit1_id=%1 OR unit2_id=%2;" ).arg( unitID ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Remove associated ingredient weights
+ command = QString( "DELETE FROM ingredient_weights WHERE unit_id=%1" ).arg( unitID );
+ unitToRemove.exec( command );
+
+ emit unitRemoved( unitID );
+}
+
+void QSqlRecipeDB::removePrepMethod( int prepMethodID )
+{
+ QString command;
+ // Remove the prep method first
+ command = QString( "DELETE FROM prep_methods WHERE id=%1;" ).arg( prepMethodID );
+ QSqlQuery prepMethodToRemove( command, database );
+
+ // Remove any recipe using that prep method in the ingredient list (user must have been warned before calling this function!)
+
+ command = QString( "SELECT DISTINCT r.id FROM recipes r,ingredient_list il, prep_method_list pl WHERE r.id=il.recipe_id AND pl.ingredient_list_id=il.id AND pl.prep_method_id=%1;" ).arg( prepMethodID );
+ prepMethodToRemove.exec( command );
+ if ( prepMethodToRemove.isActive() ) {
+ while ( prepMethodToRemove.next() ) {
+ emit recipeRemoved( prepMethodToRemove.value( 0 ).toInt() );
+ database->exec( QString( "DELETE FROM recipes WHERE id=%1;" ).arg( prepMethodToRemove.value( 0 ).toInt() ) );
+ }
+ }
+
+ // Clean up ingredient_list which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_list WHERE recipe_id NOT IN ( SELECT id FROM recipes );)
+ QStringList ids;
+ command = QString( "SELECT id FROM recipes;" );
+ prepMethodToRemove.exec( command );
+ if ( prepMethodToRemove.isActive() ) {
+ while ( prepMethodToRemove.next() ) {
+ ids << QString::number( prepMethodToRemove.value( 0 ).toInt() );
+ }
+ }
+
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ prepMethodToRemove.exec( command );
+
+ // Clean up category_list which have no recipe that they belong to. Same method as above
+ command = QString( "DELETE FROM category_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ prepMethodToRemove.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ ids.clear();
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ prepMethodToRemove.exec( command );
+ if ( prepMethodToRemove.isActive() ) {
+ while ( prepMethodToRemove.next() ) {
+ if ( prepMethodToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( prepMethodToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ prepMethodToRemove.exec( command );
+
+ emit prepMethodRemoved( prepMethodID );
+}
+
+
+void QSqlRecipeDB::createNewUnit( const Unit &unit )
+{
+ QString real_name = unit.name.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_plural = unit.plural.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_name_abbrev = unit.name_abbrev.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_plural_abbrev = unit.plural_abbrev.left( maxUnitNameLength() ).stripWhiteSpace();
+
+ Unit new_unit( real_name, real_plural );
+ new_unit.name_abbrev = real_name_abbrev;
+ new_unit.plural_abbrev = real_plural_abbrev;
+ new_unit.type = unit.type;
+
+ if ( real_name.isEmpty() )
+ real_name = real_plural;
+ else if ( real_plural.isEmpty() )
+ real_plural = real_name;
+
+ if ( real_name_abbrev.isEmpty() )
+ real_name_abbrev = "NULL";
+ else
+ real_name_abbrev = "'"+escapeAndEncode(real_name_abbrev)+"'";
+ if ( real_plural_abbrev.isEmpty() )
+ real_plural_abbrev = "NULL";
+ else
+ real_plural_abbrev = "'"+escapeAndEncode(real_plural_abbrev)+"'";
+
+
+ QString command = "INSERT INTO units VALUES(" + getNextInsertIDStr( "units", "id" )
+ + ",'" + escapeAndEncode( real_name )
+ + "'," + real_name_abbrev
+ + ",'" + escapeAndEncode( real_plural )
+ + "'," + real_plural_abbrev
+ + "," + QString::number(unit.type)
+ + ");";
+
+ QSqlQuery unitToCreate( command, database );
+
+ new_unit.id = lastInsertID();
+ emit unitCreated( new_unit );
+}
+
+
+void QSqlRecipeDB::modUnit( const Unit &unit )
+{
+ QSqlQuery unitQuery( QString::null, database );
+
+ QString real_name = unit.name.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_plural = unit.plural.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_name_abbrev = unit.name_abbrev.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_plural_abbrev = unit.plural_abbrev.left( maxUnitNameLength() ).stripWhiteSpace();
+
+ Unit newUnit( real_name, real_plural, unit.id );
+ newUnit.type = unit.type;
+ newUnit.name_abbrev = real_name_abbrev;
+ newUnit.plural_abbrev = real_plural_abbrev;
+
+ if ( real_name_abbrev.isEmpty() )
+ real_name_abbrev = "NULL";
+ else
+ real_name_abbrev = "'"+escapeAndEncode(real_name_abbrev)+"'";
+ if ( real_plural_abbrev.isEmpty() )
+ real_plural_abbrev = "NULL";
+ else
+ real_plural_abbrev = "'"+escapeAndEncode(real_plural_abbrev)+"'";
+
+ QString command = QString("UPDATE units SET name='%1',name_abbrev=%2,plural='%3',plural_abbrev=%4,type=%6 WHERE id='%5'")
+ .arg(escapeAndEncode(real_name))
+ .arg(real_name_abbrev)
+ .arg(escapeAndEncode(real_plural))
+ .arg(real_plural_abbrev)
+ .arg(unit.id)
+ .arg(unit.type);
+ unitQuery.exec( command );
+
+ emit unitRemoved( unit.id );
+ emit unitCreated( newUnit );
+}
+
+void QSqlRecipeDB::findUseOfIngGroupInRecipes( ElementList *results, int groupID )
+{
+ QString command = QString( "SELECT DISTINCT r.id,r.title FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.group_id=%1" ).arg( groupID );
+ QSqlQuery query( command, database );
+
+ // Populate data
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Element recipe;
+ recipe.id = query.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( query.value( 1 ).toCString() );
+ results->append( recipe );
+ }
+ }
+}
+
+void QSqlRecipeDB::findUseOfCategoryInRecipes( ElementList *results, int catID )
+{
+ QString command = QString( "SELECT r.id,r.title FROM recipes r,category_list cl WHERE r.id=cl.recipe_id AND cl.category_id=%1" ).arg( catID );
+ QSqlQuery query( command, database );
+
+ // Populate data
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Element recipe;
+ recipe.id = query.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( query.value( 1 ).toCString() );
+ results->append( recipe );
+ }
+ }
+
+ //recursively find dependenacies in subcategories
+ command = QString( "SELECT id FROM categories WHERE parent_id=%1" ).arg( catID );
+ QSqlQuery findDeps = database->exec( command );
+ if ( findDeps.isActive() ) {
+ while ( findDeps.next() ) {
+ findUseOfCategoryInRecipes(results,findDeps.value( 0 ).toInt() );
+ }
+ }
+}
+
+void QSqlRecipeDB::findUseOfAuthorInRecipes( ElementList *results, int authorID )
+{
+ QString command = QString( "SELECT r.id,r.title FROM recipes r,author_list al WHERE r.id=al.recipe_id AND al.author_id=%1" ).arg( authorID );
+ QSqlQuery query( command, database );
+
+ // Populate data
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Element recipe;
+ recipe.id = query.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( query.value( 1 ).toCString() );
+ results->append( recipe );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadUnitRatios( UnitRatioList *ratioList, Unit::Type type )
+{
+ ratioList->clear();
+
+ QString command;
+ if ( type == Unit::All )
+ command = "SELECT unit1_id,unit2_id,ratio FROM units_conversion";
+ else
+ command = "SELECT unit1_id,unit2_id,ratio FROM units_conversion,units unit1,units unit2 WHERE unit1_id=unit1.id AND unit1.type="+QString::number(type)+" AND unit2_id=unit2.id AND unit2.type="+QString::number(type);
+ QSqlQuery ratiosToLoad( command, database );
+
+ if ( ratiosToLoad.isActive() ) {
+ while ( ratiosToLoad.next() ) {
+ UnitRatio ratio;
+ ratio.uID1 = ratiosToLoad.value( 0 ).toInt();
+ ratio.uID2 = ratiosToLoad.value( 1 ).toInt();
+ ratio.ratio = ratiosToLoad.value( 2 ).toDouble();
+ ratioList->add( ratio );
+ }
+ }
+}
+
+void QSqlRecipeDB::saveUnitRatio( const UnitRatio *ratio )
+{
+ QString command;
+
+ // Check if it's a new ratio or it exists already.
+ command = QString( "SELECT * FROM units_conversion WHERE unit1_id=%1 AND unit2_id=%2" ).arg( ratio->uID1 ).arg( ratio->uID2 ); // Find ratio between units
+
+ QSqlQuery ratioFound( command, database ); // Find the entries
+ bool newRatio = ( ratioFound.size() == 0 );
+
+ if ( newRatio )
+ command = QString( "INSERT INTO units_conversion VALUES(%1,%2,%3);" ).arg( ratio->uID1 ).arg( ratio->uID2 ).arg( ratio->ratio );
+ else
+ command = QString( "UPDATE units_conversion SET ratio=%3 WHERE unit1_id=%1 AND unit2_id=%2" ).arg( ratio->uID1 ).arg( ratio->uID2 ).arg( ratio->ratio );
+
+ ratioFound.exec( command ); // Enter the new ratio
+}
+
+void QSqlRecipeDB::removeUnitRatio( int unitID1, int unitID2 )
+{
+ database->exec(QString( "DELETE FROM units_conversion WHERE unit1_id=%1 AND unit2_id=%2" ).arg( unitID1 ).arg( unitID2 ));
+}
+
+double QSqlRecipeDB::unitRatio( int unitID1, int unitID2 )
+{
+
+ if ( unitID1 == unitID2 )
+ return ( 1.0 );
+ QString command;
+
+ command = QString( "SELECT ratio FROM units_conversion WHERE unit1_id=%1 AND unit2_id=%2;" ).arg( unitID1 ).arg( unitID2 );
+ QSqlQuery ratioToLoad( command, database );
+
+ if ( ratioToLoad.isActive() && ratioToLoad.next() )
+ return ( ratioToLoad.value( 0 ).toDouble() );
+ else
+ return ( -1 );
+}
+
+double QSqlRecipeDB::ingredientWeight( const Ingredient &ing, bool *wasApproximated )
+{
+ QString command = QString( "SELECT amount,weight,prep_method_id,unit_id FROM ingredient_weights WHERE ingredient_id=%1 AND (unit_id=%2 OR weight_unit_id=%3)" )
+ .arg( ing.ingredientID )
+ .arg( ing.units.id ).arg( ing.units.id );
+
+ QSqlQuery query( command, database );
+
+ if ( query.isActive() ) {
+ //store the amount for the entry with no prep method. If no other suitable entry is found, we'll guesstimate
+ //the weight using this entry
+ double convertedAmount = -1;
+ while ( query.next() ) {
+ int prepMethodID = query.value( 2 ).toInt();
+
+ if ( ing.prepMethodList.containsId( prepMethodID ) ) {
+ if ( wasApproximated ) *wasApproximated = false;
+ double amount = query.value( 0 ).toDouble();
+
+ //'per_amount' -> 'weight' conversion
+ if ( query.value( 3 ).toInt() == ing.units.id )
+ convertedAmount = query.value( 1 ).toDouble() * ing.amount / amount;
+ //'weight' -> 'per_amount' conversion
+ else
+ convertedAmount = amount * ing.amount / query.value( 1 ).toDouble();
+
+ return convertedAmount;
+ }
+ if ( prepMethodID == -1 ) {
+ //'per_amount' -> 'weight' conversion
+ if ( query.value( 3 ).toInt() == ing.units.id )
+ convertedAmount = query.value( 1 ).toDouble() * ing.amount / query.value( 0 ).toDouble();
+ //'weight' -> 'per_amount' conversion
+ else
+ convertedAmount = query.value( 0 ).toDouble() * ing.amount / query.value( 1 ).toDouble();
+ }
+ }
+ //no matching prep method found, use entry without a prep method if there was one
+ if ( convertedAmount > 0 ) {
+ if ( wasApproximated ) *wasApproximated = true;
+ kdDebug()<<"Prep method given, but no weight entry found that uses that prep method. I'm fudging the weight with an entry without a prep method."<<endl;
+
+ return convertedAmount;
+ }
+ }
+ return -1;
+}
+
+WeightList QSqlRecipeDB::ingredientWeightUnits( int ingID )
+{
+ WeightList list;
+
+ QString command = QString( "SELECT id,amount,unit_id,weight,weight_unit_id,prep_method_id FROM ingredient_weights WHERE ingredient_id=%1" ).arg( ingID );
+ QSqlQuery query( command, database );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Weight w;
+ w.id = query.value(0).toInt();
+ w.perAmount = query.value(1).toDouble();
+ w.perAmountUnitID = query.value(2).toInt();
+ w.weight = query.value(3).toDouble();
+ w.weightUnitID = query.value(4).toInt();
+ w.prepMethodID = query.value(5).toInt();
+ w.ingredientID = ingID;
+ list.append(w);
+ }
+ }
+
+ return list;
+}
+
+//Finds data dependant on this Ingredient/Unit combination
+void QSqlRecipeDB::findIngredientUnitDependancies( int ingredientID, int unitID, ElementList *recipes, ElementList *ingredientInfo )
+{
+
+ // Recipes using that combination
+
+ QString command = QString( "SELECT DISTINCT r.id,r.title FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.ingredient_id=%1 AND il.unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ QSqlQuery unitToRemove( command, database );
+ loadElementList( recipes, &unitToRemove );
+ // Ingredient info using that combination
+ command = QString( "SELECT i.name,ip.name,ip.units,u.name FROM ingredients i, ingredient_info ii, ingredient_properties ip, units u WHERE i.id=ii.ingredient_id AND ii.ingredient_id=%1 AND ii.per_units=%2 AND ii.property_id=ip.id AND ii.per_units=u.id;" ).arg( ingredientID ).arg( unitID );
+
+ unitToRemove.exec( command );
+ loadPropertyElementList( ingredientInfo, &unitToRemove );
+}
+
+void QSqlRecipeDB::findIngredientDependancies( int ingredientID, ElementList *recipes )
+{
+ QString command = QString( "SELECT DISTINCT r.id,r.title FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.ingredient_id=%1" ).arg( ingredientID );
+
+ QSqlQuery ingredientToRemove( command, database );
+ loadElementList( recipes, &ingredientToRemove );
+}
+
+
+
+//Finds data dependant on the removal of this Unit
+void QSqlRecipeDB::findUnitDependancies( int unitID, ElementList *properties, ElementList *recipes, ElementList *weights )
+{
+
+ // Ingredient-Info (ingredient->property) using this Unit
+
+ QString command = QString( "SELECT i.name,ip.name,ip.units,u.name FROM ingredients i, ingredient_info ii, ingredient_properties ip, units u WHERE i.id=ii.ingredient_id AND ii.per_units=%1 AND ii.property_id=ip.id AND ii.per_units=u.id;" ).arg( unitID );
+ QSqlQuery unitToRemove( command, database );
+ loadPropertyElementList( properties, &unitToRemove );
+
+ // Recipes using this Unit
+ command = QString( "SELECT DISTINCT r.id,r.title FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.unit_id=%1;" ).arg( unitID ); // Without "DISTINCT" we get duplicates since ingredient_list has no unique recipe_id's
+ unitToRemove.exec( command );
+ loadElementList( recipes, &unitToRemove );
+
+ // Weights using this unit
+ command = QString( "SELECT i.name,weight_u.name,per_u.name,w.prep_method_id FROM ingredients i,ingredient_weights w,units weight_u,units per_u WHERE i.id=w.ingredient_id AND w.unit_id=per_u.id AND w.weight_unit_id=weight_u.id AND (weight_u.id=%1 OR per_u.id=%2)" )
+ .arg( unitID )
+ .arg( unitID );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ Element el;
+
+ QString ingName = unescapeAndDecode( unitToRemove.value( 0 ).toCString() );
+ QString weightUnit = unescapeAndDecode( unitToRemove.value( 1 ).toCString() );
+ QString perUnit = unescapeAndDecode( unitToRemove.value( 2 ).toCString() );
+
+ int prepID = unitToRemove.value( 3 ).toInt();
+ QString prep;
+ if ( prepID != -1 ) {
+ command = QString( "SELECT p.name FROM prep_methods p, ingredient_weights w WHERE p.id = w.prep_method_id AND w.prep_method_id=%1" )
+ .arg( prepID );
+ QSqlQuery query( command, database );
+ if ( query.isActive() && query.first() )
+ prep = unescapeAndDecode( query.value( 0 ).toCString() );
+ }
+
+ el.name = QString( i18n("In ingredient '%1': weight [%2/%3%4]") ).arg( ingName ).arg( weightUnit ).arg( perUnit ).arg( (prepID == -1)?QString::null:"; "+prep );
+ weights->append( el );
+ }
+ }
+
+}
+
+void QSqlRecipeDB::findPrepMethodDependancies( int prepMethodID, ElementList *recipes )
+{
+ //get just the ids first so that we can use DISTINCT
+ QString command = QString( "SELECT DISTINCT r.id FROM recipes r,ingredient_list il, prep_method_list pl WHERE r.id=il.recipe_id AND pl.ingredient_list_id=il.id AND pl.prep_method_id=%1;" ).arg( prepMethodID );
+
+ QStringList ids;
+ QSqlQuery query( command, database );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ ids << QString::number(query.value( 0 ).toInt());
+ }
+ }
+
+ //now get the titles of the ids
+ command = QString( "SELECT r.id, r.title FROM recipes r WHERE r.id IN ("+ids.join(",")+")" );
+ QSqlQuery prepMethodToRemove( command, database );
+ loadElementList( recipes, &prepMethodToRemove );
+}
+
+
+void QSqlRecipeDB::loadElementList( ElementList *elList, QSqlQuery *query )
+{
+ if ( query->isActive() ) {
+ while ( query->next() ) {
+ Element el;
+ el.id = query->value( 0 ).toInt();
+ el.name = unescapeAndDecode( query->value( 1 ).toCString() );
+ elList->append( el );
+ }
+ }
+}
+// See function "findUnitDependancies" for use
+void QSqlRecipeDB::loadPropertyElementList( ElementList *elList, QSqlQuery *query )
+{
+ if ( query->isActive() ) {
+ while ( query->next() ) {
+ Element el;
+ el.id = -1; // There's no ID for the ingredient-property combination
+ QString ingName = unescapeAndDecode( query->value( 0 ).toCString() );
+ QString propName = unescapeAndDecode( query->value( 1 ).toCString() );
+ QString propUnits = unescapeAndDecode( query->value( 2 ).toCString() );
+ QString propPerUnits = unescapeAndDecode( query->value( 3 ).toCString() );
+
+ el.name = QString( i18n("In ingredient '%1': property \"%2\" [%3/%4]") ).arg( ingName ).arg( propName ).arg( propUnits ).arg( propPerUnits );
+ elList->append( el );
+ }
+ }
+}
+
+
+//The string going into the database is utf8 text interpreted as latin1
+QString QSqlRecipeDB::escapeAndEncode( const QString &s ) const
+{
+ QString s_escaped = s;
+
+ s_escaped.replace ( "'", "\\'" );
+ s_escaped.replace ( ";", "\";@" ); // Small trick for only for parsing later on
+
+ return QString::fromLatin1( s_escaped.utf8() );
+}
+
+//The string coming out of the database is utf8 text, interpreted as though latin1. Calling fromUtf8() on this gives us back the original utf8.
+QString QSqlRecipeDB::unescapeAndDecode( const QCString &s ) const
+{
+ return QString::fromUtf8( s ).replace( "\";@", ";" ); // Use unicode encoding
+}
+
+bool QSqlRecipeDB::ingredientContainsUnit( int ingredientID, int unitID )
+{
+ QString command = QString( "SELECT * FROM unit_list WHERE ingredient_id= %1 AND unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ QSqlQuery recipeToLoad( command, database );
+ if ( recipeToLoad.isActive() ) {
+ return ( recipeToLoad.size() > 0 );
+ }
+ return false;
+}
+
+bool QSqlRecipeDB::ingredientContainsProperty( int ingredientID, int propertyID, int perUnitsID )
+{
+ QString command = QString( "SELECT * FROM ingredient_info WHERE ingredient_id=%1 AND property_id=%2 AND per_units=%3;" ).arg( ingredientID ).arg( propertyID ).arg( perUnitsID );
+ QSqlQuery recipeToLoad( command, database );
+ if ( recipeToLoad.isActive() ) {
+ return ( recipeToLoad.size() > 0 );
+ }
+ return false;
+}
+
+QString QSqlRecipeDB::categoryName( int ID )
+{
+ QString command = QString( "SELECT name FROM categories WHERE id=%1;" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) // Go to the first record (there should be only one anyway.
+ return ( unescapeAndDecode( toLoad.value( 0 ).toCString() ) );
+
+ return ( QString::null );
+}
+
+QString QSqlRecipeDB::ingredientName( int ID )
+{
+ QString command = QString( "SELECT name FROM ingredients WHERE id=%1" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) // Go to the first record (there should be only one anyway.
+ return ( unescapeAndDecode( toLoad.value( 0 ).toCString() ) );
+
+ return ( QString::null );
+}
+
+QString QSqlRecipeDB::prepMethodName( int ID )
+{
+ QString command = QString( "SELECT name FROM prep_methods WHERE id=%1" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) // Go to the first record (there should be only one anyway.
+ return ( unescapeAndDecode( toLoad.value( 0 ).toCString() ) );
+
+ return ( QString::null );
+}
+
+IngredientProperty QSqlRecipeDB::propertyName( int ID )
+{
+ QString command = QString( "SELECT name,units FROM ingredient_properties WHERE id=%1;" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) { // Go to the first record (there should be only one anyway.
+ return ( IngredientProperty( unescapeAndDecode( toLoad.value( 0 ).toCString() ), unescapeAndDecode( toLoad.value( 1 ).toCString() ), ID ) );
+ }
+
+ return ( IngredientProperty( QString::null, QString::null ) );
+}
+
+Unit QSqlRecipeDB::unitName( int ID )
+{
+ QString command = QString( "SELECT name,plural,name_abbrev,plural_abbrev,type FROM units WHERE id=%1" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) { // Go to the first record (there should be only one anyway.
+ Unit unit( unescapeAndDecode( toLoad.value( 0 ).toCString() ), unescapeAndDecode( toLoad.value( 1 ).toCString() ) );
+
+ //if we don't have both name and plural, use what we have as both
+ if ( unit.name.isEmpty() )
+ unit.name = unit.plural;
+ else if ( unit.plural.isEmpty() )
+ unit.plural = unit.name;
+
+ unit.name_abbrev = unescapeAndDecode( toLoad.value( 2 ).toCString() );
+ unit.plural_abbrev = unescapeAndDecode( toLoad.value( 3 ).toCString() );
+ unit.type = (Unit::Type) toLoad.value( 4 ).toInt();
+ unit.id = ID;
+
+ return unit;
+ }
+
+ return Unit();
+}
+
+int QSqlRecipeDB::getCount( const QString &table_name )
+{
+ m_command = "SELECT COUNT(1) FROM "+table_name;
+ QSqlQuery count( m_command, database );
+ if ( count.isActive() && count.next() ) { // Go to the first record (there should be only one anyway.
+ return count.value( 0 ).toInt();
+ }
+
+ return -1;
+}
+
+int QSqlRecipeDB::categoryTopLevelCount()
+{
+ m_command = "SELECT COUNT(1) FROM categories WHERE parent_id='-1'";
+ QSqlQuery count( m_command, database );
+ if ( count.isActive() && count.next() ) { // Go to the first record (there should be only one anyway.
+ return count.value( 0 ).toInt();
+ }
+
+ return -1;
+}
+
+bool QSqlRecipeDB::checkIntegrity( void )
+{
+
+
+ // Check existence of the necessary tables (the database may be created, but empty)
+ QStringList tables;
+ tables << "ingredient_info" << "ingredient_list" << "ingredient_properties" << "ingredient_weights" << "ingredients" << "recipes" << "unit_list" << "units" << "units_conversion" << "categories" << "category_list" << "authors" << "author_list" << "db_info" << "prep_methods" << "ingredient_groups" << "yield_types" << "prep_method_list" << "ratings" << "rating_criteria" << "rating_criterion_list";
+
+ QStringList existingTableList = database->tables();
+ for ( QStringList::Iterator it = tables.begin(); it != tables.end(); ++it ) {
+ bool found = false;
+
+ for ( QStringList::Iterator ex_it = existingTableList.begin(); ( ( ex_it != existingTableList.end() ) && ( !found ) ); ++ex_it ) {
+ found = ( *ex_it == *it );
+ }
+
+ if ( !found ) {
+ kdDebug() << "Recreating missing table: " << *it << "\n";
+ createTable( *it );
+ }
+ }
+
+ QStringList newTableList = database->tables();
+ if ( newTableList.isEmpty() )
+ return false;
+
+
+ // Check for older versions, and port
+
+ kdDebug() << "Checking database version...\n";
+ float version = databaseVersion();
+ kdDebug() << "version found... " << version << " \n";
+ kdDebug() << "latest version... " << latestDBVersion() << endl;
+ if ( int( qRound( databaseVersion() * 1e5 ) ) < int( qRound( latestDBVersion() * 1e5 ) ) ) { //correct for float's imprecision
+ switch ( KMessageBox::questionYesNo( 0, i18n( "<!doc>The database was created with a previous version of Krecipes. Would you like Krecipes to update this database to work with this version of Krecipes? Depending on the number of recipes and amount of data, this could take some time.<br><br><b>Warning: After updating, this database will no longer be compatible with previous versions of Krecipes.<br><br>Cancelling this operation may result in corrupting the database.</b>" ) ) ) {
+ case KMessageBox::Yes:
+ emit progressBegin(0,QString::null,i18n("Porting database structure..."),50);
+ portOldDatabases( version );
+ emit progressDone();
+ break;
+ case KMessageBox::No:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void QSqlRecipeDB::splitCommands( QString& s, QStringList& sl )
+{
+ sl = QStringList::split( QRegExp( ";{1}(?!@)" ), s );
+}
+
+void QSqlRecipeDB::portOldDatabases( float /* version */ )
+{}
+
+float QSqlRecipeDB::databaseVersion( void )
+{
+
+ QString command = "SELECT ver FROM db_info";
+ QSqlQuery dbVersion( command, database );
+
+ if ( dbVersion.isActive() && dbVersion.next() )
+ return ( dbVersion.value( 0 ).toDouble() ); // There should be only one (or none for old DB) element, so go to first
+ else
+ return ( 0.2 ); // if table is empty, assume oldest (0.2), and port
+}
+
+void QSqlRecipeDB::loadRatingCriterion( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM rating_criteria ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() ) {
+ while ( toLoad.next() ) {
+ Element el;
+ el.id = toLoad.value( 0 ).toInt();
+ el.name = unescapeAndDecode( toLoad.value( 1 ).toCString() );
+ list->append( el );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadCategories( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ m_command = "SELECT id,name FROM categories ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ QSqlQuery categoryToLoad( m_command, database );
+ if ( categoryToLoad.isActive() ) {
+ while ( categoryToLoad.next() ) {
+ Element el;
+ el.id = categoryToLoad.value( 0 ).toInt();
+ el.name = unescapeAndDecode( categoryToLoad.value( 1 ).toCString() );
+ list->append( el );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadCategories( CategoryTree *list, int limit, int offset, int parent_id, bool recurse )
+{
+ QString limit_str;
+ if ( parent_id == -1 ) {
+ emit progressBegin(0,QString::null,i18n("Loading category list"));
+ list->clear();
+
+ //only limit the number of top-level categories
+ limit_str = (limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset);
+ }
+
+ m_command = "SELECT id,name,parent_id FROM categories WHERE parent_id='"+QString::number(parent_id)+"' ORDER BY name "+limit_str;
+
+ QSqlQuery categoryToLoad( QString::null, database );
+ //categoryToLoad.setForwardOnly(true); //FIXME? Subcategories aren't loaded if this is enabled, even though we only go forward
+
+ categoryToLoad.exec(m_command);
+
+ if ( categoryToLoad.isActive() ) {
+ while ( categoryToLoad.next() ) {
+ emit progress();
+
+ int id = categoryToLoad.value( 0 ).toInt();
+ Element el;
+ el.id = id;
+ el.name = unescapeAndDecode( categoryToLoad.value( 1 ).toCString() );
+ CategoryTree *list_child = list->add( el );
+
+ if ( recurse ) {
+ //QTime dbg_timer; dbg_timer.start(); kdDebug()<<" calling QSqlRecipeDB::loadCategories"<<endl;
+ loadCategories( list_child, -1, -1, id ); //limit and offset won't be used
+ // kdDebug()<<" done in "<<dbg_timer.elapsed()<<" ms"<<endl;
+ }
+ }
+ }
+
+ if ( parent_id == -1 )
+ emit progressDone();
+}
+
+void QSqlRecipeDB::createNewCategory( const QString &categoryName, int parent_id )
+{
+ QString command;
+ QString real_name = categoryName.left( maxCategoryNameLength() );
+
+ command = QString( "INSERT INTO categories VALUES(%3,'%1',%2);" )
+ .arg( escapeAndEncode( real_name ) )
+ .arg( parent_id )
+ .arg( getNextInsertIDStr( "categories", "id" ) );
+ QSqlQuery categoryToCreate( command, database );
+
+ emit categoryCreated( Element( real_name, lastInsertID() ), parent_id );
+}
+
+void QSqlRecipeDB::modCategory( int categoryID, const QString &newLabel )
+{
+ QString command = QString( "UPDATE categories SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( categoryID );
+ QSqlQuery categoryToUpdate( command, database );
+
+ emit categoryModified( Element( newLabel, categoryID ) );
+}
+
+void QSqlRecipeDB::modCategory( int categoryID, int new_parent_id )
+{
+ QString command = QString( "UPDATE categories SET parent_id=%1 WHERE id=%2;" ).arg( new_parent_id ).arg( categoryID );
+ QSqlQuery categoryToUpdate( command, database );
+
+ emit categoryModified( categoryID, new_parent_id );
+}
+
+void QSqlRecipeDB::removeCategory( int categoryID )
+{
+ QString command;
+
+ command = QString( "DELETE FROM categories WHERE id=%1;" ).arg( categoryID );
+ QSqlQuery categoryToRemove( command, database );
+
+ command = QString( "DELETE FROM category_list WHERE category_id=%1;" ).arg( categoryID );
+ categoryToRemove.exec( command );
+
+ //recursively delete subcategories
+ command = QString( "SELECT id FROM categories WHERE parent_id=%1;" ).arg( categoryID );
+ categoryToRemove.exec( command );
+ if ( categoryToRemove.isActive() ) {
+ while ( categoryToRemove.next() ) {
+ removeCategory( categoryToRemove.value( 0 ).toInt() );
+ }
+ }
+
+ emit categoryRemoved( categoryID );
+}
+
+
+void QSqlRecipeDB::loadAuthors( ElementList *list, int limit, int offset )
+{
+ list->clear();
+ QString command = "SELECT id,name FROM authors ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ QSqlQuery authorToLoad( command, database );
+ if ( authorToLoad.isActive() ) {
+ while ( authorToLoad.next() ) {
+ Element el;
+ el.id = authorToLoad.value( 0 ).toInt();
+ el.name = unescapeAndDecode( authorToLoad.value( 1 ).toCString() );
+ list->append( el );
+ }
+ }
+}
+
+void QSqlRecipeDB::createNewAuthor( const QString &authorName )
+{
+ QString command;
+ QString real_name = authorName.left( maxAuthorNameLength() );
+
+ command = QString( "INSERT INTO authors VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "authors", "id" ) );
+ QSqlQuery authorToCreate( command, database );
+
+ emit authorCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::modAuthor( int authorID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE authors SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( authorID );
+ QSqlQuery authorToCreate( command, database );
+
+ emit authorRemoved( authorID );
+ emit authorCreated( Element( newLabel, authorID ) );
+}
+
+void QSqlRecipeDB::removeAuthor( int authorID )
+{
+ QString command;
+
+ command = QString( "DELETE FROM authors WHERE id=%1;" ).arg( authorID );
+ QSqlQuery authorToRemove( command, database );
+
+ emit authorRemoved( authorID );
+}
+
+int QSqlRecipeDB::findExistingAuthorByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxAuthorNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM authors WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingCategoryByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxCategoryNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM categories WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingIngredientGroupByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxIngGroupNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM ingredient_groups WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingIngredientByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxIngredientNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM ingredients WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingPrepByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxPrepMethodNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM prep_methods WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingPropertyByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxPropertyNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM ingredient_properties WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingUnitByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxUnitNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = "SELECT id FROM units WHERE name LIKE '" + search_str
+ + "' OR plural LIKE '" + search_str
+ + "' OR name_abbrev LIKE '" + search_str
+ + "' OR plural_abbrev LIKE '" + search_str
+ + "'";
+
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingRatingByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM rating_criteria WHERE name LIKE '%1'" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+
+ int id = -1;
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingRecipeByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxRecipeTitleLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM recipes WHERE title LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+
+ int id = -1;
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingYieldTypeByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxYieldTypeLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM yield_types WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+
+ int id = -1;
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+void QSqlRecipeDB::mergeAuthors( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE author_list SET author_id=%1 WHERE author_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ command = QString( "SELECT recipe_id FROM author_list WHERE author_id=%1 ORDER BY recipe_id" )
+ .arg( id1 );
+ update.exec( command );
+ int last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM author_list WHERE author_id=%1 AND recipe_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM author_list WHERE author_id=%1 AND recipe_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO author_list VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+ }
+ last_id = current_id;
+ }
+ }
+
+ //remove author with id 'id2'
+ command = QString( "DELETE FROM authors WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit authorRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergeCategories( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE category_list SET category_id=%1 WHERE category_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ command = QString( "SELECT recipe_id FROM category_list WHERE category_id=%1 ORDER BY recipe_id" )
+ .arg( id1 );
+ update.exec( command );
+ int last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM category_list WHERE category_id=%1 AND recipe_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM category_list WHERE category_id=%1 AND recipe_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO category_list VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+ }
+ last_id = current_id;
+ }
+ }
+
+ command = QString( "UPDATE categories SET parent_id=%1 WHERE parent_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //we don't want to have a category be its own parent...
+ command = QString( "UPDATE categories SET parent_id=-1 WHERE parent_id=id" );
+ update.exec( command );
+
+ //remove category with id 'id2'
+ command = QString( "DELETE FROM categories WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+
+ emit categoriesMerged( id1, id2 );
+}
+
+void QSqlRecipeDB::mergeIngredientGroups( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE ingredient_list SET group_id=%1 WHERE group_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //remove ingredient with id 'id2'
+ command = QString( "DELETE FROM ingredient_groups WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit ingGroupRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergeIngredients( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE ingredient_list SET ingredient_id=%1 WHERE ingredient_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //delete nutrient info associated with ingredient with id 'id2'
+ command = QString( "DELETE FROM ingredient_info WHERE ingredient_id=%1" )
+ .arg( id2 );
+ update.exec( command );
+
+ //update the unit_list
+ command = QString( "UPDATE unit_list SET ingredient_id=%1 WHERE ingredient_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ command = QString( "SELECT unit_id FROM unit_list WHERE ingredient_id=%1 ORDER BY unit_id" )
+ .arg( id1 );
+ update.exec( command );
+ int last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO unit_list VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+ }
+ last_id = current_id;
+ }
+ }
+
+ //update ingredient info
+ command = QString( "UPDATE ingredient_info SET ingredient_id=%1 WHERE ingredient_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ //info associated with one ingredient will be lost... they should be the same ingredient and thus info anyways
+ command = QString( "SELECT property_id FROM ingredient_info WHERE ingredient_id=%1 ORDER BY property_id" )
+ .arg( id1 );
+ update.exec( command );
+ last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM ingredient_info WHERE ingredient_id=%1 AND property_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM ingredient_info WHERE ingredient_id=%1 AND property_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO ingredient_info VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+
+ }
+ last_id = current_id;
+ }
+ }
+
+ //remove ingredient with id 'id2'
+ command = QString( "DELETE FROM ingredients WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit ingredientRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergePrepMethods( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1' in ingredient list
+ QString command = QString( "UPDATE prep_method_list SET prep_method_id=%1 WHERE prep_method_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //change all instances of 'id2' to 'id1' in ingredient weights
+ command = QString( "UPDATE ingredient_weights SET prep_method_id=%1 WHERE prep_method_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //remove prep method with id 'id2'
+ command = QString( "DELETE FROM prep_methods WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit prepMethodRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergeProperties( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE ingredient_properties SET id=%1 WHERE id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ command = QString( "UPDATE ingredient_info SET property_id=%1 WHERE property_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //remove prep method with id 'id2'
+ command = QString( "DELETE FROM ingredient_properties WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit propertyRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergeUnits( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1' in unit list
+ QString command = QString( "UPDATE unit_list SET unit_id=%1 WHERE unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //change all instances of 'id2' to 'id1' in ingredient list
+ command = QString( "UPDATE ingredient_list SET unit_id=%1 WHERE unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ command = QString( "SELECT ingredient_id FROM unit_list WHERE unit_id=%1 ORDER BY ingredient_id" )
+ .arg( id1 );
+ update.exec( command );
+ int last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO unit_list VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+ }
+ last_id = current_id;
+ }
+ }
+
+ //update ingredient info
+ command = QString( "UPDATE ingredient_info SET per_units=%1 WHERE per_units=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //change all instances of 'id2' to 'id1' in unit_conversion
+ command = QString( "UPDATE units_conversion SET unit1_id=%1 WHERE unit1_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+ command = QString( "UPDATE units_conversion SET unit2_id=%1 WHERE unit2_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure that the one to one ratio wasn't created
+ command = QString( "DELETE FROM units_conversion WHERE unit1_id=unit2_id" );
+ update.exec( command );
+
+ //update ingredient weights
+ command = QString( "UPDATE ingredient_weights SET unit_id=%1 WHERE unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+ command = QString( "UPDATE ingredient_weights SET weight_unit_id=%1 WHERE weight_unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //change all instances of 'id2' to 'id1' in ingredient weights
+ command = QString( "UPDATE ingredient_weights SET unit_id=%1 WHERE unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ command = QString( "UPDATE ingredient_weights SET weight_unit_id=%1 WHERE weight_unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //remove units with id 'id2'
+ command = QString( "DELETE FROM units WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit unitRemoved( id2 );
+}
+
+QString QSqlRecipeDB::getUniqueRecipeTitle( const QString &recipe_title )
+{
+ //already is unique
+ if ( findExistingRecipeByName( recipe_title ) == -1 )
+ return recipe_title;
+
+ QString return_title = recipe_title; //If any error is produced, just go for default value (always return something)
+
+ QString command = QString( "SELECT COUNT(1) FROM recipes WHERE title LIKE '%1 (%)';" ).arg( escapeAndEncode( recipe_title ) );
+
+ QSqlQuery alikeRecipes( command, database );
+ if ( alikeRecipes.isActive() && alikeRecipes.first() )
+ {
+ int count = alikeRecipes.value( 0 ).toInt();
+ return_title = QString( "%1 (%2)" ).arg( recipe_title ).arg( count + 2 );
+
+ //make sure this newly created title is unique (just in case)
+ while ( findExistingRecipeByName( return_title ) != -1 ) {
+ count--; //go down to find the skipped recipe(s)
+ return_title = QString( "%1 (%2)" ).arg( recipe_title ).arg( count + 2 );
+ }
+ }
+
+ return return_title;
+}
+
+QString QSqlRecipeDB::recipeTitle( int recipeID )
+{
+ QString command = QString( "SELECT title FROM recipes WHERE id=%1;" ).arg( recipeID );
+ QSqlQuery recipeToLoad( command, database );
+ if ( recipeToLoad.isActive() && recipeToLoad.next() ) // Go to the first record (there should be only one anyway.
+ return ( unescapeAndDecode(recipeToLoad.value( 0 ).toCString()) );
+
+ return ( QString::null );
+}
+
+void QSqlRecipeDB::emptyData( void )
+{
+ QStringList tables;
+ tables << "ingredient_info" << "ingredient_list" << "ingredient_properties" << "ingredients" << "recipes" << "unit_list" << "units" << "units_conversion" << "categories" << "category_list" << "authors" << "author_list" << "prep_methods" << "ingredient_groups" << "yield_types" << "ratings" << "rating_criteria" << "rating_criterion_list";
+ QSqlQuery tablesToEmpty( QString::null, database );
+ for ( QStringList::Iterator it = tables.begin(); it != tables.end(); ++it ) {
+ QString command = QString( "DELETE FROM %1;" ).arg( *it );
+ tablesToEmpty.exec( command );
+ }
+}
+
+void QSqlRecipeDB::empty( void )
+{
+ QSqlQuery tablesToEmpty( QString::null, database );
+
+ QStringList list = database->tables();
+ QStringList::const_iterator it = list.begin();
+ while( it != list.end() ) {
+ QString command = QString( "DROP TABLE %1;" ).arg( *it );
+ tablesToEmpty.exec( command );
+
+ if ( !tablesToEmpty.isActive() )
+ kdDebug()<<tablesToEmpty.lastError().databaseText()<<endl;
+
+ ++it;
+ }
+}
+
+QString QSqlRecipeDB::getNextInsertIDStr( const QString &table, const QString &column )
+{
+ int next_id = getNextInsertID( table, column );
+
+ QString id_str;
+ if ( next_id == -1 )
+ id_str = "NULL";
+ else
+ id_str = QString::number( next_id );
+
+ return id_str;
+}
+
+void QSqlRecipeDB::search( RecipeList *list, int items, const RecipeSearchParameters &parameters )
+{
+ QString query = buildSearchQuery(parameters);
+
+ QValueList<int> ids;
+ QSqlQuery recipeToLoad( query, database );
+ if ( recipeToLoad.isActive() ) {
+ while ( recipeToLoad.next() ) {
+ ids << recipeToLoad.value( 0 ).toInt();
+ }
+ }
+
+ if ( ids.count() > 0 )
+ loadRecipes( list, items, ids );
+}
+
+#include "qsqlrecipedb.moc"
diff --git a/krecipes/src/backends/qsqlrecipedb.h b/krecipes/src/backends/qsqlrecipedb.h
new file mode 100644
index 0000000..68ba6cc
--- /dev/null
+++ b/krecipes/src/backends/qsqlrecipedb.h
@@ -0,0 +1,228 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+
+#ifndef QSQLRECIPEDB_H
+#define QSQLRECIPEDB_H
+
+#include "backends/recipedb.h"
+
+#include <qglobal.h>
+#include <qobject.h>
+#include <qsqldatabase.h>
+#include <qimage.h>
+#include <qfileinfo.h>
+#include <qregexp.h>
+#include <qstring.h>
+
+#include "datablocks/recipe.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/ingredientpropertylist.h"
+#include "datablocks/unitratiolist.h"
+
+/**
+@author Unai Garro, Jason Kivlighn
+*/
+class QSqlRecipeDB : public RecipeDB
+{
+
+ Q_OBJECT
+
+protected:
+ virtual QString qsqlDriverPlugin() const { return QString::null; }
+ virtual QSqlDriver *qsqlDriver() const { return 0; }
+ virtual void createDB( void ) = 0;
+
+ virtual void portOldDatabases( float version );
+ virtual void storePhoto( int recipeID, const QByteArray &data );
+ virtual void loadPhoto( int recipeID, QPixmap &photo );
+ void loadRecipeMetadata( Recipe *recipe );
+
+ void search( RecipeList *list, int items, const RecipeSearchParameters & );
+
+ /** Return the next id for the given table and column.
+ * If the database supports getting this afterwards,
+ * leave the default implementation which returns -1.
+ *
+ * Note: Only call when an insert is actually going to take place.
+ * This function will increment the sequence counter.
+ */
+ virtual int getNextInsertID( const QString & /*table*/, const QString & /*column*/ )
+ {
+ return -1;
+ }
+
+ QSqlDatabase *database;
+ QSqlQuery m_query;
+ QString DBuser;
+ QString DBpass;
+ QString DBhost;
+ int DBport;
+
+public:
+ QSqlRecipeDB( const QString &host, const QString &user = QString::null, const QString &pass = QString::null, const QString &DBName = DEFAULT_DB_NAME, int port = 0 );
+ ~QSqlRecipeDB( void );
+
+ void connect( bool create_db, bool create_tables );
+
+ void addIngredientWeight( const Weight & );
+ void addProperty( const QString &name, const QString &units );
+ void addPropertyToIngredient( int ingredientID, int propertyID, double amount, int perUnitsID );
+ void addUnitToIngredient( int ingredientID, int unitID );
+
+ void categorizeRecipe( int recipeID, const ElementList &categoryList );
+ void changePropertyAmountToIngredient( int ingredientID, int propertyID, double amount, int per_units );
+
+ void createNewAuthor( const QString &authorName );
+ void createNewCategory( const QString &categoryName, int parent_id = -1 );
+ void createNewIngGroup( const QString &name );
+ void createNewIngredient( const QString &ingredientName );
+ void createNewPrepMethod( const QString &prepMethodName );
+ void createNewRating( const QString &name );
+ void createNewUnit( const Unit &unit );
+ void createNewYieldType( const QString &type );
+
+ void emptyData( void );
+ void empty( void );
+
+ int findExistingAuthorByName( const QString& name );
+ int findExistingCategoryByName( const QString& name );
+ int findExistingIngredientGroupByName( const QString& name );
+ int findExistingIngredientByName( const QString& name );
+ int findExistingPrepByName( const QString& name );
+ int findExistingRecipeByName( const QString& name );
+ int findExistingRatingByName( const QString& name );
+ int findExistingUnitByName( const QString& name );
+ int findExistingPropertyByName( const QString& name );
+ int findExistingYieldTypeByName( const QString& name );
+ void findIngredientUnitDependancies( int ingredientID, int unitID, ElementList *recipes, ElementList *ingredientInfo );
+ void findIngredientDependancies( int ingredientID, ElementList *recipes );
+ void findPrepMethodDependancies( int prepMethodID, ElementList *recipes );
+ void findUnitDependancies( int unitID, ElementList *properties, ElementList *recipes, ElementList *weights );
+ void findUseOfIngGroupInRecipes( ElementList *results, int groupID );
+ void findUseOfCategoryInRecipes( ElementList *results, int catID );
+ void findUseOfAuthorInRecipes( ElementList *results, int authorID );
+
+ QString getUniqueRecipeTitle( const QString &recipe_title );
+
+ bool ingredientContainsProperty( int ingredientID, int propertyID, int perUnitsID );
+ bool ingredientContainsUnit( int ingredientID, int unitID );
+
+ void loadAuthors( ElementList *list, int limit = -1, int offset = 0 );
+ void loadCategories( CategoryTree *list, int limit = -1, int offset = 0, int parent_id = -1, bool recurse = true );
+ void loadCategories( ElementList *list, int limit = -1, int offset = 0 );
+ void loadIngredientGroups( ElementList *list );
+ void loadIngredients( ElementList *list, int limit = -1, int offset = 0 );
+ void loadPossibleUnits( int ingredientID, UnitList *list );
+ void loadPrepMethods( ElementList *list, int limit = -1, int offset = 0 );
+ void loadProperties( IngredientPropertyList *list, int ingredientID = -2 ); // Loads the list of possible properties by default, all the ingredient properties with -1, and the ingredients of given property if id>=0
+ void loadRatingCriterion( ElementList *list, int limit = -1, int offset = 0 );
+ void loadRecipes( RecipeList *, int items = All, QValueList<int> ids = QValueList<int>() );
+ void loadRecipeList( ElementList *list, int categoryID = -1, bool recursive = false );
+ void loadUncategorizedRecipes( ElementList *list );
+ void loadUnits( UnitList *list, Unit::Type = Unit::All, int limit = -1, int offset = 0 );
+ void loadUnitRatios( UnitRatioList *ratioList, Unit::Type );
+ void loadYieldTypes( ElementList *list, int limit, int offset );
+
+ void mergeAuthors( int id1, int id2 );
+ void mergeCategories( int id1, int id2 );
+ void mergeIngredientGroups( int id1, int id2 );
+ void mergeIngredients( int id1, int id2 );
+ void mergeUnits( int id1, int id2 );
+ void mergePrepMethods( int id1, int id2 );
+ void mergeProperties( int id1, int id2 );
+
+ void modIngredientGroup( int ingredientID, const QString &newLabel );
+ /**
+ * set newLabel for ingredientID
+ */
+ void modIngredient( int ingredientID, const QString &newLabel );
+ /**
+ * set newLabel for unitID
+ */
+ void modUnit( const Unit &unit );
+ /**
+ * set newLabel for categoryID
+ */
+ void modCategory( int categoryID, const QString &newLabel );
+ void modCategory( int categoryID, int new_parent_id );
+ /**
+ * set newLabel for authorID
+ */
+ void modAuthor( int authorID, const QString &newLabel );
+
+ void modPrepMethod( int prepMethodID, const QString &newLabel );
+
+ void modProperty( int propertyID, const QString &newLabel );
+
+ QString recipeTitle( int recipeID );
+
+ void removeAuthor( int categoryID );
+ void removeCategory( int categoryID );
+ void removeIngredientGroup( int groupID );
+ void removeIngredient( int ingredientID );
+ void removeIngredientWeight( int id );
+ void removePrepMethod( int prepMethodID );
+ void removeProperty( int propertyID );
+ void removePropertyFromIngredient( int ingredientID, int propertyID, int perUnitID );
+ void removeRecipe( int id );
+ void removeRecipeFromCategory( int ingredientID, int categoryID );
+ void removeUnit( int unitID );
+ void removeUnitFromIngredient( int ingredientID, int unitID );
+ void removeUnitRatio( int unitID1, int unitID2 );
+
+ void saveRecipe( Recipe *recipe );
+ void saveUnitRatio( const UnitRatio *ratio );
+
+ double unitRatio( int unitID1, int unitID2 );
+ double ingredientWeight( const Ingredient &ing, bool *wasApproximated = 0 );
+ WeightList ingredientWeightUnits( int ingID );
+
+ QString escapeAndEncode( const QString &s ) const;
+ QString unescapeAndDecode( const QCString &s ) const;
+
+ QString categoryName( int ID );
+ QString prepMethodName( int ID );
+ QString ingredientName( int ID );
+ IngredientProperty propertyName( int ID );
+ Unit unitName( int ID );
+
+ int getCount( const QString &table_name );
+ int categoryTopLevelCount();
+
+ bool checkIntegrity( void );
+
+ void splitCommands( QString& s, QStringList& sl );
+
+ float databaseVersion( void );
+
+protected:
+ void execSQL( const QString &command );
+
+private:
+ void loadElementList( ElementList *elList, QSqlQuery *query );
+ void loadPropertyElementList( ElementList *elList, QSqlQuery *query );
+ QString getNextInsertIDStr( const QString &table, const QString &column );
+
+ QString DBname;
+ const QString connectionName;
+ QString m_command;
+
+ static int m_refCount;
+};
+
+
+
+
+#endif
diff --git a/krecipes/src/backends/recipedb.cpp b/krecipes/src/backends/recipedb.cpp
new file mode 100644
index 0000000..9df42df
--- /dev/null
+++ b/krecipes/src/backends/recipedb.cpp
@@ -0,0 +1,989 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2003-2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "backends/recipedb.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <kprogress.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <kaboutdata.h>
+#include <kprocess.h>
+#include <kprocio.h>
+#include <kfilterdev.h>
+#include <kmessagebox.h>
+
+#include <qfile.h>
+#include <qstringlist.h>
+#include <qtextstream.h>
+
+#include <map>
+
+#include "importers/kreimporter.h"
+
+#if HAVE_POSTGRESQL
+#include "PostgreSQL/psqlrecipedb.h"
+#endif
+
+#if HAVE_MYSQL
+#include "MySQL/mysqlrecipedb.h"
+#endif
+
+#if HAVE_SQLITE || HAVE_SQLITE3
+#include "SQLite/literecipedb.h"
+#endif
+
+#include "datablocks/categorytree.h"
+#include "datablocks/ingredientpropertylist.h"
+#include "datablocks/weight.h"
+
+#include "searchparameters.h"
+
+#include "usda_property_data.h"
+#include "usda_ingredient_data.h"
+#include "usda_unit_data.h"
+
+#define DB_FILENAME "krecipes.krecdb"
+
+struct ingredient_nutrient_data
+{
+ int usda_id;
+ QString name;
+ QValueList<double> data;
+ WeightList weights;
+};
+
+RecipeDB::RecipeDB() :
+ DCOPObject(),
+ QObject(), m_categoryCache(0), haltOperation(false)
+{
+ dbOK = false;
+ dbErr = "";
+}
+
+RecipeDB::~RecipeDB()
+{
+}
+
+double RecipeDB::latestDBVersion() const
+{
+ return 0.95;
+}
+
+QString RecipeDB::krecipes_version() const
+{
+ KInstance * this_instance = KGlobal::instance();
+ if ( this_instance && this_instance->aboutData() )
+ return this_instance->aboutData() ->version();
+
+ return QString::null; //Oh, well. We couldn't get the version (shouldn't happen).
+}
+
+RecipeDB* RecipeDB::createDatabase( const QString &dbType, const QString &file )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Server" );
+ QString host = config->readEntry( "Host", "localhost" );
+ QString user = config->readEntry( "Username", QString::null );
+ QString pass = config->readEntry( "Password", QString::null );
+ QString dbname = config->readEntry( "DBName", DEFAULT_DB_NAME );
+ int port = config->readNumEntry( "Port", 0 );
+
+ QString f = file;
+ if ( f.isEmpty() )
+ f = config->readEntry( "DBFile", locateLocal ( "appdata", DB_FILENAME ) );
+
+ return createDatabase( dbType, host, user, pass, dbname, port, f );
+}
+
+RecipeDB* RecipeDB::createDatabase( const QString &dbType, const QString &host, const QString &user, const QString &pass, const QString &dbname, int port, const QString &file )
+{
+ RecipeDB * database = 0;
+
+ if ( 0 )
+ ; //we need some condition here
+#if HAVE_SQLITE || HAVE_SQLITE3
+
+ else if ( dbType == "SQLite" ) {
+ database = new LiteRecipeDB( file );
+ }
+#endif //HAVE_SQLITE || HAVE_SQLITE3
+ #if HAVE_MYSQL
+ else if ( dbType == "MySQL" ) {
+ database = new MySQLRecipeDB( host, user, pass, dbname, port );
+ }
+#endif //HAVE_MYSQL
+ #if HAVE_POSTGRESQL
+ else if ( dbType == "PostgreSQL" ) {
+ database = new PSqlRecipeDB( host, user, pass, dbname, port );
+ }
+#endif //HAVE_POSTGRESQL
+ else {
+ kdDebug() << "No database support included (or available) for the " << dbType << " database." << endl;
+ }
+
+ return database;
+}
+
+void RecipeDB::updateCategoryCache( int limit )
+{
+ m_categoryCache = new CategoryTree;
+ loadCategories( m_categoryCache, limit, 0, -1, true );
+}
+
+void RecipeDB::clearCategoryCache()
+{
+ delete m_categoryCache;
+ m_categoryCache = 0;
+}
+
+void RecipeDB::loadCachedCategories( CategoryTree **list, int limit, int offset, int parent_id, bool recurse )
+{
+ if ( m_categoryCache ) {
+ if ( parent_id == -1 )
+ *list = m_categoryCache;
+ else //FIXME?: how slow is this find() call? the cache is loaded in sequential order, so should we iterate over the cache?
+ *list = m_categoryCache->find(parent_id);
+ //kdDebug() << "Loading category tree from the cache" << endl;
+ }
+ else {
+ loadCategories( *list, limit, offset, parent_id, recurse );
+ }
+}
+
+RecipeDB::ConversionStatus RecipeDB::convertIngredientUnits( const Ingredient &from, const Unit &to, Ingredient &result )
+{
+ result = from;
+
+ if ( from.units.id == to.id )
+ return Success;
+
+ if ( from.units.type == to.type && to.type != Unit::Other ) {
+ double ratio = unitRatio( from.units.id, to.id );
+ if ( ratio > 0 ) {
+ result.amount = from.amount * ratio;
+ result.units = to;
+
+ kdDebug()<<"Unit conversion SUCCESSFUL, from "<<unitName(from.units.id).name<<" to "<<unitName(to.id).name<<" for ingredient "<<ingredientName(from.ingredientID)<<" (ingredient not used in conversion)"<<endl;
+
+ return Success;
+ }
+ else {
+ kdDebug()<<"Unit conversion failed, you should probably update your unit conversion table."<<endl;
+ kdDebug()<<from.units.id<<" to "<<to.id<<endl;
+ return MissingUnitConversion;
+ }
+ }
+ else if ( to.type == Unit::Mass || from.units.type == Unit::Mass ) {
+ if ( from.ingredientID == -1 )
+ return MissingIngredient;
+
+ double fromToWeightRatio, weightToToRatio;
+ int unitID = -1;
+ int prepID = -2;
+
+ WeightList idList = ingredientWeightUnits( from.ingredientID );
+
+ if ( idList.count() == 0 )
+ return MissingIngredientWeight;
+
+ for ( WeightList::const_iterator it = idList.begin(); it != idList.end(); ++it ) {
+ //get conversion order correct (i.e., Mass -> Volume instead of Volume -> Mass, depending on unit type)
+ int first = (to.type == Unit::Mass)?(*it).perAmountUnitID:(*it).weightUnitID;
+ int second = (to.type == Unit::Mass)?(*it).weightUnitID:(*it).perAmountUnitID;
+ double tryFromToWeightRatio = unitRatio( from.units.id, first );
+ if ( tryFromToWeightRatio > 0 ) {
+ weightToToRatio = unitRatio( second, to.id );
+ fromToWeightRatio = tryFromToWeightRatio;
+ unitID = first;
+
+ kdDebug()<<"units work, is it the right prep method..."<<endl;
+ if ( from.prepMethodList.containsId( (*it).prepMethodID ) ) {
+ kdDebug()<<" yes"<<endl;
+ prepID = (*it).prepMethodID;
+ break;
+ }
+ kdDebug()<<" no, keep going"<<endl;
+ }
+ }
+ if ( unitID == -1 )
+ return MissingUnitConversion;
+
+ bool wasApproximated;
+
+ Ingredient i;
+ i.ingredientID = from.ingredientID;
+ i.units.id = unitID;
+ i.amount = from.amount * fromToWeightRatio;
+ i.prepMethodList = from.prepMethodList;
+ result.amount = ingredientWeight( i, &wasApproximated ) * weightToToRatio;
+ result.units = to;
+
+ if ( result.amount < 0 )
+ return MismatchedPrepMethod;
+ else if ( wasApproximated )
+ return MismatchedPrepMethodUsingApprox;
+
+ return Success;
+ }
+ else {
+ QString to_str;
+ switch ( to.type ) {
+ case Unit::Other: to_str = "Other"; break;
+ case Unit::Mass: to_str = "Mass"; break;
+ case Unit::Volume: to_str = "Volume"; break;
+ case Unit::All: kdDebug()<<"Code error: trying to convert to unit of type 'All'"<<endl; return InvalidTypes;
+ }
+ QString from_str;
+ switch ( from.units.type ) {
+ case Unit::Other: from_str = "Other"; break;
+ case Unit::Mass: from_str = "Mass"; break;
+ case Unit::Volume: from_str = "Volume"; break;
+ case Unit::All: kdDebug()<<"Code error: trying to convert from unit of type 'All'"<<endl; return InvalidTypes;
+ }
+ kdDebug()<<"Can't handle conversion from "<<from_str<<"("<<from.units.id<<") to "<<to_str<<"("<<to.id<<")"<<endl;
+
+ return InvalidTypes;
+ }
+}
+
+bool RecipeDB::backup( const QString &backup_file, QString *errMsg )
+{
+ kdDebug()<<"Backing up current database to "<<backup_file<<endl;
+
+ KProcess *p = new KProcess;
+ //p->setUseShell(true);
+
+ QIODevice *dumpFile = KFilterDev::deviceForFile(backup_file,"application/x-gzip");
+ if ( !dumpFile->open( IO_WriteOnly ) ) {
+ kdDebug()<<"Couldn't open "<<backup_file<<endl;
+ return false;
+ }
+
+ dumpStream = new QTextStream( dumpFile );
+
+ QStringList command = backupCommand();
+ if ( command.count() == 0 ) {
+ kdDebug()<<"Backup not available for this database backend"<<endl;
+ return false;
+ }
+
+ KConfig * config = kapp->config();
+ config->setGroup( "DBType" );
+
+ (*dumpStream) << "-- Generated for Krecipes v"<<krecipes_version()<<endl;
+ (*dumpStream) << "-- Krecipes database schema: "<<latestDBVersion()<<endl;
+ (*dumpStream) << "-- Krecipes database backend: "<<config->readEntry( "Type" )<<endl;
+
+ kdDebug()<<"Running '"<<command.first()<<"' to create backup file"<<endl;
+ *p << command /*<< ">" << backup_file*/;
+
+ QApplication::connect( p, SIGNAL(receivedStdout(KProcess*,char*,int)), this, SLOT(processDumpOutput(KProcess*,char*,int)) );
+ QApplication::connect( p, SIGNAL(receivedStderr(KProcess*,char*,int)), this, SLOT(processDumpOutput(KProcess*,char*,int)) );
+
+ emit progressBegin(0,QString::null,
+ QString("<center><b>%1</b></center>%2")
+ .arg(i18n("Creating complete backup"))
+ .arg(i18n("Depending on the number of recipes and amount of data, this could take some time.")),50);
+
+ bool success = p->start( KProcess::Block, KProcess::AllOutput );
+ if ( !success ) {
+ if ( errMsg ) *errMsg = QString(i18n("Unable to find or run the program '%1'. Either it is not installed on your system or it is not in $PATH.")).arg(command.first());
+ delete p;
+ delete dumpStream;
+ delete dumpFile;
+ QFile::remove(backup_file);
+ emit progressDone();
+ return false;
+ }
+
+ emit progressDone();
+
+ //User cancelled it; we'll still consider the operation successful,
+ //but delete the file we created
+ if ( !p->normalExit() ) {
+ kdDebug()<<"Process killed, deleting partial backup."<<endl;
+ QFile::remove(backup_file);
+ }
+
+ if ( p->exitStatus() != 0 ) {
+ //Since the process failed, dumpStream should have output from the app as to why it did
+ QString appOutput;
+ dumpFile->close();
+ if ( dumpFile->open( IO_ReadOnly ) ) {
+ QTextStream appErrStream( dumpFile );
+
+ //ignore our own versioning output
+ appErrStream.readLine();
+ appErrStream.readLine();
+ appErrStream.readLine();
+
+ appOutput = appErrStream.read();
+ }
+ else
+ kdDebug()<<"Unable to open file to get error output."<<endl;
+
+ if ( errMsg ) *errMsg = QString("%1\n%2").arg(i18n("Backup failed.")).arg(appOutput);
+ QFile::remove(backup_file);
+ delete p;
+ delete dumpStream;
+ delete dumpFile;
+ return false;
+ }
+
+ delete p;
+ delete dumpStream;
+ delete dumpFile;
+ return true;
+}
+
+void RecipeDB::processDumpOutput( KProcess *p, char *buffer, int buflen )
+{
+ int written = dumpStream->device()->writeBlock(buffer,buflen);
+ if ( written != buflen )
+ kdDebug()<<"Data lost: written ("<<written<<") != buflen ("<<buflen<<")"<<endl;
+
+ if ( haltOperation ) { haltOperation=false; p->kill(); return; }
+ emit progress();
+}
+
+void RecipeDB::initializeData( void )
+{
+ // Populate with data
+
+ // Read the commands form the data file
+ QFile datafile( KGlobal::dirs() ->findResource( "appdata", "data/data.sql" ) );
+ if ( datafile.open( IO_ReadOnly ) ) {
+ QTextStream stream( &datafile );
+ execSQL(stream);
+ datafile.close();
+ }
+}
+
+bool RecipeDB::restore( const QString &file, QString *errMsg )
+{
+ QIODevice *dumpFile = KFilterDev::deviceForFile(file,"application/x-gzip");
+ if ( dumpFile->open( IO_ReadOnly ) ) {
+
+ QTextStream stream( dumpFile );
+ QString firstLine = stream.readLine().stripWhiteSpace();
+ QString dbVersion = stream.readLine().stripWhiteSpace();
+ dbVersion = dbVersion.right( dbVersion.length() - dbVersion.find(":") - 2 );
+ if ( qRound(dbVersion.toDouble()*1e5) > qRound(latestDBVersion()*1e5) ) { //correct for float's imprecision
+ if ( errMsg ) *errMsg = i18n( "This backup was created with a newer version of Krecipes and cannot be restored." );
+ delete dumpFile;
+ return false;
+ }
+
+ KConfig * config = kapp->config();
+ config->setGroup( "DBType" );
+ QString dbType = stream.readLine().stripWhiteSpace();
+ dbType = dbType.right( dbType.length() - dbType.find(":") - 2 );
+ if ( dbType.isEmpty() || !firstLine.startsWith("-- Generated for Krecipes") ) {
+ if ( errMsg ) *errMsg = i18n("This file is not a Krecipes backup file or has become corrupt.");
+ delete dumpFile;
+ return false;
+ }
+ else if ( dbType != config->readEntry("Type",QString::null) ) {
+ if ( errMsg ) *errMsg = QString(i18n("This backup was created using the \"%1\" backend. It can only be restored into a database using this backend." )).arg(dbType);
+ delete dumpFile;
+ return false;
+ }
+
+
+ //We have to first wipe the database structure. Note that if we load a dump
+ //with from a previous version of Krecipes, the difference in structure
+ // wouldn't allow the data to be inserted. This remains forward-compatibity
+ //by loading the old schema and then porting it to the current version.
+ empty(); //the user had better be warned!
+
+ KProcIO *process = new KProcIO;
+
+ QStringList command = restoreCommand();
+ kdDebug()<<"Restoring backup using: "<<command[0]<<endl;
+ *process << command;
+
+ //process->setComm( KProcess::Stdin );
+ if ( process->start( KProcess::NotifyOnExit ) ) {
+ emit progressBegin(0,QString::null,
+ QString("<center><b>%1</b></center>%2")
+ .arg(i18n("Restoring backup"))
+ .arg(i18n("Depending on the number of recipes and amount of data, this could take some time.")));
+
+ do {
+ QByteArray array(4096);
+ int len = dumpFile->readBlock(array.data(),array.size());
+ array.resize(len);
+
+ if ( !process->writeStdin(array) )
+ kdDebug()<<"Yikes! Some input couldn't be written to the process!"<<endl;
+
+ if ( haltOperation ) { break; }
+ emit progress();
+ }
+ while ( !stream.atEnd() );
+
+ process->closeWhenDone();
+
+ //Since the process will exit when all stdin has been sent and processed,
+ //just loop until the process is no longer running. If something goes
+ //wrong, the user can still hit cancel.
+ int prog = 0;
+ while ( process->isRunning() ){
+ if ( haltOperation ) { break; }
+ kapp->processEvents();
+ if ( prog % 100 == 0 ) {
+ emit progress();
+ prog = 0;
+ }
+ ++prog;
+ }
+ }
+ else
+ kdDebug()<<"Unable to start process"<<endl;
+
+ delete process;
+ emit progressDone();
+
+ //Since we just loaded part of a file, the database won't be in a usable state.
+ //We'll wipe out the database structure and recreate it, leaving no data.
+ if ( haltOperation ) {
+ haltOperation=false;
+ empty();
+ checkIntegrity();
+ delete dumpFile;
+ if ( errMsg ) { *errMsg = i18n("Restore Failed"); }
+ return false;
+ }
+
+ dumpFile->close();
+
+ checkIntegrity();
+ }
+ else {
+ kdDebug()<<"Unable to open the selected backup file"<<endl;
+ return false;
+ }
+
+ delete dumpFile;
+ return true;
+}
+
+void RecipeDB::execSQL( QTextStream &stream )
+{
+ QString line, command;
+ while ( (line = stream.readLine()) != QString::null ) {
+ command += " "+line;
+ if ( command.startsWith(" --") ) {
+ command = QString::null;
+ }
+ else if ( command.endsWith(";") ) {
+ execSQL( command );
+ command = QString::null;
+ }
+ }
+}
+
+void RecipeDB::loadRecipe( Recipe *recipe, int items, int id )
+{
+ RecipeList rlist;
+ QValueList<int> ids; ids << id;
+ loadRecipes( &rlist, items, ids );
+
+ *recipe = *rlist.begin();
+}
+
+int RecipeDB::categoryCount()
+{
+ return getCount("categories");
+}
+
+int RecipeDB::authorCount()
+{
+ return getCount("authors");
+}
+
+int RecipeDB::ingredientCount()
+{
+ return getCount("ingredients");
+}
+
+int RecipeDB::prepMethodCount()
+{
+ return getCount("prep_methods");
+}
+
+int RecipeDB::unitCount()
+{
+ return getCount("units");
+}
+
+void RecipeDB::importSamples()
+{
+ QString sample_recipes = locate( "appdata", "data/samples-" + KGlobal::locale() ->language() + ".kreml" );
+ if ( sample_recipes.isEmpty() ) {
+ //TODO: Make this a KMessageBox??
+ kdDebug() << "NOTICE: Samples recipes for the language \"" << KGlobal::locale() ->language() << "\" are not available. However, if you would like samples recipes for this language included in future releases of Krecipes, we invite you to submit your own. Just save your favorite recipes in the kreml format and e-mail them to jkivlighn@gmail.com." << endl;
+
+ sample_recipes = locate( "appdata", "data/samples-en_US.kreml" ); //default to English
+ }
+ if ( !sample_recipes.isEmpty() ) {
+ KreImporter importer;
+
+ QStringList file;
+ file << sample_recipes;
+ importer.parseFiles( file );
+
+ importer.import( this, true );
+ }
+ else
+ kdDebug() << "Unable to find samples recipe file (samples-en_US.kreml)" << endl;
+}
+
+void RecipeDB::getIDList( const CategoryTree *categoryTree, QStringList &ids )
+{
+ for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ ids << QString::number(child_it->category.id);
+ getIDList(child_it,ids );
+ }
+}
+
+QString RecipeDB::buildSearchQuery( const RecipeSearchParameters &p ) const
+{
+ QStringList queryList, conditionList, tableList;
+
+ if ( p.ingsOr.count() != 0 ) {
+ tableList << "ingredient_list il" << "ingredients i";
+ conditionList << "il.ingredient_id=i.id" << "il.recipe_id=r.id";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.ingsOr.begin(); it != p.ingsOr.end();) {
+ condition += "i.name LIKE '%"+escapeAndEncode(*it)+"%' ";
+ if ( ++it != p.ingsOr.end() ) {
+ condition += "OR ";
+ }
+ }
+ condition += ")";
+
+ conditionList << condition;
+ }
+
+ if ( p.catsOr.count() != 0 ) {
+ tableList << "category_list cl" << "categories c";
+ conditionList << "cl.category_id=c.id" << "cl.recipe_id=r.id";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.catsOr.begin(); it != p.catsOr.end();) {
+ condition += "c.name LIKE '%"+escapeAndEncode(*it)+"%' ";
+ if ( ++it != p.catsOr.end() ) {
+ condition += "OR ";
+ }
+ }
+ condition += ")";
+
+ conditionList << condition;
+ }
+
+ if ( p.authorsOr.count() != 0 ) {
+ tableList << "author_list al" << "authors a";
+ conditionList << "al.author_id=a.id" << "al.recipe_id=r.id";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.authorsOr.begin(); it != p.authorsOr.end();) {
+ condition += "a.name LIKE '%"+escapeAndEncode(*it)+"%'";
+ if ( ++it != p.authorsOr.end() ) {
+ condition += "OR ";
+ }
+ }
+ condition += ")";
+
+ conditionList << condition;
+ }
+
+ if ( p.titleKeywords.count() != 0 ) {
+ QString op = (p.requireAllTitleWords) ? "AND " : "OR ";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.titleKeywords.begin(); it != p.titleKeywords.end();) {
+ condition += "r.title LIKE '%"+escapeAndEncode(*it)+"%' ";
+ if ( ++it != p.titleKeywords.end() ) {
+ condition += op;
+ }
+ }
+ condition += ")";
+ conditionList << condition;
+ }
+
+ if ( p.instructionsKeywords.count() != 0 ) {
+ QString op = (p.requireAllInstructionsWords) ? "AND " : "OR ";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.instructionsKeywords.begin(); it != p.instructionsKeywords.end();) {
+ condition += "r.instructions LIKE '%"+escapeAndEncode(*it)+"%' ";
+ if ( ++it != p.instructionsKeywords.end() ) {
+ condition += op;
+ }
+ }
+ condition += ")";
+ conditionList << condition;
+ }
+
+ if ( !p.prep_time.isNull() ) {
+ QString op;
+ switch ( p.prep_param ) {
+ case 0: op = "<= "+p.prep_time.toString( "'hh:mm:ss'" ); break;
+ case 1: //TODO: have a configurable 'about'. It tests within 15 minutes for now.
+ QTime lower = p.prep_time; lower.addSecs( 60*15 );
+ QTime upper = p.prep_time; upper.addSecs( 60*-15 );
+ op = "BETWEEN "+lower.toString( "'hh:mm:ss'" )+" AND "+upper.toString( "'hh:mm:ss'" );
+ break;
+ }
+ conditionList << "r.prep_time "+op;
+ }
+
+ if ( p.servings > 0 ) {
+ QString op;
+ switch ( p.servings_param ) {
+ case 0: op = "> "+QString::number(p.servings); break;
+ case 1: op = "< "+QString::number(p.servings); break;
+ case 2: op = "BETWEEN "+QString::number(p.servings-5)+" AND "+QString::number(p.servings+5); break;
+ }
+ conditionList << "r.yield_amount "+op;
+ }
+
+ if ( p.createdDateBegin.isValid() ) {
+ if ( p.createdDateEnd.isValid() ) {
+ conditionList << "r.ctime >= '"+p.createdDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.ctime <= '"+p.createdDateEnd.toString(Qt::ISODate)+"'";
+ }
+ else {
+ if ( p.createdDateBegin.time().isNull() ) { //we just want something on a particular date, not time
+ QDateTime end = p.createdDateBegin.addDays(1);
+ conditionList << "r.ctime >= '"+p.createdDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.ctime <= '"+end.toString(Qt::ISODate)+"'";
+ }
+ else //use the exact time
+ conditionList << "r.ctime = '"+p.createdDateBegin.toString(Qt::ISODate)+"'";
+ }
+ }
+
+ if ( p.modifiedDateBegin.isValid() ) {
+ if ( p.modifiedDateEnd.isValid() ) {
+ conditionList << "r.mtime >= '"+p.modifiedDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.mtime <= '"+p.modifiedDateEnd.toString(Qt::ISODate)+"'";
+ }
+ else {
+ if ( p.modifiedDateBegin.time().isNull() ) { //we just want something on a particular date, not time
+ QDateTime end = p.modifiedDateBegin.addDays(1);
+ conditionList << "r.mtime >= '"+p.modifiedDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.mtime <= '"+end.toString(Qt::ISODate)+"'";
+ }
+ else //use the exact time
+ conditionList << "r.mtime = '"+p.modifiedDateBegin.toString(Qt::ISODate)+"'";
+ }
+ }
+
+ if ( p.accessedDateBegin.isValid() ) {
+ if ( p.accessedDateEnd.isValid() ) {
+ conditionList << "r.atime >= '"+p.accessedDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.atime <= '"+p.accessedDateEnd.toString(Qt::ISODate)+"'";
+ }
+ else {
+ if ( p.accessedDateBegin.time().isNull() ) { //we just want something on a particular date, not time
+ QDateTime end = p.accessedDateBegin.addDays(1);
+ conditionList << "r.atime >= '"+p.accessedDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.atime <= '"+end.toString(Qt::ISODate)+"'";
+ }
+ else //use the exact time
+ conditionList << "r.atime = '"+p.accessedDateBegin.toString(Qt::ISODate)+"'";
+ }
+ }
+
+ QString wholeQuery = "SELECT r.id FROM recipes r"
+ +QString(tableList.count()!=0?","+tableList.join(","):"")
+ +QString(conditionList.count()!=0?" WHERE "+conditionList.join(" AND "):"");
+
+ kdDebug()<<"calling: "<<wholeQuery<<endl;
+ return wholeQuery+";";
+}
+
+//These are helper functions solely for use by the USDA data importer
+void getIngredientNameAndID( std::multimap<int, QString> * );
+int createUnit( const QString &name, Unit::Type, RecipeDB* );
+int createIngredient( const QString &name, int unit_g_id, int unit_mg_id, RecipeDB*, bool do_checks );
+void create_properties( RecipeDB* );
+
+void RecipeDB::importUSDADatabase()
+{
+ //check if the data file even exists before we do anything
+ QString abbrev_file = locate( "appdata", "data/abbrev.txt" );
+ if ( abbrev_file.isEmpty() ) {
+ kdDebug() << "Unable to find abbrev.txt data file." << endl;
+ return ;
+ }
+
+ QFile file( abbrev_file );
+ if ( !file.open( IO_ReadOnly ) ) {
+ kdDebug() << "Unable to open data file: " << abbrev_file << endl;
+ return ;
+ }
+
+ create_properties( this );
+
+ std::multimap<int, QString> *ings_and_ids = new std::multimap<int, QString>;
+ getIngredientNameAndID( ings_and_ids );
+
+ QTextStream stream( &file );
+ QValueList<ingredient_nutrient_data> *data = new QValueList<ingredient_nutrient_data>;
+
+ kdDebug() << "Parsing abbrev.txt" << endl;
+ while ( !stream.atEnd() ) {
+ QStringList fields = QStringList::split( "^", stream.readLine(), true );
+
+ int id = fields[ 0 ].mid( 1, fields[ 0 ].length() - 2 ).toInt();
+
+ std::multimap<int, QString>::iterator current_pair;
+ while ( ( current_pair = ings_and_ids->find( id ) ) != ings_and_ids->end() ) //there may be more than one ingredients with the same id
+ {
+ ingredient_nutrient_data current_ing;
+ current_ing.name = ( *current_pair ).second.latin1();
+
+ for ( int i = 2; i < TOTAL_USDA_PROPERTIES + 2; i++ ) //properties start at the third field (index 2)
+ current_ing.data << fields[ i ].toDouble();
+
+ Weight w;
+ w.weight = fields[ TOTAL_USDA_PROPERTIES + 2 ].toDouble();
+
+ QString amountAndWeight = fields[ TOTAL_USDA_PROPERTIES + 3 ].mid( 1, fields[ TOTAL_USDA_PROPERTIES + 3 ].length() - 2 );
+ if ( !amountAndWeight.isEmpty() ) {
+ int spaceIndex = amountAndWeight.find(" ");
+ w.perAmount = amountAndWeight.left(spaceIndex).toDouble();
+
+ QString perAmountUnit = amountAndWeight.right(amountAndWeight.length()-spaceIndex-1);
+
+ if ( parseUSDAUnitAndPrep( perAmountUnit, w.perAmountUnit, w.prepMethod ) )
+ current_ing.weights << w;
+ }
+
+ w = Weight();
+ w.weight = fields[ TOTAL_USDA_PROPERTIES + 4 ].toDouble();
+ amountAndWeight = fields[ TOTAL_USDA_PROPERTIES + 5 ].mid( 1, fields[ TOTAL_USDA_PROPERTIES + 5 ].length() - 2 );
+ if ( !amountAndWeight.isEmpty() ) {
+ int spaceIndex = amountAndWeight.find(" ");
+ w.perAmount = amountAndWeight.left(spaceIndex).toDouble();
+ QString perAmountUnit = amountAndWeight.right(amountAndWeight.length()-spaceIndex-1);
+
+ if ( parseUSDAUnitAndPrep( perAmountUnit, w.perAmountUnit, w.prepMethod ) )
+ current_ing.weights << w;
+ }
+
+ current_ing.usda_id = id;
+
+ data->append( current_ing );
+
+ ings_and_ids->erase( current_pair );
+ }
+ }
+
+ delete ings_and_ids;
+
+ //there's 13009 lines in the weight file
+ emit progressBegin( data->count(), i18n( "Nutrient Import" ), i18n( "Importing USDA nutrient data" ) );
+
+ //if there is no data in the database, we can really speed this up with this
+ bool do_checks = true;
+ {
+ ElementList ing_list;
+ loadIngredients( &ing_list );
+
+ if ( ing_list.count() == 0 ) {
+ kdDebug()<<"Found an empty database... enabling fast nutrient import"<<endl;
+ do_checks = false;
+ }
+ }
+
+ //since there are only two units used, lets just create them and store their id for speed
+ int unit_g_id = createUnit( "g", Unit::Mass, this );
+ int unit_mg_id = createUnit( "mg", Unit::Mass, this );
+
+ QValueList<ingredient_nutrient_data>::const_iterator it;
+ QValueList<ingredient_nutrient_data>::const_iterator data_end = data->end();
+ const int total = data->count();
+ int counter = 0;
+
+ for ( it = data->begin(); it != data_end; ++it ) {
+ counter++;
+ kdDebug() << "Inserting (" << counter << " of " << total << "): " << ( *it ).name << endl;
+
+ if ( haltOperation ) { haltOperation=false; break;}
+ emit progress();
+
+ int assigned_id = createIngredient( ( *it ).name, unit_g_id, unit_mg_id, this, do_checks );
+
+ //for now, only check if there is any info on the ingredient to see whether or not we will import this data,
+ //because checking to see that each property exists is quite slow
+ IngredientPropertyList ing_properties;
+ if ( do_checks ) loadProperties( &ing_properties, assigned_id );
+ if ( ing_properties.count() == 0 ) //ingredient doesn't already have any properties
+ {
+ QValueList<double>::const_iterator property_it;
+ QValueList<double>::const_iterator property_end = ( *it ).data.end();
+ int i = 0;
+ for ( property_it = ( *it ).data.begin(); property_it != property_end; ++property_it, ++i )
+ addPropertyToIngredient( assigned_id, property_data_list[ i ].id, ( *property_it ) / 100.0, unit_g_id );
+ }
+
+ WeightList existingWeights = ingredientWeightUnits( assigned_id );
+ const WeightList weights = (*it).weights;
+ for ( WeightList::const_iterator weight_it = weights.begin(); weight_it != weights.end(); ++weight_it ) {
+ Weight w = *weight_it;
+ w.perAmountUnitID = createUnit( w.perAmountUnit, Unit::Other, this );
+ w.weightUnitID = unit_g_id;
+ w.ingredientID = assigned_id;
+
+ //TODO optimze by creating all prep methods and storing them for faster non-db access
+ if ( !w.prepMethod.isEmpty() ) {
+ int prepID = findExistingPrepByName( w.prepMethod );
+ if ( prepID == -1 ) {
+ createNewPrepMethod( w.prepMethod );
+ prepID = lastInsertID();
+ }
+ w.prepMethodID = prepID;
+ }
+
+ bool exists = false;
+ for ( WeightList::const_iterator it = existingWeights.begin(); it != existingWeights.end(); ++it ) {
+ if ( (*it).perAmountUnitID == w.perAmountUnitID && (*it).prepMethodID == w.prepMethodID ) {
+ exists = true;
+ break;
+ }
+ }
+ if ( exists )
+ continue;
+
+ addIngredientWeight( w );
+ }
+ }
+
+ delete data;
+
+ kdDebug() << "USDA data import successful" << endl;
+
+ emit progressDone();
+}
+
+void getIngredientNameAndID( std::multimap<int, QString> *data )
+{
+ for ( int i = 0; !ingredient_data_list[ i ].name.isEmpty(); i++ )
+ data->insert( std::make_pair( ingredient_data_list[ i ].usda_id, ingredient_data_list[ i ].name ) );
+}
+
+int createIngredient( const QString &name, int unit_g_id, int unit_mg_id, RecipeDB *database, bool do_checks )
+{
+ bool ingredientExisted = true;
+ int assigned_id = -1;
+ if ( do_checks )
+ assigned_id = database->findExistingIngredientByName( name );
+
+ if ( assigned_id == -1 ) {
+ ingredientExisted = false;
+ database->createNewIngredient( name );
+ assigned_id = database->lastInsertID();
+ }
+
+ if ( !ingredientExisted || !database->ingredientContainsUnit( assigned_id, unit_g_id ) )
+ database->addUnitToIngredient( assigned_id, unit_g_id );
+
+ if ( !ingredientExisted || !database->ingredientContainsUnit( assigned_id, unit_mg_id ) )
+ database->addUnitToIngredient( assigned_id, unit_mg_id );
+
+ return assigned_id;
+}
+
+int createUnit( const QString &name, Unit::Type type, RecipeDB *database )
+{
+ int assigned_id = database->findExistingUnitByName( name );
+
+ if ( assigned_id == -1 ) //create unit since it doesn't exist
+ {
+ Unit unit(name, name);
+ unit.type = type;
+ database->createNewUnit( unit );
+ assigned_id = database->lastInsertID();
+ }
+ //keep what the user specified if the type here is Other
+ else if ( type != Unit::Other ) {
+ Unit unit = database->unitName(assigned_id);
+ if ( unit.type != type ) {
+ unit.type = type;
+ database->modUnit( unit );
+ }
+ }
+
+ return assigned_id;
+}
+
+void create_properties( RecipeDB *database )
+{
+ IngredientPropertyList property_list;
+ database->loadProperties( &property_list );
+
+ for ( int i = 0; !property_data_list[ i ].name.isEmpty(); i++ ) {
+ property_data_list[ i ].id = property_list.findByName( property_data_list[ i ].name );
+ if ( property_data_list[ i ].id == -1 ) //doesn't exist, so insert it and set property_data_list[i].id
+ {
+ database->addProperty( property_data_list[ i ].name, property_data_list[ i ].unit );
+ property_data_list[ i ].id = database->lastInsertID();
+ }
+ }
+}
+
+bool parseUSDAUnitAndPrep( const QString &string, QString &unit, QString &prep )
+{
+ int commaIndex = string.find(",");
+ QString unitPart = string.left(commaIndex);
+ QString prepPart = string.right(string.length()-commaIndex-2).stripWhiteSpace();
+
+ bool acceptable = false;
+ for ( int i = 0; unit_data_list[ i ].name; ++i ) {
+ if ( unitPart == unit_data_list[ i ].name || unitPart == unit_data_list[ i ].plural )
+ acceptable = true;
+ }
+ if ( !acceptable )
+ return false;
+
+ acceptable = false;
+ if ( prepPart.isEmpty() )
+ acceptable = true;
+ else {
+ for ( int i = 0; prep_data_list[ i ]; ++i ) {
+ if ( prepPart == prep_data_list[ i ] )
+ acceptable = true;
+ }
+ }
+ if ( !acceptable )
+ prepPart = QString::null;
+
+ unit = unitPart;
+ prep = prepPart;
+ return true;
+}
+
+#include "recipedb.moc"
diff --git a/krecipes/src/backends/recipedb.h b/krecipes/src/backends/recipedb.h
new file mode 100644
index 0000000..3901b56
--- /dev/null
+++ b/krecipes/src/backends/recipedb.h
@@ -0,0 +1,397 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2003-2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPEDB_H
+#define RECIPEDB_H
+
+#include <qobject.h>
+#include <qstring.h>
+#include <qvaluelist.h>
+
+#include <dcopclient.h>
+
+#include "krecipesdbiface.h"
+
+#include "datablocks/recipe.h"
+#include "datablocks/recipelist.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/ingredientpropertylist.h"
+#include "datablocks/unitratiolist.h"
+#include "datablocks/unit.h"
+
+#define DEFAULT_DB_NAME "Krecipes"
+
+/**
+@author Unai Garro
+*/
+
+class KProcess;
+class QTextStream;
+
+class CategoryTree;
+class RecipeSearchParameters;
+class Weight;
+class WeightList;
+
+typedef struct
+{
+ QValueList <int> recipeIdList;
+ IngredientList ilist;
+}
+RecipeIngredientList;
+
+class RecipeDB: public QObject, virtual public KrecipesDBIface
+{
+ Q_OBJECT
+
+public:
+ RecipeDB();
+ virtual ~RecipeDB();
+
+ virtual void connect( bool create_db = true, bool create_tables = true ) = 0;
+
+ void importSamples();
+
+ bool backup( const QString &file ){ return backup(file,0); }
+ bool backup( const QString &file, QString *errMsg = 0 );
+ bool restore( const QString &file, QString *errMsg = 0 );
+
+ // Error handling (passive)
+ bool dbOK;
+ QString dbErr;
+
+ enum RecipeItems {
+ None = 0,
+ NamesOnly = 256,
+ Noatime = 1024,
+ Photo = 1,
+ Instructions = 2,
+ Ingredients = 4,
+ Authors = 8,
+ Categories = 16,
+ PrepTime = 32,
+ Yield = 64,
+ Title = 128,
+ Meta = 512,
+ Ratings = 2048,
+ Properties = 4096,
+ IngredientAmounts = 8192,
+ All = 0xFFFF ^ NamesOnly ^ Noatime
+ };
+
+ typedef enum ConversionStatus {
+ Success,
+ MissingUnitConversion,
+ MissingIngredientWeight,
+ MissingIngredient,
+ InvalidTypes,
+ MismatchedPrepMethod,
+ MismatchedPrepMethodUsingApprox
+ };
+
+public slots:
+ void cancelOperation(){ haltOperation = true; }
+
+signals:
+ void progressBegin(int,const QString &c=QString::null,const QString &t=QString::null,int rate=1);
+ void progressDone();
+ void progress();
+
+ void authorCreated( const Element & );
+ void authorRemoved( int id );
+
+ void categoryCreated( const Element &, int parent_id );
+ void categoryRemoved( int id );
+ void categoryModified( const Element & );
+ void categoryModified( int id, int parent_id );
+ void categoriesMerged( int id1, int id2 );
+
+ void ingGroupCreated( const Element & );
+ void ingGroupRemoved( int id );
+
+ void ingredientCreated( const Element & );
+ void ingredientRemoved( int id );
+
+ void prepMethodCreated( const Element & );
+ void prepMethodRemoved( int id );
+
+ void propertyCreated( const IngredientProperty & );
+ void propertyRemoved( int id );
+
+ void unitCreated( const Unit & );
+ void unitRemoved( int id );
+
+ void ratingCriteriaCreated( const Element & );
+
+ void recipeCreated( const Element &, const ElementList &categories );
+ void recipeRemoved( int id );
+ void recipeRemoved( int id, int cat_id );
+ void recipeModified( const Element &, const ElementList &categories );
+
+ // Public methods
+public:
+ /** Returns a database object of the given type or NULL upon failure.
+ * This function should be called to create a new database, rather
+ * than directly calling the constructor of a specific backend.
+ */
+ static RecipeDB* createDatabase( const QString &dbType,
+ const QString &host,
+ const QString &user,
+ const QString &pass,
+ const QString &DBname,
+ int port,
+ const QString &file = QString::null );
+
+ /** Convenience method. Calls the above with arguments from KConfig. */
+ static RecipeDB* createDatabase( const QString &dbType, const QString &file = QString::null );
+
+ virtual void addIngredientWeight( const Weight & ) = 0;
+ virtual void addProperty( const QString &name, const QString &units ) = 0;
+ virtual void addPropertyToIngredient( int ingredientID, int propertyID, double amount, int perUnitsID ) = 0;
+ virtual void addUnitToIngredient( int ingredientID, int unitID ) = 0;
+
+ virtual void categorizeRecipe( int recipeID, const ElementList &categoryList ) = 0;
+ virtual void changePropertyAmountToIngredient( int ingredientID, int propertyID, double amount, int per_units ) = 0;
+
+ virtual void createNewAuthor( const QString &authorName ) = 0;
+ virtual void createNewCategory( const QString &categoryName, int parent_id = -1 ) = 0;
+ virtual void createNewIngGroup( const QString &name ) = 0;
+ virtual void createNewIngredient( const QString &ingredientName ) = 0;
+ virtual void createNewPrepMethod( const QString &prepMethodName ) = 0;
+ virtual void createNewRating( const QString &name ) = 0;
+ virtual void createNewUnit( const Unit &unit ) = 0;
+ virtual void createNewYieldType( const QString &type ) = 0;
+
+ virtual void emptyData( void ) = 0;
+ virtual void empty( void ) = 0;
+
+ virtual int findExistingAuthorByName( const QString& name ) = 0;
+ virtual int findExistingCategoryByName( const QString& name ) = 0;
+ virtual int findExistingIngredientGroupByName( const QString& name ) = 0;
+ virtual int findExistingIngredientByName( const QString& name ) = 0;
+ virtual int findExistingPrepByName( const QString& name ) = 0;
+ virtual int findExistingPropertyByName( const QString& name ) = 0;
+ virtual int findExistingRatingByName( const QString& name ) = 0;
+ virtual int findExistingRecipeByName( const QString& name ) = 0;
+ virtual int findExistingUnitByName( const QString& name ) = 0;
+ virtual int findExistingYieldTypeByName( const QString& name ) = 0;
+ virtual void findIngredientUnitDependancies( int ingredientID, int unitID, ElementList *recipes, ElementList *ingredientInfo ) = 0;
+ virtual void findIngredientDependancies( int ingredientID, ElementList *recipes ) = 0;
+ virtual void findPrepMethodDependancies( int prepMethodID, ElementList *recipes ) = 0;
+ virtual void findUnitDependancies( int unitID, ElementList *properties, ElementList *recipes, ElementList *weights ) = 0;
+ virtual void findUseOfIngGroupInRecipes( ElementList *results, int groupID ) = 0;
+ virtual void findUseOfCategoryInRecipes( ElementList *results, int catID ) = 0;
+ virtual void findUseOfAuthorInRecipes( ElementList *results, int authorID ) = 0;
+
+ void getIDList( const CategoryTree *categoryTree, QStringList &ids );
+ virtual QString getUniqueRecipeTitle( const QString &recipe_title ) = 0;
+ virtual void givePermissions( const QString &dbName, const QString &username, const QString &password = QString::null, const QString &clientHost = "localhost" ) = 0;
+
+ void importUSDADatabase();
+
+ virtual bool ingredientContainsProperty( int ingredientID, int propertyID, int perUnitsID ) = 0;
+ virtual bool ingredientContainsUnit( int ingredientID, int unitID ) = 0;
+
+ void initializeData( void );
+
+ virtual int lastInsertID() = 0;
+
+ virtual void loadAuthors( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadCategories( CategoryTree *list, int limit = -1, int offset = 0, int parent_id = -1, bool recurse = true ) = 0;
+ void loadCachedCategories( CategoryTree **list, int limit, int offset, int parent_id, bool recurse );
+ virtual void loadCategories( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadIngredientGroups( ElementList *list ) = 0;
+ virtual void loadIngredients( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadPossibleUnits( int ingredientID, UnitList *list ) = 0;
+ virtual void loadPrepMethods( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadProperties( IngredientPropertyList *list, int ingredientID = -2 ) = 0; // Loads the list of possible properties by default, all the ingredient properties with -1, and the ingredients of given property if id>=0
+ void loadRecipe( Recipe *recipe, int items, int id );
+
+ virtual void loadRatingCriterion( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ /** Load all recipes with the ids in @param ids into the @ref RecipeList @param recipes */
+ virtual void loadRecipes( RecipeList *, int items = All, QValueList<int> ids = QValueList<int>()/*, KProgressDialog *progress_dlg = 0*/ ) = 0;
+ virtual void loadRecipeList( ElementList *list, int categoryID = -1, bool recursive = false ) = 0;
+ virtual void loadUncategorizedRecipes( ElementList *list ) = 0;
+ virtual void loadUnits( UnitList *list, Unit::Type = Unit::All, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadUnitRatios( UnitRatioList *ratioList, Unit::Type ) = 0;
+ virtual void loadYieldTypes( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+
+ /** Change all instances of authors with id @param id2 to @param id1 */
+ virtual void mergeAuthors( int id1, int id2 ) = 0;
+
+ /** Change all instances of categories with id @param id2 to @param id1 */
+ virtual void mergeCategories( int id1, int id2 ) = 0;
+
+ virtual void mergeIngredientGroups( int id1, int id2 ) = 0;
+
+ /** Change all instances of ingredients with id @param id2 to @param id1 */
+ virtual void mergeIngredients( int id1, int id2 ) = 0;
+
+ /** Change all instances of units with id @param id2 to @param id1 */
+ virtual void mergeUnits( int id1, int id2 ) = 0;
+
+ /** Change all instances of prep methods with id @param id2 to @param id1 */
+ virtual void mergePrepMethods( int id1, int id2 ) = 0;
+
+ virtual void mergeProperties( int id1, int id2 ) = 0;
+
+
+ virtual void modIngredientGroup( int ingredientID, const QString &newLabel ) = 0;
+ /**
+ * set newLabel for ingredientID
+ */
+ virtual void modIngredient( int ingredientID, const QString &newLabel ) = 0;
+ /**
+ * set newLabel for unitID
+ */
+ virtual void modUnit( const Unit &unit ) = 0;
+ /**
+ * set newLabel for categoryID
+ */
+ virtual void modCategory( int categoryID, const QString &newLabel ) = 0;
+ virtual void modCategory( int categoryID, int new_parent_id ) = 0;
+ /**
+ * set newLabel for authorID
+ */
+ virtual void modAuthor( int authorID, const QString &newLabel ) = 0;
+
+ virtual void modPrepMethod( int prepMethodID, const QString &newLabel ) = 0;
+
+ virtual void modProperty( int propertyID, const QString &newLabel ) = 0;
+
+ virtual QString recipeTitle( int recipeID ) = 0;
+
+ virtual void removeAuthor( int categoryID ) = 0;
+ virtual void removeCategory( int categoryID ) = 0;
+ virtual void removeIngredientGroup( int ingredientID ) = 0;
+ virtual void removeIngredient( int ingredientID ) = 0;
+ virtual void removeIngredientWeight( int id ) = 0;
+ virtual void removePrepMethod( int prepMethodID ) = 0;
+ virtual void removeProperty( int propertyID ) = 0;
+ virtual void removePropertyFromIngredient( int ingredientID, int propertyID, int perUnitID ) = 0;
+ virtual void removeRecipe( int id ) = 0;
+ virtual void removeRecipeFromCategory( int ingredientID, int categoryID ) = 0;
+ virtual void removeUnit( int unitID ) = 0;
+ virtual void removeUnitFromIngredient( int ingredientID, int unitID ) = 0;
+ virtual void removeUnitRatio( int unitID1, int unitID2 ) = 0;
+
+ virtual void saveRecipe( Recipe *recipe ) = 0;
+ virtual void saveUnitRatio( const UnitRatio *ratio ) = 0;
+ virtual void search( RecipeList *list, int items, const RecipeSearchParameters &parameters ) = 0;
+
+ /** @returns true on success, false otherwise */
+ ConversionStatus convertIngredientUnits( const Ingredient &from, const Unit &to, Ingredient &result );
+ virtual double unitRatio( int unitID1, int unitID2 ) = 0;
+
+ /** @returns the number of grams in the given amount of the ingredient, or -1 on failure */
+ virtual double ingredientWeight( const Ingredient &ing, bool *wasApproximated = 0 ) = 0;
+ virtual WeightList ingredientWeightUnits( int ingID ) = 0;
+
+ virtual QString escapeAndEncode( const QString &s ) const = 0;
+ virtual QString unescapeAndDecode( const QCString &s ) const = 0;
+
+ virtual QString categoryName( int ID ) = 0;
+ virtual QString ingredientName( int ID ) = 0;
+ virtual QString prepMethodName( int ID ) = 0;
+ virtual IngredientProperty propertyName( int ID ) = 0;
+ virtual Unit unitName( int ID ) = 0;
+
+ virtual int categoryTopLevelCount() = 0;
+ virtual int getCount( const QString &table_name ) = 0;
+ int authorCount();
+ int ingredientCount();
+ int prepMethodCount();
+ int unitCount();
+ int categoryCount();
+
+ virtual bool checkIntegrity( void ) = 0;
+
+ virtual void createTable( const QString &tableName ) = 0;
+ virtual void splitCommands( QString& s, QStringList& sl ) = 0;
+
+ virtual float databaseVersion( void ) = 0;
+
+ int maxAuthorNameLength() const
+ {
+ return 50;
+ }
+ int maxCategoryNameLength() const
+ {
+ return 40;
+ }
+ int maxIngredientNameLength() const
+ {
+ return 50;
+ }
+ int maxIngGroupNameLength() const
+ {
+ return 50;
+ }
+ int maxRecipeTitleLength() const
+ {
+ return 200;
+ }
+ int maxUnitNameLength() const
+ {
+ return 20;
+ }
+ int maxPrepMethodNameLength() const
+ {
+ return 20;
+ }
+ int maxPropertyNameLength() const
+ {
+ return 20;
+ }
+ int maxYieldTypeLength() const
+ {
+ return 20;
+ }
+
+ virtual bool ok()
+ {
+ return ( dbOK );
+ }
+ virtual QString err()
+ {
+ return ( dbErr );
+ }
+
+ void updateCategoryCache( int limit );
+ void clearCategoryCache();
+
+protected:
+ virtual void portOldDatabases( float version ) = 0;
+ virtual QStringList backupCommand() const = 0;
+ virtual QStringList restoreCommand() const = 0;
+
+ //Use these with caution: SQL for one backend might not work on another!
+ void execSQL( QTextStream &stream );
+ virtual void execSQL( const QString & ) = 0;
+
+ QString buildSearchQuery( const RecipeSearchParameters &parameters ) const;
+
+ double latestDBVersion() const;
+ QString krecipes_version() const;
+
+ CategoryTree *m_categoryCache;
+
+private:
+ QTextStream *dumpStream;
+ bool haltOperation;
+
+private slots:
+ void processDumpOutput( KProcess *, char *buffer, int buflen );
+};
+
+#endif
diff --git a/krecipes/src/backends/searchparameters.h b/krecipes/src/backends/searchparameters.h
new file mode 100644
index 0000000..c3e354a
--- /dev/null
+++ b/krecipes/src/backends/searchparameters.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+* Copyright (C) 2005 *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SEARCHPARAMETERS_H
+#define SEARCHPARAMETERS_H
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qdatetime.h>
+
+class RecipeSearchParameters
+{
+public:
+ RecipeSearchParameters() : servings(-1)/*, averageRating(-1), averageRatingOffset(0)*/
+ {}
+
+ QStringList titleKeywords;
+ bool requireAllTitleWords;
+
+ QStringList instructionsKeywords;
+ bool requireAllInstructionsWords;
+
+ QStringList ingsOr;
+ QStringList catsOr;
+ QStringList authorsOr;
+
+ QTime prep_time;
+
+ /** 0 -> greater than given time
+ * 1 -> less than given time
+ * 2 -> about given time
+ */
+ int prep_param;
+
+ int servings;
+
+ /** 0 -> greater than given time
+ * 1 -> less than given time
+ * 2 -> about given time
+ */
+ int servings_param;
+
+ QDateTime createdDateBegin;
+ QDateTime createdDateEnd;
+ QDateTime modifiedDateBegin;
+ QDateTime modifiedDateEnd;
+ QDateTime accessedDateBegin;
+ QDateTime accessedDateEnd;
+
+ //RatingCriteriaList criteriaList;
+ //double averageRating;
+ //double averageRatingOffset;
+};
+
+
+#endif
diff --git a/krecipes/src/backends/usda_ingredient_data.h b/krecipes/src/backends/usda_ingredient_data.h
new file mode 100644
index 0000000..f7ad2df
--- /dev/null
+++ b/krecipes/src/backends/usda_ingredient_data.h
@@ -0,0 +1,486 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef USDA_INGREDIENT_DATA_H
+#define USDA_INGREDIENT_DATA_H
+
+#include <klocale.h>
+
+struct ingredient_data
+{
+ QString name;
+ int usda_id;
+};
+
+static ingredient_data ingredient_data_list[] = {
+ {I18N_NOOP( "active baker's yeast" ), 18375},
+ {I18N_NOOP( "all-purpose flour" ), 20081},
+ {I18N_NOOP( "allspice" ), 2001},
+ {I18N_NOOP( "almond extract" ), 12071},
+ {I18N_NOOP( "almonds" ), 12061},
+ {I18N_NOOP( "apple juice" ), 9016},
+ {I18N_NOOP( "apple slices" ), 9008},
+ {I18N_NOOP( "apples" ), 9003},
+ {I18N_NOOP( "applesauce" ), 9402},
+ {I18N_NOOP( "applesauce, unsweetened" ), 9019},
+ {I18N_NOOP( "apricot jam" ), 19719},
+ {I18N_NOOP( "bacon" ), 10123},
+ {I18N_NOOP( "baking potato" ), 11362},
+ {I18N_NOOP( "baking powder" ), 18369},
+ {I18N_NOOP( "baking soda" ), 18372},
+ {I18N_NOOP( "bananas" ), 9040},
+ {I18N_NOOP( "barley" ), 20004},
+ {I18N_NOOP( "basil" ), 2003},
+ {I18N_NOOP( "bay leaf" ), 2004},
+ {I18N_NOOP( "bean sprouts" ), 11248},
+ {I18N_NOOP( "beans, black" ), 16014},
+ {I18N_NOOP( "beef bottom round" ), 13399},
+ {I18N_NOOP( "beef bouillon" ), 6075},
+ {I18N_NOOP( "beef broth" ), 6008},
+ {I18N_NOOP( "beef stock" ), 6476},
+ {I18N_NOOP( "beef, brisket" ), 13022},
+ {I18N_NOOP( "beer" ), 14003},
+ {I18N_NOOP( "bell peppers (red, green, yellow)" ), 11333},
+ {I18N_NOOP( "black pepper" ), 2030},
+ {I18N_NOOP( "boiling water" ), 14429},
+ {I18N_NOOP( "bologna" ), 7008},
+ {I18N_NOOP( "bourbon" ), 14551},
+ {I18N_NOOP( "bran flakes" ), 8153},
+ {I18N_NOOP( "bread" ), 18069},
+ {I18N_NOOP( "bread crumbs" ), 18079},
+ {I18N_NOOP( "bread cubes" ), 18079},
+ {I18N_NOOP( "broccoli" ), 11090},
+ {I18N_NOOP( "broccoli (frozen-thawed)" ), 11092},
+ {I18N_NOOP( "broccoli spears, frozen" ), 11094},
+ {I18N_NOOP( "broccoli, frozen" ), 11092},
+ {I18N_NOOP( "brown rice" ), 20040},
+ {I18N_NOOP( "brown sugar" ), 19334},
+ {I18N_NOOP( "bulgur" ), 20012},
+ {I18N_NOOP( "butter" ), 1145},
+ {I18N_NOOP( "butter (1/2 stick)" ), 1145},
+ {I18N_NOOP( "butter or margarine" ), 1145},
+ {I18N_NOOP( "buttermilk" ), 1088},
+ {I18N_NOOP( "cabbage" ), 11109},
+ {I18N_NOOP( "canned apple slices" ), 19312},
+ {I18N_NOOP( "canned beans" ), 16006},
+ {I18N_NOOP( "canned beef" ), 13934},
+ {I18N_NOOP( "canned black beans" ), 16018},
+ {I18N_NOOP( "canned carrot slices" ), 11128},
+ {I18N_NOOP( "canned cooked squid" ), 15175},
+ {I18N_NOOP( "canned corn" ), 11174},
+ {I18N_NOOP( "canned crushed tomatoes" ), 11693},
+ {I18N_NOOP( "canned green beans" ), 11056},
+ {I18N_NOOP( "canned green peas" ), 11308},
+ {I18N_NOOP( "canned jalapeno pepper" ), 11632},
+ {I18N_NOOP( "canned kidney beans" ), 16029},
+ {I18N_NOOP( "canned pinto beans" ), 16044},
+ {I18N_NOOP( "canned potato" ), 11376},
+ {I18N_NOOP( "canned red pepper" ), 2031},
+ {I18N_NOOP( "canned red tart cherries in water" ), 9065},
+ {I18N_NOOP( "canned sweet potatoes" ), 11514},
+ {I18N_NOOP( "canned tomatoes" ), 11693},
+ {I18N_NOOP( "canned wax beans" ), 16029},
+ {I18N_NOOP( "canned white beans" ), 16051},
+ {I18N_NOOP( "canned whole kernel corn" ), 11771},
+ {I18N_NOOP( "carrot" ), 11124},
+ {I18N_NOOP( "carrot slices, canned" ), 11128},
+ {I18N_NOOP( "carrots" ), 11124},
+ {I18N_NOOP( "carrots, frozen" ), 11130},
+ {I18N_NOOP( "carrots, peeled and chopped" ), 11124},
+ {I18N_NOOP( "catsup" ), 11935},
+ {I18N_NOOP( "cauliflower, frozen" ), 11137},
+ {I18N_NOOP( "cayenne" ), 2031},
+ {I18N_NOOP( "celery" ), 11143},
+ {I18N_NOOP( "celery leaf" ), 11143},
+ {I18N_NOOP( "celery salt" ), 2007},
+ {I18N_NOOP( "celery seed" ), 2007},
+ {I18N_NOOP( "cheddar cheese" ), 1009},
+ {I18N_NOOP( "cheese" ), 1009},
+ {I18N_NOOP( "cherry pie filling" ), 19314},
+ {I18N_NOOP( "chicken" ), 5011},
+ {I18N_NOOP( "chicken (1-2 lb)" ), 5011},
+ {I18N_NOOP( "chicken bouillon" ), 6081},
+ {I18N_NOOP( "chicken breast" ), 5062},
+ {I18N_NOOP( "chicken broth" ), 6013},
+ {I18N_NOOP( "chicken pieces" ), 5008},
+ {I18N_NOOP( "chicken stock" ), 6413},
+ {I18N_NOOP( "chicken thigh" ), 5080},
+ {I18N_NOOP( "chicken, cooked" ), 5011},
+ {I18N_NOOP( "chickens, whole" ), 5123},
+ {I18N_NOOP( "chili" ), 16059},
+ {I18N_NOOP( "chili powder" ), 2009},
+ {I18N_NOOP( "chinese turnip" ), 11564},
+ {I18N_NOOP( "chives" ), 11615},
+ {I18N_NOOP( "chocolate chip" ), 19139},
+ {I18N_NOOP( "chocolate pudding mix (instant)" ), 19123},
+ {I18N_NOOP( "chopped capers" ), 2054},
+ {I18N_NOOP( "chopped carrot" ), 11124},
+ {I18N_NOOP( "chopped celery" ), 11143},
+ {I18N_NOOP( "chopped chives" ), 11156},
+ {I18N_NOOP( "chopped green chilies" ), 11980},
+ {I18N_NOOP( "chopped onion" ), 11282},
+ {I18N_NOOP( "chopped onions" ), 11282},
+ {I18N_NOOP( "chopped onions, frozen" ), 11287},
+ {I18N_NOOP( "chopped parsley" ), 11297},
+ {I18N_NOOP( "chopped pimientos" ), 11943},
+ {I18N_NOOP( "chopped walnuts" ), 12154},
+ {I18N_NOOP( "cider vinegar" ), 2048},
+ {I18N_NOOP( "cilantro" ), 11971},
+ {I18N_NOOP( "cinnamon" ), 2010},
+ {I18N_NOOP( "clear honey" ), 19296},
+ {I18N_NOOP( "cloves" ), 2011},
+ {I18N_NOOP( "cloves garlic" ), 11215},
+ {I18N_NOOP( "cloves or allspice" ), 2011},
+ {I18N_NOOP( "cocoa" ), 19165},
+ {I18N_NOOP( "coconut" ), 12104},
+ {I18N_NOOP( "coconut milk (canned)" ), 12118},
+ {I18N_NOOP( "cold butter" ), 1145},
+ {I18N_NOOP( "cold water" ), 14429},
+ {I18N_NOOP( "condensed milk (sweetened)" ), 1095},
+ {I18N_NOOP( "cooked chicken" ), 5124},
+ {I18N_NOOP( "cooked chicken breast" ), 5065},
+ {I18N_NOOP( "cooked ham" ), 10151},
+ {I18N_NOOP( "cooked turkey" ), 5182},
+ {I18N_NOOP( "cooked wild rice" ), 20089},
+ {I18N_NOOP( "cookie crust (chocolate - 8-9 in)" ), 18398},
+ {I18N_NOOP( "Cool Whip" ), 1054},
+ {I18N_NOOP( "corn chips" ), 19003},
+ {I18N_NOOP( "corn meal" ), 20020},
+ {I18N_NOOP( "corn oil" ), 4518},
+ {I18N_NOOP( "corn starch" ), 20027},
+ {I18N_NOOP( "corn syrup" ), 19350},
+ {I18N_NOOP( "cornmeal" ), 20022},
+ {I18N_NOOP( "cornstarch" ), 20027},
+ {I18N_NOOP( "cottage cheese" ), 1015},
+ {I18N_NOOP( "cream (heavy)" ), 1053},
+ {I18N_NOOP( "cream cheese" ), 1017},
+ {I18N_NOOP( "cream of celery soup" ), 6479},
+ {I18N_NOOP( "cream of chicken soup" ), 6483},
+ {I18N_NOOP( "cream of chicken soup, condensed" ), 6016},
+ {I18N_NOOP( "cream of mushroom soup" ), 6443},
+ {I18N_NOOP( "cream of mushroom soup, condensed" ), 6043},
+ {I18N_NOOP( "croutons to serve" ), 18243},
+ {I18N_NOOP( "crushed pineapple in juice" ), 9268},
+ {I18N_NOOP( "cucumber" ), 11205},
+ {I18N_NOOP( "cucumber, whole" ), 11206},
+ {I18N_NOOP( "cucumbers" ), 11206},
+ {I18N_NOOP( "cucumbers, whole" ), 11206},
+ {I18N_NOOP( "cumin" ), 2014},
+ {I18N_NOOP( "currants" ), 9084},
+ {I18N_NOOP( "dehydrated onion flakes" ), 11284},
+ {I18N_NOOP( "diced chicken" ), 5039},
+ {I18N_NOOP( "dried basil" ), 2044},
+ {I18N_NOOP( "dried onion" ), 11284},
+ {I18N_NOOP( "dried oregano" ), 2027},
+ {I18N_NOOP( "dried parsley" ), 2029},
+ {I18N_NOOP( "dried porcini mushrooms" ), 11268},
+ {I18N_NOOP( "dried red pepper" ), 2031},
+ {I18N_NOOP( "dried sage" ), 2038},
+ {I18N_NOOP( "dry bread crumbs" ), 18079},
+ {I18N_NOOP( "dry mustard" ), 2024},
+ {I18N_NOOP( "dry nonfat milk powder" ), 1091},
+ {I18N_NOOP( "egg" ), 1123},
+ {I18N_NOOP( "egg noodles (medium)" ), 20409},
+ {I18N_NOOP( "egg substitute" ), 1144},
+ {I18N_NOOP( "eggplant" ), 11209},
+ {I18N_NOOP( "eggplant (baby)" ), 11209},
+ {I18N_NOOP( "eggs" ), 1134},
+ {I18N_NOOP( "eggs, hard cooked" ), 1129},
+ {I18N_NOOP( "eggs, hard-boiled" ), 1129},
+ {I18N_NOOP( "egg whites" ), 1124},
+ {I18N_NOOP( "egg yolk" ), 1125},
+ {I18N_NOOP( "egg yolks" ), 1125},
+ {I18N_NOOP( "elbow macaroni" ), 20099},
+ {I18N_NOOP( "flaked almonds" ), 12061},
+ {I18N_NOOP( "flat anchovies" ), 15001},
+ {I18N_NOOP( "flour" ), 20081},
+ {I18N_NOOP( "flour tortillas" ), 18364},
+ {I18N_NOOP( "flour, all-purpose" ), 20081},
+ {I18N_NOOP( "flour, white" ), 20581},
+ {I18N_NOOP( "flour, whole-grain wheat" ), 20080},
+ {I18N_NOOP( "fresh spinach" ), 11457},
+ {I18N_NOOP( "freshly chopped coriander" ), 11165},
+ {I18N_NOOP( "freshly chopped parsley" ), 11297},
+ {I18N_NOOP( "frozen broccoli" ), 11092},
+ {I18N_NOOP( "frozen broccoli spears" ), 11094},
+ {I18N_NOOP( "frozen cauliflower" ), 11137},
+ {I18N_NOOP( "frozen corn" ), 11179},
+ {I18N_NOOP( "frozen egg whites" ), 1172},
+ {I18N_NOOP( "frozen fish" ), 15027},
+ {I18N_NOOP( "frozen green beans" ), 11060},
+ {I18N_NOOP( "frozen orange juice concentrate" ), 9214},
+ {I18N_NOOP( "frozen peas" ), 11814},
+ {I18N_NOOP( "frozen whole egg" ), 1171},
+ {I18N_NOOP( "garlic" ), 11215},
+ {I18N_NOOP( "garlic clove" ), 11215},
+ {I18N_NOOP( "garlic cloves" ), 11215},
+ {I18N_NOOP( "garlic powder" ), 2020},
+ {I18N_NOOP( "garlic salt" ), 2020},
+ {I18N_NOOP( "gelatin" ), 19173},
+ {I18N_NOOP( "gelatin (peach-flavored)" ), 14397},
+ {I18N_NOOP( "ginger" ), 2021},
+ {I18N_NOOP( "ginger, fresh" ), 2021},
+ {I18N_NOOP( "graham cracker crust (8 or 9 in)" ), 18173},
+ {I18N_NOOP( "granny Smith apple" ), 9003},
+ {I18N_NOOP( "granulated garlic" ), 16125},
+ {I18N_NOOP( "grapefruit" ), 9112},
+ {I18N_NOOP( "gravy" ), 6116},
+ {I18N_NOOP( "green beans (fresh)" ), 11052},
+ {I18N_NOOP( "green chile" ), 11980},
+ {I18N_NOOP( "green chiles" ), 11670},
+ {I18N_NOOP( "green chili pepper" ), 11670},
+ {I18N_NOOP( "green onion" ), 11282},
+ {I18N_NOOP( "green pepper" ), 11333},
+ {I18N_NOOP( "green peppers" ), 11333},
+ {I18N_NOOP( "ground almonds" ), 12061},
+ {I18N_NOOP( "ground beef" ), 13309},
+ {I18N_NOOP( "ground chuck" ), 13302},
+ {I18N_NOOP( "ground cinnamon" ), 2010},
+ {I18N_NOOP( "ground clove" ), 2011},
+ {I18N_NOOP( "ground cloves" ), 2011},
+ {I18N_NOOP( "ground cumin" ), 2014},
+ {I18N_NOOP( "ground ginger" ), 2021},
+ {I18N_NOOP( "ground nutmeg" ), 2025},
+ {I18N_NOOP( "ground oregano" ), 2027},
+ {I18N_NOOP( "ground pepper" ), 2030},
+ {I18N_NOOP( "ground thyme" ), 2042},
+ {I18N_NOOP( "ham" ), 7027},
+ {I18N_NOOP( "hamburger" ), 13309},
+ {I18N_NOOP( "hamburger bun" ), 18641},
+ {I18N_NOOP( "hamburger buns" ), 18641},
+ {I18N_NOOP( "hamburger rolls" ), 18641},
+ {I18N_NOOP( "hazelnuts" ), 12120},
+ {I18N_NOOP( "heavy cream" ), 1053},
+ {I18N_NOOP( "herb stuffing" ), 18082},
+ {I18N_NOOP( "honey" ), 19296},
+ {I18N_NOOP( "horseradish" ), 2055},
+ {I18N_NOOP( "hot pepper flakes" ), 2031},
+ {I18N_NOOP( "hot pepper sauce" ), 6168},
+ {I18N_NOOP( "hot sauce" ), 6164},
+ {I18N_NOOP( "hot water" ), 14429},
+ {I18N_NOOP( "instant dry milk" ), 1155},
+ {I18N_NOOP( "instant rice" ), 20048},
+ {I18N_NOOP( "Italian salad dressing" ), 4114},
+ {I18N_NOOP( "jalapeno peppers" ), 11979},
+ {I18N_NOOP( "juice of 1 lemon" ), 9152},
+ {I18N_NOOP( "ketchup" ), 11935},
+ {I18N_NOOP( "lasagna noodles (8 noodles)" ), 20409},
+ {I18N_NOOP( "leaf lettuce" ), 11253},
+ {I18N_NOOP( "leeks" ), 11246},
+ {I18N_NOOP( "lemon" ), 9150},
+ {I18N_NOOP( "lemon juice" ), 9152},
+ {I18N_NOOP( "lemon juice, bottled" ), 9153},
+ {I18N_NOOP( "lemon juice, frozen" ), 9154},
+ {I18N_NOOP( "lemon peel" ), 9156},
+ {I18N_NOOP( "lemon, sliced" ), 9151},
+ {I18N_NOOP( "lentil" ), 16069},
+ {I18N_NOOP( "lettuce" ), 11253},
+ {I18N_NOOP( "lime juice" ), 9160},
+ {I18N_NOOP( "long-grain white rice" ), 20044},
+ {I18N_NOOP( "low sodium beef broth" ), 6475},
+ {I18N_NOOP( "low sodium chicken broth" ), 6413},
+ {I18N_NOOP( "lowfat 1% milk" ), 1084},
+ {I18N_NOOP( "lowfat cheddar cheese" ), 1168},
+ {I18N_NOOP( "lowfat cottage cheese" ), 1016},
+ {I18N_NOOP( "lowfat mayonnaise" ), 4018},
+ {I18N_NOOP( "lowfat mozzarella cheese" ), 1028},
+ {I18N_NOOP( "lowfat yogurt" ), 1117},
+ {I18N_NOOP( "low-sodium chicken broth" ), 6413},
+ {I18N_NOOP( "low-sodium soy sauce" ), 16125},
+ {I18N_NOOP( "low-sodium vegetable stock" ), 6468},
+ {I18N_NOOP( "margarine" ), 4071},
+ {I18N_NOOP( "margarine, soft" ), 4092},
+ {I18N_NOOP( "marjoram" ), 2023},
+ {I18N_NOOP( "marjoram leaves" ), 2023},
+ {I18N_NOOP( "mayonnaise" ), 4025},
+ {I18N_NOOP( "medium-size egg" ), 1123},
+ {I18N_NOOP( "milk" ), 1081},
+ {I18N_NOOP( "mint leaves" ), 2064},
+ {I18N_NOOP( "mixed fruit in syrup" ), 9099},
+ {I18N_NOOP( "mixed vegetables" ), 11318},
+ {I18N_NOOP( "mixed vegetables, frozen" ), 11583},
+ {I18N_NOOP( "molasses" ), 19304},
+ {I18N_NOOP( "molasses, dark" ), 19305},
+ {I18N_NOOP( "monterey jack cheese" ), 1025},
+ {I18N_NOOP( "mozzarella cheese" ), 1028},
+ {I18N_NOOP( "mung beans" ), 11626},
+ {I18N_NOOP( "mushroom" ), 11260},
+ {I18N_NOOP( "mushrooms" ), 11260},
+ {I18N_NOOP( "mustard" ), 2046},
+ {I18N_NOOP( "mustard seeds" ), 2024},
+ {I18N_NOOP( "navy beans, cooked" ), 16338},
+ {I18N_NOOP( "nonfat dry milk" ), 1092},
+ {I18N_NOOP( "nonfat milk" ), 1085},
+ {I18N_NOOP( "noodles" ), 20409},
+ {I18N_NOOP( "noodles (lasagne)" ), 20409},
+ {I18N_NOOP( "noodles (lasagne) (6-8 bunches)" ), 20409},
+ {I18N_NOOP( "noodles, egg (medium)" ), 20409},
+ {I18N_NOOP( "nutmeg" ), 2025},
+ {I18N_NOOP( "nuts, chopped" ), 16087},
+ {I18N_NOOP( "oil" ), 4518},
+ {I18N_NOOP( "oil, peanut" ), 4042},
+ {I18N_NOOP( "oil, sesame" ), 4058},
+ {I18N_NOOP( "olive oil" ), 4053},
+ {I18N_NOOP( "onion" ), 11282},
+ {I18N_NOOP( "onion, medium" ), 11282},
+ {I18N_NOOP( "onion powder" ), 2026},
+ {I18N_NOOP( "onion salt" ), 2026},
+ {I18N_NOOP( "onion soup" ), 6445},
+ {I18N_NOOP( "onion, chopped" ), 11282},
+ {I18N_NOOP( "onion, large" ), 11282},
+ {I18N_NOOP( "onions" ), 11284},
+ {I18N_NOOP( "onions, chopped" ), 11282},
+ {I18N_NOOP( "orange juice" ), 9206},
+ {I18N_NOOP( "orange juice, from frozen concentra" ), 9215},
+ {I18N_NOOP( "orange rind" ), 9216},
+ {I18N_NOOP( "orange zest" ), 9216},
+ {I18N_NOOP( "oranges" ), 9200},
+ {I18N_NOOP( "oregano" ), 2027},
+ {I18N_NOOP( "Oreo Cookies" ), 18166},
+ {I18N_NOOP( "paprika" ), 2028},
+ {I18N_NOOP( "paprika pepper" ), 2028},
+ {I18N_NOOP( "parmesan cheese" ), 1032},
+ {I18N_NOOP( "parsley" ), 2029},
+ {I18N_NOOP( "parsley flakes" ), 2029},
+ {I18N_NOOP( "parsley stalks" ), 11297},
+ {I18N_NOOP( "parsnips" ), 11298},
+ {I18N_NOOP( "pasta shells" ), 20100},
+ {I18N_NOOP( "peach slices in syrup" ), 9240},
+ {I18N_NOOP( "peanut butter" ), 16098},
+ {I18N_NOOP( "pearled barley" ), 20005},
+ {I18N_NOOP( "peas, canned" ), 11308},
+ {I18N_NOOP( "peas, frozen" ), 11312},
+ {I18N_NOOP( "pecans" ), 12142},
+ {I18N_NOOP( "pepper" ), 2030},
+ {I18N_NOOP( "Pepperidge Farm stuffing" ), 18082},
+ {I18N_NOOP( "pickle relish" ), 11945},
+ {I18N_NOOP( "pickles" ), 11937},
+ {I18N_NOOP( "pimento" ), 11943},
+ {I18N_NOOP( "pine kernels" ), 12149},
+ {I18N_NOOP( "pineapple chunks in juice" ), 9268},
+ {I18N_NOOP( "pineapple juice" ), 9273},
+ {I18N_NOOP( "pinto beans" ), 16042},
+ {I18N_NOOP( "pinto beans, canned" ), 16044},
+ {I18N_NOOP( "plain flour" ), 20081},
+ {I18N_NOOP( "plain low-fat yogurt" ), 1117},
+ {I18N_NOOP( "plain yogurt" ), 1117},
+ {I18N_NOOP( "pork roast" ), 10083},
+ {I18N_NOOP( "potato" ), 11352},
+ {I18N_NOOP( "potato flakes" ), 11379},
+ {I18N_NOOP( "potato Granules" ), 11380},
+ {I18N_NOOP( "potato, canned" ), 11376},
+ {I18N_NOOP( "potatoes" ), 11352},
+ {I18N_NOOP( "potatoes (red-skinned)" ), 11352},
+ {I18N_NOOP( "poultry seasoning" ), 2034},
+ {I18N_NOOP( "powdered sugar" ), 19335},
+ {I18N_NOOP( "processed American cheese" ), 1149},
+ {I18N_NOOP( "provolone cheese" ), 1035},
+ {I18N_NOOP( "prunes" ), 9291},
+ {I18N_NOOP( "pumpkin" ), 11422},
+ {I18N_NOOP( "radishes" ), 11429},
+ {I18N_NOOP( "raisins" ), 9298},
+ {I18N_NOOP( "red burgundy wine" ), 14096},
+ {I18N_NOOP( "red onion" ), 11282},
+ {I18N_NOOP( "red pepper" ), 2031},
+ {I18N_NOOP( "red snapper fillets" ), 15101},
+ {I18N_NOOP( "red wine" ), 14096},
+ {I18N_NOOP( "reduced calorie mayonnaise" ), 4013},
+ {I18N_NOOP( "relish" ), 11945},
+ {I18N_NOOP( "rice" ), 20045},
+ {I18N_NOOP( "ricotta cheese" ), 1037},
+ {I18N_NOOP( "ripe dessert pears" ), 9252},
+ {I18N_NOOP( "rolled oats" ), 20038},
+ {I18N_NOOP( "rosemary" ), 2063},
+ {I18N_NOOP( "rum flavoring or vanilla" ), 2050},
+ {I18N_NOOP( "sage" ), 2038},
+ {I18N_NOOP( "salad dressing (Miracle Whip)" ), 4025},
+ {I18N_NOOP( "salad onions" ), 11282},
+ {I18N_NOOP( "salsa" ), 6164},
+ {I18N_NOOP( "salt" ), 2047},
+ {I18N_NOOP( "sandwich rolls" ), 18349},
+ {I18N_NOOP( "sausage" ), 7063},
+ {I18N_NOOP( "scallions" ), 11291},
+ {I18N_NOOP( "self-raising flour" ), 20082},
+ {I18N_NOOP( "sesame oil" ), 4058},
+ {I18N_NOOP( "shallots" ), 11677},
+ {I18N_NOOP( "shortening" ), 4547},
+ {I18N_NOOP( "shredded carrots" ), 11124},
+ {I18N_NOOP( "shredded lettuce" ), 11252},
+ {I18N_NOOP( "shrimp (raw, medium-size)" ), 15149},
+ {I18N_NOOP( "skim milk" ), 1085},
+ {I18N_NOOP( "skinless boneless chicken breast" ), 5062},
+ {I18N_NOOP( "sliced carrots" ), 11124},
+ {I18N_NOOP( "small mushrooms" ), 11260},
+ {I18N_NOOP( "small onions or shallots" ), 11282},
+ {I18N_NOOP( "smoked bacon" ), 10124},
+ {I18N_NOOP( "soft bread crumbs" ), 18079},
+ {I18N_NOOP( "sour cream" ), 1056},
+ {I18N_NOOP( "soy sauce" ), 16125},
+ {I18N_NOOP( "spaghetti" ), 20120},
+ {I18N_NOOP( "spinach leaf" ), 11457},
+ {I18N_NOOP( "sprig fresh thyme" ), 2049},
+ {I18N_NOOP( "stew beef" ), 13286},
+ {I18N_NOOP( "stock" ), 6413},
+ {I18N_NOOP( "sugar" ), 19335},
+ {I18N_NOOP( "sweet pickle" ), 11940},
+ {I18N_NOOP( "sweet pickle relish" ), 11945},
+ {I18N_NOOP( "sweet potato" ), 11647},
+ {I18N_NOOP( "sweet potato, canned" ), 11645},
+ {I18N_NOOP( "swiss cheese" ), 1040},
+ {I18N_NOOP( "taco sauce" ), 6168},
+ {I18N_NOOP( "taco seasoning mix" ), 2034},
+ {I18N_NOOP( "taco shells" ), 18360},
+ {I18N_NOOP( "taco spice" ), 2034},
+ {I18N_NOOP( "thyme" ), 2042},
+ {I18N_NOOP( "tomato juice" ), 11540},
+ {I18N_NOOP( "tomato paste" ), 11887},
+ {I18N_NOOP( "tomato sauce" ), 11549},
+ {I18N_NOOP( "tomatoes" ), 11529},
+ {I18N_NOOP( "tomatoes, canned" ), 11533},
+ {I18N_NOOP( "tomatoes, stewed" ), 11693},
+ {I18N_NOOP( "tortilla chips" ), 19056},
+ {I18N_NOOP( "tostada shell" ), 18363},
+ {I18N_NOOP( "tuna" ), 15126},
+ {I18N_NOOP( "tuna in water, canned" ), 15126},
+ {I18N_NOOP( "turkey" ), 7079},
+ {I18N_NOOP( "turkey ham" ), 7264},
+ {I18N_NOOP( "turmeric" ), 2043},
+ {I18N_NOOP( "turnip" ), 11564},
+ {I18N_NOOP( "unflavored gelatin" ), 19173},
+ {I18N_NOOP( "vanilla" ), 2050},
+ {I18N_NOOP( "vanilla extract" ), 2050},
+ {I18N_NOOP( "vanilla pudding mix (instant)" ), 19202},
+ {I18N_NOOP( "veal shank" ), 17278},
+ {I18N_NOOP( "vegetable oil" ), 4518},
+ {I18N_NOOP( "vegetable stock" ), 6468},
+ {I18N_NOOP( "Velveeta" ), 1149},
+ {I18N_NOOP( "vinegar" ), 2048},
+ {I18N_NOOP( "walnuts" ), 12154},
+ {I18N_NOOP( "warm water" ), 14429},
+ {I18N_NOOP( "water" ), 14429},
+ {I18N_NOOP( "whipped topping" ), 1054},
+ {I18N_NOOP( "white flour" ), 20481},
+ {I18N_NOOP( "white pepper" ), 2032},
+ {I18N_NOOP( "white rice" ), 20050},
+ {I18N_NOOP( "white turnips" ), 11564},
+ {I18N_NOOP( "white vinegar" ), 2048},
+ {I18N_NOOP( "white wine" ), 14106},
+ {I18N_NOOP( "whole wheat flour" ), 20080},
+ {I18N_NOOP( "yeast" ), 18375},
+ {I18N_NOOP( "yellow squash" ), 11641},
+ {I18N_NOOP( "zucchini" ), 11477},
+ {I18N_NOOP( "zucchini slices" ), 11953},
+ {0, 0}
+ };
+
+#endif //USDA_INGREDIENT_DATA_H
diff --git a/krecipes/src/backends/usda_property_data.h b/krecipes/src/backends/usda_property_data.h
new file mode 100644
index 0000000..a0ae784
--- /dev/null
+++ b/krecipes/src/backends/usda_property_data.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef USDA_PROPERTY_DATA_H
+#define USDA_PROPERTY_DATA_H
+
+#include <klocale.h>
+
+struct property_data
+{
+ int id;
+ QString name;
+ const char *unit;
+};
+
+#define TOTAL_USDA_PROPERTIES 43
+
+//NOTE: the following must be in this order
+static property_data property_data_list[] = {
+ { -1, I18N_NOOP( "water" ), "g"},
+ { -1, I18N_NOOP( "energy" ), "kcal"},
+ { -1, I18N_NOOP( "protein" ), "g"},
+ { -1, I18N_NOOP( "fat" ), "g"},
+ { -1, I18N_NOOP( "ash" ), "g"},
+ { -1, I18N_NOOP( "carbohydrates" ), "g"},
+ { -1, I18N_NOOP( "dietary fiber" ), "g"},
+ { -1, I18N_NOOP( "sugar" ), "g"},
+ { -1, I18N_NOOP( "calcuim" ), "mg"},
+ { -1, I18N_NOOP( "iron" ), "mg"},
+ { -1, I18N_NOOP( "magnesium" ), "mg"},
+ { -1, I18N_NOOP( "phosphorus" ), "mg"},
+ { -1, I18N_NOOP( "potassium" ), "mg"},
+ { -1, I18N_NOOP( "sodium" ), "mg"},
+ { -1, I18N_NOOP( "zinc" ), "mg"},
+ { -1, I18N_NOOP( "copper" ), "mg"},
+ { -1, I18N_NOOP( "manganese" ), "mg"},
+ { -1, I18N_NOOP( "selenium" ), "g"},
+ { -1, I18N_NOOP( "vitamin C" ), "mg"},
+ { -1, I18N_NOOP( "thiamin" ), "mg"},
+ { -1, I18N_NOOP( "riboflavin" ), "mg"},
+ { -1, I18N_NOOP( "niacin" ), "mg"},
+ { -1, I18N_NOOP( "pantothenic acid" ), "mg"},
+ { -1, I18N_NOOP( "vitamin B" ), "mg"},
+ { -1, I18N_NOOP( "folate" ), "g"},
+ { -1, I18N_NOOP( "folic acid" ), "g"},
+ { -1, I18N_NOOP( "food folate" ), "g"},
+ { -1, I18N_NOOP( "folate (DFE)" ), "g"},
+ { -1, I18N_NOOP( "vitamin B12" ), "g"},
+ { -1, I18N_NOOP( "vitamin A" ), "IU"},
+ { -1, I18N_NOOP( "vitamin A (RAE)" ), "mg"},
+ { -1, I18N_NOOP( "retinol" ), "g"},
+ { -1, I18N_NOOP( "vitamin E" ), "g"},
+ { -1, I18N_NOOP( "vitamin K" ), "g"},
+ { -1, I18N_NOOP( "alpha-carotene" ), "g"},
+ { -1, I18N_NOOP( "beta-carotene" ), "g"},
+ { -1, I18N_NOOP( "beta-cryptoxanthin" ), "g"},
+ { -1, I18N_NOOP( "lycopene" ), "g"},
+ { -1, I18N_NOOP( "lutein+zeazanthin" ), "g"},
+ { -1, I18N_NOOP( "saturated fat" ), "g"},
+ { -1, I18N_NOOP( "monounsaturated fat" ), "g"},
+ { -1, I18N_NOOP( "polyunsaturated fat" ), "g"},
+ { -1, I18N_NOOP( "cholesterol" ), "mg"},
+ { 0, 0, 0 }
+ };
+
+#endif //USDA_PROPERTY_DATA_H
diff --git a/krecipes/src/backends/usda_unit_data.h b/krecipes/src/backends/usda_unit_data.h
new file mode 100644
index 0000000..2c3a5ef
--- /dev/null
+++ b/krecipes/src/backends/usda_unit_data.h
@@ -0,0 +1,104 @@
+/***************************************************************************
+* Copyright (C) 2006 *
+* *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef USDA_UNIT_DATA_H
+#define USDA_UNIT_DATA_H
+
+#include <klocale.h>
+
+#include <qstring.h>
+
+struct unit_data {
+ const char *name;
+ const char *plural;
+};
+
+static unit_data unit_data_list[] = {
+ {"bag","bags"},
+ {"block","blocks"},
+ {"bottle","bottles"},
+ {"box","boxes"},
+ {"bunch","bunches"},
+ {"can","cans"},
+ {"cone","cones"},
+ {"container","containers"},
+ {"cube","cubes"},
+ {"cup","cups"},
+ {"fl oz","fl oz"},
+ {"glass","glasses"},
+ {"item","items"},
+ {"loaf","loaves"},
+ {"large","large"},
+ {"lb","lbs"},
+ {"junior","junior"},
+ {"leaf","leaves"},
+ {"medium","medium"},
+ {"oz","oz"},
+ {"pack","packs"},
+ {"package","packages"},
+ {"packet","packets"},
+ {"piece","pieces"},
+ {"pouch","pouches"},
+ {"quart","quarts"},
+ {"scoop","scoops"},
+ {"sheet","sheets"},
+ {"slice","slices"},
+ {"small","small"},
+ {"spear","spears"},
+ {"sprout","spouts"},
+ {"sprig","sprigs"},
+ {"square","squares"},
+ {"stalk","stalks"},
+ {"stem","stems"},
+ {"strip","strips"},
+ {"tablespoon","tablespoons"},
+ {"tbsp","tbsp"},
+ {"teaspoon","teaspoons"},
+ {"tsp","tsp"},
+ {"tube","tubes"},
+ {"unit","units"},
+ {0,0}
+};
+
+static const char * prep_data_list[] = {
+ "chopped",
+ "diced",
+ "sliced",
+ "crumbled",
+ "crushed",
+ "ground",
+ "grated",
+ "mashed",
+ "melted",
+ "packed",
+ "pureed",
+ "quartered",
+ "thawed",
+ "shredded",
+ "sifted",
+ "pared",
+ "flaked",
+ "unpacked",
+ "unsifted",
+ "unthawed",
+ "pitted",
+ "peeled",
+ "cooked",
+ "hulled",
+ "shelled",
+ "raw",
+ "whipped",
+ 0
+};
+
+bool parseUSDAUnitAndPrep( const QString &string, QString &unit, QString &prep );
+
+#endif //USDA_UNIT_DATA_H