From 0c0966322b3018fa5fe3be3f52eef867da6ec779 Mon Sep 17 00:00:00 2001 From: Guillaume Melquiond Date: Thu, 3 Aug 2006 17:37:34 +0000 Subject: Simplified and sped up account flushing: only modified accounts are flushed. --- src/account.cpp | 14 +- src/account.h | 17 ++- src/accounthandler.cpp | 8 +- src/dalstorage.cpp | 376 ++++++++++--------------------------------------- src/dalstorage.h | 61 ++------ src/storage.h | 110 +++------------ 6 files changed, 136 insertions(+), 450 deletions(-) (limited to 'src') diff --git a/src/account.cpp b/src/account.cpp index 11a1232a..b81adfe8 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -20,6 +20,8 @@ * $Id$ */ +#include + #include "account.h" #include "utils/functors.h" @@ -29,8 +31,10 @@ */ Account::Account(const std::string& name, const std::string& password, - const std::string& email) - : mName(name), + const std::string& email, + int id) + : mID(id), + mName(name), mPassword(password), mEmail(email), mCharacters(), @@ -207,3 +211,9 @@ PlayerPtr Account::getCharacter(const std::string& name) if (it != end) return *it; return PlayerPtr(); } + +void Account::setID(int id) +{ + assert(mID < 0); + mID = id; +} diff --git a/src/account.h b/src/account.h index b1ba59d0..7597fdea 100644 --- a/src/account.h +++ b/src/account.h @@ -58,7 +58,8 @@ class Account */ Account(const std::string& name, const std::string& password, - const std::string& email); + const std::string& email, + int id = -1); /** @@ -195,6 +196,19 @@ class Account PlayerPtr getCharacter(const std::string& name); + /** + * Get account ID. + * + * @return the unique ID of the account, a negative number if none yet. + */ + int getID() const + { return mID; } + + /** + * Set account ID. + * The account shall not have any ID yet. + */ + void setID(int); private: Account(); @@ -203,6 +217,7 @@ class Account private: + int mID; /**< unique id */ std::string mName; /**< user name */ std::string mPassword; /**< user password (encrypted) */ std::string mEmail; /**< user email address */ diff --git a/src/accounthandler.cpp b/src/accounthandler.cpp index b08e34e4..9287fa22 100644 --- a/src/accounthandler.cpp +++ b/src/accounthandler.cpp @@ -282,7 +282,7 @@ void AccountHandler::processMessage(NetComputer *comp, MessageIn &message) LOG_INFO("Character " << name << " was created for " << computer.getAccount()->getName() << "'s account.", 1); - store.flush(); // flush changes + store.flush(computer.getAccount()); // flush changes result.writeByte(ERRMSG_OK); } break; @@ -361,7 +361,7 @@ void AccountHandler::processMessage(NetComputer *comp, MessageIn &message) std::string deletedCharacter = chars[charNum].get()->getName(); computer.getAccount()->delCharacter(deletedCharacter); - store.flush(); + store.flush(computer.getAccount()); LOG_INFO(deletedCharacter << ": Character deleted...", 1); result.writeByte(ERRMSG_OK); @@ -639,7 +639,6 @@ AccountHandler::handleRegisterMessage(AccountClient &computer, MessageIn &msg) { AccountPtr acc(new Account(username, password, email)); store.addAccount(acc); - store.flush(); LOG_INFO(username << ": Account registered.", 1); reply.writeByte(ERRMSG_OK); @@ -691,8 +690,7 @@ AccountHandler::handleUnregisterMessage(AccountClient &computer, // Delete account and associated characters LOG_INFO("Farewell " << username << " ...", 1); - store.delAccount(username); - store.flush(); + store.delAccount(accPtr); reply.writeByte(ERRMSG_OK); } } diff --git a/src/dalstorage.cpp b/src/dalstorage.cpp index e20a5508..2b3d8b5e 100644 --- a/src/dalstorage.cpp +++ b/src/dalstorage.cpp @@ -20,6 +20,8 @@ * $Id$ */ +#include + #include "dalstorage.h" #include "configuration.h" @@ -32,6 +34,24 @@ #include "utils/functors.h" #include "utils/logger.h" +/** + * Functor used to search an Account by name in Accounts. + */ +class account_by_name +{ + public: + account_by_name(const std::string& name) + : mName(name) + {} + + bool operator()(std::pair const &elem) const + { return elem.second->getName() == mName; } + + private: + std::string mName; /**< the name to look for */ +}; + + /** * Constructor. */ @@ -165,16 +185,11 @@ DALStorage::getAccount(const std::string& userName) open(); // look for the account in the list first. - Accounts::iterator it = - std::find_if( - mAccounts.begin(), - mAccounts.end(), - account_by_name(userName) - ); - - if (it != mAccounts.end()) { - return it->first; - } + Accounts::iterator it_end = mAccounts.end(), + it = std::find_if(mAccounts.begin(), it_end, account_by_name(userName)); + + if (it != it_end) + return it->second; using namespace dal; @@ -193,25 +208,22 @@ DALStorage::getAccount(const std::string& userName) return AccountPtr(NULL); } + // specialize the string_to functor to convert + // a string to an unsigned int. + string_to toUint; + unsigned id = toUint(accountInfo(0, 0)); + // create an Account instance // and initialize it with information about the user. AccountPtr account(new Account(accountInfo(0, 1), accountInfo(0, 2), - accountInfo(0, 3))); - - // specialize the string_to functor to convert - // a string to an unsigned int. - string_to toUint; + accountInfo(0, 3), id)); // specialize the string_to functor to convert // a string to an unsigned short. string_to toUshort; - // add the new Account to the list. - AccountInfo ai; - ai.status = AS_ACC_TO_UPDATE; - ai.id = toUint(accountInfo(0, 0)); - mAccounts.insert(std::make_pair(account, ai)); + mAccounts.insert(std::make_pair(id, account)); // load the characters associated with the account. sql = "select * from "; @@ -280,79 +292,6 @@ DALStorage::getAccount(const std::string& userName) } -/** - * Add a new account. - */ -void -DALStorage::addAccount(const AccountPtr& account) -{ - if (account.get() == 0) { - LOG_WARN("Cannot add a NULL Account.", 0); - // maybe we should throw an exception instead - return; - } - - // mark this account as new so that the next flush will execute a SQL - // insert query instead of a SQL update query. - AccountInfo ai; - ai.status = AS_NEW_ACCOUNT; - // the account id is set to 0 because we know nothing about it at the - // moment, it will be updated once saved into the database. - ai.id = 0; - mAccounts.insert(std::make_pair(account, ai)); -} - - -/** - * Delete an account. - */ -void -DALStorage::delAccount(const std::string& userName) -{ - // look for the account in memory first. - Accounts::iterator it = - std::find_if( - mAccounts.begin(), - mAccounts.end(), - account_by_name(userName) - ); - - if (it != mAccounts.end()) { - switch ((it->second).status) { - case AS_NEW_ACCOUNT: - { - // this is a newly added account and it has not even been - // saved into the database: remove it immediately. - mAccounts.erase(it); - } - break; - - case AS_ACC_TO_UPDATE: - // change the status to AS_ACC_TO_DELETE so that it will be - // deleted at the next flush. - (it->second).status = AS_ACC_TO_DELETE; - break; - - default: - break; - } - - // nothing else to do. - return; - } - - using namespace dal; - - try { - // look for the account directly into the database. - _delAccount(userName); - } - catch (const dal::DbSqlQueryExecFailure& e) { - // TODO: throw an exception. - LOG_ERROR("SQL query failure: " << e.what(), 0); - } -} - /** * Return the list of all Emails addresses. */ @@ -603,37 +542,6 @@ DALStorage::updateChannels(std::map& channelList) } } -/** - * Save changes to the database permanently. - */ -void -DALStorage::flush(void) -{ - Accounts::iterator it = mAccounts.begin(); - Accounts::iterator it_end = mAccounts.end(); - for (; it != it_end; ) { - switch ((it->second).status) { - case AS_NEW_ACCOUNT: - _addAccount(it->first); - ++it; - break; - - case AS_ACC_TO_UPDATE: - _updAccount(it->first); - ++it; - break; - - case AS_ACC_TO_DELETE: - _delAccount(it->first); - mAccounts.erase(it++); - break; - - default: - break; - } - } -} - /** * Create the specified table. @@ -675,15 +583,9 @@ DALStorage::createTable(const std::string& tblName, /** * Add an account to the database. */ -void -DALStorage::_addAccount(const AccountPtr& account) +void DALStorage::addAccount(AccountPtr const &account) { - if (account.get() == 0) { - return; - } - - // assume that account is an element of mAccounts as this method is - // private and only called by flush(). + assert(account->getCharacters().size() == 0); using namespace dal; @@ -707,83 +609,32 @@ DALStorage::_addAccount(const AccountPtr& account) << " where username = \"" << account->getName() << "\";"; const RecordSet& accountInfo = mDb->execSql(sql2.str()); string_to toUint; - - Accounts::iterator account_it = - std::find_if( - mAccounts.begin(), - mAccounts.end(), - account_by_name(account->getName()) - ); - - // update the info of the account. - (account_it->second).status = AS_ACC_TO_UPDATE; - (account_it->second).id = toUint(accountInfo(0, 0)); - - // insert the characters. - Players &characters = account->getCharacters(); - - Players::const_iterator it = characters.begin(), it_end = characters.end(); - for (; it != it_end; ++it) { - std::ostringstream sql3; - sql3 << "insert into " << CHARACTERS_TBL_NAME - << " (name, gender, hair_style, hair_color, level, money, x, y, " - << "map_id, str, agi, vit, int, dex, luck)" - << " values (" - << (account_it->second).id << ", \"" - << (*it)->getName() << "\", " - << (*it)->getGender() << ", " - << (int)(*it)->getHairStyle() << ", " - << (int)(*it)->getHairColor() << ", " - << (int)(*it)->getLevel() << ", " - << (*it)->getMoney() << ", " - << (*it)->getX() << ", " - << (*it)->getY() << ", " - << (int)(*it)->getMapId() << ", " - << (*it)->getRawStat(STAT_STR) << ", " - << (*it)->getRawStat(STAT_AGI) << ", " - << (*it)->getRawStat(STAT_VIT) << ", " - << (*it)->getRawStat(STAT_INT) << ", " - << (*it)->getRawStat(STAT_DEX) << ", " - << (*it)->getRawStat(STAT_LUK) - << ");"; - mDb->execSql(sql3.str()); - - // TODO: inventories. - } + unsigned id = toUint(accountInfo(0, 0)); + account->setID(id); + mAccounts.insert(std::make_pair(id, account)); } +/** + * Update all the accounts from the database. + */ +void DALStorage::flushAll() +{ + for (Accounts::iterator i = mAccounts.begin(), + i_end = mAccounts.end(); i != i_end; ++i) + flush(i->second); +} /** * Update an account from the database. */ -void -DALStorage::_updAccount(const AccountPtr& account) +void DALStorage::flush(AccountPtr const &account) { - if (account.get() == 0) { - return; - } - - // assume that account is an element of mAccounts as this method is - // private and only called by flush(). + assert(account->getID() >= 0); using namespace dal; // TODO: we should start a transaction here so that in case of problem - // the lost of data would be minimized. - - Accounts::iterator account_it = - std::find_if( - mAccounts.begin(), - mAccounts.end(), - account_by_name(account->getName()) - ); - - // doublecheck that this account already exists in the database - // and therefore its status must be AS_ACC_TO_UPDATE. - if ((account_it->second).status != AS_ACC_TO_UPDATE) { - return; // Should we throw an exception here instead? No, because this can happen - // without any bad consequences as long as we return -- Bertram. - } + // the loss of data would be minimized. // update the account. std::ostringstream sql1; @@ -792,17 +643,15 @@ DALStorage::_updAccount(const AccountPtr& account) << "password = \"" << account->getPassword() << "\", " << "email = \"" << account->getEmail() << "\", " << "level = '" << account->getLevel() << "' " - << "where id = '" << (account_it->second).id << "';"; + << "where id = '" << account->getID() << "';"; mDb->execSql(sql1.str()); // get the list of characters that belong to this account. Players &characters = account->getCharacters(); // insert or update the characters. - Players::const_iterator it = characters.begin(), it_end = characters.end(); - using namespace dal; - - for (; it != it_end; ++it) { + for (Players::const_iterator it = characters.begin(), + it_end = characters.end(); it != it_end; ++it) { // check if the character already exists in the database // (reminder: the character names are unique in the database). std::ostringstream sql2; @@ -813,17 +662,9 @@ DALStorage::_updAccount(const AccountPtr& account) std::ostringstream sql3; if (charInfo.rows() == 0) { sql3 << "insert into " << CHARACTERS_TBL_NAME - << " (" -#ifdef SQLITE_SUPPORT - << "user_id, " -#endif - << "name, gender, hair_style, hair_color, level, money, x, y, map_id, str, agi, vit, int, dex, luck)" - << " values (" -#ifdef SQLITE_SUPPORT - << (account_it->second).id << ", \"" -#else - << "\"" -#endif + << " (user_id, name, gender, hair_style, hair_color, level, money," + " x, y, map_id, str, agi, vit, int, dex, luck) values (" + << account->getID() << ", \"" << (*it)->getName() << "\", " << (*it)->getGender() << ", " << (int)(*it)->getHairStyle() << ", " @@ -844,9 +685,9 @@ DALStorage::_updAccount(const AccountPtr& account) sql3 << "update " << CHARACTERS_TBL_NAME << " set name = \"" << (*it)->getName() << "\", " << " gender = " << (*it)->getGender() << ", " - << " hair_style = " << (*it)->getHairStyle() << ", " - << " hair_color = " << (*it)->getHairColor() << ", " - << " level = " << (*it)->getLevel() << ", " + << " hair_style = " << (int)(*it)->getHairStyle() << ", " + << " hair_color = " << (int)(*it)->getHairColor() << ", " + << " level = " << (int)(*it)->getLevel() << ", " << " money = " << (*it)->getMoney() << ", " << " x = " << (*it)->getX() << ", " << " y = " << (*it)->getY() << ", " @@ -877,7 +718,7 @@ DALStorage::_updAccount(const AccountPtr& account) std::ostringstream sql4; sql4 << "select name, id from " << CHARACTERS_TBL_NAME - << " where user_id = '" << (account_it->second).id << "';"; + << " where user_id = '" << account->getID() << "';"; const RecordSet& charInMemInfo = mDb->execSql(sql4.str()); // We compare chars from memory and those existing in db, @@ -886,8 +727,8 @@ DALStorage::_updAccount(const AccountPtr& account) for ( unsigned int i = 0; i < charInMemInfo.rows(); ++i) // in database { charFound = false; - it = characters.begin(); - for (; it != it_end; ++it) // In memory + for (Players::const_iterator it = characters.begin(), + it_end = characters.end(); it != it_end; ++it) // In memory { if ( charInMemInfo(i, 0) == (*it)->getName() ) { @@ -929,89 +770,26 @@ DALStorage::_updAccount(const AccountPtr& account) /** * Delete an account and its associated data from the database. */ -void -DALStorage::_delAccount(const AccountPtr& account) +void DALStorage::delAccount(AccountPtr const &account) { - if (account.get() != 0) { - _delAccount(account->getName()); - } -} + using namespace dal; + + account->setCharacters(Players()); + flush(account); + mAccounts.erase(account->getID()); + // delete the account. + std::ostringstream sql; + sql << "delete from " << ACCOUNTS_TBL_NAME + << " where id = '" << account->getID() << "';"; + mDb->execSql(sql.str()); +} /** - * Delete an account and its associated data from the database. + * Unload an account from memory. */ -void -DALStorage::_delAccount(const std::string& userName) +void DALStorage::unloadAccount(AccountPtr const &account) { - using namespace dal; - - // TODO: optimize, we may be doing too much SQL queries here but this - // code should work with any database :( - - // get the account id. - std::string sql("select id from "); - sql += ACCOUNTS_TBL_NAME; - sql += " where username = \""; - sql += userName; - sql += "\";"; - const RecordSet& accountInfo = mDb->execSql(sql); - - // the account does not even exist in the database, - // there is nothing to do then. - if (accountInfo.isEmpty()) { - return; - } - - // save the account id. - std::string accountId(accountInfo(0, 0)); - - // get the characters that belong to the account. - sql = "select id from "; - sql += CHARACTERS_TBL_NAME; - sql += " where user_id = '"; - sql += accountId; - sql += "';"; - const RecordSet& charsInfo = mDb->execSql(sql); - - // save the character ids. - using namespace std; - vector charIds; - for (unsigned int i = 0; i < charsInfo.rows(); ++i) { - charIds.push_back(charsInfo(i, 0)); - } - - // TODO: we should start a transaction here so that in case of problem - // the lost of data would be minimized. - // db.set-transaction-type of this-db to db.manual-commit, for instance - // Agreed, but will sqlite support this ? - - // actually removing data. - vector::const_iterator it = charIds.begin(); - vector::const_iterator it_end = charIds.end(); - for (; it != it_end; ++it) { - // delete the inventory. - sql = "delete from "; - sql += INVENTORIES_TBL_NAME; - sql += " where owner_id = '"; - sql += (*it); - sql += "';"; - mDb->execSql(sql); - - // now delete the character. - sql = "delete from "; - sql += CHARACTERS_TBL_NAME; - sql += " where id = '"; - sql += (*it); - sql += "';"; - mDb->execSql(sql); - } - - // delete the account. - sql = "delete from "; - sql += ACCOUNTS_TBL_NAME; - sql += " where id = '"; - sql += accountId; - sql += "';"; - mDb->execSql(sql); + flush(account); + mAccounts.erase(account->getID()); } diff --git a/src/dalstorage.h b/src/dalstorage.h index c3848ed9..ec80a8cc 100644 --- a/src/dalstorage.h +++ b/src/dalstorage.h @@ -79,10 +79,16 @@ class DALStorage: public Storage /** * Delete an account. * - * @param userName the owner of the account. + * @param account the account to delete. */ - void - delAccount(const std::string& userName); + void delAccount(AccountPtr const &account); + + /** + * Flush and unload an account. + * + * @param account the account to unload. + */ + void unloadAccount(AccountPtr const &account); /** * Get the list of Emails in the accounts list. @@ -130,9 +136,8 @@ class DALStorage: public Storage * * @exception tmwserv::dal::DbSqlQueryExecFailure. */ - void - flush(void); - + void flushAll(); + void flush(AccountPtr const &); private: /** @@ -174,50 +179,6 @@ class DALStorage: public Storage const std::string& sql); - /** - * Add an account to the database. - * - * @param account the account to add. - * - * @exeception tmwserv::dal::DbSqlQueryExecFailure. - */ - void - _addAccount(const AccountPtr& account); - - - /** - * Update an account from the database. - * - * @param account the account to update. - * - * @exception tmwserv::dal::DbSqlQueryExecFailure. - */ - void - _updAccount(const AccountPtr& account); - - - /** - * Delete an account and its associated data from the database. - * - * @param account the account to update. - * - * @exception tmwserv::dal::DbSqlQueryExecFailure. - */ - void - _delAccount(const AccountPtr& account); - - - /** - * Delete an account and its associated data from the database. - * - * @param userName the owner of the account. - * - * @exeception tmwserv::dal::DbSqlQueryExecFailure. - */ - void - _delAccount(const std::string& userName); - - private: std::auto_ptr mDb; /**< the data provider */ }; diff --git a/src/storage.h b/src/storage.h index 75cea54a..d66c3e26 100644 --- a/src/storage.h +++ b/src/storage.h @@ -31,95 +31,10 @@ #include "account.h" #include "chatchannel.h" -/** - * Enumeration type for the account status: - * - * AS_NEW_ACCOUNT : set to an account that is added to the storage and - * hence not yet save to disk (file or database); - * accounts with this status will be saved to disk at - * the next flush. - * AS_ACC_TO_UPDATE: set to an account that was loaded from disk; - * accounts with this status will be updated at the next - * flush. - * AS_ACC_TO_DELETE: set to an account that was loaded from disk; - * accounts with this status will be deleted from disk at - * the next flush. - * - * Notes: an account that is requested to be deleted and still has the - * status AS_NEW_ACCOUNT (and therefore not yet saved to disk) will - * be immediately deleted from memory to save useless transactions - * to disk. - */ -typedef enum { - AS_NEW_ACCOUNT, - AS_ACC_TO_UPDATE, - AS_ACC_TO_DELETE -} AccountStatus; - - -/** - * Structure type for the account info. - */ -typedef struct { - AccountStatus status; - unsigned int id; -} AccountInfo; - - -/** - * Functor to be used as the sorting criterion of the map defined below. - */ -struct account_sort_by_name - : public std::binary_function -{ - bool - operator()(const AccountPtr& acc1, const AccountPtr& acc2) const - { - return (acc1->getName() < acc2->getName()); - } -}; - - /** * Data type for the list of accounts. - * - * Notes: - * - the account id is not attribute of Account but AccountInfo because - * only the storage should modify this value, this attribute is for - * internal use. */ -typedef std::map Accounts; - - -/** - * Functor used to search an Account by name in the map defined above. - */ -class account_by_name -{ - public: - /** - * Constructor. - */ - account_by_name(const std::string& name) - : mName(name) - { - // NOP - } - - - /** - * Operator(). - */ - bool - operator()(std::pair elem) const - { - return (elem.first)->getName() == mName; - } - - - private: - std::string mName; /**< the name to look for */ -}; +typedef std::map Accounts; /** @@ -258,7 +173,6 @@ class Storage virtual AccountPtr getAccount(const std::string& userName) = 0; - /** * Add a new account. * @@ -267,14 +181,21 @@ class Storage virtual void addAccount(const AccountPtr& account) = 0; - /** * Delete an account. * - * @param userName the owner of the account. + * @param account the account to delete. */ virtual void - delAccount(const std::string& userName) = 0; + delAccount(AccountPtr const &account) = 0; + + /** + * Flush and unload an account. + * + * @param account the account to unload. + */ + virtual void + unloadAccount(AccountPtr const &account) = 0; /** * Get the list of Emails in the accounts list. @@ -323,12 +244,15 @@ class Storage virtual void updateChannels(std::map& channelList) = 0; + /** + * Saves the changes to all the accounts permanently. + */ + virtual void flushAll() = 0; /** - * Saves the changes permanently. + * Saves the changes to one account permanently. */ - virtual void - flush(void) = 0; + virtual void flush(AccountPtr const &account) = 0; protected: -- cgit v1.2.3-70-g09d2