/*************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "importers/kreimporter.h" #if HAVE_POSTGRESQL #include "PostgreSQL/psqlrecipedb.h" #endif #if HAVE_MYSQL #include "MySQL/mysqlrecipedb.h" #endif #if 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; TQString name; TQValueList data; WeightList weights; }; RecipeDB::RecipeDB() : DCOPObject(), TQObject(), m_categoryCache(0), haltOperation(false) { dbOK = false; dbErr = ""; } RecipeDB::~RecipeDB() { } double RecipeDB::latestDBVersion() const { return 0.95; } TQString RecipeDB::krecipes_version() const { TDEInstance * this_instance = TDEGlobal::instance(); if ( this_instance && this_instance->aboutData() ) return this_instance->aboutData() ->version(); return TQString::null; //Oh, well. We couldn't get the version (shouldn't happen). } RecipeDB* RecipeDB::createDatabase( const TQString &dbType, const TQString &file ) { TDEConfig * config = kapp->config(); config->setGroup( "Server" ); TQString host = config->readEntry( "Host", "localhost" ); TQString user = config->readEntry( "Username", TQString::null ); TQString pass = config->readEntry( "Password", TQString::null ); TQString dbname = config->readEntry( "DBName", DEFAULT_DB_NAME ); int port = config->readNumEntry( "Port", 0 ); TQString 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 TQString &dbType, const TQString &host, const TQString &user, const TQString &pass, const TQString &dbname, int port, const TQString &file ) { RecipeDB * database = 0; if ( 0 ) ; //we need some condition here #if HAVE_SQLITE3 else if ( dbType == "SQLite" ) { database = new LiteRecipeDB( file ); } #endif // 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 "< 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..."<setUseShell(true); TQIODevice *dumpFile = KFilterDev::deviceForFile(backup_file,"application/x-gzip"); if ( !dumpFile->open( IO_WriteOnly ) ) { kdDebug()<<"Couldn't open "<config(); config->setGroup( "DBType" ); (*dumpStream) << "-- Generated for Krecipes v"<readEntry( "Type" )<" << backup_file*/; TQApplication::connect( p, TQ_SIGNAL(receivedStdout(TDEProcess*,char*,int)), this, TQ_SLOT(processDumpOutput(TDEProcess*,char*,int)) ); TQApplication::connect( p, TQ_SIGNAL(receivedStderr(TDEProcess*,char*,int)), this, TQ_SLOT(processDumpOutput(TDEProcess*,char*,int)) ); emit progressBegin(0,TQString::null, TQString("
%1
%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( TDEProcess::Block, TDEProcess::AllOutput ); if ( !success ) { if ( errMsg ) *errMsg = TQString(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; TQFile::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."<exitStatus() != 0 ) { //Since the process failed, dumpStream should have output from the app as to why it did TQString appOutput; dumpFile->close(); if ( dumpFile->open( IO_ReadOnly ) ) { TQTextStream 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."<device()->writeBlock(buffer,buflen); if ( written != buflen ) kdDebug()<<"Data lost: written ("<kill(); return; } emit progress(); } void RecipeDB::initializeData( void ) { // Populate with data // Read the commands form the data file TQFile datafile( TDEGlobal::dirs() ->findResource( "appdata", "data/data.sql" ) ); if ( datafile.open( IO_ReadOnly ) ) { TQTextStream stream( &datafile ); execSQL(stream); datafile.close(); } } bool RecipeDB::restore( const TQString &file, TQString *errMsg ) { TQIODevice *dumpFile = KFilterDev::deviceForFile(file,"application/x-gzip"); if ( dumpFile->open( IO_ReadOnly ) ) { TQTextStream stream( dumpFile ); TQString firstLine = stream.readLine().stripWhiteSpace(); TQString dbVersion = stream.readLine().stripWhiteSpace(); dbVersion = dbVersion.right( dbVersion.length() - dbVersion.find(":") - 2 ); if ( tqRound(dbVersion.toDouble()*1e5) > tqRound(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; } TDEConfig * config = kapp->config(); config->setGroup( "DBType" ); TQString 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",TQString::null) ) { if ( errMsg ) *errMsg = TQString(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; TQStringList command = restoreCommand(); kdDebug()<<"Restoring backup using: "<setComm( TDEProcess::Stdin ); if ( process->start( TDEProcess::NotifyOnExit ) ) { emit progressBegin(0,TQString::null, TQString("
%1
%2") .arg(i18n("Restoring backup")) .arg(i18n("Depending on the number of recipes and amount of data, this could take some time."))); do { TQByteArray 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!"<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"<close(); checkIntegrity(); } else { kdDebug()<<"Unable to open the selected backup file"< 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() { TQString sample_recipes = locate( "appdata", "data/samples-" + TDEGlobal::locale() ->language() + ".kreml" ); if ( sample_recipes.isEmpty() ) { //TODO: Make this a KMessageBox?? kdDebug() << "NOTICE: Samples recipes for the language \"" << TDEGlobal::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; TQStringList 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, TQStringList &ids ) { for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) { ids << TQString::number(child_it->category.id); getIDList(child_it,ids ); } } TQString RecipeDB::buildSearchQuery( const RecipeSearchParameters &p ) const { TQStringList 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"; TQString condition = "("; for ( TQStringList::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"; TQString condition = "("; for ( TQStringList::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"; TQString condition = "("; for ( TQStringList::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 ) { TQString op = (p.requireAllTitleWords) ? "AND " : "OR "; TQString condition = "("; for ( TQStringList::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 ) { TQString op = (p.requireAllInstructionsWords) ? "AND " : "OR "; TQString condition = "("; for ( TQStringList::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() ) { TQString 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. TQTime lower = p.prep_time; lower.addSecs( 60*15 ); TQTime 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 ) { TQString op; switch ( p.servings_param ) { case 0: op = "> "+TQString::number(p.servings); break; case 1: op = "< "+TQString::number(p.servings); break; case 2: op = "BETWEEN "+TQString::number(p.servings-5)+" AND "+TQString::number(p.servings+5); break; } conditionList << "r.yield_amount "+op; } if ( p.createdDateBegin.isValid() ) { if ( p.createdDateEnd.isValid() ) { conditionList << "r.ctime >= '"+p.createdDateBegin.toString(TQt::ISODate)+"'"; conditionList << "r.ctime <= '"+p.createdDateEnd.toString(TQt::ISODate)+"'"; } else { if ( p.createdDateBegin.time().isNull() ) { //we just want something on a particular date, not time TQDateTime end = p.createdDateBegin.addDays(1); conditionList << "r.ctime >= '"+p.createdDateBegin.toString(TQt::ISODate)+"'"; conditionList << "r.ctime <= '"+end.toString(TQt::ISODate)+"'"; } else //use the exact time conditionList << "r.ctime = '"+p.createdDateBegin.toString(TQt::ISODate)+"'"; } } if ( p.modifiedDateBegin.isValid() ) { if ( p.modifiedDateEnd.isValid() ) { conditionList << "r.mtime >= '"+p.modifiedDateBegin.toString(TQt::ISODate)+"'"; conditionList << "r.mtime <= '"+p.modifiedDateEnd.toString(TQt::ISODate)+"'"; } else { if ( p.modifiedDateBegin.time().isNull() ) { //we just want something on a particular date, not time TQDateTime end = p.modifiedDateBegin.addDays(1); conditionList << "r.mtime >= '"+p.modifiedDateBegin.toString(TQt::ISODate)+"'"; conditionList << "r.mtime <= '"+end.toString(TQt::ISODate)+"'"; } else //use the exact time conditionList << "r.mtime = '"+p.modifiedDateBegin.toString(TQt::ISODate)+"'"; } } if ( p.accessedDateBegin.isValid() ) { if ( p.accessedDateEnd.isValid() ) { conditionList << "r.atime >= '"+p.accessedDateBegin.toString(TQt::ISODate)+"'"; conditionList << "r.atime <= '"+p.accessedDateEnd.toString(TQt::ISODate)+"'"; } else { if ( p.accessedDateBegin.time().isNull() ) { //we just want something on a particular date, not time TQDateTime end = p.accessedDateBegin.addDays(1); conditionList << "r.atime >= '"+p.accessedDateBegin.toString(TQt::ISODate)+"'"; conditionList << "r.atime <= '"+end.toString(TQt::ISODate)+"'"; } else //use the exact time conditionList << "r.atime = '"+p.accessedDateBegin.toString(TQt::ISODate)+"'"; } } TQString wholeQuery = "SELECT r.id FROM recipes r" +TQString(tableList.count()!=0?","+tableList.join(","):"") +TQString(conditionList.count()!=0?" WHERE "+conditionList.join(" AND "):""); kdDebug()<<"calling: "< * ); int createUnit( const TQString &name, Unit::Type, RecipeDB* ); int createIngredient( const TQString &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 TQString abbrev_file = locate( "appdata", "data/abbrev.txt" ); if ( abbrev_file.isEmpty() ) { kdDebug() << "Unable to find abbrev.txt data file." << endl; return ; } TQFile file( abbrev_file ); if ( !file.open( IO_ReadOnly ) ) { kdDebug() << "Unable to open data file: " << abbrev_file << endl; return ; } create_properties( this ); std::multimap *ings_and_ids = new std::multimap; getIngredientNameAndID( ings_and_ids ); TQTextStream stream( &file ); TQValueList *data = new TQValueList; kdDebug() << "Parsing abbrev.txt" << endl; while ( !stream.atEnd() ) { TQStringList fields = TQStringList::split( "^", stream.readLine(), true ); int id = fields[ 0 ].mid( 1, fields[ 0 ].length() - 2 ).toInt(); std::multimap::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(); TQString 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(); TQString 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(); TQString 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"<::const_iterator it; TQValueList::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 { TQValueList::const_iterator property_it; TQValueList::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 *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 TQString &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 TQString &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 TQString &string, TQString &unit, TQString &prep ) { int commaIndex = string.find(","); TQString unitPart = string.left(commaIndex); TQString 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 = TQString::null; unit = unitPart; prep = prepPart; return true; } #include "recipedb.moc"